fix(studio): Reduced effects also stop the looping run animations

A long wait keeps the running state, and its three infinite animations, on
screen for minutes; the R mark's stroke-dashoffset trace alone costs a style
recalc every frame. Reduced effects, the mode meant for a window that keeps
the GPU busy, now draws the mark whole and stops the row sweep and spinners.

Fixes #14919
This commit is contained in:
esengine committed 2026-11-01 10:14:02 -08:00
1 parent f6aa704059
commit 5dcb9a3a35
5 files changed
+49 -2

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
import assert from "node:assert/strict";
import { chromium } from "playwright";
// A long run (a wait on a build) keeps the running indicators moving for minutes.
// Counts the style recalculations they cost over five seconds, by default and with
// the power-saving effects mode or the OS reduced-motion preference.
const browser = await chromium.launch(process.env.CHROME ? { executablePath: process.env.CHROME } : {});
const measure = async (motion, reduced) => {
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, reducedMotion: motion });
const cdp = await page.context().newCDPSession(page);
await cdp.send("Performance.enable");
await page.goto(process.env.PERF_URL ?? "http://127.0.0.1:4399/perf.html?pref=zh", { waitUntil: "networkidle" });
await page.waitForFunction(() => window.__panes?.() > 0);
await page.evaluate((reduced) => {
if (reduced) document.documentElement.dataset.effects = "reduced";
window.__feed({ kind: "turn_started", authoredTurn: 1, msgIndex: 0, text: "wait for the build" });
window.__feed({ kind: "tool_dispatch", tool: { id: "w1", name: "wait", readOnly: true, args: "{}" } });
}, reduced);
await page.waitForTimeout(800);
const read = async () => Object.fromEntries((await cdp.send("Performance.getMetrics")).metrics.map((m) => [m.name, m.value]));
const a = await read();
await page.waitForTimeout(5000);
const b = await read();
const running = await page.evaluate(() => document.getAnimations().filter((x) => x.playState === "running" && x.effect?.getTiming?.().iterations === Infinity).length);
await page.close();
return { style: b.RecalcStyleCount - a.RecalcStyleCount, loops: running };
};
try {
const normal = await measure("no-preference", false);
const saving = await measure("no-preference", true);
const motion = await measure("reduce", false);
console.log(JSON.stringify({ normal, saving, motion }));
assert.ok(normal.loops > 0, "the running state shows its indicators by default");
for (const [name, r] of Object.entries({ saving, motion })) {
assert.equal(r.loops, 0, `${name}: a looping animation still runs`);
assert.ok(r.style < normal.style / 5, `${name}: ${r.style} style recalcs against ${normal.style} by default`);
}
} finally {
await browser.close();
}
+1 -1
View File
@@ -764,7 +764,7 @@ export const EN: Record<string, string> = {
"等宽字体": "Monospace font",
"文字粗细": "Text weight",
"视觉效果": "Visual effects",
"窗口持续占用显卡时,改为「节能」:界面不变,只去掉磨砂玻璃、动态天空和颗粒这几层。": "If the window keeps your GPU busy, choose Reduced: the layout stays as it is, only frosted glass, the animated sky and the grain layer go.",
"窗口持续占用显卡时,改为「节能」:界面不变,只去掉磨砂玻璃、动态天空、颗粒和运行中的循环动画。": "If the window keeps your GPU busy, choose Reduced: the layout stays as it is, only frosted glass, the animated sky, the grain layer and the looping run animations go.",
"节能": "Reduced",
"背景模糊、动态天空与颗粒质感全部开启": "Background blur, the animated sky and the grain layer are all on",
"关闭背景模糊、动态天空与颗粒质感,集成显卡或老旧电脑上更省电": "Turns off background blur, the animated sky and the grain layer; lighter on integrated graphics and older machines",
@@ -4218,3 +4218,8 @@ button, input, select, textarea { font-size: calc(13.3333px * var(--fs-scale, 1)
.other-field .studio-control-tip { left: auto; right: 0; }
.other-wrap .refine-card { margin: 8px 0 0; }
.keys-row .ds kbd { display: inline-block; min-width: 13em }
:root[data-effects="reduced"] .studio-runstate .rmark path { stroke-dasharray: 100; stroke-dashoffset: 0; animation: none; }
:root[data-effects="reduced"] .rail .sessrow[data-run]::after,
:root[data-effects="reduced"] .rail .sessrow[data-run="halt"],
:root[data-effects="reduced"] .tool-state[data-state="running"] .studio-icon,
:root[data-effects="reduced"] .ws-reading .studio-icon { animation: none; }
+1 -1
View File
@@ -636,7 +636,7 @@ export function Appearance({ port, theme, onTheme, contrast, onContrast, weight,
<div className="grp-hd">
<h3>{t("视觉效果")}</h3>
</div>
<p className="hint">{t("窗口持续占用显卡时,改为「节能」:界面不变,只去掉磨砂玻璃、动态天空和颗粒这几层。")}</p>
<p className="hint">{t("窗口持续占用显卡时,改为「节能」:界面不变,只去掉磨砂玻璃、动态天空、颗粒和运行中的循环动画。")}</p>
<div className="grp-items">
<div className="seg" data-text role="group" aria-label={t("视觉效果")}>
{EFFECTS.map(([id, name, why]) => (
@@ -0,0 +1,2 @@
- 修复:视觉效果设为「节能」后,运行中的循环动画(状态标志描线、会话行流光、工具旋转图标)也会停下,长时间等待任务时不再让显卡持续工作。关联 #14919。
- Fixed: Reduced visual effects now also stop the looping run animations (status mark, session-row sweep, tool spinner), so a long wait no longer keeps the GPU busy. Refs #14919.