From b1bf09970fc7935ab6e2de1463a2540bfbb0f0db Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 11 Mar 2026 21:51:23 -0500 Subject: [PATCH] Render transcript markdown and fold command stdout Co-Authored-By: Paperclip --- .../transcript/RunTranscriptView.test.tsx | 60 +++++++++++++++++++ .../transcript/RunTranscriptView.tsx | 46 +++++++------- 2 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 ui/src/components/transcript/RunTranscriptView.test.tsx diff --git a/ui/src/components/transcript/RunTranscriptView.test.tsx b/ui/src/components/transcript/RunTranscriptView.test.tsx new file mode 100644 index 00000000..580d7a20 --- /dev/null +++ b/ui/src/components/transcript/RunTranscriptView.test.tsx @@ -0,0 +1,60 @@ +// @vitest-environment node + +import { describe, expect, it } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import type { TranscriptEntry } from "../../adapters"; +import { ThemeProvider } from "../../context/ThemeContext"; +import { RunTranscriptView, normalizeTranscript } from "./RunTranscriptView"; + +describe("RunTranscriptView", () => { + it("keeps running command stdout inside the command fold instead of a standalone stdout block", () => { + const entries: TranscriptEntry[] = [ + { + kind: "tool_call", + ts: "2026-03-12T00:00:00.000Z", + name: "command_execution", + toolUseId: "cmd_1", + input: { command: "ls -la" }, + }, + { + kind: "stdout", + ts: "2026-03-12T00:00:01.000Z", + text: "file-a\nfile-b", + }, + ]; + + const blocks = normalizeTranscript(entries, false); + + expect(blocks).toHaveLength(1); + expect(blocks[0]).toMatchObject({ + type: "command_group", + items: [{ result: "file-a\nfile-b", status: "running" }], + }); + }); + + it("renders assistant and thinking content as markdown in compact mode", () => { + const html = renderToStaticMarkup( + + + , + ); + + expect(html).toContain("world"); + expect(html).toContain("
  • first
  • "); + expect(html).toContain("
  • second
  • "); + }); +}); diff --git a/ui/src/components/transcript/RunTranscriptView.tsx b/ui/src/components/transcript/RunTranscriptView.tsx index 762f8df4..7bc24c97 100644 --- a/ui/src/components/transcript/RunTranscriptView.tsx +++ b/ui/src/components/transcript/RunTranscriptView.tsx @@ -98,16 +98,6 @@ function truncate(value: string, max: number): string { return value.length > max ? `${value.slice(0, Math.max(0, max - 1))}…` : value; } -function stripMarkdown(value: string): string { - return compactWhitespace( - value - .replace(/```[\s\S]*?```/g, " code ") - .replace(/`([^`]+)`/g, "$1") - .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") - .replace(/[*_#>-]/g, " "), - ); -} - function humanizeLabel(value: string): string { return value .replace(/[_-]+/g, " ") @@ -329,7 +319,7 @@ function groupCommandBlocks(blocks: TranscriptBlock[]): TranscriptBlock[] { return grouped; } -function normalizeTranscript(entries: TranscriptEntry[], streaming: boolean): TranscriptBlock[] { +export function normalizeTranscript(entries: TranscriptEntry[], streaming: boolean): TranscriptBlock[] { const blocks: TranscriptBlock[] = []; const pendingToolBlocks = new Map>(); const pendingActivityBlocks = new Map>(); @@ -486,6 +476,17 @@ function normalizeTranscript(entries: TranscriptEntry[], streaming: boolean): Tr continue; } + const activeCommandBlock = [...blocks].reverse().find( + (block): block is Extract => + block.type === "tool" && block.status === "running" && isCommandTool(block.name, block.input), + ); + if (activeCommandBlock) { + activeCommandBlock.result = activeCommandBlock.result + ? `${activeCommandBlock.result}${activeCommandBlock.result.endsWith("\n") || entry.text.startsWith("\n") ? entry.text : `\n${entry.text}`}` + : entry.text; + continue; + } + if (previous?.type === "stdout") { previous.text += previous.text.endsWith("\n") || entry.text.startsWith("\n") ? entry.text : `\n${entry.text}`; previous.ts = entry.ts; @@ -519,15 +520,14 @@ function TranscriptMessageBlock({ User )} - {compact ? ( -
    - {truncate(stripMarkdown(block.text), 360)} -
    - ) : ( - - {block.text} - - )} + *:first-child]:mt-0 [&>*:last-child]:mb-0", + compact ? "text-xs leading-5 text-foreground/85" : "text-sm", + )} + > + {block.text} + {block.streaming && (
    @@ -549,14 +549,14 @@ function TranscriptThinkingBlock({ density: TranscriptDensity; }) { return ( -
    *:first-child]:mt-0 [&>*:last-child]:mb-0", density === "compact" ? "text-[11px] leading-5" : "text-sm leading-6", )} > {block.text} -
    + ); }