docs(planning): W15 hygiene cluster phase report

Wave 5 hygiene wave summary covering the four atomic commits and
their verification matrix:
  fddb9754 — 380 dead i18n keys retired (25 false-positives kept)
  41c7425b — BASE_SYSTEM_PROMPT + INSIGHTS_SYSTEM_PROMPT removed
  61d156e7 — safeRequestProp narrow-catch + Tailwind @source base
  76dea94b — Cat-C comment path rewrite on /api/insights/targets
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 21:48:11 +02:00
1 parent 76dea94bae
commit 4ab0d17808
1 file changed
+296
+296
View File
@@ -0,0 +1,296 @@
# Phase W15 — v1.4.25 Hygiene Cluster
Branch: `develop` · Started: 2026-05-14 21:30 · Finished: 2026-05-14 21:50
## Scope
Four hygiene items rolled up under one wave so the locale-bundle and
api-handler surfaces do not get touched twice across parallel agents:
1. Dead i18n key cleanup from the W10 runtime probe (414 candidate
entries — actual removable count subject to per-key spot-check).
2. Deletion of the two `@deprecated` system-prompt constants
`BASE_SYSTEM_PROMPT` + `INSIGHTS_SYSTEM_PROMPT`.
3. W7d hardening: narrow `safeRequestProp` catch + Tailwind `@source`
path resolution anchored to the project root.
4. Cat-C typo polish on the `/api/insights/targets` route comment that
referenced a wrong-pathed prompt directory.
Touch boundary: this wave owned `messages/*.json` exclusively; W18 +
W20-rest stayed out of the locale files. The api-handler narrow-catch
+ `@source` change are dev-infra hardening, not user-visible.
## Commits
```
fddb9754 chore(i18n): drop 380 dead translation keys flagged in W10 runtime probe
41c7425b chore(ai): drop dead BASE_SYSTEM_PROMPT and INSIGHTS_SYSTEM_PROMPT constants
61d156e7 fix(api-handler): narrow safeRequestProp catch + harden @source path resolution
76dea94b chore: Cat-C typo and naming polish from W10 dead-code probe
```
## Commit 1 — Dead i18n key cleanup (`fddb9754`)
Started from the 405-entry list at `/tmp/w10-i18n-unused-list.txt`
generated by the W10 reconcile probe. A second-pass verification
script (`/tmp/w15-final-dead-keys.mjs`) re-scanned every `*.ts` /
`*.tsx` file under `src/` for exact-string matches in three quote
flavours (`"key"` / `'key'` / `` `key` ``). The verification surfaced
**25 false-positive dead-keys** — keys the static analyser missed
because they appear inside template literals or wrapped expressions
that the probe regex did not parse:
| key | live caller |
| --- | --- |
| `charts.heatmapLate` | `src/components/charts/compliance-heatmap.tsx:257` |
| `charts.heatmapOnTime` | same |
| `charts.heatmapVeryLate` | same |
| `charts.movingAverage7d` | `src/components/charts/health-chart.tsx:1566` |
| `charts.trend` | `src/components/charts/health-chart.tsx:1581` |
| `doctorReport.{avgBp,classification,createdOn,dateOfBirth,gender,height,patient,period,practiceLabel}` | `src/lib/doctor-report-pdf-core.ts` + sibling |
| `medications.{importDuplicatesSkipped,importInvalidSkipped}` | `src/app/medications/page.tsx` |
| `medications.{nextScheduleDaily,nextSchedulePrefix,scheduleEveryNWeeks,scheduleIntervalPrefix}` | `src/components/medications/medication-form.tsx` |
| `medications.{pausedSince,yesterday}` | `src/components/medications/{glp1-,}medication-card.tsx` |
| `telegram.{helpBody,helpHeader}` | `src/app/api/telegram/webhook/route.ts` |
| `thresholds.defaultLabel` | `src/components/settings/thresholds-editor-section.tsx` |
These 25 keys were KEPT in all six locales.
The other **380 keys** were removed from `messages/{de,en,fr,es,it,pl}.json`
in a single sweep via `/tmp/w15-remove-keys.mjs`, which walks each JSON
catalog, deletes the dotted-path entry, and prunes empty parent objects.
Per-locale stats: 380 keys removed, 0 not-found in every locale → the
catalogs were already in parity before this wave (W9d guards that
invariant) and stay in parity after.
The `i18n-locale-integrity` placeholder allow-list pruned four entries
that no longer exist in the catalogs:
`classifications.bp.Optimal`, `classifications.bp.Normal`,
`classifications.pulse.Normal`, `classifications.bodyFat.Fitness`.
The `targets.{bmi,label.BMI,status.*}` allow-list entries stay — those
keys are still live.
Top retired namespaces:
| namespace | removed | rationale |
| --- | --- | --- |
| `settings.*` | 114 | old setup screens, audit-log leaf labels, ai providerChain scaffolding |
| `classifications.*` | 69 | `alerts.*` block + legacy per-metric leaf labels; live UI uses `targets.*` now |
| `admin.*` | 38 | superseded by `admin/[section]` shell |
| `onboarding.*` | 21 | v1.4.22 onboarding rewrite leftovers |
| `medications.*` | 18 | (12 fewer than report — 12 were template-built) |
| `dashboard.*` | 15 | v1.4.20 insights redesign leftovers |
| `bugreport.*` | 12 | simplified bug-report screen |
| `common.*` | 12 | generic strings no longer referenced |
| `charts.*` | 15 | v1.4.20 chart-label slugs (5 fewer — template-built) |
| `auth.*` | 10 | auth UI now uses inline copy |
| `notifications.*` | 10 | toast variants no longer fired |
| `targets.*` | 9 | replaced by `targets.{label,status}.*` |
| `measurements.*` | 7 | stale list-row labels |
| `comparison.*` | 6 | `comparison.deltaVs.*` replaces |
| `mood.*` | 5 | mood-tag descriptors |
| `thresholds.*` | 4 | `thresholds.defaultLabel` kept (live in editor) |
| `achievements.*` | 4 | three more keys cycled out |
| `format.*` | 3 | unused helpers |
| `gettingStarted.*` | 2 | leftover quick-starts |
| `nav.*` | 1 | `nav.charts` |
Diff stats: each locale lost ~444 lines (380 keys + their JSON
container nesting closures). Total locale-file size: **2,484 lines
removed** across the six files in one atomic commit.
Spot-check confirmations the brief asked for:
- **`classifications.alerts.*` (26 keys)** — `git grep -F
'classifications.alerts' src` returns zero hits. Templated-prefix
patterns under `classifications.alerts.` are also zero (no
`\`classifications.alerts.${…}\`` in code). Safe to remove.
- **`targets.bloodPressure / targets.weight / targets.steps`** etc.
(9 keys) — all zero hits in `src/` and in `prisma/`. The iOS
shadow-key worry from the brief turned out to be unfounded; iOS
adapters use the `targets.label.*` / `targets.status.*` cluster
which is live.
### Verification
| step | result |
| --- | --- |
| `pnpm typecheck` | ✓ clean |
| `pnpm lint` | ✓ clean |
| `pnpm vitest run` (full unit) | ✓ 3731 / 3731, 1 skipped |
| `i18n-locale-integrity` | ✓ 26 / 26 (parity still holds) |
| `pnpm vitest run src/app/__tests__/targets-i18n.test.tsx` | ✓ 8 / 8 |
## Commit 2 — Dead prompt constants (`41c7425b`)
`BASE_SYSTEM_PROMPT` (in `src/lib/ai/prompts/base-system.ts:196`) and
`INSIGHTS_SYSTEM_PROMPT` (in `src/lib/insights/prompt.ts:106`) were
`@deprecated` since v1.4.20 with zero callers. The Coach surface
migrated to `getBaseSystemPrompt(locale)` + the per-locale
safety-contract matrix (W14c); the Insights surface uses
`getInsightsSystemPrompt(locale)` + per-page-type prompts from the
W3/W6 redesign. The backwards-compatibility re-exports outlived their
purpose.
Pre-removal `grep -rn 'BASE_SYSTEM_PROMPT[^_]|INSIGHTS_SYSTEM_PROMPT[^_]' src`
returned only the export-site definitions themselves — no consumers.
Post-removal, the same grep returns zero hits.
This also clears the Cat-C `@deprecated` markers that flagged both
constants in `.planning/research/w10-dead-code-candidates.md` §C1.
### Verification
| step | result |
| --- | --- |
| `pnpm typecheck` | ✓ clean |
| `pnpm lint` | ✓ clean |
| `pnpm vitest run src/lib/ai src/lib/insights` | ✓ 1205 / 1205 |
## Commit 3 — W7d hardening (`61d156e7`)
Both follow-ups from `.planning/phase-W7d-v1425-dev-server-repair-report.md`
+ `.planning/v1426-backlog.md` §P1-3:
### `safeRequestProp` narrow-catch
`src/lib/api-handler.ts` previously caught every exception from the
request-property read so logging instrumentation never crashed the
wrapped handler. A regression in a header parser or a `null` access
inside `read` would have silently fallen back to the default and
hidden the bug.
The catch is now narrowed to the V8 private-field `TypeError` that
Next 16 raises when the request is a synthetic placeholder on
`force-static` handlers. Three message-shape variants are recognised:
- `Cannot read private member #state …` (current V8)
- `Cannot read private field #x …` (alternative V8 wording)
- `Cannot read private name #y …` (Bun / older V8)
Every other `Error` rethrows. A new `__testables` export exposes the
helpers for the unit-test surface.
### `@source` path resolution
`src/app/globals.css` now imports Tailwind with an explicit
`source("../..")` base. Tailwind v4 auto-detection otherwise walks
up from the CSS file's resolver root, which can land at an unexpected
level depending on how the build tool reaches the stylesheet
(turbopack vs. webpack vs. test runner). Pinning the base mirrors the
cwd-anchored fix the safety-contracts loader landed in `commit dd44a0d`
so every build-tooling integration point reads paths from the same
root.
The `@source not "../../.planning"` + `@source not "../../docs"`
directives stay file-relative (Tailwind resolves them from the
stylesheet, not the source-base option) but their resolved targets
remain the same `<repo-root>/.planning` + `<repo-root>/docs` paths.
Source-base anchoring matters for auto-detection, not for the
explicit `not` exclusions.
### New tests
`src/lib/__tests__/api-handler-safe-request-prop.test.ts` — 9 tests:
- `isPrivateFieldAccessError`: the three message wordings classify
match; generic TypeErrors, RangeErrors, bare Errors, and non-Error
values classify non-match.
- `safeRequestProp`: returns the read result on a normal request,
returns the fallback on a private-field TypeError, rethrows any
other exception (downstream parser, undefined-is-not-a-function,
out-of-range). Null-fallback variant covered for header lookups.
### Verification
| step | result |
| --- | --- |
| `pnpm typecheck` | ✓ clean |
| `pnpm lint` | ✓ clean |
| `pnpm vitest run src/lib/__tests__/api-handler-safe-request-prop.test.ts` | ✓ 9 / 9 |
| `pnpm vitest run src/lib/__tests__/require-auth-bearer.test.ts` | ✓ 10 / 10 (regression unaffected) |
## Commit 4 — Cat-C typo polish (`76dea94b`)
`.planning/research/w10-dead-code-candidates.md` §C2 flagged a comment
at `src/app/api/insights/targets/route.ts:807`:
```
// general-status}.ts already enforce this attribution; this
```
On re-read this is not a stray-brace typo — it is a brace-expansion
abbreviation `{base-system, general-status}.ts`. The substantive bug
is that the path `src/lib/insights/prompts/` does not exist; the
referenced files actually sit under `src/lib/ai/prompts/`. The
comment is now rewritten in full path form so future readers can
grep the referenced files directly.
This was the only Cat-C typo entry in the dead-code report (§C1
items were the prompt-constant `@deprecated` markers, already
cleared by commit 2; §C3 has no findings).
### Verification
| step | result |
| --- | --- |
| `pnpm typecheck` | ✓ clean |
| `pnpm lint` | ✓ clean |
| `grep -rn 'general-status}' src` | ✓ zero hits |
| `grep -rn 'insights/prompts/' src` | ✓ zero hits |
## Numbers
- **380** dead i18n keys removed from each of six locale files
(= 2 280 catalog entries gone, ~444 file-line deletions per locale).
- **25** keys originally flagged as dead but verified live and kept.
- **5** keys whose only references were in `__tests__/` allow-lists
(4 in `i18n-locale-integrity`, 1 in `sections.test.tsx` —
`settings.export` test asserts the key's *absence*, so the test
is unaffected by removing the catalog entry).
- **2** dead `@deprecated` constants removed.
- **1** narrow-catch helper added with 9 unit tests.
- **1** Tailwind `@source` base pinned to project root.
- **1** comment path rewritten to real file locations.
## Tests delta
| suite | before | after |
| --- | --- | --- |
| unit | 3 731 passed, 1 skipped | 3 740 passed, 1 skipped |
| ↳ new in `api-handler-safe-request-prop.test.ts` | — | 9 |
| i18n-locale-integrity | 26 | 26 (parity intact) |
No integration / Playwright suite touched; no schema / migration
changes; no Coach prompt YAML touched.
## Touch-disjoint compliance
| surface | who | mine? |
| --- | --- | --- |
| `messages/*.json` | W15 | yes (exclusive owner) |
| `src/lib/ai/prompts/base-system.ts` | W15 | yes |
| `src/lib/insights/prompt.ts` | W15 | yes |
| `src/lib/api-handler.ts` | W15 | yes |
| `src/app/globals.css` | W15 | yes |
| `src/app/api/insights/targets/route.ts` | W15 | yes (comment only) |
| `src/lib/__tests__/api-handler-safe-request-prop.test.ts` | W15 | yes (new) |
| `src/lib/__tests__/i18n-locale-integrity.test.ts` | W15 | yes (allow-list prune) |
Parallel agents' work observed in the same window (W18 / W20-rest)
was left untouched — `glp1-medication-card.tsx`,
`maintainership-banner.tsx`, the new `translation.yml` issue template,
the `pickCanonicalSourceRows` simplifier, etc. None of those collide
with this wave's diff.
## Deferrals
None. Every Wave-15 scope item landed. Remaining v1.4.26 hygiene
budget left for future waves:
- P4-3 `coach.batch.too_large` rename already landed in `f16d4b3f`
by a parallel agent.
- P4-4 `detectGlp1Plateau` direct-test coverage already landed in
`c2e5c860` by a parallel agent.
- Other P4-* / P6-* items remain as documented in
`.planning/v1426-backlog.md`.