Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 38 additions & 22 deletions apps/web/core/components/command-palette/actions/search-results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Command } from "cmdk";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { MouseEvent } from "react";
// plane imports
import type { IWorkspaceSearchResults } from "@plane/types";
// hooks
Expand Down Expand Up @@ -35,28 +36,43 @@ export const CommandPaletteSearchResults: React.FC<Props> = observer((props) =>
if (section.length > 0) {
return (
<Command.Group key={key} heading={`${currentSection.title} search`}>
{section.map((item: any) => (
<Command.Item
key={item.id}
onSelect={() => {
closePalette();
router.push(currentSection.path(item, projectId));
const itemProjectId =
item?.project_id ||
(Array.isArray(item?.project_ids) && item?.project_ids?.length > 0
? item?.project_ids[0]
: undefined);
if (itemProjectId) openProjectAndScrollToSidebar(itemProjectId);
}}
value={`${key}-${item?.id}-${item.name}-${item.project__identifier ?? ""}-${item.sequence_id ?? ""}`}
className="focus:outline-none"
>
<div className="flex items-center gap-2 overflow-hidden text-custom-text-200">
{currentSection.icon}
<p className="block flex-1 truncate">{currentSection.itemName(item)}</p>
</div>
</Command.Item>
))}
{section.map((item: any) => {
const targetPath = currentSection.path(item, projectId);
const itemProjectId =
item?.project_id ||
(Array.isArray(item?.project_ids) && item?.project_ids?.length > 0
? item?.project_ids[0]
: undefined);

const handleMetaClick = (event: MouseEvent<HTMLDivElement>) => {
if (!event.metaKey && !event.ctrlKey) return;

event.preventDefault();
event.stopPropagation();

closePalette();
window.open(targetPath, "_blank", "noopener,noreferrer");
Comment on lines +48 to +54
Copy link

Copilot AI Oct 23, 2025

Choose a reason for hiding this comment

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

The early return on line 48 doesn't prevent the default behavior. When neither metaKey nor ctrlKey is pressed, the function returns but onMouseDown still fires, which can interfere with the normal onSelect flow. Consider calling event.preventDefault() before the early return, or restructure to only call preventDefault/stopPropagation when meta/ctrl keys are detected.

Suggested change
if (!event.metaKey && !event.ctrlKey) return;
event.preventDefault();
event.stopPropagation();
closePalette();
window.open(targetPath, "_blank", "noopener,noreferrer");
if (event.metaKey || event.ctrlKey) {
event.preventDefault();
event.stopPropagation();
closePalette();
window.open(targetPath, "_blank", "noopener,noreferrer");
}

Copilot uses AI. Check for mistakes.
};
Comment on lines +47 to +55
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that onMouseDown preventDefault prevents onSelect from firing.

The preventDefault() and stopPropagation() calls in onMouseDown may not prevent the subsequent click event and onSelect from firing—standard DOM behavior allows click events to fire even after mousedown is prevented. This could result in both the new-tab open and the router.push executing when meta/ctrl+click is used.

Test both scenarios to confirm the behavior:

  1. Normal click: should only trigger onSelect (navigate in same tab)
  2. Meta/Ctrl+click: should only trigger handleMetaClick (open in new tab), not onSelect

If both handlers fire on meta+click, consider these alternatives:

Option 1: Use onClick instead of onMouseDown and check for default prevented

const handleClick = (event: MouseEvent<HTMLDivElement>) => {
  if (event.metaKey || event.ctrlKey) {
    event.preventDefault();
    event.stopPropagation();
    closePalette();
    window.open(targetPath, "_blank", "noopener,noreferrer");
  }
};

Then set onClick={handleClick} and let onSelect check if default was prevented.

Option 2: Track meta/ctrl state and check in onSelect

let isMetaClick = false;

const handleMetaClick = (event: MouseEvent<HTMLDivElement>) => {
  if (event.metaKey || event.ctrlKey) {
    isMetaClick = true;
    event.preventDefault();
    event.stopPropagation();
    closePalette();
    window.open(targetPath, "_blank", "noopener,noreferrer");
  }
};

// In onSelect:
onSelect={() => {
  if (isMetaClick) {
    isMetaClick = false;
    return;
  }
  // existing logic...
}}

Option 3: Move meta/ctrl check into onSelect

onSelect={(event?: any) => {
  closePalette();
  if (event?.metaKey || event?.ctrlKey) {
    window.open(targetPath, "_blank", "noopener,noreferrer");
  } else {
    router.push(targetPath);
    if (itemProjectId) openProjectAndScrollToSidebar(itemProjectId);
  }
}}

(Check if cmdk passes the original event to onSelect)



---

🏁 Script executed:

```shell
#!/bin/bash
# Inspect the target file to see full context and component structure
cat apps/web/core/components/command-palette/actions/search-results.tsx

Length of output: 2942


🏁 Script executed:

#!/bin/bash
# Search for onSelect handlers and how they interact with mouse events
rg -nP -C5 '\bonSelect\b' apps/web/core/components/command-palette/ --type=ts --type=tsx

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check how cmdk is used in the codebase to understand event handling
rg -nP 'import.*cmdk|from.*cmdk' apps/web --type=ts --type=tsx | head -20

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for other examples of meta/ctrl click handling in the codebase
rg -nP '\b(metaKey|ctrlKey)\b.*\bonMouseDown' apps/web --type=ts --type=tsx -B2 -A5

Length of output: 27



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for cmdk imports to find how it's being used
rg -n 'import.*cmdk|from.*cmdk' apps/web --type=ts

Length of output: 1081


🏁 Script executed:

#!/bin/bash
# Search for other meta/ctrl click handling patterns in codebase
rg -n 'metaKey|ctrlKey' apps/web --type=ts -B2 -A2 | head -40

Length of output: 3607


🏁 Script executed:

#!/bin/bash
# Look for Command.Item usage to understand cmdk patterns
rg -n 'Command\.Item' apps/web --type=ts -B1 -A5 | head -60

Length of output: 6591


🏁 Script executed:

#!/bin/bash
# Check package.json for cmdk version to understand its API
rg -n '"cmdk"' . -B1 -A1

Length of output: 3649



---

🌐 Web query:

```web
cmdk Command.Item onSelect event handling mousedown click

💡 Result:

Short answer

  • cmdk's Command.Item uses its onSelect callback as the selection API (it intentionally triggers selection on pointer down / activation rather than waiting for native click). Use onSelect for command handling. [1]

Why this matters

  • Stopping or changing mousedown/click ordering can be browser-dependent (calling preventDefault() on mousedown affects default actions and :active behavior in some browsers — notably Firefox). That can change whether click or focus behaviors run. See MDN on preventDefault and a writeup about Firefox mousedown behavior. [2][3]

Practical recommendations

  1. Handle selection with Command.Item's onSelect — that's the supported, cross-browser hook. [1]
  2. Avoid depending on the native click event for command behavior inside cmdk; relying on onSelect prevents race/ordering bugs. [1]
  3. If you must intercept pointer interactions (for example to prevent focus styling), prefer pointer events (pointerdown/pointerup) or manage focus programmatically — and be careful with event.preventDefault() on mousedown because it can change browser behavior (especially Firefox). Test in target browsers. [2][3]
  4. If you need to stop cmdk selection for a specific item, use disabled/aria-disabled or conditionally render the item rather than preventing events. [1]

If you want, paste the minimal code you’re using and I’ll show the exact changes (e.g., moving logic into onSelect or using pointerdown with guarded preventDefault).

Sources
[1] cmdk README / Command.Item docs.
[2] MDN: Event.preventDefault().
[3] Article: Firefox behavior when using preventDefault() on mousedown.
[4] StackOverflow: preventDefault and mousedown/click interaction.


Move meta+click handling into onSelect to prevent both handlers from executing.

The current implementation has a critical bug: cmdk's Command.Item triggers onSelect on pointer down activation, not on click. When a user meta+clicks, both handleMetaClick (on onMouseDown) and onSelect execute sequentially, causing both window.open() and router.push() to run. The preventDefault() on mousedown does not prevent the subsequent onSelect firing.

Move the meta+click logic into onSelect to consolidate routing decisions into a single handler:

onSelect={(event?: any) => {
  closePalette();
  if (event?.metaKey || event?.ctrlKey) {
    window.open(targetPath, "_blank", "noopener,noreferrer");
  } else {
    router.push(targetPath);
    if (itemProjectId) openProjectAndScrollToSidebar(itemProjectId);
  }
}}

Remove the onMouseDown={handleMetaClick} handler. If cmdk doesn't pass the original MouseEvent to onSelect, track the modifier keys in a ref or state variable during onMouseDown and check it in onSelect.

🤖 Prompt for AI Agents
In apps/web/core/components/command-palette/actions/search-results.tsx around
lines 47 to 55, the meta+click logic currently lives in an onMouseDown handler
which does not prevent cmdk's onSelect from also firing; move the meta/ctrl
modifier handling into the onSelect handler so routing decisions happen once:
call closePalette(), then if metaKey or ctrlKey open the target path in a new
tab via window.open(...), else call router.push(targetPath) and call
openProjectAndScrollToSidebar(itemProjectId) when applicable; remove the
onMouseDown={handleMetaClick} binding entirely; if cmdk’s onSelect does not
receive the original MouseEvent, record modifier keys into a ref/state on mouse
down and read that ref inside onSelect to decide between window.open and
router.push.


return (
<Command.Item
key={item.id}
onMouseDown={handleMetaClick}
onSelect={() => {
closePalette();
router.push(targetPath);
if (itemProjectId) openProjectAndScrollToSidebar(itemProjectId);
}}
value={`${key}-${item?.id}-${item.name}-${item.project__identifier ?? ""}-${item.sequence_id ?? ""}`}
className="focus:outline-none"
>
<div className="flex items-center gap-2 overflow-hidden text-custom-text-200">
{currentSection.icon}
<p className="block flex-1 truncate">{currentSection.itemName(item)}</p>
</div>
</Command.Item>
);
})}
</Command.Group>
);
}
Expand Down
Loading