Repository navigation
fix(ui): stop recalculating styles between Wisp blinks - #330
Merged
Merged
Conversation
This was referenced Oct 31, 2026
Each blinking Wisp ran an endless CSS animation that only moves for 4% of its six-second cycle. Chromium still recalculated styles on every frame in between, so an untouched window with seven Wisps spent about 5% of its main thread on them and never went idle. A timer now starts a short animation only when a blink is due, so nothing runs between blinks and that window's cost drops to about 2.7%. Each Wisp keeps the same rhythm and its place in it, so the blink looks the same. Hidden windows and reduced motion skip it.
Surfaces that fade in used the weak built-in ease, while dialogs asked for ease-out. A single strong --ease-out token now drives every entrance, so menus and panels are readable sooner at the same duration. Moving between tooltips no longer replays the fade, since the first one already answered the wait. The switch thumb moves with translate instead of left, and the working pulse scales a disc instead of repainting a box shadow on every frame.
gustmrg
force-pushed
the
fix/wisp-idle-blink
branch
from
November 1, 2026 03:13
5422713 to
13b7eeb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #329.
An untouched window never went idle: every blinking Wisp kept Chromium recalculating styles on every frame, even though the eyes only move for 4% of the time. Nothing runs between blinks now, and the blink looks the same. Menus and panels also become readable sooner, and moving between tooltips no longer replays their fade.
Blink
useWispBlinkstarts a 240ms animation withelement.animatewhen a blink is due, and replaces the endlesswisp-blinkCSS animation.hashSeed, and the same Wisps: thexlpicture and the conversation list.prefers-reduced-motionitself. It also skips hidden windows.Measured over 10 seconds in an idle window with seven Wisps:
Layouts stay about the same because they come from the blink itself, which still moves the eyes. These are single runs under xvfb, so read them as an order of magnitude.
Other changes
--ease-outis nowcubic-bezier(0.23, 1, 0.32, 1), and the--animate-*entrance tokens use it. This also changes everyease-oututility, such as the splash fade and the connection progress bar.ease-out. They were falling back to the built-inease.TooltipContentskips its fade withdata-instant:animate-none. Base UI setsdata-instantwhen one tooltip follows another, when focus opens one, and when Escape dismisses one.ToggleSwitchmoves its thumb withtranslateinstead ofleft, so toggling no longer triggers layout.chat-subline-pulsescales an::afterdisc from 1 to 3 and fades it, instead of repaintingbox-shadowon every frame. The size and fade match the old pulse.Testing
npm run typecheck,npm run lint,npm run format:check, andnpm run buildpass. Lint reports the same 2 warnings asmain.npm teston Node 22: 2448 tests pass.use-wisp-blink.test.tsxcover the timing per name, which Wisps blink, reduced motion, and stopping on unmount.scalekeyframes over 240ms.data-instant="delay"andanimation-name: none.cubic-bezier(0.23, 1, 0.32, 1).translate.opacityandscale, on::after, with nobox-shadow.To check by hand before merging:
Not in this PR
scaleproperty. fix(ui): cut the main thread cost of working Wisps and panel drags #332 moves it totransform, which costs less.transition-[height]on the recording meter's bars andbackground-positionon the connection shimmer still avoid the compositor. fix(ui): cut the main thread cost of working Wisps and panel drags #332 fixes both.prefers-reduced-motionrule removes all motion, including fades that would be safe to keep. Changing it is an accessibility decision of its own.Refs SWE-268