Below md the score ring sat centred under the Today facts as a 168 px dial, where the bottom navigation covered it. The hero is now one grid with two arrangements: on a phone the ring is a compact 80 px dial at the top right beside the lead and signal line, flush with the card's trailing edge, and the Today facts run the full card width under both; from md up the ring spans both rows of the trailing column exactly as before (desktop captures are pixel-identical). Without a lead the facts take the leading cell, so the ring never sits beside an empty row. ScoreRing gains a `responsive` size (80 px, md:168 px) whose label is left out below md; the ring keeps its aria-label and the surrounding link names it. The meta lines under the phone dial are capped to its width, so the lead keeps at least 180 px beside it at 360 px. Each fact now stacks its muted label above its foreground value on the reading edge at every width, instead of a label-left / value-right row that broke long values into a ragged right-aligned block. Rows stay at the 44 px tap floor below md. The Today overview e2e covers 1440, 390 and 360 px in both themes for an AI lead, a long AI lead, an AI-off day and a quiet day, and asserts the dial size, its place beside the lead, symmetric insets, full-width facts and stacked 44 px rows on a phone.
e2e/
End-to-end browser tests. Playwright 1.60 drives a production build of the app against a real PostgreSQL, with @axe-core/playwright for accessibility assertions. This is the heaviest test tier; CI runs it on every PR, and locally it is worth running when a change touches a UI flow.
Running
pnpm e2e
The Playwright config (../playwright.config.ts) builds the app (next build) and brings up Postgres before the specs run; setup/global-setup.ts seeds the auth + fixture state. The suite is not part of the fast local gate (typecheck / lint / test) — it surfaces at PR CI, so expect responsive/wizard/nav reworks to flag here first.
Layout
setup/—test.ts(the suite'stest+expectentry point),global-setup.ts(auth + DB seeding),test-helpers.ts(shared login + navigation helpers).utils/— fixtures such asmock-dashboard-snapshot.ts.fixtures/— synthetic payloads a journey uploads, built in code rather than committed as blobs so the content stays reviewable (apple-health-export.ts).*.spec.ts— one spec per flow: auth/login/redirect, dashboard, charts, the medication-wizard cadences (medications-wizard-*), insights generation + scroll restoration, doctor report, settings, mobile-viewport consistency, locale switch, accessibility (a11y.spec.ts), and thev1427-*regression set.
Conventions
Import test and expect from ./setup/test, never from @playwright/test — types (Page, Route, …) still come from the package. The entry point extends Playwright's test with an automatic fixture that drops route handlers and page listeners after every test, so a handler left in flight cannot fail the test it belonged to once the context closes. src/__tests__/e2e-route-cleanup-guard.test.ts enforces the import boundary.
Assert against stable data-* attributes (data-slot, data-display-step, root display attrs), not viewport-dependent visible text. Responsive sm:hidden UI changes break getByText on desktop while the underlying element is still present — anchoring on data attributes keeps the specs stable across breakpoints. See ../CLAUDE.md.