The Input and Textarea primitives already opted config and secret fields
out of 1Password and LastPass autofill (data-1p-ignore / data-lpignore)
whenever autocomplete was off, but Bitwarden had no opt-out hint anywhere,
so it kept offering to fill and save API keys, tokens and integration
URLs. Emit data-bwignore alongside the existing hints, which covers every
field flowing through the primitives. Pin explicit autocomplete on the
change-password dialog so genuine credentials still autofill, and add the
ignore hints to the export passphrase and the few new-password admin
config fields that opt in directly.
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.
Consolidated hotfix landing the findings from a six-axis audit
covering mobile security, UX + accessibility, code quality + docs,
README + discoverability, web performance, and mobile-deep responsive
behaviour. None of the changes break the public API or the existing
data shape; every fix is additive and SAFE under the v1.4.34.x web
freeze. Full audit reports live in `.planning/round-audit-*.md`.
Security
- Password rotation now wipes API tokens + refresh tokens in the
same `$transaction` as session revocation. A stolen iOS Bearer no
longer survives a user-initiated password change or an admin reset.
- `/.well-known/` proxy bypass tightened to an explicit-allowlist
`Set`. Stale `/api/auth/codex/callback` PUBLIC_PATHS entry dropped.
- HSTS gains `preload`. Withings host removed from the global CSP
and gated to the integration's own routes.
- `getClientIp` warns once per process on `TRUST_PROXY_HOPS`/XFF
mismatch + exposes a tagged helper for tighter universal buckets.
- Service worker rejects off-origin notification-click URLs. Offline
fallback body switched to a locale-neutral icon. Manifest gains
`scope`, `id`, `display_override`.
UX + accessibility
- i18n leaks closed on Dialog/Sheet close-X, ChartSkeleton's loading
announcement, and the notifications-section breadcrumb.
- Focus rings on bottom-nav, the More overflow button, and the
mobile top-bar user menu.
- Tap-target floor: Input / NativeSelect / Select / DateTimeInput
switch to `h-11 sm:h-10`.
- Admin chip-strip auto-scroll-into-view (matches v1.4.33 settings).
- List-page subtitles visible on mobile.
- Insights empty-state CTA vocabulary normalised so the link opens
the right measurement form (was silently failing for
BODY_TEMPERATURE / BLOOD_GLUCOSE / PULSE).
Mobile-deep
- `viewport.themeColor` reacts to light/dark mode.
- TopBar + Coach FAB reserve safe-area-inset-top / -bottom for
notched iPhones.
- Sonner toaster switches to `theme="system"`.
Code quality
- `SESSION_SECRET` dropped — the env var was documented as required
but never read by code. Three secrets, not four. Self-hosters
upgrading can delete the existing line.
- `toJson<T>()` helper consolidates seven Prisma JSON-cast sites.
- `db-compat.ts` gains a header docstring naming it the
schema-bootstrap path.
- Passkey response types adopted from `@simplewebauthn/server`;
`any` + eslint-disable pragmas drop out.
- Zero-TODO CI gate added at `.github/workflows/no-todo-markers.yml`.
Documentation + discoverability
- README rework: How-it-compares matrix, Apple Health + AI Coach
promoted to standalone Key-Features bullets, status block, latest
release + GHCR badges, OpenAPI pointer, three-secret Quick Start.
- openGraph + twitter cards now carry `images` and a
`summary_large_image` Twitter card.
- GitHub repo metadata: description tightened from 290 to 156
chars + keyword-front, `homepageUrl` flipped from a personal
tenant to the demo site, six generic / PII-adjacent topics
swapped for six high-intent ones.
- Five new user-facing docs: `docs/self-hosting/getting-started.md`,
`docs/self-hosting/reverse-proxy.md`,
`docs/integrations/apple-health.md`,
`docs/integrations/withings.md`,
`docs/integrations/ai-providers.md`.
- `docs/README.md` clarifies the docs tree as internal playbooks
vs the user-facing `docs.healthlog.dev` site.
* docs(ui): add UI guidelines as single source of truth + shadcn MCP server
- `docs/ui-guidelines.md` lays out Dracula tokens, spacing, typography,
component picks (button/input/dialog/sheet decision tree), layout
rules (settings split target, dashboard one-row), accessibility
baseline (WCAG 2.1 AA + axe-core in CI), the autofill / honeypot
pattern for measurement forms, and a reusable "Test button" UX for
every integration. New components in v1.4 reference this doc.
- `.mcp.json` wires the official `shadcn` MCP server so the design
system can be browsed and components installed without leaving the
editor.
- Adds `@vitest/coverage-v8` to dev dependencies — `pnpm test
--coverage` now reports real per-file coverage (the v1.4 cycle uses
this to lift critical-path coverage).
Co-Authored-By: Marc-André Bombeck <noreply@anthropic.com>
* fix(settings/ai): connection-test honours the unsaved provider selection
Changing the AI provider in /settings, then clicking "Verbindung testen"
without saving first, used to silently run the test against the *saved*
provider instead — so the result felt unrelated to what the user had on
screen. This often surfaced as a confusing OK / failure depending on
whichever provider had previously been persisted.
The test endpoint now accepts an optional JSON body that mirrors the
form fields (provider, model, baseUrl, anthropicKey, localKey). When
present, the server resolves a transient provider from those values —
falling back to the persisted, encrypted credentials per field, so a
user with a stored Anthropic key can swap models in the dropdown
without re-typing the key. Plaintext keys never persist; the existing
SSRF guard for the Local provider's base URL is reused, the existing
rate limit (5/min) and the V3 error-leak shielding stay in place.
Empty body keeps the previous behaviour for any other caller.
Tests cover the new override path (provider forwarded, unknown
provider rejected with 422, empty body uses persisted config, config
errors surface the resolver's status). Whitespace in
`src/app/settings/page.tsx` is reformatted by Prettier as a
side-effect; logic touches only the AI section.
Co-Authored-By: Marc-André Bombeck <noreply@anthropic.com>
* feat(ui): add Skeleton and EmptyState primitives
Two pieces every page in v1.4 needs and the codebase didn't have:
- `<Skeleton>` — a presentation `<div>` with the standard pulse
animation, `aria-hidden`, and `motion-reduce:animate-none`. Used to
shape the page while data loads instead of dropping a spinner. The
visible layout no longer jumps when the first measurement arrives.
- `<EmptyState>` — the shape every list, tile, and chart panel
reaches for when it has no data: optional icon, title, description,
primary action; `card` (default) and `plain` variants;
`default`/`compact` density. Lives inside an `aria-live="polite"`
region so screen readers announce the message and its action.
Tests cover screen-reader semantics, prettier-merged class names,
density/variant switches.
These two land first because the v1.4 pages — the split settings
sections, the new dashboard hero, the redesigned admin status grid —
all assume the primitives exist. The patterns are documented in
`docs/ui-guidelines.md` §4.1–4.2.
Co-Authored-By: Marc-André Bombeck <noreply@anthropic.com>
* fix(ui): block password-manager autofill on health-data inputs by default
Browsers and password managers were happily autofilling the user's
account credentials into measurement, medication, mood, and AI-token
fields whenever those forms didn't explicitly set `autoComplete`. The
codebase had zero `autoComplete` attributes across the data-entry
surfaces, so this happened often enough to be both a UX papercut
(account password landing in a "Notiz" field) and a real privacy issue
(secrets persisting through CSV/JSON export).
The base `<Input>` primitive now:
- Defaults `autoComplete` to `"off"` when the caller doesn't supply a
value.
- Adds `data-lpignore="true"` and `data-1p-ignore="true"` whenever
autoComplete resolves to `"off"`, so LastPass and 1Password skip
the field too.
- Keeps the password manager active for any form that already passes a
semantic `autoComplete` (`"username"`, `"email"`,
`"current-password"`, `"new-password"`) — the existing login,
register, and AI-key inputs continue to work unchanged.
Tests cover the three meaningful states (default off, explicit
semantic value, login-flow `username`).
Co-Authored-By: Marc-André Bombeck <noreply@anthropic.com>
---------
Co-authored-by: Marc-André Bombeck <noreply@anthropic.com>
HealthLog — personal health-tracking PWA with weight, blood pressure,
pulse, mood tracking, medication compliance, Withings integration,
doctor report PDF export, and Dracula-themed UI.