diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md index 7172394..4dceabb 100644 --- a/IMPROVEMENTS.md +++ b/IMPROVEMENTS.md @@ -45,3 +45,17 @@ and work offline. 68 tests across 10 test classes covering: **Files changed:** `desktop/tests/__init__.py`, `desktop/tests/test_ping_tester.py`, `desktop/tests/test_cli.py`, `desktop/pytest.ini` **Lines:** +370 / -0 + +## 2026-03-18 — UI/UX: Skeleton loading screen for dashboard + +The dashboard previously showed a centered spinner while fetching results from +/api/results, giving no visual indication of page structure and causing a jarring +layout shift when content arrived. + +Replaced the spinner with a DashboardSkeleton component that mirrors the real +dashboard layout — four stat cards, two chart panels, and a six-row results table +— all with the existing .skeleton shimmer animation. No new dependencies. Screen +reader support via aria-busy="true" and aria-label on the skeleton root. + +**Files changed:** `web/src/components/DashboardSkeleton.tsx` (new), `web/src/app/dashboard/page.tsx` +**Lines:** +123 / -4 diff --git a/web/src/app/dashboard/page.tsx b/web/src/app/dashboard/page.tsx index deff416..5f1ba6c 100644 --- a/web/src/app/dashboard/page.tsx +++ b/web/src/app/dashboard/page.tsx @@ -14,6 +14,7 @@ import { } from "lucide-react"; import { Navbar } from "@/components/Navbar"; import { Footer } from "@/components/Footer"; +import { DashboardSkeleton } from "@/components/DashboardSkeleton"; import { LineChart, Line, @@ -181,10 +182,7 @@ export default function DashboardPage() { {loading ? ( -
-
-

Loading results...

-
+ ) : error ? (
diff --git a/web/src/components/DashboardSkeleton.tsx b/web/src/components/DashboardSkeleton.tsx new file mode 100644 index 0000000..cf23217 --- /dev/null +++ b/web/src/components/DashboardSkeleton.tsx @@ -0,0 +1,121 @@ +/** + * DashboardSkeleton — shimmer placeholders that mirror the real dashboard layout. + * + * Renders four stat cards, two chart panels, and a results table skeleton so the + * page feels instantly populated while data is in-flight. Uses a CSS animation + * defined in globals.css (shimmer keyframes) to avoid a runtime dependency. + */ + +function SkeletonBlock({ className = "" }: { className?: string }) { + return ( +