Commit Graph
62 Commits
Author SHA1 Message Date
Marc-André Bombeck 111047491e fix(medications): end every medication and intake read on the row id
Postgres returns rows that tie on an ORDER BY in no particular order,
and not the same one twice. The medication reads ordered by columns two
rows share all the time: every read that hands schedules on included
them with no order at all, medications were listed by createdAt alone
(one import writes many in a millisecond), intakes by scheduledFor (a
twice-daily medication puts every morning dose on one instant), and
"the latest intake" lookups by takenAt. A client read the same account
in a different order from one request to the next, and a latest-intake
lookup picked either of two rows.

Every findMany / findFirst on a medication table, nested includes
included, now ends its order on the id in the direction of its last key,
and schedules are read by window, then id (MEDICATION_SCHEDULE_ORDER, the
order the backup already wrote). medication-read-order-guard.test.ts
holds the tree to it. medication-read-order.test.ts writes tying rows in
the opposite order to the one each read promises, through the medication
list and today's intakes, and fails without the tie-breakers.

The parity suite's own state reader had the same gap: it sorts rows by
content with the ids blanked, and rows that tie then kept whatever order
the unordered read returned, so their <id:N> labels swapped under a
loaded run. Its reads are ordered by id, which for a cuid is the order
the rows were written in.

The JSON import's schema left the route, so its entry in the inline
schema strictness register goes; the schema module says why its objects
are not strict.
2026-10-23 21:54:25 +02:00
Marc-André Bombeck a840e30fac feat(import): carry every field the app's own files write
The JSON import read six measurement fields and six mood fields while
the full backup and the JSON export carry twice that. A sleep night came
back as stageless rows, a range reading as a single value, an ECG alert
without its verdict, and a mood entry without its note, its level-A
values, its zone, its tag and factor links or its day context.

The import now reads valueMin/valueMax, sleepStage,
rhythmClassification and deviceType on a reading, and note, a1 to a5,
tz, factors, structuredTags and context on a mood entry. Each mood entry
is written with its links and context in one transaction, the way the
bulk mood ingest writes one. A link to a tag the importing account does
not have is dropped and named in droppedTagKeys / droppedFactorKeys, and
the import card says which. The schema moves to
src/lib/import/json/schema.ts so the guards can read it.

json-import-field-coverage.test.ts runs the JSON export and both backup
variants on an account with every column filled in: every field they
write is either read by the import or named with the reason it is not
(row ids, source, deletion records, ciphertext, sync bookkeeping), and a
reason for a field no exporter writes fails as stale. The round trip
imports each file into a fresh account and compares the re-export field
for field. The published contract is held to the runtime schema.
2026-10-23 21:33:06 +02:00
Marc-André Bombeck 879473305c Merge remote-tracking branch 'origin/release/v1.46.0' into feat/v146-ui
# Conflicts:
#	src/__tests__/baselines/source-function-lines.json
#	src/__tests__/baselines/version-stamp-comments.json
2026-10-23 19:54:08 +02:00
Marc-André Bombeck fd34ee13b9 fix(charts): round y ticks, calendar x ticks and labels beside the plot
The value chart's axes and labels, as drawn on a 90-day blood pressure
view on a tablet:

- Y ticks were whatever Recharts split the padded domain into, so the
  labels read 57, 83, 109. The domain is now widened to a 1/2/5 step
  and every tick is handed to the axis, evenly spaced and all drawn.
- X ticks were every n-th point, so their dates drifted with the data.
  They now fall on a calendar grid (days, Mondays, two weeks, months,
  quarters, years), each on the point nearest the grid line, and the
  step is the smallest whose labels fit the measured plot width.
- The "your average" label sat inside the plot over the readings, and a
  target zone's label was clipped to its first letter. Both now sit
  beside the plot, in a right margin sized to the longest label.

A browser test draws the axes with Recharts and pins the tick values,
the Monday grid, and both labels outside the plot clear of every dot.
2026-10-23 19:53:28 +02:00
Marc-André Bombeck f55a86a08c Merge remote-tracking branch 'origin/release/v1.46.0' into feat/v146-server
# Conflicts:
#	src/__tests__/baselines/source-function-lines.json
2026-10-23 19:28:58 +02:00
Marc-André Bombeck d3385478a7 feat(settings): switch Apple Health write-back per measurement token
Each measurement token row carries a switch, the mint form an opt-in, and
the card a notice when readings exist that no token reaches. Copy in all
nine locales. The endpoint catalogue moves to its own file and the
revoked list out of the token card.
2026-10-23 19:28:05 +02:00
Marc-André Bombeck 0e7a186677 test(ratchet): lower the shell and rail baselines
sidebar-nav.tsx drops under 800 lines with its scroll region in a module
of its own, and AuthShell and SidebarNav are shorter.
2026-10-23 18:24:30 +02:00
Marc-André Bombeck 551fbbbdec Merge branch 'release/v1.46.0' into feat/v146-ui 2026-10-23 16:44:26 +02:00
Marc-André Bombeck 31048e5e63 test: tighten the size and version-stamp baselines after the insights moves 2026-10-23 15:45:16 +02:00
Marc-André Bombeck 356d947ab0 refactor(dashboard): move the summary builder out of its route 2026-10-23 15:25:48 +02:00
Marc-André Bombeck af5cf1ace2 refactor(auth): move the SSO callback's account resolution and sign-in endings out of its route 2026-10-23 15:17:27 +02:00
Marc-André Bombeck 91a5464aa3 refactor(documents): move the vault upload reading and the list read out of their route 2026-10-23 15:10:26 +02:00
Marc-André Bombeck c6238cbe14 test: tighten the size and version-stamp baselines after the medication moves 2026-10-23 14:52:43 +02:00
Marc-André Bombeck 11a7124d12 refactor(import): move the JSON import writers out of their route 2026-10-23 14:51:51 +02:00
Marc-André Bombeck ad27e5b89d refactor(sync): move the delta feed's domain pages out of its route 2026-10-23 14:45:26 +02:00
Marc-André Bombeck c40d728b47 Merge branch 'release/v1.46.0' into feat/v146-server
# Conflicts:
#	src/__tests__/baselines/source-function-lines.json
#	src/__tests__/baselines/version-stamp-comments.json
2026-10-23 14:35:19 +02:00
Marc-André Bombeck 315ee0ff98 refactor(mood): move the bulk ingest out of its route 2026-10-23 14:34:36 +02:00
Marc-André Bombeck 6555acefe1 Merge branch 'release/v1.46.0' into feat/v146-ui 2026-10-23 14:33:21 +02:00
Marc-André Bombeck a60111cc9d Merge branch 'feat/v146-ui-browser' into feat/v146-ui
# Conflicts:
#	src/__tests__/baselines/test-source-reads.json
#	src/__tests__/baselines/version-stamp-comments.json
2026-10-23 14:20:08 +02:00
Marc-André Bombeck 870c8e053a Merge branch 'release/v1.46.0' into feat/v146-integrations 2026-10-23 14:13:32 +02:00
Marc-André Bombeck 8b3c095df5 refactor(fitbit): keep the Fitbit jobs inside the Fitbit folder
The backfill, the sleep repair, the OAuth-state cleanup and the sync
handlers move from lib/jobs into lib/fitbit/jobs, beside the queue facts
and definitions the registry already spreads. Their tests move with them.
Removing the integration is now the folder, its routes, the registry
spread and the drain's two cases.

The updatedAt backfill guard also walks provider jobs folders, so the
moved backfill stays covered.
2026-10-23 13:33:11 +02:00
Marc-André Bombeck 7e54b6be81 refactor(integrations): split the provider clients into wire types, endpoints and mappers
Oura, Polar, WHOOP and Strava each keep the transport and endpoints in
`client.ts` and gain `wire-types.ts` (the record shapes) and `map.ts` (the
pure record-to-row mappers, no I/O). Google Health's 1,600-line client
becomes `budget.ts` (slice budget and outcome tally), `request.ts` (error
detail, retry, rate limits), `data-points.ts` (the paginated walker) and a
small `client.ts` for the profile and roll-up reads; its `export * from
"./mappers"` facade is gone and its importers name the module that defines
what they use.

The code moved verbatim; the mapper goldens written before the move and
the provider integration tests pass unchanged.
2026-10-23 13:30:10 +02:00
Marc-André Bombeck 5eafed8dd5 refactor(admin): split the backups section
The section becomes a composer over backups/: the restore dialog with its
preview, the refusal, skip and schedule notices, the restore jobs and
their request, the upload flow, the run and download actions, and the
two row layouts sharing one action pair. The upload total is a pure
function and tested as one. The read-failure guard's 250-character
window had stopped reaching the restore preview's own failure lines; the
preview now sits in a smaller component, the guard sees it, and it is
listed as a deliberate exception with its reason.
2026-10-23 13:20:48 +02:00
Marc-André Bombeck 36fe53bbaa test: write the ratchet baselines back after the medication and widget moves 2026-10-23 13:16:46 +02:00
Marc-André Bombeck e626f2123f refactor(integrations): run one write step after every provider sync
`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.
2026-10-23 13:12:23 +02:00
Marc-André Bombeck 7c1ed25915 refactor(documents): split the document detail sheet
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.
2026-10-23 13:11:20 +02:00
Marc-André Bombeck 3ab85f85f9 test(measurements): drive the measurement form in a real browser
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.
2026-10-23 13:07:29 +02:00
Marc-André Bombeck cc36f46f10 test(filters): drive the URL filter rail in a real browser
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.
2026-10-23 13:00:19 +02:00
Marc-André Bombeck d7f0d88d59 refactor(checkups): split the checkups section into form, card and postpone sheet
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.
2026-10-23 12:59:49 +02:00
Marc-André Bombeck b6f45c0d88 refactor(integrations): move every provider OAuth client onto one factory
`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.
2026-10-23 12:57:47 +02:00
Marc-André Bombeck 25b5833b11 refactor(settings): split the dashboard layout section into draft, lists and hero settings
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.
2026-10-23 12:52:36 +02:00
Marc-André Bombeck 58215f3338 refactor(medications): split the dose-history ledger into its hook, row and dialogs
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.
2026-10-23 12:41:46 +02:00
Marc-André Bombeck 069e07b510 Merge branch 'release/v1.46.0' into feat/v146-server
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).
2026-10-23 12:39:42 +02:00
Marc-André Bombeck 1a84cf27a8 Merge branch 'release/v1.46.0' into feat/v146-integrations
# Conflicts:
#	src/__tests__/baselines/source-file-lines.json
#	src/__tests__/baselines/source-function-lines.json
2026-10-23 12:39:13 +02:00
Marc-André Bombeck 4807a70c1d refactor(jobs): declare every queue in a typed registry and load handlers on first job
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.
2026-10-23 12:36:09 +02:00
Marc-André Bombeck 3eb5aa9364 refactor(documents): split the vault page into filters, library, selection and detail
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.
2026-10-23 12:35:46 +02:00
Marc-André Bombeck 3c0063edb2 refactor(measurements): split the measurement list into view state, data, rows and sheet
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.
2026-10-23 11:42:43 +02:00
Marc-André Bombeck 5075536f7f refactor(charts): split HealthChart into its read, view, layers and header
HealthChart was a 1,929-line component. Its parts move to
components/charts/health-chart/:

- series-math: series names, day keys, moving average, personal
  baseline, annotation and marker placement (pure)
- use-health-chart-series: the fetch window, the batched-slice decision,
  the daily read and the data-ready latch
- use-health-chart-view: the visible slice, bucketing, the average and
  trend lines, the prior period, the visible-range stats and the y domain
- use-health-chart-overlays: value bands, target zones, baselines, the
  trend read-out, the spoken summary and the table columns
- chart-header, band-overlay, chart-tooltip-content
- chart-layers: the reference layers and series as plain functions that
  return the Recharts elements, so the plot's child list is unchanged

health-chart.tsx keeps the component and its path (chart-runtime still
imports it from there) and shrinks to 488 lines. Output is unchanged.
The guards pinned to the file follow the code into the folder, and the
baseline-gating check now calls the layer instead of matching source.
2026-10-23 11:25:16 +02:00
Marc-André Bombeck 0a88155aec Merge branch 'release/v1.46.0' into feat/v146-ui
# Conflicts:
#	src/__tests__/baselines/source-file-lines.json
#	src/__tests__/baselines/source-function-lines.json
2026-10-23 11:08:24 +02:00
Marc-André Bombeck e25b6365fd refactor(dashboard): compose the page from a tile registry
The dashboard page held every tile and chart card in one 2,100-line
function: a 1,279-line inline block pushed 24 cards behind 36 show flags.
The cards now live in components/dashboard/tiles/, each with its layout
widget, its own visibility rule (data floor, module, resolved layout) and
its render; the layout toggle and order are applied once by the registry.

- use-dashboard-data: the snapshot or legacy reads, the layout, mood,
  thresholds, the batched chart series and the loading gate
- tile-context: summaries, display units, comparison deltas, freshness,
  directions and target bands, derived once per render
- strip-tiles / chart-tiles: the two registries and their builders
- dashboard-tiles: the strip, the chart row, their skeletons and the
  empty state

The page is a composer of about 170 lines. Rendering is unchanged. The
guards that read page-client.tsx read the whole registry through one
helper; the weight-direction, HRV freshness, BP-in-target comparison and
chart-reveal checks now build the real tiles instead of matching text.
2026-10-23 11:06:32 +02:00
Marc-André Bombeck 78ac2c5aeb refactor(sharing): read and judge grants from a leaf module
lib/sharing/grants.ts held both the grant transitions and the predicates
every reader asks (state, level, scope, the live grant for a pair). The
transitions reach into the managed-profile lifecycle and the OAuth connect
target, and both of those read a live grant, so grants, lifecycle and
connect-target formed an import cycle.

The predicates, findActiveGrant and touchGrantUsage move verbatim to
lib/sharing/grant-access.ts, which imports only the database client and
the scope vocabulary. Importers and the mocks that replaced those
functions point at the new module. Runtime import cycles fall from 7 to 6.
2026-10-23 11:02:10 +02:00
Marc-André Bombeck 5a8c736017 chore: keep the v1.45 size baselines for the navigation the topics preview had split
Reverting the topics preview puts sidebar-nav, bottom-nav and the
/api/auth/me handler back to their v1.45 code; their baselines return to
the v1.45 values, nothing grows beyond them.
2026-10-23 11:00:45 +02:00
Marc-André Bombeck ee1561496b refactor(auth): move the request guards out of api-handler into lib/auth/guards
api-handler.ts held two modules: the route wrapper and thirteen guards. The
guards now live in lib/auth/guards/, one module per family (the caller,
the acting-account carrier, the record modes, the cookie-only helpers, the
fresh-factor and recent-proof gates, the Bearer elevation, MFA management).
api-handler.ts keeps the wrapper and re-exports every guard, so it stays the
one public entry and no route changes its import.

The bodies move verbatim; only the version prefixes of moved comments are
dropped. The structural guards that read the auth layer now find the module
that declares a guard by name (helpers/auth-guards.ts) instead of a fixed
path, and a new guard holds the entry: nothing outside the folder but
api-handler.ts imports a guard module.
2026-10-23 10:54:23 +02:00
Marc-André Bombeck c1911e71e3 Revert "feat(topics): add the Topics preview area, its navigation entry and its switch"
This reverts commit c68e833dcc.
2026-10-23 10:46:54 +02:00
Marc-André Bombeck 9de64cae3e Merge branch 'release/v1.46.0' into feat/v146-questionnaires
# Conflicts:
#	src/__tests__/baselines/source-file-lines.json
#	src/__tests__/baselines/source-function-lines.json
2026-10-23 10:20:34 +02:00
Marc-André Bombeck c68e833dcc feat(topics): add the Topics preview area, its navigation entry and its switch
/topics shows a tile per topic the record holds data for, and names the
empty topics and the module-hidden ones once each. /topics/<topic>
shows the topic today, its signals side by side over 7, 30, 90 days or
a year against their usual range, how they move together, their trends,
its blocks, the single-metric pages behind it and what it is missing.
Every topic links into the metric pages, which stay as they are, and
each page ends on a neutral note that this is a preview.

Settings > System > Preview features is listed again and switches the
area on for the person reading. While it is on, the sidebar carries a
Topics entry with a Beta badge directly below Insights and the phone's
More sheet leads with a Topics tile; the bottom bar does not change.
The pages leave for Insights when the switch is off.

The charts load through the shared chart runtime. The sidebar rows move
into their own module and the More sheet tile into its own component,
so neither shell file grows; the lab trend wording is shared between the
marker page and the metabolism topic. Copy in all nine locales.
2026-10-23 08:18:53 +02:00
Marc-André Bombeck 14273051db feat(doctor-report): mark screener totals answered in an unvalidated translation
The report data counts, per screener score type, how many of the
window's results were answered in a translation that has not been
validated (`screenerUnvalidatedTranslations`, read only when a screener
total is in the report). The PDF marks such a row with an asterisk and
says under the vitals table how many of how many read as orientation;
the clinician share view says the same under the row.

To keep the aggregator and the PDF section from growing, the wellness
summary and the per-context glucose rows move into helpers; the size
ratchets only fall.
2026-10-23 08:14:32 +02:00
Marc-André Bombeck bafda973ee refactor(insights): read the mood block's daily points in a module of their own
The rollup-or-fallback read of the per-day mood points moves out of features.ts into mood-daily-points.ts, unchanged, which keeps features.ts under its size baseline after the calendar-day ages.
2026-10-22 21:31:27 +02:00
Marc-André Bombeck 32b71a7df9 Merge branch 'release/v1.45.0' into fix/v145-sheet-motion
# Conflicts:
#	src/__tests__/baselines/source-file-lines.json
#	src/__tests__/baselines/source-function-lines.json
#	src/__tests__/baselines/version-stamp-comments.json
2026-10-22 20:51:07 +02:00
Marc-André Bombeck 038f8b10cd feat(mental-health): one check-in pattern for every questionnaire, worded as published
Every instrument now runs the same wizard: the form's own question stem
above each item (the PHQ-9 and GAD-7 stems were missing), the item's
exact answer options as one radio group in source order, progress, Back,
number-key answers, and a review step that lists every answer, lets any
of them be changed and is the only step that submits. The result leads
with the crisis card when it applies, then shows the score on the
instrument's scale, the band, what the band means, the change since the
last check-in against the meaningful-change threshold, and the next
step. Scoring is untouched.

The wording was read against the published versions. German follows the
PHQ-D and Löwe's GAD-7 verbatim, including the follow-up question in its
published formal address. Spanish, Italian and Polish carry the official
PHQ-9 and GAD-7 translations, French the official GAD-7, and the GAD-7
gets its own answer set because the Italian forms word it differently.
Where a version could not be matched to a published form (the French
PHQ-9, every Korean form) the language now shows the English original.
Each locale list names its sources, and spot checks pin the corrected
wording.
2026-10-22 19:28:29 +02:00