feat: author-colored changed/added blocks in review; unchanged blocks render plain

This commit is contained in:
BenStullsBets
2026-06-26 13:54:24 -07:00
parent 0ad040711f
commit 08557827da
2 changed files with 99 additions and 54 deletions
+33 -15
View File
@@ -841,18 +841,26 @@ function renderReviewOp(
render: (src: string) => string,
colored: (raw: string) => string,
src: string,
changedHtml?: string,
): string {
// removed blocks and any changed block (atomic fences diffed whole; non-atomic prose
// word-merged) render via renderOp — no author sentinels (deletions neutral, spec §6.7).
// `src` is "" for a removed block (no live source — INV-36).
// A non-atomic changed prose block is pre-rendered with author-colored ins/del
// (changedHtml). Removed blocks and atomic changed fences stay neutral via renderOp
// (standalone deletion → neutral, per the adjacency heuristic). `src` is "" for a
// removed block (no live source — INV-36).
if (op.kind === "changed" && !op.atomic && changedHtml !== undefined) {
return `<div class="cw-blk cw-changed"${src}>${changedHtml}</div>`;
}
if (op.kind === "removed" || op.kind === "changed") return renderOp(op, render, src);
return `<div class="cw-blk ${op.kind === "added" ? "cw-added" : "cw-unchanged"}"${src}>${colored(op.block.raw)}</div>`;
// "added": use changedHtml (author-colored word-diff from empty before, all insertions).
// "unchanged": colored() returns plain render(raw) — color means "changed since baseline".
return `<div class="cw-blk ${op.kind === "added" ? "cw-added" : "cw-unchanged"}"${src}>${changedHtml ?? colored(op.block.raw)}</div>`;
}
/**
* On-state body (INV-33): the F7 baseline diff — added/changed PROSE author-colored
* via colorByAuthor (F9 sentinels), deletions struck — overlaid with F4 pending
* proposals as blue cw-proposal blocks (✓/✗). One pass, pure, vscode-free.
* On-state body: the F7 baseline diff — added/changed PROSE author-colored via
* wordDiffByAuthor (cw-ins-{author}/cw-del-{author} per token), unchanged blocks render plain,
* deletions struck neutral — overlaid with F4 pending proposals as blue cw-proposal
* blocks (✓/✗). One pass, pure, vscode-free.
*
* A resolved proposal renders INLINE, right after the current-side block its anchor
* falls in (#31) — so "what is Claude proposing, and where?" is answered by
@@ -903,11 +911,9 @@ export function renderReview(
const ops = diffBlocks(baselineText, landedText);
// #48: right after a PIN (baseline reason "pinned") with no changes since, the
// panel is fully clean: no change marks (already absent) AND no authorship
// coloring, so the pin reads as "this is my clean starting point". Skip
// colorByAuthor for every block in this case; data-src mapping and any pending
// proposals (review actions, not annotations) are kept below. A baseline advanced
// by a machine-landing (accept) is ALSO zero-diff but is NOT pinned — it keeps
// its authorship coloring (F10 INV-33), so this is gated on the pin specifically.
// coloring, so the pin reads as "this is my clean starting point". Suppress
// wordDiffByAuthor for every block in this case; data-src mapping and any pending
// proposals (review actions, not annotations) are kept below.
const clean = opts.pinned === true && ops.every((o) => o.kind === "unchanged");
// Map a currentText offset to its landedText offset (account for reverted spans
@@ -959,9 +965,21 @@ export function renderReview(
for (const op of ops) {
const blockIndex = op.kind === "removed" ? -1 : ci;
const blk = op.kind === "removed" ? undefined : ranges[ci++];
const colored = (raw: string): string =>
blk && !clean ? colorByAuthor(raw, blk.start, landedSpans, render) : render(raw);
bodyParts.push(renderReviewOp(op, render, colored, srcAttr(blk)));
// Only ADDED and CHANGED prose blocks are author-colored (they differ since baseline);
// UNCHANGED blocks render plain (color means "changed since baseline").
const colored = (raw: string): string => render(raw);
// A non-atomic changed prose block: author-colored word diff (ins by author,
// del by adjacency). An added block: word diff from empty before — all insertions.
// `blk.start` is the landed offset of the after-side text.
const changedHtml =
blk && !clean
? op.kind === "changed" && !op.atomic
? render(wordDiffByAuthor(op.before.raw, op.block.raw, blk.start, landedSpans))
: op.kind === "added"
? render(wordDiffByAuthor("", op.block.raw, blk.start, landedSpans))
: undefined
: undefined;
bodyParts.push(renderReviewOp(op, render, colored, srcAttr(blk), changedHtml));
const here = blockIndex >= 0 ? byBlock.get(blockIndex) : undefined;
if (here) for (const p of here) bodyParts.push(proposalBlockHtml(p, render));
}