TUI layout reshape + Tools pane (§5 v1 entry point) #13

Closed
opened 2026-05-23 19:27:39 -07:00 by vh · 1 comment
Owner

Problem

Today's TUI is a single-pane vertical stack:

Header
thinking-current   (dock: top, height: auto)
transcript         (RichLog, 1fr)
prompt             (Input, dock: bottom)
identity + hint    (dock: bottom)
Footer

This shape was correct for v0.x's chat-only scope. Design-brief §5 commits ratatoskr's actual product surface to a multi-pane debug-observability dashboard:

§5: Textual Horizontal with two columns. Left column is the chat transcript (primary pane, RichLog widget) with input field below. Right column is TabbedContent cycling through the side panes — Persona, Tools, AdminEvents, BifrostState, ServerLog. Tab key (Ctrl+1..5) jumps between tabs without losing focus on the input field. Status footer carries session id, agent id, admin-key-present indicator, and current pane name.

§5 is the biggest reshape since v0.1.0. Most panes are blocked on substrate that isn't here yet:

Pane Blocked by Unblocked?
Persona remote-Worldtree topology (file-tail across host) OR new /persona/log endpoint (weeks of cross-repo work)
AdminEvents admin.events.read scope (issue #11)
BifrostState widget admin scope on /admin/sessions/{id}/bifrost
ServerLog remote topology (same as Persona)
Tools (inline-from-SSE v1) nothing — uses existing ToolStart/ToolResult SSE events
Layout refactor (Horizontal 2-col + TabbedContent chrome) nothing — pure UI restructure

The realistic §5 entry point is layout refactor + Tools pane together: both unblocked, both compose, together they ship the multi-pane shape the design brief commits to without depending on cross-repo or scope-grant work first.

Solution

One in-place amendment to issue #4 (ratatoskr.tui). Three coupled pieces:

Layout reshape

RatatoskrApp.compose() shifts from vertical-stack to Horizontal two-column:

Header
Horizontal:
  left column (Vertical, 2fr):
    thinking-current  (dock: top)
    transcript        (1fr)
    prompt            (Input, dock: bottom)
  right column (1fr):
    TabbedContent:
      TabPane "Tools" (id="tools-pane")
        tools_log     (RichLog)
identity + hint + current-pane-name + Footer

The vertical-stack DEFAULT_CSS (dock: top / dock: bottom) gets reshaped around the new Horizontal parent. Width split ~2:1 (chat is primary).

Tools pane

Routes ToolStart / ToolResult SSE events from the main transcript to a dedicated tools_log: RichLog in the right column's first tab. The current presenter (TuiPresenterState) treats these as demoted telemetry (· tool_start: … / · tool_result: … inline); post-amendment they go to the pane instead.

Rationale: per design-brief §5 "side pane (inline-from-SSE for v1)" — the events are routed, not duplicated. The main transcript becomes cleaner (chat is chat; tool activity is the pane); the Tools pane gains independent scrollback for long tool-call sequences.

CLI mode is unaffected — --send is non-interactive, no tabs concept; CLI keeps its current inline tool-event rendering.

Status footer

Current footer has identity (agent_id · …session_id_tail) + hint (Ctrl-C state). §5 adds a current-pane-name indicator. v1 only has one tab (Tools) so the indicator is fixed; it extends naturally as panes are added.

Keybindings

Ctrl+1 activates the Tools tab without stealing input focus. v1 has just one tab; bindings extend to Ctrl+2..5 as Persona/AdminEvents/BifrostState/ServerLog land. Input field retains keyboard focus across tab switches (the design brief's "without losing focus on the input field" invariant).

Out of scope (this issue)

  • Persona pane. Blocked on remote-Worldtree topology. File via worldtree-dev if/when the cross-host case becomes worth the contract cost; or set up sshfs/tunnel for a workaround. Separate issue.
  • AdminEvents pane. Blocked on issue #11. Lands as a follow-up after the admin scope is granted.
  • BifrostState widget. Blocked on admin scope.
  • ServerLog pane. Blocked on remote topology + --server-log flag.
  • Tool-call result smart truncation. Current {!r:.200} truncate behavior stays; smart truncation is its own concern.
  • Persistent tab selection across launches. v1 picks the Tools tab on mount; no preference persistence.
  • Pane drag-to-resize. v1 ships a fixed 2:1 width split; user resize is future polish.
  • Thinking pane / Debug pane proposals mentioned in persistent-memory: defer pending a concrete proposal contract. The TabbedContent shape makes them additive when the time comes.

Acceptance

  • Contract docs/contracts/issues/13.contract.md (amends #4) drift-checks clean.
  • All existing TUI tests stay GREEN (presenter routing for Text/Thinking/WorkerPhase/Done/Error/Cancelled events unchanged).
  • New tests cover: layout shape (Horizontal parent, TabbedContent right), ToolStart/ToolResult routing to tools_log not transcript, Ctrl+1 switches tab without focus loss, identity widget still renders, single-turn smoke through the new layout.
  • uv run ruff check src/ tests/ clean.
  • Manual smoke (ratatoskr --new): picker → mimir → chat pane is left column, Tools pane is right column, type a message, tool-using turn (e.g. ask Mimir to search the KB) → tool events render in the right pane, model text renders in the left transcript.

Dependencies

  • Issue #4 (ratatoskr.tui) — landed; this issue is an in-place amendment.
  • Issue #12 (presenter contract semantics amendment) — landed; the TuiPresenterState model gets a new routing target (tools_log Widget) threaded through render().
  • Independent of issues #9, #10, #11 (blocked or deferred).
## Problem Today's TUI is a single-pane vertical stack: ``` Header thinking-current (dock: top, height: auto) transcript (RichLog, 1fr) prompt (Input, dock: bottom) identity + hint (dock: bottom) Footer ``` This shape was correct for v0.x's chat-only scope. Design-brief §5 commits ratatoskr's actual product surface to a **multi-pane debug-observability dashboard**: > §5: Textual `Horizontal` with two columns. Left column is the chat transcript (primary pane, `RichLog` widget) with input field below. Right column is `TabbedContent` cycling through the side panes — `Persona`, `Tools`, `AdminEvents`, `BifrostState`, `ServerLog`. Tab key (`Ctrl+1..5`) jumps between tabs without losing focus on the input field. Status footer carries session id, agent id, admin-key-present indicator, and current pane name. §5 is the biggest reshape since v0.1.0. Most panes are blocked on substrate that isn't here yet: | Pane | Blocked by | Unblocked? | |---|---|---| | Persona | remote-Worldtree topology (file-tail across host) OR new `/persona/log` endpoint (weeks of cross-repo work) | ❌ | | AdminEvents | `admin.events.read` scope (issue #11) | ❌ | | BifrostState widget | admin scope on `/admin/sessions/{id}/bifrost` | ❌ | | ServerLog | remote topology (same as Persona) | ❌ | | **Tools (inline-from-SSE v1)** | nothing — uses existing `ToolStart`/`ToolResult` SSE events | ✅ | | **Layout refactor** (`Horizontal` 2-col + `TabbedContent` chrome) | nothing — pure UI restructure | ✅ | The realistic §5 entry point is **layout refactor + Tools pane together**: both unblocked, both compose, together they ship the multi-pane shape the design brief commits to without depending on cross-repo or scope-grant work first. ## Solution One in-place amendment to issue #4 (`ratatoskr.tui`). Three coupled pieces: ### Layout reshape `RatatoskrApp.compose()` shifts from vertical-stack to `Horizontal` two-column: ``` Header Horizontal: left column (Vertical, 2fr): thinking-current (dock: top) transcript (1fr) prompt (Input, dock: bottom) right column (1fr): TabbedContent: TabPane "Tools" (id="tools-pane") tools_log (RichLog) identity + hint + current-pane-name + Footer ``` The vertical-stack DEFAULT_CSS (`dock: top` / `dock: bottom`) gets reshaped around the new Horizontal parent. Width split ~2:1 (chat is primary). ### Tools pane Routes `ToolStart` / `ToolResult` SSE events from the main transcript to a dedicated `tools_log: RichLog` in the right column's first tab. The current presenter (`TuiPresenterState`) treats these as demoted telemetry (`· tool_start: …` / `· tool_result: …` inline); post-amendment they go to the pane instead. Rationale: per design-brief §5 "side pane (inline-from-SSE for v1)" — the events are routed, not duplicated. The main transcript becomes cleaner (chat is chat; tool activity is the pane); the Tools pane gains independent scrollback for long tool-call sequences. CLI mode is unaffected — `--send` is non-interactive, no tabs concept; CLI keeps its current inline tool-event rendering. ### Status footer Current footer has `identity` (`agent_id · …session_id_tail`) + `hint` (Ctrl-C state). §5 adds a current-pane-name indicator. v1 only has one tab (`Tools`) so the indicator is fixed; it extends naturally as panes are added. ### Keybindings `Ctrl+1` activates the Tools tab without stealing input focus. v1 has just one tab; bindings extend to `Ctrl+2..5` as Persona/AdminEvents/BifrostState/ServerLog land. Input field retains keyboard focus across tab switches (the design brief's "without losing focus on the input field" invariant). ## Out of scope (this issue) - **Persona pane.** Blocked on remote-Worldtree topology. File via worldtree-dev if/when the cross-host case becomes worth the contract cost; or set up sshfs/tunnel for a workaround. Separate issue. - **AdminEvents pane.** Blocked on issue #11. Lands as a follow-up after the admin scope is granted. - **BifrostState widget.** Blocked on admin scope. - **ServerLog pane.** Blocked on remote topology + `--server-log` flag. - **Tool-call result smart truncation.** Current `{!r:.200}` truncate behavior stays; smart truncation is its own concern. - **Persistent tab selection across launches.** v1 picks the Tools tab on mount; no preference persistence. - **Pane drag-to-resize.** v1 ships a fixed 2:1 width split; user resize is future polish. - **Thinking pane / Debug pane proposals** mentioned in persistent-memory: defer pending a concrete proposal contract. The TabbedContent shape makes them additive when the time comes. ## Acceptance - Contract `docs/contracts/issues/13.contract.md` (amends #4) drift-checks clean. - All existing TUI tests stay GREEN (presenter routing for Text/Thinking/WorkerPhase/Done/Error/Cancelled events unchanged). - New tests cover: layout shape (Horizontal parent, TabbedContent right), ToolStart/ToolResult routing to tools_log not transcript, `Ctrl+1` switches tab without focus loss, identity widget still renders, single-turn smoke through the new layout. - `uv run ruff check src/ tests/` clean. - Manual smoke (`ratatoskr --new`): picker → mimir → chat pane is left column, Tools pane is right column, type a message, tool-using turn (e.g. ask Mimir to search the KB) → tool events render in the right pane, model text renders in the left transcript. ## Dependencies - Issue #4 (`ratatoskr.tui`) — landed; this issue is an in-place amendment. - Issue #12 (presenter contract semantics amendment) — landed; the TuiPresenterState model gets a new routing target (tools_log Widget) threaded through `render()`. - Independent of issues #9, #10, #11 (blocked or deferred).
vh added the observabilityenhancementtasktui labels 2026-05-23 19:27:39 -07:00
vh closed this issue 2026-05-23 21:41:35 -07:00
Author
Owner

Shipped at 24e4371 (v0.4.0).

TUI reshaped to Horizontal two-column per design-brief §5. Left column = chat surface; right column = TabbedContent with Tools tab. ToolStart/ToolResult route to the Tools pane (INV-014); routing-not-duplication confirmed via live smoke against personal Worldtree (mimir KB search populated tools_log with search_library + read_note events; transcript stayed chat-only).

Surface added:

  • TuiPresenterState.render() widened to take tools_log: RichLog.
  • RatatoskrApp.action_focus_tools + Ctrl+1 binding.
  • Static#pane-name footer element (renders "Tools" v1; dynamic when more tabs land — no handler wiring lands in this issue per Volva amendment).

Contract: docs/contracts/issues/13.contract.md — drift-check clean, two amendments from Völva contract-paraphrase pass applied (Input-focus invariant priority over assumption; pane-name handler-wiring explicit deferral).

Tests: +9 net; 236/236 GREEN; ruff clean.

Follow-ups unblocked by this foundation:

  • Persona pane — still blocked on remote-Worldtree topology; file Worldtree-side /persona/log endpoint if needed.
  • AdminEvents pane — still blocked on issue #11 (admin.events.read scope).
  • BifrostState widget / ServerLog pane — still blocked.
  • Tab-switch handler + dynamic pane-name update — lands when the second tab ships.
Shipped at 24e4371 (v0.4.0). TUI reshaped to Horizontal two-column per design-brief §5. Left column = chat surface; right column = TabbedContent with Tools tab. ToolStart/ToolResult route to the Tools pane (INV-014); routing-not-duplication confirmed via live smoke against personal Worldtree (mimir KB search populated tools_log with search_library + read_note events; transcript stayed chat-only). **Surface added:** - `TuiPresenterState.render()` widened to take `tools_log: RichLog`. - `RatatoskrApp.action_focus_tools` + `Ctrl+1` binding. - `Static#pane-name` footer element (renders "Tools" v1; dynamic when more tabs land — no handler wiring lands in this issue per Volva amendment). **Contract**: `docs/contracts/issues/13.contract.md` — drift-check clean, two amendments from Völva contract-paraphrase pass applied (Input-focus invariant priority over assumption; pane-name handler-wiring explicit deferral). **Tests**: +9 net; 236/236 GREEN; ruff clean. **Follow-ups unblocked by this foundation:** - Persona pane — still blocked on remote-Worldtree topology; file Worldtree-side `/persona/log` endpoint if needed. - AdminEvents pane — still blocked on issue #11 (`admin.events.read` scope). - BifrostState widget / ServerLog pane — still blocked. - Tab-switch handler + dynamic pane-name update — lands when the second tab ships.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vh/ratatoskr#13