extensionsfix(drafting): the drafts a send leaves behind, the drafts nobody sees, and the link Space ate Three A5 items, all in the gap between a draft existing in Gmail and Cedar knowing about it. Leftover drafts after a send (Vooma, Horison). Gmail retires the draft that was actually sent; nothing retired the others, and a send without a draftId only deleted one when an emailHeaderMessageId/id happened to ride along. So a thread that had collected a second Cedar draft kept it, and the rep found a stale pre-send copy under a message they had already sent — 77 of these across 21 accounts in three weeks. mail.send now sweeps the thread afterwards, deleting only drafts Cedar authored (checked against analytics_draft_actions, the same authorship record the rewrite path adopts by). Fire-and-forget: the mail is sent, and nothing here may fail that. Drafts with no task at all. The chat/headless save-draft path created a real Gmail draft and attached it to nothing — 90% of emailResponses drafts and 100% of preMeetingPrep drafts in the week to Aug 30 had no user_tasks row pointing at them, so the user could not open, close or track them and nothing could find them to clean up. It now upserts the draft task the on-event path has always created, via the same idempotent upsertDraftTask, skipping interactive surfaces where the draft card already carries its own task descriptor. Link formatting (Concentrate). ExitLinkOnSpace bound Space globally and called unsetLink() whenever the caret was anywhere inside a link — and unsetLink extends over the whole mark range on a collapsed selection, so editing a word in the middle of a hyperlink and pressing space stripped the href off the entire link, with the next autosave writing the de-linked body to Gmail. It now acts only at the END of a link, which is the case it was written for; mid-link it declines and the space types normally. The Link mark is inclusive only when autolink is on, which the composer turns on, so the extension does still have a job. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 9:19 PMmarkdown-editor-liveness.test.tsxfix(mail): stop a destroyed editor reaching onEditorCreated and killing the page Follow-on to 0bcd5cde0, which guarded the effect sites that touch a TipTap editor but left the handoff itself unguarded. `MarkdownEditor` announces its editor to the parent from a `useEffect` on `[editor]`, gated by `if (editor)` — the half-guard the last fix was about. TipTap builds the editor a tick after mount and tears it down on a 1ms timer after unmount, and a destroyed editor is not null: it keeps its identity while commandManager/view/state are nulled underneath it. A surface that disappears in that gap leaves the effect holding a live-LOOKING editor, and every consumer callback reaches for `.commands` or `.getMarkdown()` first thing — so the throw lands inside an effect and escapes to the route error boundary, replacing the whole page with "Something went wrong!". That is what four users hit today. The deployed bundle (release 7ddb16f8) still carries two unguarded callbacks in the ConversationView chunk — NextStepsCard's `editor.commands.setContent(nextSteps, …)` and the Slack composer's `setContent`/`getMarkdown` pair — and the resolved frames on every event today are `onEditorCreated` -> TipTap's `get commands`, throwing "Cannot read properties of null (reading 'commands')" on /agent?conversationId=… and /home?chat=… URLs. Guarding the handoff rather than the seven callbacks that consume it means no consumer can ever be handed a dead editor — including the four outside the conversation view (AgentsEditor, the share page, the agents demo) that would otherwise each need the same line. Nothing is lost by skipping the call: a destroyed editor cannot accept content, and a real remount builds a new editor, which changes the `[editor]` dep and re-runs the effect to seed normally. Why this started: the seeding code is unchanged since April, the handoff since March, and TipTap's destroy machinery is byte-identical between 3.18 and 3.26. What changed is where the card lives — b6ded45e9 put NextStepsCard on the Overview tab (2026-07-13) and 47dc63f32 gave that tab doc embeds (2026-07-27), so an artifact now slides over it and unmounts it mid-creation. First page-replacing occurrence is 2026-07-29 17:00Z, on a /strategicOverview/ URL. The test drives `useEditor` directly so the destroyed state is exact rather than timing-dependent; it fails on the old line with the production error text. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 14, 2026, 2:01 PMmarkdown-prose-classes.test.tsstyle(coaching): space the section breaks in the markdown, not the stylesheet Reverts the hr margin to its original my-4. The breathing room above a section break is now authored per-document: a paragraph containing only a zero-width space on the blank line above each `---`. A document that wants the space asks for it; one that does not is not padded by the stylesheet. The filler has to be exactly `&#8203;`, and the near-misses are the reason this came with a test. Measured through the real parse/serialize round trip that runs on every agent rewrite: &#8203; 4 -> 4 -> 4 -> 4 nodes survives &nbsp; 4 -> 4 -> 3 -> 3 renders once, gone by the third rewrite U+00A0 4 -> 3 -> 3 -> 3 gone by the second blank 3 collapses immediately So the obvious choices fail silently: correct on the first render, and the spacing quietly evaporates the next time the coaching agent rewrites the file, with nothing in the diff to explain it. The playbook now names the token and says not to tidy it away. 42 spacers across the 7 documents that have section rules; the per-call docs use headings and take none. Verified in the published tree: 42 zero-width spaces across 43 documents. Heading and paragraph spacing from the previous commit is kept — only the rule margin is reverted. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 25, 2026, 6:25 PMmenu-selection-indicator.test.tsxfix(ui): a menu marks the chosen row with a tick on the right, never a black dot The agent workspace's Folder and Default file submenus put a solid filled disc beside the current value, in a left gutter — shadcn's `RadioItem` default, inherited without anyone choosing it. It out-weighed the label it was marking, it sat in the lane that belongs to the option's own icon, and it looked nothing like the checkbox rows in the same menu, which were already ticking on the right. One mark, one side, for every "which one(s)?" surface: `Check` on the right in a lane every row reserves. Applied to `DropdownMenuRadioItem`, its checkbox sibling, both `ContextMenu` items and the cedar-os copy of the menu; `SelectItem` and `OptionPicker` were already correct. `radio-group.tsx` keeps its dot — there the dot IS the control, not an annotation on one. Written into the design system (crystallized.md → Counts, ticks and ordinals, plus the panel checklist and CLAUDE.md), and guarded by a test, because the dot is a one-line regression and nothing else in the suite would notice. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 7, 2026, 1:43 PM