You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] Desktop app (macOS) pegs GPU at 70-90% while idle — should be near-zero #2959
Buzz Desktop (v0.4.26, xyz.block.buzz.app, Tauri + React) continuously consumes 70-90% GPU on an M1 Pro (MacBookPro18,1) while the window is simply open and idle — no scrolling, no typing, no huddle active. GPU drops to 0% immediately when the window is minimized, confirming it's active rendering/compositing rather than a background task.
For a chat app displaying mostly static text, idle GPU should be in the single digits. 70-90% sustained is a significant resource drain (battery, fan noise, energy) on Apple Silicon.
Likely contributors found in code:
src/shared/ui/SpoilerParticles.tsx — a 2D canvas that calls requestAnimationFrame(frame) every frame regardless of whether content changed (lines 135/144), gated only by prefers-reduced-motion.
src/shared/theme/ThemeProvider.tsx — macOS window vibrancy (set_window_vibrancy, material sidebar) is force-applied by "Buzz" themes with no user opt-out (line 324, 348). Full-window NSVisualEffectView blur forces continuous GPU compositing. Switching to a non-Buzz theme did not reduce idle GPU in my testing.
tauri.conf.json sets "backgroundThrottling": "disabled" — worth revisiting given the above.
Repro:
Open Buzz Desktop on macOS (Apple Silicon), any channel, leave idle.
Observe GPU usage (Activity Monitor ▸ GPU History, or a stats tool): 70-90%.
Minimize window → drops to 0%.
Expected: GPU near-idle (single digits) when no visible content is animating. The particle canvas should pause when its content hasn't changed, and vibrancy should be a user-toggleable setting rather than theme-mandated.
Workaround: enabling macOS Reduce Motion helps (disables the particle loop) but shouldn't be required to use the app without excessive GPU draw.
Same symptom on Windows, and I think I can narrow the cause. Two of the three
suspects in the original report look clear, and there is a fourth that has not
come up yet.
A CSS animation is producing a frame on every vsync.
.buzz-shimmer in desktop/src/shared/styles/globals/animations.css animates background-position on a background-clip: text element, 2600ms linear infinite. background-position is not compositor-animatable, so each frame is
real paint work rather than a composite, and background-clip: text means the
glyph mask is re-rasterised with it.
desktop/src/shared/ui/Shimmer.tsx mounts it whenever an agent is working, via BotActivityBar, TypingIndicatorRow and CardMintComposerChip.
The measurement. The stylesheet already ships a prefers-reduced-motion: reduce block that sets animation: none on this exact
class, so it can be switched off without patching anything. That makes a clean
A/B rather than an inference. Same window, same session, same working agents,
one variable changed. MainFrame.Draw counted from a CDP tracing profile:
shimmer running (as shipped) 1924 MainFrame.Draw in 12s = 160.3 fps
shimmer off (reduced motion) 12 MainFrame.Draw in 12s = 1.0 fps
160.3 fps is exactly this display's refresh rate. Per 10 seconds, paint went
from 1173 ms to 11 ms, and summed renderer + compositor + GPU thread time fell
61%.
document.getAnimations() on the live page returned exactly two running
animations: this one, and a Tailwind pulse. pulse animates opacity, which
the compositor handles without repainting.
Why reading the code has not found it. A 30-second V8 CPU profile taken
during the exact hot moment showed the main thread 72% idle, with no JS
frame above 0.1% of a core. A CSS animation executes no JavaScript, so a
JS-only profile is structurally blind to this. It took a tracing profile — one
that records paint, raster and composite — to see it at all. Worth flagging
because it is an easy wrong turn for anyone investigating this issue.
On the original report's suspects.SpoilerParticles.tsx is a genuine
unconditional requestAnimationFrame loop and is worth fixing on its own
merits, but it only mounts where a spoiler is rendered. Enabling Reduce Motion
disables the particle loop and the shimmer, so that workaround cannot
distinguish between them — which may be why the shimmer has not surfaced.
Fix options, measured. Same harness, one variable per row:
approach
paint
renderer+compositor+GPU CPU
own compositing layer (will-change + translateZ(0))
-3%
+2%
steps(52, end) on the existing keyframes
-85%
-42%
steps(26, end)
-92%
-47%
steps(13, end)
-95%
-51%
animation removed (reference floor only, not proposed)
-99%
-61%
The compositing-layer approach is worth calling out as a dead end. It is the
reflexive fix and it does essentially nothing here — background-position still
has to be painted, and promoting the element just adds a layer to manage. I
tried it first and it cost slightly more than it saved.
Quantising the timing function is the cheap win. At steps(52, end) the
highlight advances every 50 ms instead of every vsync and still reads as
continuous motion. A transform-animated overlay should reach the floor while
preserving the look exactly, but that is a larger rewrite and I have not
measured it, so I am not claiming a number for it.
Not proposing removal. The shimmer communicates real state and should stay.
Why this is probably quiet in the tracker. The cost scales with refresh rate
and pixel count, because it is per-frame paint of a rasterised text mask. A 4K
160 Hz user pays roughly 2.7x what a 1080p 60 Hz user pays for the identical UI
state. On a 60 Hz laptop it reads as "the fans spin up while agents are busy"
and may never get filed at all.
Caveats, stated plainly. These numbers are one machine — Windows 11,
3840x2160 @ 160 Hz — from a build carrying unrelated local changes elsewhere.
The .buzz-shimmer rule is byte-identical to current main, so the mechanism
transfers, but I have not re-run the numbers against a clean main build. The
2.7x scaling figure is arithmetic from refresh rate and pixel count, not a
second measured configuration. And I measured frame production and paint totals,
not invalidated area — I am not claiming the whole window repaints, only that a
frame is produced every vsync and that paint dominates it.
Happy to open a PR for the steps() change if that is the direction you want,
or to leave it to you if you would rather do the transform rewrite. The
measurement seemed like the part worth handing over.
Yeah, it's the animation. For me in Linux (Kubuntu) it takes between 25 and 30% of CPU, but only when the "Agent is typing..." appears on the screen, because it has that stupid animation like a glowing effect or something. Please remove the animation or make it so we can configure it in Settings.
Activity
Seb0 commentedon Jul 26, 2026
same for me, iGPU goes up to 85%+ on M3 👍
GeneralJah215 commentedon Aug 13, 2026
Same symptom on Windows, and I think I can narrow the cause. Two of the three
suspects in the original report look clear, and there is a fourth that has not
come up yet.
A CSS animation is producing a frame on every vsync.
.buzz-shimmerindesktop/src/shared/styles/globals/animations.cssanimatesbackground-positionon abackground-clip: textelement,2600ms linear infinite.background-positionis not compositor-animatable, so each frame isreal paint work rather than a composite, and
background-clip: textmeans theglyph mask is re-rasterised with it.
desktop/src/shared/ui/Shimmer.tsxmounts it whenever an agent is working, viaBotActivityBar,TypingIndicatorRowandCardMintComposerChip.The measurement. The stylesheet already ships a
prefers-reduced-motion: reduceblock that setsanimation: noneon this exactclass, so it can be switched off without patching anything. That makes a clean
A/B rather than an inference. Same window, same session, same working agents,
one variable changed.
MainFrame.Drawcounted from a CDP tracing profile:160.3 fps is exactly this display's refresh rate. Per 10 seconds, paint went
from 1173 ms to 11 ms, and summed renderer + compositor + GPU thread time fell
61%.
document.getAnimations()on the live page returned exactly two runninganimations: this one, and a Tailwind
pulse.pulseanimatesopacity, whichthe compositor handles without repainting.
Why reading the code has not found it. A 30-second V8 CPU profile taken
during the exact hot moment showed the main thread 72% idle, with no JS
frame above 0.1% of a core. A CSS animation executes no JavaScript, so a
JS-only profile is structurally blind to this. It took a tracing profile — one
that records paint, raster and composite — to see it at all. Worth flagging
because it is an easy wrong turn for anyone investigating this issue.
On the original report's suspects.
SpoilerParticles.tsxis a genuineunconditional
requestAnimationFrameloop and is worth fixing on its ownmerits, but it only mounts where a spoiler is rendered. Enabling Reduce Motion
disables the particle loop and the shimmer, so that workaround cannot
distinguish between them — which may be why the shimmer has not surfaced.
Fix options, measured. Same harness, one variable per row:
will-change+translateZ(0))steps(52, end)on the existing keyframessteps(26, end)steps(13, end)The compositing-layer approach is worth calling out as a dead end. It is the
reflexive fix and it does essentially nothing here —
background-positionstillhas to be painted, and promoting the element just adds a layer to manage. I
tried it first and it cost slightly more than it saved.
Quantising the timing function is the cheap win. At
steps(52, end)thehighlight advances every 50 ms instead of every vsync and still reads as
continuous motion. A transform-animated overlay should reach the floor while
preserving the look exactly, but that is a larger rewrite and I have not
measured it, so I am not claiming a number for it.
Not proposing removal. The shimmer communicates real state and should stay.
Why this is probably quiet in the tracker. The cost scales with refresh rate
and pixel count, because it is per-frame paint of a rasterised text mask. A 4K
160 Hz user pays roughly 2.7x what a 1080p 60 Hz user pays for the identical UI
state. On a 60 Hz laptop it reads as "the fans spin up while agents are busy"
and may never get filed at all.
Caveats, stated plainly. These numbers are one machine — Windows 11,
3840x2160 @ 160 Hz — from a build carrying unrelated local changes elsewhere.
The
.buzz-shimmerrule is byte-identical to currentmain, so the mechanismtransfers, but I have not re-run the numbers against a clean
mainbuild. The2.7x scaling figure is arithmetic from refresh rate and pixel count, not a
second measured configuration. And I measured frame production and paint totals,
not invalidated area — I am not claiming the whole window repaints, only that a
frame is produced every vsync and that paint dominates it.
Happy to open a PR for the
steps()change if that is the direction you want,or to leave it to you if you would rather do the transform rewrite. The
measurement seemed like the part worth handing over.
fix(mobile): keep #-prefixed markdown link labels visible
fix(mobile): keep token labels visible in authored markdown links
ktecho commentedon Sep 11, 2026
Yeah, it's the animation. For me in Linux (Kubuntu) it takes between 25 and 30% of CPU, but only when the "Agent is typing..." appears on the screen, because it has that stupid animation like a glowing effect or something. Please remove the animation or make it so we can configure it in Settings.
fix(mobile): keep token labels visible in authored Markdown links (#7545