cmux NEXT spaces: Move Space to This Window and Merge All Windows (carries #27327)

Merged
TE
6d ago
+1374-32

Carries #27327 (its commits unchanged) merged with current feat-cmux-next, plus one repair of #27327's own red. #27327's branch is not a lane/ branch, so this lander can't push to it; #27327 closes once this lands.

The red: cmux-next checks → "Pushed commits add no violations" (the pre-push gate on the PR) refused two guardrails #27327 added:

  • action-wait: registry.bind("window.mergeAll", run:) was an untyped handler. It now binds with the throwing bindEffect(_:requires:effect:), which refuses a thrown ActionFailure exactly as run: did and returns .done.
  • app-size: Windows/WindowManager+Gather.swift was a new CmuxNextApp file. Its extension moves unchanged into WindowManager+Profiles.swift (moving a space is a profile operation); the rule only warns about growth of an existing feature file.

python3 scripts/cmux-next/prepush-gate.py now reports "no new violations". plans/cmux-next/actions.md conflicted in the base merge; it is a generated export, so the PR side was taken and the autofix regenerates it.

#27327's description

Leo (2026-10-29): "could u guys also now work on the per space pins and groups? rn anything u make in teh default space goes across to new ones... there's also other ergo wrt new windows being like new spaces, incognito spaces, etc... this could legit make our thing more useful than chrome by having windows be switchable in this way. think of the potential perf savings~"

This PR covers the windows part of that ask. Per-space pins and groups are not in it.

What changed

  • space.bringHere "Move Space to This Window": the reverse of Open Space in New Window. Every workspace of the space that another normal window holds (open or closed) moves into the acting window. That window then shows the space and selects the workspace the space last showed (the one a window showing the space had selected, else the one remembered for the space). If no other window holds any of the space's workspaces, it is a plain space switch. Workspaces only change owner, so terminals keep running and nothing restarts. A source window left with no workspace closes through the existing WindowRegistry.move / closeEmptied path. A source window that still holds other spaces' workspaces stays open. Moving an incognito space, or moving into an incognito window, is refused with the existing RefusalStrings.incognitoMismatch HUD. Surfaces: the palette, the space's right-click menu ("Move to This Window", next to Open in New Window), cmux space bring-here, and MCP (spaces-overhaul-DESIGN.md rule 7).
  • window.mergeAll "Merge All Windows": every other open normal window's workspaces move into the acting window, and each workspace keeps its space. The other windows close. The acting window stays on its space with the same selection. For spaces it never showed, it takes each merged window's last shown workspace, so switching there shows what that window showed. Incognito windows are left alone, and running it from an incognito window is refused. Surfaces: the Window menu (after New Incognito Window), the palette, cmux app merge-all-windows, and MCP. It has a context-menu exemption, as Close Other Windows does.
  • Titles are in ProfileActions.xcstrings and ActionCatalog.xcstrings (en and ja translated; the other locales are needs_review, as in neighbouring entries).
  • plans/cmux-next/action-surfaces.json and actions.md are left to the base autofix (regenerate-action-exports.sh).

Risk

  • Additive. No existing default, component or menu row is changed or removed.
  • The Window menu gains one row, so MainMenuPlacementTests now expects 10 Window rows instead of 9.
  • Both actions run one registry transition (transition { $0.move(...) }), the same path switchProfile adoption and Move to Window use. Incognito windows are filtered out of the sources before the move, so crossesIncognito never drops the batch.

Tests

  • New GatherWindowsTests (.closesItsWindows):
    • Bring-here closes the window it emptied and keeps the space's last shown workspace selected.
    • Only that space's workspaces move, and a source window that keeps other spaces stays open.
    • Incognito space or window is refused and nothing changes.
    • Merge-all keeps each workspace's space, closes the merged windows and leaves the incognito window alone.
  • SpaceSurfacesTests names space.bringHere and checks it is in the space menu.
  • Focused fleet Mac run (package-test-lane.sh suite GatherWindowsTests, SpaceSurfacesTests, MainMenuPlacementTests, ActionSurfaceParityTests, ShowWorkspaceOtherRoomTests, IncognitoSpaceTests): all 6 suites passed (ActionSurfaceParityTests: 2 export-freshness tests skipped as usual). cmux-ci step 0ca0e2da7b669522ed80b23f, at 62fed54c5b9f: this head (69b56f9016ae) with #27301 merged in, on a CI-only branch. At this head alone, CmuxNextAppTests does not compile because of the base red from #27166 (BrowserEntryRetirementTests.swift:43:57, "returning main actor-isolated ... as a 'sending' result"; step c559ae7de547b95c91d859b5). This PR does not touch that file, and #27301 fixes it.

No capture: the only visible change is new rows in the Window menu, the space's right-click menu and the command palette, and the main window is unchanged. A fleet capture (steps-6d58e8bbea at 59c45ae21f5a: open the palette, type "merge all", then "to this window") keeps one still of the main window, and the palette panel and menus draw in their own windows outside it, so the still shows nothing new. SpaceSurfacesTests and MainMenuPlacementTests pin the new rows instead.

Performance

No perf numbers: the word "perf" here is Leo's quote ("think of the potential perf savings"). This PR adds two window actions and changes no hot path, timer or observer; it measures nothing.

Agent session: agent session

šŸ¤– Generated with Claude Code


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


Summary by cubic

Carries #27327 onto feat-cmux-next, adding two cmux NEXT window actions — Move Space to This Window (space.bringHere) and Merge All Windows (window.mergeAll) — while repairing #27327's guardrail violations: window.mergeAll binds as a throwing effect and the gathering code moved into WindowManager+Profiles.swift.

What changed

  • Move Space to This Window pulls every workspace of the space held by other normal windows into the acting window, which shows the space at the workspace an open window last selected; a source window left with no workspace closes and terminals keep running. Without focus permission the workspaces still move but the window keeps its own space and selection.
  • Workspaces whose space no session reports yet stay in their window instead of moving with the space.
  • Merge All Windows pulls every other normal window's workspaces into the acting window, each keeping its space, and those windows close.
  • Both are refused when they would close a window still creating another — a pending claim, sidebar placement, queued create, or provisional row — and nothing changes until that create finishes; a claim on a workspace the window already holds is not a create.
  • A window's own Home, and the store's Home, stay with their window, including when Move Space's fallback is a plain space switch, which also leaves other windows' Homes alone; moves into or out of a window keeping its own browser tabs, or bringing a pane whose id collides with this window's browser tabs, are refused. Kept tabs never block a move that moves nothing.
  • Acting on a window not yet registered (the launch window, still restoring) is refused.
  • Both surface in the palette, the space right-click/Window menus, the CLI (cmux space bring-here, cmux app merge-all-windows), and MCP.
  • A window known incognito only by its space is ruled out as a source: its workspaces stay and it stays open, alongside the refused (Move) or left-alone (Merge) incognito cases.
  • Additive: the Window menu gains one row; no existing default, menu, or component changes. The action exports and defaults schema are regenerated.
  • New GatherWindowsTests covers the moves, refusals, and the incognito, incognito-by-space, unresolved-workspace, in-flight-create, Home, browser-tab, pane-id-collision, no-op, and unregistered-window cases over the real registry transition.