diff --git a/frontend/src/app/boards/[boardId]/page.tsx b/frontend/src/app/boards/[boardId]/page.tsx
index d79d68f1..f775827a 100644
--- a/frontend/src/app/boards/[boardId]/page.tsx
+++ b/frontend/src/app/boards/[boardId]/page.tsx
@@ -20,6 +20,7 @@ import {
} from "lucide-react";
import { Markdown } from "@/components/atoms/Markdown";
+import { StatusDot } from "@/components/atoms/StatusDot";
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
import { TaskBoard } from "@/components/organisms/TaskBoard";
import { DashboardShell } from "@/components/templates/DashboardShell";
@@ -2425,14 +2426,12 @@ export default function BoardDetailPage() {
>
{agentAvatarLabel(agent)}
-
diff --git a/frontend/src/components/BoardApprovalsPanel.tsx b/frontend/src/components/BoardApprovalsPanel.tsx
index 0b02b785..db0d47b0 100644
--- a/frontend/src/components/BoardApprovalsPanel.tsx
+++ b/frontend/src/components/BoardApprovalsPanel.tsx
@@ -16,6 +16,7 @@ import {
useUpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch,
} from "@/api/generated/approvals/approvals";
import type { ApprovalRead } from "@/api/generated/model";
+import { StatusDot } from "@/components/atoms/StatusDot";
import {
ChartContainer,
ChartTooltip,
@@ -85,12 +86,6 @@ const humanizeAction = (value: string) =>
const formatStatusLabel = (status: string) =>
status.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
-const statusDotClass = (status: string) => {
- if (status === "approved") return "bg-emerald-500";
- if (status === "rejected") return "bg-rose-500";
- return "bg-amber-500";
-};
-
const rubricColors = [
"#0f172a",
"#1d4ed8",
@@ -584,10 +579,11 @@ export function BoardApprovalsPanel({
-
diff --git a/frontend/src/components/atoms/StatusDot.tsx b/frontend/src/components/atoms/StatusDot.tsx
new file mode 100644
index 00000000..552d9490
--- /dev/null
+++ b/frontend/src/components/atoms/StatusDot.tsx
@@ -0,0 +1,77 @@
+import { cn } from "@/lib/utils";
+
+type StatusDotVariant = "agent" | "approval" | "task";
+
+const AGENT_STATUS_DOT_CLASS_BY_STATUS: Record = {
+ online: "bg-emerald-500",
+ busy: "bg-amber-500",
+ provisioning: "bg-amber-500",
+ updating: "bg-sky-500",
+ deleting: "bg-rose-500",
+ offline: "bg-slate-400",
+};
+
+const APPROVAL_STATUS_DOT_CLASS_BY_STATUS: Record = {
+ approved: "bg-emerald-500",
+ rejected: "bg-rose-500",
+ pending: "bg-amber-500",
+};
+
+const TASK_STATUS_DOT_CLASS_BY_STATUS: Record = {
+ inbox: "bg-slate-400",
+ in_progress: "bg-purple-500",
+ review: "bg-indigo-500",
+ done: "bg-emerald-500",
+};
+
+const STATUS_DOT_CLASS_BY_VARIANT: Record<
+ StatusDotVariant,
+ Record
+> = {
+ agent: AGENT_STATUS_DOT_CLASS_BY_STATUS,
+ approval: APPROVAL_STATUS_DOT_CLASS_BY_STATUS,
+ task: TASK_STATUS_DOT_CLASS_BY_STATUS,
+};
+
+const DEFAULT_STATUS_DOT_CLASS: Record = {
+ agent: "bg-slate-300",
+ approval: "bg-amber-500",
+ task: "bg-slate-300",
+};
+
+export const statusDotClass = (
+ status: string | null | undefined,
+ variant: StatusDotVariant = "agent",
+) => {
+ const normalized = (status ?? "").trim().toLowerCase();
+ if (!normalized) {
+ return DEFAULT_STATUS_DOT_CLASS[variant];
+ }
+ return (
+ STATUS_DOT_CLASS_BY_VARIANT[variant][normalized] ??
+ DEFAULT_STATUS_DOT_CLASS[variant]
+ );
+};
+
+type StatusDotProps = {
+ status?: string | null;
+ variant?: StatusDotVariant;
+ className?: string;
+};
+
+export function StatusDot({
+ status,
+ variant = "agent",
+ className,
+}: StatusDotProps) {
+ return (
+
+ );
+}