420f00494f
Sets up the Cargo workspace (harness-core, harness-tools, harness-providers, harness-mcp, harness-lsp, harness-app, harness-tui) and the ten architecture docs under docs/. harness-core gets its foundational types (session/message/part/model ids), an in-process event bus, a table-driven permission evaluate function, and a SQLite-backed storage actor with schema and roundtrip coverage. Every crate compiles empty and a bin stub prints its version.
2.9 KiB
2.9 KiB
07 — TUI
harness-tui, binary harness. Single ratatui app over the engine; zero business logic — it renders AppEvents and calls EngineHandle.
Layout
┌─ header: session title · agent · provider/model ─────────────┐
│ chat viewport (scroll) │
│ user block / assistant markdown / tool cards │
│ (tool card: status icon, title, collapsible output, diff) │
├─ jobs strip when active: ● exp-1 running · ✓ fix-1 done │
├─ input (tui-textarea, multiline, / command completion) │
└─ status: tokens in/out · cost · spinner · keymap hints ──────┘
Event loop
tokio::select! over:
crossterm::event::EventStream(keys, resize)bus.subscribe()→AppEvents- 33 ms render tick
Events mutate AppState { sessions, current: SessionView { messages: Vec<MessageView> }, modal: Option<Modal>, jobs, input } and set a dirty flag; render at most once per tick.
Streaming markdown rendering
MessageViewcaches renderedVec<Line>per part, keyed(part_id, revision, width).- Finished parts render once through the
pulldown-cmark→ ratatui renderer (headings, bold/italic, fenced code, lists, blockquotes; syntect highlighting optional in M6). - The in-flight tail part re-renders only itself on
PartDelta; only the portion after the last blank line gets full markdown treatment — earlier paragraphs of the same part are frozen into the cache. This bounds per-delta work. - Risk: ratatui has no incremental layout. The cache + tail-only strategy is the mitigation; fall back to a plain-text tail if profiling shows stutter.
Modals
- Permission dialog (on
PermissionAsked): permission key, pattern list, metadata — for edits, a colorized unified diff. Keys:yallow once,aallow always,nreject →engine.permission_reply(id, …). Multiple pending asks queue FIFO. - Device-code login (on
AuthPrompt): shows user code + verification URL; dismisses when the flow completes. - Pickers (shared fuzzy-filter list widget): sessions
Ctrl+S, agents (mode: Primary|All)Ctrl+A, models (grouped by provider)Ctrl+M. - Jobs pane
Ctrl+J: listsJobRecords (alias, agent, state, age, objective);Enterdrills into the child session view — a read-only live follow of its stream (trivial since all sessions publish to the same bus).
Keybinds & commands
Escaborts the running turn / closes modal;Ctrl+Ctwice quits.- Slash commands:
/model,/agent,/new,/sessions,/login <provider>,/compact, plus markdown-defined commands with completion.
Terminal hygiene
- Logging via
tracingto~/.local/share/ai-harness/log/(the TUI owns the terminal — nothing writes to stdout). - Panic hook +
Dropguard restore the terminal (disable raw mode, leave alternate screen).