v0.2 · TERMINAL-FAITHFUL

## ·Vision

Every visual element on this page is something the terminal can paint: box-drawing characters, truecolor fg/bg, bold/dim/italic, and a single monospace font. No rounded corners, no shadows, no gradients — that's what makes it look like a terminal app instead of a web UI pretending to be one. Inspirations: k9s, btop, lazygit, glow.

## 01Inline shell

Two zones, no fullscreen. Scrollback on top — every card prints once and stays in the terminal's native scroll history (mouse wheel works, ⇧+drag selection works, copy-paste works). Composer block at the bottom — sticky via Ink's normal render loop, holds the live status row + input + hint. Nothing app-managed scrolls; the terminal's own scrollback is the source of truth.

No top chrome. A top status bar would be pushed off-screen the moment a card prints. Live state (mode / cost / cache / balance) sits in the bottom strip above the input — the only row Ink can pin reliably without alt-screen. Session metadata (workspace · branch · model) prints once at session start as the first row of scrollback and is allowed to scroll away.

      ◈ session-7  ·  main  ·  ~/projects/reasonix  ·  deepseek-chat

         YOU   · just now
        ↳ refactor the SKIP_DIRS list out of chunker.ts so directory_tree
          can reuse it

      ▎  REASONING    v4-pro   412 tok · 3 ¶                                  3.1s

      ▎  TASK   2 / 5  Refactor exclude config                       4.2s · running
      ▎
      ▎   ↳ Pull SKIP_DIRS / SKIP_FILES out of chunker.ts so directory_tree
      ▎     can reuse them.
      ▎
      ▎   ✓  read     src/index/semantic/chunker.ts    0.08s · 250 lines
      ▎   ✓  read     src/tools/filesystem.ts           0.07s · 712 lines
      ▎   ✓  write    src/index/config.ts               0.12s · created
      ▎   ▶  edit     src/tools/filesystem.ts           running…
      ▎   ○  verify  npm run typecheck && npm test     queued

      ▎ ▶  The change maps to three edits — I'll start with the config module,
      ▎    then the chunker, then wire it through the CLI command. Each step
      ▎    ships a passing test before moving on

    ─────────────────────────────────────────────────────────────────────────────────────
      ● auto   ·   session-7 · main   ·   $0.018 session   ·   ¥28.4   ·   cache 91%

    › type a message · / for commands · @ to attach a file

      ⏎ send  ·  ^J newline  ·  ↑↓ history  ·  esc abort  ·  ctrl-c quit

Fixed-width baseline: 88 cols. Cards reflow at narrower widths (down to ~60). Wider terminals get extra right-side gutter (we don't full-bleed past 100 cols).

## 02Palette

Truecolor (24-bit). Every modern terminal supports it — Windows Terminal, iTerm2, VS Code, kitty, alacritty, gnome-terminal, WezTerm. We're not back-porting to 16-color VTs.

### Surfacessolid backgrounds — never tint over user's terminal bg

--bg#0a0c10
--bg-input#0d1015
--bg-code#06080c
--bg-elev#11141a

### Accentsone color identifies a card type — never two on the same card

brand · sky#79c0ff
accent · purple#d2a8ff
violet#b395f5
ok · green#7ee787
warn · amber#f0b07d
err · coral#ff8b81

### Textfive-step grayscale, hierarchy via tone not size

fg-0#e6edf3 · titles
fg-1#c9d1d9 · body
fg-2#8b949e · sub
fg-3#6e7681 · meta
fg-4#484f58 · faint

## 03Glyph vocabulary

All Unicode, all renderable in JetBrains Mono / Cascadia Code / SF Mono / DejaVu Sans Mono. Color comes from the card type, not the glyph.

### Card types one glyph per card · always at column 0

◇
user msg
◆
reasoning
▶
running
▣
tool call
⊞
plan / todo
±
diff / edit
✖
error
⚠
warning
Σ
usage / cost
⌬
sub-agent
?
approval
⊙
search
⌑
memory

### Status / stateused inside cards — color carries the state

✓
success
▶
in progress
○
queued
✗
failed
!
blocked
▸
collapsed
▾
expanded
●
focused

### Structuralbox-drawing + block characters — terminal native

▎
accent bar
┌─┐│└┘
box (sharp)
█▓▒░
density
▰▱
progress
─
rule
·
separator

## 04Type weights

Terminal can't change font size. Hierarchy comes from weight, tone (fg-0 → fg-4), and style (italic). That's it. No sizes, no spacing tricks.

    Title           ·  bold + fg-0   ·  card titles, key names
    Body            ·  regular fg-1  ·  primary content text
    Sub / hint      ·  regular fg-2  ·  card subtitles, group labels
    Meta            ·  regular fg-3  ·  timing, counts, secondary
    Faint           ·  regular fg-4  ·  inactive, queued, dim borders
    Reasoning text  ·  italic + fg-3 ·  thinking blocks (always italicised)
    Accent          ·  bold + color  ·  glyphs, status pills, focus

## 05Cards · user message

No accent bar — the user's input is the conversational anchor, deserves a quieter treatment than agent activity. The YOU pill uses a neutral bg with muted fg so it reads as identification, not status.

CARD · .user

         YOU   · 2 min ago
        ↳ refactor the SKIP_DIRS list out of chunker.ts so directory_tree
          can reuse it

Body anchor uses the muted ↳ (fg-3) — the user card has no accent color to take from, so the anchor stays neutral.

## 06Cards · reasoning

No collapse / expand — TUI can't host interactive disclosure cleanly. The card adapts to content size in four tiers: streaming (live tail), settled-short (full body), settled-long (head + tail, middle elided), settled-XL (tail only — head dropped). The XL drop is deliberate: at >800 tok the opening is almost always restating the prompt the model has since moved past, while the conclusion carries the actionable synthesis. Header carries two bg-tinted pills — a REASONING section pill and a model pill (color = model class). Body is italic + dim so it never competes with primary content. The ↳ anchor marks the absolute beginning of the body — it appears only when that beginning is actually visible (so it's absent in streaming-with-overflow and in XL).

HEADER + BODY ANATOMY

      ▎  REASONING    v4-pro   412 tok · 3 ¶                                  3.1s
      ▎
      ▎   ↳ first line of body…
      ▎     subsequent lines align under the anchor's content column
           ↑ rule  ↑ section pill   ↑ model pill  ↑ counts                              ↑ duration

Two pills replace the old `◆` glyph + emoji prefix. Section pill is accent-purple-tinted bg with accent fg — one fixed style per card type. Model pill uses neutral bg-elev with fg color = model class: v4-flash sky-blue (cheap), v4-pro purple (premium), r1 violet (reasoner). Color carries the signal — no emoji needed. The ↳ body anchor is a project-wide convention: every card body section opens with one in the card's accent color.

STREAMING · .reasoning .streaming · live tail-3-lines while bytes arrive

      ▎  REASONING    r1   247 tok                                  1.2s · thinking…
      ▎
      ▎   ⋮  earlier lines scrolled past preview window
      ▎
      ▎     First weighing two approaches: should I patch the chunker to
      ▎     accept a config arg, or pull the constants up to a shared
      ▎     module… going with shared module since it's cleaner.

Tail-3-lines is a fixed window — newer lines push older lines into the dim ⋮ gutter mark. No ↳ anchor when overflow is active — the absolute body start has scrolled past, so labelling the visible top as "body begins" would be a lie. The ⋮ gutter is the indicator that content is scrolling past. Block cursor on the live edge. Token count ticks live; duration freezes on stream end. (When streaming starts and content is still under 3 lines, the ↳ appears normally on the absolute first line — it disappears the moment overflow kicks in.)

SETTLED · SHORT · .reasoning .settled .short · ≤ ~4 lines, render in full

      ▎  REASONING    v4-pro   87 tok · 1 ¶                                  1.2s
      ▎
      ▎   ↳ The user wants the storm guard to soften, not be removed. Plan: track
      ▎     first-vs-second storm per turn, only end the turn on the second one.
      ▎     Keep the warning copy plain.

No elision needed — full reasoning fits in the visual budget. Header gains N ¶ paragraph count once the stream settles.

SETTLED · LONG · .reasoning .settled .long · > 4 lines OR > 200 tok, head + tail paragraphs · middle elided

      ▎  REASONING    r1   412 tok · 3 ¶                                  3.1s
      ▎
      ▎   ↳ Two paths: replace the hardcoded list when config is set, or merge
      ▎     user values in. The first matches the explicit "config-driven" ask;
      ▎     the second is safer default.
      ▎
      ▎   ⋯  1 ¶ elided  ·  /reasoning last  ⋯
      ▎
      ▎     Files to touch: chunker.ts (drop constants, accept resolved config),
      ▎     filesystem.ts (drop its own copy), and the index command (load + pass).

First paragraph (thesis — "what I'm trying to do") and last paragraph (conclusion — "what I decided") always render. Middle paragraphs collapse to a single faint elision row that names the count and the slash command to retrieve the full body. The tail paragraph does NOT carry its own ↳ — the anchor only marks the absolute beginning of the body. Vertical budget stays bounded (~9 lines).

SETTLED · XL · .reasoning .settled .xl · > 800 tok OR any single ¶ > 6 lines · TAIL ONLY · head dropped

      ▎  REASONING    r1   2,847 tok · 7 ¶                                8.2s
      ▎
      ▎   ⋯  6 ¶ + ~2,540 tok scrolled past  ·  /reasoning last  to view full  ⋯
      ▎
      ▎     All suppressed paths are wired through and the auto-escalate label
      ▎     flips from "storm-broken" to "repeat-loop". Tests cover both the
      ▎     bad-args recovery and the second-storm fallback paths.

Tail-only — at this scale the head paragraph is almost always restating the prompt or weighing options the model has since moved past. The conclusion is the actionable summary, so we keep that and drop the rest. No ↳ anchor (the absolute body start isn't visible). The `⋯` elision row reports both paragraph count and approximate tokens scrolled past so the user can judge what they're not seeing — and the `/reasoning last` command brings up the full body in a pager when they need to. Vertical budget bounded at ~6 lines no matter how big the input. Triggered by total > 800 tok OR any single paragraph that wouldn't fit in 6 lines on its own.

EMPTY · .reasoning .none · model returned non-thinking response

      ▎  REASONING   no thinking — direct answer

When the producing model emits an empty `reasoning_content` (instruct-mode v4 on a simple prompt), surface a single dim line so the absence is explained, not silently missing. Section pill renders at reduced opacity to signal "card type was attempted but skipped". No body, no anchor. Clarifies "did the model skip thinking" vs. "is the panel broken".

## 07Cards · task / step

A multi-step work unit — wraps tool calls + reasoning under one header. The TASK section pill recolors with state: TASK running (brand), TASK done (ok), TASK failed (err). Step counter sits next to the pill so progress is visible without reading title text.

RUNNING · .task .running

      ▎  TASK   2 / 5  Refactor exclude config                       4.2s · running
      ▎
      ▎   ↳ Pull SKIP_DIRS / SKIP_FILES out of chunker.ts so directory_tree
      ▎     can reuse them.
      ▎
      ▎   ✓  read     src/index/semantic/chunker.ts    0.08s · 250 lines
      ▎   ✓  read     src/tools/filesystem.ts           0.07s · 712 lines
      ▎   ✓  write    src/index/config.ts               0.12s · created
      ▎   ▶  edit     src/tools/filesystem.ts           running…
      ▎   ○  verify  npm run typecheck && npm test     queued

Tool rows inside the task body use the path-pill style for filenames — bg-elev tint, fg-2, regular weight. Reads as data not chrome. Step counter format `N / M` sits where the title used to start, so glancing at any task row tells you progress at a glance.

DONE · .task .done

      ▎  TASK   1 / 5  Read chunker + filesystem             0.4s · 2 tools · done

Done tasks render as a single header row — body is omitted permanently (not collapsed-but-recallable). The user can recall what happened from the events log if needed.

FAILED · .task .failed

      ▎  TASK   4 / 5  Sandbox check                              0.2s · failed
      ▎
      ▎   ↳ ✓  read     src/sandbox/policy.ts      0.04s · 88 lines
      ▎     ✗  verify   policy.allows("rm")         denied

Failed tasks always render their body — the user needs the failure trail visible without recall. Anchor uses err color to match the card.

## 08Cards · tool call

Single tool invocation. Section pill TOOL uses info-cyan; the tool function name follows in info-bold; the path/target sits in a path-pill. Quick scan order: card type → which tool → what target → how it went.

QUICK · .tool .quick · single-row, no body — fast read-only ops

      ▎  TOOL   read_file   src/cli/ui/App.tsx           0.08s · 1224 lines · ok

Default state for fast read-only tools (read_file, search_content, directory_tree). No body, no recall — the result is summarized in the metadata strip. If the user wants the file content, the file's at the path; reasonix won't waste rows redrawing it.

PREVIEW · .tool .preview · short body when output ≤ 6 lines and worth surfacing

      ▎  TOOL   search_content   "stormBreaker"             0.21s · 4 hits · ok
      ▎
      ▎   ↳  src/repair/storm.ts :13  export class StormBreaker {
      ▎      src/repair/index.ts :33  private readonly storm: StormBreaker;
      ▎      src/repair/index.ts :38  this.storm = new StormBreaker(opts.stormWindow ?? 6, ...);
      ▎      tests/repair/storm.test.ts :2  import { StormBreaker } from "...";

Used for grep / search / list outputs where 4-6 hit lines is the answer. Body anchor on the first hit row; subsequent rows align under it.

SHELL · .tool .shell · long stdout · tail-window with overflow ⋮

      ▎  SHELL   run_command   npm run verify              23.4s · 1818 lines · exit 0
      ▎
      ▎   ⋮  1812 lines streamed past preview window
      ▎
      ▎   ↳ Test Files  115 passed (115)
      ▎          Tests  1818 passed (1818)
      ▎       Duration  23.81s

Long stdout follows the same tail-window pattern as streaming reasoning — tail-3-lines plus a ⋮ overflow gutter. The full stream is on disk in the events log; recall via `/output last` if needed.

FAILED · .tool .failed · err-tinted pill, error message inline

      ▎  TOOL   edit_file   src/loop.ts                    0.05s · failed
      ▎
      ▎   ↳ ✗ SEARCH text not found — model emitted `repairCalls` but file
      ▎       has `repairedCalls`. Suggest /retry with corrected name.

Failure cards switch the rule color to err and surface the error inline (not collapsed). Most useful info first: what kind of failure + the actionable hint.

## 09Cards · plan / todo

Ordered checklist. PLAN pill + plan title + progress fraction in the header. State per item via the bracket char + color: [✓] done · [▶] running · [ ] queued · [!] blocked · [✗] failed.

CARD · .plan

      ▎  PLAN   Migrate selection to terminal-native             5 / 7 done
      ▎
      ▎   ↳ [✓] 1. Snapshot current selection state
      ▎     [✓] 2. Drop @xterm/headless dep
      ▎     [✓] 3. Remove screen-mirror.ts
      ▎     [✓] 4. Strip LogSelection from log-frame.tsx
      ▎     [✓] 5. Strip drag handlers from App.tsx
      ▎     [▶] 6. Add /copy slash command          ← in progress
      ▎     [ ] 7. Update CHANGELOG & push

Body anchor on the first plan item; subsequent items align under it. The footer action row from the previous design is dropped — TUI doesn't host per-item interactive shortcuts cleanly. Plan revision happens via slash commands (`/plan revise`, `/plan skip 4`) which are discoverable through `/help`.

XL · .plan .xl · > 12 items · head + tail with middle elided, same as Reasoning XL

      ▎  PLAN   v0.24 release readiness                          8 / 18 done
      ▎
      ▎   ↳ [✓] 1. Bump version + CHANGELOG entry
      ▎     [✓] 2. Run full verify gate
      ▎     [▶] 3. Update docs/MIGRATION.md           ← in progress
      ▎
      ▎   ⋯  12 items elided  ·  /plan view  ⋯
      ▎
      ▎     [ ] 17. Tag release
      ▎     [ ] 18. Publish to npm

Same head + tail elision pattern as Reasoning XL — first 3 items + last 2 items + a middle elision row. The currently-running item is always promoted into the head window even if it would otherwise fall in the elided range, so progress stays visible.

## 10Cards · diff / edit

Per-file changeset. Removed lines coral-red foreground · added green foreground · context dim. No background tinting (fights user's terminal bg). Footer = apply / skip / reject.

CARD · .diff

      ▎ ± Edit  src/index/semantic/chunker.ts           +12 / -47     ▾
      ▎
      ▎     @@ -30,40 +30,5 @@
      ▎      /** Skip lists shared with src/tools/filesystem.ts */
      ▎     -const SKIP_DIRS: ReadonlySet<string> = new Set([
      ▎     -  "node_modules", ".git", ".hg",
      ▎     -  ... 18 more lines collapsed
      ▎     -]);
      ▎     +import { DEFAULT_INDEX_EXCLUDES } from "../config.js";
      ▎     +const SKIP_DIRS = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
      ▎
      ▎   [a] apply   [s] skip   [r] reject

## 11Cards · error

Failed tool call or hard error. Stack folded by default. Coral-red bar + glyph; the body stays at fg-1 except the actual error message line.

CARD · .error

      ▎ ✖ Error  tool call failed                                  2 retries  ▾
      ▎
      ▎   read_file  src/index/semantic/chunker.ts
      ▎
      ▎   ENOENT: no such file or directory, open
      ▎   '/usr/local/etc/secrets/api.key'
      ▎
      ▎   The agent attempted to read outside the sandbox root. Path was
      ▎   normalised but the absolute prefix put it outside.
      ▎
      ▎   ▸ stack trace
      ▎
      ▎   [r] retry   [s] skip

## 12Cards · warning

Non-fatal: degraded service, slow upstream, soft policy hit. No actions usually — informational.

CARD · .warn

      ▎ ⚠ MCP server slow                              notion · 8.4s elapsed  ▾
      ▎
      ▎   The notion server hasn't responded to tools/list in 8.4s.
      ▎   The session continues without it; reconnection on next turn.

## 13Cards · usage / cost

Per-turn meter with three tracks (prompt / reason / output) plus a session running total. Bars use density blocks █░ — terminal renders these natively.

CARD · .usage

      ▎ Σ Usage  turn 12                                  $0.0014 · 1.2s  ▾
      ▎
      ▎    prompt    ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░  41,238 / 1M  · 4.1%
      ▎    reason    ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  412
      ▎    output    ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  1,847
      ▎
      ▎    cache hit ██████████████████████████░░░  91.3%
      ▎
      ▎    session ⛁ $0.018  ·  balance ¥ 28.4   ≈ ¥0.10 / turn at this rate

## 14Cards · sub-agent

Forked agent runs in a nested mini-stream. Each nesting level adds another ▎ bar — depth is visually obvious without extra chrome.

CARD · .subagent

      ▎ ⌬ Sub-agent · code-reviewer                                  running  ▾
      ▎
      ▎   Task    review the diff in src/index/config.ts for safety
      ▎   Tools   read_file, search_content
      ▎
      ▎   ▸ sub-agent stream
      ▎   ▎  REASONING    v4-flash   134 tok · 2 ¶
      ▎   ▎ ▣ read_file  src/index/config.ts                  0.08s
      ▎   ▎ ▶ streaming response …

## 15Cards · approval prompt

Modal — cannot scroll past until resolved. Header band uses bg-elev + 3-cell amber left edge; body sits on default bg. The bg→default transition is the visual divider, no extra ruling required.

CARD · .approval

     ?  Approve · run_command                                                            awaiting

      The agent wants to run:

          $ rm -rf node_modules dist

      Working dir   /home/user/project
      Effect        removes 12,847 files (228 MB)

      ▸ allow once     run this command, ask again next time
        allow always   remember `rm -rf` for this project
        deny           skip; agent will pick an alternative

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ confirm  ·  esc cancel

## 16Cards · streaming response

Live text in progress. Brand-blue accent bar like a task card, but the body is bare prose — no glyph header, just a leading ▶ caret on the first line and a blinking cursor at the tail.

CARD · .streaming

      ▎ ▶  The change you described maps cleanly to the existing
      ▎    ResolvedIndexConfig structure. Three edits are needed:
      ▎
      ▎    1. src/index/config.ts: add the new excludePatterns field
      ▎    2. src/cli/ui/App.tsx: surface it in the Settings card
      ▎    3. tests/index-config.test.ts: cover the merge semantics
      ▎
      ▎    Want me to draft the diff?

## 17Cards · search results

Hit list grouped by file. Match terms inverse-highlighted (terminal native). Each row clickable — opens a tool-call card focused on that file:line.

CARD · .search

      ▎ ⊙ Search  "writeClipboard"           3 hits in 2 files · 0.04s     ▾
      ▎
      ▎   src/cli/ui/clipboard.ts
      ▎       15 │  export function writeClipboard(text: string): ClipboardWrite
      ▎
      ▎   src/cli/ui/App.tsx
      ▎       85 │  import { writeClipboard } from "./clipboard.js";
      ▎     1491 │      writeClipboard(text);
      ▎
      ▎   ↑↓ navigate   ⏎ open hit   [n] narrow…

## 18Cards · memory / context

What's currently in scope from persistent memory. Default collapsed — a one-line summary. Expanded breaks down by category (user / feedback / project / reference).

COLLAPSED · .memory

      ▎ ⌑ Context  ·  4 user · 2 feedback · 1 reference             ~1.2K tok  ▸

EXPANDED · .memory[open]

      ▎ ⌑ Context  ·  4 user · 2 feedback · 1 reference             ~1.2K tok  ▾
      ▎
      ▎   USER
      ▎   ◇ Reasonix maintainer · prefers terse Mandarin replies
      ▎   ◇ Windows Terminal + PowerShell · CNY/RMB balance
      ▎
      ▎   FEEDBACK
      ▎   ✦ No Co-Authored-By: Claude trailer in commits
      ▎   ✦ Comments document why, not chat history
      ▎
      ▎   REFERENCE
      ▎   → Linear "INGEST" project tracks pipeline bugs

## 19Composer · input states

The composer is the bottom-sticky input zone. One row of input + one row of hints. Pickers (`/`, `@`, history) overlay above the input row, never below — mouse / scroll never hides them.

### Empty / placeholderfirst row, no text yet

CARD · .composer .empty

    › type a message · / for commands · @ to attach a file

      ⏎ send  ·  ^J newline  ·  ↑↓ history  ·  esc abort  ·  ctrl-c quit

### Typingcursor at end of single line

CARD · .composer .typing

    › refactor the SKIP_DIRS list out of chunker.ts so directory_tree

      ⏎ send  ·  ^J newline  ·  ↑↓ history  ·  esc abort  ·  ctrl-c quit

### Multi-line^J inserts newline; continuations indent under the prompt glyph

CARD · .composer .multiline

    › refactor the SKIP_DIRS list out of chunker.ts
      so directory_tree can reuse it,
      also strip the duplicate from filesystem.ts

      ⏎ send  ·  ^J newline  ·  ↑↓ history  ·  esc abort  ·  ctrl-c quit

### History recall↑ pops a popover with prior turns; ↵ loads the highlighted entry into the input

POPOVER · .composer .history

     history  ·  12 / 47

        14 ·  3m  show the last failing tool call
        13 ·  8m  what's the cache hit rate today
      ▸ 12 · 14m  refactor the SKIP_DIRS list out of chunker.ts so directory_tree …
        11 · 22m  drop the screen-mirror module entirely
        10 ·  1h  why is the indexer skipping .gitignore'd dirs?

      ↑↓ pick  ·  ⏎ load  ·  esc cancel

    › refactor the SKIP_DIRS list out of chunker.ts so directory_tree can …

### Paste collapsedlarge clipboard payloads collapse to a chip; ^O expands into a separate panel

CARD · .composer .paste

    › here's the stack trace:
      ┌ 📋 pasted  142 lines · 4.8 KB  · stacktrace  ^O expand · ⌫ remove ┐
      what's going on?

      ⏎ send  ·  ^J newline  ·  ↑↓ history  ·  esc abort  ·  ctrl-c quit

### @ mention pickertyping `@` opens a file picker filtered by the substring after it

POPOVER · .composer .mention

     files  ·  "ui/log" · 8 matches

      ▸ src/cli/ui/log-frame.tsx                              1134 lines · ts
        src/cli/ui/log-rows.tsx                                613 lines · ts
        src/cli/ui/EventLog.tsx                                961 lines · ts
        src/cli/ui/LiveRows.tsx                                360 lines · ts
        … 4 more

      ↑↓ pick  ·  ⏎ insert  ·  esc cancel

    › why is @ui/log

### / command pickertyping `/` opens slash-command picker; descriptions are dim, names are fg-0

POPOVER · .composer .slash

     commands

      ▸ /cost     show cost & token usage for this turn
        /context  show what's currently in the prompt context
        /memory   view / edit persistent memory
        /diff     diff session changes vs HEAD
        /copy     copy last N rows to clipboard
        /init     generate CLAUDE.md from current repo
        /doctor   health check (api / index / workspace)
        /clear    clear the on-screen scrollback
        … 6 more

      ↑↓ pick  ·  ⏎ run  ·  esc cancel

    › /

### / arg pickercommands with required args open a second-stage picker

POPOVER · .composer .slasharg

     /copy  ·  pick range

      ▸ last 1     most recent card only
        last 5     last five cards
        last 10    last ten cards
        all        whole session
        custom…    type a number

      ↑↓ pick  ·  ⏎ run  ·  esc cancel

    › /copy

### ! shell modeleading `!` swaps the prompt to a shell; sends the line to a shell tool, not the model

CARD · .composer .bang

    $ git status

      shell mode  ·  ⏎ run  ·  esc back to chat  ·  output appears as a tool card above

### Abortedesc during a turn — the agent stops, the composer reopens with a faint hint

CARD · .composer .aborted

    ›

      turn aborted by user · esc again to clear · ⏎ to ask a follow-up

## 20Status row · live state

Single row pinned above the composer input. The only place live state can sit reliably without alt-screen — Ink redraws this row every frame, so it never scrolls away. Carries: mode pill · session id · running cost · balance · cache hit. Mockups below show the row + the input/hint underneath for context.

### Mode pillsone of: auto · ask · plan · edit; pill color reflects the action class

      ─────────────────────────────────────────────────────────────────────────────
      ● auto     ·   session-7 · main   ·   $0.018   ·   ¥28.4   ·   cache 91%

      ─────────────────────────────────────────────────────────────────────────────
      ◐ ask      ·   session-7 · main   ·   $0.018   ·   ¥28.4   ·   cache 91%

      ─────────────────────────────────────────────────────────────────────────────
      ⊞ plan     ·   session-7 · main   ·   $0.018   ·   ¥28.4   ·   cache 91%

      ─────────────────────────────────────────────────────────────────────────────
      ± edit     ·   session-7 · main   ·   $0.018   ·   ¥28.4   ·   cache 91%

### Network statesdot color = state; verbose text appears only when not green

      ─────────────────────────────────────────────────────────────────────────────
      ● auto · online             ·   session-7 · main   ·   $0.018   ·   cache 91%

      ─────────────────────────────────────────────────────────────────────────────
      ◌ auto · slow · 4.2s p95    ·   session-7 · main   ·   $0.018   ·   cache 91%

      ─────────────────────────────────────────────────────────────────────────────
      ✗ disconnect · retry 3/5    ·   session-7 · main   ·   $0.018   ·   cache 91%

      ─────────────────────────────────────────────────────────────────────────────
      ↻ reconnecting…             ·   session-7 · main   ·   $0.018   ·   cache 91%

### Auto-confirm countdownin auto mode after a tool emits an approval — countdown digit flashes brand, esc to cancel

      ─────────────────────────────────────────────────────────────────────────────
      ● auto   ·   approving in 3s · esc to interrupt   ·   cache 91%

### Live cost tickerturn cost on the left, session total on the right; balance ¥ shows when DeepSeek wallet is hooked

      ─────────────────────────────────────────────────────────────────────────────
      ● auto   ·   ▸ $0.0014 turn  ·  $0.0193 session  ·  ¥30.5  ·  cache 91%

### RecordingREC pill replaces the mode pill while a recording is being written

      ─────────────────────────────────────────────────────────────────────────────
      ●REC 1.4 MB · 142 evt   ·   → ~/.reasonix/recordings/2026-04-29.jsonl   ·   ^R stop  ·  ^P pause

## 21Modals · the full family

Every modal opens with a header band — a single bg-elev row with a 3-cell colored left edge that signals the action class (warn / accent / info / err / ok). Body sits on default bg below; the bg→default transition is the divider, so no full-box border is needed. Up/down picks, ⏎ confirms, esc cancels — always.

### Plan · confirmdrafted plan above; the user picks the disposition

CARD · .modal.plan-confirm

     ⊞  Approve plan                                                                  awaiting

      The agent has drafted a 5-step plan above.

      ▸ accept      run it now, in order
        refine      give the agent more guidance, draft a new plan
        revise      edit the plan inline before running
        reject      discard, agent will retry from scratch

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ confirm  ·  esc cancel

### Plan · refinefree-text guidance that goes back into the planner prompt

CARD · .modal.plan-refine

     ✎  Refine plan

      Tell the agent what to change about the plan above. Free text; the planner
      re-runs with this added as guidance.

      › skip step 4 — the sandbox check is overkill, just rely on the verify gate

      ─────────────────────────────────────────────────────────────────────────────
      ⏎ submit  ·  esc cancel

### Plan · revisestructural edit of the plan: skip / reorder / strike steps without retalking to the model

CARD · .modal.plan-revise

     ✎  Revise plan  ·  5 steps

         [✓] 1. Read chunker + filesystem
         [✓] 2. Drop @xterm/headless dep
         [s] 3. Remove screen-mirror.ts                              ← skipped
       ▸ [ ] 4. Strip drag handlers from App.tsx
         [ ] 5. Run verify gate

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ focus  ·  space toggle skip  ·  k/j move  ·  ⏎ accept  ·  esc cancel

### Plan · checkpointsnapshot the plan + workspace before running so abort can resume cleanly

CARD · .modal.plan-checkpoint

     ⛁  Save checkpoint

      Snapshot current plan + workspace before running?

      If something goes wrong mid-run, you can resume the plan from this exact
      state instead of starting over.

      ▸ save & continue   recommended for plans > 3 steps
        skip              run without snapshotting

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ confirm  ·  esc cancel

### Workspace · switchopening a different folder mid-session — surfaces unsaved-plan risk

CARD · .modal.workspace

     ?  Switch workspace

      current   ~/projects/reasonix
      new       ~/work/customer-portal

      Switching ends the current session. Plan progress (3 of 7 done) will be
      archived; you can replay it later via /replay.

      ▸ open & archive plan     recommended
        open & discard plan     throw away the snapshot
        cancel                  stay in this workspace

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ confirm  ·  esc cancel

### Shelltighter than the generic approval card — shows just the command + 3-way choice

CARD · .modal.shell

     ?  Shell command

          $ npm run verify

      cwd        ~/projects/reasonix
      timeout    120s

      ▸ allow once      run this command, ask again next time
        allow always    remember `npm run verify` for this project
        deny            skip; agent will pick an alternative

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ confirm  ·  esc cancel

### Edit · multi-filebatch confirmation when several files change in one turn

CARD · .modal.edit

     ±  Apply 3 edits                                                                  awaiting

      src/index/config.ts                          +84 /   -0   created
      src/index/semantic/chunker.ts                +12 /  -47
      src/tools/filesystem.ts                       +4 /  -28

      ▸ apply all          land all three, run verify next
        review one by one  step through each diff card with [a/s/r]
        reject all         discard everything; agent will revise

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ confirm  ·  esc cancel

### Deny w/ reasonafter pressing "deny" on any approval — optional free-text feeds the next attempt

CARD · .modal.deny

     ✗  Deny — provide context                                                          optional

      Tell the agent why you denied this. The next attempt will see your reason
      as additional context.

      › that command would clobber my git stash — try with `git stash --keep-index` instead

      ─────────────────────────────────────────────────────────────────────────────
      ⏎ submit  ·  esc skip (deny without reason)

### Generic choicefor ambiguous prompts the agent can't resolve on its own; info-blue border (non-destructive)

CARD · .modal.choice

     ?  Continue with this approach?

      My confidence in step 4 is low — the policy file format may have changed
      in a way I can't verify without running it.

      ▸ continue                  trust me, run it
        try a different approach  drop this branch, plan again
        abort                     stop here, give me the partial result

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ confirm  ·  esc cancel

## 22Onboarding · welcome / setup / picker

Pre-session screens. These print to scrollback like everything else; once dismissed they don't come back unless the user explicitly opens them again.

### Welcome bannerfirst launch in a workspace · single-print, then the empty session screen

SCREEN · .welcome

                      ╔═══════════════════════════════════╗
                      ║                                   ║
                      ║            ◈  REASONIX            ║
                      ║                                   ║
                      ║      DeepSeek-native coding agent ║
                      ║      cache-first · flash-first    ║
                      ║                                   ║
                      ╚═══════════════════════════════════╝

                    type a message to start your session

                    /help   ·   /init   ·   /memory   ·   /cost

### Setup wizardlaunched on first run or via `reasonix setup`; key/value rows, ↑↓ between fields

SCREEN · .setup

     ◈ REASONIX · setup

      Provider           ▸ DeepSeek      Anthropic      OpenAI      ↩ pick

      Model                deepseek-chat   ·  tab to cycle

      API key              ••••••••••••••••••••••••••••  ✓ verified

      Default mode         ● auto     ◐ ask     ⊞ plan     space toggle

      Telemetry          ▸ on (anonymous)     off

      Workspace root       ~/projects/reasonix

      Index database       ✓ ~/.reasonix/index/reasonix.db  12 days fresh

      ─────────────────────────────────────────────────────────────────────────
      ↑↓ field  ·  ⏎ next  ·  esc back  ·  ctrl-s save & exit

### Session pickerresume an old session or start fresh; sorted newest-first

SCREEN · .session-picker

     ◈ REASONIX · pick a session  ·  ~/projects/reasonix

      ▸ session-7  · main · refactor exclude config             2 min ago   18 turns · $0.18
        session-6  · main · TUI redesign                        yesterday   44 turns · $0.62
        session-5  · feat-bg · MCP probe                        2 days ago   7 turns · $0.04
        session-4  · main · v0.13 row pipeline                  3 days ago  93 turns · $1.42
        … 12 more

      ─────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ open  ·  [n] new session  ·  [d] delete  ·  [r] rename  ·  esc quit

## 23Replay & Record

Replay re-renders an old session's events.jsonl in card form. Record snapshots the live event stream for later replay or as bug repro material.

### Replay timelineread-only; bottom strip controls playback like a video scrubber

SCREEN · .replay

     ◈ REASONIX · replay  ·  session-6 · main · "TUI redesign"      ⏸ 12 / 44 turns

      ◇ you  · 14:22:11
        abandon fullscreen mode, switch to inline scrollback

      ▎  REASONING    v4-pro   587 tok · 4 ¶                                  4.7s

      ▎ ⊞ Plan · 5 steps                                       5 of 5 done  ▾
      ▎    [✓] 1. Snapshot current selection state
      ▎    [✓] 2. Drop @xterm/headless dep
      ▎    …

      ─────────────────────────────────────────────────────────────────────────────────────
      ⏮ first   ⏪ -10   ◀ -1    ⏯ play    ▶ +1   ⏩ +10   ⏭ last      speed 1×   ·   [q] quit

### Recordwhile recording, the REC pill replaces the mode pill in the bottom status row (see §20 · Recording)

SCREEN · .record

      … normal session cards stream as usual above …

      ─────────────────────────────────────────────────────────────────────────────
      ●REC 1.4 MB · 142 evt   ·   → ~/.reasonix/recordings/2026-04-29.jsonl   ·   ^R stop · ^P pause

    › type a message · / for commands · @ to attach a file

### Stats panelreplay-only overlay (or `/stats` in live) — turn-by-turn drill-down

SCREEN · .stats

     Σ Stats  · session-6 · 44 turns · 1h 12m

      turn  role       tokens (in / out)   tools  cache      cost       elapsed
      ────  ─────────  ──────────────────  ─────  ─────────  ──────────  ────────
        1   user          412 /     0      ·         ·           ·          ·
        2   assistant   37,121 / 1,847       3      91.2%      $0.0014     1.2s
        3   user           12 /     0      ·         ·           ·          ·
        4   assistant   38,003 / 2,402       5      93.7%      $0.0016     1.6s
        …
      ────  ─────────  ──────────────────  ─────  ─────────  ──────────  ────────
      total            1,612,840 / 84,202   142     91.8%      $0.62     1h 12m

      ↑↓ pick row  ·  ⏎ jump in replay  ·  q quit

## 24MCP · server browser

Reasonix talks to MCP servers (notion / linear / github / fs / …). The browser is a focused panel — list of attached servers, their tool surface, last health-check.

SCREEN · .mcp-browse

     ◈ MCP browser  ·  ~/.reasonix/mcp.json · 4 servers

      ▸  notion          ● healthy · 142ms      12 tools · 8 resources · 0 prompts
                         tools/list      tools/call     resources/list     prompts/list

         linear          ◌ slow · 4.2s p95      7 tools · 3 resources · 0 prompts

         github          ● healthy · 88ms       22 tools · 0 resources · 4 prompts

         fs-local        ✗ handshake failed · ENOENT     ─

      ─────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ inspect tools  ·  [r] reconnect  ·  [d] disable  ·  esc quit

## 25States · empty / streaming / nesting / banners

The variants below are not new card types — they're alternate states of cards already covered, plus a few session-level overlays.

### Empty sessionafter /clear or first launch — the only place we volunteer slash-command hints

                      ◈   nothing yet — say something

                      /help     commands & shortcuts
                      /init     generate CLAUDE.md from this repo
                      /memory   view persistent memory
                      /cost     token usage & spend so far

### Streaming reasoninglive tail-3-lines while bytes arrive; settles into one of the three sized variants in §06 once the stream ends

      ▎  REASONING    r1   247 tok                                  1.2s · thinking…
      ▎
      ▎   ⋮  earlier lines scrolled past preview window
      ▎
      ▎   Two paths: replace the hardcoded list when config is set, or merge
      ▎   user values in. The first matches the explicit "config-driven" ask;
      ▎   the second is safer default. Going with the first since the user's

### Long stdout streaminge.g. npm install — tail mode, auto-scroll, ⏯ pauses to let you read

      ▎ ▣ run_command  npm install                          12.4s · streaming…     ▾
      ▎
      ▎   $ npm install
      ▎   ⠋ resolving (1542 packages)
      ▎   added react@18.3.1
      ▎   added react-dom@18.3.1
      ▎   added ink@5.1.0
      ▎   added ink-text-input@6.0.0
      ▎   …
      ▎   [tail · auto-scroll · ⏯ to pause · ⌫ collapse]

### Tool · no outputtool ran but returned nothing useful — single line, no expand

      ▎ ▣ search_content  "writeClipboard"          0.04s · 0 hits           ▸

### Sub-agent · deep nestingeach level adds a bar — depth becomes obvious without indent text

      ▎ ⌬ Sub-agent · researcher                                       running  ▾
      ▎
      ▎   ▎ ⌬ Sub-agent · code-reader                                  running  ▾
      ▎   ▎
      ▎   ▎   ▎ ▣ read_file  src/cli/ui/App.tsx           0.08s
      ▎   ▎   ▎  REASONING    v4-flash   62 tok · 1 ¶
      ▎   ▎
      ▎   ▎   ▶ summarising findings…
      ▎
      ▎   ▶ aggregating sub-agent reports…

### Plan · resumedloaded from a prior session checkpoint; the resume marker shows where to pick up

      ▎ ⊞ Plan · resumed from session-6                3 of 7 done · ⏮ resume     ▾
      ▎
      ▎    [✓] 1. Snapshot current selection state
      ▎    [✓] 2. Drop @xterm/headless dep
      ▎    [✓] 3. Remove screen-mirror.ts
      ▎    [▸] 4. Strip LogSelection from log-frame.tsx      ← resume here
      ▎    [ ] 5. Strip drag handlers from App.tsx
      ▎    [ ] 6. Add /copy slash command
      ▎    [ ] 7. Update CHANGELOG & push
      ▎
      ▎    [↵] resume   [r] revise   [d] discard checkpoint

### Plan · replay archivehistorical, read-only — single line until expanded; ⏪ icon and dim accent

      ▎ ⊞ Plan · ⏪ archive  · session-3 · 2026-04-26            7 of 7 done  ▸

### Step progresssingle-line completion notice — emitted between steps so you don't need to expand the task card

      ✓ Step 3 of 5  ·  Remove screen-mirror.ts                0.4s · done

### Disconnect bannernetwork fell over mid-turn — toast-style above the composer, persists until reconnect

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
      ✗ Disconnected from api.deepseek.com  — retrying in 4s          [r] retry now · [c] cancel turn
      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

## 26Inline elements

Tiny stylings the agent (or user) drops inside running text — they're not cards, they're enrichments. Each pattern is recognised by a regex on emit and styled by the renderer.

### File:line referencerecognised: `path/to/file.ts:42`. Sky underlined; OSC-8 hyperlink so terminals that support it open the editor at that line.

      The change you described maps cleanly to src/index/config.ts:24,
      where DEFAULT_INDEX_EXCLUDES is defined. See also src/cli/ui/App.tsx:1491.

### @ mentionamber underline distinguishes user-attached files from agent-discovered references

      As we discussed in @src/index/config.ts and @CLAUDE.md, the rules
      should live in one place.

### Countdownlive-decrementing digit, brand color, used in approval / disconnect banners

      auto-approving in 3…           retrying in 4s…           timeout in 12s…

### Highlightterminal-native inverse for substring matches; used in search hits and `/find` output

      function writeClipboard(text: string): ClipboardWrite
      import { writeClipboard } from "./clipboard.js";

## 27Command outputs

Slash commands emit a card just like any other event. Most reuse existing card types — `/cost` produces a Usage card, `/context` produces a Memory card. The two interactive ones (`/memory`, `/doctor`) get their own variants.

### /costprints a one-shot Usage card for the most recent turn

      ◇ you  · just now
        /cost

      ▎ Σ Usage  turn 12                                  $0.0014 · 1.2s  ▾
      ▎
      ▎    prompt    ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░  41,238 / 1M  · 4.1%
      ▎    cache hit ██████████████████████████░░░  91.3%
      ▎    session ⛁ $0.018  ·  balance ¥ 28.4

### /contextprints a Memory card with what's in the current prompt, including system + history budget

      ◇ you  · just now
        /context

      ▎ ⌑ Context  ·  4 user · 2 feedback · 1 reference             ~1.2K tok  ▾
      ▎
      ▎   SYSTEM        CLAUDE.md (132 lines)                 ~480 tok
      ▎   MEMORY        7 entries from MEMORY.md              ~720 tok
      ▎   HISTORY       last 8 turns kept verbatim            ~38K tok
      ▎   TOOLS         14 tools (incl. 4 MCP)                ~1.8K tok
      ▎   FILES         2 attached via @                      ~6.2K tok

### /memoryinteractive memory editor; navigate, edit, delete entries

      ▎ ⌑ Memory                                      7 entries · ~2.1K tok  ▾
      ▎
      ▎   USER (3)
      ▎   ▸ Reasonix maintainer · prefers terse Mandarin replies
      ▎     Windows Terminal + PowerShell · CNY/RMB balance
      ▎     Internal checkpoints over git pollution
      ▎
      ▎   FEEDBACK (3)
      ▎     No Co-Authored-By: Claude trailer in commits
      ▎     Comments document why, not chat history
      ▎     Use libs for text width / unicode
      ▎
      ▎   REFERENCE (1)
      ▎     Linear "INGEST" project tracks pipeline bugs
      ▎
      ▎   [a] add new   [e] edit focused   [d] delete focused   ↑↓ navigate

### /doctorhealth check — pass/fail per check, summary at the bottom

      ▎ ⚕ Doctor                                            7 checks · 6 passed · 1 warn  ▾
      ▎
      ▎   ✓  node version         v22.10.0                         OK
      ▎   ✓  api key              present, 47 chars                 OK
      ▎   ✓  deepseek reachable   api.deepseek.com 142ms            OK
      ▎   ✓  workspace            writable, in git, on main         OK
      ▎   ✓  CLAUDE.md            found, 132 lines                  OK
      ▎   ⚠  index database       12 days stale, run /reindex       warn
      ▎   ✓  permissions          ~/.reasonix readable + writable   OK

## 28Compare · SplitDiff

Side-by-side diff of one file. Used for `/diff <file>` and historical compares. Both panes share line numbers so the eye can sweep across; matching anchor lines align.

CARD · .splitdiff

      ▎ ± Compare  src/cli/ui/App.tsx                                +12 / -47     ▾
      ▎
      ▎     HEAD                                          working tree
      ▎     ───────────────────────────────────────       ───────────────────────────────────────
      ▎        1  /** App.tsx — primary chat … */              1  /** App.tsx — chat surface … */
      ▎        2                                                 2
      ▎        3  import React from "react";                    3  import React from "react";
      ▎        …  …                                             …  …
      ▎      142  <Box>                                        142  <InlineShell>
      ▎      143    <LogFrame ...>                             143    <CardStream ...>
      ▎      144  </Box>                                       144  </InlineShell>
      ▎
      ▎   ↑↓ scroll  ·  h/l switch pane  ·  n/N next/prev hunk  ·  q quit

## 29Live indicators

Transient one-row signals that print inline, between cards. They're not cards (no accent bar, no expand) — just a visual notification you can scroll past.

### Thinking spinnerwindow between user msg and the first reasoning / streaming card

      ◐ thinking · deepseek-chat  · 2.3s                                    esc abort

        spinner cycles: ◐ ◓ ◑ ◒  (200ms cadence, ink-spinner pattern)

### Context pressureprompt budget warning at 80% / 95% / over-limit

      ▎ ⚠ Context  821K / 1M  ·  82%                                          ▾
      ▎   approaching the budget; older turns will be dropped past 95%

      ▎ ✖ Context  990K / 1M  ·  99%                                          ▾
      ▎   trimming oldest 12 turns to fit; expect some short-term memory loss

### Undo bannerctrl+z reverted an edit; banner stays for ~5s

      ↶ Undid: edit src/cli/ui/App.tsx  +12 / -47             5s · ctrl+y to redo

### Aborted cardesc cut a streaming / tool card mid-flight; the card retains what was printed and tags itself stopped

      ▎ ▶ — aborted —                                              1.2s · stopped
      ▎   The change you described maps cleanly to the existing
      ▎   ResolvedIndexConfig structure. Three edits ar…[truncated by esc]

### Tool retry / repaircache-first loop retried a failed call — header annotates the attempt

      ▎ ▣ run_command  npm run typecheck                          ↻ retry 1/3     ▾
      ▎
      ▎   [last attempt timed out at 30s · increasing to 60s]
      ▎   $ npm run typecheck
      ▎   …

### Checkpoint firedinternal checkpoint system snapshot landed (auto, never via git)

      ⛁ Checkpoint saved  ·  edit-history#142  ·  3 files · 248 bytes      /undo to revert

## 30Markdown rendering

Reference for how markdown elements look when emitted by the model inside reasoning / streaming / assistant cards. Inline spans are styled in place; block elements get their own row.

### Inlinestyling that doesn't break the line

      A normal sentence with bold and italic and both mixed in.
      Inline code looks like stringWidth(s) — bg-elev pad, fg-0.
      A link reads as jump to docs (https://reasonix.dev/docs).
      A file ref like src/cli/ui/App.tsx:142 is sky underline.
      A keyboard hint: ⏎ esc — same chip style as inline code.

### Blockelements that take whole rows

       Heading 2

      A paragraph below a heading. Headings render as a band; H1 / H2 / H3
      share the same styling — terminal can't change font size.

      ▎ A blockquote.  Sky bar + italic dim, single rule.

      A bulleted list:
        · first item
        · second item
        · third item

      A numbered list:
        1. first step
        2. second step

      A code block — bg-elev panel, no box, monospace already (we're a TUI):

       const SKIP = new Set(["node_modules", ".git"]);
      if (SKIP.has(name)) continue;

      A table — borders are rule chars, no box-drawing:

          name        size    role
          ────        ────    ────
          App.tsx     4.7K    root + loop
          log-frame   1.1K    renderer
          PromptIn      569   composer

      ─────────────────────────────────────────────────────────────────────────────

      A horizontal rule above looks like that — fg-4 hairline.

## 31Editor mode

Triggered by /edit <file> or ctrl+e on a focused diff card. Replaces the composer block (input + status row) with an inline editor pane bound to one file. Esc returns to chat with the buffer intact (dirty marker stays).

PANE · .editor

     ±  edit  src/cli/ui/App.tsx                                   dirty ·  4740 lines

             1   /** App.tsx — primary chat surface, owns log + input. */
             2
             3   import React from "react";
             …
       ▸ 142   <InlineShell>
           143     <CardStream ...>
           144   </InlineShell>
             …

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ scroll  ·  ⏎ edit row  ·  ^s save  ·  ^z undo  ·  ^y redo  ·  esc back to chat

SAVING · .editor.saving

     ±  edit  src/cli/ui/App.tsx                              saving… ·  4740 lines

SAVED · .editor.saved

     ±  edit  src/cli/ui/App.tsx                          ✓ saved · 0.2s ·  4740 lines

## 32Toasts · transient banners

A toast appears just above the status row hairline, pushing the status row down by one row for ~3s, then unmounts. Used for events the user should notice but doesn't need to act on. Disconnect (§25) is the persistent variant — sticks until resolved.

SUCCESS · .toast.ok

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
      ✓ Checkpoint saved  ·  142 events  ·  3 files snapshotted           3s

INFO · .toast.info

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
      ⓘ Memory updated  ·  1 entry added · feedback / no-coauthor          3s

WARN · .toast.warn

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
      ⚠ MCP `notion` slow  ·  8.4s p95 over the last 5 calls                5s

ERR · .toast.err

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
      ✗ Tool denied  ·  rm -rf node_modules · sandbox policy               5s

## 33Help & key reference

Two surfaces: a printed /help card that scrolls into history, and a transient ? overlay that takes over the composer for as long as you hold the key.

### /help · printed cardscrollable, comprehensive; reuses the regular card shell

      ▎ ⓘ Help  · keys, commands, modes                                       ▾
      ▎
      ▎   COMPOSER
      ▎     ⏎       send the message
      ▎     ^J      newline (multi-line input)
      ▎     ↑↓      cycle history
      ▎     /       slash command picker
      ▎     @       file attachment / mention
      ▎     !       shell mode (one-shot bash)
      ▎     esc     abort current turn
      ▎     ^c      quit
      ▎
      ▎   CARDS
      ▎     j / k   focus next / prev
      ▎     ⏎       expand / collapse focused
      ▎     y       copy focused card text
      ▎     ?       key overlay
      ▎
      ▎   MODES
      ▎     /auto   approve all tool calls
      ▎     /ask    prompt for each tool call
      ▎     /plan   draft a plan before executing
      ▎     /edit   enter editor mode on focused file
      ▎
      ▎   SLASH COMMANDS  — full list at /help all
      ▎     /cost  /context  /memory  /diff  /copy  /init  /doctor

### ? overlaytransient cheat-sheet — replaces composer for as long as `?` is held; releases on any keypress

      ─────────────────────────────────────────────────────────────────────────────
       shortcuts  ·  press any key to dismiss

       ⏎     send             ↑↓    history          /     commands
       ^J    newline          @     attach           !     shell mode
       esc   abort            ^c    quit             ^L    clear screen
       j / k focus card       y     copy card        ?     this overlay

      ─────────────────────────────────────────────────────────────────────────────

## 34Shell output (! mode)

When the user submits with a leading !, the line bypasses the model and runs as a shell command via the same tool-call path. It lands as a regular ▣ shell tool card — same expand/collapse rules as any other tool result.

      ◇ you  · just now
        ! git status

      ▎ ▣ shell  git status                                  0.04s · exit 0     ▾
      ▎
      ▎   On branch main
      ▎   Your branch is up to date with 'origin/main'.
      ▎
      ▎   nothing to commit, working tree clean

      ▎ ▣ shell  git push                                    2.1s · exit 1     ▾
      ▎
      ▎   error: failed to push some refs to 'origin'
      ▎   hint: Updates were rejected because the remote contains work…
      ▎   hint: integrate the remote changes first.

## 35DiffApp · standalone CLI

Invoked as reasonix diff <file> — a one-shot terminal app that opens a SplitDiff card with a session intro at the top and a key hint at the bottom. No composer, no agent. Quits on q / esc / ^c.

      $ reasonix diff src/cli/ui/App.tsx

      ◈ diff  ·  ~/projects/reasonix  ·  src/cli/ui/App.tsx  ·  HEAD → working

      ▎ ± Compare  src/cli/ui/App.tsx                            +12 / -47     ▾
      ▎
      ▎     HEAD                                          working tree
      ▎     ───────────────────────────────────────       ───────────────────────────────────────
      ▎        1  /** App.tsx — primary chat … */              1  /** App.tsx — chat surface … */
      ▎        …  …                                             …  …
      ▎      142  <Box>                                        142  <InlineShell>
      ▎      144  </Box>                                       144  </InlineShell>

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ scroll  ·  h/l switch pane  ·  n/N next/prev hunk  ·  q quit

## 36Account & quota

Three states: balance low (warn) · exhausted (err) · rate limited (warn). Each lands as a regular card so it's part of scrollback and the user can scroll up to find it later.

CARD · .quota.low

      ▎ ⚠ Balance low  ·  ¥ 1.24 remaining                                       ▾
      ▎
      ▎   At your current burn rate (¥0.10 / turn) ≈ 12 more turns.
      ▎   Top up at https://platform.deepseek.com/usage.

CARD · .quota.out

      ▎ ✖ Out of balance  ·  ¥ 0.00                                              ▾
      ▎
      ▎   Cannot send. Composer disabled until top-up.
      ▎   Top up at https://platform.deepseek.com/usage,
      ▎   then /refresh to re-check.

CARD · .quota.rate

      ▎ ⚠ Rate limited  ·  retry in 4s                                          ▾
      ▎
      ▎   api.deepseek.com responded 429: 60 RPM exceeded
      ▎   Reasonix will retry automatically with backoff. esc to cancel.

## 37MCP lifecycle

One-line cards for each lifecycle event of an MCP server connection. Steady-state servers don't print anything — only state changes emit a card so scrollback isn't noise.

      ⌘ MCP · notion          ↻ handshake…   initialise → tools/list → resources/list

      ⌘ MCP · notion          ✓ connected    12 tools · 8 resources · 142ms

      ⌘ MCP · notion          ◌ slow         tools/list took 8.4s · added p95 to context

      ⌘ MCP · notion          ↻ reconnect 2/5  backoff 4s

      ⌘ MCP · notion          ✖ failed      handshake error · ENOENT: server binary missing

      ⌘ MCP · notion          ○ disabled     via /mcp disable notion

## 38Session ops

One-line outputs from /fork, /archive, /resume, /reset. They land as a single inline row so the chain of session state changes is readable in scrollback.

      ◍ Forked session-7 → session-8  from turn 12 · 142 events copied      reasonix --session=session-8

      ⌑ Archived session-7            ~/.reasonix/sessions/session-7.jsonl  ·  /resume to bring back

      ↺ Resumed session-7             at turn 12 · 142 events replayed · plan reloaded

      ⚠ Reset — session-9 cleared      142 events archived · /resume session-9 to recover

## 39Dropped surfaces

Things that used to exist in older Reasonix versions but don't fit the inline + bottom-pinned model. Listed here so the absence is intentional, not a TODO.

### File tree sidebar

A persistent sidebar requires alt-screen (otherwise it'd scroll away with content). Replaced by @ mention picker (§19) for picking files into a turn, and /files slash command for an on-demand printable file list. Same job, no sticky chrome required.

### Persistent top chrome bar

Replaced by the bottom status row (§20). Anything that was on the top bar (mode pill / cost / cache / balance) now lives one row above the input — Ink can pin it, top-row positioning can't.

### App-managed scroll viewport

No more ↑ 24 ▕───●─────▏ 62% ↓ 12 indicator. The terminal's native scrollback is the source of truth — wheel up, ⇧+drag to select, the OS handles it. Reasonix doesn't try to clip / paginate.

### Mouse-tracking modes

No ?1002h / ?1006h button-event tracking. Without alt-screen there's no point — and disabling it lets the terminal's native selection (⇧+drag, double-click word, triple-click line) just work.

## 40Motion & cadence

Terminals don't do tweens, opacity, or sub-cell positions. What Ink can do is rerender any row on an interval — that gives us discrete-frame animation, color steps, and content swaps. Below: the seven primitives we use, their cadence, and what we never try.

Live previews below — every animation in this section actually runs. If you don't see motion you're either looking at a screenshot or your browser is too old (needs CSS `content` animation, ≥ Chrome 109 / Firefox 119 / Safari 16).

### 1 · Spinner — circle200ms / frame · 4-frame cycle · used for "thinking" / model wait

      ◐ thinking · deepseek-chat  · 2.3s                                    esc abort

      frames cycle:  ◐  →  ◓  →  ◑  →  ◒  →  ◐ …  (the live row above is rotating ◐ at 200ms / step)

### 2 · Spinner — braille80ms / frame · 8-frame cycle · used for tool calls (faster, distinguishable from "thinking")

      ▎ ▣ run_command  npm install           12.4s     ▾

      frames cycle:  ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧  (live row above swaps content every 80ms)

### 3 · Streaming cursor1s blink · always at the tail of in-progress streaming content

      ▎ ▶  The change you described maps cleanly to the existing
      ▎    ResolvedIndexConfig structure. Three edits are needed

      cycle: on (500ms)  →  off (500ms)  →  …  the brand block at the tail above is the live cursor

### 4 · Focus pulseaccent bar pulses at 1.4s ease-in-out when a card is the current focus

      ▎ ⊞ Plan · Migrate selection     5 of 7 done     ● FOCUSED  ▸

      opacity: .35  →  1.0  →  .35  →  …  ease-in-out  (the bar to the left is the live pulse)

### 5 · Toast fadesolid 2s → fade to faint over 1s → unmount. Tone drop, not alpha (which terminals can't do)

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
      ✓ Checkpoint saved  ·  142 events  ·  3 files snapshotted

      cycle: solid 2s  →  dim over 1s  →  loop. In Ink we replace opacity with a fg-1→fg-2→fg-3 ramp.

### 6 · Number tickerdata-driven · new value flashes brand for one render frame then settles to fg-1

      ▸   turn      ·      cycles every 4s in this preview · in real session, ticks on each cost-emit event

      applies to: cost ticker, cache hit %, balance ¥, token counters

### 7 · Countdown1Hz tick · digit flashes brand on each step · used in auto-confirm + disconnect retry

      approving in s · esc to interrupt

      cadence 1000ms · digit always brand · surrounding text stays warn · flash-and-step, no slide

### 8 · Row arrivala 600ms fade-in when a new card lands. The only transition we permit — and only for newly-printed cards, never for already-on-screen content

      ▎ ✓ Step 1 of 5 · Read chunker + filesystem           0.4s · 2 tools · done  ▸

      a one-shot fade from opacity 0 to 1; reload the page to see this row appear again

### Things we never dothese are unsafe / ineffective in a terminal cell grid

      ✗  sliding / position transitions          terminals only redraw whole rows
      ✗  opacity / alpha fade                    no opacity per cell — drop tone instead
      ✗  color gradients across cells            stutters at 256 / 16 color depth
      ✗  marquee / scrolling text                hides content, hurts scrollback
      ✗  whole-screen flash / inverse blink      accessibility hazard, photosensitive risk
      ✗  automatic auto-scroll override          terminal scrollback is the user's, not ours

## 41Edge cases

Smaller surfaces I missed in the per-feature pass — clipboard feedback, empty pickers, fatal crash, dirty exit. Each reuses an existing pattern (toast / picker / card), no new primitives.

### Clipboard copy feedbacky key on a focused card or /copy — shows an ok toast

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
      ✓ Copied to clipboard  ·  3 cards  ·  1.4 KB                              3s

### Empty picker/ or @ with no matches — picker stays open with a single zero-state row

     commands  ·  "/xyz"

      no matches.  type to filter or esc to close

    › /xyz

### Fatal crashunhandled exception in the agent loop — print the trace, offer /report, exit cleanly on ^c

     ✖  Reasonix crashed  ·  this is a bug, not your fault

      TypeError: Cannot read property 'then' of undefined
          at App.tsx:142:18
          at processTicksAndRejections (node:internal/process/task_queues:96:5)
          … 4 more frames hidden

      The session log is preserved at:
        ~/.reasonix/sessions/session-7.jsonl

      ─────────────────────────────────────────────────────────────────────────────
      [r] /report  file an issue with the trace + last 10 events
      [s] stack     show the full trace
      [c] copy      copy crash report to clipboard
      ^c            quit

### Dirty exit warning^c with unsaved editor buffer — block once, second ^c discards

     ⚠  Unsaved editor buffer

      src/cli/ui/App.tsx has +12 / -3 unsaved.

      ▸ save & quit     ^s then quit
        discard & quit   ^c again
        cancel           esc — back to editor

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ confirm  ·  esc cancel

### Long card · "show more"a card whose collapsed preview hits the row budget — truncate with a single dim row

      ▎ ▣ search_content  "writeClipboard"           3 hits in 2 files          ▾
      ▎
      ▎   src/cli/ui/clipboard.ts
      ▎       15 │  export function writeClipboard(text: string)
      ▎
      ▎   … 4 more rows  ·  press space to load all  ·  ⏎ to expand fully

### All MCP servers offlinewhen zero MCP tools are available, the agent says so explicitly so the user isn't confused why /tools is short

      ⌘ All 4 MCP servers offline     notion · linear · github · fs-local     /mcp browse

## 42Interaction

Mouse + keyboard parity. Focus a card, expand, fire actions — every click target has a key.

### Focused vs unfocused focus = brighten the accent bar from dim to full

      ▎ ⊞ Plan · Migrate selection                                              ▸
      ▎ ⊞ Plan · Migrate selection           5 of 7 done     ● FOCUSED  ▸

### Keyboard parity no mouse required — every action has a key

expand / collapse focused card
`↵`
focus next / prev card
`j` / `k`
focus next / prev action button
`tab` / `⇧tab`
fire focused action / open search hit
`↵`
jump to top / bottom
`home` / `end`
page scroll up / down
`pgup` / `pgdn`
slash command picker
`/`
file mention / attach
`@`
abort current turn
`esc`
approve modal pick / confirm
`↑` `↓` · `↵`
copy text from screen
`shift`+drag (terminal native)
copy beyond viewport
`/`copy [last N]
quit
`ctrl`+`c`

## 43Demo flow

A real session, in card order. User asks → context sweep → reasoning → plan → step (tools) → diff → approval. Terminal scrolls naturally as cards arrive.

      ◇ you  · just now
        refactor the SKIP_DIRS list out of chunker.ts so directory_tree can reuse it

      ▎ ⌑ Context  ·  4 user · 2 feedback · 1 reference             ~1.2K tok  ▸

      ▎  REASONING    r1   412 tok · 3 ¶                                  3.1s

      ▎ ⊞ Plan · 5 steps                                      0 of 5 done  ▾
      ▎
      ▎    [▶] 1. Read chunker + filesystem to understand current structure
      ▎    [ ] 2. Create src/index/config.ts with shared defaults
      ▎    [ ] 3. Strip constants from chunker.ts
      ▎    [ ] 4. Strip duplicate from filesystem.ts
      ▎    [ ] 5. Run verify gate

      ▎ ▶ Step 1 · Read chunker + filesystem                          0.4s     ▾
      ▎
      ▎   ✓  read    src/index/semantic/chunker.ts      0.08s · 250 lines
      ▎   ✓  read    src/tools/filesystem.ts            0.07s · 712 lines

      ▎ ▶ Step 2 · Create src/index/config.ts                         0.2s     ▾
      ▎
      ▎   ✓  write   src/index/config.ts                0.12s · 84 lines · created

      ▎ ± Edit  src/index/semantic/chunker.ts           +12 / -47     ▾
      ▎
      ▎     @@ -30,40 +30,5 @@
      ▎     -const SKIP_DIRS: ReadonlySet<string> = new Set([
      ▎     -  "node_modules", ".git", ".hg",
      ▎     -  ... 18 more lines
      ▎     -]);
      ▎     +import { DEFAULT_INDEX_EXCLUDES } from "../config.js";
      ▎     +const SKIP_DIRS = new Set(DEFAULT_INDEX_EXCLUDES.dirs);

     ?  Approve · apply edits to 2 files                                                awaiting

      The agent wants to apply the edit shown above plus a related one in
      src/tools/filesystem.ts.

      ▸ apply both      land both edits, run verify next
        apply this only land chunker.ts; review filesystem.ts separately
        reject          discard both; agent will revise

      ─────────────────────────────────────────────────────────────────────────────
      ↑↓ pick  ·  ⏎ confirm  ·  esc cancel