agent-manager · settings → appearance

Six palettes, four typefaces, one interface

A working section in Settings, not a mock-up: pick a palette or a typeface and the whole app changes — sidebar, pane headers, the reader, Overview, and Settings itself. Everything below is a screenshot of that app.

The control

Two rows of choices in Settings → General → Appearance. Each card shows what it does: the palette cards carry three of their own colours in the theme you are currently in, and each typeface card is set in the face it offers. Pressing one applies it immediately and remembers it, the same way theme and zoom are remembered.

The Appearance section in Settings, with Paper and IBM Plex selected
The section, after choosing Paper and IBM Plex — note that Settings itself has changed with everything else. (Six cards now: Mono was added after the first round.)

The palettes

Each is a complete set of the interface's colour tokens — background, panel, border, text, muted, accent and the rest — defined for light and dark. Nothing inherits from the default palette, so no surface is left half-swapped.

Teal — what the app has always been

Teal, light
light
Teal, dark
dark

Indigo — cooler and quieter, with the accent doing the work

Indigo, light
light
Indigo, dark
dark

Paper — warm and low-contrast, like reading off paper

Paper, light
light
Paper, dark
dark

Phosphor — a terminal that got out

Phosphor, dark
dark — the one it was designed for
Phosphor, light
light

Mono — black and white, with no hue anywhere

Added after the first round, at the operator's request. It is the one palette that cannot lean on hue for anything, which makes it the most interesting: every job colour used to do has to be done by lightness, weight or pattern. The section below is about what that costs and what it does not.

Mono, light
light
Mono, dark
dark

Plum — neutral greys with one loud colour in them

Plum, light
light
Plum, dark
dark

Overview, in the same palettes

The tiles and the cards read from the same tokens, so they follow without knowing a palette exists.

Overview, teal
Teal
Overview, paper
Paper
Overview, phosphor
Phosphor, dark
Overview, plum
Plum, dark

The typefaces

Departure Mono was the operator's example and is the reason this is interesting: it sets the entire interface in a pixel font. The other three are a familiar pair, a taller mono for the terminal, and a warmer sans/mono family.

Geist
Geist — the current pair.
Departure Mono
Departure Mono — pixel font, everywhere.
JetBrains Mono
JetBrains Mono — taller mono, Geist for prose.
IBM Plex
IBM Plex — Plex Sans and Plex Mono.

The terminal, which is not CSS

xterm draws on a canvas, so it inherits nothing: its font and colours are options handed over in JavaScript. The first version of this study left it out, and the review caught it — Departure/JetBrains/Plex left terminal glyphs in Geist, and Paper/Phosphor left the canvas teal. It now derives both from the active tokens and updates live, refitting the grid through the same path the zoom control uses, because a typeface changes the cell size and therefore how many columns the pane fits.

Terminal, teal
Teal — the canvas is --term-bg.
Terminal, paper
Paper — the canvas follows to #fbf7f0, sampled from the painted pixels.
Terminal, phosphor
Phosphor — #060b0d, cursor on the accent.
Terminal, Departure Mono
Departure Mono — the terminal's glyphs change with the app's.

The sixteen ANSI colours deliberately do not follow a palette. They are the program's palette — ls colours, a TUI's own theme, a diff's red and green — and they have to stay distinguishable from each other. Under Phosphor, an accent-green ANSI green would be indistinguishable from success; under any single-hue palette every stream would look alike. They stay tuned per theme.

Black and white: what replaces hue

A palette with one channel is a different problem from a palette with three. Measured at 3× — where there is no subpixel text rendering to fake colour — the monochrome chrome has a maximum chroma of zero. Not one coloured pixel in the state marks or in the delete confirmation, against 120 and 137 for the same crops in Teal.

The accent, and the pair that must not collapse

waiting ("your turn") is the accent; idle is the muted grey. They are two 1px frames at 12px, and they collapsed once before, in #92. In a grey palette the accent takes the extreme of the only channel there is — pure black on light, pure white on dark — and the numbers say why that is not decoration:

paletteaccent vs muted, in luminance
Teal (light)1.05:1
Paper (light)1.04:1
Phosphor (dark)1.48:1
Mono (light)3.71:1
Mono (dark)3.23:1

Every hued palette separates those two marks by about one: they are telling them apart by hue, and this arithmetic cannot see that. Mono has to do it in the channel the arithmetic does see, and does it 2.5–3.5× harder. The contrast test asserts 3:1 for Mono and skips the hued palettes with the reason written next to it.

The four state marks in Mono
Mono at 4×: dashed black, solid black, mid grey, light grey.
The four state marks in Teal
Teal, same crop — working and your turn are teal, idle is grey.

Danger, which loses its red

Delete is the irreversible action and red is what says so. With no hue, the fill takes a pattern instead: an inset ring in the surface colour, on the two filled destructive controls. In the confirm row below, the black Delete carries a white halo that the black View segment does not.

Delete confirmation in Mono
Mono — the ring is the whole signal.
Delete confirmation in Teal
Teal — red does it without asking.

What is lost, plainly. The ring covers controls. Danger text — an error line, a failed row, a warning notice — has nowhere left to go and reads as ordinary ink; there is no token that can bring it back, and inventing an icon for it would be a component rewrite, not a palette. Success and warning lose their colour outright, and both were already carried by the word next to them ("ok", "saved", "failed"). So: in pure black and white, "irreversible" survives as emphasis on a button and loses its at-a-glance recognisability in prose. That is the honest cost of the option, not a bug in it.

Where the colour is allowed to stay

The line is drawn by surface, not by origin. Everything the app paints goes grey — including the harness logos in the sidebar, which get a greyscale filter alongside the two invert rules that already bend a logo to a theme. Everything that is content keeps its colour: an attached image, a preview, and above all the terminal's sixteen ANSI colours, which are the program's own bytes and stay tuned per theme.

A shell in the Mono palette
A shell prompt keeps its own green and cyan inside a black-and-white frame.
Claude Code's welcome banner in Mono dark
Claude Code's welcome screen, in full colour, in a grey chrome. It reads as a window into another program — which is exactly what it is.

The editor is the other side of that line, and it goes grey. Its syntax colours are ours — our paint on someone's plain text — so a palette with no hue cannot leave a rainbow in the file pane. What is left is three steps of ink (structure, values, everything else) plus the cues the editor theme already carries without colour: comments italic, headings bold, links underlined. It reads. It does not sort code by hue the way nine colours do, and that is the trade.

A TypeScript file in the Mono palette
The same file the coloured palettes render in nine hues.

Two things the palette found in the existing code

An invisible delete button. Three rules painted #fff on the danger fill. That is safe for every red and wrong the moment a palette's danger colour is light — Mono's dark theme would have been white on white. It is a token now, and the dark palettes take dark ink instead: white on their light reds measured 3.10:1 (default), 2.88:1 (Paper), 2.55:1 (Phosphor) — under AA for a button label, and true since the fill existed. Dark ink is 6.1–7.4:1.

A cascade trap. The editor's syntax colours live in a third :root block further down the stylesheet, so at equal specificity they win on order: the palette's overrides never applied, and light Mono showed purple keywords while dark looked correct. Caught by reading the computed value in a browser, not by reading the file. The selector carries an extra :root now, and a new check resolves specificity-then-order on the source for all nine tokens.

Contrast

Text 7:1, muted 4.5:1, accent 3:1, danger 3:1, and the new ink-on-danger pair 4.5:1 — in both themes, for all six palettes. Mono's tightest is muted on the window background at 4.88:1. A grey palette is the one most likely to fail these, since every pair competes in the same channel; it does not.

What the study had to not break

The status marks are measured geometry — the state frames were built from a braille glyph's ink box at a specific size in a specific font. Three things were checked before any of the above was believed:

  • No font here draws the braille at all. Measured in Chromium: at 12.5px has the same 5.5 × 8.8125px ink under Geist Mono, Departure Mono, JetBrains Mono and IBM Plex Mono, which is the system fallback rendering it every time. What does change is where it sits: 1.1875px from the top of the cell under Geist Mono, 2.1875px under IBM Plex Mono.
  • The marks now read their own font token (--font-mark), pinned to the bundled Geist Mono, so a typeface change cannot move them. A browser test asserts exactly that: swap --font-mono to a serif and the cell is still 15 × 13.125px and still in the pinned face.
  • The sidebar and Overview frames were already safe — they became SVG in #92 and touch no font. Pixel-identical under all four typefaces.

Code colour is two systems. Five of the editor's syntax tokens carry app meaning and now follow the palette — a link is the accent, an error is danger, and the quiet ink (comments, punctuation, metadata) is the muted grey. --cm-link and --cm-invalid had the teal accent and danger values typed out, which is how a palette used to leave teal in the file editor. The other nine are a hue system for reading code — keyword purple, string green, number orange — held invariant on purpose and asserted as such, because re-hueing them per palette would make every palette its own legibility problem.

Colour was checked the same way, by computing contrast for every pair on every surface in both themes: body text ≥ 7:1, muted ≥ 4.5:1, accent ≥ 3:1. All four new palettes pass. The default teal palette misses AA in one place that predates this work — muted text on the app background is 4.16:1 — which is reported rather than quietly changed.

The fonts, and their licences

FamilyVersionLicenceSize on the wire
Geist / Geist Monoas already bundledOFL 1.129 KB / 23 KB
Departure Mono1.500OFL 1.122 KB
JetBrains Mono2.304OFL 1.121 KB + 22 KB
IBM Plex Sans / Mono1.1.0OFL 1.123 + 24 + 15 KB

All self-hosted from the Space — no CDN, or the study would be of whatever the browser fell back to. The JetBrains and Plex files are the Latin subsets (about a quarter the size of the full releases, which carry Cyrillic and Greek this interface never renders).