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  · 3 paragraphs · 412 tok                              

   ▶ Step 2 of 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.

CARD · .user
   you  · 2 min ago
    refactor the SKIP_DIRS list out of chunker.ts so directory_tree
    can reuse it

06Cards · reasoning

Default collapsed — reasoning is opt-in noise. Always italic + dim when expanded so it never competes with primary content.

CARD · .reasoning
   ◆ Reasoning  · 3 paragraphs · 412 tok                              
EXPANDED · .reasoning[open]
   ◆ Reasoning  · 3 paragraphs · 412 tok                              
  
     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
     words outrank my safety instinct here.
  
     Files to touch: chunker.ts (drop constants, accept resolved config),
     filesystem.ts (drop its own copy), and the index command (load + pass).

07Cards · task / step

A multi-step work unit — wraps tool calls + reasoning under one collapsible header. Default expanded for the active step, collapsed for completed ones.

CARD · .task .running
   ▶ Step 2 of 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
CARD · .task .done .collapsed
   ✓ Step 1 of 5 · Read chunker + filesystem      0.4s · 2 tools · done  
CARD · .task .failed
   ✗ Step 4 of 5 · Sandbox check                       0.2s · failed  
  
       read    src/sandbox/policy.ts          0.04s · 88 lines
       verify  policy.allows("rm")             denied

08Cards · tool call

Single tool invocation. Collapsed = one row. Expanded = input args summary + output preview inside a sharp-cornered code box.

COLLAPSED · .tool
   ▣ read_file  src/cli/ui/App.tsx                0.08s · 1224 lines     
EXPANDED · .tool[open]
   ▣ read_file  src/cli/ui/App.tsx                0.08s · 1224 lines     
  
           1   /** App.tsx — primary chat surface, owns log + input. */
           2
           3   import React, ... from "react";
           [1218 lines hidden — press space to load all]
        1224   export default App;
SHELL TOOL · .tool .shell
   ▣ run_command  npm run verify                 23.4s · exit 0      
  
     $ npm run verify
     > reasonix@0.17.0 verify
     > npm run lint && npm run typecheck && npm run test --silent
     
      Test Files  99 passed (99)
           Tests  1784 passed (1784)

09Cards · plan / todo

Ordered checklist. State per item via the bracket char + color: [✓] done · [▶] running · [ ] queued · [!] blocked · [✗] failed. Footer offers revise / skip actions.

CARD · .plan
   ⊞ Plan · Migrate selection to terminal-native                5 of 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
  
      [r] revise   [s] skip step   [↵] expand item

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 · 2 paragraphs                                
       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?

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  · 4 paragraphs                                       


   ⊞ 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    ·   [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 italic dim while bytes arrive; settles into the collapsed reasoning card on completion

   ◆ Reasoning  · streaming…                                            
  
     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 · 1 paragraph                              
     
         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  · 3 paragraphs                                       


   ⊞ 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