perf(watch): stop watch-mode signature checks from stalling the review UI - #677
Open
benvinegar wants to merge 2 commits into
Open
perf(watch): stop watch-mode signature checks from stalling the review UI#677benvinegar wants to merge 2 commits into
benvinegar wants to merge 2 commits into
Conversation
…w UI Watch mode re-runs `watchSignature` on every debounced file event and every safety poll. The Git backend's implementation ran three `Bun.spawnSync` calls — a full `git diff`, a `rev-parse`, and `ls-files --others` — so during active editing, which is exactly when events fire most, the TUI froze once a second for as long as Git took on the repo. A render-loop proxy ticking at 10ms across five signature checks saw 5 ticks before and 12 after over the same wall time. Total time is unchanged: this does not make the check faster, it stops it freezing the terminal. Three changes, all Effect-independent findings from the migration spike: - Add async Git runners alongside the sync ones. Only the spawn differs; argument building, exit-code policy, and stderr translation stay in shared helpers so the two paths cannot drift. `watchSignature` widens to `string | Promise<string>` — backward compatible, an existing synchronous implementation still satisfies it — and `ExtensionVcsLoadContext` gains an optional `signal`. - Funnel the controller's cancellation checks. `beginCheck` had four `isClosed()` guards and two identical catch blocks, so safety depended on remembering a guard at every new await site. One `runCheckStep` helper now answers both questions in one place, and closing aborts the signal handed to `getSignature`/`refresh` so in-flight work stops rather than running to completion for a result nobody reads. - Extract the named-deadline scheduler. Four deadlines collapsed into one chained timer moves to `watchDeadlines.ts` with its own tests, leaving the controller to talk about phases instead of timer handles. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YLF3qLZdxVvT87YXBESEib
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
Greptile SummaryThe PR moves Git-backed watch signatures off the UI event loop, propagates cancellation through watch operations, and extracts watch timing into a named-deadline scheduler.
Confidence Score: 5/5The PR appears safe to merge, with no concrete changed-code failure identified. The asynchronous Git paths preserve the existing command arguments and error interpretation, controller closure prevents stale results from being applied, and deadline consumption retains the prior debounce and polling behavior. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
Event[Filesystem event or safety deadline] --> Controller[Watch controller]
Controller -->|AbortSignal| Signature[Compute watch signature]
Signature --> Adapter[VCS adapter]
Adapter --> AsyncGit[Async Git subprocess]
AsyncGit --> Compare{Signature changed?}
Compare -->|No| Schedule[Schedule next deadline]
Compare -->|Yes| Refresh[Refresh review]
Refresh --> Schedule
Close[Watcher closes] -->|Abort| Controller
Scheduler[Named deadline scheduler] --> Controller
Reviews (1): Last reviewed commit: "perf(watch): stop watch-mode signature c..." | Re-trigger Greptile |
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.
Why
Watch mode re-runs
watchSignatureon every debounced file event and every safety poll. The Git backend's implementation ran threeBun.spawnSynccalls — a fullgit diff, arev-parse, andls-files --others— plus astatSyncper untracked file. So during active editing, which is exactly when file events fire most, the TUI froze roughly once a second for as long as Git took on the repo.Measured with a 10 ms interval standing in for the render loop, across five signature checks on this repo:
Total wall time is unchanged (~120 ms either way). This does not make the check faster — it stops it freezing the terminal. End-to-end against a live repo (tracked edit, then a new untracked file) both refreshes fire and the loop ticks 203 times over 2.1 s.
What changed
Async Git runners, sharing all logic with the sync ones. Only the spawn differs; argument building, exit-code policy, and stderr translation moved into shared helpers (
interpretGitResult,planWorkingTreeGitDiffCheck,revsIncludeWorkingTree) so the two paths cannot drift on what counts as a failure. The one-shotloadpath stays synchronous, where blocking is free.Cancellation funnelled into one place.
beginCheckhad fourisClosed()guards and two identical catch blocks, so safety depended on remembering a guard at every newawaitsite. OnerunCheckStephelper now answers both questions — did we close, did the work fail — in a single place. Closing also aborts the signal handed togetSignature/refresh, so an in-flightgit diffis killed rather than running to completion for a result nobody reads.Deadline scheduler extracted. The four named deadlines collapsed into one chained timer move to
src/core/watchDeadlines.tswith their own tests, leaving the controller to talk about phases instead of timer handles.Extension API
watchSignaturewidens tostring | Promise<string>andExtensionVcsLoadContextgains an optionalsignal. Both are additive: an existing synchronouswatchSignaturestill satisfies the contract, so third-party adapters keep working unchanged.check:packverifies the published.d.tsstill typechecks for consumers under bothnodenextandbundlerresolution across all 21docs/extensions.mdexamples. Docs updated indocs/extensions.mdand the website VCS-adapter page.Sized as
minoron the grounds that the extension contract gained a capability. If you'd rather treat it purely as the UI-responsiveness fix it is for end users,patchis defensible.Testing
typecheck,lint,format:check,check:pack,check:docs— cleanbun test— 1774 pass, 0 failtest:tty-smoke— 9 passtest:integration— 84 pass, 2 fail; the same 2 fail on unmodifiedmain(PTY file views > retains three preview types…andPTY chrome > filter focus narrows…), and both pass in isolation. Pre-existing flakes under parallel load.Not done
The signature is still the entire patch text, so a large changeset allocates and compares megabytes of string on every poll. Hashing would fix that but trades away being able to eyeball a signature when debugging — left as a separate call.
Generated by Claude Code