2026-06-25 16:59:05 +02:00
|
|
|
# Glasspane / colibri-tui — operator usability enhancements (working doc)
|
|
|
|
|
|
|
|
|
|
Working/design notes for the operator-facing supervision surface. The published
|
2026-06-26 09:15:39 +02:00
|
|
|
summary lives in [`wiki/glasspane.md`](./wiki/glasspane.md) (_Usability roadmap_);
|
2026-06-25 16:59:05 +02:00
|
|
|
this doc is the scratch space where we collect references and shape the work
|
|
|
|
|
before it lands.
|
|
|
|
|
|
2026-06-26 09:15:39 +02:00
|
|
|
**Premise:** make _"does this agent need me right now?"_ the primary,
|
2026-06-25 16:59:05 +02:00
|
|
|
impossible-to-miss object. We already have the spine — the glasspane state
|
|
|
|
|
machine (`Idle → Working → Done / Error / Stalled`), the snapshot API, and the
|
2026-06-26 09:15:39 +02:00
|
|
|
`colibri-tui` dashboard. The open work is surfacing and _pushing_ attention, not
|
2026-06-25 16:59:05 +02:00
|
|
|
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)
|
|
|
|
|
|
2026-06-26 09:15:39 +02:00
|
|
|
| Key | Action |
|
|
|
|
|
| ---------------------- | ----------------------------------------------- |
|
|
|
|
|
| `q` / `Esc` | Quit, or close detail pane if open |
|
|
|
|
|
| `r` | Refresh snapshot now |
|
|
|
|
|
| `s` | Spawn a local `colibri-test-agent` |
|
refactor: kill→stop across API surface, CLI, TUI, and docs
Clean sweep — no kill on the Colibri wire protocol, CLI surface,
TUI keybinding, or documentation. Backward-compat aliases removed;
daemon and client deploy together so no transitional period needed.
Wire: KillAgent→StopAgent, "kill-agent"→"stop-agent" (no alias)
CLI: colibri kill→stop, Command::KillAgent→StopAgent
Lib: client.kill_agent()→stop_agent()
TUI: kill_selected()→stop_selected(), "kill"→"stop" label
Docs: spawn/kill→spawn/stop, kill-agent→stop-agent (40+ instances)
Retained kill only where it belongs:
- child.kill() / handle.kill() (OS SIGKILL)
- Unix kill(1) in sigterm tests
- OOM kill, process-group kill comments (kernel mechanism)
2026-06-26 14:38:53 +02:00
|
|
|
| `x` | Stop the selected pane |
|
2026-06-26 09:15:39 +02:00
|
|
|
| `Enter` | Open/close the detail pane for the selected row |
|
|
|
|
|
| `Tab` / `Shift-Tab` | Cycle through distinct sessions |
|
|
|
|
|
| `j` / `k` or `↓` / `↑` | Navigate the pane table |
|
2026-06-25 16:59:05 +02:00
|
|
|
|
|
|
|
|
→ `crates/colibri-glasspane-tui/src/main.rs`
|
|
|
|
|
|
|
|
|
|
## Reference: agent-cockpit shortcut vocabulary (example, not a spec)
|
|
|
|
|
|
2026-06-26 09:15:39 +02:00
|
|
|
Captured as an example of the _interaction vocabulary_ a mature agent cockpit
|
2026-06-25 16:59:05 +02:00
|
|
|
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**
|
2026-06-26 09:15:39 +02:00
|
|
|
|
2026-06-25 16:59:05 +02:00
|
|
|
- ⌘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 ★**
|
2026-06-26 09:15:39 +02:00
|
|
|
|
2026-06-25 16:59:05 +02:00
|
|
|
- `J`/`K` (+ `⌃N`/`P`, `H`/`L`) — vim-style row/pane movement (we already do `j`/`k`)
|
|
|
|
|
- ⌘1…9 / ⌃1…9 — jump directly to workspace / surface N
|
|
|
|
|
|
|
|
|
|
**Workspaces / surfaces (GUI tab model — mostly N/A to a TUI)**
|
2026-06-26 09:15:39 +02:00
|
|
|
|
2026-06-25 16:59:05 +02:00
|
|
|
- ⌘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")**
|
2026-06-26 09:15:39 +02:00
|
|
|
|
2026-06-25 16:59:05 +02:00
|
|
|
- ⌘⇧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.
|
|
|
|
|
|
2026-06-25 17:18:13 +02:00
|
|
|
## Resolved decisions (25.jun.2026 brainstorm — Sam & Claude)
|
|
|
|
|
|
|
|
|
|
### What counts as "attention"?
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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:
|
2026-06-26 09:15:39 +02:00
|
|
|
|
2026-06-25 17:18:13 +02:00
|
|
|
```
|
|
|
|
|
╔═ ⚠ 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
|
|
|
|
|
|
2026-06-26 09:15:39 +02:00
|
|
|
| Row state | Normal | Selected |
|
|
|
|
|
| --------- | --------------------------- | -------------------------------------- |
|
|
|
|
|
| Attention | `bg(DarkRed)` + `fg(White)` | `bg(DarkGray)` + `fg(LightRed)` + bold |
|
|
|
|
|
| Normal | (current — plain) | `bg(DarkGray)` (current) |
|
2026-06-25 17:18:13 +02:00
|
|
|
|
|
|
|
|
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()` over `filtered_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: bool` field to `App`
|
|
|
|
|
- Modify `filtered_panes()`: chain `needs_attention()` when `attention_only`
|
|
|
|
|
- `a` toggles 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 on `needs_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:
|
2026-06-26 09:15:39 +02:00
|
|
|
|
2026-06-25 17:18:13 +02:00
|
|
|
- TextArea widget (from `tui-textarea` crate or `ratatui::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)
|
|
|
|
|
|
2026-06-25 16:59:05 +02:00
|
|
|
## Proposed colibri-tui keymap (draft — to be refined as we build)
|
|
|
|
|
|
|
|
|
|
Additive to the baseline; nothing here collides with existing keys.
|
|
|
|
|
|
2026-06-26 09:15:39 +02:00
|
|
|
| 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 |
|
2026-06-25 16:59:05 +02:00
|
|
|
|
|
|
|
|
(Mnemonics provisional — `n`ext-attention, `a`ttention-filter, `i`nput, `t`oggle-notify.)
|
|
|
|
|
|
2026-06-25 17:18:13 +02:00
|
|
|
## Remaining open design questions
|
2026-06-25 16:59:05 +02:00
|
|
|
|
|
|
|
|
- **Outbound notify transport:** a `colibri notify` subcommand, 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.
|
2026-06-25 17:18:13 +02:00
|
|
|
- **Pre-existing "All sessions" bug:** once any pane has a `session_id`,
|
|
|
|
|
`rebuild_session_list()` forces `session_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.
|
2026-06-25 16:59:05 +02:00
|
|
|
|
|
|
|
|
## See also
|
|
|
|
|
|
|
|
|
|
- [`wiki/glasspane.md`](./wiki/glasspane.md) — state machine + published roadmap
|
|
|
|
|
- [`wiki/tui.md`](./wiki/tui.md) — the dashboard client and its current keybindings
|