feat: group workspace moves under a "Move to" menu on desktop and mobile

AN
12h ago
+2124-237

Links

  • Issue: SUPER-3362

Summary

Desktop

  • The sidebar workspace context menu and the workspace header ⋯ menu now group teleport actions under one Move to › submenu. It lists real destinations: Cloud ("New cloud workspace"), then each computer the workspace can move to. This computer is titled "This computer", with its host name underneath, which matches the move dialog and toast. Other computers show "Online" or "Offline" under their name.
  • Picking a computer opens the move dialog with that computer already selected.
  • Move to group (single and multi-select menus) now uses a folder-symlink icon, so ⇄ only means a teleport move.
  • The move-to-computer dialog, which explains what a move does, has a Don't ask again checkbox. Once it is ticked, picking a computer from the submenu starts the move at once. Settings → Behavior → Confirm before moving a workspace turns the dialog back on.

Mobile and host-service

  • The mobile workspace row's long-press menu gets the same Move to › submenu, for workspaces on a computer: Cloud, then the other computers on the account, with offline ones disabled. A confirmation alert states what the move does, in the same words as the desktop dialogs.
  • The phone does not run the move. A new host-service procedure, teleport.moveWorkspace, makes the source computer do the whole move itself. The phone asks for it and can close straight after.

Why / Context

The menu had separate top-level items, "Move to cloud" and "Move to another computer" / "Move to a computer". The issue asks for them to sit under one "Move to" submenu. Listing the actual destinations also skips a step: the user picks the computer in the menu instead of in the dialog.

Review asked for the same menu on mobile. A desktop move is run by the desktop renderer: it prepares the workspace, makes the destination, and moves each chat while it stays open. A phone cannot do that work, and it can be closed at any time, so the work moves into host-service on the source computer.

How It Works

Desktop

  • useMoveDestinationComputers(fromHostId) (new, _dashboard/hooks/) builds the destination list. It puts this computer first, then other computers with online ones before offline ones. It leaves out the source host and cloud boxes. MoveWorkspaceToComputerDialog used to compute this list inline and now uses the hook, so the menu and the dialog always agree.
  • listMoveDestinationComputers holds those rules as a pure function with a co-located test.
  • MoveToDestinationItems (new, _dashboard/components/) renders the submenu items. It takes the menu item component, so the sidebar passes ContextMenuItem and the header passes DropdownMenuItem. Offline computers are disabled. If the to-computer direction is allowed but no computer qualifies, it shows the existing "No other computer is set up for this account." text.
  • The menu stores { direction, hostId }. MoveWorkspaceDialog passes hostId to the computer dialog as initialHostId.
  • useStartMoveToComputer holds the move-start code that the dialog had inline. The dialog and StartMoveToComputer, the no-dialog path, share it. If the picked computer is not an online destination, the no-dialog path shows the dialog instead. The "Don't ask again" choice is read once when the dialog opens, so ticking the box cannot start a second move. It persists as workspace-move-confirm-v1, registered in the persisted-key registry.
  • Moves to cloud keep their dialog, because that is where the environment and machine size are picked.
  • Desktop still runs its moves in the renderer. Switching it to teleport.moveWorkspace is left for later.

Host-service: teleport.moveWorkspace (packages/host-service/src/teleport/moveWorkspace/)

  • Input: { workspaceId, name?, destination }, where destination is { kind: "cloud", environmentId, machineSize? } or { kind: "computer", hostId }. Returns { workspaceMoveId } as soon as the move is recorded. Callers follow the move through workspaceMove.latest, as the desktop toast already does.
  • beginWorkspaceMove runs the same steps as the desktop flow, with its dependencies injected so it can be tested. It records the move with workspaceMove.begin, sends heartbeats, then runs prepareWorkspace({ stopIfBusy: true }). After that:
    • Cloud: cloudWorkspace.create with the move attached.
    • Computer: it reaches the destination host over the relay with the user's login and finds the project by repository. Then it runs fetchBranch and createEnqueued, polls workspace.get until the workspace exists, and calls workspaceMove.attach. Each chat then moves through chatMove.claim/events/complete, importChat and continueChat.
    • Any failure records workspaceMove.abandon and releases the chats, and a destination workspace that was made but not attached is removed.
  • The destination computer only receives procedures it already has, so the source runs this code and the destination can be on a released build.
  • Rejections: NOT_FOUND for an unknown workspace, BAD_REQUEST for a move to the same computer, CONFLICT while the workspace is already moving. It is machineOnlyProcedure, so cloud boxes do not serve it.
  • A cloud environment that does not open the workspace's repository is refused. The cloud API does not check this, and desktop checks it only in its dialog. The environment is read before the workspace is prepared, so an unknown environment fails before any agent stops.
  • keepMoveAlive moved from the desktop renderer to @superset/shared/move-book so desktop and host-service share it.

Mobile (WorkspaceRow/hooks/useWorkspaceMove/)

  • Gated on the workspace-moves flag; Cloud also needs cloud-workspaces and an environment with repositories.
  • Destinations come from the org host roster minus the source and cloud boxes (listMoveComputers, with a test).
  • For Cloud, the phone reads the workspace's repository from the host's projects and offers the environments that open it. With one it confirms, with several it asks which, with none it says so.
  • It calls teleport.moveWorkspace on the source host through the relay. A host that does not serve the procedure gets "Update Paradev on computer to move its workspaces from this phone." (isMissingProcedureError).

Compatibility

  • No existing procedure changed; teleport.moveWorkspace is new.
  • Mobile calls it, so it only works once the source computer runs a host-service with it. Older hosts answer "No procedure found on path", which mobile shows as the update message above. Checked against a released 2.0.8 host.
  • Released desktops keep their own renderer-driven moves.

Manual QA Checklist

Desktop, run against the production API with a local production build:

  • A local workspace shows Move to › Cloud / New cloud workspace. It opens the cloud move dialog, and a move to cloud completes.
  • With a second registered computer online, Move to lists it as "name / Online". Picking it opens the computer dialog with it selected, and the move completes: the workspace, the uncommitted file and the chat history arrive on the other computer.
  • Without another registered computer, only Cloud is offered, same as before.
  • The workspace header ⋯ menu shows the same Move to submenu. This was added after manual testing and is covered by typecheck and tests only.
  • Tick "Don't ask again", then Move: the next pick from the submenu starts the move without the dialog, and the Settings switch brings the dialog back. Added after manual testing; covered by typecheck and tests only.
  • Move to group shows the folder-symlink icon, and its submenu still works.
  • Moving a cloud workspace to a computer from the submenu. Not completed: a pre-existing move got stuck at "creating", unrelated to the menu (see Known Limitations).

teleport.moveWorkspace, called on a local production build's host-service (the same request the phone sends) against the production API:

  • Computer → computer, from the new host to a released 2.0.8 host: the workspace, the uncommitted file and the chat arrive, and a follow-up message continues the chat in the new worktree. The call returns in about a second.
  • Computer → cloud: the cloud workspace is ready about 15 s later, and the chat continues there with the file in /workspace.
  • A released 2.0.8 host answers "No procedure found on path", which mobile's isMissingProcedureError recognises. An ordinary error from a new host is not mistaken for it.
  • A move to the same computer is refused (400), an unknown workspace returns 404, a second call during a move returns 409, and an unreachable destination computer abandons the move with "Host is not online".
  • A cloud environment for a different repository is refused before any cloud workspace is made.
  • A successful cloud move after the repository check was added. Covered by tests; the refused case showed the environment lookup works with the host's credentials.
  • Moves out of a cloud workspace. Out of scope: cloud boxes do not serve the procedure, and mobile does not offer moves for cloud rows.

Mobile

  • Not run on a simulator or device yet: the menu, the alerts, the environment pick, and the call through the relay with the user's login. Typecheck and the destination-list test cover them so far.

Testing

  • bun run typecheck (desktop, mobile, host-service): pass
  • bun run lint:fix, bunx sherif: clean
  • bun run check:i18n: pass. New messages are translated in all 18 locales; the mobile alerts reuse the desktop dialog and toast wording where it fits.
  • New tests: beginWorkspaceMove (computer, cloud, failure before the destination exists, environment for another repository), listMoveComputers, and the move-book tests after keepMoveAlive moved.
  • bun run test: desktop suite 9559 pass. The 2 failures (shell PATH and simple-git env) and the agent-setup / host-service failures come from this NixOS machine: tests spawn with a clean PATH where git isn't found. None touch these files.

Known Limitations

These were found while testing; they predate this change and are left out of it:

  • A computer only appears once its app has Settings → Remote Access on, because host registration is gated on it. The menu gives no hint about this.
  • A move between computers commits uncommitted work with the message "Work in progress, moved to a cloud workspace". The workspace also lands on a superset/teleport-* branch instead of the original branch name. The host-driven move does the same.
  • A host-driven move to a computer that turns out to be unreachable has already stopped the workspace's agents and pushed its branch when it fails. Mobile only offers computers on the account and disables offline ones.
  • Production builds after #9582 (Vite 8) emit fade-edge.css as an early src-*.css chunk that declares @layer utilities first. That makes utilities the lowest-priority layer and breaks the layout. A local production build needed @layer theme, base, components, utilities; added to index.html to render correctly.

Summary by cubic

Groups workspace move actions in the sidebar context menu and the workspace header ⋯ menu under one "Move to" submenu, addressing SUPER-3362. The submenu lists real destinations — Cloud, then each computer the workspace can move to with its online state — and picking a computer opens the move dialog with that computer pre-selected.

  • New useMoveDestinationComputers hook shares the destination list between the menus and the move dialog so they always stay in sync; "This computer" appears with its host name underneath, matching the move dialog and toast.
  • "Move to group" now uses a folder-symlink icon, so the ⇄ icon only means a teleport move.

Summary by CodeRabbit

  • New Features
    • Workspace move menus now offer a unified destination picker for cloud workspaces and computers. Computer destinations show their online status, and unavailable destinations can’t be selected.
    • Moving a workspace to a computer now preselects the chosen destination and can start immediately when it’s available.
    • On mobile, workspace menus now include eligible cloud and computer destinations, with offline computers unavailable for selection.
    • You can choose whether to see a confirmation before moving a workspace, both from the move dialog and in Behavior settings.
    • Added translated labels and guidance for workspace moves, cloud workspaces, and environment selection across supported languages.