-
-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Plan: TUI Fixes — 8 Issues, 8 Discrete Commits
Context
Testing the TUI locally revealed eight issues. Each is a separate commit.
Commit 1: Disable mouse capture so text is copyable
Problem
ail/src/tui/mod.rs:29 calls EnableMouseCapture, forwarding mouse events to the app. The TUI doesn't use mouse events — this breaks native terminal text selection and copy.
Changes
- Remove
EnableMouseCapturefromexecute!on line 29 - Remove
DisableMouseCapturefrom cleanupexecute!on line 38 - Remove the unused imports
Files
ail/src/tui/mod.rs
Commit 2: Redirect tracing to stderr
Problem
main.rs:9 inits tracing with tracing_subscriber::fmt().json().init() which writes to stdout. When the TUI owns the alternate screen, stdout writes corrupt the display — JSON log lines appear at the cursor and vanish on resize.
Changes
- Change to
.with_writer(std::io::stderr)— tracing is diagnostic and should never mix with program output.
Files
ail/src/main.rs
Commit 3: Enable text wrapping in viewport
Problem
The viewport Paragraph has no wrap mode. Long lines are truncated — LLM output falls offscreen and is unreadable.
Changes
- Add
.wrap(Wrap { trim: false })to theParagraphinviewport::draw(). - Import
ratatui::widgets::Wrap.
Files
ail/src/tui/ui/viewport.rs
Commit 4: Fix scrollback — don't snap to bottom during streaming
Problem
viewport_scroll is reset to 0 on every StreamDelta, StepStarted, ToolUse, etc. If the user scrolls up (PageUp) to read earlier output, the next streaming chunk snaps them back to the bottom.
Changes
- In
apply_executor_event(app.rs), only resetviewport_scroll = 0when it's already 0 (i.e., user hasn't scrolled away). Replace all theself.viewport_scroll = 0;lines with a conditional: only auto-scroll if the user is already at the bottom. - Add a helper:
fn auto_scroll(&mut self) { if self.viewport_scroll == 0 { /* already at bottom, nothing to do — 0 means "follow tail" */ } }— actually, since 0 already means "at bottom / auto-follow", the fix is simpler: just remove the explicit= 0resets from streaming events (StreamDelta,ToolUse,Completed). Theviewport_scrollfield already defaults to 0 (auto-follow). Only reset to 0 at the start of a new run (reset_for_run) and when the user explicitly scrolls to bottom (viewport_page_downgoing past 0). - Keep
viewport_scroll = 0inStepStartedto auto-scroll when a new step begins (reasonable UX). - Remove
viewport_scroll = 0from:StreamDelta,ToolUse,Completed,HitlGateReached,PipelineCompleted.
Files
ail/src/tui/app.rs
Commit 5: Replace cost display with token counters
Problem
Cost display shows dollar amounts from runner-reported cost_usd, but when running against Ollama (free local model), it reports bogus cost ($17 for one query). Cost data is unreliable across providers.
Changes
Replace the $0.0032 cost display with a token counter format: [↑1.2K | ↓3.4K] showing input and output tokens separately. Format with K/M/B suffixes and one decimal when crossing thresholds.
A. Add a formatting helper — ail/src/tui/ui/statusbar.rs
fn fmt_tokens(n: u64) -> String:
< 1_000 → "123"
< 10_000 → "1.2K" (one decimal)
< 1_000_000 → "12K" (no decimal)
< 10_000_000 → "1.2M"
< 1_000_000_000 → "12M"
else → "1.2B"
B. Replace cost spans — ail/src/tui/ui/statusbar.rs
- Remove the
$0.0032cost span - Replace with:
[↑{fmt(input)} | ↓{fmt(output)}] - Keep the format in both
RunningandCompletedphases
Files
ail/src/tui/ui/statusbar.rs
Commit 6: TUI backend missing invocation step (BUG)
Problem
Spec §4.1: "Every pipeline has an implicit first step called invocation. The pipeline's authored steps begin executing only after invocation completes."
main.rs (--once) correctly runs the user's prompt through the runner before execute() when the pipeline lacks an invocation step. backend.rs (TUI) skips this — jumps to execute_with_control() directly. The user's prompt never reaches the LLM for custom pipelines.
Changes
A. Add pre-invocation logic — ail/src/tui/backend.rs
Before execute_with_control():
- Check if pipeline's first step has
id == "invocation" - If not:
- Emit
StepStarted { step_id: "invocation", step_index: 0, total_steps: declared + 1 } - Call
runner.invoke_streaming()forwardingRunnerEvents - Append
TurnEntrytosession.turn_log - Emit
StepCompleted { step_id: "invocation" }
- Emit
B. Prepend "invocation" to sidebar — ail/src/tui/app.rs
When building sidebar steps for a pipeline whose first step is NOT invocation, prepend StepDisplay { id: "invocation", glyph: NotReached }. Also in hot-reload path (mod.rs:109-116).
Files
ail/src/tui/backend.rsail/src/tui/app.rsail/src/tui/mod.rs
Commit 7: Streaming feedback — thinking blocks
Problem
ClaudeCliRunner::invoke_streaming() drops "thinking" content blocks silently (catch-all _ => {} at claude.rs:263). No visual indication the LLM is reasoning.
Changes
A. Add RunnerEvent::Thinking variant — ail-core/src/runner/mod.rs
B. Parse thinking blocks — ail-core/src/runner/claude.rs
- Match
"thinking"→ emitRunnerEvent::Thinking { text }
C. Handle in AppState — ail/src/tui/app.rs
- New match arm, prefix with
[thinking], append to viewport + step buffer
D. Style thinking lines — ail/src/tui/ui/viewport.rs
- Lines starting with
[thinking]render inColor::DarkGray
Files
ail-core/src/runner/mod.rsail-core/src/runner/claude.rsail/src/tui/app.rsail/src/tui/ui/viewport.rs
Commit 8: Lower sidebar breakpoints
Problem
Sidebar disappears at < 100 cols. User wants it visible at narrow widths.
Changes — ail/src/tui/ui/layout.rs
FULL_WIDTH: 120 → 60GLYPH_WIDTH: 100 → 40STATUS_WIDTH: 80 → 30- Cap
sidebar_widthatarea.width / 2
Files
ail/src/tui/ui/layout.rs
Verification
After each commit: cargo build && cargo clippy -- -D warnings && cargo nextest run
Manual TUI test after all commits:
- Text is selectable/copyable in terminal
- No JSON log lines corrupt the TUI
- Long lines wrap within the viewport
- ScrollUp preserves position during streaming; new-step resets to bottom
- Status bar shows
[↑1.2K | ↓3.4K]token counts, no dollar amounts - Sidebar shows "invocation" → "review" for
.include-review.yaml - Invocation actually runs the user's prompt before pipeline steps
- Thinking lines appear in dim grey
- Sidebar visible at 40-col terminal width