FileLinkChip.tsxfix(playbook): the four things wrong with the trigger UI 1. TWO GLYPHS THAT SAID NOTHING. The meeting glyph was invisible. It asked `connectionLogo('meetings')`, which resolves to the hand-drawn icons.tsx Calendar — an `<svg fill="none">` whose path sets no fill of its own, so it inherits `none` and paints NOTHING unless the call site passes a `fill-*` class. That row passes only a size. Fixed at both ends: the glyph row now uses lucide's Calendar (which carries its own stroke), and the shared icon gets `fill="currentColor"` on its path so every other call site that passes only a size renders too. It is a presentation attribute, which a CSS class beats, so date-picker-dialog's `fill-[#9D9D9D]` is unaffected. The external-CRM glyph was sync arrows, which read as "refresh" — an action, not an event — and named neither the event nor the system. Both marks now come from the conversation timeline (Calendar, Building2), which is where a user has already learned what these events look like. The trigger badge follows suit. 2. THE MEMBER PICKER MOVES INTO THE BREADCRUMB ROW. It answers the same question the crumbs do — WHOSE playbook is this — so on its own row underneath it read as a filter over the content rather than as part of the address, and cost a row of height on every load for a control most sessions never touch. Only the picker moves: the banner and the permission notice stay in AdministeredUserBar, because they are full-width messages and squeezing either into a title row would truncate it or shove the crumbs sideways as it appears. The bar now collapses to nothing when it holds neither. 3. A REF WITH NO INSTRUCTION HAD NO WAY TO GET ONE. Every ref authored before this feature is self-closing, so it parses to the inline chip — and the chip had nowhere to put an instruction. That is not a migration to wait out; it is every ref in every playbook today. The chip now carries a second half, `+ Instructions`, which converts it into the panel with the caret already in the instruction area. Offered ONLY inside a trigger: a ref in <always-loaded> is a document the agent reads, not an agent a trigger fires, so there is no trigger for it to have an instruction for. The shared FileLinkNode does not learn what a trigger is — it asks its host "does anyone want a segment on this chip", and the playbook is the only host that answers. The conversion carries `section`/`when` across (dropping them would turn "add an instruction" into "quietly widen this ref's scope") and has two shapes: a chip alone in its paragraph replaces the paragraph, leaving no empty one behind; a chip among prose leaves the prose as the block's own note. 4. THE DISPATCH EXPLAINER MOVES INTO A `?`. It was a line under the pill. But it is REFERENCE, not status — identical for every trigger of a kind, never changing — and a sentence that never changes on a row you read daily stops being read within a week while still costing a line of height on every block in the document. Behind a `?` it is one click away on the day you need it and invisible on the days you do not. The popover answers two things in the order they are needed: what wakes it (with the four event glyphs when the trigger is `any`, since "every event" is the one label that names no thing), and what happens then — whether an orchestrator reads this block and CHOOSES, or every agent in it simply runs. 4,103 mail tests green; tsc -b clean. Co-Authored-By: Claude Opus 5 <<email>>Sep 7, 2026, 11:00 PMFileLinkMenu.tsxfeat: typed cells, live ref chips, and tables as first-class files (design: table-documents phases 6, 7) Phase 6 — a cell chip and a prose chip are literally the same component. ConversationNode and EventNode were split the way FileLinkChip already was (Content + the NodeViewProps wrapper that mounts it), so behaviour stays identical by construction: same data hooks, same click handler, same setSelectedArtifact path. The only PM-specific parts were NodeViewWrapper and reading node.attrs, which is why the extraction is small. Editing mounts a one-line TipTap instance for the focused cell ONLY, configured with the EXISTING suggestion extensions — so typing `@` in a table cell runs the same extension, the same search, and inserts the same node as typing `@` in a prose document, rather than a parallel implementation that drifts. Exactly one editor is alive at a time, so the descriptor-tree costs that rule out a ProseMirror grid never apply here. Phase 7 — "New table" in the file-creation menu, a table graph-node kind, "N rows × M columns" from the metadata the server hook already writes, the grid in the artifact panel (discriminated inside FileArtifact on the FETCHED documentType, since the outer switch is on ContextKind where a table is just a `file`), a read-only grid on the public share page, resolveOpenDoc, and Download as Excel/CSV wired to documents.exportTable. Also completes Phase 10's agent-facing half: the `table` tool's `create` now takes a `from` clause, so an agent materializes rows from a real query instead of transcribing them. The query runs BEFORE the document is provisioned, so an over-cap or malformed filter leaves no half-built table behind, and truncation plus any binding error is surfaced as a tool warning rather than silently yielding a short or empty column. Three fixes found by running the suites rather than by reading: - The tool description had grown to 2134 chars, past the 2048 limit Claude Code truncates at — text past the cutoff is silently dropped for every caller. Trimmed to 2030 while ADDING the `from` guidance, which is the part most worth having early. - tableTool.test.ts enumerated its mock of the documents barrel, so every new import in the tool broke the file with a mock error rather than a real failure. It now spreads the real module. - A cell-ref offset expectation was hand-counted one short (`[[conversation: 9f2e]]` is 22 chars, so end is 27). The implementation was right; the test now asserts by slicing the value, since these offsets exist precisely so the grid can interleave text and chips. Tests: 1787 server, 55 mail across the table/home/files suites. Both apps typecheck clean. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 10, 2026, 5:04 PMFileLinkNode.tsxfix(playbook): the four things wrong with the trigger UI 1. TWO GLYPHS THAT SAID NOTHING. The meeting glyph was invisible. It asked `connectionLogo('meetings')`, which resolves to the hand-drawn icons.tsx Calendar — an `<svg fill="none">` whose path sets no fill of its own, so it inherits `none` and paints NOTHING unless the call site passes a `fill-*` class. That row passes only a size. Fixed at both ends: the glyph row now uses lucide's Calendar (which carries its own stroke), and the shared icon gets `fill="currentColor"` on its path so every other call site that passes only a size renders too. It is a presentation attribute, which a CSS class beats, so date-picker-dialog's `fill-[#9D9D9D]` is unaffected. The external-CRM glyph was sync arrows, which read as "refresh" — an action, not an event — and named neither the event nor the system. Both marks now come from the conversation timeline (Calendar, Building2), which is where a user has already learned what these events look like. The trigger badge follows suit. 2. THE MEMBER PICKER MOVES INTO THE BREADCRUMB ROW. It answers the same question the crumbs do — WHOSE playbook is this — so on its own row underneath it read as a filter over the content rather than as part of the address, and cost a row of height on every load for a control most sessions never touch. Only the picker moves: the banner and the permission notice stay in AdministeredUserBar, because they are full-width messages and squeezing either into a title row would truncate it or shove the crumbs sideways as it appears. The bar now collapses to nothing when it holds neither. 3. A REF WITH NO INSTRUCTION HAD NO WAY TO GET ONE. Every ref authored before this feature is self-closing, so it parses to the inline chip — and the chip had nowhere to put an instruction. That is not a migration to wait out; it is every ref in every playbook today. The chip now carries a second half, `+ Instructions`, which converts it into the panel with the caret already in the instruction area. Offered ONLY inside a trigger: a ref in <always-loaded> is a document the agent reads, not an agent a trigger fires, so there is no trigger for it to have an instruction for. The shared FileLinkNode does not learn what a trigger is — it asks its host "does anyone want a segment on this chip", and the playbook is the only host that answers. The conversion carries `section`/`when` across (dropping them would turn "add an instruction" into "quietly widen this ref's scope") and has two shapes: a chip alone in its paragraph replaces the paragraph, leaving no empty one behind; a chip among prose leaves the prose as the block's own note. 4. THE DISPATCH EXPLAINER MOVES INTO A `?`. It was a line under the pill. But it is REFERENCE, not status — identical for every trigger of a kind, never changing — and a sentence that never changes on a row you read daily stops being read within a week while still costing a line of height on every block in the document. Behind a `?` it is one click away on the day you need it and invisible on the days you do not. The popover answers two things in the order they are needed: what wakes it (with the four event glyphs when the trigger is `any`, since "every event" is the one label that names no thing), and what happens then — whether an orchestrator reads this block and CHOOSES, or every agent in it simply runs. 4,103 mail tests green; tsc -b clean. Co-Authored-By: Claude Opus 5 <<email>>Sep 7, 2026, 11:00 PMFileLinkSuggestion.tsfeat(playbook): the agent panel — a ref stops being a chip (design: per-trigger-instructions phase 6) A ref inside a trigger was a 12px FileText glyph and a document title at py-0.5 — the smallest thing in the block, indistinguishable from a link to a resource doc, and the most important thing in the block. It becomes a block-level panel: the real AgentAvatar and the agent's name, then its instruction underneath. The instruction area is ALWAYS rendered. No toggle, no disclosure. An instruction you have to click to see is one nobody audits, and an empty slot that shows itself is what teaches the feature exists. The empty state is a CSS ::before, never seeded text. Seeded text would round-trip into the XML and every ref would ship with "Instructions for this trigger…" as its real instruction, delivered to the agent. The test mutation-checks this: seeding the placeholder as content makes both assertions fail. Two keyboard rules the panel needs and a plain block node does not: - Enter inserts a hardBreak (which the serializer writes as \n) rather than splitting the panel into two refs pointing at the same agent. - isolating, plus Backspace-deletes-empty-panel: without isolating, an instructed panel merges its text upward and takes its documentId with it. `triggerRef` is registered in ID_NODE_TYPES. Omitting it makes the schema strip the node's nodeId on every save and re-seed the whole Y.Doc — the bug documented in that file. Also fixes a PRE-EXISTING round-trip bug found on the way: compile-playbook reads `section`/`when` off a ref, but the editor emitted and parsed only `id`, so any playbook edited in the UI silently lost them. Both ref shapes now carry them, in both directions — fixing only one shape would have relocated the loss rather than removed it. @-insertion could not produce a panel as the design assumed: the @ menu is PATH-addressed and a <ref> needs an id. ReferenceOption gained an optional documentId, so an option that knows its document produces a panel and the system tokens (@crm-updater, @next-steps), which have no document behind them, correctly stay chips. 8 new panel tests + 5 attribute round-trip cases; 55 playbook and 190 document-saving tests green. Co-Authored-By: Claude Opus 5 <<email>>Sep 7, 2026, 10:25 PMindex.tschore: snapshot in-progress working tree before layout-slice refactor Pre-existing WIP across chat input, file-link mentions, nav buttons, agenda, and server file-system, committed to clear the runway for the unified layout slice implementation (design: layout-slice). Co-Authored-By: Claude Opus 4.8 (1M context) <<email>>Jul 11, 2026, 8:20 PMmarkdown-bridge.tsrefactor: consolidate document code into documents/ + add {{ event mention Frontend: move the editor primitive, file-link/, mention/, yjs/, and history/ under apps/mail/modules/documents/ so every doc-editor concern lives in one place. Rename AgendaConversationMention -> ConversationMention and generalize it to take an onSelect callback so the agenda keeps its task-promotion dispatch while regular docs insert a plain conversationNode chip. Backend: rename services/document-store/ -> services/documents/ and absorb services/sse/doc-event-bus.ts + services/sse/doc-events-delta.ts alongside their tests. New: {{ event mention in documents. Custom Tiptap suggestion matcher requires the literal {{ prefix; picker shows a type-icon, title, conversation context, and relative time; selecting inserts an eventNode chip. New tRPC route crm.searchEventsForMention runs a global search across the user's crm_events (emails, slack, meetings, calls, notes, custom) joined to company info. crm_conversation_updates lives in its own table and stays excluded by design. Co-Authored-By: Claude Opus 4.7 (1M context) <<email>>May 26, 2026, 9:30 AMremarkFileLinksPlugin.tsrefactor: consolidate document code into documents/ + add {{ event mention Frontend: move the editor primitive, file-link/, mention/, yjs/, and history/ under apps/mail/modules/documents/ so every doc-editor concern lives in one place. Rename AgendaConversationMention -> ConversationMention and generalize it to take an onSelect callback so the agenda keeps its task-promotion dispatch while regular docs insert a plain conversationNode chip. Backend: rename services/document-store/ -> services/documents/ and absorb services/sse/doc-event-bus.ts + services/sse/doc-events-delta.ts alongside their tests. New: {{ event mention in documents. Custom Tiptap suggestion matcher requires the literal {{ prefix; picker shows a type-icon, title, conversation context, and relative time; selecting inserts an eventNode chip. New tRPC route crm.searchEventsForMention runs a global search across the user's crm_events (emails, slack, meetings, calls, notes, custom) joined to company info. crm_conversation_updates lives in its own table and stays excluded by design. Co-Authored-By: Claude Opus 4.7 (1M context) <<email>>May 26, 2026, 9:30 AMuseCachedFileLinkSearch.tschore: snapshot in-progress working tree before layout-slice refactor Pre-existing WIP across chat input, file-link mentions, nav buttons, agenda, and server file-system, committed to clear the runway for the unified layout slice implementation (design: layout-slice). Co-Authored-By: Claude Opus 4.8 (1M context) <<email>>Jul 11, 2026, 8:20 PM