+
{formatTokens(agent.tokenCount)}
tokens
-
+
0 ? "text-red-400" : "text-foreground"}`}>{agent.errorCount}
errors
-
+
{formatRelativeTime(agent.lastHeartbeat)}
heartbeat
-
- {(agent.status === "running" || agent.status === "paused") && (
-
- )}
-
+ {(agent.status === "running" || agent.status === "paused") && (
+
+
+
+ )}
);
})}
diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx
index 8ef7d5d..cf76615 100644
--- a/frontend/src/app/dashboard/page.tsx
+++ b/frontend/src/app/dashboard/page.tsx
@@ -8,6 +8,8 @@ import { Badge } from "@/components/ui/badge";
import { Agent, Alert, AlertDetails, CostData, AgentStatus, AlertSeverity, Session, Project, Profile, AnalyticsData, SpendData, CostLimits } from "@/lib/types";
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";
import { AgentsTab } from "./components/AgentsTab";
import { SessionsTab } from "./components/SessionsTab";
import { AnalyticsTab } from "./components/AnalyticsTab";
@@ -477,10 +479,18 @@ function DashboardContent() {
if (loading) {
return (
-
-
-
- Loading dashboard...
+
);
@@ -618,39 +628,47 @@ function DashboardContent() {
{/* Tab Content */}
{tab === "agents" && (
-
+
+
+
)}
{tab === "sessions" && (
-
+
+
+
)}
{tab === "analytics" && (
-
+
+
+
)}
{tab === "projects" && (
-
+
+
+
)}
diff --git a/frontend/src/components/ui/dashboard-skeletons.tsx b/frontend/src/components/ui/dashboard-skeletons.tsx
new file mode 100644
index 0000000..5ba305d
--- /dev/null
+++ b/frontend/src/components/ui/dashboard-skeletons.tsx
@@ -0,0 +1,129 @@
+import { Card, CardContent, CardHeader } from "@/components/ui/card"
+import { Skeleton } from "@/components/ui/skeleton"
+
+export function StatsOverviewSkeleton() {
+ return (
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+
+
+
+
+
+
+
+
+ ))}
+
+ )
+}
+
+export function AgentsTabSkeleton() {
+ return (
+
+
+
+
+
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+ )
+}
+
+export function SessionsTabSkeleton() {
+ return (
+
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+export function AnalyticsTabSkeleton() {
+ return (
+
+
+
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+
+
+
+ ))}
+
+
+
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/frontend/src/components/ui/error-boundary.tsx b/frontend/src/components/ui/error-boundary.tsx
new file mode 100644
index 0000000..defedbe
--- /dev/null
+++ b/frontend/src/components/ui/error-boundary.tsx
@@ -0,0 +1,56 @@
+"use client"
+
+import { Component, type ReactNode } from "react"
+
+interface ErrorBoundaryProps {
+ children: ReactNode
+ fallback?: ReactNode
+ onReset?: () => void
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean
+ error: Error | null
+}
+
+export class ErrorBoundary extends Component
{
+ constructor(props: ErrorBoundaryProps) {
+ super(props)
+ this.state = { hasError: false, error: null }
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ return { hasError: true, error }
+ }
+
+ reset = () => {
+ this.setState({ hasError: false, error: null })
+ this.props.onReset?.()
+ }
+
+ render() {
+ if (this.state.hasError) {
+ if (this.props.fallback) return this.props.fallback
+
+ return (
+
+
💥
+
+ Something went wrong
+
+
+ {this.state.error?.message || "An unexpected error occurred"}
+
+
+
+ )
+ }
+
+ return this.props.children
+ }
+}
diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..8099a66
--- /dev/null
+++ b/frontend/src/components/ui/skeleton.tsx
@@ -0,0 +1,12 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Skeleton }