Files
HealthLog/docs/audit/2026-04-26-PLAN.md
T
Marc-André BombeckandClaude Opus 4.7 02b99553b9 chore(style): repo-wide prettier sweep before v1.4.6 tag
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>
2026-05-09 02:27:17 +02:00

13 KiB
Raw Blame History

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)