From 3a41ec7b9c170591a2b65cc7fe5c037170084657 Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Fri, 10 Apr 2026 17:40:32 +0000 Subject: [PATCH] feat(nexus): design system phase 3 raw utility sweep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third phase of the DESIGN.md migration. Removes every raw Tailwind color palette utility (bg-red-*, text-amber-*, border-blue-*, etc.) from component source and replaces them with the semantic tokens introduced in phases 1 and 2. Scope: - 84 files touched under ui/src/ - ~420 raw palette utility instances replaced - 23 hardcoded hex fallbacks replaced with var(--token) refs - Zero raw palette utilities remain in component source (verified with rg '(bg|text|border|ring)-(red|blue|green|amber| yellow|cyan|violet|purple|pink|slate|zinc|neutral|sky|teal| emerald|indigo|rose|orange|fuchsia)-[0-9]+' ui/src) Mapping rules applied: - red-* -> destructive - amber-/yellow-/orange-* -> warning - green-/emerald-* -> success - blue-/cyan-/sky-* -> primary (info/in-progress) or muted-foreground - slate-/gray-/zinc-/neutral-* -> muted / muted-foreground / border - violet-/purple-/pink-/indigo-/rose-/teal-* -> collapsed to primary or muted (most were one-off decorative choices, not role-bearing). Role-bearing uses go through lib/agent-role-colors which was rewritten in phase 2. - Opacity modifiers preserved (/10, /15, /20, etc.) - dark: variant duplicates removed (theme tokens auto-switch) Hardcoded hex fallbacks fixed: - #6366f1 (indigo) -> var(--primary) / var(--volt) - #64748b (slate) -> var(--muted-foreground) / var(--silver) - #4f46e5 (indigo) -> var(--primary) - #89b4fa (old Catppuccin blue) -> var(--primary) / #faff69 - OrgChart status dots (#22d3ee/#4ade80/#facc15/#f87171/#a3a3a3) -> var(--primary) / var(--success) / var(--warning) / var(--destructive) / var(--muted-foreground) per status - VoiceWaveform fallback #89b4fa -> #faff69 (volt) Legitimate hex values left untouched (12 total): - lib/color-contrast.ts WCAG reference constants - lib/worktree-branding.ts contrast fallback references - lib/mention-chips.ts runtime-generated SVG fills - context/ThemeContext.tsx theme metadata brand hexes - components/ThemeSeedInput.tsx user-facing hex picker Ambiguous decisions (flagged for visual QA): - AgentDetail.tsx invocation-source badges (timer/assignment/ on_demand) collapsed to primary/muted — visual distinction is reduced, labels still differ. Consider chart-role slots if differentiation matters. - AgentDetail.tsx mixed-opacity amber banners: bg-warning/60 against new warning base reads heavier than original amber-50 base. - Live-state dots in KanbanBoard/AgentDetail: bg-blue-* -> bg-primary — will glow volt in dark mode, probably desirable. Verification: - npx tsc --noEmit in ui/ — zero errors introduced. Pre-existing errors in AgentConfigForm, command.tsx, useKeyboardShortcuts, usePiperTts, useVadRecorder, PersonalAssistant remain, all unrelated to color work. - Dev server on :6100 returns 200. Not changed in this commit: - ui/src/lib/company-routes.ts — separate routing fix for broken Assistant/ContentStudio/Convert links, committed next. - Test files — a few will need assertion updates but are out of phase 3 scope. Phase 4 follow-ups (rounded-xl/2xl collapse, soft shadow removal, gradient removal) noted in .planning/AUDIT-RADIUS-SHADOWS.md. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../adapters/hermes-local/config-fields.tsx | 6 +- ui/src/components/AccountingModelCard.tsx | 6 +- ui/src/components/ActiveAgentsPanel.tsx | 8 +- ui/src/components/ActivityCharts.tsx | 34 +- ui/src/components/AgentConfigForm.tsx | 18 +- ui/src/components/AgentProperties.tsx | 2 +- ui/src/components/ApprovalCard.tsx | 10 +- ui/src/components/BudgetIncidentCard.tsx | 14 +- ui/src/components/BudgetPolicyCard.tsx | 18 +- ui/src/components/BudgetSidebarMarker.tsx | 2 +- ui/src/components/ChatMessageIdentityBar.tsx | 2 +- ui/src/components/ChatSearchDialog.tsx | 2 +- ui/src/components/ChatStatusUpdateBadge.tsx | 2 +- ui/src/components/ClaudeSubscriptionPanel.tsx | 6 +- ui/src/components/CodexSubscriptionPanel.tsx | 6 +- ui/src/components/CommentThread.tsx | 18 +- ui/src/components/CompanyRail.tsx | 6 +- ui/src/components/CompanySwitcher.tsx | 8 +- ui/src/components/DevRestartBanner.tsx | 12 +- .../ExecutionWorkspaceCloseDialog.tsx | 8 +- ui/src/components/FinanceTimelineCard.tsx | 2 +- ui/src/components/IssueDocumentsSection.tsx | 22 +- ui/src/components/IssueProperties.tsx | 6 +- ui/src/components/IssueRow.tsx | 4 +- ui/src/components/IssueWorkspaceCard.tsx | 6 +- ui/src/components/IssuesList.tsx | 10 +- ui/src/components/KanbanBoard.tsx | 4 +- ui/src/components/LiveRunWidget.tsx | 12 +- ui/src/components/MarkdownEditor.tsx | 2 +- ui/src/components/NewAgentDialog.tsx | 2 +- ui/src/components/NewIssueDialog.tsx | 6 +- ui/src/components/NexusOnboardingWizard.tsx | 465 ++++++++---------- ui/src/components/OfflineBanner.tsx | 2 +- ui/src/components/OnboardingWizard.tsx | 22 +- ui/src/components/OutputFeedbackButtons.tsx | 4 +- ui/src/components/ProjectProperties.tsx | 10 +- ui/src/components/ProviderQuotaCard.tsx | 6 +- ui/src/components/QuotaBar.tsx | 6 +- ui/src/components/ReportsToPicker.tsx | 4 +- .../components/RoutineRunVariablesDialog.tsx | 4 +- ui/src/components/SidebarAgents.tsx | 6 +- ui/src/components/SidebarNavItem.tsx | 12 +- ui/src/components/SidebarProjects.tsx | 2 +- ui/src/components/SkillCard.tsx | 4 +- ui/src/components/StarRating.tsx | 2 +- ui/src/components/SwipeToArchive.tsx | 4 +- ui/src/components/ToastViewport.tsx | 16 +- ui/src/components/VoiceWaveform.tsx | 4 +- ui/src/components/agent-config-primitives.tsx | 4 +- .../onboarding/HardwareSummaryStep.tsx | 57 ++- ui/src/components/onboarding/ModeSelector.tsx | 61 ++- .../onboarding/OnboardingSummaryStep.tsx | 60 ++- .../onboarding/ProviderSelectionStep.tsx | 174 +++++-- ui/src/components/onboarding/TelegramStep.tsx | 126 ++--- ui/src/components/onboarding/VoiceStep.tsx | 91 ++-- .../transcript/RunTranscriptView.tsx | 70 +-- ui/src/pages/AgentDetail.tsx | 152 +++--- ui/src/pages/Agents.tsx | 8 +- ui/src/pages/ApprovalDetail.tsx | 14 +- ui/src/pages/Approvals.tsx | 2 +- ui/src/pages/Companies.tsx | 6 +- ui/src/pages/CompanyExport.tsx | 6 +- ui/src/pages/CompanyImport.tsx | 30 +- ui/src/pages/CompanySettings.tsx | 4 +- ui/src/pages/ContentStudio.tsx | 2 +- ui/src/pages/Costs.tsx | 6 +- ui/src/pages/Dashboard.tsx | 18 +- ui/src/pages/DesignGuide.tsx | 26 +- ui/src/pages/ExecutionWorkspaceDetail.tsx | 2 +- ui/src/pages/Inbox.tsx | 42 +- ui/src/pages/InstanceExperimentalSettings.tsx | 4 +- ui/src/pages/InstanceGeneralSettings.tsx | 4 +- ui/src/pages/InviteLanding.tsx | 2 +- ui/src/pages/IssueDetail.tsx | 12 +- ui/src/pages/Org.tsx | 10 +- ui/src/pages/OrgChart.tsx | 16 +- ui/src/pages/PluginManager.tsx | 26 +- ui/src/pages/PluginSettings.tsx | 28 +- ui/src/pages/ProjectDetail.tsx | 12 +- ui/src/pages/ProjectWorkspaceDetail.tsx | 2 +- ui/src/pages/RoutineDetail.tsx | 14 +- ui/src/pages/Routines.tsx | 8 +- ui/src/pages/RunTranscriptUxLab.tsx | 20 +- ui/src/pages/SkillDetail.tsx | 6 +- 84 files changed, 1049 insertions(+), 913 deletions(-) diff --git a/ui/src/adapters/hermes-local/config-fields.tsx b/ui/src/adapters/hermes-local/config-fields.tsx index 568f2f6a..56f91f15 100644 --- a/ui/src/adapters/hermes-local/config-fields.tsx +++ b/ui/src/adapters/hermes-local/config-fields.tsx @@ -118,13 +118,13 @@ export function HermesLocalConfigFields({ } > {showInstallCallout && ( -
- Ollama is not detected. +
+ Ollama is not detected. Install Ollama diff --git a/ui/src/components/AccountingModelCard.tsx b/ui/src/components/AccountingModelCard.tsx index 98aca4f5..b3ddec41 100644 --- a/ui/src/components/AccountingModelCard.tsx +++ b/ui/src/components/AccountingModelCard.tsx @@ -8,21 +8,21 @@ const SURFACES = [ description: "Request-scoped usage and billed runs from cost_events.", icon: Database, points: ["tokens + billed dollars", "provider, biller, model", "subscription and overage aware"], - tone: "from-sky-500/12 via-sky-500/6 to-transparent", + tone: "from-primary/12 via-primary/6 to-transparent", }, { title: "Finance ledger", description: "Account-level charges that are not one prompt-response pair.", icon: ReceiptText, points: ["top-ups, refunds, fees", "Bedrock provisioned or training charges", "credit expiries and adjustments"], - tone: "from-amber-500/14 via-amber-500/6 to-transparent", + tone: "from-warning/14 via-warning/6 to-transparent", }, { title: "Live quotas", description: "Provider or biller windows that can stop traffic in real time.", icon: Gauge, points: ["provider quota windows", "biller credit systems", "errors surfaced directly"], - tone: "from-emerald-500/14 via-emerald-500/6 to-transparent", + tone: "from-success/14 via-success/6 to-transparent", }, ] as const; diff --git a/ui/src/components/ActiveAgentsPanel.tsx b/ui/src/components/ActiveAgentsPanel.tsx index c0883192..134479a2 100644 --- a/ui/src/components/ActiveAgentsPanel.tsx +++ b/ui/src/components/ActiveAgentsPanel.tsx @@ -93,7 +93,7 @@ function AgentRunCard({
@@ -102,8 +102,8 @@ function AgentRunCard({
{isActive ? ( - - + + ) : ( @@ -129,7 +129,7 @@ function AgentRunCard({ to={`/issues/${issue?.identifier ?? run.issueId}`} className={cn( "line-clamp-2 hover:underline", - isActive ? "text-cyan-700 dark:text-cyan-300" : "text-muted-foreground hover:text-foreground", + isActive ? "text-primary" : "text-muted-foreground hover:text-foreground", )} title={issue?.title ? `${issue?.identifier ?? run.issueId.slice(0, 8)} - ${issue.title}` : issue?.identifier ?? run.issueId.slice(0, 8)} > diff --git a/ui/src/components/ActivityCharts.tsx b/ui/src/components/ActivityCharts.tsx index f72e4e57..ef1c2be7 100644 --- a/ui/src/components/ActivityCharts.tsx +++ b/ui/src/components/ActivityCharts.tsx @@ -88,9 +88,9 @@ export function RunActivityChart({ runs }: { runs: HeartbeatRun[] }) {
{total > 0 ? (
- {entry.succeeded > 0 &&
} - {entry.failed > 0 &&
} - {entry.other > 0 &&
} + {entry.succeeded > 0 &&
} + {entry.failed > 0 &&
} + {entry.other > 0 &&
}
) : (
@@ -105,10 +105,10 @@ export function RunActivityChart({ runs }: { runs: HeartbeatRun[] }) { } const priorityColors: Record = { - critical: "#ef4444", - high: "#f97316", - medium: "#eab308", - low: "#6b7280", + critical: "var(--destructive)", + high: "var(--chart-4)", + medium: "var(--chart-1)", + low: "var(--muted-foreground)", }; const priorityOrder = ["critical", "high", "medium", "low"] as const; @@ -158,13 +158,13 @@ export function PriorityChart({ issues }: { issues: { priority: string; createdA } const statusColors: Record = { - todo: "#3b82f6", - in_progress: "#8b5cf6", - in_review: "#a855f7", - done: "#10b981", - blocked: "#ef4444", - cancelled: "#6b7280", - backlog: "#64748b", + todo: "var(--chart-1)", + in_progress: "var(--chart-3)", + in_review: "var(--chart-4)", + done: "var(--chart-2)", + blocked: "var(--destructive)", + cancelled: "var(--muted-foreground)", + backlog: "var(--muted-foreground)", }; const statusLabels: Record = { @@ -208,7 +208,7 @@ export function IssueStatusChart({ issues }: { issues: { status: string; created {total > 0 ? (
{statusOrder.map(s => (entry[s] ?? 0) > 0 ? ( -
+
) : null)}
) : ( @@ -219,7 +219,7 @@ export function IssueStatusChart({ issues }: { issues: { status: string; created })}
- ({ color: statusColors[s] ?? "#6b7280", label: statusLabels[s] ?? s }))} /> + ({ color: statusColors[s] ?? "var(--muted-foreground)", label: statusLabels[s] ?? s }))} />
); } @@ -245,7 +245,7 @@ export function SuccessRateChart({ runs }: { runs: HeartbeatRun[] }) { {days.map(day => { const entry = grouped.get(day)!; const rate = entry.total > 0 ? entry.succeeded / entry.total : 0; - const color = entry.total === 0 ? undefined : rate >= 0.8 ? "#10b981" : rate >= 0.5 ? "#eab308" : "#ef4444"; + const color = entry.total === 0 ? undefined : rate >= 0.8 ? "var(--chart-2)" : rate >= 0.5 ? "var(--chart-4)" : "var(--destructive)"; return (
0 ? Math.round(rate * 100) : 0}% (${entry.succeeded}/${entry.total})`}> {entry.total > 0 ? ( diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index d52121c3..006995d3 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -550,7 +550,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { }} /> -
+
Prompt template is replayed on every heartbeat. Keep it compact and dynamic to avoid recurring token cost and cache churn.
@@ -687,7 +687,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { }} /> -
+
Prompt template is replayed on every heartbeat. Prefer small task framing and variables like {"{{ context.* }}"} or {"{{ run.* }}"}; avoid repeating stable instructions here.
@@ -786,7 +786,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { {adapterType === "codex_local" && codexSearchEnabled && currentThinkingEffort === "minimal" && ( -

+

Codex may reject `minimal` thinking when search is enabled.

)} @@ -813,7 +813,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { }} /> -
+
Bootstrap prompt is legacy and will be removed in a future release. Consider moving this content into the agent's prompt template or instructions file instead.
@@ -993,10 +993,10 @@ function AdapterEnvironmentResult({ result }: { result: AdapterEnvironmentTestRe result.status === "pass" ? "Passed" : result.status === "warn" ? "Warnings" : "Failed"; const statusClass = result.status === "pass" - ? "text-green-700 dark:text-green-300 border-green-300 dark:border-green-500/40 bg-green-50 dark:bg-green-500/10" + ? "text-success border-success/30 bg-success/10" : result.status === "warn" - ? "text-amber-700 dark:text-amber-300 border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10" - : "text-red-700 dark:text-red-300 border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10"; + ? "text-warning border-warning/30 bg-warning/10" + : "text-destructive border-destructive/30 bg-destructive/10"; return (
@@ -1504,7 +1504,7 @@ function ModelDropdown({ {value} - + current @@ -1523,7 +1523,7 @@ function ModelDropdown({ {detectedModel} - + detected diff --git a/ui/src/components/AgentProperties.tsx b/ui/src/components/AgentProperties.tsx index a89d8fb6..f76b66a5 100644 --- a/ui/src/components/AgentProperties.tsx +++ b/ui/src/components/AgentProperties.tsx @@ -78,7 +78,7 @@ export function AgentProperties({ agent, runtimeState }: AgentPropertiesProps) { )} {runtimeState?.lastError && ( - {runtimeState.lastError} + {runtimeState.lastError} )} {agent.lastHeartbeatAt && ( diff --git a/ui/src/components/ApprovalCard.tsx b/ui/src/components/ApprovalCard.tsx index 2123fc57..312242ac 100644 --- a/ui/src/components/ApprovalCard.tsx +++ b/ui/src/components/ApprovalCard.tsx @@ -7,10 +7,10 @@ import { timeAgo } from "../lib/timeAgo"; import type { Approval, Agent } from "@paperclipai/shared"; function statusIcon(status: string) { - if (status === "approved") return ; - if (status === "rejected") return ; - if (status === "revision_requested") return ; - if (status === "pending") return ; + if (status === "approved") return ; + if (status === "rejected") return ; + if (status === "revision_requested") return ; + if (status === "pending") return ; return null; } @@ -74,7 +74,7 @@ export function ApprovalCard({
{parsed !== null && parsed <= incident.amountObserved ? ( -

+

The new budget must exceed current observed spend.

) : null} diff --git a/ui/src/components/BudgetPolicyCard.tsx b/ui/src/components/BudgetPolicyCard.tsx index 7834e8cb..6abc5d1b 100644 --- a/ui/src/components/BudgetPolicyCard.tsx +++ b/ui/src/components/BudgetPolicyCard.tsx @@ -23,9 +23,9 @@ function windowLabel(windowKind: BudgetPolicySummary["windowKind"]) { } function statusTone(status: BudgetPolicySummary["status"]) { - if (status === "hard_stop") return "text-red-300 border-red-500/30 bg-red-500/10"; - if (status === "warning") return "text-amber-200 border-amber-500/30 bg-amber-500/10"; - return "text-emerald-200 border-emerald-500/30 bg-emerald-500/10"; + if (status === "hard_stop") return "text-destructive border-destructive/30 bg-destructive/10"; + if (status === "warning") return "text-warning border-warning/30 bg-warning/10"; + return "text-success border-success/30 bg-success/10"; } export function BudgetPolicyCard({ @@ -104,10 +104,10 @@ export function BudgetPolicyCard({ className={cn( "h-full rounded-full transition-[width,background-color] duration-200", summary.status === "hard_stop" - ? "bg-red-400" + ? "bg-destructive" : summary.status === "warning" - ? "bg-amber-300" - : "bg-emerald-300", + ? "bg-warning/15" + : "bg-success/15", )} style={{ width: `${progress}%` }} /> @@ -116,7 +116,7 @@ export function BudgetPolicyCard({ ); const pausedPane = summary.paused ? ( -
+
{summary.scopeType === "project" @@ -166,9 +166,9 @@ export function BudgetPolicyCard({ className={cn( "inline-flex items-center gap-2 text-[11px] uppercase tracking-[0.18em]", summary.status === "hard_stop" - ? "text-red-300" + ? "text-destructive" : summary.status === "warning" - ? "text-amber-200" + ? "text-warning" : "text-muted-foreground", )} > diff --git a/ui/src/components/BudgetSidebarMarker.tsx b/ui/src/components/BudgetSidebarMarker.tsx index 43f10b95..30d35ef7 100644 --- a/ui/src/components/BudgetSidebarMarker.tsx +++ b/ui/src/components/BudgetSidebarMarker.tsx @@ -5,7 +5,7 @@ export function BudgetSidebarMarker({ title = "Paused by budget" }: { title?: st diff --git a/ui/src/components/ChatMessageIdentityBar.tsx b/ui/src/components/ChatMessageIdentityBar.tsx index b076937b..011c5b2f 100644 --- a/ui/src/components/ChatMessageIdentityBar.tsx +++ b/ui/src/components/ChatMessageIdentityBar.tsx @@ -26,7 +26,7 @@ export function ChatMessageIdentityBar({ {agentName} {isStreaming && ( - + )} {timestamp && ( diff --git a/ui/src/components/ChatSearchDialog.tsx b/ui/src/components/ChatSearchDialog.tsx index af3d9e48..8eec4514 100644 --- a/ui/src/components/ChatSearchDialog.tsx +++ b/ui/src/components/ChatSearchDialog.tsx @@ -48,7 +48,7 @@ function HighlightedText({ text, query }: { text: string; query: string }) { <> {segments.map((seg, i) => seg.highlight ? ( - + {seg.text} ) : ( diff --git a/ui/src/components/ChatStatusUpdateBadge.tsx b/ui/src/components/ChatStatusUpdateBadge.tsx index ea197586..ed442540 100644 --- a/ui/src/components/ChatStatusUpdateBadge.tsx +++ b/ui/src/components/ChatStatusUpdateBadge.tsx @@ -21,7 +21,7 @@ export function ChatStatusUpdateBadge({ agentName, taskId, taskTitle, taskUrl }: )} role="status" > - + {agentName} completed {taskId} {displayTitle ? `: ${displayTitle}` : ""} diff --git a/ui/src/components/ClaudeSubscriptionPanel.tsx b/ui/src/components/ClaudeSubscriptionPanel.tsx index 3559a320..91d3a705 100644 --- a/ui/src/components/ClaudeSubscriptionPanel.tsx +++ b/ui/src/components/ClaudeSubscriptionPanel.tsx @@ -45,9 +45,9 @@ function orderedWindows(windows: QuotaWindow[]): QuotaWindow[] { } function fillClass(usedPercent: number | null): string { - if (usedPercent == null) return "bg-zinc-700"; - if (usedPercent >= 90) return "bg-red-400"; - if (usedPercent >= 70) return "bg-amber-400"; + if (usedPercent == null) return "bg-muted"; + if (usedPercent >= 90) return "bg-destructive"; + if (usedPercent >= 70) return "bg-warning"; return "bg-primary/70"; } diff --git a/ui/src/components/CodexSubscriptionPanel.tsx b/ui/src/components/CodexSubscriptionPanel.tsx index db1ac2e4..58ea0c20 100644 --- a/ui/src/components/CodexSubscriptionPanel.tsx +++ b/ui/src/components/CodexSubscriptionPanel.tsx @@ -41,9 +41,9 @@ function detailText(window: QuotaWindow): string | null { } function fillClass(usedPercent: number | null): string { - if (usedPercent == null) return "bg-zinc-700"; - if (usedPercent >= 90) return "bg-red-400"; - if (usedPercent >= 70) return "bg-amber-400"; + if (usedPercent == null) return "bg-muted"; + if (usedPercent >= 90) return "bg-destructive"; + if (usedPercent >= 70) return "bg-warning"; return "bg-primary/70"; } diff --git a/ui/src/components/CommentThread.tsx b/ui/src/components/CommentThread.tsx index b0b5c618..99a5ae40 100644 --- a/ui/src/components/CommentThread.tsx +++ b/ui/src/components/CommentThread.tsx @@ -183,17 +183,17 @@ function runTimestamp(run: LinkedRunItem) { function runStatusClass(status: string) { switch (status) { case "succeeded": - return "text-green-700 dark:text-green-300"; + return "text-success"; case "failed": case "error": - return "text-red-700 dark:text-red-300"; + return "text-destructive"; case "timed_out": - return "text-orange-700 dark:text-orange-300"; + return "text-warning"; case "running": - return "text-cyan-700 dark:text-cyan-300"; + return "text-primary"; case "queued": case "pending": - return "text-amber-700 dark:text-amber-300"; + return "text-warning"; case "cancelled": return "text-muted-foreground"; default: @@ -258,7 +258,7 @@ function CommentCard({ id={`comment-${comment.id}`} className={`border p-3 overflow-hidden min-w-0 rounded-sm transition-colors duration-1000 ${ isQueued - ? "border-amber-300/70 bg-amber-50/70 dark:border-amber-500/40 dark:bg-amber-500/10" + ? "border-warning/70 bg-warning/70" : isHighlighted ? "border-primary/50 bg-primary/5" : "border-border" @@ -277,7 +277,7 @@ function CommentCard({ )} {isQueued ? ( - + Queued ) : null} @@ -765,14 +765,14 @@ export function CommentThread({ {queuedComments.length > 0 && (
-

+

Queued Comments ({queuedComments.length})

{onInterruptQueued && queuedComments[0]?.queueTargetRunId ? (
diff --git a/ui/src/components/CompanySwitcher.tsx b/ui/src/components/CompanySwitcher.tsx index c178db0a..f9c6c3c0 100644 --- a/ui/src/components/CompanySwitcher.tsx +++ b/ui/src/components/CompanySwitcher.tsx @@ -15,13 +15,13 @@ import { Button } from "@/components/ui/button"; function statusDotColor(status?: string): string { switch (status) { case "active": - return "bg-green-400"; + return "bg-success"; case "paused": - return "bg-yellow-400"; + return "bg-warning"; case "archived": - return "bg-neutral-400"; + return "bg-muted"; default: - return "bg-green-400"; + return "bg-success"; } } diff --git a/ui/src/components/DevRestartBanner.tsx b/ui/src/components/DevRestartBanner.tsx index 2ff666d9..38d8961b 100644 --- a/ui/src/components/DevRestartBanner.tsx +++ b/ui/src/components/DevRestartBanner.tsx @@ -33,14 +33,14 @@ export function DevRestartBanner({ devServer }: { devServer?: DevServerHealthSta const sample = devServer.changedPathsSample.slice(0, 3); return ( -
+
Restart Required {devServer.autoRestartEnabled ? ( - + Auto-Restart On ) : null} @@ -49,7 +49,7 @@ export function DevRestartBanner({ devServer }: { devServer?: DevServerHealthSta {describeReason(devServer)} {changedAt ? ` · updated ${changedAt}` : ""}

-
+
{sample.length > 0 ? ( Changed: {sample.join(", ")} @@ -67,17 +67,17 @@ export function DevRestartBanner({ devServer }: { devServer?: DevServerHealthSta
{devServer.waitingForIdle ? ( -
+
Waiting for {devServer.activeRunCount} live run{devServer.activeRunCount === 1 ? "" : "s"} to finish
) : devServer.autoRestartEnabled ? ( -
+
Auto-restart will trigger when the instance is idle
) : ( -
+
Restart pnpm dev:once after the active work is safe to interrupt
diff --git a/ui/src/components/ExecutionWorkspaceCloseDialog.tsx b/ui/src/components/ExecutionWorkspaceCloseDialog.tsx index f0547684..52385e5a 100644 --- a/ui/src/components/ExecutionWorkspaceCloseDialog.tsx +++ b/ui/src/components/ExecutionWorkspaceCloseDialog.tsx @@ -30,9 +30,9 @@ function readinessTone(state: "ready" | "ready_with_warnings" | "blocked") { return "border-destructive/30 bg-destructive/5 text-destructive"; } if (state === "ready_with_warnings") { - return "border-amber-500/30 bg-amber-500/10 text-amber-800 dark:text-amber-300"; + return "border-warning/30 bg-warning/10 text-warning"; } - return "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"; + return "border-success/30 bg-success/10 text-success"; } export function ExecutionWorkspaceCloseDialog({ @@ -163,7 +163,7 @@ export function ExecutionWorkspaceCloseDialog({

Warnings

    {readiness.warnings.map((warning, idx) => ( -
  • +
  • {warning}
  • ))} @@ -262,7 +262,7 @@ export function ExecutionWorkspaceCloseDialog({ {currentStatus === "cleanup_failed" ? ( -
    +
    Cleanup previously failed on this workspace. Retrying close will rerun the cleanup flow and update the workspace status if it succeeds.
    diff --git a/ui/src/components/FinanceTimelineCard.tsx b/ui/src/components/FinanceTimelineCard.tsx index 1fa516ba..4af8a725 100644 --- a/ui/src/components/FinanceTimelineCard.tsx +++ b/ui/src/components/FinanceTimelineCard.tsx @@ -59,7 +59,7 @@ export function FinanceTimelineCard({
    {formatCents(row.amountCents)}
    {row.currency}
    - {row.estimated ?
    estimated
    : null} + {row.estimated ?
    estimated
    : null}
diff --git a/ui/src/components/IssueDocumentsSection.tsx b/ui/src/components/IssueDocumentsSection.tsx index 31a6dd46..d05eaa9b 100644 --- a/ui/src/components/IssueDocumentsSection.tsx +++ b/ui/src/components/IssueDocumentsSection.tsx @@ -764,13 +764,13 @@ export function IssueDocumentsSection({
- - + + PLAN
@@ -834,7 +834,7 @@ export function IssueDocumentsSection({ size="sm" className={cn( "h-auto px-1.5 py-0 text-[11px] font-normal text-muted-foreground hover:text-foreground", - isHistoricalPreview && "text-amber-300 hover:text-amber-200", + isHistoricalPreview && "text-warning hover:text-warning", )} > rev {displayedRevisionNumber} @@ -963,10 +963,10 @@ export function IssueDocumentsSection({ : undefined} > {isHistoricalPreview && selectedHistoricalRevision && ( -
+
-

+

Viewing revision {selectedHistoricalRevision.revisionNumber}

@@ -998,10 +998,10 @@ export function IssueDocumentsSection({

)} {activeConflict && !isHistoricalPreview && ( -
+
-

Out of date

+

Out of date

This document changed while you were editing. Your local draft is preserved and autosave is paused.

@@ -1074,7 +1074,7 @@ export function IssueDocumentsSection({ }`} > {isHistoricalPreview ? ( -
+
{renderBody(displayedBody, documentBodyContentClassName)}
) : activeDraft ? ( @@ -1107,9 +1107,9 @@ export function IssueDocumentsSection({ p.id === issue.projectId)?.color ?? "#6366f1" }} + style={{ backgroundColor: orderedProjects.find((p) => p.id === issue.projectId)?.color ?? "var(--primary)" }} /> {projectName(issue.projectId)} @@ -479,7 +479,7 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp > {p.name} diff --git a/ui/src/components/IssueRow.tsx b/ui/src/components/IssueRow.tsx index 8a01e585..3a28a2fc 100644 --- a/ui/src/components/IssueRow.tsx +++ b/ui/src/components/IssueRow.tsx @@ -116,14 +116,14 @@ export function IssueRow({ }} className={cn( "inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors", - selected ? "hover:bg-muted/80" : "hover:bg-blue-500/20", + selected ? "hover:bg-muted/80" : "hover:bg-primary/20", )} aria-label="Mark as read" > diff --git a/ui/src/components/IssueWorkspaceCard.tsx b/ui/src/components/IssueWorkspaceCard.tsx index 6637c9c3..474227aa 100644 --- a/ui/src/components/IssueWorkspaceCard.tsx +++ b/ui/src/components/IssueWorkspaceCard.tsx @@ -87,7 +87,7 @@ function CopyableInline({ value, label, mono }: { value: string; label?: string; onClick={handleCopy} title={copied ? "Copied!" : "Copy"} > - {copied ? : } + {copied ? : } ); @@ -144,9 +144,9 @@ function workspaceDetailLink(input: { function statusBadge(status: string) { const colors: Record = { - active: "bg-green-500/15 text-green-700 dark:text-green-400", + active: "bg-success/15 text-success", idle: "bg-muted text-muted-foreground", - in_review: "bg-blue-500/15 text-blue-700 dark:text-blue-400", + in_review: "bg-primary/15 text-primary", archived: "bg-muted text-muted-foreground", }; return ( diff --git a/ui/src/components/IssuesList.tsx b/ui/src/components/IssuesList.tsx index 38d5f00e..3535b9fb 100644 --- a/ui/src/components/IssuesList.tsx +++ b/ui/src/components/IssuesList.tsx @@ -400,7 +400,7 @@ export function IssuesList({ {/* Filter */} -
diff --git a/ui/src/components/LiveRunWidget.tsx b/ui/src/components/LiveRunWidget.tsx index 2c0f702e..2bf262d5 100644 --- a/ui/src/components/LiveRunWidget.tsx +++ b/ui/src/components/LiveRunWidget.tsx @@ -87,9 +87,9 @@ export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) { if (runs.length === 0) return null; return ( -
-
-
+
+
+
Live Runs
@@ -111,7 +111,7 @@ export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) {
{run.id.slice(0, 8)} @@ -125,7 +125,7 @@ export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) { - -
- - )} - - {/* Step 2 — Mode Selection */} - {step === 2 && ( - <> -
-

- Choose your mode -

-
- - - -
- - - -
- - )} - - {/* Step 3 — Provider Selection (NEW) */} - {step === 3 && ( - <> -
-

- Choose a provider -

-

- No API keys needed for the zero-config path. -

-
- - setApiKeyData({ provider, apiKey })} - onSkip={() => setStep(4)} - onContinue={() => setStep(4)} - detectedAdapters={detectedAdapters} - /> - - - - )} - - {/* Step 4 — Voice */} - {step === 4 && ( - <> -
-

- Voice features -

-

- Speak to your assistant and hear responses read aloud. Runs entirely on your device. -

-
- - { - setVoiceEnabled(true); - setStep(5); + onClick={() => { + if (isCompleted) setStep(s.id); }} - onSkip={() => setStep(5)} - voiceCapability={hardwareInfo?.voiceCapability} - /> - - - - )} - - {/* Step 5 — Telegram Bridge */} - {step === 5 && ( - setStep(6)} - onBack={() => setStep(4)} - /> - )} - - {/* Step 6 — Root Directory (was step 5, now step 6) */} - {step === 6 && ( - <> - {/* Header */} -
-

- Welcome to {VOCAB.appName} -

-

- {defaultAdapter === "hermes_local" - ? `${VOCAB.appName} will set up a local AI workspace with a ${VOCAB.ceo.toLowerCase()}, engineer, and generalist — no API key needed.` - : `Choose a project root directory. ${VOCAB.appName} will set up a ${VOCAB.ceo.toLowerCase()} and engineer to start working.`} -

-
- - {/* Form */} -
-
- - setRootDir(e.target.value)} - disabled={loading} - autoFocus - autoComplete="off" - className="font-mono text-sm" - /> -
- - {error && ( -

- {error} -

+ disabled={isUpcoming} + className={cn( + "flex items-center gap-3 px-3 py-2.5 text-sm rounded-lg transition-colors text-left", + isCurrent && "bg-primary/10 text-primary font-medium", + isCompleted && "text-foreground hover:bg-muted/50 cursor-pointer", + isUpcoming && "text-muted-foreground/50 cursor-default" )} + > + {isCompleted ? ( + + ) : ( + + )} + {s.name} + + ); + })} + + - - - - - -
- - )} - - {/* Step 7 — Summary (was step 6) */} - {step === 7 && ( - setStep(6)} + {/* Mobile step indicator - horizontal bar at top */} +
+
+ Nexus Setup + Step {step} of 6 +
+
+ {STEPS.map((s) => ( +
step && "bg-muted" + )} /> - )} + ))}
- + + {/* Right content area */} +
+
+
+ {/* Step heading */} +

{currentStep.title}

+

{currentStep.description}

+ + {/* Step content */} + {renderStepContent()} + + {/* Shared navigation footer — only for steps 1 and 2 */} + {!stepHasOwnNav && ( +
+ {step > 1 ? ( + + ) : ( +
+ )} + +
+ )} +
+
+
+
); } diff --git a/ui/src/components/OfflineBanner.tsx b/ui/src/components/OfflineBanner.tsx index 516d3d94..7ea9cd67 100644 --- a/ui/src/components/OfflineBanner.tsx +++ b/ui/src/components/OfflineBanner.tsx @@ -35,7 +35,7 @@ export function OfflineBanner({ queuedCount = 0 }: OfflineBannerProps) { return (
diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx index e4be302b..657b474c 100644 --- a/ui/src/components/OnboardingWizard.tsx +++ b/ui/src/components/OnboardingWizard.tsx @@ -796,7 +796,7 @@ export function OnboardingWizard() { }} > {opt.recommended && ( - + Recommended )} @@ -1050,7 +1050,7 @@ export function OnboardingWizard() { {adapterEnvResult && adapterEnvResult.status === "pass" ? ( -
+
Passed
@@ -1059,8 +1059,8 @@ export function OnboardingWizard() { ) : null} {shouldSuggestUnsetAnthropicApiKey && ( -
-

+

+

Claude failed while{" "} ANTHROPIC_API_KEY{" "} is set. You can clear it in this {VOCAB.ceo} adapter config @@ -1224,7 +1224,7 @@ export function OnboardingWizard() {

{VOCAB.company}

- +
@@ -1236,7 +1236,7 @@ export function OnboardingWizard() { {getUIAdapter(adapterType).label}

- +
@@ -1246,7 +1246,7 @@ export function OnboardingWizard() {

Task

- +
@@ -1337,7 +1337,7 @@ export function OnboardingWizard() { {/* Right half — ASCII art (hidden on mobile) */}
@@ -1362,10 +1362,10 @@ function AdapterEnvironmentResult({ : "Failed"; const statusClass = result.status === "pass" - ? "text-green-700 dark:text-green-300 border-green-300 dark:border-green-500/40 bg-green-50 dark:bg-green-500/10" + ? "text-success border-success/30 bg-success/10" : result.status === "warn" - ? "text-amber-700 dark:text-amber-300 border-amber-300 dark:border-amber-500/40 bg-amber-50 dark:bg-amber-500/10" - : "text-red-700 dark:text-red-300 border-red-300 dark:border-red-500/40 bg-red-50 dark:bg-red-500/10"; + ? "text-warning border-warning/30 bg-warning/10" + : "text-destructive border-destructive/30 bg-destructive/10"; return (
diff --git a/ui/src/components/OutputFeedbackButtons.tsx b/ui/src/components/OutputFeedbackButtons.tsx index efcf62eb..7d210dbd 100644 --- a/ui/src/components/OutputFeedbackButtons.tsx +++ b/ui/src/components/OutputFeedbackButtons.tsx @@ -115,7 +115,7 @@ export function OutputFeedbackButtons({ size="sm" variant="outline" disabled={disabled || isSaving} - className={cn(visibleVote === "up" && "border-green-600/50 bg-green-500/10 text-green-700")} + className={cn(visibleVote === "up" && "border-success/50 bg-success/10 text-success")} onClick={() => handleVote("up")} > @@ -126,7 +126,7 @@ export function OutputFeedbackButtons({ size="sm" variant="outline" disabled={disabled || isSaving} - className={cn(visibleVote === "down" && "border-amber-600/50 bg-amber-500/10 text-amber-800")} + className={cn(visibleVote === "down" && "border-warning/50 bg-warning/10 text-warning")} onClick={() => handleVote("down")} > diff --git a/ui/src/components/ProjectProperties.tsx b/ui/src/components/ProjectProperties.tsx index b25fb7e3..81169a2d 100644 --- a/ui/src/components/ProjectProperties.tsx +++ b/ui/src/components/ProjectProperties.tsx @@ -62,7 +62,7 @@ function SaveIndicator({ state }: { state: ProjectFieldSaveState }) { } if (state === "saved") { return ( - + Saved @@ -738,9 +738,9 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa className={cn( "rounded-full px-1.5 py-0.5 text-[10px] uppercase tracking-wide", service.status === "running" - ? "bg-green-500/15 text-green-700 dark:text-green-300" + ? "bg-success/15 text-success" : service.status === "failed" - ? "bg-red-500/15 text-red-700 dark:text-red-300" + ? "bg-destructive/15 text-destructive" : "bg-muted text-muted-foreground", )} > @@ -891,7 +891,7 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa data-slot="toggle" className={cn( "relative inline-flex h-5 w-9 items-center rounded-full transition-colors", - executionWorkspacesEnabled ? "bg-green-600" : "bg-muted", + executionWorkspacesEnabled ? "bg-success" : "bg-muted", )} type="button" onClick={() => @@ -930,7 +930,7 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa data-slot="toggle" className={cn( "relative inline-flex h-5 w-9 items-center rounded-full transition-colors", - executionWorkspaceDefaultMode === "isolated_workspace" ? "bg-green-600" : "bg-muted", + executionWorkspaceDefaultMode === "isolated_workspace" ? "bg-success" : "bg-muted", )} type="button" onClick={() => diff --git a/ui/src/components/ProviderQuotaCard.tsx b/ui/src/components/ProviderQuotaCard.tsx index 21800bc1..918e6069 100644 --- a/ui/src/components/ProviderQuotaCard.tsx +++ b/ui/src/components/ProviderQuotaCard.tsx @@ -338,10 +338,10 @@ export function ProviderQuotaCard({ qw.usedPercent == null ? null : qw.usedPercent >= 90 - ? "bg-red-400" + ? "bg-destructive" : qw.usedPercent >= 70 - ? "bg-yellow-400" - : "bg-green-400"; + ? "bg-warning" + : "bg-success"; return (
diff --git a/ui/src/components/QuotaBar.tsx b/ui/src/components/QuotaBar.tsx index 89c25c6e..87dfc967 100644 --- a/ui/src/components/QuotaBar.tsx +++ b/ui/src/components/QuotaBar.tsx @@ -12,9 +12,9 @@ interface QuotaBarProps { } function fillColor(pct: number): string { - if (pct > 90) return "bg-red-400"; - if (pct > 70) return "bg-yellow-400"; - return "bg-green-400"; + if (pct > 90) return "bg-destructive"; + if (pct > 70) return "bg-warning"; + return "bg-success"; } export function QuotaBar({ diff --git a/ui/src/components/ReportsToPicker.tsx b/ui/src/components/ReportsToPicker.tsx index 75924dc6..48833e2f 100644 --- a/ui/src/components/ReportsToPicker.tsx +++ b/ui/src/components/ReportsToPicker.tsx @@ -45,7 +45,7 @@ export function ReportsToPicker({ type="button" className={cn( "inline-flex max-w-full min-w-0 items-center gap-1.5 overflow-hidden rounded-md border border-border px-2 py-1 text-xs hover:bg-accent/50 transition-colors", - terminatedManager && "border-amber-600/45 bg-amber-500/5", + terminatedManager && "border-warning/45 bg-warning/5", disabled && "opacity-60 cursor-not-allowed", )} disabled={disabled} @@ -61,7 +61,7 @@ export function ReportsToPicker({ {`Reports to ${current.name}${terminatedManager ? " (terminated)" : ""}`} diff --git a/ui/src/components/RoutineRunVariablesDialog.tsx b/ui/src/components/RoutineRunVariablesDialog.tsx index 5521c738..bf67830c 100644 --- a/ui/src/components/RoutineRunVariablesDialog.tsx +++ b/ui/src/components/RoutineRunVariablesDialog.tsx @@ -274,11 +274,11 @@ export function RoutineRunVariablesDialog({ {missingRequired.length > 0 ? ( -

+

Missing: {missingRequired.join(", ")}

) : workspaceSelectionEnabled && !workspaceConfigValid ? ( -

+

Choose an existing workspace before running.

) : ( diff --git a/ui/src/components/SidebarAgents.tsx b/ui/src/components/SidebarAgents.tsx index 0b7c7d95..a3efdbf2 100644 --- a/ui/src/components/SidebarAgents.tsx +++ b/ui/src/components/SidebarAgents.tsx @@ -124,12 +124,12 @@ export function SidebarAgents() { ) : null} {runCount > 0 ? ( - - + + ) : null} {runCount > 0 ? ( - + {runCount} live ) : null} diff --git a/ui/src/components/SidebarNavItem.tsx b/ui/src/components/SidebarNavItem.tsx index e0cd7f6f..15035046 100644 --- a/ui/src/components/SidebarNavItem.tsx +++ b/ui/src/components/SidebarNavItem.tsx @@ -50,7 +50,7 @@ export function SidebarNavItem({ {alert && ( - + )} {label} @@ -59,7 +59,7 @@ export function SidebarNavItem({ className={cn( "ml-auto rounded-full px-1.5 py-0.5 text-[10px] font-medium leading-none", textBadgeTone === "amber" - ? "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400" + ? "bg-warning/10 text-warning" : "bg-muted text-muted-foreground", )} > @@ -69,10 +69,10 @@ export function SidebarNavItem({ {liveCount != null && liveCount > 0 && ( - - + + - {liveCount} live + {liveCount} live )} {badge != null && badge > 0 && ( @@ -80,7 +80,7 @@ export function SidebarNavItem({ className={cn( "ml-auto rounded-full px-1.5 py-0.5 text-xs leading-none", badgeTone === "danger" - ? "bg-red-600/90 text-red-50" + ? "bg-destructive/90 text-destructive" : "bg-primary text-primary-foreground", )} > diff --git a/ui/src/components/SidebarProjects.tsx b/ui/src/components/SidebarProjects.tsx index 3c63ebc3..0fb8ab6f 100644 --- a/ui/src/components/SidebarProjects.tsx +++ b/ui/src/components/SidebarProjects.tsx @@ -86,7 +86,7 @@ function SortableProjectItem({ > {project.name} {project.pauseReason === "budget" ? : null} diff --git a/ui/src/components/SkillCard.tsx b/ui/src/components/SkillCard.tsx index f549e253..c9f79839 100644 --- a/ui/src/components/SkillCard.tsx +++ b/ui/src/components/SkillCard.tsx @@ -65,7 +65,7 @@ export function SkillCard({ {hasUpdate && !isReadOnly && ( Update @@ -91,7 +91,7 @@ export function SkillCard({ {skill.sourceId} {skill.averageRating != null && ( - + {skill.averageRating.toFixed(1)} )} diff --git a/ui/src/components/StarRating.tsx b/ui/src/components/StarRating.tsx index 1a829eaa..0ba20332 100644 --- a/ui/src/components/StarRating.tsx +++ b/ui/src/components/StarRating.tsx @@ -41,7 +41,7 @@ export function StarRating({ diff --git a/ui/src/components/SwipeToArchive.tsx b/ui/src/components/SwipeToArchive.tsx index 639179c3..95d2bc75 100644 --- a/ui/src/components/SwipeToArchive.tsx +++ b/ui/src/components/SwipeToArchive.tsx @@ -141,7 +141,7 @@ export function SwipeToArchive({ >