// ZTMUX — ENGINEERING REPORT

A Rust port of tmux — full server + client, ported against the tmux C sources.

Status: 100% functional — building, running, and self-hosting its own tooling. ztmux is the tmux program itself — server and client — reimplemented from the C in Rust. It builds and runs (ztmux new-session, ztmux attach), passes its 1107-case parity suite at 100% (byte-for-byte vs upstream tmux, zero known divergences), logs every bug its harnesses catch in BUGS.md — fixed, or still open and named — and ships original extensions the C tmux has no equivalent for.

>_PARITY VS tmux

1107
Cases passing
1107
Total parity cases
100%
vs tmux next-3.7
0
Known divergences

ztmux is a port of tmux, so "correct" means tmux itself. The parity suite runs the same inputs through the vendored tmux (reference) and ztmux (port) and compares byte-for-byte — the same shape as the sibling ports (zshrs vs zsh, strykelang vs perl). The suite has grown from a handful of format cases to 1107 covering formats, arithmetic, conditionals, string ops, options, buffers, windows, panes, and layouts — and every one now passes byte-for-byte, with zero known divergences.

>_REPOSITORY LAYOUT

Original code is kept apart from the port so the fidelity gate only judges what is meant to mirror C:

PathWhat
src/ported/The tmux port — 138 modules plus 23 compat shims that mirror vendor/tmux one-to-one. Held to C-name fidelity by the anti-drift gate.
src/extensions/Original ztmux features with no C counterpart — structured output, the dashboard and fuzzy-switcher TUIs, and the family of pipeable client subcommands (see Extensions). Exempt from the gate.
src/lib.rs, src/main.rsCrate roots: the module tree (which re-points every ported module's file into ported/) plus the binary entry.
vendor/tmuxRead-only, SHA-pinned reference — see How the port is built.

>_EXTENSIONS — BEYOND tmux

Because ztmux owns the whole stack, it adds capabilities upstream tmux does not have. They are plain clients — they re-invoke the ztmux binary and read its own machine-readable output, so they need no linkage to the server internals. The foundation is structured output plus the two interactive TUIs:

FeatureInvocationWhat
Structured outputlist-* -o json|jsonl|csv|tsv|tableTyped, machine-readable output for every list-* command, driven by the same #{…} format engine. table is aligned columns for humans; json feeds scripts and the TUIs below.
Live dashboardztmux dashboardA ratatui TUI: the session→window→pane tree, per-pane detail table, live server stats with a pane-count sparkline, kill-with-confirm, and 1 s auto-refresh.
Fuzzy switcherztmux switcherType-to-filter picker over every session, window, and pane; Enter runs the right switch-client / select-window / select-pane to jump there.

On top of that sits a family of one-shot client subcommands, each resolving the running server over the list-* -o json query layer. The inspection verbs are pipeable tables that also emit -o json / --json; the action verbs are dry-run by default and only mutate when passed -f / --force.

// INSPECTION — read-only, pipeable:

CommandWhat
ztmux activeFocused window/pane of every session.
ztmux ageSessions ranked by creation age, oldest first.
ztmux aheadHow far each repo is ahead of/behind upstream.
ztmux alertsWindows with a pending bell/activity/silence alert.
ztmux autonameWindows with a pinned name (automatic-rename off).
ztmux bordersWindows that draw a status line on their pane borders.
ztmux buffersThe server's paste buffers, largest first.
ztmux busyPanes running a program, not idle at a prompt.
ztmux changesUncommitted file count per pane's repo, dirtiest first.
ztmux cmdHistogram of commands running across panes.
ztmux commitThe last commit in every pane's repo.
ztmux conflictsRepos with unresolved merge conflicts.
ztmux connectedAttached clients ranked by connection age.
ztmux constrainAttached clients ranked by screen size, smallest first.
ztmux controlClients attached in control mode (-CC).
ztmux cwdWorking directories in use, busiest first.
ztmux deadDead panes still held open in a window.
ztmux dedupFind redundant panes (same cwd + command).
ztmux densityWindows ranked by pane count, most first.
ztmux destroySessions that self-destruct when the last client detaches.
ztmux detachedSessions with no client attached, freshest first.
ztmux diskFilesystem usage behind each pane's cwd.
ztmux doctorEnvironment / server health check.
ztmux elapsedHow long each pane's process has been running.
ztmux envPer-session environment overrides.
ztmux eventsStream server lifecycle events as JSONL.
ztmux fanoutSessions ranked by total pane count.
ztmux finderSearch panes by command/path/title/window.
ztmux focusThe active pane of every window.
ztmux gitGit branch + dirty state of every pane's repo.
ztmux gonePanes whose working directory no longer exists.
ztmux graphRender the server tree as DOT/Mermaid/HTML.
ztmux grepSearch the live contents of every pane.
ztmux groupsCluster sessions by session group.
ztmux historyRank panes by scrollback buffer size.
ztmux hooksThe command hooks configured on each session.
ztmux idleAttached clients ranked by time since last activity.
ztmux infoDeep inspector for a single pane.
ztmux inputPanes that are ignoring keyboard input.
ztmux keysHow many key bindings live in each key table.
ztmux keytableClients parked on a non-root key table.
ztmux layoutsThe current layout string of every window.
ztmux limitEach session's scrollback capacity, largest first.
ztmux linkedWindows linked into more than one session.
ztmux autolockSessions set to lock themselves after idle time.
ztmux marksThe marked pane(s).
ztmux memPanes ranked by process resident memory.
ztmux modePanes currently frozen in a mode.
ztmux monitorWindows armed to alert on activity or silence.
ztmux mouseWhich sessions have mouse mode enabled.
ztmux namedWindows with a deliberate name, not the running command.
ztmux nestedPanes running a nested terminal multiplexer.
ztmux netEstablished outbound connections per pane.
ztmux peekDump the visible contents of every pane.
ztmux pipedPanes with an active pipe-pane capture.
ztmux portsListening TCP ports mapped to panes.
ztmux projectProject kind and root behind every pane.
ztmux psOne-shot pipeable per-pane process table.
ztmux pstreeProcess tree running under every pane.
ztmux readonlyClients attached in read-only mode.
ztmux recentList sessions ranked by last activity.
ztmux remainWindows that keep panes open after they exit.
ztmux remoteThe git remote each pane's repo points at.
ztmux shellsPanes sitting at a bare shell prompt.
ztmux sizeReport pane geometry, smallest first.
ztmux snapshotDump the whole server as one nested JSON document.
ztmux soloWindows holding a single, unsplit pane.
ztmux sshWhich panes hold an SSH connection, and where.
ztmux startcmdThe command line each pane was launched with.
ztmux stashRepositories with stashed work behind a pane.
ztmux statePanes whose process is in an abnormal state.
ztmux statsOne-shot server summary report.
ztmux statusSessions with the status line turned off.
ztmux submodulesRepos with submodules and how many are out of sync.
ztmux syncWindows with synchronize-panes turned on.
ztmux tagThe git describe/tag context of every pane's repo.
ztmux termHistogram of attached client terminal types.
ztmux titlebarSessions that push a title to the outer terminal.
ztmux titlesEvery pane's advertised title.
ztmux treePrint the session/window/pane tree.
ztmux ttyMap every pane to its terminal device.
ztmux usagePer-session CPU/MEM/RSS resource rollup.
ztmux userThe owner of every pane's process.
ztmux utf8The UTF-8 state of every attached client.
ztmux vcsWhich version-control system each pane is under.
ztmux viewersHow many clients are attached to each session.
ztmux visualSessions that show alerts visually instead of just beeping.
ztmux watchTop-like live per-pane process monitor.
ztmux whoClients attached to the server, by session.
ztmux winsizeWindows whose sizing mode differs from the default.
ztmux worktreePanes sitting in a linked git worktree.
ztmux writablePanes whose working directory is read-only.
ztmux zoomWindows with a zoomed pane.

// ACTIONS — dry-run by default, apply with -f:

CommandWhat
ztmux bcastBroadcast a command to many panes at once.
ztmux clearallFree the scrollback of every pane.
ztmux equalizeRe-balance every multi-pane window's layout.
ztmux layoutApply a named layout preset to a window.
ztmux pickBatch ops over the multi-pane mark set (sync/unmark/clear/list).
ztmux pruneRemove dead/empty/idle server objects.
ztmux reviveRevive every dead pane in place.
ztmux retitleLabel every pane with its running command.

>_HOW THE PORT IS BUILT

Two references, both vendored under vendor/ as plain, read-only, SHA-pinned copies so the clone is self-contained:

PathWhatRole
vendor/tmuxupstream tmux C sources (next-3.7)Source of truth — every ported module is diffed against its C counterpart
src/ported/the ztmux portThe living code we own and evolve toward safe, idiomatic Rust

Every ported function carries a back-link to its C origin as a doc comment, e.g. /// C vendor/tmux/grid.c:320: grid_create(). The port report tracks per-function C→Rust coverage: 2,127 of tmux's 2,696 functions ported so far.

>_BUGS THE HARNESS CAUGHT

Verifying against C at every step turns "looks right" into "is right." A sample of faults the harnesses root-caused to a single line — the parity suite and unit tests, plus a sweep that drives every command against a private socket and, for the paths that only run with a client attached (modes, redraw, status), against a real client on a pty. Aborts are keyed on crash reports rather than on "is the server still up," since a dying client can legitimately take the server with it. Each fault below is now locked by a regression test or a build gate (the full log, including what is still open, is in BUGS.md):

  1. A mouse event could kill a window — tmux dispatches keys with switch (key) over the full 64-bit key_code. Five ported handlers matched key as u8 against byte literals, discarding the top bits. KEYC_* codes run sequentially from KEYC_BASE (0x10e000), so 18 real keys alias an ASCII command letter: KEYC_MOUSEUP11_STATUS_DEFAULT (0x10e078) truncates to 'x' — the Kill prompt — and KEYC_DOUBLECLICK11_PANE to 'X', Kill Tagged. Each dispatch now gates on key < 0x80, so only a bare ASCII byte reaches those arms; a build gate fails on any match key as u<N>.
  2. "Not found" returned an arbitrary element — C's TAILQ_FOREACH leaves the loop variable NULL when it runs to completion, and callers branch on that NULL; a Rust for loop that assigns each element retains the last one visited. Five ports had it. cmd_find_client returned a session-less client, so lock-client -t nosuch dereferenced c->session and killed the server — and every CMD_CLIENT_TFLAG command shared it, so detach-client -t <typo> silently acted on the wrong client instead of erroring. window_pane_set_mode reused a wrong-mode entry (type confusion on its data); session_group_synchronize_to synced a lone session from itself and wiped its own window list.
  3. Zeroing a struct that holds a Rust typeVec, String, CString and Box all need a non-null data pointer, but xcalloc hands back all-zero bytes. window_client_modedata holds item_list: Vec, so the first drain(..) in window_client_build dereferenced null and choose-client killed the server. Nothing complains at the allocation; it detonates later, far from the cause. Now built with Box::new and reclaimed with Box::from_raw so Drop frees them — and a build gate fails when any struct with such a field is xcalloc'd, because converting a char * field to an owned CString turns every C-style allocation of its struct into UB.
  4. Destroying a pane rebuilt its mode against the dead windowwindow_pane_destroy called window_pane_reset_mode_all, the interactive teardown that resizes the next mode, redraws and notifies. C calls window_pane_free_modes, which the port was missing entirely, so tearing down a pane rebuilt the customize-mode tree against a window that was already gone.
  5. Freeing a pointer Rust never allocatedsession_group_find mirrored C's throwaway stack struct used as the RB_FIND key. In Rust (*sg).name = … is a place assignment, so it drops the previous value — uninitialized stack garbage that happened to look like an owned string. ztmux new-session -t ggg called free() on a junk pointer and aborted the server. Fixed by searching with rb_find_by: same descent, no fabricated key node.
  6. Mouse-driven TUIs froze mid-pane — a stale - 1 in the SGR-mouse encoder (input_keys.rs) dropped the sequence's M/m terminator after xsnprintf was corrected to exclude the NUL. ztmux wrote \033[<35;69;44 with no terminator; crossterm blocked in read() waiting for an end byte that never came, so rich ratatui/crossterm panes locked up on any click or focus change while keyboard input still worked.
  7. ztmux hijacked real tmux's socket — it resolved its default socket from $TMUX, so launched inside a tmux pane it connected to tmux's server and spoke protocol 8 at it (server exited unexpectedly). Fixed to resolve only from $ZTMUX and advertise both $TMUX and $ZTMUX to panes, so the two multiplexers run side by side.
  8. Server crash on bind-key h then l — the red-black tree's delete rebalance (rb_remove_color, compat/tree.rs) rotated around the wrong node. Rebinding a key that already had a default removed the old node first, corrupting the key-bindings tree and segfaulting the server on startup.
  9. Powerline glyphs rendered as \202\202\202utf8_strvis's inner loop never advanced the source pointer (the C is while (++src < end …)), so each multibyte UTF-8 character was re-read and mangled into raw bytes plus octal escapes.
  10. even-* layouts off by onelayout_spread_cell dumped the leftover column on the last pane instead of handing it to the leading cells; an 80-col two-pane split came out 39|40 instead of tmux's 40|39.
  11. Pane spawn hung on macOS — the ported closefrom looped close() up to the server-raised RLIMIT_NOFILE; replaced with the libproc PROC_PIDLISTFDS path tmux actually compiles on macOS, so the forked child reaches execvp.

>_ANTI-DRIFT GATES

A port can be faked by inventing Rust-only "helper" functions that don't exist in tmux, inflating apparent completeness. tests/ported_fn_names_match_c.rs fails the build when a free fn is added under src/ported/ whose name has no counterpart in vendor/tmux. src/extensions/ is exempt by design — it is original code, not a mirror of C — exactly as #[cfg(test)] blocks are skipped. Pre-existing exceptions (libc/libevent wrappers, Rust glue) are frozen in an allowlist: an audit trail to burn down, not a free pass.

Two more gates grew out of the bugs above, because each was a class rather than a one-off — the kind that the C→Rust ownership migration keeps re-creating:

Both were verified by mutation: reintroducing the defect makes the gate fail with the offending file:line and the fix.

>_TESTING

1107/1107
Parity cases (100%)
1708
Unit tests
2189
C fns ported
125k
Lines of Rust

Four layers stack: byte-for-byte parity against the vendored tmux; in-tree unit tests for pure logic (red-black tree invariants under randomized insert/delete, UTF-8 round-trips, layout arithmetic, and the extensions' rendering via ratatui's headless backend); a crash gate (tests/server_survives_bad_targets.rs) that drives the real binary on a private socket and asserts the server survives commands whose target resolves to nothing; and the anti-drift gates above.

>_"DONE RIGHT"

  1. Start from a working skeleton — a running program to refactor, not a blank page.
  2. Shrink the unsafe surface — replace raw-pointer intrusive lists and C-isms with safe Rust where behavior allows.
  3. Verify against C at every step — a module isn't "ported" until it matches the C reference (parity suite).
  4. Keep it greencargo build and cargo clippy stay clean as code comes over.

>_BUILD & RUN

Requires a C libevent and a terminfo database (ncurses), matching tmux itself. On macOS the build links Homebrew's libevent automatically (TMUX_RS_DISABLE_HOMEBREW_LIBS=1 to opt out). Then the binary ztmux speaks the same commands as tmux — on its own socket namespace (ztmux-<uid>, from $ZTMUX, never $TMUX) so it never collides with a running tmux — plus the extension subcommands:

CommandDoes
cargo build --releaseBuilds the ztmux server + client binary.
ztmux new-session -s workStart a session (as tmux).
ztmux list-panes -a -o tableEvery pane, as an aligned table (or -o json).
ztmux dashboard / ztmux switcherLaunch the live dashboard / the fuzzy switcher.