Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
272 commits
Select commit Hold shift + click to select a range
82cb131
docs: record ios dogfood execution status
lawrencecchen Mar 17, 2026
56f8037
test: lock cache-first mobile workspace behavior
lawrencecchen Mar 18, 2026
4d78a01
fix: keep workspace inbox cache stable before live sync
lawrencecchen Mar 18, 2026
c052f67
fix: route mobile push and read state through HTTP
lawrencecchen Mar 18, 2026
7b5f2b7
ios: track mobile workspace analytics
lawrencecchen Mar 18, 2026
0cc1cbf
docs: record convex mobile cache-first architecture
lawrencecchen Mar 18, 2026
7280c82
ios: copy local config into dogfood builds
lawrencecchen Mar 18, 2026
3997738
Fix iOS terminal discovery auth state
lawrencecchen Mar 21, 2026
033252b
test: add cmuxd binary stdio compatibility harness
lawrencecchen Mar 21, 2026
9debe35
test: freeze cmuxd direct tls compatibility
lawrencecchen Mar 21, 2026
b6e5dbc
feat: scaffold zig cmuxd-remote binary
lawrencecchen Mar 21, 2026
a246726
feat: add zig terminal session core with ghostty-vt
lawrencecchen Mar 21, 2026
5f5cdd7
feat: add zig stdio terminal session handlers
lawrencecchen Mar 21, 2026
cd848c0
feat: port zig proxy streams and cli relay
lawrencecchen Mar 21, 2026
6afe13b
feat: port zig direct tls daemon auth
lawrencecchen Mar 21, 2026
4a4eefb
build: switch dev remote daemon fallback to zig
lawrencecchen Mar 21, 2026
70656f7
build: switch remote daemon asset pipeline to zig
lawrencecchen Mar 21, 2026
4e7b732
build: isolate zig remote daemon asset builds
lawrencecchen Mar 21, 2026
6b905db
docs: document zig remote daemon rewrite
lawrencecchen Mar 21, 2026
06f8d81
test: add bonsplit compatibility regressions
lawrencecchen Mar 21, 2026
e1d5e70
fix: restore bonsplit compatibility shims
lawrencecchen Mar 21, 2026
ca1252c
build: sync xcode project metadata
lawrencecchen Mar 21, 2026
0428c93
build: update bonsplit submodule
lawrencecchen Mar 21, 2026
d35e9cd
build: pin ghosttykit checksum for current ghostty
lawrencecchen Mar 21, 2026
53c2e9f
fix: restore go cli relay oracle behavior
lawrencecchen Mar 21, 2026
a70a36d
fix: rebuild ghostty helper for current upstream
lawrencecchen Mar 21, 2026
4541919
fix: correct ubuntu zig archive path
lawrencecchen Mar 21, 2026
1332e3c
refactor: extract zig session service core
lawrencecchen Mar 23, 2026
0a62d6f
feat: add local unix socket mode for cmuxd
lawrencecchen Mar 23, 2026
59703af
feat: add cmuxd session cli commands
lawrencecchen Mar 23, 2026
e1830f6
test: add session attach compat coverage
lawrencecchen Mar 23, 2026
93f29a3
feat: add cmuxd session attach cli
lawrencecchen Mar 23, 2026
f8f6bbb
chore: add session cli smoke script
lawrencecchen Mar 23, 2026
d0d3bdd
Merge remote-tracking branch 'origin/main' into task-move-ios-app-int…
lawrencecchen Mar 25, 2026
520b4ed
feat: back desktop terminals with local cmuxd sessions
lawrencecchen Mar 25, 2026
953557b
test: add cmuxd remote transport regressions
lawrencecchen Mar 25, 2026
aeb40a3
fix: drain pty output during daemon writes
lawrencecchen Mar 25, 2026
b908e5f
fix: drop duplicate bonsplit tab drop shim
lawrencecchen Mar 25, 2026
66324d5
build: use clt sdk for local zig daemon builds
lawrencecchen Mar 25, 2026
d0afa9a
feat: add WebSocket transport for iOS-to-Mac terminal connections
lawrencecchen Mar 30, 2026
496fbe2
fix: build errors for macOS and iOS
lawrencecchen Mar 30, 2026
5d07362
fix: iOS WS debug config, auto-login persistence, toolbar layout
lawrencecchen Mar 30, 2026
f2b055b
fix: resolve build errors and RPC decode failure
lawrencecchen Mar 30, 2026
797da21
fix: terminal.open command, processOutput rendering, PTY exhaustion
lawrencecchen Mar 30, 2026
4217092
fix: rendering and WebSocket connection stability
lawrencecchen Mar 30, 2026
0e38b3a
fix: inline Monokai Classic colors, add processOutput logging
lawrencecchen Mar 30, 2026
9688324
investigate: GhosttyKit iOS doesn't load config files
lawrencecchen Mar 31, 2026
5695aa9
feat: propagate machineStatus through iOS data model
lawrencecchen Mar 31, 2026
b7497e8
fix: invalidate URLSession on WebSocket transport disconnect
lawrencecchen Mar 31, 2026
150fa3d
fix: protect TerminalSSHTransport mutable state with NSLock
lawrencecchen Mar 31, 2026
7c6a4b0
Harden iOS RPC client with ID validation, timeout, and better errors
lawrencecchen Mar 31, 2026
0948181
fix: GhosttyKit config loading now works with CI-built xcframework
lawrencecchen Mar 31, 2026
00d2d5a
fix: GhosttySurfaceView lifecycle + GhosttyRuntime robustness
lawrencecchen Mar 31, 2026
7b2c504
Merge pull request #2380 from manaflow-ai/worktree-agent-ae07067c
lawrencecchen Mar 31, 2026
ad3c334
Merge pull request #2381 from manaflow-ai/fix-websocket-session-leak
lawrencecchen Mar 31, 2026
8284691
Merge pull request #2382 from manaflow-ai/fix-ios-ssh-transport-threa…
lawrencecchen Mar 31, 2026
8e3afd5
Merge pull request #2384 from manaflow-ai/fix-ios-rpc-client-hardening
lawrencecchen Mar 31, 2026
cadf01c
feat: offline badge, stale workspace filtering, connection-priority s…
lawrencecchen Mar 31, 2026
cbaa5e1
Update ghostty submodule to include iOS build improvements
lawrencecchen Mar 31, 2026
06e3ed6
trigger CI: rebuild GhosttyKit with iOS improvements
lawrencecchen Mar 31, 2026
71d876d
chore: trigger CI
lawrencecchen Mar 31, 2026
d1121e1
ci: add workflow_dispatch trigger to build-ghosttykit
lawrencecchen Mar 31, 2026
6ce2c7d
fix: update ghostty submodule with missing text.zig
lawrencecchen Mar 31, 2026
43d5830
build: update GhosttyKit to ff171a15 with iOS improvements
lawrencecchen Mar 31, 2026
b2cacff
fix: apply config to surface, set UIView bg from config
lawrencecchen Mar 31, 2026
274af05
fix: update ghostty to drain renderer mailbox in embedded draw
lawrencecchen Mar 31, 2026
5826954
fix: ghostty embedded renderer applies config directly
lawrencecchen Mar 31, 2026
8ebe743
revert: remove ghostty_surface_update_config, rely on initial config
lawrencecchen Mar 31, 2026
22e1d61
debug: ghostty bg_color logging
lawrencecchen Mar 31, 2026
f848c99
debug: unconditional bg_color log
lawrencecchen Mar 31, 2026
aa0e98b
fix: ghostty renderer falls back to config bg, revert transparent layer
lawrencecchen Mar 31, 2026
71727ed
experiment: red bg test
lawrencecchen Mar 31, 2026
8e25526
fix: ghostty iOS drawFrame skip early returns for bg_color
lawrencecchen Mar 31, 2026
7a20839
fix: Monokai colors for snapshot fallback, restore config loading
lawrencecchen Mar 31, 2026
17b1333
fix: update ghostty with iOS Metal rendering fixes (IOSurface, layer,…
lawrencecchen Mar 31, 2026
3b9b766
milestone: iOS terminal renders text + Monokai background via Metal
lawrencecchen Mar 31, 2026
a16709e
fix: ANSI colors, keyboard return key, CADisplayLink vsync
lawrencecchen Apr 1, 2026
2e5da2f
Clean up iOS terminal sidebar labels
lawrencecchen Apr 2, 2026
2557476
fix(ios): auto-detect Mac IP for physical device builds
lawrencecchen Apr 2, 2026
e1de296
fix(ios): add missing -c flag to codex button command
lawrencecchen Apr 3, 2026
164f281
feat: port MobilePresence publisher for desktop-to-mobile sync
lawrencecchen Apr 3, 2026
dc7f441
fix(ios): dynamic terminal bg color + COLORTERM=truecolor for SSH ses…
lawrencecchen Apr 3, 2026
08b6e39
feat: wire desktop auth + MobilePresence heartbeat publisher
lawrencecchen Apr 4, 2026
5973bbe
Merge origin/main into task-move-ios-app-into-cmux-repo
lawrencecchen Apr 7, 2026
b777bc9
Merge origin/main + Ghostty manual IO + iOS fixes
lawrencecchen Apr 7, 2026
63b9200
iOS: enable Metal renderer colors via synchronous rendering
lawrencecchen Apr 8, 2026
fd23931
iOS: remote daemon client, server discovery, and sidebar improvements
lawrencecchen Apr 8, 2026
60670b1
Add terminal rendering test script for iOS
lawrencecchen Apr 8, 2026
1e93e44
iOS: default to Menlo font for terminal rendering
lawrencecchen Apr 8, 2026
f6a1d61
iOS: use small initial frame to avoid surface size mismatch
lawrencecchen Apr 8, 2026
7c9f86f
iOS: widen input proxy frame to full view width
lawrencecchen Apr 8, 2026
d983b57
iOS: fetch scrollback on connect to populate terminal history
lawrencecchen Apr 8, 2026
47dd639
Merge remote-tracking branch 'origin/main' into task-move-ios-app-int…
lawrencecchen Apr 8, 2026
a524077
iOS: fix terminal resize and rendering after initial frame
lawrencecchen Apr 8, 2026
10fed42
iOS: fix staircase rendering and remove plain-text scrollback
lawrencecchen Apr 8, 2026
801f47c
iOS: add terminal scrolling via pan gesture
lawrencecchen Apr 8, 2026
fb428f6
iOS: fix scroll direction and last line clipped by accessory bar
lawrencecchen Apr 8, 2026
110d3c6
iOS: fix type-check error in TerminalSidebarRootView
lawrencecchen Apr 8, 2026
36e1cff
iOS: move arrow nub into accessory toolbar
lawrencecchen Apr 8, 2026
057f5bb
iOS: smaller nub circle without extra background/padding
lawrencecchen Apr 8, 2026
2f0a112
Remove outer border from arrow nub
lawrencecchen Apr 8, 2026
598a41c
iOS: fix surface dispose deadlock, workspace rename sync, sidebar cle…
lawrencecchen Apr 8, 2026
42a3e00
Add MobileDaemonBridge and tagged iOS builds
lawrencecchen Apr 8, 2026
46fb971
iOS: retain bridge in surface leak workaround to prevent dangling poi…
lawrencecchen Apr 8, 2026
aed0e7b
Resolve cmuxApp.swift merge conflicts, add project files
lawrencecchen Apr 8, 2026
0f6b2a7
iOS: fix surface teardown deadlock (libxev EVFILT_MACHPORT bug)
lawrencecchen Apr 8, 2026
d22c813
iOS: fix double-subtracted accessory bar height when keyboard visible
lawrencecchen Apr 8, 2026
db3db01
iOS: rename Beta→Nightly, align bundle IDs with macOS, fix testflight…
lawrencecchen Apr 8, 2026
9a4fc2f
iOS: reduce spacing between navigation title and servers section
lawrencecchen Apr 8, 2026
9fbf6c3
iOS: hide implementation details from auth error messages
lawrencecchen Apr 8, 2026
047f74b
iOS: account for safe area bottom inset in terminal height
lawrencecchen Apr 8, 2026
f5d1bfc
iOS: handle all auth error types with user-friendly messages
lawrencecchen Apr 8, 2026
6ad55e0
iOS: disable window padding to eliminate gap above accessory bar
lawrencecchen Apr 8, 2026
dd0bcd2
iOS: fix crash on Apple Sign In with invalid credentials
lawrencecchen Apr 8, 2026
d3d99f9
iOS: use main bundle ID (com.cmuxterm.app) for TestFlight/Nightly
lawrencecchen Apr 8, 2026
64ab3a9
Update Stack Auth credentials to new production/dev projects
lawrencecchen Apr 8, 2026
213964d
iOS: add INVALID_APPLE_CREDENTIALS to user-facing error messages, imp…
lawrencecchen Apr 9, 2026
ad30e50
iOS: restore nightly bundle ID, testflight script uploads to both apps
lawrencecchen Apr 9, 2026
18fe4fa
iOS: parallel TestFlight uploads for Release and Nightly
lawrencecchen Apr 9, 2026
1ac3801
iOS: remove Convex, unify WebSocket sync, add server scanner
lawrencecchen Apr 9, 2026
d20fe1b
TailscaleKit discovery + port migration to 52100
lawrencecchen Apr 9, 2026
5464a5f
iOS: shared terminal sessions, manual server entry, pin sorting
lawrencecchen Apr 9, 2026
533ae1e
Daemon-owned PTY: Manual I/O mode + session sync
lawrencecchen Apr 10, 2026
32b9a25
reload.sh: build cmuxd-remote with ReleaseFast, parallelize zig builds
lawrencecchen Apr 10, 2026
efcba9d
Fix terminal latency and attachment leak in daemon bridge
lawrencecchen Apr 10, 2026
e4607bc
Pass per-surface env vars to daemon shell via env command
lawrencecchen Apr 10, 2026
b0084e1
iOS: stable attachment ID to prevent daemon attachment leaks
lawrencecchen Apr 10, 2026
6ea8932
Pre-create daemon sessions on workspace restore
lawrencecchen Apr 10, 2026
56327c0
Pass shell integration env vars to pre-created daemon sessions
lawrencecchen Apr 10, 2026
0190346
Simplify daemon session ID to use only surface ID
lawrencecchen Apr 10, 2026
bfbd93d
iOS: add ⌘ modifier button to terminal keyboard toolbar (Mac only)
lawrencecchen Apr 10, 2026
bd52403
iOS: resync surface geometry after pinch-zoom font change
lawrencecchen Apr 10, 2026
86a66ca
Fix iOS app freeze, daemon drift, and discovery split-brain
lawrencecchen Apr 12, 2026
0de127e
Fix prompt re-rendering and remove unnecessary discovery polling
lawrencecchen Apr 12, 2026
749c7b8
Sync pinned workspace state and add pin swipe action on iOS
lawrencecchen Apr 12, 2026
ca28f15
Add multi-pane sync and pane switcher dropdown on iOS
lawrencecchen Apr 12, 2026
d138a63
Fix Ctrl+D pane close, live pane picker, daemon offline cleanup
lawrencecchen Apr 12, 2026
8e73df0
Tap-title pane dropdown and multi-pane session_ids in daemon
lawrencecchen Apr 12, 2026
646de9b
Sync per-pane titles and directories to iOS
lawrencecchen Apr 12, 2026
2e3cc4b
Fix tab rename sync and surface lifecycle on pane switch
lawrencecchen Apr 12, 2026
a4dc14a
Auto-sync panel title/directory changes to iOS
lawrencecchen Apr 12, 2026
9bf0466
Extract OSC title/directory from PTY output in daemon
lawrencecchen Apr 12, 2026
d918362
Graceful offline handling: keep workspaces, backoff, auto-recover
lawrencecchen Apr 12, 2026
8f30127
Reuse existing daemon on macOS app restart
lawrencecchen Apr 12, 2026
fb39b20
Fix quit dialog not showing when daemon was reused
lawrencecchen Apr 12, 2026
7dd37e0
Fix line wrapping by deferring resize after surface layout
lawrencecchen Apr 12, 2026
c8790b3
Persist daemon session IDs across quit+reopen
lawrencecchen Apr 12, 2026
1451fd3
Fix resize on launch: queue pending resize until bridge connects
lawrencecchen Apr 12, 2026
490ec92
Fix session restore: use saved IDs throughout the restore flow
lawrencecchen Apr 12, 2026
4c12077
Fix snapshot: save actual daemon session ID, not recomputed one
lawrencecchen Apr 12, 2026
4d57a27
Fix pre-create to use saved session IDs
lawrencecchen Apr 12, 2026
aee65d4
Fix blank pane: isRunning now does live socket check for reused daemons
lawrencecchen Apr 12, 2026
3f71afc
Kill child shells on daemon exit to prevent PTY leaks
lawrencecchen Apr 12, 2026
4e8ed2f
Add workspace.ensure_surfaces socket command for headless testing
lawrencecchen Apr 13, 2026
39774a6
docs: Phase 0 push protocol spec for daemon refactor
lawrencecchen Apr 13, 2026
9444728
Phase 4.1: Incremental OSC state machine in TerminalSession
lawrencecchen Apr 13, 2026
3a8b267
daemon/remote: kqueue-based PTY pump thread
lawrencecchen Apr 13, 2026
47a8235
daemon/remote: terminal.subscribe + terminal.output push events
lawrencecchen Apr 13, 2026
c383797
Phase 4.2 Zig: include notifications in terminal.output frames
lawrencecchen Apr 13, 2026
295b4b2
daemon/remote: CLI attach switches from terminal.read polling to subs…
lawrencecchen Apr 13, 2026
94d5456
Phase 1c: per-connection outbound queue for Unix socket
lawrencecchen Apr 13, 2026
ef80b04
ios: terminal transport switches to terminal.subscribe push
lawrencecchen Apr 13, 2026
a7697ca
hello: advertise terminal.subscribe/workspace.subscribe/notifications…
lawrencecchen Apr 13, 2026
f430286
Phase 2.3: per-session has_unread_output flag
lawrencecchen Apr 13, 2026
18a70b4
Add DaemonConnection: single persistent socket for macOS
lawrencecchen Apr 13, 2026
373e253
iOS Phase 3.1: TerminalDaemonConnection + workspace event handler
lawrencecchen Apr 13, 2026
0b7bb52
iOS Phase 3.2: TerminalSidebarStore migrates to TerminalDaemonConnection
lawrencecchen Apr 13, 2026
9619698
iOS Phase 3.3: pool terminal sessions through TerminalDaemonConnection
lawrencecchen Apr 13, 2026
87d5a10
iOS Phase 4.2: NotificationManager schedules local push from terminal…
lawrencecchen Apr 13, 2026
91f335d
Collapse bridges onto single DaemonConnection socket
lawrencecchen Apr 13, 2026
597c7f3
Phase 2.1: macOS subscribes to workspace.changed
lawrencecchen Apr 13, 2026
a1a63f4
Phase 2.2: feature-flagged migration of pinned/customTitle/customColor
lawrencecchen Apr 13, 2026
f10ccf3
Phase 2.1-extended: flag-gated daemon-authority for workspace existence
lawrencecchen Apr 13, 2026
7682476
Phase 2.1-ext caveat: skip workspace.sync when daemon owns existence
lawrencecchen Apr 13, 2026
0b87a3f
Phase 4.3 web: APNs bridge at /api/notifications/push
lawrencecchen Apr 13, 2026
199426c
iOS Phase 4.3: APNs remote push provisioning
lawrencecchen Apr 13, 2026
e8c7313
Phase 4.3 Zig: daemon.configure_notifications + remote APNs push
lawrencecchen Apr 13, 2026
400236a
iOS tests: add sessionID to TerminalRemoteDaemonSessionClient mocks
lawrencecchen Apr 13, 2026
fc89fc1
iOS tests: match 4-arg session transport factory + sharedSessionID re…
lawrencecchen Apr 13, 2026
f7d37bc
Phase 1 integration tests: test harness + first test
lawrencecchen Apr 13, 2026
23ff44a
Phase 1 integration tests: backpressure, EOF, reconnect, stress, leaks
lawrencecchen Apr 14, 2026
82c94d0
Fix unresolved merge conflict in GhosttyConfigTests.swift
lawrencecchen Apr 14, 2026
e47031c
Tests: non-blocking fd for integration test client
lawrencecchen Apr 14, 2026
c8b634d
Tests: harden timing-sensitive integration tests
lawrencecchen Apr 14, 2026
a5d425a
daemon: deliver eof-only push event when PTY closes after last byte
lawrencecchen Apr 14, 2026
0950c44
daemon: exit pump loop on kevent error instead of blind-sleep retry
lawrencecchen Apr 14, 2026
81b7e55
Tests: replace subscribe-race pre-sleep with terminal.read RPC barrier
lawrencecchen Apr 14, 2026
df537d2
Tests: relax backpressure test to avoid kernel-buffer dependence
lawrencecchen Apr 14, 2026
f4c610a
daemon: close two UAF races under disconnect-during-flood
lawrencecchen Apr 14, 2026
396ebcd
Tests: clarify test 8 post-disconnect sleep comment
lawrencecchen Apr 14, 2026
8f089f4
daemon: replace pump processing_mutex with per-Entry in_flight counter
lawrencecchen Apr 14, 2026
71c9743
Tests: preserve non-matching frames across awaitResponse
lawrencecchen Apr 14, 2026
c503f81
Fix blank terminal: read terminal.output fields from top of frame
lawrencecchen Apr 14, 2026
e6c7798
Tests: replace disconnect-settle sleep with subscriberCount poll
lawrencecchen Apr 14, 2026
97ad01a
Tests: make integration deadlines scalable for loaded hosts
lawrencecchen Apr 14, 2026
94134ee
daemon: advertise workspace.sync capability
lawrencecchen Apr 14, 2026
4017899
Fix iOS discovery: ws-port must fall inside 52100-52199
lawrencecchen Apr 14, 2026
4a4ca73
daemon: emit session.size_changed push on effective-size change
lawrencecchen Apr 14, 2026
9202fe5
iOS+macOS: pin Ghostty surface to daemon's effective grid + letterbox…
lawrencecchen Apr 14, 2026
45b8b34
Instrument daemon effective-size path with NSLog / dlog trace
lawrencecchen Apr 14, 2026
e81e53b
macOS: draw subtle border around pinned terminal area when letterboxed
lawrencecchen Apr 15, 2026
77ff67d
iOS empty-state: surface a 'Start on <server>' button when servers ex…
lawrencecchen Apr 15, 2026
d6949c7
macOS letterbox: left-align + shrink surfaceView frame to fix ghosting
lawrencecchen Apr 15, 2026
ee0e39a
macOS letterbox: add debug logs + fix pin feedback loop on natural-si…
lawrencecchen Apr 15, 2026
0bb48e1
macOS letterbox: don't pin on daemon-echo; only when effective < natural
lawrencecchen Apr 15, 2026
8ff6260
macOS letterbox: use lastReportedToDaemon to detect true sibling-shrink
lawrencecchen Apr 15, 2026
0dc7d56
iOS: invalidate pool entries when daemon endpoint changes
lawrencecchen Apr 15, 2026
dfc88dd
Single-source-of-truth view sizing across daemon + iOS + macOS
lawrencecchen Apr 15, 2026
51fb525
daemon: add workspace.open_pane RPC
lawrencecchen Apr 15, 2026
4a21acb
Steps 2/5: wire clients to daemon-minted session_ids
lawrencecchen Apr 15, 2026
0189bb5
Step 7: mark computeSessionID as legacy; point new call sites at open…
lawrencecchen Apr 15, 2026
a160c5b
DaemonConnection.openPane: retry not_found with backoff
lawrencecchen Apr 15, 2026
804510c
Mark computeSessionID/preCreateSession as @deprecated
lawrencecchen Apr 15, 2026
54ecf65
Remove computeSessionID: daemon is the sole session id minter
lawrencecchen Apr 15, 2026
b39a8c0
Adopt daemon pane session id for daemon-materialized workspaces
lawrencecchen Apr 16, 2026
1094102
Invalidate cached cell metrics on font change + config reload
lawrencecchen Apr 16, 2026
c47591f
Defer workspace.sync while openPane is in flight
lawrencecchen Apr 16, 2026
54007d7
Unblock workspace.sync when openPane exhausts retries
lawrencecchen Apr 16, 2026
98765d3
Drop vestigial socketPath parameter from DaemonTerminalBridge
lawrencecchen Apr 16, 2026
89c4c87
Defer workspace.sync when a panel has no bridge yet; log sync path
lawrencecchen Apr 16, 2026
ba459db
Fix ghost-attachment leak and centralize daemon-size reporting
lawrencecchen Apr 16, 2026
1bcafeb
Stop letterbox pin from monotonically shrinking
lawrencecchen Apr 16, 2026
eea2fcf
Create workspace on daemon before surface fires openPane
lawrencecchen Apr 16, 2026
e4c81be
Detach workspace.open_pane's bootstrap attachment
lawrencecchen Apr 16, 2026
77c0246
Revert "Detach workspace.open_pane's bootstrap attachment"
lawrencecchen Apr 16, 2026
0ad537f
Spawn cmuxd-remote via nohup so it survives mac app quit
lawrencecchen Apr 16, 2026
d0d05a7
Detach cmuxd-remote via sh '& disown' so it survives mac app quit
lawrencecchen Apr 16, 2026
ef85bc5
Revert "Detach cmuxd-remote via sh '& disown' so it survives mac app …
lawrencecchen Apr 16, 2026
8a6f9f4
Revert "Spawn cmuxd-remote via nohup so it survives mac app quit"
lawrencecchen Apr 16, 2026
30c2c87
Add debug.app.quit_keep_daemon socket action
lawrencecchen Apr 16, 2026
e525cfd
Spawn daemon with perl POSIX::setsid + /tmp stop tracing
lawrencecchen Apr 16, 2026
fa33d48
Drop "Keep Daemon" feature; daemon lifecycle is tied to mac app
lawrencecchen Apr 16, 2026
58d5da5
Respect "Warn Before Quit" for tagged DEV builds too
lawrencecchen Apr 16, 2026
5635f4d
Pull-to-refresh on iOS workspace list + daemon push on connect
lawrencecchen Apr 16, 2026
6f6f65f
Document cross-stack reload checklist (mac + iOS + daemon)
lawrencecchen Apr 16, 2026
19f49b0
Remove TailscaleKit entirely
lawrencecchen Apr 16, 2026
36f5003
ios: swap print() for os.Logger and migrate AuthManager to @Observable
lawrencecchen Apr 16, 2026
9b58e3e
ios: migrate NotificationRouteStore, ServerScanner, NotificationManag…
lawrencecchen Apr 16, 2026
8582615
ios: migrate LiveAnchormuxConfigStore to @Observable, drop stale Tail…
lawrencecchen Apr 16, 2026
41af789
ios: migrate TerminalSessionController to @Observable
lawrencecchen Apr 16, 2026
f80a062
ios: swap three NSLog sites for os.Logger
lawrencecchen Apr 16, 2026
ffc8a32
ios: migrate more NSLog sites to os.Logger
lawrencecchen Apr 16, 2026
7a1430a
ios: finish NSLog → os.Logger sweep
lawrencecchen Apr 16, 2026
0feb004
ios tests: conform Stub session clients to workspaceList()
lawrencecchen Apr 16, 2026
e370ca4
ios: make TerminalSSHTransport lock usage async-safe
lawrencecchen Apr 16, 2026
2d13c04
ios: migrate TerminalSidebarStore to @Observable
lawrencecchen Apr 16, 2026
984aae5
ios: split TerminalModels into concern-based files
lawrencecchen Apr 16, 2026
cab80d1
ios: drop three hacky sleeps for event-driven signals
lawrencecchen Apr 16, 2026
cbafa76
ios: cancel inbox-empty-snapshot fallback on team switch
lawrencecchen Apr 16, 2026
135bceb
ios: cancel stale 'copied' reset on rapid re-tap in scanner log
lawrencecchen Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .github/workflows/build-ghosttykit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- main
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ jobs:
with:
go-version-file: daemon/remote/go.mod

- name: Install zig
run: |
set -euo pipefail
ZIG_REQUIRED="0.15.2"
if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Use an exact version check for zig version; the current prefix grep can accept unintended versions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/ci.yml, line 56:

<comment>Use an exact version check for `zig version`; the current prefix grep can accept unintended versions.</comment>

<file context>
@@ -49,6 +49,22 @@ jobs:
+        run: |
+          set -euo pipefail
+          ZIG_REQUIRED="0.15.2"
+          if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then
+            echo "zig ${ZIG_REQUIRED} already installed"
+          else
</file context>
Suggested change
if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then
if command -v zig >/dev/null 2>&1 && [ "$(zig version 2>/dev/null)" = "${ZIG_REQUIRED}" ]; then
Fix with Cubic

echo "zig ${ZIG_REQUIRED} already installed"
else
echo "Installing zig ${ZIG_REQUIRED} from tarball"
curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-x86_64-linux-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Add integrity verification (checksum/signature) for the Zig tarball before extracting and installing it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/ci.yml, line 60:

<comment>Add integrity verification (checksum/signature) for the Zig tarball before extracting and installing it.</comment>

<file context>
@@ -49,6 +49,22 @@ jobs:
+            echo "zig ${ZIG_REQUIRED} already installed"
+          else
+            echo "Installing zig ${ZIG_REQUIRED} from tarball"
+            curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-x86_64-linux-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz
+            tar xf /tmp/zig.tar.xz -C /tmp
+            sudo mv /tmp/zig-x86_64-linux-${ZIG_REQUIRED}/zig /usr/local/bin/zig
</file context>
Fix with Cubic

tar xf /tmp/zig.tar.xz -C /tmp
sudo mv /tmp/zig-x86_64-linux-${ZIG_REQUIRED}/zig /usr/local/bin/zig
sudo rm -rf /usr/local/lib/zig
sudo mv /tmp/zig-x86_64-linux-${ZIG_REQUIRED}/lib /usr/local/lib/zig
zig version
fi

- name: Run remote daemon tests
working-directory: daemon/remote
run: go test ./...
Expand Down
418 changes: 95 additions & 323 deletions .github/workflows/test-e2e.yml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@
[submodule "vendor/bonsplit"]
path = vendor/bonsplit
url = https://github.com/manaflow-ai/bonsplit.git
[submodule "vendor/tls.zig"]
path = vendor/tls.zig
url = https://github.com/ianic/tls.zig
branch = zig-0.15.x
34 changes: 33 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,32 @@ After making code changes, always run the reload script with a tag to build the
./scripts/reload.sh --tag fix-zsh-autosuggestions
```

### Cross-stack reloads (mac + iOS + daemon)

iOS clients connect to mac-side `cmuxd-remote` over WebSocket. A code
change in any one of mac swift, iOS swift, or zig daemon usually needs
a reload of **every** affected surface. Defaulting to "only reload
what I edited" leaves the user with stale binaries on the other side
and produces phantom bug reports.

| Edited | Mac reload | iOS reload | Notes |
|--------|-----------|-----------|-------|
| `Sources/**` (mac swift only) | yes | no | mac UI / control socket |
| `daemon/remote/zig/**` | **yes** | **yes** | daemon respawns when mac launches; iOS connects over WS |
| `ios/Sources/**` | no | **yes** | iOS UI / transport |
| Shared RPC schema (json-rpc method names, params) | yes | yes | both sides parse the wire format |
| `MobileDaemonBridgeInline` (mac), WS port hash, secret loader | yes | yes | iOS caches endpoint; stale port → silent connect failures |

iOS reload runs from the worktree's `ios/` dir:

```bash
cd ios && ./scripts/reload.sh
```

When in doubt, reload both. End every applicable handoff stating
exactly what was reloaded: `Reloaded mac (tag: pty-shared-size) +
iOS simulator. iPhone unavailable.` Don't make the user infer it.

By default, `reload.sh` builds but does **not** launch the app. The script prints the `.app` path so the user can cmd-click to open it. Pass `--launch` to kill any existing instance and open the app automatically:

```bash
Expand Down Expand Up @@ -187,7 +213,12 @@ The app has a **Debug** menu in the macOS menu bar (only in DEBUG builds). Use i
- Only explicit focus-intent commands may mutate in-app focus/selection (`window.focus`, `workspace.select/next/previous/last`, `surface.focus`, `pane.focus/last`, browser focus commands, and v1 focus equivalents).
- All non-focus commands should preserve current user focus context while still applying data/model changes.

## Testing policy
## Timing policy

- Do not use sleep-based timing in application/runtime code when an event-, state-, or callback-driven mechanism is available.
- Deterministic sleeps are acceptable in tests when they are the smallest practical verification tool.

## E2E mac UI tests

**Never run tests locally.** All tests (E2E, UI, python socket tests) run via GitHub Actions or on the VM.

Expand Down Expand Up @@ -272,3 +303,4 @@ Notes:
- README download button points to `releases/latest/download/cmux-macos.dmg`.
- Versioning: bump the minor version for updates unless explicitly asked otherwise.
- Changelog: update `CHANGELOG.md`; docs changelog is rendered from it.

250 changes: 250 additions & 0 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,11 @@ struct CMUXCLI {
return
}

if command == "login" {
try runLogin(commandArgs: commandArgs, socketPath: resolvedSocketPath, explicitPassword: socketPasswordArg)
return
}

if command == "claude-teams" {
try runClaudeTeams(
commandArgs: commandArgs,
Expand Down Expand Up @@ -1922,6 +1927,41 @@ struct CMUXCLI {
let response = try client.sendV2(method: "system.identify", params: params)
print(jsonString(formatIDs(response, mode: idFormat)))

case "daemon-status", "daemon":
let subcommand = commandArgs.first?.lowercased() ?? "status"
if subcommand == "stop" {
let response = try client.sendV2(method: "daemon.stop")
if jsonOutput {
print(jsonString(response))
} else {
print("Daemon stopped.")
}
} else {
let response = try client.sendV2(method: "daemon.status")
if jsonOutput {
print(jsonString(response))
} else {
if let bridge = response["bridge"] as? [String: Any] {
let status = bridge["status"] as? String ?? "unknown"
let socketPath = bridge["socket_path"] as? String ?? "unknown"
let syncCount = bridge["sync_count"] as? Int ?? 0
let lastSync = bridge["last_sync"] as? String ?? "never"
print("Bridge: \(status)")
print(" Socket: \(socketPath)")
print(" Syncs: \(syncCount)")
print(" Last sync: \(lastSync)")
}
if let daemon = response["daemon"] as? [String: Any] {
let running = daemon["running"] as? Bool ?? false
let wsPort = daemon["ws_port"] as? Int
let daemonSocket = daemon["socket_path"] as? String
print("Daemon: \(running ? "running" : "stopped")")
if let wsPort { print(" WebSocket port: \(wsPort)") }
if let daemonSocket { print(" Socket: \(daemonSocket)") }
}
}
}

case "list-windows":
let response = try sendV1Command("list_windows", client: client)
if jsonOutput {
Expand Down Expand Up @@ -2949,6 +2989,189 @@ struct CMUXCLI {
try activateApp()
}

// MARK: - Login (Stack Auth CLI flow)

private func runLogin(commandArgs: [String], socketPath: String, explicitPassword: String?) throws {
let noOpen = hasFlag(commandArgs, name: "--no-open")

// Resolve Stack Auth config from environment (mirrors AuthEnvironment.swift)
let env = ProcessInfo.processInfo.environment
let stackBaseURL = env["CMUX_STACK_BASE_URL"]?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "https://api.stack-auth.com"
#if DEBUG
let devProjectID = "1467bed0-8522-45ee-a8d8-055de324118c"
let devClientKey = "pck_pt4nwry6sdskews2pxk4g2fbe861ak2zvaf3mqendspa0"
#else
let devProjectID = "8a877114-b905-47c5-8b64-3a2d90679577"
let devClientKey = "pck_pqghntgd942k1hg066m7htjakb8g4ybaj66hqj2g2frj0"
#endif
let projectID = env["CMUX_STACK_PROJECT_ID"]?.trimmingCharacters(in: .whitespacesAndNewlines) ?? devProjectID
let clientKey = env["CMUX_STACK_PUBLISHABLE_CLIENT_KEY"]?.trimmingCharacters(in: .whitespacesAndNewlines) ?? devClientKey

// Resolve the handler URL (where the browser opens).
let handlerOrigin: String
if let override = env["CMUX_AUTH_WWW_ORIGIN"]?.trimmingCharacters(in: .whitespacesAndNewlines),
!override.isEmpty {
handlerOrigin = override
} else {
#if DEBUG
let cmuxPort = env["CMUX_PORT"]?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "3000"
handlerOrigin = "http://localhost:\(cmuxPort)"
#else
handlerOrigin = "https://cmux.com"
#endif
}

// Step 1: Initiate CLI auth session
let initURL = URL(string: "\(stackBaseURL)/api/v1/auth/cli")!
var initRequest = URLRequest(url: initURL)
initRequest.httpMethod = "POST"
initRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
initRequest.setValue(projectID, forHTTPHeaderField: "x-stack-project-id")
initRequest.setValue(clientKey, forHTTPHeaderField: "x-stack-publishable-client-key")
initRequest.setValue("client", forHTTPHeaderField: "x-stack-access-type")
initRequest.httpBody = try JSONSerialization.data(withJSONObject: [
"expires_in_millis": 7_200_000, // 2 hours
])

let initResult = try syncHTTPRequest(initRequest)
guard let initJSON = initResult as? [String: Any],
let pollingCode = initJSON["polling_code"] as? String,
let loginCode = initJSON["login_code"] as? String else {
throw CLIError(message: "login: failed to initiate auth session")
}

// Step 2: Open browser (or print URL)
let confirmURL = "\(handlerOrigin)/handler/cli-auth-confirm?login_code=\(loginCode)"
if noOpen {
cliPrint("Open this URL to sign in:")
cliPrint(confirmURL)
} else {
cliPrint("Opening browser for sign-in...")
let proc = Process()
proc.executableURL = URL(fileURLWithPath: "/usr/bin/open")
// Open in Safari explicitly to avoid cmux's built-in browser intercepting
proc.arguments = ["-a", "Safari", confirmURL]
try proc.run()
proc.waitUntilExit()
}

// Step 3: Poll for completion
cliPrint("Waiting for sign-in to complete...")
let pollURL = URL(string: "\(stackBaseURL)/api/v1/auth/cli/poll")!
let pollBody = try JSONSerialization.data(withJSONObject: [
"polling_code": pollingCode,
])

let startTime = Date()
let timeout: TimeInterval = 300 // 5 minutes
while Date().timeIntervalSince(startTime) < timeout {
Thread.sleep(forTimeInterval: 2.0)

var pollRequest = URLRequest(url: pollURL)
pollRequest.httpMethod = "POST"
pollRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
pollRequest.setValue(projectID, forHTTPHeaderField: "x-stack-project-id")
pollRequest.setValue(clientKey, forHTTPHeaderField: "x-stack-publishable-client-key")
pollRequest.setValue("client", forHTTPHeaderField: "x-stack-access-type")
pollRequest.httpBody = pollBody

guard let pollJSON = try? syncHTTPRequest(pollRequest) as? [String: Any],
let status = pollJSON["status"] as? String else {
continue
}

switch status {
case "success":
guard let refreshToken = pollJSON["refresh_token"] as? String else {
throw CLIError(message: "login: got success but no refresh token")
}
// Send token to the running app via socket
try seedTokenViaSocket(refreshToken: refreshToken, socketPath: socketPath, explicitPassword: explicitPassword)
cliPrint("OK Signed in successfully.")
Comment on lines +3088 to +3090
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist login token when app socket is unavailable

After the browser flow succeeds, the CLI only forwards the refresh token to account.seed_tokens over the app socket; if the app is not running (or the socket path is stale), seedTokenViaSocket throws and the login fails even though a valid refresh token was obtained. Because CLI auth tokens are single-use, this forces users through the full browser flow again. There is no local keychain fallback in this path, so cmux login is brittle in exactly the first-time/offline-app scenario.

Useful? React with 👍 / 👎.

return
case "expired":
throw CLIError(message: "login: session expired. Run `cmux login` again.")
case "used":
throw CLIError(message: "login: session already used.")
case "waiting":
continue
default:
continue
}
}

throw CLIError(message: "login: timed out waiting for sign-in (5 minutes)")
}

private func cliPrint(_ message: String) {
fputs(message + "\n", stdout)
fflush(stdout)
}

private func syncHTTPRequest(_ request: URLRequest) throws -> Any {
let semaphore = DispatchSemaphore(value: 0)
var responseData: Data?
var responseError: Error?

let task = URLSession.shared.dataTask(with: request) { data, _, error in
responseData = data
responseError = error
semaphore.signal()
}
task.resume()
semaphore.wait()

if let error = responseError {
throw CLIError(message: "login: network error: \(error.localizedDescription)")
}
guard let data = responseData else {
throw CLIError(message: "login: empty response")
}
return try JSONSerialization.jsonObject(with: data)
}

private func seedTokenViaSocket(refreshToken: String, socketPath: String, explicitPassword: String?) throws {
let client = SocketClient(path: socketPath)
try client.connect()
defer { client.close() }
try? authenticateClientIfNeeded(client, explicitPassword: explicitPassword, socketPath: socketPath)
let result = try client.sendV2(method: "account.seed_tokens", params: [
"refresh_token": refreshToken,
])
if let error = result["error"] as? [String: Any],
let message = error["message"] as? String {
throw CLIError(message: "login: app rejected token: \(message)")
}
}

private func storeAuthToken(refreshToken: String, projectID: String) throws {
// Use the same keychain service and account names as the app's KeychainStackTokenStore
let service = "com.cmuxterm.app.auth"
let account = "cmux-auth-refresh-token"
let tokenData = refreshToken.data(using: .utf8)!

// Delete existing
let deleteQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
]
SecItemDelete(deleteQuery as CFDictionary)

// Add new
let addQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecValueData as String: tokenData,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
guard status == errSecSuccess else {
throw CLIError(message: "login: failed to store token in keychain (error \(status))")
}
}

private func runFeedback(
commandArgs: [String],
socketPath: String,
Expand Down Expand Up @@ -6696,6 +6919,20 @@ struct CMUXCLI {
/// Return the help/usage text for a subcommand, or nil if the command is unknown.
private func subcommandUsage(_ command: String) -> String? {
switch command {
case "daemon", "daemon-status":
return """
Usage: cmux daemon [status|stop] [--json]

Show the sync daemon status (bridge connection, WebSocket port, sync count).

Subcommands:
status Show daemon status (default)
stop Stop the daemon process

The daemon syncs workspace state to iOS via WebSocket. It persists
across app restarts so terminal sessions survive upgrades. Use
'cmux daemon stop' to kill it explicitly.
"""
case "ping":
return """
Usage: cmux ping
Expand All @@ -6721,6 +6958,17 @@ struct CMUXCLI {

Show top-level CLI usage and command list.
"""
case "login":
return """
Usage: cmux login [--no-open]

Sign in to your cmux account using browser-based authentication.
Opens a browser for sign-in, then polls until complete.
Tokens are stored in the system keychain.

Flags:
--no-open Print the sign-in URL instead of opening the browser.
"""
case "welcome":
return """
Usage: cmux welcome
Expand Down Expand Up @@ -14319,6 +14567,7 @@ struct CMUXCLI {
--password takes precedence, then CMUX_SOCKET_PASSWORD env var, then password saved in Settings.

Commands:
login [--no-open]
welcome
shortcuts
feedback [--email <email> --body <text> [--image <path> ...]]
Expand All @@ -14333,6 +14582,7 @@ struct CMUXCLI {
capabilities
rpc <method> [json-params]
identify [--workspace <id|ref|index>] [--surface <id|ref|index>] [--no-caller]
daemon [status|stop]
list-windows
current-window
new-window
Expand Down
Loading