reporters/tui

Source: src/reporters/tui.ts

Formatting for the pi TUI, with no pi-tui in sight.

This file turns a {@link RunSnapshot} into strings and rows; the extension draws them. Nothing here holds state - the picture is picture.ts, folded once for every reader - so a line is tested by calling the function that makes it, never by scraping a terminal.

callLine

function

export function callLine(call: ToolCall): string { /* … */ }

A call as a row lists it: {@link formatToolCall}, and when it came back an error, ✗ first and pi’s words after, so a call pi refused never reads as one that ran: ✗ write notes.txt · Tool write not found.

currentActivity

function

export function currentActivity(snapshot: SubagentSnapshot): string { /* … */ }

What a subagent is doing right now, in a few words.

The last tool call while it works; its verdict once it is done. This is the “minimal information” of the widget - enough to know it is alive and on the right track, not enough to read instead of the transcript.

detailLine

function

export function detailLine(snapshot: SubagentSnapshot, now?: number): string { /* … */ }

provider/model · ↑12k ↓209 · 12.4s, the tokens once its first turn has ended.

formatToolCall

function

export function formatToolCall(name: string, args: unknown): string { /* … */ }

Formats a tool call the way the pi TUI shows built-in tools.

$ cmd, read ~/path:1-10, grep /pat/ in ~/path - shapes a pi user already reads without thinking. Anything unknown degrades to name arg=value rather than dumping raw JSON at them.

progressLine

function

export function progressLine(snapshot: RunSnapshot): string { /* … */ }

2/3 done, 1 running - what a parallel run looks like while it runs.

standingOf

function

export function standingOf(snapshot: SubagentSnapshot): Standing { /* … */ }

Reads how a subagent stands off its snapshot.

The one place ok and status are folded into a word, so that a widget, a card, a table and a console cannot each decide differently what a finished failure looks like - they did, and one of them drew a tick on it.

statusColour

function

export function statusColour(standing: Standing): "error" | "success" | "warning" | "accent" { /* … */ }

The theme colour a standing is drawn in, by the name pi’s theme knows it under.

statusIcon

function

export function statusIcon(standing: Standing): string { /* … */ }

● while it lives, ✓ once it succeeded, ✗ once it failed.

summaryTable

function

export function summaryTable(snapshot: RunSnapshot, wallMs: number): string[] { /* … */ }

The end-of-workflow table: one line per subagent, total at the bottom.

wallMs is passed in because a snapshot cannot know it: on a fan-out the elapsed time is not the sum of the branches, and that difference is the whole point of the number.

A delegated subagent is indented under the one that asked for it, and the total is still the sum of every row: what ruins a run is what the tree cost altogether, never what one leaf of it cost.

WidgetRow

type

export type WidgetRow =
	| {
			kind: "activity";
			icon: string;
			status: Standing;
			id: string;
			activity: string;
			/** Model, tokens and time, when they belong on this line rather than under it. */
			detail?: string;
			depth: number;
	  }
	| { kind: "detail"; text: string; depth: number };

A dot per subagent, above the prompt - the Claude Code shape.

Two lines while it works: the dot with what it is doing, then a dimmed line with model, tokens and time. One line once it is over, because the second line of a finished subagent held its last tool call, which nobody needs any more; its numbers move up beside the tick instead. A fan-out of three took seven lines from the first dot to the last, and now shrinks as it finishes.

Colour is not applied here; the caller wraps the lines, because a colour code depends on a theme this file must not know about. It gets {@link widgetRows} instead, which says what each line is.

widgetRows

function

export function widgetRows(snapshot: RunSnapshot): WidgetRow[] { /* … */ }

The widget, as rows that say what they are.

Layout without colour, so it can be asserted on without a terminal. depth is how far under a root the subagent sits; the caller turns it into indent, because how wide a level is drawn is a decision about a terminal.