#33: harden author-coloring PUA sentinels against intra-emphasis markdown
The F9/F10 author-coloring technique injects paired PUA sentinels into prose source at span offsets, renders, then maps sentinels → cw-by-* spans. Two failure modes when a span boundary met emphasis markup (characterized in session 0032): - CASE1: a boundary strictly inside a delimiter run (a**b**c, between the two *) split `**`, breaking markdown-it's delimiter pairing (stray <em></em>, raw **). - CASE3: a boundary inside an emphasis run (**bold** span covering **bo) rendered the emphasis but MISNESTED span/element (<strong>bo</span>ld</strong>). Token-aware fix (both, per the issue #33 comment): - injectSentinels: clamp any sentinel offset that lands strictly inside a delimiter run (* _ ~ `) to the run's start, so a sentinel never splits a run (CASE1). Delimiters are invisible once rendered, so this only shifts the colored boundary across markup. Drop spans that clamp to empty. - sentinelsToSpans: replace the naive global split/join with a walker over the rendered HTML that emits the cw-by-* span only around TEXT runs — closing it before any <tag> and reopening after — so a span is always well-nested within inline elements (one span segment per text run, CASE3). Tags are copied verbatim with any stray sentinel stripped (no Private-Use-Area char leaks). Pure, vscode-free, deterministic (INV-33). No regression: existing colorByAuthor / renderReview cases stay green; the common no-emphasis case is byte-identical. 222 unit + 74/5 host E2E green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,6 +168,45 @@ describe("colorByAuthor", () => {
|
||||
expect(html).toContain('<span class="cw-by-human">hello</span>');
|
||||
expect(html).toContain("world");
|
||||
});
|
||||
|
||||
// #33: author-coloring must be sentinel-safe around markdown emphasis. These
|
||||
// exercise the REAL markdown-it renderer (via renderReview on an unchanged doc),
|
||||
// since the failure is in markdown-it's inline parsing / element nesting.
|
||||
// CASE1 — a span boundary strictly inside a delimiter run must not split it.
|
||||
test("#33 CASE1: a span boundary inside ** does not break emphasis parsing", () => {
|
||||
const doc = "a**b**c";
|
||||
const html = renderReview(doc, doc, [{ start: 0, end: 2, author: "human" }], []);
|
||||
expect(html).toContain("<strong>b</strong>"); // emphasis still renders
|
||||
expect(html).not.toContain("**"); // no raw delimiters left
|
||||
expect(html).not.toContain("<em></em>"); // no stray empty emphasis (the parse-break symptom)
|
||||
expect(html).toContain('class="cw-by-human"'); // coloring present
|
||||
});
|
||||
// CASE3 — a span boundary inside an emphasis run must color the text without
|
||||
// misnesting span/element (the span is split at the element boundary).
|
||||
test("#33 CASE3: a span boundary inside **bold** colors the text without misnesting", () => {
|
||||
const doc = "**bold**";
|
||||
const html = renderReview(doc, doc, [{ start: 0, end: 4, author: "human" }], []); // covers "**bo"
|
||||
expect(html).toContain("<strong>");
|
||||
expect(html).toContain('<span class="cw-by-human">bo</span>ld</strong>'); // span INSIDE strong, closed before "ld"
|
||||
expect(html).not.toContain('cw-by-human"><strong>'); // NOT the old misnest (span wrapping the <strong> open)
|
||||
});
|
||||
// CASE2 — a span covering a whole emphasis run stays correct (regression).
|
||||
test("#33 CASE2: a span over the whole **bold** colors it correctly", () => {
|
||||
const doc = "**bold**";
|
||||
const html = renderReview(doc, doc, [{ start: 0, end: 8, author: "human" }], []);
|
||||
expect(html).toContain("<strong>");
|
||||
expect((html.match(/cw-by-human/g) ?? []).length).toBe(1);
|
||||
expect(html).toContain("bold");
|
||||
});
|
||||
test("#33: no Private-Use-Area sentinel chars leak into the rendered output", () => {
|
||||
const doc = "a**b**c and `co de` and _x_";
|
||||
const spans: AuthorSpan[] = [
|
||||
{ start: 0, end: 2, author: "human" },
|
||||
{ start: 12, end: 16, author: "claude" },
|
||||
];
|
||||
const html = renderReview(doc, doc, spans, []);
|
||||
expect(html).not.toMatch(/[\uE000-\uF8FF]/); // no leftover BMP Private-Use-Area sentinels
|
||||
});
|
||||
});
|
||||
|
||||
import { renderPlain } from "../src/trackChangesModel";
|
||||
|
||||
Reference in New Issue
Block a user