Files
HealthLog/docs/audit/2026-04-26-PLAN.md
T
Marc-André BombeckandClaude Opus 4.7 9d0983dc22 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>
2026-04-26 23:22:10 +02:00

11 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)