F10 SLICE-2: extract colorByAuthor from renderAuthorship sentinels (#29)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-12 00:08:10 -07:00
parent 831710ed0c
commit ebe3d4bab5
2 changed files with 30 additions and 4 deletions
+17 -2
View File
@@ -351,6 +351,22 @@ function sentinelsToSpans(html: string): string {
.split(SENT.human.close).join("</span>"); .split(SENT.human.close).join("</span>");
} }
/**
* 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 * Pure authorship render (INV-26/28): the CURRENT text with each F3-attributed
* span colored by author. Prose blocks get inline `<span class="cw-by-*">`; * span colored by author. Prose blocks get inline `<span class="cw-by-*">`;
@@ -379,8 +395,7 @@ export function renderAuthorship(
if (!badge) return `<div class="cw-blk">${inner}</div>`; if (!badge) return `<div class="cw-blk">${inner}</div>`;
return `<div class="cw-blk ${badge.cls}"><span class="cw-badge">${badge.label}</span>${inner}</div>`; return `<div class="cw-blk ${badge.cls}"><span class="cw-badge">${badge.label}</span>${inner}</div>`;
} }
const injected = injectSentinels(b.raw, b.start, overlapping); return `<div class="cw-blk">${colorByAuthor(b.raw, b.start, overlapping, safe)}</div>`;
return `<div class="cw-blk">${sentinelsToSpans(safe(injected))}</div>`;
}) })
.join("\n"); .join("\n");
} }
+13 -2
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest"; import { describe, it, test, expect } from "vitest";
import { splitBlocks, splitBlocksWithRanges, diffBlocks, renderTrackChanges, renderAuthorship, type AuthorSpan } from "../src/trackChangesModel"; import { splitBlocks, splitBlocksWithRanges, diffBlocks, renderTrackChanges, renderAuthorship, colorByAuthor, type AuthorSpan } from "../src/trackChangesModel";
describe("splitBlocks", () => { describe("splitBlocks", () => {
it("splits prose paragraphs on blank lines, dropping empties", () => { 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) => `<p>${src}</p>`;
const html = colorByAuthor(raw, 0, spans, render);
expect(html).toContain('<span class="cw-by-human">hello</span>');
expect(html).toContain("world");
});
});
import { renderTrackChanges as rtc2 } from "../src/trackChangesModel"; import { renderTrackChanges as rtc2 } from "../src/trackChangesModel";
describe("renderTrackChanges — intra-diagram mermaid (#22)", () => { describe("renderTrackChanges — intra-diagram mermaid (#22)", () => {