selfware 0.6.7

Your personal AI workshop — software you own, software that lasts
Documentation
{
  "component": "ui",
  "tier": "tooling",
  "loop_stage": "perceive",
  "summary": "The ui component is the visual surface of the loop: TaskDisplay renders live task state (elapsed time, token counters, current tool), the TUI (ratatui app, palette, status_line, dashboard_widgets) owns the alternate-screen frame, and components.rs renders headers, step banners, tool-call rows, diffs, and checkpoints. On the loop it is a pure 'perceive' sink that projects AgentState, ToolCall progress, and Budget into human-readable frames — but on a touch canvas it doubles as the interface surface itself, where loop-stage nodes are drawn, dragged, and connected. It never mutates loop control; it observes and renders.",
  "loop_objects": ["TaskDisplay", "DiffView", "Selection", "WorkshopContext", "ProgressPhase", "PhaseStatus", "ToolCall", "AgentState", "Budget", "TokenUsage", "Checkpoint", "PaletteEntry", "StatusLine"],
  "context_basis": "Recommendations formed with ui read in the context of the full engine (~600k budget framing), where the TUI owns rendering and stdout print functions become no-ops once set_tui_active(true).",
  "examples": [
    {
      "id": "ui-01",
      "title": "Mount TaskDisplay as the loop's live perceive frame",
      "loop_stage": "perceive",
      "pattern": "perceive-then-render",
      "intent": "Give the operator a continuously updating projection of the running loop without touching control flow.",
      "how_it_shapes_the_loop": "Reads AgentState, TokenUsage and current ToolCall on every tick and repaints; it adds zero iterations and consumes no budget, staying strictly downstream of the driver.",
      "loop_objects_touched": ["TaskDisplay", "AgentState", "TokenUsage", "ToolCall"],
      "wiring": {"inputs_from": ["agent (AgentState, step counter)", "output (record_tokens)"], "outputs_to": ["terminal frame", "operator eyes"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the TaskDisplay node opens a live HUD chip showing elapsed time, tokens in/out, and the current tool label.", "visual": "Node pulses a soft cyan on each repaint tick; the fox glyph blinks when idle and wags its tail while a tool runs."},
      "mini_scenario": "The driver enters Executing; TaskDisplay begins ticking, showing '3 tools, 12.4K tokens, 8s' as each shell_exec streams back.",
      "pitfall": "Never let the display mutate the counters it reads — it is an observer; the atomics are owned by output/agent."
    },
    {
      "id": "ui-02",
      "title": "Suppress stdout when the TUI owns the frame",
      "loop_stage": "control",
      "pattern": "single-writer-surface",
      "intent": "Prevent raw prints from corrupting the ratatui alternate screen once the TUI launches.",
      "how_it_shapes_the_loop": "set_tui_active(true) turns every output print function into a no-op, so the loop's perceive channel funnels through one renderer instead of racing writers.",
      "loop_objects_touched": ["StatusLine", "AgentState"],
      "wiring": {"inputs_from": ["output::set_tui_active", "agent events"], "outputs_to": ["ratatui frame buffer"]},
      "touch_interaction": {"gesture": "double-tap", "canvas_action": "Double-tapping the TUI root node toggles the full-screen render surface, collapsing all inline print nodes into it.", "visual": "The node expands to fill the canvas with a dimmed border; suppressed print nodes fade to 30% opacity."},
      "mini_scenario": "Operator launches the TUI; thinking() and final_answer() calls silently drop because is_tui_active() is true, and the frame stays intact.",
      "pitfall": "Forgetting set_tui_active(false) on exit leaves the loop mute — later CLI runs print nothing."
    },
    {
      "id": "ui-03",
      "title": "Render a DiffView before an edit lands",
      "loop_stage": "verify",
      "pattern": "gate-before-act",
      "intent": "Let the operator visually verify a file change on the canvas before it is committed to disk.",
      "how_it_shapes_the_loop": "The diff renderer projects old vs new content as a red/green DiffView; the loop pauses at the verify node awaiting approval, gating the act transition.",
      "loop_objects_touched": ["DiffView", "Checkpoint", "ToolCall"],
      "wiring": {"inputs_from": ["agent (file_edit ToolCall)", "session edit_history"], "outputs_to": ["operator approval", "act stage"]},
      "touch_interaction": {"gesture": "spread", "canvas_action": "Spreading two fingers over the DiffView node expands the hunk list into a scrollable full-width panel of ± lines.", "visual": "Added lines glow green, deleted lines red; an approve/reject badge pulses amber until the operator taps."},
      "mini_scenario": "Agent proposes editing config.rs; the DiffView node shows +4/-2, operator spreads to inspect, taps approve, and the edit proceeds.",
      "pitfall": "Do not render diffs while the TUI is active via stdout — display_file_diff returns early; route through the TUI widget instead."
    },
    {
      "id": "ui-04",
      "title": "Drive multi-phase progress through ProgressPhase nodes",
      "loop_stage": "control",
      "pattern": "phase-bar",
      "intent": "Give a spatial, staged read-out of a long multi-step plan.",
      "how_it_shapes_the_loop": "TaskProgress advances PhaseStatus (Pending→Active→Completed/Failed) as the driver crosses plan steps, mirroring the state machine without altering it.",
      "loop_objects_touched": ["ProgressPhase", "PhaseStatus", "AgentState"],
      "wiring": {"inputs_from": ["agent plan steps"], "outputs_to": ["progress bar frame", "ETA estimate"]},
      "touch_interaction": {"gesture": "drag", "canvas_action": "Dragging along the phase rail scrubs a preview cursor over each ProgressPhase, surfacing its status and percent.", "visual": "Active phase fills a bright-cyan bar; completed phases show a green check, failed a red cross, with a live ETA chip."},
      "mini_scenario": "A three-phase scaffold runs: 'plan' completes green, 'write' is active at 60%, 'verify' still pending — the rail reflects it live.",
      "pitfall": "overall_progress divides by phase count — an empty phase list must return 0.0, never NaN into the bar width."
    },
    {
      "id": "ui-05",
      "title": "Project the swarm as connected agent nodes",
      "loop_stage": "perceive",
      "pattern": "fanout-visualization",
      "intent": "Show a parallel team of sub-agents as live nodes so the operator sees the fanout topology.",
      "how_it_shapes_the_loop": "swarm_viz reads each sub-agent's AgentState and renders them as siblings under the coordinator; it is a perceive-only mirror of the budget-scoped fanout.",
      "loop_objects_touched": ["AgentState", "Budget", "ToolCall"],
      "wiring": {"inputs_from": ["orchestration (sub-agent states)"], "outputs_to": ["swarm canvas view"]},
      "touch_interaction": {"gesture": "pinch", "canvas_action": "Pinching zooms the whole swarm out to a constellation of agent dots, or in to a single agent's tool timeline.", "visual": "Each agent node glows by status — spinning for running, green settled for done — with edges to the coordinator pulsing on message flow."},
      "mini_scenario": "The coordinator spawns four reviewers; the swarm view shows four cyan nodes, one turning red as its budget exhausts.",
      "pitfall": "Rendering must tolerate agents appearing and vanishing mid-frame — never index a fixed-size slot array."
    },
    {
      "id": "ui-06",
      "title": "Open the command palette as a searchable node index",
      "loop_stage": "control",
      "pattern": "quick-access-menu",
      "intent": "Let the operator jump to any command/loop-node without leaving the canvas.",
      "how_it_shapes_the_loop": "The palette lists PaletteEntry items backed by the command_registry; selecting one injects a control action into the driver.",
      "loop_objects_touched": ["PaletteEntry", "StatusLine"],
      "wiring": {"inputs_from": ["input command_registry"], "outputs_to": ["agent control input"]},
      "touch_interaction": {"gesture": "long-press", "canvas_action": "Long-pressing empty canvas raises a floating palette overlay filtered by fuzzy search as you type.", "visual": "Overlay slides up with a frosted backdrop; the matched entry highlights and its category tag glows."},
      "mini_scenario": "Operator long-presses, types 'undo', and taps the /undo entry — the loop rolls back the last edit via a checkpoint.",
      "pitfall": "Palette entries must be sourced from is_known_command, not free text, or you dispatch an unregistered command."
    },
    {
      "id": "ui-07",
      "title": "Render the header WorkshopContext as the loop identity chip",
      "loop_stage": "foundation",
      "pattern": "context-banner",
      "intent": "Anchor the canvas with the active model, endpoint, and execution mode.",
      "how_it_shapes_the_loop": "WorkshopContext is read-only config projected once; it frames the loop's identity but never gates iteration.",
      "loop_objects_touched": ["WorkshopContext", "StatusLine"],
      "wiring": {"inputs_from": ["config (endpoint, model, mode)"], "outputs_to": ["header frame"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the header chip reveals the full endpoint URL and current ExecutionMode.", "visual": "Chip tints by mode — grey normal, amber auto-edit, red yolo — so risk is visible at a glance."},
      "mini_scenario": "Header shows 'Qwen3.5-122B · yolo' in red, warning the operator that confirmations are disabled.",
      "pitfall": "The mode color is a safety signal — never render yolo in a calm color."
    },
    {
      "id": "ui-08",
      "title": "Show a tool-call row per act step",
      "loop_stage": "act",
      "pattern": "act-trace",
      "intent": "Give one visible row per ToolCall as the loop acts.",
      "how_it_shapes_the_loop": "render_tool_call/success/error mirror each act transition; the row set is an append-only trace of the loop's actions.",
      "loop_objects_touched": ["ToolCall", "AgentState"],
      "wiring": {"inputs_from": ["agent tool execution"], "outputs_to": ["scrollback frame"]},
      "touch_interaction": {"gesture": "flick", "canvas_action": "Flicking up scrolls the tool-call trace; flicking a row left reveals its raw args and result.", "visual": "Running rows show a spinner, success rows a green tick, error rows a red cross with the error preview."},
      "mini_scenario": "shell_exec row spins, then flips to a green tick 'exit 0'; the next file_edit row appears below it.",
      "pitfall": "Error rows must carry the message — render_tool_error takes the error string; don't drop it."
    },
    {
      "id": "ui-09",
      "title": "Make a Selection to scope a bulk operation",
      "loop_stage": "control",
      "pattern": "select-then-act",
      "intent": "Let the operator multi-select nodes/files to batch a control action.",
      "how_it_shapes_the_loop": "A Selection set becomes the scope for a subsequent act (e.g. approve all diffs), collapsing many decisions into one control transition.",
      "loop_objects_touched": ["Selection", "DiffView", "ToolCall"],
      "wiring": {"inputs_from": ["operator taps"], "outputs_to": ["batch approval to agent"]},
      "touch_interaction": {"gesture": "long-press", "canvas_action": "Long-press enters selection mode; subsequent taps toggle nodes into the Selection with a check overlay.", "visual": "Selected nodes gain a bright ring and a running count badge; a floating action bar shows batch options."},
      "mini_scenario": "Operator long-presses a diff, taps three more, and hits 'approve all' — four edits land in one control step.",
      "pitfall": "A stale Selection after nodes change must be pruned, or a batch action targets vanished items."
    },
    {
      "id": "ui-10",
      "title": "Pin a sticky status line for budget awareness",
      "loop_stage": "perceive",
      "pattern": "always-visible-gauge",
      "intent": "Keep budget and context usage in view regardless of scroll.",
      "how_it_shapes_the_loop": "The sticky bar reads Budget/TokenUsage and context percent, projecting the loop's remaining runway without affecting it.",
      "loop_objects_touched": ["StatusLine", "Budget", "TokenUsage"],
      "wiring": {"inputs_from": ["output token counters", "agent context percent"], "outputs_to": ["pinned bottom frame"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the sticky bar expands a breakdown of prompt vs completion tokens and context fill.", "visual": "Bar fills green→amber→red as context approaches the window limit; pulses red near saturation."},
      "mini_scenario": "At 82% context the sticky bar turns amber, cueing the operator to consider a summary before the next turn.",
      "pitfall": "Context percent must clamp to [0,1] before mapping to bar color; an over-100% value is a bug upstream."
    },
    {
      "id": "ui-11",
      "title": "Animate a spinner while the loop reasons",
      "loop_stage": "reason",
      "pattern": "liveness-indicator",
      "intent": "Signal that the loop is thinking, not hung.",
      "how_it_shapes_the_loop": "The GardenSpinner ticks on a timer during the reason phase; purely cosmetic, it adds no budget and gates nothing.",
      "loop_objects_touched": ["AgentState"],
      "wiring": {"inputs_from": ["agent reasoning phase"], "outputs_to": ["inline spinner frame"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the reasoning node pauses/resumes the spinner animation for a still snapshot.", "visual": "A growing-plant glyph cycles frames; it finishes with a bloom on success or a wilt on failure."},
      "mini_scenario": "While the model streams a plan, the spinner grows; when the plan arrives it blooms and hands off to act.",
      "pitfall": "Spinner frames must be suppressed in plain/JSON mode — a spinning glyph corrupts piped output."
    },
    {
      "id": "ui-12",
      "title": "Render a checkpoint-saved badge on the timeline",
      "loop_stage": "learn",
      "pattern": "milestone-marker",
      "intent": "Mark durable rollback points the operator can return to.",
      "how_it_shapes_the_loop": "render_checkpoint_saved emits a marker when the session persists a Checkpoint, giving the loop visible undo anchors.",
      "loop_objects_touched": ["Checkpoint", "AgentState"],
      "wiring": {"inputs_from": ["session edit_history checkpoint"], "outputs_to": ["timeline frame"]},
      "touch_interaction": {"gesture": "double-tap", "canvas_action": "Double-tapping a checkpoint marker offers 'restore to here', wiring into /undo.", "visual": "A small anchor badge with the task id; glows green on save, blue when it is the active restore target."},
      "mini_scenario": "After a successful edit batch, a checkpoint marker appears; later the operator double-taps it to revert.",
      "pitfall": "The marker's task_id must match the real checkpoint or /timeline restores the wrong state."
    },
    {
      "id": "ui-13",
      "title": "Two-finger pan the loop canvas",
      "loop_stage": "perceive",
      "pattern": "spatial-navigation",
      "intent": "Move around a large graph of loop-stage nodes.",
      "how_it_shapes_the_loop": "Pure viewport control over the perceive surface; it repositions the camera, never the loop.",
      "loop_objects_touched": ["Selection", "AgentState"],
      "wiring": {"inputs_from": ["operator gesture"], "outputs_to": ["camera transform"]},
      "touch_interaction": {"gesture": "two-finger-rotate", "canvas_action": "Two-finger drag pans the canvas; adding rotation reorients the graph layout around the pivot.", "visual": "Nodes glide with momentum; off-screen active nodes leave a comet trail toward their direction."},
      "mini_scenario": "Operator two-finger pans from the coordinator toward a busy sub-agent cluster to watch its tool trace.",
      "pitfall": "Panning must not deselect the active Selection — treat camera moves as orthogonal to selection state."
    },
    {
      "id": "ui-14",
      "title": "Draw an edge to wire a downstream node",
      "loop_stage": "control",
      "pattern": "compose-by-connection",
      "intent": "Let the operator connect one loop node's output to another's input on the canvas.",
      "how_it_shapes_the_loop": "Drawing a connection declares a wiring edge that the driver later honors, shaping which component feeds which on the loop.",
      "loop_objects_touched": ["AgentState", "ToolCall"],
      "wiring": {"inputs_from": ["operator draw"], "outputs_to": ["loop graph topology"]},
      "touch_interaction": {"gesture": "draw-connection", "canvas_action": "Dragging from a node's output port to another's input port draws a live edge that snaps when released.", "visual": "The edge renders as a bright bezier that pulses in the direction of intended data flow once connected."},
      "mini_scenario": "Operator draws an edge from the diff-viewer node to the checkpoint node so every approved diff auto-checkpoints.",
      "pitfall": "Reject edges that create a cycle in the perceive→act graph, or the loop projection deadlocks."
    },
    {
      "id": "ui-15",
      "title": "Flash a warning banner on a recoverable error",
      "loop_stage": "verify",
      "pattern": "surface-then-recover",
      "intent": "Make a soft failure visible without halting the loop.",
      "how_it_shapes_the_loop": "render_warning projects a recoverable condition during verify; the loop may still transition to ErrorRecovery, the banner just informs.",
      "loop_objects_touched": ["AgentState", "ToolCall"],
      "wiring": {"inputs_from": ["agent failure_mode classifier"], "outputs_to": ["warning frame"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the warning banner expands the classified failure detail and suggested recovery.", "visual": "Amber banner slides in and auto-dismisses; the offending node briefly outlines amber."},
      "mini_scenario": "A test flakes; a warning banner notes 'retry 1/3' while the loop re-enters the act step.",
      "pitfall": "Warnings must not look like hard errors — reserve red for terminal failure, amber for recoverable."
    },
    {
      "id": "ui-16",
      "title": "Render the final answer as a settled node",
      "loop_stage": "act",
      "pattern": "terminal-frame",
      "intent": "Present the loop's conclusion clearly at Completed.",
      "how_it_shapes_the_loop": "render_assistant_response draws the final content; in streaming mode it is skipped to avoid duplicating live-streamed tokens.",
      "loop_objects_touched": ["AgentState", "Message"],
      "wiring": {"inputs_from": ["agent final content"], "outputs_to": ["answer frame"]},
      "touch_interaction": {"gesture": "spread", "canvas_action": "Spreading over the answer node expands markdown rendering with code blocks and links.", "visual": "Node settles to a calm green border on Completed; a subtle checkmark seals it."},
      "mini_scenario": "Loop reaches Completed; the answer node renders the summary with a green seal and stops ticking.",
      "pitfall": "In streaming mode do not re-render the answer — is_streaming_mode() must gate it or it prints twice."
    },
    {
      "id": "ui-17",
      "title": "Zoom a single tool node to full telemetry",
      "loop_stage": "perceive",
      "pattern": "detail-on-demand",
      "intent": "Inspect one ToolCall's args, output, and timing without leaving context.",
      "how_it_shapes_the_loop": "Read-only detail expansion; drills into a captured ToolCall record, altering nothing on the loop.",
      "loop_objects_touched": ["ToolCall", "TokenUsage"],
      "wiring": {"inputs_from": ["agent tool record"], "outputs_to": ["detail panel"]},
      "touch_interaction": {"gesture": "pinch", "canvas_action": "Pinch-in on a tool row scales it from a one-line chip to a full telemetry card.", "visual": "Card grows to show duration, exit code, and a scrollable result; edges to neighbors dim to focus it."},
      "mini_scenario": "Operator pinches a shell_exec row to read the full 200-line stdout that the summary truncated.",
      "pitfall": "The card must page long output — never render an unbounded result string into the frame."
    },
    {
      "id": "ui-18",
      "title": "Render a step banner at each plan transition",
      "loop_stage": "control",
      "pattern": "phase-announce",
      "intent": "Announce each numbered step as the driver advances the plan.",
      "how_it_shapes_the_loop": "step_start emits a banner on each step boundary, mirroring the Planning→Executing transitions in the state machine.",
      "loop_objects_touched": ["AgentState", "ProgressPhase"],
      "wiring": {"inputs_from": ["agent step counter"], "outputs_to": ["step banner frame"]},
      "touch_interaction": {"gesture": "flick", "canvas_action": "Flicking horizontally across step banners scrubs the plan timeline forward/back for review.", "visual": "Each banner slides in with the step number badge; the active step's badge glows brighter."},
      "mini_scenario": "'Step 3: run tests' banner appears as the driver crosses into the third plan step.",
      "pitfall": "Step banners are suppressed in compact/quiet/JSON — don't rely on them for machine parsing."
    },
    {
      "id": "ui-19",
      "title": "Collapse the dashboard to a summary chip",
      "loop_stage": "perceive",
      "pattern": "progressive-disclosure",
      "intent": "Reduce a busy dashboard to a glanceable chip when the operator zooms out.",
      "how_it_shapes_the_loop": "dashboard_widgets aggregate multiple loop reads (tokens, tools, phases) into one collapsible view; observation only.",
      "loop_objects_touched": ["TaskDisplay", "Budget", "ProgressPhase"],
      "wiring": {"inputs_from": ["task_display", "TaskProgress"], "outputs_to": ["dashboard frame"]},
      "touch_interaction": {"gesture": "pinch", "canvas_action": "Pinch-out collapses the dashboard grid into a single summary chip with an overall health dot.", "visual": "Widgets fold into a chip; the health dot is green while all phases progress, amber if any stalls."},
      "mini_scenario": "Operator zooms out to a chip during a long run, expanding it only when the health dot turns amber.",
      "pitfall": "The collapsed chip must still reflect failures — don't hide a failed phase behind a green dot."
    },
    {
      "id": "ui-20",
      "title": "Hand rendering back to CLI on TUI exit",
      "loop_stage": "foundation",
      "pattern": "restore-writer",
      "intent": "Cleanly return the perceive channel to inline printing when leaving the TUI.",
      "how_it_shapes_the_loop": "set_tui_active(false) re-enables output print functions so subsequent loop turns render inline; a lifecycle boundary, not a control change.",
      "loop_objects_touched": ["StatusLine", "AgentState"],
      "wiring": {"inputs_from": ["TUI teardown"], "outputs_to": ["output print functions"]},
      "touch_interaction": {"gesture": "double-tap", "canvas_action": "Double-tapping the TUI root node again dismisses the full-screen surface and restores inline node prints.", "visual": "The full-screen frame contracts back into the canvas; faded inline print nodes return to full opacity."},
      "mini_scenario": "Operator exits the TUI; is_tui_active() flips false and the next final_answer prints inline as expected.",
      "pitfall": "Restore the alternate screen and the active flag together — leaving the flag set silences all later output."
    }
  ]
}