Skip to content

Improve file explorer search navigation - #6968

Closed
austinywang wants to merge 32 commits into
mainfrom
issue-5741-hope-to-have-an-ide-level-file-editor-that-is
Closed

austinywang wants to merge 32 commits into
mainfrom
issue-5741-hope-to-have-an-ide-level-file-editor-that-is

Conversation

@austinywang

@austinywang austinywang commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5741

Summary

  • Open file-search results in the cmux preview at the matching line and column.
  • Preserve existing local file activation settings for normal tree opens and external editor opens.
  • Refresh the local file explorer recursively so nested file additions/deletions update expanded folders.
  • Add behavioral coverage for search-result routing, pending editor navigation, and nested file refresh.

Validation

  • ./scripts/lint-pbxproj-test-wiring.sh
  • git diff --check
  • Localizable.xcstrings JSON parse

No reload.sh or xcodebuild run per issue instructions.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Opens file‑explorer search results at the exact line and UTF‑8 byte column, applying the jump after text loads or when the editor attaches. Keeps git‑status badges current by watching .git metadata (including worktrees/submodules) and installing the watcher when a repo appears after a folder is opened.

  • New Features

    • Precise text navigation for local and remote files; remote files materialize first. Markdown and .xcodeproj routing is preserved.
    • Recursive watcher with coalesced events and capped exclusions (max 8). Dedicated .git watcher resolves gitdir:, excludes objects/logs, installs on .git creation, and triggers an eager refresh.
  • Bug Fixes

    • Clear stale navigation when reopening without a line target, when the file becomes unavailable, or when a dirty buffer reloads with different content.
    • Coalesce git status runs with a per‑root/provider generation guard so slow or stale results don’t pile up or overwrite current state.
    • Bound .git pointer reads to 64 KiB and reject non‑regular .git entries to avoid hangs.

Written for commit 88a32b9. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • File previews can now open at a specific line and column, including from search results and file browser actions.
    • Text previews now remember pending navigation until content finishes loading, improving “open at location” behavior.
  • Bug Fixes

    • File tree updates are now more reliable when files or folders change on disk.
    • Git status refreshes are more responsive and less prone to stale updates.
  • Tests

    • Added coverage for line/column navigation, preview routing, file-watching, and Git metadata handling.

@vercel

vercel Bot commented Jun 26, 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 Jul 5, 2026 9:20am
cmux-staging Building Building Preview, Comment Jul 5, 2026 9:20am

@coderabbitai

coderabbitai Bot commented Jun 26, 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

File preview open callbacks throughout the file explorer, sidebar, and right sidebar panel now carry optional line/column position, propagated through a new FileExplorerPreviewOpenHandler type and openFileSurfacesNavigatingTextPosition. FilePreviewPanel gains deferred navigation state applied once text loads. The local file explorer watcher switches to RecursivePathWatcher with exclusion paths, adds a separate .git-metadata watcher, and coalesces git status refreshes with generation tracking.

Changes

File preview navigation and recursive watching

Layer / File(s) Summary
Preview callback contract
Sources/FileExplorerKeyboardShortcuts.swift, Sources/FileExplorerView.swift, Sources/RightSidebarPanelView.swift, Sources/ContentView.swift, Sources/RightSidebarToolPanel.swift, cmuxTests/FileExplorerStoreTests.swift
Introduces FileExplorerPreviewOpenHandler carrying optional lineNumber/columnNumber, updates all preview-open call sites and property types accordingly, and extends test helpers/assertions to capture the new coordinates.
Preview navigation state
Sources/Panels/FilePreviewWorkspaceOpenSupport.swift, Sources/Panels/FilePreviewPanel.swift, cmuxTests/FilePreviewTextEditorTextKitTests.swift
Adds openFileSurfacesNavigatingTextPosition and navigateToTextPosition, defers cursor placement until text content loads, computes UTF-16 offsets from line/column, and adds tests covering navigation, byte-offset columns, surface routing, and dirty/unavailable load edge cases.
Recursive local tree watching
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/FileWatch/FileSystemEventStream.swift, .../RecursivePathWatcher.swift, Sources/FileExplorerStore.swift, cmuxTests/FileExplorerStoreTests.swift, Packages/.../RecursivePathWatcherTests.swift
Adds excludedPaths support to FileSystemEventStream and RecursivePathWatcher, switches the local root watcher to recursive watching, and adds a nested-file tree-refresh regression test.
Git metadata watcher and status coalescing
Sources/FileExplorerStore.swift, cmuxTests/FileExplorerStoreTests.swift
Adds a dedicated recursive .git-metadata watcher, git metadata resolution/validation helpers, and generation-tracked in-flight coalescing for git status refreshes via applyGitStatusResult.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FileExplorerPanelView.Coordinator
  participant RightSidebarToolPanel
  participant Workspace
  participant FilePreviewPanel

  FileExplorerPanelView.Coordinator->>RightSidebarToolPanel: onOpenFilePreview(path, lineNumber, columnNumber)
  RightSidebarToolPanel->>Workspace: openFileSurfacesNavigatingTextPosition(filePath, lineNumber, columnNumber)
  Workspace->>FilePreviewPanel: openFileSurfaces(filePath)
  Workspace->>FilePreviewPanel: navigateToTextPosition(lineNumber, columnNumber)
  FilePreviewPanel->>FilePreviewPanel: applyPendingTextNavigationIfReady() (after text load)
Loading
sequenceDiagram
  participant FileExplorerStore
  participant RecursivePathWatcher
  participant FileSystemEventStream
  participant GitStateWatcher

  FileExplorerStore->>RecursivePathWatcher: updateDirectoryWatcher(rootPath, excludedPaths)
  RecursivePathWatcher->>FileSystemEventStream: init(paths, excludedPaths)
  FileSystemEventStream-->>FileExplorerStore: tree change event
  FileExplorerStore->>FileExplorerStore: reload() + refreshGitStatus()
  FileExplorerStore->>GitStateWatcher: startGitStateWatcher(gitMetadataDirectory)
  GitStateWatcher-->>FileExplorerStore: metadata change event
  FileExplorerStore->>FileExplorerStore: refreshGitStatus() (coalesced, generation-checked)
Loading

Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Cmux Architecture Rethink ❌ Error .git is always excluded, and installGitStateWatcherIfNeeded() only runs on later tree events, so a repo created after open can stay stale until unrelated changes. Watch the parent/root for .git creation (or delay excluding a missing .git), then hand off to gitStateWatcher once metadata exists.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description covers summary and validation, but it misses the required Demo Video, Review Trigger, and Checklist sections. Add the missing Demo Video, Review Trigger, and Checklist sections, and format the Testing section to match the template.
✅ Passed checks (22 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy all three issue goals: search results navigate to line/column, file opens preserve preview/editor routing, and the tree refreshes recursively.
Out of Scope Changes check ✅ Passed The extra watcher and git-status changes support the stated refresh and navigation goals and don't add unrelated behavior.
Cmux Swift Actor Isolation ✅ Passed The diff keeps new UI/store access on MainActor paths and adds no new Sendable mutable references or implicit-MainActor protocols.
Cmux Swift Blocking Runtime ✅ Passed Filtered diff shows no new waits, sleeps, main-sync, semaphores, or locks; changes use async tasks/watchers for navigation and refresh.
Cmux Browser Automation Off-Main ✅ Passed PR only changes file explorer/preview navigation and watchers; no edits to TerminalController or socket-worker browser automation routing.
Cmux Expensive Synchronous Load ✅ Passed PASS: This PR only forwards preview coords and refreshes file watching; the only Data(contentsOf:) here is in FilePreviewTextLoader, wrapped by Task.detached off-main.
Cmux Cache Substitution Correctness ✅ Passed PR only adds freshness-guarded async git status refresh and transient preview navigation; no persistence/history/undo/snapshot path swaps fresh reads for stale cache.
Cmux No Hacky Sleeps ✅ Passed Diff is Swift/test-only; no non-Swift runtime scripts were changed, and no new sleeps/timers/polling hacks were introduced.
Cmux Algorithmic Complexity ✅ Passed The new scans are bounded or single-pass (8-item exclusion sorting, 64KiB .git pointer read, one text-position walk); no nested rescans or hot-path re-sorts were introduced.
Cmux Swift Concurrency ✅ Passed New async work is either stored/actor-bound (watcher Tasks) or a UI callback bridge; no new background queue, Combine app-state, or unowned fire-and-forget pattern was introduced.
Cmux Swift @Concurrent ✅ Passed Touched async paths already hop off the main actor via Task.detached/DispatchQueue, and no new nonisolated async function lacks @concurrent.
Cmux Swift File And Package Boundaries ✅ Passed Touched oversized files grew modestly (FileExplorerStore +232, FilePreviewPanel +86) and changes are focused UI/file-watch glue, with watcher logic extracted to CmuxFoundation.
Cmux Swiftpm Lockfiles ✅ Passed Changed files are source/tests only; no Package.swift, Package.resolved, .gitignore, or workflow dependency files were modified, so the lockfile rule isn’t triggered.
Cmux Swift Logging ✅ Passed No new production logging was added in the PR-diffed Swift files; the only Swift logs found are existing #if DEBUG NSLogs in FileExplorerStore, which the rule allows.
Cmux User-Facing Error Privacy ✅ Passed Diff only changes file-navigation and watcher logic; no new user-facing error/alert copy exposes vendor/internal details or raw upstream messages.
Cmux Full Internationalization ✅ Passed PR changes navigation/watcher logic only; no new user-facing strings, string-catalog/Info.plist edits, or web locale files were added.
Cmux Swiftui State Layout ✅ Passed No violating SwiftUI state/layout patterns were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only changes file preview/search routing and watchers; no new or modified standalone NSWindow/NSPanel/WindowGroup code or cmux.* identifiers.
Cmux Source Artifacts ✅ Passed Only submodule gitlinks changed; no logs, build output, caches, or scratch artifacts appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The PR adds navigation/watcher logic only; touched production files introduce no new #if DEBUG or *ForTesting test/debug seams.
Cmux No Ambient Global State ✅ Passed The PR only adds/updates scoped methods and injected callbacks; no new file-scope mutable state, singletons, or static-only namespaces were introduced.
Title check ✅ Passed The title clearly summarizes the main change: improved file explorer search navigation.
✨ 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-5741-hope-to-have-an-ide-level-file-editor-that-is

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.

@greptile-apps

greptile-apps Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves file-explorer search navigation by opening search results at the exact line and UTF-8 byte column in the cmux preview, upgrades the local directory watcher from a flat FileWatcher to a recursive RecursivePathWatcher with exclusion support, and adds a dedicated git-state watcher plus a bootstrap git-creation watcher so status badges stay fresh after git init or metadata-only commits.

  • Search navigation: Threads (lineNumber, columnNumber) from FileExplorerContainerView through FileExplorerPreviewOpenHandler to FilePreviewPanel.navigateToTextPosition, with correct UTF-8/grapheme-cluster offset math and pending-jump clearing for dirty-buffer, unavailable-file, and nil-line re-open cases.
  • Recursive watcher: Replaces the previous flat watcher with RecursivePathWatcher (capped at 8 FSEvents exclusions); excludes .build, .git, node_modules, and similar high-churn trees to keep the main watcher cheap.
  • Git-state / git-creation watchers: A separate RecursivePathWatcher on .git (excluding objects/logs) refreshes status badges after metadata-only commits without triggering a full tree reload; a short-lived bootstrap watcher detects git init after a folder is already open and tears itself down once the permanent git-state watcher is installed.

Confidence Score: 4/5

The navigation logic, watcher coalescing, and generation-guard are well-implemented, but a named hasPendingTextNavigationForTesting accessor with no production caller was added to the production FilePreviewPanel source — the canonical fix is to widen the backing pendingTextNavigation property to internal and read it from the test target directly.

The hasPendingTextNavigationForTesting accessor in Sources/Panels/FilePreviewPanel.swift is a named test-only seam (ForTesting suffix, no production caller, doc comment says 'Exposed for tests') that lives in a production Sources/ path. The project rule requires using @testable import on the widened backing property instead. This is the only structural issue; the watcher upgrade, navigation state machine, and git-status coalescing are all correct.

Sources/Panels/FilePreviewPanel.swift — the hasPendingTextNavigationForTesting accessor should be removed and the backing pendingTextNavigation property widened to internal instead.

Important Files Changed

Filename Overview
Sources/Panels/FilePreviewPanel.swift Adds pending-navigation state machine (line/column jump, dirty-buffer and unavailable-file clearing) and the textLocation UTF-8/grapheme-cluster helper. Includes a named hasPendingTextNavigationForTesting accessor that violates the no-test-seam-in-production-source rule.
Sources/FileExplorerStore.swift Upgrades from a flat FileWatcher to a RecursivePathWatcher, adds a dedicated git-state watcher and a bootstrap git-creation watcher, and coalesces concurrent git status runs with a generation-guard. gitStateWatchTask is not cancelled in deinit, creating a minor asymmetry. Several properties widened to internal for test access via @testable import (canonical approach).
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/FileWatch/FileSystemEventStream.swift Adds excludedPaths support with a shared maximumExclusionPathCount = 8 constant mirroring FSEvents' hard cap; streams are torn down cleanly if FSEventStreamSetExclusionPaths fails.
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/FileWatch/RecursivePathWatcher.swift Exposes excludedPaths as a nonisolated let (correctly initialized in both production and test-stub init paths) and threads it through to FileSystemEventStream.
Sources/Panels/FilePreviewWorkspaceOpenSupport.swift Adds openFileSurfacesNavigatingTextPosition, a thin wrapper that calls openFileSurfaces then navigateToTextPosition on any resulting FilePreviewPanel; correctly no-ops for Markdown and Xcode project surfaces.
Sources/FileExplorerKeyboardShortcuts.swift Introduces FileExplorerPreviewOpenHandler typealias carrying (path, lineNumber, columnNumber) and threads it through performFileExplorerFileOpen; normal tree opens correctly pass nil line/column.
Sources/ContentView.swift Updates openFilePreviewFromSidebar to accept and forward lineNumber/columnNumber, routing to openFileSurfacesNavigatingTextPosition.
Sources/Panels/FilePreviewTextEditor.swift Calls applyPendingTextNavigationIfReady() from attachTextView so a jump queued before the text view is ready fires once the view attaches.
cmuxTests/FileExplorerStoreTests.swift Adds tests for recursive file-tree refresh, git metadata directory resolution (including FIFO rejection with a race-vs-timeout harness), creation-watcher exclusion invariants, and the git-state watcher install triggering an eager status refresh.
cmuxTests/FilePreviewTextEditorTextKitTests.swift Adds navigation tests covering loaded-line selection, CRLF line-ending counting, UTF-8 byte column offsets (emoji regression), markdown/Xcode routing preservation, unavailable-dirty clear, and dirty-buffer content-differ clear.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Search result selected] --> B[openSelectedSearchResult]
    B --> C{Local provider?}
    C -->|Yes| D[performFileExplorerFileOpen\npath + lineNumber + columnNumber]
    C -->|No| E[onOpenFilePreview\npath + lineNumber + columnNumber]
    D --> E
    E --> F[openFilePreviewFromSidebar\nor RightSidebarToolPanel.openFilePreview]
    F --> G{SSH provider?}
    G -->|Yes| H[materializeRemoteFileForPreview\ndownload to local URL]
    G -->|No| I[openFileSurfacesNavigatingTextPosition]
    H --> I
    I --> J[openFileSurfaces\nroutes to correct panel type]
    J --> K{Panel type?}
    K -->|FilePreviewPanel| L[navigateToTextPosition\nlineNumber + columnNumber]
    K -->|MarkdownPanel| M[no-op]
    K -->|ProjectPanel| N[no-op]
    L --> O{Text already loaded?}
    O -->|Yes + textView attached| P[applyPendingTextNavigationIfReady\nscroll + select]
    O -->|No| Q[store as pendingTextNavigation]
    Q --> R{File loads} --> P
    Q --> S{textView attaches} --> P
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Search result selected] --> B[openSelectedSearchResult]
    B --> C{Local provider?}
    C -->|Yes| D[performFileExplorerFileOpen\npath + lineNumber + columnNumber]
    C -->|No| E[onOpenFilePreview\npath + lineNumber + columnNumber]
    D --> E
    E --> F[openFilePreviewFromSidebar\nor RightSidebarToolPanel.openFilePreview]
    F --> G{SSH provider?}
    G -->|Yes| H[materializeRemoteFileForPreview\ndownload to local URL]
    G -->|No| I[openFileSurfacesNavigatingTextPosition]
    H --> I
    I --> J[openFileSurfaces\nroutes to correct panel type]
    J --> K{Panel type?}
    K -->|FilePreviewPanel| L[navigateToTextPosition\nlineNumber + columnNumber]
    K -->|MarkdownPanel| M[no-op]
    K -->|ProjectPanel| N[no-op]
    L --> O{Text already loaded?}
    O -->|Yes + textView attached| P[applyPendingTextNavigationIfReady\nscroll + select]
    O -->|No| Q[store as pendingTextNavigation]
    Q --> R{File loads} --> P
    Q --> S{textView attaches} --> P
Loading

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

Comment thread Sources/Panels/FilePreviewPanel.swift Outdated
Comment thread Sources/Panels/FilePreviewPanel.swift
Comment thread Sources/ContentView.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.

Caution

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

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

892-910: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Recursive root watching can turn subtree churn into repeated explorer reloads. RecursivePathWatcher(paths: [rootPath]) here listens to every descendant change, and this path doesn't filter ignored/high-churn directories. A busy tree can keep retriggering reload() and kicking off new git-status fetches every coalesced event, repeatedly tearing down and rebuilding the explorer on the main actor. Scope the watch or drop events from .git, node_modules, and build-output subtrees before reloading.

🤖 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 892 - 910, The recursive
watcher setup in updateDirectoryWatcher() is reacting to every descendant change
under rootPath, which can cause repeated reload() and refreshGitStatus() churn
on busy trees. Update the FileExplorerStore watcher logic to scope
RecursivePathWatcher or filter its events so high-churn subtrees like .git,
node_modules, and build-output directories are ignored before triggering
reloads. Keep the change localized to updateDirectoryWatcher() and the watcher
event loop so the explorer only refreshes for relevant file changes.

Source: Path instructions

Sources/ContentView.swift (1)

2305-2346: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Duplicated preview-open + navigate logic across two surfaces.

openFilePreviewFromSidebar here is nearly identical to RightSidebarToolPanel.openFilePreview(_:lineNumber:columnNumber:) (remote materialize → openFilePreviewSurfaces → navigateToTextPosition; local lineNumber != nil branch vs openFileSurfaces). Two copies of this branching will drift over time. Consider extracting a shared Workspace helper (e.g. openFilePreviewSurfacesNavigating(inPane:filePaths:lineNumber:columnNumber:remoteMaterialize:)) so both entry points route through one path, per the repo's shared-behavior policy for multi-entrypoint actions.

🤖 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/ContentView.swift` around lines 2305 - 2346,
openFilePreviewFromSidebar duplicates the same preview-open-and-navigate flow
that RightSidebarToolPanel.openFilePreview(_:lineNumber:columnNumber:) already
uses, so the two entry points can drift. Extract the shared branching into a
Workspace helper that handles remote materialization, openFilePreviewSurfaces vs
openFileSurfaces, and navigateToTextPosition, then have both
openFilePreviewFromSidebar and the sidebar tool panel call that single path.
🤖 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/ContentView.swift`:
- Around line 2305-2346: openFilePreviewFromSidebar duplicates the same
preview-open-and-navigate flow that
RightSidebarToolPanel.openFilePreview(_:lineNumber:columnNumber:) already uses,
so the two entry points can drift. Extract the shared branching into a Workspace
helper that handles remote materialization, openFilePreviewSurfaces vs
openFileSurfaces, and navigateToTextPosition, then have both
openFilePreviewFromSidebar and the sidebar tool panel call that single path.

In `@Sources/FileExplorerStore.swift`:
- Around line 892-910: The recursive watcher setup in updateDirectoryWatcher()
is reacting to every descendant change under rootPath, which can cause repeated
reload() and refreshGitStatus() churn on busy trees. Update the
FileExplorerStore watcher logic to scope RecursivePathWatcher or filter its
events so high-churn subtrees like .git, node_modules, and build-output
directories are ignored before triggering reloads. Keep the change localized to
updateDirectoryWatcher() and the watcher event loop so the explorer only
refreshes for relevant file changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1355b07c-a807-4c13-a848-6acf03d6d79e

📥 Commits

Reviewing files that changed from the base of the PR and between 58830db and a5a0b85.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (13)
  • Sources/ContentView.swift
  • Sources/FileExplorerKeyboardShortcuts.swift
  • Sources/FileExplorerStore.swift
  • Sources/FileExplorerView.swift
  • Sources/Panels/FilePreviewPanel.swift
  • Sources/RightSidebarPanelView.swift
  • Sources/RightSidebarToolPanel.swift
  • cmuxTests/FileExplorerShortcutSettingsTests.swift
  • cmuxTests/FileExplorerStoreTests.swift
  • cmuxTests/FilePreviewTextEditorTextKitTests.swift
  • cmuxTests/FileSearchRipgrepParserTests.swift
  • cmuxTests/HiddenRightSidebarContentMountingTests.swift
  • cmuxTests/WindowAndDragTests.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
cmuxTests/FilePreviewTextEditorTextKitTests.swift (1)

178-185: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Attach the editor before exercising dirty pending navigation.

Both dirty-buffer regressions attach SavingTextView after the reload, so a bug that moves an already-open dirty editor during navigateToTextPosition / loadTextContent(replacingDirtyContent: false) would still pass.

Suggested test shape
         await panel.loadTextContent().value
         panel.updateTextContent("dirty needle\n")
         `#expect`(panel.isDirty)
 
+        let textView = SavingTextView.makeFilePreviewTextView()
+        textView.string = panel.textContent
+        textView.setSelectedRange(NSRange(location: 0, length: 0))
+        panel.attachTextView(textView)
+
         panel.navigateToTextPosition(lineNumber: 1, columnNumber: 7)
         loader.result = FilePreviewTextLoader.Result.unavailable
         await panel.loadTextContent(replacingDirtyContent: false).value
 
-        let textView = SavingTextView.makeFilePreviewTextView()
-        textView.string = panel.textContent
-        textView.setSelectedRange(NSRange(location: 0, length: 0))
-        panel.attachTextView(textView)
-
         `#expect`(textView.selectedRange().location == 0)

Apply the same setup-before-navigation pattern to the loaded-dirty-buffer test.

Also applies to: 211-217

🤖 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 `@cmuxTests/FilePreviewTextEditorTextKitTests.swift` around lines 178 - 185,
The dirty-buffer tests currently attach SavingTextView after navigation and
reload, which can hide regressions in already-open editors. Update the
loaded-dirty-buffer test setup to attach the text view before calling
panel.navigateToTextPosition and panel.loadTextContent(replacingDirtyContent:
false), using the same pattern in FilePreviewTextEditorTextKitTests and the
related dirty-buffer test block, so movement of an existing editor is actually
exercised.
🤖 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
`@Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/FileWatch/FileSystemEventStream.swift`:
- Around line 95-97: The exclusion handling in FileSystemEventStream currently
ignores the result of FSEventStreamSetExclusionPaths, so oversized ignore lists
can silently fail. Update the logic around the stream setup to check the boolean
return value, and adjust the excludedPaths strategy in FileSystemEventStream so
it stays within the FSEventStream limit by trimming or splitting the paths
before calling FSEventStreamSetExclusionPaths.

---

Outside diff comments:
In `@cmuxTests/FilePreviewTextEditorTextKitTests.swift`:
- Around line 178-185: The dirty-buffer tests currently attach SavingTextView
after navigation and reload, which can hide regressions in already-open editors.
Update the loaded-dirty-buffer test setup to attach the text view before calling
panel.navigateToTextPosition and panel.loadTextContent(replacingDirtyContent:
false), using the same pattern in FilePreviewTextEditorTextKitTests and the
related dirty-buffer test block, so movement of an existing editor is actually
exercised.
🪄 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: b25b6e7b-a718-4b1d-89a5-b3ad513cdf36

📥 Commits

Reviewing files that changed from the base of the PR and between a5a0b85 and 3c0b30e.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (7)
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/FileWatch/FileSystemEventStream.swift
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/FileWatch/RecursivePathWatcher.swift
  • Sources/ContentView.swift
  • Sources/FileExplorerStore.swift
  • Sources/Panels/FilePreviewWorkspaceOpenSupport.swift
  • Sources/RightSidebarToolPanel.swift
  • cmuxTests/FilePreviewTextEditorTextKitTests.swift

austinywang and others added 3 commits June 29, 2026 16:33
…ve-an-ide-level-file-editor-that-is

# Conflicts:
#	.github/swift-file-length-budget.tsv
The recursive directory watcher delivers a coalesced event roughly once
per throttle window, but a sustained filesystem storm (build output, log
writes, nested high-churn dirs not covered by the top-level-only FSEvents
exclusion list) still drove one `refreshGitStatus()` per window. Each
fetch spawned an uncancelled `git status` process, so on a large repo
where the fetch outlasts the window those processes piled up unbounded.

Guard `refreshGitStatus()` so at most one `git status` runs at a time and
coalesce concurrent requests into a single trailing re-run. `reload()`
already supersedes its own prior load via `cancelAllLoads()`, so the
watcher loop now has bounded backpressure on both work items.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The main recursive watcher excludes .git (and build/dependency dirs) to
avoid reload churn, but that also suppressed the events behind metadata-
only git operations — commit, add, reset — which change `git status`
output without touching the working tree. Badges could stay stale until
an unrelated working-tree event happened.

Add a second, low-churn watcher over `.git` (excluding the high-churn
`objects`/`logs` subtrees) that triggers only `refreshGitStatus()`, never
a tree `reload()`, since `.git` writes don't change the visible file
tree. No-op when `.git` is absent or a worktree/submodule link file.

Refresh the Swift file length budget for the added watcher.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/FileExplorerStore.swift`:
- Around line 973-978: The git watcher in startGitStateWatcher(under:) only
handles .git as a directory, so it misses worktrees and submodules where .git is
a file. Update the .git detection logic to read and resolve a gitdir: pointer
when .git is a file, then use that target as the authoritative metadata
directory to watch instead of returning early. Keep the existing
FileManager-based existence check, but extend it so the watcher covers both
direct .git directories and external gitdir locations.
- Around line 916-918: Guard the git-status apply path against stale workspace
data: `applyGitStatusResult(_:)` currently updates `gitStatusByPath` without
verifying the result was fetched for the current `rootPath`, so a completion
from an old fetch can overwrite the new repository state. Capture the active
`path` when dispatching the git-status request from `FileExplorerStore`, thread
it through to `applyGitStatusResult(_:)`, and only apply the status when that
path still matches `rootPath`; otherwise discard the stale result or trigger a
fresh refresh immediately. Use the `rootPath`, `applyGitStatusResult(_:)`, and
the fetch/refresh entrypoint in `FileExplorerStore` to locate the change.
🪄 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: c9f77e86-a2fd-474c-80ab-361bb84cab65

📥 Commits

Reviewing files that changed from the base of the PR and between f2323e5 and 40a7b28.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (5)
  • Sources/ContentView.swift
  • Sources/FileExplorerStore.swift
  • Sources/RightSidebarPanelView.swift
  • cmuxTests/HiddenRightSidebarContentMountingTests.swift
  • cmuxTests/WindowAndDragTests.swift
💤 Files with no reviewable changes (3)
  • cmuxTests/WindowAndDragTests.swift
  • cmuxTests/HiddenRightSidebarContentMountingTests.swift
  • Sources/RightSidebarPanelView.swift

Comment thread Sources/FileExplorerStore.swift Outdated
Comment thread Sources/FileExplorerStore.swift Outdated
austinywang and others added 3 commits June 29, 2026 17:31
The in-flight coalescing gate was store-global, so on a workspace switch
A -> B while A's `git status` was slow or hung: B's refresh only set the
trailing flag (blocked until A returned), and A's stale result was then
applied to the now-current B before the trailing refresh ran.

Key each fetch to a context generation that bumps on every root/provider
change. `invalidateGitStatusRefresh()` (called from setRootPath and
setProvider) releases the in-flight gate so the new context's fetch starts
immediately, and a completion whose generation no longer matches is
dropped — so a slow/hung fetch for a previous workspace can neither block
nor overwrite the active one. Same-root watcher churn stays coalesced to a
single in-flight process plus one trailing refresh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
startGitStateWatcher only handled `.git` as a directory, so in Git
worktrees and submodules — where `.git` is a file containing a
`gitdir:` pointer to an external metadata directory — metadata-only
operations (commit, add, reset) updated that external gitdir and the
explorer's status badges never refreshed.

Resolve the `gitdir:` pointer (absolute or relative to the root) to the
authoritative metadata directory and watch that, so worktrees and
submodules get the same badge-refresh behavior as plain checkouts. Add a
deterministic unit test for the resolver covering plain `.git` dirs,
absolute and relative gitdir pointers, missing repos, and dangling
pointers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A `.git` file's `gitdir:` pointer (and a `.git` symlink) is controlled by
repository contents, so a checked-out workspace could aim it at `/`, a
home directory, or another large/sensitive tree and make cmux watch that
recursively and re-run `git status` on unrelated churn — a trust-boundary
issue.

Before watching, require the resolved candidate to have the Git metadata
shape: a `HEAD` file plus either `config` (main checkout or submodule) or
`commondir` (linked worktree). This also rejects a `.git` symlink that
resolves to a non-Git directory. Extend the resolver test with bare-`.git`
and arbitrary-target rejection cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
austinywang and others added 2 commits June 29, 2026 17:55
gitMetadataDirectory read a non-directory `.git` entry with
String(contentsOfFile:), an unbounded synchronous read on the main actor
during watcher setup. A malformed or malicious workspace with a huge
`.git` regular file could hang the UI or exhaust memory just by being
opened in the explorer.

Read at most 64 KiB via a bounded FileHandle read — a real `gitdir:`
pointer is a single short line — and bail if the pointer isn't found in
that prefix. Add a regression test that a valid pointer buried past the
cap is not parsed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The main tree watcher excludes `.git`, so when a non-git folder becomes a
repository while open (git init, or adding a worktree/submodule) the
`.git` creation isn't observed and no git-state watcher was installed —
badges stayed empty until a root reset.

Re-check on the next working-tree event: once `.git` resolves, install the
git-state watcher so metadata changes refresh badges from then on. Cheap
(a single stat per coalesced event, only while no repo is present) and
self-disabling once installed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
austinywang and others added 2 commits July 1, 2026 18:46
…ve-an-ide-level-file-editor-that-is

# Conflicts:
#	.github/swift-file-length-budget.tsv
FileExplorerPreviewOpenHandler took three positional arguments
(path, lineNumber, columnNumber). Every site that merely constructs the
handler — including several pre-existing XCTest suites unrelated to this
feature — had to change its closure arity, and the two Int? line/column
arguments were positionally swappable.

Collapse the handler to a single labeled-tuple parameter
(path:lineNumber:columnNumber:). Handler-constructing closures that
ignore the value revert to `{ _ in }`, matching origin/main, so the
unrelated XCTest files (FileSearchRipgrepParserTests, WindowAndDragTests)
are no longer touched by this PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@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: 3

Caution

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

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

2331-2357: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate remote/local navigate-open branching logic vs. RightSidebarToolPanel.openFilePreview.

The remote-materialize-then-navigate / local-navigate branching here is essentially identical to RightSidebarToolPanel.openFilePreview (Sources/RightSidebarToolPanel.swift lines 93-124). Both now call openFileSurfacesNavigatingTextPosition with the same shape of arguments. Since this PR touches both call sites together, it's a good opportunity to extract one shared helper (e.g. a Workspace method taking paneId, filePath, lineNumber, columnNumber, and a materializer closure/store) so future navigation changes (e.g. editor support per the linked issue) don't need to be kept in sync across two files.

Based on learnings, the repo's shared-behavior policy states: "When a behavior is exposed through multiple entrypoints ... implement one shared action/model path ... Do not patch one surface while leaving the others with duplicated logic."

🤖 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/ContentView.swift` around lines 2331 - 2357, The remote-vs-local open
logic in the ContentView navigation path is duplicated with
RightSidebarToolPanel.openFilePreview, so extract a shared helper instead of
keeping two separate branches in sync. Move the materialize-then-open behavior
into one reusable Workspace-level method or shared utility that accepts paneId,
filePath, lineNumber, columnNumber, and the remote-materialization step, then
have both ContentView and RightSidebarToolPanel call that single path. Keep the
existing openFileSurfacesNavigatingTextPosition call as the common final step so
future navigation changes only need to be made in one place.

Source: Coding guidelines

🤖 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/FileExplorerKeyboardShortcuts.swift`:
- Line 5: Replace the labeled tuple payload used by
FileExplorerPreviewOpenHandler with a small dedicated request type, such as
FileExplorerPreviewOpenRequest, so the preview-open API is easier to extend.
Update the handler signature and all call sites that pass or consume the
preview-open payload to use the new struct. Make the new request type conform to
Equatable and Sendable to match the intended navigation/editor support growth.

In `@Sources/FileExplorerStore.swift`:
- Line 981: The git watcher setup in FileExplorerStore should be triggered from
a reliable `.git` creation signal rather than waiting for a working-tree change
that may never occur after `git init`. Update the logic around
installGitStateWatcherIfNeeded and the surrounding git-state watcher
registration so the parent/root observes the `.git` entry until it exists, then
hand off to the metadata watcher; avoid filtering out a missing `.git` path
before it can be watched. This keeps status refreshes from staying stale when
the first post-init operation is metadata-only.
- Around line 1035-1042: The `readGitPointerFile(at:)` path can still block
because it opens whatever is passed without verifying it is a regular file
first. Update the caller that decides whether to read a `.git` pointer file to
reject non-regular files before invoking `readGitPointerFile(at:)`, using
file-type checks on the path and only proceeding for regular files. Keep the fix
scoped around the `readGitPointerFile(at:)` flow and any nearby `.git` pointer
detection logic.

---

Outside diff comments:
In `@Sources/ContentView.swift`:
- Around line 2331-2357: The remote-vs-local open logic in the ContentView
navigation path is duplicated with RightSidebarToolPanel.openFilePreview, so
extract a shared helper instead of keeping two separate branches in sync. Move
the materialize-then-open behavior into one reusable Workspace-level method or
shared utility that accepts paneId, filePath, lineNumber, columnNumber, and the
remote-materialization step, then have both ContentView and
RightSidebarToolPanel call that single path. Keep the existing
openFileSurfacesNavigatingTextPosition call as the common final step so future
navigation changes only need to be made in one place.
🪄 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: ce8302d4-e6f3-4dd8-a79f-3602ff786c04

📥 Commits

Reviewing files that changed from the base of the PR and between 40a7b28 and 666882c.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (6)
  • Sources/ContentView.swift
  • Sources/FileExplorerKeyboardShortcuts.swift
  • Sources/FileExplorerStore.swift
  • Sources/FileExplorerView.swift
  • Sources/RightSidebarToolPanel.swift
  • cmuxTests/FileExplorerStoreTests.swift
💤 Files with no reviewable changes (1)
  • cmuxTests/FileExplorerStoreTests.swift

Comment thread Sources/FileExplorerKeyboardShortcuts.swift
Comment thread Sources/FileExplorerStore.swift
Comment thread Sources/FileExplorerStore.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.

5 issues found across 14 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/FileExplorerStore.swift
Comment thread Sources/Panels/FilePreviewWorkspaceOpenSupport.swift
Comment thread Sources/FileExplorerStore.swift
Address review findings on the file-explorer git-watcher and file-preview
navigation changes:

- Reject non-regular `.git` entries before opening. A repository-controlled
  `.git` can be a FIFO/socket/device; `FileHandle(forReadingFrom:)` blocks on
  open() for a FIFO reader (waits for a writer), hanging watcher setup on the
  main actor before the byte cap applies. Gate on `stat`+S_IFREG, which follows
  symlinks (matching the existing `fileExists` probe) and never blocks. Add a
  timeout-raced regression test that fails cleanly rather than wedging CI.

- Single-source the FSEvents exclusion-path cap: RecursivePathWatcher now
  references FileSystemEventStream.maximumExclusionPathCount instead of a
  duplicate constant, so the two truncation sites can never drift.

- Document that CRLF (`\r\n`) is one Swift `Character` for which `isNewline` is
  true once, so the file-preview line counter advances once per CRLF. Add a
  pure-function regression test covering CRLF and LF offsets.

- Document that text-position navigation applies only to plain-text previews;
  Markdown/Xcode-project surfaces intentionally ignore it.

Refresh the Swift file-length budget for the documented files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
austinywang and others added 2 commits July 2, 2026 02:06
…ve-an-ide-level-file-editor-that-is

# Conflicts:
#	.github/swift-file-length-budget.tsv
The main recursive tree watcher excludes `.git`, so `git init` in a folder
opened before it was a repository writes only under the excluded `.git` and
produces no main-watcher event. The git-state watcher's only recovery path,
`installGitStateWatcherIfNeeded()`, runs solely from that main-watcher loop,
so status badges stayed empty until an unrelated working-tree change fired.

Add a short-lived bootstrap watcher on the same root that does NOT exclude
`.git`, so `.git`'s creation is observed as an ordinary descendant event. Its
handler installs the git-state watcher and then tears the bootstrap watcher
down, so it never observes steady-state `.git` churn. It keeps every other
high-churn exclusion (node_modules, build, etc.) so it stays cheap while it
waits on a folder that may never become a repository.

The regression test is deterministic and pure — it asserts the bootstrap
watcher's exclusions equal the main watcher's minus `.git` (a regression to
reusing the main exclusions would silently reinstate the stale-badge bug).
A behavioral test would require live FSEvents timing; the pure-function guard
covers the load-bearing mechanism and cannot compile without the fix, so test
and fix land together.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@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 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/FileExplorerStore.swift
austinywang and others added 6 commits July 2, 2026 02:32
`navigateToTextPosition` queues a `pendingTextNavigation` jump that is applied
once the text content finishes loading. A nil line number returned early
without touching that queued state, so this sequence left a stale jump armed:
a search-result open queues a jump for line N while the text is still loading,
then the same file is reopened normally (no line target, `reuseExisting: true`)
before the load completes. The nil-line open did not cancel the older jump, so
when the load finished the reused panel scrolled to the stale search location
instead of staying put.

Treat a nil line as an explicit no-navigation request and clear
`pendingTextNavigation` before returning. Both sidebar open paths
(`openFilePreviewFromSidebar`, `RightSidebarToolPanel.openFilePreview`) take an
optional line number and reuse the existing preview, so the nil path is
reachable in normal use.

Adds a deterministic regression test: with no text view attached the queued
jump cannot be consumed, so the test asserts a line-carrying open sets the
pending jump and a subsequent nil-line open clears it, without depending on
async load timing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The git-state watcher started by `startGitStateWatcher` only calls
`refreshGitStatus()` on *subsequent* `.git` events. For a repository that
appears after the folder is opened (`git init`), the bootstrap creation watcher
installs the git-state watcher only after `.git` already exists — its initial
metadata writes are finished, and no further `.git` event is guaranteed. Status
badges therefore stayed empty until some unrelated working-tree or metadata
change happened to fire the watcher.

Trigger an eager `refreshGitStatus()` the moment `installGitStateWatcherIfNeeded()`
successfully installs the watcher, mirroring the eager refresh the already-a-repo
path gets from `setRootPath`. Both watcher entrypoints (main tree watcher and
bootstrap creation watcher) share this path; `refreshGitStatus()` coalesces, so
the main-watcher entrypoint's redundant call (it refreshes immediately before)
collapses into a single `git status` process.

Adds a deterministic regression test that drives the shared install path against
a freshly `git init`-ed fixture and asserts the install kicks off a status
refresh. `refreshGitStatus()` sets its in-flight flag synchronously before
dispatching, and the MainActor test never yields before asserting, so the check
is race-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The FIFO probe raced gitMetadataDirectory() against a timeout inside a
withTaskGroup. Structured concurrency will not let the group scope return
until every child finishes, and cancelAll() cannot interrupt a synchronous
open(), so if the blocking-open regression returned, the probe child would
wedge the group forever — the test would hang CI instead of reaching the
.timedOut assertion, leaving the main-actor-hang regression without a clean
red signal.

Run the probe on a detached OS thread raced against a timeout through a
single-resume checked continuation. On timeout the continuation settles and
the test records the failure while the wedged thread is simply abandoned
(parked in open(), leaked, harmless — the test has already failed and the
process is torn down). The passing path is unchanged: the regular-file guard
rejects the FIFO without opening, so the probe resumes .completed(nil) at once.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The FIFO-hang regression test drove FileExplorerStore through three
DEBUG-only accessors (simulateGitRepositoryAppearedForTesting,
isGitStatusRefreshInFlightForTesting, stopWatchersForTesting). Per the
Aziz test/debug-seam policy, test-only accessors do not belong in
production Sources/. Remove them and widen the five members the test
drives (directoryWatchPath, gitStateWatcher, isGitStatusRefreshInFlight,
installGitStateWatcherIfNeeded, stopDirectoryWatcher) from private to
internal, reaching them from the test via the existing @testable import —
the same resolution applied in #6452.

The test now points the store at the repo (rootPath/directoryWatchPath)
and calls installGitStateWatcherIfNeeded() directly, asserting the
git-state watcher installs and eagerly kicks off a status refresh.
Behavior is unchanged; the grandfathered setProviderForTesting stays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ve-an-ide-level-file-editor-that-is

# Conflicts:
#	.github/swift-file-length-budget.tsv
…ve-an-ide-level-file-editor-that-is

# Conflicts:
#	.github/swift-file-length-budget.tsv
…ve-an-ide-level-file-editor-that-is

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	Sources/Panels/FilePreviewPanel.swift
#	cmuxTests/FilePreviewTextEditorTextKitTests.swift
…ve-an-ide-level-file-editor-that-is

Resolve the .github/swift-file-length-budget.tsv conflict by regenerating it with scripts/swift_file_length_budget.py --write-budget on the merged tree (branch grew FileExplorerStore.swift, main grew the iOS TerminalInputTextView.swift).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@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 — 88a32b99 Deployed Jul 5, 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.

Hope to have an IDE level file editor that is more user-friendly

3 participants