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;