a sixth tone, and no raw palette left in the core apps

The five core apps — FileBrowser, FileViewer, SystemMonitor, Chat, Plugins — now
contain ZERO raw Tailwind palette classes. Every colour in them names a state or a
surface, so all of it follows the theme.

── Busy ──

The 38 ambers were the blocker, and the answer was not to pick between two wrong
mappings. They mean RUNNING — Chat's own comment says "the amber a RUNNING task chip
wears" — and the vocabulary had no word for it. `warning` would have made a working
job look like a failing one, and `--status-in-progress` is blue.

So `tone.ts` has six tones instead of five. It was always written as a closed set that
could grow when a genuine sixth state appeared; this is that.

`--busy-ink` comes with it, and generalises: a fill bright enough to carry black text
is far too light to BE text. The other five tones are dark enough to do both jobs,
which is why the problem had not surfaced. `text-amber-950` — ink ON the fill — became
`text-busy-foreground`, which is what it always was.

── On fidelity, which stopped being the constraint ──

Duck's exact colours were preserved up to this point because an extraction has to be
provably invisible. The owner has since said the palette is not precious: the
wallpaper, the graph-paper grid and the mark are what matter, and everything else can
move. So shades collapse — one busy, one red, one green — rather than being
approximated to keep old pixels.

The verification built for the old constraint keeps its value: it proves the token
layer resolves, which is a different claim from "the colours did not change".

Verified: the utilities compile (`text-busy-ink`, `bg-busy`, `border-busy`,
`text-busy-foreground`) and resolve in the browser.

950 pass / 7 fail unchanged. tsgo clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 23:13:19 +00:00
co-authored by Claude Opus 5
parent a52d3e06f3
commit dfb8a6d6cb
12 changed files with 56 additions and 30 deletions
+7 -2
View File
@@ -1,6 +1,6 @@
// The state vocabulary for data surfaces. See docs/design-language-interface.md § 3.
//
// Five tones and no others. The point of a closed set is that colour keeps meaning something: when a
// Six tones and no others. The point of a closed set is that colour keeps meaning something: when a
// view can reach for any of Tailwind's twenty-two palettes, green stops reading as "open" and starts
// reading as "the author liked green here". Every tone maps to a semantic token, never a palette
// number, so it follows the theme and survives dark mode.
@@ -8,7 +8,7 @@
// Class strings are written out in full rather than composed (`text-${tone}`) because Tailwind's
// scanner reads source text — an interpolated class name is not in the output CSS at all.
export type Tone = 'neutral' | 'success' | 'warning' | 'danger' | 'info';
export type Tone = 'neutral' | 'success' | 'warning' | 'danger' | 'info' | 'busy';
/** Foreground only — for an icon or a word carrying state inside otherwise normal text. */
export const toneText: Record<Tone, string> = {
@@ -17,6 +17,9 @@ export const toneText: Record<Tone, string> = {
warning: 'text-warning',
danger: 'text-destructive',
info: 'text-info',
// `-ink`, not the fill: busy's fill is bright enough to carry black text, which makes it unreadable AS
// text. The other five are dark enough to serve both jobs.
busy: 'text-busy-ink',
};
/**
@@ -29,6 +32,7 @@ export const toneFill: Record<Tone, string> = {
warning: 'bg-warning/15 text-warning',
danger: 'bg-destructive/15 text-destructive',
info: 'bg-info/15 text-info',
busy: 'bg-busy/15 text-busy-ink',
};
/** A left border or rule carrying state, for rows that mark status without a pill. */
@@ -38,4 +42,5 @@ export const toneBorder: Record<Tone, string> = {
warning: 'border-warning',
danger: 'border-destructive',
info: 'border-info',
busy: 'border-busy',
};