cmux-next iOS: per-user hub, WireGuard default + WebRTC browser video, redial, terminal r13, app fixes, production naming

Merged
AZ
17d ago
+110427-6400

Summary

One merge of today's cmux-next iOS work into feat-cmux-next. The user asked for it ("merge everything into cmux next"). It supersedes these PRs; their own bodies hold the full details:

PRBranch (head)What
#22260feat-cnx-ios-userhub (05427a94a76)cnx-ios-api: signaling hub per user, not per team (PROTOCOL 0.10.0, already live on the prod Worker); team hubs retired on first use and by a one-minute cron sweep
#22354feat-cnx-ios-fixes-r1 (e9e9bf0b232)app full-verify fixes D1–D14: Chief availability and Try Again, POST /v1/teams, workspace-folder file previews (D9), Codex default model (D4), prompt resend keeps one id, Close All, native Feed rows, copy polish
#22355feat-cnx-ios-macnext-fixes-r1 (b9a5aeeaa81)Mac side of #22354: hostd gets the app's own Chief owner (D2), hostd D9 file roots, acpmux D4. #22354 already carries the same commits as cherry-picks onto the feat-cnx-ios line, so nothing extra was applied
#22337feat-cnx-ios-hybrid-link (bb0dd17a1de)WireGuard is the default link; browser video uses a WebRTC media session beside it, with tunnel RTP as the floor
#22419feat-cnx-ios-redial-video (c1c147a9404)after a redial, the browser tab on screen keeps its video; a terminal reattach finds its terminal by id after the Mac renumbers surfaces; "type to try again"
#22574feat-cmux-next-mobile-naming (f2c0841b539)production naming: cnx-ios/ to cmux-next-ios/, CnxKit to CmuxMobileKit, cnx-hostd to cmux-mobile-host, wire names cmux-mobile.v1 / x-cmux-mobile-*, service mobile-api.cmux.dev (API 0.11.0). Merged into this branch with a merge commit
#22387feat-cnx-ios-termfix-r13 (580932e5ffc)terminal round 13: feed pacing, keep the link on a presence blip, restart a dial when the Mac's host comes back, per-Mac offline state, stale input never sent, live grid cards, AutoFill pill

CmuxTabsKit and CmuxConversation are byte-identical to origin/feat-cmux-next-tabs-kit (8ef7a76b09f1) and origin/feat-imsg-parity-gui (63199dee057b) (cnx-ios/scripts/check-tabskit-sync.sh).

Conflicts resolved

  • acpmux start model (hub/start_defaults.rs, tests, fake_agent.py). The precedence is the saved pick, then the harness's own config model (feat-cmux-next), then the catalog default only when the harness offers it (#22354 D4). Both test sets are kept.

  • IPhoneHostService.swift: #22354's ChiefPlacement and launch(), in feat-cmux-next's lint style (no = nil).

  • DaemonTerminalSession.swift (#22419 and #22387). connectionState is:

    • attached or ended: .attached;
    • otherwise, Mac offline: .offline;
    • otherwise, a reattach that gave up: .attached, the last frame with "Type to try again".

    In send(), a keystroke on a gave-up session starts a fresh retry round before #22387's stale-input refusal.

  • LinkMachine.swift: #22387's offlineAfter/showsTerminalsOffline plus #22337's media-aware statusLine(macName:media:).

  • DaemonTerminalSessionTests.swift: the fake daemon keeps both liveSurface/terminals (#22419) and replay/afterAttach (#22387).

  • cnx-ios-api env.ts keeps both TEAM_LIMIT (#22354) and TEAM_HUB_RETIRE_RETRY_MS (#22260). http.test.ts expects 0.10.0.

Breaking change: production naming (#22574)

The app, its Mac host and the Worker now use production names. The full naming map, storage migrations and backend cutover are in #22574.

OldNew
cnx-ios/, CnxKit, Cnx* modulescmux-next-ios/, CmuxMobileKit, CmuxMobile*
cnx-hostd (Resources/bin/cnx-hostd), cnx-link, cnx-fficmux-mobile-host, cmux-mobile-link, cmux-mobile-link-ffi
bundle ids dev.cmux.cnx[.cnx/.prod]dev.cmux.mobile[.demo/.prod] (TestFlight stays dev.cmux.app.internal)
wire: subprotocol cnx.v1, headers x-cnx-*, data channels cnx-link/cnx-input, signature contexts cnx-*-v1, push key cnxcmux-mobile.v1, x-cmux-mobile-*, cmux-mobile-link/cmux-mobile-input, cmux-mobile-*-v1, cmuxMobile
service cnx-ios-api.cmux.dev (Workers cnx-ios-api-*)mobile-api.cmux.dev (Workers cmux-mobile-api-*, API 0.11.0, already live)

Impact:

  • No transition period: cmux INTERNAL 1.2.0 and every earlier phone or Mac build can no longer connect, because the old host and wire names are gone. They work again with the next TestFlight build plus a cmux NEXT build of this branch.
  • Sign-in: a dogfood install under a new bundle id (dev.cmux.mobile.*) is a new app, so phones may need to sign in again. TestFlight keeps its bundle id; its storage moves in place (LegacyStorage).
  • TestFlight builder: an admin fast-forwards rc/cnx-ios-builder after this lands (see #22574).
  • Builder notes paths: scripts/ci/ios_internal_notes.py (What to Test notes, already on feat-cmux-next) now reads cmux-next-ios/ paths. It also still counts cnx-ios/ commits for ranges that span the rename (tests/test_ios_internal_notes.py passes).

Risk tier

risk_tier.py grades this high. The one reason is a new data delete: #22260's team-hub retire in cnx-ios/backend/src/team-hub.ts deletes the old team hub's Durable Object rows, which mixed a team's users. The Worker is already deployed with it (API 0.10.0). This PR needs an approving review on the head from a collaborator other than the author, or risk:human-ok from a maintainer.

Trust-relevant change for reviewers (#13742 follow-up needed)

#22354 D9 lets cnx-hostd's file service (phone file previews) read the folders of the Mac's open workspace tabs. These are the same roots already shared with phones for agent chats (agentcwd), which was approved for dogfood on 2026-10-14. Before, previews could read only agent-session artifact paths.

  • The roots never include /, the home folder or any ancestor of home.
  • Without a resolvable HOME, there are no roots.
  • Roots are compared in the stored spelling, as acpmux does.
  • Only a paired phone of the same owner on a live link can ask.

Review follow-up: a folder inside home's credential and settings trees (.ssh, .gnupg, .aws, .azure, .kube, .docker, .config, .password-store, Library) is never a preview root, because a tab's OSC 7 cwd can be set by any output.

Draft recommendation for #13742 (not posted):

Recommendation: keep phone file previews scoped to open workspace folders (approve as shipped in cmux-next dogfood). A paired iPhone of the same account can already start agent chats in the folders of the Mac's open workspace tabs (agentcwd, approved for dogfood 2026-10-14). Since #22354 D9 the phone's read-only file preview may read files under those same folders, and nothing else. It never reads /, the home folder or its ancestors, and it reads nothing when HOME cannot be resolved. Reading is bounded to what an agent the phone starts could already read and show, so this adds no new reach. The option was to keep previews limited to agent-session artifacts, but that broke previews of files an agent edited (full-verify D9). Before a public release: show the shared folders on the Mac's iPhone settings card, and add an "iPhone file previews" toggle tied to mobile.allowIPhoneConnections.

Verification

Done:

  • Swift CnxKit (mini-cmux11s, Xcode 26.6, full swift test at 42a140246e1): 851 tests in 235 suites passed.
  • Rust cnx-ios/rust (rbx, cargo test --locked --workspace): all green.
  • Rust acpmux (rbx, cargo test --locked --no-fail-fast -p acpmux): all 10 start_defaults tests pass, including the merged D4 test. Eleven other tests fail, and the same 11 fail at origin/feat-cmux-next ccf2c3b99fd on the same builder (protocol_registry_names_every_served_method, four durable_agents crash and restart tests, two hermetic_daemon_guard, session_offers, tool_inventory, subsessions, start_mode), so they are not from this merge.
  • Mac Swift CmuxNext (cmux-ci run step dfdf0fb063eeea197256f824, aws-m4pro-6): IPhoneHostChiefTests and CnxHostTests pass. Failing suites: PhoneAccessGateTests (DaemonLanePolicy fields), plus GUI and app suites in CmuxNextApp, Files, Palette and Actions. Their sources are unchanged from the base. The base step 24c0cc5c518742fcd5cc841a at ccf2c3b99fd fails the same suites, except it fails BrowserEngineStateRestoreTests where this branch fails PaletteNavigationTests (palette sources unchanged here, so it is flaky).
  • cnx-ios-api (mini-cmux11s, vitest in workerd at 0dbbebbd38c): tsc clean, 6 files and 64 tests pass, including teams.test.ts and isolation.test.ts.
  • iOS Production build, simulator and device (mini-cmux11s): check-production-binary.sh passes on both. Installed on a real iPhone (dev.cmux.cnx.prod 0.1.0 (47213)).
  • Mac Release production build at 42a140246e1: fleet job e57126efafa4468a221ffadf (aws-m4pro-3). The bundled cnx-hostd, cmux-tui and acpmux are stamped with this commit, and codesign --verify --deep --strict passes.
  • Smoke over the real link on mini-cmux8s at 42a140246e1: rig fleet build cnx-e2e-42a14024 (94c5fa45e43b2ac8bafeca47), Demo simulator.
    • Pass: sign-in, Mac found by device id, terminal typing (echo median 15 ms), and a WireGuard/WebRTC switch with the terminal working after.
    • Browser tab: video frames and the carrier arrive, but the page stayed blank (the Mac tab stayed on about:blank). That mini logged CVDisplayLinkCreateWithCGDisplay failed; a rerun on another mini is in progress.

Skipped:

  • Local xcodebuild tests and laptop Rust (repo rule).
  • A full CmuxNext pass (pre-existing failures above).
  • Freestyle (no credential on this laptop), so the Worker tests ran on a mini.
  • Each source PR's live checks are not repeated here; see their PRs.

Known open items (from the source PRs)

  • #22260: two users of the same Stack team were not tested live, only in the Worker tests. The team-hub retire path is temporary; remove it once no team hub holds sockets.
  • #22354: D1 team creation needs the Workers' STACK_SECRET_SERVER_KEY and the TEAM_LIMIT binding deployed. The prod Worker is 0.10.0; whether POST /v1/teams works there is unverified. D8 root cause is pending a live check. The D2 unavailable bar showed "didn't answer (daemon channel closed)" instead of the "isn't running" copy in one rig.
  • #22337: the Mac's iPhone card video label is unit-tested only (checking it would need focus).
  • #22419: the rb.visibility reminder and a hidden tab's open use different channels and can reorder at hostd. A renamed placeholder's Mac host stays until the host limit stops it.
  • #22387: first grid-to-tab switch at the 0.15 s bar, the grid "+" label, and two-host link latency were not scored.
  • Merge note: a "type to try again" keystroke that takes over 2 s to reattach is dropped with "input not sent", following #22387's no-stale-input rule. Before, #22419 alone sent it late.

Changelog

  • cmux-next iOS (unreleased): WireGuard by default with WebRTC browser video. Browser video and terminals survive redials and Mac restarts. Terminals have clearer offline and reconnect states. Chief availability, team creation, file previews of workspace folders and the Codex default model are fixed. The signaling hub is per user.

🤖 Generated with Claude Code