Skip to content

Anchor textbox autocomplete to the cursor#5021

Merged
lawrencecchen merged 23 commits into
mainfrom
feat-textbox-autocomplete
Jun 3, 2026
Merged

Anchor textbox autocomplete to the cursor#5021
lawrencecchen merged 23 commits into
mainfrom
feat-textbox-autocomplete

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented May 30, 2026

Copy link
Copy Markdown
Contributor

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
Touches global window key routing and async filesystem/git/rg indexing for completions; mistakes could affect textbox shortcuts or performance on large repos.

Overview
Reworks textbox @ / / $ mention completion so the UI is a cursor-anchored, non-activating child panel (replacing NSPopover), with repositioning on window move/resize and a loading row when suggestions are still fetching.

Keyboard and acceptance behavior is tightened: Ctrl-N / Ctrl-P (and related keys) are forwarded from performKeyEquivalent only for the textbox; navigation applies only when suggestions are current and visible; bare / and $ skill triggers no longer auto-accept on Return; $ inserts plain skill tokens while @ / / keep markdown links.

Mention logic is split into dedicated modules with a heavier TextBoxMentionIndexStore: longer-lived file index cache, coalesced background refreshes, optional ripgrep file listing, directory suggestions, git-ignore filtering, package-directory skips, index warmup on root change, and higher suggestion limits. The popover UI is simplified (title + query highlight, scroll cap).

Adds TextBoxMentionCompletionTests and extends shortcut-routing tests; registers the completion panel as a non–Cmd+W auxiliary window.

Reviewed by Cursor Bugbot for commit 1bd3564. 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

vercel Bot commented May 30, 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 Jun 3, 2026 10:35pm
cmux-staging Building Building Preview, Comment Jun 3, 2026 10:35pm

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 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, 2 warnings)

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.
Description check ⚠️ Warning The PR description is missing critical sections from the required template: no Demo Video is provided, and the Checklist is entirely absent. Add a Demo Video URL/link showing the textbox autocomplete UI changes, and complete the Checklist section with all required items checked (local testing, test updates, docs/changelog updates, bot review requests, and comment resolution).
✅ Passed checks (11 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.
✨ 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 Outdated
@greptile-apps

greptile-apps Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the NSPopover-based mention completion UI with a cursor-anchored NSPanel, extracts the mention indexing logic into dedicated new files (TextBoxMentionIndexStore, TextBoxMentionCandidateIndex, TextBoxMentionCompletionController, and several small supporting types), and wires up a $ trigger for bare-token skill insertion. Subprocess-blocking issues from the prior review round are resolved via the new TextBoxProcessTerminationStatus actor and async byte streaming.

  • Async indexing improvements: file indexing correctly uses Task.detached(priority: .utility) with coalesced refresh tasks and proper cancellation; ripgrep is used when available with a synchronous-I/O-free async byte reader.
  • Panel lifecycle: the child NSPanel repositions on window move/resize and dismisses on window resign-key; the hidesOnDeactivate flag and the isKeyWindow guard in repositionMentionCompletionPanelIfNeeded together ensure the panel is not orphaned over other windows.
  • Skill indexing: skillIndex has no async boundary and runs directory enumeration and String(contentsOf:) file reads synchronously on the actor's executor on every cache miss, blocking concurrent suggestion lookups.

Confidence Score: 4/5

Safe to merge with one follow-up: the skill index scan runs synchronously on the actor executor on every cache miss, which can stall concurrent suggestion requests during warmup or after a root change.

The file-index path is well-designed — detached tasks, coalesced refreshes, async git calls via TextBoxProcessTerminationStatus — but the skill-index path (skillIndex) takes a different shape: it calls scanSkillFiles (directory enumeration) and up to 800 String(contentsOf:) reads directly on the actor's executor with no detached task boundary. A user with many plugin skills or a slow volume will stall the actor for the duration of that scan, blocking any concurrent file-suggestion lookup until it completes. The rest of the refactor — panel lifecycle, keyboard routing, $ trigger, controller extraction — is sound.

Sources/TextBoxMentionIndexStore.swift — specifically the skillIndex method at line 268

Important Files Changed

Filename Overview
Sources/TextBoxMentionIndexStore.swift New 928-line actor owning file and skill indexing; skill index computation blocks the actor executor synchronously on cache miss (directory scan + file reads)
Sources/TextBoxInput.swift Significant refactor: mention completion logic moved into new dedicated files; panel-based anchored popover replaces NSPopover; panel dismissal on key-window resign now correctly handled via repositionMentionCompletionPanelIfNeeded guard
Sources/TextBoxMentionCompletionController.swift New @observable MainActor controller for suggestion state; generation-based staleness checks prevent applying stale async results; looks correct
Sources/TextBoxProcessTerminationStatus.swift New actor wrapping process termination with withCheckedContinuation; properly handles the previously-blocking waitUntilExit pattern
Sources/App/ShortcutRoutingSupport.swift Adds shouldDispatchTextBoxInputControlNavViaFirstResponderKeyDown for Ctrl-N/P routing to the textbox; depth guard prevents recursion
Sources/TextBoxMentionCandidateIndex.swift New struct combining nucleo fuzzy search with Swift sequential search; mergedRankedCandidates deduplicates correctly; looks correct
Sources/TextBoxMentionMarkdown.swift New markdown link escaping helper; handles whitespace, parens, and angle brackets correctly for CommonMark link destinations
Sources/TextBoxMentionCompletionDetector.swift Extracted from TextBoxInput.swift; same detection logic with $ trigger support added; no issues found
cmuxTests/TextBoxMentionCompletionTests.swift Comprehensive new tests for empty-query behavior, escaping, nested skill lookup, and submission; covers the new trigger cases

Sequence Diagram

sequenceDiagram
    participant TB as TextBoxInputTextView
    participant MCC as TextBoxMentionCompletionController
    participant IS as TextBoxMentionIndexStore (actor)
    participant DT as Task.detached (utility)
    participant FS as FileSystem / rg

    TB->>MCC: refresh(for: query, rootDirectory:)
    MCC->>IS: suggestions(for: query, rootDirectory:)
    alt .file query with empty query (cache miss)
        IS->>IS: scanRootFileSystemCandidates (async, await git check)
        IS->>DT: fileIndexRefreshTask → scanFiles (detached)
        DT->>FS: rg --files / FileManager.enumerator
        FS-->>DT: paths
        DT-->>IS: TextBoxMentionCandidateIndex (stored)
    else .file query with non-empty query
        IS->>DT: fileIndexRefreshTask → scanFiles (detached, coalesced)
        DT-->>IS: TextBoxMentionCandidateIndex
        IS-->>MCC: ranked suggestions
    else .skill query (/ or $)
        IS->>IS: skillIndex (SYNC on actor executor on cache miss)
        IS->>FS: scanSkillFiles + skillName String(contentsOf:) [BLOCKING]
        IS-->>MCC: ranked suggestions
    end
    MCC-->>TB: onStateChanged → syncMentionCompletionPopover
    TB->>TB: show/reposition NSPanel
Loading

Reviews (19): Last reviewed commit: "Merge origin/main into textbox autocompl..." | Re-trigger Greptile

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

coderabbitai[bot]
coderabbitai Bot previously requested changes May 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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 Outdated
Comment thread Sources/TextBoxInput.swift Outdated
Comment thread Sources/TextBoxInput.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.

♻️ 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
coderabbitai[bot]
coderabbitai Bot previously requested changes May 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ 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 Outdated
Comment thread Sources/TextBoxInput.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/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 Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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 Outdated
Comment thread Sources/TextBoxInput.swift Outdated
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).

Comment thread Sources/TextBoxInput.swift
Comment thread Sources/TextBoxInput.swift
Comment thread Sources/TextBoxInput.swift
Comment thread Sources/TextBoxMentionCompletionController.swift
Comment thread Sources/TextBoxInput.swift
@socket-security

socket-security Bot commented Jun 2, 2026

Copy link
Copy Markdown

Comment thread Sources/TextBoxInput.swift
Comment thread Sources/TextBoxInput.swift Outdated
@cubic-dev-ai

cubic-dev-ai Bot commented Jun 2, 2026

Copy link
Copy Markdown

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

Comment thread Sources/TextBoxInput.swift
Comment thread Sources/TextBoxMentionCompletionController.swift Outdated
@lawrencecchen lawrencecchen dismissed stale reviews from coderabbitai[bot], coderabbitai[bot], and coderabbitai[bot] June 3, 2026 21:58

Stale automated review addressed by later commits; current CodeRabbit, Cursor, Greptile, and CI checks pass.

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

guard mentionCompletionPanel?.isVisible == true,
!mentionCompletionRepositionIsScheduled else {
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Completion panel hidden after refocus

Medium Severity

After the parent window loses key status, syncMentionCompletionPopover hides the mention panel but leaves completion active. Nothing calls sync again when the window becomes key, and reposition scheduling only runs while the panel is already visible. The popup can stay missing while @///$ completion is still active until the query changes enough to trigger a new lookup.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1bd3564. Configure here.

@lawrencecchen lawrencecchen merged commit d35cffe into main Jun 3, 2026
23 checks passed
@lawrencecchen lawrencecchen deleted the feat-textbox-autocomplete branch June 3, 2026 22:43
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