mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
chore(release): v1.4.38
Robustness sweep, perf hotspots, full localization, coach gate cascade closure. See CHANGELOG.md for the full set; headline: - /api/dashboard/summary cold-mount 4.6 s → ~500 ms via DAY-bucket sparkline + 60 s response cache. - Coach API gate inventory test surfaced and closed two orphan routes (chat[id] GET+DELETE, chat-message feedback POST). - Cross-tz fast-path runtime guard falls back to live for users more than ±3 h from UTC; proper bucket minting stays v1.5. - es / fr / it / pl coverage 27 % → 63 % (~3 400 new strings) plus three medication-cluster placeholder restorations. - IANA timezone validation closes the User.timezone self-DoS regression the new dashboard SQL would have surfaced. - Unit suite 4520 → 4524.
This commit is contained in:
1 parent
3d21a22ee2
commit
7cdfe3feef
2 files changed
+203
-1
No files matched your search
+202
@@ -1,5 +1,207 @@
|
||||
# Changelog
|
||||
|
||||
## [1.4.38] — 2026-05-17 — Robustness sweep, perf hotspots, full localization
|
||||
|
||||
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 (per-type latest, sparkline raw, today's
|
||||
intakes, 365 d intakes plus a conditional fifth) replaced with six
|
||||
bounded reads: a `$queryRaw DISTINCT ON (type)` over the 7 d
|
||||
window, a `$queryRaw` over `measurement_rollups` DAY buckets, the
|
||||
existing `groupBy` for type stats, the 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` so
|
||||
the next perf-verify can attribute regressions without
|
||||
re-instrumenting.
|
||||
- **`/api/insights/comprehensive` minor.** Consolidated BP sys / dia
|
||||
raw-row reads into a single `findMany({ type: { in: [...] } })`
|
||||
with JS partition (preserving order) — one round-trip instead of
|
||||
two.
|
||||
- **Sparkline contract.** For BP / weight / HRV the DAY-bucket mean
|
||||
matches the raw read (≤ 1 per day). For ACTIVITY_STEPS / sleep /
|
||||
glucose the smoother per-day average is a better trend signal.
|
||||
|
||||
### Coach
|
||||
|
||||
- **Two orphan API gates closed.** A new discovery test
|
||||
(`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 a single source of truth, and the SSR-mode proof
|
||||
swapped to a spy-based assertion that does not need a full DOM
|
||||
render to verify the lazy-load chain.
|
||||
|
||||
### Robustness
|
||||
|
||||
- Geo-backfill batch cap drops 5000 → 500 rows per pass; an
|
||||
in-process singleton flag guards the worker handler against
|
||||
re-entry across hot-reloads.
|
||||
- `DRAIN_CUMULATIVE_CUTOFF_HOURS` lifts into the helper module so
|
||||
the cron registration and the manual-drain CLI agree on the same
|
||||
constant.
|
||||
- The drill-down `take` parameter is now Zod-refined to a hard
|
||||
1000-row ceiling; the route emits 422 above the cap instead of
|
||||
serving the unbounded read.
|
||||
- Analytics `daysAgo` is derived per request from the cached
|
||||
`lastSeenAt` so day-boundary crossings can no longer stale-serve
|
||||
yesterday's count.
|
||||
- `ensureUserRollupsFresh` now dedups concurrent same-userId callers
|
||||
through a `Map<string, Promise>`, so a probe-storm folds into one
|
||||
round-trip.
|
||||
- WEEK / MONTH / YEAR rollup enqueues on measurement write fan out
|
||||
in parallel instead of serial.
|
||||
- `getClientIp` source matching tightens from a hand-rolled IPv4
|
||||
regex to `node:net.isIP` so IPv6 addresses round-trip cleanly and
|
||||
malformed strings are rejected at the boundary.
|
||||
- The MEDICATION categorical enum gains a drift-guard test against
|
||||
the label-key map so an enum-only change can no longer ship copy
|
||||
fallbacks.
|
||||
- The dashboard medication checklist and the quick-add modal now
|
||||
share their TanStack Query cache (same `staleTime`) so toggling a
|
||||
dose no longer fans out two refetches.
|
||||
- The cumulative drain emits a per-user COMPLETE log line so
|
||||
multi-account drains can be reconciled per row in production
|
||||
logs.
|
||||
- BP fast-path priorYear read window is calendar-aware (leap-year
|
||||
safe).
|
||||
- The `correlations.degraded` sentinel carries a TODO marker until
|
||||
load-shedding lands — no semantic change, just a hand-off note.
|
||||
- The private `dayKey` helper in `bp-in-target-fast-path` renames to
|
||||
`bucketDayKey` so the cross-file term lines up with the cross-tz
|
||||
guard wave naming.
|
||||
- Health-score `bpInTargetPct` reuses the same prior-week query
|
||||
across windows instead of issuing one read per window.
|
||||
|
||||
### Cross-tz fragility
|
||||
|
||||
- New `isNearUtc(userTz, now)` helper in `@/lib/tz/format`. The
|
||||
correlations and bp-in-target rollup fast paths now invoke it as
|
||||
a runtime guard: when the user is more than ±3 h from UTC the
|
||||
helper forces a fall-through to the live SQL path so per-day
|
||||
aggregates re-key via `userDayKey(measuredAt, userTz)` instead of
|
||||
the rollup's UTC bucket. Meta annotates land
|
||||
(`correlations.tz_guard`, `bpInTarget.tz_guard`) so production
|
||||
logs prove branch selection. Proper per-user-tz bucket minting
|
||||
remains a v1.5 iOS-sprint 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.
|
||||
- The Hinzufügen dropdown wraps at `max-w-[calc(100vw-2rem)]` on
|
||||
small viewports; the longest label trims from "Medikamenteneinnahme
|
||||
erfassen" to "Einnahme erfassen".
|
||||
- Quick-add labels test extends from en+de to all six locales for
|
||||
collision-guard coverage.
|
||||
- Select trigger right padding tightens on Safari for chevron
|
||||
parity.
|
||||
- Medication intake empty state promotes the CTA into the footer
|
||||
slot.
|
||||
- Insights load now announces via a polite live region for screen
|
||||
reader users.
|
||||
- The GLP-1 take-now window-status colour now pairs with a Lucide
|
||||
glyph so colour-blind users perceive the same affordance.
|
||||
- Arztbericht and dismiss button min-h floors tighten for visual
|
||||
hierarchy.
|
||||
|
||||
### 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 (medical ranges, status labels), and
|
||||
the doctor-report PDF strings.
|
||||
- Three medication-cluster keys had their placeholders restored or
|
||||
their hybrid English fragments translated: `glp1NextInjectionDays`
|
||||
(now reads natively with `{label}` + `{days}` in each locale),
|
||||
`intakeHistoryPageInfo` (re-appends `· {count}` clause for the
|
||||
per-page row count), and `dayActivate` / `dayDeactivate` (restore
|
||||
`{day}` so a11y labels announce per-weekday).
|
||||
- `admin`, `settings`, and `achievements` namespaces remain
|
||||
intentional T3 — operator-facing surface that will land in a
|
||||
follow-up wave alongside the iOS launch.
|
||||
|
||||
### Security
|
||||
|
||||
- `getClientIp` source matching uses `node:net.isIP` for IPv4 / IPv6
|
||||
validation instead of a hand-rolled IPv4 regex; malformed
|
||||
forwarded headers are rejected at the boundary.
|
||||
- Profile updates validate `User.timezone` against the IANA zone
|
||||
list through a Zod `refine` chained on the existing
|
||||
`isValidTimezone` helper. 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 (four new profile-update timezone-refine
|
||||
tests). Integration baseline unchanged.
|
||||
|
||||
### Deferred to v1.4.39 / v1.5
|
||||
|
||||
- Named `AnalyticsCachedBody` / `AnalyticsEnrichedBody` envelope
|
||||
split in the analytics route, plus the same-microtask
|
||||
rejection-retry safety fix on `ensureUserRollupsFresh`.
|
||||
- Coach client-side disabled-state copy on `message-thread.tsx`
|
||||
(server API gates are closed; only the local message copy
|
||||
remains).
|
||||
- Repo hygiene sweep (orphan imports, unused helpers, dead i18n
|
||||
keys).
|
||||
- Cumulative sparkline read should use `r.sum` instead of `r.mean`
|
||||
for `ACTIVITY_STEPS` / `ACTIVITY_FLIGHTS` / cumulative metrics.
|
||||
- Coach gate inventory walker should match the closed
|
||||
`route.{ts,tsx,js,jsx,mjs,mts}` set.
|
||||
- Dashboard sparkline live-fallback for empty rollup tail on
|
||||
brand-new accounts.
|
||||
- BP / pulse term unification in es / pl (`tensión` vs `presión`,
|
||||
`ciśnienie tętnicze` vs `ciśnienie krwi`, `puls` vs `tętno`).
|
||||
- Proper per-user-tz rollup bucket minting (v1.5 iOS sprint
|
||||
deliverable; today's cheap path is the ±3 h guard with live
|
||||
fallback).
|
||||
|
||||
### 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.
|
||||
|
||||
## [1.4.37.2] — 2026-05-17 — Slim summaries SQL aggregation
|
||||
|
||||
Second hotfix on top of v1.4.37. The v1.4.37.1 fire-and-forget
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "healthlog",
|
||||
"version": "1.4.37.2",
|
||||
"version": "1.4.38",
|
||||
"description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.",
|
||||
"license": "AGPL-3.0-only",
|
||||
"homepage": "https://healthlog.dev",
|
||||
|
||||
Reference in new issue
Block a user