diff --git a/media/preview.css b/media/preview.css index fa944b0..f706ff1 100644 --- a/media/preview.css +++ b/media/preview.css @@ -35,6 +35,14 @@ del { text-decoration: line-through; opacity: 0.7; } .cw-del-claude { background: rgba(188,140,255,0.13); text-decoration: line-through; text-decoration-color: #bc8cff; } .cw-del-none { background: var(--vscode-diffEditor-removedTextBackground, rgba(248,81,73,0.11)); text-decoration: line-through; opacity: 0.7; } +/* Task 8 — overlap: if a future render path nests cw-ins-{A} inside cw-del-{B}, + both marks must show. cw-ins-* sets text-decoration:none which would otherwise + suppress the parent del's strikethrough. `inherit` restores the parent's + line-through while the child's border-bottom underline is unaffected. + The engine currently emits SIBLING ins/del (never nested) so this rule is a + forward defensive guarantee only. See task-8-report.md for details. */ +.cw-del-claude .cw-ins-human, .cw-del-human .cw-ins-claude { text-decoration: inherit; } + /* whole-block ops: an added block is an insertion (its author runs are emitted as cw-ins-* via colorByAuthor with kind="ins"); a standalone removed block is neutral struck (adjacency heuristic fallback) */ diff --git a/test/trackChangesModel.test.ts b/test/trackChangesModel.test.ts index 9e350a0..101ea91 100644 --- a/test/trackChangesModel.test.ts +++ b/test/trackChangesModel.test.ts @@ -246,6 +246,21 @@ describe("wordDiffByAuthor", () => { expect(html).not.toContain("cw-del-human"); expect(html).not.toContain("cw-del-claude"); }); + test("emits SIBLING ins/del elements, never nested — CSS overlap rule in preview.css is a defensive forward guarantee", () => { + // The engine assigns the cluster's insertion author to all deletions in the same + // cluster (adjacency heuristic), so and always carry the SAME author + // as siblings. Cross-author nesting () + // is never produced today. The CSS rule `.cw-del-claude .cw-ins-human { text-decoration: inherit }` + // guards against a future regression where such nesting appears. + const spans: AuthorSpan[] = [{ start: 0, end: 100, author: "human" }]; + const html = wordDiffByAuthor("old text", "new text", 0, spans); + expect(html).toContain(''); + expect(html).toContain(''); + // Structural check: no immediately wraps an (sibling, not nested). + // /]*>[^<]* tag. + expect(html).not.toMatch(/]*>[^<]*