- 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>
123 lines
7 KiB
Markdown
123 lines
7 KiB
Markdown
# Alacritty parity — what the language needs to build a terminal-class application
|
||
|
||
Source: [`.dev/reference/alacritty/`](../../../../.dev/reference/alacritty/)
|
||
(shallow clone, surveyed 2026-09-01). Companion study to
|
||
[`fiber/00-fiber-parity.md`](../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 (the `vte` crate), the grid data structure, and an event loop over
|
||
the `polling` crate (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 (`notify` file 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 `@table` already
|
||
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_lot` locks 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.
|
||
|
||
1. **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).
|
||
2. **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 `polling` crate is the same shape);
|
||
this is a seam widening, not a new subsystem.
|
||
3. **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.
|
||
4. **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-width` crate.)
|
||
5. **`time.mono` returns, 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.
|
||
6. **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.
|
||
7. **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`/`font` builtin 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 `.wo` program spawns a shell, feeds it a script,
|
||
captures and asserts the output — a `script`/`expect` clone. 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`](../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.
|