F12: inline editable proposed-change diff in the Markdown editor + Accept/Reject control parity (#64) #66
@@ -828,6 +828,17 @@ export function renderReview(
|
||||
return curOff + delta;
|
||||
};
|
||||
|
||||
// F12/#64 (INV-49/50): blocks are split from `landedText`, so `blk.start` is a
|
||||
// landedText offset — but `authorSpans` arrive in `currentText` coordinates. A
|
||||
// colored block sitting AFTER a length-changing pending proposal would otherwise
|
||||
// be mis-colored (the offsets diverge by the revert delta). Map the spans into
|
||||
// landedText coordinates once so authorship coloring stays aligned.
|
||||
const landedSpans: AuthorSpan[] = authorSpans.map((s) => ({
|
||||
...s,
|
||||
start: toLanded(s.start),
|
||||
end: toLanded(s.end),
|
||||
}));
|
||||
|
||||
// Associate each resolved proposal with the landedText block index whose range
|
||||
// it anchors into: the largest block with start <= anchorStart (the containing
|
||||
// block, or the nearest preceding block when the anchor sits in a gap). A
|
||||
@@ -857,7 +868,7 @@ export function renderReview(
|
||||
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, authorSpans, render) : render(raw);
|
||||
blk && !clean ? colorByAuthor(raw, blk.start, landedSpans, render) : render(raw);
|
||||
bodyParts.push(renderReviewOp(op, render, colored, srcAttr(blk)));
|
||||
const here = blockIndex >= 0 ? byBlock.get(blockIndex) : undefined;
|
||||
if (here) for (const p of here) bodyParts.push(proposalBlockHtml(p, render));
|
||||
|
||||
@@ -392,6 +392,26 @@ describe("renderReview", () => {
|
||||
// the applied paragraph is NOT also emitted as a word-merged changed block
|
||||
expect(html).not.toContain("<del>brown</del>"); // no double-render of the change as a baseline diff
|
||||
});
|
||||
|
||||
test("renderReview keeps author coloring aligned on a block AFTER a length-changing pending proposal (INV-49/50)", () => {
|
||||
// block 1 has a pending proposal whose applied text is MUCH LONGER than its
|
||||
// original; block 2 carries a Claude authorship span (in currentText coords).
|
||||
const baseline = "one short.\n\ntwo stable here.\n";
|
||||
const current = "one MUCH LONGER REPLACED TEXT.\n\ntwo stable here.\n";
|
||||
const proposals: ProposalView[] = [{
|
||||
id: "pr_1",
|
||||
anchorStart: current.indexOf("one MUCH LONGER REPLACED TEXT."),
|
||||
anchorEnd: current.indexOf("one MUCH LONGER REPLACED TEXT.") + "one MUCH LONGER REPLACED TEXT.".length,
|
||||
replaced: "one short.",
|
||||
replacement: "one MUCH LONGER REPLACED TEXT.",
|
||||
original: "one short.",
|
||||
}];
|
||||
const sStart = current.indexOf("stable");
|
||||
const authorSpans = [{ start: sStart, end: sStart + "stable".length, author: "claude" as const }];
|
||||
const html = renderReview(baseline, current, authorSpans, proposals);
|
||||
// the Claude coloring wraps "stable" exactly — not shifted by the proposal's length delta.
|
||||
expect(html).toMatch(/<span class="cw-by-claude">stable<\/span>/);
|
||||
});
|
||||
});
|
||||
|
||||
import { renderTrackChanges as rtc2 } from "../src/trackChangesModel";
|
||||
|
||||
Reference in New Issue
Block a user