fix: render added blocks via markdown-safe sentinel path emitting cw-ins-* (heading/list structure preserved)
Defect: an ADDED block was colored via `render(wordDiffByAuthor("", raw, ...))`, which
wrapped the whole raw markdown in an inline `<ins>` BEFORE markdown-it parsed it — so
`## X` rendered as literal text, not `<h2>`. Same structural bug for list, blockquote,
and thematic-break blocks.
Fix (three-part, pure / vscode-free):
1. `blockContentStart(raw)`: new helper returning the offset past block-level markers
(ATX headings `## `, unordered/ordered list markers, blockquotes). Open sentinels must
not precede these markers or markdown-it cannot recognise the block construct.
2. `injectSentinels`: clamp the open-sentinel lower-bound to `blockContentStart(raw)`,
so for `## New Section` the sentinel lands AFTER `## ` (at position 3) instead of at
position 0, letting the heading parse correctly.
3. `sentinelsToSpans` / `colorByAuthor`: add optional `kind: "by" | "ins" = "by"`
parameter so the sentinel path can emit `cw-ins-{author}` for added blocks while all
existing `colorByAuthor`/`cw-by-*` call-sites are unchanged.
4. `renderReview` ADDED block path: `render(wordDiffByAuthor("", raw, ...))` →
`colorByAuthor(raw, blk.start, landedSpans, render, "ins")`. The CHANGED non-atomic
prose path (`wordDiffByAuthor(before, after, ...)`) is untouched — it correctly keeps
the block markers at column 0 of the unchanged prefix.
5. `renderReviewOp`: removed the now-vestigial `colored` parameter (was always `render`
after the added-block path moved to `changedHtml`); unchanged blocks now call
`render(op.block.raw)` directly.
Regression test added: "renderReview: an added heading block renders as a heading AND
is author-colored as an insertion" — RED on old code, GREEN after fix. All 259 tests
pass; typecheck clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -491,6 +491,18 @@ describe("renderReview", () => {
|
||||
const html = renderReview("Keep.", "Keep.\n\nFresh line.", [{ start: 6, end: 17, author: "human" }], []);
|
||||
expect(html).toContain("cw-ins-human");
|
||||
});
|
||||
|
||||
test("renderReview: an added heading block renders as a heading AND is author-colored as an insertion", () => {
|
||||
// "Intro.\n\n" = 8 chars; "## New Section" = 14 chars → offset 8..22 in currentText.
|
||||
// Before fix: wordDiffByAuthor wrapped raw in <ins> BEFORE markdown-it parsed it,
|
||||
// so "## New Section" rendered as literal text (structural bug). After fix:
|
||||
// colorByAuthor uses the sentinel technique — markdown-it parses the block first,
|
||||
// so ## becomes a real <h2>, THEN sentinels are replaced with cw-ins-* spans.
|
||||
const html = renderReview("Intro.", "Intro.\n\n## New Section", [{ start: 8, end: 22, author: "human" }], []);
|
||||
expect(html).toMatch(/<h[12][^>]*>/); // real heading element, not literal "## "
|
||||
expect(html).toContain("cw-ins-human"); // author-colored insertion
|
||||
expect(html).not.toContain(">>"); // sanity: markers not escaped as text
|
||||
});
|
||||
});
|
||||
|
||||
import { renderTrackChanges as rtc2 } from "../src/trackChangesModel";
|
||||
|
||||
Reference in New Issue
Block a user