feat(desktop): restore Workspaces tab with host-scoped workspace list
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 lintandbun run --cwd apps/desktop typecheckclean.- 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
v2Hostswas 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?): singleworkspace.list, liveworkspace:changedupdates, IndexedDB snapshot for offline; no cloudv2Workspacesfallback 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:
undefinedfans out once in the provider; a host id targets one host;nulldefers and runs nothing. Scoped calls share query keys; avoid unscoped duplication outside the provider. - Simplified
useAccessibleV2Workspaces: removedpinned/othersand device counts; now returnsisReadyto 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.
- Unified source hook:
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.