Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Alacritty Terminal Integration

TUICommander uses alacritty_terminal 0.26.0 as its terminal emulation backend. We maintain a local patch at src-tauri/patches/alacritty_terminal/ referenced via [patch.crates-io] in Cargo.toml.

Why a local patch

alacritty_terminal is designed for the Alacritty GUI app. Several methods and fields needed by an embedded terminal backend are private. Rather than forking the entire repo, we patch the crate locally — minimal changes, easy to audit, easy to rebase on upstream updates.

Our patches

FileChangeWhy
src/term/mod.rspub fn resize_reflow(size, reflow: bool)Disable reflow on resize. Ink/Claude Code uses CUU cursor positioning that breaks when reflow merges/splits lines.
src/term/mod.rspub fn mark_fully_damaged() (was fn)Lets us force full-frame damage directly instead of maintaining a parallel flag.
src/term/mod.rsParse-side damage: TermParseDamage enum, TermDamageState.parse_lines/parse_full, pub fn parse_damage()/reset_parse_damage(), damage recorded in write_at_cursorA SECOND, independent damage view for TUIC’s PTY parse path (TerminalGrid::processChangedRow), read+reset separately from the render damage so the two consumers never steal each other’s damage. Lets process() diff only changed rows instead of rebuilding+diffing the whole screen per PTY chunk. write_at_cursor now damages the written cell (upstream reconstructs input damage lazily at damage() time from cursor deltas, which left the parse consumer blind to typed text); this is at worst a safe over-damage for the render consumer. Correctness pinned by the process_damage_matches_full_diff differential test.
src/term/mod.rsfn osc7770(&mut self, verb, payload)OSC 7770 TUIC protocol handler. Fires Event::Tuic { verb, payload } for in-band state/suggest/intent signalling.
src/term/color.rspub fn named_color_to_index(NamedColor) -> Option<u8>Maps named colors to xterm-256 indices. Eliminates 30-line match duplication in our serializer.
src/event.rsEvent::Tuic { verb, payload } variantCarries parsed OSC 7770 events from VTE to the application layer.
src/term/mod.rsConfig.alt_scrolling_history + alt-grid history in Term::new/set_options, era reset in swap_altUser-visible parity with iTerm2’s optional alternate-screen scrollback, implemented with Alacritty’s separate grids rather than iTerm2’s shared persistent line buffer. Upstream gives the alternate grid capacity 0 (XTerm semantics), so an app printing more than a screenful (gh run watch, less, man) loses whatever scrolls off. The field defaults to 0, preserving upstream behavior for consumers that do not opt in; TUICommander uses the primary cap. Each enter/exit starts a fresh alternate era, so sessions never inherit one another and no alternate lines remain logically retained after exit. Oversized repeated redraws remain repeated because the emulator is byte-faithful, not a semantic snapshot deduplicator.
src/term/mod.rspub fn primary_history_size()Returns primary-grid history even while the alternate grid is active. Durable-log resize synchronization must stay in this coordinate space; using active alternate history can suppress the first normal-shell lines after exit.
src/grid/mod.rspub fn reset_history_era()clear_history() keeps lines_scrolled monotonic because absolute row ids must be stable for the life of a physical line. The alternate screen is a separate content universe wiped on every enter/exit, so it gets a fresh era instead: history and counter reset. Frame-protocol keyboard_flags bit 5 marks the transition; the frontend then atomically invalidates row, scroll, selection, search, and link state.
src/grid/mod.rslines_scrolled field + pub fn total_scrolled()Monotonic count of lines ever scrolled into history (incremented in scroll_up). total_scrolled() - history_size() gives lines evicted from the top, the base for an eviction-stable absolute row coordinate. Excluded from PartialEq; serde(default) so old ref fixtures still load.

VTE patch (src-tauri/patches/vte/)

We also patch the vte crate (0.15.0) to extend the Handler trait:

MethodPurpose
fn osc133(&mut self, command: char, params: &str)Shell integration markers (A/B/C/D). Routes OSC 133;X from osc_dispatch.
fn osc7(&mut self, url: &str)Current working directory. Routes OSC 7;url from osc_dispatch.
fn osc7770(&mut self, verb: &str, payload: &str)TUIC protocol. Routes OSC 7770;verb=payload from osc_dispatch.

OSC 7770 — TUIC Protocol

In-band signalling via the PTY stream. Never written to the grid (consumed by VTE before rendering).

Format: ESC ] 7770 ; verb=payload BEL or ESC ] 7770 ; verb=payload ST

Verbs:

VerbPayloadEffect
stateidle, busy, or awaitingidle/busy: immediate shell state transition (bypasses silence timer). awaiting: emits a confident Question (sets awaiting_input); busy also clears a prior awaiting. Driven by native agent hooks (see AI Agents → Native Hook Instrumentation). Unknown payloads are ignored.
suggestA|B|C (pipe-separated)Emits ParsedEvent::Suggest — never hits the grid, no conceal needed.
intenttext or text (Title)Emits ParsedEvent::Intent with optional tab title.

Advantages over text-based detection:

  • Zero cross-chunk issues (OSC has delimiter-based framing in VTE)
  • Zero conceal (never written to grid cells)
  • Zero regex (structured parse in VTE dispatcher)
  • Zero stale rescan (not in visible buffer)

Upstream API we use directly (no patch needed)

APIUsage
Term::new(config, dimensions, event_proxy)Create terminal grid
Processor::advance(&mut term, data)Feed PTY bytes
term.grid() / term.grid_mut()Read cell grid, cursor, history
term.damage() / term.reset_damage()Dirty-row tracking for incremental serialization
term.scroll_display(Scroll::Delta)Viewport scrolling
term.mode()Check TermMode flags (ALT_SCREEN, SHOW_CURSOR, kitty keyboard)
term.cursor_style()Cursor shape (block/beam/underline)
term.colors()Dynamic color palette (OSC 4/10/11/12 overrides)
term.selection / term.selection_to_string()Native selection API
RegexSearch::new(query) + term.regex_search_right()Native DFA regex search across grid + scrollback
EventListener traitCapture bell, title, clipboard, PTY write-back events

Canonical HTTP text snapshots read a single absolute range from this grid. They must not rebuild a snapshot by appending a separately retained log to the screen: increasing terminal rows can move history back into the viewport and make the two representations overlap.

Notable forks and patches (external)

Zed Editor (zed-industries/alacritty)

Zed maintains branches on their fork with patches not yet upstream:

BranchWhatRelevance
osc-133Semantic cell tagging — cells get Osc133CellType (Prompt/Input/Output) from OSC 133 sequences. Fires Event::Osc133. Requires Zed’s VTE fork (osc-133-2 branch).High — would replace our regex-based extract_osc133() pre-parser. Enables prompt zone rendering. See story 1552.
v0.16-child-exit-patchUses exit_status.into_raw() for ChildExit. Removed from fork (confirmed 2026-05-04).Story 1553 needs re-evaluation — implement independently if needed.
use-zed-vtePins to Zed’s VTE fork with Serialize/Deserialize on parser state. Removed from fork (confirmed 2026-05-04).Was prerequisite for OSC 133; check if osc-133 branch still depends on it.
grid-mutMakes grid_mut() public (removes #[cfg(test)]).Low — we already expose grid access via our own patches.
click-linksURL detection + click-to-open in grid. Ancient branch (pre-0.26 API).None — we handle link detection in our Canvas renderer.
cursor-blinkCursor blink timer via mio::Timer. WIP with debug prints.None — we handle blink in Canvas/JS.
cursor-configRestructures cursor config into cursor.style/hide_when_typing/custom_colors.None — we don’t use alacritty’s config system.
scrollbackAdded scrollback buffer — already merged into upstream alacritty.None (already upstream).
scroll/fix-alt-grid-sizeAlt screen gets zero scrollback — already merged upstream.None (already upstream).

Other projects

  • Rio Terminal — built on alacritty_terminal but maintains its own fork with rendering changes (not relevant to us since we do our own Canvas2D rendering).
  • Ghostty — uses its own terminal emulation written in Zig, not alacritty_terminal.
  • Warp — uses vte + forked alacritty grid internally, tightly coupled to their warpui framework. Not extractable.

Update procedure

Checking for upstream updates

# Check latest version on crates.io
cargo search alacritty_terminal

# Compare with our pinned version
grep "alacritty_terminal" src-tauri/Cargo.toml

Rebasing our patch on a new upstream version

  1. Download the new version:

    cargo download alacritty_terminal@<new_version> -o /tmp/alacritty_new
    

    Or copy from ~/.cargo/registry/src/ after adding the new version to Cargo.toml.

  2. Diff our patches against the old upstream:

    diff -ru ~/.cargo/registry/src/*/alacritty_terminal-0.26.0/src/term/mod.rs \
             src-tauri/patches/alacritty_terminal/src/term/mod.rs
    
  3. Apply patches to the new version. Our changes are small and isolated:

    • resize_reflow in term/mod.rs — add method, modify resize() to call it
    • mark_fully_damaged visibility in term/mod.rsfnpub fn
    • named_color_to_index in term/color.rs — new function, no existing code modified
  4. Update Cargo.toml version and the patches/ directory.

  5. Run tests: cargo test terminal_grid && cargo test vt_log

Checking Zed’s fork for new patches

# List branches on Zed's fork
gh api repos/zed-industries/alacritty/branches --jq '.[].name'

# Compare a specific branch
# https://github.com/zed-industries/alacritty/compare/master...<branch>

Periodic review cadence

Driven by the alacritty-upstream entry in .claude/scheduled-checks.json (every 20 days). Each run:

  • Check crates.io for new alacritty_terminal releases (cargo search alacritty_terminal).
  • Review Zed fork branches for new patches relevant to our embedded backend.
  • On major issues: If we hit terminal emulation bugs, check if upstream or Zed has a fix before writing our own.

Planned patches (stories)

StoryPriorityDescriptionStatus
1552-02ffP2Port Zed OSC 133 semantic cell tagging (requires VTE fork)Done — cell_type tagging + VTE osc133/osc7 handlers implemented
1550-64b1P3Move OSC 133 extraction into VTE handler (blocked by 1552)Done — VTE routes OSC 133 directly to Handler::osc133()
P2OSC 7770 TUIC protocol (state/suggest/intent)Done — full pipeline from VTE→Event→PTY→ParsedEvent
P3Use cell_type for idle detection (OSC 133 shells)Pending — next step after TUIC protocol
1553-5e8cP3Port Zed child-exit raw waitpid statusPending