diff --git a/src/trackChangesModel.ts b/src/trackChangesModel.ts index ffb44bc..d5399ab 100644 --- a/src/trackChangesModel.ts +++ b/src/trackChangesModel.ts @@ -351,6 +351,22 @@ function sentinelsToSpans(html: string): string { .split(SENT.human.close).join(""); } +/** + * Color one prose block's HTML by F3 author spans (PUA-sentinel technique, + * salvaged from F9 renderAuthorship). `blockStart` is the block's char offset in + * the source so spans map correctly. Pure; deterministic. + */ +export function colorByAuthor( + raw: string, + blockStart: number, + spans: AuthorSpan[], + render: (src: string) => string, +): string { + const overlapping = spans.filter((s) => s.end > blockStart && s.start < blockStart + raw.length); + const injected = injectSentinels(raw, blockStart, overlapping); + return sentinelsToSpans(render(injected)); +} + /** * Pure authorship render (INV-26/28): the CURRENT text with each F3-attributed * span colored by author. Prose blocks get inline ``; @@ -379,8 +395,7 @@ export function renderAuthorship( if (!badge) return `
${inner}
`; return `
${badge.label}${inner}
`; } - const injected = injectSentinels(b.raw, b.start, overlapping); - return `
${sentinelsToSpans(safe(injected))}
`; + return `
${colorByAuthor(b.raw, b.start, overlapping, safe)}
`; }) .join("\n"); } diff --git a/test/trackChangesModel.test.ts b/test/trackChangesModel.test.ts index 0297ee1..0fb1b49 100644 --- a/test/trackChangesModel.test.ts +++ b/test/trackChangesModel.test.ts @@ -1,5 +1,5 @@ -import { describe, it, expect } from "vitest"; -import { splitBlocks, splitBlocksWithRanges, diffBlocks, renderTrackChanges, renderAuthorship, type AuthorSpan } from "../src/trackChangesModel"; +import { describe, it, test, expect } from "vitest"; +import { splitBlocks, splitBlocksWithRanges, diffBlocks, renderTrackChanges, renderAuthorship, colorByAuthor, type AuthorSpan } from "../src/trackChangesModel"; describe("splitBlocks", () => { it("splits prose paragraphs on blank lines, dropping empties", () => { @@ -235,6 +235,17 @@ describe("renderAuthorship", () => { }); }); +describe("colorByAuthor", () => { + test("colorByAuthor wraps human-authored prose in cw-by-human spans", () => { + const raw = "hello world"; + const spans: AuthorSpan[] = [{ start: 0, end: 5, author: "human" }]; + const render = (src: string) => `

${src}

`; + const html = colorByAuthor(raw, 0, spans, render); + expect(html).toContain('hello'); + expect(html).toContain("world"); + }); +}); + import { renderTrackChanges as rtc2 } from "../src/trackChangesModel"; describe("renderTrackChanges — intra-diagram mermaid (#22)", () => {