From cf6d25f1bce0b1deeb611fafe2355940ab8a719d Mon Sep 17 00:00:00 2001 From: roboomp Date: Sat, 11 Jul 2026 00:52:32 +0000 Subject: [PATCH] fix(tui): preserved blank streamed diff rows Preserved completed blank rows in transient diff rendering, including leading blank code rows and blank rows appended through the streaming diff cache. Added regression coverage for both blank-row scrollback layout cases from PR review. --- .../transcript-streaming-commit-repro.test.ts | 29 +++++++++++++++++++ packages/tui/src/components/markdown.ts | 12 +++----- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/packages/coding-agent/test/transcript-streaming-commit-repro.test.ts b/packages/coding-agent/test/transcript-streaming-commit-repro.test.ts index c92ca031b..2fd6f8b46 100644 --- a/packages/coding-agent/test/transcript-streaming-commit-repro.test.ts +++ b/packages/coding-agent/test/transcript-streaming-commit-repro.test.ts @@ -190,4 +190,33 @@ describe("transcript streaming commit (assistant text)", () => { await term.flush(); } }); + + it("renders a leading completed blank row in an open streamed diff body", () => { + const block = new StreamingMarkdownBlock(); + block.setStreamingText("```diff\n\n+next"); + + const rows = block.render(40).map(row => Bun.stripANSI(row).trimEnd()); + const fenceRow = rows.indexOf("```diff"); + + expect(fenceRow).toBeGreaterThanOrEqual(0); + expect(rows.slice(fenceRow, fenceRow + 4)).toEqual(["```diff", "", " +next", "```"]); + }); + + it("appends a completed blank row when the streamed diff line cache grows", () => { + const block = new StreamingMarkdownBlock(); + + block.setStreamingText("```diff\n+a\n+streaming"); + const initialRows = block.render(40).map(row => Bun.stripANSI(row).trimEnd()); + const initialCodeRow = initialRows.indexOf(" +a"); + expect(initialCodeRow).toBeGreaterThanOrEqual(0); + expect(initialRows.slice(initialCodeRow, initialCodeRow + 2)).toEqual([" +a", " +streaming"]); + + block.setStreamingText("```diff\n+a\n\n+streaming"); + + const rows = block.render(40).map(row => Bun.stripANSI(row).trimEnd()); + const codeRow = rows.indexOf(" +a"); + + expect(codeRow).toBeGreaterThanOrEqual(0); + expect(rows.slice(codeRow, codeRow + 3)).toEqual([" +a", "", " +streaming"]); + }); }); diff --git a/packages/tui/src/components/markdown.ts b/packages/tui/src/components/markdown.ts index 6f1ebd40d..9d87eac21 100644 --- a/packages/tui/src/components/markdown.ts +++ b/packages/tui/src/components/markdown.ts @@ -1425,10 +1425,8 @@ export class Markdown implements Component { const lineEnd = tokenText.lastIndexOf("\n"); if (closedFence || lineEnd >= 0) { const completedText = closedFence ? tokenText : tokenText.slice(0, lineEnd); - if (closedFence || completedText.length > 0) { - for (const hlLine of this.#highlightStreamingDiffLines(completedText, lang)) { - bodyLines.push(`${codeIndent}${hlLine}`); - } + for (const hlLine of this.#highlightStreamingDiffLines(completedText, lang)) { + bodyLines.push(`${codeIndent}${hlLine}`); } if (!closedFence) { for (const codeLine of tokenText.slice(lineEnd + 1).split("\n")) { @@ -1502,10 +1500,8 @@ export class Markdown implements Component { if (completedText.length === cache.text.length) return cache.lines; const lines = cache.lines.slice(); const addedText = completedText.slice(cache.text.length === 0 ? 0 : cache.text.length + 1); - if (addedText.length > 0) { - for (const codeLine of addedText.split("\n")) { - lines.push(...highlightCode(codeLine, lang)); - } + for (const codeLine of addedText.split("\n")) { + lines.push(...highlightCode(codeLine, lang)); } this.#streamingDiffLineCache = { ...signature, lang, text: completedText, lines }; return lines;