From 781e6e180dce5aab6acc29e53ebf4584408ac319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 14 May 2026 21:46:41 +0200 Subject: [PATCH] docs(planning): W20-rest P6 polish phase report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shipped 3 of 11 P6 polish items in this wave under touch-disjoint constraints (W15 owns messages/*.json + system prompts, W18 owns charts core / errors / audit-log / deviceType / drift-guard / CSS anti-patterns / S1-S12): - P6-1 Pearson incomplete-beta replacement - P6-3 Coolify auto-deploy maintainer toggle - P6-8 GH translation-feedback issue template Deferred to v1.4.26 (8 items + rationale): - P6-2 sentinel HK envelopes (no parser yet) - P6-4 Coach lastYear window (spec ambiguity + 9 surfaces) - P6-5 Coach prefill on row tap (Wave 4b stable footprint) - P6-6 per-night sleep-stage chart (W18 surface + new endpoint) - P6-7 locale-native date formats (i18n only — W15) - P6-9 hand-review FR/ES/IT/PL prose (i18n only — W15) - P6-10 Cat-B endpoint triage (per-endpoint maintainer go/no-go; 7 recommendations enumerated in the report) - P6-11 mood verbal labels (already shipped in v1.4.25 polish pass; no remaining surfaces) --- .../phase-W20-rest-v1425-p6-polish-report.md | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 .planning/phase-W20-rest-v1425-p6-polish-report.md diff --git a/.planning/phase-W20-rest-v1425-p6-polish-report.md b/.planning/phase-W20-rest-v1425-p6-polish-report.md new file mode 100644 index 000000000..9cb2190a0 --- /dev/null +++ b/.planning/phase-W20-rest-v1425-p6-polish-report.md @@ -0,0 +1,151 @@ +# Phase W20-rest — v1.4.25 P6 polish cluster + +**Wave**: 5 cleanup (parallel with W15 + W18) +**Scope**: 11 P6 polish items per `.planning/v1426-backlog.md`. W20a (single-line headings + inline trend arrow + baseline alignment) already shipped earlier. +**Branch**: `develop` +**Pre-state**: `ab54d86b` (Wave 4b closeout) + +## Strict-rule reminders honoured + +- No `messages/*.json` edits (W15 territory). +- No CSS hsl(var) anti-pattern files, charts core, errors, audit-log, deviceType, drift-guard, simplifier S1-S12 (W18 territory). +- No Coach prompt YAMLs (W19c-Safety stable). +- No `Co-Authored-By: Claude` trailer. +- No `--no-verify`. + +## Shipped (3 commits) + +### 1. `c80e0a08` — P6-1 Pearson incomplete-beta replacement + +`fix(insights): exact Student's-t p-value via incomplete beta` + +Replaced the standard-normal approximation of the t-distribution tail with a rigorous regularised-incomplete-beta evaluation. Numerical Recipes §6.4 Lentz continued-fraction recurrence + Lanczos lnGamma prefactor. The two-sided p of |t| with df = I_x(df/2, 1/2) for x = df / (df + t^2); symmetric-tail identity I_x(a,b) = 1 - I_{1-x}(b,a) keeps the recurrence in its convergent half-plane. + +Reference values agree with R `pt()` to ~1e-4 across the surfacing range: +- r=0.5, df=18 → exact p ≈ 0.0248 (R: 0.02493, our impl: 0.02477). +- r=0.3, df=18 → exact p ≈ 0.1988 (R: 0.19905). +- r=0.7, df=18 → exact p ≈ 0.00059 (R: 0.00060). + +Side effect: the pre-existing "noise → no significance" fixture had r=-0.494 (a real moderate correlation) that the old normal-approx loosely accepted but the exact survival correctly flags as p≈0.027. Fixture regenerated against a truly orthogonal LCG-derived stream (r≈0.0005). + +Surface: `src/lib/insights/correlations.ts` + `src/lib/insights/__tests__/correlations.test.ts`. Three new exact-p reference tests. Touch-disjoint with W18 (chart files) and W15 (i18n). + +Tests: 20 pass (correlations.test.ts), 106 pass (all of `src/lib/insights/`). + +### 2. `e23dd28` — P6-3 Coolify auto-deploy maintainer toggle + +`ci(coolify): explicit maintainer toggle for auto-deploy gate` + +Three releases in a row shipped via host-side SSH retag fallback because the COOLIFY_WEBHOOK / COOLIFY_TOKEN secrets were absent, the deploy step warned and exited 0, and the workflow stayed green. Silent miss → surfaced only when `/api/version` refused to advance. + +`vars.COOLIFY_AUTO_DEPLOY` repository variable now disambiguates: +- `on` — fail the step when secrets are absent (loud catch on next release). +- `off` — unconditional skip with a notice (host-side SSH is the chosen path). +- unset / other — legacy warn-and-skip behaviour. + +Variable lives under repo Settings → Secrets and variables → Actions → Variables (intentionally readable). Backward-compatible default; flipping the toggle to `on` turns silent miss into a noisy error. + +Surface: `.github/workflows/docker-publish.yml`. YAML parse verified. + +### 3. `766b64e` — P6-8 GitHub translation-feedback issue template + +`feat(github): translation-feedback issue template` + +The maintainership banner that surfaces on AI-initial locales (FR / ES / IT / PL) linked to `issues/new?template=translation.md`, but the template file was never added to the repo. GitHub fell back to a blank issue body and the funnel produced unstructured reports. + +New `translation.yml` form asks for locale, the route/component where the wording appeared, current vs. suggested phrasing, and the kind of issue (grammar, register, terminology, ...). Banner URL moves to `template=translation.yml` to match the filename. + +Surface: `.github/ISSUE_TEMPLATE/translation.yml` (new), `src/components/i18n/maintainership-banner.tsx` (URL update only). No i18n string churn (banner copy is unchanged). + +Tests: 6 pass (`src/components/i18n/__tests__/maintainership-banner.test.tsx`); no test pins the URL. + +## Deferred to v1.4.26 (8 items + rationale) + +### P6-2 — Sentinel parser observability for HealthKit envelopes +**Defer.** No HealthKit envelope parser exists yet — the v1.4.23 schema landed but the actual XML / FHIR envelope parsing is scheduled for v1.5 iOS Swift sprint against the locked contracts. Extending the W5 `SentinelParseResult.malformedEntries[]` pattern to HealthKit envelopes is premature before the parser surface exists. Revisit when the iOS adapter (e.g. `/api/integrations/healthkit/import-envelope`) lands. + +### P6-4 — Coach `lastYear` window option +**Defer — spec ambiguity + UI integration spans i18n.** The enum extension in `src/lib/ai/coach/types.ts` is touch-disjoint clean, and `windowToDays()` could trivially map `lastYear → 365`. But: + +1. **Semantic ambiguity**. Chart-side `lastYear` (`src/lib/dashboard-layout.ts`) means "overlay the matching window from 365 days earlier" — a comparison shift, not a window length. Coach-side `last7days / last30days / last90days / allTime` are all window lengths from now back. `allTime` already caps the timeline at 365 days. A Coach `lastYear` should mean either: + - "the same 30-day window, shifted 365 days back" (comparison semantics), or + - "calendar year YYYY-1" (Jan 1 to Dec 31 of last year), or + - "rolling 365 days back from today" (which `allTime` already covers). + Each meaning produces a different snapshot prompt and a different system-prompt vocabulary update. + +2. **9 surfaces touch the existing union**: `mood-chart.tsx`, `mini-window.ts`, `generate-insight.ts`, `schema.ts`, `types.ts`, `coach/types.ts`, `coach/system-prompt.ts` (EN + DE — prompt-text grammar), `rationale-schema.test.ts`. Most are NOT Coach-specific (they're chart windows + insight dataWindow rationale). Extending only the Coach union without the others creates an inconsistent contract. + +3. **UI exposure requires `messages/*.json`** — the picker calls `t(\`insights.coach.window.${w}\`)`, which needs a `lastYear` key in six locales. W15 owns that file this wave. + +Recommend: Marc decides which semantic before v1.4.26 wave splits this into a feature-shaped phase rather than a polish item. + +### P6-5 — Coach prefill on health-score row tap +**Defer.** Per-row callback (tap row → "tell me more about my mood component") requires plumbing through the hero strip → health-score-card → coach drawer with a new prefill prop. The hero-strip surface touches Coach drawer state (`coach-drawer.tsx` is touched by the W19c-stable footprint). Risk of merge conflict with W19c-stable contracts and the broader Coach state machine. Revisit when the post-Wave-4b Coach surface is locked. + +### P6-6 — Per-night sleep-stage stacked column chart +**Defer.** Requires a new server endpoint that returns per-day × per-stage time series (the current `/api/insights/sleep` returns aggregate + perNight only for the trailing 7/14/30 nights). Touches `src/components/insights/sleep-stage-stacked-bar.tsx`, which is W18's surface this wave (P4-2 chart-tz audit). Cannot ship in parallel. Revisit in v1.4.26 alongside the iOS body-composition sub-page work. + +### P6-7 — Locale-native date format ordering +**Defer.** Sits entirely in `messages/{fr,es,it,pl}.json` `format.dateShort` / `format.timeShort` / `format.dateTime` keys. W15 territory this wave. + +### P6-9 — Hand-review FR/ES/IT/PL prose on high-traffic surfaces +**Defer.** Sits entirely in `messages/{fr,es,it,pl}.json`. W15 territory this wave. Per the v1.4.25 W9e report the prose rewrite is a per-locale L-effort item that wants a native-speaker review pass anyway — not a polish surface. + +### P6-10 — Cat-B endpoint triage +**Defer with maintainer recommendations.** The 17 endpoints flagged need human go/no-go per `.planning/research/w10-dead-code-candidates.md` §Category B. Listed below with my recommended action: + +| Endpoint | Recommendation | Rationale | +| --- | --- | --- | +| `/api/audit-log` (user-facing) | **wire UI in v1.4.26** | `/settings/audit-log` placeholder exists; the route returns the user's own entries — a natural settings sub-page. Cost: ~80 LOC for a paginated list. Documented as iOS-adapter-ready in the OpenAPI gate. | +| `/api/admin/ai-settings` (GET/PUT) | **delete** | Live admin AI surface is `/api/admin/ai-quality` (read-only). Settings are configured via env vars + AppSettings row directly. PUT path is a foot-gun (in-flight key rotation could break running providers). | +| `/api/admin/backup/test` (singular) | **delete** | Plural `/api/admin/backups/...` is the live family. Singular endpoint is a leftover. | +| `/api/admin/status-overview` | **keep** (verify inline-consumer first) | Admin dashboard likely consumes via React Query without a string-literal URL. Run a quoted-grep + a `git log` on `src/app/admin/` before deleting. | +| `/api/import` | **delete** | Tests cover it but no UI calls it. Looks like a deferred backup-restore sibling that never landed. If Marc decides it's a planned restore surface, mark with a `// PLANNED` comment. | +| `/api/settings/account` (DELETE account) | **delete OR wire** | Doc-comment promises cascading account deletion. No settings page exposes a delete-account button. GDPR-adjacent — Marc should wire a settings affordance for the iOS launch or delete the route. | +| `/api/monitoring/glitchtip/test` + `/api/monitoring/umami/test` | **delete the non-admin pair** | The `/api/admin/monitoring/{glitchtip,umami}-test` variants are the admin-callable surfaces (also orphan by static scan, but those are the ones an admin button would call). The non-admin pair is a duplicate. | + +No code touched here — endpoint deletion requires Marc's per-endpoint go-ahead. Recommendation feeds into a v1.4.26 dedicated cleanup phase. + +### P6-11 — Mood verbal labels follow-up +**Already shipped in v1.4.25; no remaining surfaces.** The v1.4.25 polish pass (W11 "Refactor" CHANGELOG bullet) wired: +- `MOOD_LABEL_KEYS` + `t(MOOD_LABEL_KEYS[mood])` in `src/components/mood/mood-list.tsx` (desktop table + mobile rows + Select filter). +- `formatMoodTick` + `formatTooltipValue` in `src/components/charts/mood-chart.tsx` (y-axis ticks + tooltip rows). +- Mood-form already uses `labelKey` per level. + +No remaining numeric-only mood display surfaces found in `src/components/` or `src/app/insights/`. The Coach key-value parser emits `mood: 4.1 [/5]` per the system-prompt grammar — that line is the prompt's load-bearing contract with the model, not user-facing UI. Effectively closed. + +## Test summary + +| Surface | Tests run | Pass | Fail | +| --- | --- | --- | --- | +| `src/lib/insights/__tests__/correlations.test.ts` | 20 | 20 | 0 | +| `src/lib/insights/` (full directory) | 106 | 106 | 0 | +| `src/components/i18n/__tests__/maintainership-banner.test.tsx` | 6 | 6 | 0 | +| `pnpm typecheck` (full repo) | — | clean | — | +| `pnpm lint` (full repo) | — | clean | — | + +## v1.4.26 backlog additions + +- **P6-1 follow-up**: now that exact incomplete-beta is in place, consider revisiting `MIN_PAIRED_N`. The v1.4.23 H6 raise from 14 to 20 was defence-in-depth for the loose normal approximation; the exact survival is accurate at every df. A future product-lead memo (post-iOS) may lower MIN_PAIRED_N back to 14 if usage data from the v1.4.16 B5e feedback aggregator shows users miss the borderline cards. Pin: the n=15 surfacing-gate test in `correlations.test.ts` is the touch-point. +- **P6-4 spec clarification**: Marc to decide `lastYear` semantics for Coach scope before the next P6 wave. +- **P6-5 plumbing prerequisite**: hero-strip → coach-drawer prefill callback hierarchy needs a Wave-4b post-mortem review first. +- **P6-10 endpoint cleanup phase**: dedicated phase to act on the 7 recommendations above, plus the audit-log UI wire-up. + +## Parallel-wave coordination notes + +During this work I observed multiple commits landing in parallel from other Wave-5 agents: +- `f16d4b3f fix(api): rename coach.batch.too_large to measurement.batch.too_large` (P4-3, W18). +- `fbb3c2c4 fix(charts): anchor sleep-stage day-tick Date to UTC` (P4-2 sleep-stage UTC; that commit incorrectly captured my Pearson incomplete-beta delta in the diff while carrying the sleep-stage message). The author then `git reset HEAD~1` and re-committed under `fddb9754 chore(i18n): drop 380 dead translation keys` (P1-1, W15). + +My subsequent Pearson commit (`c80e0a08`) re-staged the same Pearson delta cleanly with the correct message. The duplicate Pearson code in the abandoned `fbb3c2c4` is no longer in HEAD's ancestry (reset moved past it). Reflog entry `HEAD@{2}` documents the abandoned commit if archaeology is needed later. + +No destructive ops requested; no force-push; no rebase. All commits are forward-merge. + +## Files touched + +- `src/lib/insights/correlations.ts` (+~130 LOC: regularizedIncompleteBeta, betaContinuedFraction, lnGamma). +- `src/lib/insights/__tests__/correlations.test.ts` (+3 tests, 1 fixture regen). +- `.github/workflows/docker-publish.yml` (+29 LOC: COOLIFY_AUTO_DEPLOY var gate). +- `.github/ISSUE_TEMPLATE/translation.yml` (new, 81 LOC). +- `src/components/i18n/maintainership-banner.tsx` (URL `translation.md` → `translation.yml`). +- `.planning/phase-W20-rest-v1425-p6-polish-report.md` (this report).