Skip to content

feat(sidebar): put main's sidebar inputs behind the ORCA_SIDEBAR_INPUTS dark build switch - #30050

Merged
Jinwoo-H merged 1 commit into
mainfrom
jinwoo/sidebar-inputs-switch
Oct 17, 2026
Merged

Jinwoo-H merged 1 commit into
mainfrom
jinwoo/sidebar-inputs-switch

Conversation

@Jinwoo-H

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

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​635 $\color{#cf222e}{\Huge{\mathbf{−}}}$​45 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​590
Prod 20 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​310 $\color{#cf222e}{\Huge{\mathbf{−}}}$​120 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​190

ELI5

Over the last few PRs, the main process started building the sidebar's inputs itself (agent and terminal activity per workspace, PR state, focus and pins, and a live list of each paired server's workspaces) and checking them against what the window computes. None of that is used by the sidebar yet, but it already runs in every build. This PR puts all of it behind a build switch, ORCA_SIDEBAR_INPUTS=main, exactly like the existing layout-owner switch. Without the switch (every shipped build), none of it is built, started, or registered, and the phone's server list goes back to fetching a server's workspaces only when the phone asks.

What Changed

  • One switch, read in one place. src/shared/sidebar-inputs-switch.ts exports ORCA_SIDEBAR_INPUTS_IS_MAIN, filled by the electron-vite define and absent (off) in tests, the CLI and the web client. Every gate branches on that constant.
  • One build helper for both switches. config/layout-owner-build.cjs becomes config/dark-build-switches.cjs, listing both switches. Each switch gets its define, its out/main marker, and the packaging and orcad-template refusals. The layout switch keeps its variable, marker file name, marker text and error messages.
  • Off means not built, not started, not registered.
    • The activity and PR facts layer is chosen as the runtime's base class only when the switch is on, so no hook-server subscription and no tab or PTY overrides run when it is off. It is reached through runtime.sidebarFacts, which is null when off.
    • runtime.pairedServerRows (the live server rows, their per-server event streams and the 30 s refresh) is null when off.
    • The sidebarInputs.* RPC methods are registered only when the switch is on.
    • The window's shadow comparison is installed only when the switch is on.
  • Phone server list, switch off. This restores the fetch-on-demand behaviour that feat(sidebar): main keeps one live set of paired-server rows, checked against the window in dev and test #30027 replaced. OnDemandServerWorktreeRows is the old catalog cache moved behind a small interface (ServerWorktreeRows), and the producer implements the same interface. The old catalog test file is restored next to it and runs against it, unchanged apart from the new constructor argument and two stub fields. The one difference: a removed server's late answer is dropped when it arrives, where it used to be dropped at the next list read. The result is the same, because server ids are never reused.
  • Kept live: the PR-cache save-timing fix from feat(sidebar): main produces per-workspace PR facts, checked against the window in dev and test #30026, and the refactors in the series (shared cache keys, moved helpers).
  • Pattern for feat(sidebar): main keeps a registry of workspace creations in progress, checked against the window in dev and test #30021 and later producers (also stated in the switch file):
    • Add a runtime field, ORCA_SIDEBAR_INPUTS_IS_MAIN ? new Registry() : null.
    • Put its RPC in SIDEBAR_INPUT_METHODS, which is already gated.
    • Add its shadow to the gated list in main.tsx.

Why

The owner asked for the new sidebar work to land on main without being part of runtime behaviour. I copied the layout-owner switch's shape because it is already proven and already enforced at packaging time. I generalized the build helper rather than copying it, so that each call site (vite config, orcad entry build, packaging, orcad template, RPC catalog generator) keeps a single call that covers both switches. For the facts layer, a switch-chosen base class was the only option that keeps strict types, needs no casts, and leaves no override running when the switch is off. Guarding each of the seven overrides would still have left their bookkeeping in place.

Linked Issue

Follow-up to #29980, #30018, #30026 and #30027.

Visual Proof

N/A. With the switch off, nothing the user sees changes, and the phone's server list behaves as it did before #30027. With the switch on, behaviour matches main today. The checks below cover both.

Testing

  • Bundle comparison, switch off, before (37f23b9) vs after, built in the same lane path:

    • Preload: byte-identical.
    • Renderer: only the entry chunk changes, plus the embedded build commit. Its string literals lose exactly the shadow-comparison strings ([sidebar-shadow] ..., sidebarInputs.*, the domain names), and none are added.
    • Main, unminified, region by region: the gated modules change only at their switch points (...[], pairedServerRows = null, OrcaRuntimeSidebarBase = OrcaRuntimeWithMigrationCatalog, sidebarFactsRuntime folds to return null). The sidebarInputs RPC module, the desktop-facts read and the server-rows producer class are gone. The restored on-demand class is added.
    • Main, minified: sidebarInputs. 8 → 0, producer internals 2–4 → 0. The activity and PR layer classes stay as uncalled class definitions, because the bundler keeps classes with a base class; they are never constructed.
    • Other diffs come from the bundler re-splitting chunks.
  • Bundle comparison, switch on: preload is byte-identical and the renderer differs only by the embedded build commit.

  • Hidden-app e2e (ORCA_BACKGROUND_LAUNCH=1, headless, CDP only), on six specs: the three sidebar specs, sidebar-review-facts, paired-server-rows-shadow and paired-mobile-desktop-mirror-workspace-visibility.

    • Switch on (ORCA_SIDEBAR_INPUTS=main): 6 passed, with 0 shadow mismatches.
    • Switch off: 4 passed, and the two that read sidebarInputs.* were skipped. The shadow helper also asserts that no comparison is installed.
    • Switch off, phone mirror visibility: passes on the restored fetch-on-demand path.
  • Unit tests:

  • CI: no added CI cost. CI e2e builds have the switch off, so the shadow assertions check that nothing is installed and the two RPC specs skip.

  • Gates: pnpm tc, oxlint, oxfmt, check:code-quality:changed, verify:rpc-params-catalog, the focus-read ratchet and the runtime-electron ratchet all pass. The runtime-rpc and cross-version-wire suites fail the same 19 tests with and without this change; those failures come from the local runtime, which has no node:sqlite and has socket timeouts.

  • I manually tested these changes locally

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

Review

An independent review of the elegance and bugs found no bugs. I applied its design notes:

  • ServerWorktreeRows is now a plain interface.
  • The fetch constants moved to a neutral module.
  • The unreachable null fallbacks in the RPC handlers became an explicit throw.
  • The pattern rule is stated in the switch file.

A second review, of the changes after those fixes, also found no bugs. Following it, runtime.sidebarFacts is now a getter rather than a field that holds the runtime itself. That change also fixed a problem I found myself: the first version value-imported the facts layer from the RPC methods module, which pulled the whole runtime into every importer of the method list. A cross-version wire test caught this through a leaked global.

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

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)

@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

The build configuration now handles ORCA_LAYOUT_OWNER and ORCA_SIDEBAR_INPUTS through shared define, marker, and packaging-guard helpers. The sidebar-input switch gates runtime facts, paired-server rows, RPC methods, and renderer shadow comparison setup. Relay catalogs use on-demand worktree-row fetching when paired-server rows are unavailable. The new fetch path validates replies, stamps host IDs, caches rows, and handles stale or failed fetches.

Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to e005d

After a server re-pairs, the phone can temporarily lose its last worktree rows if an older fetch finishes late. This is a bounded risk, but the cache-write guard should be fixed before merge if practical.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 31.03% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 29 functions across 29 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 main sidebar inputs behind the ORCA_SIDEBAR_INPUTS dark build switch.
Description check Passed The description is complete and focused. It includes the ELI5 summary, scope, rationale, linked issue context, visual-proof explanation, detailed testing, review notes, compatibility considerations, a…
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 complete and focused. It includes the ELI5 summary, scope, rationale, linked issue context, visual-proof explanation, detailed testing, review notes, compatibility considerations, and checklist status. The template's Fixes # field is not populated with a direct issue link, but the description references the relevant follow-up issues and is otherwise substantially complete.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • 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: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 80e509b5-6417-4d22-b544-088b7e10ef8c
📥 Commits

Reviewing files that changed from the base of the PR and between 90c1c0a and e005d1d.

📒 Files selected for processing: 31
  • config/dark-build-switches.cjs
  • config/electron-builder.config.cjs
  • config/layout-owner-build.cjs
  • config/scripts/build-orcad-template.mjs
  • config/scripts/dark-build-switches.test.mjs
  • config/scripts/generate-rpc-params-catalog.mjs
  • config/scripts/headless-detector-compiler-cache.test.mjs
  • config/scripts/layout-owner-build.test.mjs
  • config/scripts/orcad-entry-build.mjs
  • electron.vite.config.ts
  • src/main/orcad/orcad-entry.ts
  • src/main/orcad/orcad-push-startup.test.ts
  • src/main/runtime/mobile-desktop-relay/mobile-relay-host-catalog.ts
  • src/main/runtime/mobile-desktop-relay/on-demand-server-worktree-rows.test.ts
  • src/main/runtime/mobile-desktop-relay/on-demand-server-worktree-rows.ts
  • src/main/runtime/mobile-desktop-relay/server-worktree-fetch.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/rpc/methods/index.ts
  • src/main/runtime/rpc/methods/sidebar-inputs.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-pairing.ts
  • src/main/runtime/sidebar-inputs/orca-runtime-workspace-activity-facts.test.ts
  • src/main/runtime/sidebar-inputs/orca-runtime-workspace-review-facts.test.ts
  • src/main/runtime/sidebar-inputs/orca-runtime-workspace-review-facts.ts
  • src/main/runtime/sidebar-inputs/paired-server-rows-producer.ts
  • src/main/runtime/sidebar-inputs/sidebar-inputs-switch-off.test.ts
  • src/main/startup/main-process-runtime-service.ts
  • src/renderer/src/main.tsx
  • src/shared/sidebar-inputs-switch.ts
  • tests/e2e/helpers/sidebar-shadow.ts
  • tests/e2e/paired-server-rows-shadow.spec.ts
  • tests/e2e/sidebar-review-facts.spec.ts
💤 Files with no reviewable changes: 2
  • config/scripts/layout-owner-build.test.mjs
  • config/layout-owner-build.cjs

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

Comment on lines +72 to +82
if (
!reply?.success ||
!this.hosts.list().environments.some((entry) => entry.id === host.environmentId)
) {
return
}
this.cached.set(host.environmentId, {
// Why the identity taken before the fetch: the call's own check cannot see a re-pair after dispatch.
fence: relayHostFence(host.identity),
worktrees: stampServerWorktreeRows(host.environmentId, reply.data.worktrees)
})

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 | 🟡 Minor | ⚡ Quick win

Do not let a late answer from an old pairing overwrite the current pairing's cached rows.

refresh keys in-flight fetches by environmentId and fence. After a re-pair, a fetch under the old pairing and a fetch under the new pairing can therefore run at the same time. cached is keyed only by environmentId. The guard at lines 72-75 checks only that the environment still exists. The comment at line 79 states that the transport's expected check cannot detect a re-pair after dispatch, so the old call can still succeed.

The test at lines 311-332 covers this sequence. The new-pairing fetch stores its rows first. The old-pairing answer then arrives and replaces them with an entry that has the old fence. rows(env, newFence) then returns null. Suppose the server becomes unreachable, or the next refresh fails, before another fetch succeeds. In that case the phone gets { worktrees: null } instead of the last rows from the current pairing. This contradicts the "keeps its last rows" contract.

Store the rows only when the current listing's fence still equals the fence captured before the fetch.

🐛 Proposed fix
--- "a/src/main/runtime/mobile-desktop-relay/on-demand-server-worktree-rows.ts"
+++ "b/src/main/runtime/mobile-desktop-relay/on-demand-server-worktree-rows.ts"
@@ -67,19 +67,19 @@
         // Why the transport bound too: it ends the hung call, so the next poll fetches afresh instead of joining it.
         { timeoutMs: SERVER_FETCH_TIMEOUT_MS, expected: host.identity }
       )
       const reply = response.ok ? WorktreePsReplySchema.safeParse(response.result) : null
-      // Why: a removed server's late answer must not outlive it.
-      if (
-        !reply?.success ||
-        !this.hosts.list().environments.some((entry) => entry.id === host.environmentId)
-      ) {
+      const fence = relayHostFence(host.identity)
+      const current = this.hosts
+        .list()
+        .environments.find((entry) => entry.id === host.environmentId)
+      // Why: a removed or re-paired server's late answer must not replace newer rows.
+      if (!reply?.success || !current || relayHostFence(current) !== fence) {
         return
       }
       this.cached.set(host.environmentId, {
-        // Why the identity taken before the fetch: the call's own check cannot see a re-pair after dispatch.
-        fence: relayHostFence(host.identity),
+        fence,
         worktrees: stampServerWorktreeRows(host.environmentId, reply.data.worktrees)
       })
     } catch {
       // An unreachable server keeps its last rows.
     }

Add an assertion to the re-pair test: after answerOldFetch, mark the server unreachable, call worktrees, and expect new-server-row.

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