Skip to content

Fix Files sidebar flicker on pane close - #3747

Closed
austinywang wants to merge 39 commits into
mainfrom
issue-3742-files-sidebar-flicker
Closed

austinywang wants to merge 39 commits into
mainfrom
issue-3742-files-sidebar-flicker

Conversation

@austinywang

@austinywang austinywang commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3742.

Repro:

  • Built and launched HEAD of main with ./scripts/reload.sh --tag repro-3742-main --launch.
  • Opened Files with Cmd+Shift+E then Ctrl+1, split a pane, and closed the split pane.
  • Observed the Files outline visibly flash/repaint while pane geometry changed.

Root cause:

  • FileExplorerPanelView.updateNSView ran during unrelated parent SwiftUI transactions, including pane close.
  • The coordinator always called reloadIfNeeded(), which walked visible loaded directories and called reloadItem(..., reloadChildren: true) even when FileExplorerStore had not changed.

Fix:

  • FileExplorerStore now owns an explicit outlineRevision for tree-affecting changes.
  • FileExplorerPanelView.Coordinator only refreshes/reloads outline rows when that Files-owned revision changes.
  • Parent layout/pane-count updates still update sidebar chrome/visibility, but they no longer repaint the file tree.

Regression test:

  • Commit 1 adds a failing coordinator boundary test proving unchanged store revisions must not refresh loaded outline rows.
  • Commit 2 adds the revision-gated implementation.

Verification so far:

  • git diff --check.

Not run locally:

  • XCTest/build, because this task explicitly forbids direct xcodebuild and requires CI to own executable verification before the final tagged reload.

Note

Medium Risk
Medium risk because it changes Files sidebar state invalidation and search process execution (including SSH command construction), which could affect UI refresh correctness and search behavior across local/remote providers.

Overview
Fixes Files sidebar flicker by gating AppKit outline reload/refresh on a store-owned outlineRevision, so unrelated SwiftUI updates (like closing a split) no longer repaint the file tree.

Refactors Files state mutations to be idempotent and revision-scoped (expand/collapse no-ops, silent prefetch, git status updates deduped and stale-result guarded), and adds FileSearchScope to run Find searches locally via rg or remotely via /usr/bin/ssh with shared shell quoting and clearer localized error messaging.

Adds DEBUG-only redraw counters and terminal/Python plumbing plus new unit/integration tests to assert no outline/layout invalidation during split close and to cover the new revision/search behaviors.

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


Summary by cubic

Fixes #3742 by removing the Files sidebar flicker when closing a split. Redraws are now gated by a Files-owned outlineRevision, UI layout updates are idempotent, and Find adds SSH support with clearer errors.

  • Bug Fixes

    • Gate outline reload/refresh on outlineRevision; ignore unrelated parent SwiftUI updates.
    • Make header/visibility/search layout idempotent; keep Find hidden while root loads and across same-presentation updates.
    • Skip no-op collapse; expand loads children without double-bumping the outline; silent prefetches don’t dirty the tree.
    • Dedupe git-status results and ignore stale async updates; apply SSH args only when the current connection matches.
    • Find uses a FileSearchScope (local vs remote over /usr/bin/ssh); improved messages: “Search unavailable” and “Search executable is missing: %@”.
    • Share ShellCommandQuoting.singleQuoted for consistent command quoting.
  • Tests

    • DEBUG counters and terminal commands file_explorer_debug_counts / reset_file_explorer_debug_counts; new Python test asserts no outline reload/refresh and no search-layout invalidations on split close.
    • XCTest covers revision-gated refresh, no-op collapse, expand behavior, silent prefetch, git-status dedupe/staleness (local and SSH), SSH search command/quoting and missing-exec handling, unsupported-scope precedence, loading-aware Find layout, and preserving completed results on rerender.

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

Summary by CodeRabbit

  • Performance & Stability

    • Track outline revisions to avoid redundant refreshes; background git/status work updates internals without spurious UI invalidation
    • Silent child loads and reload cancellation now avoid unnecessary UI churn
  • Bug Fixes

    • Skip redundant visibility, layout and header updates via cached state checks
  • Tests

    • New regression/unit tests for revision-driven refreshes and no-redraw on closing a surface; updated window-move suppression tests
  • New Features

    • Added debug counters and debug-only commands to inspect/reset Files sidebar metrics

Review Change Stack

Closes #3742.

@vercel

vercel Bot commented May 8, 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 May 22, 2026 2:12am
cmux-staging Building Building Preview, Comment May 22, 2026 2:12am

@coderabbitai

coderabbitai Bot commented May 8, 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

FileExplorerStore gains an outlineRevision token and mutateOutline(_:); git-status updates centralize via applyGitStatus. Outline-affecting flows use mutateOutline. Coordinator caches lastAppliedOutlineRevision and skips redundant outline reloads. Debug counters, socket commands, client helpers, tests, and a regression script were added.

Changes

Outline Revision Tracking & Flicker Mitigation

Layer / File(s) Summary
Outline Revision State
Sources/FileExplorerStore.swift
Adds outlineRevision counter to FileExplorerStore to track outline invalidation.
mutateOutline & Git Status
Sources/FileExplorerStore.swift
Adds mutateOutline(_:) (increments outlineRevision) and applyGitStatus(_:) (calls mutateOutline and sets gitStatusByPath); refreshGitStatus() now uses applyGitStatus(_:) and fetches on a background queue when rootPath is set. Adds DEBUG-only git-status test setter.
Store Invalidation Points
Sources/FileExplorerStore.swift
Wraps outline-affecting state changes in mutateOutline across reload(), expand(node:), collapse(node:), and multiple places in loadChildren(for:at:silent:) (startup, after children wiring, auto-expand loop, and non-silent error handling).
View: Debug counters & Coordinator binding
Sources/FileExplorerView.swift
Adds FileExplorerDebugCounters (DEBUG-only), replaces direct coordinator assignments with Coordinator.updateBindings(...); Coordinator adds lastAppliedOutlineRevision and resets cached counters/revision when the store instance changes.
Early Return Optimization
Sources/FileExplorerView.swift
Coordinator.reloadIfNeeded() returns early when store.outlineRevision equals lastAppliedOutlineRevision, skipping redundant NSOutlineView reloads/selection restoration.
Visibility / Layout / Header Optimizations
Sources/FileExplorerView.swift
Caches last visibility tuple to avoid redundant visibility updates; updateSearchLayout(...) applies constraint/layout changes only when needed; FileExplorerHeaderView.update(displayPath:) returns early when unchanged.
Socket Debug Commands
Sources/TerminalController.swift
Adds debug socket commands (file_explorer_debug_counts, reset_file_explorer_debug_counts) and helpers returning/resetting FileExplorerDebugCounters summaries (DEBUG-only).
Tests / Clients / Regression
cmuxTests/FileExplorerStoreTests.swift, tests/cmux.py, tests/test_files_sidebar_no_redraw_on_close_surface.py, cmuxTests/WindowAndDragTests.swift
Adds CountingFileExplorerOutlineView test double and coordinator tests; adds cmux client methods to fetch/reset counters; adds regression script that verifies closing a surface does not trigger search layout invalidations or outline reload/refreshes; updates drag-suppression tests for reference-counted suppression.

Sequence Diagram(s)

sequenceDiagram
  participant View
  participant Coordinator
  participant Store
  participant OutlineView

  View->>Coordinator: updateBindings(store,...)
  Coordinator->>Coordinator: reset caches if store changed
  Store->>Coordinator: objectWillChange (outlineRevision++)
  Coordinator->>Coordinator: reloadIfNeeded() (compare lastAppliedOutlineRevision)
  alt revision unchanged
    Coordinator-->>OutlineView: (no reload)
  else revision changed
    Coordinator->>OutlineView: reloadData / reloadItem
    Coordinator->>Coordinator: update lastAppliedOutlineRevision
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#1963: Modifies FileExplorer Store/Coordinator and tests; related implementation history.
  • manaflow-ai/cmux#2880: Related test changes around FileExplorerStore behavior; overlaps with test stabilization strategies.

Poem

🐰 I hop through trees of code and cheer,
A tiny count keeps redraws clear.
When nothing changed, I skip the race,
The sidebar stays a steady place.
Hooray — no flicker, peace is near!

🚥 Pre-merge checks | ✅ 14 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix Files sidebar flicker on pane close' directly summarizes the main objective and is clearly related to the primary change in the changeset.
Linked Issues check ✅ Passed The PR implementation directly addresses all coding requirements from issue #3742: introduces outlineRevision to gate tree updates, makes coordinator refresh revision-aware, deduplicates git-status changes, keeps Find UI hidden during root load, and includes comprehensive tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the sidebar flicker issue: outline revision gating, idempotent container updates, git-status deduplication, debug counters for verification, and relevant test coverage. Window drag test updates reflect current API changes and are appropriately scoped.
Cmux Swift Actor Isolation ✅ Passed Outlinerevision properly added as private UInt64. All mutations on main thread or @MainActor tasks. Background operations properly dispatched. No Sendable or implicit MainActor violations.
Cmux Swift Blocking Runtime ✅ Passed No blocking/timing primitives in production code. Async Tasks use @MainActor isolation. Debug v2MainSync calls are #if DEBUG only. Test sleeps allowed.
Cmux No Hacky Sleeps ✅ Passed PR modifies only test files with no production code changes. Sleeps in test_files_sidebar_no_redraw_on_close_surface.py are deterministic test scaffolding, explicitly allowed by the rule.
Cmux Swift Concurrency ✅ Passed No new legacy async patterns introduced. All DispatchQueue usage pre-exists (required for OS/AppKit file watching). PR improves MainActor isolation on Tasks and maintains proper async/await.
Cmux Swift @Concurrent ✅ Passed No @concurrent violations introduced. No new async functions added. File I/O operations maintain existing structure. Explicit @MainActor annotations on Task creations are appropriate.
Cmux Swift File And Package Boundaries ✅ Passed All additions below 250-line threshold. No new oversized files or mixed responsibilities. FileExplorer appropriately in Sources/. Focused bug fix with clear extraction path.
Cmux Swift Logging ✅ Passed All logging additions comply with swift-logging.md: NSLog in FileExplorerStore.swift and dlog in FileExplorerView.swift are properly guarded with #if DEBUG blocks.
Cmux Swiftui State Layout ✅ Passed outlineRevision is not @Published (internal token). Changes confined to existing ObservableObject and AppKit bridge. No GeometryReader, no lazy store refs, no render mutations.
Cmux Architecture Rethink ✅ Passed Revision-gating outline refresh, single source of truth, no timing workarounds, no split lifecycle, clear invariant, proper test-only synchronization.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No new standalone windows added. Changes are to existing FileExplorer sidebar UI and debug tooling only.
Description check ✅ Passed The PR description is comprehensive, covering root cause, implementation details, tests, and verification approach, though it lacks a formal Testing section with manual verification steps and demo video as specified in the template.

✏️ 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 issue-3742-files-sidebar-flicker

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

greptile-apps Bot commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes the Files sidebar flicker on pane close by introducing a FileExplorerStore-owned outlineRevision counter and routing all tree-affecting mutations through a mutateOutline(_:) boundary, so the Coordinator only repaints the AppKit outline when Files-owned state actually changes — not on every unrelated SwiftUI transaction. Also extends Find search to support SSH remotes via FileSearchScope, adds idempotency guards across visibility/layout/header updates, deduplicates git-status writes, and adds a Python regression test plus XCTest coverage for all new invariants.

  • Core fix: lastAppliedOutlineRevision in the Coordinator gates reloadData/refreshLoadedNodes on store.outlineRevision; mutateOutline is the single documented invalidation boundary; applyGitStatus early-returns on identical status to prevent spurious outline refreshes from filesystem-watcher noise.
  • SSH search: FileSearchScope replaces the isLocal: Bool flag; local search runs rg directly, SSH search wraps rg args in ShellCommandQuoting.singleQuoted and pipes them over /usr/bin/ssh; a missing SSH binary falls through to .unsupported rather than surfacing an internal path in error copy.
  • Localization: the new fileExplorer.search.executableMissing key and the updated fileExplorer.search.unsupported copy carry only en/ja translations, but the catalog already declares 19 locales — all other locales will silently fall back to English.

Confidence Score: 4/5

Safe to merge once the missing locale translations are addressed; the outline-refresh gating and SSH search additions are correct and well-tested.

The fileExplorer.search.executableMissing string key is added with only en and ja entries while the catalog already supports 19 locales — users on any other locale will silently receive English error copy. The core outlineRevision fix is clean, the mutateOutline boundary is well-enforced, SSH search falls back to .unsupported when /usr/bin/ssh is absent (no internal path leaked), and the new XCTest and Python regression coverage is thorough.

Resources/Localizable.xcstrings — the two new/changed fileExplorer.search keys need translations for the 17 additional locales the catalog already supports.

Important Files Changed

Filename Overview
Resources/Localizable.xcstrings Adds fileExplorer.search.executableMissing key and updates fileExplorer.search.unsupported copy, but both only carry en/ja translations while the catalog already supports 19 locales.
Sources/FileExplorerSearchController.swift Introduces FileSearchScope enum replacing the isLocal: Bool flag; routes local rg and SSH-tunnelled rg through a unified SearchProcessCommand; missing-SSH case falls through to .unsupported (no internal path exposed); SSH args moved to SSHFileExplorerConnection.sshProcessArguments. Clean.
Sources/FileExplorerStore.swift Adds outlineRevision counter gated by mutateOutline; routes all tree-affecting mutations through that boundary; applyGitStatus early-returns on equal status; SSH args refactored to connection-owned method; git-status staleness guards added for both root-change and provider-change cases.
Sources/FileExplorerView.swift Coordinator gates reloadIfNeeded on lastAppliedOutlineRevision; updateVisibility and updateSearchLayout are now idempotent via cached state structs; updateSearchLayoutForCurrentVisibility returns early before the first visibility snapshot, fixing the pre-snapshot Find reveal bug.
Sources/TerminalController.swift Adds DEBUG-only terminal commands file_explorer_debug_counts and reset_file_explorer_debug_counts that read/reset FileExplorerDebugCounters via v2MainSync; no production impact.
Sources/App/CmuxCLIPathInstaller.swift Replaces private shellQuoted helper with the new shared ShellCommandQuoting.singleQuoted; behaviour is identical, deduplication is clean.
cmuxTests/FileExplorerStoreTests.swift Adds comprehensive XCTest coverage: revision skip/refresh symmetry, expand dedupe, silent prefetch, git-status staleness (local + SSH), SSH search command construction/quoting, missing-exec handling, loading-aware Find layout, and same-presentation no-restart. All new tests are well-structured.

Sequence Diagram

sequenceDiagram
    participant SwiftUI as SwiftUI Transaction
    participant Coord as Coordinator
    participant Store as FileExplorerStore
    participant OV as NSOutlineView

    SwiftUI->>Coord: updateNSView (pane close / layout)
    Coord->>Coord: updateBindings(store:state:...)
    Coord->>Coord: reloadIfNeeded()
    Coord->>Store: outlineRevision
    Note over Coord: lastAppliedOutlineRevision == outlineRevision?
    Coord-->>OV: skip — no repaint

    SwiftUI->>Coord: updateNSView (Files mutation)
    Coord->>Coord: reloadIfNeeded()
    Coord->>Store: outlineRevision (incremented by mutateOutline)
    Note over Coord: revision changed → proceed
    Coord->>Coord: "lastAppliedOutlineRevision = new revision"
    alt rootNodes.count changed
        Coord->>OV: reloadData()
        Coord->>OV: restoreExpansionState(expandedPaths)
    else same root count
        Coord->>OV: refreshLoadedNodes()
    end
    Coord->>OV: applyStoredSelection()
Loading

Reviews (34): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread Sources/FileExplorerStore.swift Outdated
Comment thread cmuxTests/FileExplorerStoreTests.swift
@austinywang
austinywang force-pushed the issue-3742-files-sidebar-flicker branch from 4762eff to 6b39b8a Compare May 8, 2026 20:47
The Files sidebar repaint path needs coverage at the coordinator boundary: a parent SwiftUI update with unchanged file-store state must not walk loaded directories and refresh their outline rows. The companion positive assertion also proves Files-owned disclosure changes still refresh the outline when the store revision changes.

Constraint: Repro shows pane close mutates the shared ContentView tree while the file store itself does not change.

Confidence: high

Scope-risk: narrow

Directive: Keep file-tree outline refreshes gated by file-store revisions, not by pane geometry or parent SwiftUI transactions.

Tested: git diff --check

Not-tested: Local XCTest not run; direct xcodebuild is prohibited for this task and CI will prove the red test.
@austinywang
austinywang force-pushed the issue-3742-files-sidebar-flicker branch from 6b39b8a to 8b43f0e Compare May 8, 2026 20:51

@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

🤖 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/FileExplorerStore.swift`:
- Line 703: The call to markOutlineDirty() unconditionally increments
outlineRevision and triggers refreshLoadedNodes() even for silent
(hover-prefetch) successes; update the success path in the prefetch completion
handler so markOutlineDirty() is only invoked when silent == false (i.e., move
the markOutlineDirty() call inside the existing non-silent branch), ensuring
silent prefetches do not bump outlineRevision or cause
reloadIfNeeded()/refreshLoadedNodes() to walk the tree unnecessarily.
🪄 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: f8a1472b-aaaf-4d37-833f-85c47ec78cc4

📥 Commits

Reviewing files that changed from the base of the PR and between 4762eff and 6b39b8a.

📒 Files selected for processing (3)
  • Sources/FileExplorerStore.swift
  • Sources/FileExplorerView.swift
  • cmuxTests/FileExplorerStoreTests.swift

Comment thread Sources/FileExplorerStore.swift Outdated

@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

🤖 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/FileExplorerStore.swift`:
- Around line 721-742: The closure applyLoadedChildren currently references many
stored properties without explicitly capturing self; add an explicit capture
list [self] on the applyLoadedChildren closure to satisfy Swift's
non-escaping/escaping rules (e.g. `let applyLoadedChildren = { [self] in ...
}`), leaving the body unchanged; also rename the inner local variable that
shadows the function parameter (the let path inside the
pendingDescendIntoFirstChildPath branch) to something like targetPath to avoid
shadowing the outer path used later when calling loadingPaths.remove(path) and
loadTasks.removeValue(forKey: path). Ensure references to selectedPath,
selectedPaths, rootNodes, isRootLoading, parentNode,
pendingDescendIntoFirstChildPath, loadingPaths and loadTasks remain as-is but
are now allowed via the explicit [self] capture.
🪄 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: 95f466a3-a1da-43bb-8619-61d53f00e639

📥 Commits

Reviewing files that changed from the base of the PR and between 6b39b8a and 0da73c6.

📒 Files selected for processing (3)
  • Sources/FileExplorerStore.swift
  • Sources/FileExplorerView.swift
  • cmuxTests/FileExplorerStoreTests.swift

Comment thread Sources/FileExplorerStore.swift Outdated
Pane close re-evaluates the shared SwiftUI ContentView, but the Files outline should only repaint when FileExplorerStore changes. The store now owns an outline revision and the AppKit coordinator ignores unchanged revisions, so parent layout transactions no longer walk loaded directories.

Constraint: Direct xcodebuild is prohibited; final tagged reload must happen only after CI passes.

Rejected: Rendering masks such as drawingGroup or pre-rendering | they hide repaint instead of removing the spurious invalidation.

Confidence: high

Scope-risk: narrow

Directive: Do not call outline row refreshes from parent SwiftUI transactions without a FileExplorerStore revision change.

Tested: git diff --check

Not-tested: Local XCTest/build; CI and final reload own executable verification.
@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 8, 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.

The settled screenshot regression was not proving the flicker path. This adds DEBUG-only Files sidebar counters plus a socket repro that closes a split while the Files right sidebar is visible and asserts that the Files subtree does not dirty layout or refresh outline data.

Constraint: The flicker is transient, so screenshots after the window settles can pass while the visible redraw still happens.

Rejected: Keep relying on visual screenshot comparison | it misses the AppKit layout invalidation that happens during close.

Confidence: high

Scope-risk: narrow

Directive: Use the counter-based repro for this flicker class; do not replace it with a settled screenshot-only check.

Tested: python3 -m py_compile tests/cmux.py tests/test_files_sidebar_no_redraw_on_close_surface.py

Not-tested: Intermediate commit is expected to fail the new repro against the old layout behavior.
Closing a split can still cause SwiftUI to call the Files NSViewRepresentable update path, even when no Files-owned state changed. The container now treats visibility and search layout as applied state, updates AppKit properties only when values change, and skips header work when the displayed path is unchanged.

Constraint: Parent workspace updates are expected during split close; the Files subtree must tolerate those calls without dirtying layout.

Rejected: Suppress updateNSView itself | SwiftUI legitimately re-enters that bridge during parent host churn.

Confidence: high

Scope-risk: narrow

Directive: Future Files sidebar changes should preserve idempotent AppKit updates during unrelated workspace/split updates.

Tested: CMUX_SOCKET_PATH=/tmp/cmux-debug-issue-3742-files-loop.sock python3 tests/test_files_sidebar_no_redraw_on_close_surface.py

Tested: ./scripts/reload.sh --tag issue-3742-files-loop --launch

Tested: git diff --check

Tested: python3 -m py_compile tests/cmux.py tests/test_files_sidebar_no_redraw_on_close_surface.py
The PR branch needed the latest main before CI iteration. The only conflict was in the Files sidebar search layout path; the resolution preserves main's search height changes while keeping this branch's idempotent AppKit layout guard.

Constraint: origin/main advanced with right-sidebar Find typing-lag changes that touched the same Files search layout method.

Rejected: Rebase the branch | keeping the PR history stable avoids rewriting already published commits.

Confidence: high

Scope-risk: moderate

Directive: Preserve searchBarVisibleHeight from main when editing the Files search layout path.

Tested: ./scripts/reload.sh --tag issue-3742-files-sidebar-flicker

Tested: git diff --check
Merging main brought the current window-drag API, where temporary movability is covered by withTemporaryWindowMovableEnabled and suppression depth is covered separately. The old disable/restore helper tests referenced functions that no longer exist, so this removes the stale assertions while keeping the current lifecycle coverage.

Constraint: CircleCI macos-unit-tests failed at compile time before running the Files tests.

Rejected: Reintroduce temporarilyDisableWindowDragging helpers | the production API has moved to scoped temporary movability plus suppression depth.

Confidence: high

Scope-risk: narrow

Tested: xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -destination 'platform=macOS' -derivedDataPath /Users/austinwang/Library/Developer/Xcode/DerivedData/cmux-issue-3742-files-sidebar-flicker-unit -only-testing:cmuxTests/FolderWindowMoveSuppressionTests test

Tested: xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -destination 'platform=macOS' -derivedDataPath /Users/austinwang/Library/Developer/Xcode/DerivedData/cmux-issue-3742-files-sidebar-flicker-unit -only-testing:cmuxTests/FileExplorerStoreTests -only-testing:cmuxTests/FolderWindowMoveSuppressionTests test

@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 (1)
Sources/FileExplorerStore.swift (1)

515-535: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Drop stale async git-status results when root changes.

The callbacks at Lines 527 and 534 apply status for the captured path without verifying the current root. If the user switches folders before the async fetch returns, stale status can overwrite the active tree decoration and bump outlineRevision incorrectly.

🛡️ Proposed fix
                 DispatchQueue.main.async { [weak self] in
-                    self?.applyGitStatus(status)
+                    guard let self, self.rootPath == path else { return }
+                    self.applyGitStatus(status)
                 }

Apply the same guard in both SSH and local branches.

🤖 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/FileExplorerStore.swift` around lines 515 - 535, The async callbacks
use a captured `path` and unconditionally call `self?.applyGitStatus(status)`,
which can overwrite the active tree if the root changed; in both the SSH branch
(GitStatusProvider.fetchStatusSSH) and the local branch
(GitStatusProvider.fetchStatus) add a guard inside the DispatchQueue.main.async
closure that verifies the store's current `rootPath` still equals the captured
`path` (e.g. guard let self = self, self.rootPath == path else { return })
before calling `applyGitStatus(status)` so stale results don't update
decorations or bump `outlineRevision`.
🤖 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/FileExplorerStore.swift`:
- Around line 692-695: applyGitStatus currently sets gitStatusByPath inside
mutateOutline unconditionally which increments outlineRevision every git
refresh; change applyGitStatus to compare the incoming status with the existing
gitStatusByPath and only call mutateOutline when they differ. Specifically, in
applyGitStatus (the function name) perform an equality check between the new
status and the store's gitStatusByPath (or use a deep/element-wise compare), and
only then update gitStatusByPath inside mutateOutline so outlineRevision is
incremented only on real changes.

In `@tests/test_files_sidebar_no_redraw_on_close_surface.py`:
- Around line 93-98: The except block handling cmuxError currently raises
SystemExit(1) without preserving the original exception context; change the
raise to use exception chaining so the cmuxError remains the __cause__ (i.e.,
use "raise SystemExit(1) from exc" in the except cmuxError as exc block) and
keep the existing print/fail message and the try/raise SystemExit(main()) around
main() unchanged.

---

Outside diff comments:
In `@Sources/FileExplorerStore.swift`:
- Around line 515-535: The async callbacks use a captured `path` and
unconditionally call `self?.applyGitStatus(status)`, which can overwrite the
active tree if the root changed; in both the SSH branch
(GitStatusProvider.fetchStatusSSH) and the local branch
(GitStatusProvider.fetchStatus) add a guard inside the DispatchQueue.main.async
closure that verifies the store's current `rootPath` still equals the captured
`path` (e.g. guard let self = self, self.rootPath == path else { return })
before calling `applyGitStatus(status)` so stale results don't update
decorations or bump `outlineRevision`.
🪄 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: 24ef93e9-0a6e-4546-acf5-070073aed525

📥 Commits

Reviewing files that changed from the base of the PR and between 0da73c6 and 5e6208c.

📒 Files selected for processing (6)
  • Sources/FileExplorerStore.swift
  • Sources/FileExplorerView.swift
  • Sources/TerminalController.swift
  • cmuxTests/FileExplorerStoreTests.swift
  • tests/cmux.py
  • tests/test_files_sidebar_no_redraw_on_close_surface.py

Comment thread Sources/FileExplorerStore.swift
Comment thread tests/test_files_sidebar_no_redraw_on_close_surface.py Outdated
Comment thread Sources/FileExplorerStore.swift
The redraw gate depends on outlineRevision representing real Files-owned visible changes. Git status refreshes now ignore stale async results after the root changes and avoid bumping outlineRevision when the status map is unchanged.

Constraint: Git status fetches run off-main and can complete after the Files root has changed.

Rejected: Let every git poll bump outlineRevision | it can still create unnecessary row walks under the new redraw gate.

Confidence: high

Scope-risk: narrow

Directive: Keep async Files metadata callbacks root-checked before applying decorations.

Tested: xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -destination 'platform=macOS' -derivedDataPath /Users/austinwang/Library/Developer/Xcode/DerivedData/cmux-issue-3742-files-sidebar-flicker-unit -only-testing:cmuxTests/FileExplorerStoreTests test

Tested: python3 -m py_compile tests/cmux.py tests/test_files_sidebar_no_redraw_on_close_surface.py

Tested: git diff --check
@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 10, 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 cmuxTests/FileExplorerStoreTests.swift
Comment thread Sources/FileExplorerSearchController.swift

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

2 issues found across 9 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="Sources/FileExplorerSearchController.swift">

<violation number="1" location="Sources/FileExplorerSearchController.swift:417">
P2: Avoid showing absolute executable paths in user-facing error text; this exposes local environment details.</violation>
</file>

<file name="Sources/FileExplorerStore.swift">

<violation number="1" location="Sources/FileExplorerStore.swift:732">
P2: Path-only staleness checks can apply git status from a previous SSH connection when the new workspace has the same root path. Include provider/connection identity in the callback guard.</violation>
</file>

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

Comment thread Sources/FileExplorerSearchController.swift Outdated
Comment thread Sources/FileExplorerStore.swift Outdated

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

1 issue found across 2 files (changes from recent commits).

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="Sources/FileExplorerSearchController.swift">

<violation number="1" location="Sources/FileExplorerSearchController.swift:422">
P3: Remove the `.unsupported` switch arm here; it is unreachable after the earlier `guard scope != .unsupported` return.</violation>
</file>

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

Re-trigger cubic

Comment thread Sources/FileExplorerSearchController.swift Outdated
Comment thread Sources/FileExplorerStore.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 68d3626. Configure here.

Comment thread Sources/FileExplorerSearchController.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 — 590a71e4 Deployed May 22, 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.

Files sidebar flickers when closing a pane (nightly)

3 participants