Skip to content

feat(sidebar): the desktop and phone list workspaces from main in every build (flip 1/3) - #30790

Closed
Jinwoo-H wants to merge 39 commits into
mainfrom
sidebar-flip-a
Closed

Jinwoo-H wants to merge 39 commits into
mainfrom
sidebar-flip-a

Conversation

@Jinwoo-H

@Jinwoo-H Jinwoo-H commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 78 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2557 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1930 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​627
Prod 71 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2499 $\color{#cf222e}{\Huge{\mathbf{−}}}$​8684 $\color{#cf222e}{\Huge{\mathbf{−}}}$​6185

ELI5

The desktop sidebar and the phone's workspace list now get their list from one place: the Orca process that owns the workspaces. Until now that only happened in test builds. This PR turns it on for everyone. For most people nothing looks different. The phone list now matches the desktop list, and a few small things change on purpose (listed below).

This is part 1 of 3. Parts 2 and 3 only delete code that this PR leaves unused.

What Changed

The switch. ORCA_SIDEBAR_INPUTS_IS_MAIN is now true in every build: the desktop app, the headless server (orca serve/orcad), the CLI, the browser client, and the phone page. The switch is removed from config/dark-build-switches.cjs, so release packaging no longer refuses it. The branches that check it stay in place for now; part 3 deletes the off side.

Before and after, as a user sees it. For most users, nothing changes. These are the deliberate visible changes, all built and signed off in earlier dark PRs:

  • The phone's workspace list matches the desktop sidebar: same order, groups, labels, pins, highlight and collapse.
  • The unread dot shows when you unhide a workspace you hid (the host always raises the dot; each client mutes only its own banners and sounds).
  • New phone text: "Saving to the desktop", "Still waiting for the desktop", "Reopen to update" and "Creating workspace…".
  • Fix: opening a folder or SSH workspace on the phone now clears its unread dot. Before, it stayed.
  • Sort changes, from the approved sort-stamp rules (Recent and Smart now follow the host's stamps):
    • Recent no longer moves when a terminal exits, when you edit a comment, or when you start a terminal in the window. Only terminals started from the CLI count.
    • Sending a prompt, or an agent finishing or asking for input, moves a row even while you are looking at it.
    • Smart has no 3-second settle. Rows hold still under the pointer and during a drag.
    • "Done" means finished and unread, with no 30-minute fade.
    • After a restart, the order is the same as before it.
    • A new workspace stays on top only until something newer happens.
    • Cleanup, the jump palette and the workspace table treat "last active" as the later of the Recent time and your last visit.

Mixed versions. Desktops, servers and phones update separately. This PR adds two small things for that:

  1. The window decides who raises a workspace's unread dot with one capability check, now in owner-host-sidebar-inputs.ts. Only an older paired server makes the window raise it. If the user reads the workspace while that check is still in flight, the late raise is dropped, both for opening it and for "Mark as read". Before, the dot could stick.
  2. On the phone, "Reopen to update" now shows above the rows the page already has, not instead of them. This only happens when a desktop or server is downgraded under a phone page it served earlier.
Pair What happens
New desktop, new paired server Both use the new path.
New desktop, older paired server Rows come from the server's worktree.ps, which it has always had. The window raises the unread dot itself, because the server does not advertise workspace-unread.host-raises.v1. Recent order works as before: the window never stamped a server's rows on terminal activity, before or after. Known: in Smart sort, an older server's rows sort by Recent, with no Needs you / Working groups, because older servers write no Smart stamps. This lasts until the server updates.
Older desktop (or older browser client), new host The host sees no host-raises capability from that client and leaves raising to it while it is connected. Unread clears still work. Older clients ignore new optional fields such as sortedByStamps.
Older phone page, new desktop The desktop still serves the old phone methods (worktree.ps, mobileRelay.hosts.*). The paired server rows behind them now come from main's own copy.
New phone page, older desktop Not possible normally: each host serves its own page, and pages are cached per host. Only after a downgrade (failed download, or offline at open) does a newer cached page meet an older host. It then shows the cached rows under "Reopen to update". Reopening downloads the host's own page.
SSH Same build as the desktop: the desktop's main owns SSH workspaces' stamps and unread.
Headless server, no window Main raises unread and writes sort stamps itself (measured earlier: no measurable cost).

Known gaps with mixed versions:

  • A phone page from before this change that is already open, in Smart sort, shows a frozen order until it reloads. It does not read the new stamps.
  • Paired-server rows depend on that server answering worktree.ps within 5 s (server-worktree-fetch.ts:7). A slower server keeps its last rows until the next refresh.

Tests. The unit tests that encoded the old behaviour (the window raising unread for local workspaces, settle timers, the old Smart sort, comment stamping) were rewritten or deleted:

  • Rewritten: tests whose logic still runs for older paired servers now run against an "older host". The WorktreeList.* render tests draw from a feed built from their store, the way the desktop now does.
  • Deleted: tests for behaviour that no longer exists anywhere. The old sidebar goldens are deleted too: the stamp-order goldens already cover the new order.
  • Mobile RPC goldens were re-recorded for the page's feed reads.

Why

The owner chose to land this series dark behind one build switch and flip it in one PR. I split the flip into three:

  • Part 1 (this PR): behaviour only, small product diff (about +84/−65 outside tests and goldens).
  • Part 2: deletes the phone's own list code.
  • Part 3: deletes the switch and the desktop's dead paths.

Splitting this way lets a reviewer check the mixed-version behaviour here, and then check parts 2 and 3 as pure deletions.

I kept the switch's branches here instead of deleting the switch at once. Deleting it at once would have mixed about 60 branch removals and several thousand deleted lines into the behaviour review.

Linked Issue

N/A (maintainer; sidebar list series, flip of #30050)

Visual Proof

Before and after, from hidden windows (CDP screenshots) and the real phone page served by the hidden desktop. Kept locally for the owner: ~/orca-lanes/sidebar-flip-shots/index.html.

Testing

  • I manually tested these changes locally

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

  • Full unit suite: the only failures left also fail on origin/main on this machine (fish shell, pg in cloud/, process-host rebuilds, orcad forked children, /private/var paths, the macOS CLI launcher).

  • Full mobile suite, pnpm tc, mobile tsc, oxlint, anti-slop, changed-code quality, focus-read and max-lines ratchets, oxfmt.

  • e2e, hidden windows (ORCA_BACKGROUND_LAUNCH=1): 34 sidebar, unread, sort-stamp, phone-feed, lineage and notification specs. 60 passed, 6 skipped (switch-off-only tests that part 3 deletes), 1 failed (layout-compat-old-phone-on-new-server: no darwin baseline, same on main).

  • sidebar-desktop-reads-main now runs in every build. Two timing bugs in the test, neither in the product:

    • It picked the sidebar's "last" row before the list had drawn its end (1 in 3 on origin/main with the switch on).
    • On Linux CI it never saw the drag's input: a native drag ends in a drop event, with no mouseup, so no frame counted as "after the drop". It now counts the drop too.
  • sidebar-phone-feed-list compared the phone and desktop right after a pin, while both still showed the old list. The phone hears the change about 300 ms after the desktop. Each step now waits for both to show the change (the Pinned header, the extra highlight, the fold).

  • New tests:

    • a read or "Mark as read" before the owner check cancels the window's raise (fails without the fix);
    • the phone keeps its rows under "Reopen to update";
    • sidebarInputsChanged reaches the renderer.
  • Folder and SSH unread clear on the phone: orca-runtime-workspace-unread.test.ts "clears a folder workspace and an SSH worktree on their own records when the phone opens them" now runs in the shipped build.

Review

Two independent reviews, one of the PR and one of each fix. Applied:

  • The window's Recent stamp for older paired servers was dead code. Terminal events for a server's panes never reach it, before or after this PR. I removed it instead of keeping it behind the check.
  • "Mark as read" also drops a pending raise.
  • Removed tests the change made vacuous.

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

Security: no new surface. The methods the phone page calls were already allowlisted for the paired desktop. Cross-platform: no platform-specific code. Performance: measured earlier in the series (sidebar-perf/results-flip.md).

Checklist

  • This PR is small and focused (behaviour diff is small; test churn is large)
  • 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)

E2E Test added 5 commits October 19, 2026 12:24
…p, 1/3)

The desktop window and the phone page now list workspaces from main's
sidebar feed in shipped builds. ORCA_SIDEBAR_INPUTS_IS_MAIN is true
everywhere and the switch leaves the dark-build guards; the follow-ups
delete the switch-off paths.

Mixed versions: the window still raises the unread flag and stamps
Recent for rows of an older paired server (one capability check); a read
cancels a raise still waiting on that check; the phone keeps its rows on
screen under "Reopen to update". Switch-off unit tests are rewritten to
run against an older host or deleted where the behaviour is gone.
…p no stamp path; Mark as read also drops a pending raise
@coderabbitai

coderabbitai Bot commented Oct 19, 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.

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
📝 Walkthrough

The diff removes ORCA_SIDEBAR_INPUTS as a build-time switch and makes the main sidebar-input producer active in every build. Workspace unread handling now checks owner-host capability and records reads during pending checks. Sidebar feed, sorting, mobile reopening, RPC recordings, and end-to-end tests are updated. Additional tests cover visit timestamps, terminal scanning, runtime routing, and deferred attention state.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to d4b5c

Unread state can be applied to the wrong host’s workspace or lost when same-ID rows coexist. Bind pending decisions to their original host before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 42.19% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 64 functions across 58 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 identifies the main change: enabling the desktop and phone workspace lists from main in every build. It is specific and concise enough for project history.
Description check Passed The description is comprehensive and follows the required structure. It explains the user-visible changes, implementation, rationale, mixed-version behavior, linked issue context, testing, risks, and …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description is comprehensive and follows the required structure. It explains the user-visible changes, implementation, rationale, mixed-version behavior, linked issue context, testing, risks, and checklist items. The visual proof references local screenshots rather than attached artifacts, but the overall description is substantially complete.

✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • 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.

@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: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1d7392ee-f938-4433-822c-6282259a63f9
📥 Commits

Reviewing files that changed from the base of the PR and between 40ac8e5 and e60f563.

📒 Files selected for processing: 80
  • config/dark-build-switches.cjs
  • config/scripts/dark-build-switches.test.mjs
  • config/scripts/generate-rpc-params-catalog.mjs
  • config/scripts/mobile-web-bundle-manifest.test.mjs
  • config/scripts/orcad-mobile-web-bundle.test.mjs
  • config/scripts/verify-packaged-mobile-web-bundle.test.mjs
  • mobile/rpc-foundation/goldens/host-worktree-actions-pin-open-delete.json
  • mobile/rpc-foundation/goldens/matrix-host.view-settings-ui.set-1.json
  • mobile/rpc-foundation/goldens/matrix-host.worktree-actions-worktree.activate-1.json
  • mobile/rpc-foundation/goldens/matrix-host.worktree-actions-worktree.rm-1.json
  • mobile/rpc-foundation/goldens/matrix-host.worktree-actions-worktree.set-1.json
  • mobile/src/host-screen/host-workspace-list-reopen.test.tsx
  • mobile/src/host-screen/host-workspace-list.tsx
  • mobile/src/host-screen/use-host-list-sections.ts
  • mobile/src/host-screen/use-host-project-sections.test.tsx
  • mobile/src/worktree/host-workspace-list-states.tsx
  • src/cli/runtime/websocket-transport.test.ts
  • src/main/runtime/mobile-rpc-allowlist.test.ts
  • src/main/runtime/orca-runtime-tests/mobile-creation-and-orchestration-part-04.spec.ts
  • src/main/runtime/orca-runtime-tests/terminal-side-effect-facts.spec.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-mobile-method-routing.test.ts
  • src/main/runtime/sidebar-inputs/sidebar-inputs-switch-off.test.ts
  • src/main/window/attach-main-window-services.test.ts
  • src/main/window/main-window-service-stubs.test-fixture.ts
  • src/main/window/runtime-window-lifecycle-file-open.test.ts
  • src/main/window/runtime-window-pane-title-notification.test.ts
  • src/renderer/src/attention/hidden-workspace-notification-mute.test.ts
  • src/renderer/src/components/native-chat/StructuredAgentSessionAttentionBridge.test.tsx
  • src/renderer/src/components/native-chat/structured-attention-dispatch.test.ts
  • src/renderer/src/components/sidebar/WorktreeList.card-memo-stability.test.tsx
  • src/renderer/src/components/sidebar/WorktreeList.empty-project-rows.test.ts
  • src/renderer/src/components/sidebar/WorktreeList.folder-workspace-rows.test.ts
  • src/renderer/src/components/sidebar/WorktreeList.group-headers.test.ts
  • src/renderer/src/components/sidebar/WorktreeList.lineage-agent-expansion-coupling.test.tsx
  • src/renderer/src/components/sidebar/WorktreeList.lineage-child-card.test.ts
  • src/renderer/src/components/sidebar/WorktreeList.lineage-child-real-card.test.tsx
  • src/renderer/src/components/sidebar/WorktreeList.status-lane-lineage-drop.test.tsx
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/group-by-none.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/group-by-pr-status.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/group-by-repo.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/group-by-workspace-status.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/scenario-hide-default-branch.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/scenario-hide-sleeping.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/scenario-host-sections.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/scenario-name-order.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/scenario-new-external-inbox.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/scenario-repo-filter.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/scenario-single-host-scope.txt
  • src/renderer/src/components/sidebar/__goldens__/sidebar-list/unranked-rows-last.txt
  • src/renderer/src/components/sidebar/sidebar-list-goldens.test.tsx
  • src/renderer/src/components/sidebar/sidebar-list-parity.test-fixture.ts
  • src/renderer/src/components/sidebar/sidebar-list-parity.test.tsx
  • src/renderer/src/components/sidebar/window-state-sidebar-feed.test-fixture.ts
  • src/renderer/src/components/sidebar/workspace-view-evaluator-parity.test.ts
  • src/renderer/src/components/sidebar/worktree-list-lineage-card-test-harness.ts
  • src/renderer/src/components/sidebar/worktree-list/listing/use-sort-order.test.tsx
  • src/renderer/src/components/terminal-pane/terminal-handle-links.test.ts
  • src/renderer/src/hooks/useIpcEvents-terminal-create-surfacing.test.ts
  • src/renderer/src/hooks/useUnreadDockBadge.test.ts
  • src/renderer/src/lib/unread-badge-count-selector.test.ts
  • src/renderer/src/lib/workspace-surface-reveal.test.ts
  • src/renderer/src/lib/worktree-activation-created-agent.test.ts
  • src/renderer/src/store/settled-sort-epoch.test.ts
  • src/renderer/src/store/slices/folder-workspace-activation-and-activity.test.ts
  • src/renderer/src/store/slices/store-active-worktree-selection.test.ts
  • src/renderer/src/store/slices/store-session-terminal-activity.test.ts
  • src/renderer/src/store/slices/workspace-attention/workspace-attention-slice.ts
  • src/renderer/src/store/slices/workspace-attention/workspace-unread-characterization.test.ts
  • src/renderer/src/store/slices/workspace-attention/workspace-unread-flag-host-raises.test.ts
  • src/renderer/src/store/slices/workspace-attention/workspace-unread-flag.ts
  • src/renderer/src/store/slices/workspace-attention/workspace-unread-on-screen-characterization.test.ts
  • src/renderer/src/store/slices/workspace-attention/workspace-unread-removal-characterization.test.ts
  • src/renderer/src/store/slices/workspace-attention/workspace-unread-screen-moves-characterization.test.ts
  • src/renderer/src/store/slices/worktrees-activity-persistence.test.ts
  • src/renderer/src/store/slices/worktrees-unread-state.test.ts
  • src/renderer/src/store/slices/worktrees/listing/owner-host-sidebar-inputs.ts
  • src/shared/sidebar-inputs-switch.ts
  • src/shared/sidebar-list/smart-sort.test.ts
  • src/shared/workspace-last-active.test.ts
  • tests/e2e/helpers/sidebar-inputs-build.ts
💤 Files with no reviewable changes: 14
  • src/renderer/src/components/sidebar/sidebar-list-goldens.test.tsx
  • src/renderer/src/components/sidebar/goldens/sidebar-list/group-by-pr-status.txt
  • src/renderer/src/components/sidebar/goldens/sidebar-list/scenario-repo-filter.txt
  • src/renderer/src/components/sidebar/goldens/sidebar-list/scenario-hide-sleeping.txt
  • src/renderer/src/components/sidebar/goldens/sidebar-list/unranked-rows-last.txt
  • src/main/runtime/sidebar-inputs/sidebar-inputs-switch-off.test.ts
  • src/renderer/src/components/sidebar/goldens/sidebar-list/scenario-hide-default-branch.txt
  • src/renderer/src/components/sidebar/goldens/sidebar-list/scenario-new-external-inbox.txt
  • src/renderer/src/components/sidebar/goldens/sidebar-list/scenario-name-order.txt
  • src/renderer/src/components/sidebar/goldens/sidebar-list/group-by-none.txt
  • src/renderer/src/components/sidebar/goldens/sidebar-list/scenario-single-host-scope.txt
  • src/shared/workspace-last-active.test.ts
  • src/renderer/src/store/settled-sort-epoch.test.ts
  • src/renderer/src/store/slices/store-active-worktree-selection.test.ts

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

Comment on lines +895 to +926
"state": "6b0066cfa6bd",
"effects": ["ca91c555e48e", "da6c9e1a507d", "a8d6bd902609", "4fb40764fe7e"]
}
},
{
"id": "host-worktree-actions-pin-open-delete.inner-false-string-error:settled",
"observation": {
"sender": ["1c5bb0f1882e", "40f6e6f5e2cd", "47aef1f5918f"],
"payloads": ["9a2d39ba928f", "47602c8e53e9", "0eb00a9a894a"],
"sender": ["69cdbfebf29e", "49cbf9dfccdb", "1ef0289204ce"],
"payloads": ["adc3282cbaf6", "facde8e4bc87", "7a2251b6d74d"],
"settlements": {
"mount": "eb79a9b3682a",
"toggle-pin": "eb79a9b3682a",
"open-session": "eb79a9b3682a",
"delete": "eb79a9b3682a"
},
"state": "38c12b517ad0",
"effects": [
"ca91c555e48e",
"da6c9e1a507d",
"14ce9f03298e",
"047f5e4dfc2b",
"989153283864",
"04adcc2c4239"
]
"state": "6b0066cfa6bd",
"effects": ["ca91c555e48e", "da6c9e1a507d", "a8d6bd902609", "4fb40764fe7e"]
}
},
{
"id": "host-worktree-actions-pin-open-delete.inner-false-object-error:settled",
"observation": {
"sender": ["1c5bb0f1882e", "40f6e6f5e2cd", "d15cb8748ac4"],
"payloads": ["9a2d39ba928f", "47602c8e53e9", "0eb00a9a894a"],
"sender": ["69cdbfebf29e", "49cbf9dfccdb", "49c6657ebc51"],
"payloads": ["adc3282cbaf6", "facde8e4bc87", "7a2251b6d74d"],
"settlements": {
"mount": "eb79a9b3682a",
"toggle-pin": "eb79a9b3682a",
"open-session": "eb79a9b3682a",
"delete": "eb79a9b3682a"
},
"state": "38c12b517ad0",
"effects": [
"ca91c555e48e",
"da6c9e1a507d",
"14ce9f03298e",
"047f5e4dfc2b",
"989153283864",
"04adcc2c4239"
]
"state": "6b0066cfa6bd",
"effects": ["ca91c555e48e", "da6c9e1a507d", "a8d6bd902609", "4fb40764fe7e"]

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
fd -t f use-host-worktree-actions.ts mobile --exec sed -n '1,400p' {}
rg -n -C4 "worktree\.rm|ok === false|isPinned|inner" mobile/src/host-screen/use-host-worktree-actions.ts
# Compare settled states/effects against main for the inner-refusal checkpoints
git show origin/main:mobile/rpc-foundation/goldens/matrix-host.worktree-actions-worktree.rm-1.json 2>/dev/null | python3 -c '
import json,sys
d=json.load(sys.stdin)
for c in d["recording"]["checkpoints"]:
    o=c["observation"]; print(c["id"], o.get("state"), o.get("effects"))
'
rg -n -C10 '"host-worktree-actions-pin-open-delete"' mobile/rpc-foundation/pilot-scenarios.json | rg -n "worktrees|isPinned|reply" | head -50

Repository: stablyai/orca

Length of output: 14402


Restore the row for an inner refusal.

When worktreeRemove.interpret(reply).accepted is false, handleDeleteWorktree restores the item to both lists. The inner-refusal checkpoints must therefore preserve the row and its existing isPinned value, with the corresponding restore effects.

void ownerRaisesUnreadFlag(get(), worktreeId).then((ownerRaises) => {
if (!ownerRaises) {
// A read while the owner was being asked already covers this news.
if (!ownerRaises && (clearsByWorkspace.get(worktreeId) ?? 0) === clearsBefore) {

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep a deferred unread decision bound to its original host.

If two folder rows share a workspace ID, a host change during the owner check changes which row raiseWorkspaceUnreadFlag selects. An unread event for the SSH row can then mark and persist the local row instead. A read on the local row can also increment this workspace-wide counter and discard the pending SSH raise. Capture the row’s host when the unread event arrives. Scope both the read counter and the eventual raise to that host.

…dered

The virtualizer renders after the scroll, so the old pick could take a mid-viewport row
that stays within overscan of the top and is still mounted there.

@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.

🧹 Nitpick comments: 1
tests/e2e/sidebar-desktop-reads-main.spec.ts: 1

242-257: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Poll for the final fixture row, not any mounted row near the bottom.

The predicate can return a non-final mounted row before the virtualized list finishes rendering its end. Poll for many.at(-1) so the reveal assertions cover the intended final row.

Suggested fix
-    await expect.poll(lastRowAtBottom).not.toBe('')
+    await expect.poll(lastRowAtBottom).toBe(many.at(-1))
     const bottomId = await lastRowAtBottom()

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e0fa3028-8b58-45cd-b3f7-a7a08cbfef2a
📥 Commits

Reviewing files that changed from the base of the PR and between e60f563 and b9433f9.

📒 Files selected for processing: 1
  • tests/e2e/sidebar-desktop-reads-main.spec.ts

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

E2E Test added 6 commits October 19, 2026 16:12
…x window paints rarely during a native drag; uncover rows under the setup-script card before hovering
… painted frames, and the observer watches only the sidebar
…op, so a drag in Smart lands and switches to Manual

test(e2e): the sort-stamps spec holds the pane's foreground while its posted hooks stand in for an agent

@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: 1e9967c4-648b-404c-a8af-e02e565e5232
📥 Commits

Reviewing files that changed from the base of the PR and between 4f80b8c and d4b5c4f.

📒 Files selected for processing: 7
  • src/renderer/src/components/sidebar/worktree-list/listing/use-order-hold.test.ts
  • src/renderer/src/components/sidebar/worktree-list/listing/use-order-hold.ts
  • tests/e2e/helpers/sidebar-setup-prompt.ts
  • tests/e2e/helpers/slept-workspace-probe.ts
  • tests/e2e/sidebar-desktop-reads-main.spec.ts
  • tests/e2e/sidebar-sort-stamps.spec.ts
  • tests/e2e/worktree-lineage-toggle-shortcut.spec.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.

E2E Test added 3 commits October 19, 2026 22:21
…llows the list's drop commit, instead of a timer
…d before the pane's terminal spawns are cleared by the spawn
…shed creation leaves no empty gap that closes under a drag

Main keeps listing a creation for a moment after the window's own entry ends; its row draws
nothing, so the rows below jumped 12px when it went. Also drops the order-hold change, which the
drag probe showed was not the cause.
E2E Test added 6 commits October 19, 2026 23:54
…ided for, not whichever row is on screen when the owner answers

test(e2e): the reads-main frame log records painted frames only, since every check needs one
… DOM state the input's own task commits, not from painted frames

test: the orcad teardown fixture skips a process older than its claimed parent, a stale parent pid that Windows reused for the daemon
…uest bound, so a server slower than 5 s still lists

The 5 s bound was made for the phone's synchronous catalog read, which keeps its own 5 s wait.
Nothing waits on the producer's background fetch; one runs per server and change notices during
it coalesce into one follow-up.
… the desktop does

The catalog the desktop window publishes for the phone (mobileRelay.projectSections) left out each
project's group and its order in that group, and the phone's reply parser dropped them too. The
shared list builder on the phone then filed every grouped project as ungrouped: the group header
showed with nothing under it and its projects moved down among the rest.

The catalog now carries projectGroupId and projectGroupOrder (optional, so older desktops and
pages keep the old list), and the phone draws a group header with the group icon and the desktop's
per-level indent. The parity suite now builds the phone's catalog from what the window publishes,
with a nested group, a folder-only group and an empty group; an e2e covers a group holding a repo
and a folder project, and folding it from either side.
…desktop's cards do

Workspace rows under a nested project group now step in with their project header, by what the
desktop adds over an ungrouped card: a full tree step a level in a hand-made group, and the header
step in a folder-scanned group (one level less for its folder workspaces), capped at six levels as
the headers are. The group icon takes the foreground text colour, as on the desktop.
…ved view and project catalog are in, so rows no longer regroup and move in the first moments

With the sidebar reading main's feed, main's first snapshot could land before the window had read its saved view settings and its projects, project groups and folders. The list then drew main's rows under the default grouping and sort with 'Unknown' project headers, and regrouped twice within the next ~150 ms. The main-fed list now waits until both halves are in, then draws once.
Jinwoo-H and others added 8 commits October 20, 2026 15:07
… has not answered within 500 ms

A collapse the desktop answers at once no longer flashes a spinner on its section header.
…omain

The window-state feed fixture skips main's projectCatalog domain, which reads servers asynchronously
and which the window does not list from; the phone's grouped-project test builds its catalog with
main's shared buildSidebarFeedCatalog.
…ished project catalog

That catalog is served only with the switch off; switched on, main answers mobileRelay.projectSections
from its own catalog (orca-runtime-project-catalog.test.ts).
…st, else the desktop's selection (#31081)

* fix(mobile): highlight the workspace this phone last opened on the host, never the desktop's selection

* fix(mobile): a list tap highlights its row in the list's own page, and an app list follows a session page's write

* fix(mobile): show the desktop's selection until this phone opens a workspace on the host

* fix(mobile): fall back to the desktop's selection when this phone's row is not shown, and keep storage off the e2e list helper's imports

* fix(mobile): the host list's auto-scroll stays still while a search is in progress

* chore(mobile): satisfy React Doctor on the changed lines

---------

Co-authored-by: E2E Test <e2e@test.local>
…st the known names

Main's zod bump leaves the root and mobile on different zod copies, so the reply's inferred type
arrived as {} in the e2e typecheck.
…eeping with main's feed (#31249)

* fix(sidebar): a slept workspace leaves the list at once under Hide sleeping with main's feed

The window stops its own PTYs when it sleeps a workspace, so main never saw
the sleep and read the exits as a reconnect gap: its retention kept the row
for the 1.5 s settle window. The window now tells main which workspaces it
holds asleep, before any kill, and main treats their inactivity as definitive.

* fix(sidebar): only this host's window may report its slept workspaces

Review: a phone or paired desktop could otherwise send a slept set and skip the
reconnect grace for any id. Also states that the report is fire-and-forget.
Jinwoo-H and others added 4 commits October 20, 2026 20:45
… the desktop's (#31301)

* fix(mobile): the phone's host list never flashes a list shaped unlike the desktop's

Before the host's sidebar feed installed, the feed-built list drew worktree.ps's rows flat:
no Pinned section, no project groups, no host badges, in another order, then snapped to the
desktop's list. It now waits for the feed (the loading spinner), or opens on the host's last
installed feed when this page has one. An older desktop without the feed still gets the
worktree.ps list.

* test(mobile): name the first-frame test's values for their role
…every feed read fails (#31309)

Once the sidebar feed reader spent its retries without installing anything, the list stayed
on the spinner until the next stream-ready or foreground, though worktree.ps had rows. The
reader now says it gave up, and the list draws the worktree.ps rows (or keeps the host's
last installed feed) until a later read installs.
…ist at once with main's feed (#31306)

* fix(sidebar): closing a last tab and deleting a workspace update main's list at once

Closing a workspace's last terminal tab left main counting its PTY live until the
exit arrived, so Hide sleeping kept the row ~300-500 ms longer than the window's own
list. Main now stops counting a PTY it was asked to stop once no pane shows it, and
re-reads activity on the graph change and on the stop request.

A deleted row came back as a plain card until main's worktree re-scan landed. The
window's pending layer now holds a removed row out until main drops it (the overlay
moves to sidebar-pending-overlay.ts to keep the layer under the line limit).

* fix(sidebar): a closing terminal stays unverifiable when out of contact, and a failed stop reads running

- An unverifiable verdict now wins over "closing": only a live terminal is dropped once
  it was asked to stop and no pane shows it.
- A failed or refused stop (renderer close, stop-and-wait) takes back its stop request,
  so a terminal that runs on is counted again.
- Re-creating a just-removed id on the same host drops that removal's hold, so the new
  row is listed at once.
@Jinwoo-H

Copy link
Copy Markdown
Contributor Author

Replaced by #31466, which landed the fixes from this branch without the switch flip or the interim bridges. The flip itself comes later as its own one-line PR.

@Jinwoo-H Jinwoo-H closed this Oct 21, 2026
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