feat(extensions): give command handlers the review selection - #616
Merged
Conversation
A command that acts on what the user is looking at — copy this file's path, open this hunk elsewhere — had no way to ask where the review is: handlers received cwd, notify, and sidebar controls only, so the only route to the selection was shadow-tracking `selection_changed` into module state and hoping it stayed in sync. `ctx.selection` now carries the selected file and hunk index, captured when the command fires. It resolves through the same frozen file views the sidebar panes render from — App converts once and both surfaces read that list — so a command and a sidebar can never describe the review differently, and a snapshot an extension holds cannot reach the review model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JML2m7oy2sUbFftN7QoHhi
Review follow-ups: the frozen-view conversion was hoisted into App as an eager memo keyed on the visible-files list, whose identity changes on every selection change — so every navigation keypress reconverted every visible file, in every session, extensions or not. The conversion now runs on demand behind a per-source cache: hidden sidebars and extension-free sessions never pay for it, panes and command snapshots still share the exact same frozen view objects, and the command callback stays identity-stable so the dispatch table and menus do not rebuild per keypress. Also rights the contract docs: Hunk keeps the selection inside the visible files, so a null selection.file means nothing is visible at all, not a hidden selection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JML2m7oy2sUbFftN7QoHhi
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
Greptile SummaryAdds review-selection snapshots to extension command contexts.
Confidence Score: 3/5The PR should not merge until command selection files are isolated from the mutable review metadata they promise not to expose. The new command API hands extension handlers a top-level frozen object whose nested metadata and hunk array still alias the live review model, allowing the documented read-only boundary to be bypassed. Files Needing Attention: src/ui/App.tsx, src/extensions/events.ts, and src/extension-api/types.ts Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
R[Review controller state] --> C[Lazy frozen-view cache]
C --> S[Extension sidebar props]
C --> B[Selection snapshot builder]
B --> K[Extension command context]
R --> B
Prompt To Fix All With AI### Issue 1
src/ui/App.tsx:515
**Nested metadata remains mutable**
When an extension command mutates nested data such as `ctx.selection.file.metadata.hunks`, the shallow-frozen file view writes through to the live review metadata, causing subsequent rendering, selection, and navigation to operate on extension-modified state despite the documented read-only contract.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "fix(ui): build extension selection snaps..." | Re-trigger Greptile |
… surface Review follow-up on #616: the read-only file views froze their own properties but shared metadata, stats, and agent with the live review model, so an extension writing through selection.file.metadata.hunks (or a sidebar prop's agent annotations) mutated what the renderer draws from despite the documented read-only contract. The shared state now sits behind a lazy deep read-only proxy: reads pass through — including the host's own metadata readers — while writes, deletes, and redefinitions throw inside the extension, and the cost the original shallow design avoided (walking the whole diff model to deep-freeze it per conversion) stays avoided because wrapping happens only on access. Proxies cache per source object so repeated conversions hand out identical views; exotic objects (a Map's internal-slot methods would break through a proxy) read through unwrapped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JML2m7oy2sUbFftN7QoHhi
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.
Item 2 from the extension-system roadmap: extension command handlers previously received no review selection, so a "copy current file path" or "open this hunk in my tool" command had to shadow-track the
selection_changedlifecycle event into module state.What changed
Contract (
src/extension-api/types.ts, additive within apiVersion 1, still import-free):ExtensionReviewSelection { file: ExtensionDiffFile | null; hunkIndex: number | null }ExtensionCommandContext.selection— a snapshot built when the command fires: a handler that awaits still sees the selection it was run from.fileis the same frozen read-only view sidebar components receive; since Hunk keeps the selection inside the visible files, it isnullonly when nothing is visible at all.hunkIndexis clamped against the file's real hunk count (guarding the one-frame window after a reload shrinks a file) andnullfor files with no hunks.One pipeline, lazily: the
toReadOnlyFileViewsconversion moved out ofExtensionSidebarPaneand now runs on demand behind a per-source cache inApp— sidebar panes and command snapshots consume literally the same frozen objects, hidden sidebars and extension-free sessions never pay for the conversion, and the command callback stays identity-stable so the dispatch table, keymap, and Extensions menu don't rebuild on navigation keypresses.readMetadataHunkCountjoinsreadMetadataChangeTypeinsrc/extensions/events.tsso knowledge of the opaque metadata shape stays in one module.Works identically whether the command fires from its key or from the Extensions menu (both dispatch through the same run path).
Docs & release
docs/extensions.md:ctx.selectionsemantics plus a typechecked example command (gated bycheck:packalongside the other 16 examples).docs/extension-architecture.md: command-system note on the shared frozen-view pipeline.minor.Testing
src/ui/lib/extensionSelection.test.ts: resolve/no-selection/not-visible cases, clamp high/low/NaN, zero-hunk file → null, frozen-ness and exact-object identity with the sidebar list.AppHost.extension-sidebar.test.tsx: a fixture command logsctx.selection.file?.path+hunkIndexthrough real key dispatch; asserts the startup snapshot, then]-navigation updates it.typecheck,bun test src/ui src/extensions src/lib(797 pass),check:pack, lint/format. PTY integration: green except two failures confirmed pre-existing on a main-equivalent tree in this container ("a command key opens an extension sidebar", "filter focus narrows the visible review stream").An internal adversarial review pass confirmed ref-timing, callback-identity stability, and contract purity, and caught the eager-conversion perf regression fixed in the second commit.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JML2m7oy2sUbFftN7QoHhi
Generated by Claude Code