componentsMerge origin/staging into feat/inbox-client-composed-feed Three resolutions, two of them semantic rather than textual: - scope-map.ts: staging renamed the `document` family to `document-write` and split it into a per-action switch; this branch had introduced `requiresPlaybookWrite`, which carves subagent docs back out of the playbook escalation. Kept staging's structure and pointed its `write` action at our predicate — the auto-merged surroundings (FAMILIES_WITH_ACTION_OVERRIDES, the `table` case) already referenced both, so this is what the rest of the file expects. - scope-map.test.ts: the subagent carve-out tests merged textually but still called the old `'document'` tool id, so they had stopped exercising the carve-out (one would have asserted against the switch default). Re-pointed both at `'document-write'`. - tableTool.ts: the `table` description conflicted on prose only, but each side carried facts the other lacked — staging's `'<ref>.stage'` binding form and the `'conversation.stage'` shorthand, ours the `_section` heading rule and `orderBy`'s type-aware sort. Both verified against the merged code. Combining them ran to 2142 bytes, and staging had meanwhile tightened the description budget by reserving 156 bytes for the Armature telemetry hint (2048 -> 1892 effective), so the text is trimmed to 1874: rationale and advice the `tables` skill already covers gave way, every API-contract fact was kept. Verified: @zero/server and @zero/mail typecheck; scope-map (131), document + table (504) and the full mcp suite (389) pass; deps:check and check:brand clean. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 20, 2026, 11:16 PM2 defectshooksMerge origin/staging into feat/inbox-client-composed-feed Three resolutions, two of them semantic rather than textual: - scope-map.ts: staging renamed the `document` family to `document-write` and split it into a per-action switch; this branch had introduced `requiresPlaybookWrite`, which carves subagent docs back out of the playbook escalation. Kept staging's structure and pointed its `write` action at our predicate — the auto-merged surroundings (FAMILIES_WITH_ACTION_OVERRIDES, the `table` case) already referenced both, so this is what the rest of the file expects. - scope-map.test.ts: the subagent carve-out tests merged textually but still called the old `'document'` tool id, so they had stopped exercising the carve-out (one would have asserted against the switch default). Re-pointed both at `'document-write'`. - tableTool.ts: the `table` description conflicted on prose only, but each side carried facts the other lacked — staging's `'<ref>.stage'` binding form and the `'conversation.stage'` shorthand, ours the `_section` heading rule and `orderBy`'s type-aware sort. Both verified against the merged code. Combining them ran to 2142 bytes, and staging had meanwhile tightened the description budget by reserving 156 bytes for the Armature telemetry hint (2048 -> 1892 effective), so the text is trimmed to 1874: rationale and advice the `tables` skill already covers gave way, every API-contract fact was kept. Verified: @zero/server and @zero/mail typecheck; scope-map (131), document + table (504) and the full mcp suite (389) pass; deps:check and check:brand clean. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 20, 2026, 11:16 PM1 defectlibfix(tasks): write the completion on the click, and clean up the draft on every check-mark Two defects on the task-completion path, reported independently by Zach (#cedar-concentrate) and Mihir (#cedar-invoicebutler). **Completion was lost on a reload within 5s.** The server write sat inside a `setTimeout` behind the undo toast, so a reload, tab close or crash in that window took it down with the JS context: `completeTask` never fired, the mask did not survive either, and the row came back on the next fetch with no `completed_at` and no server-side trace the user had ever closed it. The deferral was guarding a side effect that did not exist — `completeTask` writes a status and cancels a KV entry; draft deletion lives on the delete path. The write now goes out on the click. Undo becomes a real reopen (`updateTask status: 'todo'`), which means the server has to restore what completing cancelled: `updateTask` now re-schedules the agent run on a reopen (gated on `shouldScheduleTask`, so a past due date is left alone) and clears `completedAt`. Without that, tick-then-undo silently killed an agent task's execution. The pending-resolution mask is kept, not deleted. The write is immediate but not instantaneous, and a refetch landing mid-flight would still re-hydrate the row — the window shrinks from five seconds to a round trip rather than closing. **Ticking a task off now deletes its Gmail draft, the way deleting it always has.** 9,754 `done` tasks across 79 users still pointed at a live draft. All five check-off surfaces are wired, not just the one with an undo toast: - conversation Overview / timeline / CRM canvas — cleanup deferred behind the undo window, since it is the one irreversible half - task list, kanban (checkbox, `e`, drag-to-Done), execution list — no undo window, so cleanup rides with the status write - agenda checkbox and conversation-inbox row, which call the mutation directly `optimisticCompleteTask` served both check-offs AND next-steps TaskBlock, which closes a task right after SENDING its draft. It now takes a required cause rather than a default, because that ambiguity is what let this get missed. `completeTask` gains an opt-in `cleanupDraft` for the same reason: the send path reaches it via `completeTaskByDraftId`, where the draft is already a sent message. Deleting the draft also clears the draft pointer, keeping the threadId. `deriveAgendaRightSlot` renders "Open draft" off `draftId` alone and already withholds it when there is none, precisely so users do not click and find nothing — a surviving pointer recreated that state, newly visible because done tasks render and can be reopened. Written as a paired axes write (`taskOutput: producedOutput(...)`, stripped to the bare kind rather than nulled) per TASK_CLEANUP_DESIGN.md 1.2. Also: the delete path was missing its `crm.getConversation` invalidation, so the Overview's Due Tasks list could serve a deleted task back once the mask lifted. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 4, 2026, 12:28 AM1 defectslicefix(tasks): fix task-filters test fixture and teammate AOP types in filter menu Add filterTypes to the TaskFilters test fixture and cover the types facet (makeTaskFilter + taskFacetCounts) — the missing required field was breaking tsc and the whole Jest suite (Mail app tests CI failure on #2801). Also fix the Type filter submenu to include teammate-owned AOPs: it only listed the caller's own useAOPs() result, so a task filed against a teammate's deal (hydrateTaskConversations is org-wide) could carry an aopId/aopName that never appeared as a selectable option, even though it was already counted by FACET_DEFS.types.keyOf. The submenu now unions useAOPs() with the distinct aopId/aopName pairs observed on the loaded tasks, deduped by aopId. Requires threading aopId/aopName through HydratedTaskConversation, which the server already returns but the client type didn't declare. Co-Authored-By: Claude Sonnet 5 <<email>> Claude-Session: https://claude.ai/code/session_019jTe7TuDHKYaFL6Ny2Z5h6Sep 16, 2026, 7:20 AMutilsMerge origin/staging into feat/inbox-client-composed-feed Three resolutions, two of them semantic rather than textual: - scope-map.ts: staging renamed the `document` family to `document-write` and split it into a per-action switch; this branch had introduced `requiresPlaybookWrite`, which carves subagent docs back out of the playbook escalation. Kept staging's structure and pointed its `write` action at our predicate — the auto-merged surroundings (FAMILIES_WITH_ACTION_OVERRIDES, the `table` case) already referenced both, so this is what the rest of the file expects. - scope-map.test.ts: the subagent carve-out tests merged textually but still called the old `'document'` tool id, so they had stopped exercising the carve-out (one would have asserted against the switch default). Re-pointed both at `'document-write'`. - tableTool.ts: the `table` description conflicted on prose only, but each side carried facts the other lacked — staging's `'<ref>.stage'` binding form and the `'conversation.stage'` shorthand, ours the `_section` heading rule and `orderBy`'s type-aware sort. Both verified against the merged code. Combining them ran to 2142 bytes, and staging had meanwhile tightened the description budget by reserving 156 bytes for the Armature telemetry hint (2048 -> 1892 effective), so the text is trimmed to 1874: rationale and advice the `tables` skill already covers gave way, every API-contract fact was kept. Verified: @zero/server and @zero/mail typecheck; scope-map (131), document + table (504) and the full mcp suite (389) pass; deps:check and check:brand clean. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 20, 2026, 11:16 PMCURATED_AGENDA_DESIGN.mdMerge remote-tracking branch 'origin/staging' into feat/slack-style-reactions # Conflicts: # apps/mail/modules/cedar-os/src/store/agentConnection/responseProcessors/clientExecutionResponseProcessors.ts # apps/server/.claude/skills/documents/agenda-format.md # apps/server/.claude/skills/tasks/SKILL.md # apps/server/src/mastra/tools/draft-comms/__tests__/saveDraftTool.test.tsAug 24, 2026, 3:57 PMindex.tsrefactor(tasks): delete listRecentPostMeetingTasks — dead, and wrong by 4x Nothing called it. The hook was exported from the userTasks barrel and invoked from no component; the tRPC procedure had no other consumer. It was also already wrong, which is why fixing it was never worth it. The query asked "which tasks came out of a meeting run?" by reading `task_type = 'post-meeting'` — a category the AGENT chooses, not a record of what triggered the run. Measured against creation_run_id -> agent_executions.event_type: labelled post-meeting 5,942 actually from a meeting 1,570 (26%) not from a meeting 4,372 (74%) The 4,372 are mostly automation runs with no execution row, plus 132 triggered by an EMAIL. Provenance and category are different facts and they disagree three times out of four. So this was not a reader to migrate off task_type (TASK_CLEANUP_DESIGN.md phase 6c left it deliberately, pending a creation_run_id join). With no consumer, the join is not worth writing — delete instead. Anything needing "came out of the meeting run" should join creation_run_id to the triggering execution's event_type, not resurrect this. Also drops dead SQL it carried: ne(status,'deleted') and ne(status, 'agent_deleted') sat beside eq(status,'todo'), which already excludes both. Co-Authored-By: Claude Opus 5 <<email>>Aug 15, 2026, 11:16 PMTASK_ATTRIBUTES_DESIGN.mdfeat: task execution mode — list view, context-driven empty chat, output colour Adds the task-execution surface (TaskOutputPanel, per-task chat threads, tasks as a context item), the flat List layout with a Linear-style toolbar (view switcher + grouping/filters), context-driven empty-chat surfaces (task/thread ThreadContextCards), an --output green token, and thinner default/sm button heights to match the composer Send. Co-Authored-By: Claude Opus 4.8 (1M context) <<email>>Jul 26, 2026, 10:45 AMTASK_AXES_DESIGN.mdremove: unused task-group hard-pin feature (zero production usage) task_group_conversations let a user pin a CRM conversation to a task group, bypassing the AI router. A direct production query found zero rows in this table, ever, for any user, despite it being a shipped, reachable UI feature. Removes the schema, tRPC procedures, UI, router step, CLI verbs, and headless smoke-test coverage, and ships a DROP TABLE migration to run through the normal deploy pipeline. Co-Authored-By: Claude Sonnet 5 <<email>> Claude-Session: https://claude.ai/code/session_019jTe7TuDHKYaFL6Ny2Z5h6Sep 20, 2026, 1:04 PMTASK_CLEANUP_DESIGN.mdrefactor(tasks): move task_type readers onto group and output axes (design: task-cleanup phase 6c) The grep overstated this by ~7x. `task_type` appears in ~102 files, but that conflates a TOOL PARAMETER (`inputData.taskType` — the agent telling a drafting tool "this is a post-meeting recap") with the COLUMN. The true column readers are ~15 sites in 5 files, and they are overwhelmingly filters and groupBy rather than behavioural branches. admin-at-a-glance.ts already read COALESCE(taskGroups.name, userTasks.taskType) — half-migrated and unnoticed. Split along the four jobs the column was doing: - OUTPUT CONTRACT — crm.ts's activity feed reads task_output->>'kind'. - FILING — admin-at-a-glance's five groupBys, admin.ts's drill-down, and the list-tasks filter now key off task_group_id / the group name, so admin lanes are the user's real renameable groups instead of a fixed nine-value enum. - RECAP CATEGORISATION — categorizeTask(taskOutput, taskGroupName); output first, lane second. Applied to both duplicated copies, which would otherwise disagree between the recap email and the admin viewer. - PROVENANCE — listRecentPostMeetingTasks is left alone. "Came out of the meeting run" is not modelled by either axis: the lane is user-renameable and already split in two for some accounts, the output is an ordinary email. The honest replacement joins creation_run_id to the triggering execution's event type, which is a change of meaning rather than of column. list-tasks' `taskType` param becomes two — `groups` (lane names) and `outputKinds` (artifacts), ANDed. They are not interchangeable: a task can sit in the Follow-ups lane and produce a Slack message. Two latent bugs surfaced: - admin.ts never selected ut.task_output while calling categorizeTask, which already preferred the output axis — the admin recap viewer was categorising on a value it was not fetching. It gains the crm-opportunity / field-approval / calendar buckets it never had. - SKILL.md still instructed agents to pass `taskType` to list-tasks. Unknown params are silently stripped, so every "just show me follow-ups" would have returned everything — a filter that fails open with no error to notice. Co-Authored-By: Claude Opus 5 <<email>>Aug 15, 2026, 10:41 PMTASK_EXECUTION_MODE_DESIGN.mdfeat: task execution mode — list view, context-driven empty chat, output colour Adds the task-execution surface (TaskOutputPanel, per-task chat threads, tasks as a context item), the flat List layout with a Linear-style toolbar (view switcher + grouping/filters), context-driven empty-chat surfaces (task/thread ThreadContextCards), an --output green token, and thinner default/sm button heights to match the composer Send. Co-Authored-By: Claude Opus 4.8 (1M context) <<email>>Jul 26, 2026, 10:45 AMTASK_GROUPS_DESIGN.mdremove: unused task-group hard-pin feature (zero production usage) task_group_conversations let a user pin a CRM conversation to a task group, bypassing the AI router. A direct production query found zero rows in this table, ever, for any user, despite it being a shipped, reachable UI feature. Removes the schema, tRPC procedures, UI, router step, CLI verbs, and headless smoke-test coverage, and ships a DROP TABLE migration to run through the normal deploy pipeline. Co-Authored-By: Claude Sonnet 5 <<email>> Claude-Session: https://claude.ai/code/session_019jTe7TuDHKYaFL6Ny2Z5h6Sep 20, 2026, 1:04 PMTASK_KANBAN_DESIGN.mdfeat(tasks): drag a card to reorder it, and drop one on Upcoming to reschedule it Phases 3-6 of TASK_REORDERING_DESIGN.md. Phases 1-2 shipped the sort_order column, its placement triggers and a Manual ordering mode; none of it was reachable, because the cards were plain draggables and Upcoming rejected every drop. - Cards are sortable, so one can land BETWEEN two others rather than only onto a column. A drop midpoints its two new neighbours into a single sort_order write. - Upcoming accepts drops. It writes nothing and asks for a date, because "upcoming" means "due later" and there is no later until the user says which. Dismissing needs no rollback: the column is derived from dueDate, which never changed. Dragging OUT of Upcoming sets dueDate to now alongside the re-file, or the card snaps back. - Switching to Manual re-stamps every open task from the ordering you were just in and clears every pin (userTasks.restampSortOrder), so Manual opens as exactly the board you were looking at rather than whatever was last dragged. - What a drop MEANS is a pure function (task-drop-plan.ts) rather than logic inside the drag handler. One gesture means one of four things depending on where it started, where it landed and the ordering mode; leaving that matrix in the handler put every branch behind a real mouse. - moveTaskToGroup carries the whole gesture in one write, and only relabels the Gmail thread when the lane actually changed - same-column reorders now reach it too. - Equal sort_orders tiebreak by due date then id, in the browser and in the CLI projection alike, so a placement collision is harmless rather than dependent on wire order. groups board sorts by sortOrder and marks pinned cards. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 10:29 PMTASK_OPTIMISTIC_RENDERING_DESIGN.mdfeat: in-flight work across layout, kanban, and account deletion Carries the working tree as it stood: the /agent home hero and the rail behind it, back/up navigation through LayoutUrlSync and useBackOrUp, the kanban conversation canvas, a list-field chip renderer for CRM cells, and further account-deletion purge coverage. Committed as one unit at the user's request so it travels with the branch's PR rather than sitting uncommitted. Boundaries between the pieces are the individual files, not this commit. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 2, 2026, 10:29 PMTASK_REORDERING_DESIGN.mdfix(tasks): one answer about where a dragged card lands Two systems were arranging the lane and only one was asked about the write. The board renders the card at the slot the pointer resolved and plans the drop off that same list; every lane ALSO ran verticalListSortingStrategy, which displaces by activeIndex vs overIndex — indices into the list the board has already rearranged, against an `over` from dnd-kit's own collision detection. Crossing a card's midpoint moved the board's gap one way and dnd-kit's displacement the other, so the card settled into a slot the drop was never going to write and snapped back on release. Lanes now displace nothing; the parting is the board's DOM order alone. `arrangeForDrag` is the single arrangement both the render and the drop derive from, and the drop reads it from refs the pointer writes synchronously rather than from state a throttled render may not have committed. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 31, 2026, 2:28 PMTASK_REORDERING_HANDOFF.mddocs(tasks): record phase 7 as applied and verified (design: task-reordering phase 7) The placement SQL is live and the repair has run. Evidence, all from the headless drivers built in the previous commit: - stress, same command and account: 13 collisions before the SQL, 0 after (25 concurrent inserts, 25 distinct sort_orders) - repair --mode reseed --apply: 28 users, 4461 rows, 0 skipped - audit: 96 users, 9268 open tasks, 0 collision groups, 0 inversions - re-entry branch, proved live inside a rolled-back transaction: a card pinned at 9999 holds through a due-date change, and done -> todo returns it to sort_order 1 with the pin cleared The handoff now carries what is genuinely left, none of it this design's scope: there is still no headless way to apply a function or trigger change (this is the second written-but-unapplied migration recorded in that file), listUserTasks caps at 500 rows ordered by DATE so manual mode can drop placed cards past that, and five `as HydratedUserTask` casts are safe only while a select stays `*`. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 10:49 PMTASK_SLICE_REFACTOR_DESIGN.mdfeat: task board renders solely from Zustand (design: task-slice-refactor) Render the kanban board + groups accordion from userTasksSlice; listUserTasks flows INTO the slice via hydrateTodoTasks (upsert + reconcile removals) and is never rendered from directly — mirroring the mail thread/conversation model. Optimistic complete/snooze/delete mutate the slice so cards leave/move instantly; cancelQueries before each server call kills the stale-refetch resurrection race. Adds x / shift+x task selection with a persisted anchor. Verified: jest tests/modules/userTasks/userTasksSlice.test.ts 12/12 green; production files type-clean under tsc --noEmit. NOTE: the shared files (TaskKanbanBoard, TaskGroupsAccordion, TaskKanbanCard, use-optimistic-task-actions, config/shortcuts, use-hydrate-tasks-slice) also carried concurrent "Task Execution Mode" WIP in the working tree that could not be separated from this change within the same files, so it is included here. Co-Authored-By: Claude Opus 4.8 <<email>>Jul 24, 2026, 9:41 PM