refactor(theme): migrate high-priority components to new token system

- social-card.tsx: status colors, borders, surfaces
- assignment-panel.tsx: all hardcoded hex colors replaced
- graph-node-card.tsx: node styles, borders, glows, text colors
This commit is contained in:
zenchantlive 2026-02-26 16:04:19 -08:00
parent fcc1482967
commit 6564272987
3 changed files with 114 additions and 114 deletions

View file

@ -244,10 +244,10 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
};
const renderTaskItem = (issue: BeadIssue, showAssignButton: boolean = false, archetypeBadges: AgentArchetype[] = []) => (
<div key={issue.id} className="flex items-center gap-2 p-2 bg-[#0a111a] rounded-md border border-[var(--ui-border-soft)]">
<div key={issue.id} className="flex items-center gap-2 p-2 bg-[var(--surface-input)] rounded-md border border-[var(--border-subtle)]">
<div className="flex-1 min-w-0">
<div className="text-[10px] font-mono text-[var(--ui-text-muted)]">{issue.id}</div>
<div className="text-xs text-[var(--ui-text-primary)] truncate">{truncateTitle(issue.title)}</div>
<div className="text-[10px] font-mono text-[var(--text-tertiary)]">{issue.id}</div>
<div className="text-xs text-[var(--text-primary)] truncate">{truncateTitle(issue.title)}</div>
</div>
{archetypeBadges.length > 0 && (
<div className="flex gap-1">
@ -269,17 +269,17 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
<div className="relative">
<button
onClick={() => setQuickAssignDropdown(quickAssignDropdown === issue.id ? null : issue.id)}
className="h-6 w-6 flex items-center justify-center rounded bg-[var(--ui-accent-info)]/20 hover:bg-[var(--ui-accent-info)]/30 text-[var(--ui-accent-info)] transition-colors"
className="h-6 w-6 flex items-center justify-center rounded bg-[var(--accent-info)]/20 hover:bg-[var(--accent-info)]/30 text-[var(--accent-info)] transition-colors"
>
<UserPlus className="w-3 h-3" />
</button>
{quickAssignDropdown === issue.id && (
<div className="absolute right-0 top-full mt-1 z-10 bg-[#111f2b] border border-[var(--ui-border-soft)] rounded-md shadow-lg py-1 min-w-[120px]">
<div className="absolute right-0 top-full mt-1 z-10 bg-[var(--surface-tertiary)] border border-[var(--border-subtle)] rounded-md shadow-lg py-1 min-w-[120px]">
{archetypes.map((a: AgentArchetype) => (
<button
key={a.id}
onClick={() => handleQuickAssign(issue.id, a.id)}
className="w-full px-3 py-1.5 text-left text-xs text-[var(--ui-text-primary)] hover:bg-[#1a2d3d] transition-colors"
className="w-full px-3 py-1.5 text-left text-xs text-[var(--text-primary)] hover:bg-[var(--surface-hover)] transition-colors"
>
{a.name}
</button>
@ -296,14 +296,14 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
<div className="flex gap-2">
<button
onClick={() => setShowArchetypeList(!showArchetypeList)}
className={`flex-1 flex items-center justify-center gap-2 px-3 py-2 border rounded-lg text-sm font-medium text-[var(--ui-text-primary)] transition-colors ${showArchetypeList ? 'bg-[#1a2d3d] border-[var(--ui-accent-info)]' : 'bg-[#111f2b] hover:bg-[#1a2d3d] border-[var(--ui-border-soft)]'}`}
className={`flex-1 flex items-center justify-center gap-2 px-3 py-2 border rounded-lg text-sm font-medium text-[var(--text-primary)] transition-colors ${showArchetypeList ? 'bg-[var(--surface-active)] border-[var(--accent-info)]' : 'bg-[var(--surface-tertiary)] hover:bg-[var(--surface-hover)] border-[var(--border-subtle)]'}`}
>
<Blocks className="w-4 h-4 text-[var(--ui-accent-info)]" />
<Blocks className="w-4 h-4 text-[var(--accent-info)]" />
Archetypes
</button>
<button
onClick={() => setShowTemplateList(!showTemplateList)}
className={`flex-1 flex items-center justify-center gap-2 px-3 py-2 border rounded-lg text-sm font-medium text-[var(--ui-text-primary)] transition-colors ${showTemplateList ? 'bg-[#1a2d3d] border-amber-500' : 'bg-[#111f2b] hover:bg-[#1a2d3d] border-[var(--ui-border-soft)]'}`}
className={`flex-1 flex items-center justify-center gap-2 px-3 py-2 border rounded-lg text-sm font-medium text-[var(--text-primary)] transition-colors ${showTemplateList ? 'bg-[var(--surface-active)] border-[var(--accent-warning)]' : 'bg-[var(--surface-tertiary)] hover:bg-[var(--surface-hover)] border-[var(--border-subtle)]'}`}
>
<FileCode2 className="w-4 h-4 text-amber-500" />
Templates
@ -349,20 +349,20 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
/>
{selectedEpic && (
<div className="bg-[#111f2b] rounded-xl border border-purple-500/30 flex flex-col overflow-hidden">
<div className="px-4 py-3 border-b border-[var(--ui-border-soft)] bg-[#14202e] flex items-center gap-2">
<div className="bg-[var(--surface-tertiary)] rounded-xl border border-purple-500/30 flex flex-col overflow-hidden">
<div className="px-4 py-3 border-b border-[var(--border-subtle)] bg-[var(--surface-tertiary)] flex items-center gap-2">
<Layers className="w-4 h-4 text-purple-400" />
<h3 className="font-semibold text-sm text-[var(--ui-text-primary)]">Epic Template</h3>
<h3 className="font-semibold text-sm text-[var(--text-primary)]">Epic Template</h3>
</div>
<div className="p-4 space-y-3">
<div>
<div className="text-[10px] font-mono text-[var(--ui-text-muted)] uppercase tracking-wider mb-1">{selectedEpic.id}</div>
<div className="text-sm font-semibold text-[var(--ui-text-primary)] leading-snug">{selectedEpic.title}</div>
<div className="text-[10px] font-mono text-[var(--text-tertiary)] uppercase tracking-wider mb-1">{selectedEpic.id}</div>
<div className="text-sm font-semibold text-[var(--text-primary)] leading-snug">{selectedEpic.title}</div>
</div>
{epicTemplate ? (
<div className="space-y-3">
<div className="bg-[#0a111a] rounded-md p-3 border border-[var(--ui-border-soft)]">
<div className="bg-[var(--surface-input)] rounded-md p-3 border border-[var(--border-subtle)]">
<div className="flex items-center gap-2 mb-2">
<div
className="h-6 w-6 rounded flex items-center justify-center text-xs font-bold"
@ -373,13 +373,13 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
>
{epicTemplate.icon || 'T'}
</div>
<div className="font-semibold text-sm text-[var(--ui-text-primary)]">{epicTemplate.name}</div>
<div className="font-semibold text-sm text-[var(--text-primary)]">{epicTemplate.name}</div>
</div>
{epicTemplate.description && (
<div className="text-xs text-[var(--ui-text-muted)] mb-3">{epicTemplate.description}</div>
<div className="text-xs text-[var(--text-tertiary)] mb-3">{epicTemplate.description}</div>
)}
<div>
<div className="text-[10px] font-mono text-[var(--ui-text-muted)] uppercase tracking-wider mb-2">Team Roster</div>
<div className="text-[10px] font-mono text-[var(--text-tertiary)] uppercase tracking-wider mb-2">Team Roster</div>
<div className="space-y-1">
{Array.from(new Set(epicTemplate.team.map(m => m.archetypeId))).map(archetypeId => {
const archetype = archetypes.find((a: AgentArchetype) => a.id === archetypeId);
@ -396,8 +396,8 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
>
{getArchetypeDisplayChar(archetype)}
</div>
<span className="text-[var(--ui-text-primary)]">{archetype.name}</span>
<span className="text-[var(--ui-text-muted)] ml-auto">x{count}</span>
<span className="text-[var(--text-primary)]">{archetype.name}</span>
<span className="text-[var(--text-tertiary)] ml-auto">x{count}</span>
</div>
);
})}
@ -407,7 +407,7 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
<div className="flex gap-2">
<button
onClick={() => setShowTemplateList(true)}
className="flex-1 py-1.5 text-xs font-medium text-[var(--ui-text-muted)] bg-[#0a111a] border border-[var(--ui-border-soft)] rounded-md hover:bg-[#14202e] transition-colors"
className="flex-1 py-1.5 text-xs font-medium text-[var(--text-tertiary)] bg-[var(--surface-input)] border border-[var(--border-subtle)] rounded-md hover:bg-[var(--surface-hover)] transition-colors"
>
Change Template
</button>
@ -425,7 +425,7 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
</div>
) : (
<div className="space-y-3">
<div className="text-xs text-[var(--ui-text-muted)] text-center py-3">
<div className="text-xs text-[var(--text-tertiary)] text-center py-3">
No template assigned
</div>
<button
@ -441,26 +441,26 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
)}
{selectedIssue && selectedIssue.issue_type !== 'epic' && (
<div className="bg-[#111f2b] rounded-xl border border-[var(--ui-accent-info)]/30 flex flex-col overflow-hidden">
<div className="px-4 py-3 border-b border-[var(--ui-border-soft)] bg-[#14202e] flex items-center gap-2">
<Zap className="w-4 h-4 text-[var(--ui-accent-info)]" />
<h3 className="font-semibold text-sm text-[var(--ui-text-primary)]">Task Assignment</h3>
<div className="bg-[var(--surface-tertiary)] rounded-xl border border-[var(--accent-info)]/30 flex flex-col overflow-hidden">
<div className="px-4 py-3 border-b border-[var(--border-subtle)] bg-[var(--surface-tertiary)] flex items-center gap-2">
<Zap className="w-4 h-4 text-[var(--accent-info)]" />
<h3 className="font-semibold text-sm text-[var(--text-primary)]">Task Assignment</h3>
</div>
<div className="p-4 space-y-4">
<div>
<div className="text-[10px] font-mono text-[var(--ui-text-muted)] uppercase tracking-wider mb-1">{selectedIssue.id}</div>
<div className="text-sm font-semibold text-[var(--ui-text-primary)] leading-snug">{selectedIssue.title}</div>
<div className="text-xs text-[var(--ui-text-muted)] mt-1">Status: <span className="font-semibold uppercase">{selectedIssue.status}</span></div>
<div className="text-[10px] font-mono text-[var(--text-tertiary)] uppercase tracking-wider mb-1">{selectedIssue.id}</div>
<div className="text-sm font-semibold text-[var(--text-primary)] leading-snug">{selectedIssue.title}</div>
<div className="text-xs text-[var(--text-tertiary)] mt-1">Status: <span className="font-semibold uppercase">{selectedIssue.status}</span></div>
</div>
{(selectedIssue.status === 'open' || selectedIssue.status === 'blocked') ? (
<div className="space-y-3">
<div>
<label className="text-xs font-medium text-[var(--ui-text-muted)] mb-1.5 block">Assign Agent Archetype</label>
<label className="text-xs font-medium text-[var(--text-tertiary)] mb-1.5 block">Assign Agent Archetype</label>
<select
value={selectedArchetypeForPrep}
onChange={(e) => setSelectedArchetypeForPrep(e.target.value)}
className="w-full bg-[#0a111a] border border-[var(--ui-border-soft)] rounded-md px-3 py-2 text-sm text-[var(--ui-text-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--ui-accent-info)]"
className="w-full bg-[var(--surface-input)] border border-[var(--border-subtle)] rounded-md px-3 py-2 text-sm text-[var(--text-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--accent-info)]"
>
<option value="" disabled>Select archetype...</option>
{archetypes.map((a: AgentArchetype) => (
@ -471,7 +471,7 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
<button
onClick={handlePrepTask}
disabled={!selectedArchetypeForPrep || isPrepping || prepSuccess}
className={`w-full py-2 text-white text-sm font-bold rounded-md disabled:opacity-50 transition-colors flex items-center justify-center ${prepSuccess ? 'bg-emerald-500' : 'bg-[var(--ui-accent-info)] hover:bg-[var(--ui-accent-info)]/90'}`}
className={`w-full py-2 text-[var(--text-inverse)] text-sm font-bold rounded-md disabled:opacity-50 transition-colors flex items-center justify-center ${prepSuccess ? 'bg-[var(--accent-success)]' : 'bg-[var(--accent-info)] hover:bg-[var(--accent-info)]/90'}`}
>
{isPrepping ? <Loader2 className="w-4 h-4 animate-spin" /> : prepSuccess ? 'Prep Successful!' : 'Prep Task for Swarm'}
</button>
@ -485,20 +485,20 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
</div>
)}
<div className="bg-[#111f2b] rounded-xl border border-[var(--ui-border-soft)] flex flex-col overflow-hidden">
<div className="bg-[var(--surface-tertiary)] rounded-xl border border-[var(--border-subtle)] flex flex-col overflow-hidden">
<button
onClick={() => setNeedsAgentCollapsed(!needsAgentCollapsed)}
className="w-full px-4 py-3 border-b border-[var(--ui-border-soft)] bg-[#14202e] flex items-center gap-2 hover:bg-[#1a2d3d] transition-colors"
className="w-full px-4 py-3 border-b border-[var(--border-subtle)] bg-[var(--surface-tertiary)] flex items-center gap-2 hover:bg-[var(--surface-hover)] transition-colors"
>
{needsAgentCollapsed ? <ChevronRight className="w-4 h-4 text-[var(--ui-text-muted)]" /> : <ChevronDown className="w-4 h-4 text-[var(--ui-text-muted)]" />}
{needsAgentCollapsed ? <ChevronRight className="w-4 h-4 text-[var(--text-tertiary)]" /> : <ChevronDown className="w-4 h-4 text-[var(--text-tertiary)]" />}
<AlertCircle className="w-4 h-4 text-orange-400" />
<h3 className="font-semibold text-sm text-[var(--ui-text-primary)]">Needs Agent</h3>
<span className="ml-auto text-xs text-[var(--ui-text-muted)]">{needsAgentTasks.length}</span>
<h3 className="font-semibold text-sm text-[var(--text-primary)]">Needs Agent</h3>
<span className="ml-auto text-xs text-[var(--text-tertiary)]">{needsAgentTasks.length}</span>
</button>
{!needsAgentCollapsed && (
<div className="max-h-40 overflow-y-auto p-3 space-y-2 custom-scrollbar">
{needsAgentTasks.length === 0 ? (
<div className="text-center text-[var(--ui-text-muted)] text-xs py-2">
<div className="text-center text-[var(--text-tertiary)] text-xs py-2">
All actionable tasks have agents assigned
</div>
) : (
@ -511,17 +511,17 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
<div className="bg-[#111f2b] rounded-xl border border-[var(--ui-border-soft)] flex flex-col overflow-hidden">
<button
onClick={() => setPreAssignedCollapsed(!preAssignedCollapsed)}
className="w-full px-4 py-3 border-b border-[var(--ui-border-soft)] bg-[#14202e] flex items-center gap-2 hover:bg-[#1a2d3d] transition-colors"
className="w-full px-4 py-3 border-b border-[var(--border-subtle)] bg-[var(--surface-tertiary)] flex items-center gap-2 hover:bg-[var(--surface-hover)] transition-colors"
>
{preAssignedCollapsed ? <ChevronRight className="w-4 h-4 text-[var(--ui-text-muted)]" /> : <ChevronDown className="w-4 h-4 text-[var(--ui-text-muted)]" />}
<Clock className="w-4 h-4 text-amber-400" />
<h3 className="font-semibold text-sm text-[var(--ui-text-primary)]">Pre-assigned</h3>
<h3 className="font-semibold text-sm text-[var(--text-primary)]">Pre-assigned</h3>
<span className="ml-auto text-xs text-[var(--ui-text-muted)]">{preAssignedTasks.length}</span>
</button>
{!preAssignedCollapsed && (
<div className="max-h-40 overflow-y-auto p-3 space-y-2 custom-scrollbar">
{preAssignedTasks.length === 0 ? (
<div className="text-center text-[var(--ui-text-muted)] text-xs py-2">
<div className="text-center text-[var(--text-tertiary)] text-xs py-2">
No pre-assigned tasks waiting
</div>
) : (
@ -537,25 +537,25 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
)}
</div>
<div className="flex-1 bg-[#111f2b] rounded-xl border border-[var(--ui-border-soft)] flex flex-col overflow-hidden min-h-0">
<div className="flex-1 bg-[var(--surface-tertiary)] rounded-xl border border-[var(--border-subtle)] flex flex-col overflow-hidden min-h-0">
<button
onClick={() => setSquadRosterCollapsed(!squadRosterCollapsed)}
className="w-full px-4 py-3 border-b border-[var(--ui-border-soft)] bg-[#14202e] flex items-center gap-2 hover:bg-[#1a2d3d] transition-colors"
className="w-full px-4 py-3 border-b border-[var(--border-subtle)] bg-[var(--surface-tertiary)] flex items-center gap-2 hover:bg-[var(--surface-hover)] transition-colors"
>
{squadRosterCollapsed ? <ChevronRight className="w-4 h-4 text-[var(--ui-text-muted)]" /> : <ChevronDown className="w-4 h-4 text-[var(--ui-text-muted)]" />}
<Users className="w-4 h-4 text-emerald-500" />
<h3 className="font-semibold text-sm text-[var(--ui-text-primary)]">Squad Roster</h3>
<span className="ml-auto text-xs text-[var(--ui-text-muted)]">{activeRoster.length} active</span>
<h3 className="font-semibold text-sm text-[var(--text-primary)]">Squad Roster</h3>
<span className="ml-auto text-xs text-[var(--text-tertiary)]">{activeRoster.length} active</span>
</button>
{!squadRosterCollapsed && (
<div className="flex-1 overflow-y-auto p-3 space-y-2 custom-scrollbar">
{activeRoster.length === 0 ? (
<div className="text-center text-[var(--ui-text-muted)] text-xs py-4">
<div className="text-center text-[var(--text-tertiary)] text-xs py-4">
No active agents
</div>
) : (
activeRoster.map(({ issue, archetype }) => (
<div key={issue.id} className="flex items-center gap-2 p-2 bg-[#0a111a] rounded-md border border-[var(--ui-border-soft)]">
<div key={issue.id} className="flex items-center gap-2 p-2 bg-[var(--surface-input)] rounded-md border border-[var(--border-subtle)]">
<div
className="h-6 w-6 rounded flex items-center justify-center text-xs font-bold"
style={{
@ -566,8 +566,8 @@ export function AssignmentPanel({ selectedIssue, projectRoot, issues, epicId }:
{archetype ? getArchetypeDisplayChar(archetype) : '?'}
</div>
<div className="flex-1 min-w-0">
<div className="text-xs font-medium text-[var(--ui-text-primary)] truncate">{issue.assignee}</div>
<div className="text-[10px] text-[var(--ui-text-muted)] truncate">{issue.id}</div>
<div className="text-xs font-medium text-[var(--text-primary)] truncate">{issue.assignee}</div>
<div className="text-[10px] text-[var(--text-tertiary)] truncate">{issue.id}</div>
</div>
</div>
))

View file

@ -71,8 +71,8 @@ function statusDot(status: BeadIssue['status']): string {
*/
function nodeStyle(kind: GraphNodeData['kind']): string {
return kind === 'epic'
? 'bg-sky-500/10 border-sky-400/30'
: 'bg-slate-800/60 border-white/10';
? 'bg-[var(--graph-node-epic)] border-[var(--accent-info)]/30'
: 'bg-[var(--graph-node-default)] border-[var(--border-subtle)]';
}
/**
@ -224,23 +224,23 @@ export function GraphNodeCard({ id, data, selected }: NodeProps<Node<GraphNodeDa
data.isCycleNode ? 'ring-2 ring-rose-400/55' : ''
} ${
data.isActionable && !selected
? 'ring-1 ring-emerald-400/30 shadow-[0_0_20px_rgba(16,185,129,0.12)]'
? 'ring-1 ring-[var(--accent-success)]/30 shadow-[var(--glow-success)]'
: ''
} ${
selected
? 'border-sky-400/50 shadow-[0_20px_48px_-8px_rgba(0,0,0,0.5)] ring-1 ring-sky-400/20 node-select-pulse'
: 'hover:border-white/20 hover:shadow-[0_8px_32px_-4px_rgba(0,0,0,0.3)]'
? 'border-[var(--accent-info)]/50 shadow-[var(--shadow-lg)] ring-1 ring-[var(--accent-info)]/20 node-select-pulse'
: 'hover:border-[var(--border-default)] hover:shadow-[var(--shadow-md)]'
} ${
data.isDimmed ? 'opacity-30' : 'opacity-100'
}`}
>
<div className="flex items-center justify-between gap-2 border-b border-white/5 pb-1.5 mb-1.5">
<span className="font-mono text-[9px] uppercase tracking-[0.12em] text-text-muted/60">{id}</span>
<div className="flex items-center justify-between gap-2 border-b border-[var(--border-subtle)] pb-1.5 mb-1.5">
<span className="font-mono text-[9px] uppercase tracking-[0.12em] text-[var(--text-tertiary)]/60">{id}</span>
<div className="flex items-center gap-1.5 flex-wrap">
{assignedArchetypes.map((archetype) => (
<span
key={archetype.id}
className="rounded-md px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-white ring-1"
className="rounded-md px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-[var(--text-inverse)] ring-1"
style={{
backgroundColor: `${archetype.color}20`,
borderColor: `${archetype.color}40`,
@ -251,42 +251,42 @@ export function GraphNodeCard({ id, data, selected }: NodeProps<Node<GraphNodeDa
</span>
))}
{data.isActionable ? (
<span className="rounded-md bg-emerald-500/15 px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-emerald-400 ring-1 ring-emerald-500/20">
<span className="rounded-md bg-[var(--status-ready)] px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-[var(--accent-success)] ring-1 ring-[var(--accent-success)]/20">
Ready
</span>
) : null}
{data.status === 'in_progress' ? (
<span className="rounded-md bg-amber-400/10 px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-amber-400">
<span className="rounded-md bg-[var(--status-in-progress)] px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-[var(--accent-warning)]">
In Progress
</span>
) : data.status === 'blocked' ? (
<span className="rounded-md bg-rose-400/10 px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-rose-400">
<span className="rounded-md bg-[var(--status-blocked)] px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-[var(--accent-danger)]">
Blocked
</span>
) : data.status === 'closed' ? (
<span className="rounded-md bg-emerald-400/10 px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-emerald-400">
<span className="rounded-md bg-[var(--status-closed)] px-1.5 py-0.5 text-[7px] font-bold uppercase tracking-wider text-[var(--accent-success)]">
Done
</span>
) : null}
<span className="text-[9px] font-bold uppercase tracking-wider text-text-muted/40">p{data.priority}</span>
<span className={`h-2 w-2 rounded-full ring-2 ring-black/40 ${statusDot(data.status)}`} />
<span className="text-[9px] font-bold uppercase tracking-wider text-[var(--text-tertiary)]/40">p{data.priority}</span>
<span className={`h-2 w-2 rounded-full ring-2 ring-[var(--alpha-black-medium)] ${statusDot(data.status)}`} />
</div>
</div>
<p className={`text-[15px] font-bold leading-[1.2] tracking-tight text-text-strong group-hover:text-sky-100 transition-colors ${data.status === 'closed' ? 'line-through opacity-70' : ''}`}>
<p className={`text-[15px] font-bold leading-[1.2] tracking-tight text-[var(--text-primary)] group-hover:text-[var(--accent-info)] transition-colors ${data.status === 'closed' ? 'line-through opacity-70' : ''}`}>
{data.title}
</p>
{data.blockerTooltipLines.length > 0 ? (
<div className="mt-2 border-t border-white/5 pt-1.5">
<p className="text-[8px] font-bold uppercase tracking-widest text-rose-400/70 mb-0.5">Waiting on</p>
<div className="mt-2 border-t border-[var(--border-subtle)] pt-1.5">
<p className="text-[8px] font-bold uppercase tracking-widest text-[var(--accent-danger)]/70 mb-0.5">Waiting on</p>
{data.blockerTooltipLines.slice(0, 2).map((line) => (
<p key={line} className="text-[9px] text-text-muted/70 truncate leading-tight">
<p key={line} className="text-[9px] text-[var(--text-tertiary)]/70 truncate leading-tight">
{line}
</p>
))}
{data.blockerTooltipLines.length > 2 ? (
<p className="text-[8px] text-text-muted/50">
<p className="text-[8px] text-[var(--text-tertiary)]/50">
+{data.blockerTooltipLines.length - 2} more
</p>
) : null}
@ -294,14 +294,14 @@ export function GraphNodeCard({ id, data, selected }: NodeProps<Node<GraphNodeDa
) : null}
{!isClosed && archetypes.length > 0 ? (
<div className="mt-2 border-t border-white/5 pt-2">
<div className="mt-2 border-t border-[var(--border-subtle)] pt-2">
{assignSuccess ? (
<div className="text-[9px] text-emerald-400 font-medium mb-1.5">
<div className="text-[9px] text-[var(--accent-success)] font-medium mb-1.5">
{assignSuccess}
</div>
) : null}
{assignError ? (
<div className="text-[9px] text-rose-400 font-medium mb-1.5">
<div className="text-[9px] text-[var(--accent-danger)] font-medium mb-1.5">
{assignError}
</div>
) : null}
@ -338,7 +338,7 @@ export function GraphNodeCard({ id, data, selected }: NodeProps<Node<GraphNodeDa
<button
type="button"
disabled={isAssigning}
className="flex items-center gap-1.5 w-full rounded-md bg-white/5 hover:bg-white/10 px-2 py-1.5 text-[9px] font-medium text-text-muted/80 transition-colors disabled:opacity-50"
className="flex items-center gap-1.5 w-full rounded-md bg-[var(--alpha-white-low)] hover:bg-[var(--alpha-white-medium)] px-2 py-1.5 text-[9px] font-medium text-[var(--text-tertiary)]/80 transition-colors disabled:opacity-50"
>
{isAssigning ? (
<Loader2 className="h-3 w-3 animate-spin" />
@ -351,7 +351,7 @@ export function GraphNodeCard({ id, data, selected }: NodeProps<Node<GraphNodeDa
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="min-w-[180px] rounded-lg border border-white/10 bg-[#0d0f14]/95 p-1 shadow-[0_12px_32px_rgba(0,0,0,0.6)] backdrop-blur-lg z-50"
className="min-w-[180px] rounded-lg border border-[var(--border-subtle)] bg-[var(--surface-overlay)] p-1 shadow-[var(--shadow-lg)] backdrop-blur-lg z-50"
sideOffset={4}
>
{archetypes.map((archetype) => {
@ -364,7 +364,7 @@ export function GraphNodeCard({ id, data, selected }: NodeProps<Node<GraphNodeDa
className={`flex items-center gap-2 rounded-md px-2 py-1.5 text-[11px] font-medium outline-none cursor-pointer transition-colors ${
isAssigned
? 'opacity-50 cursor-not-allowed'
: 'text-text-strong hover:bg-white/10 focus:bg-white/10'
: 'text-[var(--text-primary)] hover:bg-[var(--alpha-white-low)] focus:bg-[var(--alpha-white-low)]'
}`}
>
<span
@ -373,7 +373,7 @@ export function GraphNodeCard({ id, data, selected }: NodeProps<Node<GraphNodeDa
/>
<span>{archetype.name}</span>
{isAssigned && (
<span className="ml-auto text-[9px] text-text-muted/60">Assigned</span>
<span className="ml-auto text-[9px] text-[var(--text-tertiary)]/60">Assigned</span>
)}
</DropdownMenu.Item>
);
@ -387,23 +387,23 @@ export function GraphNodeCard({ id, data, selected }: NodeProps<Node<GraphNodeDa
{hovered ? (
<div className="absolute left-1/2 top-full z-50 mt-2 -translate-x-1/2 animate-fade-in">
<div className="max-w-xs rounded-lg border border-white/10 bg-[#0d0f14]/95 px-3 py-2 shadow-[0_12px_32px_rgba(0,0,0,0.6)] backdrop-blur-lg">
<div className="max-w-xs rounded-lg border border-[var(--border-subtle)] bg-[var(--surface-overlay)] px-3 py-2 shadow-[var(--shadow-lg)] backdrop-blur-lg">
{data.isActionable ? (
<>
<p className="text-[10px] font-bold text-emerald-400">Ready to work</p>
<p className="mt-0.5 text-[10px] text-text-muted/80">
<p className="text-[10px] font-bold text-[var(--accent-success)]">Ready to work</p>
<p className="mt-0.5 text-[10px] text-[var(--text-tertiary)]/80">
No open blockers. {data.blocks} task{data.blocks === 1 ? '' : 's'} depend{data.blocks === 1 ? 's' : ''} on this.
</p>
</>
) : (
<>
<p className="text-[10px] font-bold text-rose-400">
<p className="text-[10px] font-bold text-[var(--accent-danger)]">
Blocked by {data.blockedBy} task{data.blockedBy === 1 ? '' : 's'}
</p>
{data.blockerTooltipLines.length > 0 ? (
<ul className="mt-1 space-y-0.5">
{data.blockerTooltipLines.map((line) => (
<li key={line} className="text-[9px] text-text-muted/80">
<li key={line} className="text-[9px] text-[var(--text-tertiary)]/80">
&bull; {line}
</li>
))}

View file

@ -34,8 +34,8 @@ function handleCardKeyDown(event: KeyboardEvent<HTMLDivElement>, onClick?: Mouse
function statusVisual(status: SocialCardData['status']) {
if (status === 'blocked') {
return {
border: '#ff4c72',
badgeBg: 'rgba(255, 76, 114, 0.25)',
border: 'var(--accent-danger)',
badgeBg: 'var(--status-blocked)',
badgeText: '#ffd5df',
chipText: 'Blocked',
};
@ -43,8 +43,8 @@ function statusVisual(status: SocialCardData['status']) {
if (status === 'in_progress') {
return {
border: '#ffb24a',
badgeBg: 'rgba(255, 178, 74, 0.25)',
border: 'var(--accent-warning)',
badgeBg: 'var(--status-in-progress)',
badgeText: '#ffe5c7',
chipText: 'Active',
};
@ -52,17 +52,17 @@ function statusVisual(status: SocialCardData['status']) {
if (status === 'ready') {
return {
border: '#35d98f',
badgeBg: 'rgba(53, 217, 143, 0.25)',
border: 'var(--accent-success)',
badgeBg: 'var(--status-ready)',
badgeText: '#d6ffe7',
chipText: 'Ready',
};
}
return {
border: 'var(--ui-border-strong)',
badgeBg: 'rgba(168, 164, 154, 0.15)',
badgeText: 'var(--ui-text-muted)',
border: 'var(--border-default)',
badgeBg: 'var(--status-closed)',
badgeText: 'var(--text-tertiary)',
chipText: 'Closed',
};
}
@ -75,7 +75,7 @@ function dependencyPanel(
if (details.length === 0) return null;
return (
<div className="rounded-md border border-[var(--ui-border-soft)] bg-[color-mix(in_srgb,var(--ui-bg-panel)_82%,black)] px-2.5 py-2">
<div className="rounded-md border border-[var(--border-subtle)] bg-[var(--surface-tertiary)] px-2.5 py-2">
<p className="mb-1 font-mono text-[10px] uppercase tracking-[0.12em]" style={{ color }}>
{title}
</p>
@ -83,20 +83,20 @@ function dependencyPanel(
{details.slice(0, 1).map((item) => (
<div
key={`${title}-${item.id}`}
className="rounded border border-[var(--ui-border-soft)] bg-[color-mix(in_srgb,var(--ui-bg-card)_88%,black)] px-2 py-1.5"
className="rounded border border-[var(--border-subtle)] bg-[var(--surface-quaternary)] px-2 py-1.5"
>
<div className="mb-0.5 flex items-center gap-1">
<span className="h-1.5 w-1.5 rounded-full bg-[var(--ui-accent-info)]" />
<span className="font-mono text-[10px] text-[var(--ui-text-muted)]">{item.id}</span>
<span className="h-1.5 w-1.5 rounded-full bg-[var(--accent-info)]" />
<span className="font-mono text-[10px] text-[var(--text-tertiary)]">{item.id}</span>
</div>
<p className="line-clamp-1 text-xs text-[var(--ui-text-primary)]">{item.title}</p>
<p className="line-clamp-1 text-xs text-[var(--text-primary)]">{item.title}</p>
{item.epic ? (
<p className="line-clamp-1 text-[10px] text-[var(--ui-accent-info)]"> {item.epic}</p>
<p className="line-clamp-1 text-[10px] text-[var(--accent-info)]"> {item.epic}</p>
) : null}
</div>
))}
</div>
{details.length > 1 ? <p className="mt-1 text-[11px] text-[var(--ui-text-muted)]">+{details.length - 1} more</p> : null}
{details.length > 1 ? <p className="mt-1 text-[11px] text-[var(--text-tertiary)]">+{details.length - 1} more</p> : null}
</div>
);
}
@ -127,12 +127,12 @@ export function SocialCard({
tabIndex={0}
aria-label={`Open ${data.title}`}
className={cn(
'group relative flex min-h-[290px] cursor-pointer flex-col rounded-[14px] border px-3.5 py-3 text-left transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent-info)]',
'group relative flex min-h-[290px] cursor-pointer flex-col rounded-[14px] border px-3.5 py-3 text-left transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-info)]',
className,
)}
style={{
background: 'var(--ui-bg-card)',
borderColor: selected ? status.border : 'var(--ui-border-strong)',
background: 'var(--surface-quaternary)',
borderColor: selected ? status.border : 'var(--border-default)',
boxShadow: selected
? `0 0 0 2px ${status.border}, 0 20px 40px -20px rgba(0,0,0,0.6)`
: '0 4px 12px -6px rgba(0,0,0,0.4)',
@ -143,24 +143,24 @@ export function SocialCard({
<Badge className="rounded px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.1em]" style={{ backgroundColor: status.badgeBg, color: status.badgeText }}>
{status.chipText}
</Badge>
<span className="font-mono text-[11px] text-[var(--ui-accent-info)]">{data.priority}</span>
<span className="truncate font-mono text-[11px] text-[var(--ui-text-muted)]">{data.id}</span>
<span className="font-mono text-[11px] text-[var(--accent-info)]">{data.priority}</span>
<span className="truncate font-mono text-[11px] text-[var(--text-tertiary)]">{data.id}</span>
{unreadCount > 0 ? (
<span className="inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--ui-accent-action-red)] px-1 text-[10px] font-semibold text-white">
<span className="inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--accent-danger)] px-1 text-[10px] font-semibold text-[var(--text-inverse)]">
{unreadCount}
</span>
) : null}
</div>
</div>
<h3 className="line-clamp-2 text-[27px] font-semibold leading-[1.13] tracking-[-0.01em] text-[var(--ui-text-primary)]">{data.title}</h3>
<p className="mt-1.5 line-clamp-3 min-h-[56px] text-[13px] leading-relaxed text-[var(--ui-text-muted)]">
<h3 className="line-clamp-2 text-[27px] font-semibold leading-[1.13] tracking-[-0.01em] text-[var(--text-primary)]">{data.title}</h3>
<p className="mt-1.5 line-clamp-3 min-h-[56px] text-[13px] leading-relaxed text-[var(--text-tertiary)]">
{description || 'No summary provided yet.'}
</p>
<div className="mt-2 flex flex-col gap-2">
{dependencyPanel('Blocked By', 'var(--ui-accent-blocked)', blockedByDetails)}
{dependencyPanel('Unblocks', 'var(--ui-accent-ready)', unblocksDetails)}
{dependencyPanel('Blocked By', 'var(--accent-danger)', blockedByDetails)}
{dependencyPanel('Unblocks', 'var(--accent-success)', unblocksDetails)}
</div>
<div className="mt-2 flex items-center gap-2">
@ -173,17 +173,17 @@ export function SocialCard({
size="sm"
/>
))}
{data.agents.length === 0 ? <span className="text-xs text-[var(--ui-text-muted)]">No crew</span> : null}
{data.agents.length === 0 ? <span className="text-xs text-[var(--text-tertiary)]">No crew</span> : null}
</div>
<div className="mt-auto border-t border-[var(--ui-border-soft)] pt-1.5">
<div className="mb-1.5 flex items-center justify-between text-xs text-[var(--ui-text-muted)]">
<div className="mt-auto border-t border-[var(--border-subtle)] pt-1.5">
<div className="mb-1.5 flex items-center justify-between text-xs text-[var(--text-tertiary)]">
<span className="inline-flex items-center gap-1"><Clock3 className="h-3.5 w-3.5" aria-hidden="true" />{updatedLabel}</span>
<span className="font-mono text-[11px] text-[var(--ui-accent-ready)]">stage active</span>
<span className="font-mono text-[11px] text-[var(--accent-success)]">stage active</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 text-xs text-[var(--ui-text-muted)]">
<div className="flex items-center gap-3 text-xs text-[var(--text-tertiary)]">
<span className="inline-flex items-center gap-1"><Link2 className="h-3.5 w-3.5" aria-hidden="true" />{dependencyCount ?? data.blocks.length + data.unblocks.length}</span>
<span className="inline-flex items-center gap-1"><MessageCircle className="h-3.5 w-3.5" aria-hidden="true" />{commentCount ?? 0}</span>
</div>
@ -195,7 +195,7 @@ export function SocialCard({
event.stopPropagation();
onJumpToGraph?.(data.id);
}}
className="inline-flex h-7 w-7 items-center justify-center rounded-md border border-[var(--ui-border-soft)] bg-[var(--ui-bg-panel)] text-[var(--ui-accent-info)] transition-colors hover:bg-white/5"
className="inline-flex h-7 w-7 items-center justify-center rounded-md border border-[var(--border-subtle)] bg-[var(--surface-tertiary)] text-[var(--accent-info)] transition-colors hover:bg-[var(--alpha-white-low)]"
aria-label="Open in graph"
>
<GitBranch className="h-3.5 w-3.5" aria-hidden="true" />
@ -206,7 +206,7 @@ export function SocialCard({
event.stopPropagation();
onJumpToActivity?.(data.id);
}}
className="inline-flex h-7 w-7 items-center justify-center rounded-md border border-[var(--ui-border-soft)] bg-[var(--ui-bg-panel)] text-[var(--ui-accent-warning)] transition-colors hover:bg-white/5"
className="inline-flex h-7 w-7 items-center justify-center rounded-md border border-[var(--border-subtle)] bg-[var(--surface-tertiary)] text-[var(--accent-warning)] transition-colors hover:bg-[var(--alpha-white-low)]"
aria-label="Open in activity"
>
<Orbit className="h-3.5 w-3.5" aria-hidden="true" />
@ -217,7 +217,7 @@ export function SocialCard({
event.stopPropagation();
onOpenThread?.();
}}
className="inline-flex h-7 w-7 items-center justify-center rounded-md border border-[var(--ui-border-soft)] bg-[var(--ui-bg-panel)] text-[var(--ui-accent-ready)] transition-colors hover:bg-white/5"
className="inline-flex h-7 w-7 items-center justify-center rounded-md border border-[var(--border-subtle)] bg-[var(--surface-tertiary)] text-[var(--accent-success)] transition-colors hover:bg-[var(--alpha-white-low)]"
aria-label="Open thread"
>
<Activity className="h-3.5 w-3.5" aria-hidden="true" />