Skip to content

Fix right sidebar Find typing lag - #3739

Merged
austinywang merged 15 commits into
mainfrom
issue-3738-find-sidebar-typing-lag
May 8, 2026
Merged

austinywang merged 15 commits into
mainfrom
issue-3738-find-sidebar-typing-lag

Conversation

@austinywang

@austinywang austinywang commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #3738

Bottleneck analysis

Baseline confirmed the static-analysis findings in the right-sidebar Find path:

  • FileExplorerContainerView.controlTextDidChange synchronously called refreshSearchIfNeeded() on each keystroke, so fast typing restarted rg per character.
  • FileSearchController.consumeStdout parsed rg --json output on the main actor, including JSONSerialization and FileExplorerTerminalPathInsertion.relativePath path normalization for each result.
  • FileExplorerContainerView.applySearchSnapshot did full table reloads for streaming result updates.
  • Follow-up non-crash repro also confirmed a layout contributor: the status label's intrinsic width changes as match-count text streams, and the search field was laid out against that changing label width.

Reproduction and baseline sample

Baseline build/run:

./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag-baseline --launch

Repro workspace: /Users/austinwang/manaflow/term/cmux1

Steps: focused Find with Cmd+Shift+F, typed private at about 8 chars/sec, and sampled the baseline app process for 5 seconds while typing.

Baseline sample saved locally at /tmp/cmux-3738-baseline-sample.txt.

Observed baseline behavior: visible cursor/result stalls under fast input, no dropped characters in this run, and results stabilized roughly 1 second after the last keystroke.

Relevant baseline sample frames:

FileExplorerContainerView.controlTextDidChange -> refreshSearchIfNeeded -> FileSearchController.search: 16 main-thread samples
FileSearchController.consumeStdout on main actor: 147 samples
FileSearchRipgrepParser.parseMatchLine on main actor: 91 samples
FileExplorerTerminalPathInsertion.relativePath / normalizedFileSystemPath on main actor: 50+ samples
FileExplorerContainerView.applySearchSnapshot -> reloadData on streaming updates

Fix

  • Added a FileSearchControlling seam plus a regression test showing a typing burst should not invoke one search per character.
  • Debounced text-field changes for 200 ms before starting rg.
  • Moved rg stdout/stderr buffering, JSON parsing, and per-result path normalization into a single background actor (FileSearchOutputPipeline).
  • Batched result snapshots to at most one UI update every 50 ms while streaming.
  • Made process termination non-blocking for the caller.
  • Replaced unconditional full table reloads with append/same-size targeted updates, falling back to reloadData() only when needed.
  • Decoupled status text from search-field width by making the query field full-width and moving status text onto a compact line below it.
  • Made the search field cell scrollable and explicitly scrolls the field editor to the insertion point so long queries remain visible while typing.
  • Added DEBUG-only file.search.* diagnostics for input cadence, debounce firing, search request count, snapshot apply time, and search-bar width changes.

Post-fix verification

Fixed build/run:

./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Repeated the same repro in /Users/austinwang/manaflow/term/cmux1 with private at the same cadence.

Post-fix sample saved locally at /tmp/cmux-3738-fixed-sample.txt.

Observed fixed behavior: no visible input lag, no dropped characters, private remained focused in the Find field, and results stabilized within the debounce window plus the first streamed batch, about 250 ms after the last keystroke.

Relevant fixed sample frames:

FileExplorerContainerView.controlTextDidChange: schedules debounce only
FileExplorerContainerView.scheduleSearchRefresh -> refreshSearchIfNeeded -> FileSearchController.search: one debounced search path, sampled during process start
FileSearchOutputPipeline.consumeStdout on background cooperative queues: parser/path-normalization work moved off main thread
FileExplorerContainerView.applySearchResultsUpdate: targeted table updates for appended results
No main-thread parseMatchLine / relativePath hot path in the fixed sample

Follow-up layout instrumentation from the non-crash repro (/tmp/cmux-debug-issue-3738-find-sidebar-typing-lag.log, around 13:16:46):

file.search.input.changed: 7 keystrokes, 18.5-26.8 ms apart
file.search.debounce.fire: one search fired about 223 ms after the last keystroke
file.search.snapshot: status intrinsic text width changed while field/status frames stayed stable
file.search.snapshot: applyMs stayed <= 4.5 ms in this repro

Long-input follow-up:

Typed abcdefghijklmnopqrstuvwxyz-private-function-search-long-query into the launched Find field.
The visible field text followed the suffix (...search-long-query) instead of staying stuck at the prefix.
Status text displayed below the full-width field and no longer stole horizontal input space.

Test plan

  • Not run locally: repository policy says not to run local tests; CI should run the new XCTest.
  • Built and launched baseline dev app with ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag-baseline --launch.
  • Captured baseline CPU sample during fast Find typing.
  • Built and launched fixed dev app with ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch.
  • Captured fixed CPU sample during the same fast Find typing repro.
  • Rebuilt and launched after the layout/diagnostic updates with ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch.
  • Verified the Swift warning budget with python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log.

Summary by CodeRabbit

  • Improvements

    • Search input now debounces automatically, reducing unnecessary requests while typing.
    • Results stream more responsively with throttled intermediate updates and incremental table updates (fewer full reloads).
    • Searches enforce result limits and provide clearer completion/error status in the UI.
    • Closing search cancels pending work to avoid stale results.
  • New Features

    • Search controller is now injectable to support alternative implementations.
  • Tests

    • Added test ensuring typing bursts coalesce into a single debounced search.

Note

Medium Risk
Moderate risk: refactors file search process lifecycle and introduces new async/actor pipeline and debouncing, which could affect cancellation/termination behavior and snapshot timing.

Overview
Reduces right-sidebar Find typing lag by debouncing keystroke-driven searches (200ms) and updating UI snapshots at a capped rate while results stream.

Refactors FileSearchController to use a background actor pipeline for stdout/stderr processing and result parsing, with async streaming, generation-based cancellation, and SIGTERM termination; also introduces a FileSearchControlling protocol for injection.

Improves Find UI responsiveness by avoiding full table reloads on every snapshot (incremental row inserts/targeted reloads), reworking the search bar layout so status text no longer shrinks the field, and ensuring long queries keep the insertion point visible. Adds a regression test verifying typing bursts coalesce into a single debounced search.

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

The Find search field had no injectable boundary, so the regression test could not count backend searches without launching ripgrep. This adds the smallest controller seam and a test that types a burst through controlTextDidChange and expects one backend search after the debounce window.

Constraint: Regression test must exercise observable input behavior instead of grepping source text

Confidence: high

Scope-risk: narrow

Directive: Keep the spy-backed input test on the AppKit delegate path so future changes preserve typing debounce semantics

Tested: Baseline UI repro and sample captured at /tmp/cmux-3738-baseline-sample.txt

Not-tested: Local XCTest run intentionally skipped per instruction
Typing in the right-sidebar Find field used to synchronously restart rg and process every JSON result through the main actor. The fix gives the text field a single debounced search trigger, moves rg output parsing and path normalization behind a background actor, batches result snapshots, and only applies targeted table updates for append/same-size changes.

Constraint: Local test runs are disabled for this repo; verification used the required tagged reload build and manual reproduction sampling.

Rejected: Only adding debounce | leaves JSON parsing and path normalization able to starve the main actor once rg starts streaming matches

Rejected: Sprinkling detached work at each parse site | keeps lifecycle ownership fragmented instead of one output pipeline

Confidence: high

Scope-risk: moderate

Directive: Keep Find search input, rg lifecycle, output parsing, and table updates on one cancellable pipeline; do not reintroduce per-keystroke synchronous search starts.

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch; manual fast typing of private in /Users/austinwang/manaflow/term/cmux1; post-fix sample at /tmp/cmux-3738-fixed-sample.txt

Not-tested: Local XCTest/XCUITest suites per repository policy
@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 8, 2026 11:07pm
cmux-staging Building Building Preview, Comment May 8, 2026 11:07pm

Merged the latest origin/main into the PR branch before CI iteration so this fix is evaluated against current workflow and auxiliary-window close-shortcut checks.

Constraint: iterate-pr workflow requires syncing the PR branch with the latest base branch before acting on CI feedback.

Confidence: high

Scope-risk: narrow

Tested: Merge completed cleanly

Not-tested: Local tests per repository policy
@coderabbitai

coderabbitai Bot commented May 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

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

Adds a debounce to find input, moves ripgrep JSON parsing and path normalization into a background FileSearchOutputPipeline actor, makes search types Sendable, integrates the pipeline with FileSearchController for generation-aware updates and safer termination, applies incremental table updates, and adds debounce tests.

Changes

Search Debouncing and Async Pipeline

Layer / File(s) Summary
Data Contracts & Thread-Safe Types
Sources/FileExplorerSearchController.swift
FileSearchResult and FileSearchSnapshot (and nested Status) gain Sendable. New FileSearchControlling protocol and pipeline update types are introduced.
Async Output Pipeline Actor
Sources/FileExplorerSearchController.swift
Adds FileSearchOutputPipeline actor that parses rg JSON lines off-main-thread, buffers/aggregates stderr, and emits throttled FileSearchPipelineUpdate values.
Controller Pipeline Integration
Sources/FileExplorerSearchController.swift
FileSearchController owns the pipeline actor, wires Process stdout/stderr readability to the pipeline, applies updates via applyPipelineUpdate/finish, advances generation on cancel, and terminates processes via a detached Task while clearing pipeline handlers.
View MainActor & Dependency Injection
Sources/FileExplorerView.swift
FileExplorerContainerView is @MainActor, stores any FileSearchControlling, accepts an optional injected controller (defaults to FileSearchController()), and retains debounce state; Coordinator.reloadIfNeeded wraps UI updates with MainActor.assumeIsolated.
Search Field Layout & Priorities
Sources/FileExplorerView.swift
Search field cell configured for single-line scrolling/clipping; content-hugging/compression-resistance priorities adjusted for field and status label; search bar visible height centralized.
Keystroke Debouncing
Sources/FileExplorerView.swift
Replaces immediate refresh with scheduleSearchRefresh()/cancelPendingSearchRefresh() debouncing; refreshSearchIfNeeded() cancels pending debounce before issuing search; pending tasks are cancelled when the view is removed from window or search closes; insertion-point scrolling helper added.
Incremental Table Updates
Sources/FileExplorerView.swift
applySearchSnapshot computes previousResults and delegates rendering to applySearchResultsUpdate, which inserts appended rows, reloads changed rows when counts match, or falls back to full reload; DEBUG timing/logging helpers added.
Debounce Test Coverage
cmuxTests/FileExplorerStoreTests.swift
Adds test testTypingBurstDebouncesFindSearches, a recursive findSearchField(in:) helper, and SpyFileSearchController recording search calls and cancellations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A rabbit taps the keyboard light,
Debounce holds fast through typing's flight.
Parser hops off-thread with grace,
Snapshots trickle, UI keeps pace.
Now find is quick — a nimble chase! 🐰✨


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (3 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error Introduces DispatchQueue.main.asyncAfter (200ms) in typing path. Rules flag asyncAfter in typing paths as failures by default. Combine.debounce already used elsewhere in same file. Replace asyncAfter with Combine debounce on NSTextField text changes, using existing pattern in FileExplorerView
Cmux Swift Concurrency ❌ Error Introduces fire-and-forget Task.detached in stopCurrentProcess (FileExplorerSearchController.swift:380) for process termination with meaningful lifecycle. Task not stored/cancelled/tied to operation. Store termination Task or await completion. Alternatively use synchronous background dispatch instead of fire-and-forget.
Cmux Swift @Concurrent ❌ Error New nonisolated async functions streamStdout/streamStderr perform I/O-heavy work from MainActor context but lack @concurrent annotations required by the rule. Add @concurrent annotation: "nonisolated static async func streamStdout/streamStderr()" → "nonisolated static async func streamStdout/streamStderr() @concurrent"
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 (11 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: addressing Find typing lag in the right sidebar through debouncing and performance optimizations.
Linked Issues check ✅ Passed All five priority objectives from issue #3738 are fully addressed: debouncing (200 ms), off-main-thread JSON parsing and path normalization via FileSearchOutputPipeline actor, incremental table updates instead of full reloads, asynchronous process termination, and regression testing with typing coalescing verification.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #3738: debounce logic, FileSearchOutputPipeline actor, FileSearchControlling protocol, incremental table updates, search-bar layout refinement, and regression tests—no unrelated modifications detected.
Cmux Swift Actor Isolation ✅ Passed Value models conform to Sendable without MainActor. FileSearchControlling is explicitly @MainActor. FileSearchOutputPipeline is a proper background actor. No actor isolation violations introduced.
Cmux No Hacky Sleeps ✅ Passed Not applicable. PR contains only Swift code changes. Rule explicitly scopes to non-Swift runtime (TS/JS/shell). Swift primitives covered separately.
Cmux Swift File And Package Boundaries ✅ Passed FileExplorerSearchController has clear single responsibility. FileExplorerView growth of 235 lines is under 250 threshold and consists of AppKit UI code. Protocol injection provides proper seam.
Cmux Swift Logging ✅ Passed Complies with swift-logging.md. All dlog() calls DEBUG-guarded. No print/NSLog/dump in production. No file-scoped Logger issues. No sensitive data logged.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state violations. Changes only affect existing legacy @ObservedObject state and AppKit NSView/backend code. No new problematic patterns introduced.
Cmux Architecture Rethink ✅ Passed Uses proper structured concurrency with generation tokens, actor isolation, non-blocking async termination, and clear ownership. No prohibited timing or observer patterns found.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR modifies NSView subclasses and controllers only. No NSWindow, NSPanel, NSWindowController, or SwiftUI Window/WindowGroup additions. Check does not apply.
Description check ✅ Passed The PR description thoroughly documents the bottleneck analysis, reproduction steps, fix implementation, verification results, and test plan, exceeding template requirements with comprehensive technical detail.
✨ 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-3738-find-sidebar-typing-lag

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.

The initial main-actor isolation fix made the whole file explorer coordinator actor-isolated, which compiled locally but added new Swift isolation warnings that the macOS debug-build warning budget rejects. Scope the actor hop to the container visibility update instead, preserving synchronous outline refresh behavior without expanding coordinator isolation.

Constraint: CI enforces scripts/swift_warning_budget.py on debug build output.

Rejected: Refresh the warning budget | this was avoidable new debt from the PR, not accepted project debt

Confidence: high

Scope-risk: narrow

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch; python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: Local XCTest/XCUITest suites per repository policy
@greptile-apps

greptile-apps Bot commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes right-sidebar Find typing lag by debouncing keystrokes (200ms via Combine PassthroughSubject), moving ripgrep stdout/stderr parsing and result aggregation off the main actor into a FileSearchOutputPipeline actor, throttling streaming snapshot updates to 50ms intervals, and switching to incremental table updates instead of unconditional reloadData().

  • FileExplorerSearchController.swift: Introduces FileSearchControlling protocol, FileSearchOutputPipeline actor for off-main-thread parsing and buffering, FileSearchTerminationSignal actor for clean process-exit coordination, and replaces readability-handler callbacks with a structured async let streaming task. Generation guards and cooperative task cancellation handle search restarts and the max-results early-exit path correctly.
  • FileExplorerView.swift: Adds Combine debounce pipeline, applySearchResultsUpdate with prefix-append / same-size / full-reload branches, search-field scrolling to insertion point, and status label moved below a full-width field to eliminate horizontal layout churn. ~245 new lines bring the file to 2124 (budget 1879).
  • cmuxTests/FileExplorerStoreTests.swift: Adds SpyFileSearchController and a regression test verifying a typing burst coalesces into one search call.

Confidence Score: 5/5

Safe to merge; the concurrency design is correct and the generation-based guards handle all search restart and early-exit orderings.

The actor isolation and task cancellation paths are correct: searchTask self-cancels cleanly via terminationSignal returning nil, finish() guards against stale generations in both the normal and shouldStopProcess paths, and no applyPipelineUpdate can race past pipeline.finish() because stdoutDone resolution drains all pending await applyUpdate calls. The only finding is a file-length and responsibility-mixing note on FileExplorerView.swift, which does not affect correctness.

Sources/FileExplorerView.swift — now at 2124 lines with debounce orchestration state woven into the view class.

Important Files Changed

Filename Overview
Sources/FileExplorerSearchController.swift Introduces FileSearchControlling protocol, FileSearchOutputPipeline actor, FileSearchTerminationSignal actor, and migrates parsing/buffering off the main actor. Generation-based guards, task self-cancellation, and the shouldStopProcess path all appear correctly ordered.
Sources/FileExplorerView.swift Adds Combine-based debounce, incremental table-update logic, scrollable search field, and ~100 lines of #if DEBUG diagnostics to a file that is now 2124 physical lines (budget 1879). Debounce orchestration state is woven directly into the AppKit view class rather than extracted into a separate type.
cmuxTests/FileExplorerStoreTests.swift Adds SpyFileSearchController and a debounce regression test. Task.sleep use is test-only scaffolding and is allowed per cmux rules.

Sequence Diagram

sequenceDiagram
    participant NSTextField
    participant ContainerView as FileExplorerContainerView (@MainActor)
    participant Combine as Combine Debounce (RunLoop.main)
    participant Controller as FileSearchController (@MainActor)
    participant Task as searchTask (@MainActor)
    participant Pipeline as FileSearchOutputPipeline (actor)
    participant TermSig as FileSearchTerminationSignal (actor)
    participant RG as rg process

    NSTextField->>ContainerView: controlTextDidChange
    ContainerView->>Combine: searchDebounceSubject.send(generation)
    Note over Combine: 200ms debounce
    Combine->>ContainerView: generation matches - refreshSearchIfNeeded()
    ContainerView->>Controller: search(query:rootPath:isLocal:)
    Controller->>Controller: stopAndAdvanceGeneration()
    Controller->>RG: process.run()
    Controller->>Task: "Task { streamStdout + streamStderr + wait }"
    par Background streams
        Task->>Pipeline: "consumeStdoutLine (off main actor via @concurrent)"
        Pipeline-->>Task: FileSearchPipelineUpdate (every 50ms or at limit)
        Task->>Controller: applyPipelineUpdate - emit snapshot
        Controller->>ContainerView: onSnapshotChanged - applySearchSnapshot
        ContainerView->>ContainerView: applySearchResultsUpdate (insertRows / reloadData)
    and stderr drain
        Task->>Pipeline: consumeStderrLine
    end
    RG->>TermSig: terminationHandler - complete(status)
    Task->>TermSig: await wait() - status
    Task->>Pipeline: finish(status) - final update
    Task->>Controller: finish(generation:update:) - emit final snapshot
    Controller->>ContainerView: onSnapshotChanged - applySearchSnapshot
Loading

Reviews (10): Last reviewed commit: "Clarify Find search task ownership" | Re-trigger Greptile

Comment thread Sources/FileExplorerView.swift
Comment thread Sources/FileExplorerSearchController.swift
Comment thread Sources/FileExplorerView.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 8, 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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 22185ca9-6f43-4281-98fb-e84022cc2020

📥 Commits

Reviewing files that changed from the base of the PR and between fbbd75e and 9505578.

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

Comment thread Sources/FileExplorerSearchController.swift Outdated
Comment thread Sources/FileExplorerSearchController.swift
The right-sidebar Find status label changes text as ripgrep streams matches. Its intrinsic width was still participating in the search-bar layout, so status updates could move the search field while the user was typing. Give the status area a stable width and add DEBUG-only evidence logs for input cadence, debounce firing, snapshot apply cost, and search-bar width changes.

Constraint: Use the project reload script only; never invoke xcodebuild directly for this branch.

Constraint: User needs debug-log evidence for a reproducible non-crash latency issue.

Rejected: Hide or remove the status label | loses progress feedback and does not prove whether status text was driving field geometry churn.

Confidence: medium

Scope-risk: narrow

Directive: Revisit the DEBUG file.search.* probes after the active repro is resolved; keep only logs that remain useful for future typing-lag diagnostics.

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: Typed private into the right-sidebar Find input on /Users/austinwang/manaflow/term/cmux1; file.search.* logs showed one debounced search and stable field/status widths while status intrinsic width changed.

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: XCTest and XCUITest, per local testing policy.
coderabbitai[bot]
coderabbitai Bot previously requested changes May 8, 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

🤖 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/FileExplorerView.swift`:
- Around line 1051-1056: Replace the expensive allocation
Array(nextResults.prefix(previousResults.count)) used in applySearchSnapshot
with the non-allocating prefix check nextResults.starts(with: previousResults);
specifically, in the block that checks if nextResults.count >
previousResults.count and currently compares the prefix, change that comparison
to nextResults.starts(with: previousResults) so searchResultsView.insertRows(at:
IndexSet(integersIn: insertedRange), withAnimation: []) still runs when
appropriate but without allocating a new array each snapshot.
🪄 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: 849b3bff-b8b4-4f5d-82d7-80a129a21838

📥 Commits

Reviewing files that changed from the base of the PR and between 9505578 and a885946.

📒 Files selected for processing (1)
  • Sources/FileExplorerView.swift

Comment thread Sources/FileExplorerView.swift
The fixed inline status slot stopped width churn but took too much space from the search field, especially for long queries. Move status text below the full-width search field and make the field cell scrollable so typing follows the insertion point.

Constraint: Preserve the no-resize property for streaming status updates without sacrificing input visibility.

Rejected: Keep shrinking the inline status slot | still trades query visibility against result status and repeats the same layout coupling.

Confidence: high

Scope-risk: narrow

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: Typed abcdefghijklmnopqrstuvwxyz-private-function-search-long-query into the launched Find field; visible text followed the suffix instead of remaining stuck at the prefix.

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: XCTest/XCUITest locally, per repository policy.
coderabbitai[bot]
coderabbitai Bot previously requested changes May 8, 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

🤖 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/FileExplorerView.swift`:
- Around line 1033-1058: The debounce Task stored in searchDebounceTask is only
cancelled on teardown, allowing a pending debounce to fire after the Find panel
is hidden; fix this by cancelling and clearing any pending debounce when Find is
hidden: call cancelPendingSearchRefresh() from the code path that changes
isSearchVisible to false (e.g., the isSearchVisible setter or the method that
hides the Find UI), and ensure cancelPendingSearchRefresh() keeps cancelling and
nil-ing searchDebounceTask; this prevents scheduleSearchRefresh /
refreshSearchIfNeeded from running after the Find UI is no longer visible.
🪄 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: ef7c04ac-2901-48b7-9177-caf092cdcb06

📥 Commits

Reviewing files that changed from the base of the PR and between c5037a3 and 8f9a886.

📒 Files selected for processing (1)
  • Sources/FileExplorerView.swift

Comment thread Sources/FileExplorerView.swift
Comment thread Sources/FileExplorerView.swift Outdated
Review feedback exposed two remaining latency-path risks: the debounce still used a Swift sleep timer, and process finalization could race pipe consumption. This changes the Find debounce to a cancellable main-queue work item, cancels pending work when Find is hidden, avoids prefix array allocation during incremental table updates, and makes the search controller finalize only after process termination plus stdout/stderr stream completion.

Constraint: Build must use scripts/reload.sh only; no direct xcodebuild and no local tests.

Rejected: Keep readabilityHandler callbacks and guard late results individually | still allows final status to race tail output.

Confidence: high

Scope-risk: moderate

Directive: Keep search parsing/finalization ordered off the text input path; do not reintroduce per-keystroke sleeps or main-thread pipe parsing.

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: Local XCTest per repository policy.
Greptile's summary correctly pointed out that Coordinator.reloadIfNeeded relied on MainActor.assumeIsolated. The method is UI work, so it now carries @mainactor directly and the Combine store observer performs an explicit main-actor hop before reloading.

Constraint: Build must use scripts/reload.sh only; no direct xcodebuild and no local tests.

Rejected: Leave assumeIsolated because the sink uses RunLoop.main | keeps a runtime trap where compile-time isolation is available.

Confidence: high

Scope-risk: narrow

Directive: Prefer @mainactor annotations for FileExplorer UI reload paths when the caller can hop explicitly.

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: Local XCTest per repository policy.
Comment thread Sources/FileExplorerSearchController.swift Outdated
The structured async stream path consumes ripgrep output line-by-line through consumeStdoutLine. Keeping the old Data-buffered consumeStdout method and stdoutBuffer field created dead code after the review-response refactor, so this removes the unused path and leaves one output ingestion model.

Constraint: Build must use scripts/reload.sh only; no direct xcodebuild and no local tests.

Rejected: Keep both buffered and line-based ingestion paths | invites future edits to the wrong path and weakens the pipeline ownership model.

Confidence: high

Scope-risk: narrow

Directive: FileSearchOutputPipeline stdout ingestion is line-based through AsyncBytes.lines; add buffering only if a future reader no longer provides complete lines.

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: Local XCTest per repository policy.
Comment thread Sources/FileExplorerView.swift Outdated
Comment thread Sources/FileExplorerView.swift Outdated
The debounce path now uses a generation token to invalidate stale callbacks instead of letting a DispatchWorkItem closure retain the work item it is assigned to. The deferred field-editor scroll also uses an explicit MainActor task so the handoff is visible to Swift concurrency checking.

Constraint: Build must use ./scripts/reload.sh only; no direct xcodebuild invocation

Rejected: Capture DispatchWorkItem inside its own closure | canceled items can retain themselves

Confidence: high

Scope-risk: narrow

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: Local XCTest per repo policy

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

Caution

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

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

579-1366: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Extract the new Find plumbing out of this already over-budget view file.

This block adds debounce orchestration, snapshot diffing, and DEBUG diagnostics to a file that is already well past the repo’s large-file threshold. Please move the new search-specific controller/state helpers into dedicated Swift file(s) and keep Sources/FileExplorerView.swift focused on AppKit view composition.

As per coding guidelines: “do not accept more than 250 lines added to an existing production Swift file that is already over 800 lines, unless an extraction exception is met by removing/moving mixed responsibilities and decreasing total line count by more than 200 lines.”

🤖 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/FileExplorerView.swift` around lines 579 - 1366, The file is doing
too much — extract all search-specific state, debounce logic, snapshot diffing,
and debug helpers into a new helper type and move related
NSTableView/NSSearchField delegate plumbing into separate file(s). Create a new
FileExplorerSearchManager (or similar) that owns: properties searchController,
searchSnapshot, searchDebounceGeneration, searchDebounceWorkItem,
searchDebounceDelaySeconds, searchBarVisibleHeight; methods
refreshSearchIfNeeded(search:rootPath:isLocal:contentRevision:),
scheduleSearchRefresh(), cancelPendingSearchRefresh(), applySearchSnapshot(_:),
applySearchResultsUpdate(previous:next:), statusText(for:), and all DEBUG helper
funcs; expose a closure onSnapshotChanged and simple API to start/cancel
searches and query status. Remove those fields/methods from
FileExplorerContainerView and replace them with an instance of the manager,
calling manager.scheduleSearchRefresh() from controlTextDidChange,
manager.refreshSearchIfNeeded() when presentation/root changes, and wiring
manager.onSnapshotChanged to update UI (searchStatusLabel, searchResultsView
reload/selection). Move NSTableView/NSSearchField delegate implementations that
are search-specific into a new FileExplorerSearchUI.swift extension that uses
the manager. Keep only view composition, presentation toggles, and keyboard
focus logic in FileExplorerContainerView.
🤖 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.

Outside diff comments:
In `@Sources/FileExplorerView.swift`:
- Around line 579-1366: The file is doing too much — extract all search-specific
state, debounce logic, snapshot diffing, and debug helpers into a new helper
type and move related NSTableView/NSSearchField delegate plumbing into separate
file(s). Create a new FileExplorerSearchManager (or similar) that owns:
properties searchController, searchSnapshot, searchDebounceGeneration,
searchDebounceWorkItem, searchDebounceDelaySeconds, searchBarVisibleHeight;
methods refreshSearchIfNeeded(search:rootPath:isLocal:contentRevision:),
scheduleSearchRefresh(), cancelPendingSearchRefresh(), applySearchSnapshot(_:),
applySearchResultsUpdate(previous:next:), statusText(for:), and all DEBUG helper
funcs; expose a closure onSnapshotChanged and simple API to start/cancel
searches and query status. Remove those fields/methods from
FileExplorerContainerView and replace them with an instance of the manager,
calling manager.scheduleSearchRefresh() from controlTextDidChange,
manager.refreshSearchIfNeeded() when presentation/root changes, and wiring
manager.onSnapshotChanged to update UI (searchStatusLabel, searchResultsView
reload/selection). Move NSTableView/NSSearchField delegate implementations that
are search-specific into a new FileExplorerSearchUI.swift extension that uses
the manager. Keep only view composition, presentation toggles, and keyboard
focus logic in FileExplorerContainerView.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8d472256-a61c-4d56-85aa-2f7131bf51d0

📥 Commits

Reviewing files that changed from the base of the PR and between 8f9a886 and 88ddeac.

📒 Files selected for processing (2)
  • Sources/FileExplorerSearchController.swift
  • Sources/FileExplorerView.swift

The Find field debounce now uses a Combine subject and generation guard instead of queueing Dispatch work items. Search process cancellation sends SIGTERM directly, and the stdout/stderr readers are marked @Concurrent so their I/O and parsing path cannot inherit main-actor execution.

Constraint: Build must use ./scripts/reload.sh only; no direct xcodebuild invocation

Rejected: DispatchQueue.asyncAfter debounce | reviewer rule treats queue timers in typing paths as blocking-risk code

Rejected: Task.detached process termination | detached tasks obscure lifecycle and were flagged by review

Confidence: high

Scope-risk: moderate

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: Local XCTest per repo policy
CircleCI still runs Xcode 16.4, so the search stream readers keep the @Concurrent annotation only for Swift 6.2+ compilers. Newer local toolchains still force the I/O-heavy readers off the caller actor, while older CI compilers can build the same source.

Constraint: CircleCI macOS executor uses Xcode 16.4

Constraint: Build must use ./scripts/reload.sh only; no direct xcodebuild invocation

Rejected: Ungated @Concurrent | Swift 6.1 CI toolchains cannot parse the attribute

Confidence: high

Scope-risk: narrow

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: Local XCTest per repo policy
Comment thread Sources/FileExplorerSearchController.swift
FileSearchTerminationSignal now tracks cancellable wait continuations by id, so canceling a search task resumes the waiter with nil instead of holding the task until the process termination handler runs. Normal process completion still resumes every pending waiter with the exit status.

Constraint: Build must use ./scripts/reload.sh only; no direct xcodebuild invocation

Rejected: Plain CheckedContinuation wait | cancellation cannot release the search task promptly

Confidence: high

Scope-risk: narrow

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: Local XCTest per repo policy
@austinywang
austinywang dismissed stale reviews from coderabbitai[bot], coderabbitai[bot], and coderabbitai[bot] May 8, 2026 22:52

Superseded by later commits; the referenced inline CodeRabbit findings are resolved and the current CodeRabbit check is passing.

@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 e9fbd9e. Configure here.

Comment thread Sources/FileExplorerSearchController.swift
The search stream task used a weak capture but immediately promoted it to a task-long strong controller reference. Keep the task weak with an async update handler so streaming updates and final completion only retain the controller at the call site.

Constraint: Cursor flagged the misleading weak-to-strong promotion after CI completed.

Rejected: Capture self strongly for the whole task | preserves the temporary retain cycle the review identified.

Confidence: high

Scope-risk: narrow

Tested: ./scripts/reload.sh --tag issue-3738-find-sidebar-typing-lag --launch

Tested: python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-3738-find-sidebar-typing-lag.log

Not-tested: Local tests per repository policy; CI will run after push.
@austinywang
austinywang merged commit 52db9e3 into main May 8, 2026
26 checks passed
@greptile-apps greptile-apps Bot mentioned this pull request May 10, 2026
3 of 4 tasks

This branch was successfully deployed

1 active deployment
Preview – cmux — 899d0e7e Deployed May 8, 2026 by vercel[bot]
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.

Right sidebar find input is laggy while typing — debounce + main-thread parsing

1 participant