- 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>
5.9 KiB
tmux parity — the multiplexer as the language's next driving workload
Source: .dev/reference/tmux/ (shallow
clone, surveyed 2026-09-01). Companion to
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.c3,280 ·input.c3,745 (the escape parser) ·tty.c3,221 (the output driver) ·screen-write.c3,204 ·window.c2,893 ·grid.c1,839 ·layout.c2,022 (the pane split tree) ·tty-keys.c1,883 (key decoding) ·utf8.c1,043 · plus session/spawn/proc/job plumbing. - A command LANGUAGE, ~12k: 63
cmd-*.cfiles behind a yacc grammar (cmd-parse.y) — the config file is just commands;format.cis 7,182 lines implementing a template DSL with ~380 variables;options-table.c2,070 lines of typed settings. - Interactive UX, ~20k:
window-copy.c7,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_unixlanded (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 replacesformat.c, and a@tableof settings replacesoptions-table.c. - Durable sessions beyond tmux. tmux state dies with the server; a
writeonce multiplexer's session/layout tables can be
durable: truefor 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
- PTY + bounded subprocess, signals, arbitrary-fd readiness, unicode
width, monotonic timers — identical to alacritty study gaps 1–5;
spawn.c/job.c/compat/fdforkpty.care the reference reading. - 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.)
- 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. - 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. - 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.care 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.