- claim the lang42 prefix; iteration 42 story (readiness: ready), approved spec, and the 11-task implementation plan - board: pending row for 42; graph: node 42 with green edges (11, 24) - graph: porch track section added (same sweep) - parity studies that motivated 42: alacritty, tmux, zen-browser under docs/plan/exploration/ — staged path, gap lists, refused routes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
104 lines
5.9 KiB
Markdown
104 lines
5.9 KiB
Markdown
# tmux parity — the multiplexer as the language's next driving workload
|
||
|
||
Source: [`.dev/reference/tmux/`](../../../../.dev/reference/tmux/) (shallow
|
||
clone, surveyed 2026-09-01). Companion to
|
||
[`alacritty/00-alacritty-parity.md`](../alacritty/00-alacritty-parity.md),
|
||
whose stage C ("tmux-lite") this study scopes for real. tmux is the more
|
||
instructive reference of the two: it needs **no graphics at all**, so the
|
||
entire program sits inside the territory the alacritty study's stages A–C
|
||
cover — it is the proof target, not a stepping stone to one.
|
||
|
||
## What tmux actually is (measured)
|
||
|
||
~112,000 lines of C (100,027 in the 153 top-level `.c` files; the rest is
|
||
`compat/` shims and headers). Exactly **two** external dependencies —
|
||
libevent (fd loop + buffers) and ncurses used *only* as a terminfo(5) reader
|
||
(`tty-term.c` calls `setupterm`/`tigetstr` and nothing curses-like). Every
|
||
other need is vendored in `compat/`: OpenBSD's imsg framing, `forkpty` for
|
||
five platforms, `daemon`.
|
||
|
||
The architecture is one daemonized **server** owning every session, window,
|
||
pane and PTY (`server.c:190` forks it), and a deliberately thin **client**
|
||
(808 lines): the client connects over a unix socket and **passes its own
|
||
terminal fd to the server** with SCM_RIGHTS (`compat/imsg-buffer.c:798`,
|
||
`proc.c` — `imsgbuf_allow_fdpass`); from then on the server writes escape
|
||
sequences directly to the client's tty. Detach survives because the client
|
||
process is disposable — the server never was attached to a terminal it
|
||
doesn't hold as a passed fd.
|
||
|
||
Where the lines actually go — the multiplexer kernel is small, the UX is not:
|
||
|
||
- **Kernel, ~27k**: `server-client.c` 3,280 · `input.c` 3,745 (the escape
|
||
parser) · `tty.c` 3,221 (the output driver) · `screen-write.c` 3,204 ·
|
||
`window.c` 2,893 · `grid.c` 1,839 · `layout.c` 2,022 (the pane split
|
||
tree) · `tty-keys.c` 1,883 (key decoding) · `utf8.c` 1,043 · plus
|
||
session/spawn/proc/job plumbing.
|
||
- **A command LANGUAGE, ~12k**: 63 `cmd-*.c` files behind a yacc grammar
|
||
(`cmd-parse.y`) — the config file is just commands; `format.c` is 7,182
|
||
lines implementing a template DSL with ~380 variables; `options-table.c`
|
||
2,070 lines of typed settings.
|
||
- **Interactive UX, ~20k**: `window-copy.c` 7,300 (copy mode is the single
|
||
biggest file in tmux) · choose-tree/customize/menus/prompt.
|
||
|
||
## What writeonce already answers
|
||
|
||
- **Client/server over a unix socket** — `listen_unix` landed (iteration 35);
|
||
the request/actor shape is the porch daily bread.
|
||
- **Server = actor tree.** tmux multiplexes everything through one
|
||
single-threaded libevent loop with callbacks; sessions, windows, panes and
|
||
clients as actors with ownership-move messages is the same topology with
|
||
the concurrency written down instead of implied. Iteration 24 (chat rooms,
|
||
fan-out, lifecycle, SIGTERM drain) already proved every piece of the
|
||
pattern.
|
||
- **The DSL layer is free.** tmux hand-rolls a command grammar, a format
|
||
template language and a typed options table (~12k lines) because C has no
|
||
expression language to lend. writeonce's config/scripting surface can be
|
||
the language itself — `${ }` interpolation replaces `format.c`, and a
|
||
`@table` of settings replaces `options-table.c`.
|
||
- **Durable sessions beyond tmux.** tmux state dies with the server; a
|
||
writeonce multiplexer's session/layout tables can be `durable: true` for
|
||
free — scrollback in the WAL is the kind of trick the storage engine
|
||
exists for. Feature, not parity.
|
||
|
||
## Gaps — mostly shared with the alacritty list, two new, one promoted
|
||
|
||
1. **PTY + bounded subprocess, signals, arbitrary-fd readiness, unicode
|
||
width, monotonic timers** — identical to alacritty study gaps 1–5;
|
||
`spawn.c`/`job.c`/`compat/fdforkpty.c` are the reference reading.
|
||
2. **fd passing (SCM_RIGHTS) — PROMOTED.** The alacritty study placed it in
|
||
stage D (Wayland). Wrong stage: detach/attach — the whole point of a
|
||
multiplexer — is built on handing the client's tty fd across a unix
|
||
socket. Second consumer found; the builtin belongs to the multiplexer
|
||
stage. (The alacritty study is corrected in place.)
|
||
3. **NEW — termios control of an existing terminal.** The client must put
|
||
*its own* tty into raw mode and restore it on exit (tmux:
|
||
`cfmakeraw`, `tcgetattr`/`tcsetattr`). The PTY gap covers creating
|
||
terminals; this is adopting one you were given. Small builtin family,
|
||
nothing else substitutes.
|
||
4. **NEW — the terminfo fork.** tmux answers "what escape sequences does
|
||
THIS client's terminal speak" from the terminfo database. Two honest
|
||
options: read the compiled terminfo format in pure `.wo` (a documented
|
||
binary file — a parser, not a linked library; ncurses would NOT be
|
||
imported) or emit a fixed xterm-256color profile and refuse exotic
|
||
terminals by name. Decide at brainstorm; start fixed, the refusal names
|
||
the gap.
|
||
5. **Daemonization** — fork-and-detach with the socket handed over. Cheap,
|
||
and arguably skippable first (a foreground server under systemd was good
|
||
enough for writeonce.de).
|
||
|
||
## Corrected staged path (supersedes the alacritty study's stage C sizing)
|
||
|
||
- **Stage A/B unchanged** — headless PTY runner, then the VTE grid replayed
|
||
against recorded sessions (`input.c` + `grid.c` are the behaviours to pin).
|
||
- **Stage C — the multiplexer** now carries its real bill: A + B **plus**
|
||
fd passing (gap 2), termios adoption (gap 3), and the terminfo decision
|
||
(gap 4). Proof: detach, kill the client, reattach from another terminal,
|
||
scrollback intact — then restart the *server* and reattach with layout and
|
||
scrollback replayed from the WAL, which is the demo tmux cannot give.
|
||
- **Stage D/E unchanged** (Wayland shm, then the GPU fork) — and stage D
|
||
gets gap 2 for free once C lands.
|
||
|
||
Scope honesty: parity with tmux the product is ~100k lines including a 7k
|
||
copy mode and 20k of chooser UX — not the goal. The kernel a driving
|
||
workload needs is the ~27k-line column, and the DSL third of tmux dissolves
|
||
into language features writeonce already has.
|