feat: dashboard polish — skeletons, error boundaries, responsive cards#72
feat: dashboard polish — skeletons, error boundaries, responsive cards#72
Conversation
…ive agent cards - Add Skeleton, ErrorBoundary, and dashboard skeleton components - Replace full-page spinner with skeleton layout (nav + stats + agents) - Wrap each tab in ErrorBoundary for graceful per-tab error recovery - Fix agent cards: responsive stacking on mobile, truncate overflow - Remove hardcoded min-w values, use grid-cols-2 on small screens - Zero TS errors
There was a problem hiding this comment.
Pull request overview
Polishes the dashboard UX by replacing the initial spinner with layout-preserving skeletons, introducing per-tab error boundaries to isolate failures, and improving agent card responsiveness for mobile layouts.
Changes:
- Add a shared
Skeletoncomponent and tab-oriented dashboard skeleton layouts. - Add a reusable
ErrorBoundaryand wrap each dashboard tab independently. - Refactor agent list cards to stack on mobile and truncate text instead of using hard min-widths.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
frontend/src/components/ui/skeleton.tsx |
Adds a base skeleton UI primitive. |
frontend/src/components/ui/error-boundary.tsx |
Adds a client-side error boundary with retry UI. |
frontend/src/components/ui/dashboard-skeletons.tsx |
Adds skeleton layouts for dashboard sections/tabs. |
frontend/src/app/dashboard/page.tsx |
Uses skeletons during load and wraps tab content in error boundaries. |
frontend/src/app/dashboard/components/AgentsTab.tsx |
Updates agent cards to be responsive (mobile stacking, truncation, button sizing). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| </nav> | ||
| <div className="max-w-7xl mx-auto px-6 py-6 space-y-6"> | ||
| <StatsOverviewSkeleton /> | ||
| <AgentsTabSkeleton /> |
There was a problem hiding this comment.
When loading is true, the page always renders AgentsTabSkeleton() regardless of the active tab (e.g. ?tab=sessions / ?tab=analytics). This causes a mismatched skeleton for deep links. Render the skeleton corresponding to the current tab (and consider adding a projects skeleton or a generic one).
| <AgentsTabSkeleton /> | |
| {tab === "sessions" | |
| ? <SessionsTabSkeleton /> | |
| : tab === "analytics" | |
| ? <AnalyticsTabSkeleton /> | |
| : <AgentsTabSkeleton />} |
| <div className="text-xs text-muted-foreground mb-4 max-w-md mx-auto break-words"> | ||
| {this.state.error?.message || "An unexpected error occurred"} | ||
| </div> | ||
| <button |
There was a problem hiding this comment.
The retry control is a plain <button> without an explicit type. If this boundary is ever rendered inside a <form>, the default type="submit" can cause unintended form submissions. Set type="button" on this button.
| <button | |
| <button | |
| type="button" |
| import { cn } from "@/lib/utils" | ||
|
|
||
| function Skeleton({ className, ...props }: React.ComponentProps<"div">) { |
There was a problem hiding this comment.
Skeleton uses React.ComponentProps but the file doesn’t import React (or ComponentProps) anywhere. With the repo’s TS config this will fail to typecheck (Cannot find namespace 'React'). Import * as React or switch to import type { ComponentProps } from "react" and use ComponentProps<"div">.
| import { cn } from "@/lib/utils" | |
| function Skeleton({ className, ...props }: React.ComponentProps<"div">) { | |
| import type { ComponentProps } from "react" | |
| import { cn } from "@/lib/utils" | |
| function Skeleton({ className, ...props }: ComponentProps<"div">) { |
| import { getAgents, getAlerts, getAlertDetails, getCosts, pauseAgent, resumeAgent, acknowledgeAlert, acknowledgeAllAlerts, getSessions, getProjects, getProfiles, getVersion, getAnalytics, getSpend, setCostLimits, isUsingMockData } from "@/lib/api"; | ||
| import { ClaWatchLogo, ClaWatchIcon } from "@/components/clawatch-logo"; | ||
| import { ErrorBoundary } from "@/components/ui/error-boundary"; | ||
| import { StatsOverviewSkeleton, AgentsTabSkeleton, SessionsTabSkeleton, AnalyticsTabSkeleton } from "@/components/ui/dashboard-skeletons"; |
There was a problem hiding this comment.
Unused imports: SessionsTabSkeleton and AnalyticsTabSkeleton are imported here but never referenced in this file. This will trip Next/ESLint no-unused-vars during CI. Remove them or use them in the loading UI.
| import { StatsOverviewSkeleton, AgentsTabSkeleton, SessionsTabSkeleton, AnalyticsTabSkeleton } from "@/components/ui/dashboard-skeletons"; | |
| import { StatsOverviewSkeleton, AgentsTabSkeleton } from "@/components/ui/dashboard-skeletons"; |
Changes
Loading Skeletons
Skeletonbase component +dashboard-skeletons.tsxwith tab-specific skeletonsError Boundaries
ErrorBoundarycomponent with friendly error UI and retry buttonResponsive Agent Cards
sm:+min-w-[80px]/min-w-[200px]— usesmin-w-0+truncateinsteadgrid-cols-2on mobile, flex row on desktopTechnical