Commit Graph
5 Commits
Author SHA1 Message Date
Marc-André Bombeck c72b3ce890 fix(geo-backfill): drop batch cap from 5000 to 500 rows per pass
Each row spends up to 3 s on the lookupIpLocation online fallback;
5000 rows × 3 s let a single backfill burst block for ~4 h and starve
the next hourly cron. Tighten the cap to 500 so the worst-case pass
fits well inside the hour-long schedule slot. The helper is
idempotent, so a tenant with a large null-location backlog drains
over multiple hourly passes instead of one mega-pass.
2026-05-17 17:12:33 +02:00
Marc-André Bombeck 2327332206 test(coach): invariant — every Coach-bearing surface respects the global flag
Marc directive: when the operator turns the global Coach flag off,
every Coach affordance on every surface must vanish — no grey-out,
no error, no inert button, no DOM trace. The recent v1.4.37 W5
hotfixes closed three holes the UX audit caught (HeroStrip action
button, SuggestedPrompts chip strip, /targets CoachDrawer mount); a
follow-up audit item asked for an automated invariant so the gate
contract can't regress.

Walk every Coach-bearing web surface (HeroStrip action row,
SuggestedPrompts chips, CoachLaunchButton inline pill, LayoutCoachFab
mobile sticky, LayoutCoachMount drawer subtree) under a QueryClient
pre-seeded with `coach: false` and assert each one paints nothing
measurable in the SSR output. The fixture maintains a known surface
list; a trailing count check forces contributors to revisit the
fixture when a new Coach-bearing component lands.
2026-05-17 14:30:34 +02:00
Marc-André Bombeck af17db5dd5 perf(coach): 60s LRU on buildCoachSnapshot keyed on (userId, scope)
`buildCoachSnapshot()` reads only persisted data — ~10 measurement
findManys plus the GLP-1 / mood / intake side-fetches — yet a single
chat conversation sends 2-4 turns within a minute and rebuilds the
same snapshot every time. Wrap the existing builder (renamed to
`buildCoachSnapshotImpl`) with an in-memory cache keyed on
`(userId, scope.window, sorted scope.sources)` with a 60 s TTL and a
64-entry LRU cap. Different scopes recompute; the cache evicts on the
next eligible read.

`.planning/round-v1433-audit-perf.md` §3.3 estimates 200-800 ms of
server-side tail saved on the second+ turn — the bulk of the
"blockt komplett" complaint between user-clicks-send and first SSE
`token` frame. The `__resetCoachSnapshotCacheForTests` export gives
the existing two snapshot-test suites a hook so each fixture is read
fresh; both add a `beforeEach` reset alongside the existing
`vi.clearAllMocks()` call. Two new tests pin the cache-hit (one
Prisma findMany call across two `buildCoachSnapshot` calls with the
same scope) and the scope-changed miss (different `window` → two
findManys). Feature-flag tests gain a parallel "per-request
memoisation" describe-block that pins the same behaviour for
`getAssistantFlags()` against a stable mocked event reference.
2026-05-16 17:23:07 +02:00
Marc-André Bombeck 4f63bd5f1e perf(coach): memoise assistant-flags per request + 60s LRU on snapshot builder
Two server-side latency cuts on the Coach drawer open / first turn path.

1. **Per-request memo on `getAssistantFlags()`.** Opening the Coach
   drawer fires five gated fetches in parallel — `/api/insights/chat`,
   the rail list, `/api/insights/comprehensive`, plus the per-metric
   status calls already on the page — and every gated route lands a
   `requireAssistantSurface(surface)` call near the top. Today each
   call issues its own `AppSettings.singleton` `SELECT`. The new
   `src/lib/request-cache.ts` carrier keys a `Map` on the active
   `WideEventBuilder` (already in `AsyncLocalStorage` via the
   api-handler) so the same request reads the row once. Outside an
   event context (unit tests, background jobs) the factory runs
   uncached so behaviour is unchanged.

2. **60-second in-memory LRU on `buildCoachSnapshot()`.** A chat
   conversation sends 2-4 turns within a minute and the snapshot reads
   only persisted data — ~10 measurement reads + GLP-1 + mood + intake
   side-fetches per turn. Cache the result keyed on
   `(userId, scope.window, sorted scope.sources)` with a 60 s TTL and
   a 64-entry LRU cap. Different scopes recompute; the cache evicts
   on the next eligible read. Tests exercise both the hit and the
   scope-changed miss; legacy tests gain an explicit reset between
   describe blocks.

`.planning/round-v1433-audit-perf.md` §3.3 estimates 200-800 ms of
server-side tail saved on the second+ turn and roughly 4×
`AppSettings.singleton` reads avoided on a cold Coach-drawer open.
2026-05-16 17:19:52 +02:00
Marc-André Bombeck b68206eca4 feat(settings): add 6 assistant-surface feature flags to AppSettings
Operator-side kill matrix for every LLM-driven surface. Master flag
`assistantEnabled` plus five sub-flags carve the seven surfaces:
Coach + chat, Daily Briefing + advisor, per-metric status cards,
correlation narration, Health-Score delta explainer. Defaults are
`true` so existing installs upgrade without losing affordances.

New `src/lib/feature-flags/` module exposes `getAssistantFlags()`,
`resolveAssistantFlags()` and the `AssistantDisabledError` typed
error route handlers throw to surface the 403 +
`errorCode: "assistant.disabled.<surface>"` envelope. The resolver
forces every sub-flag false when the master is off so callers never
have to compose `master && sub`.

Locks per `.planning/RESPONSE-TO-IOS-TEAM-2026-05-16.md` §3 R5:
the same matrix gates server-routed AND iOS on-device surfaces.

Migration 0065 is additive only; `app_settings` gains six boolean
columns, all default `true`, idempotent under `IF NOT EXISTS`.
2026-05-16 13:38:49 +02:00