Commit Graph
11 Commits
Author SHA1 Message Date
Marc-André BombeckandClaude Opus 4.7 e3a689954b fix(ui): disambiguate dashboard quick-add menu items
Phase-3 e2e suite documented an i18n collision: the dashboard's
quick-entry dropdown trigger says "Add" and both menu items underneath
also rendered the bare verb "Add" because `measurements.addMeasurement`
and `mood.addEntry` both translate to "Add". Screen-reader users heard
two indistinguishable rows; the e2e spec was forced to use
`getByRole("menuitem").first()` as a workaround.

The dropdown trigger already says "Add" — the menu items label what's
being added.

  - Add `dashboard.quickAddMeasurement` ("Measurement" / "Messung")
  - Add `dashboard.quickAddMood` ("Mood entry" / "Stimmungseintrag")
  - Wire the two `<DropdownMenuItem>`s on `/`
  - Mark the leading icons `aria-hidden` so the accessible name is
    just the noun
  - Update `e2e/measurement-flow.spec.ts` to target the menu item by
    name (`/measurement|messung/i`) instead of `.first()`

Dialog titles still use the existing `addMeasurement` / `addEntry`
keys — those work in the larger dialog context where "Add" reads fine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:22:08 +02:00
Marc-André BombeckandClaude Opus 4.7 987ce0d5f6 fix(a11y): clear axe-core violations on /admin and key sub-routes
Re-enable the /admin axe-core spec that phase-3 parked while phase-4b
restructured the admin into per-section dynamic routes. The fixme is
gone, /admin/system-status and /admin/users are added as representative
sub-route audits, and the violations the original audit surfaced are
fixed:

- `src/components/admin/_shared.tsx`: drop the duplicate
  `<PasswordInput>` whose toggle button had no accessible name.
  Re-export the canonical `<PasswordInput>` from
  `src/components/settings/password-input.tsx` (added in phase-3) which
  already carries the `aria-label="Show password" / "Hide password"`
  pair. Affects `/admin/integrations` (bug-report token, web-push VAPID
  private key) and `/admin/users` (password-reset dialog).
- `src/components/admin/user-management-section.tsx`: icon-only edit /
  reset-password / force-logout buttons now carry `aria-label` (axe
  `button-name` is `serious`); the leading icon is `aria-hidden` so the
  accessible name is just the action verb.
- `src/components/admin/status-card-grid.tsx`: drop the placeholder
  `{ label: "—", value: "" }` tuple from the audit-log card. An empty
  `<dd>` next to a "—" `<dt>` is not navigable and the `<dl>` was the
  most likely source of an axe violation on `/admin`. The 3-column grid
  still works with two metrics; the empty cell at the end is unrendered
  because there is no `<div>` for it.
- `src/components/admin/__tests__/status-card-grid.test.tsx`: rework
  the 3-metric assertion to allow 1..3 metrics with non-empty,
  non-em-dash labels so the audit-log change doesn't regress.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:11:01 +02:00
Marc-André BombeckandClaude Opus 4.7 ca3c599e7c test(e2e): extend axe-core to authenticated surfaces; fix login regex
Add /, /settings/integrations to the WCAG 2.1 AA serious+critical
sweep under an authenticated test.use(storageState). /admin is
parked as test.fixme() because the overview page surfaces three
real critical violations (button-name × N, select-name × 1,
colour-contrast × 1) all originating in src/components/admin/**,
which Phase 4b is restructuring concurrently — re-enable the audit
once 4b ships.

Repair pre-existing breakage in login.spec.ts: the "Login with
password" copy was renamed to "Sign in with password" months ago,
but the regex never tracked it. Also disambiguate the strict-mode
violation between the passkey CTA and the password submit (both
match /sign in/) by restricting to button[type=submit].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:49:22 +02:00
Marc-André BombeckandClaude Opus 4.7 a106547e20 test(e2e): doctor-report PDF + mobile-viewport smoke
doctor-report.spec.ts mocks /api/doctor-report with a
shape-correct payload, clicks the "Doctor report" button on
/settings/advanced, captures the resulting download via Playwright's
`waitForEvent('download')`, and asserts the file is >1KB and starts
with the `%PDF` magic.

mobile-viewport.spec.ts is a Pixel 5 only smoke that verifies the
dashboard has no horizontal scroll, the fixed bottom-nav doesn't
clobber main content, and every interactive CTA visible above the
fold meets WCAG 2.5.5 (≥44×44 CSS px). Skips on the desktop project
via testInfo.project.name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:49:09 +02:00
Marc-André BombeckandClaude Opus 4.7 2b6f959434 test(e2e): mocked Codex device-flow + insights regenerate specs
codex-flow.spec.ts intercepts /api/auth/codex/device-start +
device-poll + /api/insights/settings to drive the UI through the
three-state machine (CTA → device-code panel → "ChatGPT connected"
badge) without touching auth.openai.com / chatgpt.com.

insights-generate.spec.ts mocks /api/insights/generate with the
canonical InsightResult shape (changed/stable/drivers/nextSteps/
confidence/limitations) and confirms the regenerate button surfaces
the inline success message — the AI provider stays uninvolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:49:00 +02:00
Marc-André BombeckandClaude Opus 4.7 3999ea7b3c test(e2e): authenticated dashboard render + add-measurement flow
Two new specs exercise the core happy path: the dashboard tile strip
+ Recharts surface render against a mocked /api/analytics, and the
quick-entry dropdown → MeasurementForm dialog → POST /api/measurements
round-trip surfaces a weight reading on /measurements.

Both rely on the global setup's pre-captured session cookie, so
neither hits the auth rate limit. The mocks fully isolate from the
DB so we don't need a 90-day measurement seed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:48:52 +02:00
Marc-André BombeckandClaude Opus 4.7 535465c0ef test(e2e): add seeded test user + auth storageState fixture
Authenticated specs need a deterministic user and a persistent session
cookie. global-setup.ts upserts an `e2e-tester` admin via raw `pg` (the
generated Prisma client uses ESM `import.meta.url`, which Playwright's
TS loader cannot resolve), then logs the user in once and writes
`storageState.json` so every authenticated spec reuses the same
session — the per-IP login rate-limit (5 attempts / 15 min) would
otherwise lock out the suite the moment six specs run in parallel.

Adds `pg` + `@types/pg` as devDependencies — required for the seed
step. `pg` is already used by `scripts/seed-demo.ts` for the same
loader-incompatibility reason, so we're not introducing a new
dependency footprint, only making it explicit in the lockfile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:48:45 +02:00
Marc-André BombeckandClaude Opus 4.7 46e686f396 fix(e2e): switch mobile project to chromium and repair spec flakiness
Three orthogonal fixes that account for most of the e2e suite's
ongoing red:

- The `chromium-mobile` project was actually iPhone 13 (webkit) — CI's
  `playwright install chromium` therefore could not launch it
  (`webkit-2272/pw_run.sh` does not exist). Renaming-to-Pixel-5 makes
  the project name match what the runner provides without expanding
  the install footprint.
- `locale-switch.spec.ts` tried to anchor the cookie at `page.url()`,
  which on a fresh context is `about:blank`; Playwright rejected the
  cookie outright. Using the project's configured baseURL is the
  documented way and works regardless of navigation timing.
- `login.spec.ts` looked for the username/password inputs immediately
  after navigation, but the password form is mounted lazily once the
  user clicks "Login with password" (passkey is the default mode).
  A small helper now opens the form before the assertions run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 01:52:57 +02:00
Marc-André BombeckandMarc-André Bombeck 643e6ec3a0 release: v1.4.2 — resurrect v1.4.1 work + bug-fix triple (#152)
* Revert "revert: roll source tree back to v1.4.0 to restore production"

This reverts commit e444a90b10.

* fix(v1.4.2): summaries.WEIGHT + medications array guards + healthcheck IPv4

Three production bugs surfaced after the v1.4.0 deploy stabilized:

1. `TypeError: undefined is not an object (evaluating 'E?.summaries.WEIGHT')`
   on /dashboard and /insights for users without weight readings —
   the optional chain stopped at `data` when it needed to also cover
   `data.summaries`. Fixed: `data?.summaries?.WEIGHT` (one level deeper).

2. `TypeError: y?.filter is not a function` on /medications — the
   medications query value was non-array (likely a stale
   service-worker response from an older API shape). Replaced with
   `Array.isArray(medications) ? medications : []` so the page is
   immune to any future shape drift.

3. Healthcheck-induced 503 outage (already in 1.4.1 — included here
   so 1.4.2 is the single coherent release). The docker-compose
   healthcheck used `localhost` which busybox-wget resolves to IPv6
   ::1; Next.js standalone listens on IPv4 0.0.0.0 only. Pinned
   to 127.0.0.1.

This release also includes everything from v1.4.1 (which never
published a clean GHCR tag): the per-section admin extraction, the
testcontainers integration suite, the Playwright + axe-core E2E
foundation, the moodLog SSRF fix, the central error-redaction in
WideEventBuilder + Glitchtip, the medical-citation drift fixes
(ESH 2023, Saint-Maurice attribution, steps source), and the docs
sync.

Co-Authored-By: Marc-André Bombeck <mbombeck@gmail.com>
2026-05-08 17:46:31 +02:00
Marc-André Bombeck e444a90b10 revert: roll source tree back to v1.4.0 to restore production
Production at healthlog.bombeck.io has been 503-ing since the v1.4.1
deploys started landing on apps-01 (Coolify). The container boots —
Next.js prints "Ready" and the pg-boss workers run — but never
accepts HTTP on :3000, so the Docker healthcheck fails and Traefik
takes the upstream out of rotation. A manual restart, a Coolify
force-rebuild, and a docker-compose pin to the GHCR :1.4.0 multi-arch
image all failed to bring the site back up — Coolify rebuilds the
image from main HEAD on every deploy regardless of the compose
directives.

This commit resets the working tree to commit 21bd46d (v1.4.0
release). Same content that's been running for self-hosters since
yesterday's tag-and-release. The next Coolify deploy will build
from this tree and produce a healthy container.

The v1.4.1 work is NOT lost:
  - PRs #144, #145, #137, #146, #147, #148, #149, #150 remain in
    git history.
  - Their commits are still tagged (`v1.4.1`), still on the GHCR
    multi-arch image (`ghcr.io/mbombeck/healthlog:1.4.1`), still in
    the GitHub Release notes.
  - Self-hosters who have already pulled the v1.4.1 image keep it.
  - Local development continues from main HEAD with the v1.4.1
    code — the regression only surfaced under the Coolify build
    flow.

Re-applying v1.4.1 to production will need a separate cycle to
reproduce the runtime failure under the Coolify build path. That
work is tracked in docs/ops/v141-followup-issues.md (added back
when the tree is reapplied) and the deploy gating in
.github/workflows/e2e.yml will catch this class of bug going
forward.

No DB migration. No env-var change. No API contract change.
Co-Authored-By: Marc-André Bombeck <mbombeck@gmail.com>
2026-05-08 17:15:34 +02:00
Marc-André Bombeck fdf8dd6eb9 test(e2e): add Playwright smoke suite + axe-core a11y gate + CI (#146)
Builds out the foundation for end-to-end tests against the production
build: Playwright with desktop + mobile projects, axe-core for WCAG
2.1 AA gating, and a GitHub Actions workflow that caches the browser
download and uploads the HTML report on failure.

What lands now (public surfaces only — no DB seed required):

  e2e/version.spec.ts          /api/version returns shape, no auth
  e2e/auth-redirect.spec.ts    proxy gate bounces / → /auth/login etc.
  e2e/login.spec.ts            login form renders + autoComplete hints
                               + invalid-credential error surfaces
  e2e/locale-switch.spec.ts    DE/EN cookie switch, no raw i18n keys
                               leak into the DOM
  e2e/a11y.spec.ts             axe-core fails on serious or critical
                               WCAG 2.1 A/AA violations on /auth/login

The four authenticated flow specs from the v1.4.1 plan
(quick-entry, doctor-report, settings-roundtrip, test-buttons,
onboarding) need a seeded test user; they're tracked as a follow-up
because the seeding pipeline + WebAuthn polyfill they require warrant
their own PR. The smoke specs above cover the proxy, the login surface,
the version healthcheck endpoint, and the locale machinery — i.e.
every path a v1.4.X regression could realistically break without
deeper UAT.

CI workflow (.github/workflows/e2e.yml):
  - postgres:16-alpine service container
  - prisma migrate deploy seeds an empty schema
  - playwright install --with-deps chromium (cached)
  - next build, then `playwright test` against `next start`
  - playwright-report uploaded as an artifact on failure

Quality gates:
  - pnpm typecheck — clean
  - pnpm test — 658/658 pass (vitest excludes e2e/**)
  - pnpm lint — 0 errors
2026-05-08 16:08:02 +02:00