A cmux Dock control that shows the git changes of the selected workspace, IDE-sidebar style. Diff, stage, discard and open a file in your editor without leaving the Dock — and it follows you as you switch workspace.
The control is the panel on the right — a Staged Changes section, a Changes
section, status letters down the right edge, deletions struck through. ⏎ on
ProductCard.tsx opened the diff on the left, in cmux's own viewer.
curl -fsSL https://raw.githubusercontent.com/AlexDemzz/cmux-changes/main/install.sh | bash
cmux-changes dockThe first line builds the panel and puts cmux-changes on your PATH — it needs
the Rust toolchain (rustup.rs). The second adds the control
to ~/.config/cmux/dock.json, backing that file up first and leaving your other
controls alone. Run it again after moving things and it just repoints itself.
What lands on your PATH is a launcher, not the binary: it rebuilds the panel
when the source changed (a no-op ~0.3 s otherwise), so a plain git pull is a
complete update.
Prefer to edit dock.json yourself? The control is:
{
"controls": [
{
"id": "changes",
"title": "Changes",
"command": "$HOME/.local/share/cmux-changes/scripts/cmux-changes.sh run",
"height": 500
}
]
}Then reload the cmux Dock config. Reloading replaces the whole Dock, so any other control you have restarts too — there is no way to restart a single one.
| key | does |
|---|---|
↑ ↓, k j |
move |
g G |
top, bottom |
⏎ |
open the diff in cmux's diff viewer — or fold, on a section header |
e |
open the file in your IDE |
␣ |
stage / unstage |
x |
discard |
← → |
Changes tab, Commits tab — positional, they do not toggle |
⇥ |
alternate tabs |
o |
open the commit on GitHub (Commits tab) |
r |
reload |
q |
quit |
Mouse: one click selects, two clicks within 400 ms open, the wheel scrolls. One click on a section header folds it, one click on a tab label switches tab. One click never opens a file — headers are the deliberate exception, since folding is instant and reversible.
The Commits tab lists git log -n 100 HEAD, with unpushed commits marked ↑.
Each tab remembers its own cursor and scroll. Nothing ever switches tab for you.
e opens an editor, resolved in this order:
$CMUX_DOCK_IDEide=in~/.config/cmux-changes/config- the first of
cursor,codefound onPATH
cmux-changes editor # show all three rungs and which one wins
cmux-changes editor code # write ide=codeA Dock control also takes its own env map, if you would rather keep it in
dock.json:
"env": { "CMUX_DOCK_IDE": "code" }$VISUAL and $EDITOR are deliberately ignored. cmux starts a Dock control with
$shell -lc — a login but non-interactive shell, so zsh reads .zshenv and not
.zshrc, and those variables are usually empty there. Where they are set they
name a terminal editor, which has nowhere to render from a detached GUI launch.
Set icons=0 in ~/.config/cmux-changes/config to replace the Nerd Font
glyphs with a dot.
cmux-changes runRuns the same panel in the current terminal — handy in a cmux split, or to try it before registering the control.
cmux-changes updatePulls the latest version in place. If you have edited the files yourself it stops and says so, rather than stashing your work behind your back. The launcher rebuilds the panel on its next start; reload the Dock to restart it now.
- It follows workspace selection:
cmux workspace list --jsonfor the current worktree, thencmux events --name workspace.selectedon a fifo. A 1 s idle tick re-runsgit status -uall(~43 ms warm) as a safety net. - Diffs are piped to
cmux diff, always targeting the workspace explicitly. A Dock terminal exports its own surface, which lives in no workspace, and cmux would otherwise fail with Source surface not found. - Rendering is plain absolute cursor moves and SGR — no curses. Every row is drawn
to
cols - 1, leaving one column of slack so nothing wraps and scrolls the frame. - The help bar picks a variant from hand-measured width thresholds. Change the
length of a help string and you have to re-measure, or it silently clips to
….tests/t4_widths.pyis what catches that. - The panel is Rust (crossterm for raw mode and input events, the escape sequences written by hand). It started life as a bash script; the pty test suite is what pinned the rewrite to the exact same rendering.
A pty harness that drives the real panel (through its launcher) and asserts on
the rendered screen —
fake-cmux logs every CLI call, so "did it open a diff" is measured, not assumed.
bash tests/mkrepos.sh "$PWD/tests/repos" # 4 throwaway repos, ~1.6 MB, ~20 s
cd tests && python3 t3_click.py| file | covers |
|---|---|
harness.py |
pty plus a minimal ANSI screen emulator; key and SGR-mouse injection |
t1_layout.py |
tab switching, underline position |
t2_nav.py |
scroll, clamp, g/G, fold, per-tab cursor memory |
t3_click.py |
9 click assertions, single vs double, headers, tab labels |
t4_widths.py |
9 widths × 4 repo states, asserts zero column overflow. ~2 min |
t5_states.py |
the three upstream states, empty states |
t6_actions.py |
stage, discard, list-goes-empty. Mutates repos/noup, restores it |
Pass a path as argv[1] to run any of them against a copy elsewhere on disk.
- cmux
- the Rust toolchain —
cargoon PATH (rustup.rs) - git
- python3, for the tests
- a Nerd Font, for the file-type glyphs
cmux-changes uninstallRemoves the command, its config and its control in ~/.config/cmux/dock.json.
Reload the Dock afterwards.
MIT
