singularity-forge/docs-internal/pi-ui-tui/05-persistent-ui-elements.md
Lex Christopherson d20d5e8fb5 docs: add Mintlify documentation site and move internal docs
Add a proper public-facing documentation site using Mintlify with 19 MDX
pages covering getting started, auto mode, commands, configuration, and
all user-facing features. Move internal/SDK documentation (Pi SDK, TUI,
context & hooks, research notes, ADRs) to docs-internal/ since they
should not be part of the public documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:54:41 -06:00

3.4 KiB

Persistent UI Elements

These stay on screen until explicitly cleared:

// Set (persists until cleared or overwritten)
ctx.ui.setStatus("my-ext", "● Active");
ctx.ui.setStatus("my-ext", ctx.ui.theme.fg("accent", "● Mode: Plan"));

// Clear
ctx.ui.setStatus("my-ext", undefined);

Multiple extensions can set independent status entries. They appear in the footer.

Widgets (Above/Below Editor)

// Simple string array (above editor, default)
ctx.ui.setWidget("my-widget", ["Line 1", "Line 2", "Line 3"]);

// Below editor
ctx.ui.setWidget("my-widget", ["Below the editor!"], { placement: "belowEditor" });

// With theme (component factory)
ctx.ui.setWidget("my-widget", (_tui, theme) => {
  const lines = items.map(item =>
    item.done
      ? theme.fg("success", "✓ ") + theme.fg("muted", theme.strikethrough(item.text))
      : theme.fg("dim", "○ ") + item.text
  );
  return {
    render: () => lines,
    invalidate: () => {},
  };
});

// Clear
ctx.ui.setWidget("my-widget", undefined);

Working Message (During Streaming)

ctx.ui.setWorkingMessage("Analyzing code structure...");
ctx.ui.setWorkingMessage();  // Restore default
ctx.ui.setFooter((tui, theme, footerData) => ({
  invalidate() {},
  render(width: number): string[] {
    const branch = footerData.getGitBranch();  // Not accessible elsewhere!
    const statuses = footerData.getExtensionStatuses();  // All setStatus values
    const left = theme.fg("dim", `${ctx.model?.id || "no-model"}`);
    const right = theme.fg("dim", branch || "no git");
    const pad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right)));
    return [truncateToWidth(left + pad + right, width)];
  },
  // Reactive: re-render when branch changes
  dispose: footerData.onBranchChange(() => tui.requestRender()),
}));

// Restore default
ctx.ui.setFooter(undefined);

footerData provides:

  • getGitBranch(): string | null — current git branch (not accessible through any other API)
  • getExtensionStatuses(): ReadonlyMap<string, string> — all setStatus values
  • onBranchChange(callback): () => void — subscribe to branch changes, returns dispose function

Custom Header

ctx.ui.setHeader((tui, theme) => ({
  render(width: number): string[] {
    return [theme.fg("accent", theme.bold("My Custom Header"))];
  },
  invalidate() {},
}));

Editor Control

// Set editor text
ctx.ui.setEditorText("Prefilled text for the user");

// Get current editor text
const current = ctx.ui.getEditorText();

// Paste into editor (triggers paste handling, including collapse for large content)
ctx.ui.pasteToEditor("pasted content");

// Tool output expansion
const wasExpanded = ctx.ui.getToolsExpanded();
ctx.ui.setToolsExpanded(true);   // Expand all
ctx.ui.setToolsExpanded(false);  // Collapse all

// Terminal title
ctx.ui.setTitle("pi - my project");

Theme Management

const themes = ctx.ui.getAllThemes();       // [{ name: "dark", path: ... }, ...]
const lightTheme = ctx.ui.getTheme("light");  // Load without switching
const result = ctx.ui.setTheme("light");      // Switch by name
if (!result.success) ctx.ui.notify(result.error!, "error");
ctx.ui.setTheme(lightTheme!);               // Switch by Theme object
ctx.ui.theme.fg("accent", "styled text");    // Access current theme