User messaging¶
The Linux UI has six places to tell the user something. Each one carries one kind of message, so a new message has an obvious home and two surfaces never say the same thing in different words.
| Surface | Carries | Lifetime | Tone | Read over IPC |
|---|---|---|---|---|
| Toast (bottom right) | The outcome of something you just did: a refusal, an error, a receipt | 5 s | error or info |
app.notice_dump bottom_line, source: "status" |
| Bottom line (the same slot) | A standing condition of the workspace: the last save failed | Until it clears; a toast covers it while up | error |
app.notice_dump bottom_line, source: "durability" |
| Terminal notice | A condition that takes over the terminal area and has actions: the session under this frame ended, or the local session can't start | While the condition holds | info, warning or error |
app.notice_dump terminal |
| Sidebar band | A host's connection state, and a short reason | While the state holds | The band's dot | app.sidebar_dump sections, and host.status for the full reason |
| Dialog | A decision that must be made now | Until answered | — | app.dialog_dump (test mode) |
| Desktop notification | An agent event: a tab wants attention | The desktop's | — | The notification.fired event |
A terminal notice draws in one of two places. Over a frame, it sits
at the top of a frame the window keeps on screen, under a scrim, because
nothing will update those pixels again. In an empty area, it sits at
the top of a terminal area with nothing selected, with no scrim. The first
notice drawn there is "Roost couldn't start its local session.": under
local-backend = session, a launch that settled without a session would
otherwise leave the window blank, with only a band line to explain it. It
names the reason and the log that has the rest, and offers Try again
and Use in-process tabs. It never switches on its own; the second
button raises the same confirm card the palette's row does.
Three rules¶
- Content comes from a pure table. Which message, which words and
which buttons are decided by a pure function over plain inputs, with a
table test over every state it can be given. The terminal notice is
roost_ui_model::notice::terminal_notice; the band ishost_sidebar::SectionState; the dialogs arehost_noticeandhost_dialog. The widgets paint the answer and add nothing of their own. The toast is the exception today: its call sites compose their own strings. - Every surface is dumpable over IPC. The dump reports the rendered strings, the ones the widgets read, not the state behind them. A test then asserts what the user is told, rather than re-deriving the copy rule a second time.
- A click names what it was drawn on. A button carries the identity
of the thing it was drawn for, and the handler checks it against what
is on screen now before acting. A press can land after the window
moved on — a second click, or a click on pixels not yet repainted — and
acting on whatever replaced it is how a stale click aborts a reconnect
or starts a second session. Over IPC,
app.notice_answeralso names the notice'sgeneration, so a notice that went away and came back is not mistaken for the one a test read.