refactor(app): unify status colours on one token family

Three systems were reporting the same states: a full and a muted status
tier in the theme, plus raw palette steps in StatusBadge and
getStatusDotColor. A PR icon was muted in the sidebar and full-strength
in the PR panel, and the Resolved badge used a theme-independent green
so it painted a dark block in light mode. The muted tier is deleted and
every status surface now reads the one token per signal.

Dots keep a band of their own. A dot is a few points of solid colour
with no shape to read and no label attached, and the running one pulses,
so at the status band's lightness it sat below the check icons and host
badge on its own row — backwards, since the dot is the row's state.
Chroma caps at 90% of gamut rather than 100% because at the edge green
reaches C=0.215 while blue manages 0.116 and the set stops reading as
one family; dark lightness caps at 0.72 because red desaturates to pink
above it.

Host badges needed a foreground variant of the identity table. That
table is tuned for a white letter on the colour as a fill; used as text
it cannot clear 4.5:1 on both themes at once, since light needs
luminance <= 0.183 and dark needs >= 0.212. The new set is generated on
the status family's exact lightness and chroma fraction, so a host name
and a CI check in the same meta row carry equal weight instead of the
brighter one winning the row.

Also: PR avatars had their own eight-hex palette and hash duplicating
identity-colors; workspace rows under a project group now indent to the
same rail as status-grouped rows; group headers are muted; and the
pulsing dot is faster and fades further.
This commit is contained in:
Mohamed Boudra
2026-08-05 21:42:07 +02:00
parent 48bdc5610f
commit 211489c658
22 changed files with 330 additions and 191 deletions
+11 -7
View File
@@ -8,20 +8,24 @@ export function getStatusDotColor(input: {
}): string | null {
const { theme, bucket, showDoneAsInactive = false } = input;
// Working and needs_input share amber: both mean "this agent has your turn in it". They
// never collide in practice because needs_input always draws the alert glyph, never a bare
// dot — see getProjectStatusBadgeContent.
// The one place the statusDot* band is read. Dots sit louder than the check icons and host
// badges on the same row — see the band's note in theme.ts — so going through the status
// family here instead would quietly put the row's state below its metadata.
//
// needs_input is amber because it wants something from you. Working is blue: an agent doing
// its job is the one busy state that asks for nothing, so it should not sit in the same
// color as the states that do.
if (bucket === "needs_input") {
return theme.colors.palette.amber[500];
return theme.colors.statusDotWarning;
}
if (bucket === "failed") {
return theme.colors.palette.red[500];
return theme.colors.statusDotDanger;
}
if (bucket === "running") {
return theme.colors.palette.amber[500];
return theme.colors.statusDotRunning;
}
if (bucket === "attention") {
return theme.colors.palette.green[500];
return theme.colors.statusDotSuccess;
}
if (bucket === "done") {
return showDoneAsInactive ? theme.colors.border : null;