Skip to content

perf: defer closed menus to speed up cold worktree switching - #20311

Merged
nwparker merged 1 commit into
mainfrom
nwparker/Faster-Worktree-switch
Sep 12, 2026
Merged

nwparker merged 1 commit into
mainfrom
nwparker/Faster-Worktree-switch

Conversation

@nwparker

@nwparker nwparker commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​40 $\color{#cf222e}{\Huge{\mathbf{−}}}$​3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​37
Prod 4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​320 $\color{#cf222e}{\Huge{\mathbf{−}}}$​286 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​34

ELI5

Returning to a worktree after a while briefly shows a blank terminal while Orca rebuilds its renderer. That rebuild also prepared menus and a confirmation dialog that were closed. This PR waits until those surfaces open to construct their contents, leaving less work ahead of terminal restoration.

What Changed

  • Move terminal context-menu rows and shortcut computation into a component beneath DropdownMenuContent.
  • Move close-confirmation content beneath DialogContent, while keeping checkbox/reset state with its existing owner so reopening and queued confirmations retain their behavior.
  • Render the static New Tab entries as a component beneath the dropdown, instead of eagerly calling a render function.
  • Add regression tests proving closed context menus/dialogs do not translate their body copy. The original code fails with 13 and 5 translations respectively; the changed code performs zero.

Why

This removes repeated work from cold activation without extending terminal retention or adding a cache. It targets the reported case of returning after a while; it does not eliminate snapshot restore latency.

In a hidden macOS Electron test app using a production-built renderer served through Vite, real sidebar clicks switched between two Git worktrees with three terminals each and 1,500 lines of scrollback. Cold samples reload onto the other worktree first and verify that the target terminal manager is absent.

Measurement Before After
Cold restoration, four samples (ms) 104.2, 119.7, 115.7, 86.1 91.6, 73.4, 93.3, 63.6
Cold median 110.0 ms 82.5 ms (~25% lower)
Warm switches, two samples (ms) 24.0, 19.4 24.4, 20.6

The metric is pointerdown to the first animation-frame sample containing restored terminal-buffer text, not pixel-paint timing or a packaged-app benchmark. This is a small local sample, not a guaranteed improvement on every machine. A separate development-renderer folder-workspace fixture with 10,000 lines improved from 194.8 ms to 156.1 ms median.

User trade-offs and remaining opportunities

No observed loss of functionality or visible menu regression. The deliberate trade-off is that menu/dialog body construction and translation now happen when opened, rather than repeatedly while closed. Opening could absorb that small amount of work; menu-opening latency was not separately benchmarked. Existing Radix mount/exit behavior and confirmation state ownership are preserved.

There is no additional terminal memory retention, no shorter scrollback, and no change to PTY identity, replay ordering, geometry settling, repaint safeguards, SSH/remote protocols, or resource policies. A brief cold-restore blank interval remains.

No further material, trade-off-free improvement is established by this investigation. Hidden sibling admission did not delay initial content in the three-tab fixture. Additional probes saw snapshot writes at 62–74 ms and restored content at 73–97 ms; the startup-command grid-settle gate does not apply to that fixture. Keeping more renderers alive would spend memory, while skipping geometry/repaint safeguards risks incorrect or stale terminal rendering. Further elimination of redundant UI work remains a profiling candidate, not a claimed benefit.

Linked Issue

Fixes #20308

Visual Proof

These before/after screenshots verify equivalent restored terminal content and layout; the timing samples above provide the performance evidence. They do not capture the duration of the blank interval.

Before After
Before After

Testing

  • Manually tested locally in an agent-launched hidden Electron app with ORCA_BACKGROUND_LAUNCH=1, through Playwright CDP.
  • Automated tests added/updated.

23 tests passed across TerminalContextMenu, CloseTerminalDialog, and the TabBar Windows shell launch / SSH host / remote runtime suites. New closed-copy tests were verified failing against the original source and passing with the change.

pnpm tc:web, pnpm run check:code-quality:changed, formatting checks, and git diff --check passed. Production renderer builds succeeded for both benchmark variants.

Rendered checks covered the terminal context menu, New Tab menu, creating a terminal, and opening/cancelling the running-command close confirmation. A live alternate-screen redraw fixture survived sidebar switches and one renderer reload with the same PTY ID, continued advancing frames, and restored its normal buffer on completion.

Actual runtime testing was on macOS, with local Git and folder workspaces. Windows/SSH routing was covered by automated tests; Linux, Windows, and live SSH performance were not benchmarked. Full repository checks remain for CI.

Review

Self-reviewed component boundaries, event callbacks, confirmation state resets, and unchanged platform/remote routing. No new effect, cache, timer, IPC, or lifecycle policy.

Agent skill upstream boundary

  • Not applicable; no skill source changes.

Checklist

  • This PR is small and focused
  • Explained what changed and why, including user trade-offs
  • Before/after screenshots attached, with measurement limitations
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and shortcut impact considered
  • Targeted checks passed; full repository checks left to CI

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 1 minute.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 6cfb209f-1607-4afc-aefd-a795ad1441bd

📥 Commits

Reviewing files that changed from the base of the PR and between ef6eeab and 4451fdf.

📒 Files selected for processing (6)
  • src/renderer/src/components/tab-bar/tab-bar-static-create-menu.tsx
  • src/renderer/src/components/tab-bar/tab-bar-surface.tsx
  • src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx
  • src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx
  • src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx
  • src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pullfrog pullfrog Bot left a comment

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.

✅ No new issues found.

Reviewed changes

This is a behavior-preserving refactor that exploits Radix's existing mount-on-open behavior to skip constructing closed menus and dialogs during terminal/worktree activation. Closed DropdownMenuContent/DialogContent already do not mount children (no forceMount), so relocating translate() calls and JSX construction into components rendered under the content defers that work until a surface opens. Child-component mount semantics are unchanged — they were already inside the content subtree.

  • TerminalContextMenu: the outer component now renders only DropdownMenu/trigger/content; the full item list moved to a module-scope TerminalContextMenuItems child, including the useMemo shortcut-label computation. The onOpenChange 100 ms guard and all onSelect handlers are byte-for-byte equivalent.
  • CloseTerminalDialog: body extracted to CloseTerminalDialogBody; useId/useState and the render-phase reopen/subjectKey reset stay in the always-mounted parent, so queued confirmations and reopen resets keep working.
  • TabBarStaticCreateMenu: renderTabBarStaticCreateMenu() converted to a component; tab-bar-surface.tsx now suspends it under DropdownMenuContent instead of eagerly calling it on every render (which happened regardless of menu open, since showStaticCreateMenuItems only depends on the query).
  • Tests: added closed-state tests asserting zero translate work while closed and construction on open. I confirmed the TerminalContextMenu test is falsifiable — forcing eager construction makes it fail with 13 translate calls — and both changed suites pass, as does pnpm tc:web.

Pullfrog  | View workflow run | Using openrouter/deepseek/deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@nwparker
nwparker merged commit 923858e into main Sep 12, 2026
27 of 29 checks passed
@nwparker
nwparker deleted the nwparker/Faster-Worktree-switch branch September 12, 2026 07:52
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.

Reduce closed-menu rendering overhead when returning to a worktree

1 participant