Skip to content

perf(renderer): pause long-lived status animations offscreen and honor reduced motion - #37162

Merged
nwparker merged 1 commit into
mainfrom
nwparker/long-running-indicator-motion
Nov 2, 2026
Merged

nwparker merged 1 commit into
mainfrom
nwparker/long-running-indicator-motion

Conversation

@nwparker

@nwparker nwparker commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 0 0 0 0
Prod 10 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​63 $\color{#cf222e}{\Huge{\mathbf{−}}}$​16 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​47

Stacked on #37153 (reuses its offscreen observer). Retarget to main after that merges.

ELI5

Some status animations (the shimmer on a sidebar card whose PR info is stale, the spinner on pending CI checks) can run for an hour or more. One of them redraws its text every single frame. Now they redraw less often, pause when scrolled out of view, and stop for people who asked their OS to reduce motion.

What Changed

  • Stale-review shimmer (sidebar card status icon and hover-card reference rows): steps(30) per 2.4 s sweep, via a new shimmer-steps-* utility (--shimmer-timing, default linear, so other shimmers are unchanged). It also pauses while its card is out of view.
  • Generic offscreen pause: [data-animation-offscreen] now pauses animations on the marked element and its descendants (animation-play-state: paused). The agent ring keeps its stricter animation: none from perf(renderer): stop agent spinners that are scrolled out of view #37153 so reveal re-anchors phase. observeOffscreenAnimation now accepts null so it can be used directly as a React ref.
  • Pending CI checks (checks list rows, folder-workspace PR rows): pause offscreen.
  • Reduced motion added where it was missing on long-lived indicators: pending CI check spinners (list, PR rows, triage strip), the phone-driver dot on a terminal pane, the notebook kernel busy pulse.
  • The triage strip reads the pending color from CHECK_COLOR.pending (same amber) instead of repeating the raw class.

Why: audit of long-lived animations

Any running CSS animation keeps the compositor ticking at the display refresh rate. If it animates a paint property, the main thread also repaints every frame. The common practice for always-on indicators is: pause when offscreen, pause when the document is hidden, use steps() to cap frame work, and honor reduced motion. Audit of every infinite animation in the renderer (~450 animate-spin, ~83 animate-pulse, 12 custom keyframes):

Long-lived animation Runs while Offscreen pause Hidden steps() Reduced motion
Agent working ring agent working ✅ #37153 ✅ ✅ ✅
Stale-review shimmer review data stale (can last indefinitely offline) ✅ this PR ✅ ✅ this PR ✅
Pending CI check spinner check pending (5–60 min) ✅ this PR ✅ n/a ✅ this PR
Phone-driver dot phone driving a pane (one per pane) ✅ n/a ✅ this PR
Notebook kernel busy cell running (one per notebook) ✅ n/a ✅ this PR
Native chat live/subagent/awaiting pulses turn in progress per message ✅ n/a ✅ already
Browser agent glow agent driving a page (one per pane) ✅ n/a ✅ already

"Hidden" is handled by Chromium throttling a hidden/occluded window (re-enabled in #9395). Everything else found is bounded (button and fetch loaders, skeletons, onboarding/tips).

Not changed here (follow-up): the client-hosted browser page position loop runs requestAnimationFrame every frame while any client-hosted page is attached. It tracks pane moves that fire no events, so replacing it needs its own change.

Measurements

Minimal Electron page, 20 stale-style shimmer labels plus 20 rings, 60 Hz, 4 s trace, renderer threads (ms of CPU per second):

Shimmer timing Paints/s Main thread Compositor Total
linear (before) 60 28.8 17.9 46.7
steps(30) (after) 12.5 17.8 7.3 25.1 (−46%)

Offscreen labels drop to zero animation work, the same as the rings in #37153 (200-workspace lineage: renderer+GPU −55%, main thread −81%). Paint cost scales with refresh rate, so on 120/144 Hz displays the linear shimmer costs 2–2.4× more, and steps(30) stays at 12.5 updates/s.

Verified in a dev build: the shimmer computes steps(30) / 2.4s; descendants of an offscreen-marked element compute paused; the agent ring under the attribute computes animation-name: none.

Visual Proof

Stale shimmer, before (linear) vs after (steps(30)), same 2.4 s sweep:

stale-shimmer-before-after.mp4

Reduced-motion and offscreen changes have no visible effect (static icon, or element not on screen).

Testing

  • pnpm test over sidebar, right-sidebar, terminal-pane, notebook and the observer tests: 589 files / 5399 tests pass
  • pnpm tc:web, oxlint/oxfmt, pnpm check:code-quality:changed origin/main (0 findings)
  • Dev build CSS checks above; minimal-page trace for the shimmer numbers
  • Platforms: macOS measured. CSS and IntersectionObserver are platform-independent. Web client shares the renderer. Mobile is native and unaffected.

Agent skill upstream boundary

  • Not applicable

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • Before/after video attached for the visible change
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered

@nwparker
nwparker force-pushed the nwparker/long-running-indicator-motion branch from d8acb00 to 005530a Compare November 1, 2026 21:58
@nwparker
nwparker force-pushed the nwparker/bug-renderer-compositor-thread-spins-at-100-of-o branch 2 times, most recently from b0c628e to 19b51cf Compare November 1, 2026 23:29
@nwparker
nwparker force-pushed the nwparker/long-running-indicator-motion branch from 005530a to 38d6180 Compare November 1, 2026 23:29
@nwparker
nwparker changed the base branch from nwparker/bug-renderer-compositor-thread-spins-at-100-of-o to main November 2, 2026 00:05
@nwparker
nwparker force-pushed the nwparker/long-running-indicator-motion branch from 38d6180 to 865e3fc Compare November 2, 2026 00:05
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 314a91e1-061d-4872-9bbe-0879855647e1
Commits

Reviewing files that changed from the base of the PR and between 865e3fc and d8130bf.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1ad5d717-39b7-4c27-8b7f-956f500188aa
Commits

Reviewing files that changed from the base of the PR and between ec112c6 and 865e3fc.

Files selected for processing: 10
  • src/renderer/src/assets/main.css
  • src/renderer/src/assets/text-shimmer.css
  • src/renderer/src/components/editor/IpynbKernelToolbar.tsx
  • src/renderer/src/components/right-sidebar/FolderWorkspacePrChecksRow.tsx
  • src/renderer/src/components/right-sidebar/checks-panel/checks-list.tsx
  • src/renderer/src/components/right-sidebar/checks-panel/triage-strip.tsx
  • src/renderer/src/components/sidebar/WorktreeCardReferenceRow.tsx
  • src/renderer/src/components/sidebar/WorktreeCardStatusSlot.tsx
  • src/renderer/src/components/terminal-pane/MobileDriverOverlay.tsx
  • src/renderer/src/lib/offscreen-animation-pause.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.

Walkthrough

The changes add offscreen animation handling to selected pending and stale indicators. The shimmer animation now uses configurable step timing. Selected indicators disable animation when reduced motion is preferred. The triage spinner uses the shared pending color.

Merge Risk: Minimal · previous review 865e3

No actionable current-head risk was established; the PR is mergeable with normal checks.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Description check Warning The description provides strong coverage of the changes, rationale, measurements, visual proof, and testing. However, it omits the required Risk section and does not provide an actual linked issue; re… Add a Linked Issue section with the applicable issue reference. Add a Risk section with the risk level, affected users, possible failure modes, and rollback method. Complete any applicable checklist items and required template sections befo…
Docstring Coverage Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main renderer performance and reduced-motion changes.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description provides strong coverage of the changes, rationale, measurements, visual proof, and testing. However, it omits the required Risk section and does not provide an actual linked issue; referencing stacked PR #37153 does not satisfy the template's issue requirement.

Resolution

Add a Linked Issue section with the applicable issue reference. Add a Risk section with the risk level, affected users, possible failure modes, and rollback method. Complete any applicable checklist items and required template sections before merging.

Full details: Docstring Coverage

Explanation

Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 8 functions across 8 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

…r reduced motion

The stale-review shimmer animates background-position on the main thread for
as long as review data stays stale, once per sidebar card. It now steps 30
times per sweep and pauses when its card is out of view. Pending-check
spinners also pause offscreen, and pending checks, the phone-driver dot and
the notebook busy pulse stop under reduced motion.
@nwparker
nwparker force-pushed the nwparker/long-running-indicator-motion branch from 865e3fc to d8130bf Compare November 2, 2026 00:25
@nwparker
nwparker merged commit 7c90323 into main Nov 2, 2026
49 checks passed
@nwparker
nwparker deleted the nwparker/long-running-indicator-motion branch November 2, 2026 00:58
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