Commit Graph
1009 Commits
Author SHA1 Message Date
Marc-André Bombeck 8d10e5ab94 feat(measurements): resolve each reading's Apple Health write-back
A reading sent with a measurements:write token keeps its EXTERNAL source
and now points at the token that carried it (measurements.ingest_token_id).
Every stored row answers a server-resolved appleHealthMirror: true for
WITHINGS, IMPORT and MANUAL, the token's choice for EXTERNAL, false
otherwise. The plain list accepts appleHealthMirror=true so a client can
page exactly those rows.

Migration 0476 adds the columns and links existing EXTERNAL readings only
where one measurement token provably sent them; 0477 builds the index
concurrently. Backups carry the link and a restore keeps it only for a
token the account still holds. A guard holds every EXTERNAL writer to
setting the link.
2026-10-23 19:27:52 +02:00
Marc-André Bombeck a0dce10c1a fix(activity): word the refusals that now reach a delegate's feed
The custom-metric create and update refusals and the medication layout
refusal are filed through auditLog now, so a delegate's refused request
reaches the owner's activity feed, where it read as the generic change.
Each gets its own sentence in every bundle; the refusal sentences live in
their own function. The audit write guard states its floor, and the
schedule-reader guard names the medication create and edit, which answer
through medicationDetail.
2026-10-23 17:19:21 +02:00
Marc-André Bombeck c82ed69549 Merge remote-tracking branch 'origin/release/v1.46.0' into feat/v146-server
# Conflicts:
#	src/__tests__/baselines/source-file-lines.json
2026-10-23 17:18:53 +02:00
Marc-André Bombeck 55a6287069 fix(audit): file every refused request through auditLog()
Twelve routes wrote their <surface>.validation-failed row with a bare
prisma.auditLog.create, so they skipped the two decisions auditLog makes:
an owner's own refused request stays on the wide event
(meta.rejected_attempt) instead of becoming a row, and a delegate's names
the delegate as the actor. The bulk medication and mood ingests, the
layout, overlay, device, health-sync, metric and ingest routes all go
through auditLog now, and a structural guard keeps raw writes to the one
writer and the briefing failure marker. The unit tests assert the auditLog
call, and the bulk walks are re-pinned without the owner's refusal row.
2026-10-23 16:54:57 +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 b4166cdc84 fix(logging): keep detached work from renaming the request's wide event
Work a request starts and does not wait for runs inside the request's
context, so an annotate({ action }) from it landed on the request's event
whenever it ran before the event was emitted. The measurement tail's
status re-warm logged a hand-entered reading as
insights.status.invalidate.skipped on some requests and as
measurement.create on others.

runDetached starts such work so that its action is dropped for the event
it was detached from; meta, warnings and the auth it reads stay as they
were, and an event the work opens for itself keeps its own action.
fireAndForget now takes a function and starts it detached, and every
trackBackgroundTask handle on a request path is built with runDetached,
which a structural guard holds.
2026-10-23 16:32:10 +02:00
Marc-André Bombeck 453bf6c6f3 test(guards): let the file-size floor follow the ratchet down
The splits on this line took the files over 800 lines from 115 to 96,
under the liveness floor of 100. The floor guards the matcher, so it
moves to 80, below the real count.
2026-10-23 15:54:10 +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 af15be4708 Merge branch 'feat/v146-server-insights' into feat/v146-server 2026-10-23 15:44:47 +02:00
Marc-André Bombeck 1a5d79f24e refactor(insights): move the briefing read and generation out of their route 2026-10-23 15:44:04 +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 98875debf4 refactor(insights): move the comprehensive overview builder out of its route 2026-10-23 15:25:20 +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 de96891ff5 refactor(import): move the CSV import writer out of its route 2026-10-23 15:00:35 +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 40eb1255b7 Merge branch 'feat/v146-server-meds2' into feat/v146-server
# Conflicts:
#	src/__tests__/external-id-stability-guard.test.ts
2026-10-23 14:52:03 +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 a1ab7136f5 test(medications): reach the actions of moved handler bodies in the activity inventory
The inventory reads a delegated route and its direct imports. With the medication handlers moved into lib/medications, the lifecycle reconciliation a dose write files was two levels from its route and dropped out of the walk. A route's handler-body modules now count at the route's own level, listed in one map whose every entry must be a direct import of its route.
2026-10-23 14:49:42 +02:00
Marc-André Bombeck 24af254249 refactor(medications): move the bulk dose ingest out of its route
POST /api/medications/intake/bulk keeps authentication, its own bulk bucket, the body with its size and schema refusals, the payload schema and the HTTP mapping. The ingest, one 846-line function, moves to lib/medications/intake-bulk/ in the order it always ran: the ownership read, the Apple mirror gate and the batch pre-reads (ingest.ts), each entry's refusals, site, write bookkeeping and P2002 answer (entry.ts), its slot attribution (entry-attribution.ts) and its slot, replay, probe or standalone write (entry-write.ts), then the audit row, the dose days, the snooze, the tails, the arrival record and the checkpoint. The two identical canonical-slot write blocks of the loop are one function now. The route falls from 1044 to 231 lines; no function in the new modules is over 70.

The parity walk recorded before the move (medication-intake-bulk.parity) is unchanged. The per-entry external-id markers name the module that classifies each entry, the batch-surface and key-scope legs read the moved code, and the intake-tracking allowlist and the dense-read guard follow it.
2026-10-23 14:47:57 +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 1b5e586a06 test(medications): strip line comments first in the intake tracking guard
The guard stripped block comments before line comments, so a '/*' inside a
line comment opened a block that swallowed the code after it. The delta
feed's intake read sat behind such a comment and was never seen. The
shared stripper handles that order; with it the feed shows up, and it is
listed: it mirrors recorded rows and computes no rate.
2026-10-23 14:45:08 +02:00
Marc-André Bombeck 2fcc2bdab2 refactor(medications): move the today list, the compliance buckets and the status update out of their route
GET and POST /api/medications/intake keep authentication, the query and body parsing with their refusals, the 404 and the HTTP mapping. The today list moves to lib/medications/intake-today.ts and the cached compliance buckets to intake-compliance.ts. The status update, one 432-line callback, moves to lib/medications/update-intake-status/ in the order it always ran: the event lookup, the record-only rule, the delegated refusals and the injection site (admission.ts), the take in place or onto another slot (take.ts), then the skip or snooze, the audit row, the rollups and the tails (update-intake-status.ts). The route falls from 634 to 149 lines; no function in the new modules is over 70.

The parity walk recorded before the move (medication-intake-status.parity) is unchanged. The sharing and activity-verb guards read the status-update modules beside the route, and the dense-read guard keeps the moved code in scope.
2026-10-23 14:37:47 +02:00
Marc-André Bombeck 3e9ed8e2e3 test(mood): name the raced-upsert winner read the bulk ingest now carries 2026-10-23 14:37:20 +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 c4ee519012 refactor(medications): move the per-medication dose write and history out of their route
POST and GET /api/medications/{id}/intake keep authentication, the intake and ownership guards, the body and query parsing with their refusals, the delegated outcome-change refusal and the HTTP mapping. The dose write, one 607-line function, moves to lib/medications/record-intake/ in the order it always ran: the injection-site check and the slot attribution (attribution.ts), the replay and dedup answers and the slot, probe or standalone write (write.ts), then the inventory, the audit row, the rollup, the lifecycle and the tails (record-intake.ts). The history list moves to list-intake-events.ts. The route falls from 767 to 200 lines; no function in the new modules is over 75.

The parity walk recorded before the move (medication-intake.parity) is unchanged. The activity-verb guard now reads the awaited audit actions of a delegable write's handler modules beside the route, for this route and for the medication create that moved before it, and holds each module to its route's imports; the sharing guard, the intake-tracking allowlist and the dense-read guard follow the moved code.
2026-10-23 14:29:58 +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 8a6c6bfdd7 Merge branch 'feat/v146-ui-e2e' into feat/v146-ui 2026-10-23 14:19:29 +02:00
Marc-André Bombeck da937718bc refactor(measurements): move the series readers out of their route
GET /api/measurements/series keeps authentication, the query and its
refusal; readSeries (lib/measurements/series/read-series.ts) picks the
reader in the old order (sleep nights, paired blood pressure, the bucketed
read of a dense kind, the raw rows) and adds the direction, change,
reference lines and stat strip. The kinds, units and point shape live in
kinds.ts. The route falls from 752 to 95 lines.

The parity walk recorded before the move is unchanged. The guards on the
verdict resolver, the spread, the pulse day mean and the unit literals read
the new files, the last two with their scope widened to the series folder.
2026-10-23 14:13:52 +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 967fa2c56b refactor(medications): move the medication read, edit and delete out of their route
GET, PUT and DELETE /api/medications/{id} keep authentication, the ownership guard, the body and the HTTP mapping. The read moves to lib/medications/read-medication.ts; the edit, one 886-line callback, moves to lib/medications/update-medication/, split along its stages in the order they always ran: the preflight checks and refusals, the schedule normalisation, the transaction (era archive, tombstones, the row update, the pause era, the course write) and the commit tail with its audit row. The delete moves to delete-medication.ts. The route falls from 1270 to 100 lines; no function in the new modules is over 115.

The parity walk recorded before the move (medication-detail.parity) is unchanged. The sharing guard reads the edit and delete modules for the handler body, the intake-tracking allowlists name the modules that now read the schedule and the open slots, the published-DTO leg reads the read module, and the dense-read guard keeps the moved code in scope.
2026-10-23 14:10:17 +02:00
Marc-André Bombeck 817a922df5 refactor(workouts): move the workout detail read and the manual delete out of their route
GET keeps authentication, the module gate and the AI capability, and hands
the rest to readWorkoutDetail (lib/workouts/detail.ts): the stored row, the
canonical winner of its cluster, the enrichment reads and the body, as
named stages. DELETE keeps authentication and the HTTP answers; the
ownership check, the synced-source refusal and the transactional removal
with its records, audit, cache and detection tail move to
lib/workouts/delete-manual-workout.ts.

The parity walk recorded before the move is unchanged; the guards that name
the canonical-identity reads and the track readers point at the service.
2026-10-23 14:00:28 +02:00
Marc-André Bombeck 51835fe1d0 refactor(analytics): move the default body builder out of its route
The route keeps authentication, the slice refusal, the summaries slice, the
cache read and the headers. buildAnalyticsResponse (446 lines) moves to
lib/analytics/response/default-body.ts and calls one module per block, in
its old order: the blood-pressure in-target windows, the glucose summaries
and clinical panel, the correlation hypotheses with their stored patterns,
the Health Score with its Rest Mode framing, and the sleep stages. The
route falls from 761 to 155 lines.

The parity walk recorded before the move is unchanged; the guards on the
glucose window and the bounded reads follow the code.
2026-10-23 13:52:57 +02:00
Marc-André Bombeck c2a63307ed test: point guards at the split provider modules
The page-walk exception for the Google Health list walk follows
forEachDataPointPage into data-points.ts, and the workout provenance guard
reads the raw Polar and Strava sport labels where the mappers now live.
The write-step helper's tests assert the result it returns, including a
failed fold and a caller that re-warms the status cards itself.
2026-10-23 13:44:21 +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 232c3db6ef docs(testing): describe the browser project and its helper
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.
2026-10-23 13:16:56 +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 c9917d2de0 refactor(settings): split the managed record settings forms
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.
2026-10-23 13:13:55 +02:00
Marc-André Bombeck 4e0e3019ad test: drop an auth-guard helper nothing reads 2026-10-23 13:13:06 +02:00
Marc-André Bombeck de970ac079 test(e2e): restore a person's full backup into their recreated account
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.
2026-10-23 13:12:45 +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 34a93b883f test(e2e): a settings change persists on the server
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.
2026-10-23 13:04:04 +02:00
Marc-André Bombeck 0e9daa51f9 test(e2e): read a WHO-5 check-in word for word in German and Brazilian Portuguese
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.
2026-10-23 13:02:32 +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