Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

CanvasTerminal Feature Audit

Last updated: 2026-08-03 Branch: refactor/solid-architecture

CanvasTerminal is the sole terminal renderer. xterm.js has been fully removed. The renderer is powered by alacritty_terminal (Rust) sending binary grid frames over a Tauri Channel.

Architecture

Terminal.tsx (outer shell)
  +-- Session lifecycle (create/resume/reconnect PTY)
  +-- Parsed event handling (status-line, question, progress, etc.)
  +-- Activity tracking, notifications, auto-retry
  +-- OSC 0/2 title change handling
  +-- Resume/reconnect banners (JSX)
  +-- ComposePanel
  +-- TerminalSearch
  +-- TerminalRef registration with terminalsStore
  |
  +-- CanvasTerminal (sole renderer)
        +-- subscribe_terminal_grid (binary frame push from Rust via Channel/WS)
        +-- Base canvas: text cells, backgrounds, block/box-drawing chars
        +-- Overlay canvas (pointer-events:none): cursor, selection, search highlights, gutter markers
        +-- Custom scrollbar (drag + track click)
        +-- Suggest/intent overlay (DOM divs over canvas)
        +-- Link detection (hover: file paths, web URLs, OSC 8)
        +-- Keyboard input (VT100 + Kitty protocol)
        +-- Touch input (tap/swipe/pinch for mobile/tablet via offscreen textarea)
        +-- IntersectionObserver flow control (skip paint when hidden)
        +-- Plugin raw output forwarding (pluginRegistry.processRawOutput)
        +-- OSC 7 CWD + OSC 133 shell integration
        +-- Imperative controllers (no reactive frame-path state)
              +-- selection + search
              +-- cancellable link verification + caches
              +-- smooth-scroll position + styled-row cache
              +-- keyboard/IME/mouse listener lifecycle

Frame decode, row reconciliation, scheduling, and paint remain colocated in CanvasTerminal. The extracted controllers own independent state and cleanup; they do not add Solid signals, effects, or store writes to the render hot path.

Key insight: Terminal.tsx handles parsed events, session lifecycle, banners, and compose panel. CanvasTerminal is purely a renderer + input handler with no session logic.

Binary Frame Format

Each frame: 26-byte header + variable row data. The header ends with a historyBase: u32 (lines evicted from the history top so far); historyBase + (historySize - displayOffset + screenRow) is the eviction-stable absolute index the smooth-scroll row cache keys by, so a cached row never aliases onto a different line after the scrollback cap rotates. keyboard_flags bits 0–4 remain the public keyboard-mode mask; bit 5 carries the active primary/alternate-screen identity and is removed before exposing keyboardFlags to input code. Per cell: 4 bytes codepoint + 3 bytes fg RGB + 3 bytes bg RGB + 1 byte attrs bitmask = 11 bytes. Decoded in decodeBinaryFrame using struct-of-arrays (SoA) typed arrays — zero per-cell object allocation.

Primary and alternate grids can reuse identical numeric row coordinates while representing unrelated content. A bit-5 transition therefore starts a new renderer generation: smooth-scroll animation, delayed row fetches, selection, search, link verification, reconciliation, and absolute-row caches are invalidated as one transaction. Partial transition frames wait for a full replacement instead of merging into the previous grid.

Performance Notes

  • RAF coalescing: All paint triggers (frame arrival, keydown selection clear, mousedown) go through scheduleRepaint() which schedules a single requestAnimationFrame. No synchronous paint calls — prevents double-paint in a single event loop turn.
  • send_grid_frame clone guard: Frame is only cloned for the grid_watch channel when receiver_count() > 0 (i.e. WS clients connected). Desktop-only path (Tauri Channel) is zero-copy.
  • screen_text_rows_ref(): TerminalGrid exposes a borrowed &[String] view of cached screen rows. Used in process_chunk for chrome cutoff detection to avoid cloning 50 Strings per PTY chunk. Downstream parsers (slash-menu, choice-prompt) share a single owned snapshot computed once per chunk.
  • No per-chunk parser logs: Slash-menu detection can remain active during a large output burst, so its hot path emits events only when a menu is found and never writes a debug record for every parse.
  • Trim in-place: read_screen_text() and row_to_text() use String::truncate() instead of .trim_end().to_string(), eliminating one allocation per row.

Feature Table

Rendering

FeatureStatusNotes
Cell rendering (text + colors)OKfillText per cell, SoA typed arrays
Bold / italic / dim / underline / strikeoutOK
Inverse videoOKresolveFg/resolveBg swap
Block elements (U+2580-259F)OKdrawBlockChar() draws as geometry
Box-drawing (U+2500-257F)OKdrawBoxDrawingChar() draws as geometry
LigaturesOKAdjacent cells with matching attrs grouped into text runs
Cursor shapes (block/beam/underline)OKcomputeCursorRect()
Cursor blinkOK700ms interval, reset on keypress
Unfocused cursor (outline)OKstrokeRect
Overlay canvas (cursor+selection+search)OKSeparate canvas cleared+redrawn every frame; base canvas only repaints dirty rows
DPI/Retina scalingOKdpr * logical sizing + ctx.scale()
DPR change listenerOKmatchMedia(resolution) re-register on change
Theme colors (ANSI 16)OKColors come from Rust/Alacritty in frame data
Default fg/bg from terminal themeOKgetTerminalTheme(settingsStore.state.theme)
Scrollbar themedOKUses var(--fg-primary) CSS custom property with configurable opacity

Zoom / Font

FeatureStatusNotes
Per-terminal fontSizeOKReads terminalsStore[terminalId].fontSize
Global defaultFontSizeOKFallback when per-terminal not set
Font family reactiveOKcreateEffect watches settingsStore.state.font
Font weight reactiveOK
Line height (snapped)OKsnapLineHeight()
Zoom Cmd+/-OKVia terminalsStore.setFontSize
Font preloadOKdocument.fonts.load() targeting configured terminal font

Scroll

FeatureStatusNotes
Mouse wheel scrollOKterminal_scroll IPC
Scrollbar visibilityOKShows when historySize > 0
Scrollbar thumb dragOKCustom implementation
Scrollbar track click-to-positionOK
Arrow Down snap-to-bottomOKWhen displayOffset > 0
Page Up/DownOKVia Terminal.tsx refMethods using terminal_scroll_info IPC
scrollToTopOKVia Terminal.tsx refMethods
scrollToBottomOKVia Terminal.tsx refMethods
scrollToLine (absolute)OKterminal_scroll_to IPC
Viewport lock (ESC[3J suppression)N/AWontfix — no equivalent needed in canvas path

Resize

FeatureStatusNotes
ResizeObserverOK
Debounce (100ms)OKclearTimeout + setTimeout(remeasure, 100)
Minimum size guardOKGuards both resize_pty IPC and remeasure()
resize_pty IPCOK

Input / Keyboard

FeatureStatusNotes
VT100 escape sequencesOKkeyToSequence() in terminalInput.ts
Kitty keyboard protocol (flag 1)OKkittySequenceForKey()
Shift+Enter (ESC CR)OK
Shift+Tab (CSI Z)OK
macOS Ctrl+letter (emacs)OKUses e.code for reliability
macOS Left Option as MetaOKaltSequenceFromCode()
Windows Ctrl+V pasteOK
Cmd+Enter passthroughOK
IME compositionOKcompositionstart/compositionend; hidden input positioned at cursor coords via syncImePosition() for East Asian IME candidate windows
Bracketed pasteOK\x1b[200~...\x1b[201~
Image paste detectionOKChecks items[i].type.startsWith("image/")
Resume banner keyboardOKSpace/Enter/Escape/printable
Touch tap/swipe/pinch (mobile)OKinstallTouchHandlers via offscreen textarea

Selection & Clipboard

FeatureStatusNotes
Mouse drag selectionOK
Double-click word selectOKterminal_select_start with word:true
Triple-click line selectOK
Cmd+C copy with selectionOKterminal_select_text IPC
Trailing-space trim on copyOKline.replace(/\s+$/, "")
Copy-on-selectOKcopySelection() called from onMouseUp
getSelection() ref methodOKgetLocalSelectionText() reads from rowMap codepoints

Focus

FeatureStatusNotes
focus() ref methodOKcanvasTerminalRef?.focus()
Auto-focus on tab activationOKVisibility effect in Terminal.tsx
onFocus callback propOKWired in CanvasTerminalProps
Focus/blur cursor visualOK
focus() ref raceOKResolved via deferred ref registration
FeatureStatusNotes
File path detection (hover)OKAsync row text fetch + regex
File path Cmd+click openOK
Pointer cursor on linkOK
Web URL links (http/https)OKwebUrlRe regex in checkLinksAtRow
OSC 8 hyperlinksOKterminal_hyperlink_at IPC, priority over other link types
FeatureStatusNotes
Cmd+F opens search barOK
Escape closes searchOK
Search results highlightingOKpaintSearchHighlights on overlay canvas
Next/prev match navigationOKsearchNext/searchPrev with wrap-around
searchBuffer() ref methodOKterminal_search_buffer IPC
openSearch/closeSearch refOK

Terminal Bell

FeatureStatusNotes
Visual flashOKframe.bellbell-flash CSS class (150ms)
Audio bellOKnotificationsStore.play("info") via Terminal.tsx

OSC Handlers

FeatureStatusNotes
OSC 0/2 and structured intent title changeOKHandled in Terminal.tsx wrapper; spawn labels remain replaceable, explicit user renames are protected
OSC 7 cwd trackingOKpty-cwd-{sessionId} event
OSC 133 command blocksOKpty-osc133-{sessionId} event
OSC 133 gutter decorationOKpaintGutterMarkers on overlay canvas
User-prompt scrollbar markersOKGreen ticks at userPromptLines — distinct from command-block marks, drawn in paintGutterMarkers
Cmd+Up/Down block navigationOKReads commandBlocks + activeBlock
OSC 9 progress barOKterminal()?.progress → 2px green bottom-edge fill on tab

TerminalRef Methods

MethodStatusNotes
fit()OKDelegates to refresh() (full redraw via ResizeObserver)
write(data)OKpty.write(sessionId, data)
writeln(data)OKpty.write(sessionId, data + "\n")
input(data)OKpty.write(sessionId, data)
clear()OKSends \x1b[2J\x1b[H\x1b[3J via pty.write
refresh()OKClears buffer + requests fresh frame
focus()OK
getSessionId()OK
openSearch()OK
closeSearch()OK
toggleCompose()OKextractCurrentInput reads canvas row text
openComposeWithText(text)OK
searchBuffer(query)OKterminal_search_buffer IPC
scrollToLine(lineIndex)OKterminal_scroll_to IPC
getSelection()OKgetLocalSelectionText()
scrollToTop()OK
scrollToBottom()OK
scrollPages(pages)OK
getBufferLines(start, end)OKterminal_get_lines IPC

Other

FeatureStatusNotes
File drag-and-drop (internal)OKapplication/x-tuic-path MIME from file tree
OS file drag-and-dropOKFinder/Explorer drag via tauri://drag event
Parsed eventsOKHandled by Terminal.tsx wrapper
Suggest overlayOKDOM divs over canvas
Intent row highlightOK
Notifications (sounds)OKHandled by Terminal.tsx wrapper
Flow control / backpressureOKIntersectionObserver: skip paint+ack when hidden
Plugin raw output forwardingOKpty-output-{sessionId}pluginRegistry.processRawOutput

Remaining Gaps

None. All tracked gaps have been resolved or marked wontfix.