Add zmx-backed workspace/session persistence
MD
15d ago+680+680+012345678901234567890123456789-50-50-01234567890123456789
Summary
This PR implements session persistence in cmux by wiring workspace restore/save to zmx-backed Ghostty surfaces.
At a high level:
- terminal processes are owned by zmx sessions (not tied to cmux process lifetime)
- cmux persists workspace layout/state metadata and zmx session mappings
- on relaunch, cmux restores layout and reconnects to existing zmx sessions when available
Why This Approach
Persistence has two independent concerns:
- Process lifetime persistence (shell/session survives app exit)
- UI/layout persistence (tabs, panes, focused pane, metadata restore)
This implementation intentionally splits them:
- zmx handles durable terminal session lifetime and reattach
- cmux handles workspace structure + per-panel metadata snapshots
That separation keeps each layer focused and avoids embedding process supervision logic into cmux UI state management.
What Changed
1) zmx-backed terminal wiring in cmux
TerminalSurfacenow passeszmx_session/zmx_createinto Ghostty surface config.TerminalPanelconstructors accept zmx session parameters.- New app settings for session persistence:
Enable zmx Session PersistenceKill Sessions on Workspace Close
2) Workspace snapshot + restore model
- Added codable snapshot model in
WorkspaceStatePersistence.swift. - Persisted data includes:
- workspace list + selected workspace
- split tree + divider positions
- focused pane index
- panel metadata (type, directory, custom title, pinned, browser URL)
- per-terminal zmx session names
- Snapshot is written to:
~/Library/Application Support/cmux/workspace-state.json
3) Deterministic zmx session naming + lifecycle
- Added stable workspace ID + deterministic naming:
cmux-<stable-id-prefix>-<panel-index>
- Session mapping is assigned across all relevant creation paths:
- initial workspace terminal
- new tab terminals
- split terminals (including auto-created split path)
- replacement terminal after closing last panel
- Rehydrates panel index counter on restore to prevent collisions.
4) Restore behavior and resilience
- Restore reconstructs split tree and panel metadata.
- For restored terminal panels:
- if session exists/alive => attach (
zmx_create = false) - if missing => recreate (
zmx_create = true)
- if session exists/alive => attach (
- If zmx is unavailable at launch:
- layout still restores
- terminals start fresh (non-zmx reattach path)
5) Mapping consistency fixes
- zmx mappings are removed on panel close / pane close.
- Detached surface transfer now carries zmx session mapping and restores it on attach.
- Restore flow force-closes preexisting tabs before reconstruction to avoid stale residual tabs/metadata.
- Panel directories are rehydrated from snapshot during restore.
6) Submodule/fork wiring
.gitmodulespoints Ghostty submodule tomdsakalu/ghosttyfork branch containing zmx backend support.ghostty.hsynced with new zmx fields used by cmux.
Tests / Validation
- Added regression tests in
cmuxTests/CmuxWebViewKeyEquivalentTests.swiftcovering:- initial/replacement panel zmx mapping lifecycle
- detach/reattach zmx mapping preservation
- restore path directory rehydration
- Local validation:
- Debug build succeeds via
xcodebuild - target test set for
WorkspaceZmxPersistenceRegressionTestspasses
- Debug build succeeds via
Dependency
- Depends on: https://github.com/manaflow-ai/ghostty/pull/2
Related Issues
Summary by CodeRabbit
-
New Features
- Terminal session persistence via zmx with deterministic per-panel names and automatic restoration
- Settings UI to enable persistence and to kill sessions on workspace close (localized strings added)
- zmx availability checks and session management (probe/kill) exposed
- New action to copy a tab/window title to the clipboard
-
Improvements
- More robust initial presentation and retry behavior for terminals
- Prevents zmx environment leakage on startup; closes/kills zmx on workspace/window close
-
Chores
- Updated submodule configuration