Long-overdue prettier sweep; prettier had drifted vs. checked-in code across ~130 files (mostly long-argument line-break wrapping in TS and table-padding in markdown). Pure formatting — no semantic changes — and required to clear the v1.4.6 release gate's `pnpm format:check`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 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)