Skip to content

fix: browser import profile follow-up regressions#1582

Merged
lawrencecchen merged 14 commits intomainfrom
task-browser-import-followups
Mar 17, 2026
Merged

fix: browser import profile follow-up regressions#1582
lawrencecchen merged 14 commits intomainfrom
task-browser-import-followups

Conversation

@lawrencecchen
Copy link
Copy Markdown
Contributor

@lawrencecchen lawrencecchen commented Mar 17, 2026

Summary

  • ignore stale browser navigation callbacks after profile and web view switches before recording history
  • only persist the preferred browser profile after profile switching, new browser creation, or browser splitting actually succeeds
  • harden the follow-up tests around Safari profile detection, UUID fixtures, and the browser import profile UI helper

Testing

  • xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -destination 'platform=macOS' -only-testing:cmuxTests/WorkspaceBrowserProfileSelectionTests -only-testing:cmuxTests/BrowserPanelProfileIsolationTests -only-testing:cmuxTests/BrowserInstallDetectorTests -only-testing:cmuxTests/SessionPersistenceTests/testSessionBrowserPanelSnapshotHistoryRoundTrip test\n- xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-ci -destination 'platform=macOS' -derivedDataPath /tmp/cmux-task-browser-import-followups-ci build-for-testing\n- ./scripts/reload.sh --tag task-browser-import-followups\n- gh workflow run test-e2e.yml --repo manaflow-ai/cmux -f ref=task-browser-import-followups -f test_filter=BrowserImportProfilesUITests -f record_video=true\n\nFollow-up for the remaining review threads after Browser: import data from detected installed browsers #318.

Summary by cubic

Fixes browser profile handling and refines the browser‑import UI for a smoother setup. Adds import hint variants, debug controls, and a clear menu entry (with settings deep‑link) to start imports.

  • Bug Fixes

    • Bind navigation callbacks to the active web view/history store; ignore stale callbacks; keep find‑in‑page open and clear title/favicon on failed loads.
    • Persist the preferred profile only when a switch/creation/split succeeds; new browsers inherit the target pane’s selected profile only if that profile exists.
  • UI Improvements

    • Add browser‑import hint variants (inline strip, floating card, toolbar chip, settings‑only) with dismiss/show settings; defer dialog presentation and scroll the settings hint to import controls.
    • Add “Import From Browser…” to the sidebar help menu and deep‑link Preferences to the Browser Import section; add UI test launch flags for hint variant/show/dismissed; stabilize the import menu test flow.
    • Add debug controls for toolbar accessory spacing and profile popover padding; update Japanese strings for new debug items.
    • Tighten the import sheet layout: shrink/cap the source profile list and adjust help text behavior.

Written for commit f5d610e. Summary will update on new commits.

Summary by CodeRabbit

  • Localization

    • Expanded and corrected translations (browser, workspace, menus, settings, commands), notably improved Japanese coverage.
  • New Features

    • Browser import hints (toolbar chip, popover, blank-tab hint), updated import wizard UI and sizing, settings navigation for browser/import, plus debug windows to preview import and profile popover behaviors.
  • Bug Fixes

    • Prevented stale navigation events from affecting profile history; failed create/split no longer mutates preferred profile.
  • Tests

    • New and updated unit and UI tests for import hints, profile inheritance/isolation, wizard flows, and related behaviors.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 17, 2026

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 17, 2026 11:50pm

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

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
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c0ac2a7-5715-44ac-9169-003778fa785f

📥 Commits

Reviewing files that changed from the base of the PR and between 7f589bf and f5d610e.

📒 Files selected for processing (10)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxTests/CmuxWebViewKeyEquivalentTests.swift
  • cmuxTests/GhosttyConfigTests.swift
  • cmuxTests/SessionPersistenceTests.swift

📝 Walkthrough

Walkthrough

Adds a browser import hint UI and import-wizard dynamic sizing/presentation, centralizes WebView navigation delegate callbacks, implements source-panel-aware browser profile inheritance for new browser surfaces, expands localization entries, and adds unit and UI tests for import/profile behaviors. No API signature removals; some enums/structs and debug hooks were added.

Changes

Cohort / File(s) Summary
Localization
Resources/Localizable.xcstrings
Added and corrected many localization keys/values (primarily Japanese) for browser, workspace, settings, commands, and import UI.
BrowserPanel core & wizard
Sources/Panels/BrowserPanel.swift, .../ImportWizardWindowController
Centralized navigation delegate callbacks (configureNavigationDelegateCallbacks()), added import-hint enums/structs/presentation types, dynamic panel sizing/presentation helpers, source-profiles presentation logic, and debug-only import-wizard window hooks.
BrowserPanel view / import hint UI
Sources/Panels/BrowserPanelView.swift
Added import-hint UI (toolbar chip, popover / card variants), AppStorage-backed debug/settings (toolbar spacing, popover padding), presentation helpers, actions to present import dialog and open settings, and conditional rendering logic.
App & Settings wiring
Sources/cmuxApp.swift, Sources/AppDelegate.swift, Sources/ContentView.swift
Introduced Settings navigation targets (browser, browserImport), AppStorage keys and bindings for import-hint state, debug windows and views for import hint and profile popover, UI-test environment hooks to preconfigure/present import hint flows, and a Sidebar help action to open Import From Browser….
Workspace profile inheritance
Sources/Workspace.swift
Passed sourcePanelId into newBrowserSurface/newBrowserSplit and adjusted resolvedNewBrowserProfileID to prefer the originating panel’s profile when valid; deferred preferred-profile propagation until after panel mapping.
Tests: unit & UI additions
cmuxTests/..., cmuxUITests/BrowserImportProfilesUITests.swift
Added many tests and helpers covering profile-selection/inheritance, import-hint presentation variants, stale navigation isolation, import-wizard window construction, blank-import-hint UI flows, and UI helpers/polling utilities.
Minor test fixes
cmuxTests/GhosttyConfigTests.swift, cmuxTests/SessionPersistenceTests.swift
Made comparisons order-insensitive, standardized path resolution (resolving symlinks), tightened UUID unwrapping, and improved test file creation error handling.

Sequence Diagram(s)

sequenceDiagram
  participant App as AppDelegate
  participant Workspace
  participant ProfileStore as BrowserProfileStore
  participant Panel as BrowserPanel
  participant PanelView as BrowserPanelView
  participant WebView
  participant ImportCoord as BrowserDataImportCoordinator

  App->>Workspace: launch / configure (UI-test import-hint env)
  Workspace->>ProfileStore: resolveNewBrowserProfileID(sourcePanelId?)
  ProfileStore-->>Workspace: profileID
  Workspace->>Panel: create panel (profileID, sourcePanelId)
  Panel->>PanelView: render (import hint state)
  Panel->>WebView: bind & configure navigation callbacks
  WebView-->>Panel: didFinish / didFail navigation
  Panel->>Panel: recordVisit (if profile matches)
  PanelView->>ImportCoord: presentImportDialog() / open settings
  ImportCoord-->>PanelView: show import-wizard (dynamic sizing / steps)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nudged a chip into the toolbar bright,
Poked profiles, popped the hint to guide the light.
Strings tidied, panels sized to fit the view,
Tests hop round to check the pathways true.
A little rabbit cheers — import's ready, too!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.52% 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 title 'fix: browser import profile follow-up regressions' accurately describes the main changes—addressing profile handling regressions and refining browser-import UI introduced in earlier work.
Description check ✅ Passed The pull request description comprehensively covers all required sections from the template: summary explains what changed and why, testing section details how changes were validated with specific commands, and a checklist is provided.

✏️ 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-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.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

1 issue found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmuxUITests/BrowserImportProfilesUITests.swift">

<violation number="1" location="cmuxUITests/BrowserImportProfilesUITests.swift:141">
P2: Do one final capture-file read before returning `nil`; otherwise writes that occur near the timeout boundary can be missed and make the UI test flaky.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

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.

Caution

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

⚠️ Outside diff range comments (1)
cmuxUITests/BrowserImportProfilesUITests.swift (1)

131-141: ⚠️ Potential issue | 🟡 Minor

Restore the final file read after the poll loop.

This now misses captures written during the last sleep interval, so the test can fail even when the JSON lands before the timeout expires.

💡 Suggested fix
     while Date() < deadline {
         if let data = try? Data(contentsOf: url),
            let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
             return object
         }
         RunLoop.current.run(until: Date().addingTimeInterval(0.05))
     }
+    if let data = try? Data(contentsOf: url),
+       let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
+        return object
+    }
     return nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxUITests/BrowserImportProfilesUITests.swift` around lines 131 - 141, The
poll loop in waitForCapturedSelection(timeout:) can miss a capture written
during the last sleep interval because it returns nil immediately after the
loop; add one final attempt to read and parse the file at capturePath (same
logic using Data(contentsOf:) and JSONSerialization.jsonObject(with:) as used
inside the loop) after the while loop but before returning nil so captures that
arrive right at the deadline are picked up.
🧹 Nitpick comments (3)
Sources/Workspace.swift (1)

2389-2396: Harden source-profile inheritance with the same validation as the other branches.

The new sourcePanelId path flows through resolvedNewBrowserProfileID(...), but that resolver only validates preferredProfileID and preferredBrowserProfileID. If the selected source browser is still carrying a deleted profile, this path will propagate a stale UUID into the new surface instead of falling back to a valid profile.

🔧 Suggested hardening in `resolvedNewBrowserProfileID`
private func resolvedNewBrowserProfileID(
    preferredProfileID: UUID? = nil,
    sourcePanelId: UUID? = nil
) -> UUID {
    if let preferredProfileID,
       BrowserProfileStore.shared.profileDefinition(id: preferredProfileID) != nil {
        return preferredProfileID
    }
    if let sourcePanelId,
       let sourceBrowserPanel = browserPanel(for: sourcePanelId),
       BrowserProfileStore.shared.profileDefinition(id: sourceBrowserPanel.profileID) != nil {
        return sourceBrowserPanel.profileID
    }
    if let preferredBrowserProfileID,
       BrowserProfileStore.shared.profileDefinition(id: preferredBrowserProfileID) != nil {
        return preferredBrowserProfileID
    }
    return BrowserProfileStore.shared.effectiveLastUsedProfileID
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Workspace.swift` around lines 2389 - 2396, The selected-source path
is not validated: update resolvedNewBrowserProfileID to accept sourcePanelId and
validate the source panel's profile the same way as other branches;
specifically, inside resolvedNewBrowserProfileID preserve the existing checks
for preferredProfileID and preferredBrowserProfileID, then if sourcePanelId is
non-nil call browserPanel(for: sourcePanelId) and verify
BrowserProfileStore.shared.profileDefinition(id: sourceBrowserPanel.profileID)
!= nil before returning sourceBrowserPanel.profileID, otherwise fall back to
BrowserProfileStore.shared.effectiveLastUsedProfileID; update the call site
(where effectiveSelectedPanelId(inPane:) is used to compute sourcePanelId for
BrowserPanel init) to pass that sourcePanelId into resolvedNewBrowserProfileID.
cmuxTests/GhosttyConfigTests.swift (1)

2031-2033: Consider attaching the failing path to the thrown write error.

The fail-fast behavior is great; adding NSFilePathErrorKey will make failures easier to diagnose in CI logs.

Proposed tweak
-        guard FileManager.default.createFile(atPath: url.path, contents: contents) else {
-            throw CocoaError(.fileWriteUnknown)
+        guard FileManager.default.createFile(atPath: url.path, contents: contents) else {
+            throw CocoaError(
+                .fileWriteUnknown,
+                userInfo: [NSFilePathErrorKey: url.path]
+            )
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/GhosttyConfigTests.swift` around lines 2031 - 2033, Guard that
creates the file currently throws a CocoaError(.fileWriteUnknown) without
context; modify the throw to include a userInfo dictionary with
NSFilePathErrorKey set to url.path so failures show the path (e.g., throw
CocoaError(.fileWriteUnknown, userInfo: [NSFilePathErrorKey: url.path])) and
keep the FileManager.default.createFile(atPath:contents:) check and behavior
otherwise unchanged.
cmuxTests/CmuxWebViewKeyEquivalentTests.swift (1)

6280-6286: Consider deduplicating makeProfile(named:) across the two new test classes.

A single file-private fixture helper would reduce maintenance drift for profile test setup.

Also applies to: 6456-6462

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift` around lines 6280 - 6286, The
helper makeProfile(named:) is duplicated between two test classes; extract it as
a single file-private fixture function (e.g., file-private func
makeProfile(named: String) throws -> BrowserProfileDefinition) and have both
test classes call this shared helper instead of redefining it; use the existing
logic that calls BrowserProfileStore.shared.createProfile(named:
"\(prefix)-\(UUID().uuidString)") and wrap with try XCTUnwrap to preserve
current behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@cmuxUITests/BrowserImportProfilesUITests.swift`:
- Around line 131-141: The poll loop in waitForCapturedSelection(timeout:) can
miss a capture written during the last sleep interval because it returns nil
immediately after the loop; add one final attempt to read and parse the file at
capturePath (same logic using Data(contentsOf:) and
JSONSerialization.jsonObject(with:) as used inside the loop) after the while
loop but before returning nil so captures that arrive right at the deadline are
picked up.

---

Nitpick comments:
In `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift`:
- Around line 6280-6286: The helper makeProfile(named:) is duplicated between
two test classes; extract it as a single file-private fixture function (e.g.,
file-private func makeProfile(named: String) throws -> BrowserProfileDefinition)
and have both test classes call this shared helper instead of redefining it; use
the existing logic that calls BrowserProfileStore.shared.createProfile(named:
"\(prefix)-\(UUID().uuidString)") and wrap with try XCTUnwrap to preserve
current behavior.

In `@cmuxTests/GhosttyConfigTests.swift`:
- Around line 2031-2033: Guard that creates the file currently throws a
CocoaError(.fileWriteUnknown) without context; modify the throw to include a
userInfo dictionary with NSFilePathErrorKey set to url.path so failures show the
path (e.g., throw CocoaError(.fileWriteUnknown, userInfo: [NSFilePathErrorKey:
url.path])) and keep the FileManager.default.createFile(atPath:contents:) check
and behavior otherwise unchanged.

In `@Sources/Workspace.swift`:
- Around line 2389-2396: The selected-source path is not validated: update
resolvedNewBrowserProfileID to accept sourcePanelId and validate the source
panel's profile the same way as other branches; specifically, inside
resolvedNewBrowserProfileID preserve the existing checks for preferredProfileID
and preferredBrowserProfileID, then if sourcePanelId is non-nil call
browserPanel(for: sourcePanelId) and verify
BrowserProfileStore.shared.profileDefinition(id: sourceBrowserPanel.profileID)
!= nil before returning sourceBrowserPanel.profileID, otherwise fall back to
BrowserProfileStore.shared.effectiveLastUsedProfileID; update the call site
(where effectiveSelectedPanelId(inPane:) is used to compute sourcePanelId for
BrowserPanel init) to pass that sourcePanelId into resolvedNewBrowserProfileID.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6de5a2ae-956b-4541-8f99-ca314fa2205f

📥 Commits

Reviewing files that changed from the base of the PR and between 746fb8e and fdde470.

📒 Files selected for processing (8)
  • Resources/Localizable.xcstrings
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/Workspace.swift
  • cmuxTests/CmuxWebViewKeyEquivalentTests.swift
  • cmuxTests/GhosttyConfigTests.swift
  • cmuxTests/SessionPersistenceTests.swift
  • cmuxUITests/BrowserImportProfilesUITests.swift

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

@lawrencecchen
Copy link
Copy Markdown
Contributor Author

Addressed the remaining cubic and CodeRabbit follow-up comments in aac8a41.

This restores the final timeout-boundary capture read in BrowserImportProfilesUITests, hardens source-profile inheritance validation, adds file-path context to the GhosttyConfig write error, and consolidates the duplicated browser-profile test helper.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift`:
- Around line 68-75: The helper makeTemporaryBrowserProfile currently creates
profiles via BrowserProfileStore.shared.createProfile but never removes them;
update tests to record the returned BrowserProfileDefinition (or its identifier)
and ensure teardown removes the created profile using
BrowserProfileStore.shared.removeProfile (or the store's deletion API) — e.g.,
capture the created profile inside makeTemporaryBrowserProfile or return an
opaque handle and register it for removal in the test's tearDown method so each
profile created by makeTemporaryBrowserProfile is deleted after the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9733e021-d92d-46d2-b84c-7e1f6b3c947e

📥 Commits

Reviewing files that changed from the base of the PR and between fdde470 and aac8a41.

📒 Files selected for processing (4)
  • Sources/Workspace.swift
  • cmuxTests/CmuxWebViewKeyEquivalentTests.swift
  • cmuxTests/GhosttyConfigTests.swift
  • cmuxUITests/BrowserImportProfilesUITests.swift

Comment on lines +68 to +75
@MainActor
private func makeTemporaryBrowserProfile(named prefix: String) throws -> BrowserProfileDefinition {
try XCTUnwrap(
BrowserProfileStore.shared.createProfile(
named: "\(prefix)-\(UUID().uuidString)"
)
)
}
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

Add teardown cleanup for temporary browser profiles.

This helper creates profiles in the shared store but never removes them. Over time, repeated test runs can accumulate persistent profiles and destabilize profile-dependent tests. Please clean up created profiles in teardown using the profile-store removal API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift` around lines 68 - 75, The
helper makeTemporaryBrowserProfile currently creates profiles via
BrowserProfileStore.shared.createProfile but never removes them; update tests to
record the returned BrowserProfileDefinition (or its identifier) and ensure
teardown removes the created profile using
BrowserProfileStore.shared.removeProfile (or the store's deletion API) — e.g.,
capture the created profile inside makeTemporaryBrowserProfile or return an
opaque handle and register it for removal in the test's tearDown method so each
profile created by makeTemporaryBrowserProfile is deleted after the test.

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

…ollowups

# Conflicts:
#	Sources/Workspace.swift
@lawrencecchen lawrencecchen merged commit 43d1fd4 into main Mar 17, 2026
10 of 11 checks passed
@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.

bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
…ort-followups

fix: browser import profile follow-up regressions
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