9961 Commits
Author SHA1 Message Date
Marc-André Bombeck 0310172095 Merge pull request #1374 from MBombeck/release/v1.47.4 v1.47.4 2026-11-12 15:46:07 +01:00
Marc-André Bombeck 80ee228899 fix(pwa): clear the leftover app badge from the page, not the worker
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.
2026-11-12 15:28:37 +01:00
Marc-André Bombeck 45a5d4e353 fix(pwa): never let the badge call hold up the worker's activation
The new worker cleared a leftover app badge inside its activation and
waited for it. A browser that never settles that call keeps the worker
activating, so it never takes over its pages and every offline path is
gone. The badge is now cleared beside the activation, best effort.

The sign-in journey expects the page's own sentence for a wrong
password, not the server's text.
2026-11-12 15:02:11 +01:00
Marc-André Bombeck fbb3ad7852 chore(release): v1.47.4 2026-11-12 14:24:36 +01:00
Marc-André Bombeck 02f4f8116c Merge remote-tracking branch 'origin/main' into release/v1.47.4 2026-11-12 14:23:50 +01:00
Marc-André Bombeck 62988f95ff Merge branch 'fix/v1474-review' into release/v1.47.4 2026-11-12 14:23:49 +01:00
Marc-André Bombeck ae005f020b refactor(auth): navigate for single sign-on at the call sites again
Moving the navigation into the shared helper took both sign-in hand-offs
out of what the headerless-transport guard walks, which only reads the
component and page trees. The helper now only clears the offline copies
with the capped wait, and each page navigates itself. The device
registration test checks the end of the no-device rest inside the test
that already pairs an iPhone instead of a copy of it.
2026-11-12 14:19:47 +01:00
Marc-André Bombeck f78c9b0b78 chore: restore the Telegram handler's size 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.
2026-11-12 14:16:17 +01:00
Marc-André Bombeck a880f764fc chore: keep the handlers this release touched within their size baseline
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.
2026-11-12 14:16:06 +01:00
Marc-André Bombeck d8c270859b refactor(auth): leave for single sign-on through one helper
The sign-in page and the profile take-over page each cleared the offline
copies with the same capped wait before leaving for the identity
provider, with the limit written down twice. leaveForSingleSignOn does
it once for both.
2026-11-12 14:15:55 +01:00
Marc-André Bombeck 4ff3e4e1b7 fix(notifications): rest a channel that had nobody to send to
A send with no browser subscribed, no iPhone paired or a transport the
operator has not set up no longer counts as a failure, but nothing else
replaced it. The mood reminder and the daily briefing retry every
minute until delivered, so such a channel wrote a skipped row each
minute, for good; the dispatch still counted as an attempt, so the
watchdog and status alerts took it as announced; and the status screen
said "active" with no hint why nothing arrived.

The channel now rests for an hour after such a send, with the reason on
its status, and the send does not count as an attempt. Subscribing a
browser or pairing an iPhone ends the rest at once.
2026-11-12 14:12:45 +01:00
Marc-André Bombeck 5d1c3ffbaf Merge pull request #1373 from MBombeck/release/v1.47.3 v1.47.3 2026-11-12 14:11:26 +01:00
Marc-André Bombeck 95e2ba5477 test(e2e): read the dashboard snapshot inside a record, not only the caches
The record fence journey called the page cache its positive control,
but signed-in pages are never stored, so the entry it counted was the
install precache, and the half inside the record repeated a claim that
holds by construction now that the worker keeps no API answers. The
comment now names the precache, and inside the record the journey reads
the IndexedDB dashboard snapshot, which must be missing or still the
caller's own.
2026-11-12 14:10:32 +01:00
Marc-André Bombeck 5a274b8792 fix(push): give the subscription removal a five-second limit
Sign-out removes the push row first, through the raw fetch helper, which
has no default timeout. On a connection that is up but carries nothing,
the sign-out waited for the browser's own timeout before the logout
request even started. The removal now gives up after five seconds.
2026-11-12 14:09:36 +01:00
Marc-André Bombeck e498a44d70 chore: correct comments that describe the removed clear push and API cache
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.
2026-11-12 14:09:18 +01:00
Marc-André Bombeck d15fb27bce docs: list Russian among the README's languages
The line also lost its dash.
2026-11-12 14:08:35 +01:00
Marc-André Bombeck 95a906b2a7 fix(pwa): keep the status bar readable on the sign-in pages too
The light-theme fix painted the strip behind the white status bar only
in the signed-in shell. The sign-in, registration and take-over pages
and the onboarding shell padded the strip without painting it, so after
a sign-out in the installed iOS app the clock was white on near-white.
They now use the same shell-safe-area band.
2026-11-12 14:08:34 +01:00
Marc-André Bombeck e59ef4533d fix(push): say why a test push failed, under its own title
The Web Push test answered a refused push with `ok: false` and the
sender's `reason`, but the Test button reads `meta.errorCode`, so it
only said "Test failed". The answer now carries `upstream_rejected`, or
`connection_failed` when no push service answered.

The test push used the administrator's test title, which put the brand
first in several languages; it has its own title now.
2026-11-12 14:07:19 +01:00
Marc-André Bombeck d5980bdaee fix(i18n): tidy the German and Spanish sign-out failure
The German sentence dropped its article, and the Spanish one read as if
the sign-out had been the session's own state ("Tu sesión sigue
abierta"); it now says the person is still signed in.
2026-11-12 14:05:54 +01:00
Marc-André Bombeck 7c3988865d fix(push): name the real button in the Home Screen hint
The hint for iPhone and iPad Safari ended with "turn on push here", but
the button reads "Enable push"; it now names the button in every
language. The hint is two sentences of steps, so it is shown as content
rather than muted, and the French one uses the typographic apostrophe
like its neighbours.
2026-11-12 14:05:37 +01:00
Marc-André Bombeck fd92e9fd23 fix(i18n): say "back online" once in the offline strip
The offline strip joins two sentences, and both ended on "once you're
back online"; in French it ran to four lines on a phone, and the
Russian one was ungrammatical. The first sentence now only says that
changes can't be saved.
2026-11-12 14:05:13 +01:00
Marc-André Bombeck b617c1f88e fix(shell): stop restoring the list's offset once the person scrolls
On Back, the shell set the list's saved offset again on every frame
while the list was still shorter than that offset. Someone who started
scrolling in that time was pulled back, and a list whose data took more
than a second ended up where the skeleton allowed. The restore now stops
at the first wheel, touch, key or pointer input and keeps trying for up
to three seconds.
2026-11-12 14:04:31 +01:00
Marc-André Bombeck 12a23bf779 fix(auth): leave for single sign-on even when the cache wipe hangs
The sign-in page and the profile take-over page waited for the offline
copies to be cleared before leaving for the identity provider, with no
limit. WebKit can leave an IndexedDB open unsettled after the installed
app returns from the background, and the button then did nothing. The
wait is now capped at one and a half seconds.
2026-11-12 14:03:46 +01:00
Marc-André Bombeck 444e1eaca8 fix(auth): replace the page with the sign-in page on sign-out
The menu's sign-out pushed the sign-in page onto the history. Back then
reopened the page just signed out of, and the edge sent the next person
to the sign-in page with that page as `next`, so their first sign-in on
the device landed where the last person had been. The sign-out now
replaces the page.
2026-11-12 14:02:30 +01:00
Marc-André Bombeck 510ce3128d fix(auth): end the session only on HealthLog's own 401 or 403
A refetch of the account that answered 401 or 403 now signs the person
out and clears the offline copies, which is right for HealthLog's own
answer. But a self-hosted instance behind a forward-auth proxy or a
firewall can get a 401 or 403 that HealthLog never wrote, and that
signed a person out whose session was fine.

HealthLog's error envelope always names a code, so only a 401 or 403
that carries one is read as the session's end; one without is treated
like a network failure and keeps the last known state. The tests now
give the account route the answer the server really sends.
2026-11-12 14:02:13 +01:00
Marc-André Bombeck 37734c9fd8 fix(auth): say why a registration or passkey sign-in was refused
Once the sign-in and registration pages read codes instead of the
server's prose, a taken username or address said only "Registration
failed", an expired invitation said "You don't have permission for
this", and a passkey this server does not know, a passkey sign-in that
took too long, an unreadable passkey answer, a single-sign-on-only
server and an already signed-in visitor all said "Login failed".

The registration route now names its refusals (auth.register.taken for
both 409s, auth.register.invite_invalid for a refused invitation), and
those codes, the three passkey refusals, oidc_only and
auth.already_authenticated get a sentence in every language.
2026-11-12 14:00:18 +01:00
Marc-André Bombeck 94ea48d723 fix(push): make a new subscription deliver for its account at once
Subscribing re-enabled a disabled channel but left the failure counter
and cooldown on one that was still enabled. Failures counted before the
new subscription (no browser left, a push service outage) kept every
reminder skipped until the cooldown ran out, and one more transient
failure switched the channel off. Subscribing now resets them too.

A browser also kept the row of an account whose session ended without a
sign-out, so the next person on it saw that account's reminders. An
endpoint is one browser: subscribing now removes other accounts' rows
for it, and the settings card, which posts its subscription on open,
claims the browser for whoever uses it now.
2026-11-12 13:57:36 +01:00
Marc-André Bombeck 38299f7c6e fix(pwa): clear the dose count earlier releases left on the app icon
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.
2026-11-12 13:56:33 +01:00
Marc-André Bombeck 1dde3034c6 fix(auth): say a refused sign-in in the sign-in page's own words
The sign-in refusals carry only their status's generic code, so once the
sign-in page read codes, a wrong password, a wrong second-factor code
and a refused security key all said "Your session has ended", and a
lockout said "too many requests just now".

localizedSignInError reads a generic 401 as the step's own failure and a
generic 429 as "Too many attempts. Wait a few minutes and try again.";
a code the step names itself keeps its sentence. The password, passkey,
code and security-key steps use it. The rate-limit sentence moves from
auth.claim to auth.signInRateLimited, since the claim page and the
sign-in page now share it, and the Korean one takes the polite form the
rest of the catalogue uses.
2026-11-12 13:54:34 +01:00
Marc-André Bombeck 367ba65bf6 fix(auth): take the push subscription off on every sign-out button
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.
2026-11-12 13:51:44 +01:00
Marc-André Bombeck 6914f844be fix(push): send the browser's subscription again when the card opens
The Web Push card reads its state from the browser only. When the server
had lost the row, or the channel had been switched off, the card said
enabled and no reminder came. Opening the card now posts the subscription
the browser holds, which stores the row and turns the channel back on.
A refused post (demo, push off instance-wide) changes nothing on screen.
2026-11-12 13:48:52 +01:00
Marc-André Bombeck d5c0e759b8 fix(push): turn Web Push back on for accounts it was switched off for
Before this release, a send with no browser subscription counted as a
failure, and five of them, or one 410, switched the WEB_PUSH channel off.
Subscribing again stored the browser but left the channel off, so the
card read "enabled", a test push arrived, and no reminder was ever sent.
Subscribing now re-enables the channel, but nothing prompts the people
already caught to subscribe again.

The migration re-enables every WEB_PUSH channel the dispatcher switched
off (a disabled_reason is set; a channel switched off by hand has none)
whose account still has a subscription, and clears the counter, the
cooldown and the last failure the same way reEnableChannel does.
2026-11-12 13:48:51 +01:00
Marc-André Bombeck 9dde803efb Revert "fix(settings): list the Coach entry only while the Coach can answer"
This reverts commit 0d41abcda. With the entry hidden, Settings, AI showed
the stored Coach memory instead, and that card reads the conversation
list: an account without any AI provider then made a Coach request on a
page that should make none. Settings lists the Coach while its module is
on, as before.
2026-11-12 13:48:16 +01:00
Marc-André Bombeck 6004b0c98d Revert "fix(settings): keep the Coach entry for a managed record"
This reverts commit 5b8543f471.
2026-11-12 13:48:08 +01:00
Marc-André Bombeck a1591c4ea0 fix(i18n): keep one apiErrors.http block per locale
Two branches each added the block, and the merge kept both. A JSON parser keeps the last one, so the sign-in sentence for a rate limit was dead text.
2026-11-12 13:42:34 +01:00
Marc-André Bombeck d3cec13f98 docs(api): regenerate the contract
The test push answers with a reason instead of per-endpoint counts.
2026-11-12 13:29:02 +01:00
Marc-André Bombeck 031379d966 Merge branch 'release/v1.47.3' into release/v1.47.4
# Conflicts:
#	src/__tests__/baselines/source-file-lines.json
#	src/__tests__/baselines/source-function-lines.json
#	src/__tests__/baselines/test-as-never.json
#	src/__tests__/baselines/version-stamp-comments.json
2026-11-12 13:27:52 +01:00
Marc-André Bombeck 9b4b5a1255 chore(release): v1.47.3 2026-11-12 13:09:39 +01:00
Marc-André Bombeck 8974158c58 Merge branch 'fix/v1473-review' into release/v1.47.3 2026-11-12 13:08:58 +01:00
Marc-André Bombeck bde455e3be docs(api): regenerate the contract
Carries the targets read at read level, the two new error codes
(`customMetric.name_taken`, `auth.mfa.already_active`) and the
notification categories' description without its em dash.
2026-11-12 13:06:18 +01:00
Marc-André Bombeck 5b8543f471 fix(settings): keep the Coach entry for a managed record
The Settings Coach entry now follows whether the Coach can answer, as
the sidebar does, with Settings, AI keeping the stored memory in reach.
A managed record has no Settings, AI, so while its Coach could not
answer yet (no operator key, no consent) the guardian lost the only way
to the record's Coach data scope and its switch. For a managed record
the entry follows the module switch alone again.
2026-11-12 13:05:37 +01:00
Marc-André Bombeck 8b6d9826ea chore: drop stale references to the onboarding offer and old resolver
The Playwright project list still named the deleted onboarding AI
offer spec. The CoachPlan comments said the review writes
`outcomeEncrypted` and surfaces it behind a flag; nothing writes or
reads it now, and the column is marked internal. Comments that named
`resolveProvider` as the resolver now name the chain, and the function
itself says it is test-only.
2026-11-12 13:03:58 +01:00
Marc-André Bombeck 2af0751b69 test: type the new fixtures instead of casting them to never
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.
2026-11-12 13:03:51 +01:00
Marc-André Bombeck 315c89d229 fix(targets): classify a weight with the height its band was built from
The child-band change left the classification dividing by the record's
height where it had checked the adult-band height, which the type checker
refused. Both now read the same value.
2026-11-12 13:03:51 +01:00
Marc-André Bombeck 8c9fbbdd2b fix(errors): name three refusals that fell back to a generic sentence
Since a codeless refusal reads by its status, three answers lost their
meaning. A failed ChatGPT sign-in poll said "Error saving" instead of
asking to start the sign-in again; it now falls back to the sign-in's
own failure sentence. A dose-history import still running when the
card stopped watching said "Nothing was written" although the job may
still write; that case now has its own sentence. Setting up an
authenticator while one is active answered "setup failed"; the route
now sends `auth.mfa.already_active`, which says to disable the active
one first, in every locale.
2026-11-12 12:58:52 +01:00
Marc-André Bombeck 31c4440154 refactor(custom-metrics): read a refused save in one helper
Matching the name clash by its code grew the form component past its
size baseline. The refusal's sentence now comes from a helper beside it.
2026-11-12 12:58:37 +01:00
Marc-André Bombeck e71b1b9fd4 fix(coach): leave no plan result to invent once a plan is reviewed
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.
2026-11-12 12:55:15 +01:00
Marc-André Bombeck 2d1009136a chore: drop the em dashes this release added to prose and test titles
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.
2026-11-12 12:54:20 +01:00
Marc-André Bombeck cad4c89831 test: mark the visit, dose and address-book deletes restorable
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.
2026-11-12 12:53:36 +01:00
Marc-André Bombeck 55b223d4ed fix(onboarding): show the closing AI line as body copy
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.
2026-11-12 12:53:12 +01:00