diff --git a/canvas/src/__tests__/reduced-motion.test.ts b/canvas/src/__tests__/reduced-motion.test.ts
index 3f7eee05..328eb7df 100644
--- a/canvas/src/__tests__/reduced-motion.test.ts
+++ b/canvas/src/__tests__/reduced-motion.test.ts
@@ -13,6 +13,12 @@ function readSrc(rel: string) {
return readFileSync(join(root, "src", rel), "utf8");
}
+function usesGuardedPulse(src: string): boolean {
+ if (src.includes("motion-safe:animate-pulse")) return true;
+ if (src.includes("from \"@/lib/design-tokens\"") || src.includes("from '@/lib/design-tokens'")) return true;
+ return false;
+}
+
describe("prefers-reduced-motion compliance", () => {
it("globals.css contains @media (prefers-reduced-motion: reduce) block", () => {
const css = readSrc("app/globals.css");
@@ -34,10 +40,10 @@ describe("prefers-reduced-motion compliance", () => {
expect(src).toContain("motion-safe:animate-pulse");
});
- it("StatusDot.tsx uses motion-safe:animate-pulse", () => {
+ it("StatusDot.tsx uses motion-safe:animate-pulse (inline or via shared tokens)", () => {
const src = readSrc("components/StatusDot.tsx");
expect(src.includes("animate-pulse") && !src.includes("motion-safe:animate-pulse")).toBe(false);
- expect(src).toContain("motion-safe:animate-pulse");
+ expect(usesGuardedPulse(src)).toBe(true);
});
it("Toolbar.tsx uses motion-safe:animate-pulse", () => {
@@ -52,16 +58,16 @@ describe("prefers-reduced-motion compliance", () => {
expect(src).toContain("motion-safe:animate-pulse");
});
- it("Legend.tsx uses motion-safe:animate-pulse", () => {
+ it("Legend.tsx uses motion-safe:animate-pulse (inline or via shared tokens)", () => {
const src = readSrc("components/Legend.tsx");
expect(src.includes("animate-pulse") && !src.includes("motion-safe:animate-pulse")).toBe(false);
- expect(src).toContain("motion-safe:animate-pulse");
+ expect(usesGuardedPulse(src)).toBe(true);
});
- it("SearchDialog.tsx uses motion-safe:animate-pulse", () => {
+ it("SearchDialog.tsx uses motion-safe:animate-pulse (inline or via shared tokens)", () => {
const src = readSrc("components/SearchDialog.tsx");
expect(src.includes("animate-pulse") && !src.includes("motion-safe:animate-pulse")).toBe(false);
- expect(src).toContain("motion-safe:animate-pulse");
+ expect(usesGuardedPulse(src)).toBe(true);
});
it("TerminalTab.tsx uses motion-safe:animate-pulse", () => {
@@ -76,6 +82,12 @@ describe("prefers-reduced-motion compliance", () => {
expect(src).toContain("motion-safe:animate-pulse");
});
+ it("design-tokens.ts uses motion-safe:animate-pulse, not bare animate-pulse", () => {
+ const src = readSrc("lib/design-tokens.ts");
+ expect(src.includes("animate-pulse") && !src.includes("motion-safe:animate-pulse")).toBe(false);
+ expect(src).toContain("motion-safe:animate-pulse");
+ });
+
it("globals.css disables animate-in and slide-in classes under reduced-motion", () => {
const css = readSrc("app/globals.css");
expect(css).toContain(".animate-in");
diff --git a/canvas/src/components/CommunicationOverlay.tsx b/canvas/src/components/CommunicationOverlay.tsx
index 76e50157..ebc2c177 100644
--- a/canvas/src/components/CommunicationOverlay.tsx
+++ b/canvas/src/components/CommunicationOverlay.tsx
@@ -3,6 +3,7 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { useCanvasStore } from "@/store/canvas";
import { api } from "@/lib/api";
+import { COMM_TYPE_LABELS } from "@/lib/design-tokens";
interface Communication {
id: string;
@@ -143,7 +144,7 @@ export function CommunicationOverlay() {
{typeIcon}
-
{c.type === "a2a_send" ? "sent" : c.type === "a2a_receive" ? "received" : "task update"}
+
{COMM_TYPE_LABELS[c.type] ?? c.type}
{c.sourceName}
diff --git a/canvas/src/components/ContextMenu.tsx b/canvas/src/components/ContextMenu.tsx
index d4be6ec5..5e1d2f4f 100644
--- a/canvas/src/components/ContextMenu.tsx
+++ b/canvas/src/components/ContextMenu.tsx
@@ -5,6 +5,7 @@ import { useCanvasStore, type WorkspaceNodeData } from "@/store/canvas";
import { api } from "@/lib/api";
import { showToast } from "./Toaster";
import { ConfirmDialog } from "./ConfirmDialog";
+import { statusDotClass } from "@/lib/design-tokens";
interface MenuItem {
label: string;
@@ -277,9 +278,7 @@ export function ContextMenu() {
{contextMenu.nodeData.status}
diff --git a/canvas/src/components/EmptyState.tsx b/canvas/src/components/EmptyState.tsx
index 9f12f257..52cab350 100644
--- a/canvas/src/components/EmptyState.tsx
+++ b/canvas/src/components/EmptyState.tsx
@@ -4,7 +4,8 @@ import { useState, useEffect } from "react";
import { api } from "@/lib/api";
import { useCanvasStore } from "@/store/canvas";
import { OrgTemplatesSection } from "./TemplatePalette";
-import { TIER_COLORS } from "@/lib/design-tokens";
+import { Spinner } from "./Spinner";
+import { TIER_CONFIG } from "@/lib/design-tokens";
interface Template {
id: string;
@@ -100,16 +101,13 @@ export function EmptyState() {
{/* Template grid */}
{loading ? (
-
+
Loading templates...
) : templates.length > 0 ? (
{templates.map((t) => {
- const tierColor = TIER_COLORS[t.tier] || TIER_COLORS[1];
+ const tierColor = TIER_CONFIG[t.tier]?.border || TIER_CONFIG[1].border;
return (