From 9d0983dc223896c7993936445e80c2c2ac698cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 26 Apr 2026 22:33:43 +0200 Subject: [PATCH] docs(audit): add 2026-04-26 full-audit plan Captures the eight-phase remediation plan synthesized from the 2026-04-26 multi-agent audit covering Issues #87, #88, #89 and the broader codebase health (security, perf, reliability, i18n, a11y, test coverage, docs consistency). Plan reorders the original sequence to do the typed apiClient refactor (Phase 4) BEFORE the onboarding redesign (Phase 3.5), so the new flow inherits the .ok-checked client wrapper and the silent-failure class of bugs (root cause of #87) is structurally eliminated rather than re-introduced. Custom measurement types are explicitly deferred to v1.5+ via the Cronometer pattern when user demand crosses a threshold; v1.3 ships TBW + Bone Mass hardcoded (#89, Phase 3) following the v1.2 glucose blueprint. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/audit/2026-04-26-PLAN.md | 235 ++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 docs/audit/2026-04-26-PLAN.md diff --git a/docs/audit/2026-04-26-PLAN.md b/docs/audit/2026-04-26-PLAN.md new file mode 100644 index 000000000..a34f1784e --- /dev/null +++ b/docs/audit/2026-04-26-PLAN.md @@ -0,0 +1,235 @@ +# HealthLog Audit-Plan — Phasen-Übersicht +**Datum:** 2026-04-26 +**Audit-Bericht:** `~/infra/reports/healthlog-audit-2026-04-26.md` +**Status:** Geplant, noch nicht in Execution + +## Strategische Decisions + +1. **Onboarding-Redesign:** 4-Step-Wizard → 2-Step + Empty-State-Dashboard + Getting-Started-Card. +2. **Custom-Measurement-Types:** Pragmatic Hardcoding (jetzt #89), Cronometer-Pattern als v1.5+-Roadmap. +3. **`apiClient`-Refactor vor Onboarding-Redesign**, damit neues Onboarding nicht denselben Silent-Failure-Bug reproduziert. + +--- + +## Phasen-Sequenz (10 Phasen) + +| # | Phase | Skill | Effort | Issue-Bezug | +|---|---|---|---|---| +| **1** | Setup-Notfall + Versions-Sync | `gsd:quick` | ≤4 h | #87 (Pflaster), #88 | +| **2** | Docker-Image-Pipeline (GHCR-Push + Doc-Konsolidierung) | `gsd:plan-phase` | ~1 d | #88 | +| **3** | Issue #89 — TBW + Bone Mass hardcoded | `gsd:plan-phase` | ~1 d | #89 | +| **4** | `apiClient`-Wrapper + geteilte Zod-Schemas | `gsd:research-phase` → `gsd:plan-phase` | 2-3 d | #87 (strukturell) | +| **3.5** | **Onboarding-Redesign** (2 Steps, TTFV <60s) | `gsd:research-phase` → `gsd:plan-phase` | ~2 d | #87 (strukturell) | +| **4.5** | Empty-State-Dashboard-Tiles (eine pro Type) | `gsd:plan-phase` | ~1 d | #87, #89 | +| **4.6** | „Getting Started"-Checklist auf Dashboard | `gsd:plan-phase` | ~4 h | #87 | +| **5** | Security + Reliability Hardening | `gsd:plan-phase` | 1-2 d | — | +| **6** | Test-Coverage-Boost (Auth/Passkey/Withings/pg-boss) | `gsd:plan-phase` | 1-2 d | — | +| **7** | UX/i18n/a11y Polish (Residuals aus #73) | `gsd:quick` | ≤4 h | #73 (Residuals) | +| **8** | Settings-Page-Splitting (3143 → Sub-Components) | `gsd:research-phase` → `gsd:plan-phase` | 2-3 d | — | + +**Reihenfolge:** 1 → 2 → 3 → **4 (apiClient)** → **3.5 (Onboarding nutzt apiClient)** → 4.5 → 4.6 → 5 → 6 → 7 → 8. + +--- + +## Phase 1 — Setup-Notfall (≤4 h) + +**Goal:** Self-Hoster kann mit `git clone && docker compose up -d` tatsächlich starten + Issue #87 stoppt zu blocken. + +- `docker-compose.yml`: `expose:3000` → `ports: "3000:3000"` (DX-002) +- `docker-compose.yml`: `POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?required}"` (SEC-003) +- `.env.example`: `localhost:5432` → `db:5432` (DX-003) +- `package.json`: `"version": "1.2.0"` (DOC-007) +- `CLAUDE.md` + `AGENTS.md`: 23 Models statt 21/22 (DOC-001) +- `src/app/onboarding/page.tsx`: `await fetch(...)` mit `.ok`-Check + `toast.error()` (DX-005 Pflaster) +- README „minutes"-Claim entschärfen (DX-006) + +**Verification:** Frischer Clone + neue VM → `cp .env.example .env && set secrets && docker compose up -d` → App auf `http://localhost:3000` erreichbar in <5 min. + +--- + +## Phase 2 — Docker-Image-Pipeline (~1 d) + +**Goal:** Issue #88 vollständig schließen. Pre-built Image auf GHCR. Docs konsolidiert. + +- `.github/workflows/docker-publish.yml`: Multi-Arch (amd64+arm64), Trigger auf Tag-Push + main, GHCR-Push mit GITHUB_TOKEN +- `healthlog-docs/getting-started/installation.mdx` → Verweis auf `self-hosting/docker.mdx` statt Duplikat (DX-004) +- Repo-`docker-compose.yml` vs Docs-`docker-compose.yml`: eine Variante als Single-Source-of-Truth (Compose verwendet `image:` aus GHCR; Doc-Snippet wird daraus generiert) +- `SECURITY.md` + Image-Pull-Instructions +- `healthlog-landing` prüfen ob „Quick Start" aktualisiert werden muss + +--- + +## Phase 3 — Issue #89: TBW + Bone Mass (~1 d) + +**Goal:** User-Anfrage von Manschk3rl beantworten. Blueprint = v1.2-Glucose-Migration. + +**Touchpoints:** +1. `prisma/schema.prisma`: `MeasurementType` Enum + 2 neue Werte +2. `prisma/migrations/0019_*` (additive `ALTER TYPE ... ADD VALUE`) +3. `src/lib/validations/measurement.ts`: `unitMap`, `VALUE_RANGES` +4. `src/lib/format.ts` + i18n-Keys (4-6 neue Keys) +5. `src/lib/doctor-report-pdf.ts`: `TYPE_LABEL_KEYS`, `TYPE_UNIT_KEYS`, `vitalTypes` +6. `src/components/measurements/measurement-form.tsx`: `MEASUREMENT_TYPES` Array +7. `src/lib/withings/client.ts` + `sync.ts`: Mapping aus Withings `body_composition` +8. `src/app/api/dashboard/widgets/route.ts` + `DEFAULT_DASHBOARD_LAYOUT`: 2 neue Widget-IDs +9. healthlog-docs `health-metrics.mdx` aktualisieren +10. Optional: `/api/ingest/measurements` Bearer-Endpoint für n8n (analog `/api/ingest/medication`) + +--- + +## Phase 4 — `apiClient`-Wrapper (2-3 d) + +**Goal:** Silent-Failure-Pattern strukturell eliminieren. Voraussetzung für 3.5. + +- `src/lib/api-client.ts`: `apiClient.{get,post,put,delete}(path, body?, schema?) → Result` +- Auto-`.ok`-Check + Toast bei Fehler (mit i18n-Mapping) +- Geteilte Zod-Schemas: Validations-Module in `src/lib/validations/` werden client-importable +- Migration: alle ~50 `fetch(...)`-Aufrufe in `src/app/` und `src/components/` → `apiClient.*` +- Tests für `apiClient` (Mock-Fetch, Error-Mapping, Schema-Validation) + +--- + +## Phase 3.5 — Onboarding-Redesign (~2 d) + +**Goal:** TTFV <60s, 90% Completion (statt aktuell ~8% global). + +**Neuer Flow:** +- Step 1: Welcome + Anzeigename (optional) + Einheiten + Größe (optional). Sekundärlink „Skip everything → Dashboard". +- Step 2: Multi-Select-Chips „Was willst du tracken?" (Weight/BP/Pulse/Mood/Glucose/Sleep/Steps/BodyFat/TBW/BoneMass/Medications). Pre-checked Defaults. Speichert in `User.dashboardWidgetsJson`. +- Routing nach Onboarding: direkt zum Dashboard mit „Getting Started"-Card (s. Phase 4.6). + +**Entfällt ersatzlos:** Targets-Step, Notifications-Teaser-Step, Front-loaded-Med-Wizard. + +**Permissions deferred:** Notifications nur bei „Add medication → Reminder ein", PWA-Install nur bei Day-2-Return-Visit oder nach 3. Eintrag. + +--- + +## Phase 4.5 — Empty-State-Dashboard-Tiles (~1 d) + +**Goal:** Mealie/Tandoor/Linear-Pattern. Jede Metric-Tile hat Empty-State mit „Add your first X"-CTA. + +- `src/components/dashboard/EmptyStateTile.tsx` als wiederverwendbare Komponente +- Pro `MeasurementType`: eigene Empty-State-Card (Icon + Headline + CTA-Button → opens MeasurementForm prefilled) +- `Medications` analog: Empty-State „Add your first medication" → opens MedicationForm mit Default-Schedule (08:00–09:00 daily) +- Dashboard-Routing: User mit `dashboardWidgetsJson` ohne Daten sieht Empty-States, nicht leeres Grid + +--- + +## Phase 4.6 — „Getting Started"-Checklist (~4 h) + +**Goal:** Linear-Pattern. Persistent dismissible Card auf Dashboard. + +- `User.gettingStartedDismissedAt: DateTime?` (nullable, NULL = noch sichtbar) +- 4 Items mit Live-Status: + - ☐ Log your first measurement (✓ wenn ≥1 Measurement existiert) + - ☐ Add a medication (✓ wenn ≥1 Medication existiert) + - ☐ Set a target value (✓ wenn `thresholdsJson` non-empty) + - ☐ Install HealthLog as App (✓ wenn `beforeinstallprompt` accepted, sonst nur sichtbar bei PWA-fähigen Browsern) +- „Dismiss"-Button → setzt Timestamp, Card verschwindet +- Vollständig durchgeklickt → Card verschwindet automatisch + +--- + +## Phase 5 — Security + Reliability Hardening (1-2 d) + +- SEC-002: SSRF-IPv4-Parser-Härtung (führende Nullen) +- SEC-004: GitHub-PAT-Logging-Sanitization (Sensitive-Field-Filter) +- SEC-005: AES-256-GCM Key-Rotation-Doc + optional `keyVersion` +- SEC-006: Custom-Thresholds-Rate-Limit +- PERF-001: N+1 in `insights/comprehensive/route.ts:219` per `groupBy` auflösen +- REL-002: Onboarding-Error-Handling über Pflaster hinaus (via apiClient aus Phase 4) +- REL-003: pg-boss `process.on('SIGTERM', ...)` für graceful shutdown +- LOG-001: 6× `console.log()` in `src/lib/jobs/reminder-worker.ts` → Wide-Event-Annotations +- CI: ESLint+Typecheck `continue-on-error: true` → blocking + +--- + +## Phase 6 — Test-Coverage-Boost (1-2 d) + +Coverage 35% → 60% auf kritischen Pfaden. + +**Top-5-Test-Suites:** +1. Session-Validation Roundtrip (`src/lib/auth/session.ts`) +2. Passkey-Register/Verify-Integration (Attestation-Flow) +3. Notification-Dispatcher Multi-Channel (TELEGRAM/NTFY/WEB_PUSH) +4. pg-boss Reminder-Worker (Queue-Init, Job-Execution, Cron, Retry) +5. Withings-OAuth + Sync (`exchangeCode` → `refresh` → `fetchMeasurements`) + +**Plus:** TEST-006 Custom-Thresholds-Resolution, TEST-007 AI-Provider-SSRF. + +--- + +## Phase 7 — UX/i18n/a11y Polish (≤4 h) + +**Residuals aus Issue #73 + neue Findings:** +- I18N-002: Admin-Page Hardcoded „Web Push" / „Bug Report" → `t(...)` +- I18N-003: `medication-card.tsx` `Intl.DateTimeFormat("en-CA")` und `toLocaleString("en-US")` → `useFormatters()` +- A11Y-001: `phase-config-dialog.tsx` Text-Indicator neben Farb-Punkt (WCAG 1.4.1) +- A11Y-002: Admin StatusItem `aria-label` verifizieren +- UX-002: Admin-Save-Error-UX (Field-Highlight bei Fehler) +- UX-003: Bottom-Nav-Touch-Targets `p-2.5` → `p-3` (≥44×44 px) + +--- + +## Phase 8 — Settings-Page-Splitting (2-3 d) + +**Goal:** 3143 Zeilen → Sub-Components mit klarer Boundary. + +- Research-Phase: Sub-Routes (`/settings/profile`, `/settings/notifications`, …) vs Sub-Components in einer Page? +- Plan-Phase: Migrationsstrategie ohne URL-Bruch +- Insights-Page (1750 Zeilen) folgt analogem Pattern + +--- + +## Issue-Vorschlags-Abdeckung + +### Issue #87 (Onboarding-Meds verschwinden) +- **Pflaster:** Phase 1 (`.ok`-Check + Toast) +- **Strukturelle Lösung:** Phase 4 (`apiClient`) + Phase 3.5 (neuer Flow ohne Med-Wizard) + Phase 4.5 (Empty-State-Tiles, Meds direkt sichtbar) + Phase 4.6 (Checklist) + +### Issue #88 (Setup holprig — 4 Hauptpunkte + 3 Verbesserungsvorschläge) +| #88 Punkt | Phase | Status | +|---|---|---| +| (a) Bundled compose ≠ docs version | Phase 2 (DOC-005) | abgedeckt | +| (b) ghcr.io 404 (kein Image gepusht) | Phase 2 (DX-001 GHA-Workflow) | abgedeckt | +| (c) `.env.example` `localhost:5432` vs Compose `db:5432` | Phase 1 (DX-003) | abgedeckt | +| (d) `expose:3000` statt `ports:3000:3000` | Phase 1 (DX-002) | abgedeckt | +| Vorschlag: GHA für Image-Push (GHCR/Docker Hub) | Phase 2 | abgedeckt | +| Vorschlag: Merge „Getting started > Installation" + „Self-Hosting > Docker" | Phase 2 (DX-004) | abgedeckt | +| Vorschlag: Same presets across compose+docs+.env.example | Phase 1 + Phase 2 | abgedeckt | + +### Issue #89 (TBW + Bone Mass) +- Phase 3 hardcoded mit Withings-Mapping. Optional `/api/ingest/measurements` für n8n in Phase 3. + +### Issue #73 (UX-Liste mit 8 Punkten — CLOSED in v1.2) +| #73 Punkt | v1.2-Status | Residual? | +|---|---|---| +| 1. Passkey-Errors unklar | Resolved (Package C, typed errors) | TEST-002 (keine Tests) → Phase 6 | +| 2. Number-Formatting (DE 70,5 vs US 70.5) | Resolved (Package B, locale-aware formatters) | I18N-003 in `medication-card.tsx` → Phase 7 | +| 3. Chart-Labels „7P/30P/90P/All" | Resolved (Package A, „7 Pkt"/„7 pts") | — | +| 4. Dashboard nicht real-time | Resolved (`invalidateKeys` durchgängig) | — | +| 5. GitHub-Setup-Confusion | Resolved (Built-in Feedback in v1.2) | — | +| 6. Built-in Feedback | Done in v1.2 | — | +| 7. Admin-Save ohne Error-UX | Partial (Success-Msg ja, Error-Highlight schwach) | UX-002 → Phase 7 | +| 8. Blood-Sugar | Done in v1.2 | — | + +### Issue #74 (raw locale keys in UI) +Resolved in v1.2. + +--- + +## Time-to-First-Value (TTFV) Ziel + +Nach Onboarding-Redesign + Empty-State-Pattern: +- Login → Dashboard mit Empty-States: 2 Klicks +- Klick auf „Add your first weight" → Form prefilled „now" → Submit: 30 Sek +- **TTFV-Ziel: <60 Sekunden** (Branchenvergleich: führende Health-Apps 30-90s) + +## Anti-Patterns explizit vermieden + +- Permissions-Cold-Prompt (Notifications/PWA) auf Day 1 +- Front-loaded Targets vor erster Messung +- Multi-Step-Wizard mit identischem Skip-Button +- `fetch()` ohne `.ok`-Check (apiClient löst strukturell) +- Mega-Page-Pattern (Phase 8 löst Settings 3143 Z.) +- Custom-Measurement-Types vorzeitig (40-60h Wartung/Jahr für 50% Feature-Loss)