Skip to content

Add zmx multiplexer support - #326

Open
dave-atx wants to merge 2 commits into
kitknox:mainfrom
dave-atx:zmx-upstream
Open

Add zmx multiplexer support#326
dave-atx wants to merge 2 commits into
kitknox:mainfrom
dave-atx:zmx-upstream

Conversation

@dave-atx

Copy link
Copy Markdown

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 -CC projection engine.

Three choices that look inconsistent on purpose

No bindRawMultiplexer for zmx, though all three neighbours have one. That binding is a suppression switch — it disables project identity, the working|blocked → idle completion 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 no AgentProjectIdentity.Source.zmx and no rawMultiplexerType(launching:) entry.

zmxAutoEnable syncs through HistoryExtensionPayload, not its own CKRecord field like herdrAutoEnable right 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: currentVersion goes to 3 with a zmxAutoEnableVersion constant. 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 under SIGSTOP: plain returns all 25 rows in 4.0 s; timeout 2 returns 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-history timeouts stay, since truncation there costs one preview. The capture loop reuses that listing instead of a second --short run.

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 is err in field two with no pid. cwd= (HEAD) is percent-decoded; start_dir= (v0.7.0) must not be — zmx version says 0.7.0 for both, so the field name is the only discriminator.

Drive-by: MultiplexerType gains an iconName, replacing two hardcoded icon ladders that disagreed (settings and the picker drew tmux and zellij swapped). Guide page retitled, orphaned "tmux Tips" key removed. The Localizable.xcstrings diff 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 #Preview running both wire formats through the real parser into the real SessionPickerOverlay. A podman harness behind the branch (zmx HEAD, v0.7.0, no-zmx, plus tmux and zellij) adds a 34-assertion parser test that compiles ZmxDiscoveryParser.swift with swiftc; 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; ⌘D over a zmx tab gives a native split (isTmuxPane == false); an agent inside zmx gets a card with a project; a finished zmx run replays with no spurious card or "Done"; auto-start lands in zmx attach main without popping the picker; with discovery off the emitted command contains zmx zero times.

Zero warnings in touched files on the iOS Simulator and Mac Catalyst (rootshell-Standalone defines STANDALONE, so those paths type-check too). Not covered: CloudKit sync across two devices, and a launched Catalyst build.

@kitknox

kitknox commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Two issues to flag before merging.

  1. Profile editor uses tmux settings for zmx in ProfileEditorSheet.swift, ProfileMultiplexerSessionEditor only distinguishes herdr from the other multiplexers, so zmx currently falls through to the tmux path. Could you add explicit zmx handling? The editor should also reject zmx session names beginning with -

  2. Discovered zmx start_dir - Would it be better to drive this from OSC 7/PWD since that replays when attaching in the latest version? Otherwise we are stuck with the startup dir only?

Thanks for pulling this together.

@dave-atx

dave-atx commented Aug 25, 2026

Copy link
Copy Markdown
Author

Sure! Just added fixes for those two issues. Additional fix for tab expose coming next.

@kitknox

kitknox commented Aug 25, 2026

Copy link
Copy Markdown
Owner

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 zmx a foo1. This is tricky to do correctly, was this missed or just more challenging to make possible with zmx architecture? The zmx tab exposé thumbnails are also not scaling correctly so I get line wrapped for example if I have as a test htop running in a session. Also please back out the .gitgnore for your private repo docs.

@kitknox

kitknox commented Aug 31, 2026

Copy link
Copy Markdown
Owner

@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.

@dave-atx

Copy link
Copy Markdown
Author

@kitknox Apologies for taking so long! Give me till end of day -- I'm pretty close to having something useful for you.

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.
@dave-atx

dave-atx commented Sep 1, 2026

Copy link
Copy Markdown
Author

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: zmx terminal multiplexer support

2 participants