From a8eb13e828b03508fdd59251c9c95143484fb374 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 21 Apr 2026 19:21:00 -0500 Subject: [PATCH] fix(tui): dedupe inline diffs, strip CLI review-diff header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the prior inline-diff fix, the gateway still prepends a literal " ┊ review diff" line to inline_diff (it's terminal chrome written by `_emit_inline_diff`). Wrapping that in a ```diff fence left that header inside the code block. The agent also often narrates its own edit in a second fenced diff, so the assistant message ended up stacking two diff blocks for the same change. - Strip the leading "┊ review diff" header from queued inline diffs before fencing. - Skip appending the fenced diff entirely when the assistant already wrote its own ```diff (or ```patch) fence. Keeps the single-surface diff UX even when the agent is chatty. --- .../createGatewayEventHandler.test.ts | 39 +++++++++++++++++++ ui-tui/src/app/turnController.ts | 16 +++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts index 22a6b281f..e242e5bdd 100644 --- a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +++ b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts @@ -195,6 +195,45 @@ describe('createGatewayEventHandler', () => { expect((appended[0]?.text.match(/```diff/g) ?? []).length).toBe(1) }) + it('strips the CLI "┊ review diff" header from queued inline diffs', () => { + const appended: Msg[] = [] + const onEvent = createGatewayEventHandler(buildCtx(appended)) + const raw = ' \u001b[33m┊ review diff\u001b[0m\n--- a/foo.ts\n+++ b/foo.ts\n@@\n-old\n+new' + + onEvent({ + payload: { inline_diff: raw, summary: 'patched', tool_id: 'tool-1' }, + type: 'tool.complete' + } as any) + onEvent({ + payload: { text: 'done' }, + type: 'message.complete' + } as any) + + expect(appended).toHaveLength(1) + expect(appended[0]?.text).not.toContain('┊ review diff') + expect(appended[0]?.text).toContain('--- a/foo.ts') + }) + + it('suppresses inline_diff when assistant already wrote a diff fence', () => { + const appended: Msg[] = [] + const onEvent = createGatewayEventHandler(buildCtx(appended)) + const inlineDiff = '--- a/foo.ts\n+++ b/foo.ts\n@@\n-old\n+new' + const assistantText = 'Done. Clean swap:\n\n```diff\n-old\n+new\n```' + + onEvent({ + payload: { inline_diff: inlineDiff, summary: 'patched', tool_id: 'tool-1' }, + type: 'tool.complete' + } as any) + onEvent({ + payload: { text: assistantText }, + type: 'message.complete' + } as any) + + expect(appended).toHaveLength(1) + expect(appended[0]?.text).toBe(assistantText) + expect((appended[0]?.text.match(/```diff/g) ?? []).length).toBe(1) + }) + it('keeps tool trail terse when inline_diff is present', () => { const appended: Msg[] = [] const onEvent = createGatewayEventHandler(buildCtx(appended)) diff --git a/ui-tui/src/app/turnController.ts b/ui-tui/src/app/turnController.ts index 005eed4bc..bf9d2926c 100644 --- a/ui-tui/src/app/turnController.ts +++ b/ui-tui/src/app/turnController.ts @@ -185,7 +185,13 @@ class TurnController { } queueInlineDiff(diffText: string) { - const text = diffText.trim() + // Strip CLI chrome the gateway emits before the unified diff (e.g. a + // leading "┊ review diff" header written by `_emit_inline_diff` for the + // terminal printer). That header only makes sense as stdout dressing, + // not inside a markdown ```diff block. + const text = diffText + .replace(/^\s*┊[^\n]*\n?/, '') + .trim() if (!text || this.pendingInlineDiffs.includes(text)) { return @@ -239,7 +245,13 @@ class TurnController { const rawText = (payload.rendered ?? payload.text ?? this.bufRef).trimStart() const split = splitReasoning(rawText) const finalText = split.text - const remainingInlineDiffs = this.pendingInlineDiffs.filter(diff => !finalText.includes(diff)) + // Skip appending if the assistant already narrated the diff inside a + // markdown fence of its own — otherwise we render two stacked diff + // blocks for the same edit. + const assistantAlreadyHasDiff = /```(?:diff|patch)\b/i.test(finalText) + const remainingInlineDiffs = assistantAlreadyHasDiff + ? [] + : this.pendingInlineDiffs.filter(diff => !finalText.includes(diff)) const inlineDiffBlock = remainingInlineDiffs.length ? `\`\`\`diff\n${remainingInlineDiffs.join('\n\n')}\n\`\`\`` : ''