Skip to content

Workspaces#558

Open
ussaama wants to merge 209 commits intomainfrom
workspaces
Open

Workspaces#558
ussaama wants to merge 209 commits intomainfrom
workspaces

Conversation

@ussaama
Copy link
Copy Markdown
Contributor

@ussaama ussaama commented May 8, 2026

No description provided.

spashii added 30 commits April 16, 2026 09:07
Add scripts/create_schema.py for programmatic Directus collection
creation via REST API. Update CLAUDE.md Directus rules to document
the new workflow: script -> verify -> sync pull -> commit.
Indirection layer between domain tables and Directus auth. All new
workspace/org tables FK to app_user.id instead of directus_users.id.

Fields: id (uuid PK), directus_user_id (unique), email, display_name,
created_at, updated_at.
org: id, name, logo_url, created_by (FK app_user), deleted_at, timestamps.
org_membership: id, org_id (FK org), user_id (FK app_user), role
(owner/admin/member), custom_policies (json), deleted_at, timestamps.

Policies use computed-at-runtime pattern: role maps to preset policy
array in Python, custom_policies stores extras beyond preset.
workspace: id, org_id (FK org CASCADE), name, description, logo_url,
tier (pioneer default), billed_to_workspace_id (self-ref FK SET NULL),
is_default, legal_basis, privacy_policy_url, settings (json),
deleted_at, created_by (FK app_user), timestamps.

workspace_membership: id, workspace_id (FK workspace CASCADE),
user_id (FK app_user CASCADE), role (owner/admin/member/viewer),
custom_policies (json), source (direct/inherited), is_external,
deleted_at, timestamps.
workspace_invite: id, workspace_id (FK workspace CASCADE), email,
role, invited_by (FK app_user), token (unique), expires_at,
accepted_at, created_at.

project_membership: id, project_id (FK project CASCADE), user_id
(FK app_user CASCADE), role (editor/viewer), custom_policies (json),
granted_by (FK app_user SET NULL), created_at.
Append-only audit/billing log. Never updated, never deleted.
No FK constraints on reference fields (org_id, workspace_id,
project_id, user_id) to prevent CASCADE side effects.

Fields: id, trace_id, org_id, workspace_id, project_id, user_id,
event_type, event_data (json, always include "v": 1), created_at.
workspace_id: uuid, nullable, FK to workspace (SET NULL). NULL only
during migration window.
visibility: string, default 'workspace'. workspace/private.
deleted_at: timestamp, nullable. Soft delete support.
Soft delete support for existing collections. All nullable timestamps,
default null. No existing behavior changes until Session 3 converts
delete operations to use these fields.
The chat collection (not project_chat) was confirmed unused: zero code
references, zero rows, no permissions, no flows. Removed from Directus
and schema sync files. project_chat remains as the active chat system.
Session 1 exploration report covering: full Directus schema map,
Python API routes, frontend routes, delete operation inventory,
auth flow, Directus client patterns, CASCADE analysis, email
capability assessment, and PRD reconciliation.
Replace the requests library with httpx in the DirectusClient. httpx
is a modern HTTP client with the same sync API but better async
support for future migration. Key changes:

- requests.request() -> httpx.request()
- requests.exceptions.ConnectionError -> httpx.ConnectError
- requests.exceptions.HTTPError -> httpx.HTTPStatusError
- Explicit 30s timeout (httpx defaults to 5s, requests had none)
- Removed urllib3 warning suppression (not needed with httpx)

All callers catch DirectusServerError/DirectusBadRequest (our custom
exceptions), not library-specific exceptions, so this change is fully
contained within directus.py.
New async Directus client using httpx.AsyncClient with connection
pooling. Same API surface and return value semantics as the sync
DirectusClient. Raises the same custom exceptions.

Used by new workspace/org endpoints (Session 4). Existing sync
code and Dramatiq workers continue using the sync client unchanged.

Includes: retry logic with exponential backoff, lazy client init,
send_mail() for workspace invite emails via Directus /utils/mail.
Billing data lives in domain tables (conversation.duration,
workspace_membership). Analytics tracked via PostHog.
usage_event table is unnecessary complexity for now.
The schema remains available in git history if needed later.
Missed in Session 2 schema work. Needed for soft delete conversion
of webhook deletion in Session 3.
- New DELETE /api/projects/:id endpoint that PATCHes deleted_at
  instead of hard-deleting. Uses admin client, verifies ownership.
- Updated frontend useDeleteProjectByIdMutation to call BFF
  endpoint instead of Directus SDK deleteItem("project").
- Added onError toast to the mutation (was missing before).
- S3 audio files are preserved (no cascade deletion).
- ConversationService.delete now PATCHes deleted_at instead of
  hard-deleting the conversation and its S3 audio files.
- Audio files preserved for grace period recovery.
- Conversation data stays in DB, excluded by deleted_at IS NULL filter.
- The endpoint already goes through Python API, no frontend changes needed.
- New DELETE /api/chats/:id endpoint that PATCHes deleted_at
  instead of hard-deleting. Verifies chat ownership.
- Updated frontend useDeleteChatMutation to call BFF endpoint
  instead of Directus SDK deleteItem("project_chat").
- Added onError toast and lingui translation to the mutation.
- DELETE /api/projects/:id/reports/:rid now PATCHes deleted_at
  instead of hard-deleting the report.
- Report content preserved in DB. No frontend changes needed
  (endpoint already called via Python API).
- DELETE /api/projects/:id/webhooks/:wid now PATCHes deleted_at
  instead of hard-deleting. No frontend changes needed.
- New DELETE /api/projects/:id/tags/:tid endpoint (hard delete via admin client)
- New POST /api/projects/:id/conversations/:cid/tags/delete endpoint for
  batch conversation-tag junction deletion (hard delete via admin client)
- Updated frontend useDeleteTagByIdMutation to call BFF (now takes projectId)
- Updated ProjectTagPill to pass projectId prop
- Updated useUpdateConversationTagsMutation to call BFF instead of
  Directus SDK deleteItems
- Removed unused deleteItems import from conversation hooks
Ensures soft-deleted items are excluded from all list/search queries
across 5 collections: project, conversation, project_chat,
project_report, project_webhook.

Python backend (29 queries):
- service/conversation.py: _list_conversations, list_by_project_with_filters,
  get_recent_titles_for_project
- service/chat.py: get_recent_user_queries
- service/webhook.py: get_webhooks_for_project
- api/project.py: pinned projects, paginated list, count aggregate,
  draft check, list reports, latest report, other published, conversation freshness
- api/search.py: projects, conversations, chats
- api/stats.py: projects, conversations
- api/agentic.py: conversation list
- api/project_webhook.py: list webhooks, copyable webhooks
- conversation_utils.py: 3 scheduler queries (unfinished, needing flag, unsummarized)
- report_generation.py: fetch conversations for report
- reply_utils.py: adjacent conversations
- summary_utils.py: conversations with summaries, all conversations for overview
- tasks.py: scheduled reports check

Frontend (8 queries):
- conversation hooks: useConversationsByProjectId, useInfiniteConversationsByProjectId
- chat hooks: useProjectChats, useInfiniteProjectChats
- project hooks: useInfiniteProjects
- report hooks: timeline reports, timeline conversations
- api.ts: getProjectConversationCounts
Removes Directus DELETE permissions on 9 collections from the Basic
User policy. All deletes now go through Python API endpoints which
use the admin token for soft-delete (PATCH deleted_at).

Collections affected: project, conversation, project_chat,
project_chat_message, project_tag, conversation_project_tag,
project_chat_conversation, conversation_chunk, conversation_artifact.

This makes it physically impossible for non-admin users to hard-delete
data via the Directus SDK or admin panel. All deletion must go
through the controlled BFF endpoints.
Adds PostHog SDK for frontend analytics and event tracking.
Instruments key user flows: auth, project creation, chat,
report generation, and file uploads.
New /api/v2/ router with typed Pydantic response models, workspace
context middleware, policy-based access control, and app_user
resolution helpers.

Structure:
  api/v2/__init__.py    — v2 router aggregator
  api/v2/schemas.py     — typed response/request models
  api/v2/middleware.py   — get_workspace_context dependency
  api/v2/me.py          — GET /v2/me (placeholder)
  api/v2/onboarding.py  — POST /v2/onboarding/complete (placeholder)
  policies.py           — role presets + has_policy() (workspace/org/project)
  app_user.py           — resolve/create app_user, directus user profile lookup
Returns lightweight user profile with onboarding status.
Checks if app_user record exists (onboarding_completed).
Falls back to directus_users profile for display info.
Typed response via MeResponse Pydantic model.
Creates app_user + org + default workspace + moves user's projects.
Fully idempotent: each step checks if already done before creating.

Flow:
1. Get or create app_user (linked to directus_user)
2. Get or create org (user becomes owner)
3. Get or create default workspace in org
4. Move all user's projects (where workspace_id is null) into workspace
V2 endpoints (all under /api/v2/):
- GET /me: user profile + orgs + onboarding status + pending invites
- POST /onboarding/complete: smart onboarding with invite auto-accept
- GET /workspaces: list accessible workspaces with avatar previews
- POST /workspaces/:id/invite: invite by email (internal/external)
- POST /projects/:id/move: move between workspaces

Onboarding decision tree:
- Internal invite → join org + workspace, skip personal org
- External invite → own org + workspace as external
- No invites → personal org + default workspace

Invite flow:
- Existing user → immediate membership + notification email
- New user → workspace_invite record + invite email
- Expired invites enforced (7-day), self-invite prevented

Infrastructure:
- SendGrid email module (sync + async, Jinja2 templates)
- Workspace invite email template (brand-compliant)
- Pre-seed script (YAML config, handles existing + new users)
- Reverted httpx migration on sync DirectusClient (production safety)
- Fixed DirectusClient.search() error handling (returns [] not error dict)
3-step onboarding flow:
- Loading screen ("Setting things up for you")
- Org name step (brand illustration hero + "Team name" form)
- Invite step (email fields + send invites)

Login redirect:
- After login, checks /v2/me for onboarding status
- Fault-tolerant: errors never block login
- 300ms delay ensures session cookie is available
- "Welcome to dembrane" for new users, "Welcome back" for returning

Header menu:
- "Set up workspace" item (blue, sparkles icon) shown when not onboarded
- Uses shared useV2Me hook with 60s staleTime

Design:
- Parchment background with soft radial gradient blur orbs
- Brand illustration as hero image (520px width)
- Staggered fade-in animations
- "Use default" escape hatch for quick onboarding
- Brand-compliant: no bold, Royal Blue primary, left-aligned
GET /v2/workspaces enhanced:
- Per-workspace: audio_hours, conversation_count
- Team rollups: total projects, unique members, total audio hours,
  total conversations, workspace count across all team workspaces

POST /v2/workspaces:
- Creates workspace in user's team
- Auto-adds creator as owner + team admins as inherited members
- Requires team admin/owner role
Frontend:
- Workspace selector page (/workspaces): cards grouped by team with
  usage stats (audio hours, conversations, projects), avatar bubbles,
  tier badge, search (when >3 workspaces), external section
- Create workspace page (/workspaces/new): name + tier selector
- WorkspaceContext provider: localStorage-persisted current workspace
- Header: shows workspace name next to logo (clickable → /workspaces)
- Post-login router: multi-workspace or team admin → /workspaces,
  solo user → /projects

Security fixes from code review:
- Project move: verify ownership for orphaned projects (IDOR fix)
- Tier validation: reject invalid tier values on workspace creation
- Name length limits on workspace + onboarding requests (1-100 chars)
spashii and others added 17 commits April 24, 2026 08:15
Matrix v1.1 §1 prices a guest cap per tier (2, 5, 20, 50, unlimited)
but neither the admin billing rollup nor the team usage table
surfaced the current count. Staff had no way to see which workspaces
were close to their external-collaborator ceiling short of opening
each one.

Backend:
- OrgUsageWorkspaceRow + BillingRow gain guest_count, guest_cap,
  guest_cap_hit.
- orgs.py tracks per-workspace guests during the dedupe pass using
  the same (workspace_id, user_id) key it already uses for seats,
  so a user with both a direct and a legacy inherited guest row
  counts once.
- admin.py adds _workspace_guest_count helper + threads the count
  through billing_rollup. Cap comes from tier_capacity.guest_cap.

Frontend:
- Both tables add a "Guests" column right after Overage seats using
  the same UsageBar component. Progress bar turns red when the cap
  is hit.
- Admin table footer row sums guests across the filtered set; CSV
  export adds guest_count + guest_cap columns.
- Guests are read-only here: they aren't billed, so no overage col.
Aligns user-facing vocabulary with matrix v1.1 — workspaces and project
sharing now consistently say "organisation" (British spelling). Touches
frontend routes, components, identifiers; backend variables, settings
JSON keys, scripts, email labels; docs across docs/workspaces and
docs/workspaces-validate.

Routes:
- /:locale/t        → /:locale/o
- /:locale/t/:id    → /:locale/o/:organisationId

JSON keys inside workspace.settings:
- inherit_team_admins  → inherit_organisation_admins
- inherit_team_members → inherit_organisation_members
Pre-existing rows fall back to defaults; safe pre-prod and converging
on the workspace.visibility enum per matrix §6.

Preserved (intentional):
- Directus columns (billed_to_team_id, partner_team_id,
  handoff_target_team_id, effective_client_team_id, etc.)
- Email sign-offs ("— the dembrane team")
- Generic English usage ("support team", "product team", "our team")
- Proper nouns ("Slack/Teams", "Microsoft Teams")
- Lingui .po files (regenerate via `pnpm messages:extract`)

Lint sweep (no behaviour change):
- notifications.py: type-annotate **emit_kwargs
- orgs.py: hoist module imports, OrgUsageResponse.model_validate(payload)
- project.py: drop unused import + locals, define project_service before
  use, chain raise … from None, isort fix
- workspaces.py: introduce DependencyWorkspaceContext Annotated alias
  (mirrors DependencyDirectusSession), strict=True on zip(),
  raise … from exc, guard get_item("org", org_id), isort fix

Verified: tsc 0 errors, biome lint identical to baseline, mypy clean
on touched files, FastAPI app imports with all routes loaded.
…ity with org inheritance

- fix(inbox): open Members tab for workspace access request notifications
  Caps
  - Add seat_capacity.py: assert_can_add_member (Pilot hard-block, Pioneer+
    allow seat overage), assert_can_add_guest (hard cap at every finite-cap
    tier), and count_pending_invites
  - Wire cap checks into invite send, all 3 accept paths, onboarding auto-
    accept, and access-request approve
  - Send-paths use include_pending=True so outstanding workspace_invite
    rows count toward the cap; admins can no longer over-issue invites
    that'll fail at accept-time
  - Count derived org admins as seats via inheritance.get_effective_members
    in both per-workspace and org-rollup endpoints
  - Add invalidate_workspace_and_org_usage helper; bust both layers on every
    membership mutation; new _invalidate_org_workspace_usage covers org-
    role change and removal so derived seat counts don't go stale
  - Move accept-invite rate limit past the cap check (both accept_my_invite
    and accept_invite_by_hash) so cap-blocked retries don't burn quota;
    raise capacity 30 -> 60

  Guest scope (matrix v1.1 §4)
  - New strict 'guest' policy preset: project read/update, conversation:read,
    chat:use, report view/generate -- nothing else
  - effective_workspace_role swaps is_external=True to 'guest' at policy
    lookup in middleware + bff/_access
  - Add report:publish gate on update_report (single access lookup, no
    double round-trip on publish)
  - Onboarding: track joined_any_workspace + had_pending_invite so pure-
    guest signups and cap-blocked invitees no longer get a stray personal
    organisation

  UI
  - SeatCapBanner (workspace) + OrganisationCapBanner (org) -- level-2
    banners with refetchOnMount: 'always' so cap state is always fresh
  - WorkspaceInviteWizard: cap-blocked steps disabled; soft blue alert on
    Pioneer+ overage with concrete EUR/seat rate
  - OrganisationInviteWizard: greyed cap-blocked workspace cards + tooltip;
    per-card inline error after failed submit (e.g. "An invite is already
    pending for this email") with reason-aware toast
  - InviteMemberCard: tooltip prop wrapped in Box for cross-browser
    reliability; overage-aware helper text on Pioneer+ over included seats
  - Split org usage page: hard-block tiers stay in "Needs attention" with
    Upgrade CTA; Pioneer+ over-cap rows move to new "Overage this cycle"
    panel with concrete EUR forecast and View billing CTA
  - AcceptInviteRoute + MyInvitesRoute: persistent inline alert on cap-
    blocked accept with retry button; alert color uses HTTP status code
    (402) instead of substring match
  - Email pre-fill + lock on /register when coming from invite link
  - Wrong-account warning on /invite/accept when authenticated user's
    email doesn't match the invite
  - WorkspaceSettingsRoute usage probe matches UsageCard's refetch policy
    so Members tab cap flags refresh on tab switches

  Notifications
  - WORKSPACE_GUEST_ADDED (info, intentionally) to workspace admins on
    every guest-join site
  - INVITE_BLOCKED_AT_CAP (action_required) to inviter when onboarding
    skips an invite due to cap
  - INVITE_PENDING_AT_CAP (action_required) to invitee for the same event,
    so the bell goes red immediately without a refresh
  - Onboarding success invalidates inbox + invites queries so
    notifications surface without a page reload
  - Defensive: hoist inviter_id at top of accept_my_invite so future
    refactors don't NameError on the WORKSPACE_GUEST_ADDED branch

  Misc
  - Mirror tier_capacity.py to tiers.ts (TIER_SEAT_OVERAGE_EUR)
  - Refetch workspace and org usage queries on mount so stale React Query
    cache doesn't outlast cache-bust on the server
  - Fix response tier of POST /v2/workspaces (was 'pioneer', stored 'pilot');
    matrix §9 contract now consistent. Stored value was already pilot
    pre-session; only the lying response field changed
  - Strip em dashes from all client-facing copy added in this change

  Tests
  - 15 unit tests in test_seat_capacity.py covering the per-tier matrix +
    derived-admin counting (all pass)

  Lint
  - biome clean across all touched frontend files (143 errors -> 0)
…states

Remove deleted_at filters from usage queries so conversation durations
survive project/conversation deletion (PRD §270). Applies consistently
across workspace, org, and pilot hard-block paths.

Also:
- Single-call cache invalidation via Directus relational expansion
- Recent-removal empty state on /w for guests who lost access
- 30s polling on workspace projects and settings
- Invalidate usage cache on invite cancel / member removal
- Consistent outline variant on Back buttons
- Prevent silent re-accept of already-consumed invite links
- Fix verify-email infinite loading for authenticated users
- Block registration with already-registered emails (check-email endpoint)
- Surface dead/expired invites to unauthenticated visitors
- Fix existing-user invite email landing on /workspaces instead of workspace
- Replace broken "Request a new link" button with login CTA + support hint
- Read-only invite inspection so the frontend renders state before acting
- Cosmetic: consistent spacing on access requests and pending invites sections
- Prevent cross-user data leaks by wiping the full query cache and
  session workspace selection on logout
- Block open-redirect via ?next= by validating same-origin paths
- Check workspace access before honoring deep-link redirects so a
  previous user's workspace URL doesn't leak to the next login
- Preserve full URL (path + query + hash) across login round-trips
- Extract isAuthPath to shared utility to avoid redirect loops on
  auth pages
- Eliminate duplicate /v2/workspaces fetch in post-login routing
- Route legacy /projects to workspace selector when multiple
  workspaces exist but none auto-resolved
…nter scope

- Show an AccessDeniedPanel on workspace settings and projects pages
  when the API returns 401/403/404, replacing the infinite spinner and
  fake empty state (#9)
- Add a "Billing" filter chip to the org People tab and workspace
  Members tab, visible only when at least one billing-role member
  exists (#20)
- Lock the role picker for the sole admin in a workspace, with a
  tooltip prompting them to promote someone else first; add a matching
  backend guard in change_member_role to prevent last-admin demotion
  via direct API calls (#19)
- Scope the org hero card on /w to "N workspaces you can access" for
  non-admin members instead of showing misleading zero counts (#13)
Fetch failures in workspace/org routes silently degraded to empty states
(no workspaces, no invites, no usage) — indistinguishable from a new
account. Invite email queue failures were swallowed, always reporting
success to the admin.

Backend: _enqueue_invite_email now returns success/failure; both invite
paths plumb it into the response's email_sent field.

Frontend: fetch functions throw on !res.ok instead of returning empty
data. Routes and cards render error+retry UI via React Query's isError.
Invite wizards surface email queue failures so admins know to resend.

Adds FetchErrorPanel as a reusable full-page error component (not yet
wired — consumers still use inline blocks).
Consolidate logo branding from user settings and organisation to the
workspace settings surface. Logo upload is gated to changemaker+ tiers
with an upgrade prompt for lower tiers. Downgraded workspaces show
their existing logo with a remove option. Adds image crop modal before
upload, confirmation modal before removal, and a loader in the header
while the workspace logo resolves to prevent flash on refresh.
… tab

The column visibility checkboxes were non-functional because
preventDefault() on Menu.Item suppressed native checkbox toggling.
Replaced with stopPropagation on the checkbox and a direct toggle on
the menu item. Also switched from getVisibleCells() to explicit
column-id filtering so body cells properly hide when columns are
toggled. Unified all columns to left-alignment for readability.

Co-authored-by: Cursor <cursoragent@cursor.com>
Tertiary buttons now use Royal Blue instead of gray (Back to
workspaces link). Status filter group uses consistent Royal Blue for
the selected state rather than semantic green/gray. Interactive "Show
more" links use Royal Blue instead of dimmed text. Attention and
overage panel buttons sized to xs for visual consistency.
The 30-min Redis usage cache wasn't being invalidated after project
create/delete, conversation delete/move, or retranscribe, so the
dashboard kept showing stale audio hours and project counts until TTL.
Drive-by carries `org_id` on `ResourceAccess` so the new invalidation
paths avoid a second `get_item("workspace", …)`.

Frontend: pair org-members keys in WorkspaceInviteWizard, drop dead
`["v2", "orgs"]` key in OrganisationRoute, refresh `workspaces-context`
after joining in DiscoverableWorkspaces.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Important

Review skipped

Too many files!

This PR contains 288 files, which is 138 over the limit of 150.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1bdf2ce9-5947-40c2-a152-1cc99254862e

📥 Commits

Reviewing files that changed from the base of the PR and between d22d401 and 06dd277.

⛔ Files ignored due to path filters (12)
  • echo/docs/workspaces-validate/qa/_shots/00-login.png is excluded by !**/*.png
  • echo/docs/workspaces-validate/qa/_shots/01-register-verify.png is excluded by !**/*.png
  • echo/docs/workspaces-validate/qa/_shots/02-post-verify-login.png is excluded by !**/*.png
  • echo/docs/workspaces-validate/qa/_shots/03-onboarding-team.png is excluded by !**/*.png
  • echo/docs/workspaces-validate/qa/_shots/04-onboarding-team-full.png is excluded by !**/*.png
  • echo/docs/workspaces-validate/qa/_shots/05-post-onboarding-landing.png is excluded by !**/*.png
  • echo/docs/workspaces-validate/qa/_shots/06-home-empty.png is excluded by !**/*.png
  • echo/docs/workspaces-validate/qa/_shots/10-anna-home.png is excluded by !**/*.png
  • echo/frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • echo/frontend/public/dembrane-logo-new.png is excluded by !**/*.png
  • echo/frontend/public/dembrane-logo-new.svg is excluded by !**/*.svg
  • echo/frontend/public/illustrations/onboarding-banner.png is excluded by !**/*.png
📒 Files selected for processing (288)
  • echo/.devcontainer/devcontainer.json
  • echo/.devcontainer/docker-compose.yml
  • echo/.devcontainer/setup.sh
  • echo/.gitignore
  • echo/.vscode/sessions.json
  • echo/CLAUDE.md
  • echo/directus/.env.sample
  • echo/directus/sync/collections/permissions.json
  • echo/directus/sync/collections/policies.json
  • echo/directus/sync/snapshot/collections/access_request.json
  • echo/directus/sync/snapshot/collections/app_user.json
  • echo/directus/sync/snapshot/collections/notification.json
  • echo/directus/sync/snapshot/collections/org.json
  • echo/directus/sync/snapshot/collections/org_membership.json
  • echo/directus/sync/snapshot/collections/project_membership.json
  • echo/directus/sync/snapshot/collections/referral_ledger.json
  • echo/directus/sync/snapshot/collections/workspace.json
  • echo/directus/sync/snapshot/collections/workspace_invite.json
  • echo/directus/sync/snapshot/collections/workspace_membership.json
  • echo/directus/sync/snapshot/fields/access_request/actioned_at.json
  • echo/directus/sync/snapshot/fields/access_request/actioned_by.json
  • echo/directus/sync/snapshot/fields/access_request/deleted_at.json
  • echo/directus/sync/snapshot/fields/access_request/id.json
  • echo/directus/sync/snapshot/fields/access_request/requested_at.json
  • echo/directus/sync/snapshot/fields/access_request/status.json
  • echo/directus/sync/snapshot/fields/access_request/user_id.json
  • echo/directus/sync/snapshot/fields/access_request/workspace_id.json
  • echo/directus/sync/snapshot/fields/app_user/created_at.json
  • echo/directus/sync/snapshot/fields/app_user/directus_user_id.json
  • echo/directus/sync/snapshot/fields/app_user/display_name.json
  • echo/directus/sync/snapshot/fields/app_user/email.json
  • echo/directus/sync/snapshot/fields/app_user/id.json
  • echo/directus/sync/snapshot/fields/app_user/updated_at.json
  • echo/directus/sync/snapshot/fields/conversation/deleted_at.json
  • echo/directus/sync/snapshot/fields/notification/action.json
  • echo/directus/sync/snapshot/fields/notification/actor_user_id.json
  • echo/directus/sync/snapshot/fields/notification/audience_user_id.json
  • echo/directus/sync/snapshot/fields/notification/created_at.json
  • echo/directus/sync/snapshot/fields/notification/event_code.json
  • echo/directus/sync/snapshot/fields/notification/expires_at.json
  • echo/directus/sync/snapshot/fields/notification/id.json
  • echo/directus/sync/snapshot/fields/notification/message.json
  • echo/directus/sync/snapshot/fields/notification/params.json
  • echo/directus/sync/snapshot/fields/notification/read_at.json
  • echo/directus/sync/snapshot/fields/notification/ref_chat_id.json
  • echo/directus/sync/snapshot/fields/notification/ref_conversation_id.json
  • echo/directus/sync/snapshot/fields/notification/ref_invite_id.json
  • echo/directus/sync/snapshot/fields/notification/ref_org_id.json
  • echo/directus/sync/snapshot/fields/notification/ref_project_id.json
  • echo/directus/sync/snapshot/fields/notification/ref_report_id.json
  • echo/directus/sync/snapshot/fields/notification/ref_workspace_id.json
  • echo/directus/sync/snapshot/fields/notification/scope.json
  • echo/directus/sync/snapshot/fields/notification/severity.json
  • echo/directus/sync/snapshot/fields/notification/title.json
  • echo/directus/sync/snapshot/fields/notification/updated_at.json
  • echo/directus/sync/snapshot/fields/org/created_at.json
  • echo/directus/sync/snapshot/fields/org/created_by.json
  • echo/directus/sync/snapshot/fields/org/deleted_at.json
  • echo/directus/sync/snapshot/fields/org/id.json
  • echo/directus/sync/snapshot/fields/org/logo_url.json
  • echo/directus/sync/snapshot/fields/org/name.json
  • echo/directus/sync/snapshot/fields/org/updated_at.json
  • echo/directus/sync/snapshot/fields/org_membership/created_at.json
  • echo/directus/sync/snapshot/fields/org_membership/custom_policies.json
  • echo/directus/sync/snapshot/fields/org_membership/deleted_at.json
  • echo/directus/sync/snapshot/fields/org_membership/id.json
  • echo/directus/sync/snapshot/fields/org_membership/org_id.json
  • echo/directus/sync/snapshot/fields/org_membership/role.json
  • echo/directus/sync/snapshot/fields/org_membership/updated_at.json
  • echo/directus/sync/snapshot/fields/org_membership/user_id.json
  • echo/directus/sync/snapshot/fields/project/deleted_at.json
  • echo/directus/sync/snapshot/fields/project/visibility.json
  • echo/directus/sync/snapshot/fields/project/workspace_id.json
  • echo/directus/sync/snapshot/fields/project_chat/deleted_at.json
  • echo/directus/sync/snapshot/fields/project_membership/created_at.json
  • echo/directus/sync/snapshot/fields/project_membership/custom_policies.json
  • echo/directus/sync/snapshot/fields/project_membership/granted_by.json
  • echo/directus/sync/snapshot/fields/project_membership/id.json
  • echo/directus/sync/snapshot/fields/project_membership/project_id.json
  • echo/directus/sync/snapshot/fields/project_membership/role.json
  • echo/directus/sync/snapshot/fields/project_membership/user_id.json
  • echo/directus/sync/snapshot/fields/project_report/deleted_at.json
  • echo/directus/sync/snapshot/fields/project_webhook/deleted_at.json
  • echo/directus/sync/snapshot/fields/referral_ledger/created_by_staff_id.json
  • echo/directus/sync/snapshot/fields/referral_ledger/deleted_at.json
  • echo/directus/sync/snapshot/fields/referral_ledger/expires_at.json
  • echo/directus/sync/snapshot/fields/referral_ledger/id.json
  • echo/directus/sync/snapshot/fields/referral_ledger/notes.json
  • echo/directus/sync/snapshot/fields/referral_ledger/partner_kickback_percent.json
  • echo/directus/sync/snapshot/fields/referral_ledger/partner_team_id.json
  • echo/directus/sync/snapshot/fields/referral_ledger/starts_at.json
  • echo/directus/sync/snapshot/fields/referral_ledger/workspace_id.json
  • echo/directus/sync/snapshot/fields/workspace/billed_to_team_id.json
  • echo/directus/sync/snapshot/fields/workspace/billed_to_workspace_id.json
  • echo/directus/sync/snapshot/fields/workspace/created_at.json
  • echo/directus/sync/snapshot/fields/workspace/created_by.json
  • echo/directus/sync/snapshot/fields/workspace/deleted_at.json
  • echo/directus/sync/snapshot/fields/workspace/description.json
  • echo/directus/sync/snapshot/fields/workspace/downgraded_at.json
  • echo/directus/sync/snapshot/fields/workspace/downgraded_from_tier.json
  • echo/directus/sync/snapshot/fields/workspace/effective_client_team_id.json
  • echo/directus/sync/snapshot/fields/workspace/handoff_status.json
  • echo/directus/sync/snapshot/fields/workspace/handoff_target_team_id.json
  • echo/directus/sync/snapshot/fields/workspace/id.json
  • echo/directus/sync/snapshot/fields/workspace/is_default.json
  • echo/directus/sync/snapshot/fields/workspace/legal_basis.json
  • echo/directus/sync/snapshot/fields/workspace/logo_url.json
  • echo/directus/sync/snapshot/fields/workspace/name.json
  • echo/directus/sync/snapshot/fields/workspace/org_id.json
  • echo/directus/sync/snapshot/fields/workspace/privacy_policy_url.json
  • echo/directus/sync/snapshot/fields/workspace/settings.json
  • echo/directus/sync/snapshot/fields/workspace/tier.json
  • echo/directus/sync/snapshot/fields/workspace/updated_at.json
  • echo/directus/sync/snapshot/fields/workspace/visibility.json
  • echo/directus/sync/snapshot/fields/workspace_invite/accepted_at.json
  • echo/directus/sync/snapshot/fields/workspace_invite/created_at.json
  • echo/directus/sync/snapshot/fields/workspace_invite/email.json
  • echo/directus/sync/snapshot/fields/workspace_invite/expires_at.json
  • echo/directus/sync/snapshot/fields/workspace_invite/id.json
  • echo/directus/sync/snapshot/fields/workspace_invite/include_org_membership.json
  • echo/directus/sync/snapshot/fields/workspace_invite/invited_by.json
  • echo/directus/sync/snapshot/fields/workspace_invite/role.json
  • echo/directus/sync/snapshot/fields/workspace_invite/workspace_id.json
  • echo/directus/sync/snapshot/fields/workspace_membership/created_at.json
  • echo/directus/sync/snapshot/fields/workspace_membership/custom_policies.json
  • echo/directus/sync/snapshot/fields/workspace_membership/deleted_at.json
  • echo/directus/sync/snapshot/fields/workspace_membership/id.json
  • echo/directus/sync/snapshot/fields/workspace_membership/is_external.json
  • echo/directus/sync/snapshot/fields/workspace_membership/role.json
  • echo/directus/sync/snapshot/fields/workspace_membership/source.json
  • echo/directus/sync/snapshot/fields/workspace_membership/updated_at.json
  • echo/directus/sync/snapshot/fields/workspace_membership/user_id.json
  • echo/directus/sync/snapshot/fields/workspace_membership/workspace_id.json
  • echo/directus/sync/snapshot/relations/access_request/actioned_by.json
  • echo/directus/sync/snapshot/relations/access_request/user_id.json
  • echo/directus/sync/snapshot/relations/access_request/workspace_id.json
  • echo/directus/sync/snapshot/relations/chat/user_created.json
  • echo/directus/sync/snapshot/relations/chat/user_updated.json
  • echo/directus/sync/snapshot/relations/notification/actor_user_id.json
  • echo/directus/sync/snapshot/relations/notification/audience_user_id.json
  • echo/directus/sync/snapshot/relations/notification/ref_chat_id.json
  • echo/directus/sync/snapshot/relations/notification/ref_conversation_id.json
  • echo/directus/sync/snapshot/relations/notification/ref_invite_id.json
  • echo/directus/sync/snapshot/relations/notification/ref_org_id.json
  • echo/directus/sync/snapshot/relations/notification/ref_project_id.json
  • echo/directus/sync/snapshot/relations/notification/ref_workspace_id.json
  • echo/directus/sync/snapshot/relations/org/created_by.json
  • echo/directus/sync/snapshot/relations/org_membership/org_id.json
  • echo/directus/sync/snapshot/relations/org_membership/user_id.json
  • echo/directus/sync/snapshot/relations/project/workspace_id.json
  • echo/directus/sync/snapshot/relations/project_membership/granted_by.json
  • echo/directus/sync/snapshot/relations/project_membership/project_id.json
  • echo/directus/sync/snapshot/relations/project_membership/user_id.json
  • echo/directus/sync/snapshot/relations/referral_ledger/created_by_staff_id.json
  • echo/directus/sync/snapshot/relations/referral_ledger/partner_team_id.json
  • echo/directus/sync/snapshot/relations/referral_ledger/workspace_id.json
  • echo/directus/sync/snapshot/relations/workspace/billed_to_team_id.json
  • echo/directus/sync/snapshot/relations/workspace/billed_to_workspace_id.json
  • echo/directus/sync/snapshot/relations/workspace/created_by.json
  • echo/directus/sync/snapshot/relations/workspace/effective_client_team_id.json
  • echo/directus/sync/snapshot/relations/workspace/handoff_target_team_id.json
  • echo/directus/sync/snapshot/relations/workspace/org_id.json
  • echo/directus/sync/snapshot/relations/workspace_invite/invited_by.json
  • echo/directus/sync/snapshot/relations/workspace_invite/workspace_id.json
  • echo/directus/sync/snapshot/relations/workspace_membership/user_id.json
  • echo/directus/sync/snapshot/relations/workspace_membership/workspace_id.json
  • echo/directus/templates/email-base.liquid
  • echo/directus/templates/password-reset.liquid
  • echo/directus/templates/report-notification-en.liquid
  • echo/directus/templates/report-notification-nl.liquid
  • echo/directus/templates/user-invite.liquid
  • echo/directus/templates/user-registration.liquid
  • echo/docs/audit-report.md
  • echo/docs/workspaces-validate/.gitignore
  • echo/docs/workspaces-validate/00-DOC-AUDIT.md
  • echo/docs/workspaces-validate/00-PLAN.md
  • echo/docs/workspaces-validate/02-DELTA.md
  • echo/docs/workspaces-validate/03-DECISIONS.md
  • echo/docs/workspaces-validate/04-QUESTIONS-FOR-SAMEER.md
  • echo/docs/workspaces-validate/05-PROGRESS.md
  • echo/docs/workspaces-validate/06-AUDIT-LEDGER.md
  • echo/docs/workspaces-validate/06-VALIDATION-PLAN.md
  • echo/docs/workspaces-validate/07-HCD-AUDIT.md
  • echo/docs/workspaces-validate/flows/derivation-walkback.md
  • echo/docs/workspaces-validate/matrix.md
  • echo/docs/workspaces-validate/note.md
  • echo/docs/workspaces-validate/qa/_overnight-summary.md
  • echo/docs/workspaces-validate/qa/accounts.md
  • echo/docs/workspaces-validate/qa/brief-01-onboarding-fixes.md
  • echo/docs/workspaces-validate/qa/pains.md
  • echo/docs/workspaces-validate/qa/questions.md
  • echo/docs/workspaces-validate/screens/destructive-confirm.md
  • echo/docs/workspaces-validate/screens/empty-state.md
  • echo/docs/workspaces-validate/screens/feature-locked.md
  • echo/docs/workspaces-validate/screens/manage-list.md
  • echo/docs/workspaces-validate/screens/readonly-data.md
  • echo/docs/workspaces-validate/screens/request-submitted.md
  • echo/docs/workspaces-validate/screens/status-banner.md
  • echo/docs/workspaces/architecture-review.md
  • echo/docs/workspaces/codebase-exploration-report.md
  • echo/docs/workspaces/designer-brief-v2.md
  • echo/docs/workspaces/designer-brief.md
  • echo/docs/workspaces/designer-return.html
  • echo/docs/workspaces/execution-plan-final.md
  • echo/docs/workspaces/failure-analysis.md
  • echo/docs/workspaces/gate-check-protocol.md
  • echo/docs/workspaces/gripes.md
  • echo/docs/workspaces/inheritance-rules.md
  • echo/docs/workspaces/reference.md
  • echo/docs/workspaces/release-checklist.md
  • echo/docs/workspaces/workspaces-prd-v3-final.md
  • echo/frontend/AGENTS.md
  • echo/frontend/package.json
  • echo/frontend/posthog-setup-report.md
  • echo/frontend/src/App.tsx
  • echo/frontend/src/Router.tsx
  • echo/frontend/src/components/announcement/AnnouncementDrawerHeader.tsx
  • echo/frontend/src/components/announcement/AnnouncementErrorState.tsx
  • echo/frontend/src/components/announcement/AnnouncementIcon.tsx
  • echo/frontend/src/components/announcement/AnnouncementSkeleton.tsx
  • echo/frontend/src/components/announcement/Announcements.tsx
  • echo/frontend/src/components/announcement/WhatsNewItem.tsx
  • echo/frontend/src/components/auth/hooks/index.ts
  • echo/frontend/src/components/auth/utils/authPaths.ts
  • echo/frontend/src/components/chat/ChatTemplatesMenu.tsx
  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/components/chat/hooks/index.ts
  • echo/frontend/src/components/chat/hooks/useUserTemplates.ts
  • echo/frontend/src/components/common/AccessDeniedPanel.tsx
  • echo/frontend/src/components/common/FetchErrorPanel.tsx
  • echo/frontend/src/components/common/Logo.tsx
  • echo/frontend/src/components/common/UsageFreshness.tsx
  • echo/frontend/src/components/common/UserAvatar.tsx
  • echo/frontend/src/components/common/WorkspaceRedirect.tsx
  • echo/frontend/src/components/conversation/MoveConversationButton.tsx
  • echo/frontend/src/components/conversation/OngoingConversationsSummaryCard.tsx
  • echo/frontend/src/components/conversation/hooks/index.ts
  • echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx
  • echo/frontend/src/components/inbox/Inbox.tsx
  • echo/frontend/src/components/layout/AuthLayout.tsx
  • echo/frontend/src/components/layout/Header.tsx
  • echo/frontend/src/components/layout/ProjectOverviewLayout.tsx
  • echo/frontend/src/components/layout/WorkspaceLayout.tsx
  • echo/frontend/src/components/members/InviteMemberCard.tsx
  • echo/frontend/src/components/members/MembersToolbar.tsx
  • echo/frontend/src/components/members/index.ts
  • echo/frontend/src/components/organisation/OrganisationCapBanner.tsx
  • echo/frontend/src/components/organisation/OrganisationInviteWizard.tsx
  • echo/frontend/src/components/project/PinnedProjectCard.tsx
  • echo/frontend/src/components/project/ProjectAccessGuard.tsx
  • echo/frontend/src/components/project/ProjectAnalysisRunStatus.tsx
  • echo/frontend/src/components/project/ProjectListItem.tsx
  • echo/frontend/src/components/project/ProjectSharingModal.tsx
  • echo/frontend/src/components/project/ProjectSharingStrip.tsx
  • echo/frontend/src/components/project/ProjectTagsInput.tsx
  • echo/frontend/src/components/project/ProjectUsageAndSharing.tsx
  • echo/frontend/src/components/project/hooks/index.ts
  • echo/frontend/src/components/report/CreateReportForm.tsx
  • echo/frontend/src/components/report/hooks/index.ts
  • echo/frontend/src/components/settings/MyAccessCard.tsx
  • echo/frontend/src/components/workspace/AccessRequestsList.tsx
  • echo/frontend/src/components/workspace/DiscoverableWorkspaces.tsx
  • echo/frontend/src/components/workspace/DowngradeBanner.tsx
  • echo/frontend/src/components/workspace/FeatureGate.tsx
  • echo/frontend/src/components/workspace/OrganisationProjectsTable.tsx
  • echo/frontend/src/components/workspace/OrganisationUsageRollup.tsx
  • echo/frontend/src/components/workspace/PeriodSelect.tsx
  • echo/frontend/src/components/workspace/PilotBlockModal.tsx
  • echo/frontend/src/components/workspace/SeatCapBanner.tsx
  • echo/frontend/src/components/workspace/TierBadge.tsx
  • echo/frontend/src/components/workspace/TierCapacityMatrix.tsx
  • echo/frontend/src/components/workspace/UsageCard.tsx
  • echo/frontend/src/components/workspace/WorkspaceInviteWizard.tsx
  • echo/frontend/src/hooks/useMyInvites.ts
  • echo/frontend/src/hooks/useNotifications.ts
  • echo/frontend/src/hooks/useProjectSharing.ts
  • echo/frontend/src/hooks/useUrlSearch.ts
  • echo/frontend/src/hooks/useV2Me.ts
  • echo/frontend/src/hooks/useWhitelabelLogo.tsx
  • echo/frontend/src/hooks/useWorkspace.ts
  • echo/frontend/src/hooks/useWorkspaceProjects.ts
  • echo/frontend/src/lib/accessDenied.ts
  • echo/frontend/src/lib/api.ts
  • echo/frontend/src/lib/avatar.ts
  • echo/frontend/src/lib/bff.ts
  • echo/frontend/src/lib/frozenFeatureAttempt.ts
  • echo/frontend/src/lib/pilotBlock.ts
  • echo/frontend/src/lib/roles.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch workspaces

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread echo/scripts/seed_dev.py Fixed
Comment thread echo/server/dembrane/api/v2/auth.py Fixed
Comment thread echo/server/dembrane/api/v2/auth.py Fixed
Comment thread echo/server/dembrane/api/v2/workspaces.py Fixed
Comment thread echo/server/dembrane/api/v2/workspaces.py Fixed
Comment thread echo/server/dembrane/notifications.py Fixed
Import ordering, unused imports/vars, exception chaining (raise...from
None), Annotated[..., Depends(...)] for FastAPI handlers, and minor JSX
cleanups. No behavior changes.
- notifications: drop audience_user_id from emit/emit_sync warning logs
  (py/clear-text-logging-sensitive-data)
- workspaces: redact org IDs and actor user_id in partner-handoff
  initiated/accepted info logs; workspace_id alone is enough to trace
- auth: rewrite _EMAIL_RE so domain segments exclude '.', removing the
  ambiguous overlap that triggered py/polynomial-redos. Behavior
  verified unchanged against valid/invalid email shapes
- seed_dev: stop echoing SEED_USER_PASSWORD on completion; print a
  hint that points operators at the env var instead

Drive-by: rename leaked `ws` loop variable in list_workspaces to
`removed_ws_item` to clear a pre-existing mypy assignment-type error
that surfaced once the file was touched.
- project: sanitize conversation_id with _sanitize_for_filename before
  using it in os.path.join for the transcript directory. Closes the
  py/path-injection alert; UUIDs in practice are unaffected (hyphens
  become underscores, no behavioral change for valid data)
- conversation: stop returning str(e) to the client in the retranscribe
  handler's catch-all and 500-raise paths. Full traceback still goes
  to the server log via logger.exception; client now sees a generic
  "Failed to process audio" / "internal error" detail
  (py/stack-trace-exposure)
- docs/inbox.html: suppress js/xss-through-dom on the DOMParser call
  with an lgtm[] comment. The template is loaded from a script tag
  shipped inside the same self-contained bundle, not from network or
  user input — the alert is a known false positive for the bundled
  docs format
- project.py: write the transcripts zip via `tempfile.mkstemp` so the
  on-disk path is OS-generated, not derived from the project name
  (closes py/path-injection). User-derived name is kept only in the
  Content-Disposition header. Also makes cleanup resilient and fixes
  a latent race between concurrent downloads of the same project.

- docs/workspaces/inbox.html: drop the 1.3 MB designer mockup that
  tripped js/xss-through-dom. It was never loaded at runtime — the
  inbox lives in `frontend/src/components/inbox/Inbox.tsx`. Updated
  the three stale comment references to point there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants