CanvasHeader.tsxfix(pipeline,inbox): filters that survive a refresh, and a lost deal that leaves the board Three reported bugs, two of them the same mechanism seen from different ends. A rejected viewConfig save was invisible. Every filter control fires `void saveCanvasViewConfig(id)`, so a rejection had no observer at all: no toast, no retry, and a local config the user believed was stored. `dirtyCanvasIds` kept the edit on screen — but that Set was never persisted while `canvasesById` was, so a reload came back with the change still in localStorage and nothing marking it unsaved. The server's pre-save row then won rehydration, which is why this read as "my pipeline filters don't survive a refresh" rather than as a write that failed. The save now reports failure and resolves false instead of rejecting, so all ~20 `void` call sites are covered without touching them; the dirty set persists, so the guard already in upsertCanvas holds across the reload it was being thrown away by; and CanvasHeader stops flashing "Saved" over a failure. Saves are also chained per canvas. Working through a filter popover fired several updateCanvas mutations at one row concurrently, so the last RESPONSE to land, not the last edit made, decided what the server kept. Closed Lost deals stayed in the pipeline because status is filtered SERVER-side: a page's membership is fixed when it is fetched, and marking a deal Closed Lost writes to the store, never to that page. The status/priority change now invalidates listConversations — it was the only successful-update path that never did — and the fetched rows are re-checked against the optimistic value, so the row leaves in the same frame as the click and the refetch merely agrees. That check is one shared predicate now; the kanban had its own copy, which is why two views of one pipeline disagreed about what a stage filter admits. Escape clears the inbox filters, in a second stage. Selection first, filters only once nothing is selected — a single stage would throw a filter set away on every ordinary deselect. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 29, 2026, 4:06 PMCanvasRenderer.tsxfeat(inbox): unibox keyboard parity, undo-send that puts the draft back, sharper chat context Unibox now navigates like the email list — j/k, Enter, Escape all walk whichever list is on screen via a new `activeListSource` on the thread slice, and Escape clears the full selection (focused row, bulk checkboxes, ambient conversation) instead of only the keyboard cursor. Undo send stops round-tripping through localStorage and a URL rewrite: the send records where the draft came from and undo reopens that surface, restoring the un-quoted body onto the draft row (recreating it when the send removed it). Chat context: an auto-committed primary conversation is provisional while the chat is empty and drops when you move on, the context cards follow the deal you're actually on, threads with no conversation get a link prompt, and the conversation surface shows a keyboard legend rather than restating cards already on screen. Also: agenda Trash removes the row outright, Next steps renders as one card with its booked calls beneath, and channel chats focus the composer on open.Jul 30, 2026, 11:54 PMCardListCanvasView.tsxfix(calendar,ui): a Zoom connect dialog, and four things the review found The Zoom work lands its last piece: picking Zoom on an unconnected account used to open a bare browser window with no explanation and no way back. ZoomConnectDialog owns everything around the consent screen — why Cedar is asking, the wait, and the four different reasons a popup ends without a connection — and resolves back to the composer so one click on "Change to Zoom" still ends with Zoom on the event. The OAuth callback URL now falls back to the server's own origin, so an unset env var is no longer the difference between a working integration and a message telling a user to edit a file they have never heard of. Agent avatars move to components/icons/agent-avatar.tsx as one component, and the conversation/agent reading column narrows from 100ch to 80ch across all four places that hold the measure. Review fixes: - Breadcrumb decided "you are here" positionally, so it rendered the last crumb as an inert span and silently DISCARDED any href it was handed. A task's bar is `Tasks / <lane>` above the task's own title — the h1 below is the page, and the lane is somewhere to go — so its link had become dead markup. The current crumb is now the last one with nowhere to go, which every other call site already is. - modifyCalendarEventTool passed a start time but no duration to Zoom, which silently books 60 minutes: an AI-updated 25-minute event held an hour on the host's own account. - schema.ts declared two_factor_required without .notNull(), drifting from the NOT NULL the migration actually applied. - HomeAgentsWidget cast a tRPC result that was already typed — the redundant-cast shape that took down the home rail once before. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 11:54 PMConversationCanvasView.tsxfix: lastContacted popover + filters working with individual canvasesFeb 27, 2026, 3:38 AMConversationCard.tsxfeat(tasks): one plain-text task line everywhere, no cards, no agenda groups A task's description is now two parts — `<headline> — <detail>` — and every surface paints the same split: headline bold, deal as an inline badge, detail muted. `parseTaskLine` derives it from the string, so descriptions that already carried a dash render in the new shape with no migration. The agenda's conversation card is gone (rows are plain text; the deal is a badge in the sentence, deferring to an `@[id]` chip the row already carries), and so are its task-group sections: grouping and ordering belong to the daily-agenda agent alone. Legacy `taskGroupSection` wrappers are unwrapped on read — deep copied by hand, since `Y.XmlElement.clone()` drops non-string attrs and would have unticked every completed row. The agents write only the description now: no `reason`, no `flags`, neither of which rendered anywhere. The digest email reads the detail half instead. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 26, 2026, 8:31 PMKanbanConversationView.tsxfeat: v0 kanban viewMar 29, 2026, 4:19 PMReportCanvasView.tsxfix(meetings): a Circleback recording that plays, on open, with a link that goes to Circleback Three things stood between a Circleback meeting and its recording. The recording could not be fetched at all. credentials-encryption derives its AES key from ORG_CREDENTIALS_ENCRYPTION_KEY || BETTER_AUTH_SECRET, so which secret a credential was written under is a property of the environment at WRITE time. Every Circleback MCP token set was written under BETTER_AUTH_SECRET; once the org key was set, every decrypt died with "Cipher job failed", the call sites swallowed it into "no credentials", and the player said "No playable recording for this meeting" with the real cause visible nowhere. Decryption now tries each configured secret; encryption still writes with the first. The meeting dialog waited for a press before fetching. That poster exists so a coaching document with ten moments does not fire ten mints on open against a Fathom budget of 30/min — an argument that does not apply to one recording the reader opened to watch. `autoLoad` skips the press without autoplaying. "Open in Circleback" went to storage.googleapis.com. What ingest stored is a signed GCS object whose signature dies 24h later; unsigned it answers 403 forever. The page is built from the meeting id instead, and no surface will link to a raw media file any more. Webhook-era rows (numeric external_id, all before 2026-07-29) still cannot mint or link — neither ReadMeetings nor the page route takes a numeric id. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 5, 2026, 12:21 PMStatisticsCanvasView.tsxfeat(forms): the settings row layout, and a select that admits it opens Every field is one row now — icon and name left, the sentence under it, the control right — because a form with eight fields laid out label-over-control gives the eye nothing to run down. Fields read the layout from context (`FieldRows`), so a form declares it once instead of per field. The select rendered no chevron at all, and no call site had noticed: it was a bordered box with a word in it. It now owns its chevron, comes in two sizes instead of the four that had accumulated across 62 call sites, and has a `ghost` variant for the ones that sit inside a sentence. Two bugs found by reading computed styles rather than screenshots: FormControl is a Slot that hardcoded `bg-popover`, so it was overriding the background of every control in every react-hook-form in the app; and the menu's `shadow-[...]` compiled to three fully transparent layers. Geometry is measured off Linear's settings surface; the colours are Cedar's own tokens, since our light theme is warm and theirs is grey. Co-Authored-By: Claude Opus 5 (1M context) <<email>> Claude-Session: https://claude.ai/code/session_01DFkAfWRuLsdTkpxrFhTKYWAug 30, 2026, 11:37 PMTableCanvasView.tsxfeat: new leftSidebar homeviewApr 6, 2026, 11:06 PM