Skip to content

Add file explorer sidebar entrypoint - #3976

Open
austinywang wants to merge 12 commits into
mainfrom
issue-888-file-explorer
Open

austinywang wants to merge 12 commits into
mainfrom
issue-888-file-explorer

Conversation

@austinywang

@austinywang austinywang commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a persistent folder button in the left sidebar footer that opens the existing right-sidebar Files tree
  • route the button through the shared right-sidebar focus path so Files, shortcuts, and command palette stay on one model/action path

Closes #888

Testing

  • Not run locally per repo policy; CI will cover build/tests.

Note

Low Risk
UI and focus-routing changes around the file explorer; no auth, data, or security-sensitive paths.

Overview
Adds a folder control in the left sidebar footer that opens the right sidebar in Files mode with focus, using the same shortcut labels/tooltips as keyboard settings and showing selected state when Files is visible.

Introduces AppDelegate.showRightSidebarModeInActiveMainWindow as the shared path: prefer the active main-window context (focus window, then focusRightSidebar), otherwise update global fileExplorerState; it does not fall back to global state when a registered window exists but sidebar state or focus fails. The command palette’s right-sidebar mode actions now call this API and beep on failure instead of locally toggling visibility.

Tests cover global-state fallback with no window context and the case where a registered context has no sidebar state—global mode/visibility must stay unchanged.

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


Summary by cubic

Adds a persistent folder button in the left sidebar footer that opens and focuses the Files tree in the right sidebar. Introduces a shared entrypoint to show and focus right-sidebar modes, used by both the button and the command palette. Closes #888.

  • New Features

    • Adds SidebarFileExplorerButton that opens Files, focuses the first item, reflects selected state, updates tooltip/shortcut text when settings change, sets accessibility label/identifier, and beeps on failure.
  • Refactors

    • Adds AppDelegate.showRightSidebarModeInActiveMainWindow to validate availability, pick the active window, set visibility/mode, and focus; falls back to global state when no main-window context exists, and returns failure without mutating global state if a registered context lacks sidebar state or focus fails. Command palette now uses this entrypoint and beeps on failure; tests cover the no-context fallback and the failure-without-mutation path.

Written for commit 30b8234. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Added a "files" button to the sidebar footer for quick access to the right-side file explorer; shows selected state and keeps its keyboard-shortcut tooltip in sync.
    • Button focuses the file explorer in the appropriate window and prioritizes the first item when opening.
  • Bug Fixes / UX

    • Plays an audible alert if opening or focusing the file explorer fails.
  • Tests

    • Added tests for behavior when no main-window context exists and when focus attempts fail; includes a helper to preserve right-sidebar defaults during tests.

Review Change Stack

@vercel

vercel Bot commented May 12, 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 24, 2026 4:27pm
cmux-staging Building Building Preview, Comment May 24, 2026 4:27pm

@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an AppDelegate API to show/focus the right sidebar in a mode, routes command-palette requests to it, adds a sidebar footer "files" button that invokes it (beeps on failure), and covers fallback behavior with unit tests that preserve user defaults.

Changes

Files focus and command routing

Layer / File(s) Summary
AppDelegate: showRightSidebarModeInActiveMainWindow
Sources/AppDelegate.swift
Adds showRightSidebarModeInActiveMainWindow(mode:focusFirstItem:preferredWindow:) to validate mode, focus the main window for right-sidebar commands, delegate to the keyboard focus coordinator, or fall back to updating FileExplorerState and making the sidebar visible.
Command-palette delegation
Sources/ContentView+RightSidebarCommandPalette.swift
handleCommandPaletteRightSidebarMode now delegates to AppDelegate.shared?.showRightSidebarModeInActiveMainWindow(...) == true and beeps+returns on failure.
Footer files button and handler
Sources/ContentView.swift
Adds SidebarFileExplorerButton, integrates it into SidebarFooterButtons with selection state and localized help text, and implements showFileExplorer() which requests .files mode via AppDelegate and beeps on failure.
Tests: defaults wrapper and behavior tests
cmuxTests/RightSidebarCommandPaletteTests.swift
Adds two @MainActor tests for showRightSidebarModeInActiveMainWindow(...) (fallback to global FileExplorerState and non-mutation when registered focus fails) and a withSavedRightSidebarDefaults helper to snapshot/restore fileExplorer.isVisible and rightSidebar.mode.

Sequence Diagram(s)

sequenceDiagram
  participant SidebarFileExplorerButton
  participant ContentView
  participant AppDelegate
  participant KeyboardFocusCoordinator
  participant FileExplorerState
  participant NSSound

  SidebarFileExplorerButton->>ContentView: tap -> showFileExplorer()
  ContentView->>AppDelegate: showRightSidebarModeInActiveMainWindow(.files, focusFirstItem:true, preferredWindow)
  AppDelegate->>KeyboardFocusCoordinator: focusRightSidebar(mode:.files, focusFirstItem:true)
  alt focus succeeded
    KeyboardFocusCoordinator-->>AppDelegate: true
  else focus failed or no context
    AppDelegate->>FileExplorerState: set mode = .files
    AppDelegate->>FileExplorerState: setVisible(true)
  end
  alt overall failure
    AppDelegate-->>NSSound: NSSound.beep()
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A folder-button hops to the bar,
It nudges the right pane to show each file,
If focus hides, a tiny beep says "no",
The tree unfolds, or stays awhile,
The rabbit smiles and twitches its nose.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (2 errors, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Full Internationalization ❌ Error New string shortcut.switchRightSidebarToFiles.label has only 3 locale translations (en, ja, ko) but catalog supports 20 locales. Add complete translations for all 20 supported locales to shortcut.switchRightSidebarToFiles.label in Resources/Localizable.xcstrings.
Cmux Architecture Rethink ❌ Error showRightSidebarModeInActiveMainWindow splits UI lifecycle ownership: context path only focuses; no-context path mutates visibility/mode. Same action, different owners depending on path execution. Consolidate ownership: always mutate visibility/mode consistently in both paths, or delegate all mutations to context's fileExplorerState as single owner.
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.
Cmux Swift Actor Isolation ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (13 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: adding a file explorer sidebar entrypoint as a folder button in the left sidebar footer.
Linked Issues check ✅ Passed The PR implements all requirements from issue #888: adds visible UI entrypoint (SidebarFileExplorerButton), enables programmatic showing/focusing via AppDelegate method, and integrates with command palette for keyboard access.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #888: new button view, shared AppDelegate entrypoint, command palette integration, and tests validating the new behavior; no unrelated modifications detected.
Cmux Swift Blocking Runtime ✅ Passed No blocking synchronization patterns detected in the 159 lines of new production code. All code uses async-safe UI methods and state mutations, avoiding sleep, semaphores, locks, and polling.
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift files. This check applies to TypeScript, JavaScript, shell, and non-Swift build/runtime scripts; Swift code is covered by swift-blocking-runtime.md.
Cmux Swift Concurrency ✅ Passed PR introduces no legacy async patterns. New code is synchronous only: no DispatchQueue/background queues, no fire-and-forget Tasks, no new Combine state. Pre-existing observer used.
Cmux Swift @Concurrent ✅ Passed All Swift changes are synchronous, UI-bound code with proper @MainActor isolation. No nonisolated async work, invalid @concurrent annotations, or heavy operations without boundaries.
Cmux Swift File And Package Boundaries ✅ Passed PR adds 83 lines total to existing oversized files (48 to ContentView.swift, 35 to AppDelegate.swift), well under the 250-line threshold. Additions are UI view and AppKit window glue code.
Cmux Swift Logging ✅ Passed New code adds no print, debugPrint, dump, NSLog, or unauthorized logging. All new production code complies with logging rules.
Cmux User-Facing Error Privacy ✅ Passed PR contains only safe generic product terms ("Show Sidebar Files") and implements silent failure with system beep—no user-facing errors or sensitive implementation details exposed.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state layout violations. New button receives value snapshots, not store refs. Existing legacy state used incidentally. No Layout/List/mutation issues.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds no new NSWindow, NSPanel, NSWindowController, or SwiftUI Window/WindowGroup. Changes are a SwiftUI View button, window management methods, and tests—within allowed exceptions.
Description check ✅ Passed The PR description covers the main changes (folder button, shared entrypoint) and testing approach, but lacks a demo video and incomplete checklist.
✨ 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-888-file-explorer

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.

Comment thread Sources/ContentView.swift Outdated
@greptile-apps

greptile-apps Bot commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a persistent folder button to the left sidebar footer that opens the right-sidebar Files view, and introduces AppDelegate.showRightSidebarModeInActiveMainWindow as the single shared entrypoint for showing a right-sidebar mode from any surface.

  • New entrypoint: showRightSidebarModeInActiveMainWindow validates availability, guards nil fileExplorerState before activating the window, delegates to keyboardFocusCoordinator.focusRightSidebar, and falls back to global fileExplorerState when no registered main-window context exists.
  • Command palette refactor: handleCommandPaletteRightSidebarMode is simplified to use the new shared path, removing the previous dual-path state mutation that bypassed focus coordination.
  • New button: SidebarFileExplorerButton is a clean value-input leaf view; SidebarFooterButtons computes isSelected, title, and helpText and owns the showFileExplorer action that routes through the shared entrypoint and beeps on failure.

Confidence Score: 5/5

Safe to merge; the new shared entrypoint correctly orders guards before side-effects, and both callers handle the false return with a beep.

All previously flagged issues (window-focus ordering, dual-path state mutation, ObservableObject in leaf view) are addressed. The only remaining concern is a new @ObservedObject subscription added to an already-legacy parent struct, which has no impact on correctness or runtime behavior.

No files require special attention; Sources/ContentView.swift has a minor style note about @ObservedObject footprint in SidebarFooterButtons.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Adds showRightSidebarModeInActiveMainWindow: validates availability, guards nil fileExplorerState before window focus, delegates to focusRightSidebar, and falls back to global state when no registered context exists. Previous ordering issue (window focus before nil guard) is correctly fixed.
Sources/ContentView+RightSidebarCommandPalette.swift Simplifies handleCommandPaletteRightSidebarMode to use the new shared showRightSidebarModeInActiveMainWindow path; removes dual-path state mutation and beeps on failure.
Sources/ContentView.swift Adds SidebarFileExplorerButton (clean value-input leaf view) and showFileExplorer action. SidebarFooterButtons picks up a new @ObservedObject for KeyboardShortcutSettingsObserver.shared — extends legacy ObservableObject footprint (style concern).
cmuxTests/RightSidebarCommandPaletteTests.swift Adds two @mainactor tests covering the no-context fallback and the nil-fileExplorerState failure path; adds withSavedRightSidebarDefaults helper to isolate UserDefaults side-effects across test runs.

Sequence Diagram

sequenceDiagram
    participant Button as SidebarFileExplorerButton
    participant Footer as SidebarFooterButtons
    participant AD as AppDelegate
    participant Ctx as MainWindowContext
    participant GS as FileExplorerState (global)

    Button->>Footer: action()
    Footer->>AD: showRightSidebarModeInActiveMainWindow(mode:.files)
    AD->>AD: guard mode.isAvailable()
    AD->>AD: preferredRegisteredMainWindowContext()
    alt context found
        AD->>AD: "guard context.fileExplorerState != nil"
        AD->>Ctx: focusForInWindowCommand(window)
        AD->>Ctx: focusRightSidebar(mode:focusFirstItem:)
        Ctx-->>AD: Bool
        AD-->>Footer: Bool
    else no context
        AD->>GS: "state.mode = mode"
        AD->>GS: setVisible(true)
        AD-->>Footer: true
    end
    alt "result == false"
        Footer->>Footer: NSSound.beep()
    end
Loading

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

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

🤖 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/AppDelegate.swift`:
- Around line 5496-5509: The code currently sets state.mode and calls
state.setVisible(true) before calling
context.keyboardFocusCoordinator.focusRightSidebar(...), which ignores failure
and returns true even if focusRightSidebar fails; change this so that
focusRightSidebar is attempted first (or if UI mutation must occur first,
capture previous state and on focusRightSidebar returning false roll back to the
previous mode/visibility) and propagate the failure by returning false; use the
symbols state.mode, state.setVisible(true), and
context.keyboardFocusCoordinator.focusRightSidebar(mode:focusFirstItem:) to
locate and either reorder the calls (call focusRightSidebar before mutating
state) or implement a rollback that restores the prior state when
focusRightSidebar returns false, then only return true when focusRightSidebar
succeeded.
🪄 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: d24e56f5-3173-447d-9fab-717808dc64a3

📥 Commits

Reviewing files that changed from the base of the PR and between de461a6 and d76b7a0.

📒 Files selected for processing (4)
  • Sources/AppDelegate.swift
  • Sources/ContentView+RightSidebarCommandPalette.swift
  • Sources/ContentView.swift
  • cmuxTests/RightSidebarCommandPaletteTests.swift

Comment thread Sources/AppDelegate.swift
Comment thread Sources/AppDelegate.swift
Comment thread Sources/AppDelegate.swift Outdated
Comment on lines +5488 to +5494
if let window {
mainWindowVisibilityController.focusForInWindowCommand(window, reason: .rightSidebarFocus)
}

guard let state = context?.fileExplorerState ?? fileExplorerState else {
return false
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Window focus side-effect fires before state guard

mainWindowVisibilityController.focusForInWindowCommand is called unconditionally before the guard let state = context?.fileExplorerState ?? fileExplorerState check. When a registered context has a resolved window but a nil fileExplorerState (the exact scenario covered by testShowRightSidebarModeDoesNotMutateGlobalStateWhenRegisteredFocusFails), the window is brought into focus and then the function returns false — callers beep. The user sees a window activation with no sidebar change and hears the error sound. Moving the if let window block to after the state guard ensures the focus side-effect only happens when the sidebar will actually be shown.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3ca32d9 by moving validation ahead of the window focus side effect; the stale pre-fix path no longer applies.

— Claude Code

Comment thread Sources/AppDelegate.swift
Comment thread Sources/AppDelegate.swift

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3ca32d9. Configure here.

Comment thread Sources/AppDelegate.swift
return context.keyboardFocusCoordinator.focusRightSidebar(
mode: mode,
focusFirstItem: focusFirstItem
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sidebar state mutated before focus result causes false beep

Medium Severity

When showRightSidebarModeInActiveMainWindow delegates to context.keyboardFocusCoordinator.focusRightSidebar, the internal implementation of focusRightSidebar unconditionally calls state.setVisible(true) and sets state.mode before determining the focus result. If focus ultimately fails (endpoint and fallback both fail), focusRightSidebar returns false, which propagates back to callers in SidebarFooterButtons.showFileExplorer and handleCommandPaletteRightSidebarMode, causing NSSound.beep(). The user sees the sidebar open but also hears an error beep — contradicting the PR's goal of not partially applying sidebar state on failure.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3ca32d9. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not changed: this is not a real failure path in the current coordinator contract. Once context.fileExplorerState exists, focusRightSidebar sets the pending request and returns success even if the concrete endpoint is not mounted yet; the false-return paths are validated before any sidebar mutation in showRightSidebarModeInActiveMainWindow.

— Claude Code

# Conflicts:
#	cmuxTests/RightSidebarCommandPaletteTests.swift
@teamleaderleo

Copy link
Copy Markdown
Collaborator

cmux-reconcile: partly-useful

Usefulness verdict: Treat as a discoverability enhancement, not implementation of a missing file explorer.

The diff adds a persistent folder button and shared focus routing into the already-existing right-sidebar Files tree. current Files view and current CLI already provide that capability. Keep only if the extra entrypoint improves the current sidebar layout; compare it against current buttons/command palette. #888’s basic explorer requirement is not a reason to merge this UI change automatically.

Reviewed patch head: 30b8234af1753e3adf90589e151b6a539d3d31fc. Source/diff triage on September 18, 2026; no new build or runtime validation. No issue state, label, or merge decision changed.

Older issue/PR tracking index — remaining scope and competing implementations are recorded there.

@lawrencecchen

Copy link
Copy Markdown
Contributor

Mac fleet instructions for head 30b8234af1753e3adf90589e151b6a539d3d31fc. Planned tag: pr-3976-30b8234a; this is not yet a published build.

JOB_JSON=$(~/.local/bin/cmux-ci submit --kind cmux --command 'CMUX_FLEET_BUILD_TAG=pr-3976-30b8234a /Users/Shared/cmux-build-fleet/recipes/cmux.sh https://github.com/manaflow-ai/cmux.git 30b8234af1753e3adf90589e151b6a539d3d31fc' --artifact artifacts/cmux.app.zip --workspace https://github.com/manaflow-ai/cmux/pull/3976 --source-digest 30b8234af1753e3adf90589e151b6a539d3d31fc --cache-key cmux:pr-3976 --min-free-bytes 268435456000 --label cmux --label ram48)
JOB_ID=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$JOB_JSON")
~/.local/bin/cmux-ci wait "$JOB_ID" --receipt artifacts/fleet/$JOB_ID.json
~/.local/bin/cmux-ci publish-hq "$JOB_ID"

Use an existing campaign job ID if one is already posted; do not submit a duplicate. A wait timeout leaves the remote job running. Published results will include an exact-head artifact link and timing/disk receipt. This recipe validates the macOS app only, not iOS or tests. Never use maclease or put credentials in a PR comment.

@teamleaderleo teamleaderleo added area: sidebar The workspace sidebar: list, groups, status, reordering S4: cosmetic Wording or appearance, with no effect on what cmux does labels Sep 30, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 30b8234a Deployed May 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: sidebar The workspace sidebar: list, groups, status, reordering S4: cosmetic Wording or appearance, with no effect on what cmux does

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File Explorer

3 participants