Repository navigation
cmux NEXT: New Cloud Space, a space that is one Cloud machine - #27810
austinywang wants to merge 16 commits into
Conversation
A Cloud space follows one Cloud machine's session and makes it the space's default session, in one create-profile command. Switching to it shows the machine's own workspaces, terminals and browser tabs; New Workspace, Cmd-N, an empty space's first workspace, the CLI and the palette create on the machine (startWorkspace resolves the space's default session). A paused machine resumes on that create, and the workspace is made once it connects, never on the Mac instead. The session resolves through the live daemon, else the home registry's transport record, so a paused machine still resolves after a relaunch. Entry points: palette, File > New, a Cloud machine's right-click menu, `cmux space new-cloud` and MCP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nc in an autoclosure) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ace shows its workspaces; an unknown machine refuses Review findings: - A create carrying this Mac's folder, command, chat or session (Finder's New Workspace Here, cmux open <dir>, a resumed or imported chat) stays on the Mac. - Cmd-N decides its page and key hold on the daemon the space creates on. - Entering a space whose machine has not loaded waits for its tree and adopts the workspaces it already has; only an empty machine gets a new one. - A space naming a session no listed machine serves refuses the create instead of pinning a Mac workspace into it (MachineRegistry.SpaceCreation). - The session resolves through the shared transport reader (any machine kind); the space follows the identity's lowercased session id; a machine the person disconnected fails at once instead of waiting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review (correctness subagent) at 3e95edb, then fixed in eefffde:
|
|
Found 8 test failures on Blacksmith runners: Failures
|
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
red: cmux-next checks (god files, concurrency, crash safety, l10n); NEW: Default changesThis pull request changes 9 defaults at
Needs a review. It passes with an independent review receipt on this commit: a comment or review whose first line is Regenerate the snapshot with |
…les and scripts stay on the Mac Re-review findings: - Switching into a space whose machine is not listed is refused; the window keeps its space (regression test: switchingIntoASpaceWhoseMachineIsUnknown...). - A wake or load that fails returns the window to the space it came from, and says why only while the window still shows the Cloud space. - First launch's workspace, a file opened from this Mac, a browser-profile create and a script's create without --session stay on this Mac. - A resume failure from an earlier attempt no longer ends a new wait at once. - The space follows the tree's registry_id, as membership qualifies it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Third review: the switch back to the previous space could start that space's own wait (another unloaded Cloud space) and loop between the two. fallBack goes to the previous space only when the window shows something there now, else to default, else to default with a new workspace on this Mac. Regression test: aFailedCloudSpaceFallsBackToDefaultNotIntoAnotherWait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re-review (fresh correctness subagent) at eefffde: FIX. Resolved in 6512ca0:
Third review (fresh subagent) at 6512ca0: FIX. Resolved in 5aa55b4:
|
…a closed window before making one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Receipts for head
|
…its chats on the machine A Cloud machine whose daemon starts and attaches chats (agent-session-start-v1, agent-session-attach-v1) now opens the New Tab page first, as this Mac does, instead of a terminal. Its first page is built for that machine (its folders, no Mac home or projects, not warmed in this Mac's acpmux), and its tab is a registry: record, so the first send starts the chat in the machine's acpmux (the same path Cmd-T in a remote pane already takes). A machine without those capabilities keeps the terminal. Cmd-N holds typed keys only for a connected machine, never through a resume. Tests: aCloudWorkspacesFirstPageShowsTheMachineNotThisMac, aCloudMachineThatCannotStartChatsOpensATerminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Cloud space gets no icon: it shows its color dot like every space. The home daemon makes default follow each session it first records, so the machine's workspaces also showed in Default; New Cloud Space now takes the machine's session out of default's follows (also for an existing Cloud space). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # Packages/macOS/CmuxNext/Sources/CmuxNextApp/AgentTabs+Wiring.swift # Packages/macOS/CmuxNext/Sources/CmuxNextApp/NewTabPage.swift
|
Sweep fix: merge conflict with |
|
Conflict wave: I resolved the merge with feat-cmux-next 7f4c8b4 locally, but I did not push it. The prepush arch ratchet refuses it: Question for the author: where should the New Cloud Space code live? The answer is one of these two: |
…ext-cloud-space Kept both sides: space.bringHere (feat) and space.newCloud (this PR) in the exemptions; feat's NewTabPage paletteActions: false with this PR's machine; feat's sourceWindow key hold (the window the create came from) with this PR's rule that only a connected space machine holds the keys; this PR's Cloud space routing in startWorkspace, without followClassicMode, which feat removed with Classic Mode (74d73a3).
…, never made on this Mac (red) Review of the merge bf39da2 found this in the PR's routing: with no space and a non-person origin, an unresolved `machine` fell back to this Mac's connected daemon and passed the not-connected check, so `cmux new-workspace --session <bad> --command ...` ran the command on the person's Mac. A fake local daemon (with profiles-v1, so the old path pins and creates) records the commands it gets; the test awaits the handler's captured work, then expects the machineNotConnected refusal and no create-workspace. Red here.
…ever made on this Mac (fail closed) The not-connected check reads the daemon the request named (`asked`), not the fallback daemon a script's create without a machine goes to.
…ace (owner austinywang, expiry 2026-11-08) Lead decision 2026-11-01 07:30Z. Receipt: the independent review of the code head 5d39eb0. Austin moves the cloud-space and window-space logic into packages before the expiry.
|
@austinywang, the lead decided at 07:30Z: this PR lands with a reviewed arch-ratchet What I pushed (the work and the credit stay yours):
Still open from the review (non-blocking, for your move): a Cmd-N while a Cloud space's machine wakes can make two workspaces (WindowManager+Profiles.swift:80), and the doc references to |
…ions and requires that New Workspace ran
The test's services never bound AppActions, so perform("newTab") returned false and captured no work on
both the red and the green (CloudSpaceTests.swift:206, work.count 0). It now binds them, requires the run,
and has a one-minute limit so the red's create on this Mac cannot hang the suite.
There was a problem hiding this comment.
10 issues found across 26 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextActions/ActionSurfaceCatalog+Exemptions.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextActions/ActionSurfaceCatalog+Exemptions.swift:53">
P2: This adds `space.newCloud` to `cliNamed`, which changes the CLI surface, but `plans/cmux-next/action-surfaces.json` was not regenerated. The export check in `ActionSurfaceParityTests.exportIsFresh` will fail in CI with "action-surfaces.json is stale". Regenerate with `CMUX_UPDATE_ACTION_SURFACES=1 swift test --filter ActionSurfaceParityTests`, and refresh `web/data/next-docs/cli-reference.generated.json` as well if the docs generator requires it.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Handlers/RoomHandlers.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Handlers/RoomHandlers.swift:372">
P2: Concurrent `space.newCloud` runs can create duplicate spaces for one machine because both can pass this lookup before either profile is mirrored. Serialize creation per session or make the daemon request idempotent by session.</violation>
<violation number="2" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Handlers/RoomHandlers.swift:388">
P1: This whole-list write is built from the app mirror, which can lag the daemon. Sessions the daemon added to `default` since the last `personal-changed` would be dropped. Fetch `listPersonal()` from the daemon first, as `RoomDefaultsUpdates` does, and subtract from that list.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Onboarding/ExternalOpenController.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Onboarding/ExternalOpenController.swift:151">
P2: The new comment says only files are Mac-bound, but `createWorkspaceForPending` runs for every route `perform` handles: browser tabs, ssh, Handoff, and deep links with no focused pane. This line now pins all of them to the Mac, even when the window shows a Cloud space, and that is not in the PR description. Either reword the comment to cover every external open, or pin only the file route if that is the intent.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/WorkspaceSpawn.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/WorkspaceSpawn.swift:26">
P3: The `.unresolved` refusal reuses the 'not connected yet, wait or resume' message, which misleads when the session is missing from the fleet (signed out or machine deleted). Use a dedicated message for an unresolved space.</violation>
<violation number="2" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/WorkspaceSpawn.swift:50">
P2: When no window profile resolves, `spaceCreation` returns `.mac` while `WorkspaceSpawner` still births the workspace in `.defaultProfile`. Fall back to `.defaultProfile` here so both use the same room and the Cloud default session is honored.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/MachineRegistry.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/MachineRegistry.swift:268">
P2: A profile missing from the local store makes `creation(in:)` return `.mac`, so a create in a Cloud space lands on this Mac. The PR says unresolved sessions must be refused, not sent to the Mac. Return `.unresolved` when the profile is absent, and keep `.mac` only for a profile that exists with no default session.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudMachineSession.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudMachineSession.swift:555">
P2: A disconnect during this wait leaves `connectedForSpace()` blocked for the full 180-second timeout because the condition never observes `disconnected`. Include the terminal disconnect state in the wait and reject it after the wait returns.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Windows/WindowManager+Profiles.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Windows/WindowManager+Profiles.swift:74">
P2: This continuation can mutate a window after the user closes it. Require the window to still be registered and open before adopting workspaces or creating one.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/AppActions+Workspaces.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/AppActions+Workspaces.swift:166">
P3: For an unresolved space, `spaceDaemon` falls back to `services.daemon`, so the New Tab page check and the key hold use the Mac even though this create is refused. The comment above says these are "that daemon's", which does not hold in this case. Use an optional `spaceDaemon` and skip the page and key decision when the space resolves to nothing.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| // workspaces would show on the Mac's space too: the machine is the Cloud space's alone. | ||
| let defaultFollows = local.store.personal.follows[.defaultProfile] ?? [] | ||
| if let followed = defaultFollows.first(where: { $0.lowercased() == session.lowercased() }) { | ||
| _ = try await connection.request(SetProfileFollowsRequest(profile: .defaultProfile, |
There was a problem hiding this comment.
P1: This whole-list write is built from the app mirror, which can lag the daemon. Sessions the daemon added to default since the last personal-changed would be dropped. Fetch listPersonal() from the daemon first, as RoomDefaultsUpdates does, and subtract from that list.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/Handlers/RoomHandlers.swift, line 388:
<comment>This whole-list write is built from the app mirror, which can lag the daemon. Sessions the daemon added to `default` since the last `personal-changed` would be dropped. Fetch `listPersonal()` from the daemon first, as `RoomDefaultsUpdates` does, and subtract from that list.</comment>
<file context>
@@ -315,3 +316,78 @@ final class RoomDefaultsUpdates {
+ // workspaces would show on the Mac's space too: the machine is the Cloud space's alone.
+ let defaultFollows = local.store.personal.follows[.defaultProfile] ?? []
+ if let followed = defaultFollows.first(where: { $0.lowercased() == session.lowercased() }) {
+ _ = try await connection.request(SetProfileFollowsRequest(profile: .defaultProfile,
+ sessionIDs: defaultFollows.subtracting([followed]).sorted()))
+ }
</file context>
| "workspaceGroup.moveToSpace", "space.setTheme", "space.clearTheme", | ||
| // Every space action is a verb (Lawrence 14223: no exceptions): the space keys and the color family too. | ||
| "space.next", "space.previous", "space.selectByNumber", "space.openInNewWindow", "space.bringHere", "space.newIncognito", "space.color.grey", "space.color.blue", | ||
| "space.next", "space.previous", "space.selectByNumber", "space.openInNewWindow", "space.bringHere", "space.newIncognito", "space.newCloud", "space.color.grey", "space.color.blue", |
There was a problem hiding this comment.
P2: This adds space.newCloud to cliNamed, which changes the CLI surface, but plans/cmux-next/action-surfaces.json was not regenerated. The export check in ActionSurfaceParityTests.exportIsFresh will fail in CI with "action-surfaces.json is stale". Regenerate with CMUX_UPDATE_ACTION_SURFACES=1 swift test --filter ActionSurfaceParityTests, and refresh web/data/next-docs/cli-reference.generated.json as well if the docs generator requires it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextActions/ActionSurfaceCatalog+Exemptions.swift, line 53:
<comment>This adds `space.newCloud` to `cliNamed`, which changes the CLI surface, but `plans/cmux-next/action-surfaces.json` was not regenerated. The export check in `ActionSurfaceParityTests.exportIsFresh` will fail in CI with "action-surfaces.json is stale". Regenerate with `CMUX_UPDATE_ACTION_SURFACES=1 swift test --filter ActionSurfaceParityTests`, and refresh `web/data/next-docs/cli-reference.generated.json` as well if the docs generator requires it.</comment>
<file context>
@@ -50,7 +50,7 @@ nonisolated extension ActionSurfaceCatalog {
"workspaceGroup.moveToSpace", "space.setTheme", "space.clearTheme",
// Every space action is a verb (Lawrence 14223: no exceptions): the space keys and the color family too.
- "space.next", "space.previous", "space.selectByNumber", "space.openInNewWindow", "space.bringHere", "space.newIncognito", "space.color.grey", "space.color.blue",
+ "space.next", "space.previous", "space.selectByNumber", "space.openInNewWindow", "space.bringHere", "space.newIncognito", "space.newCloud", "space.color.grey", "space.color.blue",
"space.color.red", "space.color.yellow", "space.color.green", "space.color.pink", "space.color.purple",
"space.color.cyan", "space.color.orange",
</file context>
| Task { [weak self] in | ||
| do { | ||
| _ = try await windows.createWorkspace(WorkspaceSpawn(), into: target) | ||
| // The file was opened from this Mac: its workspace is this Mac's, whatever space the window shows. |
There was a problem hiding this comment.
P2: The new comment says only files are Mac-bound, but createWorkspaceForPending runs for every route perform handles: browser tabs, ssh, Handoff, and deep links with no focused pane. This line now pins all of them to the Mac, even when the window shows a Cloud space, and that is not in the PR description. Either reword the comment to cover every external open, or pin only the file route if that is the intent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/Onboarding/ExternalOpenController.swift, line 151:
<comment>The new comment says only files are Mac-bound, but `createWorkspaceForPending` runs for every route `perform` handles: browser tabs, ssh, Handoff, and deep links with no focused pane. This line now pins all of them to the Mac, even when the window shows a Cloud space, and that is not in the PR description. Either reword the comment to cover every external open, or pin only the file route if that is the intent.</comment>
<file context>
@@ -148,7 +148,8 @@ final class ExternalOpenController {
Task { [weak self] in
do {
- _ = try await windows.createWorkspace(WorkspaceSpawn(), into: target)
+ // The file was opened from this Mac: its workspace is this Mac's, whatever space the window shows.
+ _ = try await windows.createWorkspace(WorkspaceSpawn(), on: windows.services.daemon, into: target)
} catch {
</file context>
| // The file was opened from this Mac: its workspace is this Mac's, whatever space the window shows. | |
| // Opened from this Mac (a file, link or tab from another app): its workspace is this Mac's, whatever space the window shows. |
| guard let profile = spawn.profile ?? windowID.flatMap({ states[$0]?.profileID }) ?? active?.state.profileID else { return .mac } | ||
| return services.machines.creation(in: profile) |
There was a problem hiding this comment.
P2: When no window profile resolves, spaceCreation returns .mac while WorkspaceSpawner still births the workspace in .defaultProfile. Fall back to .defaultProfile here so both use the same room and the Cloud default session is honored.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/WorkspaceSpawn.swift, line 50:
<comment>When no window profile resolves, `spaceCreation` returns `.mac` while `WorkspaceSpawner` still births the workspace in `.defaultProfile`. Fall back to `.defaultProfile` here so both use the same room and the Cloud default session is honored.</comment>
<file context>
@@ -19,7 +20,35 @@ extension WindowManager {
+ guard spawn.cwd == nil, spawn.command == nil, spawn.firstChat == nil, spawn.firstSession == nil, spawn.browserProfile == nil else {
+ return .mac
+ }
+ guard let profile = spawn.profile ?? windowID.flatMap({ states[$0]?.profileID }) ?? active?.state.profileID else { return .mac }
+ return services.machines.creation(in: profile)
}
</file context>
| guard let profile = spawn.profile ?? windowID.flatMap({ states[$0]?.profileID }) ?? active?.state.profileID else { return .mac } | |
| return services.machines.creation(in: profile) | |
| let profile = spawn.profile ?? windowID.flatMap({ states[$0]?.profileID }) ?? active?.state.profileID ?? .defaultProfile | |
| return services.machines.creation(in: profile) |
| self?.resumeFailure != nil || daemon.connection != nil | ||
| }) else { throw DaemonError.notConnected } |
There was a problem hiding this comment.
P2: A disconnect during this wait leaves connectedForSpace() blocked for the full 180-second timeout because the condition never observes disconnected. Include the terminal disconnect state in the wait and reject it after the wait returns.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudMachineSession.swift, line 555:
<comment>A disconnect during this wait leaves `connectedForSpace()` blocked for the full 180-second timeout because the condition never observes `disconnected`. Include the terminal disconnect state in the wait and reject it after the wait returns.</comment>
<file context>
@@ -536,3 +537,23 @@ final class CloudMachineSession {
+ }
+ let daemon = daemon
+ guard await CloudMove.holds(within: .seconds(180), until: { [weak self] in
+ self?.resumeFailure != nil || daemon.connection != nil
+ }) else { throw DaemonError.notConnected }
+ if let failure = resumeFailure { throw ActionFailure(message: failure) }
</file context>
| self?.resumeFailure != nil || daemon.connection != nil | |
| }) else { throw DaemonError.notConnected } | |
| self?.disconnected == true || self?.resumeFailure != nil || daemon.connection != nil | |
| }), !disconnected else { throw ActionFailure(message: CloudStrings.notConnected) } |
| } catch { | ||
| failure = RefusalStrings.describe(error) | ||
| } | ||
| guard let state = states[windowID], state.profileID == profile else { return } |
There was a problem hiding this comment.
P2: This continuation can mutate a window after the user closes it. Require the window to still be registered and open before adopting workspaces or creating one.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/Windows/WindowManager+Profiles.swift, line 74:
<comment>This continuation can mutate a window after the user closes it. Require the window to still be registered and open before adopting workspaces or creating one.</comment>
<file context>
@@ -49,6 +60,48 @@ extension WindowManager {
+ } catch {
+ failure = RefusalStrings.describe(error)
+ }
+ guard let state = states[windowID], state.profileID == profile else { return }
+ if let failure {
+ fallBack(preferring: previous, in: state)
</file context>
| guard let state = states[windowID], state.profileID == profile else { return } | |
| guard let state = states[windowID], registry.value.window(windowID)?.isOpen == true, state.profileID == profile else { return } |
| if daemon == nil { | ||
| switch spaceCreation(spawn, into: windowID) { | ||
| case .mac: break | ||
| case .unresolved: return { throw ActionFailure(message: CloudStrings.notConnected) } |
There was a problem hiding this comment.
P3: The .unresolved refusal reuses the 'not connected yet, wait or resume' message, which misleads when the session is missing from the fleet (signed out or machine deleted). Use a dedicated message for an unresolved space.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/WorkspaceSpawn.swift, line 26:
<comment>The `.unresolved` refusal reuses the 'not connected yet, wait or resume' message, which misleads when the session is missing from the fleet (signed out or machine deleted). Use a dedicated message for an unresolved space.</comment>
<file context>
@@ -19,7 +20,35 @@ extension WindowManager {
+ if daemon == nil {
+ switch spaceCreation(spawn, into: windowID) {
+ case .mac: break
+ case .unresolved: return { throw ActionFailure(message: CloudStrings.notConnected) }
+ case let .machine(space):
+ // A Cloud space creates on its machine. A paused machine or an ended link wakes on this create,
</file context>
| case .unresolved: return { throw ActionFailure(message: CloudStrings.notConnected) } | |
| case .unresolved: return { throw ActionFailure(message: CloudStrings.spaceUnresolved) } |
| } | ||
| // With no machine asked for, the create goes where the space creates (a Cloud space's machine), so the page | ||
| // decision and the key hold below are that daemon's. | ||
| let spaceDaemon = windows.spaceCreation(spawn, into: target).daemon ?? services.daemon |
There was a problem hiding this comment.
P3: For an unresolved space, spaceDaemon falls back to services.daemon, so the New Tab page check and the key hold use the Mac even though this create is refused. The comment above says these are "that daemon's", which does not hold in this case. Use an optional spaceDaemon and skip the page and key decision when the space resolves to nothing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/AppActions+Workspaces.swift, line 166:
<comment>For an unresolved space, `spaceDaemon` falls back to `services.daemon`, so the New Tab page check and the key hold use the Mac even though this create is refused. The comment above says these are "that daemon's", which does not hold in this case. Use an optional `spaceDaemon` and skip the page and key decision when the space resolves to nothing.</comment>
<file context>
@@ -152,17 +152,26 @@ extension AppActions {
}
+ // With no machine asked for, the create goes where the space creates (a Cloud space's machine), so the page
+ // decision and the key hold below are that daemon's.
+ let spaceDaemon = windows.spaceCreation(spawn, into: target).daemon ?? services.daemon
// A person's Cmd-N onto the New Tab page in the window they type in: every key from this
// moment waits for the new page's field, in order (cx-kql5h), not for the workspace to show.
</file context>
|
Review (claude-review-subagent-cw): SHIP at a5a5f98 Correctness first. I read the code; I did not compile or run tests on this machine. The fleet results below are the author's runs.
Non-blocking follow-ups:
|
|
Conflict wave evidence at head a5a5f98:
@austinywang: you own the exception. Before 2026-11-08, please move the cloud-space and window-space logic out of CmuxNextApp into packages: RoomHandlers.bindCloudSpace, WindowManager+Profiles enterLoaded/fallBack/SpaceCreation, the MachineRegistry creation helpers and the WorkspaceSpawn Cloud-space routing. Then remove the exception. Review follow-ups that do not block: a Cmd-N while a Cloud space's machine wakes can make two workspaces (WindowManager+Profiles.swift:80); stale |
…or Austin's package move (merge service rule, cmux-ad 12:25Z)
|
Conflict wave: the merge service held this PR. It adds an app_lines exception (+248) while the tip watcher's grow flag is set, and its body names no merged move PR (cmux-ad 12:25Z). The lead told me to change the PR, not the rule. I looked for a mechanical move of 248+ existing CmuxNextApp lines in the cloud-space / window-space area. None exists. RoomHandlers, WindowManager+Profiles, MachineRegistry and WorkspaceSpawn depend on App-only types (AppActionContext, AppServices, WindowManager, CloudMachineSession), so they need new seams first. RoomStrings is a static namespace enum, which the package namespace lint refuses. So I took the exception out (3f3693f) and removed @austinywang: this PR now waits for your package move. Open a move PR that takes at least 248 lines of the cloud-space / window-space logic out of CmuxNextApp (for example RoomHandlers.bindCloudSpace, WindowManager+Profiles enterLoaded/fallBack/SpaceCreation, the MachineRegistry creation helpers, the WorkspaceSpawn Cloud-space routing), land it, then name it in this PR's body. After that, this PR needs a new review at its head and the label again. The fix, the regression test (red 6c03c3ef, green 83030322) and the compile (ce72737f) at a5a5f98 stay valid for the code; only the ratchet entry changed. |
|
Review (codex gpt-6-astra high, next-review): SHIP at 3f3693f SHIP at 3f3693f Visual verdict: no images; Leo tries the build
Follow-ups: [P1] Await or reconcile session registration before performing the one-time Default follow removal. |
|
Review (codex gpt-6-astra high, next-review): BLOCK at c79ce70 BLOCK at c79ce70 Visual verdict: no images; Leo tries the build
Follow-ups:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
New Cloud Space: a space that is one Cloud machine. Switching to it shows that machine's own workspaces, terminals and browser tabs. Switching back shows the Mac's.
space.newCloud(palette, File > New, a Cloud machine's right-click menu,cmux space new-cloud, MCP) picks the target machine, else the one New Cloud Workspace uses, else a new one. It wakes the machine and creates a space in onecreate-profilecommand, withfollows = [machine session]anddefault_session_id = machine session. The daemon already had both fields; nothing in Swift sent them. Running it again for the same machine reuses that machine's space.RoomMembership, unchanged).startWorkspaceand runs on that daemon. A paused machine resumes on that create, and the workspace is made once it connects.cmux open <dir>, a resumed or imported chat, and a create with a command keep running on the Mac.Browsers in a Cloud space are the existing remote-workspace browser tabs: the page renders on the Mac, and the machine's localhost is proxied over the link. A page that runs inside the VM is still the opt-in "Open on " path.
Follow-ups, not in this PR:
forget-sessioncleanup for a deleted machine.Changelog
Added: New Cloud Space, a space that shows one Cloud machine's workspaces, terminals and browser tabs, and creates new workspaces on that machine.
Testing
New
CloudSpaceTests(CmuxNextAppTests) cover:followsmoves the machine's workspaces out of Default.create-profilesendsdefault_session_idandfollows.Also run:
SpaceSurfacesTests,ActionSurfaceParityTests,ActionSurfaceExportTestsandMachineIconTests.The base (c5af877) does not compile:
JoinConsent+App.swift:222calls the asyncFolderChanges.events()from sync code. #27722 and #27758 fix that, so the suites ran on side branchcloud-space-test-side: this head plus #27722 commit.Observed / Not run
prepush-gate.pypasses (only app-size warnings). The fleet suite results are in the comment below.No capture: the only visible change is one menu and palette row, "New Cloud Space". The behavior needs a signed-in Cloud machine, which the capture minis do not have. A tagged dogfood build follows.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Adds New Cloud Space, a space that shows one Cloud machine's workspaces, terminals, and browser tabs, and creates new workspaces on that machine instead of the Mac.
New Features
space.newCloud(palette, File > New, a Cloud machine's right-click menu,cmux space new-cloud, MCP) picks the target machine, wakes it, and creates the space in onecreate-profilecommand withfollowsanddefault_session_id; running it again reuses the existing space. The machine's session leaves Default's follows, so its workspaces show only in the Cloud space.cmux open <dir>, a resumed or imported chat, a create with a command or browser profile, first launch's workspace, and a script's create without--session— stay on the Mac.Written for commit c79ce70. Summary will update on new commits.