Commit Graph
8777 Commits
Author SHA1 Message Date
Marc-André Bombeck bb3ee6b855 fix(safe-fetch): leave the deadline to the caller's signal
The pinned dispatchers kept undici's own 300 s header and body timers. A
local model with the response timeout set above five minutes answers a
non-streamed completion only once it is done, so its headers could take
longer than 300 s and undici dropped a call the caller still allowed; the
failure read as a bare network error. Every safeFetch call carries its own
AbortSignal.timeout, so that signal is now the one deadline.

Refs #1267
2026-10-22 13:01:10 +02:00
Marc-André Bombeck ed04103eb6 chore(release): v1.44.2 2026-10-22 12:05:28 +02:00
Marc-André Bombeck 803d90c6c4 Merge branch 'fix/1261-own-targets' into release/v1.44.2 2026-10-22 12:04:29 +02:00
Marc-André Bombeck ac9693fac9 Merge branch 'feat/bottom-sheet' into release/v1.44.2 2026-10-22 12:04:28 +02:00
Marc-André Bombeck 03b3f6a3ba docs: record the history fixes and what 16.4.2 measured 2026-10-22 11:32:57 +02:00
Marc-André Bombeck aae7bce078 test(security): write the next/image import matchers as literals 2026-10-22 11:32:57 +02:00
Marc-André Bombeck 6cd8ffa526 fix(documents): follow the address bar for the open document
The sheet compared useSearchParams with the last ?doc= it had seen.
Next 16.4 applies its copy of a pushState after the write, and a Back
pressed in between leaves useSearchParams reading no document on both
sides, so the sheet saw no change and stayed open. The sheet now reads
the query from window.location through useSyncExternalStore, re-read on
popstate and on every render, and the selection and close paths build
their URLs from the same live value.
2026-10-22 11:32:57 +02:00
Marc-André Bombeck c496e390be fix(day): hold a step until the layer shows the previous one
A step writes ?day= with replaceState, past Next's router, which adopts
the write a render later. Since 16.4 that render can land after a second
quick step and write the first step's URL back, so [ then ] pressed in a
row ended a day short. A step that finds the layer still showing another
day than the URL now waits, and publishOpenDay makes it in one write as
soon as the layer catches up. Never past today still holds for the sum.
2026-10-22 11:32:56 +02:00
Marc-André Bombeck ff49bbb5fc fix(insights): shade the SpO2 chart from the own target when one is set
The oxygen saturation page drew a fixed clinical band, so a person with
a lower target of their own saw their normal range shaded as a warning.
The page now reads the resolved range from /api/user/thresholds and
draws it; without a target the clinical bands stay as they were.

Refs #1261
2026-10-22 10:23:15 +02:00
Marc-André Bombeck 90106d88c8 fix(dashboard): shade blood pressure, pulse and body fat from the own target
The dashboard bands took only the weight target into account. Blood
pressure, resting pulse and body fat were resolved from age and sex
alone, so a target set on /targets left the chart zones and the tile
ranges on the standard band.

buildDashboardBands now reads the stored targets blob and routes every
override through getEffectiveRange, the same resolver the targets page
and the doctor report use. A metric without an override keeps exactly
the band it drew before. The snapshot's targetBands carry the resolved
values, so a native client reading them needs no change; the field
descriptions now say the own target wins.

Refs #1261
2026-10-22 10:23:14 +02:00
Marc-André Bombeck 72e6fd6620 test(e2e): drive the bottom sheet on WebKit with an iPhone profile
A webkit-iphone project runs one spec: drag, spring back, flick, scroll before drag, a slider sideways, the three closes, focus return, the page's scroll position and pull-to-refresh. CI installs WebKit beside Chromium.
2026-10-22 10:20:30 +02:00
Marc-André Bombeck 55211c0e69 fix(pwa): never arm pull-to-refresh while a sheet holds the page
The listeners sit on window, so a drag inside an open sheet on the dashboard or the insights page armed the refresh behind it. They stand down while the scroll lock marks the body.
2026-10-22 10:20:30 +02:00
Marc-André Bombeck 1cc1cda7aa feat(ui): make every bottom sheet draggable
One bottom sheet on top of the Radix dialog, so focus, Escape, the
backdrop, aria and the page scroll lock stay where they were. The sheet
follows a touch with a transform, the backdrop lightens with it, and a
release past a quarter of the height or a flick down closes it; anything
less springs back. Grip and header always drag; content only from its
very top, and sideways gestures never count.

Every `side="bottom"` sheet and ResponsiveSheet on a phone render through
it: the More sheet, the capture and form sheets, the Coach drawer on a
phone and the day. The day opens on the new preview step and pulls up to
90 dvh, its footer held on the bottom edge. Focus now returns to whatever
opened a sheet, not only to a Radix trigger. The command palette fills
the window and keeps no grip or drag.
2026-10-22 10:20:28 +02:00
Marc-André Bombeck 5902401ab8 chore(release): v1.44.1 2026-10-22 08:25:49 +02:00
Marc-André Bombeck 1fa33f2aee fix(security): turn the image optimizer off
Nothing renders through next/image, so /_next/image only offered a route
that fetches, decodes and re-encodes whatever local path a request names.
With images.unoptimized Next answers it with 404 before reading a
parameter. A guard fails when next/image is imported while the optimizer
is off, or when the optimizer returns with SVG input allowed.
2026-10-22 08:25:49 +02:00
Marc-André Bombeck 44c2973a64 fix(deps): update next to 16.4.2 and nodemailer to 10.1.7
Next 16.4.2 carries the fixes for GHSA-673q-mjm6-r5xr and
GHSA-2464-fhr4-w9xw (unauthenticated remote code execution) and
GHSA-p3m5-59mp-jv5r (denial of service through Server Actions).
eslint-config-next and @next/bundle-analyzer move with it, both pinned
exactly. nodemailer 10.1.7 fixes the address-parser advisories.

The 16.4.0 ignore in the Dependabot config is gone: the history journeys
it was guarding pass on 16.4.2 with the day-step fix, and the note says
what was measured.
2026-10-22 08:25:48 +02:00
Marc-André Bombeck 76af868064 fix(day): step the open day from the URL, not from the last rendered day
A step read the day the layer had last published, which reaches the
shortcut and the header arrows through useSearchParams, a render and an
effect. Next 16.4 lets that chain trail a replaceState by more than a key
repeat, so two quick presses of ] both stepped from the same day and one
was lost. stepDay now reads ?day= from window.location, which the step
itself writes, and refuses a step past today on that value too.
2026-10-22 08:25:47 +02:00
Marc-André Bombeck fdd0f50130 Merge pull request #1262 from MBombeck/release/v1.44.0
Release v1.44.0
v1.44.0
2026-10-22 03:03:28 +02:00
Marc-André Bombeck 28abde334e fix(insights): keep the source comparison range on a 390 px phone
Three spelled-out range labels beside the card title pushed /insights/sources
past the viewport. Use the charts' short day labels and keep the long form as
the segment title. The route sweep resolves the new module pages and drops
mood from the arrangement pages, whose old address now redirects to them.
2026-10-22 02:31:56 +02:00
Marc-André Bombeck 104f4cc7bd test(e2e): assert the guardian's connect controls on a managed profile
A MANAGE grant on a managed profile now connects its OAuth providers: each
of the six cards offers one connect and says whose record it is for, while
Fitbit and Nightscout stay status-only. A view or view-and-add grant still
gets no integration surface and a 403 from the connect route.
2026-10-22 02:31:56 +02:00
Marc-André Bombeck 11cd7fbeb6 fix(integrations): name a managed profile without a display name by its username
The connect cards read the display name alone, so a profile created without
one rendered "Connect for" with nobody after it. Use the account label the
switcher and the banner use.
2026-10-22 02:31:56 +02:00
Marc-André Bombeck 25d7a01d9d test(e2e): expect the two HRV rows in the standard report set
The standard template carries SDNN and RMSSD since the wearable leaves
landed, and the cardio group has fifteen leaves. Assert the untouched
mobility group at zero instead.
2026-10-22 02:31:55 +02:00
Marc-André Bombeck c26f21fb4f test(e2e): import the insights cache cipher statically in the AI-optional fixture
A dynamic import() of app code skips the runner's @/ path mapping for that
module's own imports, so sealing the seeded briefing failed with
"Cannot find module '@/lib/crypto'".
2026-10-22 02:31:55 +02:00
Marc-André Bombeck 695bb1b10a docs(changelog): note the lighter metric pages 2026-10-22 01:44:00 +02:00
Marc-André Bombeck 173646c613 chore(bundle): raise the aggregate budget to 3900 on the measured v1.44 build
The release branch measured 3875 KB gz without the lazy catalogs
against the 3755 cap (main 3737). About 15 KB is new client code; the
rest is the aggregate's per-chunk-group double count of the shared
confirmation dialog and of the two new routes' own chunks. The summed
first load of the 108 pages fell from 39998 to 35895 KB gz with the
metric pages off Zod; the watched routes keep their baselines.
2026-10-22 01:37:14 +02:00
Marc-André Bombeck 04c9139ca6 perf(settings): load only the link card on the arrangement pages
The arrangement page imported its link to the module's own page from the
module pages file, which also holds the labs, mood and injection-site
settings, so every arrangement page shipped all three. The card lives in
its own module.
2026-10-22 01:36:06 +02:00
Marc-André Bombeck ebdb63f0d8 perf(insights): keep Zod off the first load of the metric pages
The value domain read the plausibility band out of the measurement
validation module, so every metric sub-page that asks whether a metric
can be negative or is integer-valued loaded the measurement schemas and
Zod with them. The band and its check live in a Zod-free module now,
re-exported by the validation module for the server writers, and the
values list checks its segment against the exhaustive type label map
instead of the Zod enum. About fifty metric routes drop roughly 88 KB
gzip from their first load. The client-route guard walks a metric page
and the values list too.
2026-10-22 01:36:06 +02:00
Marc-André Bombeck 41c906e862 test(encryption): drop the insights cache step from the backfill summaries
The free-text backfill no longer has an insights cache step since the
readable cached-text column was dropped, so its summary carries no
insightsCachesMigrated count. The expected summaries follow.
2026-10-22 01:19:01 +02:00
Marc-André Bombeck 75f9ef1507 test(workouts): opt the account in before expecting a stored track
GPS tracks are kept only after the account turns storeWorkoutRoutes on.
The two route-storing cases opt in first, and a new case proves that an
account which has not opted in keeps the workout, answers it as
inserted, and stores no track.
2026-10-22 01:15:01 +02:00
Marc-André Bombeck f4acf0e246 test(openapi): call the provenance reads and fill the list layout in the GET contract run
The two provenance reads take required query parameters and had no
entry, so the run could not call them. Give them a day and a range on
the steps, seed a second steps source for the last week so the pick and
the comparison have two sources to explain, and save a labs list order
so the module list layout read answers with a row.
2026-10-22 01:15:01 +02:00
Marc-André Bombeck 832a9da98a test(backup): give the activity goals a valid fill in the settings round trip
The restore admits activity goals key by key against the goals schema,
so the generic { roundTrip } JSON fill was refused as an unknown goal
rather than carried. Fill the column with real goals and pin the
per-goal admission (valid goals kept, unknown keys and out-of-bounds
goals named) in the unit suite.
2026-10-22 01:12:17 +02:00
Marc-André Bombeck 455e92c5a5 chore(release): v1.44.0 2026-10-22 00:46:34 +02:00
Marc-André Bombeck ae479c86cf Merge branch 'feat/v144-review2' into release/v1.44.0 2026-10-22 00:46:09 +02:00
Marc-André Bombeck e96a5f834f test(pulse): drop the MCP changepoint exemption from the day-mean guard
detect_changepoints no longer averages bucket means itself; it hands
them to the shared level-shift detector, so the file has no in-memory
mean left for the guard to list.
2026-10-22 00:42:19 +02:00
Marc-André Bombeck 663b8756a6 docs(import): say why lab CSV duplicates are cut on the read zone's day
A wall-clock time read in another zone on a retry is a different
instant, so neither an exact-instant match nor the zone's day catches
it near midnight, and widening the match would drop a genuine reading
of the same value on the neighbouring day. The earlier import's zone is
not stored, so the comment states the rule that never loses a reading.
2026-10-22 00:40:08 +02:00
Marc-André Bombeck 13039eb430 fix(import): write an imported medication and its category together
The medication CSV import created the medication and then set its
category in a second write. A crash between the two left a medication
without a category, and a retry skipped it as a duplicate, so it never
got one. Both writes now share one transaction.
2026-10-22 00:39:59 +02:00
Marc-André Bombeck bd811eb753 test(crypto): follow a pushed payload array into the write that sends it
The lab CSV import builds its rows into an array and writes each chunk
with one createMany({ data: toWrite }). The ciphertext-writer scan only
read payload literals at the call site, so it never saw the encrypted
note that write carries; the column stayed covered only by the name
scan. The scan now follows an identifier payload into the literals the
same file pushes onto it, and asserts it sees the chunked note write.
2026-10-22 00:38:03 +02:00
Marc-André Bombeck 08ea2b6620 fix(import): keep a failing audit write from replacing the import's answer
The CSV import wrote its audit row in a finally block, so an audit write
that threw discarded whatever the import had settled on: the result of
a finished import, the 413 of a file over the cap, or the error of one
cut off part way. The failure is now noted on the wide event and the
import answers as it would have.
2026-10-22 00:35:35 +02:00
Marc-André Bombeck e91957c017 fix(labs): word a three-year projection horizon as the cap it is
The beyond-horizon line said the range held "for more than {years}
years, the span your readings cover", but the horizon is capped at three
years, so a ten-year history was told its readings spanned three. The
trend now says whether the cap or the span set the horizon
(`horizonCapped`), and the card words each case on its own in every
locale.
2026-10-22 00:32:13 +02:00
Marc-André Bombeck 6330908619 fix(mcp): run detect_changepoints on the insights level-shift detector
The tool kept its own CUSUM with a plain standard-deviation bar, so the
per-metric floors and the drift rejection the health-status scan gained
never reached it, nor the medication-effect note that reads it: a 0.6 kg
weight step or a steady climb still came back as a dated shift. The tool
now segments the series with the shared detector, recursing on each side
of a shift with the same bar and the metric's floors. The output shape
is unchanged.
2026-10-22 00:29:19 +02:00
Marc-André Bombeck 4fbbdb12c2 fix(fhir): page _offset as far as a series reaches
The 100000 ceiling refused offsets a correctly paging client reaches on
its own: an Observation series with a heart-rate history runs well past
a hundred thousand rows, and the next link the server handed out led
straight into a 400. The 500 the ceiling was added for came from an
offset such as 1e20 that no longer floors to an exact integer and that
the database cannot read as an OFFSET. The refusal now sits at the
largest exact integer, and a next link never carries an offset the
server would refuse.
2026-10-22 00:27:02 +02:00
Marc-André Bombeck 29d6029999 fix(workouts): read a day with activity data and no workout as rest
The strain run writes no impulse row on a day without a scorable workout
or active energy, so a rest day for someone without daily active energy
was never rated: fitness and fatigue held for months instead of
decaying, and the figures named the last stored day while the coverage
count ended there too.

A day without a stored impulse is now classified from the underlying
data: a workout on the day leaves it unrated, steps, heart rate or
active energy without a workout make it rest, and no data at all leaves
it unrated. The series runs to the day the strain run scores, so a
stretch without data after the last rated day shows in the coverage
count, and the card names the last rated day when it lies further back
than the day before.
2026-10-22 00:25:29 +02:00
Marc-André Bombeck 60bef2dee8 Merge branch 'feat/v144-review2' into release/v1.44.0 2026-10-22 00:11:15 +02:00
Marc-André Bombeck 0918451e02 fix(settings): unlist Preview features until the topic pages ship
The switch was stored and shown, but nothing reads the topics flag
yet, so the page promised a change it could not make. The section,
its nav entry, its loader and its copy are gone, so its palette
entry goes with them; /settings/preview is no longer a route. The
stored flag, /api/settings/preview-features and its backup round-trip
stay, and the column's pending-consumer note says why.
2026-10-22 00:09:40 +02:00
Marc-André Bombeck 704822c010 test(sharing): say which v1.44 routes joined the record list
The note named the activity goals among the three new readable routes.
They are the calendar read and the two source provenance reads; the
goals route stays owner-only.
2026-10-22 00:02:24 +02:00
Marc-André Bombeck 176f21ab78 fix(fhir): bound _offset and answer a larger one with 400
An offset beyond what the database can skip reached Prisma unchecked
and answered 500. Every search route now refuses an _offset above
100000 with an invalid OperationOutcome.
2026-10-22 00:01:27 +02:00
Marc-André Bombeck 896ba233da fix(import): refuse unknown query parameters on the CSV imports
The lab, medication and measurement CSV routes ignored parameters they
did not know, so a misspelt dryrun=1 fell back to a real import and
wrote the file the caller meant only to check. Their query schemas are
strict now: an unknown parameter answers 422 before anything is read
or a rate bucket is spent.
2026-10-21 23:56:16 +02:00
Marc-André Bombeck f8c17ee9bb fix(import): write lab CSV rows one chunk at a time and audit cut-off imports
Every accepted row was its own insert, so a 20 000-row file meant
20 000 round trips and an import cut off part way left an arbitrary
prefix behind. Each 200-row chunk is now one createMany: it lands whole
or not at all, and uploading the same file again skips the written
chunks as duplicates. The importers report each committed write, and
the audit row is written in a finally block, marked partial with the
rows written when the import did not finish.
2026-10-21 23:49:23 +02:00
Marc-André Bombeck b828870e1b fix(import): cut lab CSV duplicate days in the zone the file is read in
The rows were parsed in the query's timezone while the duplicate keys
used the account's, so two draws on separate days of the file's zone
could collapse into one, or one day split in two.
2026-10-21 23:42:34 +02:00
Marc-André Bombeck bb6982aea2 feat(report): name the analysed window of each medication effect
The effect is fitted on up to 90 days before a start and 180 after
it, which can lie outside the report period. The PDF notes and the
clinician view now state the fitted window for every effect shown.
2026-10-21 23:37:09 +02:00