From a4a32cded514f6a12e95c3e2f30c56bc3a85bc65 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Sun, 3 May 2026 10:28:49 -0700 Subject: [PATCH] fix(canvas): WorkspaceNode + tier-config contrast in light theme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cards on the canvas had multiple low-contrast surfaces in light mode: WorkspaceNode.tsx (parent + TeamMemberChip) — same fixes both copies: - "N sub" badge: hardcoded text-violet-300 + bg-violet-900/40 → semantic text-accent + bg-accent/15 + border-accent/40 (themes correctly). - "REMOTE" pill: hardcoded violet/40 alpha → solid bg-violet-600 text-white (works on either surface with WCAG AA contrast). - Runtime pill: drop /60 + /30 alpha modifiers, use solid surface-card + border-line tokens. - Skill chips (online): text-good/80 + bg-emerald-950/30 (washed-out on warm-paper) → text-good + bg-good/15 + border-good/40 semantic. - Skill chips (offline): text-ink-mid + bg-surface-card without alpha. - Restart-to-apply banner: bg-sky-950/30 + text-sky-300/80 → bg-accent/10 + text-accent (sky-950 was nearly invisible on cream). - Provisioning status text: text-sky-400 (poor on cream) → text-accent. - "+N more" badges: text-ink-soft (3.5:1) → text-ink-mid (7:1). - Active-tasks dot: bg-amber-400 + text-warm/80 → semantic bg-warm + text-warm. - Degraded error preview: bg-amber-950/20 + text-warm/60 → bg-warm/10 + text-warm + border-warm/40. - Eject icon hover: hover:text-sky-400 → hover:text-accent. - Role text: text-ink-soft → text-ink-mid. design-tokens.ts: - TIER_CONFIG was dark-only: T2 (text-sky-400 + bg-sky-950/50), T3 (text-violet-400 + bg-violet-950/50), T4 (text-warm + bg-amber-950/50). Migrated to solid bg + white text patterns: T2=accent, T3=violet-600, T4=warm. T1 stays neutral (surface-card + ink-mid). All four pass WCAG AA on either theme. No globals.css changes; uses existing semantic tokens. --- canvas/src/components/WorkspaceNode.tsx | 46 ++++++++++++------------- canvas/src/lib/design-tokens.ts | 8 ++--- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/canvas/src/components/WorkspaceNode.tsx b/canvas/src/components/WorkspaceNode.tsx index 8dbdeb38..b2154dd3 100644 --- a/canvas/src/components/WorkspaceNode.tsx +++ b/canvas/src/components/WorkspaceNode.tsx @@ -36,7 +36,7 @@ function EjectIcon(props: React.SVGProps) { export function WorkspaceNode({ id, data }: NodeProps>) { const statusCfg = STATUS_CONFIG[data.status] || STATUS_CONFIG.offline; - const tierCfg = TIER_CONFIG[data.tier] || { label: `T${data.tier}`, color: "text-ink-soft bg-surface-card" }; + const tierCfg = TIER_CONFIG[data.tier] || { label: `T${data.tier}`, color: "text-ink-mid bg-surface-card border border-line" }; // Org-deploy context — four derived flags off one store subscription. // Drives the shimmer while provisioning, the dimmed/non-draggable // treatment on locked descendants, and the Cancel pill on the root. @@ -179,7 +179,7 @@ export function WorkspaceNode({ id, data }: NodeProps>)
{hasChildren && ( - + {descendantCount} sub )} @@ -207,13 +207,13 @@ export function WorkspaceNode({ id, data }: NodeProps>)
{runtime === "external" ? ( ★ REMOTE ) : ( - + {runtime} )} @@ -237,15 +237,15 @@ export function WorkspaceNode({ id, data }: NodeProps>) key={skill} className={`text-[10px] px-1.5 py-0.5 rounded-md border ${ isOnline - ? "text-good/80 bg-emerald-950/30 border-emerald-800/30" - : "text-ink-mid bg-surface-card/60 border-line/40" + ? "text-good bg-good/15 border-good/40" + : "text-ink-mid bg-surface-card border-line" }`} > {skill} ))} {skills.length > 4 && ( - + +{skills.length - 4} )} @@ -274,10 +274,10 @@ export function WorkspaceNode({ id, data }: NodeProps>) e.stopPropagation(); useCanvasStore.getState().restartWorkspace(id).catch(() => showToast("Restart failed", "error")); }} - className="flex items-center gap-1.5 mt-1 w-full bg-sky-950/30 px-2 py-1 rounded-md border border-sky-800/30 hover:bg-sky-900/40 transition-colors text-left focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:outline-none" + className="flex items-center gap-1.5 mt-1 w-full bg-accent/10 px-2 py-1 rounded-md border border-accent/40 hover:bg-accent/20 transition-colors text-left focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:outline-none" > - - Restart to apply changes + + Restart to apply changes )} @@ -287,8 +287,8 @@ export function WorkspaceNode({ id, data }: NodeProps>)
{statusCfg.label}
@@ -296,8 +296,8 @@ export function WorkspaceNode({ id, data }: NodeProps>) {data.activeTasks > 0 && (
-
- +
+ {data.activeTasks} task{data.activeTasks > 1 ? "s" : ""}
@@ -307,7 +307,7 @@ export function WorkspaceNode({ id, data }: NodeProps>) {/* Degraded error preview */} {data.status === "degraded" && data.lastSampleError && (
{data.lastSampleError} @@ -357,7 +357,7 @@ function TeamMemberChip({ }) { const { data } = node; const statusCfg = STATUS_CONFIG[data.status] || STATUS_CONFIG.offline; - const tierCfg = TIER_CONFIG[data.tier] || { label: `T${data.tier}`, color: "text-ink-soft bg-surface-card" }; + const tierCfg = TIER_CONFIG[data.tier] || { label: `T${data.tier}`, color: "text-ink-mid bg-surface-card border border-line" }; const isOnline = data.status === "online"; const skills = getSkillNames(data.agentCard); @@ -408,7 +408,7 @@ function TeamMemberChip({
{hasSubChildren && ( - + {descendantCount} )} @@ -423,7 +423,7 @@ function TeamMemberChip({ e.stopPropagation(); onExtract(node.id); }} - className="opacity-0 group-hover/child:opacity-100 text-ink-soft hover:text-sky-400 transition-all focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:outline-none rounded" + className="opacity-0 group-hover/child:opacity-100 text-ink-mid hover:text-accent transition-all focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:outline-none rounded" >