<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title>Design doc: the `vibe tree` TUI visual language (lore for PROP-037 §2.2)</title>
  <status stage="doc" state="done" comment="B0 2026-07-24: FULL 2026-07-16; lore for PROP-037 S2.2, PROP-037 wins on conflict"/>
  <p p="1"><fact id="status-line" status="doc/done">_Status: **FULL** (2026-07-16, TREE-TUI-PLAN v0.2 Phase 1). This is **lore** — the
aesthetics, the why, the tables the eye reads. The **normative** surface (the REQs the code is
traceable to) lives in [PROP-037 §2.2](../modules/vibe-cli/PROP-037-tree-tui.xml#theme); this document
expands it. When the two disagree, PROP-037 wins._</fact></p>
  <p p="2"><fact id="genre-line" status="doc/done">**Genre:** design doc (lore). **Contract:** [PROP-037 §2.2](../modules/vibe-cli/PROP-037-tree-tui.xml#theme).</fact></p>
  <p p="3"><fact id="related" status="doc/done">**Related:** [the action-system design](action-system.xml); the settings meta-plan
(archived: `legacy-spec/terraforms/SETTINGS-SYSTEM-META-PLAN-v0.1.md`) — the active palette + tier are a user setting.</fact></p>
  <p p="4"><fact id="OWNER-VISION" status="doc/done">**Owner vision (2026-07-16):** the TUI must look **deliberately beautiful**, not like ASCII
scaffolding. Unicode box-drawing with rounded corners (`╭╮╰╯`); truecolor with a **formal,
data-driven palette system** (five palettes; the current Rosé Pine "cosmic violet" preserved
exactly); braille/block glyphs for indicators; **symbols only** for the primary UI (SSH/tmux
portable — no Sixel/Kitty), but `ratatui-image`-ready; a graceful degradation strategy
truecolor→256→16 / rounded→unicode→ASCII; windows and modals that read as **windows**, not as a
"frame dropped on the worst terminal."</fact></p>
  <section title="1. Principles (the aesthetics)">
    <list ordered="false" p="5">
      <item><fact id="PRIN-SYMBOLIC-PRECISION" status="doc/done">**Symbolic precision.** Every glyph is chosen, not defaulted. Box-drawing, braille, and block
  elements carry the meaning; ASCII (`+ - * # x .`) is reserved for the **last** tier of degradation,
  never the primary look.</fact></item>
      <item><fact id="PRIN-TRUECOLOR-FIRST" status="doc/done">**Truecolor first.** 24-bit RGB is the primary tier; colour is expressed through **semantic role
  tokens** resolved by a swappable `Palette`, never a raw `Color` at a call site.</fact></item>
      <item><fact id="PRIN-PORTABILITY" status="doc/done">**Portability.** The primary UI is glyphs + colour roles. It works through SSH, `tmux`, a Linux
  VT, and a truecolor terminal — degraded but never broken.</fact></item>
      <item><fact id="PRIN-GRACEFUL-DEGRADATION" status="doc/done">**Graceful degradation.** A worse terminal gets a recognisably simpler look, not a broken one. One
  `Theme` value is the single source of truth, **projected** onto the detected tier.</fact></item>
      <item><fact id="PRIN-WINDOWS-ARE-WINDOWS" status="doc/done">**Windows are windows.** A modal reads as a solid panel floating over the terminal (filled
  background + rounded frame + padding + optional shadow) — never as "an error box on the worst
  terminal."</fact></item>
      <item><fact id="PRIN-ONE-CSS" status="doc/done">**One CSS.** A restyle touches only the theme; no component logic, no control flow. The `Theme`
  is the "CSS" of the TUI (PROP-037 §1.4, §2.2).</fact></item>
    </list>
  </section>
  <section id="glyph-vocabulary" title="2. Glyph vocabulary">
    <p p="6"><fact id="glyph-lead" status="doc/done">Every glyph is a constant on the `Theme`, never a hardcoded string at a call site. The table below
is the **2026-07-16 capture**: the ASCII scaffolding of the day → the target vocabulary. The targets
shipped (`theme/glyphs.rs`, the TUI goldens, MT-02), so read the "Today" column as history, not as
current state:</fact></p>
    <table p="7">
      <tr>
        <td>Purpose</td>
        <td>Today</td>
        <td>Tier 3 target</td>
        <td>Tier 0 (ASCII)</td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-TREE" status="doc/done">Tree connectors</fact></td>
        <td><fact id="ROW-GLYPH-TREE-TODAY" status="doc/done">`│ ├ └ ─` (✓ Unicode)</fact></td>
        <td><fact id="ROW-GLYPH-TREE-TIER-3-TARGET" status="doc/done">`│ ├ └ ─`</fact></td>
        <td><fact id="ROW-GLYPH-TREE-TIER-0-ASCII" status="doc/done">`| -`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-FOLD" status="doc/done">Fold indicator</fact></td>
        <td><fact id="ROW-GLYPH-FOLD-TODAY" status="doc/done">`+` / `-` (ASCII ✗)</fact></td>
        <td><fact id="ROW-GLYPH-FOLD-TIER-3-TARGET" status="doc/done">`▾` / `▸` (or `▼`/`▶`)</fact></td>
        <td><fact id="ROW-GLYPH-FOLD-TIER-0-ASCII" status="doc/done">`+` / `-`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-DAG" status="doc/done">DAG re-occurrence</fact></td>
        <td><fact id="ROW-GLYPH-DAG-TODAY" status="doc/done">`(*)` (ASCII ✗)</fact></td>
        <td><fact id="ROW-GLYPH-DAG-TIER-3-TARGET" status="doc/done">`↩` (or `⊙`/`◆`)</fact></td>
        <td><fact id="ROW-GLYPH-DAG-TIER-0-ASCII" status="doc/done">`*`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-FLAGS" status="doc/done">Flags on / off</fact></td>
        <td><fact id="ROW-GLYPH-FLAGS-TODAY" status="doc/done">`x` / `.` (ASCII ✗)</fact></td>
        <td><fact id="ROW-GLYPH-FLAGS-TIER-3-TARGET" status="doc/done">`●` / `○` (or `✓`/`·`)</fact></td>
        <td><fact id="ROW-GLYPH-FLAGS-TIER-0-ASCII" status="doc/done">`x` / `.`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-ROUNDED" status="doc/done">Rounded frame</fact></td>
        <td><fact id="ROW-GLYPH-ROUNDED-TODAY" status="doc/done">`╭ ╮ ╰ ╯` (✓ already)</fact></td>
        <td><fact id="ROW-GLYPH-ROUNDED-TIER-3-TARGET" status="doc/done">`╭ ╮ ╰ ╯`</fact></td>
        <td><fact id="ROW-GLYPH-ROUNDED-TIER-0-ASCII" status="doc/done">`+ - |`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-SQUARE" status="doc/done">Square frame</fact></td>
        <td><fact id="ROW-GLYPH-SQUARE-TODAY" status="doc/done">—</fact></td>
        <td><fact id="ROW-GLYPH-SQUARE-TIER-3-TARGET" status="doc/done">`┌ ┐ └ ┘`</fact></td>
        <td><fact id="ROW-GLYPH-SQUARE-TIER-0-ASCII" status="doc/done">`+ - |`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-CLOSE" status="doc/done">Close affordance</fact></td>
        <td><fact id="ROW-GLYPH-CLOSE-TODAY" status="doc/done">(none)</fact></td>
        <td><fact id="ROW-GLYPH-CLOSE-TIER-3-TARGET" status="doc/done">`✕` (or `✖`)</fact></td>
        <td><fact id="ROW-GLYPH-CLOSE-TIER-0-ASCII" status="doc/done">`x`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-SEPARATOR" status="doc/done">Separator / rule</fact></td>
        <td><fact id="ROW-GLYPH-SEPARATOR-TODAY" status="doc/done">`—`</fact></td>
        <td><fact id="ROW-GLYPH-SEPARATOR-TIER-3-TARGET" status="doc/done">`─` / `╌`</fact></td>
        <td><fact id="ROW-GLYPH-SEPARATOR-TIER-0-ASCII" status="doc/done">`-`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-BAR" status="doc/done">Bar / indicator</fact></td>
        <td><fact id="ROW-GLYPH-BAR-TODAY" status="doc/done">(none)</fact></td>
        <td><fact id="ROW-GLYPH-BAR-TIER-3-TARGET" status="doc/done">`▁ ▂ ▃ ▄ ▅ ▆ ▇ █` / braille `⠁ … ⣿`</fact></td>
        <td><fact id="ROW-GLYPH-BAR-TIER-0-ASCII" status="doc/done">`#`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-GLYPH-SCROLL" status="doc/done">Scroll marker</fact></td>
        <td><fact id="ROW-GLYPH-SCROLL-TODAY" status="doc/done">(none)</fact></td>
        <td><fact id="ROW-GLYPH-SCROLL-TIER-3-TARGET" status="doc/done">`▲` / `▼`</fact></td>
        <td><fact id="ROW-GLYPH-SCROLL-TIER-0-ASCII" status="doc/done">`^` / `v`</fact></td>
      </tr>
    </table>
    <p p="8"><fact id="NO-ASCII-PRIMARY" status="doc/done">**Rule (normative in PROP-037 §2.2 `#glyph-vocabulary`):** in the primary UI (Tier ≥ 1) there are no
`+`/`-`/`*`/`#`/`x`/`.` used as *semantic* glyphs. ASCII lives only behind the Tier 0 fallback. Menu
checked/unchecked already uses `◉`/`○` (kept); copy status uses `✓`/`✗` (kept).</fact></p>
  </section>
  <section id="palette-tokens" title="3. Palette system">
    <p p="9"><fact id="palette-definition" status="doc/done">A `Palette` is a data-driven mapping from **semantic role tokens** → `Color`. The roles (the full
set; everything a component can ask for):</fact></p>
    <p p="10"><fact id="palette-roles-list" status="doc/done">`base` · `surface0` · `surface1` · `muted` · `subtext` · `text` · `accent` · `love` · `gold` ·
`foam` · `rose` · `selection` · `border` · `paper` · `button_on` · `button_off`.</fact></p>
    <p p="11"><fact id="role-semantics-lead" status="doc/done">The role semantics:</fact></p>
    <list ordered="false" p="12">
      <item><fact id="ROLE-BASE" status="doc/done">`base` — the terminal/window ground. The main tree keeps the user's terminal background (so a
  themed terminal shows through); modals paint a solid `base` panel.</fact></item>
      <item><fact id="ROLE-SURFACES" status="doc/done">`surface0` / `surface1` — raised surfaces (status bar chrome / a subtle fill, an off-flag).</fact></item>
      <item><fact id="ROLE-MUTED" status="doc/done">`muted` — borders, disabled text, "why disabled" reasons.</fact></item>
      <item><fact id="ROLE-SUBTEXT" status="doc/done">`subtext` — secondary foreground (footer descriptions, header values).</fact></item>
      <item><fact id="ROLE-TEXT" status="doc/done">`text` — primary foreground.</fact></item>
      <item><fact id="ROLE-ACCENT" status="doc/done">`accent` — the brand: selection highlight, titles, the active key label, the cosmic violet.</fact></item>
      <item><fact id="ROLE-TONAL-ACCENTS" status="doc/done">`love` / `gold` / `foam` / `rose` — the four tonal accents (warnings / badges &amp; highlights /
  static-load &amp; links / secondary badges).</fact></item>
      <item><fact id="ROLE-SELECTION" status="doc/done">`selection` — the highlighted row (composed: `accent` ground + `base` text).</fact></item>
      <item><fact id="ROLE-BORDER" status="doc/done">`border` — the window/frame stroke (usually `muted`).</fact></item>
      <item><fact id="ROLE-PAPER" status="doc/done">`paper` — the detail-card panel, distinct from the tree beneath (a "paper card": light panel, dark
  text on dark themes; inverted on light themes).</fact></item>
      <item><fact id="ROLE-BUTTONS" status="doc/done">`button_on` / `button_off` — a focused vs unfocused button.</fact></item>
    </list>
    <p p="13"><fact id="five-palettes-lead" status="doc/done">**Five built-in palettes** (the full set — a worked example of mass theming):</fact></p>
    <table p="14">
      <tr>
        <td>Palette</td>
        <td>Tone</td>
        <td>`base`</td>
        <td>`surface0`</td>
        <td>`surface1`</td>
        <td>`muted`</td>
        <td>`subtext`</td>
        <td>`text`</td>
        <td>`accent`</td>
        <td>`love`</td>
        <td>`gold`</td>
        <td>`foam`</td>
        <td>`rose`</td>
      </tr>
      <tr>
        <td><fact id="ROW-PAL-ROSE-PINE" status="doc/done">**Rosé Pine**</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-TONE" status="doc/done">dark (cosmic violet)</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-BASE" status="doc/done">`#191724`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-SURFACE0" status="doc/done">`#1f1d2e`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-SURFACE1" status="doc/done">`#26233a`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-MUTED" status="doc/done">`#6e6a86`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-SUBTEXT" status="doc/done">`#908caa`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-TEXT" status="doc/done">`#e0def4`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-ACCENT" status="doc/done">`#c4a7e7`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-LOVE" status="doc/done">`#eb6f92`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-GOLD" status="doc/done">`#f6c177`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-FOAM" status="doc/done">`#9ccfd8`</fact></td>
        <td><fact id="ROW-PAL-ROSE-PINE-ROSE" status="doc/done">`#ebbcba`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-PAL-MOCHA" status="doc/done">**Catppuccin Mocha**</fact></td>
        <td><fact id="ROW-PAL-MOCHA-TONE" status="doc/done">dark</fact></td>
        <td><fact id="ROW-PAL-MOCHA-BASE" status="doc/done">`#1e1e2e`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-SURFACE0" status="doc/done">`#313244`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-SURFACE1" status="doc/done">`#45475a`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-MUTED" status="doc/done">`#6c7086`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-SUBTEXT" status="doc/done">`#a6adc8`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-TEXT" status="doc/done">`#cdd6f4`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-ACCENT" status="doc/done">`#cba6f7`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-LOVE" status="doc/done">`#f38ba8`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-GOLD" status="doc/done">`#f9e2af`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-FOAM" status="doc/done">`#94e2d5`</fact></td>
        <td><fact id="ROW-PAL-MOCHA-ROSE" status="doc/done">`#f5c2e7`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-PAL-MACCHIATO" status="doc/done">**Catppuccin Macchiato**</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-TONE" status="doc/done">dark</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-BASE" status="doc/done">`#24273a`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-SURFACE0" status="doc/done">`#363a4f`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-SURFACE1" status="doc/done">`#494d64`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-MUTED" status="doc/done">`#6e738d`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-SUBTEXT" status="doc/done">`#a5adcb`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-TEXT" status="doc/done">`#cad3f5`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-ACCENT" status="doc/done">`#c6a0f6`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-LOVE" status="doc/done">`#ed8796`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-GOLD" status="doc/done">`#eed49f`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-FOAM" status="doc/done">`#8bd5ca`</fact></td>
        <td><fact id="ROW-PAL-MACCHIATO-ROSE" status="doc/done">`#f5bde6`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-PAL-FRAPPE" status="doc/done">**Catppuccin Frappé**</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-TONE" status="doc/done">dark</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-BASE" status="doc/done">`#303446`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-SURFACE0" status="doc/done">`#414559`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-SURFACE1" status="doc/done">`#51576d`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-MUTED" status="doc/done">`#737994`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-SUBTEXT" status="doc/done">`#a5adce`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-TEXT" status="doc/done">`#c6d0f5`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-ACCENT" status="doc/done">`#ca9ee6`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-LOVE" status="doc/done">`#e78284`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-GOLD" status="doc/done">`#e5c890`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-FOAM" status="doc/done">`#81c8be`</fact></td>
        <td><fact id="ROW-PAL-FRAPPE-ROSE" status="doc/done">`#f4b8e4`</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-PAL-LATTE" status="doc/done">**Catppuccin Latte**</fact></td>
        <td><fact id="ROW-PAL-LATTE-TONE" status="doc/done">**light**</fact></td>
        <td><fact id="ROW-PAL-LATTE-BASE" status="doc/done">`#eff1f5`</fact></td>
        <td><fact id="ROW-PAL-LATTE-SURFACE0" status="doc/done">`#ccd0da`</fact></td>
        <td><fact id="ROW-PAL-LATTE-SURFACE1" status="doc/done">`#bcc0cc`</fact></td>
        <td><fact id="ROW-PAL-LATTE-MUTED" status="doc/done">`#9ca0b0`</fact></td>
        <td><fact id="ROW-PAL-LATTE-SUBTEXT" status="doc/done">`#6c6f85`</fact></td>
        <td><fact id="ROW-PAL-LATTE-TEXT" status="doc/done">`#4c4f69`</fact></td>
        <td><fact id="ROW-PAL-LATTE-ACCENT" status="doc/done">`#8839ef`</fact></td>
        <td><fact id="ROW-PAL-LATTE-LOVE" status="doc/done">`#d20f39`</fact></td>
        <td><fact id="ROW-PAL-LATTE-GOLD" status="doc/done">`#df8e1d`</fact></td>
        <td><fact id="ROW-PAL-LATTE-FOAM" status="doc/done">`#179299`</fact></td>
        <td><fact id="ROW-PAL-LATTE-ROSE" status="doc/done">`#ea76cb`</fact></td>
      </tr>
    </table>
    <list ordered="false" p="15">
      <item><fact id="ROSE-PINE-LOCKED" status="doc/done">**Rosé Pine is canonical-locked**: the eleven `Color::Rgb` values already in
  `crates/vibe-cli/src/commands/tree/tui/theme.rs` become the Rosé Pine `Palette` cell **unchanged**
  (R8). A snapshot test pins them.</fact></item>
      <item><fact id="CATPPUCCIN-MAPPING" status="doc/done">**Catppuccin** values are the canonical Catppuccin palette (the four flavours; Latte is the light
  one). Mapping: `accent`←mauve, `love`←red, `gold`←yellow, `foam`←teal, `rose`←pink, `muted`←overlay0,
  `subtext`←subtext0.</fact></item>
      <item><fact id="LIGHT-DARK-AWARENESS" status="doc/done">**Light/dark-awareness:** Latte is the light reference. A `Palette` carries an `is_light: bool`
  flag; the `paper` role and the `selection` composition invert against it (a light theme's "paper"
  card is a *dark* inset, a dark text on a light ground, etc.). The derived roles:</fact></item>
      <item><fact id="DERIVED-SELECTION" status="doc/done">`selection` = `accent` ground + `base` text (bold) — high-contrast on every palette.</fact></item>
      <item><fact id="DERIVED-BORDER" status="doc/done">`border` = `muted`.</fact></item>
      <item><fact id="DERIVED-PAPER" status="doc/done">`paper` = `surface0` (raised, distinct from the tree ground); the detail-card text stays `text`.</fact></item>
      <item><fact id="DERIVED-BUTTONS" status="doc/done">`button_on` = `accent` (focused), `button_off` = `surface1`.</fact></item>
    </list>
    <p p="16"><fact id="palette-is-setting" status="doc/done">The active palette is a `Model` field; through the settings system (Шаг 2) it persists across
L1/L2/L3 and is overridable at the CLI/env.</fact></p>
  </section>
  <section id="rendering-tiers" title="4. Rendering tiers + degradation">
    <table p="17">
      <tr>
        <td>Tier</td>
        <td>Condition</td>
        <td>Palette</td>
        <td>Frames</td>
        <td>Indicators</td>
      </tr>
      <tr>
        <td><fact id="ROW-TIER-3" status="doc/done">**3**</fact></td>
        <td><fact id="ROW-TIER-3-CONDITION" status="doc/done">`$COLORTERM` ∈ {`truecolor`,`24bit`}</fact></td>
        <td><fact id="ROW-TIER-3-PALETTE" status="doc/done">full 24-bit RGB</fact></td>
        <td><fact id="ROW-TIER-3-FRAMES" status="doc/done">rounded `╭╮╰╯`</fact></td>
        <td><fact id="ROW-TIER-3-INDICATORS" status="doc/done">braille / blocks</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-TIER-2" status="doc/done">**2**</fact></td>
        <td><fact id="ROW-TIER-2-CONDITION" status="doc/done">256-colour (`$TERM` contains `256`)</fact></td>
        <td><fact id="ROW-TIER-2-PALETTE" status="doc/done">palette quantised to the 6×6×6 cube</fact></td>
        <td><fact id="ROW-TIER-2-FRAMES" status="doc/done">rounded</fact></td>
        <td><fact id="ROW-TIER-2-INDICATORS" status="doc/done">blocks (8)</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-TIER-1" status="doc/done">**1**</fact></td>
        <td><fact id="ROW-TIER-1-CONDITION" status="doc/done">16-colour ANSI</fact></td>
        <td><fact id="ROW-TIER-1-PALETTE" status="doc/done">ANSI role mapping</fact></td>
        <td><fact id="ROW-TIER-1-FRAMES" status="doc/done">rounded (if supported) / square `┌┐└┘`</fact></td>
        <td><fact id="ROW-TIER-1-INDICATORS" status="doc/done">blocks (8)</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-TIER-0" status="doc/done">**0**</fact></td>
        <td><fact id="ROW-TIER-0-CONDITION" status="doc/done">explicitly dumb (`TERM=linux` / `dumb`)</fact></td>
        <td><fact id="ROW-TIER-0-PALETTE" status="doc/done">ANSI mono</fact></td>
        <td><fact id="ROW-TIER-0-FRAMES" status="doc/done">ASCII `+-\|`</fact></td>
        <td><fact id="ROW-TIER-0-INDICATORS" status="doc/done">`#`</fact></td>
      </tr>
    </table>
    <p p="18"><fact id="TIER-DETECTION" status="doc/done">**Detection (normative in PROP-037 §2.2 `#rendering-tiers`):** a **pure function** over the
environment — `detect_tier(colorterm: Option&lt;&amp;str&gt;, term: Option&lt;&amp;str&gt;) -&gt; Tier`. `crossterm` exposes
no colour-count API, so detection is env-driven (`$COLORTERM` first, then `$TERM`); the TUI reads the
env once, at launch, in a sanctioned spot, and feeds the values in. The **default is Tier 3** — a
modern terminal is assumed truecolour even when it does not advertise the capability via env
(notably on Windows); the lower tiers are the **fallback** (the degradation path), reached only when
the environment explicitly advertises a lower capability (a 256-colour `TERM`, or an explicitly dumb
`TERM=linux`/`dumb`). The detected tier is overridable through the settings system.</fact></p>
    <p p="19"><fact id="DEGRADATION-PROJECTION" status="doc/done">**Degradation = projection.** A `Theme` is built for Tier 3 (the full palette + rounded + braille),
then **projected** onto the detected tier: a 256-colour terminal gets each role quantised to the
nearest 6×6×6 cube colour; a 16-colour terminal gets the role mapped to one of the eight ANSI colours;
Tier 0 falls back to ASCII frames and `#` indicators. One source of truth (`Theme`), many projections.</fact></p>
  </section>
  <section id="window-aesthetics" title="5. Window / modal aesthetics">
    <p p="20"><fact id="window-composition-lead" status="doc/done">A window is not a fallback. The composition (the `ui::Window` component, PROP-037 §2.3):</fact></p>
    <list ordered="false" p="21">
      <item><fact id="WIN-SOLID-PANEL" status="doc/done">a **solid panel** background (filled `base`), floating over the terminal;</fact></item>
      <item><fact id="WIN-ROUNDED-FRAME" status="doc/done">a **rounded frame** (`╭╮╰╯` at Tier ≥ 2; the frame stroke is `border` = `muted`);</fact></item>
      <item><fact id="WIN-TITLE-CHIP" status="doc/done">a **title chip** — the window title rendered as a badge in the `accent` colour, not bare text;</fact></item>
      <item><fact id="WIN-PADDING-SHADOW" status="doc/done">**padding** inside the frame; an optional **shadow** (a low-alpha `▓`/`▒` band, or a 1-char offset)
  so the panel reads as raised;</fact></item>
      <item><fact id="WIN-CLOSE-AFFORDANCE" status="doc/done">a `[✕]` **close affordance** in the top-right.</fact></item>
    </list>
    <p p="22"><fact id="WINDOW-AT-TIER-1" status="doc/done">Even at Tier 1 the window reads as a window — the rounded/square frame + filled panel + title chip
carry the "floating panel" reading without truecolor. The depth-2 modal stack (copy-settings →
file-path, PROP-037 §6, §10.5) draws each lower layer as a dimmed backdrop; the top window is the
only one that takes input.</fact></p>
  </section>
  <section id="spacing-rhythm" title="6. Spacing &amp; rhythm">
    <p p="23"><fact id="spacing-vocabulary-lead" status="doc/done">A beautiful window is mostly **empty space used well**. The complaint that turns a "window" back
into "an error box on the worst terminal" is content jammed into a corner: a message flush against
the frame, a row of hints spilling off the left edge with no centre. The fix is a spacing
vocabulary held in three constants, so a re-space is one edit (the normative surface is
[PROP-037 §2.2.5](../modules/vibe-cli/PROP-037-tree-tui.xml#spacing)):</fact></p>
    <list ordered="false" p="24">
      <item><fact id="SPACE-INTERIOR-PADDING" status="doc/done">**Interior padding (`PAD_X` = 2, `PAD_Y` = 1).** Every window frame holds its content off the
  border — two cells left and right, one row top and bottom. The body of a dialog floats: a blank
  row under the title chip, a blank row above the base, clear margins at the sides. This is what
  makes a `╭╮╰╯` frame read as a *raised panel* and not a box drawn around text. `ui::inner_pad` is
  the one helper; a dialog sizes its window to leave room for the padding, then lays its content
  into `inner_pad(inner)`.</fact></item>
      <item><fact id="SPACE-GROUP-GUTTER" status="doc/done">**Group gutter (`GUTTER` = 1).** A radio option inside a `Group` frame is inset one cell off the
  stroke, and its selection highlight bar is inset with it — so the `●`/`○` mark and the accent bar
  sit *inside* the group, never welded to its border. Nested frames (a window, then a group inside
  it) each keep their own breathing room, so the eye separates the levels.</fact></item>
      <item><fact id="SPACE-RHYTHM-CENTRING" status="doc/done">**Rhythm &amp; centring.** Structure is read from separation: framed groups get a blank row between
  them; an inline run of hints gets a `•` separator with a space each side; and a row of controls
  is **centred in its area**. A left-jammed row reads as debug output — a website footer that slid
  to the left margin. The footer is the worked example: **two centred rows**, the F-keys above and
  the navigation below, each centred under the screen with `•`-separated rhythm.</fact></item>
    </list>
    <p p="25"><fact id="AIR-RULE" status="doc/done">The rule in one line: **content floats inside the frame with air on every side, and multi-element
rows are centred.** Air is not wasted space — it is the difference between a UI and a dump.</fact></p>
  </section>
  <section id="image-ready" title="7. ratatui-image readiness">
    <p p="26"><fact id="IMAGE-READINESS" status="doc/done">The primary UI is glyphs (portable). But the structure is **ready** for `ratatui-image` (future
package-preview images, a designed info-card image): placeholder slots and reserved image areas in
the layout, behind a capability flag. Sixel/Kitty are **not** for the primary UI — only an optional
image raster when the terminal advertises support. Reserved, not built (PROP-037 §12 non-goal).</fact></p>
  </section>
  <section title="8. What becomes normative in PROP-037 §2.2">
    <p p="27"><fact id="normative-pointer" status="doc/done">§2.2 carries the anchors — all five exist: `#palette-tokens` (the role set + the five canonical palettes),
`#glyph-vocabulary` (the replacement table + the "no ASCII in the primary UI" rule),
`#rendering-tiers` (the tier table + the pure `detect_tier` + the projection law), `#window-aesthetics`
(the window composition + "a window is not a fallback"), `#spacing` (interior padding, the group
gutter, and centred rhythm). This lore explains *why*; the contract carries the *values* the code
traces to.</fact></p>
  </section>
</spec>
