Skip to content

Fixes for cmd+k search palette#846

Open
shaneikennedy wants to merge 2 commits intoTanStack:mainfrom
shaneikennedy:main
Open

Fixes for cmd+k search palette#846
shaneikennedy wants to merge 2 commits intoTanStack:mainfrom
shaneikennedy:main

Conversation

@shaneikennedy
Copy link
Copy Markdown

@shaneikennedy shaneikennedy commented Apr 22, 2026

  1. When you use the search palette to find a bunch of things and want to open them all with cmd+click, the first cmd+click closes the search palette
  2. cmd+k should put the focus on the search input so you can start typing right away

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved search modal input focus behavior when opening the search dialog.
    • Enhanced search result activation to respect modifier keys (Cmd/Ctrl/Shift/Alt).

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 22, 2026

👷 Deploy request for tanstack pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 2f32860

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

The SearchModal's interaction and focus behavior are refined: modifier key presses during Hit activation now return early to prevent navigation, explicit focus scheduling via requestAnimationFrame replaces automatic focus, and Radix's default auto-focus is disabled to avoid conflicts.

Changes

Search Modal Interaction & Focus Refinement

Layer / File(s) Summary
Event Guard
src/components/SearchModal.tsx (Hit activation)
When Hit is activated, an early return fires if modifier keys (meta, ctrl, shift, alt) are pressed, preventing the framework persist and modal close.
Explicit Focus Management
src/components/SearchModal.tsx (useEffect on open)
A useEffect hook runs when isOpen becomes true, locating the search input and focusing it via two nested requestAnimationFrame calls to ensure proper timing and cleanup.
Auto-focus Coordination
src/components/SearchModal.tsx (DialogPrimitive & SearchBox)
DialogPrimitive.Content receives an onOpenAutoFocus handler that prevents Radix's default focus behavior; the SearchBox autoFocus prop is removed to eliminate conflicting focus mechanisms.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A rabbit hops with careful grace,
Through modifiers and focus space— 🐰✨
Frame by frame, the input glows,
No auto-clash, intention flows. 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fixes for cmd+k search palette' directly summarizes the main changes in the PR: fixing the Cmd+K search palette behavior including preventing unintended closure on Cmd+click and ensuring the search input is focused.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (1)
src/components/SearchModal.tsx (1)

424-429: Consider also guarding non-primary mouse buttons.

The modifier-key check covers cmd/ctrl/shift/alt + click, but middle-click (event.button === 1) also opens links in a new tab on most browsers and will currently close the palette. If you want the "open multiple results" flow to be consistent across input methods, include a button check:

-    if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
+    if (
+      event.button !== 0 ||
+      event.metaKey ||
+      event.ctrlKey ||
+      event.shiftKey ||
+      event.altKey
+    ) {
       return
     }

Note: many browsers now dispatch auxclick instead of click for middle-button, so this is defensive rather than strictly necessary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SearchModal.tsx` around lines 424 - 429, The click handler
handleClick currently only checks modifier keys and should also ignore
non-primary mouse buttons so middle-clicks don't close the search; update
handleClick to short-circuit when event.button !== 0 (or explicitly check for
event.button === 1) before calling closeSearch, and add or wire an auxclick
handler mirroring the same guard (so middle-button/Aux clicks are consistently
ignored). Refer to handleClick, closeSearch and the auxclick event when making
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/SearchModal.tsx`:
- Around line 785-800: The effect that focuses the search input schedules two
nested requestAnimationFrame calls but only cancels the outer id; update the
effect (useEffect in SearchModal.tsx) to capture both the outer and inner
requestAnimationFrame ids (e.g., idOuter and idInner) when scheduling
focusSearchInput (which uses containerRef and
querySelector('input[type="search"]')) and cancel both in the cleanup via
window.cancelAnimationFrame(idOuter) and window.cancelAnimationFrame(idInner) so
the inner callback cannot run after the component closes or unmounts.

---

Nitpick comments:
In `@src/components/SearchModal.tsx`:
- Around line 424-429: The click handler handleClick currently only checks
modifier keys and should also ignore non-primary mouse buttons so middle-clicks
don't close the search; update handleClick to short-circuit when event.button
!== 0 (or explicitly check for event.button === 1) before calling closeSearch,
and add or wire an auxclick handler mirroring the same guard (so
middle-button/Aux clicks are consistently ignored). Refer to handleClick,
closeSearch and the auxclick event when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9cebd3aa-985d-475a-a6a3-41b0ab319ba7

📥 Commits

Reviewing files that changed from the base of the PR and between eb49d0e and 5cb8849.

📒 Files selected for processing (1)
  • src/components/SearchModal.tsx

Comment on lines +785 to +800
React.useEffect(() => {
if (!isOpen) return

const focusSearchInput = () => {
const input = containerRef.current?.querySelector<HTMLInputElement>(
'input[type="search"]',
)
input?.focus()
}

const id = window.requestAnimationFrame(() => {
window.requestAnimationFrame(focusSearchInput)
})
return () => window.cancelAnimationFrame(id)
}, [isOpen])

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

Cleanup only cancels the outer requestAnimationFrame.

If the outer rAF has already fired before the effect cleanup runs (e.g., user closes the palette within ~1 frame of opening, or isOpen toggles quickly), the inner rAF is already scheduled and will still invoke focusSearchInput() — potentially focusing an input that's about to unmount or stealing focus after close. Track the inner id and cancel it as well.

♻️ Proposed fix
-    const id = window.requestAnimationFrame(() => {
-      window.requestAnimationFrame(focusSearchInput)
-    })
-    return () => window.cancelAnimationFrame(id)
+    let innerId = 0
+    const outerId = window.requestAnimationFrame(() => {
+      innerId = window.requestAnimationFrame(focusSearchInput)
+    })
+    return () => {
+      window.cancelAnimationFrame(outerId)
+      if (innerId) window.cancelAnimationFrame(innerId)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SearchModal.tsx` around lines 785 - 800, The effect that
focuses the search input schedules two nested requestAnimationFrame calls but
only cancels the outer id; update the effect (useEffect in SearchModal.tsx) to
capture both the outer and inner requestAnimationFrame ids (e.g., idOuter and
idInner) when scheduling focusSearchInput (which uses containerRef and
querySelector('input[type="search"]')) and cancel both in the cleanup via
window.cancelAnimationFrame(idOuter) and window.cancelAnimationFrame(idInner) so
the inner callback cannot run after the component closes or unmounts.

@nishatislam04
Copy link
Copy Markdown

YES! i was also wondering why search input was not auto focus when the search panel is opened!
thanks for the fix. i hope it added soon

@almahdi404
Copy link
Copy Markdown

This should fix #848

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 (1)
src/components/SearchModal.tsx (1)

448-479: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass the actual click event into handleActivate.

Line 461 references an undefined event variable. Since handleActivate() has no parameters and handleClick() doesn't capture or forward the click event, accessing event.metaKey, event.ctrlKey, etc. will throw a ReferenceError at runtime. The modifier-key guard will fail completely when triggered.

♻️ Suggested fix
-  const handleActivate = () => {
+  const handleActivate = (event: React.MouseEvent<HTMLAnchorElement>) => {
     const framework = hit.framework
     if (
       framework &&
       shouldPersistFrameworkForHit({
         url: hit.url,
@@
     if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
       return
     }

     closeSearch()
   }
@@
-  const handleClick = () => {
-    handleActivate()
-  }
-
   const ref = React.useRef<HTMLAnchorElement>(null!)
@@
-      onClick={handleClick}
+      onClick={handleActivate}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SearchModal.tsx` around lines 448 - 479, handleActivate
currently reads an undefined global `event`; update it to accept an event
parameter (e.g. handleActivate = (event?: MouseEvent | KeyboardEvent) => { ...
}) and replace usages of
`event.metaKey`/`event.ctrlKey`/`event.shiftKey`/`event.altKey` with checks on
that parameter (e.g. if (event?.metaKey || ... ) return). Then change
handleClick to capture and forward the click event (e.g. const handleClick = (e:
React.MouseEvent) => handleActivate(e)). Ensure existing keyboard code that
calls handleActivate without an event still works by keeping the parameter
optional and using optional chaining in the modifier checks.
♻️ Duplicate comments (1)
src/components/SearchModal.tsx (1)

832-846: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Cancel the inner requestAnimationFrame too.

If cleanup runs after the outer frame fires, the callback queued on Line 843 still survives and can refocus the search input after the dialog closes or unmounts.

♻️ Suggested fix
   React.useEffect(() => {
     if (!isOpen) return

     const focusSearchInput = () => {
       const input = containerRef.current?.querySelector<HTMLInputElement>(
         'input[type="search"]',
       )
       input?.focus()
     }

-    const id = window.requestAnimationFrame(() => {
-      window.requestAnimationFrame(focusSearchInput)
-    })
-    return () => window.cancelAnimationFrame(id)
+    let innerId = 0
+    const outerId = window.requestAnimationFrame(() => {
+      innerId = window.requestAnimationFrame(focusSearchInput)
+    })
+    return () => {
+      window.cancelAnimationFrame(outerId)
+      if (innerId) window.cancelAnimationFrame(innerId)
+    }
   }, [isOpen])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SearchModal.tsx` around lines 832 - 846, The effect sets two
nested requestAnimationFrame calls but only cancels the outer id; modify the
useEffect so you capture both ids (e.g., outerId and innerId) when scheduling
window.requestAnimationFrame and window.requestAnimationFrame(focusSearchInput)
and cancel both in the cleanup using window.cancelAnimationFrame(outerId) and
window.cancelAnimationFrame(innerId); reference the existing focusSearchInput,
containerRef, and isOpen symbols to locate where to store and cancel the inner
animation frame so the input won't be focused after the modal closes or
unmounts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/SearchModal.tsx`:
- Around line 448-479: handleActivate currently reads an undefined global
`event`; update it to accept an event parameter (e.g. handleActivate = (event?:
MouseEvent | KeyboardEvent) => { ... }) and replace usages of
`event.metaKey`/`event.ctrlKey`/`event.shiftKey`/`event.altKey` with checks on
that parameter (e.g. if (event?.metaKey || ... ) return). Then change
handleClick to capture and forward the click event (e.g. const handleClick = (e:
React.MouseEvent) => handleActivate(e)). Ensure existing keyboard code that
calls handleActivate without an event still works by keeping the parameter
optional and using optional chaining in the modifier checks.

---

Duplicate comments:
In `@src/components/SearchModal.tsx`:
- Around line 832-846: The effect sets two nested requestAnimationFrame calls
but only cancels the outer id; modify the useEffect so you capture both ids
(e.g., outerId and innerId) when scheduling window.requestAnimationFrame and
window.requestAnimationFrame(focusSearchInput) and cancel both in the cleanup
using window.cancelAnimationFrame(outerId) and
window.cancelAnimationFrame(innerId); reference the existing focusSearchInput,
containerRef, and isOpen symbols to locate where to store and cancel the inner
animation frame so the input won't be focused after the modal closes or
unmounts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e19d4d25-cc12-4b86-b980-58315a6ac95b

📥 Commits

Reviewing files that changed from the base of the PR and between 63d81b8 and 2f32860.

📒 Files selected for processing (1)
  • src/components/SearchModal.tsx

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.

3 participants