← the Atlas

Canvas tiles that show their state — border prototypes

feature · seedling ·implemented ·

Five terminals on the canvas; which one needs you? Surface each session's run-state on the tile border itself — a loudness ladder ranked by attention (alert ▸ fresh-waiting ▸ working ▸ stale-waiting ▸ idle), where "waiting" cools with last-activity age via kolu's existing activity-window. Four live, animated visual languages to choose from.

Five terminals on the canvas — which one needs you? The state exists today (a title-bar label, a dock dot), but reading a title bar isn’t a glance. Put the signal on the tile border, where peripheral vision catches it across the whole canvas.

A chooser, now decided: refined Language C (run/sweep) shipped in #1348 — motion carries the state, in the tile’s own repo colour (the one colour used throughout), and the active tile is marked by an offset repo outline. The four live border languages below are kept as the design record; each renders the attention ladder (ranked by who needs you; a waiter cools as it ages).

The attention ladder

A loudness ladder ordered by who needs you — and waiting cools as it ages (a fresh waiter outranks a busy tile; hours later it sinks below). Reuses kolu’s existing ranking + ageing, no parallel scheme:

AgentInfo.state — thinking · tool_use · waiting · awaiting_user · running_backgroundagentBucket() — working | awaiting | none (never idle: that's entryBucket's job)isUnread(id) — fresh + missed (useViewState)lastActivityAt vs activity window — useStaleCheck() / isStale()tileAura(bucket, unread, stale) — canvas-only pure mapper → tieruseTileAura() — gathers the three inputs oncecanvas tile aura bar (data-aura)minimap marker aura barstore.activeId() — focus ring (teal); clears unread; mutes own aura
Reuse the upstream classifiers, add one canvas-only mapper. AgentInfo.state → a bucket (agentBucket); an unread flag marks a fresh missed alert; lastActivityAt vs the activity window (useStaleCheck) ages a waiter — the three signals the dock already reads. The canvas would add its own pure tileAura(bucket, unread, stale) → tier, gathered once and read by both the tile and the minimap marker. Focus (activeId) paints the teal ring, clears unread, mutes the tile's own aura — so the loudest border is always elsewhere. No new state, no new clock.
RankStateWhenColorBorder
1 · Alertunread — flipped to needing you while you weren’t lookingviolet --color-alertfast throb + halo (~1.2s); self-clears on focus
2 · Waiting · freshawaiting and recent — within your activity window (esp. < 4h)violet --color-alertgentle slow breathe, bright
3 · Workingthinking / running tools / background workflowrust --color-busysteady hum — no motion
4 · Waiting · staleawaiting but older than your activity window (parked)violet --color-alertdim ember, static — cooled below the working hum
5 · Idle / doneno agent, finished, or acknowledgedrepo identitynone

Waiting cools as it ages

The same waiting tile at four ages — its glow dims and stops moving as lastActivityAt recedes, crossing below the working hum at your activity-window threshold. Same clock as the dock and minimap (useStaleCheck) — one vocabulary, one persisted choice.

Waiting · cooling with last-activity age — your activity window is the crossover
argh · claude
⏵ Waiting · just now
just now
rank 2 — loud
argh · claude
⏵ Waiting · ~2h
~2h
still above the hum
argh · claude
⏵ Waiting · ~4h
~4h
crossing your window
argh · claude
⏵ Waiting · stale · parked
stale · parked
rank 4 — ember

First tile = live breathe; the rest are the same state cooling. The crossover is wherever you set the activity window — yours to tune, not hard-coded.

The active tile — where focus meets the ladder

The ladder ranks attention demand (look next); focus marks where you are. Two axes — keep them apart:

One active tile (teal · "you are here") among the ladder — the loudest border is never the active one
argh · claude
◐ Thinking…
Active · working — focus wins, aura muted
kaval · codex
⏵ Awaiting input
Alert — loudest, always inactive
blog · claude
⏵ Waiting · fresh
Waiting · fresh — inactive
odu · claude
◐ Running tools
Working — inactive hum

One rule: loudness = state-tier × age-decay × presence. The tier picks colour + motion; age-decay folds into it (stale waiter → ember, stale worker → none); presence is the active mute. So the brightest border is always an inactive, fresh, attention-class tile — exactly where the eye should land.

Prototypes — pick a language

Four languages, live across the ladder (waiting shown fresh), each with its wins and costs tagged. Two tiebreakers decide it: a continuous loudness ramp (so a waiter can dim) and surviving the minimap (so canvas and map speak one language).

A — Halo

continuous dim-ramp leaves repo + focus untouched shrinks imperfectly to minimap

A soft outer glow carries state; the border line stays repo identity. A glow has a continuous brightness axis — it renders the whole ranked, aging ladder cleanly (alert brightest → stale waiter dims to an ember; the decay strip above is this language turned down).

A · Halo — glow (idle ▸ working ▸ waiting·fresh ▸ alert)
argh · claude
$ ▏
Idle / done
argh · claude
◐ Thinking…
Working
argh · claude
⏵ Waiting for input
Waiting · fresh
argh · claude
⏵ Awaiting input
Alert (unread)

B — Live border

most literal overwrites repo identity no room to dim

The border line itself takes the state colour. But it overwrites repo identity while busy (most of the time), and a solid line has nowhere to dim to (a faint border reads as a bug). Fine for a binary; awkward for a ranked, aging ladder.

B · Live border — the line carries state
argh · claude
$ ▏
Idle / done
argh · claude
◐ Thinking…
Working
argh · claude
⏵ Waiting for input
Waiting · fresh
argh · claude
⏵ Awaiting input
Alert (unread)

C — Run / sweep: motion is the state, colour is the terminal

most alive + theme-native motion on every busy tile no learnable colour reduced-motion deletes it needs D at minimap

The type and speed of motion carry the state, freeing the colour to be the terminal’s own:

The light is the tile’s own theme colour, brightened for louder states — a teal terminal runs teal, an amber one amber. That answers “why orange and purple?” — but state then lives only in motion (see costs above).

C · run vs sweep — one terminal colour (teal); motion-type + speed carry the state
argh · claude
⏵ Awaiting input
Alert
sweep · fastest
argh · claude
⏵ Waiting for input
Waiting · fresh
sweep · medium
argh · claude
⏵ Waiting · 6h
Waiting · stale
sweep · slow
argh · claude
◐ Thinking…
Working
marching ants
argh · claude
$ ▏
Idle
static

Read from how it moves: ants vs comet splits working from needs-you; sweep speed splits alert → fresh → stale. Colour is whatever the terminal is — the same waiting · sweep across five themes:

…and the colour is the terminal's own — same waiting · sweep, five themes
teal · agent
⏵ Waiting
teal
amber · agent
⏵ Waiting
amber
green · agent
⏵ Waiting
green
blue · agent
⏵ Waiting
blue
rose · agent
⏵ Waiting
rose
Contrast — the same teal theme, colour locked against each tile's own bg
dark theme
◐ working
Dark bg → accent lightened
light theme
◐ working
Light bg → accent darkened

The trade: most alive and theme-native of the set — but the costs are real. Moving light on every busy tile; no learnable fixed colour; reduced motion deletes the only channel; and a comet doesn’t shrink to a legible minimap marker — so C falls back to D there anyway (two languages).

D — Top bar (reads at any zoom)

scale-invariant — one shape canvas + minimap calmest, zero ambient motion least expressive range easy to miss peripherally

A thin status bar on the top edge — steady rust (working), violet pulse (fresh-waiting), sharper blink (alert), nothing (idle). The calmest of the four, and its edge is scale-invariance: the same legible shape on a full tile or a 40px minimap marker — the others can’t claim it (a glow or comet dissolves when tiny).

D · Top bar — top-edge status strip
argh · claude
$ ▏
Idle / done
argh · claude
◐ Thinking…
Working
argh · claude
⏵ Waiting for input
Waiting · fresh
argh · claude
⏵ Awaiting input
Alert (unread)

The minimap already wants this: today it paints a corner dot. Language D unifies both surfaces — the same top edge, scaled down:

D in the minimap — the same top-edge bar, scaled down (replaces today's corner dot)
teal ring = active · rust = working · violet pulse = waiting · dim/short violet = stale · blink = alert

Decay survives — dim the bar’s opacity and shrink its width as a waiter ages (the short faint marker above): a draining gauge. The honest cost vs Halo is range — a 2.5px bar carries less nuance, and a top edge is easier to miss peripherally. Richness traded for one-shape-everywhere coherence.

Open decisions

Independent of the language — a few open choices, each with a leaning. None settled.

Working color — rust, not teal

Focus already owns teal, so working can’t also be teal (teal-on-teal is ambiguous). Working = rust keeps three legible hues: teal = you’re here · rust = it’s busy · violet = it wants you. (For A/B/D; C uses the tile’s own colour.)

Focused + working — teal ring (focus) vs the working glow
argh · claude
◐ Thinking…
Rust glow — clearly distinct
argh · claude
◐ Thinking…
Teal glow — reads ambiguous

Motion = needs-you (working stays steady)

Motion is reserved for the rungs that want you; working is a steady hum. Open lever: fully static, or a barely-there breath so it still reads “alive” — as long as it stays quieter than fresh-waiting.

The decay curve & threshold

The active tile’s aura — mute or suppress

Mute to a whisper (still glimpse working/waiting on the tile you’re in) or fully suppress (focus ring only, calmest). Leaning: mute.

Idle / done · reduced motion · settings

If built — how it would wire in

Small and contained: no new state machinery, because the upstream classifiers already exist. The shape, whichever language wins:

No new state or timing — a CSS block, a small pure mapper + its socket, and the prop wiring on the tile and minimap.


Status: implemented in #1348 — refined Language C (run/sweep): working “runs” as marching ants, needs-you “sweeps” a comet whose speed is the urgency, alert throbs loudest, all in the tile’s own repo colour (one colour throughout — theme-derived hues, teal, and white were each tried and rejected). A stale waiter cools to a dim ember; a stale worker parks to nothing. The active tile is marked by an offset repo outline on the dark canvas (not a ring, glow, or chrome accent — those were tried and rejected), and prefers-reduced-motion freezes every aura to a static ring. The pure tileAura mapper + useTileAura socket reuse the dock’s existing classifiers; the minimap keeps its own marker. Shipped after a lens (lowy ⇄ hickey) + codex review gauntlet.