# Colibri-Harness: Herdr-like TUI on Colibri Primitives The `colibri-glasspane-tui` crate (binary: `colibri-tui`) was upgraded to a herdr-like supervision dashboard — now called **colibri-harness** — on commit `eb37784` (2026-05-27, Sam & Hermes). ## Relationship to Herdr | | Herdr | colibri-harness | | ------------ | -------------------------------------- | ----------------------------------------------- | | What | Terminal workspace manager (full tmux) | Single-screen pane supervision dashboard | | Protocol | Herdr binary frame diffs over SSH | Colibri GlasspaneSnapshot JSON over Unix socket | | FreeBSD | Parked — Linux/macOS only | Native (via colibri-daemon on FreeBSD) | | Agent spawn | Native PTY spawn | Calls daemon to spawn agents | | Session mgmt | Workspaces, tabs, BSP splits | Session filter cycling (tab key) | | State model | PTY lifecycle | 5-state (Idle/Working/Blocked/Done/Error) | Boundary (decided 2026-05-27): Herdr = Linux/macOS display/remote plane behind Colibri's socket API; colibri-glasspane = native FreeBSD answer. No herdr vendoring, no herdr-on-FreeBSD in the core. ## Keybindings ``` q quit / close detail s spawn agent (local provider → colibri-smoke-agent) x kill selected pane Enter open/close detail popup tab next session filter shift+tab previous session filter j/k navigate pane rows r manual refresh ``` ## App Architecture The `App` struct holds: - `DaemonClient` — talks to daemon socket - `GlasspaneSnapshot` — polled every 2s - `TableState` — ratatui row selection - `status_msg` + TTL — auto-decaying cyan feedback - `detail_pane` — index into filtered panes for Enter popup - `session_filter` — optional `pi_session_id` filter - `sessions` / `session_idx` — cycled via tab ### Ratatui borrow-checker pattern `render_table()` calls `filtered_panes()` which borrows `self` immutably, then `render_stateful_widget()` borrows `self.table_state` mutably. Fix: collect panes as owned `Vec` before rendering: ```rust fn render_table(&mut self, f: &mut Frame, area: Rect) { let panes: Vec = self.filtered_panes().iter().map(|p| (*p).clone()).collect(); // ... use panes, then render_stateful_widget with self.table_state } ``` ### Footer keybinding pattern Lifetime issues with inline closures capturing `&str` → fix by using `format!()` inside the closure instead of passing the reference: ```rust fn render_footer(&self, f: &mut Frame, area: Rect) { let key = |label: &str| -> Span { Span::styled( format!(" {label}"), // format! avoids lifetime issue Style::default().add_modifier(Modifier::BOLD).fg(Color::White).bg(Color::DarkGray), ) }; // key("q"), key("s"), etc. } ``` ## Smoke Agent Integration The TUI spawns `colibri-smoke-agent` as a local provider via the daemon socket. The smoke agent defaults to session ID `"manual-smoke"` (overridable with `--session-id`). Key behaviors: - Spawn → daemon spawns process → smoke agent writes JSONL → PiJsonlIngestor feeds state machine → snapshot reflects Idle→Working→Blocked→Done - Kill sends `KillAgent` command → daemon stops process - Snapshot may briefly retain killed panes (async cleanup) ## Test Coverage - Unit tests: 5 (TUI helpers: short_observed_at, state_color, state_icon, filtered_panes, rebuild_session_list) - Integration smoke: `live_socket_smoke.rs` — 2 tests (single agent lifecycle - double spawn session isolation) - Workspace: 65 tests total (as of `eb37784`), clippy-clean ## Related - herdr-deployment skill (this skill) - `clawdie-ai/docs/internal/sessions/2026-05-27-herdr-tailscale-remote-smoke.md` - Colibri repo: `git@codeberg.org:Clawdie/Colibri.git`