·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
Accentsone color identifies a card type — never two on the same card
Textfive-step grayscale, hierarchy via tone not size
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
Status / stateused inside cards — color carries the state
Structuralbox-drawing + block characters — terminal native
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.
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).
▎ 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.
▎ 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.)
▎ 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.
▎ 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).
▎ 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.
▎ 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.
▎ 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.
▎ 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.
▎ 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.
▎ 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.
▎ 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 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.
▎ 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.
▎ 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.
▎ 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.
▎ ± 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.
▎ ✖ 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.
▎ ⚠ 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.
▎ Σ 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.
▎ ⌬ 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.
? 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.
▎ ▶ 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.
▎ ⊙ 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).
▎ ⌑ Context · 4 user · 2 feedback · 1 reference ~1.2K tok ▸
▎ ⌑ 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
› type a message · / for commands · @ to attach a file ⏎ send · ^J newline · ↑↓ history · esc abort · ctrl-c quit
Typingcursor at end of single line
› 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
› 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
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
› 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
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
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
/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
$ 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
› 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
⊞ 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
✎ 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
✎ 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
⛁ 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
? 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
? 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
± 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
✗ 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)
? 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
╔═══════════════════════════════════╗ ║ ║ ║ ◈ 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
◈ 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
◈ 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
◈ 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)
… 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
Σ 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.
◈ 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.
▎ ± 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).
± 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
± edit src/cli/ui/App.tsx saving… · 4740 lines
± 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.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✓ Checkpoint saved · 142 events · 3 files snapshotted 3s
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⓘ Memory updated · 1 entry added · feedback / no-coauthor 3s
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⚠ MCP `notion` slow · 8.4s p95 over the last 5 calls 5s
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✗ 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.
▎ ⚠ Balance low · ¥ 1.24 remaining ▾ ▎ ▎ At your current burn rate (¥0.10 / turn) ≈ 12 more turns. ▎ Top up at https://platform.deepseek.com/usage.
▎ ✖ 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.
▎ ⚠ 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
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