github MBombeck/HealthLog v1.4.38
v1.4.38 — Robustness sweep, perf hotspots, full localization

2 hours ago

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/summary cold-mount 4.6 s → ~500 ms. Four unbounded sub-queries replaced with six bounded reads: $queryRaw DISTINCT ON (type) over the 7 d window, $queryRaw over measurement_rollups DAY buckets, the existing groupBy, two intake reads, and a 365 d to_char distinct-day scan. The whole builder wraps in caches.analytics keyed ${userId}|dashboard-summary at 60 s TTL. Per-sub-query timing annotates land in meta.dashboard.
  • /api/insights/comprehensive minor. Consolidated BP sys / dia raw-row reads into a single findMany({ 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.ts walks every src/app/api/insights/<…>/route.ts and asserts each handler either calls requireAssistantSurface("coach") or appears on an explicit allowlist. The walk surfaced two unguarded routes — GET + DELETE on /api/insights/chat/[id] and POST on /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_HOURS lifts into the helper module so cron + CLI agree on the same constant.
  • Drill-down take Zod-refined to a 1000-row ceiling; 422 above the cap.
  • Analytics daysAgo derived per request from the cached lastSeenAt so day-boundary crossings can't stale-serve.
  • ensureUserRollupsFresh dedups concurrent same-userId callers through a Map<string, Promise>.
  • WEEK / MONTH / YEAR rollup enqueues fan out in parallel on measurement write.
  • getClientIp source matching uses node:net.isIP for 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.degraded sentinel carries a TODO until load-shedding lands.
  • Private dayKey helper in bp-in-target-fast-path renames to bucketDayKey.
  • Health-score bpInTargetPct reuses 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 via userDayKey(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 as aria-controls on the trigger and id on 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), and dayActivate / dayDeactivate (restore {day} for per-weekday a11y labels).
  • admin, settings, achievements remain intentional T3 deferrals.

Security

  • getClientIp source matching uses node:net.isIP instead of a hand-rolled IPv4 regex.
  • Profile updates validate User.timezone against the IANA zone list through a Zod refine chained on isValidTimezone. Returns 422 "Invalid IANA timezone" at the write boundary so the new to_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.

Don't miss a new HealthLog release

NewReleases is sending notifications on new releases.