Skip to content

fix: stop the sidebar's working glyphs from keeping bb redrawing at the refresh rate - #30

Merged
yusuf8834 merged 4 commits into
mainfrom
perf/static-working-glyphs
Nov 11, 2026
Merged

yusuf8834 merged 4 commits into
mainfrom
perf/static-working-glyphs

Conversation

@yusuf8834

Copy link
Copy Markdown
Owner

What was wrong

While a thread worked, the sidebar's glyphs animated forever: the working spinner (animate-spin), the activity glyphs (animate-shine-icon), the Working shelf header, and the empty-Active typist. In Chromium any running animation redraws the window at the display's refresh rate. On a 120 Hz MacBook Pro that kept bb's renderer, GPU process and WindowServer at about one core while bb was on screen, however small the glyph. bb's built-in sidebar has the same cost (get-bb/bb#7233).

Measured with bb frontmost, one thread working, 15–20 s windows, at least two runs each:

Variant Total CPU (renderer + GPU + WindowServer)
Spinning SVG (before) 101–111%
Spin on a wrapper span 87–90%
steps(8), 8 fps image, or JS timer 36–44%
Nothing animating 4–7%
T3-style pulse (this PR), in a separate run with Home open +2–4 points over static

What changed

  • The working glyph is a pulse in a box (PulseRectangle01, registered as Working) instead of a spinner; a spinner held still reads as frozen. It breathes like T3 Code's status dot (pingdotgg/t3code#3978): a 2 s opacity cycle with long holds and steps(6) ramps, on an HTML wrapper rather than the SVG. Each pulse's startTime is pinned to 0 on animationstart, so working rows change in the same frames (#12962). It holds still under reduced motion. The Active shelf keeps the bare pulse.
  • The activity glyphs (workflow, background agent and command, plan mode, goal) and the Working shelf header hold still.
  • The typist in an empty Active plays for about 4 s, then rests on its at-rest pose.
  • bb's own ThreadStatusGlyph, drawn for other plugins' row statuses, is wrapped so its pulse and shine mask are removed.
  • Short loading spinners keep turning and now honour reduced motion (settings-ui, OpenPorts).
  • "Working · 5m" survives a reload. The working-since stamps are only reconciled once the thread list is ready, as usePinnedProjects already does, so a loading or failed list no longer wipes every duration.
  • CHANGELOG entries under Unreleased.

How I verified

  • npm run check passes: lint, format, Knip, typecheck, 1081/1081 tests, build and the bundle check.
  • motion.test.ts scans shipped source for looping utilities and infinite keyframes outside a listed set, each with a reason. Putting animate-spin back on the working glyph fails it.
  • working-pulse.test.ts checks that the pulse sits on an HTML wrapper, that only the working glyph pulses, and that the sync pins startTime to 0 while leaving other animations alone.
  • useWorkingSince.test.ts fails without the ready check.
  • In bb 0.49.1-nightly, with the plugin loaded from this branch, document.getAnimations() shows no endless animation in the sidebar except the working pulse, at startTime 0.
  • The CPU numbers above come from the same 120 Hz Mac.

… the refresh rate

Any endless animation keeps Chromium redrawing bb's window at the display's
refresh rate. On a 120 Hz MacBook Pro the working spinner alone kept bb's
renderer, GPU process and WindowServer near a full core while a thread
worked; a wrapper rotation, steps(8), an 8 fps image or a JS timer still
cost 37-87% of a core, and only a still glyph brought it back to idle.

- StatusGlyph: the runtime spinner and the activity glyphs keep their
  shapes and tones but no longer spin or shine
- the Working shelf header icon holds still
- the empty-Active typist plays for about 4 s, then rests on its at-rest pose
- bb's ThreadStatusGlyph, drawn for other plugins' row statuses, is wrapped
  so its pulse and shine mask are removed
- short-lived loading spinners keep turning and now honour reduced motion
- motion.test.ts fails on any new looping animation outside an allowlist

Refs get-bb/bb#7233
Reconciling against a loading or failed list cleared every stamp, so a
reload restarted every "Working · 5m" from zero. Reconcile only once the
list is ready, as usePinnedProjects already does.
A spinner held still reads as frozen. A pulse in a rounded box says
"running" without promising motion, matches the enclosed circle-x and
circle-question, and stays distinct from the Active shelf's bare pulse.
The Working shelf header uses it too.
A T3 Code-style pulse (pingdotgg/t3code#3978): a 2 s cycle with long holds
and steps(6) ramps on an HTML wrapper's opacity, pinned to the document
timeline on start so every working row changes in the same frames, and
held still under reduced motion. Measured on a 120 Hz Mac with Home open,
it cost 2-4 points of a core above a still glyph, where the old spin cost
a full core. motion.test.ts lists it with that reason.
@yusuf8834
yusuf8834 merged commit 1663038 into main Nov 11, 2026
2 checks passed
@yusuf8834
yusuf8834 deleted the perf/static-working-glyphs branch November 11, 2026 00:30
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