cmux-next: a team switch keeps the new team's Cloud workspaces; B7b review follow-ups

Merged
LA
7d ago
+220-36

Summary

Follow-ups from the review of #28358 (P-AUSTIN B7b, SHIP comment). #28358 has merged; the base is feat-cmux-next.

  • Finding 1: a team switch no longer prunes the new team's Cloud workspaces. isDead counted a Cloud machine that is not in the store's list as gone once hasLoadedMachines was true. During a team switch the user stays signed in, and the store keeps the previous team's list until the new team's list lands (CloudService.hasLoadedCurrentMachines). A saved workspace on a new-team machine was pruned from that old list. The rule now reads hasLoadedCurrentMachines: the list of the user and team signed in now.
  • Finding 4: the "machine left the account" path has a test through the real isDead call site. A small seam (WindowManager.cloudList, which reads CloudService.listFacts in the app) lets a test give a signed-in account with a loaded list. Tests cannot sign in. This seam is a named exception to REVIEW.md section 8 (no test seams in production source); #28398 (P-AUSTIN B7e) removes it with a fake signed-in CloudService for app tests. The new tests show that a deleted machine's workspace leaves the saved window, and that a listed machine's workspace stays with its machine. If the account-list rule (now CloudMachineListFacts.accountListLoaded) always returned false, or if the two arguments at the call site were swapped, these tests fail.
  • Review of this PR, finding 2: cloudSettled reads the current team's list too. During a team switch, a saved workspace this run never saw (or a forgotten SSH machine's or server's) read as gone before the new team's machines were listed. cloudSettled now reads hasLoadedCurrentMachines, as accountMachineListLoaded does. Review finding 1 (the list landing is no observation edge, R2) needs a test that can change real CloudService state: it is in #28398.
  • Finding 3: one source of truth for the SSH and server id forms. SSHHost.machineIDPrefix and ServerReach.machineIDPrefix build their machineID, and MachineRegistry.isCloudMachineID reads the same constants. No behavior change; sshAndServerMachineIDsAreNeverCloudIDs checks ids made by those types.

Finding 2 (not in this PR): design

An account or team switch still prunes the other account's Cloud workspaces: after the list of team or user B loads, the machines of A are not in it, and signing in to A again does not bring them back. This is older than #28358. Design:

  1. Save the account with each remote workspace's machine: WindowRecord.workspaceMachines gets a companion map workspace_accounts (workspace key to a hash of user id and team id, never the raw ids), written from a seenAccount map that reconcileMembership fills next to seenMachine.
  2. isDead counts a Cloud machine as "left the account" only when the loaded list is for the account saved with the workspace. A workspace of another account is kept and shows as disconnected ("signed in to another account").
  3. An older record without an account keeps today's rule.

It changes the saved window record (a new persisted field) and the display, so it is a named follow-up, not part of this PR: P-AUSTIN B7d. The display PR (stacked next) uses the same field for the space of each kept workspace if it lands first.

Owner of the area: PL-switch (window membership, plans/cmux-next/OWNERS.md "Workspace, tab and space switch"). PL-switch: this changes WindowManager+Membership.swift (isDead's account rule) only.

Testing

Commits, in this order:

  1. 975b0b42b44 refactor: the seam, no behavior change.
  2. bd35653a676 tests (red): finding 4 (green: coverage), finding 1 (red), finding 3 (green: guards the refactor).
  3. 840bb0ab51a fix: finding 1.
  4. 98caa2b7e29 refactor: finding 3.
  5. d6932541d92 correction of the test setup of aMachineDeletedFromTheAccountTakesItsWorkspaceOutOfTheSavedWindow. Reason: its second workspace was in no tree, so isDead correctly read it as gone too (WindowManager+Membership.swift:439), and WindowStateDocument.prune drops a window with no workspace (WindowStateDocument.swift:91). Its kept workspace is now on a machine the account's list has. Test-only.
  6. 1998b6d219e red: aNeverSeenWorkspaceWaitsForTheNewTeamsList (review finding 2).
  7. b3670f4635a fix: cloudSettled reads hasLoadedCurrentMachines.

Focused command: scripts/ci/package-test-lane.sh suite Packages/macOS/CmuxNext CloudWorkspaceAccountListTests,CloudWorkspaceRestoreTests (fleet cmux-ci run).

  • Red 5d0392fddeeef0d9ec5ee11a at bd35653a676: aTeamSwitchDoesNotPruneTheNewTeamsWorkspaceFromTheOldTeamsList fails on its assertion (CloudWorkspaceAccountListTests.swift:64, isDead true). It also shows the setup defect that commit 5 corrects. No crash, no compile error.
  • Compile-check 6de8c4e8ba7dcbafc81d483c at 98caa2b7e29 (app): passed. Commit 5 changes a test only.
  • Green 3e9093c4f937e414dc0b977c at d6932541d92: both suites pass (aws-m4pro-6).
  • After the review (finding 2): compile-check, red at 1998b6d219e and green at b3670f4635a: job ids follow in a comment.

After merges of feat-cmux-next (the app-size ratchet became strict on 2026-10-30):
8. 9f937401f6a refactor: the rule moves out of CmuxNextApp into CmuxNextCloud as CloudMachineListFacts (accountListLoaded, machineLeft(listed:), settled(machinesLoaded:)); the App's remoteMachineLeft and accountMachineListLoaded are gone. Compile 92fc5fda95ea9cd3402e6c78 passed; tests 0ef90bf7d8ed762fa9a0f1eb passed.
9. db69d8ee3d1 red (review of 9f937401f6a): the current-account decision was still in CloudService.listFacts, which no test ran. thePreviousTeamsStoredListIsNotTheCurrentAccountsList builds the facts from the raw list state (hasLoadedMachines, loadedFor, account); the new init still ignores whom the list is for.
10. 6b0bb4f01f9 fix: CloudMachineListFacts decides hasLoadedMachines && loadedFor == account; CloudService.hasLoadedCurrentMachines and listFacts both read it.
11. 8b69ca5bdb3 doc only: CloudService points at the facts for the rule. Fleet red, green and compile for 9-11: job ids follow in a comment.

Observed / Not run

  • Observed: prepush-gate.py --base c63ead94875 adds no violations; the fleet jobs above.
  • Not run: a live team switch in a signed-in app (tests cannot sign in; the seam gives the account state). Austin's-day numbers: no team switch in the scenario.

Changelog

Fixed: Switching your cmux Cloud team no longer removes the new team's Cloud workspaces from your windows before its machine list loads.

Proof

No capture: this changes which workspaces the saved window state keeps; nothing draws differently.