fix(coding-agent): preserve indentation in rendered diffs (#9274)

## Problem

The edit tool's intra-line renderer can drop indentation from a removed
line when text is inserted before otherwise unchanged content.

For example, this input:

```diff
-83   await commitRecordingChunk({
+83   const result = await commitRecordingChunk({
```

was rendered as:

```diff
-83 await commitRecordingChunk({
+83   const result = await commitRecordingChunk({
```

`diffWords()` groups the shared leading spaces into the added segment
(`" const result = "`). The existing highlighting logic moved those
spaces outside inverse styling, but only appended them to the added
line.

## Solution

Extract whitespace shared by both lines before calling `diffWords()`,
then initialize both rendered lines with that prefix. This preserves
indentation without changing the existing word-level highlighting
behavior. The existing handling for whitespace that belongs to only one
side remains unchanged.

Added regression coverage for:

- insertion before indented content;
- replacement within indented content;
- keeping shared indentation outside inverse highlighting.

## Validation

- `npm run check`
- `./test.sh`
- targeted `packages/coding-agent/test/diff.test.ts` (3 tests)
This commit is contained in:
Danila Poyarkov 2026-11-04 13:17:34 +04:00 • committed by GitHub
parent 66c6556771
commit dca1f55527
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 48 additions and 5 deletions

View file

@ -0,0 +1,35 @@
import { beforeAll, describe, expect, it } from "vitest";
import { renderDiff } from "../src/modes/interactive/components/diff.ts";
import { initTheme, theme } from "../src/modes/interactive/theme/theme.ts";
import { stripAnsi } from "../src/utils/ansi.ts";
describe("renderDiff", () => {
beforeAll(() => initTheme("dark"));
it.each([
{
name: "text is inserted at the start of a line",
diff: "-83 await commitRecordingChunk({\n+83 const result = await commitRecordingChunk({",
expected: ["-83 await commitRecordingChunk({", "+83 const result = await commitRecordingChunk({"],
},
{
name: "indented text is replaced",
diff: "-12 const value = oldValue\n+12 const value = newValue",
expected: ["-12 const value = oldValue", "+12 const value = newValue"],
},
])("preserves indentation when $name", ({ diff, expected }) => {
const rendered = renderDiff(diff);
expect(stripAnsi(rendered).split("\n")).toEqual(expected);
});
it("does not highlight shared indentation as an insertion", () => {
const rendered = renderDiff(
"-83 await commitRecordingChunk({\n+83 const result = await commitRecordingChunk({",
);
const inverseInsertion = theme.inverse("const result = ");
expect(rendered.indexOf("+83 ")).toBeLessThan(rendered.indexOf(inverseInsertion));
expect(rendered).toContain(inverseInsertion);
});
});