Normalize markdown formatting after the latest main updates.\n\nChecks: python3 scripts/layered_soul.py validate .; npx --yes prettier@3 --check '**/*.md'; git diff --check.
3.9 KiB
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 socketGlasspaneSnapshot— polled every 2sTableState— ratatui row selectionstatus_msg+ TTL — auto-decaying cyan feedbackdetail_pane— index into filtered panes for Enter popupsession_filter— optionalpi_session_idfiltersessions/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<Pane> before rendering:
fn render_table(&mut self, f: &mut Frame, area: Rect) {
let panes: Vec<Pane> = 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:
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
KillAgentcommand → 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