The cases for the bounded read asserted the query's where clause on a
mocked client and cast their fixtures past the type checker. Against
Postgres they now show what the read promises: a rolling medication last
taken before the window shows the dose that fell due inside it, and a year
of earlier takes answers the same ledger as a short history.
The plan review rule told the Coach to read how a plan went from its
progress line, but a progress line exists only for an active plan, and
the review sets the plan to reviewed the morning its reminder surfaces.
For the plan being discussed there was nothing to read, so the model
could make a result up. The rule now reads a result only from a
progress line the snapshot carries, and otherwise asks how it went.
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 done screen's "AI is optional" line is a sentence the person reads,
set in muted text, which the UI standards keep for meta. It is body
text now. Its test asserted the absence of strings deleted from every
bundle in the same change, so it could not fail; it now checks that the
line is not muted, that the screen links to the AI settings once, and
that no button sits beside the link.
The form read every 409 as "a metric with this name already exists".
Now that it keeps one idempotency key across taps, a second tap while
the first create is still running also answers 409, and the form told
the person their name was taken. The create and rename routes send
`customMetric.name_taken` with the clash, and the form matches the code.
The vault picker and the Coach's attachment picker each wrote their own
"showing the newest 50" line, which the UI standards leave to
`ListCapNotice`, and with a search running past fifty matches the line
still said "Search to find an older document". Both pickers now share
`DocumentPickerCapNotice` on `ListCapNotice`, which takes the sentence
to show, and a running search asks for a narrower one.
The Russian, Polish and Korean refusals for a removed practice, visit
or check-up said "choose another or none" word for word; they now name
the picker's own empty choice or say to leave the field empty. Brazilian
Portuguese called a check-up a "check". The dose-name refusal names the
vaccination card the way the form does in English, Spanish, Polish and
Dutch, and the oversized workout batch no longer says "at once" twice.
A score, rating, count or ratio has no unit label, so the range
message for a manual pain score read "Enter a value between 0 and 10 ."
with a space before the stop, and the same in every locale. A second
sentence without the unit now answers those types.
The row menu stopped offering edit and delete on a computed score
(recovery, strain, stress), but the list still drew its selection
checkbox and "select all" took it into the page, so the bulk bar could
delete a page of scores, and the tombstones kept the recompute out.
A computed row now has no checkbox on either layout and stays out of
the page selection, and the bulk-delete route skips computed rows if a
client sends them anyway. The iOS app treats these rows as read-only
and does not rely on deleting them.
Two quick deletes in the address book showed the Undo toast for the
second entry only: the callbacks `mutate` takes belong to the latest
call of the list's one mutation, so the first entry's were dropped. The
toast now hangs on each delete's own promise.
The route test that pinned the cached answer replaced the database client
and the engines, so it saw the calls the route makes and not what a reader
gets back. The integration test drives the shipped route: a reading written
past the API leaves the second open on the stored answer, while a
measurement written through the API and a module switch both show it.
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 "Mark done" button re-enables as soon as the satisfy call settles,
while the row kept reading as due until the list refetch landed, so a
second tap stored a second satisfied entry; skip and snooze behaved the
same. The row each of the three calls answers with is now written into
the cached list on success, so the row moves to its next due date at
once, and the refetch follows as before.
Logging the next dose satisfies a matching booster reminder with the
dose date, a calendar date stored at UTC midnight. The reminder engine
reads that instant in the person's zone, where west of UTC it is the
evening before, so the next booster came due a day early. The first
booster's anchor was already counted from the start of the dose day in
the person's zone; the satisfy path now hands the engine the same
instant.
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.
A create form kept its key until a save succeeded. The server stores a
refusal under the key for a day, so after a 422 or 409 the corrected
retry ran as a different body without any protection, and a timeout on
it could store the entry twice; an unchanged retry after the cause was
gone replayed the stale refusal until the form was closed.
Any answer now settles the key. Only a timeout, a dropped connection, or
the 409 that says the first request under the key is still running
(`X-Idempotent-Replay: false`, now `ApiError.inFlight`) keeps it, so
those retries still replay the first answer.
Three refusals answered without an error code, so the web printed the
status's generic sentence. A connection test with nothing configured
read "Some of the entries were not accepted", and a mistyped password
while adding a passkey read "Your session has ended. Sign in again",
which sent people to sign out for nothing.
An empty chain now resolves to no provider, which the test route
answers with `ai.provider.none`. The passkey registration and the
step-up mint answer a failed or stale proof with `auth.reproof.failed`.
`ai.provider.none`, `ai.budget.exceeded` and `auth.reproof.too_weak`
get sentences in every locale.
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.
The iOS app sends the date of birth as the instant its date picker
returned, and the profile writers stored that instant as sent. Since the
report cover and the share page read the date part of the stored value,
an entry made just after midnight in Berlin printed the day before, and a
Californian entry made in the evening printed the day after.
Both profile writers now store the calendar day the value names in the
owner's zone, at noon UTC like other date-only values, so an iOS client
reading the instant back in its own zone keeps the date. Older rows keep
their instant: dateOfBirthKey reads a date-only anchor (UTC midnight or
noon) by its date and any other instant in the owner's zone. The report
collector, the targets age, and the web forms that send the date back
read it through that helper.
Ketones joined in v1.47.2 without their two sibling lists. The metric
catalog treated them as connectable and sent a user without readings
to the integrations page, where nothing delivers ketones; they now
count as manual-only and the row offers "Log ketones". And a checkup
could not be tied to ketones, so a "measure ketones" reminder never
resolved on its own; ketones are now a reminder target next to blood
glucose, with a label in all ten locales. A catalog test also holds
every metric that cannot be connected to a way to log it.
"Log day" always opened on today and let the picker run to today, so
for an illness marked recovered on Monday a note added on Thursday was
filed three days after the recovery, and the day view showed a sick day
with no illness beside it. Both places that open the sheet now hand it
the episode's last day: today while it runs, the recovery day (read in
the profile zone, as the server reads it) once it ended. The picker
opens there and offers nothing later.
The card view marks a checkup the Coach created and one that is
switched off; the list view drew neither, so a disabled checkup looked
active there. Both views now take the badges from one helper, and the
list row shows them beside the title.
The illness screens showed "Weight", "Pulse", "Recovery" and "Day" in
English in German, French, Spanish, Italian and Polish: the four keys
had been copied from English into those catalogs, while their
neighbours were translated. The same copy left the medication wizard's
"Name" step in French, Spanish, Italian and Polish and the dashboard
layout's "Chart" column in German in English. A test now holds the
illness keys translated in every locale.
The guidelines still described the v1.4 app: a drawer nobody uses, six
languages instead of ten, a hover tooltip as the default inline help
although a tooltip never opens on a phone, Dracula utilities for status
colours, a page container and a Settings body gutter the standards
removed, and components that do not exist (a status callout, a
multi-select, honey-pot fields, a per-integration test log). Where the
UI standards own a topic, the page now points there; the rest (button
choice, form controls, settings persistence, filter bars, the dialog or
sheet choice, loading, empty and error states, accessibility, i18n,
motion) is kept and corrected, with the section numbers code comments
cite unchanged. Inline help is an InfoPopover that opens on a tap.
The demo migration was named after the host it targets, and its
README, the v1.5 demo seed and a contributing note named the
maintainer's servers, a container id, an app id and two account ids.
The script already takes every host, container and account from its
environment, so the prose now says source and target and lists the
variables instead. It is renamed to migrate-demo-account.sh; the
password guard follows the new name.
The feature bullets opened with slogans; they now open with what the
feature is. Dashes and middle dots become commas, full stops and bars.
Health scores and correlations move out of the AI bullet, since no
model computes them, and that bullet is marked optional. "Ships
releases roughly weekly" did not match the release list; the README
now says it ships often and points at the release notes rather than
the full CHANGELOG.
The encryption and PWA bullets stay as they are for now: the PWA line
is being corrected on its own.
Settings, About sent every user to CHANGELOG.md on GitHub, a 1.4 MB
file with every release since the first. The link now opens the
releases page, which carries the same notes per version and loads.
The `changelog` field of /api/version keeps its name and stays a URL.
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.
After switching between mg/dL and mmol/L, the target panels on the
glucose page kept the old unit for up to ten minutes. Their unit comes
from the cached targets payload, and the save only refreshed the
account payload. The select now invalidates the targets query, and the
route evicts the profile-derived server caches when the unit changes,
the same way the thresholds and source-priority writes do.
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 localised dispatch helper accepted a `channel` and copied it into
the metadata as `preferredChannel`, which no sender reads. No caller
passed it either; every message still fans out across the user's
enabled channels. The option, its comment and the test that pinned the
copy go.
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.
Nothing in package.json, the workflows, the Dockerfile, the docs or the
other scripts named these: the bench result dumps of v1.44 and v1.45
that no document cites, the version-pinned release checks, the v1.42
correlation dry run, the read-only diagnostics and their cleanup and
conversion companions for defects fixed long ago, the one-off locale
builder from v1.4.25, and the four June seeders for the demo account.
Kept: the two bench results the benchmarks doc cites, the Google Health
sleep repair (the duplicate-night issue is still open), the demo
migration its password guard reads, the column-reader sweep its test
imports, and the message-bundle tools. The scripts README now lists the
kept tools that it did not name before and drops a reference to a file
that does not exist.
The comment said the series route caps sleep at 365 days; the route
accepts up to 3650, which is what the "All" tab asks for and what the
clamp below applies.
The cycle page's calendar card showed one line of loading text until
its read arrived, then grew to the month grid, so the predictions and
everything below moved down once the page had drawn. The card now
draws the month header and this month's weeks in the grid's own cell
size while it loads, with the loading text kept for screen readers.
`/admin/backups` opens with the recovery readiness card, which showed a
one-line spinner until its seven signals arrived as one block, so the
backups and off-host cards below jumped down by the whole list after
they had drawn. The loading state now draws one placeholder row per
signal in the list's own row shape, with the loading text kept for
screen readers.
A metric page without data offers to ask the Coach about the metric.
44 of the 49 pages passed that question as an English literal, so a
German, French or Korean reader got an English first message; only 5
read it from the catalog. 31 of the 44 were one sentence with the
metric's name swapped in.
`HealthKitMetricPage` now asks that sentence once, translated, around
the page's own title, and the 31 pages drop the prop. The 13 pages
with a question of their own (steps, HRV, sleep, mood, workouts, blood
pressure and others) read it from the catalog like the 5 did, in all
ten languages.