mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
Six MEDs grouped per the W5 reconcile brief:
- Sec-MED-1: `hl_onboarding` cookie now `SameSite=Strict` (no
cross-site flow depends on it; sibling `healthlog_session` stays
`Lax` for the Withings OAuth callback).
- Sec-MED-2: `/onboarding` PUBLIC_PATHS match tightened from loose
`startsWith("/onboarding")` to exact path + subroute guard
(`pathname === "/onboarding" || pathname.startsWith("/onboarding/")`).
A future `/onboarding-export` route can no longer inherit the
silent-pass-through. Same guard mirrored on the redirect short-
circuit. Two new proxy tests pin the contract.
- Code-MED-3: lift `berlinDayKey()` from
`src/app/api/analytics/route.ts` to a shared
`src/lib/analytics/berlin-day.ts` and route the targets
sparkline-bucketing through it. The targets route used to bucket
by UTC `YYYY-MM-DD` so a 23:30-Berlin reading on Tuesday landed
in Wednesday's bucket. Four new unit tests pin DST + UTC-midnight
edge cases.
- Code-MED (streaming race): on slow connections SSE `done` fires
before the invalidate-refetch resolves, producing a 200-500ms
duplicate-bubble render. Add a 150ms grace window on the
message-thread that suppresses the persisted twin while the
streaming bubble is still in flight.
- Code-MED-2: drop dead i18n keys `insights.coach.settings` and
`insights.coach.settingsTooltip` (W3 removed the cog; the keys
had zero call sites and would carry confusing legacy if v1.4.23's
prompt-tuning panel picks a different key shape).
- Code-LOW-5: centralise `["user", "dashboardWidgets"]` query key
in `queryKeys.dashboardWidgets()` and migrate the three call
sites (dashboard, insights, settings/dashboard-layout-section).
Same query-key-collision discipline as `queryKeys.analytics()`.
Closes Sec-MED-1, Sec-MED-2, Code-MED-2, Code-MED-3, Code-MED-3
(streaming race), Code-LOW-5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
36 lines
1.4 KiB
TypeScript
36 lines
1.4 KiB
TypeScript
/**
|
|
* v1.4.22 W5 reconcile (Code-MED-1) — shared Berlin-day-key helper.
|
|
*
|
|
* The CLAUDE.md timezone convention is "Europe/Berlin for display,
|
|
* UTC in database". The dashboard analytics route used a private
|
|
* `berlinDayKey()` (defined in `src/app/api/analytics/route.ts:330`)
|
|
* to bucket measurements by Berlin calendar day. The targets route's
|
|
* `sparklinePoints()` was bucketing by UTC `YYYY-MM-DD` — a 23:30
|
|
* Berlin reading on Tuesday landed in Wednesday's bucket, drifting
|
|
* the sparkline 1 day on cross-DST boundaries.
|
|
*
|
|
* Lift the helper into `src/lib/analytics/` so every display surface
|
|
* shares the same bucket contract.
|
|
*/
|
|
|
|
const BERLIN_DATE_PARTS = new Intl.DateTimeFormat("en-CA", {
|
|
timeZone: "Europe/Berlin",
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
});
|
|
|
|
/**
|
|
* Format a `Date` as a `YYYY-MM-DD` string anchored to Europe/Berlin
|
|
* calendar day. Used as a stable key for daily aggregation so a
|
|
* 23:30-Berlin reading on Tuesday lands in Tuesday's bucket
|
|
* regardless of UTC offset (CET / CEST, DST).
|
|
*/
|
|
export function berlinDayKey(d: Date): string {
|
|
const parts = BERLIN_DATE_PARTS.formatToParts(d);
|
|
const y = parts.find((p) => p.type === "year")?.value ?? "1970";
|
|
const m = parts.find((p) => p.type === "month")?.value ?? "01";
|
|
const day = parts.find((p) => p.type === "day")?.value ?? "01";
|
|
return `${y}-${m}-${day}`;
|
|
}
|