AnalysisReviewMode.tsxfeat: refactorApr 23, 2026, 5:49 PMCanvasMetricsBar.tsxfix: fall back to REST when the TCP pool is capped, and scope aggregates to pinned ids Three fixes from the review of #2496. The listener path decided on `streamTransportEnabled()` alone, but a connection is only available while the process is under MAX_BLOCKING_TCP_CLIENTS. Past the cap `createBlockingTcpClient` returns null and `listenStreamFrames` ends having yielded nothing — a clean SSE close, which the browser reads as a finished, empty reply. The caller now claims the connection itself and falls back to the polling tail when it can't, which is what the generator's own contract always assumed. `tcp_capped` on the transport log separates "TCP is off" from "TCP is on but saturated". A canvas pinned to an explicit id set passes those ids to the row query but not to the aggregate, so the metrics bar described the whole unpinned filter set while the table below it showed the pinned rows — the exact drift the hook exists to prevent. `useCanvasAggregates` now takes the same pinned list. Also drops the `_`-prefixed throwaway bindings used to strip the paging keys; the repo rule is to remove unused bindings, not rename them. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 15, 2026, 2:11 PMCanvasReviewMode.tsxfeat: UI reorganization, score centering, and lint fixes Co-Authored-By: Claude Sonnet 4.6 (1M context) <<email>>Apr 24, 2026, 11:22 AMColumnConfigurationItem.tsxfix(mail): stop a list load selecting a deal, and rename the Status label to Stage A fresh /agent chat came up bound to whatever deal sorted first. setCurrentConversationList auto-selected conversations[0] into activeConversationId, which on the chat surfaces is not a row highlight but the CHAT'S CONTEXT — useRouteChatThread writes it onto the thread's selectedArtifact and the attach effect commits it as primaryConversation. So the Top Deals tile finishing its load put a deal nobody opened into a brand-new chat's chip row and into what the agent was told. Loading a list now selects nothing; j/k is unaffected (focusedIndex starts null, so the first press still resolves to row 0 — and no longer skips it). CardListCanvasView also claimed the global currentConversationList and read it back to render. useCanvasConversations gains syncCurrentList and returns the ordered list, so a tile that merely displays deals no longer repoints the app's shared selection state. That leaves nothing writing that list on /agent. Rename the deal status field's LABEL to "Stage" everywhere it shows (the filter builder, table header, group-by, kanban, profile, onboarding preview). Reported on Vooma's onboarding: "these are stages, not statuses — status is like open and closed." Column id stays `status`; statusOverview keeps its name, being a prose summary rather than the stage enum. Add mod+N for a new chat, sharing one implementation with the chat header's button so the two cannot drift, plus a row in the hotkey bar. The desktop File menu had CmdOrCtrl+N on Compose, which the main process consumes before the page sees it — moved to CmdOrCtrl+Shift+N. A plain browser tab reserves the key and will never dispatch it. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 17, 2026, 11:06 PMConversationCanvas.tsxrefactor(crm): use absolute imports for the new canvas modules CLAUDE.md requires absolute imports; the canvas-metrics work introduced six relative ones, which is what Greptile flagged. Only the specifiers this PR added are rewritten — pre-existing relative imports in these files are left alone so the diff stays about the finding. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 15, 2026, 5:32 PMConversationItem.tsxperf(crm): stop shipping list rows what they cannot render Follows the event projection with the other two thirds of the payload. On the same 50-row page: 2,809 KB -> 1,173 KB. userTasks was 58% of it. The list had no status filter at all, so every task ever attached to a conversation went over the wire — 650 deleted, 329 agent_deleted and 268 done against 59 todo on one page; 341 renderable out of 6,935 org-wide. Every list surface filters to todo before drawing (the tasks cell, the current/future action columns, the kanban card), so the rest was never rendered. Filtering to todo + recommended takes it 1,621 KB -> 175 KB. The blobs on a task are small (~350 bytes of task_output on a todo task), so this is deliberately a row filter and not a narrower field projection: the execution ids and chat_thread_id / task_group_id drive execution mode when a task is opened from a card, and trimming them would risk that for a few KB. customFields was 522 KB. Two things, neither of them the values themselves: 54% of entries carry no value at all (4,028 of 7,506), and an absent entry renders exactly what an empty one does — every reader is `field?.value || null` — so they are skipped unless they still have a signal dot to draw. And 31% of the bytes were envelope (id, conversationId, agentExecutionId, editedBy, lastEdited, createdAt) that no list surface reads; every lookup goes through the field name. updatedAt stays, because getCustomFieldsRevision keys the store's change detection on it. 522 KB -> 332 KB, now 84% real user content. Also deletes `siblingTasks`, which existed to carry the full task list to TimelineTaskItem — where it was declared as a prop and never destructured. Five call sites were threading a conversation's entire task history into a component that ignored it. Left alone deliberately: primaryCompany (75 KB across ~50 enrichment columns — trimming it needs a store guard so a list refetch cannot blank the panel's company card, which is a poor trade for 6%), and the event cap, which is a product dial rather than waste. Co-Authored-By: Claude Opus 5 <<email>>Sep 2, 2026, 11:42 PMCreateConversationDialog.tsxfix(mail): stop a list load selecting a deal, and rename the Status label to Stage A fresh /agent chat came up bound to whatever deal sorted first. setCurrentConversationList auto-selected conversations[0] into activeConversationId, which on the chat surfaces is not a row highlight but the CHAT'S CONTEXT — useRouteChatThread writes it onto the thread's selectedArtifact and the attach effect commits it as primaryConversation. So the Top Deals tile finishing its load put a deal nobody opened into a brand-new chat's chip row and into what the agent was told. Loading a list now selects nothing; j/k is unaffected (focusedIndex starts null, so the first press still resolves to row 0 — and no longer skips it). CardListCanvasView also claimed the global currentConversationList and read it back to render. useCanvasConversations gains syncCurrentList and returns the ordered list, so a tile that merely displays deals no longer repoints the app's shared selection state. That leaves nothing writing that list on /agent. Rename the deal status field's LABEL to "Stage" everywhere it shows (the filter builder, table header, group-by, kanban, profile, onboarding preview). Reported on Vooma's onboarding: "these are stages, not statuses — status is like open and closed." Column id stays `status`; statusOverview keeps its name, being a prose summary rather than the stage enum. Add mod+N for a new chat, sharing one implementation with the chat header's button so the two cannot drift, plus a row in the hotkey bar. The desktop File menu had CmdOrCtrl+N on Compose, which the main process consumes before the page sees it — moved to CmdOrCtrl+Shift+N. A plain browser tab reserves the key and will never dispatch it. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 17, 2026, 11:06 PMCumulativeFieldsSection.tsxfix: collapse the Cumulative tile picker behind a toggle Seven always-visible checkboxes pushed the presets — the part of the Fields popover people actually reach for — below the fold, to pay for a decision that gets made about once per canvas. It is now a collapsed row that expands on click, with a count badge so the current state is still readable without opening it. Co-Authored-By: Claude Opus 5 <<email>>Aug 14, 2026, 8:23 PMDraftReviewPill.tsxcapitalize review drafts for draft review pillFeb 19, 2026, 3:29 PMfilter-sort-adapters.tsfeat(mail): build an inbox's CRM rule with the real conversation filter UI (design: pipeline-inbox-crm-stage-filter phase 5) Adds a "Linked deals" section to the inbox editor: pick AOP(s), then filter on that AOP's own stages and custom fields. The column set is derived from the selection, so Customer Success offers its stages and Deals offers discovery → closed won, with no hard-coded enum anywhere. This reuses FilterSortPopoverContent rather than growing a second filter UI. The new `{ kind: 'inbox' }` scope is draft-local — the inbox does not exist yet while the form is open — so it carries its own config + setter instead of a store slice. Sharp edge found while wiring it: FilterSortPopoverContent only uses the adapter for add/remove/clear. The six leaf column popovers resolve their write target from a `canvasId` prop, falling back to the GLOBAL CRM slice when absent — so every click in the inbox builder would have silently re-filtered the user's /crm page and never touched the draft. They now consult a ColumnFilterOverride context first; a test asserts the value lands on the draft AND that the global slice stays untouched. The pre-existing 'sidebar' scope has the same latent bug (noted, not changed here). Also seeds docs/wiki entries for the mail read path and this subsystem, and comments the deleted_at sharp edge at all three SQL builders. Co-Authored-By: Claude Opus 5 <<email>>Aug 4, 2026, 7:28 PMfilter-sort-scope.tsfeat(mail): build an inbox's CRM rule with the real conversation filter UI (design: pipeline-inbox-crm-stage-filter phase 5) Adds a "Linked deals" section to the inbox editor: pick AOP(s), then filter on that AOP's own stages and custom fields. The column set is derived from the selection, so Customer Success offers its stages and Deals offers discovery → closed won, with no hard-coded enum anywhere. This reuses FilterSortPopoverContent rather than growing a second filter UI. The new `{ kind: 'inbox' }` scope is draft-local — the inbox does not exist yet while the form is open — so it carries its own config + setter instead of a store slice. Sharp edge found while wiring it: FilterSortPopoverContent only uses the adapter for add/remove/clear. The six leaf column popovers resolve their write target from a `canvasId` prop, falling back to the GLOBAL CRM slice when absent — so every click in the inbox builder would have silently re-filtered the user's /crm page and never touched the draft. They now consult a ColumnFilterOverride context first; a test asserts the value lands on the draft AND that the global slice stays untouched. The pre-existing 'sidebar' scope has the same latent bug (noted, not changed here). Also seeds docs/wiki entries for the mail read path and this subsystem, and comments the deleted_at sharp edge at all three SQL builders. Co-Authored-By: Claude Opus 5 <<email>>Aug 4, 2026, 7:28 PMFilterSortConfigurationRow.tsxfix(search): "All Org" was quietly searching only your own deals The Cmd+K default moved from "me" to org-wide in the same change that stopped hiding non-shared AOPs. That exposed a gap in the org-wide expansion: it widens an AOP filter by its org-level parent, and an AOP whose `org_aop_id` was never set expands to nothing and falls back to the requester's own id. The pill said "All Org"; the query said "just mine". Four of the six Deals AOPs on our own org are unlinked, and 48 of 236 across prod. Match those by name within the org instead, and union the requested ids back in so an expansion can never come back narrower than what was asked for. The remembered deal type had the same shape of problem from the other side: `selectedAopId` is persisted and goes straight into `aopIds`, but the pill reads its label off the AOP list. Once that AOP is deleted the label falls back to "All types" while the search keeps filtering on a dead id — nothing on screen, no results, forever. Reconcile it against the list once the list has answered. And picking the default back is not a choice worth storing: it is what "Clear filters" means, so it returns the facet to unset rather than pinning it. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 11:13 PMGroupedConversationCanvas.tsxfix(types): close 205 typecheck errors by repairing real client/server drift apps/mail typechecked against a stale apps/server/dist d.ts (the server's package.json points "types" at dist), so a chunk of the reported drift was phantom. Rebuilt it and fixed the four script errors that blocked `tsc -b` in server, which is what regenerates it. The rest was genuine contract drift, fixed at the source rather than cast away — routes that dropped fields their callers still read (agentExecutions, getUpcomingCalendarEvents' conversationId, createCanvas' description), schema splits the UI never followed (conversationUsers vs conversationContacts), and enums that grew server-side but not client-side (TASK_TYPES, ActionStatus, DateFilterOperator). Also deletes code that was already dead: an unreferenced sort popover whose store API is gone, a panel importing a deleted module, the system-skill metadata UI whose mutation was removed deliberately, and a test asserting a store method that no longer exists. apps/server: 4 -> 0. apps/mail: 228 -> 23, all remaining errors being dependency resolution (zod v3/v4, react-router dev/runtime skew, tiptap v2 via novel) rather than code. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 2, 2026, 7:45 PMindex.tsfeat(crm): split filters and sorting into separate popovers, sort from a header wins Filtering and sorting were one popover with one horizontal row of badges, where badge order silently encoded sort priority. Order now only means something in the sort list, which is the only list you can drag. Sorting a column from its header takes priority 0 and pushes the rest down, rather than landing last where it has no visible effect. Shared by all seven column popovers plus toggleColumnSort, so every entry point behaves the same. Column popovers opened from a list are scoped to that list's concern and anchor beside it like a nested menu; a "Set filter" / "Set sort" button hands the column to the other list. Enum fields keep one list for both — drag is the sort order, click is the filter — so their affordances split by mode instead. Drops ~750 lines of dead code (FilterSortBadgeRow, the deprecated inline row, OwnerUserFilter). The drag-reorder test now runs against the real function instead of a hand-copied duplicate. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 2, 2026, 4:29 PMNewConversationCanvas.tsxfix(tests): unbreak 153 server suites and repair the CRM mock's positional inserts `email-processor.ts` moved to a default import of `@barkleapp/css-sanitizer` to fix tsx scripts, but Vite leaves `default` undefined for a CJS module with no `__esModule` marker — so every suite whose import graph reaches google.ts died at collection. A namespace import with a `.default` fallback is the one shape that works under tsx and vitest both. `pnpm types:test` — a task this branch adds — was red on its own new test file and on `result.company` assertions the nullable-company widening invalidated. Also repairs failures this branch inherited rather than caused: - The CRM mock DB pinned an exact POSITIONAL insert sequence, which the crm_company / crm_account_domains / crm_person dual-write shifted by three. Replaced with a shared `mockInsertsByTable()` so insert-order changes can no longer mis-wire it silently. - `sanitizeContext` deliberately keeps `message`/`data` for failed-send debugging, and `get()` deliberately emits label IDs without a per-thread labels.list call. Both tests still asserted the pre-change contract. - email-composer-send timed out on all 42 tests: `flushPromises` scheduled on a FAKED setTimeout, react-query mocks returning fresh identities each render, four missing store selectors, and a `useForm` override leaking `to: []` into every test after it. Account deletion: order the blocking-FK set by dependency like the unreferenced set already was — inside a transaction a violation aborts everything, so the retry loop cannot rescue it — and classify blocking columns instead of assuming 'delete'. NO ACTION is Postgres's default, so the next attribution column added with a plain `references(user.id)` would have deleted rows belonging to whoever the record is actually about. pnpm types, types:test, deps:check, 166/166 mail suites and 461 server suites (5124 tests) all green. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 2, 2026, 11:29 PMPresetNavigationBar.tsxrefactor: field signal replaces strength/completeness (design: strategic-overview phase 1) Add CustomFieldSignalConfig + linkedDocPath to CustomFieldDefinition, a strategicOverview DisplayConfig layout, and a shared SignalDot. Field values now carry a red/yellow/green signal + signalReasoning in place of the two numeric scores, across server (schema, field-values service, update tool, crm routes, context formatting) and the mail CRM canvas. Co-Authored-By: Claude Opus 4.8 (1M context) <<email>>Jul 12, 2026, 3:05 PMReviewModeBottomBar.tsxfeat: refactorApr 23, 2026, 5:49 PMSexyAnalysisReviewPill.tsxclear button ui improvsFeb 24, 2026, 2:27 PMtoolbar-pill.tsfix(search): the Cmd+K filters were three different controls in three different places The deal type was a tinted badge on the input's right edge, the mode and owner were chips stacked above it, and the filter button opened a fourth thing. They are now one row of one badge under the input, and the menus are the tasks Filter menu — a submenu per facet, checkable rows, all of the user's AOPs rather than the shared ones. Owner scope and deal type persist across opens and reloads, defaulting to org-wide, so the bar stops forgetting how you set it up. The menus were non-modal, which Radix dismisses on focus-outside — the dialog's own focus trap closed them the instant they mounted, so the pills looked dead. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 10:48 PM