Skip to content

browser: expose hidden webview discard settings - #4283

Closed
austinywang wants to merge 19 commits into
mainfrom
codex/pr-4245-browser-settings-clean
Closed

austinywang wants to merge 19 commits into
mainfrom
codex/pr-4245-browser-settings-clean

Conversation

@austinywang

@austinywang austinywang commented May 18, 2026 •

Copy link
Copy Markdown
Contributor

Supersedes #4245 with conflicts resolved against current main.\n\nChanges:\n- Adds Browser Memory Saver settings backed by UserDefaults, env overrides, and cmux.json.\n- Adds schema entries, Settings search wiring, reset handling, and English/Japanese localizations.\n- Clamps the hidden discard delay to 0...3600 seconds across runtime, UI, and cmux.json parsing.\n- Defers portal lifecycle visibility updates out of updateNSView with a generation guard and keeps discard timer work on MainActor.\n\nValidation:\n- Parsed Resources/Localizable.xcstrings and web/data/cmux.schema.json as JSON.\n- Ran git diff --check.\n- Attempted ./scripts/reload.sh --tag pr4245-browser-settings; local xcodebuild lock is currently held by another agent, so no tagged app path was produced yet.


Note

Medium Risk
Moderate risk because it changes BrowserPanel webview lifecycle/timer behavior and adds new UserDefaults/env/cmux.json inputs that can affect tab restoration and navigation timing.

Overview
Adds a user-facing Browser Memory Saver that can discard hidden browser tabs after a configurable delay and restore them when shown again.

Introduces BrowserHiddenWebViewDiscardPolicy (UserDefaults-backed with env overrides + 0–3600s clamping) and a BrowserHiddenWebViewDiscardManager to centralize scheduling, blocker checks (downloads/devtools/popups/fullscreen/loading), and policy-change observation; BrowserPanel is refactored to delegate discard/restore state and telemetry fields to this manager.

Wires the new toggle + delay controls into Settings UI (including reset behavior), settings search indexing/aliases, and cmux.json support (schema, template, JSON path allowlist + parsing validation). Also defers portal visibility lifecycle updates out of updateNSView to a generation-guarded MainActor task, and updates tests to cover policy/defaults and to assert boolean defaults via object(forKey:).

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


Summary by cubic

Expose Browser Memory Saver to discard hidden tabs after a configurable delay and restore on show to reduce memory use. Adds settings, config, telemetry, and dedicated BrowserHiddenWebViewDiscardPolicy + MainActor BrowserHiddenWebViewDiscardManager with blocker-aware scheduling, policy observation, and strict 0–3600s delay clamping.

  • New Features

    • Settings: toggle and delay stepper (EN/JA), searchable (with aliases), included in Reset All; UI binding clamps and shows mm/ss labels.
    • Config/Policy: browser.discardHiddenWebViews, browser.hiddenWebViewDiscardDelaySeconds in cmux.json (schema + template), UserDefaults keys, and env CMUX_BROWSER_HIDDEN_WEBVIEW_DISCARD_ENABLED/CMUX_BROWSER_HIDDEN_WEBVIEW_DISCARD_DELAY_SECONDS with validation and clamping; JSON path allowlist + settings search anchors added.
    • Behavior/Manager: hidden tabs discard after the delay and restore on show or navigation; lifecycle state discarded, telemetry (eligibility, blockers, timestamps, last reasons), centralized scheduling via BrowserHiddenWebViewDiscardManager with blocker checks and policy change observation; tests cover defaults and clamping.
  • Bug Fixes

    • Encapsulate restored session render intent in the manager to avoid stale flags across navigation/session restores.
    • Pause/cancel scheduled discard when blockers appear (downloads, devtools, popups, fullscreen, loading) and reschedule when cleared; explicit rescheduling on download start and devtools visibility changes.
    • Observe UserDefaults on MainActor and only reschedule when the resolved policy actually changes.
    • Use a main-thread timer with generation and WebView instance guards to prevent stale discards on visibility/policy changes; safe cleanup via nonisolated stop().
    • Preserve hidden portal lifecycle visibility updates by deferring out of updateNSView with a generation-guarded MainActor task.

Written for commit 7669829. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Hidden browser-tab memory saver: hidden tabs can be auto-discarded to free memory and restored on access; scheduling respects visibility, activity, devtools/popups, navigation and other blockers.
  • Settings

    • UI: toggle and configurable discard delay (default 5 minutes); Reset All restores defaults.
    • Settings indexing and quick-search aliases added.
  • Localization

    • Localized strings and aliases for the feature and delay (English & Japanese).
  • Tests

    • Added tests for policy defaults, clamping, and discard/restore behavior.
  • Chores

    • Configuration schema and settings template updated to include the new options.

Review Change Stack

@vercel

vercel Bot commented May 18, 2026 •

Copy link
Copy Markdown

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

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

@coderabbitai

coderabbitai Bot commented May 18, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a configurable hidden-WebView memory-discard feature: a policy resolves enabled/delay settings, a manager schedules/cancels delayed discards using blocker snapshots, BrowserPanel snapshots/replaces/restores WKWebView instances, settings/schema/localization/search and parsing are wired, and tests exercise policy behavior.

Changes

Hidden WebView Discard for Memory Management

Layer / File(s) Summary
Policy configuration & defaults
Sources/Panels/BrowserHiddenWebViewDiscardPolicy.swift, Sources/KeyboardShortcutSettingsFileStore+Template.swift, web/data/cmux.schema.json, Sources/CmuxSettingsJSONPathSupport.swift
BrowserHiddenWebViewDiscardPolicy resolves enabled and delay from env/UserDefaults with validation and clamping; template, schema, and supported-settings JSON paths updated.
Discard manager implementation
Sources/Panels/BrowserHiddenWebViewDiscardManager.swift
New BrowserHiddenWebViewDiscardManager and delegate protocol implement blocker snapshot shape, schedule/cancel logic, policy observation, discard/restore/reactivate helpers, metadata, and cleanup.
BrowserPanel integration & core discard/restore
Sources/Panels/BrowserPanel.swift
Adds .discarded lifecycle state, moves discard state to manager, delegates scheduling/metadata to manager, routes discard/restore/reactivation through manager helpers, and updates lifecycle payloads.
Settings, schema, file parsing & search indexing
Sources/cmuxApp.swift, Sources/KeyboardShortcutSettingsFileStore.swift, Sources/SettingsNavigation.swift, Sources/SettingsSearchAliases.swift, Resources/Localizable.xcstrings, web/data/cmux.schema.json
Adds AppStorage-backed settings UI (toggle + delay stepper and formatting helpers), parsing support for browser.* keys, SettingsSearchIndex entries/anchors, search-alias localization, JSON schema entries, and localization strings.
Portal visibility async guard
Sources/Panels/BrowserPanelView.swift
Defers portal visibility notifications via a main-actor task guarded by coordinator generation to avoid stale coordinator-driven visibility updates.
Project file additions
cmux.xcodeproj/project.pbxproj
Adds the two new Swift source files to the Xcode project build/source groups.
Unit tests
cmuxTests/GhosttyConfigTests.swift
Adds tests verifying policy behavior reading/clamping UserDefaults and environment overrides.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • manaflow-ai/cmux#4243: Related lifecycle metadata and .discarded lifecycle work in BrowserPanel.
  • manaflow-ai/cmux#892: Changes to portal-driven WebView lifecycle and portal visibility handling intersect with this PR's portal updates.
  • manaflow-ai/cmux#1897: Overlap in back/forward navigation handling and restored-session history behavior.

"🐰 I time the ticks before the memory nap,
Hidden tabs curl up in a compact nap,
When a click wakes them they hop back to view,
Snapshots kept safe, defaults set true — hooray! 🥕"


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (2 errors, 2 warnings, 3 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error PR introduces DispatchSourceTimer in BrowserHiddenWebViewDiscardManager. Rules flag 'timers as failures by default' in shipped code, despite proper guards. Use state-based delay tracking via notifications instead of timer scheduling, or justify alignment with existing timer patterns in codebase.
Cmux Swift Concurrency ❌ Error PR introduces DispatchSourceTimer in BrowserHiddenWebViewDiscardManager to schedule discard timeouts. Modernization rules require async/await with Task.sleep instead of legacy Dispatch patterns. Replace DispatchSourceTimer with Task-based scheduling using Task.sleep and proper stored Task cancellation tracking, matching existing patterns in BrowserPanel.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description provides detailed context and validation steps, but does not follow the required template structure with specific section headings for Summary, Testing, Demo Video, Review Trigger, and Checklist. Restructure the PR description to match the template: add clear Summary, Testing, and Demo Video sections; include the Review Trigger block; and complete the Checklist items.
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 (9 passed)
Check name Status Explanation
Title check ✅ Passed The title 'browser: expose hidden webview discard settings' accurately describes the main change: exposing user-configurable settings for the browser's hidden WebView discard feature.
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 No Hacky Sleeps ✅ Passed Check not applicable: rule scope covers TypeScript/JavaScript/shell runtime only. PR modifies only Swift code and configuration files, explicitly excluded from this check.
Cmux Swift File And Package Boundaries ✅ Passed New files under 400 lines with focused responsibilities. File additions all under 250-line threshold for oversized files. No inappropriate responsibility mixing observed.
Cmux Swift Logging ✅ Passed PR introduces no logging violations. New files contain no print/debugPrint/dump/NSLog calls, no Logger violations, and no sensitive data logging. Modified files add no new logging statements.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state layout violations. Uses @AppStorage for settings, proper encapsulation via manager, no new ObservableObject/@published, no GeometryReader/lazy issues, deferred Task calls with guards.
Cmux Architecture Rethink ✅ Passed Architectural refactoring. Manager owns discard state with proper guards (generation, instance ID). No duplicate ownership, side channels or split lifecycle. Legitimate encapsulation pattern.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds browser memory saver feature without introducing new NSWindow, NSPanel, NSWindowController, Window, or WindowGroup code. No standalone window violations found.
✨ 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 codex/pr-4245-browser-settings-clean

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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 11 files

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented May 18, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR exposes a user-facing Browser Memory Saver that discards hidden WebView tabs after a configurable delay and restores them on show. It supersedes #4245 and incorporates all of the follow-up fixes requested in that round of review.

  • Introduces BrowserHiddenWebViewDiscardPolicy (nonisolated enum, env overrides, 0–3600 s clamping) and BrowserHiddenWebViewDiscardManager (@MainActor, DispatchSourceTimer with generation guard, UserDefaults change-detection via ResolvedPolicy equality), extracting all timer/state/observer logic out of BrowserPanel.
  • Wires the feature end-to-end: cmux.json schema + parser allowlist + template, Settings UI toggle + delay stepper with clamped binding and mm/ss label, search index + aliases, Reset All, EN/JA localizations, and targeted test coverage for defaults/clamping/lifecycle.
  • Defers portal visibility notifications out of updateNSView via a generation-guarded Task { @MainActor } to avoid SwiftUI render-time state mutation; adds download-start and devtools-open rescheduling hooks.

Confidence Score: 5/5

Safe to merge; all previous iteration findings are resolved and the new discard/restore paths are well-guarded.

The manager extraction, @mainactor isolation, DispatchSourceTimer with generation guard, nonisolated policy type, and delegate wiring are all correctly implemented. The only remaining note is the deferred portal visibility Task in BrowserPanelView which is not stored in Coordinator for cancellation — it relies entirely on the generation guard to discard stale runs rather than cancelling them up-front. That is a minor structural concern with no observed failure path given the guards in place.

Sources/Panels/BrowserPanelView.swift — the new schedulePortalLifecycleVisibilityUpdate helper creates Tasks that cannot be proactively cancelled between rapid updateNSView calls.

Important Files Changed

Filename Overview
Sources/Panels/BrowserHiddenWebViewDiscardManager.swift New @mainactor class centralizing discard scheduling via DispatchSourceTimer with generation guard, policy-change observation, and structured blocker logic; delegate protocol and stop() lifecycle are correctly isolated.
Sources/Panels/BrowserHiddenWebViewDiscardPolicy.swift New nonisolated enum encapsulating enable/delay policy with env-var overrides, UserDefaults reads, and 0–3600s clamping; ResolvedPolicy equatability drives change-detection in the manager.
Sources/Panels/BrowserPanel.swift Extracted discard state, timer, and policy observer into BrowserHiddenWebViewDiscardManager; adds BrowserHiddenWebViewDiscardManagerDelegate conformance and download/devtools reschedule hooks; deinit delegates stop() cleanly.
Sources/Panels/BrowserPanelView.swift Defers portal lifecycle visibility updates from updateNSView via an unstructured fire-and-forget Task guarded by generation and desiredVisibility checks; the Task is not stored in Coordinator so prior in-flight tasks cannot be cancelled before firing their guards.
Sources/cmuxApp.swift Adds @AppStorage bindings, clamped Binding wrapper, mm/ss label helper, and Settings UI rows for the toggle and delay stepper; reset-all wiring and accessibility identifiers are present.
Sources/KeyboardShortcutSettingsFileStore.swift Adds cmux.json parsing for discardHiddenWebViews (bool) and hiddenWebViewDiscardDelaySeconds (double, clamped via resolvedHiddenDelay); early-return on invalid delay matches the existing fail-fast pattern in parseBrowserSection.

Sequence Diagram

sequenceDiagram
    participant BPV as BrowserPanelView
    participant BP as BrowserPanel
    participant MGR as BrowserHiddenWebViewDiscardManager
    participant POL as BrowserHiddenWebViewDiscardPolicy
    participant UD as UserDefaults

    BPV->>BP: noteWebViewVisibility(false)
    BP->>MGR: scheduleIfNeeded(reason)
    MGR->>POL: isEnabled / hiddenDelay
    POL->>UD: reads UserDefaults.standard
    POL-->>MGR: "enabled=true, delay=300s"
    MGR->>MGR: arm DispatchSourceTimer(remaining)
    Note over MGR: 300s later, no blockers
    MGR-->>BP: didRequestDiscard
    BP->>MGR: markDiscarded(reason, now)
    Note over BP: Tab is .discarded
    BPV->>BP: noteWebViewVisibility(true)
    BP->>MGR: restoreIfNeeded(reason, performRestore)
    MGR->>MGR: cancel + clearDiscardState
    MGR-->>BP: performRestore closure
    BP->>BP: "shouldRenderWebView=true, navigate"
    Note over BP: Tab restored to .active
    UD-->>MGR: didChangeNotification
    MGR->>POL: resolved() compare policyState
    alt policy changed
        MGR-->>BP: policyDidChange
        BP->>MGR: scheduleIfNeeded / cancel
    end
Loading

Reviews (10): Last reviewed commit: "fix: preserve hidden portal lifecycle up..." | Re-trigger Greptile

Comment thread Sources/Panels/BrowserPanel.swift Outdated
Comment thread Sources/Panels/BrowserPanel.swift Outdated
Comment thread Sources/Panels/BrowserPanel.swift Outdated
Comment thread Sources/Panels/BrowserPanel.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/Panels/BrowserPanel.swift (1)

2249-3220: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Extract the discard policy/scheduler out of BrowserPanel.swift before merge.

This adds another large chunk of lifecycle/policy code to a file that is already far past the repo’s size budget, and it pushes even more independent state-management logic into an already mixed-responsibility panel implementation. Please move the hidden-discard policy and scheduling/restore logic into dedicated types/files instead of growing this file further.

As per coding guidelines **/{Sources,CLI,Packages,cmuxTests,cmuxUITests}/**/*.swift: “Do not add more than 250 lines to an existing production Swift file that is already over 800 lines” and “Do not mix UI rendering, state ownership, persistence, networking, parsing, subprocess/socket protocol, and platform bridge code in one Swift file.”

🤖 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 2249 - 3220, Extract the
hidden-webview discard policy, scheduler and restore logic into a new type (e.g.
BrowserHiddenWebViewDiscardManager) in its own file and replace the in-file
logic with a small interface from BrowserPanel that delegates to that manager;
move BrowserHiddenWebViewDiscardPolicy (the nonisolated enum), the state
properties hiddenWebViewDiscardTimer, hiddenWebViewDiscardPolicyCancellable,
isWebViewDiscardedForMemory, webViewDiscardedAt, webViewLastDiscardReason,
webViewLastRestoreReason, restoredSessionShouldRenderWebView and any related
booleans/flags plus the methods hiddenWebViewDiscardBlockers(),
scheduleHiddenWebViewDiscardIfNeeded(reason:), cancelHiddenWebViewDiscard(),
reevaluateHiddenWebViewDiscardScheduling(reason:),
installHiddenWebViewDiscardPolicyObserver(),
discardHiddenWebViewForMemory(reason:now:),
restoreDiscardedWebViewIfNeeded(reason:), clearWebViewDiscardState(reason:) and
reactivateDiscardedWebViewWithoutNavigation(reason:) into that manager; expose a
minimal API on the manager for BrowserPanel to call (e.g.
start/stop/schedule/forceDiscard/restore/queryState) and update BrowserPanel to
call those APIs (preserving behavior such as using webViewInstanceID, performing
session snapshot/restore via existing BrowserPanel helpers like
sessionNavigationHistorySnapshot(), bindWebView(_:) and
restoreSessionNavigationHistory(...), and calling
refreshWebViewLifecycleState()/refreshNavigationAvailability() where needed).
Ensure all moved logic compiles by updating references to private members to use
manager APIs and keep only UI/state orchestration in BrowserPanel.
🤖 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 3067-3093: The timer handler must be guarded by a
schedule-generation token so stale queued handlers can't act after a reschedule;
add a property like hiddenWebViewDiscardScheduleGeneration (increment it each
time scheduleHiddenWebViewDiscardIfNeeded runs), capture the current generation
in the timer's closure and verify it matches before cancelling/clearing
hiddenWebViewDiscardTimer and calling discardHiddenWebViewForMemory(reason:), in
addition to the existing webViewInstanceID check; ensure you increment the
generation before creating the new DispatchSource and clear/increment it again
when cancelling timers to fully invalidate older handlers.

---

Outside diff comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 2249-3220: Extract the hidden-webview discard policy, scheduler
and restore logic into a new type (e.g. BrowserHiddenWebViewDiscardManager) in
its own file and replace the in-file logic with a small interface from
BrowserPanel that delegates to that manager; move
BrowserHiddenWebViewDiscardPolicy (the nonisolated enum), the state properties
hiddenWebViewDiscardTimer, hiddenWebViewDiscardPolicyCancellable,
isWebViewDiscardedForMemory, webViewDiscardedAt, webViewLastDiscardReason,
webViewLastRestoreReason, restoredSessionShouldRenderWebView and any related
booleans/flags plus the methods hiddenWebViewDiscardBlockers(),
scheduleHiddenWebViewDiscardIfNeeded(reason:), cancelHiddenWebViewDiscard(),
reevaluateHiddenWebViewDiscardScheduling(reason:),
installHiddenWebViewDiscardPolicyObserver(),
discardHiddenWebViewForMemory(reason:now:),
restoreDiscardedWebViewIfNeeded(reason:), clearWebViewDiscardState(reason:) and
reactivateDiscardedWebViewWithoutNavigation(reason:) into that manager; expose a
minimal API on the manager for BrowserPanel to call (e.g.
start/stop/schedule/forceDiscard/restore/queryState) and update BrowserPanel to
call those APIs (preserving behavior such as using webViewInstanceID, performing
session snapshot/restore via existing BrowserPanel helpers like
sessionNavigationHistorySnapshot(), bindWebView(_:) and
restoreSessionNavigationHistory(...), and calling
refreshWebViewLifecycleState()/refreshNavigationAvailability() where needed).
Ensure all moved logic compiles by updating references to private members to use
manager APIs and keep only UI/state orchestration in BrowserPanel.
🪄 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: 2f77df8d-f4a8-4c07-b6c3-72305774d273

📥 Commits

Reviewing files that changed from the base of the PR and between 1956a7e and ff6d2f1.

📒 Files selected for processing (11)
  • Resources/Localizable.xcstrings
  • Sources/CmuxSettingsJSONPathSupport.swift
  • Sources/KeyboardShortcutSettingsFileStore+Template.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/SettingsNavigation.swift
  • Sources/SettingsSearchAliases.swift
  • Sources/cmuxApp.swift
  • cmuxTests/GhosttyConfigTests.swift
  • web/data/cmux.schema.json

Comment thread Sources/Panels/BrowserPanel.swift
Comment thread Sources/Panels/BrowserHiddenWebViewDiscardManager.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Sources/Panels/BrowserPanel.swift (2)

5496-5532: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reevaluate hidden-discard scheduling when DevTools becomes visible.

Lines 5498-5532 only reevaluate the discard scheduler on the hide path. That leaves any timer armed before DevTools opened still running through the DevTools-visible interval, so the next discard can happen earlier than the configured delay after DevTools is closed again.

Suggested fix
         let visible = inspector.cmuxCallBool(selector: isVisibleSelector) ?? false
         setPreferredDeveloperToolsVisible(targetVisible)
         developerToolsTransitionTargetVisible = targetVisible
+        if targetVisible {
+            reevaluateHiddenWebViewDiscardScheduling(reason: "developer_tools_visibility_changed")
+        }
 
         if targetVisible {
             if !visible {
                 _ = revealDeveloperTools(inspector)
             } else {
🤖 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 5496 - 5532, The code only
calls reevaluateHiddenWebViewDiscardAfterDeveloperToolsHidden() when DevTools
are hidden; to prevent an armed discard timer from firing while DevTools are
visible, call reevaluateHiddenWebViewDiscardAfterDeveloperToolsHidden() when
DevTools become visible as well. Specifically, in the targetVisible true path
after you detect visibleAfterTransition (the block after
inspector.cmuxCallBool(selector: isVisibleSelector) where you call
syncDeveloperToolsPresentationPreferenceFromUI(),
cancelDeveloperToolsRestoreRetry(), and
scheduleDetachedDeveloperToolsWindowDismissal()), add a call to
reevaluateHiddenWebViewDiscardAfterDeveloperToolsHidden() so the hidden-webview
discard scheduler is rechecked/cancelled when DevTools open.

3623-3641: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pause the discard countdown when a download starts.

Line 3624 only flips the download state; it never invalidates or reevaluates an already-armed hidden-discard timer. If a hidden tab becomes ineligible because a download starts, the old timer can keep counting and discard too soon after the download finishes.

Suggested fix
 private func beginDownloadActivity() {
     let apply = {
+        let wasDownloading = self.isDownloading
         self.activeDownloadCount += 1
         self.isDownloading = self.activeDownloadCount > 0
+        if !wasDownloading && self.isDownloading {
+            self.reevaluateHiddenWebViewDiscardScheduling(reason: "download.started")
+        }
     }
     if Thread.isMainThread {
         apply()
     } else {
         DispatchQueue.main.async(execute: apply)
🤖 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 3623 - 3641, When a download
starts beginDownloadActivity currently flips isDownloading but doesn't stop an
already-armed hidden-discard timer, so add logic that, when activeDownloadCount
transitions from 0→1, cancels/invalidates any pending hidden-webview-discard
timer; implement or call a helper like cancelHiddenWebViewDiscard() or
invalidateHiddenWebViewDiscardTimer() from inside the apply closure of
beginDownloadActivity (keeping the DispatchQueue.main behavior) so the discard
countdown is paused while downloading, and leave the existing
scheduleHiddenWebViewDiscardIfNeeded(reason: "download.finished") in
endDownloadActivity to re-arm/re-evaluate after downloads complete.
🤖 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/BrowserHiddenWebViewDiscardManager.swift`:
- Around line 18-51: The class BrowserHiddenWebViewDiscardManager mutates
several properties (discardTask, scheduleGeneration, isDiscardedForMemory,
discardedAt, lastDiscardReason, lastRestoreReason,
restoredSessionShouldRenderWebView, etc.) but isn't annotated with `@MainActor`,
so mark the class declaration with `@MainActor` to ensure all its instance methods
and property accessors are executed on the main actor; update the declaration
"final class BrowserHiddenWebViewDiscardManager" to "`@MainActor` final class
BrowserHiddenWebViewDiscardManager" and ensure any external callers or delegate
interactions (BrowserHiddenWebViewDiscardManagerDelegate usages) are invoked
from the main actor to avoid compiler warnings or data races.

In `@Sources/Panels/BrowserHiddenWebViewDiscardPolicy.swift`:
- Around line 31-37: The isEnabled(defaults:) function currently treats any
non-false env token as enabled; change it so the environment override only
enables the feature when the token explicitly matches a true set (e.g.
"1","true","yes","on") and otherwise falls through to honor UserDefaults. In
BrowserHiddenWebViewDiscardPolicy.isEnabled(defaults:) read
ProcessInfo.processInfo.environment["CMUX_BROWSER_HIDDEN_WEBVIEW_DISCARD_ENABLED"],
trim and lowercased, and if present return true only when it is in the
accepted-true list; if absent or any other value, return the existing
defaults.bool(forKey:) behavior (i.e. treat unknown tokens as no override).

---

Outside diff comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 5496-5532: The code only calls
reevaluateHiddenWebViewDiscardAfterDeveloperToolsHidden() when DevTools are
hidden; to prevent an armed discard timer from firing while DevTools are
visible, call reevaluateHiddenWebViewDiscardAfterDeveloperToolsHidden() when
DevTools become visible as well. Specifically, in the targetVisible true path
after you detect visibleAfterTransition (the block after
inspector.cmuxCallBool(selector: isVisibleSelector) where you call
syncDeveloperToolsPresentationPreferenceFromUI(),
cancelDeveloperToolsRestoreRetry(), and
scheduleDetachedDeveloperToolsWindowDismissal()), add a call to
reevaluateHiddenWebViewDiscardAfterDeveloperToolsHidden() so the hidden-webview
discard scheduler is rechecked/cancelled when DevTools open.
- Around line 3623-3641: When a download starts beginDownloadActivity currently
flips isDownloading but doesn't stop an already-armed hidden-discard timer, so
add logic that, when activeDownloadCount transitions from 0→1,
cancels/invalidates any pending hidden-webview-discard timer; implement or call
a helper like cancelHiddenWebViewDiscard() or
invalidateHiddenWebViewDiscardTimer() from inside the apply closure of
beginDownloadActivity (keeping the DispatchQueue.main behavior) so the discard
countdown is paused while downloading, and leave the existing
scheduleHiddenWebViewDiscardIfNeeded(reason: "download.finished") in
endDownloadActivity to re-arm/re-evaluate after downloads complete.
🪄 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: 0c22dd8d-4dc3-4e2d-b855-3fa67ace9a2d

📥 Commits

Reviewing files that changed from the base of the PR and between ff6d2f1 and a10ac20.

📒 Files selected for processing (4)
  • Sources/Panels/BrowserHiddenWebViewDiscardManager.swift
  • Sources/Panels/BrowserHiddenWebViewDiscardPolicy.swift
  • Sources/Panels/BrowserPanel.swift
  • cmux.xcodeproj/project.pbxproj

Comment thread Sources/Panels/BrowserHiddenWebViewDiscardManager.swift
Comment thread Sources/Panels/BrowserHiddenWebViewDiscardPolicy.swift
Comment thread Sources/Panels/BrowserHiddenWebViewDiscardManager.swift Outdated
Comment thread Sources/Panels/BrowserHiddenWebViewDiscardManager.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/BrowserHiddenWebViewDiscardManager.swift`:
- Around line 127-133: The current nonisolated stop() uses
MainActor.assumeIsolated which is unsafe from deinit; replace that with an
explicit asynchronous hop to the main actor so cleanup runs on Main safely:
remove MainActor.assumeIsolated and instead spawn Task { `@MainActor` in cancel();
policyObservationTask?.cancel(); policyObservationTask = nil } (or MainActor.run
{ ... } inside an async Task) so stop() remains nonisolated but performs
main-thread cleanup without invoking assumeIsolated; update any callers/tests
expecting synchronous teardown accordingly.

In `@Sources/Panels/BrowserPanel.swift`:
- Line 3561: BrowserPanel is directly setting
hiddenWebViewDiscardManager.restoredSessionShouldRenderWebView in multiple
lifecycle paths; add an encapsulating API on HiddenWebViewDiscardManager such as
setRestoredSessionShouldRenderWebView(Bool) and
clearRestoredSessionShouldRenderWebView() (or a single
updateRestoredSessionRenderIntent(_:)) and replace all direct writes to
restoredSessionShouldRenderWebView in BrowserPanel with those manager methods so
the discard/session-snapshot invariant is maintained in one place; update
HiddenWebViewDiscardManager to perform any related side effects/validation when
the flag changes and replace every occurrence where BrowserPanel writes the
property (previously using
hiddenWebViewDiscardManager.restoredSessionShouldRenderWebView) to call the new
manager API.
🪄 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: c91919d5-3457-4bf3-840a-6545e3add447

📥 Commits

Reviewing files that changed from the base of the PR and between f0ac512 and 113105f.

📒 Files selected for processing (4)
  • Sources/Panels/BrowserHiddenWebViewDiscardManager.swift
  • Sources/Panels/BrowserHiddenWebViewDiscardPolicy.swift
  • Sources/Panels/BrowserPanel.swift
  • cmuxTests/GhosttyConfigTests.swift

Comment thread Sources/Panels/BrowserHiddenWebViewDiscardManager.swift
Comment thread Sources/Panels/BrowserPanel.swift Outdated
@cubic-dev-ai

cubic-dev-ai Bot commented May 18, 2026

Copy link
Copy Markdown

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

Comment thread Sources/Panels/BrowserPanelView.swift
Comment thread Sources/Panels/BrowserHiddenWebViewDiscardManager.swift
@lawrencecchen
lawrencecchen dismissed stale reviews from coderabbitai[bot], coderabbitai[bot], and coderabbitai[bot] May 18, 2026 09:50

Stale automated review after follow-up commits. The requested extraction/scheduler changes have been implemented, the associated inline threads are resolved, and the current CodeRabbit check is green.

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread Sources/Panels/BrowserPanel.swift

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 3d15bc7. Configure here.

Comment thread Sources/Panels/BrowserPanelView.swift
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 7669829a Deployed May 18, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants