Commit Graph
1223 Commits
Author SHA1 Message Date
Marc-André Bombeck 0a71e8ffa5 fix(ui): keep the last four small fields at 16 px on a phone
The Coach memory editor, the managed profile's handover link, the cycle
length fields and the command palette's search set a font size under
16 px on touch screens, so iOS zoomed the page when one took focus. They
now read 16 px on a touch screen and keep their smaller size under a
fine pointer, like the field primitives. With no field left over, the
guard's list of known offenders goes.
2026-11-12 13:08:45 +01:00
Marc-André Bombeck 8f3b640ab6 fix(sheets): put a swiped sheet back when it asks before closing
A swipe past the close point paints the sheet where the finger let go and
asks the owner to close it. A sheet holding unsaved input asks about
discarding first and stays open, so it sat half pushed off the screen
behind the question. One frame after the close, a sheet whose content is
still open now glides back to its resting place. The function's size
baseline rises by the seven lines this takes.
2026-11-12 13:07:54 +01:00
Marc-André Bombeck 5533b6edf9 fix(sheets): ask before a dismissal drops an edit that keeps no seed
The illness episode, cycle day, custom metric, target, mood tag, symptom
and life event sheets open filled from a record or a fetch, so the body
check cannot tell typed input from what was loaded, and a stray tap
outside or a swipe dropped the change without asking. Each now keeps
what it opened with (the cycle day once its fetch has hydrated the form)
and passes whether the fields differ to the sheet's dismissal guard. The
custom metric and life event forms report it to their hosts the way the
workout form does.

The size baselines of the six sheet functions and the cycle sheet file
are raised by the few lines the comparison takes.
2026-11-12 13:06:33 +01:00
Marc-André Bombeck 4105e2c85f fix(coach): keep the last message clear of the New conversation button
Where the composer fills the column (a phone, the drawer, a narrow
column), the New conversation button floats above the composer and
covered the last message's time and actions at 390 px. The thread now
keeps the button's height free below the last message while the button
floats, the way the shell clears the Coach button, and drops back to its
plain gutter once the button sits beside a wide composer.
2026-11-12 12:58:58 +01:00
Marc-André Bombeck 367fcd5332 fix(insights): gate the tab strip on one presence read
The tab strip on every Insights page read the whole overview (22 KB),
a workouts page, the ECG list and the analytics summaries to learn which
pills to show. GET /api/insights/presence answers just that, for the
record on screen: the reading count per type after the record's module
switches, and whether it has mood entries in the last 90 days, a tracked
scheduled medication, workouts and ECG recordings. A flag whose page a
switched-off module hides is false and its rows are not read. The strip
now reads this and the saved layout; each page keeps loading its own data.
2026-11-12 12:56:16 +01:00
Marc-André Bombeck ef952c4354 test: carry the function sizes the merged branches recorded
The sheet now owns its dismissal guard and the Back handling, and three pages gained one or two lines for the save and refetch order; each branch had recorded its numbers, the merge kept only one side.
2026-11-11 22:54:20 +01:00
Marc-André Bombeck d5dd05b5ac Merge branch 'fix/v1475-cleanup' into release/v1.47.5
# Conflicts:
#	src/__tests__/baselines/source-file-lines.json
#	src/__tests__/baselines/source-function-lines.json
#	src/__tests__/baselines/version-stamp-comments.json
2026-11-11 22:51:42 +01:00
Marc-André Bombeck b5e118d086 Merge branch 'fix/v1475-speed' into release/v1.47.5
# Conflicts:
#	src/__tests__/baselines/source-function-lines.json
2026-11-11 22:51:36 +01:00
Marc-André Bombeck ad483fa4bc Merge branch 'fix/v1475-mobile' into release/v1.47.5
# Conflicts:
#	src/__tests__/baselines/source-function-lines.json
#	src/__tests__/baselines/version-stamp-comments.json
2026-11-11 22:51:30 +01:00
Marc-André Bombeck f17819f1c2 fix(settings): English addresses for the health record and checkups pages
Two Settings pages had German words in their address in every
language: /settings/gesundheitsakte and /settings/layout/vorsorge.
They are /settings/health-record and /settings/layout/checkups now,
and the old addresses (and the older /settings/sharing and
/settings/vorsorge) redirect permanently, so bookmarks and links in
old reports keep working; the report range a since-last-visit link
carries in its query passes through. The in-app links, the tour, the
command palette, the anchor forwards and the e2e specs use the new
addresses, and the two message groups follow the new slugs.
2026-11-11 22:47:40 +01:00
Marc-André Bombeck 6be88e777a fix(medications): English tab slugs in the URL, old links still open
The medication detail tabs put German words into the address for
every language (`?tab=verlauf`, `?tab=bestand`). They are overview,
effect, schedule, supply, history, injection and advanced now. Every
old slug maps to its tab, so a bookmark, a link in an old report or a
low-stock notification sent before the change opens where it did. The
low-stock push, the list card and the efficacy summary link with the
new slugs. The slug vocabulary moves into its own module with a test.
2026-11-11 22:41:42 +01:00
Marc-André Bombeck 4a40a63656 docs: remove the second contributing note and its dead pointers
CONTRIBUTING-AI.md pointed contributors at a CLAUDE.md "checked in and
kept current"; that file is not in the repository, so the pointer and
the twenty references to it in folder READMEs, docs and comments led
nowhere. The note goes; its one lasting rule (whoever commits a change
stands behind it, no real names, no live health figures, no secrets)
moves into CONTRIBUTING.md. The folder READMEs keep their own content
and drop the dead link, and a reference to the unpublished planning
folder goes with it.

The checked-in .mcp.json was a local editor config no workflow reads;
it is untracked and ignored now. The translation tiers are described
as machine-translated first drafts reviewed by the community, and the
list names Russian, which it missed.
2026-11-11 22:24:16 +01:00
Marc-André Bombeck 8f6081de89 test: delete text pins on things removed releases ago
Five tests only read source or message files to assert that something
long gone stays gone: the moodLog bridge (removed in July), the chart's
target-adjust link (v1.16.8), the mood notes timeline, the medication
detail page's old dose card and wizard host (v1.15.18), and the
obsolete v1.34.1 copy keys. They protect no behaviour, and the general
i18n guards already hold every key and placeholder across the ten
locales, which the v1.34.1 list did for nine. The source-read baseline
drops from 337 to 332.
2026-11-11 22:23:07 +01:00
Marc-André Bombeck 408c2abb39 test: lower the as-never baseline after the channel test went
The removed preferred-channel test carried one of the casts the
realism ratchet counts.
2026-11-11 22:22:31 +01:00
Marc-André Bombeck 0c07764097 refactor(settings): drop two query keys nothing reads
The unit-system and glucose-unit selects invalidated
`userUnitPreference()` and `userGlucoseUnit()` after a save, but no
query was ever registered under either key; every surface reads the
units from the account payload, which both selects already refresh.
2026-11-11 22:20:53 +01:00
Marc-André Bombeck 507d4e6fac refactor(medications): drop the side-effect slot nobody wires
The card menu took an optional side-effect item for the GLP-1 card, but
no page ever passed the handler, so the item never rendered; side
effects are logged on the detail page. The slot, its message key in ten
locales and the tests that passed it by hand go.

One test pinned behaviour only the dead slot produced: a delegate who
may write but not manage kept a menu trigger because the test supplied
the handler. In the app that delegate never had one, and the test now
says so.
2026-11-11 22:19:47 +01:00
Marc-André Bombeck fc1fadcdab chore: remove production files that only tests reached
Four files under src had no production importer:

- the keyset measurement reader for backups: both backup writers moved
  to the shared account copy and the streamed full backup, and its
  header still claimed both used it;
- the HOMA-IR calculator and the mood stability label, never wired to a
  surface;
- the backup verdict helper, which only the backup classification guard
  called. It now lives in that guard.

The queue expiry table is what the job queue guards check the registry
against and nothing in the worker reads it, so it moves to the test
helpers. The version-stamp baseline drops the deleted files.
2026-11-11 22:18:11 +01:00
Marc-André Bombeck 82125815d5 test: settle the parity snapshot and the size ratchet
An unreadable provider key is now reported once: the empty-chain fallback no longer opens the same key a second time. Two files shrank, so the size baseline steps down.
2026-11-11 22:17:07 +01:00
Marc-André Bombeck e1d143d689 fix(medications): read the dose history's window, not every intake
The history tab asks for a window of at most a year, but the route read
every intake the medication ever had: a medication taken three times a
day for five years read about 5,500 rows on each open. The full read
also served to find the last intake for rolling cadences.

The route now reads the rows anchored or taken in the window, and of
the rows before it only the latest take, which is the anchor a rolling
grid re-starts from and the last intake when none falls in the window.
Earlier takes place no slot in the window, so the ledger is the same.
The read moves into its own function, which shortens the handler.
2026-11-11 22:05:48 +01:00
Marc-André Bombeck 8352b1eef2 fix(onboarding): keep the tour card above the home indicator
On a phone the tour falls back to a card at the bottom edge, 8 px above
it, so in the installed app its Next and Skip row sat inside the home
indicator strip. The card now adds the bottom safe area, as the bottom
bar and the sheets do.
2026-11-11 21:49:56 +01:00
Marc-André Bombeck e99180fc7d fix(nav): let the More sheet scroll on a phone held sideways
Held sideways a phone keeps the bottom bar, and the sheet's 90 dvh cap
is about 350 px there, while the hub needs 410 to 540 px. The grid did
not shrink or scroll, so the last rows (documents, timeline, coach,
achievements) sat below the screen edge. It scrolls inside the sheet
now and stops at its end instead of pulling the page.
2026-11-11 21:48:17 +01:00
Marc-André Bombeck aacf06b7b3 fix(coach): say when the attachment picker list is cut
The Coach's "Choose from documents" picker reads the newest fifty
documents and dropped the route's nextCursor, so with a longer vault an
older report seemed not to exist unless one already knew to search for
it. A cut list now ends with the same line the document picker shows:
how many are shown and that the search reaches the rest. The document
row moves into its own component, which takes AttachmentPicker under
the function-size cap.
2026-11-11 21:44:42 +01:00
Marc-André Bombeck 3a249bccba fix(settings): keep note and paste fields at 16 px on a phone
On /settings/anamnesis the four note fields were raw textareas styled
by three copies of a class constant with text-sm (14 px), and on
/settings/export the three paste fields used text-xs (12 px) and the
backup passphrase text-sm. iOS zooms the page into such a field on
focus and leaves it zoomed. The notes use the Textarea primitive now
and the export fields keep the small size only under a fine pointer.

The 16 px guard read the five field primitives and could not see a
call site override their size, or a raw field styled from a constant.
It now parses every field call site; four that are still small sit on
a list that only shrinks. The focus guard drops the three records
files, whose fields come from the primitive now.
2026-11-11 21:43:10 +01:00
Marc-André Bombeck 2a60f21a8b fix(ui): open hint content on a tap instead of a hover
Seven places kept information in a hover tooltip, which never opens on
a phone: the measurement-diversity lightbulb on the metric pages, the
sleep-source warning on the dashboard tile, the score coverage
breakdown, the target status pill, the range bar's marker, the colour
hint of the dashboard averages and the full token and user names in
the admin token list. The lightbulb showed a button that did nothing,
the warning a sign nobody could read.

They open on a tap now: InfoPopover for the two icon buttons, a
Popover on the meter, pill, marker, averages and cells. Where the
trigger sits in a link (a dashboard tile, a lab row) its tap opens the
popover instead of the page.
2026-11-11 21:39:27 +01:00
Marc-André Bombeck e68041a3af fix(forms): keep one Idempotency-Key for the remaining create forms
A screening, an illness episode, a life event, a symptom, a custom
metric or an address-book entry created on a weak signal could be
stored twice: the POST committed, the answer missed the timeout, and
the second tap went out under a new key. Their routes already honour
the key; the forms now post through useIdempotentPost like the other
create forms, so the retry replays the first answer.

The screening's save moves into a small hook beside MentalWellbeing,
which sat at the function-size cap and now drops below it; the custom
metric form's create-or-update call moves into one as well.
2026-11-11 21:39:19 +01:00
Marc-André Bombeck 6f9b42c35e fix(sheets): let Back close the open sheet
Back on a phone (the Android button, the edge swipe of the installed
app) with a sheet open went back a page under it: the sheet and what
was typed in it were gone, in the edit sheets too. Only the account
form guarded Back, behind an option.

Opening a ResponsiveSheet now pushes one history entry with the page's
URL. Back lands below it and closes the sheet, asking first when it
holds unsaved input or when its owner keeps it open; a sheet closed any
other way takes its entry off again, so the next Back still leaves the
page. A history write made while that Back is under way waits for it, a
link followed from a sheet replaces the sheet's entry, and the page
entry keeps the URL the page has now, so a stripped one-shot `?add=`
does not come back. The vault's document sheet already owns `?doc=` and
opts out.
2026-11-11 21:33:48 +01:00
Marc-André Bombeck ffde57869b fix(sheets): ask before a dismissal drops typed input
The eight-step medication wizard, the add sheets on /measurements,
/mood and /labs, and the visit, vaccination, practitioner and checkup
forms closed on a stray backdrop tap or swipe and lost what was typed.
The dashboard quick entry and the capture menu asked first, through two
hand-copied dialogs and a DOM check that read the first sheet body in
the document and counted the date field's prefilled "now" as input.

ResponsiveSheet now takes a dirty flag, or a check it hands its own
body, and asks with the existing discard dialog. Forms that keep a seed
pass whether they differ from it; the wizard compares what a save would
send. Plain add forms pass the DOM check, which skips the date and time
fields again. The three copies of the dialog are gone.
2026-11-11 21:32:07 +01:00
Marc-André Bombeck 0b7afd8460 fix(errors): show the catalogue sentence, not the server's text
About forty refusal messages still printed the error string the server
sent: English in every locale, and for a dropped connection the
browser's own TypeError text. Mood tags, targets and thresholds, family
history, medication side effects, phases, API tokens and dose imports,
the Coach model picker, the AI provider, fallback chain, timeout and
Codex cards, the notification channel cards, the avatar, passkeys,
security keys, the authenticator card, the re-proof dialog and the
admin pages now resolve the refusal's code through localizedApiError
and fall back to the surface's own sentence.

Two refusals carried something a person needs and had no code: a
private AI base URL (the AI_PRIVATE_ORIGINS hint) and removing the last
passkey. Both now send a code with a sentence of their own. A throttled
re-proof says so in the reader's language. Raw fetch sites throw the
same ApiError as the verb helpers through the exported throwApiError,
which replaces the admin's getApiErrorMessage; the authenticator card's
copy of describeStepUp is gone, and describeStepUp no longer passes a
non-step-up refusal's prose through (its test pinned that).

The admin password reset keeps the server's verdict on a 422: the
server words the strength and breach verdict in the account's language.
2026-11-11 21:29:33 +01:00
Marc-André Bombeck 9227844f42 Merge branch 'fix/v1473-ai' into release/v1.47.3
# Conflicts:
#	src/__tests__/baselines/source-function-lines.json
#	src/__tests__/baselines/version-stamp-comments.json
2026-11-11 21:07:27 +01:00
Marc-André Bombeck aa6e617da0 fix(insights): grade blood pressure and pulse against the own target
Someone whose doctor agreed a target of 140/90 entered it on /targets;
the chart and the dashboard tile followed it, but the BP and pulse status
texts, the general status, the comprehensive overview, the insight-card
alert and `extractFeatures` (and through it the comprehensive text, the
daily briefing and the Coach) still graded every reading against the age
default. The text under the chart could contradict the chart.

All six places now resolve the band the way the charts do since #1261:
`resolveEffectiveBpTargets` for blood pressure, the PULSE override for
resting pulse, the default only where no target is set. The resolved band
rides in each snapshot's targets block, which is part of the input hash,
so the next generation follows a changed target. The Health Score keeps
its clinical targets on purpose.
2026-11-11 21:00:10 +01:00
Marc-André Bombeck 926c427907 fix(coach): stop computing the plan review verdict nobody sees
The nightly plan review read a before/after window of the plan's metric
and stored an English verdict in `outcomeEncrypted` for every plan. No
screen and no route returned it; only the Coach snapshot read it, behind
the `COACH_EXPERIMENT_VERDICT` flag that is off by default. The worker now
only moves an active plan whose review fired to `reviewed`, which is what
takes it out of the standing group. The flag, its snapshot block, the
three eval cases for that block and the compose and env entries go with
it. The Coach prompt keeps its experiment rules and now ties the
read-back to the plan's progress line instead of the removed block.
Existing stored outcomes stay in backups untouched.

The plans page and the Coach reminders card printed the metric as a raw
key in capitals (WEIGHT, BLOOD_PRESSURE) in every language. Both now show
the measurement's name from the catalogue, through the plan metric
resolver moved to `plan-metric.ts`; a key HealthLog does not read shows
as written. The unused `coach.plans.proposalTitle` string goes.
2026-11-11 20:51:18 +01:00
Marc-André Bombeck b793be3f84 fix(onboarding): shorten the AI pitch on the last setup screen
Every new account finished setup on a full AI panel: a gradient badge, a
sample briefing, three ways to connect a provider, a one-tap offer for the
operator's key and a disclaimer, the most prominent block on the screen.
It is now one muted line with a link to Settings, AI, shown under the same
rule as the Insights hint (could be set up here, nothing configured yet,
not opted out). That hint and the consent request on Insights and
Settings, AI stay the places that invite.

The sample briefing card, the `onboarding.ai.*` strings in ten locales and
the end-to-end spec for the removed offer go with it. The consent-grant
guard's floor drops to the two callers left.
2026-11-11 20:38:37 +01:00
Marc-André Bombeck 1d5236a326 test(ai): settle the ratchets after the empty-chain change
The resolver tests queue their fixture rows through one helper instead of
an `as never` per row, the no-provider status case also proves the
single-provider resolver is never asked, and the size and comment
baselines take the lower numbers the change left behind.
2026-11-11 20:37:48 +01:00
Marc-André Bombeck f519df4433 Merge branch 'fix/v1473-record' into release/v1.47.3 2026-11-11 20:18:59 +01:00
Marc-André Bombeck 6a02cda210 Merge branch 'fix/v1473-writes' into release/v1.47.3 2026-11-11 20:18:58 +01:00
Marc-André Bombeck da671f74aa refactor(day): share one window for date-only columns
The day view, the day index and the visit preparation each built the
UTC calendar window for a date-only column by hand (documents had a
fourth copy). `dateOnlyWindow()` beside the other date-only helpers
replaces them. The report name select becomes one spread, the share
page builds its calendar formatter outside the view, and the PDF
section reads it off the context, so the functions these fixes touched
stay within their size budget; `DocumentRecordLinks` drops under it.
2026-11-11 20:12:44 +01:00
Marc-André Bombeck 53f4ecc2c5 fix(documents): say when the document picker list is cut
The picker for linking documents to an illness and for choosing
documents for a share link reads the newest fifty and dropped the
route's nextCursor, so with a longer vault an older report seemed not
to exist unless one already knew to search for it. A cut list now ends
with a line that says how many are shown and that the search reaches
the rest. Paging was left out: the coach attachment picker shares the
picker's cache key with a single-page read.
2026-11-11 20:08:24 +01:00
Marc-André Bombeck d204b4344b fix(records): offer Undo after deleting a vaccination, visit or entry
Deleting a vaccination, a doctor visit or an address-book entry closed
the sheet with no way back, although all three are soft deletes with a
documented restore route. The vaccination hook had a restore mutation
nobody called; visits and the address book had none.

The two hooks gain a restore mutation, and each delete now confirms
with an Undo toast, the same pattern the illness episode uses. The Undo
runs after the sheet has closed, so it awaits the restore itself and
the hook's own onSuccess refreshes the lists, instead of relying on
callbacks a closed sheet never receives. Restoring an address-book
entry brings the entry back, not the links the delete cleared on the
visits that named it. The three new success toasts are pinned in the
success-affordance guard.
2026-11-11 20:06:02 +01:00
Marc-André Bombeck 5449933c4f chore: remove three components nothing renders
personal-record-badge.tsx, intake-history-editable.tsx and
intake-history-preview.tsx had no importer outside their own tests and
the preview, so they rendered nowhere while four guard tests kept them
listed as live surfaces. They go with their tests, their guard entries
and the strings only they used.

GET /api/personal-records stays: the native app reads it. The
resolved-field guard now records its benchmark band as a native-only
field instead of naming the deleted badge as its reader.
2026-11-11 20:03:00 +01:00
Marc-André Bombeck f761500247 fix(forms): send one Idempotency-Key per opened create form
A mood, symptom, lab value, custom metric value, unscheduled dose,
allergy, vaccination, visit or measurement saved on a weak signal could
be stored twice: the POST committed, the answer missed the 15 s
timeout, the form said it could not save, and the second tap was a new
call with a new key, so the server ran it again.

useIdempotentPost keeps one key until a save lands and renews it for the
next entry, so the retry replays the first answer. The create forms and
the three create hooks post through it; the comment on the per-call key
in api-fetch now says what that key does and does not cover.

LabForm and MoodForm each gain the one line that mounts the key, so
their function-size baselines go up by one. The intake toast guard now
recognises a dose posted through the hook.
2026-11-11 20:02:40 +01:00
Marc-André Bombeck 02856e7f37 fix(targets): accept a child's weight target below 30 kg
A weight target was refused below 30 kg on every save path (the own
targets editor and a managed record's settings parse the same schema),
while a weight reading is accepted from 1 kg. A guardian setting 20 to
23 kg for a child got only "please check your entries". The target
floor is now the measurement floor.

The weight bands on the dashboard and the weight page started their low
red zone at a hard-coded 30 kg, so a target below it lost that zone; the
edge now follows the same floor. Bands are clipped to the chart's own
domain, so an adult's chart looks the same. `buildWeightBandsFromHeight`
had no caller left and goes; the band builder's 30/250 defaults, which
every caller overrode, go with it. The unit-adapter test keeps its
inward-rounding check at the new floor (1 kg is 2.2046 lb, offered as
2.3).
2026-11-11 20:00:39 +01:00
Marc-André Bombeck 4fa5e6381e fix(insights): take weight and BMI bands from the record on screen
Inside a shared or managed record the weight page shaded the band for
the viewer's own height, and the record's own target never arrived
because `/api/user/thresholds` refuses under a switch: a parent saw
their own healthy range behind a child's weights. The BMI page decided
from the viewer's height whether there was anything to show, and the
target gear opened an editor that could neither load nor save there.

The weight page now reads the record's target and height from
`/api/insights/targets`, the payload the reference panel under the
chart already uses, and paints the same wings as before. The BMI page
reads the record's height there inside somebody else's record. The
gear stays hidden in another person's record, as the BMI chart
already draws nothing there rather than a wrong figure.
2026-11-11 19:56:49 +01:00
Marc-André Bombeck 71fe874ef2 fix(medications): silence a switched-off module and an ended course
With the Medications module switched off, dose reminders, missed-dose
rows and low-stock alerts kept coming, a tap on one landed on
"Medications is switched off", and the Telegram buttons still booked
intakes. The reminder tick now drops the medications of an owner with the
module off, one module read per owner and tick, so neither a reminder nor
a missed-dose placeholder is written while it is off. The low-stock pass
skips such a user, as the mood reminder does for its module, and the bot
offers and books nothing.

A medication whose course had ended still counted as live: the low-stock
pass kept warning about its stock, and the bot's /add list offered it and
booked a dose after the end, taking a unit off the stock. The low-stock
pass and every bot path now skip a course that has ended on the person's
clock. A course that has not started yet keeps its reorder warning and
stays in the bot. A paused medication is already active: false, so the
existing filter keeps it out.

The bot's six copies of the medication lookup go through one helper.
2026-11-11 19:55:09 +01:00
Marc-André Bombeck 8057bb20bc fix(notifications): drop the four switches nothing sends
The notification matrix listed "Unusual measurement", "Low adherence",
"Withings sync failed" and "Personal record" with a switch per channel,
three of them on by default, yet nothing dispatches any of them: a reading
far outside its range or a week of missed doses produced no message.

eventTypesForRole now leaves them out for every role, next to the
admin-only filter, and the preferences read no longer serves rows stored
for them. Their labels in all locales and their tap targets are deleted.
The types stay in EVENT_TYPES, so the preference write and the stored rows
keep parsing; no migration.

The label and tap-target tests walked EVENT_TYPES and so pinned the four
in place; they now walk the event types that are sent.
2026-11-11 19:53:01 +01:00
Marc-André Bombeck 971d5d32cc fix(documents): refresh what confirmed facts and a take-over create
Confirming a document's facts creates medications, conditions and lab
values, and the profile take-over creates a condition or an allergy.
Both evicted only the roots of those lists, so the dashboard and the
Today digest showed the old medication set until the 120 s poll, the day
and timeline kept the old state for five minutes, and adherence and
nutrient reads were not evicted at all.

Both now spread the bundles of what they create, and the condition paths
pair them with the inactive refetch of the daily reads.
2026-11-11 19:44:13 +01:00
Marc-André Bombeck 5cf4a7d213 fix(doctor-report): name a managed record by its display name
The report cover, the PDF author field, the FHIR Patient and the share
page printed `fullName ?? username`, and the collector never read the
display name. A managed record has no way to get a full name, so its
generated `managed-<hex>` login landed on every clinical document made
for it; an own account printed the login instead of its display name.

The collector now resolves the name once (`patientName()`: full name,
display name, login, never a managed record's generated handle) and the
five readers print that one field. `patient.username` and
`patient.fullName` leave the report payload; nothing else read them.
2026-11-11 19:42:17 +01:00
Marc-André Bombeck 2e73b0ffec fix(query-keys): refresh the day view and timeline after every write
Symptoms, workouts, lab results, allergies, documents and questionnaires
evicted only their own lists. An open day, its calendar dots and the
timeline kept the pre-write answer for up to five minutes, so "Capture
for this day" saved a symptom or a workout and the day showed the old
list, and a deleted row stayed on it.

Workouts, labs, allergies and documents get a bundle beside the seven
that already carry the day and timeline roots; the six lab lists become
one (the OCR dialog's own copy of the commit hook's list goes with it).
Symptoms and questionnaires add the two roots to their list. Symptoms
stay off the illness bundle on purpose: nothing on the dashboard or the
Today digest reads them. Custom metrics need nothing: neither the day
nor the timeline reads them. invalidateKeys now drops a key listed twice,
since spread bundles share the two roots.
2026-11-11 19:41:14 +01:00
Marc-André Bombeck a486c3c8a3 Merge remote-tracking branch 'origin/release/v1.47.2' into takeover/1370
# Conflicts:
#	CHANGELOG.md
#	src/lib/module-list-prefs.ts
2026-11-11 00:04:51 +01:00
Marc-André Bombeck ea3fcce80c fix(checkups): keep a missed rolling reminder due until it is measured
A rolling reminder ("every N days") is anchored on its last reading, but
the reminder tick rolled any cycle of a week or less on after a delivered
reminder, re-anchoring it on the send. A missed every-two-days blood
pressure reading therefore moved two days ahead each evening, read "in 2
days" on the card, and never showed as today or overdue.

A rolling reminder now stays due after its reminder, whatever its
interval, until a reading lands or it is done, skipped or snoozed. While
it stays open the repeat follows its interval, capped at a week, still in
its notify hour and under the per-day claim. Calendar rules that recur
within a week (a twice-daily course) keep rolling on; appointments are
unchanged.

holdsOpenAfterReminder stays the one definition for the tick and the
edit route. The daily digest asks the narrower question underneath it,
hasLongCycle, so a short rhythm still waits for its time of day on the
start page and is not pinned under the rail cap.

Refs #1369
2026-11-10 22:14:02 +01:00
Konrad Garbal b4ac67ee7c feat(labs): add biomarker filtering and sorting 2026-11-10 20:46:57 +01:00