Commit Graph
14 Commits
Author SHA1 Message Date
Marc-André Bombeck 3a45fcc3bd fix(ui): close remaining consistency-audit findings
Sweeps the banned gap-5/space-y-5/pl-5 spacing residue the audit found
scattered across coach pages, settings coach cards, the labs/records/
custom-metrics form family, onboarding, and a handful of one-off
surfaces (glucose panel, clinician view, illness correlation card,
invite-tokens, the mental-health check-in wizard including its stray
sm:p-6 shell steps) down onto the 4/6 scale.

Two medication chart colours (dose-strength-curve, drug-level-chart)
painted raw --dracula-purple with no light-theme override; both now
read --chart-1, the same alias the mood-chart fix already established.

Raises ~40 sub-text-xs muted-prose sites below the UI-STANDARDS §3/§5
floor (settings API/MCP/sharing/coach-prefs cards, insights workout
detail, suggested prompts, tab-strip group popovers, sources rail,
metric-stat-strip, the health-score provisional pill) to text-xs,
leaving genuine tabular-nums Badges untouched — the one exemption the
standard carries.

Routes the 10 hand-rolled settings/insights "no data yet" paragraphs
(coach memory, coach reminders, security sessions/activity/keys, API
tokens, sharing, insights pill order, per-metric overrides, nutrient
intake) through the shared EmptyState primitive instead of a bare
muted <p>, so they read the same, centered, icon-ready shape every
other empty list in the app already uses. The notification-status
card's empty copy stays on SettingsCardHeader's description slot —
converting it would mean restructuring the card, not routing it.
2026-07-18 03:52:32 +02:00
Marc-André Bombeck ced4e45272 docs(self-hosting): reconcile DATABASE_URL/POSTGRES_PASSWORD guidance with the compose contract
The bundled compose constructs DATABASE_URL inside the app service from
POSTGRES_PASSWORD, so the getting-started 'edit CHANGE-ME' step was a
no-op and .env.example's literal POSTGRES_PASSWORD collided with the
quick-start's appended one. Comment both out in .env.example so the
append pattern yields a single key, and rewrite step 2 to state compose
derives the URL. Add a first-user note to close public registration on
an internet-exposed host and to save the password (no-SMTP CLI reset).
Refresh the stale onboarding step-2 source comment.
2026-07-08 23:34:50 +02:00
Marc-André Bombeck 1f4ba5939d style: apply prettier across the tree
One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.
2026-06-19 22:20:31 +02:00
Marc-André Bombeck 68033b2d66 refactor(onboarding): point consumers at the kebab-case component files
Update the onboarding step page, baseline form, and the anamnesis-card
spec to import from the renamed kebab-case files. Export identifiers stay
unchanged.
2026-06-19 22:17:34 +02:00
Marc-André Bombeck 3b480b9885 perf(i18n,shell): ship one locale and stop blocking the first paint
The client bundle carries only the active locale — resolved on the
server and handed to the provider with no hydration flash, halving the
dashboard route to under a megabyte parsed (288 KiB gzip). The auth
shell renders the app chrome immediately instead of a spinner while the
session resolves in parallel, the widgets request folds into the
snapshot it duplicated, and insight pills prefetch their sub-pages —
a first visit to a sub-page lands in about ninety milliseconds instead
of nearly a second.
2026-06-11 02:19:27 +02:00
Marc-André Bombeck 17b8d8d2c2 chore(release): v1.4.25
Largest feature delta in the v1.4.x line. Insights expands into seven dedicated metric sub-pages; GLP-1 medication tracking lands end-to-end (drug-level chart with Research Mode acknowledgment, EMA titration ladder, cadence + compliance, side-effect taxonomy, pen/vial inventory); cross-source priority becomes a two-axis resolver; per-user timezone threads through ten analytics surfaces; Withings activity + sleep v2 sync; onboarding rebuilt as a nested-route wizard; Personal Records ship end-to-end; Health Score provenance accordion; Coach runs native first-party prompts across six locales with a 1800-assertion refusal-probe matrix; OpenAPI drift gate hard-fails; multi-arch GHCR image. Migrations 0043 through 0060 are additive and forward-only. PROMPT_VERSION 4.24.0 → 4.25.0 with GROUND RULE 9 (refuses dose recommendations) and GROUND RULE 15 (refuses drug-level estimates with MDR + MDCG 2021-24 cites).

See CHANGELOG.md for the full per-section delta.
2026-05-14 23:49:32 +02:00
Marc-André BombeckandClaude Opus 4.7 6b35cad079 refactor(v1.4.19): apply simplify-review safe suggestions
Mechanical clean-up sweep across five hot spots flagged by the simplify
audit:

- Lift the 11-key auth.* audit-action label map out of the two admin
  components that rebuilt it on every render. The shared
  useAuthActionLabels() hook in _shared.tsx is now the single home for
  new entries (login-overview + recent-audit-preview consume it).
- Drop the dead chipClass branches in IntegrationStatusPill: only the
  connected state actually paints a Dracula-green chip; error and
  disconnected fall back to the badge variant.
- Add thin DateInput / DateTimeInput wrappers under components/ui that
  bake in the lang={locale} hint so 14 callsites across 9 files no
  longer have to remember the prop. A future date input cannot
  silently regress.
- Remove the leading icon-only flex wrappers left behind when the
  duplicate card titles were dropped on /admin/feedback,
  /admin/danger-zone, and the thresholds editor.
- Strip the five "v1.4.19 X removed because…" narration comments from
  insights/page.tsx and the dashboard. git blame already answers what
  changed and the comments aged into history.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:24:35 +02:00
Marc-André BombeckandClaude Opus 4.7 ff6e18473a fix(forms): hint browser locale on date and datetime inputs
Native `<input type="date">` and `<input type="datetime-local">`
were rendering `mm/dd/yyyy` even when the German locale was
active, because Chromium picks the input's locale from the
nearest `lang` attribute on the element rather than from
`<html lang>`. Pass `lang={locale}` on every date/datetime input
in the app — onboarding DOB, account DOB, export filters,
doctor-report range, measurement / medication / mood timestamp
fields — so DE users now see `TT.MM.JJJJ`. Display formatting
already routed through `useFormatters()`; this closes the form
half of the same gap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:36:23 +02:00
Marc-André Bombeck 2afe3c47ce fix(onboarding): scope tour-launcher sessionStorage keys by user id (v1.4.15 H4)
Both `healthlog-tour-session-dismissed` and `healthlog-tour-referrer`
were un-scoped global keys, so a browser shared by two HealthLog
identities (admin impersonating a target user, family laptop, etc.)
inherited the first user's "tour dismissed for this session" flag —
the second user logged in and silently never saw the spotlight tour.

Both keys are now suffixed with `:${userId}`, so each identity's
state stays independent. The two builders (`tourSessionDismissedKey`
and `tourReferrerKey`) are exported so the wizard's `persistAndExit`
shares the same wire format and a unit test pins the scheme against
future drift.

One-off cost: every currently-open tab re-fires the tour once on the
next load because the legacy un-prefixed keys are no longer read.
Acceptable tax for multi-tenant correctness ahead of v1.5.
2026-05-10 00:27:20 +02:00
Marc-André BombeckandClaude Opus 4.7 8215e25876 feat(onboarding): auto-launch tour for new users on first dashboard load
v1.4.15 Phase B5 — third commit. Wires the spotlight tour
(`<OnboardingTour>` from previous commit) to auto-fire for new users
the first time they land on the dashboard, persists completion to
the DB, and exposes a window-event hook for replay.

Pieces:

  - `<TourLauncher>` (`src/components/onboarding/tour-launcher.tsx`)
    — gates and mounts the tour. Self-decides via:
      * `user.onboardingTourCompleted` (DB flag from migration 0030)
      * sessionStorage dismiss guard (defensive against in-flight
        `/api/auth/me` refetches re-opening the tour after Skip)
      * post-wizard 1500ms grace window (avoids the wizard's
        `doneToast` and the spotlight stacking on the same z-layer)
      * a `ready` prop the dashboard sets once analytics has
        resolved (so the spotlight cutout doesn't snap to a 0×0
        placeholder before tiles render)
    Listens for `healthlog:tour-restart` to force-reopen.

  - `POST /api/onboarding/tour` — single endpoint with body
    `{ completed: boolean, outcome?: "completed" | "skipped" }`.
    Flips `users.onboarding_tour_completed`. Dispatches a Wide
    Event annotation with the outcome so Loki can split completion
    from dismiss without a second column.

  - `/api/auth/me` now returns `onboardingTourCompleted`. The
    `AuthUser` type in `src/hooks/use-auth.ts` gains the field;
    no consumer outside the launcher reads it today.

  - `data-tour-id` attributes added to:
      * dashboard tile-strip wrapper (`dashboard-tile-strip`)
      * dashboard "Add" dropdown trigger (`dashboard-quick-add`)
      * sidebar nav items (`nav-insights`, `nav-settings`,
        `nav-achievements`, plus the rest for symmetry —
        targeted attribute, not class names, so renames in
        Tailwind classes don't silently break the spotlight)

  - The `/onboarding` wizard's `persistAndExit()` writes
    `sessionStorage["healthlog-tour-referrer"] = "1"` immediately
    before navigating to `/`. The launcher consumes-and-clears that
    on first mount via a `useState` lazy initialiser (presence-only
    flag — no `Date.now()` so the render-phase decision stays pure
    under `react-hooks/purity`).

ESLint compliance:

  - `react-hooks/set-state-in-effect` — no synchronous setState in
    a useEffect body. The launch decision happens in the render
    phase using the React-recommended "previous input id" guard
    (mirrors `account-section.tsx`'s `seededUserId`), and the
    post-wizard timer's setState lives inside a setTimeout
    callback (an event handler, not the effect body).
  - `react-hooks/purity` — render-phase setState only branches on
    prop values + sessionStorage-presence helpers. No `Date.now()`
    / `Math.random()` / fetch in render.

Tests: my 39 unit tests pass (was 27 — +12 across tour-state and
tour SSR shape). Pre-existing test failures in
`doctor-report-data.test.ts` are uncommitted B6 work in the
working tree, not B5. Lint clean for B5 files; one pre-existing
B6 lint error in `doctor-report-dialog.tsx` is out of scope.
Typecheck has the same pre-existing `dashboard-layout.test.ts`
errors documented in STATE.md (A4 / phase-D scope).

Restart UX (Settings → Account → "Restart tour") lands in the next
commit (B5-4) so this commit stays orthogonal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:28:51 +02:00
Marc-André BombeckandClaude Opus 4.7 3221f86eea feat(settings): introduce dynamic-route shell + redirect /settings → /settings/account (#139)
* feat(settings): introduce dynamic-route shell + redirect /settings → /settings/account

Splits the 3218-LOC /settings monolith into a routing surface only — no
section content moves in this PR. Each of the eight slugs (account,
integrations, notifications, dashboard, ai, api, advanced, about) now lives
at its own /settings/[section] route and renders inside a new
<SettingsShell> sidebar+main layout. Every section currently shows a
placeholder ("Moving here with the next release update") so reviewers can
validate the URL structure in isolation. The historic monolith is preserved
verbatim as page.legacy.tsx for section-by-section extraction by the
follow-up PRs (A2-account, A2-about, A2-ai, A2-integrations,
A2-notifications, A2-rest).

Behaviour change: existing entry points (sidebar nav, topbar dropdown,
onboarding hand-off, dashboard checklist, Withings callback, Codex
callback) now point at /settings/<slug> instead of /settings#anchor. The
/settings root is a 308 (permanentRedirect) to /settings/account so old
bookmarks and external links keep working.

Co-Authored-By: Marc-André Bombeck <mbombeck@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(settings): route every Codex OAuth callback redirect to /settings/integrations

Review-pass H1: only the late success/failure paths in the Codex OAuth
callback were updated to point at the new section route. The four early
branches — rate-limited, provider-supplied `?error=...`, missing params,
invalid state — still redirected to `/settings?codex_error=...`.

After the section split that path 308-redirects to `/settings/account`,
where the Codex error toast/banner doesn't live. Users hitting any of
those edge cases would land on the wrong screen with the error code
silently lost.

All six branches now go to `/settings/integrations?codex_error=...`,
matching the success and token-exchange-failure paths and the existing
Withings callback shape.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:15:15 +02:00
Marc-André BombeckandMarc-André Bombeck f4a2e13985 feat(v1.4): onboarding redesign — dashboard-first checklist, 3 focused steps, inline trend hints (#131)
Redesigns the first-run experience around three patterns we see in
Linear, Vercel and Notion: a short focused wizard, a persistent
getting-started checklist on the dashboard, and contextual hints
inside still-empty charts.

- Onboarding shrinks from 4 steps (one was marketing-only) to 3:
  About you (display name + locale + height + DOB + gender) → log
  your first measurement (real action via the existing form) → pick
  one notification channel (or skip).
- Every step except the first now has a Back button and exactly one
  Skip link with the honest "you can finish setup later from
  Settings" tail. The legacy duplicate Skip is removed.
- A new dashboard "Getting started" card lists the five quick wins
  (profile, first measurement, medication, Withings, notifications).
  Each row links to the right place. Items can be hidden one-by-one
  or the whole card can be dismissed; both states persist in
  localStorage. The card auto-hides once you finish onboarding and
  log your fifth measurement.
- Charts that have ≥1 but <5 readings now show a subtle inline hint
  ("First trend after 5 readings — N more to go") so the user knows
  the line will appear soon.
- Display name is captured during onboarding and saved to the
  profile.

All new strings are translated to English and German via the
existing i18n pipeline.

Co-authored-by: Marc-André Bombeck <noreply@anthropic.com>
2026-05-08 12:42:48 +02:00
Marc-André BombeckandClaude Opus 4.7 5e721e0791 fix(onboarding): surface errors and supply default schedule (#87)
Issue #87 (kevinpapst): medications added during onboarding were
never visible afterwards. Root cause: the createMedication schema
requires schedules.min(1) ("Mindestens ein Zeitfenster"), but the
onboarding wizard sent schedules:[]. The 422 was silently swallowed
and the user was redirected to the dashboard as if everything had
worked.

This patch is the minimal-impact fix. The proper fix lives in Phase
3.5 of the audit plan, which replaces the wizard entirely with a
dashboard-first empty-state flow. Until then:

- finishOnboarding() wraps each call in a try/catch, checks
  response.ok explicitly, and surfaces a sonner toast on failure.
  setSaving(false) on error so the user can retry instead of being
  stuck on a spinner.
- Each medication is sent with a default schedule (08:00-09:00) so
  it is actually persisted. A new medScheduleHint i18n key is shown
  under the meds list while the user is in the wizard, so the
  default schedule is not surprising.
- Errors are translated per stage (errorProfile, errorMedication
  with {name}, errorTargets) plus an errorGeneric fallback. en.json
  and de.json kept symmetric.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 23:22:10 +02:00
Marc-André Bombeck 38309932f3 Initial release v1.0.0
HealthLog — personal health-tracking PWA with weight, blood pressure,
pulse, mood tracking, medication compliance, Withings integration,
doctor report PDF export, and Dracula-themed UI.
2026-03-10 20:14:33 +01:00