orca/docs/reference/react-performance-audit.md

27 KiB

React Performance Audit

Status: in progress Started: 2026-05-29 Base commit: b7fe967780 (origin/main, release v1.4.35)

Goal

Scan the full React surface for $perf and $react-useeffect issues, keep coverage evidence here, and land the fixes as many small PRs with explicit merge-risk notes.

This document is the audit ledger. A section is not considered fully scanned until it has:

  1. File inventory checked from the current worktree.
  2. useEffect / useLayoutEffect / useInsertionEffect sites classified.
  3. Perf-sensitive patterns checked: timers, subscriptions, observers, JSON.stringify, storage writes, polling, broad store subscriptions, large list rendering, and render-time derived state.
  4. Each suspicious site dispositioned as "no change", "needs PR", or "covered by PR".
  5. Merge risk recorded for every PR candidate.

Scope

React hook call scan uses all repo *.ts and *.tsx files, then narrows to actual AST hook calls. The React Effect surface found on 2026-05-29 is:

  • src/renderer/src/**
  • mobile/app/**
  • mobile/src/**
  • mobile/packages/expo-two-way-audio/src/hooks.ts

Comment-only mentions outside those paths were ignored.

Initial inventory:

Metric Count
Repo *.ts / *.tsx files 3,011
Files with real Effect hook calls 285
Effect hook call sites 970
useLayoutEffect / useInsertionEffect call sites 44
Empty dependency arrays 111
Effects with cleanup returns 472
Effects with subscription/listener/observer shape 170
Effects with timer or animation-frame shape 128
Effects with JSON.stringify shape 2
Set-state-shaped Effects needing manual review 270
useMemo call sites in React scope 633
useCallback call sites in React scope 1,415
useSyncExternalStore call sites in React scope 8

Coverage Ledger

Current count after low-risk PRs #3038, #3041, #3042, #3044, #3051, #3052, #3053, #3054, #3055, #3056, #3058, #3059, #3060, #3062, #3063, #3064, #3065, #3066, #3067, #3068, and #3069: 936 Effect hook call sites.

Open medium-risk PRs #3070 and #3073 each lower their branch count to 935 Effect hook call sites, and open high-risk PR #3075 lowers its branch count to 932 Effect hook call sites. These are not counted in the merged baseline until reviewed and merged.

Area Files / signal Scan status Notes
Renderer app shell src/renderer/src/App.tsx, root components Inventory complete, manual review pending Check global listeners, beforeunload, media-query, sidebar resize, active-tab repair.
Terminal / PTY components/Terminal.tsx, components/terminal-pane/**, components/terminal/** Inventory complete, manual review pending High-risk area: xterm lifecycle, scrollback, remote/mobile parity, focus, WebGL, resize.
Browser pane components/browser-pane/** Inventory complete, manual review in progress Highest Effect density: 62 sites in BrowserPane.tsx. Check driver sync, address bar derived state, find state, webview lifetime.
Editor / markdown / Monaco components/editor/** Inventory complete, manual review in progress 117 area Effects. Check editor model cleanup, preview scroll restore, search debounce, generated decorations.
Sidebar / worktrees components/sidebar/** Inventory complete, manual review in progress Check worktree list state repair, drag/drop global listeners, kanban pointer flows, inline rename.
Right sidebar / source control components/right-sidebar/** Inventory complete, manual review pending Check polling, PR checks, file explorer watch/reveal, source-control local resets. Git provider compatibility required.
Settings components/settings/** Inventory complete, manual review pending 81 area Effects. Many draft-mirror candidates; keep SSH and cross-platform settings behavior intact.
Issue, PR, task pages TaskPage.tsx, PullRequestPage.tsx, GitHubItemDialog.tsx, GitLabItemDialog.tsx, Linear components Inventory complete, manual review in progress Large files with many Effects. Separate GitHub, GitLab, Linear, and generic review behavior.
Onboarding / feature wall components/onboarding/**, components/feature-wall/** Inventory complete, manual review pending Lower merge risk for pure visual/demo state, but avoid changing telemetry semantics.
Status, dashboard, activity components/status-bar/**, components/dashboard/**, components/activity/** Inventory complete, manual review in progress Status-bar account menu close reset covered by #3051. Check interval sharing, retained agent state, activity terminal portals.
Mobile app routes mobile/app/** Inventory complete, manual review pending 79 Effects, including large tasks.tsx and session route. Remote-client parity required.
Mobile shared source mobile/src/** Inventory complete, manual review pending Browser pane, transport client context, dictation hook, bottom drawer, new worktree modal.
Expo two-way audio hook mobile/packages/expo-two-way-audio/src/hooks.ts Inventory complete, manual review pending Single Effect plus useSyncExternalStore; verify native subscription cleanup.
Tests with hook mocks *.test.ts, *.test.tsx, e2e comments Inventory complete, manual review pending Do not count comment-only mentions as app Effect sites; update tests beside behavior changes.

Current Findings Queue

These are candidate batches, not final conclusions. Each item needs code inspection before implementation.

Candidate PR Area Symptom / wasted work to prove Likely files Merge risk
PR A Small controlled-input resets Extra render pass from Effects that mirror one prop into local draft state. BrowserAddressBar.tsx covered by #3038; continue with BrowserFind.tsx, PdfFind.tsx, selected settings draft fields Low to medium
PR B Settings draft hydration Multiple independent Effects mirror persisted settings into draft state. components/settings/** Medium
PR C Browser pane Effect cluster Large Effect cluster may mix external webview sync, derived UI state, and event-specific state repair. BrowserPane.tsx, useGrabMode.ts, browser tabs High
PR D Terminal tab repair and lifecycle Effects repair active terminal/browser tab state after render; could cause extra render and focus churn. Terminal.tsx, useTerminalTabs.ts, TerminalPane.tsx High
PR E Right-sidebar polling and source-control state repair PR checks, branch snapshots, file explorer watch/reveal may repeat work after unrelated state changes. ChecksPanel.tsx, SourceControl.tsx, useGitStatusPolling.ts, file explorer hooks High
PR F Mobile route monoliths Mobile task/session routes have many chained Effects and timer/refetch patterns. mobile/app/h/[hostId]/tasks.tsx, mobile/app/h/[hostId]/session/[worktreeId].tsx High
PR G Mobile shared components Visible/open Effects reset modal/drawer state and browser address state. mobile/src/components/**, mobile/src/browser/MobileBrowserPane.tsx Medium
PR H Editor search/preview/decorations Debounce and scroll-restore Effects may be legitimate external sync but need cleanup/count review. components/editor/** Medium to high
PR I Feature wall/onboarding demos Animation/demo Effects can often move to event handlers or tighter custom hooks. components/feature-wall/**, components/onboarding/** Low to medium
PR J GitHub filter controls Extra render pass from mirroring parsed reviewer qualifier into local mode state. PRFilterDropdowns.tsx covered by #3041 Low
PR K Sidebar project filter Extra render pass from mirroring the first filtered repo into command selection state. SidebarFilter.tsx covered by #3042 Low
PR L Diff note edit draft Extra render pass from mirroring saved note body into edit draft while not editing. DiffCommentCard.tsx covered by #3044 Low
PR M Status-bar account menus Extra render pass from closing Claude/Codex account submenus in Effects after the provider menu closes. StatusBar.tsx covered by #3051 Low
PR N Browser tab favicon fallback Extra render pass from resetting failed favicon state in an Effect after tab favicon identity changes. BrowserTab.tsx covered by #3052 Low
PR O Workspace title rename draft Redundant draft sync Effect runs while the inline title rename input is not mounted. WorktreeTitleInlineRename.tsx covered by #3053 Low
PR P Feature-wall tour static substep repair Three no-op Effects scan static step arrays and repair ids that are only written from those arrays. FeatureWallTourSurface.tsx covered by #3054 Low
PR Q Repo combobox mount-open state Mount-only auto-open path uses an Effect and ref guard instead of initializing state from the prop. RepoCombobox.tsx covered by #3055 Low
PR R Quick Open query reset Extra render pass from clearing the Quick Open input after the dialog opens. QuickOpen.tsx covered by #3056 Low
PR S Project group dialog open resets Extra render pass from seeding name/delete dialog local state after the dialog opens. ProjectGroupNameDialog.tsx, ProjectGroupDeleteDialog.tsx covered by #3058 Low
PR T Onboarding agent fallback disclosure Extra render pass from opening the fallback agent list when a selected agent first appears there. AgentStep.tsx covered by #3059 Low
PR U Feature-wall tour workflow resets Four local reset Effects run after workflow changes or close instead of in the selection/close path. FeatureWallTourSurface.tsx covered by #3060 Low
PR V Workspace board selection pruning Local kanban selection is repaired in an Effect after the drawer closes or board rows change. use-workspace-kanban-selection.ts covered by #3062 Low
PR W Workspace board column resize Two mirror Effects sync the latest commit callback and external committed width after render. use-workspace-kanban-column-resize.ts covered by #3063 Low
PR X Terminal quick-command dialog draft Dialog draft and agent preset search are reset in an Effect after the dialog opens or retargets. TerminalQuickCommandDialog.tsx covered by #3064 Low
PR Y Onboarding notification settings ref A ref mirror Effect keeps notification handlers pointed at the latest settings. NotificationStep.tsx covered by #3065 Low
PR Z Onboarding agent-selection ref mirrors Five ref mirror Effects keep stable onboarding handlers pointed at latest selection/detection snapshots. use-onboarding-flow.ts covered by #3066 Low
PR AA Workspace board drag ref mirrors Area-selection and card-drag pointer handlers receive latest board callbacks through Effect-updated refs. use-workspace-kanban-area-selection.ts, use-workspace-kanban-card-pointer-drag.ts covered by #3067 Low
PR AB Feature-wall tour telemetry refs Close telemetry reads source/depth payload inputs through Effect-updated refs. use-feature-wall-tour-telemetry.ts covered by #3068 Low
PR AC Onboarding persisted theme ref The theme cleanup ref mirrors the latest persisted setting through an Effect. use-onboarding-flow.ts covered by #3069 Low
PR AD Terminal window blur restart snapshot A mount-only Effect rewrites the same startup blur snapshot already captured by the lazy ref initializer. TerminalWindowSection.tsx covered by #3070 Medium
PR AE New-workspace composer note ref A note ref mirror Effect runs after each note edit so stable PR/MR selection callbacks can read latest text. useComposerState.ts covered by #3073 Medium
PR AF Source-control selection ref mirrors Four ref mirror Effects keep stable source-control row handlers pointed at latest selection inputs. useSourceControlSelection.ts covered by #3075 High

Merge Risk Scale

Risk Criteria Required verification
Low Isolated local UI state, no persistence, no IPC, no terminal/browser/source-control behavior. Targeted unit test if available plus pnpm run typecheck:web.
Medium Settings, editor UI, mobile local UI, or state persisted per workspace but no transport protocol changes. Targeted tests plus manual interaction or focused Playwright/Electron check when UI behavior changes.
High Terminal/PTY, browser webview, source control, mobile remote-client, SSH, polling, persistence, or provider behavior. Targeted tests, pnpm run typecheck:web, and Electron or mobile/manual parity evidence as appropriate.

PR Log

PR Branch Area Risk Status Evidence
#3038 nwparker/react-perf Audit ledger plus browser address bar top-suggestion mirror Effect removal Low Merged pnpm exec oxlint src/renderer/src/components/browser-pane/BrowserAddressBar.tsx; pnpm run typecheck:web.
#3041 nwparker/react-perf-pr-filter PR reviewer filter mode derived from parsed query plus explicit user override Low Merged pnpm exec oxlint src/renderer/src/components/github/PRFilterDropdowns.tsx; pnpm run typecheck:web.
#3042 nwparker/react-perf-sidebar-filter Sidebar project filter command selection derived from filtered repos Low Merged pnpm exec oxlint src/renderer/src/components/sidebar/SidebarFilter.tsx; pnpm run typecheck:web.
#3044 nwparker/react-perf-low-risk-2 Diff note card removes saved-body-to-draft mirror Effect Low Merged pnpm exec oxlint src/renderer/src/components/diff-comments/DiffCommentCard.tsx; pnpm run typecheck:web.
#3051 nwparker/react-perf-low-risk-3 Status-bar account submenus collapse in provider menu open-change handlers Low Merged pnpm exec oxlint src/renderer/src/components/status-bar/StatusBar.tsx; pnpm run typecheck:web.
#3052 nwparker/react-perf-low-risk-4 Browser tab favicon failure reset happens during render for new favicon IDs Low Merged pnpm exec oxlint src/renderer/src/components/tab-bar/BrowserTab.tsx; pnpm run typecheck:web.
#3053 nwparker/react-perf-low-risk-5 Inline workspace-title rename removes inactive draft sync Effect Low Merged pnpm exec oxlint src/renderer/src/components/sidebar/WorktreeTitleInlineRename.tsx; pnpm run typecheck:web.
#3054 nwparker/react-perf-low-risk-6 Feature-wall tour removes static substep id repair Effects Low Merged pnpm exec oxlint src/renderer/src/components/feature-wall/FeatureWallTourSurface.tsx; pnpm run typecheck:web.
#3055 nwparker/react-perf-low-risk-7 Repo combobox initializes mount-open state without an Effect Low Merged pnpm exec oxlint src/renderer/src/components/repo/RepoCombobox.tsx; pnpm run typecheck:web.
#3056 nwparker/react-perf-low-risk-8 Quick Open clears its query on the open edge without a reset Effect Low Merged pnpm exec oxlint src/renderer/src/components/QuickOpen.tsx; pnpm run typecheck:web.
#3058 nwparker/react-perf-project-group-dialogs Project group dialogs reset local open-state during render Low Merged pnpm exec oxlint src/renderer/src/components/sidebar/ProjectGroupDeleteDialog.tsx src/renderer/src/components/sidebar/ProjectGroupNameDialog.tsx; pnpm run typecheck:web.
#3059 nwparker/react-perf-agent-step-latch Onboarding agent fallback disclosure latch updates during render Low Merged pnpm exec oxlint src/renderer/src/components/onboarding/AgentStep.tsx; pnpm run typecheck:web.
#3060 nwparker/react-perf-feature-tour-resets Feature-wall tour reset state moves into workflow/close transitions Low Merged pnpm exec oxlint src/renderer/src/components/feature-wall/FeatureWallTourSurface.tsx; pnpm run typecheck:web.
#3062 nwparker/react-perf-kanban-selection Workspace board selection pruning moves out of an Effect Low Merged pnpm exec oxlint src/renderer/src/components/sidebar/use-workspace-kanban-selection.ts; pnpm run typecheck:web.
#3063 nwparker/react-perf-kanban-column Workspace board column width mirror Effects move to render-time synchronization Low Merged pnpm exec oxlint src/renderer/src/components/sidebar/use-workspace-kanban-column-resize.ts; pnpm run typecheck:web.
#3064 nwparker/react-perf-quick-command-dialog Terminal quick-command dialog resets draft state during render Low Merged pnpm exec oxlint src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.tsx; pnpm run typecheck:web.
#3065 nwparker/react-perf-notification-step-ref Onboarding notification settings ref mirror moves out of an Effect Low Merged pnpm exec oxlint src/renderer/src/components/onboarding/NotificationStep.tsx; pnpm run typecheck:web.
#3066 nwparker/react-perf-onboarding-agent-refs Onboarding agent-selection ref mirrors move out of Effects Low Merged pnpm exec oxlint src/renderer/src/components/onboarding/use-onboarding-flow.ts; pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/onboarding/agent-picked-payload.test.ts; pnpm run typecheck:web.
#3067 nwparker/react-perf-kanban-drag-refs Workspace board drag ref mirrors move out of Effects Low Merged pnpm exec oxlint src/renderer/src/components/sidebar/use-workspace-kanban-area-selection.ts src/renderer/src/components/sidebar/use-workspace-kanban-card-pointer-drag.ts; pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/use-workspace-kanban-card-pointer-drag.test.ts src/renderer/src/components/sidebar/workspace-kanban-area-selection.test.ts; pnpm run typecheck:web.
#3068 nwparker/react-perf-feature-wall-telemetry-ref Feature-wall tour telemetry ref mirror moves out of an Effect Low Merged pnpm exec oxlint src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts; pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.test.ts; pnpm run typecheck:web.
#3069 nwparker/react-perf-onboarding-theme-ref Onboarding persisted theme ref mirror moves out of an Effect Low Merged pnpm exec oxlint src/renderer/src/components/onboarding/use-onboarding-flow.ts; pnpm run typecheck:web.
#3070 nwparker/react-perf-terminal-window-ref Terminal window blur startup snapshot removes redundant mount Effect Medium Open pnpm exec oxlint src/renderer/src/components/settings/TerminalWindowSection.tsx; pnpm run typecheck:web.
#3073 nwparker/react-perf-composer-note-ref New-workspace composer note ref mirror moves out of an Effect Medium Open pnpm exec oxlint src/renderer/src/hooks/useComposerState.ts; pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/hooks/composer-branch-selection.test.ts; pnpm run typecheck:web.
#3075 nwparker/react-perf-source-control-selection-refs Source-control selection ref mirrors move out of Effects High Open pnpm exec oxlint src/renderer/src/components/right-sidebar/useSourceControlSelection.ts; pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/right-sidebar/useSourceControlSelection.test.ts; pnpm run typecheck:web.

Reproduction Commands

Run from the worktree root.

rg --files -g '*.tsx' -g '*.ts' | wc -l
rg -l "\\b(useEffect|useLayoutEffect|React\\.useEffect|React\\.useLayoutEffect)\\b" -g '*.tsx' -g '*.ts' | sort

The exact Effect-site counts above came from a TypeScript AST scan so comment-only mentions are not counted.