feat(desktop): a Changes tab's diff may be split side by side, its changed words marked
What was asked
Look at ReviewStage/stage-cli and ReviewStage/stage, and improve Luke's Changes and/or File viewer tabs with some of their viewing options.
ReviewStage/stage doesn't resolve from here: either it doesn't exist under that name or the Luke App isn't installed on it. So this PR draws on stage-cli alone, whose diff settings form (packages/web/src/components/diff/diff-settings-form.tsx) offers Layout (Unified / Split), Inline diffs (word / char / none), Indicators, Backgrounds, Wrapping, Line numbers, and font and theme choices.
What changed
Luke's Changes tab already had stage-cli's Wrapping and Line numbers, through the File tabs' two settings. This PR adds the two settings that change how a diff reads most, to the Changes tab's ⋯ menu, under Line numbers and Word wrap:
- Split view. Each hunk is drawn side by side: removed lines on the left, the added lines that follow them on the right, row for row. Context lines appear on both sides, and each side numbers its own lines. An empty side is hatched. Both columns sit in one CSS grid, so a row's two sides stay level when either one wraps. With wrap off, each column is as wide as its longest line (and never narrower than half the tab), and the two scroll sideways together.
- Highlight changed words (on by default). When a removed line is paired with an added line, the two are diffed word by word (jsdiff's
diffWordsWithSpace; jsdiff is already a dependency). The words only one side has get a deeper tint, as GitHub and stage-cli's "Word" inline diff show them. A line rewritten with no words in common keeps a plain whole-line tint. Lines over 1,000 characters are skipped.
Both choices are saved in file-view-settings.ts alongside the existing two. The new keys are optional in the stored schema, so a choice saved before this PR keeps its line-number and wrap settings and picks up the defaults for the new ones. Without this, the stricter decode would have reset everything.
changes-model.ts:splitRowsandmarkChangedWords, which share one pass that groups a hunk into runs. A git\ No newlinenote is never paired with a line: it stands under the line it refers to, with nothing beside it (fixed after Devin Review flagged it).agent-changes-tab.tsx:FileDiffdraws unified or split rows and marks changed words with<mark>. Find-in-tab already treatsMARKas inline, so matches still run across it.file-menu.tsx:LinesMenutakes optionallooks, extra checkbox settings for that bar only. The File tab's menu is unchanged.planning.css: the split grid, the hatched empty side, and the changed-word tints.
Not in this PR: stage-cli's indicator styles, font and theme pickers, viewed-file marks, and keyboard file navigation. Any of these could come next.
Evidence
pnpm exec vitest run apps/desktop/src/renderer/planning/changes-model.test.ts apps/desktop/src/renderer/planning/agent-changes-tab.test.ts apps/desktop/src/renderer/planning/pull-request-people.test.ts: 17 passed.changes-model.test.ts(new) covers split pairing, line numbers per side, git's note (both after context and inside a longer run), changed-word spans, and a full rewrite left whole.agent-changes-tab.test.ts(new, jsdom) mounts the tab. It checks that the diff starts unified with words marked; that choosing Split view in the ⋯ draws the sides as expected and the next tab keeps it; that turning highlights off removes the marks; and that a choice saved in the old two-field format keeps its numbers and wrap.
pnpm check:affected: lint, knip, types, and discarded-effect are clean. The only failure issrc/main/app-menu.test.ts, which can't load Electron's binary in the sandbox (Electron failed to install correctly … fetch failed). That's the environment, not this change; CI runs it.- Not run on a Mac. There's no screenshot from the app, only a preview rendered from this branch's
changes-model.tsandplanning.cssrules.