gui: fix prose-after-fence overwriting finalized code segment

In incrementalAppend(), when a prose line follows a closed code fence,
the code checked only whether the last segment was type Text (which code
fences also are) and then tried to append. Since the finalized fence HTML
ends in </pre> not </div>, it fell into the 'shouldn't happen' branch and
overwrote the segment with escapeForHtml(proseLines) — destroying the
rendered tool output.

Tool blocks are always shaped:
  ```
  <output>
  ```
  <blank>
  <blank>

so the trailing blank lines triggered this on every tool call, leaving
only <div>\n</div> in renderedHtml.

Fix: gate extension on the segment actually being an in-progress prose
segment (trailing </div>). Otherwise start a new prose segment.

Verified: tool blocks now render 'hi\nexit: 0', file_glob results, and
syntax-highlighted diffs correctly.
This commit is contained in:
Levi Neely 2026-08-09 12:14:04 +02:00
parent ed74d421c3
commit b6a7d93d0e
1 changed files with 13 additions and 12 deletions

View File

@ -538,20 +538,21 @@ void ChatBlockModel::incrementalAppend(int blockIndex, const QString &line)
const QString defaultColor = getDefaultTextColor(); const QString defaultColor = getDefaultTextColor();
const QString escapedLine = line.toHtmlEscaped(); const QString escapedLine = line.toHtmlEscaped();
if (!block.segments.isEmpty() && block.segments.last().type == BlockSegment::Text // Only extend the last segment if it is an in-progress prose segment
&& !rs.inFence) { // (identified by its trailing </div>). A finalized code fence segment
// Append to existing prose segment — strip closing </div>, add newline + content // ends in </pre> and must not be overwritten.
bool canExtend = !block.segments.isEmpty()
&& block.segments.last().type == BlockSegment::Text
&& block.segments.last().html.endsWith(QStringLiteral("</div>"));
if (canExtend) {
QString &segHtml = block.segments.last().html; QString &segHtml = block.segments.last().html;
// Remove trailing </div> to append segHtml.chop(6); // remove trailing </div>
if (segHtml.endsWith(QStringLiteral("</div>"))) { segHtml += '\n' + escapedLine + QStringLiteral("</div>");
segHtml.chop(6);
segHtml += '\n' + escapedLine + QStringLiteral("</div>");
} else {
// Shouldn't happen, but fall back to full rebuild
segHtml = escapeForHtml(rs.proseLines.join('\n'));
}
} else { } else {
// Start a new text segment // Start a new prose segment
rs.proseLines.clear();
rs.proseLines.append(line);
BlockSegment seg; BlockSegment seg;
seg.type = BlockSegment::Text; seg.type = BlockSegment::Text;
seg.html = QStringLiteral("<div style=\"white-space:pre-wrap;color:") + defaultColor + QStringLiteral("\">") seg.html = QStringLiteral("<div style=\"white-space:pre-wrap;color:") + defaultColor + QStringLiteral("\">")