mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 12:22:03 +00:00
docs(planning): record Wave 2 — Insights surface polish
Six atomic commits landed for A1-A6: BP target headline re-anchor, BP tile feature parity, comparison-toggle moved to Settings only, layout grid normalisation, "Muster" rename + section tabs lifted above the hero, and the recommendation-card token-leak fix plus DE component-label normalisation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
59a4a89b62
commit
1c0cd8c87e
2 files changed
+179
-24
No files matched your search
+26
-24
@@ -1,7 +1,7 @@
|
||||
# v1.4.22 marathon — state log
|
||||
|
||||
Status: kickoff
|
||||
Last update: 2026-05-10T20:15+02:00
|
||||
Status: Wave 2 — Insights surface polish complete
|
||||
Last update: 2026-05-10T21:00+02:00
|
||||
|
||||
> Previous patch: v1.4.21 live (image digest
|
||||
> `sha256:4e818d44702c…`, `/api/version=1.4.21`).
|
||||
@@ -38,43 +38,45 @@ rewrite needs research first.
|
||||
|
||||
### A1 — BD-Zielbereich 5th attempt
|
||||
|
||||
- [ ] Decide framing: re-anchor headline to last-30d with all-time
|
||||
footnote, OR add explicit window label ("All-time: 11%
|
||||
in target")
|
||||
- [ ] Implement; integration test
|
||||
- Detailed report: `.planning/phase-A1-report.md`
|
||||
- [x] Headline re-anchored to `windows.last30Days?.pct`; all-time
|
||||
surfaced as `bpInTargetPctAllTime` for the new sub-row
|
||||
- [x] Integration test pins the route contract
|
||||
- Detailed report: `.planning/phase-W2-report.md`
|
||||
|
||||
### A2 — BD-Kachel feature parity
|
||||
|
||||
- [ ] Trend arrow + per-tile features the other tiles ship
|
||||
- [x] Trend arrow + 7-day-trend chip + comparison overlay parity
|
||||
(synthesised slope from 7d/30d delta); `<TrendCard>` extended
|
||||
with optional `avgAllTime` sub-value
|
||||
|
||||
### A3 — Comparison-Toggle → global Settings
|
||||
|
||||
- [ ] Move toggle from `/insights` AND `/dashboard` to
|
||||
`/settings/dashboard` as a single global preference
|
||||
- [ ] Persist in `User.dashboardWidgetsJson.compareOverlay`
|
||||
- [ ] Comparison overlay activates everywhere when enabled, off
|
||||
everywhere when disabled
|
||||
- [x] On-surface `<CompareToggle />` removed from `/insights`;
|
||||
Settings → Dashboard already carries the canonical picker
|
||||
- [x] `User.dashboardWidgetsJson.comparisonBaseline` field already
|
||||
exists (v1.4.16 phase B8) — every chart still consumes it
|
||||
|
||||
### A4 — Insights layout grid normalisation
|
||||
|
||||
- [ ] Row-fill rule: 1 card → 100% width, 2 cards → 50/50
|
||||
- [ ] Trends row equal heights via `min-h-…`
|
||||
- [ ] Hide empty-state cards entirely (insufficient-data tiles
|
||||
vanish, not render placeholder)
|
||||
- [x] Row-fill rule: BP / weight / medications grids collapse to
|
||||
full-width when only one card has data
|
||||
- [x] Trends row equal heights via `min-h-[300px]`
|
||||
- [x] `<CorrelationRow>` drops insufficient-data tiles; zero ok
|
||||
cards hides the row entirely
|
||||
|
||||
### A5 — Section heading + tab rename
|
||||
|
||||
- [ ] "Muster" → "Trends" or "Zusammenhänge" (DE-locale check)
|
||||
- [ ] "KI Gesundheitsanalyse" trim or restructure
|
||||
- [ ] Tabs (Allgemein / Blutdruck / Gewicht / Puls / Stimmung) lift
|
||||
above the Hero strip; in-place tab switch
|
||||
- [x] "Muster" → "Zusammenhänge" (DE) / "Patterns" → "Relationships"
|
||||
(EN). Picked Zusammenhänge over Trends because the row above
|
||||
already uses Trends.
|
||||
- [x] `<InsightsSectionNav>` lifted above the hero strip (sticky
|
||||
scroll-anchored; tabs visible first)
|
||||
|
||||
### A6 — Raw `Metrik <Type>` token leak fix
|
||||
|
||||
- [ ] Find token-emit point in AI response or template
|
||||
- [ ] Either substitute via existing PARSE_TOKEN_REGEX or strip via
|
||||
STRIP_TOKEN_REGEX (widen to lowercase `Metrik` if needed)
|
||||
- [x] `<RecommendationCard>` text wrapped in `stripChartTokens()`
|
||||
- [x] DE `componentMood`/`componentBp`/`componentCompliance` renamed
|
||||
to German nouns; i18n-integrity test pins the contract
|
||||
|
||||
## Wave 3 — Coach polish
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# Wave 2 — Insights surface polish (v1.4.22)
|
||||
|
||||
Started: 2026-05-10T20:40+02:00
|
||||
Completed: 2026-05-10T21:00+02:00
|
||||
Branch: develop (post-v1.4.21)
|
||||
Six atomic commits delivered to `origin/develop`.
|
||||
|
||||
## A1 — BD-Zielbereich headline re-anchor
|
||||
|
||||
`fix(analytics): BP target headline reads last-30d, all-time becomes sub-row`
|
||||
|
||||
Up to v1.4.19 the headline pinned to `windows.last30Days?.pct` — a
|
||||
literal copy of the 30T sub-value, producing the 50/50/50 algorithmic
|
||||
pin Marc reported. v1.4.19 A1 routed the headline to `allTime`, which
|
||||
fixed the pin but introduced a new emotional problem: the headline
|
||||
became the slowest-moving aggregate possible, punishing recent
|
||||
improvement.
|
||||
|
||||
v1.4.22 A1 re-anchors the route's `bpInTargetPct` to
|
||||
`windows.last30Days?.pct` and adds a new `bpInTargetPctAllTime` field
|
||||
so the long-arc number stays accessible as a sub-value. The
|
||||
`computeBpInTargetWindows()` helper is unchanged — only the route's
|
||||
headline pick changed.
|
||||
|
||||
**Files touched**: `src/app/api/analytics/route.ts`,
|
||||
`tests/integration/bp-in-target.test.ts` (two new integration cases
|
||||
pinning the headline source + all-three-windows envelope shape).
|
||||
|
||||
## A2 — BD-Kachel feature parity
|
||||
|
||||
`feat(insights): BP tile gains trend arrow + comparison overlay parity`
|
||||
|
||||
Up to v1.4.21 the BD-Zielbereich tile was the only one in the strip
|
||||
without a trend arrow, 7-day-trend chip, or comparison overlay caption.
|
||||
Synthesised a slope from the difference between the 7-day and 30-day
|
||||
in-target shares: 7d > 30d means recent improvement (up-good ⇒ green
|
||||
arrow). The 7-day-trend chip uses the same delta.
|
||||
|
||||
Extended `<TrendCard>` with optional `avgAllTime` / `avgAllTimeLabelKey`
|
||||
/ `avgAllTimeColorClass` props so the all-time aggregate stays visible
|
||||
next to `7d:` and `30d:` after the A1 re-anchor. Other tiles leave the
|
||||
field undefined so the third sub-row never renders for them.
|
||||
|
||||
**Files touched**: `src/components/charts/trend-card.tsx`,
|
||||
`src/components/charts/__tests__/trend-card-all-time.test.tsx` (new),
|
||||
`src/app/page.tsx`, `messages/{en,de}.json` (new
|
||||
`charts.avgAllTimeShort` key).
|
||||
|
||||
## A3 — Comparison-Toggle → global Settings
|
||||
|
||||
`feat(settings): comparison-overlay becomes a global dashboard preference`
|
||||
|
||||
Per `feedback_settings_no_split.md`, removed the on-surface
|
||||
`<CompareToggle />` from `/insights`. The canonical picker already
|
||||
lives in Settings → Dashboard (since v1.4.16 phase B8), and the
|
||||
`User.dashboardWidgetsJson.comparisonBaseline` field already persists
|
||||
the value. Every chart on `/insights` still reads the resolved
|
||||
baseline so flipping the Settings toggle propagates atomically.
|
||||
|
||||
**Files touched**: `src/app/insights/page.tsx` (removed the meta-slot
|
||||
`<CompareToggle />` from `<DailyBriefing>` and the matching import),
|
||||
`src/app/__tests__/insights-polish.test.ts` (new A3 describe-block
|
||||
asserting the toggle is gone but `compareBaseline` plumbing remains).
|
||||
|
||||
## A4 — Insights layout normalisation
|
||||
|
||||
`fix(insights): row-fill rule + equal-height trends + hide-on-insufficient-data`
|
||||
|
||||
Three related layout polish wins:
|
||||
|
||||
- **Row-fill rule**: BP-medication grid, weight-correlation grid, and
|
||||
medications-per-day grid all hard-coded `xl:grid-cols-2` /
|
||||
`sm:grid-cols-2`, leaving half-rows of empty space when one of the
|
||||
two slots wasn't populated. All three now switch to a single
|
||||
full-width column when fewer than 2 cards render.
|
||||
- **Trends row equal heights**: each `<TrendsRow>` card is a flex
|
||||
column with `min-h-[300px]` so a multi-line AI annotation under one
|
||||
chart doesn't push that card taller than its neighbours.
|
||||
- **Hide insufficient correlations**: `<CorrelationRow>` drops cards
|
||||
whose `status !== "ok"` instead of painting a placeholder. Zero ok
|
||||
cards hides the row entirely (no header, no disclaimer); 1 card
|
||||
spans 100 % width; 2-3 cards split 50/50 on `>=md`.
|
||||
|
||||
**Files touched**: `src/components/insights/correlation-row.tsx`,
|
||||
`src/components/insights/trends-row.tsx`,
|
||||
`src/components/insights/__tests__/correlation-row.test.tsx`,
|
||||
`src/app/insights/page.tsx`.
|
||||
|
||||
## A5 — "Muster" rename + section tabs lift
|
||||
|
||||
`feat(insights): rename "Muster" + lift section tabs above the hero`
|
||||
|
||||
- **DE rename decision**: "Muster" → "Zusammenhänge". The W1b research
|
||||
suggested "Trends" or "Zusammenhänge"; picked the latter because the
|
||||
row directly above the correlation row already uses "Trends" for the
|
||||
trend-annotation strip. "Zusammenhänge" carries the relational
|
||||
meaning a correlation surface needs without borrowing terminology
|
||||
from its neighbour.
|
||||
- **EN parallel**: "Patterns" → "Relationships" for the same reason.
|
||||
- **Tab lift**: `<InsightsSectionNav>` moved from below the advisor
|
||||
card to the top of the page. Sticky scroll-anchored behaviour
|
||||
preserved (incremental — a full SPA-tab rewrite is out of scope for
|
||||
v1.4.22 polish). The user lands on Allgemein by default and sees
|
||||
the metric-tab strip before scrolling.
|
||||
|
||||
**Files touched**: `messages/{en,de}.json`, `src/app/insights/page.tsx`,
|
||||
`src/components/insights/__tests__/correlation-row.test.tsx`,
|
||||
`src/components/insights/__tests__/correlation-card.test.tsx`.
|
||||
|
||||
## A6 — Token-leak fix + DE component labels
|
||||
|
||||
`fix(insights): strip metric tokens in recommendation prose + DE component labels`
|
||||
|
||||
W1a §3 traced the production "metric:WEIGHT" leak to
|
||||
`src/components/insights/recommendation-card.tsx:336`: every other
|
||||
AI-prose surface ran through `stripChartTokens()`, the recommendation
|
||||
text didn't. Two-line fix.
|
||||
|
||||
The bonus DE-locale defect: `componentMood: "Mood"` (English in the
|
||||
German file) plus the related `componentBp: "BD"` /
|
||||
`componentCompliance: "Medis"` abbreviations. Marc reported "Mut" —
|
||||
voice-to-text rendering of "Mood". All four labels now read natively
|
||||
German: `"Blutdruck"`, `"Gewicht"`, `"Stimmung"`, `"Einnahmetreue"`.
|
||||
|
||||
i18n integrity test added to pin the four DE component labels so a
|
||||
copy-paste regression can't reintroduce the leak.
|
||||
|
||||
**Files touched**: `src/components/insights/recommendation-card.tsx`,
|
||||
`src/components/insights/__tests__/recommendation-card.test.tsx`,
|
||||
`messages/de.json`,
|
||||
`src/lib/__tests__/i18n-locale-integrity.test.ts`.
|
||||
|
||||
## Test delta
|
||||
|
||||
| Layer | Before | After |
|
||||
| ----------- | ------ | ----- |
|
||||
| Unit (vitest) | 2036 tests / 238 files | 2049 tests / 239 files |
|
||||
| Integration | (covered separately) | +2 cases in `bp-in-target.test.ts` |
|
||||
|
||||
13 new unit cases + 2 new integration cases. All green.
|
||||
|
||||
## Decisions logged
|
||||
|
||||
- **DE rename**: "Zusammenhänge" over "Trends" — relational meaning,
|
||||
doesn't collide with the Trends-row title directly above.
|
||||
- **Section tabs**: kept the scroll-anchored sticky strip and lifted
|
||||
it above the hero. A full SPA-tab rewrite is deferred (v1.5+).
|
||||
- **All-time sub-value**: rendered as a third `<span>` inside the
|
||||
existing `flex gap-3` sub-row so on mobile it wraps naturally below
|
||||
`7d:` and `30d:` instead of crowding into a single line.
|
||||
- **Synthetic slope**: BP-tile uses `slope = (pct7 - pct30) / 30` as
|
||||
the slope-per-day signal. Confidence pinned to 1 because the source
|
||||
is a deterministic difference, not a regression fit.
|
||||
Reference in new issue
Block a user