muxr 0.1.11 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0bd4e1e9036cc3f25aaa50e92b4183eb123230b3c33877d369d5350b1de8cc4
4
- data.tar.gz: 55cd2910b19e1f546e6278c7d4532f34f4627be5e30866420e3ebe6b1997f3d1
3
+ metadata.gz: f207f0ebd4d74ae0660e3659cd9ad9703ff414266621d5a2624c5771d267cd0c
4
+ data.tar.gz: f8302e2987a759d5efe1c5971cd9845815f529c2dd901d0eaf229b6e4d107888
5
5
  SHA512:
6
- metadata.gz: 4782c572a8e4ab2ba193bfd38edb0f11c37e8033decc79b5ebda1b2e2629c56adc8363e9ac000c7314eec508faf98f8b6c40fa9dfa03570ed42e9ccee0c2cfca
7
- data.tar.gz: 9f39b068f63bfcae8f72dd7caba1a539723d98534d34cac0b15048f902df3440f3e4c51ac18fa6cf8154fc4eecfec8bc7eacc2c692cbe7ca03774db6cb2bc8bb
6
+ metadata.gz: f746020a1a69c8a90fb96cde030357eb9ac999b1b99e8f88a3b3d23b92d9047bcecc0830edddf4b897fc8627029ee3f539f82bc9216712a3ebff76248ab052aa
7
+ data.tar.gz: 633ff78595a4fc0f041decea8f4a574321908bc7d3a8624e087b000d55b84487baf870f5c9b3ad3f1db5fd517f2f712ff1bd79e33b1763df951801acd1c6c60b
data/CHANGELOG.md CHANGED
@@ -6,6 +6,229 @@ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.0] - 2026-09-23
10
+
11
+ ### Fixed
12
+ - A flash is visible again in the modes that actually raise one. The status
13
+ bar's message branch was the last `elsif` in a chain starting with the
14
+ command prompt and running through scrollback, search and selection, so any
15
+ mode with a full-row overlay painted the row and the message never drew.
16
+ "not found: <query>" from a failed search and "yanked N bytes" from a
17
+ selection both land back in scrollback, where neither had ever been seen;
18
+ the scroll-source work added four more that only fire there, which is why
19
+ `Tab` on a pane with no scroll of its own read as a dead key rather than a
20
+ refusal with a reason. The message now draws after the chain, right-aligned
21
+ over the overlay — transient and specific beats a static key hint.
22
+
23
+ ### Added
24
+ - `:capture [path]` saves the focused pane's full scrollback and screen to a
25
+ plain-text file: no escape codes, trailing spaces trimmed, wide glyphs
26
+ intact. With no path it writes to
27
+ `~/.muxr/captures/<session>-<pane>-<timestamp>.txt`. A relative path is
28
+ resolved against the directory the session was started in. While a
29
+ full-screen program is up, the capture holds the shell underneath rather
30
+ than the program's frame.
31
+ - A config file at `~/.muxr/config.json` (or wherever `MUXR_CONFIG`
32
+ points) sets `layout`, `scrollback`, `master_ratio`, `master_count`,
33
+ `auto_spiral_min` (`{"cols": 180, "rows": 30}`), `prefix` (`"C-b"` to use
34
+ Ctrl-b instead of Ctrl-a), and `keys`, which remaps normal-mode and
35
+ prefix-mode keys onto existing actions (`{"normal": {"Z": "toggle_zoom",
36
+ "q": null}}`; `null` unbinds a key). Settings that don't make sense are
37
+ skipped and reported: the first one is flashed when you attach, and all of
38
+ them go to the session log. A saved session's layout still wins over the
39
+ configured default, and `MUXR_SCROLLBACK` still wins over `scrollback`.
40
+ `:reload` re-reads the file without restarting the server. A key can also
41
+ run any prompt command: `{"normal": {"Y": ":sync"}}`. A configured prefix
42
+ shows up everywhere muxr names the prefix: the help overlay, the `muxr ^b ?`
43
+ hint in the status bar, the `[^B]` chip while the prefix is pending, the
44
+ passthrough flash, and `muxr --help` (which also says which config file the
45
+ prefix came from).
46
+ - `:rename <name>` gives the focused pane a label, shown in its title in
47
+ place of the hex id (`#1 api`). A bare `:rename` clears it. Names are saved
48
+ with the session, travel with a pane when it is moved to another session,
49
+ appear in the attach picker, and are listed by `muxr_panes_list` (except
50
+ for private panes). Any MCP tool that takes `pane` now also accepts a name.
51
+ An id always wins over a name that happens to look like one, and a name
52
+ shared by two panes is refused. The bridge's refusal to let a claude read
53
+ or type into its own pane also applies when that pane is referred to by
54
+ name.
55
+ - `:sync` sends what you type to every pane in the window at once. It works
56
+ in passthrough mode, and `C-a ]` pastes go to every pane too. Use it to run
57
+ the same command on several hosts or checkouts. `:sync on` / `:sync off`
58
+ set it explicitly, and a bare `:sync` toggles it. While it is on, the
59
+ status bar shows a red `[SYNC]` and every unfocused pane gets a red border,
60
+ so you cannot forget it is on. Borrowed panes are included, and your
61
+ keystrokes reach their real shells. The drawer is left out in both
62
+ directions: typing in the drawer stays in the drawer. Sync is deliberately
63
+ not saved with the session.
64
+ - `z` (or `C-a z`, or `:zoom`) zooms the focused pane to full screen, and
65
+ pressing it again restores the layout you were in. The status bar reads
66
+ `layout:zoom:tall` while zoomed, so you can see what `z` will return to.
67
+ Picking any layout explicitly forgets the zoom. A session saved while
68
+ zoomed saves the layout underneath.
69
+ - The master area can be resized and can hold more than one pane, the way
70
+ it can in xmonad. `<` / `>` shrink or grow the master's share of the
71
+ screen in 5% steps, between 10% and 90%. `,` / `.` take a pane out of the
72
+ master area or add one to it, so two panes can share the master column.
73
+ Both keys work in normal mode and after `C-a`, and both apply to `tall`,
74
+ `wide` and `centered`. `:ratio 60` and `:masters 2` set them directly. The
75
+ current shape is flashed on every change and saved with the session.
76
+ - `:silence <secs>` alerts you when the focused pane stops printing. Once
77
+ the pane has been quiet for that long, muxr flashes `pane #2 silent for
78
+ 30s`, rings the outer terminal's bell, and marks the pane `~` in its title
79
+ and in `alerts:`. It fires once per quiet spell and re-arms on the next
80
+ output. It takes `30`, `30s` or `2m`. `:silence off` disarms it, and a bare
81
+ `:silence` reports the current setting. An armed pane shows
82
+ `[silence 30s]` in its title, and the threshold is saved with the session.
83
+ - Panes that want your attention are marked. A pane that rings the bell or
84
+ sends an OSC 9 / OSC 777 notification while you are looking elsewhere gets
85
+ a `!` after its number in the title (`#2!`), and one that prints anything
86
+ gets a `•` (`#3•`). The status bar collects them as `alerts:2!,3•`. Both
87
+ clear the moment the pane is focused. The pane you are looking at is never
88
+ marked, unless no client is attached, in which case nobody is looking at
89
+ it either. Output within 1.5 s of a pane being resized or created is not
90
+ counted: every layout change sends SIGWINCH to every shell, and each one
91
+ answers by redrawing its prompt, which would otherwise light up every pane
92
+ on screen.
93
+ - Scrollback can now drive the program instead of muxr's ring. A pane whose
94
+ program has turned on mouse tracking (DECSET 1000/1002/1003 — `lazygit`,
95
+ `k9s`, `htop`) gets `C-a [` routed to *it*: muxr synthesises
96
+ wheel reports at the centre of the pane and writes them into the pty, so
97
+ `j`/`k`, `C-d`/`C-u` and `C-f`/`C-b` scroll the program's own transcript
98
+ from the keyboard, no mouse involved. SGR encoding when the program asked
99
+ for 1006, legacy X10 otherwise. A full-screen program that wants no mouse
100
+ falls back to arrow keys, which is what `less` and `vim` respond to — so
101
+ `C-a [` no longer dead-ends with "no history while a full-screen app is
102
+ running" on an alternate-screen pane. `Tab` switches between the app's
103
+ scroll and muxr's ring at any time, and the mode chip reads `SCROLL:APP`
104
+ while the app has it. `Terminal` tracks the mouse modes and carries them in
105
+ `dump_ansi`, so a mirrored or moved pane knows its program speaks mouse.
106
+ Visual selection is clamped to the visible screen while the app is being
107
+ scrolled: a self-scrolling program repaints in place, so the ring's tail no
108
+ longer continues into row 0 and a selection spanning that seam would splice
109
+ two unrelated regions into the paste buffer.
110
+ - The MCP bridge now reaches *any* claude started inside muxr, not just the
111
+ one in the Claude drawer. `MUXR_SESSION` and `MUXR_CONTROL_SOCKET` are
112
+ injected into every PTY muxr spawns, so with the bridge registered once at
113
+ user scope (`claude mcp add muxr muxr-mcp --scope user`) a claude launched
114
+ from an ordinary pane is already wired to the session around it. Each
115
+ pane's shell also carries `MUXR_PANE`, its own id: the bridge refuses
116
+ `pane.read`, `pane.send_input`, `pane.run` and `pane.kill` against that
117
+ pane, since driving your own pty feeds your output back to you — the pane
118
+ equivalent of the drawer's `MUXR_DRAWER_SELF` guard. Panes that predate the
119
+ upgrade need reopening to pick the vars up.
120
+ - The bridge follows its pane across a move. Those env vars are a snapshot of
121
+ where the pane was when its shell started, and moving a pane to another
122
+ session leaves them naming the old one — a running process's environment
123
+ cannot be rewritten from outside. When the session the env names no longer
124
+ lists `MUXR_PANE` among its own panes, the bridge asks the other control
125
+ sockets in the same directory which of them owns it and connects there
126
+ instead, re-checking on a ten-second TTL so a pane moved out from under a
127
+ running claude is followed rather than left driving its old session. A
128
+ mirrored pane carries `origin` and is never mistaken for the owner.
129
+ - Attach a pane from another muxr session: `A` (or `C-a A`, or `:attach`)
130
+ opens a picker listing every pane the other live servers on this machine
131
+ are willing to share, grouped by session. The chosen pane is *shared, not
132
+ moved* — it keeps running where it is, both sessions show the same live
133
+ shell, and either can type into it. The owner keeps the PTY and remains
134
+ its only reader; what crosses the control socket is the raw byte stream
135
+ and keystrokes, so colors, the alternate screen and full-screen TUIs all
136
+ mirror faithfully. The PTY runs at the smallest viewport looking at it so
137
+ it fits both layouts. If the owning session stops, the borrower drops the
138
+ pane; if the borrower stops, only the mirror goes away and the pane
139
+ carries on at home. Private panes are never offered, and borrowed panes
140
+ are left out of `:save`. New control methods: `pane.mirror`,
141
+ `pane.mirror_resize`, `pane.unmirror`, `pane.redraw`; `pane.send_input`
142
+ and `pane.run` now also accept `base64: true` payloads.
143
+ - Move a pane between sessions for real, with `m` in the same picker. The
144
+ master pty file descriptor crosses the socket via `SCM_RIGHTS`, so the
145
+ same shell process keeps running — environment, background jobs, screen
146
+ and full scrollback intact — and simply belongs to the receiving session
147
+ afterwards, as an ordinary local pane. The handoff is two-phase: nothing
148
+ is torn down until the receiver confirms it has a working pane, a failure
149
+ anywhere leaves the pane where it was, and an abandoned move times out
150
+ after ten seconds instead of pausing the pane forever. muxr refuses to
151
+ move the last pane out of a session (it would shut that session down) or
152
+ to move on a pane that is itself borrowed. Sessions mirroring a pane that
153
+ moves away are told it is gone and detach. New control methods:
154
+ `pane.move`, `pane.move_commit`, `pane.move_abort`.
155
+ - Kitty graphics protocol images are saved to `~/.muxr/images` and
156
+ announced in the pane as a clickable `file://` path (OSC 8) instead of
157
+ being drawn. muxr renders a cell grid and has nowhere to put pixels, so
158
+ `[image 640×480 → ~/.muxr/images/…png]` is the honest rendering —
159
+ Cmd-click opens it. Multi-chunk (`m=1`) transmissions are reassembled,
160
+ `t=f`/`t=t` file transmissions are read from disk, and raw `f=24`/`f=32`
161
+ pixel data is re-encoded into a PNG container so the saved file is
162
+ actually openable. A capability query (`a=q`) is answered `OK` so inner
163
+ programs pick the kitty path instead of falling back to sixel. The store
164
+ keeps the 200 most recent images.
165
+ - A project page at https://roelbondoc.github.io/muxr/, served from `docs/`.
166
+ Its layout playground runs a port of `LayoutManager` in the browser, so
167
+ pressing muxr's own layout and `hjkl` keys tiles with the real algorithm.
168
+ A **Screen** control switches the simulated terminal between 132×38 and
169
+ 220×54 so `auto` can be seen changing its mind.
170
+ - An `auto` layout (`F`, or `:layout auto`) that picks its geometry from the
171
+ screen it is on: `spiral` once the content area is at least 180×30,
172
+ `stack` below that. It is a resolver rather than a tenth geometry —
173
+ `LayoutManager.resolve` maps it to a real layout and `compute` resolves
174
+ before dispatching, so nothing downstream sees `:auto`. No new size
175
+ plumbing: SIGWINCH already reaches the server as a `RESIZE` frame, so
176
+ detaching onto a laptop or plugging in a monitor re-resolves on the next
177
+ frame. The status bar names the verdict, e.g. `layout:auto:stack`.
178
+
179
+ - The alternate screen buffer (DECSET 47/1047/1049, and 1048 for its cursor
180
+ slot). A full-screen program — a pager, an editor, `fzf` — now draws on a
181
+ grid of its own, and the screen it covered is set aside untouched until it
182
+ exits. Nothing drawn there reaches scrollback and the view offset is pinned
183
+ to the live screen, so there is nothing to page into; scrollback declines to
184
+ open on such a pane, and a pane that enters an alternate screen under a
185
+ reader drops them out of the mode. `Terminal#dump_ansi` leads with a repaint
186
+ of the covered screen, so a mirrored or moved pane carries both grids and
187
+ quitting the pager on the far side uncovers the same shell it uncovers at
188
+ home.
189
+ - `MUXR_SCROLLBACK` sets the scrollback depth at server start, clamped to a
190
+ sane range.
191
+
192
+ ### Changed
193
+ - `muxr-mcp` no longer exits when it can't find a muxr session. Registering
194
+ it at user scope loads it into every claude on the machine, most of which
195
+ are nowhere near a muxr, and exiting surfaced a failed MCP server in all of
196
+ them. It now completes the handshake, advertises no tools when there is no
197
+ session to talk to, and says so if called anyway. The socket is opened on
198
+ first use rather than at startup and reopened if it drops, so a claude
199
+ running in a pane survives a restart of the session around it.
200
+ - `auto` is the default layout for new windows (was `spiral`). Saved
201
+ sessions are unaffected.
202
+ - Scrollback holds 50,000 rows per pane by default, up from 5,000. Rows are
203
+ now stored packed — one string of characters plus run-length attributes,
204
+ trimmed to their content and materialized into cells only when read — which
205
+ cut a filled ring from ~330 MB of resident memory per pane to ~12 MB at
206
+ 20,000 rows and made the deeper default affordable. Search and the transfer
207
+ serializer read the packed bytes directly, so `/` over a full 50,000-row
208
+ ring completes in tens of milliseconds.
209
+
210
+ ### Fixed
211
+ - The help overlay claimed `C-a t w g m` set layouts and `muxr --help`
212
+ claimed `C-a k` closed a pane. Neither binding exists: layout keys are
213
+ normal-mode only, and close is `C-a x`.
214
+ - `--list` and `:sessions` no longer report a phantom `<name>.ctrl`
215
+ session. Both enumerated `~/.muxr/sockets/*.sock`, which also matches
216
+ the sibling control socket `<name>.ctrl.sock`.
217
+ - A pane whose grid is larger than the box drawn for it is now clipped to
218
+ the box instead of painting over its own border. Only reachable with a
219
+ borrowed pane, whose geometry belongs to another session.
220
+ - Paging through a full-screen program no longer destroys scrollback. Without
221
+ an alternate screen buffer, a pager drew straight onto the primary grid and
222
+ every page-down pushed a screenful of its own frames into history, evicting
223
+ real output at the ring's cap — twenty page-downs of `less` cost 232 rows —
224
+ and quitting left the program's last frame on screen instead of uncovering
225
+ the shell.
226
+ - APC (`ESC _ … ST`) and DCS/SOS/PM (`ESC P`/`ESC X`/`ESC ^`) string
227
+ sequences are now consumed by the parser. Previously neither had a
228
+ parser state: the introducer was swallowed and the entire body printed
229
+ into the grid as text, so any program emitting kitty graphics or sixel
230
+ sprayed kilobytes of base64 across the pane.
231
+
9
232
  ## [0.1.11] - 2026-06-11
10
233
 
11
234
  ### Added
@@ -332,7 +555,8 @@ Initial release.
332
555
  boundaries.
333
556
  - Renderer that composes one frame and diff-emits ANSI to STDOUT.
334
557
 
335
- [Unreleased]: https://github.com/roelbondoc/muxr/compare/v0.1.11...HEAD
558
+ [Unreleased]: https://github.com/roelbondoc/muxr/compare/v0.2.0...HEAD
559
+ [0.2.0]: https://github.com/roelbondoc/muxr/releases/tag/v0.2.0
336
560
  [0.1.11]: https://github.com/roelbondoc/muxr/releases/tag/v0.1.11
337
561
  [0.1.10]: https://github.com/roelbondoc/muxr/releases/tag/v0.1.10
338
562
  [0.1.9]: https://github.com/roelbondoc/muxr/releases/tag/v0.1.9