feat(v0.2): UI modes wiring + auto-theme + error catalog + /search + /palette#5
Conversation
Phase 0 + 1 + 3 + 4 + 5 of the v0.2 blueprint, in one shippable PR.
Core foundations
----------------
- src/core/EventBus.ts: typed pub/sub bus for cross-module events
- src/core/AgentStateMachine.ts: 6-state FSM (idle/thinking/executing/
waiting/success/error) with explicit transitions
Terminal + TUI
--------------
- src/platform/TerminalCapabilities.ts: single source of truth for OS,
shell, color depth, unicode, size class, SSH/CI/dumb/Termux/WSL flags
- src/tui/LogoAnimator.tsx: status-reactive living logo with 4-tier
fidelity (Unicode-rich, Unicode-minimal, ASCII, no-anim)
Model Context Protocol (MCP)
----------------------------
- src/mcp/types.ts | config.ts | client.ts | registry.ts | permission.ts
| manager.ts | index.ts
- Loads ~/.fastcode/mcp.json + .fastcode/mcp.json + project mcp.json
- Hand-rolled JSON-RPC stdio client (no heavy SDK dep)
- Permission classifier with low/med/high risk + auto/confirm/deny
Sandbox v2
----------
- src/sandbox/PathGuard.ts: resolve + validate every fs path through one
guard, with deny globs and symlink defense
- src/sandbox/policy.ts: load .fastcode/policy.json, expose shell/network
guards, default-deny destructive commands
- gitignore-style globToRegex with **/.env etc.
Browser tool
------------
- src/browser/text.ts: text-only engine on top of lib/web.ts
- New agent tool browser_fetch wired through the policy hook
- Headless engine deferred to a follow-up PR (API stable)
Agent integration
-----------------
- runAgentTool now accepts AgentToolServices ({browser, mcp, signal})
- New agent tools: browser_fetch, mcp_call (server.tool addressing)
- AGENT_SYSTEM_PROMPT advertises the new tools
Slash commands
--------------
- /mcp [status|tools|restart <s>|permission <s> <a|c|d>]
- /sandbox [status]
- /mode [normal|compact|focus|debug|noanim]
App wiring
----------
- App holds a single AgentStateMachine + capabilities snapshot
- LogoAnimator renders above the prompt, reacts to FSM state
- MCP manager auto-starts only when .fastcode/mcp.json exists
- AppActions exposes optional mcp / sandbox / setUiMode hooks
Tests (all passing — 122 total, 47 new)
---------------------------------------
- eventBus, stateMachine, terminalCaps, logoAnimator
- sandboxPath, permission, mcpConfig, mcpRegistry
- mcpClient (real stdio echo server fixture), browserText
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…dless browser
Phase 2:
- src/lib/inputHistory.ts: persistent JSONL history at ~/.fastcode/history
- src/tui/PromptV2.tsx: multiline editor (Shift+Enter), Up/Down history recall,
paste-burst detection, slash-command autocomplete, Ctrl+U/W/A/E/K keybinds,
cursor block highlight via ink inverse, blink toggle
- src/tui/DiffView.tsx: unified-diff parser + themed +/- rendering
- src/ui/Message.tsx: BubbleRole expanded (tool/error/warn/success/command/diff)
with distinct sigils, color routing, inline DiffView for diff role
Phase 5b:
- src/browser/headless.ts: lazy playwright-core loader + HeadlessEngineUnavailableError
- src/browser/index.ts: loadBrowserEngineDetailed({ prefer, strict }) with
graceful fallback to text engine when playwright-core is missing
Phase 6:
- src/config/themes.ts: 4 new themes (cyber, nocolor, hicontrast, termux)
- src/types.ts: Theme adds optional warn/tool/command/diffAdd/diffDel/noColor
- pickThemeForCaps() picks nocolor / termux automatically when warranted
Wiring:
- src/app.tsx: Prompt -> PromptV2 with history ref; tool calls now render
via tool/success/error/diff blocks; Cancelled goes to warn role
Tests (+25 new, 146 total):
- test/inputHistory.test.ts (5)
- test/diffView.test.ts (3)
- test/themes.test.ts (6)
- test/promptHelpers.test.ts (5)
- test/headlessEngine.test.ts (5)
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…palette Phase 7a — UI mode wiring: - src/tui/DebugPanel.tsx: live FSM/caps/MCP panel for uiMode=debug - src/app.tsx: 'compact' & 'focus' hide banner; 'focus' hides StatusBar + LogoAnimator + bottom hint; 'debug' adds DebugPanel above logo Phase 7b — auto theme selection: - src/app.tsx: when config.theme is still DEFAULT_THEME, pickThemeForCaps() upgrades to 'nocolor' or 'termux' automatically based on capabilities Phase 8 — long output protection: - src/lib/transcriptVirtual.ts: virtualize() and clipLines() helpers - src/app.tsx: tool outputs are clipped to 200 lines with a trailer marker before reaching the chat bubble Phase 9 — search + palette commands: - src/commands/search.ts: /search <query> greps ~/.fastcode/history JSONL and renders the most recent matches with timestamps - src/commands/palette.ts: /palette groups every visible command into Session/Editing/Provider/Sandbox+MCP/Misc buckets with aliases Phase 10 — error catalog: - src/lib/errorCatalog.ts: explainError() and formatFriendlyError(), classifying 11 common failure shapes (FS_NOT_FOUND, FS_DENIED, PIPE_BROKEN, NET_TIMEOUT, NET_DNS, SANDBOX_DENIED, MCP_TRANSPORT, ABORTED, AUTH_FAILED, RATE_LIMITED, PROVIDER_5XX) with actionable hints - src/app.tsx: tool failures and chat exceptions both run through explainError() before getting pushed as 'error' bubbles Tests (+19 new, 184 total): - test/transcriptVirtual.test.ts (5) - test/errorCatalog.test.ts (10) - test/searchCommand.test.ts (4) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
v0.2 end-to-end TUI test — 5 passed, 2 real defects foundTested all three v0.2 PRs (#3 + #4 + #5) live in xterm by driving the TUI via simulated keystrokes. Full report with screenshots: see attached Lead — escalations first🔴 DEFECT 1 — PromptV2 paste-burst corrupts multiline input // src/tui/PromptV2.tsx:~94
if (input && input.length > 1 && !key.ctrl && !key.meta) {
insertText(input.replace(/\r\n?|\u2028|\u2029/g, "\n"));
return;
}🔴 DEFECT 2 — React duplicate-key warning on transcript pushes ℹ️ DEAD CODE — Test resultsTests verified live in TUI (click to collapse)
Out of scope
Devin session: https://app.devin.ai/sessions/0e73b43883c04a17b651ca6aa7dc1a5e |
Summary
Stacked on top of #4. Closes out the remaining v0.2 phases — Phase 7 (display modes), Phase 8 (long-output protection), Phase 9 (search + palette), Phase 10 (error catalog). All 184 tests pass; lint, typecheck, and build are clean.
Phase 7 — Display modes + auto-theme
src/tui/DebugPanel.tsx— bordered panel showing FSM state, terminal caps, MCP server count, and a flags row. Mounts only whenuiMode === "debug", so production sessions stay clean.src/app.tsx:compactandfocushide the welcome banner.focusalso hides the StatusBar, the LogoAnimator, and the bottom command-count hint — pure chat + prompt for serious coding sessions.debugadds the<DebugPanel>above the logo with FSM / caps / MCP counts / browser engine kind / transcript length.noanimalready wired throughstableflags and now propagates toPromptV2too.config.themeis still the stock default,pickThemeForCaps()upgrades tonocolor(whenNO_COLOR/ dumb terminal) ortermux(when running on Android) based onTerminalCapabilities. Explicit/themechoices are honored unchanged.Phase 8 — Long output protection
src/lib/transcriptVirtual.ts:virtualize<T>(items, { maxRender, keepRecent })returns{ visible, dropped }for future render-time virtualization.clipLines(text, maxLines, marker?)clips a long string to N lines with a configurable trailer like… 412 more lines (use /show last).src/app.tsx— every tool-call result now goes throughclipLines(result.output, 200)before reaching the chat bubble. Stops a 50k-line stack trace from blowing up the TUI; the full text is still in the chat history for/save/ future inspection.Phase 9 — Search + palette
src/commands/search.ts—/search <query>(aliasesfind,history-search) reads~/.fastcode/historyJSONL directly, fuzzy-matches on substring, and renders the 10 most recent hits with timestamps and one-line previews. Works even on a fresh restart before the live history ref has anything.src/commands/palette.ts—/palette(aliasescommands,cmds) groups every visible command into 5 buckets (Session, Editing, Provider, Sandbox + MCP, Misc) and lists each with its aliases and description. Textual fallback for a future Ctrl+P overlay.Phase 10 — Error catalog
src/lib/errorCatalog.ts— small classifier matching common error shapes:FS_NOT_FOUND(ENOENT),FS_DENIED(EACCES)NET_TIMEOUT(ETIMEDOUT/timeout),NET_DNS(ENOTFOUND/getaddrinfo),PIPE_BROKEN(EPIPE)SANDBOX_DENIED(anything withSANDBOX_*prefix)MCP_TRANSPORT(json-rpc / mcp keywords)AUTH_FAILED(401/403),RATE_LIMITED(429),PROVIDER_5XXABORTED,UNKNOWNfallbackEach entry returns
{ code, title, detail, hint? }.formatFriendlyError()renders to markdown for the chat layer.src/app.tsx— both tool failures and chat-loop exceptions now flow throughexplainError()so the user sees a labelled error block (e.g.**Network timeout** (NET_TIMEOUT)\n\n_Couldn't reachapi.openai.comwithin the timeout. Check connectivity and retry._) instead of a rawError: ...line.Tests added (19 new, 184 total)
test/transcriptVirtual.test.ts(5) —virtualizecap behavior,clipLinesdefaults and custom marker.test/errorCatalog.test.ts(10) — every classifier branch +formatFriendlyErrorshape.test/searchCommand.test.ts(4) —/searchand/paletteare reachable by name + alias and produce expected output shape.npm run check(test + typecheck + lint + build) passes locally.Review & Testing Checklist for Human
/mode focus,/mode compact,/mode debug,/mode noanim, then/mode normal. Confirm each visibly changes the UI (banner / status / logo / debug panel) and that re-running normal restores everything.NO_COLOR=1and verify the chat renders innocolorautomatically without manually running/theme nocolor. Then on Termux, verify thetermuxtheme kicks in./search: send a few prompts, restart, and run/search <substring>. Confirm matches surface from the persisted file (not just in-memory)./palette: confirm every visible command appears in exactly one bucket, with its aliases listed.… N more linestrailer instead of overflowing.Notes
main, so this PR's diff also includes the phase 1 + 2 + 5b + 6 commits for convenience. We can split / rebase once feat(v0.2): living TUI + MCP + sandbox v2 + browser tool #3 and feat(v0.2): PromptV2 + chat redesign + theme variants + headless browser #4 land./paletteandindex.tsform a circular import at the module-graph level, but the cycle resolves at runtime becauseCOMMANDSis only read inside the command'srun()function. ESM live bindings do the right thing here.clipLinesonly trims at render time. If long sessions grow uncomfortably, a future PR can render a virtual slice viavirtualize()and replace<Static>with a managed scroll buffer.Link to Devin session: https://app.devin.ai/sessions/0e73b43883c04a17b651ca6aa7dc1a5e
Requested by: @SimpleLittleDev