Skip to content

Anchor textbox autocomplete to the cursor#5021

Open
lawrencecchen wants to merge 7 commits into
mainfrom
feat-textbox-autocomplete
Open

Anchor textbox autocomplete to the cursor#5021
lawrencecchen wants to merge 7 commits into
mainfrom
feat-textbox-autocomplete

Conversation

@lawrencecchen
Copy link
Copy Markdown
Contributor

@lawrencecchen lawrencecchen commented May 30, 2026

Summary

  • Positions textbox @, /, and $ autocomplete with a non-activating panel anchored to the cursor.
  • Keeps completion navigation in the textbox, supports empty trigger queries, and prevents stale or hidden completions from being accepted.
  • Preserves file and nested skill discovery while skipping package-heavy directories.

Testing

  • /Users/lawrence/fun/cmuxterm-hq/skills/codex-review/scripts/codex-review
  • git diff --check origin/main...HEAD
  • ./scripts/reload.sh --tag tbac

Not run: local XCTest/xcodebuild test actions per repo rule.


Note

Medium Risk
Large, user-facing changes to text input, subprocess-based file indexing, and global key routing for the textbox only—no auth or data paths, but regressions in shortcuts or completions are plausible.

Overview
Reworks textbox @ / / / $ mention completion so the UI is a cursor-anchored, non-activating child panel (replacing the old popover), with loading state, scroll + match highlighting, and repositioning when the parent window moves.

Indexing and suggestions are expanded: empty-trigger queries for files and skills, folder rows in @ results, optional rg + git check-ignore scans with coalesced background refresh, warmup, and merged Nucleo/Swift ranking; $ skills insert plain tokens while / and @ stay markdown links. Completion logic avoids stale/hidden accepts, treats bare cd / / echo $ Return as submit (Tab still accepts), and keeps prior rows visible while re-querying the same trigger.

Ctrl-N / Ctrl-P are forwarded through window key equivalents only when the textbox is first responder (plus in-view handling when the list has rows), so mention navigation works without affecting terminal/browser.

Tests and auxiliary-window lint cover the new panel id and edge cases.

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

Summary by CodeRabbit

  • New Features

    • $-triggered mention completion now accepts empty queries and inserts bare skill tokens; @// continue inserting link-style suggestions
    • Mention UI moved to a panel with anchored positioning, capped rows, and current-vs-stale tracking
  • Improvements

    • Faster, more robust file and skill indexing with smarter skipping, streaming scans, and coalesced refreshes
    • Improved keyboard routing: Ctrl‑N / Ctrl‑P navigation forwarded correctly for terminal-style text inputs without recursion
  • Tests

    • Expanded tests for empty-query behavior, nested lookups, escaping, submission, and edge cases

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 31, 2026 2:57am
cmux-staging Building Building Preview, Comment May 31, 2026 2:57am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

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 Ctrl‑N/P first‑responder routing for terminal textboxes, refactors mention query/candidate shapes and trigger-specific insertion text, implements async ripgrep-backed file indexing with directory-skip/coalescing, redesigns controller staleness tracking, migrates mention UI to an NSPanel host, and expands tests covering these behaviors.

Changes

Mention Completion Workflow Overhaul

Layer / File(s) Summary
Keyboard routing for text-box input control navigation
Sources/App/ShortcutRoutingSupport.swift, Sources/AppDelegate.swift
New shouldDispatchTextBoxInputControlNavViaFirstResponderKeyDown helper routes Ctrl‑N/Ctrl‑P to the first responder for terminal textboxes without marked text; AppDelegate adds cmuxTextBoxInputControlNavForwardingDepth guard and forwards keyDown when appropriate.
Mention query and candidate structure updates
Sources/TextBoxInput.swift
Allow empty skill-query tokens for $, add targetPath to TextBoxMentionCandidate, produce trigger-specific insertionText ($ → bare skill ref; @// → markdown link), and update index structures including precomputed empty-query candidates and de-duplication by targetPath.
File and skill indexing refactor with ripgrep and skip logic
Sources/TextBoxInput.swift
Increase file-index TTL/limits, add shouldSkipIndexedDirectoryName(_:) for package-suffix skipping, implement ripgrep-driven streaming scan (rg --files) with skip globs and max-file budget, coalesce per-root refresh tasks, and update traversal to reuse the unified skip predicate and cap skill indexing.
Mention completion controller state management and staleness tracking
Sources/TextBoxInput.swift
TextBoxMentionCompletionController tracks isLoadingSuggestions, suggestionsQuery/suggestionsRootDirectory, differentiates stale vs current suggestions, clamps selection index, cancels prior lookups, and gates movement/accept/dismiss on controller eligibility.
Text view integration and NSPanel-based mention completion UI
Sources/TextBoxInput.swift
Replace NSPopover with NSPanel (hosted SwiftUI via NSHostingView), schedule index warmup on completionRootDirectory changes, compute capped visible rows for sizing, position via active query anchor rect, manage panel lifecycle/child-window relationship, and gate keyboard navigation/accept/dismiss on controller activity and current suggestions.
Test coverage for mention completion behavior and indexing
cmuxTests/AppDelegateShortcutRoutingTests.swift
Tests updated/added for empty-query skill suggestions, root-level file results for @, directory suggestions, package/dependency skipping, case-preserving directory titles, trigger-specific insertionText formats, nested skill discovery, suggestion persistence vs trigger-switch clearing, Escape fallthrough when no suggestions or loading, and Return-submit behavior for bare skill triggers.

Sequence Diagram

sequenceDiagram
  participant User
  participant AppDelegate
  participant TextBoxInputTextView
  participant TextBoxMentionCompletionController
  participant TextBoxMentionIndexStore
  participant NSPanel

  User->>TextBoxInputTextView: Type trigger (@, /, $) or press Ctrl-N/Ctrl-P
  TextBoxInputTextView->>AppDelegate: keyEquivalent handling
  AppDelegate->>TextBoxInputTextView: forward Ctrl-N/P to first responder when routed
  TextBoxInputTextView->>TextBoxMentionCompletionController: refresh(query, root)
  TextBoxMentionCompletionController->>TextBoxMentionIndexStore: load candidates async
  alt File indexing path
    TextBoxMentionIndexStore->>TextBoxMentionIndexStore: spawn `rg --files` stream or enumerate filesystem
    TextBoxMentionIndexStore->>TextBoxMentionIndexStore: apply shouldSkipIndexedDirectoryName and budgets
  end
  TextBoxMentionIndexStore-->>TextBoxMentionCompletionController: return candidates (with targetPath)
  TextBoxMentionCompletionController-->>TextBoxInputTextView: update suggestions state (current/stale, selection)
  TextBoxInputTextView->>NSPanel: create/update panel positioned at query anchor
  NSPanel-->>User: show suggestions
  User->>TextBoxInputTextView: navigate (Ctrl-N/P) or accept/submit
  TextBoxInputTextView->>TextBoxMentionCompletionController: accept or submit based on trigger and state
  TextBoxInputTextView->>NSPanel: dismiss panel
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#4333: Related keyboard-routing changes forwarding keyDown events to the first responder (arrow-key routing).

Suggested reviewers

  • Ari4ka

Poem

🐰 I hop where mentions softly bloom,
Ripgrep hums beneath my broom,
NSPanel opens, bright and small,
Ctrl‑N/P nudges cursor's call,
Suggestions dance — the rabbit's pleased.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (5 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Concurrency ❌ Error Fire-and-forget Tasks with meaningful lifecycle: Line 1898 awaits/stores file index, line 5777 repositions panel. Both lack lifecycle management. Store Tasks in instance variables with cancel() on replacement/deinit, or convert to async/await with proper Task handle management.
Cmux Swift @Concurrent ❌ Error The fileSuggestions method calls blocking file I/O scanRootFileSystemCandidates() within an actor-isolated async method called from @MainActor without explicit hop or @concurrent boundary. Wrap blocking scanRootFileSystemCandidates() in a .detached task within fileSuggestions, or mark the method @concurrent to run outside the actor's isolation.
Cmux Swift File And Package Boundaries ❌ Error TextBoxInput.swift violates 250-line addition rule: 1046 lines added to 5843-line file. Mixes UI, indexing, subprocess, and caching. Exception not met (file grew instead of shrinking). Extract TextBoxMentionIndexStore actor (async file/skill indexing, subprocess, caching logic) to a separate SwiftPM package, keeping UI in app target to achieve 200+ line reduction.
Cmux Architecture Rethink ❌ Error Split UI ownership via activeQuery/suggestionsQuery creates representable bad states; window repositioning via mutable flag + async dispatch; Escape dismiss and cache pruning issues unaddressed. Consolidate to single query truth; move repositioning to lifecycle; use hasSuggestions for Escape check; refresh cache before pruning.
Cmux Swift Auxiliary Window Close Shortcuts ❌ Error NSPanel cmux.textbox.mentionCompletionPanel assigned in TextBoxInput.swift but missing from cmuxAuxiliaryWindowIdentifiers in cmuxApp.swift. Register cmux.textbox.mentionCompletionPanel in cmuxAuxiliaryWindowIdentifiers list in Sources/cmuxApp.swift to enable shared close-shortcut ownership.
Docstring Coverage ⚠️ Warning Docstring coverage is 1.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (12 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: repositioning the textbox autocomplete panel to anchor to the cursor instead of using a popover.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed No Swift 6 actor isolation issues introduced. TextBoxMentionCompletionController is @MainActor, TextBoxMentionIndexStore is actor, AppDelegate additions properly @MainActor-isolated.
Cmux Swift Blocking Runtime ✅ Passed PR introduces no blocking synchronization patterns: pure predicate function, re-entrancy counter (state flag not blocking), and no Task.sleep/DispatchSemaphore/blocking calls in TextBoxInput changes.
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift source/test file changes. Review rule 'runtime-no-hacky-sleeps.md' explicitly excludes Swift code (covered by 'swift-blocking-runtime.md'), so this check is not applicable.
Cmux Algorithmic Complexity ✅ Passed Sorts max 8 cache entries (fixed-size), ranking merges up to 500 results with O(n) dedup, file indexing coalesces rescans; all bounded by explicit limits and meet rule requirements.
Cmux Swift Logging ✅ Passed No logging violations found. The PR adds new keyboard routing and mention completion features without introducing print, debugPrint, dump, or NSLog in production code.
Cmux User-Facing Error Privacy ✅ Passed PR contains no user-facing error messages that violate privacy rules; only internal error domain/code and test changes.
Cmux Full Internationalization ✅ Passed All user-facing Swift text uses String(localized:defaultValue:) API; all 8 localization keys have complete translations for all 20 supported locales; no web UI or unlocalized strings introduced.
Cmux Swiftui State Layout ✅ Passed Modern @Observable pattern used in AppKit view. SwiftUI views receive value snapshots, not store refs. No ObservableObject, GeometryReader, or render-time mutations.
Description check ✅ Passed The pull request description covers all required template sections: Summary clearly explains what changed and why, Testing section documents testing approach, and Checklist items are completed.
✨ 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 feat-textbox-autocomplete

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/TextBoxInput.swift
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 30, 2026

Greptile Summary

Replaces the textbox @///$ autocomplete popover with a cursor-anchored NSPanel child window, expands the mention-indexing backend to handle empty triggers, nested files/directories, and rg+git scanning with coalesced background refreshes, and adds window-level Ctrl-N/P forwarding so keyboard navigation works like arrow keys.

  • Completion panel: the old NSPopover is replaced by a non-activating NSPanel child window positioned below (or above) the trigger character. Window move/resize/screen-change notifications reposition the panel, and a stale-suggestion guard prevents accepting outdated results while a fresh scan is in flight.
  • Index expansion: empty queries now return root-level files/directories; rg streams output line-by-line up to a 6 000-file budget with a scanDirectoryCandidateSeed directory pre-pass that filters .gitignored entries via git check-ignore; a 30-second TTL cache with coalesced refresh tasks avoids redundant scans.
  • Key routing: shouldDispatchTextBoxInputControlNavViaFirstResponderKeyDown routes Ctrl-N/P into the textbox first-responder with the same depth guard used for arrow keys; $ trigger now inserts bare token text instead of a markdown link.

Confidence Score: 5/5

Safe to merge — keyboard routing, stale-completion guards, and panel lifecycle are all well-tested with no correctness regressions found.

The logic changes are narrowly scoped: key forwarding follows the established depth-guard pattern, the actor-isolated index store correctly coalesces concurrent refresh tasks, and stale-suggestion handling is explicitly exercised in the new tests. No data-loss or security paths are touched.

Sources/TextBoxInput.swift — the file's growing size and mixed responsibilities (index scanning, subprocess management, completion UI, text-view interaction) make further additions harder to review and test in isolation.

Important Files Changed

Filename Overview
Sources/TextBoxInput.swift Major addition: cursor-anchored completion panel, expanded file/skill indexing (rg + git integration), stale-suggestion tracking, Ctrl-N/P handling. File grows to 6 889 lines mixing several independent layers.
Sources/App/ShortcutRoutingSupport.swift Adds shouldDispatchTextBoxInputControlNavViaFirstResponderKeyDown for Ctrl-N/P routing, following the same depth-guarded pattern already used for arrow keys.
Sources/AppDelegate.swift Wires cmuxTextBoxInputControlNavForwardingDepth guard and calls shouldDispatchTextBoxInputControlNavViaFirstResponderKeyDown inside performKeyEquivalent, matching the existing arrow-key forwarding pattern.
cmuxTests/AppDelegateShortcutRoutingTests.swift Expands tests to cover empty-query detection, directory/nested-file suggestions, package-content skipping, trigger-change clearing, stale-suggestion Enter/Tab handling, and new Ctrl-N/P routing predicates.
scripts/lint_auxiliary_window_close_shortcuts.py Adds cmux.textbox.mentionCompletionPanel to IGNORED_IDENTIFIERS so the non-activating child panel is not flagged for lacking Cmd+W handling.

Sequence Diagram

sequenceDiagram
    participant User
    participant AppDelegate as AppDelegate(performKeyEquivalent)
    participant TV as TextBoxInputTextView
    participant Ctrl as TextBoxMentionCompletionController
    participant Store as TextBoxMentionIndexStore(actor)
    participant Panel as TextBoxMentionCompletionPanel(child NSPanel)

    User->>TV: "types @, /, or $"
    TV->>TV: refreshMentionCompletions()
    TV->>Ctrl: refresh(query, rootDirectory)
    Ctrl->>Ctrl: "set isLoadingSuggestions=true, keep stale rows if same trigger"
    Ctrl->>Store: await suggestions(query, rootDirectory)
    Store-->>Store: Task.detached: rg + git scan
    Store-->>Ctrl: [TextBoxMentionSuggestion]
    Ctrl->>Ctrl: MainActor.run: update suggestions
    Ctrl->>TV: onStateChanged()
    TV->>Panel: syncMentionCompletionPopover()
    Panel-->>User: completion list visible

    User->>AppDelegate: Ctrl-N / Ctrl-P keypress
    AppDelegate->>TV: firstResponder.keyDown(event)
    TV->>Ctrl: moveSelection(delta)
    Ctrl->>TV: onStateChanged()
    TV->>Panel: update selection highlight

    User->>TV: Return / Tab (current suggestions)
    TV->>TV: acceptMentionCompletion()
    TV->>TV: dismissMentionCompletions()

    User->>TV: Return (stale suggestions)
    TV->>TV: hasStaleSuggestions → consume key, no-op

    User->>TV: Escape
    TV->>TV: dismissMentionCompletions()
    TV->>Panel: orderOut / removeChildWindow
Loading

Reviews (6): Last reviewed commit: "Ignore textbox completion panel in close..." | Re-trigger Greptile

Comment on lines 1942 to 1998
@@ -1809,27 +1994,32 @@ actor TextBoxMentionIndexStore {
includingPropertiesForKeys: [.isDirectoryKey, .isRegularFileKey],
options: [.skipsHiddenFiles, .skipsPackageDescendants],
errorHandler: { _, _ in true }
) else {
return []
}
) else { return result }

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 Blocking cooperative thread pool in scanFilesWithRipgrep

stdoutHandle.readData(ofLength: 64 * 1024) is a synchronous blocking I/O call, and process.waitUntilExit() blocks until the subprocess exits. Both run inside Task.detached(priority: .utility), which puts them on the Swift cooperative thread pool. Blocking a cooperative pool thread with synchronous process I/O stalls the thread until rg finishes — on a large repo this can be seconds — and can starve other concurrency work. On macOS, the runtime can promote blocked threads but there is no guarantee, and sustained blocking under keystroke pressure degrades the whole app.

The correct shape is to bridge the pipe to async using a completion-handler-to-async wrapper (e.g. FileHandle.bytes / AsyncBytes, DispatchIO, or a withCheckedContinuation wrapping terminationHandler) so the cooperative thread is yielded while waiting for I/O, matching the async caller at the call site.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

Comment thread Sources/TextBoxInput.swift Outdated
Comment on lines +1724 to +1728
if query.query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return Self.scanRootFiles(rootURL: URL(fileURLWithPath: rootDirectory, isDirectory: true))
.prefix(Self.suggestionLimit)
.map { $0.suggestion(trigger: query.trigger) }
}
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 scanRootFiles synchronous I/O holds the actor executor

Self.scanRootFiles(...) calls FileManager.default.contentsOfDirectory synchronously without being wrapped in a detached task, unlike the non-empty-query path which uses Task.detached. Because fileSuggestions is an actor method, this call blocks the TextBoxMentionIndexStore actor's executor thread for the duration of the directory stat. While root scans are typically fast, the pattern is inconsistent with the rest of the method and could add noticeable latency if the root is on a mounted network volume or a slow path. Wrapping the call in a Task.detached(priority: .utility) (same as the full scan) keeps the actor free to service concurrent requests.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

Comment on lines +2355 to +2368
private static func subsequenceMatchPositions(query: String, in text: String) -> [Int] {
let q = Array(query.lowercased())
let t = Array(text.lowercased())
guard !q.isEmpty, !t.isEmpty else { return [] }
var positions: [Int] = []
var qIdx = 0
for (tIdx, char) in t.enumerated() {
if qIdx < q.count, char == q[qIdx] {
positions.append(tIdx)
qIdx += 1
if qIdx == q.count { return positions }
}
}
return qIdx == q.count ? positions : []
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.

P2 Highlight offsets computed against lowercased copy but applied to original string

subsequenceMatchPositions converts both the query and text to Array(text.lowercased()) for case-insensitive matching and returns indices into that array. Those indices are then passed to title.index(title.startIndex, offsetBy: position, ...) on the original, non-lowercased title. For characters where lowercased() changes the number of Unicode extended grapheme clusters (e.g., the German sharp-S ß lowercases to ss — two grapheme clusters instead of one), positions in the lowercased array diverge from positions in the original string, causing highlights to land on the wrong characters or crash with an out-of-bounds offset. File paths are overwhelmingly ASCII so this rarely triggers, but the invariant is broken. Matching and collecting Range<String.Index> directly in the original string (using String.compare with .caseInsensitive) avoids the mismatch entirely.

Comment on lines +5116 to +5136
private func shouldBypassHiddenMentionCompletionKeyboardInteraction() -> Bool {
guard let window else { return false }
guard NSApp.isActive,
window.isKeyWindow,
window.firstResponder === self,
mentionCompletionPanel?.isVisible == true else {
dismissMentionCompletions()
return true
}
return false
}

private func shouldBypassMentionCompletionKeyboardAcceptance() -> Bool {
guard let query = mentionCompletionController.activeQuery,
query.kind == .skill,
query.query.isEmpty else {
return false
}
dismissMentionCompletions()
return true
}
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.

P2 Predicate-named helpers performing dismissal as a side effect

Both shouldBypassHiddenMentionCompletionKeyboardInteraction() and shouldBypassMentionCompletionKeyboardAcceptance() are named like pure predicates but call dismissMentionCompletions() inside their guard path. Any future caller added during a refactor (e.g., a new key or command handler) will trigger an unconditional dismissal simply by checking the condition, with no indication at the call site that state is being mutated. Separating the dismiss call to the call site (i.e., if shouldBypass... { dismissMentionCompletions(); return false }) makes the mutation visible and prevents accidental double-dismiss if a caller path changes.

Rule Used: Flag Swift fixes that patch symptoms while leaving... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 7348-7360: The test currently asserts the first suggestion is the
temp fixture which is environment-dependent; change the assertions to search the
suggestions array for an entry whose title equals "/nested-skill" (using
TextBoxMentionIndexStore.shared.suggestions result and TextBoxMentionQuery) and
then assert that that found suggestion's insertionText
hasPrefix("[/nested-skill]("); in other words, replace
XCTAssertEqual(suggestions.first?.title, "/nested-skill") and the XCTAssertTrue
on suggestions.first with a contains/first(where:) lookup for title ==
"/nested-skill" and assert properties on that found item.

In `@Sources/TextBoxInput.swift`:
- Around line 5241-5258: The new TextBoxMentionCompletionPanel is created
without a stable cmux.* window identifier; set a deterministic
NSUserInterfaceItemIdentifier on the panel after creation (e.g., assign
panel.identifier = NSUserInterfaceItemIdentifier("cmux.mentionCompletionPanel")
or another stable cmux.* name) so global window routing/ownership rules remain
deterministic—apply this change where TextBoxMentionCompletionPanel is
instantiated and assigned to mentionCompletionPanel.
- Around line 1904-1911: The ripgrep glob changes aren’t necessary—keep the
existing skip globs as-is—and add a stable NSPanel identifier when creating the
mention panel: inside makeMentionCompletionPanel(host:) (where the
TextBoxMentionCompletionPanel is instantiated) set panel.identifier =
NSUserInterfaceItemIdentifier("cmux.textbox.mentionCompletionPanel") so the
panel has a stable cmux.* identifier for window/panel management.
🪄 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: 1220e732-67ea-44d1-8240-47eb1c4c3fcb

📥 Commits

Reviewing files that changed from the base of the PR and between b6eb5f1 and f3609b9.

📒 Files selected for processing (4)
  • Sources/App/ShortcutRoutingSupport.swift
  • Sources/AppDelegate.swift
  • Sources/TextBoxInput.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift

Comment thread cmuxTests/AppDelegateShortcutRoutingTests.swift
Comment thread Sources/TextBoxInput.swift
Comment thread Sources/TextBoxInput.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
Sources/TextBoxInput.swift (1)

5252-5268: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a stable cmux.* identifier to the mention completion panel.

Line 5252 creates a user-visible NSPanel without panel.identifier, which violates the window identity rule and can break shared window routing/ownership behavior.

Suggested fix
     let panel = TextBoxMentionCompletionPanel(
         contentRect: NSRect(origin: .zero, size: host.fittingSize),
         styleMask: [.borderless, .nonactivatingPanel],
         backing: .buffered,
         defer: false
     )
+    panel.identifier = NSUserInterfaceItemIdentifier("cmux.textbox.mentionCompletionPanel")
     panel.isFloatingPanel = true
     panel.hidesOnDeactivate = true

As per coding guidelines: “User-visible NSWindow, NSPanel, NSWindowController, SwiftUI Window, or SwiftUI WindowGroup must have a stable cmux.* window identifier.”

🤖 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/TextBoxInput.swift` around lines 5252 - 5268, The newly created
TextBoxMentionCompletionPanel instance (assigned to local variable panel and
stored in mentionCompletionPanel) needs a stable cmux.* window identifier; set
panel.identifier to an NSUserInterfaceItemIdentifier with a constant string like
"cmux.mentionCompletionPanel" (or another stable cmux.* name) immediately after
creating the panel so the user-visible NSPanel has a persistent identity for
shared window routing and ownership.
🤖 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.

Duplicate comments:
In `@Sources/TextBoxInput.swift`:
- Around line 5252-5268: The newly created TextBoxMentionCompletionPanel
instance (assigned to local variable panel and stored in mentionCompletionPanel)
needs a stable cmux.* window identifier; set panel.identifier to an
NSUserInterfaceItemIdentifier with a constant string like
"cmux.mentionCompletionPanel" (or another stable cmux.* name) immediately after
creating the panel so the user-visible NSPanel has a persistent identity for
shared window routing and ownership.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ba0564a5-c0ea-4972-b957-72461b216e97

📥 Commits

Reviewing files that changed from the base of the PR and between f3609b9 and 72033d6.

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

Comment thread Sources/TextBoxInput.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
cmuxTests/AppDelegateShortcutRoutingTests.swift (1)

7192-7194: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid asserting the first empty-query file suggestion.

This fixture now returns multiple valid empty-query candidates (@Sources/, @README.md, @ZEmpty/, etc.), so pinning suggestions.first makes the test brittle to ranking changes even when the behavior is still correct. Assert that @Sources/ is present, then verify its metadata.

Suggested change
-        XCTAssertEqual(suggestions.first?.title, "`@Sources/`")
-        XCTAssertEqual(suggestions.first?.systemImageName, "folder")
-        XCTAssertTrue(suggestions.first?.insertionText.hasPrefix("[`@Sources/`](") == true)
+        let sourcesDirectory = suggestions.first { $0.title == "`@Sources/`" }
+        XCTAssertNotNil(sourcesDirectory)
+        XCTAssertEqual(sourcesDirectory?.systemImageName, "folder")
+        XCTAssertTrue(sourcesDirectory?.insertionText.hasPrefix("[`@Sources/`](") == true)
🤖 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/AppDelegateShortcutRoutingTests.swift` around lines 7192 - 7194,
The test currently assumes suggestions.first is the "`@Sources/`" candidate which
is brittle; instead locate the candidate by searching the suggestions array for
an element whose title == "`@Sources/`" (e.g. using first(where: { $0.title ==
"`@Sources/`" })), assert that this found suggestion is non-nil, then verify its
systemImageName == "folder" and that its insertionText.hasPrefix("[`@Sources/`](")
is true; update assertions that reference suggestions.first to use the located
suggestion variable.
🤖 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/TextBoxInput.swift`:
- Around line 2000-2004: The current refresh path does a second full tree walk
because scanFilesWithRipgrep performs its own BFS and git-ignore probes even
after scanDirectoryCandidateSeed produced directorySeed
(directorySeed.candidates and seenDirectoryRelativePaths); fix by reusing or
gating that seed: modify scanFilesWithRipgrep to accept an optional
directorySeed or directoryCandidates parameter and when a non-empty
directorySeed is provided, skip the separate BFS and git check-ignore phase and
instead derive directory rows directly from the ripgrep file stream (populate
directoryCandidates and update seenDirectoryRelativePaths from file paths
emitted by rg), or cache the computed directorySeed at a higher level and
early-return when it’s available so fileCandidates/reserveCapacity still happens
without a second traversal. Ensure changes reference scanFilesWithRipgrep,
scanDirectoryCandidateSeed, directorySeed, directoryCandidates, fileCandidates,
and seenDirectoryRelativePaths.

---

Duplicate comments:
In `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 7192-7194: The test currently assumes suggestions.first is the
"`@Sources/`" candidate which is brittle; instead locate the candidate by
searching the suggestions array for an element whose title == "`@Sources/`" (e.g.
using first(where: { $0.title == "`@Sources/`" })), assert that this found
suggestion is non-nil, then verify its systemImageName == "folder" and that its
insertionText.hasPrefix("[`@Sources/`](") is true; update assertions that
reference suggestions.first to use the located suggestion variable.
🪄 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: cc9a9f87-f26f-4ac3-9dbc-a4cbecbdea2f

📥 Commits

Reviewing files that changed from the base of the PR and between 72033d6 and 41a3702.

📒 Files selected for processing (2)
  • Sources/TextBoxInput.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift

Comment thread Sources/TextBoxInput.swift
Comment thread Sources/TextBoxInput.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

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

5468-5478: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the bare / / $ special-case scoped to Return.

Line 5471 and Line 5506 route both Return and Tab through shouldBypassMentionCompletionKeyboardAcceptance(), so an empty skill query now dismisses suggestions on Tab too. The PR only calls out Return as the submit path for bare / and $; Tab completion for root skill suggestions regresses here.

Suggested fix
-    private func shouldBypassMentionCompletionKeyboardAcceptance() -> Bool {
+    private func shouldBypassMentionCompletionReturnAcceptance() -> Bool {
         guard let query = mentionCompletionController.activeQuery,
               query.kind == .skill,
               query.query.isEmpty else {
             return false
         }
         dismissMentionCompletions()
         return true
     }
         case kVK_Return, kVK_ANSI_KeypadEnter:
             guard !flags.contains(.shift) else { return false }
             if shouldBypassHiddenMentionCompletionKeyboardInteraction() { return false }
-            if shouldBypassMentionCompletionKeyboardAcceptance() { return false }
+            if shouldBypassMentionCompletionReturnAcceptance() { return false }
             if mentionCompletionController.hasStaleSuggestions { return true }
             return acceptMentionCompletion()
         case kVK_Tab:
             if shouldBypassHiddenMentionCompletionKeyboardInteraction() { return false }
-            if shouldBypassMentionCompletionKeyboardAcceptance() { return false }
             if mentionCompletionController.hasStaleSuggestions { return true }
             return acceptMentionCompletion()
         case `#selector`(NSResponder.insertNewline(_:)),
-             `#selector`(NSResponder.insertTab(_:)):
+             `#selector`(NSResponder.insertTab(_:)):
+            if commandSelector == `#selector`(NSResponder.insertNewline(_:)),
+               shouldBypassMentionCompletionReturnAcceptance() {
+                return false
+            }
-            if shouldBypassMentionCompletionKeyboardAcceptance() { return false }
             if mentionCompletionController.hasStaleSuggestions { return true }
             return acceptMentionCompletion()

Also applies to: 5503-5508, 5531-5539

🤖 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/TextBoxInput.swift` around lines 5468 - 5478, The Tab case is
incorrectly sharing the same "bare `/` / `$` submit" bypass logic as Return;
update the kVK_Tab branch so it does not call
shouldBypassMentionCompletionKeyboardAcceptance() (or otherwise apply the
bare-query bypass) — keep that special-case scoped only to the kVK_Return /
kVK_ANSI_KeypadEnter branch; in practice modify the kVK_Tab handling around
mentionCompletionController.hasStaleSuggestions and acceptMentionCompletion() to
omit the call to shouldBypassMentionCompletionKeyboardAcceptance() so Tab will
not dismiss root-skill suggestions while Return still uses the special-case
check in shouldBypassMentionCompletionKeyboardAcceptance().

2080-2326: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Bound rg/git subprocess lifetime.

scanFilesWithRipgrep, isGitWorkTree, and gitIgnoredRelativePaths run Process and then block on completion (process.waitUntilExit() / stdout reads) with no timeout or cancellation path. Since fileIndexRefreshTask stores a detached scan in fileIndexRefreshTasks[rootDirectory] and storeFileIndex clears it only after scanTask.value returns, a hung rg/git can leave the in-flight entry stuck and cause subsequent refreshes that await the index to block indefinitely. Add a timeout (terminating the Process) and ensure fileIndexRefreshTasks[rootDirectory] is cleared on timeout/failure.

🤖 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/TextBoxInput.swift`:
- Around line 5468-5478: The Tab case is incorrectly sharing the same "bare `/`
/ `$` submit" bypass logic as Return; update the kVK_Tab branch so it does not
call shouldBypassMentionCompletionKeyboardAcceptance() (or otherwise apply the
bare-query bypass) — keep that special-case scoped only to the kVK_Return /
kVK_ANSI_KeypadEnter branch; in practice modify the kVK_Tab handling around
mentionCompletionController.hasStaleSuggestions and acceptMentionCompletion() to
omit the call to shouldBypassMentionCompletionKeyboardAcceptance() so Tab will
not dismiss root-skill suggestions while Return still uses the special-case
check in shouldBypassMentionCompletionKeyboardAcceptance().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f2217da3-70b8-4900-8d1d-c6489cb2c7e2

📥 Commits

Reviewing files that changed from the base of the PR and between 41a3702 and 091f963.

📒 Files selected for processing (2)
  • Sources/TextBoxInput.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift

Comment thread Sources/TextBoxInput.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

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

1730-1741: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Extract the new mention-completion pipeline into separate Swift files.

This PR adds filesystem/git/rg indexing plus controller state to a file that is already far past the repo's size limit. Please split the new index store/controller out of TextBoxInput.swift before merge.

As per coding guidelines: “Do not add more than 250 lines to an existing production Swift file that is already over 800 lines” and “Do not mix UI rendering, state ownership, persistence, networking, parsing, subprocess/socket protocol, and platform bridge code in one Swift file.”

Also applies to: 2585-2603

🤖 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/TextBoxInput.swift` around lines 1730 - 1741, The
TextBoxMentionIndexStore actor and its supporting types (e.g.,
TextBoxMentionIndexStore, CachedIndex, fileIndexTTL, maxCachedFileIndexes,
directorySeedBatchSize and any helper methods used between the two highlighted
ranges) must be extracted into one or more new Swift source files; move the
entire actor, its nested structs/enums, constants, and any non-UI
controller/state logic out of TextBoxInput.swift into a dedicated file (e.g.,
TextBoxMentionIndexStore.swift) and update visibility (public/internal/private)
and imports so references from TextBoxInput.swift compile; ensure you also move
or refactor any helper functions, extensions, and tests that reference these
symbols, update import/module boundaries if needed, and run the build to fix any
broken references.
🤖 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/TextBoxInput.swift`:
- Around line 5542-5545: The Escape handling currently dismisses mention
completions whenever mentionCompletionController.shouldShowPopover is true, but
this is also true during the loading-only state; change the check so Escape only
dismisses when actual suggestion rows exist. In the block that calls
shouldBypassHiddenMentionCompletionKeyboardInteraction(), guard
mentionCompletionController.shouldShowPopover AND a check for visible results
(e.g., mentionCompletionController.hasRows or
mentionCompletionController.visibleResultsCount > 0) before calling
dismissMentionCompletions(); apply the same change to the analogous block at the
other occurrence (the block around the second instance that currently mirrors
lines 5571-5575).
- Around line 1858-1867: The code can evict the looked-up rootDirectory during
pruneFileIndexCache(now:), so first check fileIndexesByRoot for rootDirectory
and, if a valid CachedIndex exists (now.timeIntervalSince(cached.createdAt) <
Self.fileIndexTTL), update its lastAccessedAt immediately (write back a new
CachedIndex with cached.index and cached.createdAt but lastAccessedAt: now)
before calling pruneFileIndexCache(now:); alternatively, reorder to update the
cache entry's lastAccessedAt for rootDirectory prior to invoking
pruneFileIndexCache(now:), ensuring pruneFileIndexCache cannot evict the very
entry you're refreshing (references: fileIndexesByRoot,
pruneFileIndexCache(now:), CachedIndex, fileIndexTTL, rootDirectory).

---

Outside diff comments:
In `@Sources/TextBoxInput.swift`:
- Around line 1730-1741: The TextBoxMentionIndexStore actor and its supporting
types (e.g., TextBoxMentionIndexStore, CachedIndex, fileIndexTTL,
maxCachedFileIndexes, directorySeedBatchSize and any helper methods used between
the two highlighted ranges) must be extracted into one or more new Swift source
files; move the entire actor, its nested structs/enums, constants, and any
non-UI controller/state logic out of TextBoxInput.swift into a dedicated file
(e.g., TextBoxMentionIndexStore.swift) and update visibility
(public/internal/private) and imports so references from TextBoxInput.swift
compile; ensure you also move or refactor any helper functions, extensions, and
tests that reference these symbols, update import/module boundaries if needed,
and run the build to fix any broken references.
🪄 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: 87f2a3e8-8cda-477a-aaa5-46edf0d4fe3d

📥 Commits

Reviewing files that changed from the base of the PR and between 091f963 and 8db5b31.

📒 Files selected for processing (2)
  • Sources/TextBoxInput.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift

Comment thread Sources/TextBoxInput.swift
Comment on lines +5542 to 5545
if shouldBypassHiddenMentionCompletionKeyboardInteraction() { return false }
guard mentionCompletionController.shouldShowPopover else { return false }
dismissMentionCompletions()
return true
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't let Escape dismiss while only the loading spinner is visible.

shouldShowPopover is also true during the loading-only state, so Esc/cancel now clears the active query before any suggestion rows arrive. The new behavior contract here says Escape should dismiss only when rows exist.

Suggested fix
-            guard mentionCompletionController.shouldShowPopover else { return false }
+            guard mentionCompletionController.hasSuggestions else { return false }
             dismissMentionCompletions()
             return true
...
-            guard mentionCompletionController.shouldShowPopover else { return false }
+            guard mentionCompletionController.hasSuggestions else { return false }
             dismissMentionCompletions()
             return true

Also applies to: 5571-5575

🤖 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/TextBoxInput.swift` around lines 5542 - 5545, The Escape handling
currently dismisses mention completions whenever
mentionCompletionController.shouldShowPopover is true, but this is also true
during the loading-only state; change the check so Escape only dismisses when
actual suggestion rows exist. In the block that calls
shouldBypassHiddenMentionCompletionKeyboardInteraction(), guard
mentionCompletionController.shouldShowPopover AND a check for visible results
(e.g., mentionCompletionController.hasRows or
mentionCompletionController.visibleResultsCount > 0) before calling
dismissMentionCompletions(); apply the same change to the analogous block at the
other occurrence (the block around the second instance that currently mirrors
lines 5571-5575).

Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 2 potential issues.

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 8771467. Configure here.

var completionRootDirectory: String? {
didSet {
warmMentionCompletionIndexesIfNeeded()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Root change won't invalidate completions

Medium Severity

When completionRootDirectory changes (for example after the terminal cwd updates) without a text or selection change, only index warmup runs. The completion controller keeps its prior activeRootDirectory, so hasCurrentSuggestions can still treat file rows as current and allow accepting paths from the wrong root.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8771467. Configure here.

mentionCompletionPanel?.isVisible == true else {
dismissMentionCompletions()
return true
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hidden panel dismisses active completion

Medium Severity

The mention panel uses hidesOnDeactivate, so it can be invisible while completion state remains active. shouldBypassHiddenMmentionCompletionKeyboardInteraction then treats a hidden panel as invalid and calls dismissMmentionCompletions() on the next navigation key instead of re-showing the anchored panel.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8771467. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant