Commit Graph
8130 Commits
Author SHA1 Message Date
Marc-André Bombeck 0e5afe76a4 fix(integrations): skip the ledger page when the watchdog announced it
A failure streak reaching the alert threshold paged the operator even
when the watchdog had already announced an open incident for the same
connection. The ledger now checks for that announced incident first,
stamps the streak as announced and sends nothing; the watchdog's
recovery note still ends it.
2026-10-15 16:38:12 +02:00
Marc-André Bombeck 3e95755c3b fix(integrations): page only for trouble an operator can act on
The watchdog opened an incident for any degraded state that lasted a
day, so an Apple Health account whose phone app stopped running read as
no_new_data for good and paged. Incidents now open for action_required,
for provider errors and for the watchdog's own unexpected silence; stale
or stalled data alone never pages. The first pass after a deploy, with
no watch row yet, records what stands as already announced instead of
paging every long-broken connection at once.
2026-10-15 16:38:12 +02:00
Marc-André Bombeck 1c05813b82 fix(google-health): keep the skip's place across backfill slices
A walk that reads without the edge bound passed over everything earlier
slices wrote on every slice, and once it crossed the edge the skipped
pages were counted against the walk's page ceiling. The skip now stays
counted for the whole walk, runs on the slice's clock and its own
three-minute limit, and saves the page token it stopped at (on a limit,
a rate limit or the slice budget) so the next slice re-reads that one
page and goes on. A saved place counts as progress only when it moves
forward; a token Google refuses, or one that no longer reaches the edge,
sends the walk back to the newest page as before.
2026-10-15 16:38:12 +02:00
Marc-André Bombeck 73b9870338 Merge branch 'v1424/w2' into release/v1.42.4 2026-10-15 15:42:46 +02:00
Marc-André Bombeck 17478b08aa test(healthkit): type the card fixture's arrival fields 2026-10-15 15:38:42 +02:00
Marc-André Bombeck 09072c9f62 test(healthkit): pin dose arrivals, the background date and migration 0401 over enum rows 2026-10-15 15:36:20 +02:00
Marc-André Bombeck 03b24461ad feat(healthkit): record Apple Health dose arrivals and the last background arrival per type
Migration 0401 turns healthkit_type_syncs.type into text so the ledger can
hold MEDICATION_DOSE, and adds last_background_at (seeded from rows whose
latest arrival was a background one). POST /api/medications/intake/bulk
takes syncTrigger and records APPLE_HEALTH dose arrivals; the Apple Health
status read serves a MEDICATION_DOSE freshness entry and lastBackgroundAt
on every entry with arrival facts.
2026-10-15 15:33:30 +02:00
Marc-André Bombeck 6313c0174a Merge branch 'v1424/readiness' into release/v1.42.4 2026-10-15 15:24:29 +02:00
Marc-André Bombeck f95c2bfd41 Merge branch 'v1424/cards' into release/v1.42.4 2026-10-15 15:22:12 +02:00
Marc-André Bombeck eedd91ec64 test(e2e): drive the integration cards through every health state
One mocked page carries a provider in each state, checked at desktop width
and at 390 px by data attributes, with the menu driven from the keyboard,
the copied diagnostics compared with the shown text, and an axe pass in both
themes. The solid destructive badge replaces the red-on-red wash, which
fell below AA.
2026-10-15 15:21:49 +02:00
Marc-André Bombeck 91ccd154b1 test(labs): register the overview route in the sharing and module-gate inventories 2026-10-15 15:19:46 +02:00
Marc-André Bombeck eab4d644f7 feat(ops): log pending migrations before migrate deploy and document a dump before every update 2026-10-15 15:12:57 +02:00
Marc-André Bombeck e1369996ee Merge branch 'v1424/oura-followups' into release/v1.42.4 2026-10-15 15:10:59 +02:00
Marc-André Bombeck 648bb0d985 fix(medications): walk intakes to the drug-level horizon, read every symptom in a window, and guard fixed client fetch limits 2026-10-15 15:10:53 +02:00
Marc-André Bombeck 73922c039e fix(insights): say what fills the recovery page when it is empty
The empty note told a connected Oura account to connect Oura. The page
charts the WHOOP and Polar recharge, strain and load readings and the
Oura resilience level; Oura's readiness is the recovery score linked
above. The note now says so.
2026-10-15 15:10:18 +02:00
Marc-André Bombeck 35a33ca173 fix(oura): skip rejected and deleted sleep periods
Oura returns detected periods the person dismissed (type rest) and ones
they deleted in the sleep collection. They were imported as sleep, with
their lowest heart rate, HRV and breathing rate, which added an extra
session to the day. They now map to nothing, and rows an earlier sync
stored for such a period are soft-deleted on the next sync through the
shared write tail.
2026-10-15 15:10:18 +02:00
Marc-André Bombeck d7588fe9bf fix(sleep-score): place each night's midpoint on its main session
A nap on the same wake day moved the night's latest instant to the end
of the nap and added its minutes, so the midpoint of a regular night
landed around noon. A few such days scored consistency at 0, and a nap
after the latest night scored timing at 0 as well. The night now carries
its main session (wake instant and asleep minutes), and the midpoint is
read from that alone. Sleep rhythm and chronotype read the same midpoint.
2026-10-15 15:10:18 +02:00
Marc-André Bombeck 1a5b09a73c feat(settings): one integration card for every connection
Every connection in Settings renders the same card from the server's health
field: a badge with one word, one sentence per reason, the newest data as a
meta line, a collapsed details block whose text is exactly what the copy
button copies, and an action row with the rare actions in a menu and at most
one primary button. The per-provider banners, the raw provider error line,
the token expiry, the failure counter and the button rows are gone; every
capability they carried is reachable from the sentence, the one button, the
menu or the details. The managed-profile view renders the same card,
read-only. A structural guard keeps the cards off the retired fields.
2026-10-15 15:06:03 +02:00
Marc-André Bombeck b17a250894 feat(record-settings): publish connection health for a managed record's integrations
The guardian's read of a managed record's connections now carries the same
server-resolved health the owner's own envelope does, loaded for the record
and never for the guardian. The legacy state stays on the wire for one
transition release.
2026-10-15 15:06:02 +02:00
Marc-André Bombeck 7c905249fb fix(medications): page the side-effect list and name every GLP-1 medication in the timeline 2026-10-15 15:05:51 +02:00
Marc-André Bombeck 53e60f43f1 feat(labs): list every marker from a server-side per-biomarker overview 2026-10-15 15:03:06 +02:00
Marc-André Bombeck 600145427b docs(measurements): describe the 10-minute heart-rate bucket in the batch route header 2026-10-15 14:58:15 +02:00
Marc-André Bombeck 8ebc6fe546 fix(integrations): give Oura and WHOOP a 48 h silence window
Their rings and straps upload only when the phone app is opened, so a day
without opening it is ordinary and must not page the operator. Wearables
whose cloud receives data on its own keep 24 h, Apple Health keeps 48 h,
and scales, cuffs and Strava never alert on silence.
2026-10-15 14:30:55 +02:00
Marc-André Bombeck 228106be5f Merge branch 'chore/demo-seed-v1423' into release/v1.42.4 2026-10-15 14:26:11 +02:00
Marc-André Bombeck a4942e631f Merge branch 'v1424/ghfallback' into release/v1.42.4 2026-10-15 12:45:23 +02:00
Marc-André Bombeck 1f9180fe6f test(google-health): drive a backfill to completion against a Google that refuses the edge filter
Also documents the fallback and its log fields.
2026-10-15 12:35:49 +02:00
Marc-André Bombeck ebc0168f53 test(google-health): pin the fallback when Google refuses the resume edge filter 2026-10-15 12:33:34 +02:00
Marc-André Bombeck e2d5bf121c fix(google-health): read past the resume edge when Google refuses its filter 2026-10-15 12:31:39 +02:00
Marc-André Bombeck de40db7ad0 Merge branch 'v1424/fixes' into release/v1.42.4 2026-10-15 12:23:55 +02:00
Marc-André Bombeck 7be05608f8 test: date fixtures that feed a clock-relative window relative to now
A fixture dated near the day it was written passes until the date slides
out of a window the route counts back from the clock. Five did: a snooze
until a fixed date that becomes the past in January, a prognosis read
against the wall clock over days seeded at a fixed date, sleep rows and a
weight streak read through windows that end now, and a sync token that
would expire on a fixed date. The e2e mood heatmap mock and a timeline
URL are dated from today as well.

A guard scans the integration and e2e trees for near-now absolute dates
within reach of a window parameter, with an allowlist for files whose
windows are fixed on both ends.
2026-10-15 12:23:09 +02:00
Marc-André Bombeck b96c114a45 Merge branch 'v1424/gh' into release/v1.42.4 2026-10-15 11:45:45 +02:00
Marc-André Bombeck c8194c4a86 test(google-health): drive a three-year heart-rate backfill through the drain to completion
1.58 million readings, paged by a generated fake of the list API that
honours the edge and lower-bound filters, with a server error, a timeout and
an hour-long rate limit injected. Pins that the backfill completes over
several slices, imports every reading exactly once without rewriting a row,
lets the hourly sync run between and beside slices, writes one log event per
slice with its requests and edges, and keeps the heap flat across the
history under the production heap ceiling.
2026-10-15 11:45:15 +02:00
Marc-André Bombeck 2d03eba587 fix(google-health): resume a backfill inside a collection and run it in short slices
A backfill noted progress only once a whole collection was read to the end.
A heart-rate history of years that one attempt could not finish (its budget,
a rate limit it could not wait out, a single failed request among thousands
of pages) started the next attempt from the newest page again, finished
nothing, and failed with "marker not stamped" on every attempt.

- Every spot-reading collection read page by page now records, after each
  page it writes, the oldest sample it has written (backfillProgress.cursors).
  The next attempt asks Google only for samples at or beyond that edge. The
  edge is held back once a page's write fails, so no rows are skipped.
- The backfill runs in slices of five minutes or 500 pages. A slice that
  finished a collection or moved an edge continues with the next one; only a
  slice that moved nothing fails, and its error names each unfinished
  collection and why the slice stopped. The slice ceiling is derived from the
  collections and the page ceilings of the resumable ones.
- A rate limit Google will not lift within a minute ends the slice instead of
  failing the walk, and the next slice starts five minutes later. Server
  errors, timeouts and dropped connections are retried three times.
- The heart-rate stream is read last, so the light collections finish in the
  first slice.
- The hourly sync waits up to eight minutes for a running slice instead of
  skipping the account for as long as the import runs.
- Each slice annotates googleHealth.backfill.slice with pages, elapsed time,
  stop reason, per-collection status and edges, and reports progress on the
  job.progress line.
2026-10-15 11:26:49 +02:00
Marc-André Bombeck 47deda9d76 Merge branch 'v1424/integrations' into release/v1.42.4 2026-10-15 11:26:37 +02:00
Marc-André Bombeck 73c4dda224 fix(visits): reach every lab result, condition, document and visit from the link pickers
The visit form's lab picker offered the newest 200 results, the condition
picker the newest 100 conditions, the document pickers the newest thousand
documents and the document sheet's visit picker the newest 100 visits.
On a long record everything older could not be linked.

Each picker now opens on the records around the date of the thing being
linked, pages older and newer from there, and searches through the list
route itself. Records already linked always show, from the labels the
record's own read carries and from what was picked in the sheet.

The lab list takes a q filter over analyte and panel, the condition list
from/to/q/offset/sortDir, and the visit list an offset.
2026-10-15 11:24:40 +02:00
Marc-André Bombeck f2646bc186 test(integrations): cover the health field on the status routes 2026-10-15 11:15:06 +02:00
Marc-André Bombeck 4a49784464 chore(demo): file the demo medications under categories, let the evening supplement record itself, and store two lab readings converted from SI units 2026-10-15 11:11:57 +02:00
Marc-André Bombeck 8162afc0e1 docs(api): publish the connection health state 2026-10-15 11:09:50 +02:00
Marc-André Bombeck c094f217fc feat(integrations): watch connections for silence and lasting trouble
An hourly and a daily pass flag a continuous source that delivered on
most of the previous 14 days and has gone quiet, and open one operator
incident per connection when a connection stays degraded or needs action
for more than a day. The incident is announced once through the existing
admin alert path, adopted rather than repeated when the sync ledger
already paged, and closed with one recovery note. Scales, cuffs and
workout logs are watched for errors only, never for silence.
2026-10-15 11:07:57 +02:00
Marc-André Bombeck 82d8960044 fix(jobs): run every admitted import inside its own background event
The admission drain ran each full-history import with no wide event in
scope, so every external call, warning and annotation the provider walk
recorded was dropped and a failing backfill left only its error message.
Each admitted run now opens a job.integration_backfill event with its kind,
identity, job id and slice, emitted when the run settles. The Health Connect
import worker gets the same treatment.

The external call list on one event is bounded so a long walk cannot hold
thousands of entries: failed and annotated calls keep the larger share,
the rest is counted in external_calls_omitted.
2026-10-15 11:07:28 +02:00
Marc-André Bombeck dad9a7778e fix(medications): keep an extra Apple Health dose off a slot that already holds one
A HealthKit dose event binds to a slot by window band, and a weekly
schedule's band reaches a day either side of its slot. An extra dose
logged the day after a scheduled one landed on that slot and overwrote it
last-write-wins: the slot's time changed and the extra dose was stored
nowhere. An external dose record whose band slot already carries a
different take is now recorded as its own unscheduled dose.

The dose history now lets the take stored on a slot claim it before an
extra dose stored on its own instant, instead of following the order the
rows were read in, so the extra dose reads as off-schedule.
2026-10-15 11:02:38 +02:00
Marc-André Bombeck d10179f9a3 feat(integrations): resolve one health state per connection
Fold the ledger state, liveness verdict, plan and scope gaps and the
provider reauth flag into ok / limited / degraded / action_required with a
reason code, the one action, the unavailable types and redacted
diagnostics. Keep the last ten sync attempts per connection for those
diagnostics, publish the state on the integrations envelope, every
per-provider status route and the Apple Health config read, and page the
operator once per failure streak instead of once a day.
2026-10-15 11:01:16 +02:00
Marc-André Bombeck cdd1e9c87f Merge pull request #1219 from MBombeck/fix/lab-dialog-overflow
fix(labs): keep the lab form's single column at the dialog width
v1.42.3
2026-10-15 07:06:49 +02:00
Marc-André Bombeck 07d991adf7 fix(labs): keep the value and draw-date column inside the dialog body
The single-column grid used the implicit auto track, which grows to the
widest cell's min-content. The draw date's date and time inputs size
from the font, so with wider metrics that min-content passed the dialog
body (about 422 px against 398 px) and the value input ran 24 px into
the dialog's padding. An explicit grid-cols-1 track is minmax(0, 1fr)
and stays at the body's width; the date row already shrinks inside it.

Refs #1214
2026-10-14 21:01:13 +02:00
Marc-André Bombeck 056af17f62 Merge pull request #1218 from MBombeck/release/v1.42.3
Release v1.42.3
2026-10-14 20:33:50 +02:00
Marc-André Bombeck e0573069c5 test(sharing): seed the series weight relative to now so it stays in the 90-day window 2026-10-14 20:09:14 +02:00
Marc-André Bombeck 38ef4c7fa7 fix(labs): keep three significant digits for a lab value below one 2026-10-14 19:57:34 +02:00
Marc-André Bombeck 784ea4ca9d Merge branch 'v1423/review' into release/v1.42.3 2026-10-14 19:57:04 +02:00
Marc-André Bombeck fcced06fa7 fix(labs): never open a small lab value as zero in the edit sheet
The edit seed rounded every value to two decimals, so a TSH of 0.004 opened
as 0. Values at or above 1 keep the two-decimal display precision; below 1
the seed keeps three significant figures, written without exponent
notation.
2026-10-14 19:50:26 +02:00
Marc-André Bombeck 03e80c1a94 fix(labs): drop the printed range text when an edit retires the conversion
Changing the value of a converted reading cleared sourceValue and
sourceUnit but kept the printed range text, which is written in the
report's unit. With the unit gone every reader presented it as the marker
unit's window, beside numbers in another unit. The text now goes with the
conversion record (a whole-form echo of it does not bring it back); the
bounds, already converted into the marker's unit, stay.
2026-10-14 19:49:33 +02:00