FN-5855: fix frozen dashboard spinners
Restore shared spinner animation behavior for dashboard AI activity indicators. - switch BackgroundTasksIndicator loaders to the shared animate-spin utility class - anchor shared spin utilities on SVG centers with transform-origin and transform-box rules - add CSS regression tests covering global spin keyframes and spinner utility styles Files changed: .../app/__tests__/spinner-animation.css.test.ts | 50 ++++++++++++++++++++++ .../app/components/BackgroundTasksIndicator.tsx | 9 ++-- packages/dashboard/app/styles.css | 7 +++ 3 files changed, 60 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-5855 Fusion-Task-Lineage: b94d4333-2e78-4c4b-a304-5a13b797bbc6
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { readFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
|
||||
function extractBlock(content: string, pattern: RegExp): string {
|
||||
const match = content.match(pattern);
|
||||
expect(match?.index).toBeDefined();
|
||||
|
||||
const start = match!.index! + match![0].length;
|
||||
let index = start;
|
||||
let depth = 1;
|
||||
|
||||
while (index < content.length && depth > 0) {
|
||||
if (content[index] === "{") depth += 1;
|
||||
if (content[index] === "}") depth -= 1;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
expect(depth).toBe(0);
|
||||
return content.slice(match!.index!, index);
|
||||
}
|
||||
|
||||
describe("global spinner animation utility", () => {
|
||||
const css = readFileSync(resolve(__dirname, "../styles.css"), "utf8");
|
||||
|
||||
it("keeps top-level spin keyframes rotating to 360deg", () => {
|
||||
const topLevelSpinBlock = extractBlock(css, /@keyframes\s+spin\s*\{/);
|
||||
|
||||
expect(topLevelSpinBlock).toContain("transform: rotate(360deg);");
|
||||
expect(css.indexOf("@keyframes spin")).toBeLessThan(css.indexOf(":root {\n --bg:"));
|
||||
});
|
||||
|
||||
it("keeps the shared animate-spin and spin utilities running infinitely", () => {
|
||||
const animateSpinBlock = css.match(/\.animate-spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
|
||||
const spinBlock = css.match(/\.spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
|
||||
|
||||
expect(animateSpinBlock).toContain("animation: spin 1s linear infinite;");
|
||||
expect(spinBlock).toContain("animation: spin 1s linear infinite;");
|
||||
});
|
||||
|
||||
it("anchors SVG spinners around their own center", () => {
|
||||
const animateSpinBlock = css.match(/\.animate-spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
|
||||
const spinBlock = css.match(/\.spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
|
||||
const svgSpinnerBlock = css.match(/svg\.animate-spin,\s*svg\.spin\s*\{[\s\S]*?\}/)?.[0] ?? "";
|
||||
|
||||
expect(animateSpinBlock).toContain("transform-origin: center;");
|
||||
expect(spinBlock).toContain("transform-origin: center;");
|
||||
expect(svgSpinnerBlock).toContain("transform-box: fill-box;");
|
||||
});
|
||||
});
|
||||
@@ -119,7 +119,7 @@ export function BackgroundTasksIndicator({
|
||||
title={`${total} background AI task${total !== 1 ? "s" : ""}${needsInput > 0 ? ` (${needsInput} need${needsInput !== 1 ? "" : "s"} input)` : ""}`}
|
||||
>
|
||||
{generating > 0 && (
|
||||
<Loader2 size={12} style={{ animation: "spin 1s linear infinite" }} />
|
||||
<Loader2 size={12} className="animate-spin" />
|
||||
)}
|
||||
{needsInput > 0 && generating === 0 && <HelpCircle size={12} />}
|
||||
<span>AI {total}</span>
|
||||
@@ -188,11 +188,8 @@ export function BackgroundTasksIndicator({
|
||||
{isGenerating && (
|
||||
<Loader2
|
||||
size={14}
|
||||
className="background-tasks-indicator__session-icon"
|
||||
style={{
|
||||
animation: "spin 1s linear infinite",
|
||||
color: "var(--color-success)",
|
||||
}}
|
||||
className="background-tasks-indicator__session-icon animate-spin"
|
||||
style={{ color: "var(--color-success)" }}
|
||||
/>
|
||||
)}
|
||||
{isAwaiting && !activeElsewhere && (
|
||||
|
||||
@@ -187,10 +187,17 @@ html {
|
||||
}
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
svg.animate-spin,
|
||||
svg.spin {
|
||||
transform-box: fill-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user