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>
11 KiB
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
- Onboarding-Redesign: 4-Step-Wizard → 2-Step + Empty-State-Dashboard + Getting-Started-Card.
- Custom-Measurement-Types: Pragmatic Hardcoding (jetzt #89), Cronometer-Pattern als v1.5+-Roadmap.
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_TOKENhealthlog-docs/getting-started/installation.mdx→ Verweis aufself-hosting/docker.mdxstatt Duplikat (DX-004)- Repo-
docker-compose.ymlvs Docs-docker-compose.yml: eine Variante als Single-Source-of-Truth (Compose verwendetimage:aus GHCR; Doc-Snippet wird daraus generiert) SECURITY.md+ Image-Pull-Instructionshealthlog-landingprü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:
prisma/schema.prisma:MeasurementTypeEnum + 2 neue Werteprisma/migrations/0019_*(additiveALTER TYPE ... ADD VALUE)src/lib/validations/measurement.ts:unitMap,VALUE_RANGESsrc/lib/format.ts+ i18n-Keys (4-6 neue Keys)src/lib/doctor-report-pdf.ts:TYPE_LABEL_KEYS,TYPE_UNIT_KEYS,vitalTypessrc/components/measurements/measurement-form.tsx:MEASUREMENT_TYPESArraysrc/lib/withings/client.ts+sync.ts: Mapping aus Withingsbody_compositionsrc/app/api/dashboard/widgets/route.ts+DEFAULT_DASHBOARD_LAYOUT: 2 neue Widget-IDs- healthlog-docs
health-metrics.mdxaktualisieren - Optional:
/api/ingest/measurementsBearer-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 insrc/app/undsrc/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.tsxals wiederverwendbare Komponente- Pro
MeasurementType: eigene Empty-State-Card (Icon + Headline + CTA-Button → opens MeasurementForm prefilled) Medicationsanalog: Empty-State „Add your first medication" → opens MedicationForm mit Default-Schedule (08:00–09:00 daily)- Dashboard-Routing: User mit
dashboardWidgetsJsonohne 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
thresholdsJsonnon-empty) - ☐ Install HealthLog as App (✓ wenn
beforeinstallpromptaccepted, 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:219pergroupByauflö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()insrc/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:
- Session-Validation Roundtrip (
src/lib/auth/session.ts) - Passkey-Register/Verify-Integration (Attestation-Flow)
- Notification-Dispatcher Multi-Channel (TELEGRAM/NTFY/WEB_PUSH)
- pg-boss Reminder-Worker (Queue-Init, Job-Execution, Cron, Retry)
- 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.tsxIntl.DateTimeFormat("en-CA")undtoLocaleString("en-US")→useFormatters() - A11Y-001:
phase-config-dialog.tsxText-Indicator neben Farb-Punkt (WCAG 1.4.1) - A11Y-002: Admin StatusItem
aria-labelverifizieren - 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/measurementsfü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)