Repository navigation
refactor(sidebar): move the pure workspace-list builder into src/shared/sidebar-list (no visible change) - #29906
Conversation
Golden output of the unmodified window pipeline (filter, sort, grouping, pins, lineage, host sections, Cmd+1-9 targets) across every groupBy x sortBy x projectOrderBy x pinned policy, plus host-filter, repo-filter, hide-sleeping and hide-default-branch scenarios, each expanded and collapsed. Kept as the correctness reference for moving the row builder.
…list The sidebar's list rules (visible filter, grouping, pinned rows, project group sections, placeholders, host sections, collapse keys) move to src/shared/sidebar-list as pure functions. Headers carry an icon key instead of a React component; the window resolves glyph and tone when it paints, and passes localized header labels in. The window keeps calling through thin adapters (build-rows, compute-visible-worktrees); sort latch, settled epoch, sleeping retention and pending creations stay where they are. github-cache-key moves to src/shared/github because PR-status grouping needs it; the renderer path stays as a re-export so its callers are untouched. Other edits outside the sidebar are import paths only. The golden fixtures from the previous commit pass unchanged.
Header labels are read on every re-emit again, as before the move. Settings and labels types move next to the other row types; header visuals no longer export what nobody imports. Adds a golden for the new-external-worktrees inbox row, captured on the pre-move pipeline.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
- The builder takes a PR-lane reader instead of the PR cache and settings; getPRGroupKey stays in the window, so github-cache-key no longer moves. - Pinned, All and PR-lane headers carry only an icon key; the window names and draws them at paint (status headers keep their data label). - The status icon key carries the status id; the window looks up its look. - Window callers use planSidebarRows directly; the positional helper is a test fixture. - Folder path-status routing and rendered-row keys stay in the window. Goldens pass byte-identical.
The workspace-list builder moved to src/shared/sidebar-list (stablyai#29906), and the sidebar now draws one header per project group id when two hosts publish the same group (stablyai#30128). The branch linked each host's copy to a parent on its own host, which assumed every copy was drawn; with copies collapsed, parents now resolve by id over the drawn groups, the same links reveal walks. Same-id copies from two hosts can still close a parent loop, so section emission and getProjectGroupOccurrencePaths share one parent map that cuts it with breakProjectGroupParentCycles. compareProjectGroupSiblings adopts the device-independent sidebar order from stablyai#29946 instead of localeCompare. createOnTarget moves to project-group-create-on-host.ts, and two repo RPC test expectations fold into one, to keep both files under max-lines.
… is in A project set up on several hosts is one sidebar section anchored on each Orca's own row, and placement read only the anchor's projectGroupId. Move to group changes one host's row, so only the Orca anchored on that row drew the project inside the group; the others showed the group empty. - getProjectSectionMemberRepoIds maps every catalog row to its section, including rows whose workspaces are pinned away or absent. - getProjectSectionGroupRepo picks the first member in manual order that is in a known group, else the anchor. Placement, in-group rank and the reveal path read that row. - Header rows carry it as projectGroupRepo; header drag buckets, ranks and commits use it, and the commit names that row's host. Ported onto src/shared/sidebar-list after stablyai#29906. Fixes stablyai#29876 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is in A project set up on several hosts is one sidebar section anchored on each Orca's own row, and placement read only the anchor's projectGroupId. Move to group changes one host's row, so only the Orca anchored on that row drew the project inside the group; the others showed the group empty. - getProjectSectionMemberRepoIds maps every catalog row to its section, including rows whose workspaces are pinned away or absent. - getProjectSectionGroupRepo picks the first member in manual order that is in a known group, else the anchor. Placement, in-group rank and the reveal path read that row. - Header rows carry it as projectGroupRepo; header drag buckets, ranks and commits use it, and the commit names that row's host. Ported onto src/shared/sidebar-list after stablyai#29906. Fixes stablyai#29876 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is in A project set up on several hosts is one sidebar section anchored on each Orca's own row, and placement read only the anchor's projectGroupId. Move to group changes one host's row, so only the Orca anchored on that row drew the project inside the group; the others showed the group empty. - getProjectSectionMemberRepoIds maps every catalog row to its section, including rows whose workspaces are pinned away or absent. - getProjectSectionGroupRepo picks the first member in manual order that is in a known group, else the anchor. Placement, in-group rank and the reveal path read that row. - Header rows carry it as projectGroupRepo; header drag buckets, ranks and commits use it, and the commit names that row's host. Ported onto src/shared/sidebar-list after stablyai#29906. Fixes stablyai#29876 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is in A project set up on several hosts is one sidebar section anchored on each Orca's own row, and placement read only the anchor's projectGroupId. Move to group changes one host's row, so only the Orca anchored on that row drew the project inside the group; the others showed the group empty. - getProjectSectionMemberRepoIds maps every catalog row to its section, including rows whose workspaces are pinned away or absent. - getProjectSectionGroupRepo picks the first member in manual order that is in a known group, else the anchor. Placement, in-group rank and the reveal path read that row. - Header rows carry it as projectGroupRepo; header drag buckets, ranks and commits use it, and the commit names that row's host. Ported onto src/shared/sidebar-list after stablyai#29906. Fixes stablyai#29876 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ELI5
The code that decides what the desktop's workspace list shows (which rows, in what order, under which headers, with which counts) moves into a shared folder that has no ties to the desktop window. Nothing on screen changes. Later the phone can run this exact same code, so both lists match by construction instead of by two copies of the rules being kept in sync.
What Changed
Before and after for the user: identical. Same rows, same order, same headers, counts, collapse behaviour, highlights and Cmd+1–9 targets.
Mechanism:
Commit 1,
test(sidebar): captures golden files of the sidebar's row output from the unmodified code. The test drives the window's real hooks (filter → sort → grouping → pins → lineage → host sections → selection) over a fixture store. It records:It covers every Group by × Sort by × Project order × "show pinned in groups" combination, each expanded and collapsed. It also has host-filter, single-host, repo-filter, hide-sleeping, hide-default-branch and new-external-inbox scenarios, plus "rows missing from the frozen order go last". The fixture includes local, SSH and paired-server rows, a project on two hosts, the same workspace id on two hosts (one copy pinned), nested project groups, local and SSH folder workspaces, GitHub PR states, a GitLab MR workspace, cross-project lineage, a pending creation, an imported-worktrees card, an inbox row and an empty-project placeholder. These files are meant to stay as the correctness reference.
Commits 2–5 (the move): the pure list code moves from
src/renderer/src/components/sidebar/**tosrc/shared/sidebar-list/. That covers:planSidebarRows);selectVisibleSidebarWorktrees);The builder takes only plain data plus two readers the window supplies:
facts: is a row sleeping, created elsewhere, and so on. This was already a shared interface.prGroupOf: which PR lane a worktree belongs to. The window builds it from its PR cache, so the cache itself and the PR cache-key code stay in the window.It has no clock, store, React, i18n or Electron imports.
{ kind: 'pr-status', prGroup: 'done' }or{ kind: 'workspace-status', status }. The window turns the key into the same glyph and colour when it draws the header (rows/header-visuals.ts).planSidebarRowsdirectly.compute-visible-worktrees.tsis a thin adapter over the shared filter. The old positionalbuildSidebarRowPlan/buildRowsremain only as a test fixture, so the existing tests run unchanged.The golden files from commit 1 are byte-for-byte unchanged by the move. The golden harness changed in three ways only: import paths, renames to the
.test-fixture.tsconvention, and reading the header's colour, glyph and fallback label throughgetSidebarHeaderVisual. That is the same function the header uses when it draws, so the goldens also prove the header visuals and labels didn't change.Size: production code is 87 files, +852/−783, and almost all of that is renames and import paths. Including tests and goldens the total is 155 files, +8,566/−899. The goldens are about 870 KB of text.
Why
The phone currently has its own copy of the list rules (
mobile/src/worktree/workspace-list-sections.tsand friends), and the two drift. The agreed direction is one builder that the desktop window and the phone page both run over the same inputs. This PR is only the first, invisible step: it isolates the builder and pins today's output so later steps can't silently change it.Alternatives considered:
Linked Issue
N/A (maintainer refactor; part of the phone and desktop list parity work)
Visual Proof
N/A. No visual or interaction change. The goldens prove the rendered rows, header tone/icon and shortcut targets are identical.
Testing
--maxWorkers=2). The wider renderer run had only 3 failures (rich-markdown-escape-fidelity,terminal-shutdown-layout-capture), and they fail the same way on the base commit.pnpm tcand changed-code quality gate pass. oxlint and max-lines pass with no new disables. The focus-read ratchet passes (pruned one entry because the reading file moved to shared). Localization coverage and extraction pass; no copy changed.Review
An independent review checked correctness and design. It found the moved files identical apart from import paths, the icon key and label inputs. It confirmed the shared folder has no renderer, React, i18n or clock imports and that no other reader of header tone/icon exists. A second design review asked for the narrower inputs (a PR-lane reader instead of the cache, no settings, no labels in the shared code, and the status icon key carrying only the status id), and those are applied. A follow-up review confirmed each window caller passes exactly the values the old positional defaults gave.
Agent skill upstream boundary
Notes
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)