Skip to content

Performance audit: identified hotspots in desktop app#7

Draft
Copilot wants to merge 2 commits into
refactor-glass-design-systemfrom
copilot/add-performance-logs-and-export
Draft

Performance audit: identified hotspots in desktop app#7
Copilot wants to merge 2 commits into
refactor-glass-design-systemfrom
copilot/add-performance-logs-and-export

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 20, 2026

Follow-up to the perf-log instrumentation: a static walk of apps/desktop/ to actually name the bottlenecks, with file:line citations. No code changes in this PR — review the findings, then I'll open targeted fixes.

P0 — "the longer it runs, the slower it gets"

  • Workspace never unmounts. App.tsx:593-604 toggles display:none instead of unmounting, so every Workspace effect keeps running on Settings/History/Analytics tabs.
  • RuntimeOverviewCard polls 3 IPCs every 7s with no isActive gate. components/workspace/RuntimeOverviewCard.tsx:111-113. Only checks document.visibilityState.
  • Per-bubble 1s timer runs in background. components/workspace/WorkspaceMessageBubble.tsx:845setInterval(() => setNow(Date.now()), 1000) on every streaming bubble, regardless of tab/visibility.
  • Overlapping pollers, unaligned periods. 2.5s / 3s / 3s / 5s / 7s across InteractiveToolEventsPanel:193, HeadlessSessionsPanel:198, useWorkspaceSessionDecorations.ts:467, pages/Sessions.tsx:260, RuntimeOverviewCard.tsx:111. Most duplicate already-wired listen() event streams.

P1 — long-conversation jank

  • MarkdownRenderer is not memoized and passes an inline components={{...}} to react-markdown. components/history/MarkdownRenderer.tsx:313-326. New object identity every parent render → full markdown subtree (incl. Prism) re-renders for every visible bubble. Single biggest contributor to scroll/segment-switch jank.
  • History only grows. components/history/HistoryDetail.tsx:79-103renderedMessageCount increments via IntersectionObserver but older messages never unmount. No virtualization library in package.json.
  • ProxyDebug rebuilds the full list per event. pages/ProxyDebug.tsx:118-122[payload, ...prev.filter(i => i.id !== payload.id)].slice(0, 200) runs O(n) on every proxy-traffic event.

P2

  • 7 useAppStore call sites missing shallow — notably high-churn HeadlessSessionsPanel and WorkspaceNativeSessionView (×2). Also BindToChatDialog, AiCronPanel, PetEntry, BindToTelegramDialog, SessionsCard.
  • focus-sync chain redundant with event bus. App.tsx:401-460 fires loadCurrentEnv + loadEnvironments (+ loadSessions) on every refocus despite env-changed / perm-changed event coverage.

P3

  • backdrop-filter surface area — already mitigated by performanceMode='reduced', not the primary issue.
  • CronTasks (1226 LOC) / Sessions (924) / Skills (601) lack virtualization. Memoization gains first.

Suggested fix sequence

  1. First PR (smallest, biggest payoff): isActive gate on RuntimeOverviewCard + memo(MarkdownRenderer) with components hoisted to module scope + add shallow to the 7 sites.
  2. Second PR: unify pollers behind usePoll(fn, ms, { active, visibilityGated, idle }); gate the per-bubble 1s timer on streaming && visible; ProxyDebug → Map<id, item> + useDeferredValue.
  3. Third PR: focus-sync → staleness-triggered; evaluate @tanstack/react-virtual for History once re-render storms are gone; add Rust-side tracing on the high-frequency commands surfaced by perf-log.

Want me to proceed with step 1?

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.

2 participants