F10 SLICE-2: fix renderReview author-coloring for duplicate blocks; tidy renderReviewOp + escape proposal id (#29)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+11
-10
@@ -437,7 +437,7 @@ function proposalBlockHtml(p: ProposalView, render: (src: string) => string): st
|
||||
`<button class="cw-accept" data-action="accept">✓</button>` +
|
||||
`<button class="cw-reject" data-action="reject">✗</button>` +
|
||||
`</span>`;
|
||||
return `<div class="cw-proposal${unanchored}" data-proposal-id="${p.id}">${actions}${before}${after}</div>`;
|
||||
return `<div class="cw-proposal${unanchored}" data-proposal-id="${md.utils.escapeHtml(p.id)}">${actions}${before}${after}</div>`;
|
||||
}
|
||||
|
||||
function renderReviewOp(
|
||||
@@ -445,9 +445,9 @@ function renderReviewOp(
|
||||
render: (src: string) => string,
|
||||
colored: (raw: string) => string,
|
||||
): string {
|
||||
if (op.kind === "removed") return renderOp(op, render);
|
||||
if (op.kind === "changed" && op.atomic) return renderOp(op, render);
|
||||
if (op.kind === "changed") return renderOp(op, render); // word-merged <ins>/<del>
|
||||
// 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).
|
||||
if (op.kind === "removed" || op.kind === "changed") return renderOp(op, render);
|
||||
return `<div class="cw-blk ${op.kind === "added" ? "cw-added" : "cw-unchanged"}">${colored(op.block.raw)}</div>`;
|
||||
}
|
||||
|
||||
@@ -468,12 +468,13 @@ export function renderReview(
|
||||
const render = opts.render ?? defaultRender;
|
||||
const ranges = splitBlocksWithRanges(currentText);
|
||||
const ops = diffBlocks(baselineText, currentText);
|
||||
const colored = (raw: string): string => {
|
||||
const blk = ranges.find((r) => r.raw === raw);
|
||||
if (!blk) return render(raw);
|
||||
return colorByAuthor(raw, blk.start, authorSpans, render);
|
||||
};
|
||||
const bodyParts = ops.map((op) => renderReviewOp(op, render, colored));
|
||||
let ci = 0; // pointer into ranges; advances for every op with a current-side block
|
||||
const bodyParts = ops.map((op) => {
|
||||
const blk = op.kind === "removed" ? undefined : ranges[ci++];
|
||||
const colored = (raw: string): string =>
|
||||
blk ? colorByAuthor(raw, blk.start, authorSpans, render) : render(raw);
|
||||
return renderReviewOp(op, render, colored);
|
||||
});
|
||||
const anchored = proposals.filter((p) => p.anchorStart !== null);
|
||||
const unanchored = proposals.filter((p) => p.anchorStart === null);
|
||||
const proposalParts = [...anchored, ...unanchored].map((p) => proposalBlockHtml(p, render));
|
||||
|
||||
@@ -295,6 +295,18 @@ describe("renderReview", () => {
|
||||
expect(html).toContain("mermaid");
|
||||
expect(html).not.toContain("cw-by-");
|
||||
});
|
||||
test("renderReview: author-colors the correct block when two paragraphs are identical", () => {
|
||||
// Two identical paragraphs; baseline has only the first, so the SECOND is an
|
||||
// added block authored by human. Its span must color THAT block, not the first.
|
||||
const baseline = "Hello world";
|
||||
const current = "Hello world\n\nHello world";
|
||||
// second "Hello world" starts at offset 13; "world" at 19..24
|
||||
const spans = [{ start: 19, end: 24, author: "human" as const }];
|
||||
const html = renderReview(baseline, current, spans, []);
|
||||
// exactly one cw-by-human span (the added second block's "world"), not zero, not on the first.
|
||||
const count = (html.match(/cw-by-human/g) ?? []).length;
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
import { renderTrackChanges as rtc2 } from "../src/trackChangesModel";
|
||||
|
||||
Reference in New Issue
Block a user