The beacon (radio waves) is the default icon; the list glyph is the alternate, switchable in settings. Idle shows the plain template glyph. When sessions wait, one dot appears top-right at the highest urgency: red failed, then amber needs-you, then blue output updated. The dot counts only unacked, unhidden rows, so a dealt-with board goes quiet. The icon carries a dot only, no text count.
| Dot | State |
|---|---|
| none | idle, or everything dealt with |
| ● blue | output updated (look) |
| ● amber | needs your input (act) |
| ● red | a session failed (fix) |
One row per session in board order: status mark, agent glyph, name, age. Clicking any session
row jumps to that session. The first 10 sessions appear inline; any beyond the first 10 move into
a Show More (N) submenu in the same order, placed before the separator - N is the
number of overflow sessions, not the total. With 10 or fewer visible sessions there is no
Show More row at all.
Tags appear as dim lowercase text after the name and before the age, the same quiet signal as the jumplist pill in the form a menu can carry.
Show More (N) submenu; submenu rows
behave identically to inline rows (click to jump).Cannot reach the hub at <url>, or
Hub rejected this app (401) - set SIGNALBOX_TOKEN for <host> when a non-loopback
hub refuses the app's bearer. The app sends SIGNALBOX_TOKEN as a bearer on every hub
request, exactly as the CLI does, so a remote SIGNALBOX_URL works.signalbox CLI not found - brew install dwmkerr/tools/signalbox (or set SIGNALBOX_BIN)).
Jump, hide and remove need the CLI.The mock above shows the bar and the open dropdown together.
A small window (~360×460, titled "Connect Phone") that pairs the
iPhone app with the hub, WhatsApp-Web style: show a QR, wait, then confirm.
On open it reads the loopback hub's /healthz. A state-owning hub then receives a
POST /pair/new to mint a one-time code:
signalbox://pair?url=<percent-encoded http://LAN-IP:PORT>&code=<code>
(the LAN IP comes from the response bind when concrete, otherwise this Mac's primary
Wi-Fi IPv4), renders it as a QR (CoreImage, upscaled nearest-neighbor so it stays crisp), and shows
"Scan with your iPhone camera" with the plain LAN URL beneath. It then polls
/pair/status every 2s: on redeemed it swaps to a green check and
"Phone paired" and auto-closes after ~2s; when the code expires (180s) it offers a "New code" button.hub.bind or restarts the hub: the mode is changed only in Settings, behind its Confirm
button./healthz. The phone's board lives upstream, so the
window mints there rather than asking the forwarder (which 409s on /pair* by design):
POST <upstream>/pair/new with Authorization: Bearer from the stored
hub.token, over a 10s-timeout session because it crosses the WAN. The upstream is the
origin reported by /healthz, else the one in settings. A 200 renders the same QR as the
LAN path from
signalbox://pair?url=<percent-encoded upstream origin>&code=<code> -
no fp, because a remote hub sits behind platform TLS and the phone uses system trust -
with the origin printed beneath, and polls <upstream>/pair/status with the same
bearer on the same 2s cadence.signalbox pair --url <upstream> command with a Copy command button. The
reason is "no hub token is stored on this machine", "the remote hub rejected this machine's token"
(401), "the remote hub could not be reached", or "the remote hub returned an unexpected status
(<code>)". If no upstream address is available from health or settings, it shows "This Mac
forwards to a remote hub, but its address is not in ~/.config/signalbox/settings.json." and omits
the command and button. No path here changes hub.bind or restarts the hub.The QR carries only the one-time code and the hub URL - the LAN address, or the upstream origin when this Mac forwards - and omits the hub token. The phone learns the token by redeeming the code against that hub, so a photo of a used or expired QR grants no access.