Skip to content

iOS: add Mac power controls - #8137

Closed
lawrencecchen wants to merge 3 commits into
mainfrom
issue-6482-ios-mac-power-controls
Closed

lawrencecchen wants to merge 3 commits into
mainfrom
issue-6482-ios-mac-power-controls

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • sleep Mac displays from the iOS computer detail screen without sleeping the Mac
  • toggle a cmux-owned keep-awake assertion without touching other caffeinate processes
  • read and toggle Low Power Mode for the active power source
  • require same-account Stack authentication and a Mac-wide attach ticket for every power RPC

Supersedes the implementation approach in #6902 by Austin Wang while preserving its mobile power-control product direction. Fixes #6482.

Verification

  • swift test --package-path Packages/iOS/CmuxMobileShell --filter MobileMacPowerStatusTests
  • Swift parse checks for changed Mac, RPC, iOS, and test files
  • workspace package grouping and Package.resolved policy checks
  • Xcode project normalization
  • English/Japanese localization parity and JSON parsing
  • git diff --check

What to test

  1. On iOS, open the active Mac computer details.
  2. Toggle Keep Mac Awake on and off.
  3. Toggle Low Power Mode and confirm macOS reflects the new state.
  4. Tap Sleep Display, approve the confirmation, and confirm the display turns off while the Mac stays reachable.

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


Summary by cubic

Add Mac power controls to the iOS Mac details screen: sleep displays, toggle a cmux-owned Keep Awake, and toggle Low Power Mode via new mobile RPCs with strict authorization. Improves remote control from iOS while keeping the Mac reachable.

  • New Features

    • iOS UI: new Mac Power section with Keep Awake and Low Power Mode toggles, Sleep Display action, status fetch, and busy/error states.
    • Capability/RPCs: gated by mac.power.control.v1; adds mac.power.status, mac.power.sleep_display, mac.power.keep_awake.set, mac.power.low_power.set.
    • macOS service: MacPowerService using PowerAssertionHolder (IOKit) and existing SleepyPowerControls; returns authoritative MacPowerStatus.
    • Security: all power RPCs require same-account Stack auth and a Mac-wide attach ticket (ticket and iroh paths enforced).
    • Tests/localization: unit tests for decoding, service behavior, and authorization; EN/JA strings and changelog updated.
  • Bug Fixes

    • Fixed main-actor–isolated construction of MacPowerService to avoid cross-actor initialization and ensure safe power RPC handling.

Written for commit 877012b. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added Mac Power controls for supported Macs: view power status, keep the Mac awake, toggle Low Power Mode, and sleep the display.
    • Included confirmation prompts, loading/error states, and wired UI controls to remote power actions.
    • Added capability detection and tightened authorization for Mac power RPC methods.
    • Added English/Japanese user-facing strings for the Mac Power section.
  • Tests

    • Added coverage for Mac power status decoding, service behavior, and authorization rules.
  • Documentation

    • Updated internal release notes and dogfood validation checklist.

@vercel

vercel Bot commented Jul 15, 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 Jul 15, 2026 12:04pm
cmux-staging Building Building Preview, Comment Jul 15, 2026 12:04pm

@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Mac power status and controls are added across the host service, mobile RPC layer, iOS shell client, and Mac detail UI, with capability negotiation, authorization checks, localization, and tests for decoding, service behavior, and admission rules.

Changes

Mac power control

Layer / File(s) Summary
Mobile power contracts and RPC client
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/*, Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/*
Adds mobile power status/error types, capability detection, RPC commands, response decoding, and decoder tests.
Host power service and assertion handling
Sources/MacPower*.swift, Sources/PowerAssertion*.swift, cmux.xcodeproj/project.pbxproj
Adds Mac power state management, IOKit keep-awake assertions, low-power verification, display sleep operations, and project wiring.
Mobile RPC dispatch and authorization
Sources/TerminalController.swift, Sources/Mobile/*, cmuxTests/MobileHost*AuthorizationTests.swift
Dispatches Mac power RPCs, formats results and errors, advertises the capability, and enforces ticket and Iroh admission rules.
Mac detail power controls
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MacComputerDetailView.swift, ios/cmux/Resources/Localizable.xcstrings, ios/CHANGELOG.md
Adds capability-gated toggles, display-sleep confirmation, refresh and error handling, localized strings, and dogfood notes.
Host service validation
cmuxTests/MacPowerServiceTests.swift
Tests keep-awake and low-power state composition, mutation failures, and display sleep routing.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MacComputerDetailView
  participant MobileShellComposite
  participant TerminalController
  participant MacPowerService
  participant PowerAssertionHolder
  MacComputerDetailView->>MobileShellComposite: invoke Mac power RPC method
  MobileShellComposite->>TerminalController: send mac.power request
  TerminalController->>MacPowerService: execute status or control operation
  MacPowerService->>PowerAssertionHolder: set keep-awake assertion
  PowerAssertionHolder-->>MacPowerService: assertion state or error
  MacPowerService-->>TerminalController: return status or service error
  TerminalController-->>MobileShellComposite: return RPC result
  MobileShellComposite-->>MacComputerDetailView: update UI state
Loading

Suggested reviewers: austinywang, azooz2003-bit


Important

Pre-merge checks failed

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

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Package Boundaries ❌ Error The PR adds reusable, unit-testable Mac power domain logic (MacPowerService, status, assertion holder/protocol) directly under root Sources/ in the app target. Extract a small package target like CmuxMacPowerCore for MacPowerService, MacPowerStatus, PowerAssertionHolding, and PowerAssertionHolder; keep TerminalController/MobileHostService as glue.
Cmux Full Internationalization ❌ Error TerminalController adds new English mac.power RPC error strings not routed through localization; only the UI strings are cataloged en/ja. Localize the mac.power RPC error messages (or make them non-user-facing) and add translated entries for every supported locale.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (22 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements Mac sleep, keep-awake control, and status visibility as requested in issue #6482, with explicit safety/auth checks.
Out of Scope Changes check ✅ Passed The added low-power-mode support and related tests/docs stay within the Mac power-control objective and do not appear unrelated.
Cmux Swift Actor Isolation ✅ Passed Added mutable services stay @MainActor; new power-status types are plain Sendable values, with no new unsafe Sendable refs or off-main UI/store access found.
Cmux Swift Blocking Runtime ✅ Passed PASS: the new mac-power code is async/MainActor-based and adds no new waits, sleeps, delayed dispatch, main-queue sync, or manual locks.
Cmux Browser Automation Off-Main ✅ Passed Diff only adds MacPowerService/TerminalController init plumbing; no browser.* commands, WebKit waits, socket-worker routing, or policy coverage changed.
Cmux Expensive Synchronous Load ✅ Passed PR adds bounded Mac power RPC/UI code only; no new agent-history, transcript, JSONL, or directory-scan sync loads on interactive main-actor paths.
Cmux Cache Substitution Correctness ✅ Passed No durable path swapped fresh reads for caches: MacPowerService re-reads live power state, setLowPowerMode verifies against status(), and the UI @State is transient.
Cmux No Hacky Sleeps ✅ Passed PR changes are Swift/docs/config only; no TypeScript, JavaScript, shell, or runtime script sleeps/delays are introduced.
Cmux Algorithmic Complexity ✅ Passed New paths use Set.contains and fixed-size lists only; the added RPC/UI code has no nested scans, repeated sorting/filtering, or per-target rescans.
Cmux Swift Concurrency ✅ Passed The new Mac-power code is async/await-based; I found no new DispatchQueue, Combine, or completion-handler APIs, and the only Task usage is SwiftUI/UI-bound.
Cmux Swift @Concurrent ✅ Passed No diff introduces nonisolated async work or invalid @concurrent; the new Mac power methods are actor-bound @MainActor/UI work with explicit Task use in the view.
Cmux Swiftpm Lockfiles ✅ Passed Only source-file pbxproj entries changed; no Package.swift, package-ref, .gitignore, or Package.resolved diffs appear in the PR.
Cmux Swift Logging ✅ Passed Patch only changes MacPowerService.swift and TerminalController.swift; no added/changed print, debugPrint, dump, NSLog, or Logger calls were found.
Cmux User-Facing Error Privacy ✅ Passed Mac power UI/API errors use generic product terms only; no upstream/vendor/internal details or raw messages are exposed.
Cmux Swiftui State Layout ✅ Passed MacComputerDetailView adds only local @State and event-driven .task/Button actions; no new ObservableObject/@Published/GeometryReader or lazy-row store refs appear.
Cmux Architecture Rethink ✅ Passed New Mac power flow uses a single service/bridge owner with clear capability and auth gates; no introduced sleeps, polling, locks, or split lifecycle ownership in the diff.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No standalone NSWindow/NSPanel/WindowGroup changes appeared; the UI diff is a detail view/confirmation dialog, with no cmuxAuxiliaryWindowIdentifiers edits.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source, test, config, docs, or localization files; no artifact directories or generated outputs were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The added production files only introduce Mac power APIs and a private helper; no new #if DEBUG/test-hook members were added under Sources/ paths.
Cmux No Ambient Global State ✅ Passed PASS: the actual diff only adds instance-level init wiring; no new file-scope funcs, mutable globals, or singletons were introduced.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding Mac power controls to iOS.
Description check ✅ Passed The description covers the change, motivation, testing, and manual verification, but it omits the demo video and checklist boilerplate sections.
✨ 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 issue-6482-ios-mac-power-controls

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.

@greptile-apps

greptile-apps Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Mac power controls to the iOS Mac detail screen — sleep displays, toggle a cmux-owned keep-awake IOKit assertion, and toggle Low Power Mode — with a new MacPowerService on the Mac side and corresponding MobileShellComposite extension and SwiftUI section on the iOS side. Authorization is enforced at two layers: same-account Stack auth for the iroh admission path and a mac-wide attach ticket for the stackBearer path.

  • macOS: MacPowerService (@mainactor) owns a PowerAssertionHolder (IOKit IOPMAssertionCreateWithName) and delegates display sleep / low-power control to the existing SleepyPowerControls; v2MobileMacPower in TerminalController dispatches the four new RPC methods and serializes MacPowerStatus as {ok, result: {keep_awake_enabled, low_power_enabled}}, which correctly matches the iOS decoder.
  • iOS: MacComputerDetailView adds @State power-status/busy/error fields, a .task(id:)-based status refresh, and runPowerAction/runPowerStatusAction helpers for toggle and button actions; MobileShellComposite+MacPower gates all calls on the mac.power.control.v1 capability.
  • Auth/localization: Both irohAdmission (prefix-based) and stackBearer (explicit four-method list) enforce authorization; all seven new user-facing strings ship with EN and JA translations.

Confidence Score: 5/5

Safe to merge — the auth chain, RPC encoding, IOKit assertion lifecycle, actor isolation, and i18n are all correct and well-tested.

The RPC response format was verified end-to-end: MobileHostRPCEnvelope.encodeResponse wraps the payload under the result key, which matches the iOS MobileMacPowerStatus.decode guard chain. The two authorization layers (Stack auth on iroh, mac-wide ticket on stackBearer) are exercised by the new authorization and iroh admission tests. PowerAssertionHolder.deinit safely runs on the main actor because it is owned exclusively by @MainActor MacPowerService, which is in turn owned by the @MainActor TerminalController.shared singleton. All new user-facing strings have EN and JA translations.

No files require special attention.

Important Files Changed

Filename Overview
Sources/MacPowerService.swift New @mainactor service orchestrating IOKit power assertions and SleepyPowerControls; double-validates low-power mutations before returning authoritative status.
Sources/PowerAssertionHolder.swift @mainactor IOKit assertion holder; deinit safely releases on the main actor (owned by @mainactor service chain), IOPMAssertionRelease failure in deinit discards error silently which is acceptable cleanup-path behavior.
Sources/Mobile/MobileHostService.swift Adds isPrivilegedMacPowerMethod to enforce Stack auth on the irohAdmission path for all mac.power.* RPCs; asymmetry with the explicit ticket-auth list was flagged in prior threads.
Sources/Mobile/MobileHostService+TicketAuthorization.swift Adds explicit four-method case requiring mac-wide attach ticket for all power RPCs; correctly gates via ticketMacScopedWorkspaceMutationAuthorizationError.
Sources/TerminalController.swift Adds v2MobileMacPower dispatch and macPowerResult helper; response format is correct — V2CallResult.ok wraps payload under 'result' key matching the iOS MobileMacPowerStatus.decode expectation.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MacComputerDetailView.swift Adds macPowerSection with Keep Awake/Low Power toggles and Sleep Display button; @State power state, busy guard, error display, and task(id:)-based refresh are all correct SwiftUI patterns.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileMacPowerStatus.swift Internal decode() accessed via @testable import in tests — correct pattern, no production-source test seam introduced.
ios/cmux/Resources/Localizable.xcstrings All seven new mobile.macPower.* keys include both EN and JA translations; i18n parity is complete for the touched catalog.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant iOS as iOS MacComputerDetailView
    participant Shell as MobileShellComposite
    participant RPC as MobileCoreRPCClient
    participant Auth as MobileHostService (Mac)
    participant TC as TerminalController
    participant PS as MacPowerService
    participant IOKit as IOKit / SleepyPowerControls

    iOS->>Shell: macPowerStatus() / setMacKeepAwake() / setMacLowPowerMode() / sleepMacDisplay()
    Shell->>RPC: "sendRequest(mac.power.*)"
    RPC->>Auth: irohAdmission / stackBearer path
    Auth->>Auth: isPrivilegedMacPowerMethod? → Stack auth required
    Auth->>Auth: ticketAuthorizationError? → mac-wide ticket required
    Auth-->>RPC: authorized
    RPC->>TC: v2MobileMacPower(method, params)
    TC->>PS: status() / setKeepAwake() / setLowPowerMode() / sleepDisplay()
    PS->>IOKit: IOPMAssertionCreate/Release / setLowPowerMode / sleepDisplayNow
    IOKit-->>PS: result
    PS-->>TC: MacPowerStatus
    TC-->>RPC: "{ok: true, result: {keep_awake_enabled, low_power_enabled}}"
    RPC-->>Shell: Data
    Shell->>Shell: MobileMacPowerStatus.decode(data)
    Shell-->>iOS: MobileMacPowerStatus
    iOS->>iOS: "update @State macPowerStatus / macPowerError"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant iOS as iOS MacComputerDetailView
    participant Shell as MobileShellComposite
    participant RPC as MobileCoreRPCClient
    participant Auth as MobileHostService (Mac)
    participant TC as TerminalController
    participant PS as MacPowerService
    participant IOKit as IOKit / SleepyPowerControls

    iOS->>Shell: macPowerStatus() / setMacKeepAwake() / setMacLowPowerMode() / sleepMacDisplay()
    Shell->>RPC: "sendRequest(mac.power.*)"
    RPC->>Auth: irohAdmission / stackBearer path
    Auth->>Auth: isPrivilegedMacPowerMethod? → Stack auth required
    Auth->>Auth: ticketAuthorizationError? → mac-wide ticket required
    Auth-->>RPC: authorized
    RPC->>TC: v2MobileMacPower(method, params)
    TC->>PS: status() / setKeepAwake() / setLowPowerMode() / sleepDisplay()
    PS->>IOKit: IOPMAssertionCreate/Release / setLowPowerMode / sleepDisplayNow
    IOKit-->>PS: result
    PS-->>TC: MacPowerStatus
    TC-->>RPC: "{ok: true, result: {keep_awake_enabled, low_power_enabled}}"
    RPC-->>Shell: Data
    Shell->>Shell: MobileMacPowerStatus.decode(data)
    Shell-->>iOS: MobileMacPowerStatus
    iOS->>iOS: "update @State macPowerStatus / macPowerError"
Loading

Reviews (2): Last reviewed commit: "Fix Mac power service actor-isolated con..." | Re-trigger Greptile

Comment on lines +1094 to +1096
nonisolated private static func isPrivilegedMacPowerMethod(_ method: String) -> Bool {
method == "mac.power.status" || method.hasPrefix("mac.power.")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Redundant first condition in isPrivilegedMacPowerMethod

method == "mac.power.status" is always true when method.hasPrefix("mac.power.") is true, so the first arm of the || is dead code. A reader may believe the two conditions guard different sets — for example, that the prefix test covers a broader set than just mac.power.status — when they are actually identical in coverage. The function can be simplified to just the hasPrefix guard.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +1094 to +1096
nonisolated private static func isPrivilegedMacPowerMethod(_ method: String) -> Bool {
method == "mac.power.status" || method.hasPrefix("mac.power.")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 hasPrefix("mac.power.") is broader than the explicit ticket-auth list

isPrivilegedMacPowerMethod uses method.hasPrefix("mac.power.") for the .irohAdmission path, so every future mac.power.* method added to the v2MobileMacPower switch automatically requires Stack auth over Iroh. However, the ticket-authorization switch in MobileHostService+TicketAuthorization.swift uses an explicit four-method list. Any new mac.power.* method added to the handler but omitted from the ticket-auth list falls to the default case and bypasses the mac-wide ticket check on the .stackBearer path. The two lists are structurally asymmetric: one is implicitly open, the other is explicitly closed. A comment or shared constant set documenting the intended scope of mac.power.* methods would prevent the two enforcement points from silently diverging.

@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: 4

🤖 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/MacComputerDetailView.swift`:
- Around line 541-558: Update the catch block in runPowerStatusAction to await
refreshMacPower() before assigning macPowerError, so the status refresh cannot
clear the mutation failure message. Preserve the existing localized error text
and refresh behavior.
- Around line 120-123: Update the .task modifier in MacComputerDetailView to use
an Equatable array containing isForeground and store.supportsMacPowerControl as
its ID instead of interpolating them into a string. Preserve the existing guard
and refreshMacPower behavior and the same invalidation semantics.

In `@Sources/Mobile/MobileHostService.swift`:
- Around line 1094-1096: Update isPrivilegedMacPowerMethod to remove the
redundant exact-match comparison and rely solely on
method.hasPrefix("mac.power.").

In `@Sources/PowerAssertionHolder.swift`:
- Around line 8-13: Mark the assertionID property in PowerAssertionHolder as
nonisolated(unsafe) so deinit can access it without violating Swift 6 strict
concurrency isolation. Keep its existing type, initialization, and usage
unchanged.
🪄 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: f37e5f98-727c-496a-addf-c5c6be998112

📥 Commits

Reviewing files that changed from the base of the PR and between 1ff14b8 and 73dc66c.

📒 Files selected for processing (20)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileMacPowerError.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileMacPowerStatus.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+MacPower.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileMacPowerStatusTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MacComputerDetailView.swift
  • Sources/MacPowerService.swift
  • Sources/MacPowerStatus.swift
  • Sources/Mobile/MobileHostService+Capabilities.swift
  • Sources/Mobile/MobileHostService+TicketAuthorization.swift
  • Sources/Mobile/MobileHostService.swift
  • Sources/PowerAssertionHolder.swift
  • Sources/PowerAssertionHolding.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/MacPowerServiceTests.swift
  • cmuxTests/MobileHostAuthorizationTests.swift
  • cmuxTests/MobileHostIrohAdmissionTests.swift
  • ios/CHANGELOG.md
  • ios/cmux/Resources/Localizable.xcstrings

Comment on lines +120 to +123
.task(id: "\(isForeground)-\(store.supportsMacPowerControl)") {
guard isForeground, store.supportsMacPowerControl else { return }
await refreshMacPower()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Prefer an array for the .task ID to avoid string allocations.

Using string interpolation for the .task identifier allocates a new string on every render pass of this view. Since both properties are booleans, you can pass them as an Equatable array to achieve the exact same invalidation semantics with less overhead.

💡 Proposed refactor
-        .task(id: "\(isForeground)-\(store.supportsMacPowerControl)") {
+        .task(id: [isForeground, store.supportsMacPowerControl]) {
             guard isForeground, store.supportsMacPowerControl else { return }
             await refreshMacPower()
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.task(id: "\(isForeground)-\(store.supportsMacPowerControl)") {
guard isForeground, store.supportsMacPowerControl else { return }
await refreshMacPower()
}
.task(id: [isForeground, store.supportsMacPowerControl]) {
guard isForeground, store.supportsMacPowerControl else { return }
await refreshMacPower()
}
🤖 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/MacComputerDetailView.swift`
around lines 120 - 123, Update the .task modifier in MacComputerDetailView to
use an Equatable array containing isForeground and store.supportsMacPowerControl
as its ID instead of interpolating them into a string. Preserve the existing
guard and refreshMacPower behavior and the same invalidation semantics.

Comment on lines +541 to +558
private func runPowerStatusAction(
_ action: @escaping @MainActor () async throws -> MobileMacPowerStatus
) {
guard !isMacPowerBusy else { return }
isMacPowerBusy = true
macPowerError = nil
Task {
defer { isMacPowerBusy = false }
do {
let status = try await action()
macPowerStatus = status
} catch {
macPowerError = L10n.string("mobile.macPower.error", defaultValue: "Couldn't update Mac power controls.")
await refreshMacPower()
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent refreshMacPower() from wiping out the mutation error message.

If the backend mutation (e.g., setMacLowPowerMode) fails, the catch block correctly sets macPowerError to notify the user. However, await refreshMacPower() is called immediately afterward. Because refreshMacPower() sets macPowerError = nil upon a successful status fetch, the error message meant for the user is instantly wiped out.

Swap the order so the UI resynchronizes the toggle status before you assign the failure message for the mutation.

🐛 Proposed fix
     private func runPowerStatusAction(
         _ action: `@escaping` `@MainActor` () async throws -> MobileMacPowerStatus
     ) {
         guard !isMacPowerBusy else { return }
         isMacPowerBusy = true
         macPowerError = nil
         Task {
             defer { isMacPowerBusy = false }
             do {
                 let status = try await action()
                 macPowerStatus = status
             } catch {
-                macPowerError = L10n.string("mobile.macPower.error", defaultValue: "Couldn't update Mac power controls.")
                 await refreshMacPower()
+                macPowerError = L10n.string("mobile.macPower.error", defaultValue: "Couldn't update Mac power controls.")
             }
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private func runPowerStatusAction(
_ action: @escaping @MainActor () async throws -> MobileMacPowerStatus
) {
guard !isMacPowerBusy else { return }
isMacPowerBusy = true
macPowerError = nil
Task {
defer { isMacPowerBusy = false }
do {
let status = try await action()
macPowerStatus = status
} catch {
macPowerError = L10n.string("mobile.macPower.error", defaultValue: "Couldn't update Mac power controls.")
await refreshMacPower()
}
}
}
private func runPowerStatusAction(
_ action: `@escaping` `@MainActor` () async throws -> MobileMacPowerStatus
) {
guard !isMacPowerBusy else { return }
isMacPowerBusy = true
macPowerError = nil
Task {
defer { isMacPowerBusy = false }
do {
let status = try await action()
macPowerStatus = status
} catch {
await refreshMacPower()
macPowerError = L10n.string("mobile.macPower.error", defaultValue: "Couldn't update Mac power controls.")
}
}
}
🤖 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/MacComputerDetailView.swift`
around lines 541 - 558, Update the catch block in runPowerStatusAction to await
refreshMacPower() before assigning macPowerError, so the status refresh cannot
clear the mutation failure message. Preserve the existing localized error text
and refresh behavior.

Comment on lines +1094 to +1096
nonisolated private static func isPrivilegedMacPowerMethod(_ method: String) -> Bool {
method == "mac.power.status" || method.hasPrefix("mac.power.")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant exact-match check.

The method == "mac.power.status" check is redundant because "mac.power.status".hasPrefix("mac.power.") will already evaluate to true.

💡 Proposed refactor
-    nonisolated private static func isPrivilegedMacPowerMethod(_ method: String) -> Bool {
-        method == "mac.power.status" || method.hasPrefix("mac.power.")
-    }
+    nonisolated private static func isPrivilegedMacPowerMethod(_ method: String) -> Bool {
+        method.hasPrefix("mac.power.")
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
nonisolated private static func isPrivilegedMacPowerMethod(_ method: String) -> Bool {
method == "mac.power.status" || method.hasPrefix("mac.power.")
}
nonisolated private static func isPrivilegedMacPowerMethod(_ method: String) -> Bool {
method.hasPrefix("mac.power.")
}
🤖 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 `@Sources/Mobile/MobileHostService.swift` around lines 1094 - 1096, Update
isPrivilegedMacPowerMethod to remove the redundant exact-match comparison and
rely solely on method.hasPrefix("mac.power.").

Comment on lines +8 to +13
private var assertionID = IOPMAssertionID(0)
var isEnabled: Bool { assertionID != 0 }

deinit {
if assertionID != 0 { IOPMAssertionRelease(assertionID) }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fix Swift 6 strict concurrency violation in deinit.

In Swift 6, deinit for actor-isolated types is evaluated as nonisolated because it can theoretically execute on any thread when the last reference drops. Accessing the @MainActor-isolated var assertionID inside deinit will trigger a strict concurrency compiler error.

Since assertionID is a simple primitive that is only safely mutated on the main actor and deinit runs exclusively after all other references are gone, you can resolve the isolation error by marking the property nonisolated(unsafe).

🛠 Proposed fix
-    private var assertionID = IOPMAssertionID(0)
+    private nonisolated(unsafe) var assertionID = IOPMAssertionID(0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private var assertionID = IOPMAssertionID(0)
var isEnabled: Bool { assertionID != 0 }
deinit {
if assertionID != 0 { IOPMAssertionRelease(assertionID) }
}
private nonisolated(unsafe) var assertionID = IOPMAssertionID(0)
var isEnabled: Bool { assertionID != 0 }
deinit {
if assertionID != 0 { IOPMAssertionRelease(assertionID) }
}
🤖 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 `@Sources/PowerAssertionHolder.swift` around lines 8 - 13, Mark the assertionID
property in PowerAssertionHolder as nonisolated(unsafe) so deinit can access it
without violating Swift 6 strict concurrency isolation. Keep its existing type,
initialization, and usage unchanged.

@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 (1)
Sources/TerminalController.swift (1)

351-366: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a default parameter over a convenience initializer.

Since all other parameters in the designated initializer already have default values, you can remove the convenience init and provide a default value for macPowerService directly. This aligns with idiomatic Swift and simplifies the initializer structure.

♻️ Proposed refactor
-    private convenience init() {
-        self.init(macPowerService: MacPowerService())
-    }
-
     private init(
         passwordStore: SocketControlPasswordStore = SocketControlPasswordStore(),
         transport: SocketTransport = SocketTransport(),
         listenerPolicy: SocketListenerPolicy = SocketListenerPolicy(),
         socketClientPreauthorizationLimiter: SocketClientPreauthorizationLimiter = .init(
             maximumConcurrentClaims: 32
         ),
         remoteProxyBroker: any RemoteProxyBrokering = RemoteProxyBroker(
             tunnelProvider: RemoteDaemonProxyTunnelProvider(strings: .appLocalized, ptyBridgeStrings: AppRemotePTYBridgeStrings())
         ),
-        macPowerService: MacPowerService
+        macPowerService: MacPowerService = MacPowerService()
     ) {
🤖 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 `@Sources/TerminalController.swift` around lines 351 - 366, Remove the
parameterless convenience initializer and give the designated TerminalController
initializer’s macPowerService parameter a MacPowerService default value.
Preserve the existing dependency defaults and initialization behavior while
simplifying construction through the designated init.
🤖 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 `@Sources/TerminalController.swift`:
- Around line 351-366: Remove the parameterless convenience initializer and give
the designated TerminalController initializer’s macPowerService parameter a
MacPowerService default value. Preserve the existing dependency defaults and
initialization behavior while simplifying construction through the designated
init.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ba903d1f-f1a5-47cf-9792-b8e07fe8e9ce

📥 Commits

Reviewing files that changed from the base of the PR and between 73dc66c and 877012b.

📒 Files selected for processing (2)
  • Sources/MacPowerService.swift
  • Sources/TerminalController.swift

This branch was successfully deployed

1 active deployment
Preview – cmux — 877012b0 Deployed Jul 15, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cmux mobile should control Mac sleep and caffeinate mode

2 participants