Skip to content

refactor(renderer): split web preload API - #16181

Merged
nwparker merged 2 commits into
mainfrom
nwparker/refactor-13-preload-2
Aug 25, 2026
Merged

nwparker merged 2 commits into
mainfrom
nwparker/refactor-13-preload-2

Conversation

@nwparker

@nwparker nwparker commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 8 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​506 $\color{#cf222e}{\Huge{\mathbf{−}}}$​9 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​497
Prod 53 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​4974 $\color{#cf222e}{\Huge{\mathbf{−}}}$​4527 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​447

Architecture

  • Reduces src/renderer/src/web/web-preload-api.ts to composition and installation only.
  • Moves concrete API ownership into cohesive web/preload-api/ domain modules: runtime session/calls/catalog state, persistence and normalization, provider routes, browser/clipboard/filesystem/git adapters, and desktop-unavailable capability implementations.
  • Keeps runtime state and queue ownership in explicit singleton modules while each install creates fresh API closures.
  • Keeps GitHub/GitLab route tables and clipboard bounds with their concrete implementations; no barrel exports or forwarding facade.
  • Removes only the stale inline src/renderer/src/web/web-preload-api.ts max-lines baseline entry.

Preserved regression contracts

  • Exact public API key set and ordering; fallback namespaces remain non-enumerable and non-thenable.
  • E2E config is still snapshotted before runtime storage initialization.
  • Runtime success/failure envelopes, runtime metadata persistence, queue overload behavior, timeout forwarding, singleton client lifetime, and fresh per-install API closures.
  • Worktree catalog caching, disconnect invalidation, runtime-host ownership, stale-response fencing, folder workspace compatibility, and review/push field forwarding.
  • Clipboard size/pixel bounds, chunked uploads, fallback limits, and abort cleanup.
  • GitHub and GitLab method parity and RPC route mapping.

Root line count

  • Before: 4,596 lines
  • After: 141 lines

All changed production TypeScript modules are at most 297 lines. All changed tests are at most 721 lines.

Validation

  • pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/web/web-preload-api-clipboard.test.ts src/renderer/src/web/web-preload-api-composition.test.ts src/renderer/src/web/web-preload-api-github.test.ts src/renderer/src/web/web-preload-api-gitlab.test.ts src/renderer/src/web/web-preload-api-runtime-calls.test.ts src/renderer/src/web/web-preload-api-runtime-environment.test.ts src/renderer/src/web/web-preload-api-workspace-catalog.test.ts src/renderer/src/web/web-runtime-client-timeout-budget.test.ts — 8 files passed, 63 tests passed.
  • pnpm exec oxlint --deny-warnings — passed with zero warnings.
  • pnpm run typecheck — passed.
  • pnpm run check:max-lines-ratchet — passed: 186 grandfathered suppressions, no new bypasses.

@coderabbitai

coderabbitai Bot commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

Next included review available in 37 seconds.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a71b42b-c023-4375-9cf1-b233d551ada0

📥 Commits

Reviewing files that changed from the base of the PR and between 2960fe9 and 34f6bf7.

📒 Files selected for processing (61)
  • config/max-lines-baseline.txt
  • src/renderer/src/web/preload-api/web-agent-accounts-api.ts
  • src/renderer/src/web/preload-api/web-agent-hooks-api.ts
  • src/renderer/src/web/preload-api/web-agent-status-api.ts
  • src/renderer/src/web/preload-api/web-ai-vault-api.ts
  • src/renderer/src/web/preload-api/web-app-api.ts
  • src/renderer/src/web/preload-api/web-browser-api.ts
  • src/renderer/src/web/preload-api/web-cli-api.ts
  • src/renderer/src/web/preload-api/web-clipboard-api.ts
  • src/renderer/src/web/preload-api/web-diagnostics-api.ts
  • src/renderer/src/web/preload-api/web-e2e-api.ts
  • src/renderer/src/web/preload-api/web-e2e-config.ts
  • src/renderer/src/web/preload-api/web-fallback-api.ts
  • src/renderer/src/web/preload-api/web-filesystem-api.ts
  • src/renderer/src/web/preload-api/web-git-api.ts
  • src/renderer/src/web/preload-api/web-github-api.ts
  • src/renderer/src/web/preload-api/web-github-cache-api.ts
  • src/renderer/src/web/preload-api/web-github-routes.ts
  • src/renderer/src/web/preload-api/web-gitlab-api.ts
  • src/renderer/src/web/preload-api/web-gitlab-routes.ts
  • src/renderer/src/web/preload-api/web-host-capability-api.ts
  • src/renderer/src/web/preload-api/web-keybinding-normalization.ts
  • src/renderer/src/web/preload-api/web-keybindings-api.ts
  • src/renderer/src/web/preload-api/web-macos-tcc-api.ts
  • src/renderer/src/web/preload-api/web-memory-api.ts
  • src/renderer/src/web/preload-api/web-mobile-api.ts
  • src/renderer/src/web/preload-api/web-native-chat-api.ts
  • src/renderer/src/web/preload-api/web-notifications-api.ts
  • src/renderer/src/web/preload-api/web-onboarding-api.ts
  • src/renderer/src/web/preload-api/web-orca-profiles-api.ts
  • src/renderer/src/web/preload-api/web-platform-api.ts
  • src/renderer/src/web/preload-api/web-preference-normalization.ts
  • src/renderer/src/web/preload-api/web-preferences-store.ts
  • src/renderer/src/web/preload-api/web-rate-limits-api.ts
  • src/renderer/src/web/preload-api/web-repositories-api.ts
  • src/renderer/src/web/preload-api/web-review-api.ts
  • src/renderer/src/web/preload-api/web-runtime-api.ts
  • src/renderer/src/web/preload-api/web-runtime-calls.ts
  • src/renderer/src/web/preload-api/web-runtime-environments-api.ts
  • src/renderer/src/web/preload-api/web-runtime-session.ts
  • src/renderer/src/web/preload-api/web-runtime-worktree-catalog.ts
  • src/renderer/src/web/preload-api/web-settings-api.ts
  • src/renderer/src/web/preload-api/web-shell-api.ts
  • src/renderer/src/web/preload-api/web-star-nag-api.ts
  • src/renderer/src/web/preload-api/web-storage.ts
  • src/renderer/src/web/preload-api/web-telemetry-api.ts
  • src/renderer/src/web/preload-api/web-terminal-api.ts
  • src/renderer/src/web/preload-api/web-ui-api.ts
  • src/renderer/src/web/preload-api/web-updater-api.ts
  • src/renderer/src/web/preload-api/web-workspace-ports-api.ts
  • src/renderer/src/web/preload-api/web-workspace-session-api.ts
  • src/renderer/src/web/preload-api/web-worktrees-api.ts
  • src/renderer/src/web/web-preload-api-clipboard.test.ts
  • src/renderer/src/web/web-preload-api-composition.test.ts
  • src/renderer/src/web/web-preload-api-github.test.ts
  • src/renderer/src/web/web-preload-api-gitlab.test.ts
  • src/renderer/src/web/web-preload-api-runtime-calls.test.ts
  • src/renderer/src/web/web-preload-api-runtime-environment.test.ts
  • src/renderer/src/web/web-preload-api-workspace-catalog.test.ts
  • src/renderer/src/web/web-preload-api.ts
  • src/renderer/src/web/web-runtime-client-timeout-budget.test.ts
📝 Walkthrough

Walkthrough

Added browser-specific preload API factories across runtime, storage, settings, filesystem, Git, hosted review, terminal, UI, agent, diagnostics, and capability namespaces. Runtime calls now support environment selection, queueing, metadata persistence, ownership tagging, and worktree resolution. Browser storage supports settings, sessions, onboarding, caches, and keybindings. Tests cover API composition, runtime error and timeout behavior, client reuse, and worktree cache invalidation.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains architecture and validation but omits required sections, including ELI5, Why, Linked Issue, Visual Proof, Testing, and the final checklist. Add the missing template sections, include a valid Fixes #<issue> reference, state N/A with a reason for visual proof, and document testing platforms and checklist status.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
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.
Title check ✅ Passed The title clearly and concisely describes the main change: splitting the web preload API.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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: 11

🧹 Nitpick comments (5)
src/renderer/src/web/preload-api/web-git-api.ts (1)

217-238: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use intent-named localization IDs for new messages.

These new user-facing messages use hash-based auto.web.web.preload.api.* IDs. Replace them with intent-named localization IDs. Use named placeholders when a message needs interpolation.

  • src/renderer/src/web/preload-api/web-git-api.ts#L217-L238: replace the three new hash-based IDs.
  • src/renderer/src/web/preload-api/web-github-api.ts#L117-L121: replace the new hash-based ID.

Based on learnings, new user-facing strings must use intent-named localization IDs; hash-based IDs are grandfathered only.

Source: Learnings

src/renderer/src/web/preload-api/web-shell-api.ts (1)

7-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Return Promise<void> from the web shell methods.

window.open returns Window | null, but ShellApi declares Promise<void>. The as never casts hide this mismatch and return the wrong runtime value. Call window.open locally, then resolve with undefined; handle a blocked popup locally if required.

src/renderer/src/web/preload-api/web-browser-api.ts (1)

89-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unchecked casts hide preload stub drift in three web API factories. Each site erases the declared PreloadApi shape with a cast, so the compiler cannot verify the stub against the contract. A later change to any of these interfaces will compile cleanly and fail at runtime in the web client instead of at typecheck time.

  • src/renderer/src/web/preload-api/web-browser-api.ts#L89-L101: remove the two as unknown as casts and let createBrowserApi and createEmulatorApi typecheck against their declared return types; if one member forces the cast, narrow the cast to that member.
  • src/renderer/src/web/preload-api/web-telemetry-api.ts#L7-L9: remove as never and type the consent-state literal against the declared return type of telemetryGetConsentState.
  • src/renderer/src/web/preload-api/web-updater-api.ts#L12-L12: remove as never and return a value that satisfies the declared updater status type.
src/renderer/src/web/preload-api/web-ui-api.ts (1)

32-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated host-state merge block.

Lines 32-43 and lines 85-96 build the same next object from local and result.ui. The two copies must stay in sync. If a new merged field is added to one copy only, get and recordFeatureInteraction return different shapes for the same host response.

♻️ Proposed extraction
function reconcileHostUiState(local: PersistedUIState, hostUi: PairedUiState): PersistedUIState {
  return {
    ...mergeHostWebUIState(local, hostUi),
    osc52ClipboardDefaultOnNoticePending: mergeOsc52ClipboardNoticePending(local, hostUi),
    featureInteractions: mergeFeatureInteractionState(
      local.featureInteractions,
      hostUi.featureInteractions
    ),
    contextualToursSeenIds: mergeContextualTourSeenIds(
      local.contextualToursSeenIds,
      hostUi.contextualToursSeenIds
    )
  }
}

Then call it in both get and recordFeatureInteraction, followed by the shared writeJson and zoomLevel assignment.

src/renderer/src/web/preload-api/web-runtime-calls.ts (1)

29-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared envelope body from callRuntimeEnvelope and callEnvironmentEnvelope.

Both functions contain the same disconnect pre-check, queue enqueue, disconnect post-check, and updateEnvironmentFromResponse sequence. Only the environment resolution differs. A future change to the disconnect semantics must be applied twice.

♻️ Proposed refactor
+async function callEnvelopeForEnvironment<TResult>(
+  environment: StoredWebRuntimeEnvironment,
+  method: string,
+  params?: unknown,
+  timeoutMs?: number
+): Promise<RuntimeRpcResponse<TResult>> {
+  if (manuallyDisconnectedEnvironmentIds.has(environment.id)) {
+    return manuallyDisconnectedResponse(environment)
+  }
+  const response = await runtimeCallQueuePool.enqueue(environment.id, method, () => {
+    if (manuallyDisconnectedEnvironmentIds.has(environment.id)) {
+      return Promise.resolve(manuallyDisconnectedResponse(environment))
+    }
+    return getClientForEnvironment(environment).call(method, params, { timeoutMs })
+  })
+  if (manuallyDisconnectedEnvironmentIds.has(environment.id)) {
+    return manuallyDisconnectedResponse(environment)
+  }
+  updateEnvironmentFromResponse(environment, response)
+  return response as RuntimeRpcResponse<TResult>
+}
+
 export async function callRuntimeEnvelope<TResult = unknown>(
   method: string,
   params?: unknown,
   timeoutMs?: number
 ): Promise<RuntimeRpcResponse<TResult>> {
-  const environment = requireActiveEnvironment()
-  ...
+  return callEnvelopeForEnvironment<TResult>(requireActiveEnvironment(), method, params, timeoutMs)
 }
 
 export async function callEnvironmentEnvelope<TResult = unknown>(
   selector: string,
   method: string,
   params?: unknown,
   timeoutMs?: number
 ): Promise<RuntimeRpcResponse<TResult>> {
-  const environment = resolveEnvironment(selector)
-  ...
+  return callEnvelopeForEnvironment<TResult>(resolveEnvironment(selector), method, params, timeoutMs)
 }

StoredWebRuntimeEnvironment needs a type import from ../web-runtime-environment.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f84706b6-d6ad-4108-95bc-9cbaeee73394

📥 Commits

Reviewing files that changed from the base of the PR and between 0a613d5 and a2ff15e.

📒 Files selected for processing (61)
  • config/max-lines-baseline.txt
  • src/renderer/src/web/preload-api/web-agent-accounts-api.ts
  • src/renderer/src/web/preload-api/web-agent-hooks-api.ts
  • src/renderer/src/web/preload-api/web-agent-status-api.ts
  • src/renderer/src/web/preload-api/web-ai-vault-api.ts
  • src/renderer/src/web/preload-api/web-app-api.ts
  • src/renderer/src/web/preload-api/web-browser-api.ts
  • src/renderer/src/web/preload-api/web-cli-api.ts
  • src/renderer/src/web/preload-api/web-clipboard-api.ts
  • src/renderer/src/web/preload-api/web-diagnostics-api.ts
  • src/renderer/src/web/preload-api/web-e2e-api.ts
  • src/renderer/src/web/preload-api/web-e2e-config.ts
  • src/renderer/src/web/preload-api/web-fallback-api.ts
  • src/renderer/src/web/preload-api/web-filesystem-api.ts
  • src/renderer/src/web/preload-api/web-git-api.ts
  • src/renderer/src/web/preload-api/web-github-api.ts
  • src/renderer/src/web/preload-api/web-github-cache-api.ts
  • src/renderer/src/web/preload-api/web-github-routes.ts
  • src/renderer/src/web/preload-api/web-gitlab-api.ts
  • src/renderer/src/web/preload-api/web-gitlab-routes.ts
  • src/renderer/src/web/preload-api/web-host-capability-api.ts
  • src/renderer/src/web/preload-api/web-keybinding-normalization.ts
  • src/renderer/src/web/preload-api/web-keybindings-api.ts
  • src/renderer/src/web/preload-api/web-macos-tcc-api.ts
  • src/renderer/src/web/preload-api/web-memory-api.ts
  • src/renderer/src/web/preload-api/web-mobile-api.ts
  • src/renderer/src/web/preload-api/web-native-chat-api.ts
  • src/renderer/src/web/preload-api/web-notifications-api.ts
  • src/renderer/src/web/preload-api/web-onboarding-api.ts
  • src/renderer/src/web/preload-api/web-orca-profiles-api.ts
  • src/renderer/src/web/preload-api/web-platform-api.ts
  • src/renderer/src/web/preload-api/web-preference-normalization.ts
  • src/renderer/src/web/preload-api/web-preferences-store.ts
  • src/renderer/src/web/preload-api/web-rate-limits-api.ts
  • src/renderer/src/web/preload-api/web-repositories-api.ts
  • src/renderer/src/web/preload-api/web-review-api.ts
  • src/renderer/src/web/preload-api/web-runtime-api.ts
  • src/renderer/src/web/preload-api/web-runtime-calls.ts
  • src/renderer/src/web/preload-api/web-runtime-environments-api.ts
  • src/renderer/src/web/preload-api/web-runtime-session.ts
  • src/renderer/src/web/preload-api/web-runtime-worktree-catalog.ts
  • src/renderer/src/web/preload-api/web-settings-api.ts
  • src/renderer/src/web/preload-api/web-shell-api.ts
  • src/renderer/src/web/preload-api/web-star-nag-api.ts
  • src/renderer/src/web/preload-api/web-storage.ts
  • src/renderer/src/web/preload-api/web-telemetry-api.ts
  • src/renderer/src/web/preload-api/web-terminal-api.ts
  • src/renderer/src/web/preload-api/web-ui-api.ts
  • src/renderer/src/web/preload-api/web-updater-api.ts
  • src/renderer/src/web/preload-api/web-workspace-ports-api.ts
  • src/renderer/src/web/preload-api/web-workspace-session-api.ts
  • src/renderer/src/web/preload-api/web-worktrees-api.ts
  • src/renderer/src/web/web-preload-api-clipboard.test.ts
  • src/renderer/src/web/web-preload-api-composition.test.ts
  • src/renderer/src/web/web-preload-api-github.test.ts
  • src/renderer/src/web/web-preload-api-gitlab.test.ts
  • src/renderer/src/web/web-preload-api-runtime-calls.test.ts
  • src/renderer/src/web/web-preload-api-runtime-environment.test.ts
  • src/renderer/src/web/web-preload-api-workspace-catalog.test.ts
  • src/renderer/src/web/web-preload-api.ts
  • src/renderer/src/web/web-runtime-client-timeout-budget.test.ts
💤 Files with no reviewable changes (1)
  • config/max-lines-baseline.txt

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.

Partial<PreloadApi>['minimaxCredentials']
> {
const notConfigured = { configured: false }
const unsupportedError = new Error('MiniMax cookie storage is only available in the desktop app.')

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Localize new web fallback messages.

These messages can reach users but bypass translate. Add intent-named localization IDs and use translate at each site.

  • src/renderer/src/web/preload-api/web-agent-accounts-api.ts#L7-L7: Localize the MiniMax unsupported error.
  • src/renderer/src/web/preload-api/web-agent-hooks-api.ts#L25-L25: Localize the hook-status detail.
  • src/renderer/src/web/preload-api/web-cli-api.ts#L17-L17: Localize the CLI-status detail.
  • src/renderer/src/web/preload-api/web-diagnostics-api.ts#L34-L38: Localize unavailable diagnostics errors.
  • src/renderer/src/web/preload-api/web-host-capability-api.ts#L130-L130: Localize the computer-use next step.
  • src/renderer/src/web/preload-api/web-host-capability-api.ts#L162-L189: Localize skill-operation errors.

Based on learnings: New user-facing TypeScript strings must use intent-named localization IDs with named interpolation placeholders.

📍 Affects 5 files
  • src/renderer/src/web/preload-api/web-agent-accounts-api.ts#L7-L7 (this comment)
  • src/renderer/src/web/preload-api/web-agent-hooks-api.ts#L25-L25
  • src/renderer/src/web/preload-api/web-cli-api.ts#L17-L17
  • src/renderer/src/web/preload-api/web-diagnostics-api.ts#L34-L38
  • src/renderer/src/web/preload-api/web-host-capability-api.ts#L130-L130
  • src/renderer/src/web/preload-api/web-host-capability-api.ts#L162-L189

Source: Learnings

Comment on lines +34 to +39
return callRuntimeResult<AiVaultListResult>('aiVault.listSessions', {
limit: args?.limit,
force: args?.force,
scopePaths: args?.scopePaths,
executionHostId
})

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

Forward all list request fields.

AiVaultListArgs includes unlimited and requestToken, but this RPC payload omits both. An unlimited request becomes a capped request, and the runtime loses request correlation. Forward both fields.

Proposed fix
         limit: args?.limit,
+        unlimited: args?.unlimited,
         force: args?.force,
         scopePaths: args?.scopePaths,
+        requestToken: args?.requestToken,
         executionHostId
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return callRuntimeResult<AiVaultListResult>('aiVault.listSessions', {
limit: args?.limit,
force: args?.force,
scopePaths: args?.scopePaths,
executionHostId
})
return callRuntimeResult<AiVaultListResult>('aiVault.listSessions', {
limit: args?.limit,
unlimited: args?.unlimited,
force: args?.force,
scopePaths: args?.scopePaths,
requestToken: args?.requestToken,
executionHostId
})

Comment on lines +16 to +21
error: translate('auto.web.web.preload.api.fb290366b2', 'Unavailable on web.')
}),
copyLatestDiagnostics: () =>
Promise.resolve({
ok: false,
error: translate('auto.web.web.preload.api.fb290366b2', 'Unavailable on web.')

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use an intent-named localization ID.

The new auto.web.web.preload.api.fb290366b2 ID is hash-style. Replace it with an intent-named ID at both call sites.

Based on learnings: New user-facing TypeScript strings must use intent-named localization IDs.

Source: Learnings

Comment on lines +160 to +170
onChanged: (callback) => {
webKeybindingListeners.add(callback)
const onStorage = (event: StorageEvent): void => {
if (event.key === KEYBINDINGS_STORAGE_KEY) {
callback(getWebKeybindingSnapshot())
}
}
window.addEventListener('storage', onStorage)
return () => {
webKeybindingListeners.delete(callback)
window.removeEventListener('storage', onStorage)

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

Keep each subscription separate.

If a caller registers the same callback twice, the Set stores it once. Either disposer then removes both registrations. Local updates also notify the callback only once.

Store a wrapper listener for each onChanged call and remove that wrapper in its matching disposer.

Proposed fix
     onChanged: (callback) => {
-      webKeybindingListeners.add(callback)
+      const listener = (snapshot: KeybindingFileSnapshot): void => callback(snapshot)
+      webKeybindingListeners.add(listener)
       const onStorage = (event: StorageEvent): void => {
         if (event.key === KEYBINDINGS_STORAGE_KEY) {
           callback(getWebKeybindingSnapshot())
         }
       }
       window.addEventListener('storage', onStorage)
       return () => {
-        webKeybindingListeners.delete(callback)
+        webKeybindingListeners.delete(listener)
         window.removeEventListener('storage', onStorage)
       }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onChanged: (callback) => {
webKeybindingListeners.add(callback)
const onStorage = (event: StorageEvent): void => {
if (event.key === KEYBINDINGS_STORAGE_KEY) {
callback(getWebKeybindingSnapshot())
}
}
window.addEventListener('storage', onStorage)
return () => {
webKeybindingListeners.delete(callback)
window.removeEventListener('storage', onStorage)
onChanged: (callback) => {
const listener = (snapshot: KeybindingFileSnapshot): void => callback(snapshot)
webKeybindingListeners.add(listener)
const onStorage = (event: StorageEvent): void => {
if (event.key === KEYBINDINGS_STORAGE_KEY) {
callback(getWebKeybindingSnapshot())
}
}
window.addEventListener('storage', onStorage)
return () => {
webKeybindingListeners.delete(listener)
window.removeEventListener('storage', onStorage)

Comment on lines +8 to +15
const webOrcaProfileAuthStatus = () =>
Promise.resolve({
activeProfileId: DEFAULT_LOCAL_ORCA_PROFILE_ID,
configured: false,
state: 'unconfigured' as const,
persistence: 'none' as const,
setupMessage: 'Orca Cloud sign-in is not available in the browser fallback.'
})

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Route setupMessage through translate.

setupMessage is a user-facing display field on the auth-status result. Line 14 hardcodes English. Every other user-facing message in this cohort uses translate with an intent-named ID, for example web-runtime-environments-api.ts Line 65 and web-runtime-session.ts Line 84.

🌐 Proposed fix
+import { translate } from '`@/i18n/i18n`'
+
 export function createWebOrcaProfilesApi(): Partial<PreloadApi> {
   const webOrcaProfileAuthStatus = () =>
     Promise.resolve({
       activeProfileId: DEFAULT_LOCAL_ORCA_PROFILE_ID,
       configured: false,
       state: 'unconfigured' as const,
       persistence: 'none' as const,
-      setupMessage: 'Orca Cloud sign-in is not available in the browser fallback.'
+      setupMessage: translate(
+        'auto.web.webOrcaProfilesApi.cloudSignInUnavailable',
+        'Orca Cloud sign-in is not available in the browser fallback.'
+      )
     })

Based on learnings, for newly added user-facing strings in stablyai/orca TypeScript files, follow config/i18n-translation-source.md and use intent-named localization IDs.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const webOrcaProfileAuthStatus = () =>
Promise.resolve({
activeProfileId: DEFAULT_LOCAL_ORCA_PROFILE_ID,
configured: false,
state: 'unconfigured' as const,
persistence: 'none' as const,
setupMessage: 'Orca Cloud sign-in is not available in the browser fallback.'
})
import { translate } from '@/i18n/i18n'
const webOrcaProfileAuthStatus = () =>
Promise.resolve({
activeProfileId: DEFAULT_LOCAL_ORCA_PROFILE_ID,
configured: false,
state: 'unconfigured' as const,
persistence: 'none' as const,
setupMessage: translate(
'auto.web.webOrcaProfilesApi.cloudSignInUnavailable',
'Orca Cloud sign-in is not available in the browser fallback.'
)
})

Source: Learnings

Comment on lines +41 to +51
const previousEnvironment = webRuntimeState.activeEnvironment
closeActiveRuntimeClients()
webRuntimeState.activeEnvironment = createStoredWebRuntimeEnvironment({
name,
offer,
previousEnvironment
})
manuallyDisconnectedEnvironmentIds.clear()
saveStoredWebRuntimeEnvironment(webRuntimeState.activeEnvironment)
return { environment: redactStoredWebRuntimeEnvironment(webRuntimeState.activeEnvironment) }
},

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

Save the environment before you replace the active in-memory environment.

addFromPairingCode assigns webRuntimeState.activeEnvironment at Line 43 and calls saveStoredWebRuntimeEnvironment at Line 49. If browser storage rejects the write, the method throws after the in-memory swap. The session then points at an environment that is not persisted, and the previous environment stays in storage. A reload silently returns the user to the old host.

verifyAndAddFromPairingCode already guards this case at Lines 133-145 with the stated rule that a storage failure must leave the current host usable. Apply the same ordering here.

🐛 Proposed fix
       const previousEnvironment = webRuntimeState.activeEnvironment
-      closeActiveRuntimeClients()
-      webRuntimeState.activeEnvironment = createStoredWebRuntimeEnvironment({
+      const nextEnvironment = createStoredWebRuntimeEnvironment({
         name,
         offer,
         previousEnvironment
       })
+      // Why: a browser storage failure must leave the currently active host usable.
+      saveStoredWebRuntimeEnvironment(nextEnvironment)
+      closeActiveRuntimeClients()
       manuallyDisconnectedEnvironmentIds.clear()
-      saveStoredWebRuntimeEnvironment(webRuntimeState.activeEnvironment)
-      return { environment: redactStoredWebRuntimeEnvironment(webRuntimeState.activeEnvironment) }
+      webRuntimeState.activeEnvironment = nextEnvironment
+      return { environment: redactStoredWebRuntimeEnvironment(nextEnvironment) }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const previousEnvironment = webRuntimeState.activeEnvironment
closeActiveRuntimeClients()
webRuntimeState.activeEnvironment = createStoredWebRuntimeEnvironment({
name,
offer,
previousEnvironment
})
manuallyDisconnectedEnvironmentIds.clear()
saveStoredWebRuntimeEnvironment(webRuntimeState.activeEnvironment)
return { environment: redactStoredWebRuntimeEnvironment(webRuntimeState.activeEnvironment) }
},
const previousEnvironment = webRuntimeState.activeEnvironment
const nextEnvironment = createStoredWebRuntimeEnvironment({
name,
offer,
previousEnvironment
})
// Why: a browser storage failure must leave the currently active host usable.
saveStoredWebRuntimeEnvironment(nextEnvironment)
closeActiveRuntimeClients()
manuallyDisconnectedEnvironmentIds.clear()
webRuntimeState.activeEnvironment = nextEnvironment
return { environment: redactStoredWebRuntimeEnvironment(nextEnvironment) }
},

Comment on lines +104 to +119
export function toLegacyDetectedWorktreeResult(
repoId: string,
worktrees: Worktree[]
): DetectedWorktreeListResult {
return {
repoId,
authoritative: true,
source: 'session-fallback',
worktrees: worktrees.map((worktree) => ({
...worktree,
ownership: 'orca-managed',
selectedCheckout: false,
visible: true
}))
}
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Resolve DetectedWorktreeListResult.authoritative semantics and its consumers.
set -euo pipefail

# Definition of the result contract.
fd -t f 'types.ts' src/shared/worktree --exec rg -n -C 5 'authoritative|DetectedWorktreeListResult|source' {}

# Consumers that branch on `authoritative`.
rg -nP --type=ts -C 6 '\bauthoritative\b' -g '!**/*.test.ts'

# Other producers of session-fallback results, for comparison.
rg -nP --type=ts -C 4 "'session-fallback'"

Repository: stablyai/orca

Length of output: 3331


🏁 Script executed:

#!/bin/bash
set -u

echo '--- candidate files ---'
fd -t f . src | rg 'worktree|repo|detected|catalog' | head -200

echo '--- result type and source definitions ---'
rg -n -C 12 --type ts 'type DetectedWorktree(ListResult|ListSource)|interface DetectedWorktree|DetectedWorktreeListResult|DetectedWorktreeListSource' src

echo '--- authoritative references ---'
rg -n -C 10 --type ts --glob '!**/*.test.ts' 'authoritative' src

echo '--- session-fallback references ---'
rg -n -C 10 --type ts "'session-fallback'" src

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -u

echo '--- files declaring or using the result type ---'
rg -l --type ts 'DetectedWorktree(ListResult|ListSource)|toLegacyDetectedWorktreeResult|session-fallback' src

echo '--- exact source/type declarations ---'
rg -n -C 15 --type ts 'DetectedWorktreeListSource|DetectedWorktreeListResult' src/shared src/renderer/src/web src/renderer/src/store src/renderer/src/components

echo '--- exact worktree authoritative property uses ---'
rg -n -C 8 --type ts '(result|snapshot|catalog|detectedWorktrees|detectedWorktreeList)\??\.authoritative|authoritative\s*===|authoritative\s*&&|authoritative\s*\?' src/renderer/src src/shared

echo '--- catalog implementation ---'
cat -n src/renderer/src/web/preload-api/web-runtime-worktree-catalog.ts | sed -n '1,230p'

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -u

echo '--- shared worktree result contract ---'
cat -n src/shared/worktree/types.ts | sed -n '175,235p'

echo '--- fallback catalog implementation ---'
cat -n src/renderer/src/web/preload-api/web-runtime-worktree-catalog.ts | sed -n '1,145p'

echo '--- worktree listing authoritative uses ---'
rg -n -C 12 --type ts \
  'authoritative|session-fallback|worktree\.list|detectedList|missingWorktreeTeardowns' \
  src/renderer/src/store/slices/worktrees/listing \
  src/renderer/src/store/slices/worktrees/teardown \
  src/renderer/src/store/slices/degraded-repo-worktree-validity.ts \
  src/renderer/src/store/slices/worktree-helpers.ts

echo '--- all non-test result property consumers ---'
rg -n -C 8 --type ts --glob '!**/*.test.ts' \
  '(detected|result|snapshot)\.authoritative|authoritative:\s*(true|false)' \
  src/renderer/src/store/slices src/renderer/src/web src/shared/external-worktree-inbox.ts

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -u

echo '--- legacy converter usages ---'
rg -n -C 15 --type ts --glob '!**/*.test.ts' \
  'toLegacyDetectedWorktreeResult|callRuntimeDetectedWorktrees|fetchDetectedWorktrees' \
  src/renderer/src src/preload src/main

echo '--- removed-id reconciliation callers ---'
rg -n -C 18 --type ts --glob '!**/*.test.ts' \
  'getRemovedWorktreeIdsAfterAuthoritativeScan|teardownMissingWorktreeTerminalsBestEffort|purgeRemovedWorktrees|removedWorktreeIds' \
  src/renderer/src/store/slices/worktrees

echo '--- refresh and fetch flow ---'
cat -n src/renderer/src/store/slices/worktrees/listing/detected-worktree-refresh.ts | sed -n '1,260p'
cat -n src/renderer/src/store/slices/worktrees/listing/fetch-all-worktrees.ts | sed -n '1,230p'

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -u

python3 - <<'PY'
from pathlib import Path

catalog = Path("src/renderer/src/web/preload-api/web-runtime-worktree-catalog.ts").read_text()
ownership = Path("src/renderer/src/store/slices/worktrees/listing/worktree-host-ownership.ts").read_text()
teardown = Path("src/renderer/src/store/slices/worktrees/teardown/missing-worktree-terminal-teardown.ts").read_text()

checks = {
    "web fallback is authoritative": "authoritative: true" in catalog,
    "web fallback is session fallback": "source: 'session-fallback'" in catalog,
    "web fallback forces visible": "visible: true" in catalog,
    "web fallback forces Orca ownership": "ownership: 'orca-managed'" in catalog,
    "store fallback is authoritative": "authoritative: true" in ownership,
    "store fallback is session fallback": "source: 'session-fallback'" in ownership,
    "removed-id teardown gates only on authoritative": "if (!detected.authoritative" in teardown,
    "removed-id teardown computes absent IDs": "filter((worktreeId) => !detectedIds.has(worktreeId))" in teardown,
}

for name, result in checks.items():
    print(f"{name}: {'present' if result else 'MISSING'}")
PY

Repository: stablyai/orca

Length of output: 504


🏁 Script executed:

#!/bin/bash
set -u

echo '--- detected refresh result handling ---'
cat -n src/renderer/src/store/slices/worktrees/listing/fetch-detected-worktrees.ts | sed -n '1,260p'

echo '--- provider fallback handling ---'
cat -n src/renderer/src/store/slices/worktrees/listing/detected-worktree-provider-request.ts | sed -n '20,150p'

echo '--- authoritative removal call sites ---'
rg -n -C 10 --type ts --glob '!**/*.test.ts' \
  'getRemovedWorktreeIdsAfterAuthoritativeScan|teardownMissingWorktreeTerminalsBestEffort' \
  src/renderer/src

echo '--- fallback-specific tests and expectations ---'
rg -n -C 12 --type ts --glob '*test.ts' \
  'session-fallback|toLegacyDetectedWorktreeResult|authoritative.*true|method_not_found' \
  src/renderer/src src/shared src/preload

Repository: stablyai/orca

Length of output: 50370


Mark session-fallback results as non-authoritative. worktree.list returns only sidebar-visible rows, but authoritative results drive missing-worktree reconciliation. source does not protect consumers. Set authoritative: false in this converter and its duplicate in worktree-host-ownership.ts.

Comment on lines +100 to +103
updatePRBotAuthorOverride: (args) => updateRuntimePRBotAuthorOverride(args),
listFonts: () => Promise.resolve([]),
onChanged: () => noopUnsubscribe
} satisfies Partial<WebSettingsApi> as unknown as WebSettingsApi,

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Compare PreloadApi['settings'] members against the web implementation.
set -euo pipefail

# Locate the settings member declaration on PreloadApi.
fd -t f 'api-types.ts' src/preload --exec rg -n -A 60 '^\s*settings\??:' {}

# Members implemented by the web settings API.
ast-grep outline src/renderer/src/web/preload-api/web-settings-api.ts --items all

# Other implementations of the same surface, for a member-by-member comparison.
rg -nP --type=ts -C 3 "satisfies Partial<WebSettingsApi>"

Repository: stablyai/orca

Length of output: 2925


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- SettingsApi declaration ---'
rg -n -A 120 -B 10 'interface SettingsApi|type SettingsApi' src/preload

echo '--- WebSettingsApi declaration ---'
rg -n -A 140 -B 10 'interface WebSettingsApi|type WebSettingsApi' src/renderer/src/web/preload-api

echo '--- Web settings implementation ---'
cat -n src/renderer/src/web/preload-api/web-settings-api.ts

echo '--- Settings API references ---'
rg -n --type=ts 'createWebSettingsApi|WebSettingsApi|PreloadApi\[['\"'\"']settings['\"'\"'\]' src/renderer/src src/preload

Repository: stablyai/orca

Length of output: 24911


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Settings preview implementations and call sites ---'
rg -n -C 4 --type=ts --type=tsx 'previewGhosttyImport|previewWarpThemeImport' .

echo '--- Web preload API assembly ---'
rg -n -C 8 'createWebSettingsApi|settings:' src/renderer/src/web src/preload

echo '--- Desktop settings implementation ---'
rg -n -C 5 --type=ts 'previewGhosttyImport|previewWarpThemeImport' src/preload src

echo '--- Relevant tests ---'
rg -n -C 5 --glob '*.{test,spec}.{ts,tsx}' 'createWebSettingsApi|previewGhosttyImport|previewWarpThemeImport|settings\.getSync' .

Repository: stablyai/orca

Length of output: 239


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Settings preview implementations and call sites ---'
rg -n -C 4 -g '*.ts' -g '*.tsx' 'previewGhosttyImport|previewWarpThemeImport' .

echo '--- Web preload API assembly ---'
rg -n -C 8 -g '*.ts' -g '*.tsx' 'createWebSettingsApi|settings:' src/renderer/src/web src/preload

echo '--- Desktop settings implementation ---'
rg -n -C 5 -g '*.ts' -g '*.tsx' 'previewGhosttyImport|previewWarpThemeImport' src/preload src

echo '--- Relevant tests ---'
rg -n -C 5 -g '*.test.ts' -g '*.test.tsx' -g '*.spec.ts' -g '*.spec.tsx' \
  'createWebSettingsApi|previewGhosttyImport|previewWarpThemeImport|settings\.getSync' .

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Preview references outside tests ---'
rg -n -g '*.ts' -g '*.tsx' -g '!*.test.ts' -g '!*.test.tsx' \
  'previewGhosttyImport|previewWarpThemeImport' src

echo '--- Web API construction ---'
rg -n -C 6 -g '*.ts' -g '*.tsx' \
  'createWebSettingsApi|createWeb.*Api|web-preload-api' src/renderer/src/web src/renderer/src

echo '--- Settings API type and desktop surface ---'
cat -n src/preload/api/settings-api.ts | sed -n '1,24p'
cat -n src/renderer/src/web/preload-api/web-settings-api.ts | sed -n '20,108p'

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
terms = ("previewGhosttyImport", "previewWarpThemeImport")
for root in (Path("src/renderer/src"), Path("src/preload"), Path("src/main")):
    for path in root.rglob("*"):
        if path.suffix not in {".ts", ".tsx"} or ".test." in path.name:
            continue
        text = path.read_text(errors="replace")
        hits = [(i + 1, line.strip()) for i, line in enumerate(text.splitlines())
                if any(term in line for term in terms)]
        if hits:
            print(path)
            for line_no, line in hits:
                print(f"  {line_no}: {line}")
PY

echo '--- Web API installation ---'
rg -n -A 35 -B 10 'installWebPreloadApi|createWebSettingsApi' \
  src/renderer/src/web/web-preload-api.ts

echo '--- Explicit unsupported-operation patterns ---'
rg -n -C 3 -g '*.ts' -g '*.tsx' \
  'only available in the desktop app|not available in.*browser|unsupported.*browser|desktop-only' \
  src/renderer/src/web/preload-api src/renderer/src/web

Repository: stablyai/orca

Length of output: 18307


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Renderer call sites ---'
cat -n src/renderer/src/components/onboarding/ThemeStep.tsx | sed -n '65,145p'
cat -n src/renderer/src/components/settings/useGhosttyImport.ts | sed -n '1,80p'
cat -n src/renderer/src/components/settings/useWarpThemeImport.ts | sed -n '1,90p'

echo '--- Fallback proxy implementation ---'
rg -n -C 8 'function withFallback|const withFallback|withFallback\s*=' src/renderer/src
rg -n -C 8 'createFallbackProxy' src/renderer/src/web src/renderer/src

echo '--- Web settings tests and related assertions ---'
rg -n -C 5 'settings\.(previewGhosttyImport|previewWarpThemeImport)|previewGhosttyImport|previewWarpThemeImport' \
  src/renderer/src/web/web-preload-api-settings.test.ts \
  src/renderer/src/components/settings src/renderer/src/components/onboarding

Repository: stablyai/orca

Length of output: 49920


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Complete fallback behavior ---'
cat -n src/renderer/src/web/preload-api/web-fallback-api.ts | sed -n '1,140p'

echo '--- Web settings test file structure and fallback assertions ---'
rg -n -C 3 'fallback|settings|Ghostty|Warp|desktop app|available in the browser' \
  src/renderer/src/web/web-preload-api-settings.test.ts \
  src/renderer/src/web/web-preload-api*.test.ts

echo '--- Runtime behavior expectations for import hooks ---'
cat -n src/renderer/src/components/settings/useWarpThemeImport.test.ts | sed -n '190,225p'
cat -n src/renderer/src/components/settings/useGhosttyImport.test.ts | sed -n '150,175p'

Repository: stablyai/orca

Length of output: 50370


Provide type-correct web implementations for the missing preview methods.

withFallback returns a generic preview result, not undefined. previewWarpThemeImport therefore lacks themes, and useWarpThemeImport fails at result.themes.map(...). Add explicit results or desktop-only rejections for both methods, then remove the unsafe cast.

Comment on lines +23 to +31
export function readJson<T>(key: string, fallback: T): T {
const raw = window.localStorage.getItem(key)
if (!raw) {
return cloneJson(fallback)
}
try {
return { ...cloneJson(fallback), ...JSON.parse(raw) } as T
} catch {
return cloneJson(fallback)

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

Backfill nested persisted state.

Line 29 only merges top-level fields. An older stored onboarding checklist replaces the complete default checklist, so newly added checklist keys are absent. Normalize nested persisted state per domain before returning it.

@nwparker
nwparker merged commit d3c37b8 into main Aug 25, 2026
47 checks passed
paidaxingyo666 pushed a commit to paidaxingyo666/Manta that referenced this pull request Aug 25, 2026
dallascrilley pushed a commit to dallascrilley/orca that referenced this pull request Aug 27, 2026
brennanb2025 added a commit that referenced this pull request Aug 28, 2026
Ports preservesTranscriptOrder into main's split web preload API (#16181 moved
createNativeChatApi to preload-api/web-native-chat-api.ts) and unions it with
main's new capabilities.transcriptPending wire field.
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 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