draftIdResolved.test.tsfix(tests): types:test reaches zero in both packages The leftovers, and three of them were assertions or props that could not work. DEAD SHAPES. `model` is retired from `AgentSummary` (797 of 896 production docs said `sonnet` and the hardcoded per-tool constants were what ran), yet two fixtures still set it. `summary` is not an `AgentSharePanel` prop — it takes `{ agentId, className }`. `onAddTaskToGroup` is no longer on `AgendaTaskCallbacks`, and the mocks type is a mapped type OVER that interface, so the extra key was the one thing it could not carry. GROWN TYPES. `AgentSummary` gained `namespace` — where the agent's FOLDER lives, which is a different question from `scope`, where its document lives — plus `defaultFile` and `chatEnabled`. NARROWING, NOT CASTING. `closest()` answers `Element | null` and `within()` wants an `HTMLElement`: the non-null `!` covered the null and not the widening, so it is an `instanceof` check now and a card whose text escaped its container fails loudly. Same for `PAGE[0].createdAt` and the reference-lookup input. MOCKS FROM THE REAL THING. `applyUpdate` carries `ApplyUpdateClient`'s signature, so `mock.calls[1][0].update` — the bytes those cases exist to prove were re-sent — is a real read rather than an out-of-range one on an empty tuple. Likewise `logToolCall` (six arguments, five destructured), `createPlaybookExtensions` and `useSubagentCreation`, whose option types come off the hook rather than being exported for a test's benefit. Four `as unknown as` casts go with them. `feedScopeGate`'s `ALL` lost its `as const`: nothing read it in a type position, and the readonly tuple could not be the mutable `string[]` the resolved participation set arrives as. Also `shareDocumentTool.test.ts`, new this morning, through the same two helpers the rest of the server already uses — `toolOutput()` for the `ValidationError` arm and one named boundary for Mastra's request context. apps/mail 19 -> 0, apps/server 11 -> 0. Both `types` stay clean. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 9, 2026, 9:39 PMdraftSlice.test.tstest: add 253 tests across frontend slices and server utilities - Fix Jest 30 config (extract babel-plugin-import-meta.cjs, remove duplicate jest.config.js) - Add @vitest/coverage-v8 to server for coverage reporting - Add server tests: concurrentMap, message-utils, utils (cleanEmailAddress, truncateFileName, etc.) - Add frontend tests: mailSlice, searchSlice, draftSlice, navigationSlice, aopSlice, uxSlice, threadSlice - Add component tests: ProposalsReviewPanel, diffUtils, stylingUtils Co-Authored-By: Claude Sonnet 4.6 (1M context) <<email>>Apr 27, 2026, 11:08 PMhtmlDiffBlocks.test.tsxfix(mail): render the composer's draft diff with real blocks and bullets The diff overlay flattened both bodies to plain text, so lists lost their markup entirely (any list not a direct child of the root vanished into a single mashed line) and the pre-wrapped text no longer matched the editor's spacing. Diff the parsed block structure instead and rebuild real list, paragraph and blockquote markup under the editor's own classes. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 12, 2026, 9:56 PMrecipientClipboard.test.tsfeat(tasks): give a task with no output a place to be worked Clicking a task that has produced nothing had nowhere to land: the centre column became a read-only panel with no Execute, so the one action the task existed for was missing. It now opens the task's conversation on its Inbox tab — the mail behind the task, which is what you read to do it — with the task itself pinned above the rows as OpenTaskExecutionCard, carrying its Execute with it. The panel keeps an Execute too, for the deep link or collapsed rail where no list is on screen to defer to. A reload or a pasted link ran none of that: nothing called openTask, so the URL sync claimed the bare `task` artifact and you got the read-only card whatever kind of task it was. TaskExecutionUrlSync closes that — when `?task=` is the only artifact in the URL it resolves the task and runs the same openTask a click runs. It is a sibling of LayoutUrlSync rather than part of it because it fetches, and folding it in would make a pure URL-store adapter require a QueryClient. Execution mode also outlived its own centre column: backing out of the thread landed on an entry still carrying `?task=`/`?group=`, leaving the task rail beside a task list, the same tasks twice. useExitTaskExecutionOnClose clears both on the close transition — a transition, not the bare state, because opening writes the params first and the artifact a beat later. Alongside, on the surfaces that list tasks: - The chat empty state gains a Tasks card. Tasks are per-conversation but the empty chat is per-thread, so a deal with three follow-ups showed only the one you opened. TaskBlock now routes actions against the task's own conversation rather than the active one, and takes `isActive` so the open task is tinted the same way it is on the board. - The agenda's action slot is a fixed h-6 w-28 box whether or not a button is in it — without the height, hovering a row grew it and walked the page. A finished task keeps a disabled Execute instead of an empty box. The hover scrim is opaque bg-surface rather than a translucent backdrop-blur, whose filter region clamped at the edges and drew a visible rectangle. - The chat queue card is a flow sibling above the composer instead of floating over the transcript, so it pushes the transcript up and its bottom edge runs under the input. - Copying a recipient pill writes the bare address to the system clipboard as well as the in-app one, and selection compares cleaned addresses so a "Name <email>" entry shows as selected. - The composer's autofocus timer outlived the editor on a fast close and threw; it now clears on destroy. - Meeting detail sizes its video and transcript with min-h-0 flex-1: a percentage height resolves to auto against an auto-height flex panel, so h-full let both overflow the dialog. Also adds the MCP tool-allowlist design doc. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 13, 2026, 6:19 PMsaveDraftError.test.tsfix(drafts): four different failures shared one sentence Every draft-save failure in prod said "Failed to save draft". Among them was invalid_grant — a dead Google token, where retrying is the one thing that cannot work. The message sent the user back to the keyboard to do it anyway. Classify the cause and say it. A dead token names itself and carries a Reconnect action; a provider fault says the text is safe rather than implying the user should retype; a transport failure says so. The toast also carries a short trace ref, so "it said X" becomes a lookup in Axiom instead of a search across four identical strings. (design: bug-draft-saving-duplicates-2026-09-01 phase 4) Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 11:45 PMundoSendErrorPaths.test.tsfix: close the six audit findings on the send/telemetry work A — the relay trusted the client to redact. It now enforces its own denylist on normalized keys and drops email-shaped strings, so a mistake in one browser build cannot put a recipient or a subject into Axiom. B — teardown and keepalive were one flag. A send carrying attachments is too large for keepalive, so it fell through to awaiting the session gate while the page was going away, which is exactly how the request got lost. They are now separate: every teardown commit skips the gate, keepalive stays size-limited. C — the volume caps could silence the one event an incident needs. The sampler already refused to drop a send or a failure, but the session cap and the buffer eviction knew nothing about that, so a long-lived tab went quiet for everything after 2,000 events and overflow evicted oldest-first regardless. Sends and failures are now exempt from the session cap and evicted last. sanitizeEvent carries the flag onto the buffered copy — without that the eviction loop read undefined on every entry and the exemption did nothing, which the new test is what caught. D — findOrCreateEmailEvent (singular) never wrote rfc_message_id, so events created through that path stayed invisible to the RFC dedupe added in 51bc228ab. E — the client request id was a timestamp plus a counter that resets on page load. Two tabs, or a reload inside the same millisecond, minted identical ids and the join then matched unrelated requests. It is a uuid now. F — residue from the 22-row phantom cleanup. Two analytics_draft_actions rows still pointed at messages Gmail 404s; the table has no FK, so nothing cascaded and nothing dangled visibly, but attribution-gap.ts matches drafts to sends by message_id and the fallback only runs when that column is NULL. Both real Cedar-drafted sends were being reported as unattributed. Repointed to the surviving message, verified against live Gmail, before-state kept. The one agent_executions row with a NULL event_id is deliberately left alone: the run was triggered by the phantom, and repointing it at a different message would invent causation. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 14, 2026, 6:39 AMundoSendRestore.test.tsfix: close the six audit findings on the send/telemetry work A — the relay trusted the client to redact. It now enforces its own denylist on normalized keys and drops email-shaped strings, so a mistake in one browser build cannot put a recipient or a subject into Axiom. B — teardown and keepalive were one flag. A send carrying attachments is too large for keepalive, so it fell through to awaiting the session gate while the page was going away, which is exactly how the request got lost. They are now separate: every teardown commit skips the gate, keepalive stays size-limited. C — the volume caps could silence the one event an incident needs. The sampler already refused to drop a send or a failure, but the session cap and the buffer eviction knew nothing about that, so a long-lived tab went quiet for everything after 2,000 events and overflow evicted oldest-first regardless. Sends and failures are now exempt from the session cap and evicted last. sanitizeEvent carries the flag onto the buffered copy — without that the eviction loop read undefined on every entry and the exemption did nothing, which the new test is what caught. D — findOrCreateEmailEvent (singular) never wrote rfc_message_id, so events created through that path stayed invisible to the RFC dedupe added in 51bc228ab. E — the client request id was a timestamp plus a counter that resets on page load. Two tabs, or a reload inside the same millisecond, minted identical ids and the join then matched unrelated requests. It is a uuid now. F — residue from the 22-row phantom cleanup. Two analytics_draft_actions rows still pointed at messages Gmail 404s; the table has no FK, so nothing cascaded and nothing dangled visibly, but attribution-gap.ts matches drafts to sends by message_id and the fallback only runs when that column is NULL. Both real Cedar-drafted sends were being reported as unattributed. Repointed to the surviving message, verified against live Gmail, before-state kept. The one agent_executions row with a NULL event_id is deliberately left alone: the run was triggered by the phantom, and repointing it at a different message would invent causation. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 14, 2026, 6:39 AMundoSendUnloadGuard.test.tsfix: close the six audit findings on the send/telemetry work A — the relay trusted the client to redact. It now enforces its own denylist on normalized keys and drops email-shaped strings, so a mistake in one browser build cannot put a recipient or a subject into Axiom. B — teardown and keepalive were one flag. A send carrying attachments is too large for keepalive, so it fell through to awaiting the session gate while the page was going away, which is exactly how the request got lost. They are now separate: every teardown commit skips the gate, keepalive stays size-limited. C — the volume caps could silence the one event an incident needs. The sampler already refused to drop a send or a failure, but the session cap and the buffer eviction knew nothing about that, so a long-lived tab went quiet for everything after 2,000 events and overflow evicted oldest-first regardless. Sends and failures are now exempt from the session cap and evicted last. sanitizeEvent carries the flag onto the buffered copy — without that the eviction loop read undefined on every entry and the exemption did nothing, which the new test is what caught. D — findOrCreateEmailEvent (singular) never wrote rfc_message_id, so events created through that path stayed invisible to the RFC dedupe added in 51bc228ab. E — the client request id was a timestamp plus a counter that resets on page load. Two tabs, or a reload inside the same millisecond, minted identical ids and the join then matched unrelated requests. It is a uuid now. F — residue from the 22-row phantom cleanup. Two analytics_draft_actions rows still pointed at messages Gmail 404s; the table has no FK, so nothing cascaded and nothing dangled visibly, but attribution-gap.ts matches drafts to sends by message_id and the fallback only runs when that column is NULL. Both real Cedar-drafted sends were being reported as unattributed. Repointed to the surviving message, verified against live Gmail, before-state kept. The one agent_executions row with a NULL event_id is deliberately left alone: the run was triggered by the phantom, and repointing it at a different message would invent causation. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 14, 2026, 6:39 AM