routeGrid still dispatches on GridMsg::Notify, which felis renamed to Attention — Terminal.onNotify never fires #6

Closed
opened 2026-09-07 21:11:31 +09:00 by natsukium · 0 comments
Owner

Found by cross-checking the felis-broker.v1 JSON contract between
felis-web-gateway follow-felis-105b0899 (577138be) and this repo's
follow-felis-105b0899 (edfb8dfa). Both pin felis
105b089979369fd310757375af3df7900ae7e523.

The mismatch

felis's wire reset collapsed GridMsg::Bell and GridMsg::Notify into one
variant. crates/felis-protocol/src/messages/grid.rs:

    /// The session wants the user's attention. …
    Attention { source: AttentionSource },

pub enum AttentionSource {
    /// One BEL event, coalesced from every `0x07` byte in the same chunk.
    Bell,
    /// A desktop notification (OSC 9 / 99 / 777) fired on this session …
    Notification,
}

so the wire JSON is {"Attention": {"source": "Notification"}} or
{"Attention": {"source": "Bell"}}. There is no Notify arm left in the Grid
family — GridMsg::meta() lists Grid::Attention and no Grid::Notify.

This component still dispatches on the old name —
web/felis-terminal.ts:2031, in routeGrid:

      case "Notify":
        for (const cb of notifyCbs) cb();
        break;

variantOf returns "Attention", which falls through to the default: break
arm.

Why it matters on a live session

Terminal.onNotify is documented public API (README.md: "onNotify() for
OSC 9/99/777") and it now never fires. An embed that lit a tab badge, or an
app that used it as the "this terminal wants attention" signal, goes silent
against a current daemon — and silently: routeGrid's default arm drops the
frame, and the shadow screen applies Attention fine, so nothing logs.

The broker's own {"ev": "notify"} feed (Broker.onNotify, which
web/mobile.html:342 uses for badges) is unaffected — that comes from the
gateway's NotifyMsg::Subscribe observer, a different family. So this is only
visible to consumers of the per-terminal callback, which is why the follow
commit could pass its gates without noticing.

Fix

Rename the arm and, since one variant now carries both sources, decide what
onNotify means. The felis doc comment is explicit that a bell and a desktop
notification are the same client-side handler
(Window::request_user_attention), so the smallest correct change is:

      case "Attention":
        for (const cb of notifyCbs) cb();
        break;

If the two sources should stay distinguishable to an embedder, pass
bodyOf(msg, name)["source"] through the callback and update the onNotify
doc comment — it still says "desktop notifications (OSC 9/99/777) the session
fired" and "Needs the NOTIFY cap", and capabilities are gone with the reset.

Nothing changes on the gateway: decode_frame forwards the felis-typed value
verbatim.

Found by cross-checking the `felis-broker.v1` JSON contract between felis-web-gateway `follow-felis-105b0899` (577138be) and this repo's `follow-felis-105b0899` (edfb8dfa). Both pin felis `105b089979369fd310757375af3df7900ae7e523`. ## The mismatch felis's wire reset collapsed `GridMsg::Bell` and `GridMsg::Notify` into one variant. `crates/felis-protocol/src/messages/grid.rs`: ```rust /// The session wants the user's attention. … Attention { source: AttentionSource }, … pub enum AttentionSource { /// One BEL event, coalesced from every `0x07` byte in the same chunk. Bell, /// A desktop notification (OSC 9 / 99 / 777) fired on this session … Notification, } ``` so the wire JSON is `{"Attention": {"source": "Notification"}}` or `{"Attention": {"source": "Bell"}}`. There is no `Notify` arm left in the Grid family — `GridMsg::meta()` lists `Grid::Attention` and no `Grid::Notify`. This component still dispatches on the old name — `web/felis-terminal.ts:2031`, in `routeGrid`: ```ts case "Notify": for (const cb of notifyCbs) cb(); break; ``` `variantOf` returns `"Attention"`, which falls through to the `default: break` arm. ## Why it matters on a live session `Terminal.onNotify` is documented public API (`README.md`: "`onNotify()` for OSC 9/99/777") and it now never fires. An embed that lit a tab badge, or an app that used it as the "this terminal wants attention" signal, goes silent against a current daemon — and silently: `routeGrid`'s default arm drops the frame, and the shadow screen applies `Attention` fine, so nothing logs. The broker's own `{"ev": "notify"}` feed (`Broker.onNotify`, which `web/mobile.html:342` uses for badges) is unaffected — that comes from the gateway's `NotifyMsg::Subscribe` observer, a different family. So this is only visible to consumers of the per-terminal callback, which is why the follow commit could pass its gates without noticing. ## Fix Rename the arm and, since one variant now carries both sources, decide what `onNotify` means. The felis doc comment is explicit that a bell and a desktop notification are the same client-side handler (`Window::request_user_attention`), so the smallest correct change is: ```ts case "Attention": for (const cb of notifyCbs) cb(); break; ``` If the two sources should stay distinguishable to an embedder, pass `bodyOf(msg, name)["source"]` through the callback and update the `onNotify` doc comment — it still says "desktop notifications (OSC 9/99/777) the session fired" and "Needs the `NOTIFY` cap", and capabilities are gone with the reset. Nothing changes on the gateway: `decode_frame` forwards the felis-typed value verbatim.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
natsukium/felis-web-component#6
No description provided.