mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
Mostly table column normalisation under docs/audit/ + planning, object- literal expansion in src/lib/ai/coach/snapshot.ts after the W4 F6 Apple Health metric block landed, and small wrap/unwrap line adjustments in the device + APNs routes / tests touched across W3/W4/W5 sessions. No behavioural change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.1 KiB
8.1 KiB
v1.4.22 backlog
Items deferred from the W5 reconcile pass. Carried into v1.4.23 +.
Wave 5 reconcile carry-over
Code review
- Code MED-1 (
src/app/api/insights/targets/route.ts:199-216):sparklinePoints()once-per-day bucket now uses Berlin tz (lifted tosrc/lib/analytics/berlin-day.tsin W5 reconcile). DST-boundary edge-case unit test added.analytics/route.tsnow imports the shared helper. — APPLIED in W5 reconcile, kept here for trace. - Code MED-4 (
src/app/insights/page.tsx:1729-1747): IntersectionObserver picks the highest-intersection entry now. — APPLIED in W5 reconcile (folded into Design-H1 commit), kept here for trace. - Code LOW-1 (
src/components/insights/trends-row.tsx:148):<MoodChart title={…} />self-resolves a localised title; the explicit prop tightens coupling. Drop thetitleprop on all three TrendsRow callsites OR add a comment. Cosmetic only. - Code LOW-2 (
src/lib/ai/coach/keyvalues.ts:53):parseKeyValueLineregex uses the first:as label/value boundary. A future prompt rev that adds a time-of-day pin would break ("Mon 6 May, 7:30 reading: 142/88"). Add a parser test for a colon-bearing label as a regression pin OR document the constraint in the prompt explicitly. - Code LOW-3 (
src/app/api/insights/chat/route.ts:284-291): sentinel parse failure logged withkeptcount but not the prompt-version-vs-malformed-cause breakdown. ExtendSentinelParseResultwithreason: "ok" | "no_close_marker" | "truncated" | "empty_block". - Code LOW-4 (
src/app/targets/page.tsx:272-273):Math.min(...points)/Math.max(...points)spread-call patterns can blow stack at ~10k arguments. Replace withforloop orpoints.reduce(...). - Code LOW-5 (
src/lib/query-keys.ts):["user", "dashboardWidgets"]centralised inqueryKeys.dashboardWidgets()in W5 reconcile, three call sites migrated. — APPLIED.
Security review
- Sec MED-1 (
src/lib/auth/session.ts:32):hl_onboardingcookieSameSite=Lax→Strict. — APPLIED in W5 reconcile. - Sec MED-2 (
src/proxy.ts:28-33+:152):/onboardingstartsWithmatch tightened to exact + subroute. — APPLIED in W5 reconcile. - Sec LOW-1 (
/api/auth/me): GET that mutates state (cookie write) on every call. Idempotent; review left as-is, optional comment about not growing. - Sec LOW-2 (Coach few-shots): synthetic numeric values baked into the system prompt. Optional
WARNINGcomment so a future contributor doesn't paste real readings. - Sec LOW-3 (
src/app/api/insights/chat/route.ts:211-220): transcript builder flattens role labels into a single user prompt; user-self-injection only. v1.4.23 candidate: switch to structured chat-completion payload. - Sec LOW-4 (
src/lib/ai/coach/keyvalues.ts:147-180): truncated/closeMissing booleans not surfaced to wide-event meta. Extend withtruncated/closeMissingfor ops visibility.
Design review
- Design M1 (
coach-panel/message-thread.tsx:259-307): coach evidence disclosure has noaria-controls/aria-expandedsemantics;<summary>reports without expanded state in iOS Safari ≤ 17.3. Wrap the summary in a 36-px-min hit zone, addaria-expandedmirror viauseState+onToggle. - Design M2 (
src/app/targets/page.tsx:264-303): Sparkline has no min-points threshold guard — 3-4 points spread over 30 days reads "lots of activity". Either requirelength >= 7before painting, or render atstroke-opacity:0.5forlength < 10plus a{n} of 30 days loggedcaption. - Design M3 (
src/components/insights/recommendation-card.tsx:328-335): severity badge uppercase eyebrow re-introduces the v1.4.20 M4 marketing pattern AND surfaces the internal severity enum (urgent/suggestion) — no i18n. Droptracking-wide uppercase, replace{norm.severity}with{t(\insights.recommendation.severity.${norm.severity}`)}`. Add EN/DE keys. - Design M4 (Coach disclaimer reach): pinned the disclaimer at the bottom of the message thread in W5 reconcile (Design-H3 fix). — APPLIED.
- Design M5 (
src/app/insights/page.tsx:1070-1078): row-fill rule on the medication-correlation row usesshowMoodSectionheuristic. Either hide the BP-medication card entirely whenbpMedicationScatterData.length < 5(let mood-vs-BP take 100 %) or restore the fixed 50/50 with per-card empty state. - Design M6 (
dashboard-layout-section.tsx:204-241): comparison-toggle has no live-preview affordance. Add a tiny<TrendCard>preview swatch under the picker. - Design M7 (
src/app/targets/page.tsx:286-301): SVG sparklinearia-hiddenskips the only "things changing" signal for SR users. Addaria-labelto the<TrendIcon>parent OR move the sentiment into the delta caption prose. - Design L1 (i18n voice asymmetry on
coach.evidenceLabel): EN ("What I'm looking at") declarative; DE ("Worauf bezieht sich das?") curious. Decide on a unified voice. - Design L2 (
coach-panel/message-thread.tsx:286-304): Reactkey={\${kv.label}-${idx}`}allows duplicate-label collisions. Use a hash of${label}-${window}-${value}`. - Design L3 (
targets/page.tsx:288): sparklinepreserveAspectRatio="none"distorts y vs x. Either keep + accept, or fix Y-range to the target range. - Design L4 (
scroll-mt-28overshoot): tuned toscroll-mt-16in W5 reconcile (Design-H1 commit). — APPLIED. - Design L5 (api-tokens table date wrap): cells wrap to two lines on 1024 px. Either short-format
formatDate+ tooltip, or push<col>width to 14 %.
Senior-dev review
- Sr H1 (createSession cookie fan-out): folded into createSession in W5 reconcile. — APPLIED.
- Sr H2 (chat route SSE construction duplicated 3×): extracted
createSseStreamhelper in W5 reconcile. — APPLIED. - Sr M1 (analytics route fetches ALL BP measurements ever, twice): bound by horizon (5 years) OR materialise a per-user
bp_target_aggregaterow refreshed on measurement-write. - Sr M2 (targets route's 7-pass sparkline computation): collapse to a single Postgres-side
GROUP BY date_trunc('day', measured_at), type. - Sr M3 (targets page mutates server response inside render): wrap the glucose-target conversion in
useMemo([data, t, displayGlucoseUnit])to keep refs stable. - Sr M4 (
provenanceFromJsonhand-rolls keyValues validation): replace 35 lines of manualtypeofchecks withcoachKeyValueSchema.array().safeParse(parsed.keyValues). SamesafeParselift fixes the windows / metrics enum-membership gap. - Sr M5 (sentinel parser conflates 3 distinct malformed conditions): four-state enum (
"ok" | "truncated" | "unclosed" | "empty") so ops dashboards can distinguish the cases. - Sr L1 (
SparklinepreserveAspectRatio="none"): same as Design L3. - Sr L2 (
setOnboardingPendingCookieasyncbut does no async work): could be sync ifcookies()resolved by caller. v1.5 cleanup. - Sr L3 (
InsightsSectionNavre-creates IntersectionObserver on every mount): single mount per page, so the practical cost is negligible — observer should move with the component on extraction. - Sr L4 (
as TargetItemcast intargets/route.ts:281): the BP entry ships sys-only data without typing it. Pre-existing.
Simplify review
- S-01..S-05: applied in W5 reconcile (single commit). — APPLIED.
- S-M1..S-M5: maybe-tier — leave for now; revisit when adjacent work re-touches the surfaces.
Product Lead memo (cross-link)
- Insights page split held to v1.5 P5 (Apple Health work edits the same sub-trees).
- Pearson p-value normal approximation at low df: 30-LOC incomplete-beta or raise gate to df >= 20. Pull in v1.5 P0 before auto-discovery ships.
- Coach token-budget UX: 25 000 tokens/user/day = ~13 turns. iOS surface in v1.5 will multiply this. Replace generic 429 with localised "limit reached, resets at 00:00 UTC".
- Conversation persistence eviction policy: auto-archive after 90 days, hard-delete after 365. v1.5 P3 latest.
- OpenAPI spec drift CI guard: pull forward to v1.4.23 if possible.
- CoachDrawer
key={prefill}weaponising React keys (Sr-HIGH-4): heavy refactor; v1.5 P3 latest. - Cross-user feedback aggregation cron (P6 in product-lead memo): wired alongside v1.5 P3 once usage volume materialises.