The problem
A terminal emulator for people who run Claude Code and local models in several windows at once. Two problems drove it (README.md): identical-looking windows, so you lose track of which is which, and your own prompts getting lost in long AI chat scrollback. Fixing the second meant understanding how Claude Code actually draws its screen.
Constraints
- Windows 10/11 and macOS 14+, using OS APIs directly: ConPTY, Direct3D 11, DirectComposition, Direct2D and DirectWrite on Windows; AppKit through objc2 on Mac. No browser engine and no borrowed terminal core (README.md).
- ConPTY keeps its own copy of the screen and can drop fast repaints. A comment in
crates/pty/src/conpty.rsrecords a measurement: 84 of 300 lines never delivered, with no fix available on the client side. - Claude Code never switches to the alternate screen. It repaints from the top of the normal screen, so ordinary scrollback doesn't capture the conversation.
- Built on a Windows toolchain without Visual Studio (
gnullvmplus llvm-mingw). The Mac release is Developer ID signed and notarized.
What he built
A Rust workspace of about 10,000 lines in three crates.
crates/core-vt(no UI, no OS code): aTerminalwith its own VT parser, SGR attributes, scroll regions, mouse reporting, OSC 52 clipboard and OSC 133 shell marks. The core piece isrecord_repaint()withrun_at(): each repaint is diffed against the last screen to find the longest run of rows that moved, rows leaving the top are banked into a history document, and rows coming back are handed back. That rebuilds a readable transcript from a program that only ever repaints.crates/pty: one API over ConPTY and Unix pty.crates/app: a Windows renderer where every pixel, including the transparent corners and glow, comes from a composition swap chain; a MacTermViewwith IME and dictation; a per-window control socket; a Dock lead elected byflockso the lock drops however the process ends.- Claude Code features: a per-window glow color so no two open windows match (
theme.rs), a bubble around the user's own prompts, andprompt_edit.rs, which deletes a highlighted range inside Claude Code's input box by clicking after it and sending backspaces while watching Claude Code's caret, since that text belongs to Claude Code, not the terminal. selftest.rsand PowerShell scripts drive the real app with stand-in child processes that mimic Claude Code's layout.
docs/RESEARCH.md first planned to build on libghostty; docs/PLAN.md records the switch to writing it from scratch. Most commits carry Claude co-author trailers; it was built with Claude Code as a pair programmer.
Hard bugs
- No history for Claude Code (c57ce54). A raw byte capture showed Claude Code never sends the alternate screen sequence, and history had only been recorded on the alternate screen. Recording moved to every repaint on both screens, with a counter to avoid double-banking rows that really scrolled.
- Same screen banked 43 times (a0d6ce8). Whole-screen matching never matched because Claude Code pins its prompt box and status line to the bottom. A copy produced 859 lines with the prompt row in it 43 times, where 59 lines was correct.
run_at()now finds the longest aligned run per offset and ignores blank rows when voting. - Unsigned wraparound (8a7035a). Inside Claude Code the scrollback length is 0 while the scroll offset isn't, so
scrollback_len() - offsetwrapped as ausizeand no row highlighted, even though copy worked.
Results (from the repo)
- 58 unit tests (22 in core-vt).
- App-driving self-tests, from commit messages: 97/97 on Mac (a0d6ce8), 30/30 on the PC run twice (c57ce54).
- A built-in typing latency meter exists (
latency.rs); no latency result for the app is recorded yet.
What it shows
He works below the application layer when the problem needs it: a VT parser, OS rendering APIs on two platforms, and a screen-diff algorithm built from captured bytes rather than assumptions. The tests drive the real app.