Reasonix's dashboard is the rich-medium companion to the TUI — not a mirror,
not a replacement. It does what a 13-row terminal pane cannot:
long-form reading, real charts, multi-file editing, large-table inventory browsing.
The TUI keeps the things terminals are good at — instant feedback, slash commands,
typing-loop latency.
Why not mirror the TUI? Slavishly recreating the terminal in a browser
produces an unusable portfolio gimmick. Charts, hover tooltips, drag, and dense
tables are web-native; pretending otherwise wastes the medium.
Why not replace the TUI? Web input + AI streaming has higher latency than
a raw stdin keystroke loop. The TUI wins on responsiveness and stays the primary
surface; the dashboard is opened in a second tab when you want to read, look,
or configure.
§1Tokens
Same core palette as the TUI mockup so that switching between TUI and dashboard
feels like one product. Slightly higher chroma allowed for chart series.
Surfaces
--bg#0a0c10
--bg-elev#11141a
--bg-elev-2#161a22
--bg-input#0d1015
--bg-code#06080c
--bg-hover#1a1f29
Text
--fg-0 primary#e6edf3
--fg-1 body#c9d1d9
--fg-2 secondary#8b949e
--fg-3 dim#6e7681
--fg-4 separator#484f58
Accents role-coded — same meanings as TUI
--c-brand skyin-progress, links
--c-accent purplereasoning, plan
--c-violetsub-agent
--c-ok greensuccess
--c-warn amberapproval, warning
--c-err coralerror
Chart spectrum six-stop series — distinguishes without shouting
s1 skyprimary
s2 tealsecondary
s3 minttertiary
s4 amberquaternary
s5 coralaccent / negative
s6 purplemodel boundary
Type
Sans-serif (Inter) for prose; monospace (JetBrains Mono) for code, data, file paths, counts, glyphs, and section labels. Smaller text steps below 12px stay monospace — readability holds better at small sizes than narrow sans.
28 / 700Headline · 28px
22 / 700 monoSection title · 22px
14 / 400Body — default reading size for prose. 14px Inter at 1.55 line-height.
12.5 / 400 monoCode / data — JetBrains Mono
11 / 600 monoSECTION LABEL · 11PX UPPERCASE
Glyphs single-char icons reused from the TUI
◈brand
◆chat
✎edit
⊞plan
›sessions
$usage
▣tools
▎permissions
+system
≈semantic
Mmcp
Sskills
·memory
Hhooks
⌘settings
⏵streaming
↻reload
▲delta-up
▼delta-down
●status-dot
§2Shell
The frame: sidebar, top context bar, body, status row.
Sidebar collapses to icon-only at narrow widths or on user toggle (state persisted).
Top bar carries the high-frequency context — workspace path, session, model, cost
— so panel content can be uncluttered.
— Default: sidebar expanded, Chat panel active
~/work/reasonix›feat/dashboard-v2›2026-04-30-2014
TUI · #2modeldeepseek-chatbalance¥48.20turn12
— panel content slot —
— Sidebar collapsed (icon-only)
~/work/reasonix›feat/dashboard-v2
TUI · #2¥48.20
— collapsed sidebar trades labels for icons; tooltips on hover —
Why a left sidebar instead of top tabs?
14 panels won't fit horizontally. Vertical also lets us section them
(workspace · observe · configure) so muscle memory builds. Collapse-to-icons
keeps the option of tight-vertical dashboards (laptop) without losing the layout.
§3Components
Building blocks every panel composes. Sharp corners and 1px hairlines
inherited from the TUI; web affordances (hover, focus rings, real form controls)
are added rather than emulated.
Cards
Every panel is a stack or grid of cards. The 2px left border encodes role: brand for in-progress, accent for plan/reasoning, warn for approval, err for failures.
⏵streaming · assistant2.3s · 1.2k tok
Looking up the exit code Windows uses when SIGTERM is delivered to a console subsystem process…
⊞plan · awaiting approval5 steps
Refactor session sidecar lifecycle so .events.jsonl rename/delete tracks the parent.
▲shell · awaiting approvaldeepseek
npm publish
✕tool error · run_commandexit 1
Cannot publish over the previously published versions: 0.18.0.
Pills
Status chips. Always uppercase mono, always small. Use sparingly — too many pills in one row turns into noise.
Dense by default. Numeric columns are tabular-nums and right-aligned. Path / id columns get monospace. Header is uppercase 10.5px to keep the eye on the data.
Tool
Source
last call
calls
avg ms
read_file
native · fs
src/cli/ui/App.tsx
142
8
edit_file
native · fs
src/cli/ui/PromptInput.tsx
38
14
run_command
native · shell
npm run verify
11
23,400
grep_files
native · fs
"workspace" src/
9
42
github__get_pr
mcp · github
esengine/reasonix#13
4
280
Toasts
Top-right stack, auto-dismiss in 3s. Border-left encodes kind. One-line by default; expandable for tracebacks.
●
Published reasonix@0.18.1 to npm
×
⏵
3 events forwarded to events.jsonl
×
▲
0.18.0 has a deprecation notice — surface to users on launch?
×
✕
Failed to load skill @reasonix/python-runner — ENOENT
×
Code blocks
Kept close to the TUI's terminal feel — slightly darker than the panel surface, monospace, no ligatures-from-noise. Inline highlighting reuses accent colors.
1export functionlistSessionsForWorkspace(workspace:string):SessionInfo[] {
2// Strict match — legacy untagged sessions are hidden; 3// resume by name still works. 4return listSessions().filter((s) => s.meta.workspace === workspace);
5}
Diff view
Unified by default; side-by-side toggle lives in the §7 Edit review panel. Add/remove rows tinted ~6% opacity over the code surface; syntax highlighting reuses the .kw / .str / .com tokens from the code block, so the diff blends with surrounding code visually. Word-level intra-line diff via .word-add / .word-rem highlights only the bytes that actually changed.
src/cli/commands/chat.tsx+1 · -2
@@@@ -346,8 +346,7 @@ export async function chatCommand
Title in 11px uppercase mono · current value in 22px mono · sparkline below. Hover drives a tooltip with the date and exact value (handled by the chart lib at impl time, not in the mockup). Series follow the spectrum tokens.
cost · 7 day▲ 12%
¥18.40/day
tokens in · 7 day▼ 4%
142k/day
latency p95— flat
2.4s
Progress replaces every default browser bar
The current dashboard leans on <progress> default styling — chrome-grey trough, OS-tinted fill, no role coding. Replace with a single .progress primitive: 6px tall, 3px thin variant, 10px thick variant, role tints (ok / warn / err / acc). Always paired with a tabular-nums numeric label. Indeterminate is a shimmer slice, not a spinning circle.
linear · with caption
turn iters
3 / 10
budget
¥78 / 100
over cap
103%
cache hit
94%
reasoning
streaming
indeterminate · for unknown duration
npm install
…
A 30%-wide slice slides left-to-right on a 1.4s loop. No spinner — spinners read as "tab is busy"; a sliding bar reads as "this specific task is in flight."
thin · inline beside text
verify
1665 / 1665
segmented · breakdown of one whole
For ratios where each slice has its own meaning. Cache-hit / cache-miss is the canonical case.
cache · 7d
100%
● hit · 74%● miss · 18%● error · 8%
step · plan / wizard progress
1
2
3
4
5
planreviewapproveexecutecommit
ring · for KPIs that compress to a single number
94%cache
3/10iters
78%budget
Form controls
Monospace inputs; the focus ring is a 1px brand-color border, no glow. Labels in 10.5px uppercase mono so they sit visually as "field tags" rather than competing with the input itself.
Soft cap; warn at 80%, refuse new turn at 100%.
✓Enable plan-then-edit flow
✓Auto-launch dashboard on reasonix code
Use streaming for sub-agents
§4Chat
A first-class chat surface, not a viewer. Full composer, slash menu, file
attachments, paste handling. The dashboard wins anywhere the TUI's renderer
breaks down — older PowerShell, non-ConPTY consoles, mosh-over-flaky-network,
or terminals where Ink redraws the same row twice. A small status pill in the
topbar tells you which surface the loop currently considers "active writer."
Why does the dashboard need its own chat?
The TUI assumes a modern terminal — true cursor reporting, ConPTY, raw stdin.
On legacy PowerShell hosts (Win10 cmd, ConEmu, very-old WT builds) Ink's
diff-based renderer can re-paint the same card row, leak ANSI sequences,
or drop frames mid-stream. The dashboard's chat is HTML — it can't have
those bugs. Treating it as fallback-only means users hit the bugs first
and only then discover the workaround. Better: full peer.
Single-writer is still enforced: only one of {TUI, dashboard} owns
the input lock at a time. The pill says which. Switching is one click;
re-entering the TUI on first keystroke is automatic.
— TUI online, dashboard reading; user can submit from either
The user reports a publish conflict. Root-cause direction: did the earlier rejected tool actually upload before the rejection signal landed? Plan: query the registry for 0.18.0 timestamp + gitHead, compare against local commit history…
0.18.0 was published at 03:20:58Z, with gitHead=f8e156c — the local "release: 0.18.0" commit from before the bug fix. So the rejected tool actually uploaded to the registry; the rejection signal stopped the local CLI before the confirmation print.
Next step: bump to 0.18.1 with the fix, then deprecate 0.18.0 on the registry…
— TUI offline (renderer hung); dashboard auto-promoted to active writer
~/work/reasonix›2026-04-30-2014
TUI offline · 14s¥48.20
●TUI hasn't drained its event queue in 14 seconds — likely a renderer hang. Dashboard now owns input. force-quit TUI · reattach
⏵assistantstreaming continues here
…the deprecate command will mark 0.18.0 with the warning text on the registry. Once it's done, anyone who runs npm install reasonix@0.18.0 will see the deprecation banner and get pointed at 0.18.1.
Composer states how the input bar reads in different conditions
One composer, four states. Border + foot copy carry the difference; geometry stays put so the eye doesn't reorient.
idle
type a message · slash for commands · at-sign for files
switching is one click; releasing back to TUI is automatic on focus
Approval modal tool-call confirmations mirror from the loop
When the model wants to run a non-allowlisted command, both the TUI and the dashboard show the same approval. Either side can resolve. The dashboard frames it as a centered dialog (more body, can show full diff/output preview), the TUI shows it inline as a card. Same dispatch path either way.
▲approve · run_commanddeepseek · turn 14
The model wants to run a command that is not on the auto-approve allowlist:
npm publish
cwd: ~/work/reasonix
prefix used by allowlist match: npm
y approve · a always for prefix · n deny
Command palette Ctrl/⌘+K opens a global jump bar
Slash commands, panels, sessions, even MCP tools — all addressable through one fuzzy search. The popover from inside the composer is the same component, just anchored differently and pre-filtered to slash commands. Avoids the dashboard ever needing menus.
⌘esc
slash commands
//deprecatemark a published version as deprecated↵
panels
▣Toolsbrowse registered tools
▎Permissionsedit allowlist
recent sessions
›2026-04-30-1908tui-card-stream redesign
›2026-04-29-1602v0.14 event-log kernel
§5Overview
The cockpit. A four-column widget grid that answers "what's the system doing
right now, what did it just do, what should I worry about" in one screen.
Every widget is a link into the corresponding panel for depth.
Top row: 4 KPIs (balance · token volume · cache hit · tool calls) — the four numbers you check first when picking up an in-progress agent. Wider middle: current session + cost trend, side by side. Lower middle: plan history + tool feed — the "what's been happening" pair. Bottom KPIs: configuration health (tools / MCP / memory / version).
Every widget is a link into the corresponding panel. Hover reveals "open" affordance; click opens the deeper view.
§6Sessions
The high-traffic browse view. List on the left (filter, sort, search), detail
on the right. Designed so you can land here a week later, find the session you
half-remember, and either resume it, copy a prompt out, or delete the whole
branch of dead-end work.
Why list+detail and not a card grid?
Sessions have a strong temporal axis (you almost always want "what did I do
today" or "what was that thing last week"). A vertical list with date affordances
beats a card grid for that. The detail pane on the right gives room for the
transcript preview + plan history + cost breakdown that you actually came here for.
Don't show a sad cloud illustration — show what the user can do next.
› ›
No sessions yet in this workspace
Sessions are scoped to the launch directory. Open one with reasonix code in the terminal, or import a transcript from another machine.
Bulk operations
Select multiple rows (shift-click range, ⌘-click toggle) → action bar slides in at the bottom of the list pane: delete, archive (move to .archive/, hidden by default), export (zip with sidecars), tag. No bulk-rename — one session at a time keeps the timestamp invariant intact.
§7Edit review
Where the agent's edit_file output becomes a thing you actually read before it lands.
Multi-file aggregator at the top, per-file collapsible cards underneath, GitHub-style diff with
syntax highlighting, expand-context chevrons, intra-line word diff, and a unified ↔ split toggle.
Inline diffs in chat (§3) are the quick read; this panel is the full review.
Multi-file summary
Top-of-page aggregator. Stat row, mode toggle, bulk approve/reject. The Apply all button is disabled until every file is either approved or explicitly skipped — same gate the kernel will enforce.
3 files changed+24 · −18
Per-file card · expanded
Default state for any file with under ~80 changed lines. Header shows path + per-file stat + per-file approve/reject. Clicking the chevron collapses to header-only. Approval is sticky across panel re-renders so a long review doesn't lose state.
src/cli/commands/chat.tsx+1−2
@@@@ -346,8 +346,7 @@ export async function chatCommand
Default for files past the line-count threshold, or after the user has approved/rejected them. Header stays interactive — re-open with one click.
src/loop.ts+18−14approved
tests/loop.test.ts+5−2
Side-by-side mode
Activates from the toggle in the top summary. Two panes share row alignment so the eye scans horizontally. Empty cells in either pane render as the elevated background, signalling pure adds/removes vs. modifications. Word diff inside the cells survives the mode swap.
src/cli/commands/chat.tsx+1 · −2
@@@@ -346,8 +346,7 @@ export async function chatCommand@@
No pending edits — single line in elevated background: — no edit_file calls in this turn —. Clicking opens the most recent reviewed turn (read-only).
One edit, all approved — summary collapses to a single chip: ✓ 1 file applied · src/cli/commands/chat.tsx. Re-expand from the chip.
Test red after apply (RFC #25 stage 2) — diff stays visible, file card gains a red footer: test_run failed · vitest -t "<name>" · status fail · auto-reverted. Approve gate blocks until the model re-tries or the user opts into /refactor.
Wiring
Data source: events.jsonl via the dashboard's /api/events stream. Each tool.dispatched for edit_file + its paired tool.result + (post-#25) test_run compose one card. Apply / reject are no-ops in the design — the actual side-effect is in the kernel; the panel only reflects state.
§8Plans
Plans live longer than a turn — they survive across sessions if the work
isn't done. The Plans panel is where they're browsed (left list), inspected
(right detail), and resumed. The headline element is the horizontal step
timeline at the top of the detail — done / active / pending / failed at
a glance, click a step to drill into its dispatched tool calls and outputs.
Cost & token analytics. Time-range tabs at the top, big stacked area chart
in the middle (cost-per-day, stacked by tool source), donut breakdown for the
selected range, and a top-N tools table at the bottom. The four KPI cards
above the chart are the same set used on Overview — consistency, not duplication.
~/work/reasonix›usage›last 14 days
balance¥48.20budget78 / 100
24h7d14d30dallgroup by
total cost
¥31.84
▲ 12% vs prior 14d
tokens · in
1.42M
▲ 8%
tokens · out
186k
— flat
cache hit
94%
▲ 2 pts
cost · 14 day · stacked by source¥18.40 / day avg
native · fs¥14.20
native · shell¥10.40
mcp · *¥4.80
subagent¥2.44
cost share · 14d
● fs 45%
● shell 33%
● mcp 15%
● subagent 7%
top tools · by cost14d
Tool
Source
calls
tokens
cost
read_file
native · fs
3,420
812k
¥9.40
edit_file
native · fs
412
340k
¥4.20
run_command
native · shell
128
280k
¥3.10
grep_files
native · fs
62
42k
¥0.68
github__get_pr
mcp · github
14
38k
¥0.52
§10Inventories
Five panels share one pattern: filter chips → big table → detail drawer.
Tools, MCP servers, Skills, Memory entries, Permissions allowlist. The schema
of the data differs; the layout doesn't. Build one component, parameterize it.
Showing Tools as the master mock; the variants below render the same surface
with different data.
— Tools panel: master mock
~/work/reasonix›tools›edit_file
loaded23 / 24
all 23native · fs 7native · shell 3native · web 2mcp · github 5mcp · slack 4subagent 2failed 1×
Tool
Source
Last call
calls · 7d
read_file
native · fs
App.tsx
1,420
ok
edit_file
native · fs
PromptInput.tsx
312
ok
grep_files
native · fs
"workspace"
62
ok
run_command
native · shell
npm run verify
128
ok
run_background
native · shell
npm run dev
14
ok
github__get_pr
mcp · github
esengine/reasonix#13
8
ok
github__create_pr
mcp · github
—
0
idle
slack__post_message
mcp · slack
#dev
3
ok
python_runner
subagent
—
0
load fail
— Same pattern, different data: MCP, Skills, Memory, Permissions
M · MCP servers
running 2stopped 0errored 0
Server
Transport
tools
State
github
stdio
5
● up · 14m
slack
streamable-http
4
● up · 14m
S · Skills
all 8subagent 2inline 6
Skill
Kind
runs
init
inline
3
review
inline
12
security-review
subagent
2
simplify
inline
8
claude-api
inline
4
· Memory entries
all 14user 3feedback 5project 5reference 1
FB No Co-Authored-By trailer
FB No conversation in code comments
FB Tokenization facts (DeepSeek BPE)
PJ v0.18 dashboard redesign queue
PJ 0.18.1 ghost-frame deprecation
U User env: PowerShell + RMB
▎ Permissions allowlist
deny 2allow 18ask 5
Pattern
Verdict
hits
npm *
allow
128
git *
allow
94
npm publish
ask
3
rm -rf *
deny
0
git push --force *
deny
0
§11System
The diagnostic surface — answering "is anything wrong" in one screen. A health
grid (each check is a labeled card with a left-edge state stripe), an environment
info table, and a live log tail at the bottom for the agent's own structured
events. When something's broken, this is the first place a user looks.
The semantic-search panel: a search bar at the top, an indexing-status sidebar,
and result cards with snippets and highlight. Distinct from the global
command palette — the palette navigates known things; semantic search
finds code by what it means, given a vector index over the project.
// When change_workspace fires its WorkspaceConfirmationError,// any subsequent calls in the same parallel batch would dispatch// against the OLD sandbox before the user has approved…let workspaceSwitchPending = false;
for (const call of repairedCalls) {
src/tools/shell.tsL277 – L298 · runCommand0.84
const onAbort = () => {
aborted = true;
killChildTree();
};
// Check synchronously first — if the signal aborted before listener attachif (opts.signal?.aborted) onAbort();
// Wire parent-abort → child-abort. Two pitfalls we have to handle:// 1. The signal may already be aborted at attach time…const abortChild = () => childLoop.cancel(parentSignal.reason);
Config preview dry-run output before saving
Clicking Preview on the index-config card POSTs the pending config to /api/index-config/preview, which runs the chunker walker without writing. Shows the projected delta + a sample of files that would change category. No state is mutated.
⊕preview · pending changesunsaved
files now312
files after save287 (−25)
chunks delta~−140
excluded by reason
dirs14
exts8
patterns2
.gitignore1
sample (first 5 of 25)
−tests/fixtures/large-trace.json· patterns
−.cache/parser.bin· dirs
−assets/screenshot-12.png· exts
−build.lock· exts
−scripts/dev-only.sh· .gitignore
Build progress when index is being rebuilt
≈building index · 312 files
scan
312 / 312
chunk
1,842 / 1,842
embed
1,142 / 1,842
write
pending
38s elapsed · ~22s remaining
§13Configuration
Hooks and Settings share a layout: a left rail with sub-sections, a
main pane with the form. Hooks gets an extra concept — the event
matrix — showing at a glance which hook script fires on which
LoopEvent. Settings is mostly form-controls; the only non-trivial widget
is the JSON view on the raw settings.json.
~/work/reasonix›hooks›event matrix
active4 hooks
⊞Event matrix
+Add hook
↻Reload
⚠Recent failures3
jump · settings
⌘General
$Budget
▎Permissions
MMCP servers
{}Raw settings.json
Event matrix
Which hook script fires on which LoopEvent. Click a cell to edit timing, glob, or to disable. Adding a new hook (left rail) drops a row; the script lives in .reasonix/hooks/.
For everything not exposed via form (custom keymap, env passthroughs, exotic MCP transport overrides), the raw editor is one click away — same CodeMirror as the Editor panel, with JSON schema validation for autocomplete and warnings.
●settings.jsonjson · LF · UTF-8saved · hot-reloadedLn 7, Col 42
§14Open questions
Decisions deliberately deferred until implementation begins.
Take-over UX
When the dashboard takes input, does the TUI show the streaming response live (read-only), or pause until the dashboard releases? Lean toward live read so terminal-2 keeps reading while terminal-1 has the keyboard.
Sidebar grouping
Three groups (workspace · observe · configure) feel natural now. If the panel count grows past 14, may need a second axis (collapsible sub-sections) — defer until pressure exists.
Mobile / narrow
Out of scope for v1. The dashboard is a localhost development tool; phone-screen layout would only matter if Reasonix ever runs as a hosted service.
Theming
Single dark theme for v1. Light theme is a 1-week effort and not on the path right now — the TUI is dark-only too, theme parity is a non-goal.
Editor panel
Not mocked here. Lives in the same shell, but its core is a CodeMirror instance + tabs + tree view — those have their own design language already (CodeMirror's default dark + our palette overrides). A separate doc when we touch Editor.