slicefix: coerce legacy 'strength' sortDimension so crm.listConversations validates
Stale localStorage/preset sort configs still held the pre-rename 'strength'
dimension, which the backend enum ('value' | 'signal') rejected — failing
crm.listConversations dozens of times a day. Normalize on read and at both
backendSortBy construction sites so no stale enum reaches the server.
Co-Authored-By: Claude Opus 4.8 (1M context) <<email>>Jul 24, 2026, 10:25 AMutilsfix(sidebar): the latest event can be absent, and the type now says so
`latestEvent: events[0] ?? null` read as ALWAYS PRESENT.
`noUncheckedIndexedAccess` is off, so TypeScript believes `events[0]` on an array
always returns an event — it does not model "slot 0 of an empty list". It then
reasons that the left side of `??` can never be nullish, treats the `null` branch
as dead, and discards it. So every consumer was told `latestEvent` is a thing
that is always there, while the value is null on every deal with no events yet.
`filterConversationsByQuery` guarding it with `?.` is the tell: the author knew,
the type did not.
Annotating the const does NOT fix this — TypeScript keeps the declared type on
paper but narrows the variable to the non-null version at every use, so the
object literal still picks up the narrowed one. `Array.prototype.at` is typed
`T | undefined` unconditionally, so `events.at(0) ?? null` leaves nothing to
narrow and the union survives.
That unblocked the two sidebar test fixtures, which were building the DETAIL
projection and handing it to functions that take the LIST one — the exact
conflation the hook's own note says "took four casts, and their effect was that a
reader reaching for one of those fields compiled clean and read `undefined` at
runtime". Rebuilt against the real wire row: FLAT (the route spreads
`hydrated.conversation` and hangs the joins beside it), `occurredAt` an ISO
string rather than a `Date`, no `conversationScope`/`overviewItems`/`lastReviewedAt`,
and no `emailEvent`/`summary` on the event — which is why subject search goes
through `title`. Built once and referenced twice, since the second copy is what
drifts.
Three casts gone; the one left is the 43-column company row the haystack reads a
single field off, and it says so.
apps/mail types:test: 67 -> 52. All 40 conversationsPage tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 9, 2026, 9:11 PM