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.
This commit is contained in:
roboomp
2026-07-11 00:52:32 +00:00
parent 936e83e3de
commit cf6d25f1bc
2 changed files with 33 additions and 8 deletions
@@ -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"]);
});
});
+4 -8
View File
@@ -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;