Skip to content

feat: pinned workspaces — main and long-lived branches as sidebar items#220

Open
iabdousd wants to merge 3 commits into
mainfrom
flushing-sveltestores-285
Open

feat: pinned workspaces — main and long-lived branches as sidebar items#220
iabdousd wants to merge 3 commits into
mainfrom
flushing-sveltestores-285

Conversation

@iabdousd

Copy link
Copy Markdown
Member

Summary

  • Every project auto-gets a main pinned row that runs sessions directly in the repo root (no worktree created). Branch label tracks project.base_branch.
  • + Pin branch button in the project header opens a dialog to attach a worktree for any existing local branch (trunk, develop, etc.) as a persistent sidebar item.
  • Pinned rows sit above regular tasks (divider separates the two sections) and skip archive / merge / PR flows entirely.
  • A Pin / Main badge in the TaskPanel header distinguishes pinned workspaces from regular tasks.
  • Right-click → Unpin removes non-main pins (main pin cannot be unpinned; no archive UI shown for it).

Implementation

  • DB migration v24: ALTER TABLE tasks ADD COLUMN is_pinned; backfills one main pinned task per existing project.
  • add_project seeds a main pinned task automatically on new project creation.
  • pin_branch / unpin_task / list_local_branches IPC commands (and typed wrappers in ipc.ts).
  • Guards on archive_task, merge_branch, create_pull_request, delete_task reject pinned tasks with the operation name in the error.
  • buildTaskMenuItems pure helper drives context-menu branching (Archive vs Unpin vs neither for main) — fully unit-tested without rendering the full Sidebar.
  • PinBranchDialog: filter input (visible when >6 branches), worktree path preview, count indicator, hides already-pinned branches, error states.
  • pinnedTasksForProject / unpinnedActiveTasksForProject / isMainPinned selectors in store/tasks.ts.

Test plan

  • Fresh install → add a repo → sidebar shows one main pinned row, no divider (no regular tasks yet)
  • Click main → session runs with pwd returning repo root
  • Upgrade path: existing DB (pre-v24) → migration backfills main per project automatically
  • + Pin branch → pick an existing branch → pinned row appears with worktree under .verun/worktrees/<branch>
  • Start parallel sessions on main and a pinned branch — each runs in its own directory
  • Right-click pinned branch → Unpin → row disappears
  • Right-click main → Unpin is absent; Archive is absent
  • Create regular task → appears below divider, pinned rows unaffected
  • make check passes (556 frontend tests, 623 Rust lib tests, clippy clean, tsc clean)

Closes #61

@iabdousd iabdousd force-pushed the flushing-sveltestores-285 branch 3 times, most recently from d4bd2c2 to 2f8cd3d Compare May 1, 2026 19:51
iabdousd added 3 commits May 7, 2026 17:18
…ms (#61)

- DB migration v20 adds is_pinned column; backfills one main pinned task per project
- Backend: pin_branch attaches worktree to existing branch; unpin_task; list_local_branches
- Guards: archive/merge/PR/delete reject pinned tasks with operation in error message
- Auto-creates main pinned task on add_project (worktree_path=repo_path)
- Frontend: Sidebar splits into pinned section + divider + regular tasks; +Pin branch button
- PinBranchDialog: filter, worktree path preview, count indicator, hides already-pinned
- TaskPanel: Pin/Main badge in header; archive/PR actions hidden for pinned
- buildTaskMenuItems pure helper for context menu (Archive vs Unpin vs neither)
- Tests: 346 frontend, 456 Rust; clippy clean; tsc clean
- add_project: use db::next_port_offset for the seeded main task instead of
  hard-coding 0, matching the v24 migration's MAX(port_offset)+1 logic
- Sidebar: surface unpin failures via addToast instead of silently swallowing
  the rejection (e.g. cannot-unpin-main backend error)
- Document that the main pinned task's `branch` column is purely a display
  label - worktree_path == repo_path is the source of truth for HEAD
- Rust tests: build_main_pinned_task field invariants, pin_branch with empty
  project_id, idempotent re-pin without remove, unpin guards (main, missing,
  not-pinned, branch-pin success), v24 migration leaves legacy tasks
  is_pinned=0 and avoids port-offset collisions
- Frontend tests: filter-no-match disables button, reopen resets state,
  no-op submit when nothing selected, loading label during pin, missing
  project hides path preview, separator placement around Archive/Unpin,
  selectors react to live store mutation, archived main excluded, case
  sensitivity on projectId, trailing-slash equality
- delete_project: comment that cascade is the only path that may remove
  pinned tasks (IPC handler rejects pinned via reject_if_pinned)
- pin_branch: roll back the worktree on DB write failure so the user is
  not wedged with a worktree on disk and no row to match it
- Sidebar: partition tasks once per reactive update (O(T) instead of
  O(P*T)) by walking `tasks` once and bucketing by projectId/isPinned/
  archived; drops two unused selector imports
- PinBranchDialog: clarify that the worktree path preview is
  pre-canonicalize and may differ from the stored task.worktreePath when
  the repo path is a symlink
- task.rs: rename pin_branch_idempotent_after_unpin_then_repin to
  pin_branch_rejects_repin_when_worktree_already_exists (the test
  asserts failure, not idempotence)
- CHANGELOG: rephrase pinned-workspaces bullet to start with a verb
- GitActions.test.tsx: revert unrelated PrInfo body removal
@iabdousd iabdousd force-pushed the flushing-sveltestores-285 branch from 56b4926 to e669b4e Compare May 7, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pinned workspaces: main and long-lived branches as first-class sidebar items

1 participant