Skip to content

feat(sidebar): consolidate same-branch workspaces across hosts - #14

Merged
nplez1 merged 1 commit into
nplez1/mainfrom
nplez1/Stacked-Branches
Sep 21, 2026
Merged

nplez1 merged 1 commit into
nplez1/mainfrom
nplez1/Stacked-Branches

Conversation

@nplez1

@nplez1 nplez1 commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner
Files Added Deleted Net
Test 6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​459 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​459
Prod 26 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​588 $\color{#cf222e}{\Huge{\mathbf{−}}}$​55 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​533

ELI5

If the same project is checked out on two computers and both have a workspace on the same git branch, Orca used to show two separate cards in the sidebar. With the new "Group by branch" setting (on by default), those become one card with a small dropdown to switch between the computers.

What Changed

Before: workspaces from one project on two hosts rendered as one card per host. When a project spanned hosts, each card carried a host chip, but nothing tied the two same-branch cards together.

After: in Project grouping, workspaces in one project that sit on the same branch on two or more hosts render as a single card. The card header has a host dropdown (one row per host, local first). Selecting a host swaps which workspace backs the card — status, branch, PR, agents, tabs and activation all follow it. Until the user picks one, the card follows whichever host is currently active. A new Workspace options checkbox, "Group by branch", controls this; it appears only in Project grouping, is on by default, and turning it off restores today's list exactly.

Mechanism:

  • A row-merge pass over the built sidebar rows (mergeSameBranchRows) collapses depth-0, non-lineage rows in the same project section that share a branch, but only when the branch maps to exactly one worktree per host and spans at least two hosts. If a host has two checkouts of the branch, the whole branch is left unmerged rather than hiding one behind the dropdown.
  • The merged row carries branchGroup (host-ordered members) and a stable branchGroupKey. The card renders the selected member's worktree, so every existing per-worktree behavior — activation, agent rows, tabs, context menu — works unchanged.
  • The host choice lives in an in-memory branchGroupHostByKey slice field keyed by the branch-group key, so it survives sidebar virtualization. It is deliberately not persisted.
  • The branch-group derivation (getBranchGroupKeyForWorktree / getBranchGroupMembersForWorktree) is shared, so the sidebar merge and the card cannot disagree.
  • Reveal and selection match any sibling, so revealing a host's workspace still lands on the merged card.
  • The preference is plumbed through the usual UI-state path (types, RPC schema, hydration, persisted-UI write baseline), with absence meaning on.

Why

The alternatives were a per-host selector on every card regardless of grouping, or a separate "grouped" view. Merging only when the branch is unambiguous (one checkout per host) and only in Project grouping keeps the change local to the row model and the card, and makes the setting a pure presentation toggle with no data migration.

Cross-host aggregation on a single card — showing every host's agents or tabs at once — was explored and deliberately dropped. It requires a cross-host tab model the tab store does not have (unifiedTabsByWorktree is keyed by bare worktreeId, and host resolution ignores tab.executionHostId), so it is a separate, much larger change. None of that work is in this PR.

Linked Issue

nplez1/orca has GitHub Issues disabled, so there is no issue to link and none was requested upstream. This is a fork-only enhancement.

Visual Proof

Not attached. Reproducing the merged card needs the same project checked out on two hosts, and this environment has no multi-host fixture and no Orca Electron CDP capture tooling. A real-Radix render test listed under Testing exercises the dropdown opening in a portal; a before/after capture should be added by a reviewer who has two hosts configured.

Testing

  • pnpm tc, pnpm lint, the changed-code quality gate, pnpm run build:electron-vite, and verify:localization-extraction all pass.
  • Test suites: sidebar + store slices + tab-group + tab-bar — 732 files / 6490 tests.
  • New coverage: the merge rules and selection resolver (branch-groups.test.ts), the host dropdown against mocked and real Radix primitives (two WorktreeBranchHostSelect tests), hydration defaults (ui-hydration-view-layout.test.ts), the persisted-UI write baseline, and the reveal lookup for a merged row.

Platforms: verified on macOS only (typecheck, tests, lint, build). No platform-specific code was added; the change is renderer-only and host-agnostic.

AI Disclosure

Implemented by an AI coding agent (pi, model deepseek-flash) on behalf of the fork author.

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Security: no new IPC or network surface; the setting is a presentation preference.
  • Cross-platform: no path or shortcut handling was added.
  • SSH/remote: the merge is host-aware and activation routes by the selected host's execution host id; no local fallback was introduced.
  • Mobile: untouched.
  • Backwards compatibility: the preference is optional and absence means on, so old profiles opt in; no data migration.
  • Performance: the merge runs once per row-model rebuild inside the existing memo, and only in Project grouping with the all-hosts view.
  • Two new locale keys (mergeSameBranch, WorktreeBranchHostSelect.label) will need coverage when the translation pipeline next runs.

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 (see Visual Proof)
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, and pnpm test pass (build covered by build:electron-vite)

Add a Workspace options setting, "Group by branch" (on by default, shown only
in Project grouping), that merges workspaces in one project which sit on the
same git branch on two or more hosts into a single card.

The merged card carries a host dropdown in line with the title, one row per
host. Picking a host swaps which workspace backs the card — status, branch,
PR, agents, tabs and activation all follow it — and until the user picks one,
the card follows the active workspace. There is no cross-host aggregation: a
consolidated card always shows exactly one host.

Only a branch that maps to exactly one worktree per host and spans at least two
hosts is merged; pinned, lineage, detached-HEAD and single-host rows are left
untouched, so turning the setting off restores today's list.

The host choice lives in an in-memory branchGroupHostByKey slice field so it
survives sidebar virtualization, and the branch-group derivation is shared
between the sidebar merge and the card so the two cannot disagree.
@nplez1
nplez1 merged commit 353c359 into nplez1/main Sep 21, 2026
58 of 60 checks passed
@nplez1
nplez1 deleted the nplez1/Stacked-Branches branch October 24, 2026 03:23
nplez1 pushed a commit that referenced this pull request Oct 30, 2026
…tablyai#32355) (stablyai#36031)

* fix(native-chat): keep a huge unbroken paste from freezing the chat

A long run with no whitespace ("xxxx…", "0000…", "abab…") wrapped with
kerning makes Chromium reshape it at every line, quadratic in run length:
128 KiB stalls 2.4 s, 1 MiB 19 s, 5 MiB never finishes. ProseMirror forces
that layout on paste and on each keystroke, so the composer freezes in
every terminal-backed chat, including paired servers and WSL. A sent
message stalls the transcript the same way: its fold clips the text but
still lays it all out (128 KiB: 2.5 s).

Runs over 256 characters now render with font-kerning: none, so prose of
any length and Shift+Enter lines keep kerning:
- composer: inline decorations, updated only for the lines a transaction
  touches (a keystroke beside 8,000 decorated lines ~1 ms, not ~190 ms)
- user messages in the transcript and expanded queued cards: the whole
  text when it holds such a run

Text, drafts and what is sent are unchanged. After: 1 MiB single line
84 ms, 5 MiB 312 ms (keystroke 248 ms), 1 MiB of 4000-char lines 50 ms
(was 2.3 s), transcript 128 KiB 80 ms. Worst case left kerned, 256-char
runs: 1 MiB 242 ms, 5 MiB 1.2 s, like 1 MiB of short lines (291 ms).

Fixes stablyai#32355

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

* fix(native-chat): keep kerning on long CJK and Thai paragraphs

These scripts have no spaces but Chromium can break between their
characters, so a long paragraph is not one unbreakable run. Count
ideographs, kana, Hangul and Thai as break opportunities so such text
keeps kerning.

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

* fix(native-chat): rescan only the edited run, and wait out IME compositions

Splitting one run inside a block of thousands replaced every decoration
in that block, and ProseMirror's removal is quadratic: 1,411 ms for one
space in a paragraph of 8,000 long runs. A change now rescans only from
the run edge before it to the run edge after it, and removes only the
decorations that overlap: ~1 ms for the same edit.

Decorations are only mapped while an IME composition is in progress, so
the composing text node is not re-rendered. The deferred range is
rescanned with the next non-composing change, or on compositionend when
none follows.

The keystroke-timing test is replaced by deterministic ones: unaffected
decorations are reused and the result equals a full scan; a composition
defers the rescan and compositionend runs it.

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

* fix(native-chat): never split a CJK code point, and treat NBSP as glue

A supplementary ideograph (U+20000-U+3FFFF) broke a run at its high
surrogate only, so the decoration began on the low surrogate and split
the pair across elements. Both the run scan and the leftward edge scan
now treat the pair as one break.

NBSP joins words for Chromium's line breaker (UAX #14 class GL), so an
NBSP-joined paste is one unbreakable run and must lose kerning; drop it
from the break set. No other non-breaking characters were in it.

Drop a composer test whose coverage the decoration reuse test already
has.

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

* fix(native-chat): classify surrogate pairs whole at a rescan's edge

Two lone-surrogate edge cases made the incremental rescan disagree with
a full scan:
- an edit completing a pair at the window start (a lone high surrogate,
  then a pasted low surrogate and run) began the decoration on the low
  surrogate; the window now widens past the whole pair
- a lone low surrogate after any break counted as a pair end, dropping
  it from the run; that now requires a preceding high surrogate

A seeded fuzz also found inclusive decoration edges mapping across a
joined block's boundary, where ProseMirror cannot remove them; the
decorations are no longer inclusive.

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

---------

Co-authored-by: Manan Chataut <mananchataut@gmail.com>
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.

1 participant