--- name: dock-state description: >- The Dock's agent-state folds — one fenced source of truth in @kolu/terminal-vocab, the load-bearing order≠colour split, and the one "is something happening" question. Use when editing agentProjection or the canvas dock. --- ## The Dock's agent-state folds kolu's on-canvas **Dock** (`packages/client/src/canvas/dock`) renders agent state through fenced folds that live **once** in **`@kolu/terminal-vocab/agentProjection`**. - **One source of truth.** The agent-state vocabulary — how `AgentInfo['state']` folds to a coarse class — lives **once** in `agentProjection`, as `state satisfies never`-fenced folds: `agentUrgency` (needs-you ranking), `agentPaintClass` (pip/glyph colour), `attentionClass` + `attentionActive` (which attention list a terminal is in, and whether something is happening in it), `alertClass` (notify membership). **Never re-derive any of these** with a hand-rolled switch over the state literals — import the fold. A new state literal must force one decision, in the fenced fold, not silently route through a copy. - **The order≠colour split is load-bearing — don't collapse it.** A just-finished `waiting` agent **RANKS** idle (`agentUrgency`) but **PAINTS** `linger` (`agentPaintClass`) — the dimmed "it just finished" cue, distinct from the full-strength `awaiting` a genuinely blocked agent wears. Every surface keeps that decoupling. Since #2141 no dock surface SORTS at all (row order is structural), so the live form of the split is **select** by paint, **colour** by paint, and never re-derive either from metadata: the pinned needs-you strip takes its membership from `pip` (the attention class), the same fold its violet capsule paints from — see `statePipBind`'s `asking`, which names itself the ONE test for "blocked on you". `awaiting_user` (genuinely blocked) is what earns the strip; `waiting` (the post-turn lull) does not. `agentUrgency` remains the shared RANK vocabulary, still pinned by the dock parity test. - **"Is something happening" is ONE question with ONE answer.** `attentionActive(attentionClass(agent, finished), live)` decides a mark's MOTION and every count that summarises a scope — a host tab, a repo section header. Never fold your own notion of busy for a count: four such notions once coexisted, and every attention bug in PR #2019 was two of them disagreeing (a tab reading 2 beside three moving marks). It takes padi's EF2 finish verdict and raw byte motion as well as the agent state, so it lives beside — not inside — the state-only folds above. - **The folds' home is settled — don't repatriate them.** They live in the shared `@kolu/terminal-vocab` package even though the Dock is today their only paint consumer. That is history, not misplacement: the browser fleet mirror (`pulam-web`) they once also served **died with the pulam world** (the W2.3 burial deleted every `pulam*` package), and `padi-tui` is deliberately *not* a paint mirror (a thin uncoloured `status`/`watch`/`wait`/`create` CLI — no needs-you sort, no paint classes, no alert). Judging a fold's home by *today's* consumer count and "tidying" it into a kolu-local module is churn, not structure — and if a fleet mirror ever returns, it consumes these same folds, and the old two-surface sync contract (git history of this file) revives with it.