- 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>
7 KiB
Alacritty parity — what the language needs to build a terminal-class application
Source: .dev/reference/alacritty/
(shallow clone, surveyed 2026-09-01). Companion study to
fiber/00-fiber-parity.md, which asked the same
question for the web-framework surface. This one asks it for a native,
event-driven, GPU-rendered desktop application — the workload class writeonce
is furthest from today.
What alacritty actually is (measured, not summarized from its README)
33,699 lines of Rust across a 4-crate workspace, ~22 direct dependencies:
alacritty_terminal— the portable core, no GUI anywhere in it: PTY creation and lifecycle (tty/unix.rs, 448 lines — openpty, fork/exec of the shell, window-size ioctls, child reaping), a byte-level escape-sequence parser (thevtecrate), the grid data structure, and an event loop over thepollingcrate (epoll/kqueue on arbitrary fds).alacritty— the application: windowing (winit), OpenGL context creation (glutin, EGL/WGL), glyph rasterization (crossfont), a timer-driven scheduler (scheduler.rs— cursor blink, repaint deadlines), config with live reload (notifyfile watching), clipboard, signal handling (signal-hook), CLI (clap).alacritty_config/_derive— config plumbing (a proc-macro crate; writeonce's analogue would be compile-time codegen, which@tablealready does for a different domain).
The split matters more than the line count: the terminal CORE is a files-and-processes program with zero graphics, and the GUI shell around it is where every heavyweight dependency lives.
What already fits — the part that costs nothing
- The concurrency model is a better fit than alacritty's own. Alacritty
runs a PTY-reader thread and a window-event thread synchronized through
parking_lotlocks around the grid. Shard actors with ownership-move messages express this without shared state: a reader actor owns the parser, sends grid deltas; a display actor owns the grid. Iteration 24's chat app already proved the shape (fd-driven actor, fan-out, lifecycle). - Single self-contained binary — alacritty's distribution story is writeonce's existing one.
- Bytes + bitwise operators (iterations 19, 36) — the VTE parser is a byte state machine; the primitive layer for it exists.
fn main, exit codes, env, fs, time (plan 9 stdlib) — config discovery, CLI-shaped startup.
The gaps, ordered by what unblocks what
Each maps onto an existing iteration where one exists; only two items are genuinely new.
- PTY + bounded subprocess — the heart of the core. Openpty, fork/exec with the child on the slave side as its controlling terminal, resize ioctls, orderly child shutdown. This is exactly iteration 28's named gap fan-out ("bounded subprocess, stdin/stdout transport") plus the PTY-specific ioctls. ~450 lines of Rust in the reference; a C-builtin family in writeonce's existing style (crypto/net precedent: small id-numbered builtins, no general FFI).
- Readiness on arbitrary fds. The runtime's io_uring loop watches sockets
it created. A PTY master fd — and later a display-server fd — must be
registrable in the same loop, parking the owning fiber until readable.
Alacritty needs nothing fancier (its
pollingcrate is the same shape); this is a seam widening, not a new subsystem. - Signals as events. SIGCHLD (child died) and SIGWINCH (resize) must arrive as mailbox messages, the way iteration 24 handles fd events. Today signals are runtime-internal (SIGTERM drain). Small, but nothing else can substitute for it.
- Unicode width + UTF-8 decode in the stdlib. The grid is addressed in
cells; every printed byte-run needs "how many columns". A data-table
problem, not a design problem — but without it a terminal misrenders
immediately. (Alacritty:
unicode-widthcrate.) time.monoreturns, plus a timer wheel. Cut 2026-08-10 with "returns when a workload needs monotonic math" — this workload is that consumer.time.after(id 90) exists; a repaint/blink scheduler needs monotonic deadlines that survive wall-clock jumps.- Outbound connection + fd passing + shared-memory buffers. Iteration 38
already names
net.connect. Wayland is a unix-socket protocol: with connect, SCM_RIGHTS fd passing, and an mmap/shm builtin, a Wayland client with software rendering (wl_shm) is expressible in pure.wo— a windowed terminal with no C dependency linked at all. This is the outside-the-box route the reference makes visible: alacritty predates it culturally (X11 era) and pays for GL instead. - The GPU fork — decide late. OpenGL/EGL and font rasterization
(freetype/fontconfig) cannot be spoken over a socket; they are C ABI or
nothing. Three options, same fork iteration 28 already recorded as
"FFI-vs-out-of-process": (a) general FFI — largest doctrine change, rejected
until a second consumer demands it; (b) subsystem C builtins (the crypto
precedent) — a
gfx/fontbuiltin family; (c) an out-of-process render server writeonce talks to over its own socket — fits the actor model, keeps the language pure, costs a second process. Software rendering via route 6 defers this fork entirely: glyph rasterization from a pre-baked bitmap font atlas is pure byte math.
The maturity path, as driving workloads (the project's own method)
Each stage is a shippable proof, ordered so no stage waits on the fork in 7:
- Stage A — headless terminal: PTY builtins + signals + fd readiness
(items 1–3). Proof: a
.woprogram spawns a shell, feeds it a script, captures and asserts the output — ascript/expectclone. Closes iteration 28's bounded-subprocess gap as a side effect. - Stage B — VTE grid: parser + grid in pure
.wo(item 4). Proof: replay recorded terminal sessions (vttest, asciinema casts) and assert final grid state against the reference implementation's. - Stage C — multiplexer: A + B + existing net = a tmux-lite: sessions
survive detach, clients attach over a unix socket. No graphics, real
product, exercises everything server-side writeonce is already good at.
Corrected by the tmux study (2026-09-01): this stage also needs
SCM_RIGHTS fd passing (item 6's builtin, promoted here), termios adoption
of the client's own tty, and the terminfo fork — see
../tmux/00-tmux-parity.md. - Stage D — windowed, software-rendered: item 6 (connect, fd passing,
shm) + a Wayland client library in
.wo. First pixel on screen with zero linked C. - Stage E — GPU: only now decide item 7, with D as the measured baseline that says whether GL is worth an FFI doctrine change.
What this does NOT recommend
No general FFI now (one consumer, and stages A–D never need it); no bundled font rasterizer until D shows bitmap atlases failing; no attempt at winit-class cross-platform windowing — Linux/Wayland first, the same way the runtime is Linux/io_uring first.