mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 12:22:03 +00:00
docs(planning): record Wave 5 reconcile + v1.4.22 backlog
Wave 5 reconcile pass complete: 7 HIGH applied inline, ~6 MEDs applied inline, remaining MED + LOW deferred to `.planning/v1422-backlog.md` under "Wave 5 reconcile carry-over". - `.planning/phase-W5-v1422-reconcile-report.md`: 10 commit SHAs with cross-references to each finding, test count delta (+14 unit tests, +3 integration tests; final 2111 / 245 files), per-bucket HIGH / MED / LOW disposition, and items the maintainer should weigh in on (CSP scope, insights split, sentinel parser malformed-enum priority, /me resync stays as fall-back). - `.planning/v1422-backlog.md`: every deferred MED + LOW with a one-line trace per item, organised by review bucket. - `.planning/STATE.md`: Wave 5 ticked complete; tail updated to point at the W5 review files + reconcile report. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
6c0c69cbce
commit
b094eaa2ae
3 files changed
+185
-9
No files matched your search
+9
-9
@@ -1,7 +1,7 @@
|
||||
# v1.4.22 marathon — state log
|
||||
|
||||
Status: Wave 4 — backlog cleanup + bug closure complete
|
||||
Last update: 2026-05-10T21:45+02:00
|
||||
Status: Wave 5 — multi-agent QA + reconcile complete
|
||||
Last update: 2026-05-10T22:30+02:00
|
||||
|
||||
> Previous patch: v1.4.21 live (image digest
|
||||
> `sha256:4e818d44702c…`, `/api/version=1.4.21`).
|
||||
@@ -172,13 +172,13 @@ Apply or confirm-defer every item:
|
||||
|
||||
## Wave 5 — Multi-agent QA + Product-Lead review
|
||||
|
||||
- [ ] code-reviewer
|
||||
- [ ] security review
|
||||
- [ ] design / UX review
|
||||
- [ ] senior-dev review
|
||||
- [ ] simplify
|
||||
- [ ] Product Lead — v1.5 plan refresh based on v1.4.22 state
|
||||
- [ ] Reconcile applied CRITICAL + HIGH; defer MED/LOW with reasoning
|
||||
- [x] code-reviewer (`.planning/phase-W5-v1422-code-review.md` — 0 CRIT, 2 HIGH, 4 MED, 5 LOW)
|
||||
- [x] security review (`.planning/phase-W5-v1422-security-review.md` — 0 CRIT, 0 HIGH, 2 MED, 4 LOW)
|
||||
- [x] design / UX review (`.planning/phase-W5-v1422-design-review.md` — 0 CRIT, 3 HIGH, 7 MED, 5 LOW)
|
||||
- [x] senior-dev review (`.planning/phase-W5-v1422-senior-dev-review.md` — 0 CRIT, 2 HIGH, 5 MED, 4 LOW)
|
||||
- [x] simplify (`.planning/phase-W5-v1422-simplify-review.md` — 5 apply-yes, 4 apply-maybe, 6 apply-no)
|
||||
- [x] Product Lead — v1.5 plan refresh based on v1.4.22 state (`.planning/phase-W5-v1422-product-lead-review.md`)
|
||||
- [x] Reconcile applied CRITICAL + HIGH; defer MED/LOW with reasoning (10 commits; report at `.planning/phase-W5-v1422-reconcile-report.md`; backlog at `.planning/v1422-backlog.md`)
|
||||
|
||||
## Wave 6 — Release v1.4.22
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Wave 5 reconcile — v1.4.22
|
||||
|
||||
Branch: `develop` (10 reconcile commits ahead of `363e73c`).
|
||||
Date: 2026-05-10.
|
||||
Inputs: six W5 reviews + product-lead strategic memo.
|
||||
|
||||
## Summary
|
||||
|
||||
- 0 CRITICAL findings across all reviews.
|
||||
- 7 HIGH findings — all applied inline.
|
||||
- ~6 MEDs applied inline (judgment-pick high-leverage).
|
||||
- Remaining MED + LOW deferred to `.planning/v1422-backlog.md`.
|
||||
|
||||
## Commits
|
||||
|
||||
| # | SHA | Title | Cross-ref |
|
||||
|---|-----|-------|-----------|
|
||||
| 1 | `75ccc01` | fix(coach): graceful fallback when sentinel-only output strips empty | Code-H1 |
|
||||
| 2 | `ef597bf` | fix(insights): align BD-Zielbereich delta math with comparison window | Code-H2 |
|
||||
| 3 | `b1ce6eb` | refactor(auth): fold onboarding-cookie write into createSession + destroySession | Sr-H1 |
|
||||
| 4 | `2075d39` | refactor(api): extract createSseStream helper from chat route | Sr-H2 |
|
||||
| 5 | `a2e24b1` | a11y(insights): polish sticky section nav (aria-current, focus, motion-reduce) | Design-H1, Design-H3, Code-MED-4, Design-LOW-4 |
|
||||
| 6 | `651a529` | fix(insights): collapse BP tile to single secondary row at <sm | Design-H2 |
|
||||
| 7 | `65a8b5a` | fix(coach): pin medical disclaimer at bottom of message thread | Design-H3, Design-M4 |
|
||||
| 8 | `1631d80` | refactor: simplify-pass apply-yes cleanup | S-01..S-05 |
|
||||
| 9 | `6c0c69c` | fix: MED-cluster — SameSite=Strict, exact-match PUBLIC_PATHS, Berlin tz, streaming race | Sec-MED-1, Sec-MED-2, Code-MED-2, Code-MED-3 (×2), Code-LOW-5 |
|
||||
| 10 | _this commit_ | docs(planning): record Wave 5 reconcile + v1.4.22 backlog | meta |
|
||||
|
||||
## Findings by bucket
|
||||
|
||||
### HIGH (7 / 7 applied)
|
||||
|
||||
- **Code-H1** ✅ sentinel-only output no longer leaks `---KEYVALUES---` markers. New integration test covers the empty-prose-after-strip branch.
|
||||
- **Code-H2** ✅ BD-Zielbereich tile compareDelta now matches caption: subtracts `bpInTargetPctPriorMonth` / `bpInTargetPctPriorYear` instead of `bpInTargetPctAllTime`. Two new bp-in-target unit tests + two insights-polish guards.
|
||||
- **Sr-H1** ✅ `createSession` requires `onboardingPending` parameter; cookie-write fan-out reduced to one site. Three new auth-flow integration tests pin the contract.
|
||||
- **Sr-H2** ✅ `createSseStream` extracted to `src/lib/sse/create-stream.ts`. Three unit tests pin sync, async, and throw paths.
|
||||
- **Design-H1** ✅ section nav `aria-current`, `focus-visible` ring, `motion-reduce` gate, `bg-background/95 backdrop-blur`, `-mx-` removed, scrollbar hidden, observer ratio-sorted, `scroll-mt-28` → `scroll-mt-16`.
|
||||
- **Design-H2** ✅ BP tile `<sm` collapses to a single combined "All-time X% · Δ Y last month" secondary row; `>=sm` keeps the full layout.
|
||||
- **Design-H3** ✅ medical disclaimer pinned at bottom of message thread (always visible regardless of viewport); rail footer kept for desktop redundancy.
|
||||
|
||||
### MED (applied)
|
||||
|
||||
- **Sec-MED-1** ✅ `hl_onboarding` cookie `SameSite=Strict`.
|
||||
- **Sec-MED-2** ✅ `/onboarding` exact-match + subroute guard.
|
||||
- **Code-MED-2** ✅ dead i18n keys (`coach.settings`, `coach.settingsTooltip`) dropped.
|
||||
- **Code-MED-3 (Berlin tz)** ✅ `berlinDayKey()` lifted to `src/lib/analytics/berlin-day.ts`; targets sparkline + analytics route share the helper. Four new unit tests for DST + UTC-midnight edge cases.
|
||||
- **Code-MED-3 (streaming race)** ✅ 150 ms grace window on message-thread suppresses the persisted twin while the streaming bubble is still in flight.
|
||||
- **Code-MED-4** ✅ IntersectionObserver picks highest-intersection entry; folded into Design-H1 commit.
|
||||
- **Code-LOW-5** ✅ `queryKeys.dashboardWidgets()` centralised; three call sites migrated.
|
||||
|
||||
### MED (deferred)
|
||||
|
||||
All other MEDs documented in `.planning/v1422-backlog.md` under "Wave 5 reconcile carry-over":
|
||||
|
||||
- Sr-M1..M5 (analytics-route unbounded findMany; targets-route 7-pass sparkline; targets-page render mutation; provenanceFromJson hand-rolled validation; sentinel parser malformed enum).
|
||||
- Design-M1..M3, M5..M7 (coach evidence aria semantics; sparkline min-points threshold; severity badge eyebrow + i18n; row-fill heuristic; live-preview affordance; sparkline aria sentiment).
|
||||
- Code-MED-1 already applied; Code-MED-2 already applied; Code-MED-3 already applied (×2); Code-MED-4 already applied.
|
||||
|
||||
### LOW (deferred)
|
||||
|
||||
All LOWs deferred to backlog: Code-LOW-1..LOW-4, Sec-LOW-1..LOW-4, Design-L1..L5, Sr-L1..L4.
|
||||
|
||||
### Simplify (5 / 5 apply-yes applied)
|
||||
|
||||
- **S-01..S-05** ✅ all five applied in single `refactor: simplify-pass apply-yes cleanup` commit (`1631d80`).
|
||||
- **S-M1..S-M4** deferred (maybe-tier).
|
||||
- **S-N1..S-N6** rejected (per simplify review).
|
||||
|
||||
## Test count delta
|
||||
|
||||
- Baseline (pre-W5 reconcile): 2097 unit tests across 243 files.
|
||||
- Final (post-W5 reconcile): **2111 unit tests across 245 files** (+14 unit tests, +2 files).
|
||||
- Integration tests: 81 → 84 (+3) per the new auth-flow + coach-chat + proxy entries; verified locally (`pnpm test:integration auth-flow` passes 6/6).
|
||||
|
||||
New test surfaces:
|
||||
- `src/lib/sse/__tests__/create-stream.test.ts` (3 tests)
|
||||
- `src/lib/analytics/__tests__/berlin-day.test.ts` (4 tests)
|
||||
- `src/lib/analytics/__tests__/bp-in-target.test.ts` (3 new tests for priorMonth/priorYear/null windows)
|
||||
- `src/app/__tests__/insights-polish.test.ts` (2 new guards for Code-H2)
|
||||
- `src/__tests__/proxy-onboarding-redirect.test.ts` (2 new guards for Sec-MED-2)
|
||||
- `tests/integration/coach-chat.test.ts` (1 new test for sentinel-only leak)
|
||||
- `tests/integration/auth-flow.test.ts` (3 new tests for createSession/destroySession cookie contract)
|
||||
|
||||
## Verification gates run between commits
|
||||
|
||||
For every commit:
|
||||
- `pnpm typecheck` (clean — 0 errors)
|
||||
- `pnpm test --run` (passes after each change; final 2111/2111)
|
||||
- `pnpm lint` (15 pre-existing warnings, 0 errors)
|
||||
|
||||
For HIGH 1 + HIGH 3 + Sec-MED-2 (per brief):
|
||||
- `pnpm test:integration auth-flow` 6/6 passes
|
||||
- `pnpm test --run src/__tests__/proxy-onboarding-redirect.test.ts` 9/9 passes
|
||||
|
||||
## Items the maintainer should weigh in on
|
||||
|
||||
1. **CSP `connect-src` for `https://chatgpt.com`** is gated to `/settings/ai/**` only (per existing proxy.ts comment). The Coach SSE route hits OpenAI/Anthropic via the provider runner, which runs server-side — no browser fetch — so this is correct. Calling out only because the W5 simplify review flagged proxy.ts as out-of-scope and the cookie attribute changed; verify the connect-src isn't accidentally re-broadened by a future MED-2 follow-up.
|
||||
|
||||
2. **Insights page split deferred to v1.5 P5** per the product-lead memo. The W5 senior-dev review's L3 (`InsightsSectionNav` extraction) is a natural sub-task at that point. Leaving the page at 1700+ LOC for one more cycle is the right call but worth a v1.4.23 reminder when the next polish PR opens.
|
||||
|
||||
3. **Sr-M5 sentinel parser malformed-enum** carries the highest signal-to-effort ratio of the deferred MEDs (4-state enum, ~30 LOC, tightens ops observability before iOS volume lands). Suggest pulling forward to v1.4.23 if room.
|
||||
|
||||
4. **The `/api/auth/me` resync write** stays as a fall-back per the original v1.4.22 C4 design. Sec-LOW-1 calls it out only as a "future cargo-cult" risk — not an actual bug. Optional one-line code comment about not growing the GET-side mutation. Filed in backlog as LOW.
|
||||
|
||||
## Anything I couldn't reconcile
|
||||
|
||||
- All 7 HIGH applied successfully. No unresolved blockers.
|
||||
- The Code-LOW-2 colon-bearing label parser test was deferred (LOW + low-priority). The defensive contract is documented in the prompt few-shots; an explicit constraint comment in the prompt header is the cleanest fix when v1.4.23 next-touches the prompt.
|
||||
@@ -0,0 +1,68 @@
|
||||
# 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 to `src/lib/analytics/berlin-day.ts` in W5 reconcile). DST-boundary edge-case unit test added. `analytics/route.ts` now 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 the `title` prop on all three TrendsRow callsites OR add a comment. Cosmetic only.
|
||||
- **Code LOW-2** (`src/lib/ai/coach/keyvalues.ts:53`): `parseKeyValueLine` regex 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 with `kept` count but not the prompt-version-vs-malformed-cause breakdown. Extend `SentinelParseResult` with `reason: "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 with `for` loop or `points.reduce(...)`.
|
||||
- **Code LOW-5** (`src/lib/query-keys.ts`): `["user", "dashboardWidgets"]` centralised in `queryKeys.dashboardWidgets()` in W5 reconcile, three call sites migrated. — APPLIED.
|
||||
|
||||
### Security review
|
||||
|
||||
- **Sec MED-1** (`src/lib/auth/session.ts:32`): `hl_onboarding` cookie `SameSite=Lax` → `Strict`. — APPLIED in W5 reconcile.
|
||||
- **Sec MED-2** (`src/proxy.ts:28-33` + `:152`): `/onboarding` `startsWith` match 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 `WARNING` comment 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 with `truncated` / `closeMissing` for ops visibility.
|
||||
|
||||
### Design review
|
||||
|
||||
- **Design M1** (`coach-panel/message-thread.tsx:259-307`): coach evidence disclosure has no `aria-controls` / `aria-expanded` semantics; `<summary>` reports without expanded state in iOS Safari ≤ 17.3. Wrap the summary in a 36-px-min hit zone, add `aria-expanded` mirror via `useState` + `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 require `length >= 7` before painting, or render at `stroke-opacity:0.5` for `length < 10` plus a `{n} of 30 days logged` caption.
|
||||
- **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. Drop `tracking-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 uses `showMoodSection` heuristic. Either hide the BP-medication card entirely when `bpMedicationScatterData.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 sparkline `aria-hidden` skips the only "things changing" signal for SR users. Add `aria-label` to 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`): React `key={\`\${kv.label}-\${idx}\`}` allows duplicate-label collisions. Use a hash of `\${label}-\${window}-\${value}`.
|
||||
- **Design L3** (`targets/page.tsx:288`): sparkline `preserveAspectRatio="none"` distorts y vs x. Either keep + accept, or fix Y-range to the target range.
|
||||
- **Design L4** (`scroll-mt-28` overshoot): tuned to `scroll-mt-16` in 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 `createSseStream` helper 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_aggregate` row 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** (`provenanceFromJson` hand-rolls keyValues validation): replace 35 lines of manual `typeof` checks with `coachKeyValueSchema.array().safeParse(parsed.keyValues)`. Same `safeParse` lift 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** (`Sparkline` `preserveAspectRatio="none"`): same as Design L3.
|
||||
- **Sr L2** (`setOnboardingPendingCookie` `async` but does no async work): could be sync if `cookies()` resolved by caller. v1.5 cleanup.
|
||||
- **Sr L3** (`InsightsSectionNav` re-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 TargetItem` cast in `targets/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.
|
||||
Reference in new issue
Block a user