diff --git a/static/css/cookbook-research-memory-settings.css b/static/css/cookbook-research-memory-settings.css index 3bcbb08cd..67f425dae 100644 --- a/static/css/cookbook-research-memory-settings.css +++ b/static/css/cookbook-research-memory-settings.css @@ -28,10 +28,6 @@ 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.7; } } -@keyframes research-pulse { - 0%, 100% { background: color-mix(in srgb, var(--red) 12%, transparent); } - 50% { background: color-mix(in srgb, var(--red) 22%, transparent); } - } #cookbook-gguf-delete-overlay { background:rgba(0,0,0,0.5); backdrop-filter:blur(4px); diff --git a/static/style.css b/static/style.css index 34711963c..94788c585 100644 --- a/static/style.css +++ b/static/style.css @@ -1480,7 +1480,6 @@ body.bg-pattern-ascii-fireflies { 0% { opacity: 1; transform: translateY(0) translateX(0) scale(1); } 100% { opacity: 0; transform: translateY(6px) translateX(-3px) scale(0.94); } } - @keyframes spin { to { transform: rotate(360deg); } } .row { display:flex; gap:6px; align-items:center; } .list-item:hover, .models-row:hover { @@ -6119,9 +6118,6 @@ body.bg-pattern-ascii-fireflies { #mobile-backdrop, #mobile-menu-btn { display:none !important; } #sidebar-backdrop { display:none !important; } /* ----- Loading spinner ----- */ - @keyframes spin { - to { transform: rotate(360deg); } - } .spinner { width: 24px; height: 24px; @@ -6240,14 +6236,6 @@ body.bg-pattern-ascii-fireflies { .session-skeleton-bubble.is-user { width: 72%; } .session-skeleton-line { height: 9px; margin-top: 7px; } } - @keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } - } /* Modal styling */ .modal { position:fixed; @@ -7555,11 +7543,6 @@ body.bg-pattern-ascii-fireflies { animation: pulse 1.5s infinite; } - @keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } - } #recording-indicator { position: fixed; @@ -8720,7 +8703,6 @@ pre { background: var(--code-bg, var(--hl-bg, #282c34)) !important; } } .compare-probe-start-anyway svg { color: currentColor; } .compare-probe-start-anyway span { font-weight: 700; } - @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes pane-shake { 0%, 100% { transform: translateX(0); } 15% { transform: translateX(-3px) rotate(-0.5deg); } diff --git a/tests/test_stylesheet_keyframe_uniqueness.py b/tests/test_stylesheet_keyframe_uniqueness.py new file mode 100644 index 000000000..9fe26337d --- /dev/null +++ b/tests/test_stylesheet_keyframe_uniqueness.py @@ -0,0 +1,52 @@ +"""No two @keyframes may share a name across the app's stylesheets. + +Duplicate names resolve last-wins across the whole cascade, so an earlier +definition is dead code that still looks live at its call site. Two of them +existed and differed from the definition that actually won, which also blocked +moving either animation during the stylesheet decomposition: relocating one +changes which is last and therefore changes rendered behaviour. +""" + +import collections +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +STATIC = ROOT / "static" + + +def _stylesheets() -> list[Path]: + """App-owned stylesheets, in no particular order. Vendored CSS is excluded.""" + sheets = [STATIC / "style.css"] + sheets.extend(sorted((STATIC / "css").glob("*.css"))) + return [s for s in sheets if s.exists()] + + +def _keyframe_names(text: str) -> list[str]: + without_comments = re.sub(r"/\*.*?\*/", "", text, flags=re.S) + return re.findall( + r"@(?:-webkit-)?keyframes\s+([A-Za-z0-9_-]+)\s*\{", without_comments + ) + + +def test_app_stylesheets_exist() -> None: + """Guard the guard: a rename must not turn this file into a no-op.""" + assert _stylesheets(), "no app stylesheets found to check" + + +def test_no_keyframes_name_is_declared_twice() -> None: + counts: collections.Counter[str] = collections.Counter() + where: dict[str, list[str]] = collections.defaultdict(list) + + for sheet in _stylesheets(): + for name in _keyframe_names(sheet.read_text(encoding="utf-8")): + counts[name] += 1 + where[name].append(sheet.relative_to(ROOT).as_posix()) + + duplicates = {name: where[name] for name, n in counts.items() if n > 1} + + assert duplicates == {}, ( + "duplicate @keyframes names resolve last-wins, so every definition but " + f"the last is dead: {duplicates}" + )