index.tsfix(mail): clear 309 typecheck errors and 5 live ReferenceErrors
apps/mail 792 -> 483. Jest stays green at 111 suites / 1071 tests.
The single biggest cluster was one missing line: tsconfig pins `types` to
["node","vite/client","jest"] and jest.setup.js is a .js file outside `include`, so the
@testing-library/jest-dom matcher augmentation never entered the program — 170 errors from
`expect(...).toBeInTheDocument()` and friends. A 4-line jest-dom.d.ts clears all of them.
Stripping @jest/globals from 22 test files was a prerequisite, not a tidy-up: only the
globally-injected `expect` picks up that augmentation. The other 145 test files already
used injected globals, so this also converges on the dominant convention. Do not install
@jest/globals.
The rest: CRM store test fixtures still extending state that moved to conversationsSlice in
Dec 2025 and importing a deleted CRMTypes module; barrel files re-exporting modules that no
longer exist (each one an import-time throw); a locally-declared SelectorItem whose module
was deleted; sanitizeJson taking `object` when its body explicitly handles primitives and
both callers already cast.
Five of these were live runtime bugs, not type noise:
- setForcePropagate(false) in the AOP-save onSuccess threw on EVERY successful playbook
save (cedarAdmin/page.tsx, admin/page.tsx)
- backendResponsiveness referenced in a useMemo dependency array, evaluated every render,
while the memo body no longer mentions it
- "Force re-sync" called handleForceSync() with no argument against a z.object() input
that rejects undefined, so it always failed validation
- an empty-string icon key produced `Icon === ''`, i.e. <'' />
- showOverlay leaked an unknown attribute onto a Radix DOM node
Worth knowing separately: the 792 were invisible. Plain tsc reported 3, because a syntax
error elsewhere in the tree makes TypeScript skip ALL semantic checking. One stray comma
blinds the entire typecheck.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 2, 2026, 2:11 PMStageBatchApprovalCard.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 PMTaskEmailItem.tsxrefactor(tasks): retire task_channel onto task_output.kind (design: task-cleanup phase 5)
`multi-action` was never a channel. It meant "this task has several possible
actions", which is not a place a message is sent — and TASK_OUTPUT_KINDS omits
it deliberately for that reason. Only two sites wrote it, both CRM approvals
that already pass an explicit task_output, so the value was legacy filler.
Removing it forced the question the column had been dodging: task_channel was
NOT NULL DEFAULT 'email', so those rows had to become either a lie or nothing. A
CRM approval sends nothing. Migration 0059 makes the column nullable, drops the
default, narrows the CHECK to real channels, and nulls the 7,200 multi-action
rows. Relaxing the constraint is itself a step toward the drop — constraints
come off before columns do, so a newer server can stop writing it first.
Moving the readers is NOT a rename. The two axes disagree on ~12,000 rows and
the output axis is the correct one, so each site is a decision: does it want the
declared channel, or what the task actually produces? It is the artifact, and
that changes which rows match. On open tasks, 49 stop matching the email filter
and 9 start — a calendar task or a pure reminder no longer auto-completes
because an email thread synced, and a task whose channel says slack but whose
payload holds an email draft now does.
Two traps worth recording:
- The migration ordered `UPDATE ... SET NULL` before `DROP NOT NULL` and was
rejected by the constraint it had not yet dropped.
- createUserTaskWithExecutionUpdate had `taskChannel || 'email'`. Left alone it
would have silently converted every "no channel" back into "email" on write,
undoing the migration for new rows while backfilled ones stayed correct.
Two latent bugs surfaced by the sweep:
- The crm/conversations.ts task aggregates omitted task_output entirely despite
HydratedConversation.userTasks being typed as the full row, so nothing
downstream could read the output axis off that path.
- The task board's `keyOf` defaulted a null output to 'email', so an undecided
task would have vanished from the board when grouping by channel. There is now
an explicit Undecided bucket.
Frontend gets a shared task-output module mirroring the server kinds; the
Multi-action column, badge and icon are replaced by real output kinds.
Co-Authored-By: Claude Opus 5 <<email>>Aug 15, 2026, 6:28 PM