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) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 authored and Marc-André Bombeck committed 2026-04-26 23:22:10 +02:00
1 parent 068c40270f
commit 9d0983dc22
1 file changed
+235
+235
View File
@@ -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}<T>(path, body?, schema?) → Result<T, ApiError>`
- 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)