feat(desktop,trpc): make creating a cloud workspace feel immediate

Merged
SA
3mo ago
+769-142

Four things reported from actually using cloud workspaces, plus one refactor that fell out of the third.

Create no longer blocks on provisioning

cloudWorkspace.create inserts the row and returns; provisioning runs as a queued job (QStash, signature-verified at /api/cloud-workspaces/provision).

Queued rather than fired off after the response because Vercel freezes the function the moment it replies, so an unawaited promise dies with it — the exact failure that stranded a row in provisioning when create ran inline and hit the 60s function limit. QStash can't call localhost, so a local API runs the same job in-process, which keeps dev and prod behaving the same from the UI's point of view.

Open question worth a reviewer's opinion: after() from next/server would do this with ~3 lines and no public endpoint, at the cost of no retries and one code path that differs between local and prod. I lean after() + the reaper (SUPER-1900); this PR keeps QStash because the retry is worth something while provisioning is unproven. Easy to swap.

The client lands you in the workspace

Seeds the list cache with the new row, navigates, then invalidates for server truth. Seeded rather than only invalidated because the route decides between "provisioning" and "doesn't exist" from that list, and one refetch round trip is long enough to flash the wrong one.

Nothing invalidated cloudWorkspace.list on create before. It polls every 30s (SandboxAccessProvider.tsx, DashboardSidebarCloudSection.tsx), so a created workspace could take that long to appear. That — not backend latency — is why creating one felt like a minute of nothing: the image was warm and sandbox create measured 0.3s throughout. Corroborated by four sandboxes in the provider created 57–66s apart, i.e. someone retrying into a void.

A provisioning screen instead of a blank frame or a lie

CloudWorkspaceProvisioningState covers a row whose sandbox isn't up yet, and a ready sandbox not yet addressable (access minting, host-service booting). That gap previously reached the host-unreachable takeover, which tells you your machine is down while it is simply starting.

The create target survives a create

resetDraft() cleared hostId, and draft.hostId ?? machineId reads null as "this machine" — so a second cloud create silently landed on the user's laptop. A persisted lastHostId already existed; the gap was that re-seeding it is guarded by a ref that is set once and never cleared, on a surface that never unmounts. Carrying hostId through the reset fixes every caller at once. DevicePicker additionally drops a remembered cloud target if the flag offering it disappears (=== false, because undefined means the flags haven't loaded).

The submit spinner was invisible

PromptInputSubmit renders children ?? Icon; passing children={null} still fell through to its own size-4 Loader2Icon, which is too big for the 12px content box and inherits text-primary-foreground — dark in dark mode. Now renders the shared Spinner at the arrow's size and colour.

Route refactor

The state-screen gate moved from v2-workspace/layout.tsx to $workspaceId/layout.tsx, where the param is available by definition. Deletes a parent route that matched its own child to recover the id, and an unreachable "no id" branch. page.tsx needed no change — it already declared itself that layout's index.

Verification

End to end against real sandboxes through the app (CDP), not just unit tests:

create #1create #2
target before submitCloudCloud (the bug)
navigated0.9s0.9s
"Creating sandbox" visible1.1s1.1s
usable7.5s7.7s

Not-found and unreachable screens never appeared. Every sandbox created while testing was deleted; provider shows none of mine running.

bun test in apps/desktop: 2583 pass, 0 fail. Typecheck clean in apps/desktop and packages/trpc. Lint clean across 5,717 files.

Not covered

QStash delivery itself has only run via the local in-process path — the queued path needs a production create to be trusted, and it's unknown whether a QStash job can reach a preview deployment's URL.


Summary by cubic

Creating a cloud workspace now returns immediately and opens the workspace; provisioning runs as a background job. Old behavior awaited provider calls and the UI polled (often showing not-found or host-unreachable). New behavior inserts the row, queues provisioning, seeds the list, navigates, and renders a provisioning screen until the sandbox is ready. Side effects: the list includes provisioning/failed, polling is 1s while anything is provisioning, and access/host URLs are only minted for ready. The provisioning API now returns 401 for missing or malformed QStash signatures.

  • API: cloudWorkspace.create writes the row and queues provisioning via QStash at /api/cloud-workspaces/provision (local API runs the same job in-process); cloudWorkspace.list returns all non-deleted rows; signature verification for provisioning replies 401 on missing/invalid headers; provisioning is idempotent and safe to retry.
  • Desktop: useCloudWorkspaces centralizes the cloud list and polling; create seeds the list, navigates to /v2-workspace/$workspaceId, then invalidates; CloudWorkspaceProvisioningState covers provisioning and failure with a remove action; the route gate moved to $workspaceId/layout.tsx; access and host URL hooks only broker addresses for ready; destroy uses the cloud list to decide sandbox vs local; create target persists across reset; submit buttons render a visible Spinner. Consumers that assumed list was ready-only must account for provisioning/failed.

Rollout

  • Set QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY, QSTASH_NEXT_SIGNING_KEY, NEXT_PUBLIC_API_URL, BLAXEL_SANDBOX_IMAGE.
  • Ensure QStash can reach NEXT_PUBLIC_API_URL; local development runs provisioning in-process.
  • Update imports to use @superset/trpc/cloud-workspace-provision.

Summary by CodeRabbit

  • New Features

    • Added asynchronous cloud workspace provisioning with visible progress and status updates.
    • Added a provisioning screen with elapsed time, progress steps, warnings, and failure recovery.
    • Cloud workspaces appear immediately after creation and refresh automatically as provisioning progresses.
    • Added secure background processing for workspace provisioning.
  • Bug Fixes

    • Improved handling of provisioning and unavailable cloud workspaces.
    • Prevented non-ready workspaces from entering the access flow.
    • Preserved the selected host when resetting the new workspace form.
    • Added loading indicators during workspace creation.
  • Documentation

    • Documented cleanup requirements for failed or stalled cloud workspace creation.