On a per-minute heart-rate stream the 7-day chart showed only the first
half of the week. The range read raw rows oldest-first under a
5000-row limit, and seven days of per-minute samples are 10 080 rows,
so the newest days never arrived.
The chart folds every range into days anyway, so the 7-day range now
asks for one row per local day like the longer ranges. The whole week
arrives whatever the sampling rate, and the 7-day and 30-day ranges
share one day boundary and one source pick per day. The 7-day range
still draws no spread band, so its picture is unchanged.
Refs #1026
The Verlauf ledger dropped doses the full history showed. Three windows
ended too early or started too late:
- The server clamped the window floor to the medication's creation
instant and filtered recorded doses by the same floor, so a dose
logged for earlier the same day on a medication added afterwards
fell out of "Last 90 days". The creation floor now bounds minted
slots only; recorded doses use the requested window.
- A dose taken inside its window before the slot's own time is stored
on the slot anchor, which still lay past the window end. The read
horizon now reaches the latest recorded anchor within a day, without
minting later upcoming slots.
- The client pinned from/to at mount and reused them on every refetch,
so a dose added after the tab opened stayed hidden until a reload.
The window is now resolved when the request fires and only "from"
travels; the query key carries the window length instead of instants.
Refs #1028
The tab strip already hides the Mood pill when the mood module is off, but
the trends row still charted mood, both from a briefing finding and in its
blood pressure / weight / mood fallback. selectTrendCharts now takes the
metrics to leave out, and the overview passes mood when the module is off.
Refs #975
The webhook, ntfy, email and Telegram test routes send through the
saved channel config. After a field changed, the card kept showing the
Saved note from the last save and Test still ran, so it reported on the
old settings: a webhook switched to the Gotify format was tested with
the generic body and Gotify refused it. The note now disappears on a
change, and the test button stays off with a save-first hint while the
form differs from what is saved. Refs #947
The admin test response carried a code and status per channel that nothing rendered. The toast now lists each failing channel with the translated cause and the HTTP status or SMTP code. The relay reply is no longer part of that response, and the ntfy error no longer echoes the topic, which the sender treats as a secret.
Refs #947
The fetch and envelope reading move into runConnectionTest, which the button calls. A test stubs the fetch with the 502 envelopes the notification test routes send and asserts the rendered code, HTTP status, SMTP code and relay reply, so dropping the meta extraction fails it.
Refs #947
The anamnesis page writes through requireRecordAuth("manage",
"profile"), which refuses a grant whose sections do not reach profile.
The Settings list and the navigation entry checked only the level and
the record kind, so a MANAGE grant scoped away from profile would be
offered a page whose saves are refused.
Record-content destinations now name the section they write, and are
listed only when the active record's manageable sections include it.
A destination without a named section is never listed. The shell and
the navigation read the same predicate. MANAGE grants are whole-record
today, so managed profiles and adult MANAGE shares list what they did
before.
Refs #939
Gotify's POST /message types priority as an integer, so the generic body, which sends the word, was refused with a 400 before the token was ever the question. The webhook card now offers a payload format: generic, which stays byte for byte what it was, and gotify, which sends title, message, an integer priority and extras.
Priorities follow the Gotify Android client's channel thresholds: 5 for routine events (audible), 8 for medication reminders (the lowest heads-up value), 10 for urgent events. The extras declare plain text and carry the event name, masked to reminder in discreet mode as the generic body does.
The choice lives in the encrypted channel config, so no migration: an absent format is generic, only gotify is written, and a save that omits the field keeps the stored choice. GET returns the resolved value. The sender comment no longer suggests an Authorization Bearer header for Gotify, which takes X-Gotify-Key or a token query parameter.
Refs #947
The test-connection callout now appends the HTTP status or SMTP reply code the route reports, and shows what the relay answered on a line beneath it. The reply is rendered as a React text child, so markup in it is shown as characters.
Adds the upstream_rejected message in all seven locales.
Refs #947
The More hub carries feature destinations only; Settings and
Notifications sit in the top-bar menu. The browser-only refusal for a
manage invitation comes from the transport check, which runs before and
independently of the second-factor step-up.
A self-hoster looking after a managed profile could not find Settings
after switching into it: the utility entries vanished for every shared
record, while the Settings shell lists the profile's own configuration
and the section gate admits it.
The utility tail now answers for the record on screen. Settings is
offered when the shell lists at least one section for it and links to
the first one: the profile card for a managed profile, the anamnesis
page for an adult share at manage. Read and write shares, and a
refused or pending switch, get no utility. Notifications stays hidden
under every switch. The desktop sidebar footer and the mobile user
menu read the same list; the footer identity block stays a plain
block.
Refs #939
Move the rule that decides which Settings sections a shared record
lists out of the settings shell and into the record-settings
classification module, next to the inventory it reads. The shell
filters through it unchanged; the navigation reads the same answer
in the next change, so the two cannot disagree about whether a record
has Settings pages and where they start.
Refs #939
Background insights can no longer use up the day before somebody asks a question; a fifth of the daily limit is held back for what a person does by hand.
The connect step's new attention and credentials tiles link to the
integrations page with a label that names a path — "Open Settings →
Integrations". The button base is `whitespace-nowrap` with a fixed height and
`shrink-0`, so at 390 px the German and French renderings could neither shrink
nor wrap: the control ran 27 px past the viewport. The pre-existing connect
card was unaffected, its label being a short verb phrase; the path label and
the tiles that carry it are new, which is why the sweep passed before.
The CTA is bounded to its card, allowed to break between the words of the
path, and allowed to grow for the second line, with the 44 px tap target kept.
No copy moved, so the seven catalogues stay as they are.
CLAUDE.md forbids planning shorthand in committed artefacts, and this branch
introduced 74 lines of it across source, tests, a schema field and two
migrations. Someone reading the migration SQL in a year has no way to resolve a
wave letter or a round number, and a comment that points at something outside
the repository names no reason at all.
Three passes. A version followed by a parenthetical keeps the version and loses
the parenthetical, because the version says since when something holds. A
sentence that leaned on the vocabulary to carry its meaning is rewritten to
state the reason directly: the arrival reservation now says it was refused over
money the operator never spent, rather than naming the defect a wave existed to
remove. Three test titles carried the marker in the name itself and are renamed
to what they pin.
Comments only. No behaviour, no renames beyond those three titles.
Allowlisting `PUT /api/auth/profile` so the setup flow's baseline step could
finish also handed every visitor the shared demo account's contact email, its
display name, its full name and its insurer fields: the allowlist admits a
path and a method, and every field the handler behind it writes comes with
them. The justification beside the entry named three fields; the entry
admitted eleven.
The write is narrowed on the server, in `applyProfileUpdate`, to height, date
of birth and sex while `DEMO_MODE` is set — not in the client, which is the
party the allowlist exists to distrust. It happens ahead of the email conflict
check, which would otherwise answer "is this address registered here?" to
anybody who can open the demo. The display-name box goes read-only there, as
the anamnesis card already is, so nothing accepts input it will discard.
The entry's comment now lists what the handler writes and what is admitted.
The consent branch of the done screen's AI panel painted "insights work for
you right now" on the strength of the receipt alone, reading no health at all.
A person who had consented — including one who had just tapped the offer — was
told a dead provider was serving them. That is the claim-nobody-measured
defect closed at the offer end of the same panel, still open at the other.
The positive sentence now needs a healthy verdict as well. A consented account
on a provider that failed reads the same "not answering" note as anybody else,
and on an unmeasured one the panel claims nothing. The panel's four variants
are pinned across all three health states in every arm.
The panel paints before its status query answers, and every variant inside it
is conditional, so an assertion that a variant is absent could pass on a
question that had simply not been answered yet — which is how the journey for
the unmeasured-provider case passed on a retry against the very defect it was
written to catch.
The panel now carries the variant it settled on, absent until it has settled,
and the three journeys wait on that instead of racing it. The third journey
covers the state a freshly deployed instance is in before its first AI call:
no offer, and no claim of an outage either.
The provider-health route answered a spendToday payload that nothing rendered,
which is the pattern the two-ended rule exists to stop. It is also the screen
the operator opens when a surface refuses on budget, and the one mixed number
could not answer the question that raises: a day at 1.24 million tokens is
alarming until the card says that 151 200 of them were the instance's own key
and the rest ran on the users' own plans. The operator ceiling is enforced
against the second figure. Copy lands in all seven locales.
Thirty-one files carried internal build vocabulary and anchors pointing at
notes that are not in the repository: a reader of `checklist.ts` in a year
would find its rationale keyed to a research id they cannot open, inside a
comment that also names an internal build stage. Two test titles carried the
same anchors.
The version tags and the whole of the reasoning stay; only the vocabulary and
the dangling references go, with the few sentences that were built around an
anchor rewritten to stand on their own.
The demo allowlist had grown two entries it should not carry. The demo is one
published account every visitor signs into, so `PUT /api/coach/about-me` was
an anonymous write of arbitrary text into the Coach prompt, persisted for
every later visitor until the next reseed; it is gone, and the baseline step's
anamnesis card renders read-only under `DEMO_MODE` so no visitor meets the
refusal as a failed confirm. `POST /api/managed-profiles` bought nothing: the
route opens with a fresh-MFA requirement the demo account cannot satisfy, so
the guardian arm was limited to finishing without the profile either way.
The baseline profile write stays, and the block's preamble now describes what
is actually admitted and why the two refusals are refusals.
The confirm screen derived "Out of the navigation: …" from the acting
account's module map and rendered it in every arm. In the "someone I look
after" arm the completion never touches that account: it applies the
derivation to the managed record, or, when none was created, to nothing at
all. So a parent answering for a child was told Workouts, Cycle and Documents
were leaving their own navigation when nothing was going to move.
The arm now derives the list from an empty map, which makes it empty, and the
sentence disappears with it.
The done screen folded the `unknown` provider verdict into `unhealthy` and
painted "This instance's provider is not answering right now." `unknown` is
an empty `provider_health` table, which is the state of every instance until
its first AI call — so the most common path through the screen made a claim
about something nobody had measured. It now falls to the neutral panel, which
says nothing either way; the offer is withheld in both cases as before.
The projection itself drew the same unearned conclusion from staleness alone:
a quiet instance whose last success was two days old read `unhealthy` without
any failure behind it. A verdict now needs an observed one — a row whose last
outcome was a failure — and a merely old success reads `unknown`.
The done screen's assistant panel stops claiming that the instance's
provider already serves the person. A tri-state read over the operator's
own provider ledger decides between a one-tap offer that mints the
consent receipt, a plain "not answering right now" note, and today's
setup link; anything unknown, any read error, a managed profile and an
operator who has switched the assistant off all fall to no offer.
# Conflicts:
# e2e/setup/global-setup.ts
A provider with no sync history reports "unknown" instead of "connected",
so a fresh account is no longer told a device it never linked is already
delivering; the connect step resolves the sync verdict into four honest
tiles, records the flow's result only for a connection that is actually
serving, points an unconfigured provider at its credentials, and steps
over a source the account already has.
The getting-started checklist's data queries follow the same setup rule as
its visibility, so a settled account stops being told to do what it has
done; a failed completion leaves the confirm screen usable; an empty
multi-select is no longer recorded as an answer; the confirm screen names
the modules a run would take out of the navigation; the settings links it
offers work before the first completion; and demo mode can fill the
baseline it is shown.
Every layer of the invite chain had unit coverage and the chain was broken
anyway: the proxy test proved the edge admits the landing, the page test proved
it redirects onto the registration form, and neither could see that the
redirect arrives in an RSC flight payload after the shell has already flushed.
The operator's path — mint an invitation, open the URL in a private window,
reach a usable form — was exercised by no spec at any level.
The new spec mints a real invitation in the admin UI and opens it twice: once
in a genuinely cookie-less context, asserting both where the visitor lands and
that they never passed through the sign-in page, and once in the admin's own
window, asserting the explaining panel and that the admin's session survives. A
third case pins the server half through the wire. Against `origin/main` all
three fail — the anonymous visitor lands on `/auth/login`, the panel does not
exist, and the registration POST answers 201 over a live session.
The minted URL gains a test id so the spec reads it from its own element
rather than from the first paragraph in the ticket.
Both Settings links on the confirm screen are page routes, and the
onboarding cookie that sends every page route back into the flow is
cleared by the completion this very screen calls. On a first run they
could not go anywhere.
They now render as plain text until the flow has been completed once.
The words stay, because where a setting lives is true either way.
The screen listed what the answers switch on and said everything else
stays one click away under Settings, Modules. That is true of
reachability and false of the navigation entry: a module the answers do
not ask for is written off by the completion's derivation.
It now names those modules, read through the registry's own merge rule
and intersected with the map the navigation shows today, so nothing is
claimed about a module the account payload says nothing about.
`chooseFirstResultTask` read the answers alone, so an account whose WHOOP had
been feeding the dashboard for months was sent to go and connect WHOOP. It
takes the delivering sources now; the server still picks from the answers,
having no status in hand, and the screen re-picks once the envelope resolves.
The fall-through stops where there is nowhere to fall to: a person whose one
answer is an already-connected source keeps the step and is told what was
found, rather than ending the flow on no result at all.
A sharing invitation carries no link and nothing is e-mailed — it is addressed
to an account that already exists on this instance, and the person accepts it
from inside their own session. The card's input hint already named the account
requirement and left the rest unsaid, so an inviter looking for a link to send
had nothing to find and no reason to stop looking. The card now names where the
invitation turns up, in all seven locales.
The step decided "connected" from the ledger's `state` and had one piece of
copy for every outcome: "the first sync is in progress". That sentence was
false for a connection delivering for months, and worse for one the person
has to repair — the screen said everything was fine while the grant was gone.
`connectSourceView` resolves the envelope entry into one of four tiles and
says whether the flow may record its result. Only `pending_first_sync` and
`fresh` may; `stale` is acknowledged without being claimed as new; `stalled`,
`failing`, `reauth_required` and `parked` name the state and link at the page
that can change it. An unresolved envelope paints no tile at all.
A source the instance cannot connect — a BYO provider with no client pair, a
shared-app provider with no app — now points at the credentials instead of
offering a "Connect" button onto a card that has none.
Q2, Q4 and Q6 let "Weiter" write an answer with nothing ticked. On Q2
that answered "no areas", which is what switched every optional module
off for somebody whose click meant "no preference"; on Q6 it marked the
step done while recording no unit at all.
An empty selection now yields no body, so the primary button is dead
until something is chosen. Skip stays the way past, and Skip is the
conservative derivation the completion route already applies.
The card read one field and made two claims from it: that insights work,
and that no setup is needed. The first was a presence read - it knew a key
existed, never that it answered - and the second skipped the consent
receipt the egress actually needs, so the person who believed the note met
a consent.ai.required refusal on their first briefing.
Four branches now, one button. The offer appears only when the server says
the shared provider is known to work and nothing forbids it, and the tap
posts the affirmative web intent. An unhealthy or unknown verdict paints
'not answering' with no button. When the offer is refused for a reason that
is not health, the panel stays neutral rather than blaming the provider.
The note survives for the one state it was ever true of: consent on file.
The sample, the ladder, the 'fully useful without' line and the setup link
are identical in every branch. Copy in all seven locales.
The baseline form raised its pending flag and lowered it on only two of
three exits; the success path went through the confirm screen's finish(),
which caught its own error and returned normally. A failed completion
therefore disabled both buttons until the page was reloaded.
The reset now lives in a finally, finish() rethrows so the form sees the
failure, and the two managed-arm buttons report it themselves. The step
passes before the completion run off a ledger ref, so a retry after a
failure midway no longer re-sends a step the ledger already carries.
The ledger's synthetic "no row" snapshot returned `state: "connected"`, so
the field's default meant the opposite of its name: every account that had
never connected a provider published `connected` on the status envelope.
Settings survived it by never reading `state`; the setup flow's connect step
did read it, and told brand-new accounts their wearable was connected.
The snapshot now reads `unknown`. `syncHealth.verdict` is unchanged and stays
the only liveness truth; the managed-record projection maps `unknown` onto
`disconnected` the way it already maps a missing connection.
`/invite/<token>` has been public in `proxy.ts` since v1.17.0 and was never
added to the client-side gate in `auth-shell.tsx`. Next streams the shell
before the landing page's RSC redirect resolves, so the shell classified the
route as protected, `/api/auth/me` answered 401, and `router.replace(
"/auth/login")` won the race — the operator's invite link landed on the sign-in
page it was meant to bypass.
The shell now lists `/invite/`, and lists the `/auth/` family as the prefix the
edge already admits rather than as two individual pages, so a future auth page
cannot inherit the same defect. `PUBLIC_PATHS` is exported from `proxy.ts` so a
new parameterised guard can render the shell at every page prefix the edge lets
through and fail when the two lists drift apart again.
The card's visibility rule gained a "ran the setup flow" disjunct; the
component's query gate kept restating the older half of it. A wizard-run
account past five readings therefore saw a permanent checklist whose
medication, data-source, notification and insight rows were frozen at
"not done", because every supporting query was disabled.
Both places now read one exported predicate, isStillInSetup, which also
keeps the rows live for a restarted run nobody finished.
The string-headroom sweep went red on CI for French at phone width by
two pixels the Mac never showed: the runner's fallback fonts render a
touch wider. The box was the sex select on the confirm screen — its
value is whitespace-nowrap, and a grid item's automatic minimum is its
min-content, so the track was pinned at the padded option's width and
the shell scrolled sideways. Both cells of that row and the trigger
carry min-w-0 now: the track takes the free space and the value
ellipsises, which is the select primitive's own design. Reproduced and
verified locally by narrowing the viewport to 372 px, which stands in
for the wider fonts (336 px of scroll before, none after).
The sweep now names what overflowed: the boxes past the shell's edge,
with tag, slot, classes, text prefix and edges, ride the failure
message and the outside list, so the next red run says what rather
than only that.
"Finish without the profile" completed the flow with no record id, and
the route then derived the child's answers onto the guardian's own
record. The server now decides: answers given for somebody else with no
record to apply them to complete and latch the flow without deriving or
seeding anything, whatever the client sent. The checklist carries the
profile instead and reads none of those answers as the guardian's own.
A record id the caller does not guard answers 404 like every
id-addressed managed-profile route, one answer for missing, not managed
and not theirs alike; the contract documents the field, the 404 and the
422 text. Pinned by two integration cases and a second child journey.
A loopback, link-local or metadata target answered
private_origin_not_approved with a message telling the user to have the
operator list it, and the parser refuses such an entry forever: a dead
end the route test even pinned. Those targets now answer
private_origin_not_grantable (catalogued, on all four routes, translated
in seven locales) with a message that says no grant can open them and
to use the relay's LAN address. Both senders carry the reason into the
outcome so the test button forwards the same code.
The malformed-entry warning printed the entry verbatim, which is where
a pasted token or userinfo sits; the parser now hands callers scheme
and host only, plus the reason in words. The Nightscout list throws a
named error with that redacted entry and reason, and the connect route
answers it as a 422 with the message instead of a bare 500, so a
self-host that listed http://localhost:1337 reads what to change.
The two settings cards translate a coded save refusal like the test
button does; the docs and the changelog name the sub-host and sibling-
port narrowing; the dispatcher floor tests assert the policy marker so
a real NXDOMAIN cannot pass for a refusal.
Refs #947
"Someone I look after" derived the child's answers onto the guardian's
own record. The completion now takes the managed record the profile
step just created, checks the same access rule the guardian's modules
route applies, and lands the same derivation and dashboard seed on that
record through the same record-keyed write; the guardian's own flow is
stamped complete and latched without touching its map. Three
integration cases pin it, and the child journey reads the child's map
through a real switch — it asserted the guardian's map before, which
pinned the wrong record.
Also from the review: the checklist's visit row read a meta the visits
list never publishes and now counts its body; the first-result tile
read the measurements list as an array and now shows the reading; the
demo allowlist admits the disclaimer and tour writes, with a structural
test over every onboarding route file; restart clears the old first
result, and the screen trusts the ledger before painting a result as
done; the headroom sweep's raw-key check matches the flow's own key
shapes; the demo migration script stops copying the dropped column; the
questions-to-pass rule and the shell's onboarding-path rule are pure and
pinned; the baseline form composes the shared action row; the schema
diff loses the unrelated formatting churn.
At 390 px with labels a third longer the action row pushed "Confirm and
continue" past the viewport; both rows now wrap. The string-headroom
sweep lengthens every word by 30% rather than hanging the whole surplus
on the last one — that manufactured an unbreakable run no locale
produces — leaves screen-reader-only boxes alone, and accepts a select
value's ellipsis, which is the primitive's design app-wide.