9584 Commits
Author SHA1 Message Date
Marc-André Bombeck 2c8140a502 Merge pull request #1328 from MBombeck/release/v1.46.6
release: v1.46.6
v1.46.6
2026-11-02 21:39:10 +01:00
Marc-André Bombeck 1bde3fc618 chore: drop exports nothing imports
knip 6.44 follows the members a dynamic import reads, and the Insights
page reads two of the derived barrel's: the barrel now exports those two,
whose building blocks are imported from their own modules. BAND_VAR had
no reader, and the drawer re-exported useResettableValue for a test that
only imports nextResettableValue. The size and version-stamp baselines
take the lower numbers.
2026-11-02 21:22:16 +01:00
Marc-André Bombeck 94da0bc352 test: tighten the version-stamp baseline for the review row 2026-11-02 20:58:15 +01:00
Marc-André Bombeck 165120e845 chore(release): v1.46.6 2026-11-02 20:52:22 +01:00
Marc-André Bombeck 49a0eb92ff fix(labs): word a value without a unit the same on every remaining surface
The briefing's lab input passed the stored "1" to the model, the unit
mismatch refusal read "is in 1", and the trend card left a gap where a
language puts the unit before a particle. get_labs says that a value
without a unit has an empty unit, and a lab link id is scrubbed like its
label. The review row is back at its baseline length.
2026-11-02 20:52:18 +01:00
Marc-André Bombeck 6a8ac98613 fix(labs): let a unit typed key by key pass through "1"
The no-unit state was read from the value, so the first keystroke of
10^9/L or 1/min ticked the box and locked the field. Only the box makes
the choice now; a typed 1 stays text.
2026-11-02 20:52:17 +01:00
Marc-André Bombeck 70cc753c49 Merge branch 'fix/pdf-unicode-font' into release/v1.46.6 2026-11-02 20:38:27 +01:00
Marc-André Bombeck ee0adae99c test(doctor-report): read the font guard's files through readSource 2026-11-02 20:36:16 +01:00
Marc-André Bombeck 0115a02d22 fix(doctor-report): draw the PDF in an embedded Unicode font
Helvetica's WinAnsi encoding turned every glyph outside Latin-1 into an
empty box: a Polish report lost its diacritics, a Korean report was
unreadable, and free text in any other script broke in every locale.

The renderer now registers the embedded ReportSans family on each
document, names it in every setFont, and makes it the autoTable default.
The Hangul variant is read only when the report is Korean or its data
contains Hangul. Font files are cached per process; streams are
compressed so the subset fonts add about 30 KB to a report.

The arrows, superscripts and comparison signs now print as themselves,
so the WinAnsi sanitiser and its ASCII fallbacks are gone. Tests render
Polish, Russian and Korean strings and arrows and read them back out of
the text layer.
2026-11-02 20:32:17 +01:00
Marc-André Bombeck 80a62afbbf build(pdf): add Unicode fonts for the doctor report
The report's built-in Helvetica is WinAnsi-only. These fonts cover what
the report can carry: ReportSans (Noto Sans plus the arrows and
mathematical operators of Noto Sans Math; Latin incl. Extended-A/B,
Greek, Cyrillic) in regular, bold and italic, and ReportSansKR, the same
plus Hangul from Noto Sans KR, in regular and bold.

scripts/build-pdf-fonts.py rebuilds them from the upstream sources. All
are SIL OFL 1.1; the licence sits beside the files. The directory is
named in outputFileTracingIncludes because the renderer reads the files
by a path the tracer cannot follow.
2026-11-02 20:32:11 +01:00
Marc-André Bombeck a822aa5a0e fix(documents): send a read that states no unit itself to review
A unit of "1" is the explicit no-unit choice a person makes. When a
document read returns it on its own, the value is staged for review
instead of passing straight through, so a unit the read dropped never
lands as a dimensionless value.
2026-11-02 20:31:33 +01:00
Marc-André Bombeck 2c6fb82326 Merge branch 'feat/unitless' into release/v1.46.6 2026-11-02 20:30:41 +01:00
Marc-André Bombeck e605fe0ae8 docs(api): describe the dimensionless lab unit on every lab unit field
The unit on lab results, biomarkers, the scanned-report commit and the
document fact edit now states that "1" means the value has no unit, that
it matches only itself, and that an empty unit stays refused. The lab CSV
import description names the 1 in the unit column.

Refs #1313
2026-11-02 20:28:35 +01:00
Marc-André Bombeck f9431da64f feat(labs): show a value without a unit as the number alone
Every place a lab unit is printed beside a value now goes through one
helper, so a reading stored in "1" reads "3.6" and not "3.6 1": the
list, history and detail, the chart and its tooltips, the reference bar
and range notes, the trend card, the timeline, the day view, the
clinician page, the doctor-report PDF and the changes card. Where the
unit stands alone (a marker's subtitle, a picker row, the merge preview)
it reads "No unit".

The Coach's lab block, the get_labs history and the visit-prep summary
hand the model an empty unit for such a value, so it never quotes the
"1" either.

Refs #1313
2026-11-02 20:28:34 +01:00
Marc-André Bombeck 5405ce053d feat(fhir): code a lab value without a unit as the UCUM unity
A value stored with unit "1" carries system and code on its quantity
even when the analyte map knows no coding for it: the unity is UCUM by
definition.

Refs #1313
2026-11-02 20:28:34 +01:00
Marc-André Bombeck 79ac5670c7 feat(labs): offer an explicit no-unit choice for ratios and indices
A ratio (cholesterol/HDL, albumin/globulin) or an index such as INR has
no unit, and every lab write path refuses a numeric value without one.
That refusal stays: an empty unit usually means the extraction dropped
it. Instead the value is stored as the UCUM unity "1", chosen on purpose.

The unit field when a marker is defined, on a scanned report's row and
in the document review edit now carries a "No unit" checkbox that sets
"1". The CSV import takes 1 in the unit column, as the template and the
import sheet say; an empty cell is still refused, and other spellings
such as "ratio" or "-" stay the literal unit they always were.

"1" compares as a unit of its own: it matches only itself, so it is
refused under a marker kept in mg/dL and the other way round.

Refs #1313
2026-11-02 20:28:23 +01:00
Marc-André Bombeck cae6edec64 Merge branch 'feat/mcp-links' into release/v1.46.6 2026-11-02 20:25:04 +01:00
Marc-André Bombeck 5f9c8461a3 test(mcp): pin record links against Postgres
Source system and id without the address, the Documents switch, the empty
answer, the cap, document links to labs and medications, row ids, and that
another account's document never appears, not even through a link row that
points across accounts.
2026-11-02 20:24:28 +01:00
Marc-André Bombeck 34302867aa feat(mcp): list links on lab and medication reads, ids on visit and condition rows
get_labs readings (the latest, earlier values in full, trajectory readings)
carry the fetch ids of what they link to, with linksTruncated; each linked
record is described once per answer in linkedRecords, because one report
usually holds a whole panel and would otherwise repeat on every reading.
fetch lab: and fetch med: list their links like the other record kinds, and
get_medication_schedule rows carry their id and links inline. get_visits and
get_illness_recovery rows carry visit: and condition: ids, so a row's links
are one fetch away. The labs and illness blocks gain an opt-in to carry row
ids; the Coach never asks for them.
2026-11-02 20:24:28 +01:00
Marc-André Bombeck ea7c6c860e feat(mcp): share one record-link builder and list labs and medications on a document
The links fetch lists beside a visit, condition, document or vaccination move
out of record-search into record-links, so the lab and medication reads can
list theirs through the same builder. A linked document imported from
Paperless-ngx or Papra now names its source system and its id there, never
the instance address. fetch document: also lists the lab results read from
it and the medications filed against it, each behind its own module.
2026-11-02 20:24:27 +01:00
Marc-André Bombeck a2ce11fec4 Merge pull request #1327 from johnnyrepo026/fix/review-edit-failed-rows
fix(documents): offer the edit control on review rows whose save was refused
2026-11-02 20:15:16 +01:00
Marc-André Bombeck 7dfc2cbbea test(series): follow the hour bucket into the route mock and the dated-fixture guard
The route test recognised the bucket statement by its old GROUP BY
column, and the new fall-back-night test reads from a fixed date, which
the dated-fixture guard now allowlists with its reason.
2026-11-02 20:14:18 +01:00
Marc-André Bombeck e7c625cab7 chore(deps): next 16.4.3 and the weekly dependency updates
Folds the open dependency updates into one change: next,
@next/bundle-analyzer and eslint-config-next 16.4.3, the production
group (aws-sdk, MCP SDK, node-apn, TanStack Query and Virtual, dotenv,
jose, lucide-react, nodemailer) and the development group (Stryker,
@types/node, knip, shadcn). The runtime prisma install in the Dockerfile
pins dotenv in step with the app.
2026-11-02 20:10:38 +01:00
Marc-André Bombeck 0810285061 fix(series): keep the repeated hour of a fall-back night apart
The hour grain of the dense series read truncated the local wall clock,
so the hour a fall-back night runs twice folded both instances into one
bucket: on that day a sensor stream showed one mean for two hours of
readings. An hour bucket now starts at the reading's instant less its
local minutes and seconds, which keeps the two hours apart and still
starts a half-hour zone's buckets on the half hour.

The new integration test pins a New York fall-back night and a
half-hour zone to fixed dates, so the case runs every day instead of
only when the rolling window of the existing tests covers one.
2026-11-02 20:06:15 +01:00
johnnyrepo026 e3aae483b0 fix(documents): offer the edit control on review rows whose save was refused
A confident lab value that Finish review refused (no unit, or a unit that disagrees with the marker) showed the reason but no way to act on it, so it could only be discarded. The Check control now also opens on a row with a commit failure, not only on a low-confidence one.

FactRow grows by one line (the data-slot the new test finds the control by), so its size baseline moves from 262 to 263.

Refs #1313
2026-11-02 12:21:37 -06:00
Marc-André Bombeck 577e43004f Merge pull request #1312 from MBombeck/fix/pdf-reader-crash-report
fix(documents): keep PDF reading alive on CPUs the canvas build skips
2026-11-01 01:45:56 +01:00
Marc-André Bombeck 773b943bde fix(documents): keep PDF reading alive on CPUs the canvas build skips
The arm64 musl build of the native canvas uses LSE atomics and RCpc
loads throughout, so loading it on an ARMv8.0 core such as the
Raspberry Pi 4 ends the process with SIGILL. The CPU gate only knew
the x64 AVX2 case, and pdf.js loads the canvas as soon as it starts,
so on such a host every PDF read died in the child: no text layer, no
index, and the manual read reported a busy reader.

The gate now also closes on linux/arm64 under musl when the CPU lacks
the atomics or lrcpc feature. A closed gate travels to the PDF child,
which refuses to resolve the canvas module, so pdf.js carries on
without it and the text layer reads; rendering and thumbnails keep
their existing fail-soft path. A child that ends without an answer
now logs its exit code and signal.

Refs #1311
2026-11-01 01:24:37 +01:00
Marc-André Bombeck 9a6d9b6793 Merge pull request #1310 from MBombeck/fix/display-name-greeting
fix(ui): name the account by its display name
2026-11-01 01:14:41 +01:00
Marc-André Bombeck 1c4bcce6c7 fix(ui): name the account by its display name
The dashboard greeting, the Insights greeting, the top bar and the
sidebar read the username, so a display name set on the profile card
reached the reports but none of the surfaces that address the account.
They now read one helper that returns the display name when set and
falls back to the username otherwise.

Refs #1309
2026-11-01 00:56:24 +01:00
Marc-André Bombeck 022c6de0b5 Merge pull request #1307 from MBombeck/fix/rollup-coverage-history
Count a type as rollup-covered only when its buckets reach its oldest reading
2026-10-31 12:14:49 +01:00
Marc-André Bombeck defa43d46c test(parity): fold the whole fixture history so the walk keeps the rollup path
The fixtures built their rollups with the read refresh, which folds ninety
days. Under the stricter coverage a type folded only that far takes the
live path; the fixtures now fold the whole history first, as the boot
backfill does, so the walk still reads the rollup tier.

The analytics snapshots change only in what the old coverage hid: the
summaries count all 120 weight and blood-pressure readings instead of the
91 the folded window held, with the means and the all-time in-target share
that follow from them.
2026-10-31 11:54:28 +01:00
Marc-André Bombeck 2d1f3fef1e test(e2e): say what is known about the missed lab-reading focus
The comment named a cause that was never observed.
2026-10-31 11:25:41 +01:00
Marc-André Bombeck 3d5d5fc4ff fix(rollups): count a type as covered only when its buckets reach its oldest reading
The coverage probe called a type covered as soon as one DAY bucket existed.
A write hook folds the day it writes, so a type whose history was not yet
folded (an account before its backfill, or a read racing the detached
refresh) had one fresh bucket, took the bucket-composed path, and its
summary counted that one day: three weight readings read as one.

A type is now covered when its oldest DAY bucket starts no later than its
oldest live reading inside the fold window. The bucket holding a reading
starts at or before it in any time zone, so the comparison holds without
knowing the account's day boundaries. Both sides are one index descent per
type; anything not covered falls back to the live aggregate as before.
2026-10-31 11:25:04 +01:00
Marc-André Bombeck 779097933e Merge pull request #1306 from MBombeck/fix/google-health-account-id-heal
Read the Google Health account id back; refuse a WHOOP profile without one
2026-10-31 11:16:54 +01:00
Marc-André Bombeck c66ffa9914 test(e2e): focus the lab reading once the timeline has redrawn it
The chart redraws its point targets when the marker's readings arrive; a
target focused just before that is replaced and no tip opens.
2026-10-31 10:59:23 +01:00
Marc-André Bombeck beb7187c2b test(e2e): wait for the saved range before reloading the score chart
The range tab saves with a PUT; reloading straight after the click could
cancel it, and the reloaded chart opened on thirty days again.
2026-10-31 10:42:26 +01:00
Marc-André Bombeck 717eb1d1e1 test(rollups): wait for the warm-up refresh before writing the next row
The summaries read starts ensureUserRollupsFresh detached. When the third
row's DAY bucket landed before that refresh, WEIGHT counted as covered by
its one bucket and the read reported a count of 1; a CI shard hit exactly
that.
2026-10-31 10:22:27 +01:00
Marc-André Bombeck 087c8868cf test: type the off-spec WHOOP profile and drop a version-stamped comment
The realism ratchet counts as-never casts and the version-stamp ratchet
refuses comment lines that open with a release; both caught the previous
commits.
2026-10-31 10:00:39 +01:00
Marc-André Bombeck 369c063701 chore(deps): raise node-forge to 1.4.2 and drop its audit exception
Five high advisories against node-forge <= 1.4.0 (signature, certificate
chain and TLS record handling) joined the one the exception covered. The
copy arrives only through @parse/node-apn; 1.4.2 is what node-apn 8.1.1
pins, and with the floor raised the exception has nothing left to cover.
2026-10-31 09:37:33 +01:00
Marc-André Bombeck b42b0d5fee fix(whoop): refuse a profile without a user id instead of storing "undefined"
String(profile.user_id) turned a missing id into the literal "undefined",
so every such connection would have looked like one WHOOP account and the
unique account id would have refused the second record. The callback now
treats it like a failed exchange (reason=token), as the Polar callback
already does for a token body without x_user_id.
2026-10-31 09:34:25 +01:00
Marc-André Bombeck 5306ba7614 fix(google-health): read the account id back for connections that lost it
Migration 0491 cleared the shared "profile" placeholder to null. Those
connections sync normally, but the one-account-per-record check cannot see
them, so the same Google account could still be connected to a second
record until the first one reconnected.

After a clean sync cycle, a connection without an id now reads it the way
the callback does (profile, then identity) and stores it, guarded on the id
still being null so a reconnect landing meanwhile wins. An id another
record already holds is not written, and a failed read leaves the id for
the next cycle without touching the cycle's verdict. The outcome is on the
wide event as googleHealth.accountId.heal.
2026-10-31 09:34:25 +01:00
Marc-André Bombeck eb47f04a66 Merge pull request #1305 from MBombeck/release/v1.46.5
v1.46.5: Azure OpenAI reasoning deployments
v1.46.5
2026-10-30 17:44:07 +01:00
Marc-André Bombeck 685cb2fa01 fix(ai): learn the max_tokens refusal from the field the request carried
A call that ran beside the one that learned the lesson read today's
selection instead and failed with the 400. The matcher now also wants the
refusal to name max_tokens, so a refusal of another field that echoes the
request does not switch the token field.

Refs #1304
2026-10-30 17:13:09 +01:00
Marc-André Bombeck 31ecb75901 test(backup): golden fixture for v1.46.5 2026-10-30 17:09:12 +01:00
Marc-André Bombeck 2676f2fce0 chore(release): v1.46.5 2026-10-30 17:09:01 +01:00
Marc-André Bombeck 21af344054 fix(ai): send max_completion_tokens to Azure OpenAI reasoning deployments
An OpenAI-compatible gateway entry pointed at an Azure OpenAI v1 endpoint
was treated as a generic gateway and sent max_tokens and temperature, which
a reasoning deployment refuses with a 400. An Azure v1 endpoint now takes
the api.openai.com contract for a reasoning model, and any gateway that
refuses max_tokens for a model is asked again with max_completion_tokens
and remembers it, which covers deployments named something of their own.

Refs #1304
2026-10-30 17:08:43 +01:00
Marc-André Bombeck 0462c23841 Merge pull request #1303 from MBombeck/release/v1.46.4
v1.46.4: Google Health connects on every profile of an instance
v1.46.4
2026-10-30 13:51:10 +01:00
Marc-André Bombeck 29b5bc78a0 chore(release): v1.46.4 2026-10-30 13:18:16 +01:00
Marc-André Bombeck 0d8bcbc52b test(backup): keep the 1.46.4 backup file as a golden 2026-10-30 13:18:15 +01:00
Marc-André Bombeck cd6cbc06f5 fix(google-health): store the account's id, not the word "profile"
The profile's resource name is `users/{user}/profile`, and the connect
stored its last segment, the same constant for every account. Since the
one-account-per-record check, a second record on an instance could not
connect or reconnect Google Health: it was refused as `already_linked`.

The connect now stores the `{user}` segment, reads `users/me/identity`
when the profile answers through the `me` alias, and stores the id as
unknown when neither names the account. A migration makes the column
nullable and clears the placeholder, so the next connect stores the
real id.
2026-10-30 13:17:54 +01:00