Harden author-coloring PUA sentinels against intra-emphasis markdown #33

Closed
opened 2026-06-12 08:09:14 +00:00 by benstull · 2 comments
Owner

Harden author-coloring PUA sentinels against intra-emphasis markdown

Summary

Make the F9/F10 author-coloring technique (Private-Use-Area sentinels injected
into prose, then mapped to cw-by-* spans after markdown render) robust when an
author span starts/ends inside a markdown emphasis run (e.g. mid-**bold** or
mid-_italic_), so the injected sentinels don't perturb markdown-it's inline
parsing.

Problem / pain

colorByAuthor (salvaged into F10's renderReview) injects paired PUA sentinels
at a span's char offsets, lets markdown-it render, then replaces the sentinels with
<span class="cw-by-*"> tags. When a span boundary falls inside an emphasis
delimiter run (e.g. between the *s, or splitting **), the inserted sentinel can
break markdown-it's emphasis matching — the emphasis fails to render or the span
nests incorrectly. F9 flagged this as a known edge and deferred hardening; F10
inherits it. Today it's mitigated only by the per-block try/catch → error chip,
which degrades that block rather than rendering it correctly.

Who feels it

The human coauthor (PP-1) reviewing prose where an attributed span boundary
coincides with emphasis markup — uncommon, but it produces a visibly wrong or
error-chipped block when it happens.

Desired outcome / value

Author-coloring renders correctly even when span boundaries land inside emphasis
runs — the colored <span> wraps the intended text and the emphasis still renders,
with no error chip and no broken markup.

Acceptance — "done right"

  • A prose block with an author-span boundary inside **bold** / _italic_ /
    `code` renders with both the emphasis AND the correct cw-by-* coloring.
  • No regression to the existing colorByAuthor cases (the F9-salvaged unit tests
    stay green).
  • renderReview/colorByAuthor remain pure and deterministic (INV-33).
  • A unit test reproduces the intra-emphasis case and asserts correct output.

Scope / non-goals

In scope: the sentinel injection/extraction robustness in trackChangesModel.ts
(injectSentinels/sentinelsToSpans/colorByAuthor).

Non-goals: changing the visual language or the attribution data model; atomic
fences (already handled whole); broadening beyond emphasis edge cases that actually
reproduce.

Assumptions · constraints · dependencies

  • Robustness refinement of existing code; no new feature surface. Pure/vscode-free.
  • Likely approaches: clamp span boundaries to token edges, or post-process the
    rendered HTML rather than the source — to be decided at implementation.

Source / signal

Self-originated — capture session vscode-cowriting-plugin-0031 (2026-06-12),
from the F9 deferred refinement carried into F10. Spec:
specs/coauthoring-interactive-review.md §1.7 / §9 ("intra-emphasis sentinel-safety
hardening"); origin in the F9 authorship-preview design. Relates to F10 #29.

Priority (WSJF)

Provisional: Value low (correctness on an uncommon edge; today degrades
gracefully to an error chip) · Time-criticality low · Opportunity low ÷
Size smallprovisional priority/P3. (Decision aid, not auto-scored.)

Decomposition

Task (no independent business value — a robustness fix). Checklist:

  • Reproduce the intra-emphasis failure in a unit test.
  • Harden sentinel injection/extraction (clamp to token edges or post-process HTML).
  • Confirm no regression in existing colorByAuthor cases; determinism holds.

Solution notes (optional — NOT a design)

Non-binding: the cleanest fix may be to apply author coloring as an HTML
post-process keyed on character ranges of the rendered text, rather than injecting
sentinels into the markdown source — but that is a larger change; clamping span
boundaries to safe token edges may suffice for the common case.

<!-- Set the type/* label: type/task --> # Harden author-coloring PUA sentinels against intra-emphasis markdown ## Summary Make the F9/F10 author-coloring technique (Private-Use-Area sentinels injected into prose, then mapped to `cw-by-*` spans after markdown render) robust when an author span starts/ends **inside a markdown emphasis run** (e.g. mid-`**bold**` or mid-`_italic_`), so the injected sentinels don't perturb markdown-it's inline parsing. ## Problem / pain `colorByAuthor` (salvaged into F10's `renderReview`) injects paired PUA sentinels at a span's char offsets, lets markdown-it render, then replaces the sentinels with `<span class="cw-by-*">` tags. When a span boundary falls **inside** an emphasis delimiter run (e.g. between the `*`s, or splitting `**`), the inserted sentinel can break markdown-it's emphasis matching — the emphasis fails to render or the span nests incorrectly. F9 flagged this as a known edge and deferred hardening; F10 inherits it. Today it's mitigated only by the per-block `try/catch` → error chip, which degrades that block rather than rendering it correctly. ## Who feels it The **human coauthor** (PP-1) reviewing prose where an attributed span boundary coincides with emphasis markup — uncommon, but it produces a visibly wrong or error-chipped block when it happens. ## Desired outcome / value Author-coloring renders correctly even when span boundaries land inside emphasis runs — the colored `<span>` wraps the intended text and the emphasis still renders, with no error chip and no broken markup. ## Acceptance — "done right" - A prose block with an author-span boundary inside `**bold**` / `_italic_` / `` `code` `` renders with both the emphasis AND the correct `cw-by-*` coloring. - No regression to the existing `colorByAuthor` cases (the F9-salvaged unit tests stay green). - `renderReview`/`colorByAuthor` remain pure and deterministic (INV-33). - A unit test reproduces the intra-emphasis case and asserts correct output. ## Scope / non-goals **In scope:** the sentinel injection/extraction robustness in `trackChangesModel.ts` (`injectSentinels`/`sentinelsToSpans`/`colorByAuthor`). **Non-goals:** changing the visual language or the attribution data model; atomic fences (already handled whole); broadening beyond emphasis edge cases that actually reproduce. ## Assumptions · constraints · dependencies - Robustness refinement of existing code; no new feature surface. Pure/vscode-free. - Likely approaches: clamp span boundaries to token edges, or post-process the rendered HTML rather than the source — to be decided at implementation. ## Source / signal Self-originated — **capture session vscode-cowriting-plugin-0031** (2026-06-12), from the F9 deferred refinement carried into F10. Spec: `specs/coauthoring-interactive-review.md` §1.7 / §9 ("intra-emphasis sentinel-safety hardening"); origin in the F9 authorship-preview design. Relates to F10 `#29`. ## Priority (WSJF) Provisional: Value **low** (correctness on an uncommon edge; today degrades gracefully to an error chip) · Time-criticality **low** · Opportunity **low** ÷ Size **small** → **provisional priority/P3**. (Decision aid, not auto-scored.) ## Decomposition Task (no independent business value — a robustness fix). Checklist: - [ ] Reproduce the intra-emphasis failure in a unit test. - [ ] Harden sentinel injection/extraction (clamp to token edges or post-process HTML). - [ ] Confirm no regression in existing `colorByAuthor` cases; determinism holds. ## Solution notes (optional — NOT a design) Non-binding: the cleanest fix may be to apply author coloring as an HTML post-process keyed on character ranges of the rendered text, rather than injecting sentinels into the markdown source — but that is a larger change; clamping span boundaries to safe token edges may suffice for the common case.
benstull added the type/taskpriority/P3 labels 2026-06-12 08:09:14 +00:00
Author
Owner

Investigation (session 0032) — failure characterized; deferred to its own session.

Reproduced via renderReview with the real markdown-it renderer (unchanged emphasis block + an author span whose boundary lands in/near emphasis markup). Two distinct failure modes:

  • CASE1 — span boundary splits a delimiter run (a**b**c, span 0..2 ending between the two opening *): the injected sentinel splits **, breaking emphasis parsing entirely → <p><span class="cw-by-human">a<em></span></em>b**c</p> (stray <em></em>, b**c left raw). This is the parse-break the issue describes.
  • CASE3 — span boundary inside an emphasis run (**bold**, span covers **bo): emphasis renders, but the author <span> and <strong> misnest<strong>bo</span>ld</strong> (the </span> closes inside <strong>). Invalid/overlapping HTML, browser-tolerated — so it doesn't error-chip, it silently emits malformed markup.
  • CASE2 — whole-emphasis span (**bold** fully covered): correct (<span class="cw-by-human"><strong>bold</strong></span>).

Design fork for the fix:

  • Clamp-only (clamp span boundaries off delimiter runs): fixes CASE1, but CASE3's misnesting remains — partial.
  • Token-aware HTML post-process (color the rendered HTML by walking inline elements and splitting each author span at element boundaries, emitting one <span> segment per text run): fixes both correctly, but is a meaningful rewrite of colorByAuthor — and colorByAuthor is shared by F9/F10, so it needs care + full regression coverage.

The full token-aware approach is the genuinely-correct one (CASE3 misnesting is inherent to wrapping arbitrary char ranges across element boundaries with source-injected sentinels). Recommend implementing it in a dedicated session. No code changed for #33 in 0032 — only this characterization.

**Investigation (session 0032) — failure characterized; deferred to its own session.** Reproduced via `renderReview` with the real markdown-it renderer (unchanged emphasis block + an author span whose boundary lands in/near emphasis markup). Two distinct failure modes: - **CASE1 — span boundary splits a delimiter run** (`a**b**c`, span `0..2` ending between the two opening `*`): the injected sentinel splits `**`, breaking emphasis parsing entirely → `<p><span class="cw-by-human">a<em></span></em>b**c</p>` (stray `<em></em>`, `b**c` left raw). This is the parse-break the issue describes. - **CASE3 — span boundary inside an emphasis run** (`**bold**`, span covers `**bo`): emphasis renders, but the author `<span>` and `<strong>` **misnest** → `<strong>bo</span>ld</strong>` (the `</span>` closes inside `<strong>`). Invalid/overlapping HTML, browser-tolerated — so it doesn't error-chip, it silently emits malformed markup. - **CASE2 — whole-emphasis span** (`**bold**` fully covered): correct (`<span class="cw-by-human"><strong>bold</strong></span>`). **Design fork for the fix:** - *Clamp-only* (clamp span boundaries off delimiter runs): fixes CASE1, but CASE3's misnesting remains — partial. - *Token-aware HTML post-process* (color the rendered HTML by walking inline elements and splitting each author span at element boundaries, emitting one `<span>` segment per text run): fixes **both** correctly, but is a meaningful rewrite of `colorByAuthor` — and `colorByAuthor` is shared by F9/F10, so it needs care + full regression coverage. The full token-aware approach is the genuinely-correct one (CASE3 misnesting is inherent to wrapping arbitrary char ranges across element boundaries with source-injected sentinels). Recommend implementing it in a dedicated session. No code changed for #33 in 0032 — only this characterization.
Author
Owner

Shipped (#53, session 0047) via the token-aware approach. injectSentinels clamps any sentinel off a delimiter run (CASE1 parse-break fixed); sentinelsToSpans is now a walker that emits the cw-by-* span only around text runs, splitting at every <tag> boundary so spans never misnest with <strong>/<em>/<code> (CASE3 fixed) and no PUA char leaks. Pure/deterministic (INV-33); no regression. 222 unit + 74/5 host E2E green.

Shipped (#53, session 0047) via the token-aware approach. `injectSentinels` clamps any sentinel off a delimiter run (CASE1 parse-break fixed); `sentinelsToSpans` is now a walker that emits the cw-by-* span only around text runs, splitting at every `<tag>` boundary so spans never misnest with `<strong>`/`<em>`/`<code>` (CASE3 fixed) and no PUA char leaks. Pure/deterministic (INV-33); no regression. 222 unit + 74/5 host E2E green.
Sign in to join this conversation.