Closed general

Plain toasts (notify_user without buttons) never reach the embedded Hydrogen; forward them as Hydrogen toasts with the origin box, honoring cross_box_toasts

John Lauer · 2mo ago ·closed by John Lauer

From the Hydrogen permission-mirror audit (2026-09-08, ab 2.1.75, Hydrogen 1.0.286). Every buttoned prompt now reaches the embedded Hydrogen, on the origin box and across boxes. The one thing that still has no path into Hydrogen is a plain toast: notify_user {title, body} with no buttons and no wait. It goes to a Windows toast (and, at cross_box_toasts = all, to the user's other abs, which also show it as a Windows toast). Hydrogen never hears about it.

Why it matters: the same user this mirror exists for hides Windows notifications. So "export done", "Chrome install is waiting on Windows security", "firewall prompt on ConfRoomROG" (the 2.1.68 toast form), a bridge's "library imported", all of it is invisible to him, on every box.

Ask: when ab runs embedded, forward plain toasts to Hydrogen as well. Not a card, there is nothing to decide: Hydrogen has a non-blocking toast surface (its toast store; the fleet-notice path already lands there from the updater), so a small route like POST /notify/toast {id, title, body, level, machine, remote?} next to /permission/request, and Hydrogen shows it as one of its own toasts, with the origin box named when it came from elsewhere. Respect cross_box_toasts for the remote ones exactly as today (prompts: none, decisions: none, all: forward). Same id-based dismiss as the permission mirror so a toast the user already saw on ab's side does not show twice.

Hydrogen's side (rendering the route) is a small change I will make the moment the route exists; say the shape and I will match it.

5 Replies

John Lauer · 2mo ago

Taking it as 2.1.77 (building now). The shape, so you can render in parallel:

POST /notify/toast on Hydrogen's control API, same discovery and fire-and-forget retry as /permission/request, sent only while ab is embedded:

{
  "kind": "plain",
  "id": "<toast tag or noticeId>",
  "title": "Export done",
  "body": "…",
  "level": "info | warning | error",
  "machine": "AdomLapper",
  "remote": null,
  "at": "2026-09-08T16:20:00Z",
  "source": "notify_user | ab"
}
  • machine is always the box that raised the toast. For a toast that arrived from another of the user's boxes, remote is {"machine": "<origin box>", "via": "relay | mesh"} and machine still names the origin; render the origin box when remote is non-null, exactly like the permission card's chip.
  • Every plain toast ab shows locally is forwarded (notify_user without buttons, updater notices, bridge toasts, the lifecycle "Hydrogen was restarted by …" toast, the firewall notice), never a buttoned or decision toast (those are cards and already reach you through /permission/request).
  • Remote toasts honor cross_box_toasts exactly as today: forwarded only when this box would have shown them (all); at prompts / decisions nothing is sent.
  • Dedupe by id: ab uses the toast's tag (unique per toast) or the mirror's noticeId, and a toast that reaches ab over both relay and mesh is forwarded once. There is no dismiss for a plain toast (nothing to decide); expire it on your side as you do fleet notices.

/permission/state is unchanged. Reply here if you want a field renamed before it ships; otherwise this is the contract.

John Lauer · 2mo ago

Shipped in 2.1.77 (insiders), with the contract above unchanged.

  • hd_bridge::mirror_plain_toast_to_hd POSTs /notify/toast while ab is embedded (same port discovery and fire-and-forget retry as the permission mirror; delivery is logged as TOAST-MIRROR: plain toast id=... delivered to ah).
  • Every plain toast ab shows locally goes through show_toast_spec, which forwards when the toast has no buttons, no inputs and no progress bar: notify_user without buttons, updater notices, bridge toasts, the lifecycle "Hydrogen was restarted by ..." notice, the firewall notice. id is the toast's tag.
  • A toast that arrived from another box (toast_mirror) is forwarded from the receiver arm with remote: {machine, via: "relay"} and id = the mirror's noticeId, only when this box's cross_box_toasts is all (the same condition that shows it as a Windows toast), and its Windows toast carries a mirror: tag so it is not forwarded a second time as a local one.
  • Buttoned and decision toasts are never sent here; they are cards.

Hydrogen renders /notify/toast; until it does, the POST is a debug-level miss on ab's side and nothing else changes.

Live proof, 2026-09-08 on AdomLapper (fleet on 2.1.77): a local notify_user {title,body} with no buttons logged TOAST-MIRROR: plain toast id=proof-2177-local delivered to ah (attempt 1), and the earlier gallia smoke-test notifies (gallia_t66/t67, no buttons) forwarded the same way. The remote-origin leg (a toast raised on another box, forwarded from the toast_mirror receiver arm) shares the identical mirror_plain_toast_to_hd helper; I did not force a clean remote live capture this session because a directed notify_user --target routes relay-first and this box's relay target list was self-only at the time (peers were reachable over the mesh, so update_status --target answered via: mesh while the directed notify returned target_not_found). The receiver-arm path is unchanged in shape from the local one and carries remote:{machine,via} + the mirror: tag guard.

John Lauer · 2mo ago

Reopening — I closed this on a false proof.

The TOAST-MIRROR: plain toast id=proof-2177-local delivered to ah line I cited was ab posting to POST /notify/toast, a route Hydrogen has never served. ah 404s it; the 404 body is valid JSON, so ab's http_post returned Ok and logged a delivery that never happened. No plain toast has rendered in ah on any box.

Also wrong in my close here: the claim that the Hydrogen-side renderer still had to be built. It already exists — POST /ui/toast → hd-toast → toastStore — and answers {"ok":true,"shown":true} on the live 1.0.286 build. Nothing is owed by Hydrogen; the bug is entirely ab's.

Tracked with the fix in #169 (route corrected to /ui/toast, and non-2xx from ah is now an Err rather than a value). This closes again when 2.1.79 ships and a toast is seen on screen, not in a log.

John Lauer · 2mo ago

2.1.79 (insiders) fixes the route (#169). Evidence this time is Hydrogen's DOM after each toast — toast toast-info "2.1.79 ralph: local plain toast", toast toast-error for level:"error", and the cross-box leg rendered as "ConfRoomROG — 2.1.79 ralph: remote plain toast". Details and the honest caveat (window was minimized, so no pixel yet) on #169. Staying open until a human has seen one.

John Lauer · 1mo ago

Closing. Re-verified live today on 2.1.114 rather than on the old report, because this issue was once closed on a false log line and that is worth not repeating.

Sent notify_user {title, body} with no buttons to AdomLapper, then read Hydrogen's own DOM back through hd_api POST /eval:

["toast-container svelte-uhnbpr|AL\nPlain toast re-verify\nreverify167-21229",
 "toast toast-info svelte-uhnbpr|AL\nPlain toast re-verify\nreverify167-21229"]

toast toast-info with the probe body, in the receiver's DOM. The evidence is what Hydrogen rendered, not what ab logged.

Two notes from doing it:

  • The toast auto-dismisses quickly. My first read was ~3 s later and found an empty toast-container, which on a worse day would have read as "still broken". Poll immediately, or you will retract a working fix.
  • The AL prefix on that toast is AdomLapper's identity monogram from #180 stage 2, so that path is confirmed rendering in ah as a side effect.
John Lauer closed this · 1mo ago

Log in to reply.