fix(review): terminal consumes core's note-anchor, reveal, and command-lowering policies - #740
Merged
Merged
Conversation
The terminal re-derived note placement by testing each note's line ranges against the rows it had drawn, so a note anchored to lines the current patch collapsed — or to a gap line — matched no row and fell back to the first row in the file, stranding it far from the hunk it explains. Core already resolves ownership on the anchor for exactly this case. Every note the review stream draws — sidecar annotation, agent comment, reviewer note, open draft — is now built through one helper that resolves its anchor with the shared resolver, and the render plan places it inside the hunk that anchor names. A line no hunk contains hangs from the hunk owning the gap it falls in, which is the ownership gap addressing already documents. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
The diff pane decided which note a note-preferring reveal aimed at by scanning its measured rows for the first note card inside the selected hunk's bounds, while core already stated that policy — draft first, then the earliest anchor, arrival order breaking ties. Two statements of one rule, with different tiebreaks: a note drawn above another in the same hunk won the scan even when the other was anchored earlier. The policy now takes candidates, so the pane hands it the notes it actually draws — sidecar annotations included, which never enter the note store — and core answers which one wins. The pane keeps only the row lookup for that id, and the geometric scan is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
Three handlers built their own intent for a command whose effect the catalog already declares — toggling the note layer, toggling the gap the selection reaches, and starting a draft — so what a chord did here and what the same id lowers to for any other client were two statements that could drift. Each now lowers its catalog entry and runs the intent that comes back, keeping the terminal-owned bookkeeping around it: draft ids, line-cursor restore points, and the viewport-anchor reveal. The lowering grew one renderer-owned fact, the location a note affordance addressed, beside the measured line it already took. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
Greptile SummaryThe PR centralizes terminal review behavior around core note anchoring, reveal selection, and command lowering.
Confidence Score: 4/5The note-ID collision should be fixed before merging because it can make note-preferring navigation reveal a different annotation. The new ID-based reveal path relies on identifiers that can collide when generated index fallbacks overlap explicit annotation IDs, causing geometry lookup to return the wrong note row. Files Needing Attention: src/ui/components/panes/DiffPane.tsx Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Annotations and drafts] --> B[Core anchor resolution]
B --> C[VisibleAgentNote]
C --> D[Review render plan]
D --> E[Measured row geometry]
C --> F[Core reveal selector]
F --> G[Selected note ID]
G --> E
H[Terminal command] --> I[Command catalog lowering]
I --> J[Review intent]
J --> K[Review store and terminal bookkeeping]
Prompt To Fix All With AI### Issue 1
src/ui/components/panes/DiffPane.tsx:495-511
**Colliding note IDs break reveal**
When an annotation without an ID is at index `N` and another annotation has the explicit ID `"N"`, both receive the same synthesized ID. The reveal selector can choose one note while `rowBoundsByStableKey` resolves that shared ID to the other note's row, causing navigation to reveal the wrong annotation.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "refactor(review): run the review effects..." | Re-trigger Greptile |
…paces An annotation with the explicit id "1" and an id-less annotation at index 1 both synthesized the same visible-note id, and reveal resolves rows by that id — so the shared policy could name one note while the row lookup landed on the other. Explicit ids now mint under 🆔 and index fallbacks under :at:, and a regression pins that a reveal is not pulled to a colliding note two viewports away. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The substantive PR of the cleanup series (follows #736): the terminal was running parallel implementations of three policies that
src/core/review/already owns, with real divergences. Each finding is one commit; the terminal now consumes core and the parallel paths are deleted.1. Note placement comes from core anchor resolution — fixes a visible bug
Core's
resolveReviewNoteAnchordecides which hunk owns a note and which line it prefers, including a fallback branch for ranges the current patch collapsed. The terminal ignored it:reviewRenderPlan.tsre-derived ownership by intersecting each note's rawoldRange/newRangewith rendered rows, andDiffPanebuilt visible notes from raw annotations, discarding the anchorsreviewProjectionhad already computed. The divergence was user-visible: a note anchored to lines that no rendered row contains (collapsed into a gap, or an expanded-away range) found no matching row and fell back tofileLineRows[0]— the note rendered at the top of the file instead of beside its owning hunk. All note kinds now flow through core's resolution once, the render plan places notes by the resolved anchor (nearest row at-or-after the preferred line within the owning hunk), and the containment path is deleted. Regression tests pin the collapsed-range case at the render-plan level plus a PTY test observing the fix live; the changeset ispatchfor this fix.2. Reveal target comes from
selectActiveRevealNoteIdCore's selector (draft in the selected hunk wins; else earliest anchor line, arrival order breaking ties) had zero production importers while
DiffPanere-decided the target geometrically — first note row whose measured top falls inside the selected hunk's bounds, i.e. rendered order, a different tiebreak.DiffPanenow resolves the note row by the id the selector returns (via the existingrowBoundsByStableKeymapping) and the geometric scan is gone. This intentionally changes which note wins when several share a hunk; the core tiebreak is pinned in a UI-level test.3. Commands execute the review effects the catalog declares
lowerAppCommandToReviewIntentexisted so the command catalog's declaredrevieweffects would be the ones that run, but its only consumer was its own test —useReviewControllerrestated the same three intents by hand (notes/set-visibility, the gap toggle,notes/start-draft). The three handlers now route through the lowering; terminal-owned bookkeeping (draft ids, cursor restore points, viewport reveals) stays in the hook wrapping the produced intent. Tests assert the catalog effect and the executed intent agree for all three commands.Gates
bun run typecheckclean. Fullbun test: 2844 tests, failures limited to the environment's pre-existing baseline (two@axe-core/playwrightwebsite spec errors and PTY flakes under parallel load — the wiring-relevant suites plus the flaky PTY chrome test pass 88/88 in isolation).bun run lint0/0;bun run format:checkclean. Rebased onto current main (post-#736). A real-TTY smoke run isn't possible in this headless environment; PTY integration coverage stands in.Generated by Claude Code