writeonce/docs/plan/exploration/tmux/00-tmux-parity.md
shoney.arickathil 8bcd24969e docs(lang42): story, spec and plan for bounded subprocess
- 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>
2026-09-01 22:19:25 +02:00

104 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.