Opening a sheet pushed a history entry, and closing it took that entry
off again with a history back. The browser answers that back later, so
when the close started a navigation it landed after the router's push
and undid it: the medication wizard stayed on the list instead of
opening the new medication, a section picked from the settings list on
a phone stayed on the account page, and a link followed from a sheet
could put the page under it back.
The sheet history goes, with its wiring in ResponsiveSheet, the
document sheet's opt-out, the account form's deference to it, Back
answering the discard question, and their tests. Back behaves as
before: it leaves the page and the sheet goes with it, and the account
form's own Back guard answers every Back again. A tap outside, Escape,
the X and a swipe still ask before dropping typed input. The two size
baselines return to their earlier numbers.
A badge call from the service worker stalled Chromium's worker thread
while it activated, awaited or not: the worker reached "activated" but
never claimed its pages, so no page was ever controlled and every
offline path was gone. The worker no longer touches the badge. The
installed app clears a leftover count from the page once the worker is
registered; a browser tab leaves it alone.
The filter-sync test no longer reads `history.length`, which the
browser caps; the recorded push without a replace already proves the
new entry.
A "Measure blood ketones" checkup kept sending its push, Telegram or
email with the Glucose module switched off, while the glucose checkup
beside it went quiet. The checkup job kept its own copy of the module
map, which knew glucose but not ketones.
The job now asks the one ownership table the rest of the app gates on.
That table did not list the four mental-wellbeing screenings, which only
the job's copy knew, so they join it under the mental-health module; a
screening score row now also follows that module in the day view and
search.
- The Coach thread kept 80 px free under the last message for the New
conversation button while the keyboard hid that button.
- The remove buttons on lab alias chips and attached-document chips
reached sideways into their own label and the next chip, and an
alias is removed at once. Their tap area now stays inside the chip's
gaps sideways and keeps its height.
- A capture menu comment described a dialog that is gone.
The symptom form and the dashboard's medication intake asked "Discard?"
on every close, untouched. The sheet's fallback check read the input
Radix keeps for the intensity slider (value "0" before anyone moves
it) and the dose the form fills in from the preselected medication as
typed input.
The check now skips a slider's own input and counts a slider moved off
unanswered instead, and compares a field against the value its form
says it filled in (`data-seed`). A controlled input's defaultValue
follows its value, so it cannot tell the two apart. The dose field
names the medication's dose, one line more for the form's baseline.
Merging the previous release in kept this branch's older numbers for
telegram-webhook-handlers.ts (1335 lines, handleCallback 590), although
the file is unchanged at 1319 and 582, the numbers the previous release
recorded. The gap sat inside the ratchet's slack, so nothing flagged it.
The register refusal codes, the end of the no-device rest, the
no-recipient annotation and the sign-in shell comment had each grown a
function the size ratchet holds. The codes become constants, the rest
ends through a helper beside the one that starts it, and the
annotation moves into that helper. The settings Web Push card dropped
under the limit when its subscription post moved out, so its entry goes.
The dose tag comment still explained the clear push sent for a logged
dose, the session-end wipe comment still named the worker's API cache as
a live layer, and the worker's header and foreign-origin note did not
match what the worker stores and when it resumes.
The server set the Home Screen badge only through the clear push sent
for each logged dose, and that push is gone. A device whose last clear
left a count (one forgotten dose, an update in the middle of the day)
kept that number on the icon for good, since iOS never clears a web
app's badge by itself.
The worker now clears the badge once on activate. The badge handling in
the push handler, the unused outstanding-dose counter and the test of a
payload no server sends are removed, and the self-hosting guide no
longer promises the clear push or the badge.
Only the menu's sign-out removed this browser's push subscription. The
sign-out buttons on the registration page and the profile take-over page,
which exist so someone else can use the browser next, left it behind, and
the next person kept seeing the last account's reminders. All three now
go through one signOutRequest.
The browser's own subscription is now removed only once the server has
confirmed the sign-out; the server row still goes first, while the
session can name it. A refused sign-out stores the row again, so the
person who is still signed in keeps push. The logout test pins the new
order and the refusal.
The helper file is renamed web-push-subscription.ts, since it now also
saves a subscription for the settings card.
NEXT_PUBLIC_DASHBOARD_SNAPSHOT=false built a dashboard that read its
tiles from two analytics slices, the widget layout, the mood analytics
and the thresholds instead of the one snapshot cell. It took a rebuilt
image to reach, and the snapshot has been the only path the published
image runs since it became the default. The flag, its readers, the
legacy reads in the dashboard hook and tile context, the slim and thick
merge helper, the build-time toggle notes in .env.production.example and
the self-hosting guide, and the tests that pinned the legacy wiring go.
The root layout now reads the public monitoring switches while it
renders, so nothing calls GET /api/monitoring/settings any more (the
native client never did); the route and its unpublished entry in the
route coverage guard go. The Coach feedback card reads the AI quality
card's cell, which left queryKeys.adminCoachFeedback() without a reader.
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.
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.
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.
The fixtures added with these fixes switched the type checker off with
`as never`, which the realism ratchet refuses to see grow. They now carry
their types, three webhook mocks needed no cast at all, and the lowered
counts, with the targets route's one fewer version stamp, are written
back.
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.
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.
The notification categories' contract description, which lands in the
published API document, and a handful of new code comments and test
titles used em dashes; the house style keeps them out of new prose.
The three deletes gained an Undo toast that calls the restore route,
but the destructive-control registry still listed them as tombstoned
with no restore, with comments saying they would become restorable the
day a surface offered the undo. The correlation card test's header
still described the disabled "Coming soon" action it no longer tests.
The "taken <name>" keyword looked up the first medication of that name
and only then asked whether its course had ended, so an old ended
course beside the current one of the same name answered "not found".
The lookup now reads every match and takes the first that runs.
A reminder button judged the course at the moment of the tap, so the
last evening dose of a course, confirmed after midnight, was refused
although the web still takes it. When the message names its slot, the
course is now judged at that slot. The handler file grows by six lines;
its size baseline is raised to match.
The weight band and the BMI chart read `/api/insights/targets` inside a
shared record, and the route asked for a manage grant, so a read or
write delegate got a 403 and saw no band and no BMI. The targets are
the bands the dashboard snapshot already paints on a whole-record read
grant, computed with no provider, so the read now asks for the same
level. A section-scoped grant is still refused.
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.
Now that the BMI chart draws inside a managed record, a child's normal
BMI sat in the red under-17 band, and the weight page painted the adult
WHO band (about 26 to 35 kg for 118 cm), so a healthy 21 kg read as
underweight. The release's own weight-band test pinned that band.
No band here knows a child's age yet. For a record under 18, or a
managed record with no date of birth, the targets read names no WHO
weight range, no BMI range and no category; the record's own weight
target still shows, and the values and the chart stay. The BMI chart
and the weight band draw only what the server names. The weight-band
test now uses an adult height and gains the child case.
The targets builder and the vitals builder grow by two lines each for
the flag; their size baselines are raised to match.
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.
A person on the OpenAI-compatible gateway lost every AI surface once
their chain was saved at all: the fallback to the picked gateway asked
whether the chain was the default object itself, so a chain reset to
the default, reordered, or written by the gateway form's reasoning
setting (which appended a disabled entry to hold the value) no longer
counted, and the chain resolved empty. Rows written that way before
this release broke on deploy.
The fallback now reads the chain's content: the picked gateway serves
when nothing else resolves, unless the chain holds the gateway switched
off. A disabled gateway entry that carries a reasoning effort is the
form's holder, not a switch; the chain editor drops the effort of a
gateway it switches off, so a switch-off stays one. Saving the gateway
form with the gateway picked writes its entry switched on. Resolution,
the presence probe, the account payload and the iOS availability flag
all read the same rule.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.