Commit Graph
7294 Commits
Author SHA1 Message Date
Marc-André Bombeck f3ccc809a8 Merge remote-tracking branch 'origin/main' into wip/v140-E 2026-10-03 18:56:39 +02:00
Marc-André Bombeck c3525cd02f fix(labs): stop the biomarker backfill on its job budget
The integration-backfill drain handed its job budget only to the
Google Health backfill. The lab-biomarker backfill now takes it too,
asks it before each analyte group and, once it says stop, throws
without touching the rest. Finished groups are linked and drop out of
the next scan, so the retry resumes where the pass stopped instead of
running on past the job's expiry beside its own retry.
2026-10-03 18:53:28 +02:00
Marc-André Bombeck 9455b64375 feat(dashboard): send the glucose card in the owner's glucose unit
/api/dashboard/summary sent the glucose card's value in mg/dL with
unit null and a unitKey that reads mg/dL, so a reader on mmol/L saw
the mg/dL number. The card now carries latestValue, secondaryValue
and sparkline in the record owner's glucose unit and names it in
unit, the field that wins over unitKey, as the sleep card already
does. Applied after the summary cache read, so a unit change shows
on the next request.
2026-10-03 18:50:35 +02:00
Marc-André Bombeck cfddf097c7 fix(documents): file an undated document under the reader's upload day
The archive grouped a document without a stated date by the UTC day
of its upload, so an upload late on the last evening of a month west
of UTC opened next month's section (and early on the first morning
east of UTC, last month's). The upload day is now read in the
profile time zone for the month sections and the year filter.
2026-10-03 18:47:05 +02:00
Marc-André Bombeck 9ecdda6358 fix(dates): show a stated calendar date as that date east of UTC+12
Day keys anchored at noon UTC went through the zone-aware
dateShortSmart, which shows them as the next day in UTC+12..+14. A
dateShortSmartCalendar formatter reads the key in UTC with the same
year rule, and the predictions panel, the illness timeline, the
intraday pulse day label, the nutrient chart and the coach result
chart use it. The lab chart dates a date-only reading by its calendar
date and a timed one in the reader's zone, as the lab lists do. The
timezone formatter guard now fails on a noon anchor handed to a
zone-aware formatter.
2026-10-03 18:44:06 +02:00
Marc-André Bombeck 358ef23239 fix(clinician): write the shared view's numbers in the owner's locale
The clinician view rounded numbers and printed them bare, so a German
record showed 24.5 where every other surface shows 24,5. Its number
formatter now goes through the locale formatter it already builds,
at most two decimals.
2026-10-03 18:39:03 +02:00
Marc-André Bombeck fdd34f2c5e fix(charts): keep a chart's window open to the end of today
A chart showing the last N days fixed its window end at the mount
instant. The query key kept that bound, so the refetch after a new
reading asked for the same old window and the reading stayed hidden
until the range tab was switched. The window now ends at the end of
the current day in the profile time zone (openEndedFetchWindow, keyed
on the day): every reading saved today falls inside it, the cache key
stays the same all day, and it moves on after midnight. The dashboard
batch shares the end-of-day helper.

Refs #1102
2026-10-03 18:35:49 +02:00
Marc-André Bombeck 51e2d68a88 i18n(measurements): say how a duration entry was read
Refs #1101
2026-10-03 18:35:26 +02:00
Marc-André Bombeck 313d8491de feat(measurements): read a sleep duration written as 7:30 or 7h 30m
The sleep field asked for decimal hours only. A shared parser
(parse-duration.ts) now reads a bare number with point or comma,
H:MM, and hours and minutes with unit words (7h, 7h30, 7 h 30 min,
45m), answers in the field's unit, and refuses a negative, unknown or
longer-than-a-day entry. The form shows how the entry was read under
the field and puts a refusal on the field itself. The stored value and
the API are unchanged.

Refs #1101
2026-10-03 18:28:39 +02:00
Marc-André Bombeck d805ccf28b Merge pull request #1100 from MBombeck/ci/trivy-braces
Ignore the braces advisory until a fix is published
2026-10-03 18:28:24 +02:00
Marc-André Bombeck 7b51edcebe Merge pull request #1096 from BernhardBuckel/fix/lab-unit-mismatch
fix(labs): refuse a reading whose unit differs from its marker's
2026-10-03 18:28:20 +02:00
Marc-André Bombeck 3a35dbcf37 i18n(labs): say why a biomarker's unit cannot change once it has readings 2026-10-03 18:22:55 +02:00
Marc-André Bombeck dd7636e147 feat(labs): refuse a biomarker unit change while readings are linked
Readers label every linked reading with the marker's unit, so changing
it relabelled stored numbers without converting them. PUT
/api/biomarkers/{id} now answers 422 biomarkers.unit.locked with
meta.readingCount when the unit differs and any reading (tombstoned
ones included) is linked. The marker editor says so under the unit
field.
2026-10-03 18:19:50 +02:00
Marc-André Bombeck 7022b698e7 fix(ai): forget the remembered working provider when the chain is saved
The in-memory memo of the last provider that answered reorders the
chain for up to an hour. Saving a new chain now drops it for that
account, so a provider the person removed or disabled no longer leads
the chain they just arranged.
2026-10-03 18:04:09 +02:00
Marc-André Bombeck 916f918674 feat(workouts): save an edited resubmit of a hand-entered workout
The manual form sends one manual: id per opening and resends it after
an edit. The batch route now treats a MANUAL entry with a manual: id
as an overwrite, the way stats: measurements are: the stored row takes
the new values and the entry reports "updated", or "duplicate" when
nothing changed. Records keyed on the workout are removed under the
personal-records lock and re-derived. Every other id stays
first-write-wins. The status enum gains "updated".
2026-10-03 17:59:20 +02:00
Marc-André Bombeck 87fa2f85fa Merge branch 'main' into ci/trivy-braces 2026-10-03 17:57:51 +02:00
Marc-André Bombeck 6bb65824bf fix(workouts): serialise record detection with a workout delete
The workout-slot detection reads the best workout and writes its record
inside one transaction under a per-user advisory lock, and the workout
DELETE takes the same lock before removing records and the workout. A
delete can no longer land between the read and the write and leave a
record for a workout that is gone.

The DELETE's match for a legacy record without an external id is scoped
to the slots of the deleted workout's sport, so a record another sport
set at the same instant stays.
2026-10-03 17:54:04 +02:00
Marc-André Bombeck ab081d2a85 fix(workouts): keep a deleted workout's insight slot spent
The insight claim row is now the daily-cap ledger. Its workout_id is
nullable with ON DELETE SET NULL (migration 0367), so deleting a workout
no longer frees the day's slot. The cap counts claims that reached the
provider plus live leases, and drops the separate count of insight rows.
A daily workout-insight-claim-cleanup queue removes claim rows untouched
for seven days.
2026-10-03 17:53:36 +02:00
Marc-André Bombeck ed94e56c5e Merge branch 'main' into fix/lab-unit-mismatch 2026-10-03 17:51:46 +02:00
Marc-André Bombeck b9867caeea Merge pull request #1098 from MBombeck/fix/backup-round-trip-tombstone-age
Keep the backup round-trip tombstone inside the retention window
2026-10-03 17:51:43 +02:00
Marc-André Bombeck 1d86bb8d6d i18n(labs): unit mismatch strings for es, fr, it, ko and pl 2026-10-03 17:45:05 +02:00
Marc-André Bombeck 296ca5d2dc ci(trivy): ignore the braces advisory until a fix is published
braces is reached only by development tooling and never ships in the
production image; no patched version exists yet.
2026-10-03 17:29:51 +02:00
Marc-André Bombeck 86a6e81fc1 test(backup): keep the tombstone fixture inside the retention window
The round-trip fixture deleted its measurement on a fixed date. The restore
drops a measurement tombstone older than TOMBSTONE_RETENTION_DAYS (75) on
purpose, so on 2026-10-03 the fixture aged out and the suite failed on main
and on every open branch. The deletion is now ten days before the run.
2026-10-03 17:28:50 +02:00
BernhardBuckel d3916036c2 fix(labs): refuse a reading whose unit differs from its marker's
A numeric lab reading was stored with its value as received and the marker's
unit stamped on it, so 5.6 mmol/L against a marker kept in mg/dL became
5.6 mg/dL and read as far below range. Both the manual/API write and the
scan commit did this; nothing compared the two units.

- POST /api/labs returns 422 labs.unit.mismatch, with the marker's unit and
  the reading's unit in meta, and writes nothing. The unit is compared only
  when one was sent: on the biomarkerId path it may be omitted, and the
  marker's unit is then the stated one. A qualitative reading has no unit.
- The scan commit skips such a row with reason unit_mismatch, so the outcome
  reads partial or failed instead of a clean save, and the toast names the
  analytes concerned rather than saying "duplicates".
- The extract response carries the matched marker's unit. The review screen
  shows a calm badge on a row in another unit, starts it unticked like a
  duplicate, and treats it as a blocking error on the unit field if it is
  ticked anyway.
- A small lab-specific normaliser forgives spelling only: spacing, the micro
  prefix, the case of the litre, and a unit printed in capitals for a short
  list where capitals cannot mean another unit. Case is otherwise significant,
  so mIU/L and MIU/L, or g/L and G/L, are never merged.

The document-commit path already refuses a mismatch and is left as it is.
Readings stored wrongly before this cannot be identified, because the
original unit was never kept; they are not touched.

Adds EN and DE strings; the other locales are left to the maintainers. The
OpenAPI spec and the error-code catalogue carry the new code.

Fixes #1094
2026-10-03 15:44:51 +02:00
Marc-André Bombeck 556bd6d6cd Merge pull request #1093 from MBombeck/release/v1.39.9
Release v1.39.9
v1.39.9
2026-10-03 00:57:24 +02:00
Marc-André Bombeck e42b3f331f chore(release): v1.39.9 — log a workout by hand, a page for body fat, and the AI response time everywhere 2026-10-03 00:35:27 +02:00
Marc-André Bombeck a14ae57283 fix(workouts): refuse a manual workout that would end in the future
The form's start defaults to the minute it opened, so a workout logged
right after it finished was stored as ending up to a day ahead. The
duration now fails validation when start + duration lies past now, with
the same one-minute slack the start check uses.
2026-10-03 00:35:26 +02:00
Marc-André Bombeck 2cd8b28a22 Merge pull request #1092 from MBombeck/fix/ai-timeout-everywhere
Run every model call under the AI response-timeout setting
2026-10-03 00:23:26 +02:00
Marc-André Bombeck bd2d43e402 Merge remote-tracking branch 'origin/fix/ai-timeout-everywhere' into fix/ai-timeout-everywhere 2026-10-03 00:03:31 +02:00
Marc-André Bombeck 0f6669847c Merge remote-tracking branch 'origin/main' into fix/ai-timeout-everywhere 2026-10-02 23:59:38 +02:00
Marc-André Bombeck 2473d52f19 Merge pull request #1089 from MBombeck/feat/manual-workout-entry
Log a workout by hand
2026-10-02 23:59:11 +02:00
Marc-André Bombeck e6e5bd0adc Merge pull request #1087 from MBombeck/fix/dashboard-tile-links
Every dashboard tile opens its chart; body fat gets its page
2026-10-02 23:50:43 +02:00
Marc-André Bombeck 3b1d503e87 Merge branch 'main' into fix/ai-timeout-everywhere 2026-10-02 23:20:26 +02:00
Marc-André Bombeck 57a99e6cc8 Merge branch 'main' into feat/manual-workout-entry 2026-10-02 23:20:24 +02:00
Marc-André Bombeck 774dff410c Merge branch 'main' into fix/dashboard-tile-links 2026-10-02 23:20:23 +02:00
Marc-André Bombeck 06f69e4408 Merge pull request #1091 from MBombeck/fix/e2e-lab-review-animations
Wait for the lab review sheet to stop sliding before measuring
2026-10-02 23:20:11 +02:00
Marc-André Bombeck ae0bdd0390 fix(settings): name the real response-timeout defaults
The placeholder said "Default (~120)" while most surfaces fall back to
60 s and the briefing to 180 s. The placeholder and the help line now
interpolate both numbers from the constants the server applies
(PROVIDER_DEFAULT_TIMEOUT_MS, AI_BUDGETS.comprehensive.timeoutMs), in
all seven locales, and a test fails if a locale writes a number of its
own.

Refs #1090
2026-10-02 23:17:37 +02:00
Marc-André Bombeck 6207101006 test(ai): guard that every model call runs under the response-timeout setting
Pins the four ways the setting could be skipped again: a wire client
reading params.timeoutMs itself instead of callTimeoutMs, an exported
resolver in provider.ts returning a provider it did not bind, a client
constructed outside provider.ts (the offline evaluation harness is the
named exception), and a surface ceiling not listed with its reason.
Each matcher asserts a non-zero match count.

Refs #1090
2026-10-02 23:03:45 +02:00
Marc-André Bombeck bf7fb9c401 fix(documents): wait as long as the server may on document reads and lab OCR
The browser gave up on its own: 120 s for the document routes, 90 s for
a lab scan, and the 15 s default on the lab text path, while a slow
self-hosted model with a raised response timeout was still allowed to
answer. A five-page report read by a local model failed on the client
side with the server still working.

Each request now sizes its abort from ai.provider.responseTimeoutMs on
the account payload, times the model calls the route can make, plus
30 s: one call for suggest and summary, three for Read with AI (the
transcription, then the lab staging read and its corrective retry), two
for a lab scan (the read and its retry). With the setting unset that is
90 s, 210 s and 150 s.

Refs #1090
2026-10-02 23:01:41 +02:00
Marc-André Bombeck 5ee1b3e28f fix(ai): run every model call under the response-timeout setting
The response-timeout setting reached the briefing, the status notes and
the Coach, which threaded it by hand. Document reads, document
extraction, lab OCR and lab staging called the provider without a
timeout, so every client fell back to its own 60 s literal and a slow
self-hosted model was cut off however high the setting stood.

The setting now rides on the provider instance. resolveProvider,
resolveProviderChain and resolveProviderForTest stamp the record's
aiResponseTimeoutSeconds on everything they return, and each wire
client reads its ceiling through callTimeoutMs: the setting, else the
surface's own timeoutMs, else 60 s. A call site no longer has to pass
it, so a new one cannot forget it. The connection test runs under the
same ceiling a read does.

The Coach nudge tick and the reaction line keep their short ceilings
(timeoutPolicy: "surface-ceiling"): the tick runs accounts one after
another under a shared budget, the reaction line's claim lease is
shorter than the largest setting, and both have deterministic copy
that stands in.

/api/auth/me publishes the effective value as
ai.provider.responseTimeoutMs, so a client can size its own request
timeout from it instead of guessing.

Refs #1090
2026-10-02 23:01:36 +02:00
Marc-André Bombeck c08dfc618f test(e2e): call a lab review field beside the value when it shares its row
The spec compared field tops within 8 px. A field label that wraps onto a
second line, which depends on the runner's font metrics, moves that
field's input down by one line while it stays in the same row, and the
560 px case failed on it. A field is now beside the value when it sits to
its right and starts above the value's bottom edge.
2026-10-02 22:56:27 +02:00
Marc-André Bombeck 4125d08fb4 test(e2e): wait for the lab review sheet to stop sliding before measuring
On a phone and a small tablet the review sheet slides up from the bottom;
its width settles before the slide ends, so waiting on width alone still
compared fields from different frames. The spec now waits for every
running animation to finish and for the surface to hold its position and
width across two frames.
2026-10-02 22:33:17 +02:00
Marc-André Bombeck b079e23a83 Merge remote-tracking branch 'origin/feat/manual-workout-entry' into feat/manual-workout-entry 2026-10-02 22:30:19 +02:00
Marc-André Bombeck 9e239dd6c5 build: re-measure the aggregate client budget for the manual workout entry
Main measured 3718 KB gz after v1.39.7 and v1.39.8, almost all of it new
copy in the seven lazy locale catalogs; the workout form, its sheet, the
delete action and their copy add about 7 KB (3725). The cap moves from
3724 to 3749, the same 24 KB headroom as before; the eager routes stay
inside their drift allowances.

Refs #1085
2026-10-02 22:30:09 +02:00
Marc-André Bombeck 11936ed93c Merge remote-tracking branch 'origin/main' into feat/manual-workout-entry 2026-10-02 22:28:44 +02:00
Marc-André Bombeck 1a403b78f7 Merge pull request #1086 from MBombeck/fix/audit-node-forge
Ignore the node-forge advisory until a fix is published
2026-10-02 22:16:59 +02:00
Marc-André Bombeck e6e1b51918 Merge branch 'main' into feat/manual-workout-entry 2026-10-02 21:49:45 +02:00
Marc-André Bombeck f0abe984b0 Merge branch 'main' into fix/dashboard-tile-links 2026-10-02 21:49:44 +02:00
Marc-André Bombeck 5dde6f785c Merge branch 'main' into fix/audit-node-forge 2026-10-02 21:49:42 +02:00
Marc-André Bombeck 1dd0609535 Merge pull request #1088 from MBombeck/fix/e2e-lab-review-settle
Measure the lab review rows once the dialog has settled
2026-10-02 21:49:31 +02:00