"Reduce visual effects" was a sledgehammer: body.reduce-effects * forced animation:none + transition-duration:0s on every element. That froze every CSS loading spinner mid-rotation — including the dash-header worker-service spinners (musicbrainz / spotify / deezer / … .active .<svc>-spinner) — which read as BROKEN rather than "off". It also killed cheap hover feedback like the Quick Actions buttons. The actual lag (esp. Firefox, see the #935 sweep) is backdrop-filter / box-shadow / filter re-rasterizing every frame — NOT the animations themselves. Transform- and opacity-only motion (the spinners) composites for ~free. So: keep forcing the expensive properties to none (unchanged — that's the real fix), but drop the blanket animation/transition kills. !important author declarations outrank animation + transition declarations in the cascade, so any keyframe/transition that tries to set blur/shadow/filter is still neutralized even while it runs — the spinner spins, just without the glow. Net: functional spinners stay alive, Quick Actions hover (transform + border-colour) returns, box-shadow transitions are no-ops (shadow forced none), and the GPU-heavy rendering that caused the lag stays gone. The worker-orb CANVAS is unaffected (JS-gated separately) and stays off under reduce-effects, as intended. Static guard test pins the contract: the global rule must keep the expensive-property kills and must NOT reintroduce blanket animation:none / transition:0s.
39 lines
1.7 KiB
Python
39 lines
1.7 KiB
Python
"""Guard the reduce-visual-effects CSS contract.
|
|
|
|
Performance mode must kill the GPU-EXPENSIVE properties (backdrop-filter / box-shadow
|
|
/ filter — the real lag, esp. on Firefox) but must NOT blanket-freeze animations:
|
|
`animation: none` on every element stuck the dash-header worker-service spinners
|
|
mid-rotation, which read as "broken" (Discord/Boulder). Pins the surgical rule so a
|
|
future edit can't silently re-freeze the functional spinners or kill cheap hover
|
|
feedback."""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
_STYLE = Path(__file__).resolve().parent.parent / "webui" / "static" / "style.css"
|
|
|
|
|
|
def _reduce_effects_global_body() -> str:
|
|
css = _STYLE.read_text(encoding="utf-8")
|
|
m = re.search(r"body\.reduce-effects \*,.*?\{([^}]*)\}", css, re.DOTALL)
|
|
assert m, "global 'body.reduce-effects *' rule not found"
|
|
return m.group(1)
|
|
|
|
|
|
def test_reduce_effects_still_kills_expensive_gpu_properties():
|
|
body = _reduce_effects_global_body()
|
|
for prop in ("backdrop-filter: none", "box-shadow: none", "filter: none"):
|
|
assert prop in body, f"reduce-effects must still force {prop} (the real lag source)"
|
|
|
|
|
|
def test_reduce_effects_does_not_blanket_freeze_animations():
|
|
body = _reduce_effects_global_body()
|
|
# `animation: none` on * froze the dash-header worker-service spinners mid-spin;
|
|
# cheap transform/opacity motion must survive so a spinner still reads as "working".
|
|
assert "animation: none" not in body, (
|
|
"blanket 'animation: none' re-freezes the worker spinners — keep motion alive, "
|
|
"the expensive properties are already neutralized above"
|
|
)
|
|
assert "transition-duration: 0s" not in body, (
|
|
"blanket 'transition-duration: 0s' kills the cheap Quick Actions hover feedback"
|
|
)
|