Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

feat(files): optimistic Files tab, restoreNode undo, and Slack source workspace scoping

merged#2579CedarCopilot

CedarCopilot wants to merge 2 commits into staging from fix/dm-container-kind

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 25, 2026, 2:58 PM
  2. Aug 25, 2026, 4:28 PM
  3. Merged
    Aug 25, 2026, 5:06 PM
  4. Live on prod
    Aug 25, 2026, 5:06 PM
  5. Observed 33 hours
    Aug 25, 2026, 5:06 PM
  6. Pipelines steady after this deploy
    Aug 25, 2026, 5:06 PM
  7. Unobserved

    Live on prod, no production signal yet

    Aug 27, 2026, 2:12 AM

Behaviors Libra is checking

CRM integration behavior from crm-admin/cli.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 98 prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Value \), up from 25 in the comparable baseline, but this domain-wide failure family is not tied to this intent.

prod, checked Aug 27, 2026, 1:13 AM
Email workflow behavior from files/ConversationFileTree.tsx, files/DriveSection.tsx, files/FilesTab.tsx keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 3 recent prod failure log lines for mirror.upsertEmailThread failed (write CONNECTION_CLOSED aws-1-us-east-2.pooler.supabase.com:6543), but the same failure had 8 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR.

prod, checked Aug 27, 2026, 2:12 AM

Libra has verdicts on 0 of 2 tracked behaviors on prod; 2 are still being checked. Libra checks hourly for 3 days after each deploy.

Three bodies of work that ended up on one branch. The email-attachment work described in the original body of this PR already merged via #2578 , it is not in this diff.

1. Optimistic Files tab (262db9f42, b575e3919, 7ae255e41, 9c0ef6215, 22ad9d296)

Authored in a concurrent session. Every Files-tab action used to wait for a server round trip and a refetch before anything moved , a delete with External drives open took ~2.6s, because invalidateFiles awaited four query families and two of them are Google. Now the cache moves in the same frame as the click, with a real inverse mutation (files.restoreNode) behind Undo rather than a cached copy.

The pipeline is cancel → snapshot → patch → mutate → reconcile/rollback → settle. Cancelling first is the step that is easy to omit and impossible to notice missing: global defaults are refetchOnWindowFocus + refetchOnMount, so a read started when the window regained focus (exactly what happens returning from the Google Picker) resolves after the patch and writes the old list back , the "it deletes and then pops back in" report.

2. Review fixes (8f5c1445d)

Three findings from reviewing the above:

  • removeMany rolled the whole batch back on any failure, putting rows the server really deleted back into the cache while the toast beside them named a single failure. pinDrive already had this right , take back only what failed. The whole-batch snapshot is still used when every delete failed, the one case where restoring everything is correct.
  • The restore path-collision refusal was recognised by message.startsWith('Cannot restore '). The classification lived in prose, so rewording the sentence would silently turn a CONFLICT the toast can explain into an opaque 500. Now a DocumentRestoreConflictError, matched on the type.
  • nextNoteName counted any leaf starting with note, so a user's own notes-from-kickoff pushed the auto-name past the rows that exist , a tab with one note handed out note_3. Anchored to /^note(_\d+)?$/.

The bulk-delete regression test asserts the cache through a bare hook probe, not the DOM through <FilesTab>: settle invalidates, the stubbed listChildren answers, and that refetch decides what is on screen , so a DOM assertion passes for the broken rollback too. Verified failing before the fix, passing after.

3. Slack source workspace scoping

playbook-webhook-sources.ts matched registrations on sourceType + sourceRef (a bare Slack channel id) + enabled, with no workspace predicate , workspaceId was threaded all the way into dispatchSlackChannelSources and used only in log lines. Slack guarantees a channel id is unique within a workspace, not across them, so tenant isolation rested on ids not colliding rather than on the query. Raised by Greptile against #2575; the code was already on staging.

findWorkspaceUserIds resolves the workspace's Cedar users via connection.externalId , the same join slack-events-webhook.ts already uses , and is called only after a channel match, so the overwhelmingly common zero-registration path pays nothing. scopeSourcesToWorkspace is pure, so the boundary is testable without a database. It fails closed: an empty set, or a failed lookup, fires nothing. Foreign drops log cross_workspace_drop with the owner id, since either two workspaces share a channel id or a registration outlived its owner's Slack connection , both warrant a look.

Test plan

  • pnpm run autofix:local green: 2,622 mail tests, 7,466 server tests, oxlint and deps:check clean.
  • New: 6 workspace-scoping tests, 1 bulk-delete partial-failure regression test (verified red before / green after), 1 test that an unrelated error is not dressed up as a CONFLICT.
  • Harness gains settleEach , release/fail settle every pending call the same way and can only produce all-succeeded or all-failed, never the partial case the bug lived in.

Known issue, pr

Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/mail/docs/agent-workspace.mdno production surface mapped
  • apps/mail/docs/optimistic-files.mdno production surface mapped
  • apps/mail/docs/wiki/optimistic-mutations.mdno production surface mapped
  • apps/mail/modules/conversations/components/files/ConversationFileTree.tsxno production surface mapped
  • apps/mail/modules/conversations/components/files/DriveSection.tsxno production surface mapped
  • apps/mail/modules/conversations/components/files/FilesTab.tsxno production surface mapped
  • apps/mail/modules/conversations/components/files/__tests__/filesCachePatch.test.tsno production surface mapped
  • apps/mail/modules/conversations/components/files/filesCachePatch.tsno production surface mapped
  • apps/mail/modules/conversations/components/files/optimistic-files.tsno production surface mapped
  • apps/mail/tests/modules/conversations/filesTabHarness.tsxno production surface mapped
  • apps/mail/tests/modules/conversations/filesTabOptimistic.test.tsxno production surface mapped
  • apps/mail/tests/modules/conversations/filesTabTree.test.tsxno production surface mapped
  • apps/server/src/crm-admin/cli.tsno production surface mapped
  • apps/server/src/services/documents/index.tsno production surface mapped
  • apps/server/src/services/file-system/index.tsno production surface mapped
  • apps/server/src/services/playbook/__tests__/playbook-webhook-sources.test.tsno production surface mapped
  • apps/server/src/services/playbook/playbook-webhook-sources.tsno production surface mapped
  • apps/server/src/trpc/routes/__tests__/files-restore-node.test.tsno production surface mapped
  • apps/server/src/trpc/routes/files.tsno production surface mapped