How it works¶
Trinidad Head is a Cargo workspace of three crates (Cargo.toml), all at version 0.2.0 and
licensed MIT OR Apache-2.0.
| Crate | Path | Job | OS code? |
|---|---|---|---|
core-vt |
crates/core-vt |
Turns the bytes a shell prints into a screen of cells, plus scrollback | None |
pty |
crates/pty |
Runs a shell behind a pseudo console and hands back its byte streams | ConPTY on Windows, openpty on Unix |
trinidad-head |
crates/app |
The window: drawing, input, selection, clipboard | Win32 + Direct2D on Windows, AppKit + CoreGraphics on macOS |
keyboard / mouse
|
v
+-------------+ bytes +-----------+ bytes +-------+
| window | ---------> | pty | --------> | shell |
| (app crate) | | ConPTY or | +-------+
| | <--------- | Unix pty | <--------- output
+-------------+ redraw +-----------+
^ |
| screen of cells | reader thread
+---------- core-vt <-----+
The loop¶
- At startup the window spawns the shell through
pty::Pty::spawnat 80 x 24, then resizes it to fit. - A reader thread reads the shell's output in 64 KiB chunks and feeds it to
core_vt::Terminal::feedunder a mutex. Replies the terminal owes the shell (cursor position reports, device attributes) are written straight back. - The reader then asks the window for one repaint. On Windows it posts a private message
(
WM_APP + 1); on the Mac it queues a block on the main dispatch queue. A flag makes sure only one repaint is queued at a time. - Keys, pastes and mouse reports are turned into bytes in the window code and written to the pty.
- When the shell exits, the window closes. On the Mac a watcher thread also checks every 200 ms whether the shell process itself has ended, because a background helper can hold the terminal open after the shell is gone.
core-vt, the engine¶
crates/core-vt/src/lib.rs has no UI and no OS code, so the README notes it could later back
other views. It depends only on unicode-width.
What it handles, from csi_dispatch, private_modes, sgr and osc_end:
- Cursor movement, erase, insert and delete of characters and lines, scroll regions, tab stops, save and restore cursor.
- Colors: 16 colors, 256 indexed colors and 24-bit RGB. Bold, faint, italic, underline, inverse.
- Private modes: application cursor keys (1), autowrap (7), cursor visibility (25), alternate screen (47, 1047, 1049), bracketed paste (2004), mouse tracking (1000, 1002, 1003), SGR mouse encoding (1006), focus events (1004).
- OSC 0 and 2 (window title), OSC 52 (a program copying to the clipboard; reads with
?are ignored), OSC 133 shell-integration marks (prompt, command, output, exit code), kept but not yet shown anywhere. - Wide characters (CJK, most emoji) take two cells. Combining marks are not drawn yet.
- DCS, SOS, PM and APC strings are read and thrown away.
- Scrollback is capped at 10,000 lines.
The document for full-screen programs¶
A program on the alternate screen gives the terminal no scrollback. Claude Code goes further: it never takes the alternate screen and repaints from the top of the ordinary screen, so its old transcript is overwritten rather than scrolled. Either way a plain terminal holds only what is on screen right now, and selecting anything taller than the window does not work.
record_repaint fixes this. After every feed, it compares the screen's text with the previous
screen. If rows moved up, the rows that left the top are added to history (with their colors). If
the program scrolls back down over text already in history, those rows are handed back instead of
recorded twice. A repaint with no overlap (an overlay, a cleared screen) records nothing. History
recorded on the alternate screen is dropped when the program leaves it. This is why selecting and
scrolling inside Claude Code works like ordinary text.
pty, the shell host¶
Windows (conpty.rs) uses CreatePseudoConsole with flags 0. A comment in the code records
that ConPTY does not pass through what a program wrote; it sends what it last drew, on its own
clock, so very fast repaints can be dropped. The passthrough flag (0x8) was measured on
2026-09-21 and did not help: 84 of 300 lines were still never delivered.
macOS (unixpty.rs) uses openpty, starts $SHELL -l (with -c <command> when a command was
given) in a new session with the pty as its controlling terminal, sets TERM, COLORTERM and
TERM_PROGRAM, and removes every CLAUDE* variable. On quit the window sends SIGHUP to the
shell's whole process group.
The windows¶
Both windows are borderless and draw every pixel themselves: the glass body, the neon rim and
blurred glow in a 26 px transparent margin (which doubles as the resize border), the red, yellow
and green buttons, the sidebar with the palette button, and the resize grip. layout.rs holds the
shared geometry as plain math with tests, so both systems put things in the same place.
- Windows (
win.rs,gfx.rs): a Direct3D 11 device, a see-through swap chain handed to DirectComposition, and Direct2D with DirectWrite for text. The window is per-monitor DPI aware and asks Windows not to round its corners or draw its own border. If the GPU device is lost (driver reset, remote session change) it is rebuilt on the next frame. - macOS (
mac/): anNSWindowsubclass that can take keyboard focus while borderless, a view that implementsNSTextInputClientso dictation and input methods work, and CoreGraphics for the chrome (paint.rs).
Mouse: click or drag¶
When a program has asked for the mouse (mode 1000 or higher), a press in the text area is held back. If the pointer leaves the pressed cell, it becomes a text selection in Trinidad Head. If the button comes up on the same cell, the click is sent to the program as a press and release. Holding Shift always selects. While a drag is held past the top or bottom edge, a timer keeps scrolling, faster the further out the pointer is.
Editing Claude Code's prompt¶
prompt_edit.rs handles a highlight that sits wholly inside Claude Code's input box, which it
recognizes as the rows between two horizontal rules (─) around the caret, with text starting two
columns in (after ❯ on the first row). The terminal cannot edit Claude's text directly, so it:
- sends a mouse click just after the highlight and waits (up to 500 ms) for Claude's caret to move there;
- checks that the caret is in the box and past the start of the highlight, otherwise gives up and sends the key as if nothing were highlighted;
- sends one Backspace per painted character, then a few more one at a time if line wrapping hid characters, watching the caret each time;
- sends whatever was typed or pasted meanwhile, which was queued so it cannot overtake the deletion.
Glow colors per window¶
Each window writes its glow index into a small registry file named after its process id
(%LOCALAPPDATA%\TrinidadHead\windows\ or ~/.trinidad-head/windows/). A new window reads the
others, removes entries of processes that are gone, and picks the first color not in use (see
Configuration).
Typing-delay meter¶
latency.rs times each key from the moment it is sent to the shell to the first frame presented
after the shell printed something. Only the first unanswered key counts, and keys with no echo
are dropped after 2 seconds. It keeps the last 200 samples and reports the median and 95th
percentile. On Windows these show in the title (typing delay N ms (p95 M ms)). Every sample is
appended to latency.log. The code notes this does not include the monitor's own delay.
Control socket (Mac)¶
Each Mac window listens on ~/.trinidad-head/<token>.sock (mode 600) and hands its token to the
shell as TRINIDAD_HEAD_TOKEN. One command per connection, reply ok or error ...:
| Command | Effect |
|---|---|
ping |
Replies ok. |
type <text> |
Sends the text as if pasted (bracketed when the program asked for it; newlines become CR). |
key enter / key return |
Sends CR. |
key escape / key esc |
Sends ESC. |
key tab |
Sends Tab. |
key ctrl-c |
Sends Ctrl+C. |
The helpers in mac-tools/ (th-type, th-alive, th-self-id, th-open) are thin clients of
this socket. Sockets left by windows that were killed are cleaned up when another window starts.
Windows has no control socket.
One Dock icon (Mac)¶
Every Mac window is its own process (open -n). The app's Info.plist sets LSUIElement, so each
starts with no Dock tile. Whichever process holds an exclusive flock on
~/.trinidad-head/dock.lock becomes a regular app with the one icon; the rest wait on the lock in
the background. The kernel releases the lock when the process ends, however it ends, so the next
window takes the icon. Clicking the icon brings every Trinidad Head window forward.
Self-updater¶
update.rs runs once at startup on a background thread, unless TRINIDAD_HEAD_SELFTEST or
TRINIDAD_HEAD_NO_UPDATE is set. build.rs bakes the commit the binary was built from into it.
- Find a clone:
TRINIDAD_HEAD_REPO, else~/Desktop/PROJECTS/trinidad-head,~/dev/trinidad-head,~/trinidad-head. A folder counts only if it has.gitandcrates/app/Cargo.toml. - Skip if
git status --porcelainshows local changes. git fetch --quiet origin main. Stop iforigin/mainis the commit this build came from.- Skip unless the clone is on branch
main. Thengit merge --ff-only origin/main. - Build and install:
scripts/build-mac-app.shon the Mac;cargo build --releasethenpc-tools\install_th.ps1on Windows (with no console windows).
The window already open keeps running the old build; the next window opened is the new one. A
failed build leaves the installed app as it was. Results go to update.log.
scripts/update-check.sh tests these rules against throwaway clones.
Tests¶
Unit tests sit next to the code (#[cfg(test)] modules); the source has 62 #[test] functions
across the three crates. The larger checks drive the real app: scripts/mac-selftest.sh runs
the Mac self-test modes one throwaway window at a time, and pc-tools/th_selftest.ps1 and
pc-tools/th_prompt_test.ps1 do the same on Windows.