7 renames (no plan/proposal/handoff/enhancement in filenames):
CLAWDIE-INSTALLER-HANDOFF.md → CLAWDIE-INSTALLER-VALIDATION.md
CLAWDIE-STUDIO-PROPOSAL.md → CLAWDIE-STUDIO.md
COLIBRI-SKILLS-PLAN.md → COLIBRI-SKILLS.md
FREEBSD-BUILD-LANE-HANDOFF.md→ FREEBSD-BUILD-LANE.md
GLASSPANE-TUI-ENHANCEMENTS.md→ GLASSPANE-TUI-DESIGN.md
MULTI-AGENT-HOST-PLAN.md → MULTI-AGENT-HOST.md
PLAN-WIKI-CLAWDIE-SI.md → WIKI-CLAWDIE-SI.md
16 cross-references updated across 10 files.
wiki-lint --strict: PASS (146 refs, 0 failures).
9.6 KiB
Glasspane / colibri-tui — operator usability enhancements (working doc)
Working/design notes for the operator-facing supervision surface. The published
summary lives in wiki/glasspane.md (Usability roadmap);
this doc is the scratch space where we collect references and shape the work
before it lands.
Premise: make "does this agent need me right now?" the primary,
impossible-to-miss object. We already have the spine — the glasspane state
machine (Idle → Working → Done / Error / Stalled), the snapshot API, and the
colibri-tui dashboard. The open work is surfacing and pushing attention, not
new machinery. Ideas are drawn from agent-cockpit terminal UIs; all wiring is our
own (no external code, no dependency).
Current colibri-tui keybindings (baseline)
| Key | Action |
|---|---|
q / Esc |
Quit, or close detail pane if open |
r |
Refresh snapshot now |
s |
Spawn a local colibri-test-agent |
x |
Stop the selected pane |
Enter |
Open/close the detail pane for the selected row |
Tab / Shift-Tab |
Cycle through distinct sessions |
j / k or ↓ / ↑ |
Navigate the pane table |
→ crates/colibri-glasspane-tui/src/main.rs
Reference: agent-cockpit shortcut vocabulary (example, not a spec)
Captured as an example of the interaction vocabulary a mature agent cockpit
settles on. Most of it is GUI-only (canvas, embedded browser, diff viewer, split
panes) and irrelevant to a terminal dashboard — kept here only so we can see the
shape. The transferable clusters for colibri-tui are marked ★.
Legend: ⌘ Cmd · ⌥ Option · ⌃ Control · ⇧ Shift · ↩ Enter.
Notifications ★ — the "attention" model we care about
- ⌘I — show notifications
- ⌘⇧U — jump to latest unread
- ⌃⌘U — mark oldest-unread and jump to next
- ⌥⌘U — toggle current item unread
- ⌘⇧H — flash focused panel
Navigation ★
J/K(+⌃N/P,H/L) — vim-style row/pane movement (we already doj/k)- ⌘1…9 / ⌃1…9 — jump directly to workspace / surface N
Workspaces / surfaces (GUI tab model — mostly N/A to a TUI)
- ⌘N new workspace · ⌘T new surface · ⌘W close · ⌘⇧R rename · ⌘⇧O reopen previous session
- ⌃⌘] / ⌃⌘[ next/prev workspace · ⌘⇧] / ⌘⇧[ next/prev surface
Input / focus ★ (relevant once we add "answer a blocked agent")
- ⌘⇧A — switch focus between terminal and a text-input box
- ⌥⌘⇧A — attach file to the input box
GUI-only (canvas, browser, diff, find, splits) — out of scope for the TUI; listed for completeness only: ⌘D/⌘⇧D splits, ⌃⌘C canvas, ⌘⇧L browser, ⌃⌘⇧D diff viewer, ⌘F find, ⌥⌘= / ⌥⌘- zoom, etc.
Resolved decisions (25.jun.2026 brainstorm — Sam & Claude)
What counts as "attention"?
fn needs_attention(pane: &Pane) -> bool {
pane.state == AgentState::Error
|| pane.state == AgentState::Blocked
|| pane.stalled
}
Error + Blocked + Stalled. Blocked is included because the doc comments at
colibri-glasspane/src/lib.rs:71 explicitly say Blocked = "operator attention
needed" (it's the state for queue_update / pending steering / approval). A
free function on &Pane, used by the attention bar, the filter, and the jump
keys — one place to change the definition.
Stall threshold stays at 4h
DEFAULT_STALL_AFTER = 4 * 60 * 60 (4 hours). Stalled is a rare but critical
signal, not a frequent one. The attention bar will mostly show Errors and
Blocked panes; Stalled is the "something is deeply wrong" escalation.
Attention bar replaces the header when active
When any pane needs_attention(), the header slot is replaced by a red-bordered
attention bar (same 3-line vertical footprint). Otherwise the normal header
renders. This makes attention impossible to miss without consuming extra space.
Proposed bar layout:
╔═ ⚠ ATTENTION ════════════════════════════════════════════╗
║ 3 panes need attention (1 error · 1 blocked · 1 stalled) ║
║ scraper-19: Error · worker-7: Blocked · db-3: Stalled ║
╚══════════════════════════════════════════════════════════╝
- Counter line — derives from existing
snapshot.count()+stalled_count(). No new API calls. - Pane list line — pane IDs + state label, truncated to terminal width. If
more than fit, ends with
· (N more — press a). - The bar stays visible even when the attention filter (
a) is active, so the operator always sees the total count at a glance.
Row highlight inverts on selection
| Row state | Normal | Selected |
|---|---|---|
| Attention | bg(DarkRed) + fg(White) |
bg(DarkGray) + fg(LightRed) + bold |
| Normal | (current — plain) | bg(DarkGray) (current) |
Attention rows are impossible to miss. The inversion on selection confirms which one the cursor is on without losing the attention signal.
Filter composes with session filter (AND)
attention_only: bool is a separate field from session_filter. In
filtered_panes(), the filters chain: session filter AND attention filter.
Tab cycles sessions within the attention-only view. Turning off the attention
filter returns to the session-scoped view.
Implementation plan — build order
All work in a single file: crates/colibri-glasspane-tui/src/main.rs (727
lines currently). Estimated diff: ~100-120 lines added, ~30 modified.
Tier 1: Attention bar + needs_attention() (~40 lines)
- Add free function
needs_attention(&Pane) -> bool - Add
render_attention_bar()method - Modify
render(): conditional — attention bar replaces header when any pane needs attention - Unit tests:
needs_attention_error,needs_attention_blocked,needs_attention_stalled,needs_attention_working_false,needs_attention_done_false
Tier 2: Jump keys — n / N (~20 lines)
n— move selection to next attention pane (wrapping)N— move selection to previous attention pane (wrapping)- Uses
needs_attention()overfiltered_panes()(respects session scope) - If no attention panes: toast
"no panes need attention" - Detail pane follows the jump (existing sync logic)
- Footer updated: add
n/N attn
Tier 3: Attention filter — a key (~15 lines)
- Add
attention_only: boolfield toApp - Modify
filtered_panes(): chainneeds_attention()whenattention_only atoggles the field; toast on toggle:"attention filter on (N panes)"/"attention filter off"- Footer updated: add
a attn
Tier 4: Row highlight (~20 lines)
- Modify
render_table(): per-row style based onneeds_attention()and selection state - Attention + not selected →
Style::new().bg(DarkRed).fg(White) - Attention + selected →
Style::new().bg(DarkGray).fg(LightRed).add_modifier(BOLD) - Non-attention rows unchanged
Tier 5: Answer-from-dashboard — separate PR
Not part of this work. Needs:
- TextArea widget (from
tui-textareacrate orratatui::widgets::Textarea) - New socket command
send-input { pane_id, text } - Daemon routes to agent subprocess stdin
- Design pass: which states accept input? (Blocked at minimum)
Proposed colibri-tui keymap (draft — to be refined as we build)
Additive to the baseline; nothing here collides with existing keys.
| Key | Proposed action | Tier | Status |
|---|---|---|---|
n / N |
Jump to next / previous attention pane | 2 | Ready |
a |
Toggle the attention filter (show only panes needing the operator) | 3 | Ready |
i |
Send input / answer the selected pane (when it is blocked on input) | 5 | Future |
t |
Toggle Telegram/desktop notification mute for the session | — | Future |
(Mnemonics provisional — next-attention, attention-filter, input, toggle-notify.)
Remaining open design questions
- Outbound notify transport: a
colibri notifysubcommand, or a glasspane event type a zot/Pi hook fires? Desktop (XFCE) + Telegram (token already provisioned) are the two sinks. - Interactive write path: "send input to pane N" turns the daemon socket from read-only supervision into interactive control — needs its own design pass (auth, which panes accept input, echo/confirmation).
- Persisted pane history: how much timeline to keep across daemon restarts so "what happened while I was away" survives a reboot, without growing unbounded.
- Pre-existing "All sessions" bug: once any pane has a
session_id,rebuild_session_list()forcessession_filter = Some(...), making the aggregated view unreachable. Out of scope for the attention work, but the attention filter must compose cleanly (AND) with the session filter.
See also
wiki/glasspane.md— state machine + published roadmapwiki/tui.md— the dashboard client and its current keybindings