From 363e73cbf283d54fee02859c8d2079ca25283974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 10 May 2026 21:46:32 +0200 Subject: [PATCH] chore: voice + filename hygiene + wave-4 state wrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer-voice sweep across `src/`. ~190 references to the maintainer by first name in code comments and JSDoc neutralised to "the maintainer", "the live tenant", "users", or "the user" depending on context. Three sed passes covering ~50 distinct patterns (`Marc reported`, `Marc wants`, `Marc kept`, `Marc rolled back`, etc.). Test fixtures (`displayName: "Marc B."`, `username: "marc"`, ``) kept as opaque test data per the brief. CHANGELOG normalisation. The v1.4.14 + v1.4.15 entries paired a German bullet with its English translation by historical design — the in-app copy is English-default and the user docs are English, so the mixed-language style was a holdover from the early-marathon notes. A one-off node script extracted the `_..._` English paragraph from each bilingual bullet and rewrote the entry as that text. ~44 bullets reduced; one Docs bullet that lost a leading word in the regex fixed by hand. CLAUDE.md → CONTRIBUTING-AI.md rename so the filename is no longer AI-vendor-specific. `CONTRIBUTING.md` reference updated. `AGENTS.md` stays in place for multi-agent compatibility (the conventions doc is shared with Codex / Cursor / etc.). Wave-4 state wrap. STATE.md now records C1–C5 + D + D2 as complete; v1421-backlog.md grew a "Closed in v1.4.22" header listing the 20 items the marathon landed; `.planning/phase-W4-report.md` documents the per-commit decisions, test deltas, and the 23 items deferred to v1.4.23 with reasoning. Co-Authored-By: Claude Opus 4.7 (1M context) --- .planning/STATE.md | 50 +- .planning/phase-W1a-report.md | 508 ++++++++++++++ .planning/phase-W4-report.md | 156 +++++ .planning/v1421-backlog.md | 46 ++ CHANGELOG.md | 621 ++++++------------ CLAUDE.md => CONTRIBUTING-AI.md | 0 CONTRIBUTING.md | 2 +- src/app/__tests__/insights-polish.test.ts | 6 +- src/app/__tests__/targets-i18n.test.tsx | 4 +- src/app/__tests__/targets-spacing.test.tsx | 2 +- src/app/api/ai/test/__tests__/route.test.ts | 4 +- src/app/api/ai/test/route.ts | 2 +- .../dashboard/chart-overlay-prefs/route.ts | 2 +- src/app/api/dashboard/widgets/route.ts | 2 +- .../insights/generate/__tests__/route.test.ts | 2 +- src/app/api/internal/deploy-webhook/route.ts | 4 +- src/app/globals.css | 2 +- src/app/page.tsx | 14 +- src/app/targets/page.tsx | 4 +- .../admin/__tests__/admin-shell.test.tsx | 2 +- .../__tests__/api-token-no-collapse.test.tsx | 2 +- .../api-token-overview-responsive.test.tsx | 2 +- .../__tests__/api-token-truncate.test.tsx | 4 +- .../admin/__tests__/status-card-grid.test.tsx | 2 +- src/components/admin/admin-shell.tsx | 2 +- .../admin/api-token-overview-section.tsx | 4 +- .../__tests__/chart-overlay-controls.test.tsx | 2 +- .../health-chart-overlay-defaults.test.tsx | 2 +- .../__tests__/health-chart-polish.test.tsx | 2 +- .../__tests__/mood-chart-polish.test.tsx | 4 +- .../__tests__/touch-action-guard.test.ts | 2 +- .../__tests__/trend-card-7d-trend.test.tsx | 2 +- src/components/charts/chart-empty-state.tsx | 2 +- .../charts/chart-overlay-controls.tsx | 4 +- src/components/charts/health-chart.tsx | 4 +- src/components/charts/mood-chart.tsx | 4 +- src/components/charts/trend-card.tsx | 2 +- .../__tests__/insight-advisor-card.test.tsx | 2 +- .../__tests__/recommendation-card.test.tsx | 2 +- .../layout/__tests__/sidebar-nav.test.tsx | 4 +- src/components/onboarding/tour.tsx | 2 +- .../settings/__tests__/ai-section.test.tsx | 2 +- .../integration-status-pill.test.tsx | 2 +- .../__tests__/integrations-section.test.tsx | 6 +- src/components/settings/about-section.tsx | 2 +- src/components/settings/ai-section.tsx | 4 +- .../settings/dashboard-layout-section.tsx | 6 +- .../settings/integration-status-pill.tsx | 4 +- .../settings/integrations-section.tsx | 6 +- .../settings/notification-status-card.tsx | 2 +- src/components/ui/__tests__/tabs.test.tsx | 2 +- src/components/ui/tabs.tsx | 2 +- src/lib/__tests__/dashboard-layout.test.ts | 10 +- src/lib/__tests__/geo.test.ts | 2 +- .../ai/__tests__/citation-enforcement.test.ts | 2 +- .../ai/__tests__/codex-slug-fallback.test.ts | 2 +- .../ai/__tests__/confidence-wrapper.test.ts | 2 +- .../legacy-payload-detection.test.ts | 4 +- .../no-default-positivity-opener.test.ts | 2 +- src/lib/ai/confidence.ts | 2 +- src/lib/ai/generate-insight.ts | 2 +- src/lib/ai/legacy-payload.ts | 2 +- src/lib/ai/prompts/insight-generator.ts | 2 +- src/lib/ai/provider-runner.ts | 2 +- src/lib/ai/schema.ts | 8 +- .../analytics/__tests__/bp-in-target.test.ts | 26 +- .../analytics/__tests__/window-trend.test.ts | 4 +- src/lib/analytics/bp-in-target.ts | 4 +- src/lib/analytics/trend-delta.ts | 4 +- src/lib/analytics/window-trend.ts | 2 +- src/lib/dashboard-layout.ts | 6 +- src/lib/geo.ts | 2 +- .../insights/__tests__/chart-tokens.test.ts | 2 +- src/lib/insights/chart-tokens.ts | 2 +- .../__tests__/admin-alert.test.ts | 2 +- src/lib/jobs/host-metric-sampler.ts | 2 +- 76 files changed, 1050 insertions(+), 575 deletions(-) create mode 100644 .planning/phase-W1a-report.md create mode 100644 .planning/phase-W4-report.md rename CLAUDE.md => CONTRIBUTING-AI.md (100%) diff --git a/.planning/STATE.md b/.planning/STATE.md index 304bb12a6..55da2e290 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -1,7 +1,7 @@ # v1.4.22 marathon — state log -Status: Wave 3 — Coach polish complete -Last update: 2026-05-10T21:00+02:00 +Status: Wave 4 — backlog cleanup + bug closure complete +Last update: 2026-05-10T21:45+02:00 > Previous patch: v1.4.21 live (image digest > `sha256:4e818d44702c…`, `/api/version=1.4.21`). @@ -119,41 +119,51 @@ rewrite needs research first. ### C1 — Zielwerte page upgrade -- [ ] Visual + UX brought to the v1.4.20 polish bar +- [x] 30-day sparkline + Δ-vs-last-month caption per card (Direction + A from W1a §4). Reuses shared HealthChart styling via a + dependency-free inline SVG; API returns `points30d` + + `deltaVsLastMonth`. BMI derives both from the weight series. ### C2 — Admin / api-tokens scrollbar 5th attempt -- [ ] Live Playwright probe: which element actually overflows -- [ ] Patch the right layer this time +- [x] Live Playwright probe confirmed `whitespace-nowrap` on the date + ``s was the residual culprit (W1a §2). Dropped the two + classes; date+time wraps to two lines on narrow viewports. ### C3 — Coolify image-digest auto-deploy -- [ ] Wire watchtower-via-Coolify path OR enable Coolify - image-digest checkbox -- [ ] Validate end-to-end: tag push → fresh image deployed without - host-side retag +- [x] Workflow appends `?force=true` to the deploy webhook so doc-only + pushes still trigger a registry-digest check. Maintainer-side + toggle ("Watch image registry for new digests") documented in + `.planning/coolify-auto-deploy-howto.md` — one UI flip. ### C4 — AuthShell post-hydration redirect flicker -- [ ] Move null-`onboardingCompletedAt` redirect to `proxy.ts` - server-side, OR gate dashboard render on the field +- [x] Redirect lives in `proxy.ts`. Auth routes mirror + `onboardingCompletedAt` into a non-httpOnly `hl_onboarding` + cookie so the proxy can short-circuit before hydration. The + e2e onboarding-flicker spec dropped its workaround. ### C5 — node-26-alpine bump -- [ ] Investigate the dependabot PR that failed; either patch the - Dockerfile to support node 26 or close the PR with reasoning +- [x] Dependabot PR #162 closed with reasoning. node:26-alpine ships + without corepack pre-installed; Next.js 16 also still pins + node 22 LTS as the supported minimum. Reopen when the framework + bumps its node-version requirement. ### D — `.planning/v1421-backlog.md` cleanup wave Apply or confirm-defer every item: -- [ ] Phase D reconcile carry-over (22 MED + 16 LOW + 4 simplify-maybe) -- [ ] F6 docs site audit (9 MED + 5 LOW) -- [ ] F5 best-practice (5 LOW) -- [ ] FX carry-overs: - - [ ] 191 `Marc` references in `src/` source comments - - [ ] DE+EN bilingual CHANGELOG entries normalised to EN - - [ ] `CLAUDE.md` + `AGENTS.md` filename rename or retire decision +- [x] Phase D reconcile carry-over (22 MED + 16 LOW + 4 simplify-maybe) +- [x] FX carry-overs: + - [x] 191 `Marc` references swept across `src/` (test fixtures kept + as opaque test data per brief) + - [x] DE+EN bilingual CHANGELOG entries (v1.4.14 + v1.4.15) reduced + to English-only + - [x] `CLAUDE.md` renamed to `CONTRIBUTING-AI.md` so the filename + is not AI-vendor-specific; `AGENTS.md` stays for multi-agent + compatibility. `CONTRIBUTING.md` reference updated. ### E2E fix wave (already on develop) diff --git a/.planning/phase-W1a-report.md b/.planning/phase-W1a-report.md new file mode 100644 index 000000000..229d5530a --- /dev/null +++ b/.planning/phase-W1a-report.md @@ -0,0 +1,508 @@ +# Wave 1a — PROD probe + token-leak hunt + backlog inventory (v1.4.22) + +Started: 2026-05-10T20:20+02:00 +Branch: develop (post-v1.4.21) +Production probed: `https://healthlog.bombeck.io` (image digest +`sha256:4e818d44702c…`, v1.4.21) +Probe scripts (read-only, no commits): +- `.planning/v1422-w1a-tokens-probe.mjs` (admin/api-tokens overflow walk) +- `.planning/v1422-w1a-insights-probe.mjs` (Insights `Metrik` / token leak hunt) +- `.planning/v1422-w1a-targets-probe.mjs` (Zielwerte page screenshot) + +## 1. BD-Zielbereich tile + +### What I see today + +`src/components/charts/trend-card.tsx` paints the BD tile in the +shared `` chrome: + +- **Headline (3xl bold):** `latest = data.bpInTargetPct` — routed through + `windows.allTime?.pct` since v1.4.19 A1. For the live tenant this is + ~10.8 % rounded to 11 %. +- **Sub-row (`mt-auto` muted text-xs):** `7d: · 30d: ` — + `data.bpInTargetPct7d / pct30d`. For the live tenant, both 50 %. +- No window label on the headline. The tile reads **"11 % … 7d 50 30d 50"**, + a juxtaposition that punishes the recent improvement: the headline + is the story (large + bold) but represents the slowest-moving + aggregate possible (every paired BP reading since 2022). + +The cognitive flow is "your number is 11 %" → "wait, the recent +windows say 50 %" → "which one is real?". Even after grok-ing that the +headline is all-time, the reader is left with a number that is +technically correct but emotionally wrong: a user who put in real +recent work to drag the rate up sees an unmoving aggregate that +takes years of continued discipline to budge. + +### Three proposals (recommend one) + +#### A) Re-anchor headline to last-30d, surface all-time as a sub-value + +Pro: +- The dashboard tile becomes a "what's happening now" widget, matching + every other tile on the page (weight, pulse, mood — all show recent + reading + 7d/30d). The BD tile is currently the only one whose + headline is an all-time number. +- The 50 % feels earned; users see "improving" as movement and the + sub-line gives historical context without dominating. +- One-line API change: `bpInTargetPct = windows.last30Days?.pct` plus + a new field `bpInTargetPctAllTime` for the sub-row. + +Con: +- Inverts the v1.4.19 A1 fix story. Need a backlog entry to clarify + that the v1.4.19 fix was about *correctness* (the headline was + silently equal to the 30-day) and v1.4.22 is about *framing* (the + all-time aggregate belongs in the small text). +- Cache invalidation on the `analytics` query (existing TanStack + pattern handles this). + +#### B) Explicit window label "Allzeit / All time" on the headline + +Pro: +- Smallest diff. Add a `{t( + "dashboard.bpInTargetAllTimeLabel")}` next to the 11 %. No + data-shape change, no API change. +- Honours the v1.4.19 A1 contract verbatim — the headline still + surfaces the slowest-moving number. + +Con: +- The cognitive dissonance remains: the user still reads the big + number first and the label second. A 50 % → 50 % → 11 % "punishment + reading" persists; the label only confirms why. +- No other tile on the page carries a window label on its headline, + so this is a one-off pattern. + +#### C) Three-window chip row (no headline) — replace big-number layout + +Pro: +- Renders the tile as three equal-weight chips: `7d 50 % · 30d 50 % · + Allzeit 11 %`. No single number dominates; the user picks the + window that matters to them. +- Visually distinct enough to break the "every TrendCard is identical" + uniformity, which signals to the user that BP-in-target is a + conceptually different metric than weight / pulse. + +Con: +- Largest design lift. Diverges from the shared `` chrome + used everywhere else on the dashboard, so either (a) BD tile gets + a bespoke `` component, or (b) the shared TrendCard + picks up an optional "windowed mode". Either is a Wave-2/3 redesign + item, not a Wave-4 polish patch. +- Mobile tile is already dense at 50 %/50 % — fitting three chips + is a viewport challenge. + +### Recommendation: **A** (re-anchor to last-30d, all-time → sub-row) + +Smallest UX win for the smallest engineering lift, and aligns the BD +tile with every other tile on the dashboard. The all-time number +stays visible as a sub-value alongside `7d` and `30d`, so power users +can still reference it. The v1.4.19 A1 contract is preserved (the +all-time aggregate is computed and surfaced) — only the visual +priority changes. The fix is roughly: + +- `src/app/api/analytics/route.ts`: split `bpInTargetPct` (allTime) + → keep as `bpInTargetPctAllTime`; promote `windows.last30Days.pct` + to the new `bpInTargetPct` headline. +- `src/components/charts/trend-card.tsx` — extend `` with + an optional `tertiaryAvg` slot OR (cleaner) accept a third + sub-value pair (`avgAllTime`, `avgAllTimeLabel`) and render it as + `· Allzeit: %` next to `7T:` and `30T:`. Keep the change + optional + behind feature-detection so other tiles don't pick up + the third row. +- Tests: `bp-in-target.test.ts` already pins the three-window + contract; add a `` test that asserts the headline + matches the 30-day pct when both are passed. + +## 2. Admin / api-tokens scrollbar + +### Per-viewport probe results + +| Viewport | viewport×inner | doc overflow | body overflow | Real culprit | +| ------------------- | -------------- | ------------ | ------------- | ------------------------------------------ | +| iphone-se 375×667 | 375 / 375 | NO | NO | (none — only the intentional `nav.no-scrollbar` mobile section strip) | +| pixel5 393×851 | 393 / 393 | NO | NO | (none — same intentional nav) | +| ipad-mini 768×1024 | 768 / 768 | NO | NO | `