Skip to content

fix: eliminate terminal activation replay and light-theme GPU filter (Mac perf)#310

Merged
parsakhaz merged 2 commits into
mainfrom
mac-perf
Jul 7, 2026
Merged

fix: eliminate terminal activation replay and light-theme GPU filter (Mac perf)#310
parsakhaz merged 2 commits into
mainfrom
mac-perf

Conversation

@parsakhaz

Copy link
Copy Markdown
Member

Summary

Fixes the multi-second freezes when switching panes/tabs on macOS and the high GPU-helper energy impact (300–400 in Activity Monitor). Root causes found by investigation: every terminal activation replayed its full scrollback through xterm's parser twice (avg 585KB, max 4.8MB per panel measured in a live DB), and light themes ran a per-frame invert(1) hue-rotate(180deg) compositor filter over every terminal canvas.

Work Completed

Mac terminal performance (tmp/done-plans/2026-07-06-mac-terminal-performance.md)

  • Activation depth by cause (builds on fix: repaint terminal panels on window refocus (#306) #308's unified activation effect): performance mode does a masked fit+repaint on tab activation and a silent repaint on pure refocus — no reset(), no scrollback replay. Battery saver keeps exactly one full re-sync (its PTY output is cadence-gated while hidden, so the buffer can genuinely be stale). The manual Refresh button keeps full replay.
  • WebGL post-attach repaint deferred past next paint (double rAF): refreshing in the same task as the addon attach could hit an uncomposited canvas on blur→refocus reattach, leaving stale pixels.
  • Baked inverted light palette: :root.light now defines real --color-terminal-* tokens computed as the exact invert+hue-rotate of the dark palette, so ANSI output looks identical to the old filtered rendering with zero per-frame GPU cost; the .light .xterm filter rule is deleted. Truecolor TUI content now shows true colors (standard emulator behavior). minimumContrastRatio 4.5 in light mode.
  • IPC payload cuts: terminal:getState no longer ships the up-to-8MB serialized snapshot alongside raw scrollback, and restore payloads are ceilinged at 512KiB (matches Orca's TERMINAL_SCROLLBACK_REPLAY_BYTE_LIMIT and Superset's MAX_HISTORY_SCROLLBACK_BYTES).
  • Serialize-on-hide throttled to once per 10s per panel (snapshot is only consulted on app restart).
  • Animations pause on window blur via a window-blurred root class wired to window:focus-changed, not just document.hidden (which rarely fires for a visible-but-unfocused window on macOS).

Pre-Merge Testing

  • Switch terminal tabs rapidly in performance mode — instant, brief "Refreshing terminal…" mask, no grid-refit jank, no stale content
  • Blur the app 15+ seconds (WebGL detaches), refocus, revisit tabs — no black/blank canvas, no stale pixels
  • Battery saver mode: hide window, generate output, re-show — content catches up with one full re-sync
  • Light / light-rounded theme: shell ANSI output looks identical to pre-change; selection highlight visible; TUIs show true colors
  • TUI (htop/claude) running during tab switches and window resizes — repaints correctly
  • Quit and relaunch — terminals restore scrollback with the [Session Restored] marker

Build Verification

  • pnpm typecheck passes (all workspaces)
  • pnpm lint passes (0 errors)
  • pnpm build:main + pnpm build:frontend pass (xterm request-mode build verification OK)

parsakhaz added 2 commits July 6, 2026 22:10
Plan: mac-terminal-performance (tmp/done-plans/2026-07-06)

- Terminal activation no longer does reset()+full-scrollback replay twice;
  performance mode does fit+refresh behind the existing activation mask,
  batterySaver keeps a single re-sync
- WebGL reattach paints after next layout (fixes blank canvas on refocus)
- Light themes use a baked inverted palette instead of a per-frame
  invert()+hue-rotate() compositor filter over every terminal canvas;
  minimumContrastRatio 4.5 in light mode
- terminal:getState omits the serialized snapshot when raw scrollback
  exists and caps restore payloads at 512KiB (matches Orca/Superset)
- Serialize-on-hide throttled to 10s per panel
- CSS animations pause on window blur, not just document.hidden
…ecycle

Background-mounted keep-alive tabs ran fitAddon.fit() against their
display:none (0x0) container at mount, post-restore, and font change.
FitAddon has no cols floor, so the grid shrank to a few columns and
background PTY output parsed into a garbage-width buffer that xterm
reflow cannot repair. The old every-activation reset+replay silently
rebuilt the buffer on view, masking the defect; the light activation
repaint exposed it.

- Skip grid fits when the container is below MIN_VIABLE_RECT_PX
  (mount, post-restore, font-change), matching resizePtyToFit
- Arm needsFullActivationRefreshRef when the mount fit is skipped, so
  the first activation of a hidden-mounted panel does one full replay
- Add a component-level lifecycle doc covering mount/hidden/activation/
  WebGL/persistence/unmount invariants
@parsakhaz parsakhaz merged commit c619e44 into main Jul 7, 2026
16 checks passed
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.

1 participant