Imported from BlackBeltTechnology/pi-agent-dashboard (
packages/client/src/components/tool-renderers/AGENTS.md). Install upstream withnpx skills add BlackBeltTechnology/pi-agent-dashboard --skill tool-renderers. Copyright stays with the author.
DOX — packages/client/src/components/tool-renderers
Files in this directory. One row per source file.
| File | Purpose |
|---|---|
AgentToolRenderer.tsx |
Renders Agent tool (pi-dashboard-subagents). Card via AgentCardShell; maps AgentDetails status → running/complete/error/stopped; toolStatus:"elided" short-circuits BEFORE details.status → elided card (spliced/backfilled rows carry stale details, must not render spinner/completed). See change: fix-lazy-history-backfill-ux (D5). Shows PromptBlock, ResultBlock, stats line, CardControls expand toggle + popout button; expanded body mounts SubagentDetailView. Exports AgentToolRenderer. Popout opens SubagentDetailView (mode=popout) inside the shell ui:dialog primitive (flush, size lg, no title; onBack→close), NOT window.open — fixes Electron/PWA/mobile popout breakage, parity with flow-agent-detail. Affordance disabled unless agentId && session resolved. See change: fix-subagent-live-detail-reliability (D4). Inline expand toggle (toggleExpand) fires subagent_resync_request on open when subagent still running + timeline empty (or map missing entry) — same requestResyncIfStale helper as popout openDetail; fixes inline expand showing "Subagent not found in this session." for running subagents after refresh/late-subscribe. See change: fix-subagent-inline-expand-resync. Open inspector pulls timeline via useSubagentResyncCadence backoff while running (no emptyTimeline precondition, one timer per subagent). See change: reduce-subagent-details-payload (F1/F3/F4). PromptBlock renders text.slice(0,1000) but sets data-copy-text={text} (full) so a transcript selection copies the whole prompt, not the DOM-capped prefix. See change: chat-copy-fidelity-intercept. The subagent error line marks Error: in --severity-error-fg and the message in --text-secondary. See change: repair-tool-error-surfaces. Popout h-[70vh] is a deliberate height PIN and STAYS. See change: fix-flush-dialog-scroll-and-close-collision. |
AskUserToolRenderer.tsx |
Renders ask_user tool. Method badge + question + options + parsed User responded: result. method:"batch" branches to AskUserBatchRenderer reconstructing per-question answers from args.questions + toolDetails.results. Exports AskUserToolRenderer. Error branch: icon --severity-error-fg, message --text-secondary (see change: repair-tool-error-surfaces). |
BashToolRenderer.tsx |
Renders bash tool. Shows $ command + timeout; strips ANSI via stripAnsi then LinkifiedText in result block; ToolResultImages for image output. Exports BashToolRenderer. |
CtxToolRenderer.tsx |
Single React renderer registered for all context-mode ctx_* MCP tools. Calls parseCtxResult(toolName, result, isError), switches body layout on parsed kind. Shared header chip (⚙ shell · N lines, ▦ N cmds · N sections · N queries, 🔍 N queries, 🗂 N sections, 🌐 host · N sections) + error card. Body branches: execute/execute_file (args.code as syntax-highlighted CodeBlock + stdout + intent preview list), batch (Indexed Sections collapsible + per-query accordions), search (per-query accordions, "No results found" badge), index/fetch (compact one-liner + url link), insight (dashboard URL link button + log), error (severity-token card: --severity-error-{bg,border} chrome + --severity-error-fg label, body in --text-secondary on --bg-code; a structured runtime error renders command via CodeBlock, an exit <n> badge and labelled stdout/stderr sections, else falls back to the flat message; each stream section passes through truncateOutputForDisplay (last 200 lines + «N earlier lines hidden», same helper as BashOutputCard, test-plan #X3); collapsible Received arguments), raw fallback (LinkifiedText, no JSON-args dump). Accordions default collapsed. Body max-h-80 overflow-auto. Never renders JSON.stringify(args). See change: add-ctx-tool-renderer. |
EditToolRenderer.tsx |
Renders edit tool. Diff priority: toolDetails.diff → oldText/newText → edits[] text edits → hashline replace/append/prepend summaries. Mobile uses HomegrownDiff, desktop RichDiff. OpenFileButton for path. Exports EditToolRenderer. RichDiff lazy behind a renderer-local Suspense; mobile path stays eager. See change: add-lazy-terminal-diff-bootstrap. Lazy RichDiff wrapped in ErrorBoundary + Suspense: a rejected chunk import() stays contained in the card instead of escalating to the app-level boundary. |
FileLink.tsx |
Inline clickable file ref inside tool output. Click routes via shared useFileOpenRouting hook (no external-editor branch). Title carries resolved abs path. Inline-only styling, preserves selection. See change: linkify-tool-output. Absolute tokens (token.absolute) skip cwd join — pass path through unchanged. Click routes via shared useFileOpenRouting hook. See change: unify-file-link-openability. <button> gets draggable={false} + inline userSelect: "text" so click-drag extends text selection (highlight + Ctrl+C / right-click Copy work); plain click still opens. See change: selectable-tool-output-links. Preview state hoisted to FilePreviewProvider; renders own FilePreviewOverlay only on no-provider fallback (!hostManaged && previewTarget). See change: fix-file-preview-survives-message-churn. Resolution now routes through resolveLinkOrigin (lib/link-origin.ts): worktree session re-roots an absolute parent-checkout path onto the worktree's own tree for tooltip + open + preview target; openFile gets the re-rooted target, not the raw token. See change: fix-worktree-link-origin. Routes rel-path click through openInSplit when split context present; else preview overlay. External-editor (openEditor) branch removed; title always Preview <path>. See change: split-editor-workspace, remove-external-editor-integration. Click now LAZILY resolves via resolveFileMention(cwd, path) (lib/resolve-mention-api): server-resolved abs path opened DIRECTLY across preview overlay AND split (G2 — split routes through resolve too, absolute tokens skip split), no resolveLinkOrigin on the resolved path (D4, no double re-root). resolved:null → inline not-found affordance (data-not-found/aria-disabled/line-through, NO open, G1); resolve request FAILURE → D5 fallback to client-side resolveLinkOrigin open (rejection caught). resolveLinkOrigin survives for tooltip + fallback only. Sends token path (normalized), not verbatim text (D1). See change: server-side-file-mention-resolution. Link colour now text-[var(--link)] (mode-aware) instead of the raw text-blue-400, which measured 1.80–2.52:1 on --bg-code in all 9 light themes; gated by severity-contrast.spec.ts #F8. See change: repair-tool-error-surfaces. |
GenericToolRenderer.tsx |
Fallback renderer. Renders raw JSON args verbatim + LinkifiedText result block + ToolResultImages. Exports GenericToolRenderer. |
index.ts |
Barrel. Re-exports getToolRenderer, registerToolRenderer from registry.js; types ToolContext, ToolRendererProps, ToolRenderer from types.js. |
lang-detect.ts |
Maps file extension/basename → Prism language id via EXT_MAP. Exports detectLanguage(filePath). |
LinkifiedText.tsx |
Renders tokenised tool output. Memoises tokenize(text). Maps tokens → <UrlLink> / <FileLink> / React.Fragment. Wraps in ErrorBoundary with plain-text fallback. See change: linkify-tool-output. |
make-tool-context.tsx |
makeToolContext() (the only production ToolContext builder) + withDefaultFileLink() for embedder contexts. Attaches the fileLink renderer that gates markdown linkification; hand-built contexts silently lose it. See change: cleanup-import-cycles. |
OpenFileButton.tsx |
Plain button opening a file in the internal Monaco pane. Renders null only when no cwd or no filePath. Click routes via shared useFileOpenRouting hook. Same fallback pattern as FileLink — overlay rendered locally only when no provider. Body click → openInSplit when split context present (this session); else deep-link buildEditorUrl(sessionId, file, line); else preview overlay. Native-editor caret dropdown (openEditor) removed. See change: add-internal-monaco-editor-pane, split-editor-workspace, remove-external-editor-integration. |
parse-ctx-result.fixtures.ts |
Auto-captured real ctx_* result texts from session JSONL. Exports ctxFixtures object (execute_stdout, execute_intent, search, search_no_results, index, fetch, insight, error variants, etc.). Source of truth for parse-ctx-result.test.ts. err_runtime_fenced / err_runtime_plain cover the structured vs. flat runtime-error paths (see change: repair-tool-error-surfaces). |
parse-ctx-result.ts |
Pure parser, no React. Exports parseCtxResult(toolName, result, isError) → CtxResult typed union (kinds: error, execute, batch, search, index, fetch, insight, raw) + stripNoise + parseExecutionShape. A runtime error additionally carries the context-mode execution shape when its body has one — preamble / command / language / exitCode / stdout / stderr (CtxExecutionShape, every field optional; preamble keeps prose above the dump so no text is dropped); a plain-sentence body leaves them undefined and the renderer falls back to flat message. Extraction never throws on a partial body. A stdout:/stderr: header opens a section only when preceded by a blank line (or start-of-body) and not already opened, so a line reading stderr: INSIDE stdout's own output cannot split the section (test-plan #E9). (see change: repair-tool-error-surfaces). Strips leading ⚠️ context-mode v… upgrade banner. Classifies errors validation/timeout/runtime; validation captures Received arguments: JSON into receivedArgs. Per-tool parse arms return { kind: "raw", text } on header miss; never throws. Fixtures in parse-ctx-result.fixtures.ts (real session captures); tests in parse-ctx-result.test.ts. See change: add-ctx-tool-renderer. |
ReadToolRenderer.tsx |
Renders read tool. Path + offset/limit subtitle + OpenFileButton; SyntaxHighlighter with detectLanguage theme via getSyntaxTheme; ToolResultImages for image reads. Exports ReadToolRenderer. |
registry.ts |
Tool renderer registry. Map of tool name → renderer (read/edit/write/bash/Agent/ask_user/ctx_*). getToolRenderer falls back to CtxToolRenderer for unmapped ctx_* names, else GenericToolRenderer. Exports getToolRenderer, registerToolRenderer. |
ToolResultImages.tsx |
Shared tool-renderer component. Renders inlined type:"image" tool-result blocks as data: URL thumbnails (max 512px) + ImageLightbox. Used by ReadToolRenderer, BashToolRenderer, GenericToolRenderer. See change: inline-agent-screenshot-artifacts. |
types.ts |
Defines ToolContext (cwd, sessionId, session, send?; editors field removed — change: remove-external-editor-integration), ToolRendererProps (toolName, args, status, result, images, context, toolDetails), ToolRenderer component type. send?: (msg: BrowserToServerMessage) => void lets renderers message the server (e.g. subagent resync request); populated in App.tsx toolContext. See change: fix-subagent-live-detail-reliability. Adds optional fileLink?: FileLinkRenderer — injected so MarkdownContent depends on the contract, not the concrete FileLink (cut the upward edge). Optional by necessity: ToolContext is re-exported from chat-embed as a published surface. Module stays value-free. See change: cleanup-import-cycles. session NARROWED from SessionState to { subagents: SessionState["subagents"] } — the only per-session field any renderer reads — so a whole-SessionState field cannot advertise fresh data while silently freezing between subagents changes. Still optional (undefined when nothing selected); BREAKING for out-of-repo renderers reading another field. See change: fix-long-session-ux-degradation (D7). |
UrlLink.tsx |
Thin <a target="_blank" rel="noopener noreferrer"> wrapper. Rejects non-http(s):// hrefs (defense in depth even if tokenizer bypassed). See change: linkify-tool-output. <a> gets draggable={false} so drag-select crosses link instead of starting native link-drag. See change: selectable-tool-output-links. onClick→useLoopbackLinkOpen(): plain click of a loopback URL opens the internal live-server split viewer; LAN/external + modifier/middle-click keep the tab. See change: open-loopback-links-in-split-viewer. Link colour now text-[var(--link)] (mode-aware) instead of the raw text-blue-400, which measured 1.80–2.52:1 on --bg-code in all 9 light themes; gated by severity-contrast.spec.ts #F8. See change: repair-tool-error-surfaces. |
useFileOpenRouting.ts |
Shared file-open routing hook. Used by FileLink + OpenFileButton. Routes clicks to the FilePreviewOverlay (external-editor openEditor branch removed — change: remove-external-editor-integration). Reads FilePreviewContext via useContext (nullable). Provider present → hostManaged:true, dispatch to ctx.open (hoisted overlay). Provider absent → leaf-local useState fallback, exposes hostManaged/previewTarget/closePreview. No longer owns unconditional preview state. See change: fix-file-preview-survives-message-churn. |
WriteToolRenderer.tsx |
Renders write tool. Path + OpenFileButton; SyntaxHighlighter with detectLanguage for args.content; running placeholder + result line. Exports WriteToolRenderer. |