Skip to content

[Desktop] Text shimmer repaints every frame; propose an opacity pulse (design review) #48708

Description

@yanhenrique-dev

Description

The "working" shimmer (Thinking, running tool titles) animates background-position on text that has two gradient layers and background-clip: text. That repaints the text every frame for as long as the state is active.

Measured in a real desktop instance (1.18.29, via CDP), 3s windows, 2 visible shimmers:

variant RecalcStyleDuration TaskDuration
animating ~30ms ~330–450ms
frozen (animation: none) 0ms ~51–56ms

No dropped frames in this test (100fps steady), but the animation spends CPU continuously while an agent is working.

Proposal: replace the sweep with an opacity pulse on the overlay copy (compositor-friendly). Same "working" cue, no per-frame background paint. This is a visual change, so it's being opened as a proposal for design review.

before/after

Plugins

none

OpenCode version

OpenCode Desktop 1.18.29

Steps to reproduce

  1. Open a session and run a turn
  2. Watch the shimmer on the thinking/tool titles (or profile with DevTools Performance)
  3. Layout/paint runs every frame while the state is active

Screenshot and/or share link

See image above.

Operating System

CachyOS (Arch Linux), KDE Plasma 6.7.5, Wayland

Terminal

n/a (desktop app)

Activity

  1. skywalkerwhack commented on Oct 10, 2026

    @skywalkerwhack

    Confirmed on the v2 web client too — not just Desktop — and it scales with the display refresh rate.

    Same animation in the web client. In the v2 web UI (opencode serve, v2.0.24, Chrome) the "N running" badge carries the same text-shimmer-sweep animation: 1200ms, iteration-count: infinite, animating background-position on an element with a two-layer linear-gradient(...) + background-clip: text and will-change: background-position. Exactly the per-frame text repaint described above.

    In-page measurement (v2 web client, CDP Performance.getMetrics, ~60 Hz headless Chromium, session with the running badge visible):

    variant renderer main-thread TaskDuration
    text-shimmer-sweep running 0.915 s / 6 s (~15%, dominated by non-script paint/composite)
    same page, that one animation pause()d 0.007 s / 6 s (~0.1%)

    So the single shimmer accounts for essentially all of the renderer work on that view. (This is the idle "N running" case; streaming re-render is a different mechanism — see #49552.)

    Display refresh rate amplifies it. On a 144 Hz Windows laptop the same animation repaints at 144 fps. In a DevTools trace from that machine the GPU-process vsync thread ticks at exactly ~144/s (6.94 ms start-to-start interval) for the whole 15.6 s capture, and chrome://gpu reports Refresh Rate in Hz: 144 (HW acceleration on, Intel UHD via ANGLE/D3D11). At 144 Hz the cost is ~2.4× the 60 Hz figure, which is why the reporter sees CPU "spike the moment a session with a running indicator is opened".

    Workaround that already works today: the animation respects prefers-reduced-motion. With DevTools emulateMedia({ reducedMotion: 'reduce' }), document.getAnimations() is empty on that view while the "N running" text stays visible. So OS "reduce motion" (Windows: Settings → Accessibility → Visual effects → Animation effects → off) silences the per-frame repaint without losing the indicator.

    Ask: the sweep → opacity-pulse approach in #48709 is the right direction — please make sure it also covers the v2 web client (shared text-shimmer), not only Desktop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions