CanvasKanbanCard.tsxperf(crm): give conversation rows their own event projection
A row draws a dot timeline and three "last <type> at" columns, and nothing
else — but crm.listConversations was building up to seven nested sub-objects
per event through eight LEFT JOINs to feed it. Rows now get five scalars and a
flat threadId, ~3x smaller per event, so the cap can move 15 -> 60 and draw
three times the history for no more bytes: measured on a 50-row page, 547KB
over 641 events before, 538KB over 1,919 now.
That cap was also quietly wrong. "Last meeting" was derived by scanning
whatever events the list happened to ship, so a deal whose last meeting fell
outside its 15 most recent rendered blank — 53 of the 304 conversations with a
meeting, in the org this was measured on — while SORTING by the same field used
an unbounded MAX(occurred_at) and placed it somewhere the column contradicted.
The three columns now read scalars computed over the whole history, from the
same expressions the ORDER BY builder already used.
Splitting the projections needed guards the single shared type had been hiding:
- setConversations takes an explicit `projection`. Both queries write one store
key and replace wholesale, so a list refetch would otherwise empty the
timeline of whatever deal was open — the same bug that previously just shrank
it to 15 (client-data-architecture.md §1.2).
- getEventType/getEventTitle read `eventType` rather than sniffing which
sub-object came back from a join, which equated "this join returned nothing"
with "this is a note" and would have greyed out every dot.
- a dot click opens EventDetailDialog on an id and fetches the full event,
since the row no longer carries one.
Also carries the in-flight pending-conversation-field-writes mask and its
tests: it edits the same setConversations body, so it cannot be split out.
Co-Authored-By: Claude Opus 5 <<email>>Sep 2, 2026, 11:01 PMCanvasKanbanColumn.tsxfeat: 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 PMKanbanConfigPopover.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 PMKanbanConversationCanvas.tsxfeat: 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 PM