01 / BRAND

## Brand mark & voice

The product coordinates multi-session Claude Code workflows via Temporal. Musical vocabulary — conductor, player, ensemble, cue, tempo — is the source of the visual motif. A pendulum-and-triangle metronome is the primary mark; the quarter note ♩ is the in-product glyph for user input.

▁ ▕ ▏ ▁▁▕ ▏▁▁ ▁▁▁▕ ▏▁▁▁ ▁▁▁▁▕ ▏▁▁▁▁ ▁▁▁▁▁◉▁▁▁▁▁ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
agent-tempo
Multi-session orchestration via Temporal
v0.26

#### Voice

Direct, technical, musician-adjacent. Avoid jargon for its own sake but use the ensemble vocabulary consistently — a session is a "player", a terminated session is "gone", a held player is "released".

#### Primary glyph

The quarter note ♩ (U+2669) fronts the user's input prompt and every out-bound message. It is the only decorative glyph allowed on bare text lines.

#### Motif restraint

Musical glyphs appear in exactly three places: the splash metronome, the prompt indicator, and the conductor star ★. Do not introduce new musical symbols.

02 / COLOR

## Color tokens

Thirteen named colors cover the entire TUI. Warm cream foreground (#FAF3EE) on a near-black warm-cool neutral (#0A0E12), with a terracotta accent (#E07A5F). Status colors are pulled from the pastel-terminal family so they sit comfortably with the accent instead of fighting it.

### Surface & structure

bg
#0A0E12
Primary canvas. All TUI views sit on this.
bgTitle
#141820
TitleBar / pinned chrome tint. Slightly lifted surface.
inputBg
#1A1F28
Out-bound message row background. Signals the user's own voice.
border
#2A3140
Hairline dividers and panel edges.

### Foreground

text
#FAF3EE
Default foreground. Warm cream, not pure white.
textMuted
#9CA3AF
Secondary copy. Player part lines, third-party bodies.
dim
#6B7280
Metadata, timestamps, separators (·), hint text.
muted
#3D4555
Disabled state, version strings, placeholder shade.

### Accent & signal

accent
#E07A5F · terracotta
Brand mark, selected palette item, prompt glyph, inbound headers.
success
#81C784
Connected status, active phase, ✓ markers.
warning
#F2CC8F
Disconnected/blocked phase, "No conductor", conductor star ★.
error
#EF5350
Connection failures, hard errors, destructive-action confirms.
NO_COLOR. When the NO_COLOR environment variable is set, every token resolves to undefined and Ink renders monochrome. Every component must remain legible without color — rely on glyphs, bold weight, and labels for meaning, never color alone.
03 / TYPOGRAPHY

## Typography

One family: the terminal's monospace. Hierarchy is expressed through bold, color tokens, and a small spacing vocabulary — there are no type-size options in a TUI. Spec the exact Ink <Text> props for each role.

agent-tempo — typography roles
agent-tempo 0.26.0 Ensemble: my-band (3 players) ● alice ★ active · main · tempo-soloist Building the React dashboard. ← alice 14:22 PR is up — needs a review when you have a moment. ♩ draft the release notes for v0.27█ Press / for commands · @player to DM

Role | Ink props | Token | Used for
--- | --- | --- | ---
Brand / title | bold color={accent} | accent | TitleBar left, Splash wordmark, section headings.
Body default | color={text} | text | Primary message bodies, input value.
Body secondary | color={textMuted} | textMuted | Player part lines, third-party message bodies.
Metadata | color={dim} | dim | Timestamps, route labels, separators (·), overlay hints.
Placeholder | color={muted} | muted | Disabled prompt, version strings, empty-state tips.
Selected | bold color={accent} | accent | Highlighted palette/overlay row (▸ indicator).
Inbound header | color={accent} | accent | Sender name on ← alice 14:22 rows.
Status ok | color={success} | success | ✓ Connected, active phase dot.
Status warn | color={warning} | warning | ⚠ No conductor, disconnected phase, conductor star.

No Unicode box-drawing for hierarchy. The TUI never draws boxes around things; structure is carried entirely by indentation, blank lines, and color. This keeps the Yoga node count under control (see §09 Performance) and survives narrow terminals.
04 / ICONOGRAPHY

## Glyphs & fallbacks

Every glyph has an ASCII fallback for SSH sessions and non-UTF-8 locales. Unicode is detected via WT_SESSION, LANG, and platform; when unavailable, statusIcons(false) substitutes safe ASCII. Never ship a glyph without a fallback.

### Status icons

Name | Unicode | ASCII | Code | Semantic use
--- | --- | --- | --- | ---
active | ● | * | U+25CF | Player is attached and processing.
stale | ○ | o | U+25CB | Idle — attached but awaiting input.
pending | ◔ | ~ | U+25D4 | Booting; not yet attached.
blocked | ◐ | ! | U+25D0 | Draining or detached — disconnected.
terminated | ✕ | x | U+2715 | Gone — workflow terminated.

### Role & affordance glyphs

Name | Unicode | ASCII | Code | Used on
--- | --- | --- | --- | ---
conductor | ★ | # | U+2605 | Trailing marker on the conductor player.
player | • | - | U+2022 | Generic bullet in ensemble lists.
selected | ▸ | > | U+25B8 | Selected row in palettes, overlays, pickers.
inbound | ← | <- | U+2190 | Header prefix on messages received.
arrow | → | -> | U+2192 | Conductor routing labels: alice → bob.
check | ✓ | + | U+2714 | Splash "Connected" indicator.
scroll-up | ↑ | ^ | U+2191 | "N more above" in scrolled lists.
scroll-dn | ↓ | v | U+2193 | "N more below" in scrolled lists.
prompt | ♩ | > | U+2669 | Input prompt glyph and out-bound message marker.
cursor | █ | _ | U+2588 | Blinking cursor block.

05 / LAYOUT

## Shell anatomy

The TUI has five pinned regions and one scrolling canvas. Minimum terminal size is 80×24; below 60×15 during resize a soft warning appears. Every component renders as a single <Text> root to keep Yoga under 10 nodes.

agent-tempo tui — shell anatomy · 120×26
agent-tempo my-band · 3 players ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ← alice 14:22 PR is up — needs a review when you have a moment. ← bob 14:23 I'll take the backend changes. ♩ sounds good — push when ready also let's sync on the migration plan ▸ /recruit spawn a new player /status show all players /schedule list schedules ♩ /re█ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── my-band · 3 players (2 active, 1 idle) · ● Connected

#### ① TitleBar

Pinned top. Brand on left (accent bold), context on right (dim). Background bgTitle. One row.

#### ② Scrollback

Native terminal scrollback. Completed messages flow into it as they scroll off the ConversationStream viewport.

#### ③ ConversationStream

Live feed. Fits as many recent messages as rows allow, works backward from newest. Out-bound rows get an inputBg tint.

#### ④ CommandPalette

Floating dropdown above the prompt when the user types / or @. Max 6 visible rows; scroll indicators above/below.

#### ⑤ PromptArea

Pinned bottom. ♩ prompt + current value + █ cursor. Placeholder hint when empty, completion hint line above.

#### ⑥ StatusBar

Pinned bottom-most. Single line: ensemble · player breakdown · schedule count · conductor warning · connection dot.

Spacing. All block separation is by blank lines — one blank between peers, two blanks between groups (player cards, message clusters). Indentation is two or three spaces; never tabs. A 3-space indent (INDENT) aligns continuation lines with message-header glyphs.
06 / COMPONENTS

## Component inventory

Each component is documented as a rendered mock plus the exact tokens it consumes. Components live in src/tui/components/ and must render as a single <Text> root (see §09).

### TitleBar · pinned top

TitleBar
agent-tempo my-band · 3 players

Single-row banner. Brand stays hard-left at 2-space inset; right-hand context is padded to the right edge via calculated spaces — never a flex <Box>.

Tokens: bgTitle, accent (brand), dim (context).

### StatusBar · pinned bottom

StatusBar
my-band · 3 players (2 active, 1 idle) · 1 schedule · ⚠ No conductor · ● Connected

Comma-separated phase breakdown + schedule count + conductor warning + connection dot. Separators are always · (U+00B7) in dim. gone players never appear here.

Phase buckets: active, idle, disconnected, pending. See §07 for the full mapping.

### StatusOverlay · dismissible

/status
Ensemble: my-band (3 players) ● alice ★ active · main · tempo-conductor Coordinating frontend / backend work. ○ bob idle · feat/auth · tempo-soloist Waiting on design review. ◐ carol disconnected · main · tempo-critic ↑↓ scroll, Esc to dismiss

Player cards: icon + name + conductor star, then dim detail line (phase · branch · type), then optional part in textMuted. Cards separated by a blank line. Hint row always in dim at the bottom.

Anatomy: glyph (phase color) → bold name → warning-colored ★ → detail row → muted part. Long lines wrap at a 4-space hanging indent.

### CommandPalette · floating dropdown

palette (typed "/re")
↑ 2 more above ▸ /recruit spawn a new player /recruit-conductor recruit a conductor for this ensemble /recall read a player's message history /release release held players /restart restart a player /resume resume a paused ensemble ↓ 4 more below

Max 6 visible rows. The selected row uses accent color + bold + a leading ▸. Non-selected rows use success green for the command token (a terminal convention for runnable things) and muted for description.

Tokens: accent, success, muted, dim.

### ConversationStream · message feed

Ensemble chat
← alice 14:22 PR is up — needs a review when you have a moment. alice → bob 14:23 ship the migration once tests pass ♩ sounds good — push when ready also let's sync on the migration plan

Three message roles are rendered distinctly:

- Inbound direct: ← sender HH:MM header, text body.
- Third-party (conductor routing): dim header with from → to HH:MM, textMuted body, capped at 4 lines with "… (N more lines)".
- Out-bound: ♩ prefix, text body, full-row inputBg tint.

### PromptArea · input

Prompt — idle & typing
recruit recruit-conductor recall ♩ /re█ ♩ █Type a message, /command, or @player… ♩ ...

Three states: typing (value + cursor, with optional tab-completion hint line above in muted), empty (cursor + placeholder hint in dim), disabled (... in muted) during wizards or splash.

Tokens: accent (♩, cursor, completion chosen), text, dim, muted.

### Splash · landing screen

agent-tempo tui
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⢀⣀⠀⠀⠀ ⠀⠀⢠⡏⢳⡀⠀⠀ ⠀⢠⠟⠀⠀⢻⡄⠀ ⠀⠟⠒⠒⠒⠒⠻⠀ agent-tempo Multi-session orchestration via Temporal v0.26.0 ✓ Connected ▸ ★ my-band (3 players) • backend-team (2 players) • experiments (0 players) + Create new ensemble ↑↓ to select, Enter to continue

Vertically centered. The metronome is a 3-frame braille animation at 30×12 cells (swings left → center → right every 300ms). Ensemble list shows up to 5 with up/down scroll indicators. Selected row uses ▸ in accent + bold.

States: connecting (spinner in warning), connected & loading (⟳ Discovering ensembles… in dim), connected & ready (list + "+ Create"), connected & empty (No ensembles running.).

### Wizard pattern · Recruit / Schedule / Create ensemble

/recruit — step 2 of 4
Recruit a new player · step 2 / 4 Name frontend-eng Type tempo-soloist█ Work dir /repos/my-app Task — ▸ tempo-soloist single-focus engineer tempo-composer architect / planner tempo-critic reviewer tempo-tuner performance / quality ↑↓ select · Enter to confirm · Esc to cancel

Wizards are a stack of labeled rows: current step label in accent bold, filled rows in text, not-yet-set rows in muted. Picker overlay hangs below the active row with the same ▸ selector.

Applies to: RecruitWizard, ScheduleWizard, CreateEnsembleWizard.

07 / STATE

## Attachment-phase matrix

Seven internal attachment phases collapse to five user-facing buckets. This table is the single source of truth for how a player looks at any moment — icon, color, and label. Maps directly onto src/tui/utils/format.ts.

Internal phase | Bucket | Icon | Icon bucket | Color | Shown in StatusBar?
--- | --- | --- | --- | --- | ---
attached | active | ● | active | success | yes
processing | active | ● | active | success | yes
awaiting | idle | ○ | stale | text | yes
draining | disconnected | ◐ | blocked | warning | yes
detached | disconnected | ◐ | blocked | warning | yes
booting | pending | ◔ | pending | dim | yes
gone | gone | ✕ | terminated | dim | no (terminal)
(unknown) | unknown | ◐ | blocked | text | no

Invariant. Never hand-pick a color or icon for a player — call phaseToColor(phase) and phaseToIconName(phase) then index statusIcons(). These lookup maps are frozen at module scope so repeated calls are allocation-free.
08 / ACCESSIBILITY

## Accessibility & resilience

The TUI must work on unstyled terminals, in SSH sessions without Unicode, and for users who disable color. None of these is a degraded mode — all three are supported paths.

#### NO_COLOR

When process.env.NO_COLOR is set, THEME resolves every key to undefined. Every meaning that lives in a color must also live in a glyph, label, or bold weight.

#### Unicode fallback

supportsUnicode() checks WT_SESSION, SSH env, LANG, and platform. ASCII fallbacks are declared next to every glyph in statusIcons() and the metronome drops to a single-character animation.

#### Minimum size

80 × 24 at launch or the process exits. A soft warning appears below 60 × 15 during live resize. All component layouts assume 80 columns of usable width.

#### Keyboard-only

Nothing in the TUI requires a pointing device. Every surface documents its keys in a footer line in dim color (e.g. ↑↓ scroll, Esc to dismiss).

#### Scroll indicators

Any windowed list (palette, status, ensembles) shows ↑ N more above / ↓ N more below in dim when content is clipped, never silently truncates.

#### Echo & latency

Out-bound messages render optimistically from local echo with the ♩ glyph, then deduplicate against the server feed within a 30s window. Network blips never look like lost keystrokes.

09 / PERFORMANCE

## Rendering rules

Ink is React + Yoga layout over the terminal. Every Yoga node costs a full-screen recompute on change, so the design system's structural rules are inseparable from its visual ones.

#### One Text root per component

Every component in src/tui/components/ renders one <Text> element at the top. Children must be nested <Text> or plain strings — these become ink-virtual-text (0 Yoga nodes). Avoid <Box> for layout in leaf components.

#### Precomputed padding

Right-alignment (TitleBar, StatusBar) uses calculated space strings based on process.stdout.columns, not flex. Vertical centering (Splash) pads with \n repetitions.

#### Frozen lookup maps

Phase → label/color/icon maps are Object.freezed at module scope. No per-frame object allocation in hot paths.

#### Uncontrolled inputs

PromptArea keeps its own local state. Parent never re-renders per keystroke; it receives the final value via ref or on submit.

#### Viewport-only rendering

ConversationStream walks backward from the newest message and stops when the line budget is filled. Older messages are committed to native scrollback via <Static>.

#### Stable callbacks

Every useInput callback reads a ref.current bag instead of closing over state. The callback identity never changes, which keeps Ink's key-binding registration stable.

10 / USAGE

## Do / don't

Short rules that keep additions consistent with the existing surface.

#### Do

- Route all color access through THEME.
- Use phase helpers (phaseToLabel, phaseToColor, phaseToIconName) for anything player-related.
- Ship ASCII fallbacks alongside any new glyph.
- Keep copy direct: "No conductor", "3 players (2 active, 1 idle)".
- Indent continuation lines to 3 spaces to align with the ♩ prompt.
- End scrollable surfaces with a dim key-hint line.

#### Don't

- Introduce new colors — if a role feels missing, reuse a token or open a design review.
- Draw Unicode borders around things; indentation + color is the whole layout system.
- Use emoji; the metronome vocabulary (♩ ★ • ▸) is the full kit.
- Rely on color alone to convey a state — always pair with a glyph or label.
- Add <Box> layout inside leaf components; keep it single-Text.
- Hardcode a phase check — phases change; the lookup maps don't.