Skip to content

Make Tailscale setup banner dismissible - #10057

Merged
azooz2003-bit merged 8 commits into
mainfrom
feat-tailscale-banner-dismiss
Aug 13, 2026
Merged

azooz2003-bit merged 8 commits into
mainfrom
feat-tailscale-banner-dismiss

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

Adds a close control to the iOS Tailscale setup banner.

  • Shares session-scoped dismissal across workspace and disconnected shell presentations.
  • Keeps pairing and Settings available, and re-prompts after a fresh launch while setup is incomplete.
  • Covers the rendered close action with an accessibility identifier in the migration UI test.

The first commit is the red behavior test; the second commit implements the fix.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Makes the iOS Tailscale setup banner dismissible and defers opening the pairing scanner until authorization status is known. Previously the banner always persisted and migration could open pairing too early; now users can dismiss it for the session, it returns on relaunch until local authorization succeeds, and reconnect is suppressed while pairing is required.

  • Adds a close control with accessibility identifier MobileTailscalePairingRequiredDismiss; MobileTailscalePairingRequiredBanner now requires a dismiss action.
  • Introduces MobileTailscaleSetupStatus (notSelected, loadingAuthorization, pairingRequired, authorized) and derives tailscalePairingRequired from it.
  • Adds session-scoped MobileTailscaleSetupPromptState in CMUXMobileRootView to track loading/required/authorized and dismissal; latches “required” when selecting Tailscale during migration and follows shell status changes.
  • Changes migration action to setUpTailscale(status: MobileTailscaleSetupStatus); only opens the scanner when status == .pairingRequired to avoid pairing while authorization is still loading.
  • Hoists prompt state and threads showsTailscalePairingBanner/dismissTailscalePairingBanner through WorkspaceShellHost, WorkspaceShellView, WorkspaceListView, and DisconnectedWorkspaceShellView to gate reconnect while keeping pairing/Settings available without flicker.
  • Updates tests for route selection, prompt transitions, and UI dismissal.

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

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added a dismiss option to the Tailscale pairing-required banner.
    • Dismissed banners remain hidden during the current session.
    • The banner reappears when connection status or pairing requirements change.
    • Manual pairing remains available when required.
    • Reconnect actions are disabled while pairing setup is required.
    • Added accessibility support for the banner’s close control.
  • Tests

    • Added coverage for banner dismissal, migration prompts, loading states, and authorized setup.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The mobile shell now derives explicit Tailscale setup statuses and manages banner visibility in root-owned state. The banner supports dismissal, shell reconnect controls use that state, and iOS manual pairing reads the updated connection method immediately.

Changes

Tailscale setup prompt

Layer / File(s) Summary
Setup status and prompt state
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ReconnectRoutes.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileTailscaleSetupPromptState.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileRootPresentationState.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift
The shell reports explicit Tailscale setup states. Root state tracks selection, loading, pairing-required, authorized, and dismissed prompt phases.
Banner propagation and reconnect gating
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellHost.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileTailscalePairingRequiredBanner.swift
Shell views receive banner visibility and dismissal actions. Reconnect controls and connection chrome use the managed visibility state. The banner includes an accessible close button.
Status and UI validation
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ReconnectRouteSelectionTests.swift, Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileRootPresentationStateTests.swift, ios/cmuxUITests/cmuxUITests.swift
Tests cover loading, pairing-required, authorized, dismissal, and banner-removal behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Mergeability Score: 🟡 Moderate · up to 11e42

While this change makes the setup banner dismissible, authorization that is still loading can be mistaken for a pairing requirement, potentially sending users into an unnecessary pairing flow. Merge should wait for that state handling to be corrected; the UI-test fixture concerns are bounded follow-up items.

Possibly related PRs

Suggested reviewers: lawrencecchen

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MobileTailscalePairingRequiredBanner
  participant WorkspaceListView
  participant CMUXMobileRootView
  participant MobileShellComposite
  User->>MobileTailscalePairingRequiredBanner: tap dismiss button
  MobileTailscalePairingRequiredBanner->>WorkspaceListView: invoke dismissal action
  WorkspaceListView->>CMUXMobileRootView: apply prompt dismissal
  CMUXMobileRootView->>MobileShellComposite: read tailscaleSetupStatus
  MobileShellComposite-->>CMUXMobileRootView: return setup status
  CMUXMobileRootView->>WorkspaceListView: update banner visibility
Loading
🚥 Pre-merge checks | ✅ 23 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description explains the main changes but omits the required Testing, Demo Video, Review Trigger, and Checklist sections. Add the missing template sections, document testing and manual verification, include a demo video link or attachment, and complete the checklist.
✅ Passed checks (23 passed)
Check name Status Explanation
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 status is a pure Equatable/Sendable enum outside @MainActor; store reads remain in @MainActor composite/UI paths, with no new Sendable reference types or async protocols.
Cmux Swift Blocking Runtime ✅ Passed The production diff adds state transitions, callbacks, and UI changes only; no semaphore, wait, sleep, delayed dispatch, polling, main-queue sync, or manual lock was added.
Cmux Browser Automation Off-Main ✅ Passed The PR diff changes only iOS Tailscale files; it does not modify either browser automation path or introduce browser socket commands.
Cmux Expensive Synchronous Load ✅ Passed The diff adds only cached Tailscale state and UI presentation logic. It adds no agent-history loader, file parse, directory scan, transcript access, or synchronous JSON/JSONL read.
Cmux Cache Substitution Correctness ✅ Passed The diff changes only session-scoped Tailscale UI prompt/chrome and pairing gates; it adds no persistence, history, undo, or snapshot cache substitution, and reads connectionMethodStore.method sync...
Cmux No Hacky Sleeps ✅ Passed The diff changes 12 .swift files only. It adds no TypeScript, JavaScript, shell, or build/runtime-script delays; Swift UI-test timeouts are outside this check.
Cmux Algorithmic Complexity ✅ Passed The production diff adds state, enum, and UI plumbing only; it adds no collection scans, sorting, joins, or loops. Tailscale readiness reads the existing cached authorization flag.
Cmux Swift Concurrency ✅ Passed The diff adds only synchronous setup state and SwiftUI handlers; no new Dispatch queues, Combine state, completion APIs, or fire-and-forget Tasks appear in added lines.
Cmux Swift @Concurrent ✅ Passed The feature diff against merged main adds no async, nonisolated, @concurrent, or Task call-site changes; it only adds synchronous state and UI wiring.
Cmux Swift Package Boundaries ✅ Passed All production changes are inside the existing CmuxMobileShell and CmuxMobileShellUI SwiftPM targets; the new prompt state is package-contained UI composition, not app-target root logic.
Cmux Swiftpm Lockfiles ✅ Passed The PR diff changes only Swift source and test files; it changes no Package.swift, Package.resolved, .gitignore, workflow, or Xcode project package references.
Cmux Swift Logging ✅ Passed The complete PR diff adds no print, debugPrint, dump, NSLog, file/stdout logging, or sensitive diagnostics; the existing Logger declaration is unchanged.
Cmux User-Facing Error Privacy ✅ Passed The diff adds only a generic “Dismiss” control and state wiring; existing “Finish Tailscale setup” copy remains unchanged and Tailscale is selected in the product UI.
Cmux Full Internationalization ✅ Passed The new close label uses L10n; matching banner keys and mobile.common.dismiss already have translated en/ja entries, and the PR adds no catalog or web locale changes.
Cmux Swiftui State Layout ✅ Passed The diff adds only value-type @State prompt data and event-handler updates. It adds no ObservableObject/@published, GeometryReader, render-time writes, or new store references below list rows.
Cmux Architecture Rethink ✅ Passed The diff adds no timing or blocking workaround. Root-owned prompt state has explicit transitions, while the shell remains the readiness source and child views receive value snapshots and one dismis...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The feature diff changes iOS SwiftUI views, banner state, shell routing, and tests only; it adds no NSWindow, NSPanel, WindowGroup, identifier, or close-shortcut code.
Cmux Source Artifacts ✅ Passed All 10 changed paths are Swift source or test files under Packages/iOS; the diff has no artifact directories, binary files, logs, screenshots, caches, or build output.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The mainline-to-head production diff adds no test/debug guards or seam-named members; the new public Tailscale status is consumed by CMUXMobileRootView product logic.
Cmux No Ambient Global State ✅ Passed The diff adds scoped @State and constructable MobileTailscaleSetupPromptState with an instance apply method, plus a case-bearing status enum; it adds no top-level mutable state, free API function,...
Title check ✅ Passed The title clearly describes the primary change: making the iOS Tailscale setup banner dismissible.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-tailscale-banner-dismiss

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.

@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
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift`:
- Around line 447-450: Extract the duplicated isTailscalePairingBannerDismissed
assignment into one private method on the containing view, then replace the
dismiss closures passed to DisconnectedWorkspaceShellView and WorkspaceShellHost
with calls to that method.
🪄 Autofix

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 Plus

Run ID: 5e434b80-f461-487d-acbf-84dc8d4a2587

📥 Commits

Reviewing files that changed from the base of the PR and between 1237f6e and e0c929c.

📒 Files selected for processing (7)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileTailscalePairingRequiredBanner.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellHost.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swift
  • ios/cmuxUITests/cmuxUITests.swift

Comment thread Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift Outdated
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileRootPresentationState.swift (1)

125-130: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not infer pairing from a missing authorization result.

Line 130 converts hasUsableAuthorization == false into requiresPairing == true. That result is also false while a known paired Mac is still loading. The shell reports that case as .loadingAuthorization.

The root then opens the scanner and latches the banner as required. The prompt state preserves that requirement through loading. If the load later finds a valid grant, the user can remain in an unnecessary pairing flow.

Pass MobileTailscaleSetupStatus through this transition. Open pairing only for .pairingRequired. Select Tailscale and wait for the authoritative load result for .loadingAuthorization.

As per coding guidelines, “Correctness-critical facts must come from one reliable structured source.” As per path instructions, “For the Tailscale banner, pairing readiness, dismissal, and reconnect enablement, use one authoritative typed state source rather than independently reading potentially stale store flags.”

🤖 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
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileRootPresentationState.swift`
around lines 125 - 130, Update the setUpTailscale transition in
MobileRootPresentationState to accept and use MobileTailscaleSetupStatus as the
authoritative state source instead of inferring pairing from
hasUsableAuthorization. Open the scanner and return requiresPairing only for
.pairingRequired; for .loadingAuthorization, select Tailscale and wait for the
authoritative load result, while preserving the existing behavior for an
authorized state.

Sources: Coding guidelines, Path instructions

ios/cmuxUITests/cmuxUITests.swift (2)

2583-2604: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require a real connection-state transition.

Assert that MobileTerminalMacConnectionStatus is Connected before server.stop(), then wait for Reconnecting or Disconnected. The terminal fixture does not prove the status element's state, so a pre-existing destination state could satisfy the predicate without proving the server caused the transition.

🤖 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 `@ios/cmuxUITests/cmuxUITests.swift` around lines 2583 - 2604, Update the test
around MobileTerminalMacConnectionStatus to assert its label is Connected before
calling server.stop(). After stopping the server, retain the wait for
Reconnecting or Disconnected so the test verifies an actual connection-state
transition before deleting the saved computer.

2567-2581: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Isolate the persistent UI-test fixture state.

Manual pairing clears ui-test-mac’s hidden marker, so the unconditional tap is not the reported issue. However, setUpWithError() does not reset .standard UserDefaults or paired-Mac state. Clear or isolate that state because the fixture assumes ui-test-mac is the only saved computer.

🤖 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 `@ios/cmuxUITests/cmuxUITests.swift` around lines 2567 - 2581, Update
setUpWithError() and the launchAppAfterForgettingFinalComputer() fixture setup
to clear or isolate shared UserDefaults and paired-Mac state before launching
the app. Ensure the fixture starts with only ui-test-mac saved, without relying
on state left by previous UI tests, while preserving the existing manual-pairing
flow.

Source: Path instructions

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

Outside diff comments:
In `@ios/cmuxUITests/cmuxUITests.swift`:
- Around line 2583-2604: Update the test around
MobileTerminalMacConnectionStatus to assert its label is Connected before
calling server.stop(). After stopping the server, retain the wait for
Reconnecting or Disconnected so the test verifies an actual connection-state
transition before deleting the saved computer.
- Around line 2567-2581: Update setUpWithError() and the
launchAppAfterForgettingFinalComputer() fixture setup to clear or isolate shared
UserDefaults and paired-Mac state before launching the app. Ensure the fixture
starts with only ui-test-mac saved, without relying on state left by previous UI
tests, while preserving the existing manual-pairing flow.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileRootPresentationState.swift`:
- Around line 125-130: Update the setUpTailscale transition in
MobileRootPresentationState to accept and use MobileTailscaleSetupStatus as the
authoritative state source instead of inferring pairing from
hasUsableAuthorization. Open the scanner and return requiresPairing only for
.pairingRequired; for .loadingAuthorization, select Tailscale and wait for the
authoritative load result, while preserving the existing behavior for an
authorized state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7e8d8e3a-8eb4-4a5f-819e-1367e159cf7b

📥 Commits

Reviewing files that changed from the base of the PR and between 907da68 and 11e42f9.

📒 Files selected for processing (11)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ReconnectRoutes.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ReconnectRouteSelectionTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileRootPresentationState.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileTailscaleSetupPromptState.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellHost.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileRootPresentationStateTests.swift
  • ios/cmuxUITests/cmuxUITests.swift

@azooz2003-bit
azooz2003-bit merged commit 3166828 into main Aug 13, 2026
6 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-tailscale-banner-dismiss branch August 13, 2026 01:10
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