Skip to main content

Fullscreen TUI

The fullscreen TUI is a terminal takeover UI built with crossterm. It uses an alternate screen, raw mode, mouse capture, and fixed layout regions.

Layout​

RegionRowsResponsibility
Header9 rowsBrand bar, active model, pinned status panel, command bar.
TranscriptFlexibleUser prompts, system progress, trace summaries, assistant responses, metadata.
Input area6 rowsThree prompt rows, command hints, context meter, project/chat/activity status.

The shell scrollback is not used during fullscreen mode. The transcript owns its own scroll offset.

At least 40 columns and 16 rows are needed for the normal layout. A multiline prompt may need two more rows. Smaller windows show a resize hint without discarding the conversation or draft; expanding the window restores them.

Response metadata and detail labels appear directly beneath the answer in faint, light-gray text, aligned with the answer. This uses standard terminal styling, with gray as the fallback when faint text is unsupported. The answer keeps its existing formatting; metadata uses the terminal's normal font size.

Panels​

The pinned panel is a compact status dashboard at the top of the screen.

PanelShows
DashboardProject, model inventory summary, agent roles, last query, UI mode.
ProjectActive project, state, /project commands, file-tagging hint, project config path.
ModelsActive provider/model, inventory status, provider chips, setup commands, config path.
AgentsRequired roles, Tester/Scout/MCP ON/OFF states, their toggle commands, and prompt profile.
ContextContext Loom, context window, memory commands, pack preview, index refresh.
SessionsCurrent session, store path, recent sessions.
TimelineLatest structured agent path.
CheckRuntime readiness, active provider, workspace, config.
ConfigProvider/model/config/key hints.
VersionsCLI, Python core, and planned ACP version inventory.
HelpCommand map, scrolling, cancellation, launch forms.

The command bar exposes quick panel commands:

terminal
/dashboard /project /models /agents /context /sessions /timeline /check /config /version /help

The Agents panel reads the same Python manifest used by the shell command. All three optional workers have a visible ON/OFF row and a command to toggle their current setting. Open it with /agents.

WorkerDefaultControl
TesterOn/agents tester on or /agents tester off
ScoutOff/agents scout on or /agents scout off
MCP brokerOff/agents mcp on or /agents mcp off; /mcp configures servers.

For example:

terminal
/agents tester off
/agents scout on
/agents mcp on

Settings persist user-wide and apply to the next run. Architect, Explorer, Coder and Verifier are required and cannot be disabled. Turning Tester off removes test-design inference; repository checks remain required. Shell commands use the same arguments after proto-cli agents, such as proto-cli agents tester off.

Approval preferences and final review​

/approvals opens a picker for the active project. Choose Ask (default), Edits (auto-accept file edits, ask before commands), or Auto (auto-accept edits and commands with normal host permissions). Set directly with /approvals ask|edits|auto; /approvals status reports the saved choice. The status area shows the current mode. Preferences apply to subsequent runs; MCP access and restoration still ask. No model is needed to change the setting.

Write runs finish with a compact summary of applied files and line counts. The Edits card uses green + counts, red - counts, bold file names and a muted diff hint. Verification has one compact status line; commands and logs stay in /trace. Missing Python runners/dependencies finish as verification unavailable without installation questions or repair loops, preserving applied edits as unverified. /diff opens their combined applied diff; /diff raw prints it and /diff proposed opens separately labeled approval previews. Partial edits remain reviewable after failure or deferred verification. See approval and review behavior for native guards, external effects and review limits.

Input Editor​

The input editor wraps by terminal cell width and keeps the cursor visible in a three-row composer. Emoji and combining characters are edited as grapheme clusters.

KeyBehavior
EnterSubmit the complete prompt.
Ctrl-JInsert a newline.
Shift-Enter / Alt-EnterInsert a newline when the terminal reports the modifier.
Left / RightMove cursor.
Home / EndMove to start/end of the logical line.
Backspace / DeleteDelete complete grapheme clusters.
Up / DownMove between wrapped rows; browse history for a single-row prompt.
Ctrl-P / Ctrl-NBrowse history from any row, preserving the unsubmitted draft.
Ctrl-RSearch recent prompts and recall a selection without submitting.
Ctrl-LRedraw the terminal, including while a reply is running.
TabOpen fuzzy slash-command completion; otherwise insert two spaces.
@Open the project file picker when a project is active.

Bracketed paste keeps multiline text and @tags literal: it neither submits the prompt nor opens a picker. Control bytes are removed; tabs become two spaces.

Input history is stored in memory for the current TUI process and capped by INPUT_HISTORY_CAPACITY.

File browser and viewer​

/ls [PATH] lists a project folder. /browse [PATH] or /files opens a directory browser with typing to filter, arrow/page navigation, Enter to open and Backspace to go up when the filter is empty. Esc closes the browser. /open FILE opens the read-only source viewer directly; without a path it opens the browser. /cat FILE displays a text preview in the transcript.

The viewer supports vertical scrolling, Home/End and horizontal panning with Left/Right. Esc or q returns. Files are read once and cached while viewing; resizing does not reload them. Preview/list limits keep memory bounded. None of these commands invokes a model or adds viewed text to model context. See project file inspection for quoted paths, limits and shell equivalents. /project selects a workspace; /open is now a file-view command.

File Tag Picker​

Typing @ opens a searchable modal backed by cli/src/terminal_ui/project.rs.

The picker:

  1. Scans the active project for taggable text files.
  2. Skips hidden and ignored directories such as .git, .venv, node_modules, target, dist, and build.
  3. Skips symlink entries and common binary suffixes.
  4. Lets the user type to filter results.
  5. Inserts either @path or @"path with spaces" into the prompt.

The Python core later resolves those tags with safe_path() and loads bounded read-only context.

Streaming answers​

Replies use an AGENT / architect or AGENT / guide heading. Each starts with a steady mint _ cursor and animated thinking., thinking.., thinking... dots. While the answer is empty, a single muted line explains the current work: gathering project context, reading a file, preparing edits, waiting for approval or running a check. Runtime events replace that line in place; ordinary policy and trace bookkeeping do not overwrite it. It disappears when answer text starts streaming and is never saved as part of the answer. No model call is needed to generate these labels. As answer text arrives, it appears in that same message with a streaming indicator. Tool-call JSON stays hidden even when a native-capable model sends it as text. Actual calls appear through the muted activity line; full arguments remain in /trace. Ordinary JSON answer data is preserved. Provisional narration is cleared when the engine selects a tool, so it cannot linger as an answer. Response text and its cursor retain your terminal's default background. The cursor does not blink and reserves a fixed cell through completion; the rest of the UI keeps its existing palette.

Answers render Markdown while they stream: bold, italic, headings, yellow-highlighted inline code and mint fenced code blocks with preserved indentation. Opening and closing markers become styles instead of raw tags; partial delimiters are held at the live edge. Formatting crosses wrapped lines, and Unicode display widths keep emoji and wide characters aligned. These are presentation styles; the original Markdown stays in the saved answer.

Completion updates the status in that same header. The answer stays in place: report footnotes and expanding trace blocks do not change its position. Terminal frames use synchronized updates where supported to reduce flicker.

Unchanged messages reuse cached Markdown layouts. The header, composer and status area have separate paint caches; typing updates the composer and live replies paint changed rows. Unchanged frames write nothing. Resize, Ctrl-L, debug changes and returning from a modal restore the layout. Frames and modal overlays are composed in memory and written under one output lock, using synchronized terminal updates where supported. Pickers repaint their background when opened or resized rather than on every filter keystroke.

Python stdout/stderr and console logging handlers created before a run are captured, bounded and redacted into /trace when the call ends. File logging is preserved, and handlers created during the run are detached from temporary buffers before those buffers close. The renderer owns terminal output during the embedded core call. Native subprocess tools retain their separate capture.

Worker activity appears in the bottom status bar and, before answer text arrives, in the transient muted response line. The top model row shows the provider and model. Use /trace for the full trace and retained worker/command output, or /diff for proposed changes. Esc or Ctrl-C cancels an active task or Guide answer.

The TUI polls input and progress every 32 ms while running; animation advances every 120 ms. These are frontend scheduling intervals, not model latency guarantees. Scrolling, resizing and Ctrl-L remain available during a reply. Scrolling up anchors the viewport while new answer rows arrive; Ctrl-End returns to the live bottom. Cancellation waits for native run cleanup and is shown as canceling, rather than pretending that execution has already stopped.

Model discovery and /check run in background workers. Esc or Ctrl-C dismisses their read-only result; a probe already in progress may finish in the background. Independent model metadata probes use a bounded pool. Opening a picker validates API keys through metadata endpoints without constructing LLM clients or making inference calls. Inventory is refreshed explicitly rather than after every answer.

Questions during a task​

Architect may ask a concise question when requirements or preferences are missing. A question overlay preserves the surrounding TUI. Type any answer; Tab cycles optional suggestions, and Enter sends the current text. Suggestions are not selected or submitted automatically. Esc skips the question; Ctrl-C cancels the whole run. PageUp/PageDown scroll long questions. Answers have a 4096-character limit; pasted line breaks become spaces and control bytes are removed. Oversized input is rejected rather than silently truncated.

The question and submitted answer appear before the continuing response in the transcript. ProtoLink receives the answer as a native tool result and continues the existing task. Its 300-second question timeout and remaining run budget also close the overlay without requiring a keypress. The UI polls the native request's lifetime and redraws the overlay only on input or resize.

A skipped or timed-out question supplies no answer. Feedback never approves file changes, commands or MCP calls. This is a live wait; closing and reopening the application does not resume an interrupted task or question.

Using the TUI without a model​

Starting the TUI, static /help, configuration, agent toggles, MCP setup, project selection and checkpoints do not need a running LLM. Guide questions (/help QUESTION) and coding requests require the selected model.

Before a CLI model run, the core checks the selection and probes local server metadata. Loopback requests use a one-second timeout; other configured server URLs use three seconds. Missing local models, unreachable servers and missing cloud keys get an inline setup message before repository indexing or agent construction. Cloud keys are checked locally at this stage; the actual request determines remote availability. Compatible servers without a model-list endpoint are allowed to continue. No preflight sends an inference request.

For Ollama, start the server with ollama serve, make sure the selected model is installed, and retry. /model chooses another model; /config shows the URL. Provider errors and malformed final responses leave the TUI open, preserve partial output and allow the next command. They are reported as failures rather than completed diagnostic fallbacks. /trace includes captured console output even when no final agent report was returned.

/help QUESTION streams from Guide using the active model, without requiring an active project. Guide receives a bundled command reference and a redacted settings snapshot on each call. For example, /help what does config do? can explain /config, proto-cli config and the commands for changing settings. Guide's manual includes all three optional-worker toggles, their defaults and required roles. Its per-call snapshot includes current Tester, Scout and MCP settings; /help how do I disable Tester? explains the control without changing it.

Quiet composer and debug details​

Keyboard hints are a faint placeholder inside the empty input. They disappear when you type; slash-command suggestions sit in the lower border. Two muted horizontal lines frame the composer, with a blank gap above it. The single-line prompt sits near the bottom and expands upward to show up to three lines while the lower border, context meter and status bar stay anchored. The empty input cursor blinks slowly, with 700 ms per phase, without repainting the conversation.

/debug on shows the metadata previously displayed under answers: provider, model, status, elapsed time, transport metrics, event/approval counts and report labels, plus a /trace hint. It reveals metadata on existing answers too. /debug off returns to the clean view; /debug reports the current mode. Debug defaults off for each TUI session and does not change agent execution, approvals or trace recording. With debug on, details appear after completion; use the default off mode to keep the answer layout unchanged.

Scrolling​

InputBehavior
Mouse wheel up/downScroll chat by a small fixed amount.
PageUp / PageDownScroll by visible page size.
Ctrl-HomeJump far up.
Ctrl-EndReturn to live bottom.

When scrolled away from the bottom, the TUI renders a CHAT SCROLLED marker and keeps the live task progress from unexpectedly moving the user's viewport.

Modals​

Focused modal modules keep the TUI maintainable:

FileModal or flow
modal.rsShared modal primitives, text input, searchable picker.
project.rsProject folder prompt and file picker.
model_picker.rsProvider/model picker and masked API key prompt.
approval.rsRuntime approval prompt for ProtoLink actions.
question.rsNative user question, free-text answer and explicit suggestions.
diff_view.rsDiff review modal for proposed file changes.

Context Meter​

The bottom input area contains a context usage meter. It consumes normalized ProtoLink RunEvent payloads, especially model context events, and records:

FieldMeaning
used_tokensEstimated or measured tokens used.
window_tokensConfigured or detected context window when known.
used_percentPressure against the window.
estimatedWhether token count is estimated.
agent_nameAgent that emitted the sample.
modelModel name associated with the sample.

The meter keeps the latest sample and a high-water peak.

Task Loop​

When a user submits a task:

  1. The TUI verifies an active project.
  2. A project session id is selected unless /context off is active.
  3. A temp progress JSONL file is created.
  4. Python is called through call_process_prompt_with_progress.
  5. The TUI tails progress events and handles navigation every 32 ms.
  6. Native approval and user-question requests are displayed as modals. Approval titles distinguish commands/checks from file changes. V views details/diff; Y approves execution and N/Esc denies. Inside a review, Enter/Esc returns without deciding and Y/N can decide directly. Reviews close when the native wait ends, including cancellation and runtime expiry.
  7. Esc or Ctrl-C writes a cancellation request.
  8. The final JSON response is parsed into CoreResponse.
  9. The response finalizes the existing message and is recorded in sessions.json.

The one-shot runner in main.rs uses the same ProgressFile bridge but prints panels to stdout instead of rendering alternate-screen UI.