Skip to content

feat(review): document projection, diff geometry primitives, and the conformance harness (Phase 1 PR 2) - #719

Open
benvinegar wants to merge 3 commits into
mainfrom
claude/review-rebuild-phase-1-pr2
Open

feat(review): document projection, diff geometry primitives, and the conformance harness (Phase 1 PR 2)#719
benvinegar wants to merge 3 commits into
mainfrom
claude/review-rebuild-phase-1-pr2

Conversation

@benvinegar

Copy link
Copy Markdown
Member

Phase 1 PR 2 of the browser-review rebuild (docs/browser-review-rebuild.md). Stacked on PR 1 (#716).

What

Lands the shared diff-geometry and document-projection primitives in src/core/review/ (geometry / expansion / anchors / identity / document / stml / contentManifest), moves every terminal derivation the audit flagged onto them (deleting the local copies), and builds the conformance harness (test/review-conformance/) with hand-written adversarial fixtures. Repays audit findings A1–A10 and D2 (terminal/model sites; browser halves close in Phase 5). Three commits: core primitives → terminal adoption → harness.

What changed, in diagrams

Derivation ownership: before

Each geometric fact about a diff was computed independently wherever it was needed — the audit found up to four copies per derivation, at least one of them wrong:

flowchart TB
    subgraph before["One derivation, many owners (copy count from the audit)"]
        HR["per-side hunk range — 4 copies, 1 wrong (A3)"]
        GAP["collapsed-gap line ranges — 3 copies, terminal off by one (A1)"]
        TRAIL["trailing-gap existence — 3 formulations (A2)"]
        SPLIT["source-line splitting — 3 copies, 1 unnormalized (A4)"]
        SIDE["expansion side — 3 copies (A5)"]
        MSG["empty-diff reason — 3 precedence orders (A8)"]
        TGT["default note target — 2 divergent rules (A10)"]
        REB["hunk rebasing — 2 copies, opposite isPartial (A6)"]
    end
    P["ui/diff/pierre.ts"] --- GAP & TRAIL
    E["ui/diff/expandCollapsedRows.ts"] --- SPLIT
    DSR["ui/diff/diffSectionRowPlan.ts"] --- SIDE
    SBH["ui/diff/sourceBackedHighlight.ts"] --- REB
    RR["ui/diff/renderRows.tsx"] --- MSG
    SDP["ui/staticDiffPager.ts"] --- MSG
    LC["core/liveComments.ts"] --- HR & TGT
    REG["session/app/registration.ts"] --- TRAIL
Loading

Derivation ownership: after

One primitive per derivation, in the boundary-gated core; consumers import, never re-derive. The deleted copies are gone (leadingCollapsedRanges, trailingCollapsedRanges, trailingCollapsedLines, sliceLines, hunkLineRange, firstCommentTargetForHunk, the expansion-side recomputations, the per-surface empty-diff precedence):

flowchart LR
    subgraph core["src/core/review — one owner per derivation"]
        GEO["geometry.ts<br/>reviewHunkRange · rebaseReviewHunk<br/>normalizedReviewSourceLines · reviewDefaultHunkLineTarget"]
        EXP["expansion.ts<br/>reviewLeadingGap · reviewTrailingGap<br/>reviewGapAddress · reviewExpansionSide"]
        DOC["document.ts<br/>projectReviewDocument · reviewEmptyDiffReason<br/>split/unified line totals (A7)"]
        ANC["anchors.ts<br/>resolveReviewNoteAnchor"]
        STML["stml.ts — stmlTagRole (A9)"]
        IDN["identity.ts — content identity, no node:crypto"]
    end
    P2["pierre.ts"] --> EXP
    E2["expandCollapsedRows.ts"] --> GEO
    DSR2["diffSectionRowPlan.ts"] --> EXP
    SBH2["sourceBackedHighlight.ts"] --> GEO
    RR2["renderRows.tsx"] --> DOC
    SDP2["staticDiffPager.ts"] --> DOC
    LC2["liveComments.ts"] --> GEO
    REG2["registration.ts"] --> EXP
    LAY["ui/lib/stml/layout.ts"] --> STML
    RP2["ui/lib/reviewProjection.ts"] --> IDN & ANC & DOC
Loading

The A1 bug fix, concretely

For a hunk with a zero-count side (reachable via git diff -U0 | hunk pager), the terminal's local gap math labeled expanded context off by one. Fixed once in reviewLeadingGap, adopted by the terminal:

flowchart TB
    H["pure-insertion hunk  @@ -6,0 +7,1 @@<br/>old side contributes zero lines"]
    B["before — pierre.ts local math<br/>leading gap labeled old 0–5<br/>(line 0 does not exist)"]
    A["after — core reviewLeadingGap<br/>leading gap labeled old 1–6<br/>labels match the source file"]
    H --> B
    H --> A
Loading

The symmetric pure-deletion case had the new-side range one low, revealing text one line early. Row counts are unchanged in both cases — only labels/content alignment corrected.

The conformance harness

Every fixture's expectations are hand-written from unified-diff semantics (never generated by calling the primitive), and each adversarial case is precisely an input a deleted copy got wrong. Consumers register as phases land:

flowchart LR
    subgraph fx["test/review-conformance — golden corpus"]
        F1["pure-insertion-hunk · pure-deletion-hunk (A1/A2)"]
        F2["hunk-with-leading-context (A3)"]
        F3["crlf-source · source-without-trailing-newline (A4)"]
        F4["binary-rename-with-no-rows (A8)"]
        F5["note-body cases ×8 (D2)"]
    end
    subgraph now["Registered now"]
        C1["core model"]
        C2["terminal render planning"]
    end
    fx --> C1
    fx --> C2
    fx -.-> P2ph["producer (Phase 2)"]
    fx -.-> P3ph["broker + wire (Phase 3)"]
    fx -.-> P5ph["browser projection (Phase 5)"]
Loading

Seam probe run: reverting the A1 formula inside core made the terminal's conformance suite fail alongside core's — mechanical proof the terminal consumes the primitive rather than keeping a shadow copy.

Disclosed behavior changes (2)

  1. A1 gap labeling (bug fix, above): expanded-gap line labels for hunks with a zero-count side are now correct; pure-deletion reveals the right source lines.
  2. Source identity became content-derived: a reload that changed nothing now keeps expanded gaps open and loaded source cached (previously identity was the fetcher object, so every reload retired them); changed content still retires both. Three useReviewController unit tests were rewritten to reload with changed content and one new test pins the preservation contract — this is the rung-4 "test edit = behavior change" red flag, raised deliberately: the change is the fix the identity primitive exists for.

Deliberate residuals (recorded in the audit doc)

  • A2: reviewTrailingGap keeps the terminal's existing existence rule rather than the "correct" math — the correction would add trailing gaps to files whose last hunk has a zero-count side, an unsanctioned behavior change staged separately. Every consumer now agrees on one rule, which is the finding's point.
  • A9: the STML parser stays in ui/lib/stml (it sanitizes via lib/terminalText, which core may not import); core owns the tag→role table and the parser derives its void/raw-text sets from it. Parser relocation is Phase 5 work.
  • A6/A7: browser halves land in Phase 5 (rebaseReviewHunk returns per-side end indices so both use cases fit without either copy's isPartial opinion).

Design notes (fresh implementation, not a port)

  • Primitives take minimal structural inputs (ReviewHunkSpan, ReviewGapSource) that Pierre metadata satisfies directly — the prototype's primitives demanded a fully projected file, which is why its terminal kept local copies.
  • identity.ts is platform-neutral (no node:crypto) — the node-debt map shrank from 4 entries to 1 (jsonStream.ts only).
  • File keys are addresses (path/rename/occurrence), not content hashes — content-derived keys (the prototype's choice) orphan every note on reload; contentIdentity separately answers "did the content move".

Gates

  • bun run typecheck, bun run lint (deny-warnings), bun run format — clean.
  • bun test: 2414 pass / 5 fail — every failure pre-existing and reproduced on the base branch (dev-dep imports ×2, one baseline ui-components failure, PTY load-flakes).
  • bun run test:integration (PTY): 106/106, untouched.
  • Boundary suite green; no test/pty / test/cli / test/session edits.
  • AGENTS.md gains the test/review-conformance/ directory entry; per-finding repayment recorded inline in docs/browser-review-seam-audit.md.

Generated by Claude Code

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review (53 files, 50 file limit).

Bypass the limit by tagging @greptile-apps to review.

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hunk-web Ignored Ignored Preview Aug 13, 2026 12:49am

Request Review

claude added 3 commits August 13, 2026 00:48
… core

Every review consumer asks the same questions about a diff — which lines a
hunk occupies, which lines a collapsed gap hides, where a whole-hunk note
hangs, whether two projections describe the same content — and the prototype
answered each of them several times, differently. This lands the one answer
for each, renderer-free and platform-neutral, so the terminal, the producer,
and a browser client cannot drift.

- `geometry.ts`: per-side hunk extents, default hunk note target, hunk
  re-basing, and normalized source-line splitting (audit A3, A4, A6, A10).
- `expansion.ts`: gap ids and per-side gap addresses, trailing-gap existence,
  and the expansion side policy (A1, A2, A5).
- `anchors.ts`: one note-owner/intersection resolver, with explicit fallback
  ownership so a note core placed is not dropped by a consumer that re-derives.
- `identity.ts`: content identity, file keys, and source identity, hashed with
  plain arithmetic rather than a platform runtime.
- `document.ts`: the `DiffFile` -> `ReviewDocumentV1` projection, per-file
  split/unified row totals (A7), and the shared empty-diff reason (A8).
- `stml.ts`: the STML tag-to-role vocabulary both renderers switch on (A9).
- `contentManifest.ts`: a deterministic semantic snapshot that records derived
  geometry, so parity tests fail when a consumer re-derives it.

The primitives take the minimal structural shape they need rather than a whole
`ReviewFileV1`, which is what lets a renderer consume them from its row path
without projecting a document first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
…rimitives

The terminal kept its own copies of derivations the shared model now owns, and
one of them was wrong. Each copy is deleted here rather than left beside the
primitive that replaces it (audit A1-A6, A8-A10).

- `pierre.ts` builds collapsed rows from `reviewLeadingGap`/`reviewTrailingGap`;
  `leadingCollapsedRanges`, `trailingCollapsedRanges` and `trailingCollapsedLines`
  are gone, and `CollapsedGapPosition` is now the core gap-position vocabulary.
- `expandCollapsedRows.ts` splits source with `normalizedReviewSourceLines` and
  addresses gaps with `reviewGapId`; `sourceBackedHighlight.ts` re-bases hunks
  with `rebaseReviewHunk` and says why its own line split is different.
- `diffSectionRowPlan.ts` and `useReviewController.ts` read `reviewExpansionSide`
  instead of recomputing it; `liveComments.ts` loses `hunkLineRange` and
  `firstCommentTargetForHunk` to `reviewHunkRange(s)` and
  `reviewDefaultHunkLineTarget`.
- `renderRows.tsx` and `staticDiffPager.ts` explain an empty diff from one
  shared reason, each keeping its own wording.
- `ui/lib/stml` switches on tag roles from core, including the parser's
  void and raw-text tag sets.
- `reviewProjection.ts` hands the document projection to core and drops its
  process-local source-identity token.

Two deliberate behavior changes, both fixes:

1. A1: expanding the collapsed gap that leads into a hunk with a zero-count
   side labeled that side one line low, and read its text from one line early.
   A pure insertion parsed at zero context (`git diff -U0`) showed old line
   numbers starting at 0; a pure deletion showed the wrong new-side line. The
   gap now ends at the last line before the hunk on each side. Row counts are
   unchanged.
2. Expansion state and loaded source now survive a reload that changed nothing.
   Source identity is derived from content instead of from the fetcher object
   the reload happened to allocate, so state derived from unchanged content
   stays valid. A file whose content did change still retires both, and file
   keys stay stable across content changes so notes keep their file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
Import gates prove a consumer may use a shared primitive; they cannot prove it
does. `test/review-conformance/` closes that gap: one golden corpus, and every
consumer registers an adapter that projects the same fixtures into the same
normalized shape. Core and terminal render planning are the first two, and each
later phase joins the same corpus (rung 2 of the plan's verification ladder).

Expectations are written by hand from the unified-diff semantics, never captured
from a primitive — a captured expectation would follow a bug instead of catching
it. The adversarial fixtures are the inputs the deleted copies got wrong: a
pure-insertion hunk and a pure-deletion hunk (A1/A2), a hunk with leading
context (A3/A10), CRLF and unterminated sources (A4), and a renamed binary with
no rows (A8). A separate note-body corpus pins the one empty-body policy (D2).
A seam probe confirms the harness bites: reverting the A1 fix in core fails the
two zero-count fixtures for the terminal adapter as well as for core.

Also shrinks the review model's node-debt map: `document.ts` and `identity.ts`
landed platform-neutral, and source identity lives in `identity.ts` with no path
handling, so those three entries are repaid rather than carried.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
@benvinegar
benvinegar force-pushed the claude/review-rebuild-phase-1-pr2 branch from faa7fcf to fad85b8 Compare August 13, 2026 00:49
@benvinegar
benvinegar changed the base branch from claude/review-rebuild-phase-1-pr1 to main August 13, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants