#48: pinning the baseline leaves the review panel fully un-annotated

A pinned baseline with no changes since should read as a clean starting point,
but the F10 on-render still author-colored every block (colorByAuthor), painting
the whole document green/blue right after a pin. Now, when the baseline reason is
"pinned" and there are zero changes since (every diffBlocks op unchanged), the
on-render is fully clean — no change marks and no authorship coloring — while the
data-src block mapping (INV-36) and any pending proposals (review actions, not
annotations) are kept.

Scoped to the PIN specifically, not all zero-diff: a baseline advanced by a
machine-landing (accept) is also zero-diff but keeps its authorship coloring so
accepted Claude text stays blue (F10 INV-33). renderReview takes a `pinned`
option; the controller passes baseline.reason === "pinned" from both refresh and
the renderHtmlFor test seam.

- trackChangesModel.ts: renderReview gains the `pinned` RenderOption; when pinned
  + zero-diff, blocks render plain (no colorByAuthor).
- trackChangesPreview.ts: pass { pinned } through refresh + renderHtmlFor.
- unit: pinned+zero-diff → no cw-by-*; pinned+zero-diff still shows proposals;
  zero-diff WITHOUT pin (machine-landing) keeps coloring (INV-33); real changes
  after a pin re-color.
- s48PinClean host E2E: type → colored; pin → clean; edit → annotations return.

218 unit + 74/5 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-13 08:28:52 -07:00
parent 4584e06679
commit fdd743490d
4 changed files with 130 additions and 2 deletions
+48
View File
@@ -277,6 +277,54 @@ describe("renderReview", () => {
expect(uIdx).toBeGreaterThan(html.indexOf("Beta there")); // unanchored still trails the body
expect(html).toContain("cw-proposal-unanchored");
});
// #48: a PINNED baseline with zero changes leaves the panel fully un-annotated —
// no authorship coloring on unchanged blocks — while pending proposals still show.
test("renderReview: pinned + zero diff with author spans renders NO authorship coloring", () => {
const doc = "Human wrote this.\n\nClaude wrote that.";
const spans: AuthorSpan[] = [
{ start: 0, end: 17, author: "human" },
{ start: 19, end: doc.length, author: "claude" },
];
const html = renderReview(doc, doc, spans, [], { pinned: true });
expect(html).not.toContain("cw-by-human");
expect(html).not.toContain("cw-by-claude");
expect(html).not.toContain("cw-add");
expect(html).not.toContain("cw-del");
// still a selection→source surface (INV-36): blocks carry data-src offsets.
expect(html).toContain("data-src-start");
// the body text is still there, just plain.
expect(html).toContain("Human wrote this.");
expect(html).toContain("Claude wrote that.");
});
test("renderReview: pinned + zero diff still renders a pending proposal block", () => {
const doc = "Human wrote this.\n\nClaude wrote that.";
const spans: AuthorSpan[] = [{ start: 0, end: 17, author: "human" }];
const proposals: ProposalView[] = [
{ id: "p1", anchorStart: 0, anchorEnd: 5, replaced: "Human", replacement: "Person" },
];
const html = renderReview(doc, doc, spans, proposals, { pinned: true });
expect(html).not.toContain("cw-by-human"); // body still clean
expect(html).toContain('data-proposal-id="p1"'); // proposal still shows (it is an action)
expect(html).toContain("Person");
});
test("renderReview: zero diff WITHOUT a pin (e.g. machine-landing) keeps authorship coloring (INV-33)", () => {
// accepting a Claude edit advances the baseline (zero diff) but is NOT a pin —
// the landed author coloring must remain.
const doc = "Human wrote this.\n\nClaude wrote that.";
const spans: AuthorSpan[] = [{ start: 19, end: doc.length, author: "claude" }];
const html = renderReview(doc, doc, spans, []); // no pinned flag
expect(html).toContain("cw-by-claude");
});
test("renderReview: with REAL changes since a pin, author coloring returns", () => {
// a genuine added block is still author-colored even when pinned (only the
// zero-diff-after-pin state is clean).
const baseline = "Hello world";
const current = "Hello world\n\nHello world";
const spans: AuthorSpan[] = [{ start: 19, end: 24, author: "human" }];
const html = renderReview(baseline, current, spans, [], { pinned: true });
expect((html.match(/cw-by-human/g) ?? []).length).toBe(1);
});
test("renderReview is deterministic with mixed anchored/unanchored proposals", () => {
const doc = "one two three";
const proposals: ProposalView[] = [