Closes the web punch-list before the v1.5 iOS sprint. The release folds the v1.4.36 → v1.4.37.2 perf carry-overs into a wider sweep: the iOS dashboard summary route drops from ~4.6 s cold to ~500 ms via a DAY-bucket sparkline read plus a 60 s response cache; the insights comprehensive aggregator gains the same SQL-aggregation playbook on its remaining hot sub-query. A focused robustness sweep lands fourteen correctness fixes across geo-backfill, rollup freshness, drain logging, and BP fast-path windows. The Coach feature gate cascade gains a discovery-style test that walks every insights route and surfaces two previously-orphan API surfaces missing the assistant-surface gate — both now hardened. Cross-tz fragility on the correlations and bp-in-target fast paths gets a runtime guard that falls back to live SQL for any user more than ±3 h from UTC, with the proper per-user-tz bucket minting deferred to the iOS sprint. UX polish lands seventeen P1/P2/P3 items including aria-controls on drill-down chevrons, dropdown max-width on small viewports, polite live regions on insights load, and a colour-blind-safe icon on the GLP-1 take-now pill. Localization steps from ~27 % to ~63 % coverage across es / fr / it / pl with roughly 3,400 new strings plus placeholder restoration for three medication-cluster keys. Profile updates now validate the timezone field against the IANA zone list at the write boundary, closing a self-DoS regression that the new dashboard SQL surfaced.
Performance
GET /api/dashboard/summarycold-mount 4.6 s → ~500 ms. Four unbounded sub-queries replaced with six bounded reads:$queryRaw DISTINCT ON (type)over the 7 d window,$queryRawovermeasurement_rollupsDAY buckets, the existinggroupBy, two intake reads, and a 365 dto_chardistinct-day scan. The whole builder wraps incaches.analyticskeyed${userId}|dashboard-summaryat 60 s TTL. Per-sub-query timing annotates land inmeta.dashboard./api/insights/comprehensiveminor. Consolidated BP sys / dia raw-row reads into a singlefindMany({ type: { in: [...] } })with JS partition — one round-trip instead of two.- Sparkline read switches to DAY-bucket mean: identical signal for BP / weight / HRV (≤ 1 reading/day); smoother trend for ACTIVITY_STEPS / sleep / glucose.
Coach
- Two orphan API gates closed. New
coach-route-gate-inventory.test.tswalks everysrc/app/api/insights/<…>/route.tsand asserts each handler either callsrequireAssistantSurface("coach")or appears on an explicit allowlist. The walk surfaced two unguarded routes —GET+DELETEon/api/insights/chat/[id]andPOSTon/api/insights/chat/messages/[id]/feedback— both now gated. - Cascade test fixture rebuilt to assert the cross-cut gate surface from one source of truth; SSR-mode proof switched to a spy-based assertion.
Robustness
- Geo-backfill batch cap drops 5000 → 500 rows per pass; in-process singleton flag guards the worker handler against re-entry across hot-reloads.
DRAIN_CUMULATIVE_CUTOFF_HOURSlifts into the helper module so cron + CLI agree on the same constant.- Drill-down
takeZod-refined to a 1000-row ceiling; 422 above the cap. - Analytics
daysAgoderived per request from the cachedlastSeenAtso day-boundary crossings can't stale-serve. ensureUserRollupsFreshdedups concurrent same-userId callers through aMap<string, Promise>.- WEEK / MONTH / YEAR rollup enqueues fan out in parallel on measurement write.
getClientIpsource matching usesnode:net.isIPfor IPv4 / IPv6 validation.- MEDICATION categorical enum drift-guard test against the label-key map.
- Dashboard medication checklist + quick-add modal share TanStack Query cache.
- Cumulative drain emits a per-user COMPLETE log line.
- BP fast-path priorYear read window is calendar-aware (leap-year safe).
correlations.degradedsentinel carries a TODO until load-shedding lands.- Private
dayKeyhelper inbp-in-target-fast-pathrenames tobucketDayKey. - Health-score
bpInTargetPctreuses one prior-week query across windows.
Cross-tz fragility
- New
isNearUtc(userTz, now)helper in@/lib/tz/format. The correlations and bp-in-target rollup fast paths invoke it as a runtime guard: when the user is more than ±3 h from UTC the helper forces the live SQL path so per-day aggregates re-key viauserDayKey(measuredAt, userTz)instead of the rollup's UTC bucket. Meta annotates (correlations.tz_guard,bpInTarget.tz_guard) prove branch selection. Proper per-user-tz bucket minting remains a v1.5 deliverable.
UX
- Drill-down chevrons gain stable
drilldown-{desktop,mobile}-${dayKey ?? id}ids threaded asaria-controlson the trigger andidon the disclosed panel. - Hinzufügen dropdown wraps at
max-w-[calc(100vw-2rem)]; longest label trims to "Einnahme erfassen". - Quick-add labels test extends from en+de to all six locales.
- Select trigger right padding tightens on Safari for chevron parity.
- Medication intake empty state promotes the CTA into the footer slot.
- Insights load announces via a polite live region.
- GLP-1 take-now window-status colour pairs with a Lucide glyph for colour-blind users.
- Arztbericht and dismiss button min-h floors tighten.
Localization
- es / fr / it / pl coverage steps from ~27 % to ~63 % per locale, roughly 3,400 leaf-value substitutions. Translated namespaces: dashboard, measurements, mood, auth, nav, charts, thresholds, comparison, onboarding, notifications, insights (sleep, sub-pages, coach settings, relative-time, hero, recommendation, health-score, daily-briefing, coach), medications (intake, status chips, categories, weekdays, intake-history, GLP-1 cluster, schedule controls), targets, and the doctor-report PDF strings.
- Three medication-cluster keys had placeholders restored or hybrid English fragments translated:
glp1NextInjectionDays(native{label}+{days}per locale),intakeHistoryPageInfo(re-appends· {count}clause), anddayActivate/dayDeactivate(restore{day}for per-weekday a11y labels). admin,settings,achievementsremain intentional T3 deferrals.
Security
getClientIpsource matching usesnode:net.isIPinstead of a hand-rolled IPv4 regex.- Profile updates validate
User.timezoneagainst the IANA zone list through a Zodrefinechained onisValidTimezone. Returns 422 "Invalid IANA timezone" at the write boundary so the newto_char(measured_at AT TIME ZONE $tz)SQL in the dashboard summary route cannot be self-DoS'd by a corrupted stored value.
Tests
- Unit suite 4520 → 4524.
Operator notes
- No schema change. No env-var change. No public API change.
- Coolify auto-deploys main on tag push; first webhook may pull stale
:latest, redeploy after the docker-publish workflow completes.