Hide non-matching threads in chat search (#5651)

Fixes #5572. Replaces cmdk's default fuzzy filter with a strict substring-token filter so threads that do not match the query are hidden and the empty state shows. Each item uses its unique thread id as the cmdk value, with the searchable title and preview supplied via keywords.
This commit is contained in:
Ashwin Upadhyay 2026-06-03 17:34:00 +05:30 committed by GitHub
commit e61167b290
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -16,6 +16,21 @@ import { useEffect } from "react";
import { useChatSearchIndex } from "../hooks/use-chat-search-index";
import { useChatSearchStore } from "../stores/chat-search-store";
// cmdk's default fuzzy scorer keeps non-matching rows visible (issue #5572), so
// require every whitespace token to be a substring of the item's keywords.
// `value` is the unique thread id (cmdk selection); title/preview come via keywords.
export function chatSearchFilter(
_value: string,
search: string,
keywords?: string[],
): number {
const query = search.trim().toLowerCase();
if (query === "") return 1;
const haystack = (keywords ?? []).join(" ").toLowerCase();
const tokens = query.split(/\s+/);
return tokens.every((token) => haystack.includes(token)) ? 1 : 0;
}
function formatRelative(createdAt: number): string {
const diff = Date.now() - createdAt;
const day = 86_400_000;
@ -52,7 +67,7 @@ export function ChatSearchDialog() {
className="corner-squircle top-[25%] w-[635px] max-w-[calc(100%-2rem)] gap-0 p-0 sm:max-w-[635px] border-0 ring-0 shadow-[0_10px_34px_rgba(0,0,0,0.12)] dark:border dark:border-border dark:ring-0 dark:shadow-none"
overlayClassName="bg-transparent"
>
<Command className="rounded-4xl p-0">
<Command className="rounded-4xl p-0" filter={chatSearchFilter}>
<div className="flex items-center gap-3 border-b border-border/40 px-4 py-3">
<HugeiconsIcon
icon={SearchIcon}
@ -84,7 +99,8 @@ export function ChatSearchDialog() {
{items.map((item) => (
<CommandPrimitive.Item
key={item.id}
value={`${item.title} ${item.preview}`}
value={item.id}
keywords={[item.title, item.preview]}
onSelect={() => {
navigate({
to: "/chat",