feat(extensions): add custom file previews - #632
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| useEffect(() => { | ||
| const controller = new AbortController(); | ||
| const next = new Map<string, ResolvedFileViewLayout>(); |
There was a problem hiding this comment.
Stale layouts survive soft reloads
When a soft reload replaces a file under the same stable ID, the previous resolved layout remains active until the asynchronous preparation loop finishes, causing pre-reload content to render under the reloaded file header instead of falling back to the current raw diff.
Knowledge Base Used: UI App Shell: AppHost, App, and Controller Hooks
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ui/fileViews/useFileViews.ts
Line: 63-65
Comment:
**Stale layouts survive soft reloads**
When a soft reload replaces a file under the same stable ID, the previous resolved layout remains active until the asynchronous preparation loop finishes, causing pre-reload content to render under the reloaded file header instead of falling back to the current raw diff.
**Knowledge Base Used:** [UI App Shell: AppHost, App, and Controller Hooks](https://app.greptile.com/modem/-/custom-context/knowledge-base/modem-dev/hunk/-/docs/ui-app-host.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| }; | ||
|
|
||
| const prepare = async () => { | ||
| for (const file of files) { |
There was a problem hiding this comment.
Layout preparation blocks serially
Selected layouts are awaited one file at a time and published only after the entire loop finishes, so an early slow or timed-out layout keeps completed presentations on raw rendering and adds up to 1.5 seconds of delay per slow file.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ui/fileViews/useFileViews.ts
Line: 77
Comment:
**Layout preparation blocks serially**
Selected layouts are awaited one file at a time and published only after the entire loop finishes, so an early slow or timed-out layout keeps completed presentations on raw rendering and adds up to 1.5 seconds of delay per slow file.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Greptile SummaryThis PR adds an experimental host-rendered file-presentation extension surface.
Confidence Score: 3/5This PR should not merge until alternate presentations are invalidated when a soft reload replaces the underlying file, preventing stale pre-reload content from being shown. The layout state is keyed for rendering by file id but retains the layout produced for the previous file object until asynchronous replacement work completes, allowing stale code to remain visible after a same-id reload; serial preparation also unnecessarily delays independent layouts. Files Needing Attention: src/ui/fileViews/useFileViews.ts, src/ui/App.tsx, src/ui/components/panes/DiffSection.tsx Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
E[Extension registerFileView] --> R[Session file-view registry]
R --> S[Per-file presentation selection]
F[Current DiffFile] --> H[useFileViewLayouts]
S --> H
H --> D[Lazy document reads]
D --> L[Extension layout]
L --> V[Host validation and limits]
V -->|valid| G[Host geometry]
V -->|invalid, null, timeout| P[Pierre raw diff fallback]
G --> W[Windowed FileView rendering]
W --> N[Review stream and hunk navigation]
Prompt To Fix All With AI### Issue 1
src/ui/fileViews/useFileViews.ts:63-65
**Stale layouts survive soft reloads**
When a soft reload replaces a file under the same stable ID, the previous resolved layout remains active until the asynchronous preparation loop finishes, causing pre-reload content to render under the reloaded file header instead of falling back to the current raw diff.
### Issue 2
src/ui/fileViews/useFileViews.ts:77
**Layout preparation blocks serially**
Selected layouts are awaited one file at a time and published only after the entire loop finishes, so an early slow or timed-out layout keeps completed presentations on raw rendering and adds up to 1.5 seconds of delay per slow file.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(extensions): add review presentatio..." | Re-trigger Greptile |
30d1b57 to
4a2dc68
Compare
The source reads that verify row `sourceRanges` were awaited after the timeout race had already settled, so a slow or hung read could hold one of the four preparation slots indefinitely — even though the module promises to bound third-party layout work. Source fetchers can come from extension-provided VCS adapters, so that was a reachable contract hole, not a theoretical one. Route every awaited phase through one `withinBudget()` helper so the extension call and the reads its bindings require share a single deadline. Also distinguish an unreadable document from a wrong binding: unavailable source is an environment condition and should not be reported as a layout the extension got wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKB5sYNLXDmyh6WCbN8HN9
…puts `measureFileViewGeometry` carried a `_file` argument every caller had to pass but nothing read, and defaulted `plannedRows` to a note-less plan alongside `width = 1`. Those two defaults were only safe together: passing planned rows that contain inline notes while omitting the width would measure every note at one column and silently corrupt scroll geometry. Take one required options object instead. `fileViewUnavailableReason` likewise kept `file` and `showAgentNotes` parameters that stopped being read once committed notes moved to validated source bindings. Narrowing it to `hasDraftNote` also drops `showAgentNotes` from App's dependency list, so the reason map no longer recomputes when that toggle changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKB5sYNLXDmyh6WCbN8HN9
The `<extensionId>:<viewId>` address was templated in four places: sidebar duplicate resolution, file-view duplicate resolution, file-view selection lookup, and bare-id qualification for extension commands. Duplicate resolution and selection lookup must agree exactly or a registration is deduped under one key and looked up under another, so give them a single `qualifiedViewKey` source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKB5sYNLXDmyh6WCbN8HN9
Version 2 was the intermediate shape inside this stack and never shipped, so publishing 3 would leave extensions branching on `hunk.apiVersion` with a gap they can never have seen. main is still on 1; land the streamlined contract as 2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKB5sYNLXDmyh6WCbN8HN9
What this adds
This PR adds an experimental extension API for file previews:
hunk.registerFileView().An extension can now recognize files it knows how to present and offer an alternate view in Hunk's View menu. For example, the Markdown extension included here turns a changed
.mdfile into rendered terminal Markdown instead of showing only the raw patch.Raw diff remains the default. Previews are opt-in and live inside the normal multi-file review stream, so a review can freely mix raw diffs and custom previews.
What a file-view extension can do
A registered file view can:
Extensions describe rows and hunk locations, but Hunk still owns the review experience: measurement, scrolling, windowing, hunk navigation, selection, and inline-note rendering.
How it works for users
[/]hunk navigation, scrolling, filtering, and agent notes continue to work across mixed raw and previewed files.Included examples
Rendered Markdown
examples/extensions/rendered-markdown/is an installable file-view extension built with the public API. It renders headings, lists, links, code, tables, quotes, and other Markdown constructs as terminal-friendly rows while preserving hunk navigation and inline-note placement.It is an example, not a bundled default: users explicitly install it into their Hunk extension directory.
JSX file-view gallery
examples/extensions/jsx-file-view-gallery/demonstrates the constrained JSX row API with real diffs:The gallery also documents the important boundary: JSX can paint inside a fixed row, but it cannot take over the review stream or resize itself after mounting.
Review triage
The PR also keeps the review-triage example that motivated the broader extension API evaluation. It demonstrates commands, dialogs, notifications, lifecycle events, and a custom sidebar using only the public extension surface.
Reliability and performance
File-view layouts are validated, bounded, cancellable, and cached by file and terminal width. Source reads are lazy and deduplicated. Invalid or stale layouts fall back to raw diff, and one broken custom row cannot break the rest of the review.
All PR checks are green, including Linux tests and smoke coverage, Windows compatibility, macOS/Windows compiled portability, package validation, and website checks.
We also compared the current branch with the commit immediately before the latest file-view changes using seven samples each of the render-layout, large-stream, and interaction-latency benchmarks. The repository benchmark gate found no material regressions; windowed scrolling was 3.5% faster in the comparison.
This PR description was generated by Pi using OpenAI GPT-5.2