feat(desktop): add workspace pinning to the v2 dashboard sidebar

Merged
AV
3mo ago
+663-52

What

Right-click any workspace row in the v2 dashboard sidebar → Pin. The workspace moves into a new Pinned section at the very top of the sidebar, above all project groups, with a small project avatar for cross-project context. Unpin returns it to exactly the project/group/position it came from. Pins persist across restarts. In the collapsed rail, pins render as an icon stack above the project thumbnails.

How

  • Storage: nullable pinnedAt (epoch ms) on sidebarState in the v2WorkspaceLocalState localStorage collection — heal-forward, so no migration, no Postgres/Electric/host-service changes, and live-query reactivity for free.
  • Mutation: idempotent setWorkspacePinned(workspaceId, projectId, pinned) in useDashboardSidebarState. Repeat pins keep the original pin time; pinning an auto-included local main workspace creates its local-state row first.
  • Ordering: pinned section sorts by pinnedAt ascending — new pins append at the bottom, and pins never reorder themselves.
  • Partition, not mirror: pinned rows are split out before the project tree is built (partitionSidebarWorkspacesByPinned + buildDashboardSidebarPinnedWorkspaces, sharing a new decorateSidebarWorkspace helper), so a pinned workspace renders only in the Pinned section.
  • Lifecycle: tombstoneSidebarWorkspaceRecord (the sole isHidden = true writer) clears pinnedAt, so a hidden/removed workspace can never resurrect pre-pinned.
  • Rename: the pre-existing "pinned" concept (local-main-first sort) is now isLocalMainWorkspace / normalizeMainFirst to avoid vocabulary collision.
  • Header style: the Pinned header uses the same 10px uppercase micro-label treatment as the PROJECTS header.

Known v1 interactions with #5956: the project filter doesn't filter the Pinned section (pins stay visible), and pinned rows don't contribute to a project's "last updated" sort ranking.

Validation

  • bun run typecheck clean, bun run lint clean
  • Schema heal + partition/pinned-builder unit tests added; full _authenticated route suite 250/250
  • ExecPlan included at apps/desktop/plans/done/20260725-1947-sidebar-workspace-pinning.md

https://claude.ai/code/session_01WThJeeNfE44b1Xd1ZQsaLr


Open in Stage

Summary by cubic

Add workspace pinning to the v2 dashboard sidebar. Right‑click a workspace to Pin it into a new top Pinned section with a project avatar; Unpin returns it to the same project/section/order, and pins persist across restarts.

  • New Features

    • New Pinned section above projects; collapsed rail shows an icon stack with a divider.
    • Pinned rows show a project avatar, keep PR status, and the active‑project UI works when the active workspace is pinned.
    • Storage: sidebarState.pinnedAt in v2WorkspaceLocalState (heal‑forward); ordered by pin time ascending; tombstoning unpins; removing a project clears pins on kept main‑workspace rows.
    • API: idempotent setWorkspacePinned(workspaceId, projectId, pinned); creates a local‑state row when pinning an auto‑included main workspace.
    • Partitioned rendering: pins are removed from project groups; project filter does not hide pins; pins don’t affect a project’s “last updated” sort; context menu adds Pin/Unpin as the first item.
  • Refactors

    • Renamed the old “pinned” concept to isLocalMainWorkspace and normalizeMainFirst.
    • Added pinned partition/build helpers and tests; updated fixtures.

Summary by CodeRabbit

  • New Features
    • Workspaces can now be pinned or unpinned from the sidebar context menu.
    • Pinned workspaces are shown in a dedicated top “Pinned” section, with support for both collapsed and expanded sidebar layouts.
    • Pinned state persists across app restarts, with newly pinned items added to the bottom of the pinned list.
  • Bug Fixes
    • Workspaces that are hidden or removed no longer remain in the Pinned section.
    • Unpinning returns a workspace to its prior project/section and ordering position.