Skip to content

feat: neutral greyscale theme with fullscreen TUI, virtual scrolling, and UI redesign#17

Merged
code-crusher merged 6 commits into
mainfrom
theme/bw-grey
Jul 13, 2026
Merged

feat: neutral greyscale theme with fullscreen TUI, virtual scrolling, and UI redesign#17
code-crusher merged 6 commits into
mainfrom
theme/bw-grey

Conversation

@code-crusher

Copy link
Copy Markdown
Member

Summary

Complete visual refresh of OrbCode CLI with a neutral black/white/grey theme, alternate screen buffer TUI, virtual transcript scrolling, and redesigned header/input box.

Changes

feat(theme): neutral greyscale palette with semantic accents

  • Replace cyan/teal palette with #ffffff primary, #d0d0d0 accent, #a8a8a8 thinking, #7a7a7a dim, #1a1a1a background
  • Semantic colors: error #E34671, success #3FA266, warning #E2CE76
  • Migrate all <Text dimColor> to explicit COLORS.dim across entire UI
  • Add new tokens: userBg, popupBg, inputBorder, inputBorderInactive, orbitalOuter, orbitalInner

feat(tui): alternate screen buffer with cursor-addressed rendering

  • Enter alternate screen (\x1b[?1049h) on startup — no scrollback pollution
  • Set terminal default fg/bg via OSC 10/11 to match theme
  • createFullscreenStdout proxy: converts Ink frames to cursor-addressed rows with EL 2
  • Synchronized update DCS for tear-free rendering, only rewrites changed rows
  • Mouse tracking enabled to prevent wheel-from-scrollback
  • Graceful terminal restore on exit

feat(ui): virtual scrolling transcript with mouse wheel

  • Full transcript height tracking with viewport-based virtual rendering
  • Mouse wheel support (SGR, URXVT, X10 protocols) with smooth scroll at ~30fps
  • PageUp/PageDown navigation, Ctrl+D double-press-to-exit with 3s timeout
  • Dynamic resize handling, exact termRows root container
  • paddingX={2} for consistent horizontal margin
  • RowView now React.memo with width-aware layout

feat(ui): redesigned header and input box

  • Header: orbital brand mark with half-block glyphs in source-art colors, metadata column, action command grid, shortcut bar
  • InputBox: full borderStyle="round" chrome, styled floating popups with rounded unicode edges, Escape-to-clear

chore: bump version to 0.4.2

  • Package version, lockfile, and changelog updated

matterai-app Bot added 5 commits July 13, 2026 11:39
Replace the cyan/teal palette with a neutral black/white/grey foundation:
primary (#ffffff), accent (#d0d0d0), thinking (#a8a8a8), dim (#7a7a7a),
bg (#1a1a1a). Semantic colors use distinct tones: error (#E34671),
success (#3FA266), warning (#E2CE76).

Add new tokens: userBg, popupBg, inputBorder, inputBorderInactive,
orbitalOuter, orbitalInner, user.

Migrate all <Text dimColor> and hardcoded color="gray" usages to
explicit COLORS.dim references across the entire UI component tree.
Replace cyan/teal error/success colors in MCP auth callback page.
Switch code-block accent in markdown rendering to COLORS.accent.
…reen rendering

Enter the alternate screen buffer (\x1b[?1049h) on startup so the TUI
owns the full terminal and prior output stays in the primary buffer
— no scrollback pollution from Ink frames. Set terminal default
foreground/background via OSC 10/11 to match the greyscale palette so
text without an explicit color prop follows theme.

Implement createFullscreenStdout — a WriteStream proxy that converts
Ink serialized output into cursor-addressed rows with EL 2 (erase line)
instead of newline-based scrolling. Uses synchronized update DCS
(\x1b[?2026h/l) for tear-free rendering. Only rewrites changed rows by
retaining the previous frame.

Enable mouse tracking (button events, drag, movement, SGR coordinates)
via \x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h to prevent terminal
scrollback navigation from wheel gestures.

Register restoreTerminal on process exit to reset colors (OSC 110/111)
and leave the alternate buffer. Use combined TTY check across stdin,
stdout, and stderr for compatibility with wrapped terminals.
…roll

Rewrite the transcript rendering from a bottom-up scroll approach to a
virtual viewport system that tracks the full transcript height, computes
visible rows based on a scroll offset, and estimates wrapped line counts
for proper layout sizing.

Mouse wheel support: parse SGR, URXVT, and legacy X10 mouse sequences
to detect wheel events and queue smooth scroll deltas at ~30fps with
direction-reversal handling and a 24-line cap.

PageUp/PageDown keys scroll by viewport-sized chunks.

Implement Ctrl+D double-press-to-exit with a 3-second confirmation
timeout displayed in the status bar. The StatusBar gains new props:
exitConfirmationActive and MODE_COLORS for colored approval-mode labels.

Dynamically track terminal resize via stdout prependListener for stable
frame dimensions. Render root container at exactly termRows height with
overflow=hidden so Ink always clears/redraws in place.

Add paddingX=2 for consistent horizontal margin across the entire UI.

Refactor RowView as React.memo with a width prop for consistent line
wrapping. Implement formatUserBlock — a helper that renders user message
text as a padded, background-filled block spanning the full transcript
width. Replace hardcoded diff background colors with COLORS.error/success.
Migrate remaining dimColor usages to explicit COLORS.dim.

Create src/ui/terminal.ts with mouseScrollDelta and isMouseInput
utilities used by both App and InputBox.
…bordered popups

Header: Replace the old bordered ASCII logo with a multi-panel layout
featuring the ORBITAL_MARK character art rendered with half-block glyphs
(▄, ▀, █) in source-art colors (cyan outer, cyan inner, white core).
Add metadata column (model, workspace) with dimmed labels. Add a compact
two-row action command grid (/new, /resume, /model, /help) with padded
columns and a shortcut reference line. Remove the old borderStyle="round"
wrapping box.

InputBox: Replace top/bottom-only border with a full borderStyle="round"
box using 50%-brightness chrome (COLORS.inputBorder active /
inputBorderInactive inactive). Redesign slash-command and file-completion
popups as styled floating panels with rounded unicode edges (▗▄▖ / ▝▀▘),
background color, selected-item highlighting (❯), and proper truncation
via fitText helper. Add Escape-to-clear-input shortcut. Consume the
width prop for consistent sizing with the transcript.
Bump version from 0.4.1 to 0.4.2. Sync package-lock.json version to
match. Add changelog entry documenting the theme/bw-grey changes:
neutral greyscale palette, alternate screen buffer, virtual scrolling,
header/input box redesign.
@matterai-app

matterai-app Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary By MatterAI MatterAI logo

🔄 What Changed

Implemented a comprehensive visual and functional overhaul of the OrbCode CLI. Key changes include a fullscreen TUI using the alternate screen buffer, a neutral greyscale theme, and a new transcript system in Agent.ts to record and persist exact UI states. Added virtual scrolling for performance, mouse support, and a redesigned header. The Agent now includes legacy session conversion logic to bridge older message schemas to the new transcript format.

🔍 Impact of the Change

Significantly improves TUI performance and session persistence. Virtual scrolling prevents lag in long conversations, while the alternate buffer keeps the user's primary terminal scrollback clean. The new transcript system ensures that resumed sessions look identical to their original state, including reasoning steps and tool outputs, enhancing the professional aesthetic and reliability of the CLI.

📁 Total Files Changed

Click to Expand
File ChangeLog
Core Logic src/core/agent.ts Added transcript event recording, legacy session conversion, and state persistence.
Session Schema src/core/sessions.ts Defined SessionTranscriptEntry types and updated SessionData for persistence.
UI Orchestration src/ui/App.tsx Updated resume flow to replay transcripts and initialize agent state.
Input Logic src/ui/components/InputBox.tsx Added dynamic file re-scanning for @-references during input.
Row Rendering src/ui/components/rows.tsx Added alpha-blended backgrounds for diffs and tool execution rows.
Version Bump package.json Updated version to 0.4.2.
Theme Config src/branding.ts Defined greyscale palette and ASCII orbital mark.
TUI Adapter src/index.tsx Added fullscreen stdout proxy and terminal state management.
Auth UI src/mcp/auth.ts Updated OAuth success/error colors.
Login View src/ui/LoginView.tsx Applied new theme colors to login flow.
Markdown Style src/ui/markdown.ts Themed code block borders.
Terminal Utils src/ui/terminal.ts Added mouse input and scroll delta parsing.

🧪 Test Added/Recommended

Recommended

  • Legacy Migration Test: Verify legacyTranscript correctly parses various historical message formats.
  • Virtualization Stress Test: Ensure UI stability with 1000+ transcript rows.
  • Input Performance: Benchmark walkFiles in InputBox on large repositories to ensure no UI hang.

🔒 Security Vulnerabilities

No critical vulnerabilities detected. Terminal escape sequences are hardcoded. Ensure walkFiles in InputBox respects .gitignore to avoid leaking sensitive file paths in the UI picker.

@matterai-app matterai-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 PR Review is completed: Large-scale TUI redesign with fullscreen terminal adapter, virtual scrolling, mouse wheel support, and neutral greyscale theme. The terminal lifecycle management has a gap in signal handling that can leave the user's terminal in a broken state. Reviewed src/ui/App.tsx, src/index.tsx, src/ui/terminal.ts, src/ui/components/rows.tsx, src/ui/components/Header.tsx, src/ui/components/InputBox.tsx, src/ui/components/StatusBar.tsx: virtual scrolling binary search, smooth scroll queueing, proxy-based stdout adapter, and mouse input parsing all look correct.

Skipped files
  • CHANGELOG.md: Skipped file pattern
  • package-lock.json: Skipped file pattern

Comment thread src/index.tsx
Comment on lines +293 to +301
function restoreTerminal(): void {
if (isInteractiveTerminal && process.stdout.writable) {
process.stdout.write(DISABLE_MOUSE_TRACKING)
process.stdout.write("\x1b]110\x07")
process.stdout.write("\x1b]111\x07")
process.stdout.write("\x1b[?1049l")
}
}
process.on("exit", restoreTerminal)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Terminal State Leak on Signal-Based Termination

Issue: The new code enters the alternate screen buffer (\x1b[?1049h), enables mouse tracking (ENABLE_MOUSE_TRACKING), hides the cursor (\x1b[?25l), and overrides terminal colors (OSC 10/11). However, restoreTerminal is only registered on the "exit" event, which does NOT fire when the process is killed by SIGTERM (e.g., kill <pid>, process manager stop) or SIGHUP (e.g., closing the terminal window). This leaves the user's terminal in the alternate screen buffer with mouse tracking enabled — making the terminal unusable until they manually run reset or the disable escape sequences.

Additionally, \x1b[?25l (hide cursor) is sent on startup but restoreTerminal never sends \x1b[?25h (show cursor). While most terminals restore cursor visibility when leaving the alternate screen (\x1b[?1049l), this is not guaranteed by all terminal emulators.

Fix: Add SIGTERM and SIGHUP handlers that call process.exit() (which triggers the existing "exit" handler), and explicitly show the cursor in restoreTerminal.

Impact: Prevents terminal corruption on external process termination and guarantees cursor restoration across all terminal emulators.

Suggested change
function restoreTerminal(): void {
if (isInteractiveTerminal && process.stdout.writable) {
process.stdout.write(DISABLE_MOUSE_TRACKING)
process.stdout.write("\x1b]110\x07")
process.stdout.write("\x1b]111\x07")
process.stdout.write("\x1b[?1049l")
}
}
process.on("exit", restoreTerminal)
function restoreTerminal(): void {
if (isInteractiveTerminal && process.stdout.writable) {
process.stdout.write("\x1b[?25h")
process.stdout.write(DISABLE_MOUSE_TRACKING)
process.stdout.write("\x1b]110\x07")
process.stdout.write("\x1b]111\x07")
process.stdout.write("\x1b[?1049l")
}
}
process.on("exit", restoreTerminal)
process.on("SIGTERM", () => process.exit(143))
process.on("SIGHUP", () => process.exit(129))

New sessions record the live TUI transcript (reasoning, tool calls, results,
diffs, errors) as it streams, so resumed sessions show the full history
including thinking durations, tool summaries, and edit fragments. Older
session files reconstruct best-effort history from stored model messages.

- Add SessionTranscriptEntry union type and transcript field to SessionData
- Intercept onEvent in Agent to record transcript entries as they occur
- Expose displayTranscript getter on Agent for the TUI to consume
- Include legacyTranscript() fallback that rebuilds tool/result history
- Update App.tsx resume handler to iterate displayTranscript instead of
  re-parsing raw messages
- Fix @-file autocomplete re-scanning files each time the popup opens
- Make diff backgrounds 50% alpha-blended and use explicit foreground colors
@code-crusher code-crusher merged commit 9e972f4 into main Jul 13, 2026
1 check was pending
@code-crusher code-crusher deleted the theme/bw-grey branch July 13, 2026 06:44

@matterai-app matterai-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 PR Review is completed: Reviewed all 5 changed files. The transcript persistence and legacy migration logic in agent.ts is well-structured. Found one React anti-pattern in InputBox.tsx where setState and synchronous filesystem I/O occur during render instead of in a useEffect.

Skipped files
  • CHANGELOG.md: Skipped file pattern

Comment on lines +106 to +113
const [files, setFiles] = useState<string[]>([])
const wasClosed = useRef(true)
if (atToken && wasClosed.current) {
wasClosed.current = false
setFiles(walkFiles(process.cwd(), true, 3000).filter((f) => !f.endsWith("/")))
} else if (!atToken) {
wasClosed.current = true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Performance / React Anti-Pattern

Issue: setFiles is called during the render phase, and walkFiles performs a synchronous filesystem scan (up to 3000 files) inline during rendering. Calling setState during render is a React anti-pattern, and blocking the event loop with synchronous I/O during render will cause UI stutter every time the @-token popup opens. If walkFiles throws (e.g., permission error), the error propagates during render rather than in an effect where it can be handled gracefully.

Fix: Move the file-scan logic into a useEffect keyed on atToken so the filesystem scan runs after render, not during it.

Impact: Eliminates render-phase side effects, prevents UI blocking on filesystem I/O, and isolates potential errors from the render path.

Suggested change
const [files, setFiles] = useState<string[]>([])
const wasClosed = useRef(true)
if (atToken && wasClosed.current) {
wasClosed.current = false
setFiles(walkFiles(process.cwd(), true, 3000).filter((f) => !f.endsWith("/")))
} else if (!atToken) {
wasClosed.current = true
}
const [files, setFiles] = useState<string[]>([])
const wasClosed = useRef(true)
useEffect(() => {
if (atToken && wasClosed.current) {
wasClosed.current = false
setFiles(walkFiles(process.cwd(), true, 3000).filter((f) => !f.endsWith("/")))
} else if (!atToken) {
wasClosed.current = true
}
}, [atToken])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant