Skip to content

Fix Pair iPhone window: Cmd+W, sizing, layout, QR padding, channel scheme, failure copy#6038

Open
lawrencecchen wants to merge 8 commits into
mainfrom
fix-pairing-window
Open

Fix Pair iPhone window: Cmd+W, sizing, layout, QR padding, channel scheme, failure copy#6038
lawrencecchen wants to merge 8 commits into
mainfrom
fix-pairing-window

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Fixes the cluster of Pair iPhone window bugs Lawrence hit while dogfooding. All six live on the same surface, so they ship in one branch.

The six fixes

1. Cmd+W closed the active terminal tab instead of the pairing window. The pairing window is an auxiliary NSWindow, but its identifier was missing from cmuxAuxiliaryWindowIdentifiers, so the configured Close Tab shortcut fell through to the main window's tab manager. Added cmux.mobilePairingWindow to that set (same scoping Settings uses), so Cmd+W now performs performClose on the pairing window. Also hardened scripts/lint_auxiliary_window_close_shortcuts.py to resolve identifiers assigned through named constants (window.identifier = NSUserInterfaceItemIdentifier(Self.windowIdentifier)) — the literal-only regex is exactly why the pairing window escaped the lint. Two-commit red/green regression test testCmdWClosesMobilePairingWindowInsteadOfTerminalTab proves Cmd+W closes the window while the main window's tab survives.

2. Bigger default window size. "Pair your iPhone" was clipped and Copy IP/Port sat below the fold. Default is now 560x800 (was 540x720) with a 480x600 minimum.

3. Manual-add block moved above the QR. "Can't scan? Add this Mac manually" plus IP:port and Copy IP / Copy Port now render above the QR, so the copy controls are reachable without scrolling.

4. QR quiet zone was doubled. The bitmap bakes the spec 4-module quiet zone, and the white card added its own 12pt margin on top, doubling the visible quiet zone. Dropped the card padding so the visible quiet zone is the spec 4 modules. Still pure black/white, nearest-neighbor (.interpolation(.none)), spec quiet zone baked in, so it still scans.

5. iOS URL-scheme hijack: scheme is now channel-specific. Every build registered and emitted the same cmux-ios scheme, so a beta/prod pairing QR scanned with the system Camera app could open a dev build that also claimed the scheme (the OS picks an arbitrary registrant). The scheme is now derived per channel from one source of truth, CmxPairingURLScheme:

  • Development (DEBUG / reload.sh tagged) builds register + emit cmux-ios-dev.
  • Release (TestFlight beta + App Store) register + emit cmux-ios.

The iOS registered scheme comes from CMUX_IOS_URL_SCHEME in Config/Shared.xcconfig (dev default — an unknown config can never hijack release links) and Config/Release.xcconfig (release), interpolated into CFBundleURLSchemes in Info.plist. Emitters (CmxPairingQRCode.encode, MobileSyncPairingPayload.encodedURL, the v1 fallback in MobileAttachTicketStore) use CmxPairingURLScheme.current so the system camera routes each channel's QR to its matching build. Parsers and prefix-checks (MobilePairingScannerPolicy, MobileRootAuthGate, CmxAttachTicketInput, MobileShellComposite, PairingView) accept any channel's scheme via isPairingScheme/hasPairingScheme, so cross-channel pairing from inside the app still works. The predecessor's first pass updated the v2 emitter and two parsers; this completes it (the v1 fallback emitter in MobileAttachTicketStore still hardcoded cmux-ios, and five parser/prefix-check sites still hardcoded the scheme).

6. "Invalid pairing code" wording was wrong; added a version-mismatch path. There is no pairing-code secret anymore (the v2 QR carries bare routes and the host authorizes by Stack account, confirmed in CmxPairingQRCode v=2 + MobileCoreRPCClient). .invalidCode now reads "This isn't a cmux pairing QR. Scan the code shown in the Pair iPhone window on your Mac." A new .unrecognizedVersion failure category covers an attach URL whose grammar version (v=) is newer than this build understands — the decoder throws MobileSyncPairingPayloadError.unrecognizedURLVersion(version) (distinct from the generic invalidURL) and the UI says "This QR needs a newer version of cmux. Update the app and try again." That was the real field report (beta 1.0.2 predated the v2 QR a newer Mac emitted, fixed in 1.0.3); the wording and version-mismatch path still ship.

Channel-scheme model

One source of truth, CmxPairingURLScheme, maps the compile channel to a scheme: current returns cmux-ios-dev in DEBUG and cmux-ios in Release. Emitters use current; parsers accept all channels' schemes. The iOS bundle registers the matching scheme via the xcconfig var so the system camera routes each channel's QR to its build, while the in-app scanner stays cross-channel.

Tests

  • CmxPairingURLSchemeTests (new): pure channel-to-scheme derivation, cross-channel parsing, foreign-scheme rejection.
  • CmxAttachTicketInputTests (new cases): decodes a pairing code from any channel scheme; newer grammar version throws unrecognizedURLVersion; known version does not.
  • MobilePairingFailureTests (new cases): .invalidCode no longer mentions a "pairing code"; .unrecognizedVersion tells the user to update; exhaustive non-empty-message coverage includes the new category.
  • MobilePairingScannerPolicyTests / MobileRootAuthGateTests: extended with the dev scheme.
  • CmxPairingQRCodeTests: emitter round-trip assertions now track CmxPairingURLScheme.current so they are correct in both Debug and Release runs.
  • AppDelegateShortcutRoutingTests.testCmdWClosesMobilePairingWindowInsteadOfTerminalTab (regression, two-commit red/green).

Localization audit

The failure-message strings resolve from the iOS app catalog (ios/cmux/Resources/Localizable.xcstrings, via L10n/Bundle.main). Added/updated mobile.pairing.invalidCode, mobile.pairing.unrecognizedVersion, and mobile.pairing.guidance.updateApp with en + ja. The macOS pairing-window UI strings resolve from Resources/Localizable.xcstrings; audited all 31 referenced mobile.pairing.* keys for en + ja coverage (all present). Both catalogs validated as JSON.

Dogfood

macOS: Open the Pair iPhone window. Confirm "Pair your iPhone" is fully visible and Copy IP / Copy Port are above the fold without scrolling. Press Cmd+W in the pairing window: it should close the window, leaving the terminal tab behind it intact (previously Cmd+W closed the active tab). QR should still scan from a phone.

iOS scheme: A dev Mac's QR opens the dev iOS build; a beta/prod Mac's QR opens the release build (previously a beta QR scanned with the Camera app could open a dev build).

Failure wording: Scanning a non-cmux QR shows "This isn't a cmux pairing QR…" not "Invalid pairing code."

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Touches deep-link registration, QR emission, and attach decoding across Mac/iOS and dev tooling—wrong scheme or parser gaps would break pairing; macOS Cmd+W routing is localized to auxiliary window IDs with lint and tests.

Overview
Pair iPhone (macOS) — Registers the pairing window in cmuxAuxiliaryWindowIdentifiers so Cmd+W closes that window instead of a terminal tab; adds a regression test and extends the auxiliary-window lint to resolve constant-based window identifiers. Layout/sizing: larger default window (560×800), manual IP/port block above the QR, and less extra padding around the QR so the spec quiet zone isn’t doubled.

Channel-specific pairing URLs — Introduces CmxPairingURLScheme (cmux-ios release, cmux-ios-dev debug): emitters use current for QRs/attach links; parsers accept all schemes for in-app cross-channel pairing. iOS registers the scheme via CMUX_IOS_URL_SCHEME in xcconfig/Info.plist. Hardcoded cmux-ios checks are replaced across mobile core, RPC, shell, workspace, Mac attach ticket store, and dev scripts (attach-url.mjs, soak, QR shell).

Pairing failures (iOS) — New unrecognizedURLVersion when attach v= is newer than the app understands; updated invalid QR copy (no “pairing code” wording). Shell maps the new error to .unrecognizedVersion with update guidance; strings added in Localizable.xcstrings.

Reviewed by Cursor Bugbot for commit f417fc3. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes the Pair iPhone window and makes pairing links channel-specific across Mac, iOS, and dev tooling so Camera scans open the right build and errors are clearer.

  • Bug Fixes
    • Cmd+W now closes the Pair iPhone window (identifier added to auxiliary set); the lint resolves constant-assigned identifiers; a regression test guards this.
    • Default size set to 560×800 (min 480×600) so the title and Copy IP/Port are visible without scrolling.
    • Manual-add (IP:port + Copy buttons) moved above the QR for immediate access.
    • QR quiet zone fixed by removing extra card padding; spec 4-module margin preserved for reliable scans.
    • Channel-specific iOS URL scheme: dev uses cmux-ios-dev, release uses cmux-ios. Emitters use CmxPairingURLScheme.current; parsers accept all schemes for in-app cross-channel pairing. Info.plist reads $(CMUX_IOS_URL_SCHEME) from xcconfig.
    • Updated failure copy: non-cmux input now says it isn’t a cmux pairing QR; added an “unrecognized version” path that tells users to update the app when scanning a newer-grammar QR.
    • Dev tooling now defaults to the dev scheme: scripts/lib/attach-url.mjs emits cmux-ios-dev by default (override via RELEASE_URL_SCHEME); scripts/mobile-attach-qr.sh copy updated; scripts/mobile-stability-soak/mobile-soak.py mints dev-scheme URLs by default (override MOBILE_ATTACH_URL_SCHEME).
    • Rebaselined Swift file-length budget so CI guard checks pass; tests updated to assert MobilePairingFailureCategory.invalidCode.message and cover the new scheme/version logic.

Written for commit f417fc3. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Support pairing across multiple iOS channel schemes (dev/release)
    • Detect pairing codes that require a newer app version and show update guidance
  • Bug Fixes

    • Cmd+W now correctly closes the Pair iPhone pairing window
  • UI/UX

    • Manual entry moved above QR; QR presentation tightened
    • Increased pairing window default/minimum sizes
    • Clearer, localized pairing error and update guidance messages

lawrencecchen and others added 5 commits June 13, 2026 00:32
…he window, not a terminal tab

The pairing window's identifier is missing from cmuxAuxiliaryWindowIdentifiers,
so the Close Tab shortcut falls through to the main window's tab manager and
closes the active terminal tab behind the pairing window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add cmux.mobilePairingWindow to cmuxAuxiliaryWindowIdentifiers so the
configured Close Tab shortcut performs performClose on the pairing window
(the same scoping Settings uses) instead of falling through to the main
window's tab manager.

Harden the auxiliary-window close-shortcut lint to resolve identifiers
assigned through named constants (window.identifier =
NSUserInterfaceItemIdentifier(Self.windowIdentifier)); the literal-only
regex is exactly why the pairing window escaped the lint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Taller default (560x800) and bigger minimum (480x600) so the heading is
not clipped. Manual entry (Copy IP / Copy Port) moves above the QR so it
is reachable without scrolling. The QR loses the white card's 12pt
padding: the bitmap already bakes the spec 4-module quiet zone, so the
card doubled the visible margin; the code is capped at 380pt so the
manual block, the full QR, and the waiting indicator fit the default
window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All build variants registered and emitted the same cmux-ios scheme, so a
beta/prod pairing QR scanned with the system Camera app could open a dev
build that also claimed the scheme (the OS picks an arbitrary registrant).

The scheme is now derived per channel from a single source of truth,
CmxPairingURLScheme: development (DEBUG/tagged) builds register + emit
cmux-ios-dev, Release (TestFlight beta + App Store) register + emit
cmux-ios. The iOS registered scheme comes from CMUX_IOS_URL_SCHEME in
Config/Shared.xcconfig (dev default) and Config/Release.xcconfig
(release), interpolated into CFBundleURLSchemes in Info.plist.

Emitters (the Mac building a pairing/attach URL: CmxPairingQRCode.encode,
MobileSyncPairingPayload.encodedURL, the v1 fallback in
MobileAttachTicketStore) use CmxPairingURLScheme.current so the system
camera routes each channel's QR to its matching build. Parsers and
prefix-checks (the in-app scanner, the deep-link gate, paste handling:
MobilePairingScannerPolicy, MobileRootAuthGate, CmxAttachTicketInput,
MobileShellComposite, PairingView) accept any channel's scheme via
isPairingScheme/hasPairingScheme, so cross-channel pairing from inside the
app still works.

Adds CmxPairingURLSchemeTests (pure channel-to-scheme derivation) and
extends the scanner-policy and auth-gate tests with the dev scheme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… path

There is no pairing-code secret anymore: the v2 QR carries bare routes and
the host authorizes by Stack account, so "Invalid pairing code." was both
wrong and unhelpful. It now reads "This isn't a cmux pairing QR. Scan the
code shown in the Pair iPhone window on your Mac."

Adds a distinct unrecognized-version path. When an attach URL declares a
grammar version (v=) newer than this build understands, the decoder throws
MobileSyncPairingPayloadError.unrecognizedURLVersion(version) instead of
the generic invalidURL, and pairing surfaces the new .unrecognizedVersion
failure category: "This QR needs a newer version of cmux. Update the app
and try again." This is the real cause of the field report (beta 1.0.2
predated the v2 QR a newer Mac emitted); the wording and version-mismatch
path ship even though 1.0.3 already understands v2.

CmxPairingQRCode.attachURLVersion reads the v query item so a newer
grammar is told apart from a malformed code. en + ja localized; adds
MobilePairingFailure wording tests and CmxAttachTicketInput version tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 13, 2026 8:02am
cmux-staging Building Building Preview, Comment Jun 13, 2026 8:02am

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR centralizes iOS pairing/attach URL scheme handling into CmxPairingURLScheme (dev vs release), replaces hard-coded scheme strings across encoding/decoding/UI, adds attach URL version parsing and an unrecognizedURLVersion error path, and parametrizes the scheme via build configuration and tests.

Changes

Multi-channel pairing URL scheme centralization with version validation

Layer / File(s) Summary
URL scheme abstraction and type definitions
Packages/CMUXMobileCore/Sources/CMUXMobileCore/CmxPairingURLScheme.swift, Packages/CMUXMobileCore/Sources/CMUXMobileCore/MobileSyncProtocol.swift, Packages/CMUXMobileCore/Tests/CMUXMobileCoreTests/CmxPairingURLSchemeTests.swift
CmxPairingURLScheme defines release/development scheme constants, current selector, and helpers. MobileSyncPairingPayloadError.unrecognizedURLVersion(Int) added.
QR encoding/decoding with grammar version validation
Packages/CMUXMobileCore/Sources/CMUXMobileCore/CmxPairingQRCode.swift, Packages/CMUXMobileCore/Sources/CMUXMobileCore/MobileSyncProtocol.swift, Packages/CmuxMobileRPC/Sources/CmuxMobileRPC/CmxAttachTicketInput.swift, Packages/CMUXMobileCore/Tests/CMUXMobileCoreTests/CmxPairingQRCodeTests.swift, Packages/CmuxMobileRPC/Tests/CmuxMobileRPCTests/CmxAttachTicketInputTests.swift
CmxPairingQRCode.encode() and MobileSyncPairingPayload.encodedURL() use CmxPairingURLScheme.current. attachURLVersion() parses v queryitem. CmxAttachTicketInput.decode() rejects newer attach URL versions with unrecognizedURLVersion.
User-facing error handling and localization
Packages/CmuxMobileShell/Sources/CmuxMobileShell/MobilePairingFailure.swift, Packages/CmuxMobileShell/Tests/CmuxMobileShellTests/MobilePairingFailureTests.swift, ios/cmux/Resources/Localizable.xcstrings
MobilePairingFailureCategory.unrecognizedVersion added with analytics mapping and localized messages guiding the user to update the app. mobile.pairing.invalidCode updated.
Shell and UI workflow integration
Packages/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift, Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PairingView.swift, Packages/CmuxMobileWorkspace/Sources/CmuxMobileWorkspace/MobileRootAuthGate.swift
Replaces literal cmux-ios:// checks with CmxPairingURLScheme.hasPairingScheme() / isPairingScheme() across preview, input handling, and normalization. Maps unrecognizedURLVersion to .unrecognizedVersion failure in pairing flows.
iOS build configuration and URL scheme parametrization
ios/Config/Debug.xcconfig, ios/Config/Release.xcconfig, ios/Config/Shared.xcconfig, ios/Config/Info.plist
Adds CMUX_IOS_URL_SCHEME build setting (dev vs release) and switches Info.plist CFBundleURLSchemes to reference the build variable.
Workspace components and package dependencies
Packages/CmuxMobileWorkspace/Package.swift, Packages/CmuxMobileWorkspace/Sources/CmuxMobileWorkspace/MobilePairingScannerPolicy.swift, Packages/CmuxMobileWorkspace/Tests/CmuxMobileWorkspaceTests/MobilePairingScannerPolicyTests.swift, Packages/CmuxMobileWorkspace/Tests/CmuxMobileWorkspaceTests/MobileRootAuthGateTests.swift, Sources/Mobile/MobileAttachTicketStore.swift
Adds CMUXMobileCore package dependency. MobilePairingScannerPolicy.acceptsCode() and related tests accept both channel schemes. MobileAttachTicketStore fallback attach URL uses dynamic scheme.
Pairing window identifier and keyboard shortcut handling
Sources/Mobile/Pairing/MobilePairingWindowController.swift, Sources/cmuxApp.swift, cmuxTests/AppDelegateShortcutRoutingTests.swift, scripts/lint_auxiliary_window_close_shortcuts.py, tests/test_ci_auxiliary_window_close_shortcuts.sh
Exposes MobilePairingWindowController.windowIdentifier, adds it to cmuxAuxiliaryWindowIdentifiers for Cmd+W routing, enlarges window sizing, adds Cmd+W regression test, and extends the lint to detect constant-based window-identifier assignments with tests.
Pairing view UI restructuring and styling
Sources/Mobile/Pairing/MobilePairingView.swift
Reorders readyContent to place manual fallback above QR when reachable and updates QR container styling (constrained width, rounded clip, subtle border).
Scripts and tooling for attach URL generation/tests
scripts/lib/attach-url.mjs, scripts/lib/attach-url.test.mjs, scripts/mobile-attach-qr.sh
Exports dev/release scheme constants, allows buildAttachURL to accept an explicit scheme filter, updates tests to validate dev vs release scheme outputs, and documents scheme behavior for CLI QR generation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#5872: Also touches v2 pairing-QR encode/decode and attach URL validation; overlaps with this PR's changes to attach URL handling and version/loopback logic.
  • manaflow-ai/cmux#5727: Related changes to attach decoding and v-field detection inside pairing payloads.
  • manaflow-ai/cmux#5584: Related edits to scripts that generate attach URLs (attach-url.mjs) and encoder behavior.

Poem

🐰 I hopped across dev and release,

One scheme to bind them, neat and neat,
QR versions checked before they reach,
Windows close with keys so fleet,
A rabbit cheers: encode, decode, repeat!


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux User-Facing Error Privacy ❌ Error FAIL: iOS user-facing recovery copy mobile.pairing.guidance.updateApp includes upstream service names “App Store” and “TestFlight” in ios/cmux/Resources/Localizable.xcstrings. Update the mobile.pairing.guidance.updateApp localization to remove “App Store”/“TestFlight” wording (use generic “Update cmux to a newer version, then scan again”).
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift Concurrency ❓ Inconclusive Cannot verify the PR diff: git history has no parent/merge base here. MobileShellComposite.swift contains a labeled fire-and-forget Task block, so whether it was introduced/expanded is unclear. Need the actual PR unified diff (or parent commit) for MobileShellComposite.swift to confirm whether the fire-and-forget Task { ... } was added/expanded by this change.
✅ Passed checks (18 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the six primary fixes: Pair iPhone window (Cmd+W, sizing, layout, QR padding), channel-specific URL scheme, and failure copy updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed New Swift additions (CmxPairingURLScheme, attachURLVersion, unrecognizedURLVersion handling) are value-type helpers and synchronous error branching; no new @MainActor/Sendable reference-type or bac...
Cmux Swift Blocking Runtime ✅ Passed Searched PR diff for blocking/timing primitives (Task.sleep, DispatchSemaphore, DispatchQueue.main.sync, NSLock, sleep/timers); none found, so no added Swift blocking runtime sync.
Cmux Expensive Synchronous Load ✅ Passed Diff vs origin/main in all PR Swift changes has 0 occurrences of RestorableAgentSessionIndex.load (and no sysctl/KERN_PROCARGS2), so no expensive synchronous index load was added/moved onto main-ac...
Cmux Cache Substitution Correctness ✅ Passed No evidence of cache substitution in persistence/history/undo/snapshot paths: PR diff view contains no matches for cache/cached/snapshot/undo/history/persist keywords per web.find, and changes are...
Cmux No Hacky Sleeps ✅ Passed Fetched PR #6038 patch and searched changed non-Swift scripts for sleep/usleep/setTimeout/setInterval/timers/polling/wait-for-readiness keywords; no matches found, so no hacky sleeps added.
Cmux Algorithmic Complexity ✅ Passed New complexity-sensitive code only scans tiny fixed sets (2 schemes; maxRouteCount=8 routes) and URL parsing uses at most O(n) single/two-pass filtering; no nested full-collection rescans or hot-pa...
Cmux Swift @Concurrent ✅ Passed PR Swift diff contains no occurrences of @concurrent, nonisolated, or async (per repo PR files page search), so no concurrent-annotation violations were introduced.
Cmux Swift File And Package Boundaries ✅ Passed No boundary violations found: new CmxPairingURLScheme.swift is 71 lines (<400) in CMUXMobileCore; other touched production files are <=388 lines (except existing 4.8k-line MobileShellComposite), wi...
Cmux Swift Logging ✅ Passed Searched PR diff text for logging violations (print, debugPrint, dump, NSLog, Logger/logger, os.log, nonisolated); no matches found.
Cmux Full Internationalization ✅ Passed Validated iOS catalog ios/cmux/Resources/Localizable.xcstrings has en+ja translations for mobile.pairing.invalidCode/unrecognizedVersion/guidance.updateApp, and Swift MobilePairingFailure.swift use...
Cmux Swiftui State Layout ✅ Passed Reviewed SwiftUI files (PairingView.swift, MobilePairingView.swift): no ObservableObject/@Published/@observable, no GeometryReader, and state mutations occur only in event handlers (Button/sheet/ta...
Cmux Architecture Rethink ✅ Passed Swift diff introduces channel-specific URL-scheme + version parsing/UI layout changes; no added sleeps/asyncAfter/polling/locks/observers/side-channel wiring found, so no architectural rethink rule...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Repo state shows MobilePairingWindow uses stable identifier "cmux.mobilePairingWindow" registered in cmuxAuxiliaryWindowIdentifiers; lint script now covers constant-based .identifier assignments (s...
Cmux Source Artifacts ✅ Passed Local repo shows no tracked diffs (git diff --name-only empty); only untracked files are .coderabbit.swiftlint.yml and ruff.toml, which are config—not artifacts per the rules.
Description check ✅ Passed PR description is comprehensive and well-structured with clear sections covering what changed, why, specific fixes, testing, and dogfood instructions.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-pairing-window

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lawrencecchen lawrencecchen self-assigned this Jun 13, 2026
lawrencecchen and others added 2 commits June 13, 2026 00:42
Address autoreview: scripts/lib/attach-url.mjs (the debug-CLI QR renderer
in mobile-attach-qr.sh and the headless dev-setup auto-pair mint) still
hardcoded the release cmux-ios scheme, so a dev-rendered pairing QR scanned
with the system Camera could route to an installed TestFlight/App Store
build instead of the dev iOS build. buildAttachURL now takes a channel
scheme defaulting to cmux-ios-dev (both callers are dev-only), mirroring
CmxPairingURLScheme's default-to-dev safety; exported DEV_URL_SCHEME /
RELEASE_URL_SCHEME and added test coverage for both.

Also convert CmxPairingURLScheme from a caseless namespace enum to a
struct with a private init, matching the sibling helpers in CMUXMobileCore
(CmxMobileDefaults, CmxLoopbackHost, L10n) per the package-design policy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Cmd+W regression test adds 69 lines to AppDelegateShortcutRoutingTests
and the pairing-window aux identifier adds 1 line to cmuxApp; both are
required for the fix (a behavior-level regression test and the close-shortcut
scoping). Rebaselined the checked-in budget so workflow-guard-tests passes;
the other deltas track the rebase onto origin/main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address autoreview:

- ios/cmuxPackage cmuxFeatureTests asserted the old "Invalid pairing code."
  literal in two places (the expired pair?v=1 path and the loopback-rejected
  negative check). Now assert against
  MobilePairingFailureCategory.invalidCode.message so the tests track the copy
  instead of breaking on every reword.

- scripts/mobile-stability-soak/mobile-soak.py minted cmux-ios://attach and
  fed it to `simctl openurl`, which routes by the simulator app's registered
  CFBundleURLSchemes. Dev builds now register cmux-ios-dev, so in
  MOBILE_REATTACH_MODE=openurl the link would miss the tagged dev app (or open
  an installed release/beta build). The soak now mints the dev scheme by
  default (override with MOBILE_ATTACH_URL_SCHEME for release soaks).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR bundles six related fixes to the Pair iPhone surface: Cmd+W now closes the pairing window (not a terminal tab) by registering cmux.mobilePairingWindow in cmuxAuxiliaryWindowIdentifiers; the window is larger and the manual IP/port block moves above the QR; the QR quiet-zone doubling is eliminated; and channel-specific iOS URL schemes (cmux-ios-dev / cmux-ios) prevent Camera-app routing of a beta/prod QR to a dev build.

  • Introduces CmxPairingURLScheme as a single source of truth mapping compile channel to URL scheme; emitters use current, parsers accept all channels via isPairingScheme/hasPairingScheme, and the xcconfig pipeline wires the per-channel scheme into CFBundleURLSchemes.
  • Adds an unrecognizedURLVersion failure category (and matching localization in en + ja) for when the iOS app scans a QR with a grammar version it predates, so it surfaces "update the app" rather than the generic invalid-code copy.
  • Hardens the auxiliary-window lint to resolve identifiers assigned through named constants (Self.windowIdentifier), the exact gap that let the pairing window escape detection.

Confidence Score: 5/5

All six fixes are self-contained, well-tested, and leave no bad state representable; the channel-scheme model has a single source of truth, and parsers stay cross-channel so no pairing path is broken.

The changes touch deep-link routing and iOS URL-scheme registration — the highest-risk areas — but every emitter, parser, and xcconfig entry is consistently updated, and the test suite covers scheme derivation, cross-channel decoding, version-mismatch error paths, and the Cmd+W regression end-to-end. Localization is complete for all new strings in both supported locales. No actor-isolation, blocking-runtime, or SwiftUI state issues introduced.

No files require special attention; the xcconfig chain (Shared → Debug/Release) and the Swift CmxPairingURLScheme constants should be kept in sync if a new build configuration is added in the future.

Important Files Changed

Filename Overview
Packages/CMUXMobileCore/Sources/CMUXMobileCore/CmxPairingURLScheme.swift New single source of truth for channel-specific pairing schemes; emitter/parser split is clean, isPairingScheme and hasPairingScheme are mutually exclusive from cmux-ios vs cmux-ios-dev prefix collision, and the #if DEBUG compile-time guard is correctly surfaced as an injectable isDevelopmentBuild for testing.
Packages/CmuxMobileRPC/Sources/CmuxMobileRPC/CmxAttachTicketInput.swift Version-check for v > current is correctly placed before isPairingCodeURL and invalidURL, giving a distinct unrecognizedURLVersion error on future grammar versions; cross-channel scheme acceptance is complete.
Packages/CmuxMobileShell/Sources/CmuxMobileShell/MobilePairingFailure.swift New unrecognizedVersion category is fully wired: message, guidance, and analyticsReason are all set; invalidCode copy update removes the erroneous 'pairing code' framing; both new strings have en + ja catalog entries.
Sources/cmuxApp.swift Single one-line addition registering cmux.mobilePairingWindow in cmuxAuxiliaryWindowIdentifiers; corrects the Cmd+W fall-through bug.
Sources/Mobile/Pairing/MobilePairingWindowController.swift Window identifier promoted from private static let to static let so the regression test and lint can reference it; size bumped to 560x800 / min 480x600 to prevent clipping.
Sources/Mobile/Pairing/MobilePairingView.swift Manual-entry block moved above QR; QR quiet-zone doubling removed by dropping the 12pt-padded white card; maxWidth: 380 cap added; no state mutation during rendering, no broad invalidation introduced.
scripts/lint_auxiliary_window_close_shortcuts.py Added CONSTANT_ASSIGNMENT_RE + STRING_CONSTANT_DECL_RE to resolve per-file named constants; constant resolution is correctly file-scoped (fresh dict per file), and the new CI test validates both the failure and the passing case.
ios/Config/Shared.xcconfig Introduces CMUX_IOS_URL_SCHEME with a dev default so unknown build configurations can never hijack release deep links; Release.xcconfig overrides to cmux-ios.
ios/cmux/Resources/Localizable.xcstrings Adds mobile.pairing.unrecognizedVersion, mobile.pairing.guidance.updateApp, and updates mobile.pairing.invalidCode; all three keys have both en and ja translated entries.
cmuxTests/AppDelegateShortcutRoutingTests.swift New testCmdWClosesMobilePairingWindowInsteadOfTerminalTab regression test uses the same RunLoop.main.run(until:) + debugHandleCustomShortcut pattern as other auxiliary-window close tests in this file; correctly guarded to DEBUG builds only.
scripts/mobile-stability-soak/mobile-soak.py Module-level ATTACH_URL_SCHEME defaults to cmux-ios-dev for the dev simulator, overridable via MOBILE_ATTACH_URL_SCHEME env var for release soak runs.

Sequence Diagram

sequenceDiagram
    participant Mac as macOS App
    participant Scheme as CmxPairingURLScheme
    participant QR as QR Code / Camera
    participant iOS as iOS App

    Note over Mac,Scheme: Emitter path (channel-specific)
    Mac->>Scheme: CmxPairingURLScheme.current
    Scheme-->>Mac: cmux-ios-dev (DEBUG) or cmux-ios (Release)
    Mac->>QR: "scheme://attach?v=2&r=..."

    Note over QR,iOS: Camera scan (system routes by registered scheme)
    QR->>iOS: Opens matching channel's app via CFBundleURLSchemes

    Note over iOS,Scheme: Parser path (accepts all channels)
    iOS->>Scheme: CmxPairingURLScheme.isPairingScheme(url.scheme)
    Scheme-->>iOS: true if cmux-ios OR cmux-ios-dev

    alt "version > CmxPairingQRCode.version"
        iOS-->>iOS: unrecognizedURLVersion - Update the app
    else valid v2 grammar
        iOS-->>iOS: Decode routes, connect
    else not a cmux QR
        iOS-->>iOS: invalidCode - Scan the Pair iPhone window QR
    end
Loading

Reviews (1): Last reviewed commit: "Pair iPhone: fix stale invalid-code test..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/mobile-stability-soak/mobile-soak.py`:
- Around line 146-147: Read and validate the ATTACH_URL_SCHEME value right after
it's read (ATTACH_URL_SCHEME = os.environ.get(...).strip()) by checking it is
one of the allowed schemes ("cmux-ios-dev" or "cmux-ios"); if it's empty or not
in that allowlist, fail fast with a clear error (raise ValueError or log error
and sys.exit(1)). Also apply the same guard where the script builds attach/open
URLs (any code using ATTACH_URL_SCHEME at the later URL construction) so
invalid/typoed overrides cannot produce bad openurl targets.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d2981ccc-3d8e-477a-a4c6-91239dbcabd3

📥 Commits

Reviewing files that changed from the base of the PR and between fe676e9 and f417fc3.

📒 Files selected for processing (2)
  • ios/cmuxPackage/Tests/cmuxFeatureTests/cmuxFeatureTests.swift
  • scripts/mobile-stability-soak/mobile-soak.py

Comment on lines +146 to +147
ATTACH_URL_SCHEME = os.environ.get("MOBILE_ATTACH_URL_SCHEME", "cmux-ios-dev").strip()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate MOBILE_ATTACH_URL_SCHEME before building attach URLs.

At Line 146 and Line 153, an empty/typoed override can silently produce invalid openurl targets. Guard to the known schemes (cmux-ios-dev, cmux-ios) and fail fast with a clear error.

Proposed fix
-ATTACH_URL_SCHEME = os.environ.get("MOBILE_ATTACH_URL_SCHEME", "cmux-ios-dev").strip()
+ATTACH_URL_SCHEME = os.environ.get("MOBILE_ATTACH_URL_SCHEME", "cmux-ios-dev").strip()
+VALID_ATTACH_URL_SCHEMES = {"cmux-ios-dev", "cmux-ios"}
+if ATTACH_URL_SCHEME not in VALID_ATTACH_URL_SCHEMES:
+    raise RuntimeError(
+        "MOBILE_ATTACH_URL_SCHEME must be one of "
+        f"{sorted(VALID_ATTACH_URL_SCHEMES)}; got {ATTACH_URL_SCHEME!r}"
+    )

Also applies to: 153-153

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/mobile-stability-soak/mobile-soak.py` around lines 146 - 147, Read
and validate the ATTACH_URL_SCHEME value right after it's read
(ATTACH_URL_SCHEME = os.environ.get(...).strip()) by checking it is one of the
allowed schemes ("cmux-ios-dev" or "cmux-ios"); if it's empty or not in that
allowlist, fail fast with a clear error (raise ValueError or log error and
sys.exit(1)). Also apply the same guard where the script builds attach/open URLs
(any code using ATTACH_URL_SCHEME at the later URL construction) so
invalid/typoed overrides cannot produce bad openurl targets.

lawrencecchen added a commit that referenced this pull request Jun 13, 2026
…dding/channel-scheme/failure-copy, #6038)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant