Skip to content

Isolate browser WebKit process pools#4987

Open
lawrencecchen wants to merge 2 commits into
mainfrom
task-webview-crash-isolation
Open

Isolate browser WebKit process pools#4987
lawrencecchen wants to merge 2 commits into
mainfrom
task-webview-crash-isolation

Conversation

@lawrencecchen
Copy link
Copy Markdown
Contributor

@lawrencecchen lawrencecchen commented May 29, 2026

Summary

  • give each browser panel its own WKProcessPool so one bad page is less likely to take sibling browser panels down with it
  • keep popups in the opener process pool and close popup windows when their WebContent process terminates
  • add regression coverage for process-pool isolation, WebView replacement, and popup termination cleanup

Testing

  • ./scripts/reload.sh --tag webcrash
  • ./scripts/reload.sh --tag webcrash --launch
  • Loaded a local hostile page at http://127.0.0.1:18787/?cap=4096; it pushed the WebContent process to roughly 2.1 GiB RSS while cmux stayed around 430 MiB RSS.
  • Sent SIGKILL to the probe WebContent PID; cmux stayed alive, switching to a terminal tab worked, and echo cmux survived WebContent SIGKILL ran successfully.
  • Targeted AWS tests were not run because cmux-aws-m4pro had only 363 MiB free on / after cleanup.

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Changes core WebKit lifecycle (process pools, crash recovery, popup teardown); behavior is well-tested but affects all browser tabs and popups.

Overview
Replaces the shared WKProcessPool across browser panels with a per-panel pool so a WebContent crash is confined to that tab, while profile WKWebsiteDataStore stays shared for cookies/storage. Popups still use the opener’s pool via popupBrowserContext.

After WebContent termination, the panel no longer auto-reloads the page: it swaps in a new WKWebView (same pool), sets hasRecoverableWebContentTermination, and shows a Reload overlay; recoverTerminatedWebContent (toolbar reload, navigation, etc.) performs the restore. Popup windows close when their WebContent process terminates.

Adds regression tests for separate pools, recovery on reload, and popup cleanup.

Reviewed by Cursor Bugbot for commit 99dfbd4. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Isolated each browser panel into its own WebKit process pool to contain crashes, and added a simple reload overlay to recover after WebContent termination. Popups stay in the opener’s context and auto-close if their WebContent process dies.

  • New Features
    • Each browser panel uses its own WKProcessPool, while sharing the profile/workspace WKWebsiteDataStore for cookies and storage.
    • On WebContent termination, the panel shows a reload overlay; toolbar and menu reload actions now recover the page and clear the recovery state.
    • Popups inherit the opener’s process pool and automatically close on WebContent process termination.
    • Added regression tests for per-panel isolation, pool reuse across WKWebView rebuilds, manual recovery via reload, and popup cleanup.

Written for commit 99dfbd4. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Crashes of web content are now isolated to individual browser panels; other panels remain unaffected.
    • Popup windows now reliably close when their underlying web content terminates.
  • New Features

    • Panels support a recoverable crash state with a visible overlay and a prominent Reload button.
    • Toolbar Reload attempts recovery first before a normal reload.
  • Tests

    • Added tests for panel isolation, recovery behavior, and popup crash handling.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled May 29, 2026 10:39am
cmux-staging Building Building Preview, Comment May 29, 2026 10:39am

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

📝 Walkthrough

Walkthrough

This PR gives each BrowserPanel its own WKProcessPool, threads that pool through all WebView creation and replacement code paths, adds a manual recovery state/API for WebContent process termination, adds a recovery UI and reload interception, and closes popup windows when their web content process terminates.

Changes

Per-panel WebKit process isolation

Layer / File(s) Summary
Core process pool isolation in BrowserPanel
Sources/Panels/BrowserPanel.swift
BrowserPanel removes the shared/static process pool, adds an instance processPool property, updates makeWebView and configureWebViewConfiguration to take an explicit processPool, creates a new pool per panel, and wires that pool into all WebView recreation paths (discard/restore, profile switching, termination replacement, workspace reset).
Manual web-content termination recovery
Sources/Panels/BrowserPanel.swift
Refactors termination replacement to optionally wait for manual recovery: compute and store a pending recovery URL, set hasRecoverableWebContentTermination, provide recoverTerminatedWebContent(reason:) and clearWebContentTerminationRecovery(), clear recovery on new navigations, and try recovery from reload() and toolbar actions.
View: reload interception & recovery overlay
Sources/Panels/BrowserPanelView.swift
Toolbar reload now attempts recoverTerminatedWebContent(reason: "toolbarReload") and rendering adds a webContentRecoveryOverlay with a Reload button that calls panel.recoverTerminatedWebContent(reason: "overlayButton") when recoverable termination is present.
Popup web content process termination handling
Sources/Panels/BrowserPopupWindowController.swift
Adds a termination handler that closes popups whose web view's content process terminated; PopupNavigationDelegate forwards webViewWebContentProcessDidTerminate(_:).
Tests: pool separation & termination behavior
cmuxTests/BrowserConfigTests.swift, cmuxTests/BrowserPanelTests.swift, cmuxTests/GhosttyConfigTests.swift
Adds test asserting two BrowserPanels use separate WKProcessPool instances while sharing websiteDataStore; updates termination-replacement and empty-new-tab render-state tests to validate recovery flags and processPool retention; adds floating popup termination test and updates copied-configuration test to pass an explicit WKProcessPool.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#4243: Both PRs modify BrowserPanel's WebView replacement and lifecycle transition flows; this PR introduces per-panel process pools while that PR adjusts lifecycle metadata for replacement flows.
  • manaflow-ai/cmux#4284: Both PRs touch BrowserPanelView.swift reload behavior; this PR adds recovery interception on reload while that PR modifies reload duplication behavior.
  • manaflow-ai/cmux#4886: Overlaps on tests around web view replacement after process termination; this PR updates expectations and adds teardown concerns in related tests.

Poem

🐰 I stash a pool for every pane,
So one crash can't spread its pain.
Popups close when processes fall,
A little rabbit tends them all. 🥕


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning, 3 inconclusive)

Check name Status Explanation Resolution
Cmux Full Internationalization ❌ Error New user-facing string "browser.error.reload" added to Localizable.xcstrings lacks Khmer (km) translation despite km being a supported locale with 19 other translations present for this key. Add "km" locale entry with Khmer translation to the "browser.error.reload" key in Resources/Localizable.xcstrings to match the 20 supported app locales.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift Actor Isolation ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Cmux Swift @Concurrent ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Cmux User-Facing Error Privacy ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (13 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Isolate browser WebKit process pools' directly and clearly describes the main architectural change: giving each BrowserPanel its own WKProcessPool for crash isolation.
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.
Cmux Swift Blocking Runtime ✅ Passed No blocking synchronization primitives introduced: process pool isolation uses state management and callback-based recovery via async navigation, not semaphores, sleeps, locks, or polling.
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift files (.swift). This check applies only to TypeScript, JavaScript, shell, and build scripts; Swift changes are covered by the separate swift-blocking-runtime check.
Cmux Algorithmic Complexity ✅ Passed PR adds per-panel WKProcessPool and crash recovery with O(1) per-panel operations; no nested collection scans, batch rescans, or unbounded algorithms in production code.
Cmux Swift Concurrency ✅ Passed Closure bridge for WebKit callback boundary follows established pattern. No DispatchQueue.global(), Combine state, completion handlers, or problematic Tasks.
Cmux Swift File And Package Boundaries ✅ Passed Adds 57, 31, 12 lines to existing oversized files, all under 250-line threshold. Changes add WebContent process recovery lifecycle management to BrowserPanel—existing responsibility in those files.
Cmux Swift Logging ✅ Passed All logging in PR's new code (process pool isolation, web content recovery) uses cmuxDebugLog guarded by #if DEBUG; NSLog in snapshot/find/download functions are unrelated to PR scope.
Cmux Swiftui State Layout ✅ Passed New @Published state on legacy ObservableObject BrowserPanel; only read in render, mutations in lifecycle handlers/methods, no render-time state writes.
Cmux Architecture Rethink ✅ Passed Recovery state owned by BrowserPanel alone with no timing delays, polling, locks, or lifecycle splits. Single source of truth with clear state transitions satisfies architectural rethink requirements.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No new windows created. Existing BrowserPopupPanel has cmux.browser-popup identifier registered in cmuxAuxiliaryWindowIdentifiers with proper close-shortcut handling via AppDelegate.
Description check ✅ Passed PR description includes summary of changes and thorough testing details with manual verification steps and results.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task-webview-crash-isolation

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 and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 29, 2026

Greptile Summary

Replaces the app-wide shared WKProcessPool with one pool per BrowserPanel, so a WebContent crash or runaway page is contained to its own panel. Panels still share WKWebsiteDataStore per profile, preserving cookies and storage. Popups inherit the opener's pool and close automatically when their WebContent process terminates; the opener's crash path now shows a manual "Reload" overlay instead of auto-navigating.

  • BrowserPanel gains a private let processPool: WKProcessPool created at init; all makeWebView / configureWebViewConfiguration call-sites are updated to thread it through explicitly.
  • replaceWebViewAfterContentProcessTermination now sets hasRecoverableWebContentTermination and stores pendingWebContentRecoveryURL; the replacement webView is shown blank behind an overlay until the user clicks Reload or triggers recoverTerminatedWebContent.
  • BrowserPopupWindowController adds webViewWebContentProcessDidTerminateclosePopup() so dead popup windows are cleaned up without leaving a zombie panel.

Confidence Score: 5/5

Safe to merge; the process isolation and popup-close mechanics are correctly wired end-to-end, and the new recovery overlay path has targeted test coverage.

The crash-isolation goal is achieved cleanly: the stored processPool constant is threaded through every makeWebView call site, popup configurations are overwritten with the opener's pool, and the popup termination delegate correctly tears down the window. The two P2 observations (two-flag recovery state and the missing blankURLString guard in the manual-recovery path) are edge cases with minimal user-visible impact in practice.

Sources/Panels/BrowserPanel.swift — specifically the dual hasRecoverableWebContentTermination / pendingWebContentRecoveryURL state and the recovery navigation path around line 5019.

Important Files Changed

Filename Overview
Sources/Panels/BrowserPanel.swift Core isolation work: per-panel WKProcessPool, manual recovery overlay state (hasRecoverableWebContentTermination + pendingWebContentRecoveryURL), and recovery entry points in reload() and handleReloadOrStopButtonAction(). Two P2 observations: dual-flag recovery state leaves an ambiguous nil-URL renderable-crash path, and the manual-recovery path lacks the blankURLString guard present in the automatic-reload path.
Sources/Panels/BrowserPanelView.swift Adds the webContentRecoveryOverlay SwiftUI view behind hasRecoverableWebContentTermination and wires toolbarReload to recoverTerminatedWebContent. Uses existing localized string keys (browser.error.reload, browser.reload). No issues found.
Sources/Panels/BrowserPopupWindowController.swift Adds handleWebContentProcessTermination / webViewWebContentProcessDidTerminate to close popup windows whose WebContent process dies. Cleanup flow (closePopup → panel.close → windowWillClose → nil delegates) is correct. No issues found.
cmuxTests/BrowserConfigTests.swift New tests for per-panel pool isolation, stable pool across WebView replacement, reload-driven recovery, and empty-tab non-recovery. All assertions are correct.
cmuxTests/GhosttyConfigTests.swift Adds testFloatingPopupClosesWhenWebContentProcessTerminates; fires the delegate callback manually and asserts window hidden + delegates nil. Correct coverage for the new popup-close path.
cmuxTests/BrowserPanelTests.swift Minimal test update to pass an explicit WKProcessPool() to configureWebViewConfiguration, required now that the default-pool parameter was removed.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[WebContent Process Terminates] --> B{Is popup?}
    B -- yes --> C[PopupNavigationDelegate\nwebViewWebContentProcessDidTerminate]
    C --> D[closePopup → panel.close\n→ windowWillClose\n→ nil delegates]
    B -- no --> E[BrowserPanel\nreplaceWebViewAfterContentProcessTermination]
    E --> F{wasRenderable?}
    F -- no blank tab --> G[clearWebContentTerminationRecovery\nno overlay shown]
    F -- yes --> H[New blank WKWebView\ncreated with same processPool]
    H --> I[hasRecoverableWebContentTermination = true\npendingWebContentRecoveryURL = restoreURL]
    I --> J[Recovery overlay shown]
    J --> K{User action}
    K -- clicks Reload or toolbar reload --> L[recoverTerminatedWebContent\nclearWebContentTerminationRecovery]
    L --> M{pendingWebContentRecoveryURL?}
    M -- non-nil URL --> N[navigateWithoutInsecureHTTPPrompt]
    M -- nil --> O[refreshNavigationAvailability\noverlay dismissed, no navigation]
Loading

Reviews (2): Last reviewed commit: "Isolate browser WebKit process pools" | Re-trigger Greptile

@lawrencecchen lawrencecchen force-pushed the task-webview-crash-isolation branch from fddf122 to 99dfbd4 Compare May 29, 2026 10:20
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 99dfbd4. Configure here.

profileID: profileID,
websiteDataStore: websiteDataStore
websiteDataStore: websiteDataStore,
processPool: processPool
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context reset doesn't clear web content recovery state

Medium Severity

resetForWorkspaceContextChange resets the panel to a blank new-tab state (shouldRenderWebView = false, currentURL = nil, etc.) but never calls clearWebContentTerminationRecovery(). If a panel had hasRecoverableWebContentTermination == true when the context reset fires, the recovery overlay (with the "Reload" button) remains visible on top of an otherwise blank new-tab placeholder. The stale pendingWebContentRecoveryURL would also attempt to navigate to the old URL if the user clicks "Reload." Other reset paths like performNavigation and replaceWebViewPreservingState correctly clear this state.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 99dfbd4. Configure here.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 4921-4922: The manual-recovery gate currently uses only
waitForManualRecovery && wasRenderable, which can mark an about:blank/URL-less
view as recoverable; update the shouldShowManualRecovery condition to also
verify there is a real navigation target from sessionNavigationHistorySnapshot()
(e.g., check history has a current entry/url or hasEntries/canNavigate flag)
before presenting recovery UI, and apply the same check in the other occurrence
(around lines referenced 4976-4984) so recoverTerminatedWebContent() only runs
when an actual recoverable URL exists.
- Around line 4914-4918: When building restoreURL in BrowserPanel.swift, prefer
the attempted URL if a provisional main-frame navigation is active: check
navigationDelegate?.isMainFrameProvisionalNavigationActive and, when true, place
navigationDelegate?.lastAttemptedURL (and its remoteProxyDisplayURL) before
currentURL in the fallback chain used to compute restoreURL; otherwise keep the
existing ordering. Update the restoreURL expression that calls
Self.remoteProxyDisplayURL(for:), currentURL,
navigationDelegate?.lastAttemptedURL, and resolvedCurrentSessionHistoryURL() to
branch on isMainFrameProvisionalNavigationActive and swap the attempted-URL
entries into the first fallback positions.

In `@Sources/Panels/BrowserPanelView.swift`:
- Around line 1661-1681: The recovery overlay's Label uses the localized key
"browser.error.reload" (seen in webContentRecoveryOverlay) but the Khmer locale
is missing in Resources/Localizable.xcstrings; add a "km" entry for
"browser.error.reload" with the correct Khmer translation (matching the format
used for other locales), ensure the string encoding/quoting matches existing
.xcstrings entries and include the plural/variant if applicable so the Label
shows Khmer instead of falling back to English.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 94a99af9-8c0f-45cf-9315-27954b49ca52

📥 Commits

Reviewing files that changed from the base of the PR and between fddf122 and 99dfbd4.

📒 Files selected for processing (6)
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/Panels/BrowserPopupWindowController.swift
  • cmuxTests/BrowserConfigTests.swift
  • cmuxTests/BrowserPanelTests.swift
  • cmuxTests/GhosttyConfigTests.swift

Comment on lines +4914 to +4918
let restoreURL = Self.remoteProxyDisplayURL(for: oldWebView.url)
?? currentURL
?? Self.remoteProxyDisplayURL(for: navigationDelegate?.lastAttemptedURL)
?? navigationDelegate?.lastAttemptedURL
?? resolvedCurrentSessionHistoryURL()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prefer the attempted URL when termination interrupts a provisional load.

currentURL still points to the last committed page while a provisional navigation is in flight, so this ordering can recover page A after page B crashes during load. Use navigationDelegate?.lastAttemptedURL first when isMainFrameProvisionalNavigationActive is true.

Suggested fix
-        let restoreURL = Self.remoteProxyDisplayURL(for: oldWebView.url)
-            ?? currentURL
-            ?? Self.remoteProxyDisplayURL(for: navigationDelegate?.lastAttemptedURL)
-            ?? navigationDelegate?.lastAttemptedURL
-            ?? resolvedCurrentSessionHistoryURL()
+        let attemptedURL = Self.remoteProxyDisplayURL(for: navigationDelegate?.lastAttemptedURL)
+            ?? navigationDelegate?.lastAttemptedURL
+        let liveURL = Self.remoteProxyDisplayURL(for: oldWebView.url)
+            ?? currentURL
+        let restoreURL = (isMainFrameProvisionalNavigationActive ? attemptedURL : nil)
+            ?? liveURL
+            ?? attemptedURL
+            ?? resolvedCurrentSessionHistoryURL()
📝 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
let restoreURL = Self.remoteProxyDisplayURL(for: oldWebView.url)
?? currentURL
?? Self.remoteProxyDisplayURL(for: navigationDelegate?.lastAttemptedURL)
?? navigationDelegate?.lastAttemptedURL
?? resolvedCurrentSessionHistoryURL()
let attemptedURL = Self.remoteProxyDisplayURL(for: navigationDelegate?.lastAttemptedURL)
?? navigationDelegate?.lastAttemptedURL
let liveURL = Self.remoteProxyDisplayURL(for: oldWebView.url)
?? currentURL
let restoreURL = (isMainFrameProvisionalNavigationActive ? attemptedURL : nil)
?? liveURL
?? attemptedURL
?? resolvedCurrentSessionHistoryURL()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Panels/BrowserPanel.swift` around lines 4914 - 4918, When building
restoreURL in BrowserPanel.swift, prefer the attempted URL if a provisional
main-frame navigation is active: check
navigationDelegate?.isMainFrameProvisionalNavigationActive and, when true, place
navigationDelegate?.lastAttemptedURL (and its remoteProxyDisplayURL) before
currentURL in the fallback chain used to compute restoreURL; otherwise keep the
existing ordering. Update the restoreURL expression that calls
Self.remoteProxyDisplayURL(for:), currentURL,
navigationDelegate?.lastAttemptedURL, and resolvedCurrentSessionHistoryURL() to
branch on isMainFrameProvisionalNavigationActive and swap the attempted-URL
entries into the first fallback positions.

Comment on lines +4921 to 4922
let shouldShowManualRecovery = waitForManualRecovery && wasRenderable
let history = sessionNavigationHistorySnapshot()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only surface manual recovery when there is an actual recovery target.

shouldShowManualRecovery only checks wasRenderable, so a terminated about:blank/URL-less view still becomes “recoverable”. recoverTerminatedWebContent() then swallows Reload, clears the flag, and leaves the replacement web view blank.

Suggested fix
-        let shouldShowManualRecovery = waitForManualRecovery && wasRenderable
+        let shouldShowManualRecovery = waitForManualRecovery && shouldRestoreURL
@@
-        if shouldShowManualRecovery {
-            pendingWebContentRecoveryURL = restoreURL
+        if shouldShowManualRecovery, let restoreURL {
+            pendingWebContentRecoveryURL = restoreURL
             hasRecoverableWebContentTermination = true
             refreshNavigationAvailability()
         } else {
             clearWebContentTerminationRecovery()
         }

Also applies to: 4976-4984

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Panels/BrowserPanel.swift` around lines 4921 - 4922, The
manual-recovery gate currently uses only waitForManualRecovery && wasRenderable,
which can mark an about:blank/URL-less view as recoverable; update the
shouldShowManualRecovery condition to also verify there is a real navigation
target from sessionNavigationHistorySnapshot() (e.g., check history has a
current entry/url or hasEntries/canNavigate flag) before presenting recovery UI,
and apply the same check in the other occurrence (around lines referenced
4976-4984) so recoverTerminatedWebContent() only runs when an actual recoverable
URL exists.

Comment on lines +1661 to +1681
private var webContentRecoveryOverlay: some View {
ZStack {
Color(nsColor: browserChromeBackgroundColor)
.opacity(0.92)
Button(action: {
panel.recoverTerminatedWebContent(reason: "overlayButton")
}) {
Label(
String(localized: "browser.error.reload", defaultValue: "Reload"),
systemImage: "arrow.clockwise"
)
.font(.system(size: 13, weight: .medium))
.padding(.horizontal, 6)
}
.buttonStyle(.borderedProminent)
.controlSize(.regular)
.safeHelp(String(localized: "browser.reload", defaultValue: "Reload"))
.accessibilityIdentifier("BrowserWebContentRecoveryButton")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import json
import pathlib

key = "browser.error.reload"
xcstrings_files = sorted(pathlib.Path(".").rglob("*.xcstrings"))

if not xcstrings_files:
    print("No .xcstrings files found.")
    raise SystemExit(0)

found = False

for path in xcstrings_files:
    try:
        data = json.loads(path.read_text())
    except Exception as exc:
        print(f"{path}: failed to parse JSON: {exc}")
        continue

    strings = data.get("strings", {})
    if key not in strings:
        continue

    found = True
    entry = strings[key]
    key_locales = set((entry.get("localizations") or {}).keys())

    catalog_locales = set()
    for value in strings.values():
        if isinstance(value, dict):
            catalog_locales.update((value.get("localizations") or {}).keys())

    missing = sorted(catalog_locales - key_locales)

    print(f"{path}:")
    print(f"  key locales     = {sorted(key_locales)}")
    print(f"  catalog locales = {sorted(catalog_locales)}")
    print(f"  missing locales = {missing}")

if not found:
    print(f"Missing localization key: {key}")
PY

Repository: manaflow-ai/cmux

Length of output: 422


Fix missing Khmer translation for browser.error.reload.

browser.error.reload is present in Resources/Localizable.xcstrings, but the km locale entry is missing—this recovery overlay will fall back to English for Khmer. Add a km translation for this key.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Panels/BrowserPanelView.swift` around lines 1661 - 1681, The recovery
overlay's Label uses the localized key "browser.error.reload" (seen in
webContentRecoveryOverlay) but the Khmer locale is missing in
Resources/Localizable.xcstrings; add a "km" entry for "browser.error.reload"
with the correct Khmer translation (matching the format used for other locales),
ensure the string encoding/quoting matches existing .xcstrings entries and
include the plural/variant if applicable so the Label shows Khmer instead of
falling back to English.

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