Skip to content

fix(sidebar): place a multi-host project in the group any of its rows is in - #29877

Closed
jbamlee wants to merge 2 commits into
stablyai:mainfrom
jbamlee:fix-paired-client-group-sync
Closed

jbamlee wants to merge 2 commits into
stablyai:mainfrom
jbamlee:fix-paired-client-group-sync

Conversation

@jbamlee

@jbamlee jbamlee commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

When the same project is added on two paired computers, the sidebar shows it once, with a row for each computer. It picked the project's group by looking at only one of those rows, and each computer looked at its own row. So after you put the project in a group on one computer, the other computer showed an empty group and left the project outside it. Now, if any of the project's rows is in a group, the project is shown inside that group.

What Changed

Before: Orca A and Orca B are paired and both have the same Git project. On Orca A, Move to group puts the project inside the group. Orca B shows the group header empty, with the project at the top level.

After: Orca B also shows the project inside that group. If the computers' rows are in different groups, each Orca uses the group of the first row in its own manual order. That is normally its own row.

Mechanism:

  • src/shared/sidebar-list/project-group-sections.ts: new getProjectSectionMemberRepoIds(repoMap, projectIndex) maps every catalog row to its section key, once per row build. It covers rows whose workspaces are pinned away and rows that have no workspaces yet.
  • New getProjectSectionGroupRepo(section, memberRepoIds, …) returns the row that places a section: the first member row in manual order that is in a known group. If no row is in a known group, it is the anchor, so the section stays at the root as today. Because it reads catalog rows rather than visible ones, pins and placeholders cannot move a project. The drawn sidebar and the Cmd+1–9 order also always agree.
  • appendProjectGroupSections places sections, and orders them inside a group, by that row's projectGroupId and projectGroupOrder.
  • sidebar-row-plan.ts (planSidebarRows) takes the reveal path (which group to expand to reveal a workspace) from the same row, so reveal matches where the project is drawn. Every caller of planSidebarRows (the window's list, the Cmd+1–9 order, reveal, and the sidebar feed) reads the same placement.
  • Header rows carry that row as projectGroupRepo when it is not the anchor. Header drag passes it into the drag session next to the header's own row (since fix(projects): a project move names the row it moves; a paired server's twin by its SSH target #30794 the session carries rows, not ids). The drag takes its bucket from it, sibling ranks read it, and the commit moves that row (moveProjectToGroup(row, …)). The header's own id still marks its place in the list. Dragging a project inside a group therefore reorders the row that put it there, on that row's own host.
  • The header's … menu takes the same row (RepoHeaderProjectActionsMenu's projectGroupRepo). Move to group, its "current group" check and Remove from group act on it, so they match where the project is drawn. Project settings, icon, worktree visibility, New group from project and Remove Project keep the anchor row.

Why

Root cause: the anchor is the first of the section's rows in each Orca's catalog order, which is usually its own host's row (getManualOrderAnchorRepo). Placement read only the anchor's projectGroupId, and Move to group changes only one host's row. So only the Orca whose anchor is the moved row draws the project in the group. The group catalogs themselves sync correctly. A two-Electron run showed the paired client receives both the group and the moved row.

Alternatives considered:

  • Make Move to group change every host's row. A group lives on one host, and a row can only join a group on its own host, so the other hosts' rows cannot join it.
  • Anchor the section on the grouped row. The anchor also decides which host header actions target, such as creating a workspace. Changing it would send those actions to another computer.
  • Stop merging a project's rows from different hosts. That reverses the deliberate one-project-per-section design.

Linked Issue

Fixes #29876

Related: #29536 (folds same-named groups from different hosts into one header). This PR is about one group that only one host has; the two do not overlap in logic but touch the same file.

Visual Proof

Rerun on this branch rebased onto main a439dfa451 (two background Electron apps, host and paired client):

host sidebar:   group left=14, project left=24   (inside)
client sidebar: group left=14, project left=24   (inside; client anchor is its own row)
client drag inside the group: [group, other@24, merged@24] -> [group, merged@24, other@24]
host projectGroupOrder after the client's drag: {"merged":-1,"other":0}; client's own row group: null

Original run, before the rebase:

Two real Orca apps ran in the background, built from this branch and from its base. One is the host and one a paired client. The same Git remote was added on both, and the client lists its own row first. The project was grouped on the host. Sidebar header positions as rendered (left is the label's x offset, 14 = top level, 24 = nested):

# base f9acfffa03 — client
host sidebar:   group "Host group" left=14, project left=24   (inside)
client sidebar: group "Host group" left=14, project left=14   (outside, group empty)

# this branch — client
host sidebar:   group "Host group" left=14, project left=24
client sidebar: group "Host group" left=14, project left=24   (inside)

Drag inside the group on the paired client (this branch). The project placed through the host's row is dragged above a host-only project in the same group:

client before drag: [group, project:other@24, project:merged@24]
client after drag:  [group, project:merged@24, project:other@24]
host projectGroupOrder after the client's drag: {"merged":-1,"other":0}
client's own row group after drag: null   (only the host row moved)

Testing

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

  • New worktree-list-groups-cross-host-project-group.test.ts:

    • The same two-host data, built in both catalog orders, renders identically.
    • The reveal path follows the group the project is drawn in.
    • Both of these fail on the base.
    • Guards: when the rows are in different groups, the first row in manual order wins; with group metadata missing, the project stays at the root.
    • Pinning the grouped host's only workspace keeps the project in the group.
    • The rendered sidebar (empty-project placeholders) and the Cmd+1–9 order (no placeholders) place the project the same way, including with rows in competing groups.
  • project-header-drag-commit.test.ts: a header placed by another host's row reorders that row. project-header-drag-start.test.ts: the drag takes that row's bucket. Both fail with main's drag code, which reads only the header's own row.

  • New worktree-list-groups-cross-host-project-menu.test.tsx (review follow-up): the planned header, with this Orca's ungrouped row as anchor, renders the menu with the props SectionHeader passes. Remove from group shows and removes the paired host's row, and Move to group marks that group current and moves that row. Both fail before the change. Project Settings and Remove Project stay on the local row. The two-host fixture is shared through worktree-list-groups-cross-host-project-group.test-fixture.ts.

  • Rebased onto main a439dfa451. It conflicted with Keep project names short and show their machine #28250, which wrapped the project header in the machine tooltip and re-indented it. The PR's two SectionHeader lines (the drag start and the … menu get row.projectGroupRepo) are reapplied unchanged. Gates: pnpm test src/renderer/src/components/sidebar src/shared/sidebar-list src/renderer/src/store/slices (7327 passed), pnpm tc, oxlint, oxfmt --check, pnpm run check:code-quality:changed, and the two-Electron E2E above.

  • Rebased onto main 0a3d203f61. It conflicted with fix(projects): a project move names the row it moves; a paired server's twin by its SSH target #30794, which made the drag session carry the dragged row. The session now also carries projectGroupRepo; the commit moves that row and keeps the header's id for list positions. Gates: pnpm test src/renderer/src/components/sidebar src/shared/sidebar-list src/renderer/src/store/slices (7235 passed), pnpm tc, oxlint, oxfmt --check, pnpm run check:code-quality:changed.

  • Review follow-up gates: pnpm test src/renderer/src/components/sidebar src/shared/sidebar-list, pnpm tc, oxlint, pnpm run check:code-quality:changed pass.

  • Rebased onto main 90e99c48f4 (the list builder moved to src/shared/sidebar-list in refactor(sidebar): move the pure workspace-list builder into src/shared/sidebar-list (no visible change) #29906): pnpm test src/shared/sidebar-list src/renderer/src/components/sidebar src/renderer/src/store/slices (7221 passed, including the refactor(sidebar): move the pure workspace-list builder into src/shared/sidebar-list (no visible change) #29906 golden row files), pnpm tc, oxlint, pnpm run check:code-quality:changed. With main's builder files, 6 of the 8 new tests fail; the 2 guards pass.

  • Platforms: macOS (unit tests and a two-Electron background E2E). The change is renderer-only with no platform branches.

AI Disclosure

Implemented with Claude Code (Claude Opus 5.5). Independent review by GPT-6 Astra (xhigh reasoning) through Codex CLI before the rebase. Its findings (pinned members; Cmd+1–9 parity, including competing groups; drag buckets) are fixed in this PR.

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

  • No IPC, RPC or persisted-state change. The phone's host list is built by the same planSidebarRows through the sidebar feed, so it places the project the same way with no phone code change.
  • Single-host projects are unaffected: their only row is the anchor.
  • Cost: one pass over the repo catalog per row build, using indexed lookups, plus a scan of each section's member ids.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

  • Last reviewed commit: 81bafa5d92382c1152ef3b56773ea522f0d0c13a
  • Latest commit, not reviewed: 45d4741

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e8b7c679-01e3-4c51-82d6-5844c62fcb27
📥 Commits

Reviewing files that changed from the base of the PR and between 3791468107ae18e2dfaaf98ce4e9cc91aede4a31 and 81bafa5d92382c1152ef3b56773ea522f0d0c13a.

📒 Files selected for processing: 8
  • src/renderer/src/components/sidebar/project-header-drag-commit.test.ts
  • src/renderer/src/components/sidebar/project-header-drag-commit.ts
  • src/renderer/src/components/sidebar/project-header-drag-contract.ts
  • src/renderer/src/components/sidebar/project-header-drag-start.test.ts
  • src/renderer/src/components/sidebar/project-header-drag-start.ts
  • src/renderer/src/components/sidebar/project-header-drag.ts
  • src/renderer/src/components/sidebar/worktree-list/drag/use-header-drag.ts
  • src/renderer/src/components/sidebar/worktree-list/rows/SectionHeader.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

Sidebar project sections now use member repositories to determine project-group placement and reveal paths across paired hosts. Group headers expose the repository used for group actions and drag operations. Group moves and removal use that repository, while project settings and removal continue to use the anchor repository. Drag commits pass the selected repository ID.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🔵 Low · up to 81baf

A group shown in Move to group may reject the move on a paired client. Valid cross-host moves remain available, so this is mergeable with that limitation understood.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 34.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 22 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary change: placing multi-host projects in the group assigned by any of their rows.
Linked Issues check Passed Issue [#29876] requires a multi-host project to appear inside its group on every paired Orca. The changes collect all project rows, select the first known-group row in manual order, and use it for pla…
Out of Scope Changes check Passed The changes remain within [#29876]. The source changes align project placement and related group actions when paired hosts represent one project. The fixtures and tests support this behavior. No unrel…
Description check Passed The pull request description is complete and follows the required template. It explains the user impact, implementation, rationale, linked issue, testing, AI use, compatibility considerations, and che…
Full details: Linked Issues check

Explanation

Issue [#29876] requires a multi-host project to appear inside its group on every paired Orca. The changes collect all project rows, select the first known-group row in manual order, and use it for placement, reveal paths, ordering, drag actions, and group-menu actions. The anchor remains the target for unrelated project actions. The reported tests cover host order, competing groups, pinned rows, reveal paths, ordering parity, drag behavior, and menu behavior.

Full details: Out of Scope Changes check

Explanation

The changes remain within [#29876]. The source changes align project placement and related group actions when paired hosts represent one project. The fixtures and tests support this behavior. No unrelated product change is demonstrated.

Full details: Description check

Explanation

The pull request description is complete and follows the required template. It explains the user impact, implementation, rationale, linked issue, testing, AI use, compatibility considerations, and checklist status. The Visual Proof section provides detailed text-based before/after results but does not include attached screenshots or videos, and the full lint/typecheck/test/build checklist remains unchecked.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nwparker nwparker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the placement fix looks right. The new tests fail on current main and pass here, and the sidebar and src/shared/sidebar-list folders pass.

One gap to fix before this can merge. The project's … menu still works from the anchor row, so it doesn't match where the project is now drawn:

  • SectionHeader.tsx (around line 384) passes row.repo to RepoHeaderProjectActionsMenu. Take a header drawn inside a group through a paired host's row (row.projectGroupRepo), where the local anchor has no group. On that header, Remove from group is hidden, even though the project sits inside the group. Move to group also acts on the local row, not on the row that put the project in the group.
  • Suggested fix: the group actions (Move to group, Remove from group, the disabled check) should use row.projectGroupRepo ?? row.repo. Other repository actions should keep the anchor. A test where the header is drawn in a group through the other host's row, checking that Remove from group shows and targets that row's host, would cover it.

This interacts with #29863: that PR filters Move to group by the host of the repo it receives, so both PRs should agree on which row the menu gets.

@jbamlee

jbamlee commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks. Fixed in the new commit on top, no rebase, so it shows as a plain diff.

  • RepoHeaderProjectActionsMenu takes the header's projectGroupRepo. Move to group, its "current group" check and Remove from group act on projectGroupRepo ?? repo. Project settings, icon, worktree visibility, New group from project and Remove Project stay on the anchor row.
  • SectionHeader passes row.projectGroupRepo through.
  • New test worktree-list-groups-cross-host-project-menu.test.tsx plans the rows with this Orca's ungrouped row as the anchor and the paired host's row in the group. It then renders the menu with the props SectionHeader passes and checks three things: Remove from group shows and gets the paired row (runtime:laptop-a); Move to group marks that group as current and moves the paired row; Project Settings and Remove Project still use the local row. The first two fail without the change. The cross-host fixture moved to a .test-fixture.ts so both test files share it.

On #29863: whichever of the two lands second will filter Move to group by the same row, projectGroupRepo ?? repo, so the menu lists only groups the row it acts on can join. I'll make that change on rebase.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 48e08230-1895-481c-9dc9-99506fb3cb34
📥 Commits

Reviewing files that changed from the base of the PR and between f69719abcb7277b1f7bbffe6c7908faff2386f7b and 8c0dc57cd0e7631f4ded136c0c3821a3fbe609ad.

📒 Files selected for processing: 5
  • src/renderer/src/components/sidebar/worktree-list-groups-cross-host-project-group.test-fixture.ts
  • src/renderer/src/components/sidebar/worktree-list-groups-cross-host-project-group.test.ts
  • src/renderer/src/components/sidebar/worktree-list-groups-cross-host-project-menu.test.tsx
  • src/renderer/src/components/sidebar/worktree-list/rows/SectionHeader.tsx
  • src/renderer/src/components/sidebar/worktree-list/rows/repo-header-project-actions.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@jbamlee

jbamlee commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main 69157847e3. It conflicted with #30756, which added the dragged row's host to the project header drag commit. I kept main's host argument; this PR now changes only which row's id the commit writes, the row that placed the header in the group. Gates: pnpm test src/renderer/src/components/sidebar src/shared/sidebar-list src/renderer/src/store/slices (7226 passed), pnpm tc, pnpm run check:code-quality:changed.

@jbamlee
jbamlee force-pushed the fix-paired-client-group-sync branch 2 times, most recently from 3791468 to 81bafa5 Compare October 19, 2026 23:07
@jbamlee

jbamlee commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main 0a3d203f61. It conflicted with #30794, which made the header drag session carry the dragged row instead of its id.

  • The session now also carries the header's projectGroupRepo, passed in from SectionHeader. The drag takes its bucket from that row, and the commit moves that row (moveProjectToGroup(row, …)). The header's own id still marks its place in the list.
  • New test in project-header-drag-start.test.ts (the drag takes that row's bucket). The project-header-drag-commit.test.ts case now moves that row. Both fail with main's drag code.
  • Gates: pnpm test src/renderer/src/components/sidebar src/shared/sidebar-list src/renderer/src/store/slices (7235 passed), pnpm tc, oxlint, pnpm run check:code-quality:changed. The two-Electron background run (host and paired client) still draws the project inside the group and reorders the host's row on a client drag.

@jbamlee
jbamlee force-pushed the fix-paired-client-group-sync branch 4 times, most recently from c5b9e79 to b37f05d Compare October 20, 2026 09:51
@jbamlee

jbamlee commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main a439dfa451. It conflicted with #28250, which wrapped the project header in the machine tooltip and re-indented the block. No logic changed: the header still passes row.projectGroupRepo to the drag start and to the … menu, and #28250's machine label keys off the anchor row, which this PR keeps.

Gates: pnpm test src/renderer/src/components/sidebar src/shared/sidebar-list src/renderer/src/store/slices (7327 passed), pnpm tc, oxlint, oxfmt --check, pnpm run check:code-quality:changed. The two-Electron background E2E (host and paired client) still places the project inside the group and reorders the host row on a client drag.

@jbamlee
jbamlee force-pushed the fix-paired-client-group-sync branch from b37f05d to d52141b Compare October 20, 2026 12:49
jbamlee and others added 2 commits October 20, 2026 22:10
… 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>
…aced it in the group

A header drawn inside a paired host's group, while this Orca's own row is
ungrouped, hid Remove from group and moved the local row. Move to group, its
current-group check and Remove from group now use the header's
projectGroupRepo; the other project actions keep the anchor row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jbamlee
jbamlee force-pushed the fix-paired-client-group-sync branch from d52141b to 45d4741 Compare October 20, 2026 13:12
@jbamlee jbamlee closed this Oct 23, 2026
@jbamlee
jbamlee deleted the fix-paired-client-group-sync branch October 23, 2026 03:07
@MedAIerHHL

Copy link
Copy Markdown

@jbamlee Any follow-up?

@jbamlee

jbamlee commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for asking. I had closed this one on 2026-10-23. The bug still reproduces on current main, so I rebuilt the fix there as #34720 (Fixes #29876) and reopened the issue.

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.

[Bug]: A project grouped on one paired Orca shows outside that group on the other Orcas

3 participants