Surface the Simulator feature at moments of intent on Mac and iOS - #9918
Surface the Simulator feature at moments of intent on Mac and iOS#9918azooz2003-bit wants to merge 2 commits into
Conversation
Visibility was low on both platforms: the Mac pane hid behind config customization and the palette, and the phone showed nothing until a user already knew to open the surfaces menu inside a workspace. Mac: a contextual New Simulator tab-bar button appears while any Simulator is booted (sampled at launch and app activation, throttled and single-flight, never on a timer) and disappears otherwise; the idle pane state teaches the next step with a one-click boot of the most recent device plus the embedded device picker; a one-time dismissable chip over a live stage offers phone control and opens Tailscale Pairing. iOS: workspace rows show a Simulator chip (green dot when booted) fed by the existing list payload; a one-time dismissable banner in the workspace opens the stream directly and names the durable path (the Terminals menu), retiring itself when a stream is opened any other way; the surfaces menu teaches the Mac-side step when the capability exists but no pane does; onboarding gains a simulator card (screenshots follow with the dogfood build). All copy localized EN and JA. No analytics: every existing emit path attaches a persistent per-install id, which fails the completely- anonymous requirement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR adds simulator boot tracking, macOS simulator controls, iOS simulator onboarding, workspace indicators, persistent simulator-stream hints, and English and Japanese localization. ChangesSimulator experience
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant AppDelegate
participant SimulatorBootPresence
participant Workspace
participant SimulatorPanelView
participant SimulatorDeviceStage
AppDelegate->>SimulatorBootPresence: refresh simulator boot presence
SimulatorBootPresence->>Workspace: notify when boot state changes
Workspace->>Workspace: update New Simulator tab-bar action
SimulatorPanelView->>SimulatorDeviceStage: provide eligible phone-control teaser
SimulatorDeviceStage->>SimulatorPanelView: invoke pairing or dismissal action
Possibly related PRs
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (4 errors, 1 warning)
✅ Passed checks (20 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 93668ff. Configure here.
| struct OnboardingScreenshot: View { | ||
| enum Content: String, CaseIterable { | ||
| case workspaces | ||
| case simulator |
There was a problem hiding this comment.
Missing onboarding simulator screenshots
High Severity
OnboardingScreenshot.Content gains a .simulator case and OnboardingSimulatorView renders it, but Resources/OnboardingScreenshots has no Onboarding-simulator-*.png assets. Missing loads become an empty UIImage, so the new page shows a blank phone frame and everyLocalizedOnboardingScreenshotLoads fails its non-zero size checks.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 93668ff. Configure here.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingScreenshot.swift (1)
10-18: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd the simulator onboarding assets. Add the four missing files under
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/OnboardingScreenshots:Onboarding-simulator-en.png,Onboarding-simulator-en-dark.png,Onboarding-simulator-ja.png, andOnboarding-simulator-ja-dark.png. Without them, release builds display a blank screenshot.🤖 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/OnboardingScreenshot.swift` around lines 10 - 18, Add the four simulator onboarding PNG assets—Onboarding-simulator-en.png, Onboarding-simulator-en-dark.png, Onboarding-simulator-ja.png, and Onboarding-simulator-ja-dark.png—to the OnboardingScreenshots resources directory so the simulator case in Content loads correctly in release builds.
🤖 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 `@Sources/AppDelegate.swift`:
- Around line 1375-1379: The feature-flag change handling must also refresh
simulator boot presence when the simulator feature becomes enabled. Update the
existing observer or flag-change handler near `CmuxFeatureFlags.shared.start()`
to detect the transition to enabled and call `simulatorBootPresence.refresh()`,
while preserving the current button reapplication behavior.
In `@Sources/Panels/SimulatorPanelView.swift`:
- Around line 62-66: Update the openPairing closure in SimulatorPanelView so it
calls performMobileConnectWorkspaceAction before setting
phoneControlTeaserDismissed. Only mark the teaser dismissed when that method
returns a non-nil workspace, preserving the dismissed state when pairing cannot
be opened.
In `@Sources/SimulatorBootPresence.swift`:
- Around line 49-51: Remove the time-based minimumRefreshInterval early return
from the boot-presence refresh logic so activation always refreshes from the
authoritative service. Preserve the refreshTask single-flight guard and existing
authoritative update behavior, ensuring hasBootedDevice reflects newly booted
Simulators without throttling.
- Around line 54-55: Remove the 30-second lastRefreshAt suppression from the
hasBootedDevice refresh flow around refreshTask and discoverBootedDevice. Keep
single-flight protection, but allow a fresh
SimulatorControlService.discoverDevices() result whenever the UI state may be
stale, using freshness or event-driven invalidation so Workspace reflects newly
booted devices without a visible stale window.
---
Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingScreenshot.swift`:
- Around line 10-18: Add the four simulator onboarding PNG
assets—Onboarding-simulator-en.png, Onboarding-simulator-en-dark.png,
Onboarding-simulator-ja.png, and Onboarding-simulator-ja-dark.png—to the
OnboardingScreenshots resources directory so the simulator case in Content loads
correctly in release builds.
🪄 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: fd7f39d6-63f3-4695-8534-2e7c302f51a2
📒 Files selected for processing (29)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+SimulatorStream.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileSimulatorStreamHintDismissalStore.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileSimulatorStreamHintTests.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspacePreview.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileWorkspacePreviewSimulatorTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingFlowView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingSceneChrome.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingScreenshot.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingSimulatorView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingStage.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/Localizable.xcstringsPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+Surfaces.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceSimulatorChipLabel.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceSimulatorHintBanner.swiftPackages/macOS/CmuxSimulator/Sources/CmuxSimulatorUI/Views/SimulatorDeviceStage.swiftPackages/macOS/CmuxSimulator/Sources/CmuxSimulatorUI/Views/SimulatorPaneView.swiftPackages/macOS/CmuxSimulator/Sources/CmuxSimulatorUI/Views/SimulatorPhoneControlTeaser.swiftPackages/macOS/CmuxSimulator/Sources/CmuxSimulatorUI/Views/SimulatorStrings.swiftResources/Localizable.xcstringsSources/AppDelegate.swiftSources/Panels/SimulatorPanelView.swiftSources/SimulatorBootPresence.swiftSources/Workspace.swiftcmux.xcodeproj/project.pbxprojios/cmux/Resources/Localizable.xcstrings
…fresh Boot presence now always samples fresh on refresh (single-flight only, no time-based cache: UI-enabling state must not go stale right after a boot), reuses one discovery service across samples, and re-samples when the remote feature flags change so a late flag enable does not wait for the next app activation. The phone-control teaser stays available when pairing fails to open. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>


Simulator visibility was low on both platforms: the Mac pane hid behind config customization and the palette, and the phone showed nothing until a user already knew to open the surfaces menu inside a workspace. This PR adds moment-of-intent discovery on both ends, all copy EN+JA.
Mac
New Simulatorappears while any Simulator is booted and disappears otherwise. Presence is sampled at launch and app activation through a throttled, single-flightSimulatorBootPresence(no timers); workspaces re-apply their tab-bar buttons on the flip, reusing the feature-flag re-apply path. A button already present (default or user-configured) wins.performMobileConnectWorkspaceActionpath, gated on the mobile-connect flag so it can never advertise a dead path.iOS
simulatorspayload already in the workspace list — no protocol change. Renders in both the SwiftUI and UIKit list pipelines via the sharedWorkspaceRow.Everything gates on the existing
simulator-enabled-releaseflag (and the teaser additionally onmobile-connect-button-enabled-release), so no surface can advertise a disabled feature.No analytics events: every existing emit path attaches a persistent per-install id (iOS
client_id, Mac PostHog anonymous distinct id), which fails the completely-anonymous requirement, so the funnel instrumentation was deliberately skipped.Tests: booted-detection policy on
MobileWorkspacePreview, hint-dismissal persistence and composite write-through, plus the existing simulator stream suites (15 green);CmuxSimulatorpackage builds clean.🤖 Generated with Claude Code
Note
Low Risk
Mostly UI/onboarding and UserDefaults persistence; Mac boot sampling is throttled and flag-gated with no auth or protocol changes.
Overview
Adds moment-of-intent discovery for Mac Simulator streaming on both Mac and iOS, with EN+JA strings and feature-flag gating (
simulator-enabled-release; Mac teaser also needsmobile-connect-button-enabled-release).iOS adds workspace-list Simulator chips (green dot when
hasBootedSimulator), a one-time workspace banner that opens the stream and names the Terminals menu, global dismissal viaMobileSimulatorStreamHintDismissalStore/dismissSimulatorStreamHint()(also when opening any simulator stream), and an empty-state in the Mac Simulators menu when capability exists but no pane is attached. Onboarding inserts a simulator stage between agents and notifications.Mac introduces throttled
SimulatorBootPresence(launch + activation) to inject a contextual New Simulator tab-bar button while any device is booted, improves the idle Simulator pane with help text, quick-start, and embedded picker, and shows a one-time phone-control teaser on live stages that opens pairing throughperformMobileConnectWorkspaceAction.Reviewed by Cursor Bugbot for commit 93668ff. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Improves Simulator discovery on Mac and iOS with contextual UI, clear teaching moments, and one‑tap paths. Adds fresh boot sampling and a resilient pairing teaser so the feature shows up exactly when you’re doing iOS work.
SimulatorBootPresence) and hides otherwise. Refreshes are single‑flight and always fresh (no time‑based cache; one discovery service reused). The idle pane teaches the next step, offers one‑click Start , embeds the device picker, and shows a tooltip when booted. A one‑time “Control this Simulator from your iPhone” chip over a live stage opens pairing viaperformMobileConnectWorkspaceAction, stays retryable on failure, and can be dismissed.simulatorspayload. A one‑time banner in the workspace opens the stream and points to the durable path (Terminals menu); it retires globally on dismissal or when any simulator stream is opened (dismissSimulatorStreamHintwrite‑through). The surfaces menu keeps a “Mac Simulators” section with a teaching empty state when no pane exists. Onboarding adds a Simulator card. Includes case‑insensitivehasBootedSimulatordetection and unit tests.simulator-enabled-release; the phone‑control teaser also bymobile-connect-button-enabled-release. Boot‑presence re‑samples when flags change. All copy localized in EN and JA. No analytics added.Written for commit 3192c42. Summary will update on new commits.
Summary by CodeRabbit