Skip to content

Commit 869181f

Browse files
committed
fix(web): align command palette search and shortcut
1 parent 17073d5 commit 869181f

2 files changed

Lines changed: 89 additions & 4 deletions

File tree

apps/web/src/components/ChatView.browser.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,58 @@ describe("ChatView timeline estimator parity (full app)", () => {
11981198
}
11991199
});
12001200

1201+
it("filters command palette results as the user types", async () => {
1202+
const mounted = await mountChatView({
1203+
viewport: DEFAULT_VIEWPORT,
1204+
snapshot: createSnapshotForTargetUser({
1205+
targetMessageId: "msg-user-command-palette-search-test" as MessageId,
1206+
targetText: "command palette search test",
1207+
}),
1208+
configureFixture: (nextFixture) => {
1209+
nextFixture.serverConfig = {
1210+
...nextFixture.serverConfig,
1211+
keybindings: [
1212+
{
1213+
command: "commandPalette.toggle",
1214+
shortcut: {
1215+
key: "k",
1216+
metaKey: false,
1217+
ctrlKey: false,
1218+
shiftKey: false,
1219+
altKey: false,
1220+
modKey: true,
1221+
},
1222+
whenAst: {
1223+
type: "not",
1224+
node: { type: "identifier", name: "terminalFocus" },
1225+
},
1226+
},
1227+
],
1228+
};
1229+
},
1230+
});
1231+
1232+
try {
1233+
const useMetaForMod = isMacPlatform(navigator.platform);
1234+
window.dispatchEvent(
1235+
new KeyboardEvent("keydown", {
1236+
key: "k",
1237+
metaKey: useMetaForMod,
1238+
ctrlKey: !useMetaForMod,
1239+
bubbles: true,
1240+
cancelable: true,
1241+
}),
1242+
);
1243+
1244+
await expect.element(page.getByTestId("command-palette")).toBeInTheDocument();
1245+
await page.getByPlaceholder("Search commands and threads...").fill("settings");
1246+
await expect.element(page.getByText("Open settings")).toBeInTheDocument();
1247+
await expect.element(page.getByText("New thread")).not.toBeInTheDocument();
1248+
} finally {
1249+
await mounted.cleanup();
1250+
}
1251+
});
1252+
12011253
it("creates a fresh draft after the previous draft thread is promoted", async () => {
12021254
const mounted = await mountChatView({
12031255
viewport: DEFAULT_VIEWPORT,

apps/web/src/components/CommandPalette.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ import { type KeybindingCommand } from "@t3tools/contracts";
44
import { useQuery } from "@tanstack/react-query";
55
import { useNavigate } from "@tanstack/react-router";
66
import { MessageSquareIcon, SettingsIcon, SquarePenIcon } from "lucide-react";
7-
import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from "react";
7+
import {
8+
createContext,
9+
useCallback,
10+
useContext,
11+
useDeferredValue,
12+
useMemo,
13+
useState,
14+
type ReactNode,
15+
} from "react";
816
import { useAppSettings } from "../appSettings";
917
import { useHandleNewThread } from "../hooks/useHandleNewThread";
1018
import {
@@ -63,6 +71,10 @@ function iconClassName() {
6371
return "size-4 text-muted-foreground/80";
6472
}
6573

74+
function normalizeSearchText(value: string): string {
75+
return value.trim().toLowerCase().replace(/\s+/g, " ");
76+
}
77+
6678
export function useCommandPalette() {
6779
const context = useContext(CommandPaletteContext);
6880
if (!context) {
@@ -108,6 +120,8 @@ function CommandPaletteDialog() {
108120
function OpenCommandPaletteDialog() {
109121
const navigate = useNavigate();
110122
const { setOpen } = useCommandPalette();
123+
const [query, setQuery] = useState("");
124+
const deferredQuery = useDeferredValue(query);
111125
const { settings } = useAppSettings();
112126
const { activeDraftThread, activeThread, handleNewThread, projects } = useHandleNewThread();
113127
const threads = useStore((store) => store.threads);
@@ -118,7 +132,7 @@ function OpenCommandPaletteDialog() {
118132
[projects],
119133
);
120134

121-
const groups = useMemo<CommandPaletteGroup[]>(() => {
135+
const allGroups = useMemo<CommandPaletteGroup[]>(() => {
122136
const actionItems: CommandPaletteItem[] = [];
123137
if (projects.length > 0) {
124138
const activeProjectTitle =
@@ -248,6 +262,25 @@ function OpenCommandPaletteDialog() {
248262
threads,
249263
]);
250264

265+
const filteredGroups = useMemo(() => {
266+
const normalizedQuery = normalizeSearchText(deferredQuery);
267+
if (normalizedQuery.length === 0) {
268+
return allGroups;
269+
}
270+
271+
return allGroups
272+
.map((group) => ({
273+
...group,
274+
items: group.items.filter((item) => {
275+
const haystack = normalizeSearchText(
276+
[item.label, item.title, item.description ?? ""].join(" "),
277+
);
278+
return haystack.includes(normalizedQuery);
279+
}),
280+
}))
281+
.filter((group) => group.items.length > 0);
282+
}, [allGroups, deferredQuery]);
283+
251284
const executeItem = useCallback(
252285
(item: CommandPaletteItem) => {
253286
setOpen(false);
@@ -268,11 +301,11 @@ function OpenCommandPaletteDialog() {
268301
className="overflow-hidden p-0"
269302
data-testid="command-palette"
270303
>
271-
<Command aria-label="Command palette" items={groups}>
304+
<Command aria-label="Command palette" mode="none" onValueChange={setQuery} value={query}>
272305
<CommandInput placeholder="Search commands and threads..." />
273306
<CommandPanel className="max-h-[min(28rem,70vh)]">
274307
<CommandList>
275-
{groups.map((group) => (
308+
{filteredGroups.map((group) => (
276309
<CommandGroup items={group.items} key={group.value}>
277310
<CommandGroupLabel>{group.label}</CommandGroupLabel>
278311
<CommandCollection>

0 commit comments

Comments
 (0)