feat(desktop): restore Workspaces tab with host-scoped workspace list

Merged
SA
4mo ago
+209-259

Summary

Brings the Workspaces sidebar tab back for real (no flag, no setting) and reworks the /v2-workspaces page to be host-first: the device picker loads instantly from the hosts collections, and the workspace list always reads from exactly one explicitly-targeted host — no fan-out, no reliance on the cloud v2_workspaces table being up to date.

Closes #5881. Supersedes #5901 (which kept the tab behind an off-by-default Appearance toggle).

Changes

Tab restored — SHOW_WORKSPACES_TAB deleted from DashboardSidebarHeader; both the collapsed-rail icon and the expanded row render unconditionally.

Single-host reads — useHostWorkspacesSource now takes an optional scopedHostId: the page passes the selected host and gets one workspace.list (local direct, remote via relay), live-patched by that host's workspace:changed events, with the per-host IndexedDB snapshot for offline and deliberately no Electric v2Workspaces fallback — an unreachable host shows its last-seen snapshot or nothing, never stale cloud rows. No new hook: scoped calls reuse the fan-out's target derivation, query keys, snapshots, and event patching, so where the provider is mounted the host is fetched once, not twice.

Always host-scoped — the "All devices" merged view is removed; the device filter defaults to This device. Ten idle/slow hosts can no longer delay the page or silently thin out the list: readiness and failure are per selected host.

Instant host picker — options come from v2Hosts ∩ v2UsersHosts (locally cached Electric data), so every accessible host appears immediately, including hosts with zero workspaces. Per-host workspace counts are dropped — counting other hosts' workspaces would itself be a fan-out.

Host-served rows are their own proof — the join previously required a v2Hosts row and a v2UsersHosts row before rendering a workspace, so an empty/stale cloud host table hid live host data (caught during CDP verification below). That gate now applies only to cloud-fallback rows; host name/online status degrade gracefully when the cloud row is missing.

Fan-out trims — PR-status enrichment (pullRequests.getByWorkspaces, 10s interval) now runs only for the host-scoped page; unscoped callers (command palette search, dev sidebar seeding) don't render PR data and previously triggered it per host. Those callers keep the shared provider's merged view via the hook's no-deviceFilter path.

Empty vs loading — the empty state is gated on the selected host's settlement, so an unanswered host reads as loading, not "No workspaces yet".

Out of scope: the app-wide HostWorkspacesProvider fan-out still exists — the dashboard sidebar and palette genuinely need cross-host data. Taking those off the fan-out is its own ticket.

Testing

  • bun run lint and bun run --cwd apps/desktop typecheck clean.
  • CDP end-to-end against this branch's dev app (renderer on the workspace's Vite port, session verified via the app's bearer auth client with an active org): real mouse click on the restored tab navigated #/workspace/<id> → #/v2-workspaces; page rendered 27 workspaces host-scoped to "This device" with the picker defaulting correctly; navigate-away/back and a full reload both re-rendered the populated list. Screenshots captured at each step.
  • The verification run caught the cloud-host-table join bug live: the local host served 27 rows while the page showed "No workspaces yet" because v2Hosts was empty in that environment — fixed (host-served rows bypass the cloud gate) and re-verified populated after remount and reload.
  • Not exercised: remote-host selection (no remote hosts in the dev environment) and the visual loading state (the local host answers too fast).

https://claude.ai/code/session_01ULuSGAMv3cqtqBMjaYzDqd


Summary by cubic

Restores the Workspaces tab and makes the Workspaces page host-scoped for faster loads and clearer states. Folds single-host reads into useHostWorkspacesSource and simplifies filters and outputs.

  • New Features

    • Workspaces tab is back in the sidebar (flag removed).
    • Host-scoped list via useHostWorkspacesSource(scopedHostId?): single workspace.list, live workspace:changed updates, IndexedDB snapshot for offline; no cloud v2Workspaces fallback when scoped.
    • Device filter defaults to “This device”; removed “All devices.”
    • Host picker loads instantly from cached v2Hosts/v2UsersHosts (no per-host counts).
  • Refactors

    • Unified source hook: undefined fans out once in the provider; a host id targets one host; null defers and runs nothing. Scoped calls share query keys; avoid unscoped duplication outside the provider.
    • Simplified useAccessibleV2Workspaces: removed pinned/others and device counts; now returns isReady to gate empty states (list waits for the selected host to settle).
    • Host-served rows no longer depend on cloud host tables; names/online status degrade gracefully. PR enrichment runs only on the host‑scoped page; unscoped callers skip PR requests.

Summary by CodeRabbit

  • New Features
    • Restored the Workspaces tab in the dashboard sidebar (now shown unconditionally).
    • Workspaces now default to the current device (“This device”) rather than “All devices”.
  • Bug Fixes
    • Prevented empty-state content from showing until workspace data is ready.
    • Improved device/host labeling and fallback behavior when device info is missing or stale, including better handling of offline/unavailable devices.
  • Style
    • Simplified device filter UI by removing workspace/device count displays.
  • Documentation
    • Updated the dashboard sidebar note for the Workspaces tab behavior.