Development record
Terminal emission discipline — decoration only on interactive TTYs, machine streams undecorated, untrusted text always sanitised
Context
Until itd-112, abcd emitted no ANSI at all, and the termsafe primitive's posture — ESC never reaches a render path — was total: every C0 control in every rendered string is masked, because rendered text is so often repo-controlled (commit subjects, refs, prose from possibly hostile or archived repositories). A banner introduces the first sanctioned ANSI emission: trusted, compiled-in art and attribute sequences the renderer itself composes. Two rules were being carried as prose inside one intent — the TTY-only trigger and the termsafe carve-out — and they are the same boundary: what abcd may emit, onto which stream. A rule that binds every decorated surface that follows (itd-110 is next) belongs in the decision record, not in one intent's grill notes.
Decision
- Decoration renders only on an interactive TTY. ANSI colour,
half-block or shade-block art, and any other ornament may be written
only when stdout is an interactive terminal. Machine-consumed streams —
non-TTY stdout,
--jsonoutput, hook context injections, quiet modes — receive no decoration bytes, ever. A subcommand's output is machine-consumable by default; decoration is opt-in per surface at an explicit decision. - Only trusted-static content may carry ANSI. Compiled-in art (the
livery grids and their ANSI tables), attribute sequences the renderer
composes, and build-time-baked identity text are trusted. Everything
read at runtime — repo files, environment-derived strings, anything a
repository controls — remains untrusted and passes
termsafe.Sanitizebefore it may join a rendered line, exactly as before this ADR. - Colour honours the caller.
--no-colorand the NO_COLOR convention (present and non-empty) strip colour without stripping content; a decorated surface degrades loudly to its undecorated form, never to blank output.
Consequences
- The banner (itd-112) implements the first decorated surface under these rules and exports the colour ladder and TTY seam as the canonical primitives; later styled surfaces (itd-110 first) consume them rather than minting parallel copies (one-canonical-primitive).
- termsafe's contract is unchanged: this ADR narrows where its output may be dressed up, not what it masks.
- Tests for any decorated surface must include the machine-stream assertion: captured non-TTY output contains no escape bytes.
- Brief invariant 13 states the boundary; this ADR carries the rationale.
Amendment 2026-10-03
itd-110 (the grill interview's styled rendering, named above as the next decorated surface) is superseded by itd-2610030810350727 (one question layout); the decorated plain-Terminal surface is itd-2610030810370060, which builds on itd-112. The related intent is re-pointed; the decision itself is unchanged.
Frontmatter
| id | adr-49 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-08-22 |
.abcd/development/decisions/adrs/0049-terminal-emission-discipline.md
open on GitHub ↗ · commit history ↗
References2
- itd-112A bare abcd on an interactive terminal opens with a generated banner: the flag hoist in half-block colour on its painted panel, version beside it, tagline beneath — identity baked in at build time, full colour ladder, never a byte on a machine-consumed streamrelated
- itd-2610030810350727Every question abcd asks reads the same way, on a narrow window and a wide onerelated
Referenced by3
- itd-2610030810370060abcd's interviews run in a plain Terminal, with the same question layoutrelated
- itd-2610031214560142The board shows a product thinker what waits on them and what comes next, in a few plain linesrelated
- itd-2610031215002409A 'what next?' menu under the board offers the next step, in a Terminal and in a Claude Code sessionrelated
mentions6
- itd-110The grill interview renders with clear structure and colour instead of a wall of prose: each question visually separated, the decision point, options, and the recommended answer distinctly styled, progress through the design tree visible, so the person being grilled always knows what is being asked and where they are
- itd-144Every livery mark has a surface and earns it: the duckling, the lifeboat, and the two-by-two signal-flag icon are placed deliberately across abcd's surfaces instead of sitting as generated-but-unused assets. The lifeboat mark renders when disembark finishes packing a lifeboat and mirrors when embark unpacks one, so the direction of travel reads at a glance; the duckling appears as the mascot wherever the harness greets a human; the icon becomes the website and app icon. Settles the forge/web logo question itd-112 deferred as 'the existing logo stays for now'. Follows itd-133, which shipped the marks deliberately unwired, and consumes itd-112's colour ladder and emission discipline rather than minting its own.
- itd-2610031651058674abcd's check reports README parts that have gone stale, and writes nothing
- spc-41The bare-invocation banner: baked identity, half-block livery art, and the exported colour ladder
- spc-2610030911534855a-person-can-run-abcd-s-interviews-in-a-plain-terminal
- spc-2610031241482088connecting-a-model-service-from-inside-claude-code-works-out