Add zmx multiplexer support - #326
Conversation
|
Two issues to flag before merging.
Thanks for pulling this together. |
|
Sure! Just added fixes for those two issues. Additional fix for tab expose coming next. |
|
Just tried this out and the session discovery is working. tab exposé only works if you attached via session discovery. When I manually attach from macOS local shell it doesn't work ie running |
|
@dave-atx Checking back in here. I can take a look so we can finish this up. Just didn't want to cross streams if you were actively also working on this still. |
|
@kitknox Apologies for taking so long! Give me till end of day -- I'm pretty close to having something useful for you. |
9ddbb6f to
312c9eb
Compare
Discover, configure, and attach zmx sessions alongside the existing multiplexers. Add Tab Exposé previews and switching for local and SSH sessions while preserving zmx's transparent terminal lifecycle.
Expose visible tab previews as ordered button elements with descriptive labels and a current-tab value. Hide decorative and inactive companion views, mark scoped headers, and support the standard accessibility activation action.
312c9eb to
12ea102
Compare
|
@kitknox Thanks for your patience! This should be in a good place for you to review now. zmx tab expose now works both locally and over ssh, in both full screen TUI apps and "normal" apps. I added a second commit to improve the accessibility of tab expose panes, which isn't really zmx related. I was using that for some UI automation testing I was doing on my fork, but figured it's a good thing in general so it would make sense upstream too. |
Closes #324.
Adds zmx alongside tmux, zellij and herdr: session discovery with live previews, attach on tap, per-profile and global auto-start, settings, guide and full localization. Five commits, one per phase.
zmx is attach/detach and nothing more — one session is exactly one PTY, no windows, splits or control protocol — so it rides the existing discovery and raw-attach path and touches none of the tmux
-CCprojection engine.Three choices that look inconsistent on purpose
No
bindRawMultiplexerfor zmx, though all three neighbours have one. That binding is a suppression switch — it disables project identity, theworking|blocked → idlecompletion edge, and the alt-screen presence grant — and none of it applies to a transparent passthrough. Worse, it releases only after alt-screen ownership is observed and lost, which for zmx never happens, so agent detection would stay dead for the life of the surface. Not binding is what gives agents inside zmx a project, via OSC 7. Hence also noAgentProjectIdentity.Source.zmxand norawMultiplexerType(launching:)entry.zmxAutoEnablesyncs throughHistoryExtensionPayload, not its ownCKRecordfield likeherdrAutoEnableright above it — a new top-level field needs a production CloudKit schema deploy. Envelope absence is ambiguous ("cleared" vs "old writer"), so the merge is version-gated:currentVersiongoes to 3 with azmxAutoEnableVersionconstant. Without the gate, one reconnect from an older device mid-rollout would erase the setting.One un-timed
zmx list, against the defensive idiom used elsewhere. Measured with 4 daemons underSIGSTOP: plain returns all 25 rows in 4.0 s;timeout 2returns zero rows in 2.0 s, because zmx probes sockets serially and emits nothing until the last finishes — same wall clock, no data, indistinguishable from zmx not being installed. A 5 s backstop still catches a real hang; per-historytimeouts stay, since truncation there costs one preview. The capture loop reuses that listing instead of a second--shortrun.Parsing is positional for a related reason: labels are user-defined and print after the built-ins, so key lookup would misreport client counts and an
err=-substring rule would silently hide healthy sessions. A real error row iserrin field two with nopid.cwd=(HEAD) is percent-decoded;start_dir=(v0.7.0) must not be —zmx versionsays0.7.0for both, so the field name is the only discriminator.Drive-by:
MultiplexerTypegains aniconName, replacing two hardcoded icon ladders that disagreed (settings and the picker drew tmux and zellij swapped). Guide page retitled, orphaned"tmux Tips"key removed. TheLocalizable.xcstringsdiff is large but mechanical: 14 keys added, 1 removed, 0 modified, translated into every language, no reordering.Testing
No unit-test target for app code, so in-repo coverage is a
#Previewrunning both wire formats through the real parser into the realSessionPickerOverlay. A podman harness behind the branch (zmx HEAD, v0.7.0, no-zmx, plus tmux and zellij) adds a 34-assertion parser test that compilesZmxDiscoveryParser.swiftwithswiftc; kept out to keep the diff product-only, happy to send separately.Verified in the app on iPhone and iPad simulators against those hosts: 25+ mixed sessions listed with live previews; attach/detach/reattach replay scrollback correctly, including a path with a space; a
SIGSTOPed daemon is dropped without taking tmux and zellij down with it; the 256 KiB overflow ladder fires once per attempt and still returns everything;⌘Dover a zmx tab gives a native split (isTmuxPane == false); an agent inside zmx gets a card with a project; a finishedzmx runreplays with no spurious card or "Done"; auto-start lands inzmx attach mainwithout popping the picker; with discovery off the emitted command containszmxzero times.Zero warnings in touched files on the iOS Simulator and Mac Catalyst (
rootshell-StandalonedefinesSTANDALONE, so those paths type-check too). Not covered: CloudKit sync across two devices, and a launched Catalyst build.