Commit Graph
9 Commits
Author SHA1 Message Date
Marc-André Bombeck 25f41f88fe feat(ux,settings): undo where it was missing and settings that behave one way
Deleting a measurement or mood entry can be undone from the toast via
new restore endpoints, dialogs submit on enter with busy states, silent
mutations gain error toasts, and the settings area finishes its
consolidation: provider and integration cards live in focused
subcomponents, every setting persists server-side at exactly one home,
and the save conventions are written down. The remaining raw fetches in
settings move onto the shared client.
2026-06-11 02:19:28 +02:00
Marc-André Bombeck dbb5979a97 chore(release): v1.4.39.3 — dashboard slim/thick merge robustness and list-page precision
The post-deploy CI for v1.4.39.2 surfaced eight e2e failures across
chromium-desktop and chromium-mobile (chart-overlay-controls,
dashboard, charts-mobile, measurement-flow) that all routed through
two narrow regressions and one durable test-fixture gap.

### Fixed

- Dashboard slim/thick merge no longer blanks the tile strip when the
  slim slice resolves with an empty summaries record. The v1.4.39.2
  inline `slim?.summaries ?? thick?.summaries` short-circuited on
  JavaScript's truthy-empty-object even when thick carried the full
  payload. The merge moved to a pure `mergeSlimAndThickAnalytics`
  helper that uses object emptiness as the discriminator: populated
  slim still wins on overlapping fields (progressive-paint contract
  preserved), empty slim falls through to thick, both empty leaves
  the dashboard's data-floor gates to render the appropriate empty
  state. Eight new behaviour-level unit tests cover the edge cases.
- `MeasurementList` non-grouped rows now preserve decimal precision
  on both the desktop table and the mobile card grid. The v1.4.37 W7c
  collapsed-list view passed every value through `fmt.integer`, which
  kept the per-day step / activity aggregates correct but silently
  truncated single weight / body-fat / temperature readings — 78.4 kg
  rendered as "78 kg". Grouped rows (cumulative HK type day
  aggregates) stay on `fmt.integer` because their readings are
  integer-only; non-grouped rows now use `fmt.number` which honours
  up to three fraction digits without forcing a minimum.

### Changed

- Playwright route mocks for `/api/analytics` migrate from the
  literal `**/api/analytics` string glob to the regex form
  `/\/api\/analytics(\?|$)/` across every authenticated spec. The
  string glob does not match the sliced URL form
  `/api/analytics?slice=summaries` the v1.4.39.2 dashboard split
  fires alongside the thick request, so the slim request fell
  through to the real route on the seeded test user that returns
  empty summaries. Ten specs migrate: dashboard,
  chart-overlay-controls, measurement-flow, charts-mobile, a11y,
  v1427-responsive-sheet, v1427-coach-launch,
  v1427-measurements-add-param, v1427-insights-empty-state,
  onboarding-tour-passthrough. The regex form is the durable
  alignment with the v1.4.37 W-CI lesson that already swept
  `onboarding-flicker.spec.ts` and `mobile-viewport.spec.ts`.

### Notes

- No schema migration. Runtime + test-fixture-only changes.
- `pnpm test --run` green at 4662 / 4663 (1 long-standing skip).
- Full Playwright suite green at 116 passing / 36 skipped / 152 total
  on both `chromium-desktop` and `chromium-mobile` profiles, against
  the production Next.js build. Closes every failure from CI run
  26213226723.
2026-05-21 10:31:18 +02:00
Marc-André Bombeck 00848b7ed5 fix(e2e): skip desktop-sidebar specs on mobile + stub /api/dashboard/widgets
Three remaining e2e failures after the global-setup tour-completed fix
all came from cross-project sharing — specs that target desktop-only
DOM (the `aside[aria-label="Sidebar"]` logo + Achievements link)
were running in the chromium-mobile profile too, where the sidebar
is `hidden md:flex` by design. Added `test.skip(viewport.width < 768,
...)` guards so each spec only runs in the project that can satisfy
its contract.

The fourth failure was the `/api/dashboard/widgets` endpoint that the
A5 tile-strip work added since `onboarding-flicker.spec.ts` was
written. Without a stub the unauthenticated dashboard 500s on this
query and React Query's error boundary swallows the page render
before `<GettingStartedChecklist>` mounts. Added the stub to both
flicker specs (complete + incomplete-onboarding paths).
2026-05-10 00:50:00 +02:00
Marc-André BombeckandClaude Opus 4.7 786b3951f6 fix(a11y): skip-link no longer blocks logo click outside focus
The "Skip to content" link is `position: fixed; top-2 left-2 z-[100]`
on every authenticated page. Even with `opacity-0` it still occupied
its bounding box, so a mouse click on the desktop logo (also at the
top-left corner) sometimes hit the invisible link instead.

Fix: keep the visual treatment (opacity, focus styles) but combine
`pointer-events-none` + `-translate-y-full` in the unfocused state so
the element neither receives clicks nor occupies any visible space.
On focus, both modifiers reverse — the link slides into view and
becomes interactive again. Existing keyboard flow is unchanged: Tab
focuses the link, the focus ring + translucent slide-in is preserved.

Tests: extend `e2e/a11y.spec.ts` with a Playwright check that the
desktop logo is clickable from its centre on the dashboard and the
URL stays at "/".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:15:21 +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é 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