New conversation leaves the conversations panel's header and floats in the conversation itself: a round 56 px button with a plus, the floating Coach button's face, named "New conversation". It sits in the composer's row, so it always belongs to the chat column, never to the list or a docked day beside it. Where the column is wide it takes the bottom right corner beside the composer; where the composer fills the width (a phone, the drawer, a narrow column) it rises above the composer's edge. The drawer shows it once there is a conversation to leave, in place of the plus in its header. An empty conversation shows nothing either way. The docked conversations panel folds like the day: its header opens with a fold control, and folded it keeps a 48 px edge whose button opens it again (aria-expanded, focus moved between the two, Escape inside folds it). The top bar's toggle remains for the sheet below 1280 px. On /coach the day docks right of the panel. Below 1600 px only one of the two is open: a day opening folds the list without touching the remembered choice, and opening the list folds the day to its edge. From 1600 px both stay open. Switching or starting a conversation keeps an open day in the URL. The empty conversation drops everything under the composer: the daily seeded opener and the scope pill are gone, and with them the only reader of GET /api/insights/coach/seeded-question and of the coach.ambientSuggestions preference. The route, its OpenAPI entry, the preference and its Settings switch go too.
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.