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}
-
+
);
}