Skip to content

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

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

jbamlee wants to merge 4 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:

  • 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.
  • build-rows.ts takes the reveal path (which group to expand to reveal a workspace) from the same row, so reveal matches where the project is drawn.
  • Header rows carry that row as projectGroupRepo when it is not the anchor. Header drag uses it for the bucket, the sibling ranks and the commit. The commit now names the row's host (moveProjectToGroup(..., hostId)). Dragging a project inside a group therefore reorders the row that put it there, on that row's own host.

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

Visual Proof

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, on its host. Existing commits now pass their host too.

  • pnpm test src/renderer/src/components/sidebar src/renderer/src/store/slices (all pass), pnpm tc:web, oxlint, pnpm run check:code-quality:changed.

  • 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. 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. Mobile does not draw project groups.
  • Single-host projects are unaffected: their only row is the anchor.
  • The header menu's Remove from group still acts on the anchor row. For a project drawn in a group through another host's row, there is no Remove from group on that Orca, because the membership belongs to the other host's row.
  • 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

jbamlee and others added 4 commits October 17, 2026 07:49
… is in

A project set up on several hosts renders as one section whose anchor is
the first of its rows in this Orca's catalog order, and the section was
placed in a Project Group by the anchor's projectGroupId only. Each Orca
anchors on its own host's row, so after Move to group on one Orca, every
other Orca showing the same project kept it outside the group, next to an
empty group header.

Resolve the section's group from its rows: the anchor's group when it is
known, otherwise the first row in manual order that is in a known group.
Placement, the in-group order, and the reveal path all use that row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The section's visible members miss rows whose workspaces are pinned away
or that have none yet, and the Cmd+1-9 order builds rows without the
empty-project placeholders the sidebar passes. Pinning the grouped host's
last workspace then moved the project back to the root, and shortcut
numbering disagreed with the drawn sidebar. Collect every catalog row that
maps to the section's key and resolve the group from those.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…chor

The anchor is the first visible row, so it still depended on pins and on
the empty-project placeholders that the Cmd+1-9 order does not pass. With
rows in competing groups, the drawn sidebar and shortcut numbering then
put the project in different groups. Take the first catalog row in manual
order that is in a known group; in the usual case that is still this
Orca's own row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…group

A section drawn in a group through a member row still took its drag
bucket, sibling ranks and commit target from the anchor, so it fell in
the root bucket while drawn inside the group. Header rows now carry the
row that placed them; drag buckets, in-group ranks and the reorder
commit use it, and the commit names that row's host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

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: f019eb8c-b52b-491b-ac88-160d1d6902b0
📥 Commits

Reviewing files that changed from the base of the PR and between 3615d04 and 7979a8b.

📒 Files selected for processing: 11
  • 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-drop.ts
  • src/renderer/src/components/sidebar/worktree-list-groups-cross-host-project-group.test.ts
  • src/renderer/src/components/sidebar/worktree-list/drag/use-header-drag.ts
  • src/renderer/src/components/sidebar/worktree-list/grouping/build-rows.ts
  • src/renderer/src/components/sidebar/worktree-list/grouping/group-sections.ts
  • src/renderer/src/components/sidebar/worktree-list/grouping/project-group-sections.ts
  • src/renderer/src/components/sidebar/worktree-list/grouping/recent-project-rank-scan-budget.test.ts
  • src/renderer/src/components/sidebar/worktree-list/grouping/row-types.ts

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

📝 Walkthrough

Project section placement now uses member repository group metadata to determine the displayed group, ordering, and reveal path. Group headers carry the selected project-group repository when it differs from the anchor. Drag commits pass the execution host ID, and drag ordering uses the selected repository’s bucket. Tests cover cross-host placement and drag callbacks.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 7979a

The cross-host grouping and drag changes appear mergeable after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 11 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description check Passed The description is complete and directly matches the PR objective. It includes the user impact, implementation details, rationale, linked issue, visual proof, testing results, AI disclosure, compatibi…
Title check Passed The title clearly and concisely describes the main change: placing a multi-host project in the group selected by any of its rows.
Linked Issues check Passed Issue #29876 requires a multi-host project to appear inside the group when a row on another paired host belongs to that group. getProjectSectionMemberRepoIds includes all catalog rows, including pin…
Out of Scope Changes check Passed The changes remain within issue #29876. The drag bucket, header metadata, and host argument changes keep drag behavior consistent with the row that places the project in its group. The added tests val…
Full details: Description check

Explanation

The description is complete and directly matches the PR objective. It includes the user impact, implementation details, rationale, linked issue, visual proof, testing results, AI disclosure, compatibility notes, and checklist status. The full lint, typecheck, test, and build checklist remains unchecked, but the description reports targeted validation and CI coverage can complete the remaining checks.

Full details: Linked Issues check

Explanation

Issue #29876 requires a multi-host project to appear inside the group when a row on another paired host belongs to that group. getProjectSectionMemberRepoIds includes all catalog rows, including pinned and absent-workspace rows. getProjectSectionGroupRepo selects the first manually ordered row in a known group and falls back to the anchor when no row is grouped. The selected row drives section placement, ordering, and reveal paths. New tests cover both catalog orders, competing groups, missing group metadata, pinned workspaces, placeholder parity, and reveal paths. The drag tests also verify that reordering uses the selected row and its host.

Full details: Out of Scope Changes check

Explanation

The changes remain within issue #29876. The drag bucket, header metadata, and host argument changes keep drag behavior consistent with the row that places the project in its group. The added tests validate this behavior. No unrelated product behavior is changed in the reviewed diff.

  • Fix all pre-merge checks with AI
  • 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.

This branch has not been deployed

No deployments
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

1 participant