Skip to content

v2.7.0 - A terminal of its own

Choose a tag to compare

@theyashgupta theyashgupta released this 29 Jul 16:19
· 377 commits to main since this release
v2.7.0
8878af9

Dispatch has embedded a real terminal since the beginning, but it was never really Dispatch's terminal. It was the page ttyd ships, rewritten at every boot by a stack of string patches that reached into a minified bundle to bolt on Cmd-click, Shift+Enter, a font, a mobile viewport, and finally touch scrolling. Each patch matched one exact anchor in someone else's build output, so a routine brew upgrade ttyd could quietly switch any of them off and leave nothing behind but a warning in the boot log. v2.7.0 stops patching a page Dispatch does not own. The terminal is now Dispatch's own client, and ttyd has been demoted to what it is actually good at: handing over the PTY.

See it

Dispatch v2.7.0 terminal with the Ghostty theme and ligatures

Higher-quality video: dispatch-v2.7.0-native-terminal.webm

And the same session on a phone, zooming out to fit more of the agent's output:

Dispatch v2.7.0 zoom control on a phone

Higher-quality video: dispatch-v2.7.0-phone-zoom.webm

A terminal Dispatch owns

The terminal path now serves a bundle built from this repo instead of proxying ttyd's index. Only the WebSocket upgrade still goes to ttyd, and the client speaks ttyd's wire protocol directly: the bare JSON handshake, then opcode-prefixed frames for input and resize, with raw PTY bytes written straight into the terminal rather than decoded per frame, which is what keeps multi-byte characters intact when they land split across two frames. ttyd's own invocation lost the flags that used to carry the look, and is now just a writable, loopback-only PTY with a base path and a tmux attach.

Everything the patches used to add is now first-class code. Cmd-click opens both real OSC-8 hyperlinks and plain URLs through the same activator, which opens a blank tab and nulls its opener before navigating so the opened page cannot reach back. Shift+Enter still inserts a newline instead of submitting, swallowing both the keydown and the keypress that follows so the terminal does not also emit a carriage return. A dropped connection reconnects a bounded number of times with backoff and then stops, and the attempt budget only resets once a connection has held for a few seconds, so a backend that accepts a socket and immediately closes it cannot retry forever.

It looks like your terminal

Dispatch reads your Ghostty configuration and renders the terminal with it. On startup the theme endpoint runs ghostty +show-config, parses the fully resolved output, and maps the first sixteen palette entries, background, foreground and cursor into the terminal's own theme, along with your font size and whether you asked for thickened text. If Ghostty is not installed, if the CLI fails, or if the config comes back incomplete, it falls back to a bundled Catppuccin Mocha and logs once. It never throws, so a broken config cannot cost you a terminal.

Ligatures work, which is the part you notice first. The client renders through the DOM rather than WebGL, specifically so the browser shapes text natively, and enables ss01, calt and liga, so =>, !=, >= and friends render as the glyphs your editor shows. JetBrains Mono Nerd Font ships inside the bundle and loads from a data URI, so glyphs are never a network round trip, and the terminal waits for the font before measuring its grid, with a timeout so a font that never loads still gets you a working terminal in a plain monospace.

Momentum, and a way to actually read code on a phone

v2.6.1 added touch scrolling and said plainly that it had no momentum. It does now. A drag builds velocity, and on release the scroll keeps going and decays to a stop. That rewrite also fixed a calibration bug in the original bridge: it sent pixel deltas, but tmux moves five lines per scroll report, so every drag travelled about five times further than your finger did. The client now accumulates pixels and emits one discrete report per five rows, so the text tracks your finger.

The bigger phone problem was that a narrow screen gets a narrow terminal. At 44 columns the agent's code and diffs wrap or truncate, and detail that is perfectly readable on a desktop is simply gone. Column count is a function of font size, so the fix is a zoom control: pinch anywhere on the terminal, or use the small stepper that fades in when you touch it and fades out when you stop. Zooming out to 70% takes the same phone from 44 columns to 63, and the agent's output reflows to fill it. The level is remembered per device, so the terminal reopens where you left it, and the whole control only exists on touch devices.

Two honest notes on this. Scrolling through tmux history from a phone needs tmux's mouse mode on, which Dispatch deliberately does not turn on for you, because doing so would take over click-drag text selection everywhere else; without it the gesture is a safe no-op rather than a mess. And the smooth scrolling added for desktop wheel and keyboard is genuinely invisible during normal use, because under tmux attach the PTY owns scrolling long before the viewport would animate.

The patch system is gone

ttyd-index-setup.ts and the seven patches it applied at boot have been deleted, along with the environment flag that gated the new client while it was being built. There is one terminal now and no switch to throw. The boot log no longer captures ttyd's page or writes a patched copy to disk, and a ttyd upgrade can no longer silently cost you Cmd-click or your font. The invariant baseline moved with the code rather than around it: the invariant covering theme delivery through ttyd was retired because that mechanism no longer exists, the one covering ttyd as a writable loopback PTY stayed because it still does, and a new one now pins the parts of the client that are easy to break by accident, including the wheel-report gating that keeps a scroll from being retyped into the agent's prompt as arrow keys.

Nothing about this changes what you install or how you start it. Sessions that are already running are adopted across a restart exactly as before.

Full changelog: v2.6.1...v2.7.0

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Reaching the board remotely also needs cloudflared, which Dispatch checks for only when you enable remote access. Setup is in the README.