Say which claims belong in *.browser.test.tsx, how to run the project
alone, and what tests/browser/ provides. The browser install guard now
names the browser project beside the hero-geometry check, since both
launch the headless shell the CI slices install.
One question at a time in the source order, Back keeping the answer,
the number keys and arrows, the review with a way back to every answer,
the optional PHQ-9 follow-up, a failed submit, and the note over an
unvalidated translation staying inside the viewport on every step. The
switch to the English original is carried in the answers, and the full
path from the overview posts the instrument, the reader's language and
the item scores. Questionnaire text loads through the production chunk
loader.
The wizard's markup cases are removed from the surface suite; the
loading-frame case stays there, since a browser loads the chunk before
a test can look. The browser project inlines an empty process.env for
Next's client modules.
Each family form moves into managed-record/ beside a small kit (the
coercion helpers, the form footer and the switch row); the section keeps
the record-scoped read, the save and the family switch.
The backup journey restored a snapshot over the account it came from. A
fifth act now follows the runbook's new-host path: a second account writes a
reading and a mood entry and downloads its full backup from the export card,
the account is recreated empty under the same id, the admin uploads the file
in the backups console and restores it, and the person signs in to the same
rows under the same ids. The restore wait is shared by both restores.
`integrations/sync-kit/after-write.ts` is the tail every provider write
ends in: the arrivals go out, the touched days are refolded, the read
caches are marked stale back to the earliest touched reading, and the
touched status cards are re-warmed (through `afterMeasurementMutation`,
shared with the interactive routes); Withings also evicts the dashboard
snapshot. A full-history backfill refolds its whole touched range once
through `afterProviderBackfill`.
Withings, WHOOP, Polar, Nightscout, Google Health, Fitbit and Oura drop
their hand-written tails, and no provider module imports the comprehensive
insight generator any more; a guard holds the provider folders to that.
Differences from the copies: the status cards are re-warmed even when the
fold failed, and the caches are marked stale back to the earliest touched
reading instead of wholesale, so older closed blocks survive a sync.
Several provider unit tests mocked an export the old tails never reached
(their failures were caught and only logged); the mocks now name what the
code calls.
The sheet becomes a composer over detail/: the original-file preview and
download controls, the detail read with its writes, the AI layer with its
gate and review-first apply, and the field, meta and footer sections.
Guards that read the sheet's source now read the detail folder.
Pick a sport, type a duration and distance, press Save and read the
batch body: miles asked of an imperial reader arrive in metres, a blank
start counts back from now, a retry after a failed save reuses the
form's external id, and an edit re-posts the stored instants and the
fields the form does not show. Field errors, the pending state and the
phone layout are measured in the page.
The markup and edit-render cases are deleted from the old suite, which
keeps the save and delete helper checks as manual-workout-save.test.ts.
Type into the form, press Save and read the request body off the fetch
boundary: blood pressure as one batch, a weight typed in lb stored in
kg, a decimal comma, glucose typed in mmol/L stored in mg/dL with its
meal context, a sleep typed as 7:30 stored as 450 minutes. Native
required validation, the pending state, a refused write's localised
message, the module-off type list, the last-used type and the menu's
Reset are covered too.
The unit-label and glucose-unit markup tests are deleted; their source
pins are replaced by the wire assertions. The module-types file keeps
its pure ownership checks and drops its render.
The browser helper gains a Date-only clock pin, clears localStorage
between tests, carries an envelope's meta, and answers the providers'
locale mirror without counting it as the component's request.
The time-format choice is changed through the account settings and read
back after a reload, in a second browser context that shares only the
session cookie, and on /api/auth/me, on the settings journey's own account.
The table and the card list each took nine props per row and passed
them on to the shared row actions. One MeasurementRowContext carries
them, and rowState derives a row's grouping, selection and drill-down
state in one place for both layouts.
The check-in journeys anchor on data attributes so they read the same in
every language, which leaves the words themselves unchecked. Two journeys on
the wellbeing account now read the stem, the first and last item and the
answer labels in German (the validated WHO translation, reproduced exactly)
and in Brazilian Portuguese (the translation written for the app, with its
note), submit, and check the score on screen against the assessment the
server stored, with its language and kind of wording.
A FilterBar over useUrlFilterSync now proves the contract end to end:
a facet picked from the open menu pushes a history entry, typed input
replaces the current one, a pill's clear and Reset leave a bare URL,
two writes in a row both land, Back and an edited address re-seed the
pills, and the open menu rests inside the viewport under its pill.
The markup tests for FilterBar and useUrlFilterSync are deleted; the
popper-placement source pin is replaced by the measured menu position.
The browser project re-bundles its dependencies on every run so a
cache warmed before a new file existed cannot load a second React.
The import journey ran on the shared account in both projects, so one
project's duplicate verdict could rest on the other's import. It now runs on
the labs journey's account, emptied first, checks that the dry run writes
nothing and the import writes exactly the accepted row, and finds the result
on the labs page before the second run.
The checkups file held two components of about 620 lines each. Their
parts move to components/measurement-reminders/checkups/:
- reminder-form: the type groups, cadence presets, the form state and
its mapping to and from a reminder (pure)
- reminder-form-sheet: the create / edit sheet and its cadence fields
- checkup-card: the card in both views, its menu, details, skip and
snooze lines, next / last lines and action row
- postpone-sheet: the postpone chips and date, the honest skip and its
confirmation, and the calendar-day offsets
vorsorge-section.tsx keeps the list, the value capture and the visit
filing. Markup and behaviour are unchanged; the guards name the modules
that now hold what they check.
The mood timestamp spec answered the POST and the list read itself. It now
runs on the mood journey's account: the instant typed into the form is the
instant stored, and the entry is in the list, also after a reload.
The add-measurement flow answered its own POST and list read, so it proved
that the list renders a row the test typed in. It now runs on the
measurement journey's account, emptied first, and checks that the reading
the server stored is the one entered and that the dashboard tile, the weight
chart's data table and the day view all show it.
`oauth-kit/client.ts` builds a provider's consent URL, code exchange,
refresh and deauthorization from a declaration of what differs: endpoints
and scope, client authentication (form or HTTP Basic, with or without the
client id beside it), the fields a grant adds, how an answer is classified
and the refusal named, the error type, and where the token sits in the
body. The seven hand-written copies of the redirect URI, authorization URL,
token post, Basic header and PKCE pair are gone; each provider keeps an
`oauth.ts` with its declaration.
Shared behaviour is now uniform: a 2xx without a token object (an empty or
non-JSON body) is a transient `empty_token_body` failure for every
provider. WHOOP, Strava and Google Health used to return null, and
Withings threw an unclassified SyntaxError; the OAuth wire golden records
exactly these lines changing and nothing else. Oura's live/legacy endpoint
fallback, Google's invalid_grant lift, Strava's Fault summary and Fitbit's
errorType are declared per provider.
The login spec stubbed the login route to prove the form shows an error. It
now spends a real 401, and three journeys run on the sign-in account of
their project: a password sign-in that lands on the dashboard as that
account, a sign-out whose old cookie is refused afterwards (with a positive
control before it), and a passkey registered in settings through Chromium's
virtual authenticator that signs the account back in.
Roving focus, Home/End, wrap, toggle mode, disabled and the chosen
segment's fill are now asserted through key presses and computed styles
instead of markup greps. The static render test is deleted; its two
source sweeps move to segmented-single-implementation.test.ts.
The browser setup sets the default theme class so colour tokens
resolve, and pre-bundles the browser files' dependencies so a cold
cache does not reload the page mid-run.
DashboardLayoutSection was a 620-line component. Its parts move to
components/settings/dashboard-layout/:
- layout-model: the reorder and hero-kind helpers and the label maps
- use-dashboard-layout-draft: the tokened read, the draft, save and
reset (with the snapshot and digest refresh) and the draft edits
- hero-settings-fieldset: the top card's switch, lead and highlights
- widget-lists: the sortable web list, the native-only group and rows
The section composes them. Markup and behaviour are unchanged.
Backup and restore, the adherence journey, delegated writes and the offline
page already run against the real server. They carry the @critical tag now,
and a structural guard refuses any /api interception in a file that carries
it, directly or through an imported helper, with a floor on the tagged count.
Global setup seeds one account per critical journey and per project, so the
journeys still to come can run at both widths without writing into each
other or into the shared fixture account. pnpm e2e:critical runs the set.
Run *.browser.test.tsx in headless Chromium through the Playwright
provider. The shared helper in tests/browser/ mounts the app's own
providers, stubs only the fetch boundary, and shims next/navigation
onto the page's real location and history.
The ledger file held two components of 404 and 314 lines. It now reads:
- dose-history/use-dose-history-ledger: the window read, summary, day
groups and the optimistic mark, plus the owner's edits (toggle, pin,
release, delete) and one reconcile step after every write
- dose-history/ledger-row-item: the row and, apart from it, the owner's
row menu
- dose-history/ledger-summary-chart and ledger-confirms
dose-history-ledger.tsx keeps the ledger layout. Markup and behaviour
are unchanged; the guards name the modules that now hold what they
check.
POST /api/medications keeps authentication, the body, the validation
answer and the HTTP mapping; the create (the mirrored-medication dedupe and
cap, the one-shot shape refusal, the initial course, the nested schedules,
the insert and its tail) moves verbatim to lib/medications/create-medication.ts
and answers created, existing or refused. The route falls from 541 to 94
lines. The parity walk recorded before the move is unchanged, and the
sharing guard reads the service beside the route for the handler body.
The size baselines take the lower value of the two sides per entry, and every ratchet baseline is written back to what the tree measures now (file and function sizes, version stamps, source reads, import cycles, route body parsing).
GET /api/measurements keeps authentication and the query refusal and hands
the validated query to measurementListResponse (lib/measurements/list),
which tries the list modes in their old order. POST keeps authentication,
the body, the bridge-source refusal and the three validation answers; the
single and combined writes with their shared tail move to
lib/measurements/create/create-readings.ts, the named sleep-stage write to
create-sleep-stage.ts. The route falls from 938 to 274 lines.
The parity walk recorded before the move is unchanged; the guards that name
the measurement writers, the write tail and the bounded reads follow the
code.
The five hand-written registrars (about 5,500 lines, one 997-line function)
are replaced by `jobs/registry/`: one `defineJob` entry per queue with its
cron, schedule send options, policy, work options, lock, lane and a handler
loader. `registerJobGroup` creates, schedules, retires and binds each group
in the order the registrars used; the worker boot walks the groups and runs
their boot discovery.
Handlers load on their queue's first job. The facts a declaration needs at
boot (names, crons, concurrency, send options) moved out of the heavy job
modules into light `*-queue.ts` siblings, and their importers point there,
so loading the registry evaluates 288 modules instead of the 1,164 the
registrars pulled in. The formerly inline handlers moved verbatim into
handler modules under `jobs/reminder/`.
Fitbit's queues live in `fitbit/jobs/definitions.ts` and enter the registry
through one spread, so removing the integration later is that folder and
that line.
`tests/integration/queue-registration-parity.test.ts` proves pg-boss holds
exactly what the registrars left (queues, schedules, bindings, byte for
byte) and loads every handler once. The queue tests and the runtime guard
assert on the registry objects instead of reading registrar source.
DocumentsView carried 970 lines of state before its first element. The
state moves to components/documents/vault/:
- selection-history: the `?doc=` history entry helpers (pure, tested)
- use-vault-filters: URL filters, the debounced search and the `/` key
- use-vault-library: usage, the paged list and its processing poll,
layout, uploads and their intake paths, the corpus-index affordance
- use-vault-filter-choices: conditions, procedures, visits, years
- use-vault-selection and use-vault-bulk: the selection with ranges and
Escape, the confirmed delete, re-filing, linking and the bulk share
- use-vault-detail: the detail sheet driven by `?doc=`, its close and
the check that the close took
- vault-page-actions, vault-drop-overlay
The page composes them in about 300 lines; markup and behaviour are
unchanged, and the guards follow the code they watch.
A parity walk over GET and POST /api/measurements: every create arm (single, combined, named sleep stage) with its duplicate and refusal, a bridge token, a delegated write, and every list mode. Sealed bytes are recorded by length, since each write carries a fresh nonce.
The sleep list rows of GET /api/measurements carry the main night's stage
minutes, time in bed, time awake and awakenings, and the aggregate buckets
carry the bucket's lowest and highest reading; none of these were in the
contract. The create answer's union tried the plain row before the named
sleep-stage result, so a contract check read a named-stage answer as a plain
row and dropped its status. The responses are unchanged; the contract now
says what they hold.
The route keeps authentication, the body, the base token, the layout schema
(the inline-schema inventory reads it there), the conflict mapping and the
responses. The rest moves verbatim into lib/dashboard/widgets/, one module per
stage: the layout read and the availability probe for GET, the unknown-id
drop, the refusal report and the stored-field merge for PUT, and the locked
reset for DELETE. The route is 317 lines, down from 513; its longest
function is 85 lines, down from 199.
The parity walk recorded before the move (dashboard-widgets.parity) is
unchanged. The tombstone-read guard now also scopes lib/dashboard/widgets/,
so the moved HRV probes stay policed.
MeasurementList was a 1,586-line component with fourteen pieces of
state. Its parts move to components/measurements/list/:
- list-model: the row type, page size, labels and formatting helpers
- use-measurement-list-filters: URL-owned filters, the value-range
draft, page, sort and the page-scoped selection
- use-measurement-list-data: the list read, the soft deletes with undo
and the selection operations
- use-measurement-edit: the edit draft, its unit conversion both ways,
the save and the discard guard
- use-measurement-row-display: how a row shows its reading, as a pure
rule (rowDisplayFor) bound to the account
- measurement-filter-rail, measurement-table (one row component),
measurement-card-list (one card component), row-actions shared by
both layouts, measurement-edit-sheet, day-drill-down
measurement-list.tsx lays these out in about 330 lines. Markup and
behaviour are unchanged. The glucose edit checks call the conversion
functions instead of matching source, and the guards that named the
old file name the module that now holds what they check.
The PUT handler was one 390-line callback. The route keeps the record
resolver, the 404, the body and its schema refusal; the correction moves
verbatim into lib/medications/update-intake-event.ts, split along its
stages in the order they always ran: the start-day floor, the slot
attribution, the in-place edit or the move onto another slot, then the
audit row, the event and the tails. The undo moves into
delete-intake-event.ts. The route is 124 lines; no function in either
module is over 140.
The parity walk recorded before the move (medication-intake-event.parity)
is unchanged. The sharing guard's audit, tombstone and overwrite legs read
the route together with the modules it hands its bodies to, listed in one
map that each route must still import, and the moved-dose provenance leg
now also asserts the original source is carried.
Record the parity walk for GET, PUT and DELETE /api/dashboard/widgets on the
current code: the first and the cached read, a full save with ids outside the
catalogue, a partial save that keeps stored fields and the per-chart range,
the schema, empty-body and base-token refusals, a save on the current token,
the recovery-off availability probe with and without an HRV series, a
delegate's read and refused writes, the reset, and an anonymous call.
postBatch was a 700-line function. The route keeps authentication, the
per-account preference read, the body caps, the rate limit, the parse and
the bridge-source refusal; the ingest moves verbatim into
lib/workouts/batch-ingest/: the first pass (entries), the duplicate probe
and write plan with the manual-correction rules (plan), the transaction
that writes corrections, workouts, tracks and series (write), and the
orchestrator with the audit, record detection, wide event, cache and
arrivals (ingest). The route is 275 lines.
The parity walk recorded before the move is unchanged; the guards that
name the track writer, the Workout writer set and the per-entry
external-id check point at the service files.
A parity walk over POST /api/workouts/batch: a mixed first batch with every per-entry refusal and the cross-source twin, the same batch with an enrichment, a corrected hand entry, a kept track, both whole-batch refusals and a narrow bridge token. Responses, wide events, queue tails and stored workouts with their series and tracks are pinned.
postBatch was one 1255-line function. The route keeps authentication, the
rate limit, the body, the entry cap, the schema and the bridge-source
refusal; the ingest moves verbatim into lib/measurements/batch-ingest/, one
module per stage in the order they always ran: the first pass (prepare),
the measurement writes with their prefilters and set-based insert, the
hourly sums, the sync checkpoint, the audit and wide-event report, and the
cache and arrival tail. The route is 274 lines; no function in the folder
is over 200.
The parity walk recorded before the move (measurements-batch.parity) is
unchanged, as is the set-insert golden. The guards that read the route for
the per-entry external-id check, the plausibility validator, the write
tail, the dense-read bound and the unit audit now read the service files.
Admin, coach, consent, chart-overlay, device registration, insights
feedback and provider chain, ECG ingest, onboarding, search and the AI
provider settings move to parseJsonBody with the same message, error
code, status and wide-event action they answered with. A refusal whose
annotate meta is computed from the issues, or puts its keys in another
order than the helper would, keeps that annotate verbatim in onInvalid.
The routes that still call safeJson do something between the read and the
schema: an optimistic-concurrency token split off the body, a count cap
answered first, a refunded quota slot, a conditional read, a non-Zod
check, or the body handed to a service.