The AI settings page posted to /api/consent/ai/web on every mount and the
server minted an ai_full receipt for it, so an account that had agreed to
nothing sent its health data to the operator's provider from background
jobs. Native builds synced a receipt the same way on every launch.
- Both grant routes now require `confirm: true`; anything else writes
nothing and answers 400 consent.ai.confirmRequired.
- Remove the mount-time heal. A consent card names the operator's service
and the data that would go there, with one Agree button, in Settings > AI
and on the Insights overview while AI is closed by the missing consent
alone.
- Publish the standing consent as `ai.consent` on /api/auth/me (status,
since, recipients); the settings card renders active-since and the
withdraw control from it.
- Existing receipts stay valid.
- Guard that no client grants outside a click handler, route tests for the
confirmation, and an integration test that a fresh operator-key account
gets no background AI text until it agrees.
The doctor report PDF printed lab values and reference ranges with one
decimal and the share page with two at most, so a PSA of 0.034 read 0.0
or 0.03 and a TSH window of 0.27 read 0.3, while the labs page kept more.
The labs page itself cut values above 1 to two decimals.
One rule now decides every recorded number (src/lib/format/recorded-number):
a stored value keeps every digit it holds, minus binary float noise, and a
value converted from the unit its report printed keeps the precision of the
printed figure. The report payload carries sourceValue for that. The labs
page, its chart and history, the PDF lab table and footnote, the share
page, the day view and the OCR review all print through it, reference
bounds included. Doses on the PDF and the share page follow the same rule.
Measurement statistics in the PDF and on the share page now share one
per-row precision: up to three significant digits but never more decimals
than recorded, so a waist-to-height ratio of 0.52 no longer reads 0.5;
glucose keeps its unit's precision.
Refs #1228
A recurring bout filed under its condition nests inside that condition's
card, but the list view drew no nested episodes, so an ended recurring
bout of an ended condition had no row anywhere and was found only through
search. A bout nested under an already nested parent was lost in either
view, and the page read only the list route's first page of 50, so the
oldest episodes dropped off the page, its settings and every picker.
Grouping moves into groupEpisodes: each episode nests under the highest
ancestor that shares its status, or is a card of its own, and a looping
parent chain leaves every member a card. Both views render the nested
episodes with their course and end date; the count heading says "flares"
only when they all are. useIllnessEpisodes reads page after page until a
short one.
Refs #1224
- Composition amounts show only the decimals they carry, so 100 µg no
longer reads as 100.000 µg; the remove button stays on the amount row.
- AUDIT-C remove controls are icon-only, so they no longer spill out of
the card or cover the earlier answer.
- Life-circumstance labels keep their colon when they wrap, checkbox
groups sit in two columns and the weekly-minutes sentence ends.
- Link chips get a 44 px hit area for their remove control.
- The AI usage level reports a failed save with a sentence and the
error id instead of a bare "Error".
- The Dawarich home-country and admin refresh-gap labels sit above
their fields.
zod 4.6 grew the shared Zod chunk from 63 to 89 KB gz (the new schema
compiler and its memoizer ride along because every schema goes through
the `z` namespace), and the dashboard, measurements, mood and settings
routes each carried it for values that need no validation.
Move the measurement source list, the notes limit, the screening check
and the Coach source list into value-only modules the schemas are built
from, import the settings classifier and response check from their
leaves, and load the gate's managed settings and anamnesis sections on
demand. Re-base the four route baselines to the measured values and the
aggregate cap to 4975, with the reason in the budget comment.
The rebuild after apply now writes its state beside the dry run
(queued, running, done with counts, failed); a run left queued or
running without a job reads as failed. The card shows it and offers a
new rebuild after a failure, which the route accepts once the method is
applied, without a new dry run.
The stored language is the wording the read route offers the account;
a request naming another wording, or a takenAt more than five minutes
ahead, is refused. Latest, offerDue, the Coach block and the doctor
report all read the newest entry whose answers open.
The old one-word alcohol pattern gives way to an AUDIT-C entry in the
Coach block, and the alcohol section lists it with a remove action.