Skip to content

fix: address browser import follow-up review comments - #1638

Closed
lawrencecchen wants to merge 5 commits into
mainfrom
task-browser-import-comment-followups
Closed

lawrencecchen wants to merge 5 commits into
mainfrom
task-browser-import-comment-followups

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a regression test and fix close-shortcut ownership for the browser profile popover debug window
  • make the browser import UI test wait for the wizard Next button and clean up temporary browser profiles created by unit tests

Testing

  • xcodebuild -quiet -project GhosttyTabs.xcodeproj -scheme cmux-unit -destination 'platform=macOS' -derivedDataPath /tmp/cmux-task-browser-import-comment-followups-unit -only-testing:cmuxTests/CmuxWebViewKeyEquivalentTests/testBrowserProfilePopoverDebugWindowOwnsCloseShortcut -only-testing:cmuxTests/WorkspaceBrowserProfileSelectionTests -only-testing:cmuxTests/BrowserPanelProfileIsolationTests/testStaleDidFinishDoesNotRecordVisitIntoSwitchedProfileHistory test
  • xcodebuild -quiet -project GhosttyTabs.xcodeproj -scheme cmux-ci -destination 'platform=macOS' -derivedDataPath /tmp/cmux-task-browser-import-comment-followups-ci build-for-testing
  • ./scripts/reload.sh --tag task-browser-import-comment-followups

Related


Summary by cubic

Fixes browser import wizard flakiness by preselecting all source profiles and waiting for real step readiness in tests. Also makes Settings navigation to the Browser Import section reliable and exposes it to accessibility, and ensures the browser profile popover debug window owns Cmd+W.

  • Bug Fixes

    • Settings: buffer/apply pending navigation targets when the window becomes key or on view appear; expose the Browser Import section to accessibility for reliable scrolling and tests.
    • Debug popover now owns Cmd+W by registering cmux.browserProfilePopoverDebug; added a regression test.
    • Import wizard: all source profiles are selected by default; UI tests wait for a hittable “Next” and for the source-profiles and data-types steps to load.
  • Refactors

    • Added removeProfile(id:) to BrowserProfileStore to delete non-default profiles, clean history files/directories, and reset last-used when needed.
    • Introduced TemporaryBrowserProfileTracker in tests to create and clean up profiles reliably.

Written for commit 8d4186b. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Remove browser profiles with automatic cleanup of per-profile data and history.
    • Settings navigation now buffers pending targets and applies them when the settings view becomes active.
    • Debug browser-profile popover can own its close shortcut.
  • Behavior Changes

    • Import flow now treats all existing profiles as potential defaults when selecting source profiles.
  • Tests

    • New temporary-profile test helpers and teardown; improved UI test stability for the import wizard.

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

@vercel

vercel Bot commented Mar 18, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 18, 2026 0:57am

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Mar 18, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds guarded profile removal to BrowserProfileStore (removes data, history, updates lastUsedProfileID), changes default profile-selection fallback to treat all profiles as potential defaults, introduces buffered Settings navigation (pendingTarget) and a new auxiliary window identifier, centralizes temporary test profile tracking/teardown, and tightens import-wizard UI-test synchronization.

Changes

Cohort / File(s) Summary
Browser profiles & import selection
Sources/Panels/BrowserPanel.swift
Added removeProfile(id: UUID) -> Bool to BrowserProfileStore (disallows built-in removals, clears per-profile stores and history files, updates lastUsedProfileID, persists changes). Adjusted import-selection fallback to use Set(browser.profiles.map(\.id)) for defaultSelectedSourceProfileIDs.
Settings navigation & window IDs
Sources/cmuxApp.swift
Added "cmux.browserProfilePopoverDebug" to cmuxAuxiliaryWindowIdentifiers. Introduced SettingsNavigationTarget and buffered navigation flow (pendingTarget, post, consumePendingTarget(), clearPendingTarget(_)), SettingsWindowController.pendingNavigationTarget, and updated SettingsView/SettingsRootView to consume buffered targets and perform animated/immediate scrolls.
Tests & test helpers
cmuxTests/CmuxWebViewKeyEquivalentTests.swift, cmuxTests/..., cmuxUITests/BrowserImportProfilesUITests.swift
Added TemporaryBrowserProfileTracker for creating/cleaning temporary profiles and teardown wiring; replaced ad-hoc profile creation with tracker usage; added test asserting debug BrowserProfilePopover owns close shortcut; UI tests now wait for specific import steps and ensure Next is hittable (waitForSourceProfilesStep, waitForDataTypesStep).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SettingsNavigationRequest
  participant SettingsWindowController
  participant SettingsView
  Note over SettingsNavigationRequest,SettingsWindowController: pendingTarget buffered until window is key
  User->>SettingsNavigationRequest: post(navigationTarget)
  SettingsNavigationRequest->>SettingsNavigationRequest: store pendingTarget
  SettingsWindowController->>SettingsWindowController: windowDidBecomeKey
  SettingsWindowController->>SettingsNavigationRequest: postPendingNavigationTargetIfPossible()
  SettingsNavigationRequest->>SettingsView: Notification(with target)
  SettingsView->>SettingsNavigationRequest: consumePendingTarget()
  SettingsView->>SettingsView: perform scroll/navigation (animated or immediate)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through profiles, soft and spry,
Cleared trails of crumbs and waved goodbye.
A buffered hop to settings bright,
Tests tidy nests and hold the light —
Nibble, tap, and code takes flight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title directly relates to the main objective: addressing follow-up review comments on browser import functionality, which matches the primary changes across all modified files.
Description check ✅ Passed The description includes Summary and Testing sections with detailed information about changes and test commands. However, the Demo Video section is missing, and the Checklist items are not completed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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-browser-import-comment-followups
📝 Coding Plan
  • Generate coding plan for human review comments

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 4 files

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

🧹 Nitpick comments (1)
cmuxTests/CmuxWebViewKeyEquivalentTests.swift (1)

82-87: Make cleanup failures visible to avoid hidden test pollution.

cleanup() ignores removeProfile failures, so leaked profiles can silently affect later tests. Please surface failures in teardown.

💡 Suggested patch
 `@MainActor`
 private final class TemporaryBrowserProfileTracker {
     private var createdProfileIDs: [UUID] = []

@@
-    func cleanup() {
+    `@discardableResult`
+    func cleanup() -> [UUID] {
+        var failedProfileIDs: [UUID] = []
         for profileID in createdProfileIDs.reversed() {
-            _ = BrowserProfileStore.shared.removeProfile(id: profileID)
+            if !BrowserProfileStore.shared.removeProfile(id: profileID) {
+                failedProfileIDs.append(profileID)
+            }
         }
         createdProfileIDs.removeAll()
+        return failedProfileIDs
     }
 }
 override func tearDown() {
-    temporaryProfileTracker.cleanup()
+    XCTAssertTrue(
+        temporaryProfileTracker.cleanup().isEmpty,
+        "Temporary browser profiles failed to clean up"
+    )
     super.tearDown()
 }
 override func tearDown() {
-    temporaryProfileTracker.cleanup()
+    XCTAssertTrue(
+        temporaryProfileTracker.cleanup().isEmpty,
+        "Temporary browser profiles failed to clean up"
+    )
     super.tearDown()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift` around lines 82 - 87, The
cleanup() helper currently swallows failures from
BrowserProfileStore.shared.removeProfile(id:) when iterating createdProfileIDs,
which can silently leak profiles; update cleanup() to capture and surface errors
(e.g., assert, XCTFail, or rethrow) when removeProfile returns failure or
throws, and ensure you still attempt removal for remaining profile IDs (iterate
all IDs even on error) before clearing createdProfileIDs so test pollution is
visible and teardown remains robust; refer to cleanup(),
BrowserProfileStore.shared.removeProfile(id:), and createdProfileIDs when making
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift`:
- Around line 82-87: The cleanup() helper currently swallows failures from
BrowserProfileStore.shared.removeProfile(id:) when iterating createdProfileIDs,
which can silently leak profiles; update cleanup() to capture and surface errors
(e.g., assert, XCTFail, or rethrow) when removeProfile returns failure or
throws, and ensure you still attempt removal for remaining profile IDs (iterate
all IDs even on error) before clearing createdProfileIDs so test pollution is
visible and teardown remains robust; refer to cleanup(),
BrowserProfileStore.shared.removeProfile(id:), and createdProfileIDs when making
the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e3a6e0b1-17f5-4083-82aa-3d1489d4f109

📥 Commits

Reviewing files that changed from the base of the PR and between a42e666 and 106b402.

📒 Files selected for processing (4)
  • Sources/Panels/BrowserPanel.swift
  • Sources/cmuxApp.swift
  • cmuxTests/CmuxWebViewKeyEquivalentTests.swift
  • cmuxUITests/BrowserImportProfilesUITests.swift

@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

🧹 Nitpick comments (2)
cmuxUITests/BrowserImportProfilesUITests.swift (2)

210-215: Consider adding isHittable check to match waitForImportWizard pattern.

Tests immediately click the checkbox after this wait (e.g., lines 100-102). Checking only exists without isHittable could lead to flaky failures if the checkbox appears in the view hierarchy but isn't yet interactable.

 private func waitForDataTypesStep(_ app: XCUIApplication) {
     let dataTypesReady = browserImportPollUntil(timeout: 5.0) {
-        app.checkBoxes["BrowserImportCookiesCheckbox"].exists
+        let checkbox = app.checkBoxes["BrowserImportCookiesCheckbox"]
+        return checkbox.exists && checkbox.isHittable
     }
     XCTAssertTrue(dataTypesReady, "Expected the import data-type step to finish loading")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxUITests/BrowserImportProfilesUITests.swift` around lines 210 - 215, The
waitForDataTypesStep helper currently polls only for existence of the cookies
checkbox which can be visible but not yet interactable; update the closure used
by browserImportPollUntil inside waitForDataTypesStep to assert both existence
and hittability (e.g., checkBoxes["BrowserImportCookiesCheckbox"].exists &&
checkBoxes["BrowserImportCookiesCheckbox"].isHittable) so callers (and
subsequent immediate taps) match the waitForImportWizard pattern and avoid flaky
interactions.

203-208: Consider adding isHittable check for consistency.

waitForImportWizard now checks both exists and isHittable, but this helper only checks exists. For consistency and to ensure the checkbox is interactable before proceeding, consider adding an isHittable check:

 private func waitForSourceProfilesStep(_ app: XCUIApplication) {
     let sourceProfilesReady = browserImportPollUntil(timeout: 5.0) {
-        app.checkBoxes["You"].exists || app.staticTexts["Step 2 of 3"].exists
+        let checkbox = app.checkBoxes["You"]
+        return (checkbox.exists && checkbox.isHittable) || app.staticTexts["Step 2 of 3"].exists
     }
     XCTAssertTrue(sourceProfilesReady, "Expected the source-profile step to finish loading")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxUITests/BrowserImportProfilesUITests.swift` around lines 203 - 208, The
helper waitForSourceProfilesStep currently waits only for existence; update its
poll predicate (inside browserImportPollUntil) to require both existence and
hittability for the "You" checkbox (app.checkBoxes["You"].exists &&
app.checkBoxes["You"].isHittable) or similarly require app.staticTexts["Step 2
of 3"].exists && app.staticTexts["Step 2 of 3"].isHittable, mirroring the
waitForImportWizard behavior so the step is interactable before proceeding;
modify the predicate in waitForSourceProfilesStep accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 423-437: The history store is being removed by setting
historyStores[id] = nil while its debounced save Task may still be running; call
the store's clearHistory() to cancel its saveTask and perform clean deletion
before nil'ing the reference and removing files: locate where
profiles.remove(at: index) and historyStores[id] = nil are used and invoke
historyStores[id]?.clearHistory() (or the equivalent clear method on the store)
prior to setting historyStores[id] = nil and before removing the history file
returned by historyFileURL(for: id).

---

Nitpick comments:
In `@cmuxUITests/BrowserImportProfilesUITests.swift`:
- Around line 210-215: The waitForDataTypesStep helper currently polls only for
existence of the cookies checkbox which can be visible but not yet interactable;
update the closure used by browserImportPollUntil inside waitForDataTypesStep to
assert both existence and hittability (e.g.,
checkBoxes["BrowserImportCookiesCheckbox"].exists &&
checkBoxes["BrowserImportCookiesCheckbox"].isHittable) so callers (and
subsequent immediate taps) match the waitForImportWizard pattern and avoid flaky
interactions.
- Around line 203-208: The helper waitForSourceProfilesStep currently waits only
for existence; update its poll predicate (inside browserImportPollUntil) to
require both existence and hittability for the "You" checkbox
(app.checkBoxes["You"].exists && app.checkBoxes["You"].isHittable) or similarly
require app.staticTexts["Step 2 of 3"].exists && app.staticTexts["Step 2 of
3"].isHittable, mirroring the waitForImportWizard behavior so the step is
interactable before proceeding; modify the predicate in
waitForSourceProfilesStep accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7f4c3d02-9efd-4c42-8509-834f0a5e1385

📥 Commits

Reviewing files that changed from the base of the PR and between 106b402 and 7386f9d.

📒 Files selected for processing (3)
  • Sources/Panels/BrowserPanel.swift
  • Sources/cmuxApp.swift
  • cmuxUITests/BrowserImportProfilesUITests.swift

Comment on lines +423 to +437
profiles.remove(at: index)
dataStores[id] = nil
historyStores[id] = nil

if let historyFileURL = historyFileURL(for: id) {
try? FileManager.default.removeItem(at: historyFileURL)
let profileDirectory = historyFileURL.deletingLastPathComponent()
if let remainingContents = try? FileManager.default.contentsOfDirectory(
at: profileDirectory,
includingPropertiesForKeys: nil
),
remainingContents.isEmpty {
try? FileManager.default.removeItem(at: profileDirectory)
}
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) removeProfile implementation (check whether clearHistory is called before historyStores[id] eviction):"
rg -n -C4 'func removeProfile\(id: UUID\) -> Bool|historyStores\[id\]|clearHistory\(' Sources/Panels/BrowserPanel.swift

echo
echo "2) BrowserHistoryStore.clearHistory implementation (check cancellation semantics):"
rg -n -C4 'func clearHistory\(\)|saveTask\?\.cancel\(\)|saveTask = nil' Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 1998


Cancel pending history saves before evicting the profile history store

On line 425, historyStores[id] = nil removes the reference to the store without canceling its debounced save task. The underlying Task.detached continues independently and can rewrite the history file after deletion, reintroducing stale state.

Call clearHistory() before nil assignment. This method explicitly cancels saveTask (line 1273) and deletes the file properly.

Suggested fix
@@
-        profiles.remove(at: index)
-        dataStores[id] = nil
-        historyStores[id] = nil
+        profiles.remove(at: index)
+        // Ensure pending debounced writes are cancelled before dropping the store.
+        historyStores[id]?.clearHistory()
+        historyStores[id] = nil
+        dataStores[id] = nil
📝 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
profiles.remove(at: index)
dataStores[id] = nil
historyStores[id] = nil
if let historyFileURL = historyFileURL(for: id) {
try? FileManager.default.removeItem(at: historyFileURL)
let profileDirectory = historyFileURL.deletingLastPathComponent()
if let remainingContents = try? FileManager.default.contentsOfDirectory(
at: profileDirectory,
includingPropertiesForKeys: nil
),
remainingContents.isEmpty {
try? FileManager.default.removeItem(at: profileDirectory)
}
}
profiles.remove(at: index)
// Ensure pending debounced writes are cancelled before dropping the store.
historyStores[id]?.clearHistory()
historyStores[id] = nil
dataStores[id] = nil
if let historyFileURL = historyFileURL(for: id) {
try? FileManager.default.removeItem(at: historyFileURL)
let profileDirectory = historyFileURL.deletingLastPathComponent()
if let remainingContents = try? FileManager.default.contentsOfDirectory(
at: profileDirectory,
includingPropertiesForKeys: nil
),
remainingContents.isEmpty {
try? FileManager.default.removeItem(at: profileDirectory)
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Panels/BrowserPanel.swift` around lines 423 - 437, The history store
is being removed by setting historyStores[id] = nil while its debounced save
Task may still be running; call the store's clearHistory() to cancel its
saveTask and perform clean deletion before nil'ing the reference and removing
files: locate where profiles.remove(at: index) and historyStores[id] = nil are
used and invoke historyStores[id]?.clearHistory() (or the equivalent clear
method on the store) prior to setting historyStores[id] = nil and before
removing the history file returned by historyFileURL(for: id).

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

🧹 Nitpick comments (1)
Sources/cmuxApp.swift (1)

2517-2544: Consider making SettingsWindowController explicitly @MainActor for isolation symmetry.

SettingsNavigationRequest isolates its navigation state to the main actor (lines 2517, 2519, 2534, 2540), but SettingsWindowController's mutable pendingNavigationTarget state lacks compile-time isolation. Adding @MainActor to the class enforces that future modifications remain main-thread-safe and aligns with the patterns established in SettingsNavigationRequest.

♻️ Proposed refactor
-final class SettingsWindowController: NSWindowController, NSWindowDelegate {
+@MainActor
+final class SettingsWindowController: NSWindowController, NSWindowDelegate {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/cmuxApp.swift` around lines 2517 - 2544, The SettingsWindowController
class should be annotated with `@MainActor` to match the isolation used by
SettingsNavigationRequest and protect its mutable pendingNavigationTarget state;
update the class declaration to include `@MainActor` and ensure any initializers
or methods referencing pendingNavigationTarget (and any call sites creating or
accessing SettingsWindowController) are executed or awaited from the main actor
context so the compiler knows those accesses are main-thread-safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/cmuxApp.swift`:
- Around line 2517-2544: The SettingsWindowController class should be annotated
with `@MainActor` to match the isolation used by SettingsNavigationRequest and
protect its mutable pendingNavigationTarget state; update the class declaration
to include `@MainActor` and ensure any initializers or methods referencing
pendingNavigationTarget (and any call sites creating or accessing
SettingsWindowController) are executed or awaited from the main actor context so
the compiler knows those accesses are main-thread-safe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93197f84-b224-4f2b-bae1-057b4e7d0f4c

📥 Commits

Reviewing files that changed from the base of the PR and between 93423c3 and 8d4186b.

📒 Files selected for processing (1)
  • Sources/cmuxApp.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 — 8d4186bd Deployed Mar 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.

2 participants