feat(ui): gallery feedback round 2 — incident-card legibility, bar-fill stragglers, cyan liveness sweep

Three user rulings from the tune-session gallery (intentional visible
changes; snapshots NOT re-baselined):

A. BudgetIncidentCard hard-stop card was unreadable in LIGHT mode
   (pre-existing: dark-tuned red-50/100/200 text over the light pink
   gradient, no light variants). Eyebrow/title/description/banner/icon/
   validation now use red-600..950 light text with the original dark
   classes behind dark:; same treatment for sibling BudgetPolicyCard
   statusTone chips + red banner. B1 gradient tokens untouched.
B. Remaining bar-fill stragglers -> canonical status hues:
   ClaudeSubscriptionPanel fillClass and ProviderQuotaCard quota-window
   fills (red-400 -> bg-(--status-task-blocked), amber/yellow-400 ->
   bg-(--status-task-todo), green-400 -> bg-(--status-task-done)).
   BudgetSidebarMarker is an icon badge, not a bar — left.
C. Systematic cyan->status-blue liveness sweep (~50 sites / 20 files):
   running tones, live dots/pings, Live surfaces (LiveRunWidget,
   ActiveAgentsPanel Live-now box, AgentDetail live card, IssueDetail
   Live pill), scheduled-retry family, externalObjectStatus running
   maps, timeline now-marker #2dd4bf -> #2563eb, and liveness glow
   shadow token VALUES --shadow-extract-1/11/14 rgba(6,182,212,0.08) ->
   rgba(37,99,235,0.08) (sites unchanged). Non-liveness cyan left and
   documented (xterm cursor, on_demand chips, assets chip, import
   rename text, recovery_required, ledger Advanced/snoozed, UxLab).

DECISION-SHEET.md: 'Gallery feedback round 2' section. Gates 3/3 CLEAN,
typecheck green, ui build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
scotttong 2026-07-06 18:37:50 -07:00
parent 59e536fc08
commit e2c6f4b4b7
23 changed files with 79 additions and 72 deletions

View File

@ -46,6 +46,13 @@ User rulings from the tune-session gallery review; all intentionally visible, sn
2. **Budget/quota BAR FILLS reuse status colors** — moving-fill elements only: healthy → `bg-(--status-task-done)`, warning → `bg-(--status-task-todo)`, exceeded/hard-stop → `bg-(--status-task-blocked)` in BudgetPolicyCard.tsx, QuotaBar.tsx (feeds ProviderQuotaCard/BillerSpendCard pages), Costs.tsx, CodexSubscriptionPanel.tsx (escalation tiers only). Inspected and deliberately LEFT: Org.tsx status dot (not a bar), BudgetPolicyCard chip washes/notice borders (not fills), CodexSubscriptionPanel healthy `bg-primary/70` + null `bg-zinc-700` (healthy tier uses brand primary by design — flagged as ambiguous, not emerald).
3. **RUNNING = status blue, not cyan/teal** — IssueChatThread running chip now composes `brandChipBadge.blue` (layout classes unchanged); RunTranscriptView running label uses new `runningLabelText` export (`text-[#1D4ED8] dark:text-[#2563EB]`, hexes kept in lib/status-colors.ts for gate cleanliness); `statusBadge.running` + `agentStatusDot.running` maps and AgentDetail `runStatusIcons.running` re-pointed cyan→blue. Deliberately LEFT + flagged: `externalObjectStatusIcon/Badge.running` (same-map collision — `open` is already blue there; documented UX-spec tone system), and the cyan "Live" branding family (LiveRunWidget theme, AgentDetail live-card border + Live pulse dots, DesignGuide Live sample) — "Live" is a distinct motif from RUNNING chips; note AgentDetail's mobile Live pill is already blue, so a dedicated Live-color decision is recommended.
## Gallery feedback round 2 (preset-tune session, Jul 6) — executed
1. **BudgetIncidentCard light-mode legibility (pre-existing bug)** — the hard-stop card's eyebrow/title/description/banner used dark-tuned red-50/100/200 text with no light variants over the light pink gradient. Light mode now uses red-600..950-tier text (matching the app's existing `text-red-700 dark:text-red-300` light-red-surface pattern); dark classes preserved verbatim behind `dark:`. Sibling fix: BudgetPolicyCard statusTone chips (hard_stop/warning/ok) + its red banner had the same dark-only text — same treatment. Gradient backgrounds (kept B1 tokens) untouched.
2. **Bar fills, remaining stragglers → status hues** — ClaudeSubscriptionPanel fillClass ("Current week Opus only" salmon red) and ProviderQuotaCard quota-window fills: red-400→`bg-(--status-task-blocked)`, amber/yellow-400→`bg-(--status-task-todo)`, green-400→`bg-(--status-task-done)`. Healthy `bg-primary/70` + null `bg-zinc-700` tiers unchanged (r1 ruling). Inspected, NOT a bar: BudgetSidebarMarker circular icon badge (left).
3. **Systematic cyan→status-blue liveness sweep** (~50 sites / 20 files; supersedes r1's "Live family left" note per user ruling): running-status tones (CommentThread, interrupt-handoff, runRetryState, AgentDetail run chip + status maps), live dots/pings (AgentDetail, OnboardingChat, ArtifactsPanel generating, ActiveAgentsPanel, IssueDetail Live pill, RunTranscriptView, IssueRunLedger live chip, DesignGuide sample), Live surfaces (LiveRunWidget theme, ActiveAgentsPanel Live-now box, AgentDetail live-card border), scheduled-retry family (IssueScheduledRetryCard, runRetryState, IssueRunLedger retry-pending), externalObjectStatus icon/badge `running` (now shares blue with `open`; liveness pulse differentiates — flagged), timeline "now" marker `#2dd4bf`→`#2563eb` (1.5px line vs `#5b9bf6` delegated bars, shape differentiates — flagged), and the liveness glow shadow tokens `--shadow-extract-1/11/14` value-edited `rgba(6,182,212,0.08)`→`rgba(37,99,235,0.08)` (kept-token VALUES changed, call sites unchanged).
Deliberately LEFT (non-liveness cyan, one-line reasons): xterm terminal cursor (CompanyEnvironments — terminal chrome); `on_demand` invocation-source chips (AgentDetail x2 + DesignGuide sample — source tag, not liveness); CompanySkills "Includes assets" chip (content-type tag); CompanyImport renamed-file mono text x2 (rename annotation); BlockedReasonChip `recovery_required` (blocked-reason category); IssueRunLedger "Advanced" outcome + "Silence snoozed" tones (outcome/pause semantics, not live); UxLab decorative gradients (B1 allowlisted decoration).
## Verification status (this review)
- `pnpm check:token-gates` — re-run independently: 3/3 CLEAN (468 files, 31 allowlist entries).

View File

@ -172,7 +172,7 @@ const AgentRunCard = memo(function AgentRunCard({
<div className={cn(
"flex h-(--sz-320px) flex-col overflow-hidden rounded-xl border shadow-sm",
isActive
? "border-cyan-500/25 bg-cyan-500/[0.04] shadow-(--shadow-extract-1)"
? "border-blue-500/25 bg-blue-500/[0.04] shadow-(--shadow-extract-1)"
: "border-border bg-background/70",
className,
)}>
@ -182,8 +182,8 @@ const AgentRunCard = memo(function AgentRunCard({
<div className="flex items-center gap-2">
{isActive ? (
<span className="relative flex h-2.5 w-2.5 shrink-0">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-cyan-400 opacity-70" />
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-cyan-500" />
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-70" />
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-blue-500" />
</span>
) : (
<span className="inline-flex h-2.5 w-2.5 rounded-full bg-muted-foreground/35" />
@ -209,7 +209,7 @@ const AgentRunCard = memo(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-blue-700 dark:text-blue-300" : "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)}
>

View File

@ -193,8 +193,8 @@ export function ArtifactsPanel({ taskId, isAgentWorking, openDocKey, openDocTitl
{showGenerating ? (
<div className="mt-0.5 shrink-0">
<span className="relative flex h-4 w-4">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75" />
<span className="relative inline-flex rounded-full h-4 w-4 bg-cyan-500" />
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" />
<span className="relative inline-flex rounded-full h-4 w-4 bg-blue-500" />
</span>
</div>
) : (
@ -212,7 +212,7 @@ export function ArtifactsPanel({ taskId, isAgentWorking, openDocKey, openDocTitl
{wp.type.replace(/_/g, " ")}
</span>
{showGenerating ? (
<span className="inline-flex items-center gap-1 text-(length:--text-nano) font-medium px-1.5 py-0.5 rounded-full bg-cyan-500/10 text-cyan-600 dark:text-cyan-400">
<span className="inline-flex items-center gap-1 text-(length:--text-nano) font-medium px-1.5 py-0.5 rounded-full bg-blue-500/10 text-blue-600 dark:text-blue-400">
<Loader2 className="h-2.5 w-2.5 animate-spin" />
Generating...
</span>

View File

@ -48,25 +48,25 @@ export function BudgetIncidentCard({
<div className="flex items-start justify-between gap-3">
<div>
<div className="flex flex-wrap items-center gap-2">
<div className="text-(length:--text-micro) uppercase tracking-(--tracking-caps) text-red-200/80">
<div className="text-(length:--text-micro) uppercase tracking-(--tracking-caps) text-red-700/90 dark:text-red-200/80">
{incident.scopeType} hard stop
</div>
<Badge variant={incident.status === "resolved" ? "outline" : "secondary"}>
{stateLabel}
</Badge>
</div>
<CardTitle className="mt-1 text-base text-red-50">{incident.scopeName}</CardTitle>
<CardDescription className="mt-1 text-red-100/70">
<CardTitle className="mt-1 text-base text-red-950 dark:text-red-50">{incident.scopeName}</CardTitle>
<CardDescription className="mt-1 text-red-900/75 dark:text-red-100/70">
Spending reached {formatCents(incident.amountObserved)} against a limit of {formatCents(incident.amountLimit)}.
</CardDescription>
</div>
<div className="rounded-full border border-red-400/30 bg-red-500/10 p-2 text-red-200">
<div className="rounded-full border border-red-400/30 bg-red-500/10 p-2 text-red-600 dark:text-red-200">
<AlertOctagon className="h-4 w-4" />
</div>
</div>
</CardHeader>
<CardContent className="space-y-4 px-5 pb-5 pt-0">
<div className="flex items-start gap-2 rounded-xl border border-red-400/20 bg-red-500/10 px-3 py-2 text-sm text-red-50/90">
<div className="flex items-start gap-2 rounded-xl border border-red-400/20 bg-red-500/10 px-3 py-2 text-sm text-red-950/90 dark:text-red-50/90">
<PauseCircle className="mt-0.5 h-4 w-4 shrink-0" />
<div>
{incident.scopeType === "project"
@ -98,7 +98,7 @@ export function BudgetIncidentCard({
</Button>
</div>
{parsed !== null && parsed <= incident.amountObserved ? (
<p className="mt-2 text-xs text-red-200/80">
<p className="mt-2 text-xs text-red-700 dark:text-red-200/80">
The new budget must exceed current observed spend.
</p>
) : null}

View File

@ -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-red-700 dark:text-red-300 border-red-500/30 bg-red-500/10";
if (status === "warning") return "text-amber-700 dark:text-amber-200 border-amber-500/30 bg-amber-500/10";
return "text-emerald-700 dark:text-emerald-200 border-emerald-500/30 bg-emerald-500/10";
}
export function BudgetPolicyCard({
@ -116,7 +116,7 @@ export function BudgetPolicyCard({
);
const pausedPane = summary.paused ? (
<div className="flex items-start gap-2 rounded-xl border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-100">
<div className="flex items-start gap-2 rounded-xl border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-900 dark:text-red-100">
<PauseCircle className="mt-0.5 h-4 w-4 shrink-0" />
<div>
{summary.scopeType === "project"

View File

@ -46,8 +46,8 @@ 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 >= 90) return "bg-(--status-task-blocked)";
if (usedPercent >= 70) return "bg-(--status-task-todo)";
return "bg-primary/70";
}

View File

@ -233,7 +233,7 @@ function runStatusClass(status: string) {
case "timed_out":
return "text-orange-700 dark:text-orange-300";
case "running":
return "text-cyan-700 dark:text-cyan-300";
return "text-blue-700 dark:text-blue-300";
case "queued":
case "pending":
return "text-amber-700 dark:text-amber-300";

View File

@ -177,7 +177,7 @@ function getIconSpec(
// Heartbeat — animated when active, static otherwise
if (action.startsWith("heartbeat.")) {
if (isActive && action === "heartbeat.invoked") {
return { kind: "lucide", Icon: Loader2, color: "text-cyan-600 dark:text-cyan-400", spin: true };
return { kind: "lucide", Icon: Loader2, color: "text-blue-600 dark:text-blue-400", spin: true };
}
return { kind: "lucide", Icon: Loader2, color: "text-muted-foreground" };
}

View File

@ -121,7 +121,7 @@ const PENDING_LIVENESS_COPY: LivenessCopy = {
const RETRY_PENDING_LIVENESS_COPY: LivenessCopy = {
label: "Retry pending",
tone: "border-cyan-500/30 bg-cyan-500/10 text-cyan-700 dark:text-cyan-300",
tone: "border-blue-500/30 bg-blue-500/10 text-blue-700 dark:text-blue-300",
description: "Paperclip queued an automatic retry that has not started yet.",
};
@ -734,8 +734,8 @@ export function IssueRunLedgerContent({
{statusLabel(run.status)}
</span>
{run.isLive ? (
<span className="inline-flex items-center gap-1 rounded-md border border-cyan-500/30 bg-cyan-500/10 px-1.5 py-0.5 text-(length:--text-micro) text-cyan-700 dark:text-cyan-300">
<span className="h-1.5 w-1.5 rounded-full bg-cyan-400" />
<span className="inline-flex items-center gap-1 rounded-md border border-blue-500/30 bg-blue-500/10 px-1.5 py-0.5 text-(length:--text-micro) text-blue-700 dark:text-blue-300">
<span className="h-1.5 w-1.5 rounded-full bg-blue-500" />
live
</span>
) : null}

View File

@ -69,12 +69,12 @@ export function IssueScheduledRetryCard({
return (
<div
data-testid="issue-scheduled-retry-card"
className="mb-3 rounded-lg border border-cyan-500/30 bg-cyan-500/5 px-3 py-3"
className="mb-3 rounded-lg border border-blue-500/30 bg-blue-500/5 px-3 py-3"
>
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2 text-xs">
<span className="inline-flex items-center gap-1 rounded-full border border-cyan-500/30 bg-cyan-500/10 px-2 py-0.5 font-medium text-cyan-700 dark:text-cyan-300">
<span className="inline-flex items-center gap-1 rounded-full border border-blue-500/30 bg-blue-500/10 px-2 py-0.5 font-medium text-blue-700 dark:text-blue-300">
<Clock className="h-3 w-3" aria-hidden="true" />
{badgeLabel}
</span>

View File

@ -89,9 +89,9 @@ export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) {
if (runs.length === 0) return null;
return (
<div className="overflow-hidden rounded-xl border border-cyan-500/25 bg-background/80 shadow-(--shadow-extract-11)">
<div className="border-b border-border/60 bg-cyan-500/[0.04] px-4 py-3">
<div className="text-xs font-semibold uppercase tracking-(--tracking-caps) text-cyan-700 dark:text-cyan-300">
<div className="overflow-hidden rounded-xl border border-blue-500/25 bg-background/80 shadow-(--shadow-extract-11)">
<div className="border-b border-border/60 bg-blue-500/[0.04] px-4 py-3">
<div className="text-xs font-semibold uppercase tracking-(--tracking-caps) text-blue-700 dark:text-blue-300">
Live Runs
</div>
<div className="mt-1 text-xs text-muted-foreground">
@ -113,7 +113,7 @@ export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) {
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
<Link
to={`/agents/${run.agentId}/runs/${run.id}`}
className="inline-flex items-center rounded-full border border-border/70 bg-background/70 px-2 py-1 font-mono hover:border-cyan-500/30 hover:text-foreground"
className="inline-flex items-center rounded-full border border-border/70 bg-background/70 px-2 py-1 font-mono hover:border-blue-500/30 hover:text-foreground"
>
{run.id.slice(0, 8)}
</Link>
@ -135,7 +135,7 @@ export function LiveRunWidget({ issueId, companyId }: LiveRunWidgetProps) {
)}
<Link
to={`/agents/${run.agentId}/runs/${run.id}`}
className="inline-flex items-center gap-1 rounded-full border border-border/70 bg-background/70 px-2.5 py-1 text-(length:--text-micro) font-medium text-cyan-700 transition-colors hover:border-cyan-500/30 hover:text-cyan-600 dark:text-cyan-300"
className="inline-flex items-center gap-1 rounded-full border border-border/70 bg-background/70 px-2.5 py-1 text-(length:--text-micro) font-medium text-blue-700 transition-colors hover:border-blue-500/30 hover:text-blue-600 dark:text-blue-300"
>
Open run
<ExternalLink className="h-3 w-3" />

View File

@ -323,8 +323,8 @@ export function OnboardingChat({
{hasActiveRun ? (
<>
<span className="relative flex h-2.5 w-2.5 shrink-0">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-cyan-500" />
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-blue-500" />
</span>
{getRunStatusMessage(activeRun.status, agentName, elapsed)}
</>
@ -468,8 +468,8 @@ function WelcomeMessage({
{!showMessage && (
<div className="flex items-center gap-2 text-sm text-muted-foreground px-3 py-2">
<span className="relative flex h-2.5 w-2.5 shrink-0">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-cyan-500" />
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-blue-500" />
</span>
{phase === "waking"
? `${agentName} is waking up...`

View File

@ -338,10 +338,10 @@ export function ProviderQuotaCard({
qw.usedPercent == null
? null
: qw.usedPercent >= 90
? "bg-red-400"
? "bg-(--status-task-blocked)"
: qw.usedPercent >= 70
? "bg-yellow-400"
: "bg-green-400";
? "bg-(--status-task-todo)"
: "bg-(--status-task-done)";
return (
<div key={qw.label} className="space-y-1">
<div className="flex items-center justify-between gap-2 text-xs">

View File

@ -439,7 +439,7 @@ export function WorkTimelineChart({
);
})}
{/* now line — teal "Signal" present marker */}
{/* now line — status-blue "Signal" present marker (gallery r2; was teal) */}
{now >= layout.fromMs && now <= layout.toMs && (
<line
x1={layout.gutter + ((now - layout.fromMs) / 60000) * layout.pxPerMinute}

View File

@ -744,7 +744,7 @@ function TranscriptToolCard({
: "Completed";
const statusTone =
block.status === "running"
? "text-cyan-700 dark:text-cyan-300"
? "text-blue-700 dark:text-blue-300"
: block.status === "error"
? "text-red-700 dark:text-red-300"
: "text-emerald-700 dark:text-emerald-300";
@ -758,7 +758,7 @@ function TranscriptToolCard({
? "text-red-600 dark:text-red-300"
: block.status === "completed"
? "text-emerald-600 dark:text-emerald-300"
: "text-cyan-600 dark:text-cyan-300",
: "text-blue-600 dark:text-blue-300",
);
const summary = block.status === "running"
? summarizeToolInput(block.name, block.input, density)
@ -857,7 +857,7 @@ function TranscriptCommandGroup({
? summarizeToolInput("command_execution", runningItem.input, density)
: null;
const statusTone = isRunning
? "text-cyan-700 dark:text-cyan-300"
? "text-blue-700 dark:text-blue-300"
: "text-foreground/70";
return (
@ -885,7 +885,7 @@ function TranscriptCommandGroup({
"inline-flex h-6 w-6 items-center justify-center rounded-full border shadow-sm",
index > 0 && "-ml-1.5",
isRunning
? "border-cyan-500/25 bg-cyan-500/[0.08] text-cyan-600 dark:text-cyan-300"
? "border-blue-500/25 bg-blue-500/[0.08] text-blue-600 dark:text-blue-300"
: "border-border/70 bg-background text-foreground/55",
isRunning && "animate-pulse",
)}
@ -934,7 +934,7 @@ function TranscriptCommandGroup({
item.status === "error"
? "border-red-500/25 bg-red-500/[0.08] text-red-600 dark:text-red-300"
: item.status === "running"
? "border-cyan-500/25 bg-cyan-500/[0.08] text-cyan-600 dark:text-cyan-300"
? "border-blue-500/25 bg-blue-500/[0.08] text-blue-600 dark:text-blue-300"
: "border-border/70 bg-background text-foreground/55",
)}>
<TerminalSquare className="h-3 w-3" />
@ -985,7 +985,7 @@ function TranscriptToolGroup({
? summarizeToolInput(runningItem.name, runningItem.input, density)
: null;
const statusTone = isRunning
? "text-cyan-700 dark:text-cyan-300"
? "text-blue-700 dark:text-blue-300"
: "text-foreground/70";
return (
@ -1008,7 +1008,7 @@ function TranscriptToolGroup({
"inline-flex h-6 w-6 items-center justify-center rounded-full border shadow-sm",
index > 0 && "-ml-1.5",
isItemRunning
? "border-cyan-500/25 bg-cyan-500/[0.08] text-cyan-600 dark:text-cyan-300"
? "border-blue-500/25 bg-blue-500/[0.08] text-blue-600 dark:text-blue-300"
: isItemError
? "border-red-500/25 bg-red-500/[0.08] text-red-600 dark:text-red-300"
: "border-border/70 bg-background text-foreground/55",
@ -1049,7 +1049,7 @@ function TranscriptToolGroup({
item.status === "error"
? "border-red-500/25 bg-red-500/[0.08] text-red-600 dark:text-red-300"
: item.status === "running"
? "border-cyan-500/25 bg-cyan-500/[0.08] text-cyan-600 dark:text-cyan-300"
? "border-blue-500/25 bg-blue-500/[0.08] text-blue-600 dark:text-blue-300"
: "border-border/70 bg-background text-foreground/55",
)}>
<Wrench className="h-3 w-3" />
@ -1106,8 +1106,8 @@ function TranscriptActivityRow({
<Check className="mt-0.5 h-3.5 w-3.5 shrink-0 text-emerald-600 dark:text-emerald-300" />
) : (
<span className="relative mt-1 flex h-2.5 w-2.5 shrink-0">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-cyan-400 opacity-70" />
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-cyan-500" />
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-70" />
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-blue-500" />
</span>
)}
<div className={cn(

View File

@ -1681,7 +1681,7 @@ span.paperclip-mention-chip[data-mention-kind="external-object"] {
--pct-85: 85%; /* Extracted from ui/src/components/IssueChatThread.test.tsx (max-w-[85%]). */
--pct-neg-50: -50%; /* Extracted from ui/src/components/ui/alert-dialog.tsx (translate-x-[-50%]). */
--pct-72: 72%; /* Extracted from ui/src/pages/IssueDetail.tsx (w-[72%]). */
--shadow-extract-1: 0 16px 40px rgba(6,182,212,0.08); /* Extracted from ui/src/components/ActiveAgentsPanel.tsx (shadow-[0_16px_40px_rgba(6,182,212,0.08)]). */
--shadow-extract-1: 0 16px 40px rgba(37,99,235,0.08); /* ActiveAgentsPanel.tsx live-box glow. Gallery feedback r2: liveness glow recolored cyan->status blue (value edit, site unchanged; originally extracted as rgba(6,182,212,0.08)). */
--shadow-extract-2: 0 20px 80px -40px rgba(0,0,0,0.55); /* Extracted from ui/src/components/BudgetPolicyCard.tsx (shadow-[0_20px_80px_-40px_rgba(0,0,0,0.55)]). */
--shadow-extract-3: 0 0 0 1px rgba(255,255,255,0.08); /* Extracted from ui/src/components/BudgetSidebarMarker.tsx (shadow-[0_0_0_1px_rgba(255,255,255,0.08)]). */
--shadow-extract-4: 0 -12px 28px rgba(15,23,42,0.08); /* Extracted from ui/src/components/ChatComposer.test.tsx (shadow-[0_-12px_28px_rgba(15,23,42,0.08)]). */
@ -1691,10 +1691,10 @@ span.paperclip-mention-chip[data-mention-kind="external-object"] {
--shadow-extract-8: 0 1px 0 rgba(15,23,42,0.02); /* Extracted from ui/src/components/IssueRecoveryActionCard.tsx (shadow-[0_1px_0_rgba(15,23,42,0.02)]). */
--shadow-extract-9: 0 18px 42px rgba(15,23,42,0.06); /* Extracted from ui/src/components/IssueThreadInteractionCard.tsx (shadow-[0_18px_42px_rgba(15,23,42,0.06)]). */
--shadow-extract-10: 0 1px 0 1px hsl(var(--border)); /* Extracted from ui/src/components/KeyboardShortcutsCheatsheet.tsx (shadow-[0_1px_0_1px_hsl(var(--border))]). */
--shadow-extract-11: 0 18px 50px rgba(6,182,212,0.08); /* Extracted from ui/src/components/LiveRunWidget.tsx (shadow-[0_18px_50px_rgba(6,182,212,0.08)]). */
--shadow-extract-11: 0 18px 50px rgba(37,99,235,0.08); /* LiveRunWidget.tsx glow. Gallery feedback r2: liveness glow recolored cyan->status blue (value edit, site unchanged; originally extracted as rgba(6,182,212,0.08)). */
--shadow-extract-12: 0 0 0 2px hsl(var(--background)); /* Extracted from ui/src/components/SidebarNavItem.tsx (shadow-[0_0_0_2px_hsl(var(--background))]). */
--shadow-extract-13: 0 0 0 3px rgba(245,158,11,0.18); /* Extracted from ui/src/components/environment-variables-editor/index.tsx (shadow-[0_0_0_3px_rgba(245,158,11,0.18)]). */
--shadow-extract-14: 0 0 12px rgba(6,182,212,0.08); /* Extracted from ui/src/pages/AgentDetail.tsx (shadow-[0_0_12px_rgba(6,182,212,0.08)]). */
--shadow-extract-14: 0 0 12px rgba(37,99,235,0.08); /* AgentDetail.tsx live-card glow. Gallery feedback r2: liveness glow recolored cyan->status blue (value edit, site unchanged; originally extracted as rgba(6,182,212,0.08)). */
--shadow-extract-18: 0 18px 44px -28px rgba(0,0,0,0.45); /* Extracted from ui/src/pages/ProfileSettings.tsx (shadow-[0_18px_44px_-28px_rgba(0,0,0,0.45)]). */
}

View File

@ -42,7 +42,7 @@ export function runStatusClassName(status: string): string {
case "timed_out":
return "text-orange-700 dark:text-orange-300";
case "running":
return "text-cyan-700 dark:text-cyan-300";
return "text-blue-700 dark:text-blue-300";
case "queued":
case "pending":
return "text-amber-700 dark:text-amber-300";

View File

@ -66,7 +66,7 @@ export function describeRunRetryState(run: RetryAwareRun): RunRetryStateSummary
return {
kind: "scheduled",
badgeLabel: isMaxTurnContinuation ? "Continuation scheduled" : "Retry scheduled",
tone: "border-cyan-500/30 bg-cyan-500/10 text-cyan-700 dark:text-cyan-300",
tone: "border-blue-500/30 bg-blue-500/10 text-blue-700 dark:text-blue-300",
detail: joinFragments([attemptLabel, reasonLabel]),
secondary: dueAt
? `${isMaxTurnContinuation ? "Next continuation" : "Next retry"} ${dueAt}`

View File

@ -262,7 +262,7 @@ export const priorityColorDefault = "text-yellow-600 dark:text-yellow-400";
// unknown → backlog hue (muted, dashed circle)
// open → todo / blue
// waiting → amber (distinct from internal in_progress yellow)
// running → cyan, animated when motion is allowed
// running → status blue (gallery r2; was cyan), animated when motion is allowed
// succeeded → done / green
// failed → red
// blocked → red
@ -275,7 +275,7 @@ export const externalObjectStatusIcon: Record<string, string> = {
unknown: "text-muted-foreground border-muted-foreground",
open: "text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-400",
waiting: "text-amber-600 border-amber-600 dark:text-amber-400 dark:border-amber-400",
running: "text-cyan-600 border-cyan-600 dark:text-cyan-400 dark:border-cyan-400",
running: "text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-400", // Gallery feedback r2: running = status blue (pulse animation still distinguishes it from static blue `open`).
succeeded: "text-green-600 border-green-600 dark:text-green-400 dark:border-green-400",
merged: "text-violet-600 border-violet-600 dark:text-violet-400 dark:border-violet-400",
failed: "text-red-600 border-red-600 dark:text-red-400 dark:border-red-400",
@ -292,7 +292,7 @@ export const externalObjectStatusBadge: Record<string, string> = {
unknown: "bg-muted text-muted-foreground",
open: "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300",
waiting: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
running: "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300",
running: "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300", // Gallery feedback r2: running = status blue (now shares tint with `open`; liveness animation differentiates).
succeeded: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300",
failed: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",
blocked: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300",

View File

@ -125,7 +125,7 @@ export function isCancelledStatus(status: string): boolean {
* (kicked off by another actor) read blue; automation/self-started runs read
* amber. The blue/amber pair is colour-blind-safe and holds contrast on both
* light and dark backgrounds so the chart screenshots cleanly. Cancelled runs
* drop their fill entirely (rendered as a hollow dashed bar) and a teal "now"
* drop their fill entirely (rendered as a hollow dashed bar) and a status-blue "now"
* line marks the present.
*/
export const TIMELINE_COLORS = {
@ -133,7 +133,7 @@ export const TIMELINE_COLORS = {
automation: "#f4b740",
/** stroke/ink for a hollow, cancelled bar. */
cancelled: "#9aa3ad",
now: "#2dd4bf",
now: "#2563eb", // Gallery feedback r2: "now" liveness marker = status blue (was teal #2dd4bf); shape (1.5px vertical line) still distinguishes it from #5b9bf6 delegated bars.
} as const;
export type RunSourceKind = "delegated" | "automation";

View File

@ -477,7 +477,7 @@ function workspaceOperationStatusTone(status: WorkspaceOperation["status"]) {
case "failed":
return "border-red-500/20 bg-red-500/10 text-red-700 dark:text-red-300";
case "running":
return "border-cyan-500/20 bg-cyan-500/10 text-cyan-700 dark:text-cyan-300";
return "border-blue-500/20 bg-blue-500/10 text-blue-700 dark:text-blue-300";
case "skipped":
return "border-yellow-500/20 bg-yellow-500/10 text-yellow-700 dark:text-yellow-300";
default:
@ -1280,8 +1280,8 @@ function LatestRunCard({ runs, agentId }: { runs: HeartbeatRun[]; agentId: strin
<h3 className="flex items-center gap-2 text-sm font-medium">
{isLive && (
<span className="relative flex h-2 w-2">
<span className="animate-pulse absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-cyan-400" />
<span className="animate-pulse absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500" />
</span>
)}
{isLive ? "Live Run" : "Latest Run"}
@ -1298,7 +1298,7 @@ function LatestRunCard({ runs, agentId }: { runs: HeartbeatRun[]; agentId: strin
to={`/agents/${agentId}/runs/${run.id}`}
className={cn(
"block border rounded-lg p-4 space-y-2 w-full no-underline transition-colors hover:bg-muted/50 cursor-pointer",
isLive ? "border-cyan-500/30 shadow-(--shadow-extract-14)" : "border-border"
isLive ? "border-blue-500/30 shadow-(--shadow-extract-14)" : "border-border"
)}
>
<div className="flex items-center gap-2">
@ -4132,10 +4132,10 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin
</Button>
)}
{isLive && (
<span className="flex items-center gap-1 text-xs text-cyan-400">
<span className="flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400">
<span className="relative flex h-2 w-2">
<span className="animate-pulse absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-cyan-400" />
<span className="animate-pulse absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500" />
</span>
Live
</span>

View File

@ -1402,10 +1402,10 @@ export function DesignGuide() {
<div className="text-foreground">[12:00:17] INFO Reconnected successfully</div>
<div className="flex items-center gap-1.5">
<span className="relative flex h-1.5 w-1.5">
<span className="absolute inline-flex h-full w-full rounded-full bg-cyan-400 animate-pulse" />
<span className="inline-flex h-full w-full rounded-full bg-cyan-400" />
<span className="absolute inline-flex h-full w-full rounded-full bg-blue-400 animate-pulse" />
<span className="inline-flex h-full w-full rounded-full bg-blue-500" />
</span>
<span className="text-cyan-400">Live</span>
<span className="text-blue-600 dark:text-blue-400">Live</span>
</div>
</div>
</Section>

View File

@ -3860,10 +3860,10 @@ export function IssueDetail() {
<span className="text-sm font-mono text-muted-foreground shrink-0">{issue.identifier ?? issue.id.slice(0, 8)}</span>
{hasLiveRuns && (
<span className="inline-flex items-center gap-1.5 rounded-full bg-cyan-500/10 border border-cyan-500/30 px-2 py-0.5 text-(length:--text-nano) font-medium text-cyan-600 dark:text-cyan-400 shrink-0">
<span className="inline-flex items-center gap-1.5 rounded-full bg-blue-500/10 border border-blue-500/30 px-2 py-0.5 text-(length:--text-nano) font-medium text-blue-600 dark:text-blue-400 shrink-0">
<span className="relative flex h-1.5 w-1.5">
<span className="animate-pulse absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75" />
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-cyan-400" />
<span className="animate-pulse absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" />
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-blue-500" />
</span>
Live
</span>