Recolor agent 'done' state from sky blue to emerald green (#1246)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson
2026-04-29 10:47:28 -07:00
committed by GitHub
co-authored by Orca
parent 6abb9b5e8e
commit 006272b25c
4 changed files with 32 additions and 18 deletions
@@ -58,7 +58,7 @@ export const AgentStateDot = React.memo(function AgentStateDot({
>
<span
className={cn(
'block rounded-full border-2 border-emerald-500 border-t-transparent animate-spin',
'block rounded-full border-2 border-yellow-500 border-t-transparent animate-spin',
inner
)}
/>
@@ -76,9 +76,11 @@ export const AgentStateDot = React.memo(function AgentStateDot({
'block rounded-full',
inner,
state === 'blocked' || state === 'waiting' || state === 'permission'
? 'bg-red-500 animate-pulse'
? 'bg-red-500'
: state === 'done'
? 'bg-sky-500/80'
? // Why: emerald-500 matches StatusIndicator's done dot so the
// dashboard and sidebar read as the same state.
'bg-emerald-500'
: 'bg-neutral-500/40'
)}
/>
@@ -255,19 +255,19 @@ const AgentDashboard = React.memo(function AgentDashboard() {
<span className="ml-auto flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground">
{groupRunning > 0 && (
<span>
<span className="font-semibold text-emerald-500">{groupRunning}</span>{' '}
<span className="font-semibold text-yellow-500">{groupRunning}</span>{' '}
active
</span>
)}
{groupBlocked > 0 && (
<span>
<span className="font-semibold text-amber-500">{groupBlocked}</span>{' '}
<span className="font-semibold text-red-500">{groupBlocked}</span>{' '}
blocked
</span>
)}
{groupDone > 0 && (
<span>
<span className="font-semibold text-sky-500/80">{groupDone}</span>{' '}
<span className="font-semibold text-emerald-500">{groupDone}</span>{' '}
done
</span>
)}
@@ -1,6 +1,6 @@
import React from 'react'
import { cn } from '@/lib/utils'
import type { WorktreeStatus } from '@/lib/worktree-status'
import { getWorktreeStatusLabel, type WorktreeStatus } from '@/lib/worktree-status'
// Why: re-export WorktreeStatus under the existing `Status` alias so the
// sidebar component and the canonical lib share one source of truth — the
@@ -15,15 +15,25 @@ type StatusIndicatorProps = React.ComponentProps<'span'> & {
const StatusIndicator = React.memo(function StatusIndicator({
status,
className,
title,
...rest
}: StatusIndicatorProps) {
// Why: surface the status label as a native tooltip so hovering the dot
// reveals the state — matters especially for 'active' vs 'inactive', which
// share the same grey dot (see color-branch comment below). Callers pass
// aria-hidden="true" alongside an sr-only label, so the `title` attribute
// is ignored by AT and only serves sighted users on hover. Callers can
// override by passing their own `title`.
const resolvedTitle = title ?? getWorktreeStatusLabel(status)
if (status === 'working') {
return (
<span
className={cn('inline-flex h-3 w-3 shrink-0 items-center justify-center', className)}
title={resolvedTitle}
{...rest}
>
<span className="block size-2 rounded-full border-2 border-emerald-500 border-t-transparent animate-spin" />
<span className="block size-2 rounded-full border-2 border-yellow-500 border-t-transparent animate-spin" />
</span>
)
}
@@ -31,20 +41,22 @@ const StatusIndicator = React.memo(function StatusIndicator({
return (
<span
className={cn('inline-flex h-3 w-3 shrink-0 items-center justify-center', className)}
title={resolvedTitle}
{...rest}
>
<span
className={cn(
'block size-2 rounded-full',
status === 'active'
? 'bg-emerald-500'
: status === 'permission'
? 'bg-red-500'
: status === 'done'
? // Why: sky-500/80 matches the dashboard AgentStateDot's
// `done` color so the two surfaces read as the same state.
'bg-sky-500/80'
: 'bg-neutral-500/40'
status === 'permission'
? 'bg-red-500'
: status === 'done'
? // Green dot for done; working uses a yellow spinner so the
// two states differ by both hue and motion. 'active' (terminal
// open, quiet) collapses to the same grey as 'inactive' — the
// tooltip carries the distinction for sighted users and the
// sr-only sibling in callers carries it for AT.
'bg-emerald-500'
: 'bg-neutral-500/40'
)}
/>
</span>
@@ -157,7 +157,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
// that the spinner flickered; the blocked/waiting/done states don't have
// that problem — they're terminal (done) or attention-needed (blocked/
// waiting) and persist until the user acts. Retained "done" snapshots are
// consulted too so the sky dot keeps glowing after the agent process exits,
// consulted too so the done dot keeps glowing after the agent process exits,
// matching the dashboard's retention behavior.
//
// Priority (highest first): permission (blocked/waiting) > heuristic