Skip to content

refactor(review): extract a renderer-neutral review store into core (Phase 1 PR 1) - #716

Merged
benvinegar merged 3 commits into
claude/review-rebuild-phase-0from
claude/review-rebuild-phase-1-pr1
Aug 12, 2026
Merged

refactor(review): extract a renderer-neutral review store into core (Phase 1 PR 1)#716
benvinegar merged 3 commits into
claude/review-rebuild-phase-0from
claude/review-rebuild-phase-1-pr1

Conversation

@benvinegar

@benvinegar benvinegar commented Aug 12, 2026

Copy link
Copy Markdown
Member

Phase 1 PR 1 of the browser-review rebuild (docs/browser-review-rebuild.md). Stacked on the Phase 0 guardrails branch.

What

Extracts a renderer-neutral review store into src/core/review/types / state / actions / reducer / store / selectors / intents, each with colocated tests — and refactors the terminal (useReviewController, App, DiffPane type import) onto it. Behavior-neutral: no user-visible change, empty changeset.

  • State + named policies: ReviewState plus the value policies that read it (isRenderableStoredReviewNote, reviewLineAnchor, reviewNoteOwnerHunkIndex, reviewNoteAnchorLine, ReviewRevealRequest) — one named answer per policy instead of inline conditionals per consumer.
  • Single-transition reducer with identity-stable no-ops; synchronous three-method store (getSnapshot / subscribe / dispatch).
  • Typed intents (planReviewIntent / applyReviewIntent) with a typed outcome map and planning errors.
  • Terminal boundary adapter src/ui/lib/reviewProjection.ts owns projection between the terminal's DiffFile model and the semantic store (UserReviewNote / DraftReviewNote moved here).
  • useReviewController drops from 14 useState hooks to 3; the 3 mirroring refs are deleted; showAgentNotes moves from App state into shared review state (G1 classification: shared).

Implemented fresh against the plan and audit — the prototype branch was used as a semantic reference only; deliberate design divergences are documented in the plan's terms (named reveal requests instead of flag fields, reuse of existing ReviewNoteSource, trimmed store surface, no dead exports).

What changed, in diagrams

State ownership: before

Fourteen useState hooks in one controller, three refs mirroring state for synchronous reads, one review-relevant flag owned by App, and follow-up effects reconciling it all after the fact:

flowchart TB
    subgraph app["App.tsx"]
        SAN["showAgentNotes"]
    end
    subgraph urc["useReviewController — 14 useState hooks"]
        SEL["selectedFileId · selectedHunkIndex"]
        FIL["filter"]
        REV["fileTopAlignRequestId · hunkRevealRequestId · scrollToNote"]
        NOTES["liveCommentsByFileId · userNotesByFileId · draftNote"]
        EXP["expandedGapsByFileId"]
        SRC["sourceStatusByFileId"]
        SNAP["filesSnapshot"]
        CUR["lineCursor · lineCursorRevealRequestId"]
    end
    subgraph refs["Mirroring refs (sync reads)"]
        R1["sourceStatusRef"]
        R2["expandedGapsRef"]
        R3["savedDraftIdRef"]
    end
    SRC -. "mirrored each render" .-> R1
    EXP -. "mirrored each render" .-> R2
    NOTES -. "mirrored on save" .-> R3
    SNAP -. "follow-up effect clamps" .-> SEL
    FIL -. "follow-up effect re-selects" .-> SEL
Loading

State ownership: after

One semantic ReviewState behind a three-method store; the controller keeps only the store snapshot plus the two renderer-measured cursor values (terminal row geometry stays with the renderer that measures it, per the audit's do-not-unify list):

flowchart TB
    subgraph core["src/core/review — renderer-free, boundary-gated"]
        STATE["ReviewState<br/>selection · filter · reveal · notes<br/>expansion · source status · showAgentNotes"]
        STORE["store: getSnapshot / subscribe / dispatch"]
        STATE --- STORE
    end
    subgraph urc2["useReviewController — 3 useState hooks"]
        SNAP2["snapshot (subscribed)"]
        CUR2["lineCursor · lineCursorRevealRequestId<br/>(renderer-measured, stays terminal-side)"]
    end
    subgraph app2["App.tsx"]
        VIEW["view preferences (per-client)"]
    end
    STORE -- "subscribe → one snapshot" --> SNAP2
    SNAP2 --> app2
Loading

How a change flows now

Validation and clamping happen in the intent planner before the state changes, replacing scattered setX calls plus after-the-fact reconciliation effects:

sequenceDiagram
    participant H as key/mouse handler
    participant C as useReviewController
    participant I as intents (plan + apply)
    participant S as store
    participant R as reducer
    H->>C: select hunk / edit note / set filter
    C->>I: ReviewIntent
    Note over I: planReviewIntent —<br/>validate file, clamp hunk,<br/>typed planning errors
    I->>S: dispatch(ReviewAction)
    S->>R: reduce(state, action)
    R-->>S: next ReviewState (identity-stable no-ops)
    S-->>C: notify → single snapshot render
Loading

Module seam

flowchart LR
    subgraph core3["src/core/review (imports: core-internal + @pierre/diffs only)"]
        T3["types"] --> ST3["state"]
        ST3 --> A3["actions"] --> RD3["reducer"] --> STO3["store"]
        ST3 --> SE3["selectors"]
        ST3 --> IN3["intents"]
    end
    subgraph ui3["src/ui (terminal renderer)"]
        RP3["lib/reviewProjection.ts<br/>DiffFile ⇄ semantic adapter"]
        URC3["hooks/useReviewController"]
        APP3["App.tsx"]
        DP3["DiffPane"]
    end
    URC3 --> STO3
    URC3 --> IN3
    RP3 --> T3
    APP3 --> URC3
    DP3 --> RP3
    GATE(["scripts/source-boundaries.test.ts<br/>gates every arrow crossing the seam"]) -.- core3
Loading

Scope boundaries

Document projection, geometry primitives, and the conformance harness are Phase 1 PR 2; navigation intents and the command catalog are PR 3. Trimmed prototype surface (generations, expandedLineProof, filter selectors, additional note intents) is tracked per-phase in the plan. Nothing deleted yet → no tombstone entries.

Gates

  • bun run typecheck clean; bun run lint clean (deny-warnings); formatted.
  • bun test: 2311 pass / 4 fail — all four failures reproduce on the untouched base branch (missing @axe-core/playwright dev dep ×2, one pre-existing ui-components failure, one known-flaky PTY test); branch failures are a strict subset of baseline failures.
  • bun run test:integration (PTY): 105/106, sole failure is the pre-existing flake, reproduced on baseline.
  • Boundary suite 10/10 — seam gates active for src/core/review/ (contained in core, @pierre/diffs-only externals, node-debt map untouched).
  • No test under test/pty/, test/cli/, test/session/ modified; useReviewController.test.tsx (38 tests) and AppHost.* suites pass unedited.

Repays (partial, per the spanning rule): D2 core+terminal sites (isBlankReviewNoteBody; fixture + conformance registration land with PR 2's harness).

claude added 2 commits August 12, 2026 17:37
Sharing a review with a second surface needs one semantic model of a live
review — selection, reveal intent, filter, note visibility, notes, drafts, and
gap expansion — instead of each surface keeping its own copy and drifting. This
lands that model in src/core/review as state, actions, reducer, store,
selectors, and intents, where the source-boundary suite keeps it free of
renderers and platform runtimes.

The split follows the seam: the reducer is the one place state changes, the
selectors are the one place it is interpreted, and intents are the one place a
request is validated and lowered into actions. Rules that were previously
inline conditionals get names and documentation — reveal requests, stale-note
visibility, single-line anchoring, bulk-clear scope, content retirement, and
the empty-note-body rule the terminal and agent surfaces answer differently
while agreeing on what blank means.

Scope is only what the terminal already has today: the document carries file
identity and hunk counts, so hunk geometry, note anchoring over parsed ranges,
navigation intents, and generations arrive with the phases that need them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
useReviewController owned the review's semantic state in a dozen useState hooks,
plus mirroring refs that existed only so a callback could read state its own
setter had not committed yet. That made the terminal the owner of a model three
more consumers are about to need. It now projects the shared store instead: the
store answers synchronously, so the mirroring refs and the saved-draft guard go
away, and note visibility moves out of App state into the store beside the notes
it governs.

Behavior is unchanged by design. The controller's public API is the same, the
existing hook and pane suites pass untouched, and the store is observed through
React state rather than useSyncExternalStore so a held key still drains as one
burst against one committed snapshot instead of re-entering key routing per
press.

src/ui/lib/reviewProjection.ts is the one boundary between the semantic model
and the terminal's diff-file model: document projection, note and draft
adaptation both ways, and the transitional file key and source identity that
the document-projection phase replaces with content-derived values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
@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 12, 2026 11:52pm

Request Review

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extracts semantic review state into a renderer-neutral core store and adapts the terminal review controller to project between that store and DiffFile values.

  • Adds review types, state policies, reducer actions, selectors, intents, and a synchronous observable store.
  • Moves terminal selection, filtering, notes, drafts, visibility, and expansion state onto the shared store.
  • Adds colocated unit coverage for core transitions, intent planning, storage, selectors, and terminal projection.

Confidence Score: 4/5

The PR appears safe to merge, with only a non-blocking formatting inconsistency against the mounted repository guidance.

The store extraction preserves the investigated navigation, reload, and note-projection behavior, while the remaining accepted concern is limited to code-format consistency.

Files Needing Attention: src/core/review/actions.ts and the other newly added review-store modules

Important Files Changed

Filename Overview
src/core/review/state.ts Defines the renderer-neutral review state and named policies for note visibility, anchoring, and reveal requests.
src/core/review/reducer.ts Implements identity-stable state transitions and content-derived state retirement during document reconciliation.
src/core/review/intents.ts Validates semantic review operations and lowers them into reducer actions with typed outcomes.
src/core/review/store.ts Adds a synchronous observable store that increments revisions and publishes state-changing dispatches.
src/ui/hooks/useReviewController.ts Migrates terminal review state onto the shared store while retaining terminal-specific projection, navigation, geometry, and source-loading behavior.
src/ui/lib/reviewProjection.ts Adapts DiffFile, live-comment, user-note, draft, and source-status values to and from the semantic store.
src/ui/App.tsx Moves agent-note visibility into shared review state and updates application wiring accordingly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Terminal["App / useReviewController"] --> Projection["reviewProjection"]
  Projection --> Store["ReviewStore"]
  Store --> Reducer["reduceReviewState"]
  Intents["plan/applyReviewIntent"] --> Store
  Reducer --> State["ReviewState"]
  State --> Projection
  Projection --> Panes["DiffPane and session bridge"]
Loading
Prompt To Fix All With AI
### Issue 1
src/core/review/actions.ts:13
**Align new review-store formatting**

The newly added review-store modules use two-space indentation and double-quoted strings, conflicting with the repository guidance requiring four spaces and single quotes. Applying the repository convention across this new subsystem will avoid ongoing formatting churn and inconsistent examples.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "refactor(ui): move terminal review state..." | Re-trigger Greptile

ReviewDraftNote,
ReviewRevealRequest,
ReviewSourceStatus,
ReviewStoredNote,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Align new review-store formatting

The newly added review-store modules use two-space indentation and double-quoted strings, conflicting with the repository guidance requiring four spaces and single quotes. Applying the repository convention across this new subsystem will avoid ongoing formatting churn and inconsistent examples.

Context Used: guidelines.mdc Cursor rule (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/review/actions.ts
Line: 13

Comment:
**Align new review-store formatting**

The newly added review-store modules use two-space indentation and double-quoted strings, conflicting with the repository guidance requiring four spaces and single quotes. Applying the repository convention across this new subsystem will avoid ongoing formatting churn and inconsistent examples.

**Context Used:** guidelines.mdc Cursor rule ([source](https://github.com/modem-dev/modem/blob/main/.cursor/rules/guidelines.mdc))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

…ter a rule

Rewrite the review store's module headers to lead with what each module
does, and add the register to AGENTS.md: first sentence names the
behavior, invariants follow, no passive or self-important framing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
@benvinegar
benvinegar merged commit da5a1e2 into claude/review-rebuild-phase-0 Aug 12, 2026
12 checks passed
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