From 3d30f8f5eaf3d09a26a53706cd35770a06c5b87e Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Mon, 5 Oct 2026 11:27:00 +0300 Subject: tmuxrdr: README and roadmap What tmuxrdr is, how to install, test, maintain and later build it, and the roadmap: the tmux bindings and behaviours only a patch to herdr's code can give, with the patch and its touch points for each. --- tmuxrdr/README.md | 108 +++++++++++++++++++++++++++++++++++++ tmuxrdr/ROADMAP.md | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+) create mode 100644 tmuxrdr/README.md create mode 100644 tmuxrdr/ROADMAP.md diff --git a/tmuxrdr/README.md b/tmuxrdr/README.md new file mode 100644 index 00000000..b152b39e --- /dev/null +++ b/tmuxrdr/README.md @@ -0,0 +1,108 @@ +# tmuxrdr + +herdr without the disrespect for tmux users. + +[herdr](https://herdr.dev) is a terminal workspace manager for AI coding +agents: tmux's model -- a server owning panes, clients attaching -- plus an +agent-aware sidebar. Its keys are its own. tmuxrdr is herdr with tmux's keys +and tmux's behaviour wherever herdr can be made to have them: tmux 3.7c's +default prefix table, with this machine's `dot/config/tmux.conf` and +`~/.tmux.conf.local` on top. + +Everything tmuxrdr adds is in this directory. The rest of the checkout is +herdr, untouched, on a herdr release tag. + +| Path | What | +|---|---| +| `config/config.toml` | herdr config: tmux's bindings on herdr's actions, each commented with the tmux line it mirrors | +| `plugin/` | the tmux-mode herdr plugin: the bindings herdr has no action for -- break-pane, swap/rotate by pane order, marks, last session, select/move-window, paste-buffer, display-message, select-layout -E, kill with y/n, the `:` prompt, clock, messages, automatic-rename | +| `scripts/install` | puts this machine on the config and plugin | +| `scripts/test-e2e` | presses every binding in a real herdr client and checks the result | +| `scripts/sync-upstream` | rebases tmuxrdr onto a newer herdr release | +| `ROADMAP.md` | what only a patch to herdr's code can do, with the patch for each | + +## Status + +- Based on herdr **v0.9.3**, the release Homebrew ships. +- Of tmux's 88 default prefix bindings, **67 work** (some approximately -- + ROADMAP.md, "Coverage"), **5 are moot** in herdr, **16 need code**. + `scripts/test-e2e`: 66 checks, all passing on v0.9.3. +- **No herdr code is patched yet.** The config and plugin run on stock herdr; + the first code patches are in ROADMAP.md, smallest first. + +## Install + +```sh +tmuxrdr/scripts/install # dry run: what it would do +tmuxrdr/scripts/install yes +``` + +It links `~/.config/herdr/config.toml` to `config/config.toml`, keeping a real +file already there as `config.toml.pre-tmuxrdr`, and +`~/.local/share/tmuxrdr/plugin` to `plugin/`, which it registers with +`herdr plugin link`. herdr saves its settings in place, so changes made from +its settings screen land in `config/config.toml` too -- `git diff` shows them. +Then, in a running herdr: `prefix+r`. + +Needs `jq`. Run it again after moving the checkout. + +To leave: `herdr plugin unlink tmuxrdr.tmux-mode`, remove the two links, and +move `config.toml.pre-tmuxrdr` back if you want herdr's old config. + +## Test + +```sh +tmuxrdr/scripts/test-e2e +``` + +About a minute. It runs a herdr client inside a throwaway tmux server (its own +socket, its prefix off), on a throwaway herdr session and a copy of the +config, presses each binding, checks the effect through the herdr CLI, and +removes everything after. Your sessions and config are not touched. `HERDR=` +tests another binary -- a tmuxrdr build, once there is one. + +## Maintaining + +The fork is meant to follow herdr release by release with as little work as +possible. What keeps it that way: + +- **One branch, `tmuxrdr`:** a herdr release tag with tmuxrdr's commits on + top. Remote `upstream` is herdr, fetch-only -- its push URL is disabled. +- **Fork content stays in `tmuxrdr/`.** Upstream never touches this + directory, so these commits never conflict. Do not edit upstream files for + anything that can live here -- not even the top-level README. +- **Code patches are one commit each, and small.** Put the logic in new + files; keep the lines changed in upstream's files to the hooks that call + it. ROADMAP.md says where each patch touches. +- **rerere is on** in this checkout (`git config rerere.enabled true`), so a + conflict resolved once is resolved by itself next time. + +To move to a new herdr release: + +```sh +tmuxrdr/scripts/sync-upstream # or: sync-upstream v0.9.4 +tmuxrdr/scripts/test-e2e +``` + +`sync-upstream` keeps the branch as it was under `tmuxrdr-on-`; if +something is wrong, `git switch -C tmuxrdr tmuxrdr-on-` puts it back. +Test against the new release: upgrade the herdr on PATH (`brew upgrade +herdr`) while there are no code patches, or build this checkout once there +are. + +## Building + +Not needed until the first code patch: until then, tmuxrdr runs on stock +herdr. When it is: + +- herdr pins Rust 1.96.1 in `rust-toolchain.toml`. Install rustup with + `--no-modify-path` -- dot's shell profiles are links into a repo -- and put + `. "$HOME/.cargo/env"` in `~/.localenv`. +- Fetch crates through Socket Firewall: `sfw cargo build --release`. +- Client and server are one binary and check each other's protocol version. + Stop the server (`herdr server stop`) before switching, then install the + build as `~/.local/bin/herdr`, which comes before Homebrew's on PATH here. + Keep it named `herdr`: agent integrations call that name. +- Never `herdr update` a tmuxrdr build -- it installs stock herdr over it. + +License: Apache-2.0, like herdr (`LICENSE` at the top of the checkout). diff --git a/tmuxrdr/ROADMAP.md b/tmuxrdr/ROADMAP.md new file mode 100644 index 00000000..91dba8ee --- /dev/null +++ b/tmuxrdr/ROADMAP.md @@ -0,0 +1,152 @@ +# tmuxrdr roadmap: what only patching herdr can do + +What is left of tmux's default prefix table, and of this machine's tmux.conf, +after `tmuxrdr/config/config.toml` and the tmux-mode plugin (`tmuxrdr/plugin`). +Each gap: what tmux does, why neither config nor plugin reaches it, and the +patch -- with touch points checked against the herdr v0.9.3 source, the base +this branch sits on. None is applied yet; each becomes one commit on the +`tmuxrdr` branch, kept small for rebasing (README.md, "Maintaining"). + +## Coverage + +tmux 3.7c's default prefix table has 88 bindings (`tmux -L x -f /dev/null +start-server \; list-keys -T prefix`). With this machine's tmux.conf on top: + +- **67 bound** in config or plugin, each pressed in a herdr client (run in a + throwaway tmux on its own socket) and its effect checked through the herdr + CLI -- except `M-n`/`M-p`, seen only to do no harm with no agents running, + and of `1`..`9` only 1 and 3. Approximate rather than exact: `w` `f` `q` (herdr's + navigator), `/` (help, filtered), `C` (settings), `M-n` `M-p` (agents, not + alerts), `E` (evens the whole tab, not just the current pane's + neighbours), the C-/M-arrow step sizes (fractions of the area, not cells), + and window indexes (positions -- see "Stable window indexes"). +- **5 moot**: `S-Up` `S-Down` `S-Left` `S-Right` and `DC` pan or reset the view + of a client smaller than its window. Herdr always sizes a tab to its client. +- **16 need a fork**: `Space`, `M-1`..`M-7`, `<`, `>`, `#`, `=`, `-`, `D`, + `C-z`, `PPage` -- below, with the behaviours a binding cannot carry. + +## Where patches go + +Building, installing and rebasing are in README.md. For the patches below: +the keybinding layer is compact -- adding an action touches four files: +`src/config/keybinds.rs` (config field, parser), `src/input/keybindings.rs` +(`KeybindAction`, resolver table), `src/client/shell/input.rs` (mode +dispatch), `src/client/shell/actions.rs` (what it does). Put the logic in new +files where it can live apart, so the lines that touch upstream's stay few. + +## Patches, smallest first + +### 1. PageUp/PageDown/Home/End/Delete/Insert key names -- tmux `PPage` +- tmux: `prefix+PPage` is `copy-mode -u`, copy mode scrolled up a page. +- Gap: `parse_key_combo` (`src/config/keybinds.rs`, the `match lower.as_str()` + near line 1303) knows no `pageup`, so nothing can bind it -- upstream + issue #4892, "Keybinding will not accept PageUp", opened 2026-10-04. Copy + mode is client-side and not in the CLI, so no plugin can enter it either. +- Patch: six arms in that match (`"pageup" | "ppage" => KeyCode::PageUp`, ...); + a `copy_mode_page_up` action that enters copy mode and applies the page-up + path `src/client/shell/copy_mode.rs` already has (`KeyCode::PageUp`, line + 161). Bind `prefix+pageup`. Small; the key names alone are a likely-accepted + upstream PR. + +### 2. Repeatable bindings -- tmux `-r`, repeat-time 500 ms +- tmux: arrows (select-pane), C-arrows and M-arrows (resize) repeat -- + prefix once, then the arrow as often as wanted within 500 ms of the last. +- Gap: `src/client/shell/input.rs`, the `ClientShellMode::Prefix` arm, sets + `self.mode = return_mode` before running any binding: prefix mode is one + key, always. herdr's resize mode (`prefix+ctrl+r` here) is the nearest. +- Patch: a `repeat` flag on bindings (config: a `keys.repeatable` list of + action names, plus `repeat = true` on `[[keys.command]]` for the C-arrow + commands) and `keys.repeat_time` (ms, default 500). After a repeatable + binding, stay in Prefix with `prefix_repeat_deadline = now + repeat_time`; + on the next key past the deadline, drop to terminal mode and route the key + as typed -- tmux checks on the key too, so no timer is needed. Small to + medium. + +### 3. synchronize-panes -- tmux.conf's ``bind ` setw synchronize-panes`` +- tmux: every pane of the window gets the keys typed into one. +- Gap: no API takes input once for many panes, and input routing is in the + client. Upstream discussion "Hotkey / Toogle to Synchronize Input to All + Panes in a Workspace" asks for exactly this. +- Patch: a per-tab `synchronized` flag and a `toggle_synchronize_panes` + action; in `input.rs`'s Terminal arm, where keys go to + `self.focused_pane_id().map(ClientInputTarget::Pane)` (line 577), fan out + to every pane of the tab while the flag is set; mark it in the tab bar. + Small to medium. + +### 4. C-z suspend-client +- tmux: stops the client (SIGTSTP) and returns to the shell that started it; + `fg` brings it back. +- Gap: client-only; nothing in the client handles it (no SIGTSTP in + `src/client`). +- Patch: an action that leaves the alternate screen and raw mode, raises + SIGTSTP, and on SIGCONT re-enters them and forces a full redraw. Small. + (Under `exec herdr` from a terminal emulator there is no shell to return + to -- tmux has the same problem -- so this matters only for herdr started + by hand.) + +### 5. Stable window indexes -- tmux's renumber-windows off +- tmux: closing window 2 of 3 leaves 1 and 3; prefix+3 still reaches the + third. +- Gap: herdr shows an unnamed tab's place (`tab_display_name`, + `src/workspace.rs` line 442: `tab_idx + 1`) and switch_tab counts places + (`src/client/shell/actions.rs` line 955), so tabs renumber on every close + -- tmux with renumber-windows on. +- Patch: a `ui.stable_tab_numbers` option showing each tab's public number + (already kept: `next_public_tab_number`) and making switch_tab, `0` and `'` + select by it. Small. Optional -- many tmux users set renumber-windows on. + +### 6. `<` `>` window and pane menus +- tmux: prefix+< and prefix+> open the window and pane menus. +- Gap: herdr's menus are right-click only (`src/client/shell/context_menu.rs`); + no action opens one from the keyboard. +- Patch: `open_tab_menu` and `open_pane_menu` actions opening the existing + menus anchored on the focused tab and pane. Small to medium. + +### 7. Marked-pane border +- tmux: the marked pane's border is highlighted. +- Gap: plugins cannot style borders; tmux-mode toasts "pane N marked" + instead. +- Patch: a `marked` pane flag settable over the socket, drawn by the border + rendering in `src/ui/panes.rs`; the plugin then sets it. Small. + +### 8. Layouts -- `Space`, `M-1`..`M-7` +- tmux: next-layout and select-layout even-horizontal, even-vertical, + main-horizontal, main-vertical, tiled, and the two mirrored mains, with + main-pane-width 80 and main-pane-height 24. +- Gap: a tab is a BSP tree (`Node`, `src/layout.rs` line 73). The socket can + set split ratios (`layout.set_split_ratio`, which tmux-mode's `E` uses) but + not move a pane within its tab (`pane move` to the same tab reports + `changed: false`), and `layout.apply` replaces the tab and kills its + processes. A plugin could round-trip every pane through temporary tabs + (`pane move` keeps a pane's process) and rebuild the tree -- it works on + paper, flickers through tab creation in practice, and is not done here. +- Patch: `Layout::apply_preset(preset, ordered_panes)` in `src/layout.rs` + rebuilding the tree from the pane order -- panes are ids in the tree, so + their processes are untouched -- with the seven presets and tmux's + main-pane sizes; `select_layout_*` and `next_layout` actions (per-tab index + for the cycle). Medium. + +### 9. display-panes -- `q` +- tmux: numbers on every pane for display-panes-time (1 s); a digit picks one. +- Gap: needs an overlay over the panes and a timed input mode. herdr's + navigator stands in on `q`. +- Patch: a `DisplayPanes` client mode with a deadline, the numbers drawn by + `src/client/shell/render.rs`, digits focusing the pane. Medium. + +### 10. Paste buffers -- `#` `=` `-` +- tmux: a stack of copied buffers (buffer-limit 50); list-buffers, + choose-buffer, delete-buffer. +- Gap: herdr writes copies straight to the system clipboard + (`write_clipboard`, `src/platform/macos.rs` line 631), and plugins see no + copy event, so nothing can keep a history. tmux-mode's `]` pastes the + clipboard, which is tmux's newest buffer. +- Patch: push each copy onto a capped buffer list as it is written; a buffer + view for `#`, a picker for `=` that pastes through the bracketed-paste path + `pane send-text` already uses (`encode_api_text`, + `src/app/api_helpers.rs`), delete for `-`. Medium. + +### 11. `D` choose-client +- tmux: lists attached clients; pick one to detach. +- Gap: no client list in the CLI or UI. +- Patch: a client registry query on the server and a picker. Medium; low + value on a single-user machine. -- cgit v1.2.3