The mobile smoke test pressed Escape on the capture picker and tapped
the More button straight away. While the picker's close animation still
covers the bottom nav, that tap lands on the overlay and the hub never
opens, which failed the lucide-react update once. The test now waits
until the picker is hidden.
The malformed-payload journey installs its /api/auth/me route before it
clicks the switch. When the owner session read its own payload again in
that window, the helper threw inside the route handler: the throw came
before fulfill, so its catch never saw it, the test failed, and the
teardown's unroute then hit a closed page. It showed up on the Playwright
1.63 branch, whose timing opens that window more often. An unswitched
payload now passes through unchanged, and the test asserts that at least
one switched response was actually corrupted before trusting the refusal.
The setup flow specs answer questions through these helpers before their
first real navigation. That case still went through page.request, which
shares the runner's keep-alive agent, and a stale socket failed an answer
with read ECONNRESET on the dependency update branches. The page now opens
the public version endpoint first and sends the call with its own fetch.
The bump raised the CLI to 7.10.0 but left @prisma/client at 7.8.0 and
the migration CLI the image installs under /opt/prisma-cli at 7.8.0, so
the generated client, the one that ships and the one that migrates would
have come from two releases. All three are 7.10.0 now.
The setup flow helpers read the account, the dashboard layout and wrote
answers through page.request, which shares the runner's keep-alive agent
with every other API context. A socket the server had already closed on
its idle timeout failed one answer with read ECONNRESET, and CI counts
the passing retry as a failure. The calls go through the page's fetch
now, as the record session specs already do; before the first navigation
there is no origin, so that case keeps the API context.
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 ntfy and webhook 502 descriptions shared one list of refused credentials that neither channel fully has. Each now names its own, including the userinfo and long path segments of a webhook URL. The email description names the TLS, protocol and envelope cases.
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
Userinfo and each query value are decoded on their own, so one malformed escape no longer discards every other secret in the URL. A reply is compared with each secret in literal, JSON-escaped and percent-encoded form, and URL path segments of 16 or more characters count as secrets, which covers the token in a Discord or Slack webhook URL and an n8n webhook id.
Refs #947
A STARTTLS failure (port 465 set up as 587) and a protocol error now read as connection_failed, and an envelope or message refused without a reply code as upstream_rejected. They used to fall through to the 500 for an unnamed failure.
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
A 502 from a channel test reports the user's relay or mail setup, not a fault on this server, yet the status alone raised every press to an error-level event. The event builder gains a ceiling for the status-derived level, and the test routes cap it at warn on the 502 path. The 500 for an unnamed failure stays at error, and explicit elevations are unaffected.
The notifications guide notes that a reverse proxy which replaces 5xx pages hides the detail, so the card falls back to Test failed.
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
A same-origin auth gateway that answers an expired session with a 200
sign-in page returns HTML for both the navigation and the /api/version
probe, which read as another app. The worker then unregistered and
destroyed the Web Push subscription, which nothing re-creates, so
reminders stopped without notice.
The worker no longer unregisters while the registration holds a push
subscription, or when the lookup is unavailable. It deletes its caches
and passes requests to the network for the rest of its life instead.
The probe now sends same-origin credentials, so a valid gateway cookie
reaches HealthLog.
Refs #847
A notification target on a private network has been reachable since the
operator allowlist for private origins arrived; the hint under the URL
field and the guide still said the address had to be public.
The guard read src/lib/idempotency.ts for the hle_ prefix, which moved to src/lib/secret-shape.ts with the matcher. It now asserts that the shared matcher refuses an hle_ token and that the idempotency cache imports and calls it, so a private copy of the pattern would fail it too.
The outbound payload was documented nowhere. The webhook section now shows the generic body and the Gotify body as the Test button sends them, what each field carries, and how to point the webhook at Gotify: a URL ending in /message, the application token in an X-Gotify-Key header, and the Gotify format.
Refs #947
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
A reverse proxy with a response-header allowlist strips
X-HealthLog-Page from every real HealthLog page, and the worker then
unregistered on each load and dropped Web Push subscriptions.
A successful page without the marker now only sends requests to the
network while the worker asks /api/version with no-store and a manual
redirect. It deletes its caches and unregisters only when the answer
positively is not HealthLog: a 404, a non-JSON 2xx, or JSON without
the version shape. A failed probe, any other status or the HealthLog
shape resumes normal service. buildSha may be null on a self-built
image, so only its presence is checked.
Refs #847
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.
The webhook, ntfy and email test routes turned every delivery failure into a bare 500, so the settings card could only say "Test failed" while the server knew the relay had answered 400. A failure at the other end, or on the way to it, is now a 502 with meta.errorCode, the upstream HTTP status or SMTP reply code, and for webhook and ntfy what the relay answered when that is short and holds no credential. The codes reuse the test card's vocabulary; upstream_rejected is new, for a relay that accepted the connection and refused the payload. The private-origin 422 is unchanged, and only a failure the server cannot name stays a 500.
The guard that pinned the bare 500 now asserts the opposite, with the reason in its name.
The admin fan-out test reports the same code and status per channel, and tests the webhook and email channels, which it used to list as an unknown channel type.
Refs #947