From 0ac07a864ce88feae79d5ee395f1f0c9a1323376 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Thu, 25 Jun 2026 17:18:13 +0200 Subject: [PATCH] docs: add concrete attention system design to TUI enhancements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolved decisions from brainstorm (Sam & Claude): - needs_attention = Error + Blocked + Stalled (free function on &Pane) - Stall threshold stays at 4h (Stalled is rare/critical, not frequent) - Attention bar replaces header when active (same 3-line footprint) - Row highlight inverts on selection (red bg → bright red fg on DarkGray) - Filter composes with session filter (AND), not replaces Implementation plan: 4 tiers in main.rs (~100-120 lines added): T1: needs_attention() + attention bar (replaces header) T2: n/N jump keys T3: a key attention filter T4: per-row highlight (invert on selection) T5: answer-from-dashboard (separate PR) Also documents pre-existing 'All sessions' unreachable bug. --- docs/GLASSPANE-TUI-ENHANCEMENTS.md | 132 ++++++++++++++++++++++++++--- 1 file changed, 121 insertions(+), 11 deletions(-) diff --git a/docs/GLASSPANE-TUI-ENHANCEMENTS.md b/docs/GLASSPANE-TUI-ENHANCEMENTS.md index 689ddb0..34db452 100644 --- a/docs/GLASSPANE-TUI-ENHANCEMENTS.md +++ b/docs/GLASSPANE-TUI-ENHANCEMENTS.md @@ -58,25 +58,131 @@ Legend: ⌘ Cmd · ⌥ Option · ⌃ Control · ⇧ Shift · ↩ Enter. 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"? + +```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: +``` +╔═ ⚠ 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()` 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: +- 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) + ## Proposed colibri-tui keymap (draft — to be refined as we build) Additive to the baseline; nothing here collides with existing keys. -| Key | Proposed action | Roadmap item | -| --- | --- | --- | -| `n` / `N` | Jump to next / previous **attention** pane (Error/Stalled/waiting) | jump-to-next-attention | -| `a` | Toggle the attention filter (show only panes needing the operator) | attention signal | -| `i` | Send input / answer the selected pane (when it is blocked on input) | answer-from-dashboard | -| `t` | Toggle Telegram/desktop notification mute for the session | outbound notifications | +| 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 — `n`ext-attention, `a`ttention-filter, `i`nput, `t`oggle-notify.) -## Open design questions +## Remaining open design questions -- **Attention as flag vs. state:** keep the `AgentState` enum small and derive an - attention boolean over it, rather than adding a sixth state. Where does - "waiting for input" come from — inferred from stall + a prompt marker, or an - explicit agent-emitted event? - **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. @@ -85,6 +191,10 @@ Additive to the baseline; nothing here collides with existing keys. (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()` 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. ## See also -- 2.45.3