diff --git a/packages/ui/src/components/session/project-context/MemorySection.tsx b/packages/ui/src/components/session/project-context/MemorySection.tsx index 45c8e22a..61b4ea42 100644 --- a/packages/ui/src/components/session/project-context/MemorySection.tsx +++ b/packages/ui/src/components/session/project-context/MemorySection.tsx @@ -1,3 +1,4 @@ +import { matchesRankQuery } from '@/lib/search/fuzzySearch'; import React from 'react'; import { toast } from '@/components/ui'; @@ -186,13 +187,10 @@ export const MemorySection: React.FC<{ }; }, [markViewed, viewKey]); - const visibleEntries = React.useMemo(() => { - const needle = query.trim().toLowerCase(); - if (!needle) return entries; - return entries.filter((entry) => ( - entry.title.toLowerCase().includes(needle) || entry.body.toLowerCase().includes(needle) - )); - }, [entries, query]); + const visibleEntries = React.useMemo( + () => entries.filter((entry) => matchesRankQuery([entry.title, entry.body], query)), + [entries, query], + ); const handleDelete = React.useCallback(async (memoryId: string) => { if (!await deleteEntry(scope, memoryId)) { diff --git a/packages/ui/src/components/session/project-context/NotesSection.tsx b/packages/ui/src/components/session/project-context/NotesSection.tsx index a01b4d84..1f85be2c 100644 --- a/packages/ui/src/components/session/project-context/NotesSection.tsx +++ b/packages/ui/src/components/session/project-context/NotesSection.tsx @@ -1,3 +1,4 @@ +import { matchesRankQuery } from '@/lib/search/fuzzySearch'; import React from 'react'; import { toast } from '@/components/ui'; @@ -178,11 +179,10 @@ export const NotesSection: React.FC<{ const saveNoteBody = useProjectContextStore((state) => state.saveNoteBody); const deleteNote = useProjectContextStore((state) => state.deleteNote); - const visibleNotes = React.useMemo(() => { - const needle = query.trim().toLowerCase(); - if (!needle) return notes; - return notes.filter((note) => note.body.toLowerCase().includes(needle)); - }, [notes, query]); + const visibleNotes = React.useMemo( + () => notes.filter((note) => matchesRankQuery([note.body], query)), + [notes, query], + ); // The store keeps the failure reason; without passing it through, every // failure looks identical to the user and tells them nothing about the cause. diff --git a/packages/ui/src/components/session/project-context/PlansSection.tsx b/packages/ui/src/components/session/project-context/PlansSection.tsx index ef15d99e..a5d00fc1 100644 --- a/packages/ui/src/components/session/project-context/PlansSection.tsx +++ b/packages/ui/src/components/session/project-context/PlansSection.tsx @@ -1,3 +1,4 @@ +import { matchesRankQuery } from '@/lib/search/fuzzySearch'; import React from 'react'; import { toast } from '@/components/ui'; @@ -146,11 +147,10 @@ export const PlansSection: React.FC<{ [onTogglePinned, projectRef, t] ); - const visiblePlans = React.useMemo(() => { - const needle = query.trim().toLowerCase(); - if (!needle) return plans; - return plans.filter((plan) => plan.title.toLowerCase().includes(needle)); - }, [plans, query]); + const visiblePlans = React.useMemo( + () => plans.filter((plan) => matchesRankQuery([plan.title], query)), + [plans, query], + ); const handleOpenPlan = React.useCallback( (plan: ProjectPlanLink) => { diff --git a/packages/ui/src/components/session/project-context/TodosSection.tsx b/packages/ui/src/components/session/project-context/TodosSection.tsx index fe028ac4..788dc583 100644 --- a/packages/ui/src/components/session/project-context/TodosSection.tsx +++ b/packages/ui/src/components/session/project-context/TodosSection.tsx @@ -1,3 +1,4 @@ +import { matchesRankQuery } from '@/lib/search/fuzzySearch'; import React from 'react'; import { DndContext, @@ -183,11 +184,10 @@ export const TodosSection: React.FC<{ const completedTodoCount = todos.reduce((count, todo) => count + (todo.completed ? 1 : 0), 0); // Filtering is display-only: every handler above still edits the full list, // so reordering or clearing while a filter is active cannot drop hidden items. - const visibleTodos = React.useMemo(() => { - const needle = query.trim().toLowerCase(); - if (!needle) return todos; - return todos.filter((todo) => todo.text.toLowerCase().includes(needle)); - }, [query, todos]); + const visibleTodos = React.useMemo( + () => todos.filter((todo) => matchesRankQuery([todo.text], query)), + [query, todos], + ); return (
diff --git a/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx b/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx index cfab4159..6aea3643 100644 --- a/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx +++ b/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx @@ -1,3 +1,4 @@ +import { matchesRankQuery } from '@/lib/search/fuzzySearch'; import React from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import type { Session } from '@opencode-ai/sdk/v2'; @@ -483,7 +484,7 @@ function SessionGroupSectionBase(props: Props): React.ReactNode { return true; } - const folderMatches = entry.folder.name.toLowerCase().includes(normalizedSessionSearchQuery); + const folderMatches = matchesRankQuery([entry.folder.name], normalizedSessionSearchQuery); if (folderMatches || entry.nodes.length > 0) { keepByFolderId.set(folderId, true); return true; diff --git a/packages/ui/src/components/session/sidebar/hooks/useSessionGrouping.ts b/packages/ui/src/components/session/sidebar/hooks/useSessionGrouping.ts index c81e23e9..b75ea6dd 100644 --- a/packages/ui/src/components/session/sidebar/hooks/useSessionGrouping.ts +++ b/packages/ui/src/components/session/sidebar/hooks/useSessionGrouping.ts @@ -1,3 +1,4 @@ +import { matchesRankQuery } from '@/lib/search/fuzzySearch'; import React from 'react'; import type { Session } from '@opencode-ai/sdk/v2'; import type { WorktreeMetadata } from '@/types/worktree'; @@ -44,7 +45,7 @@ export const useSessionGrouping = (args: Args) => { } return nodes.flatMap((node) => { - const nodeMatches = buildSessionSearchText(node.session).includes(query); + const nodeMatches = matchesRankQuery([buildSessionSearchText(node.session)], query); if (nodeMatches) { return [node]; } diff --git a/packages/ui/src/components/session/sidebar/hooks/useSessionSidebarSections.ts b/packages/ui/src/components/session/sidebar/hooks/useSessionSidebarSections.ts index ec53e8f5..90658193 100644 --- a/packages/ui/src/components/session/sidebar/hooks/useSessionSidebarSections.ts +++ b/packages/ui/src/components/session/sidebar/hooks/useSessionSidebarSections.ts @@ -1,3 +1,4 @@ +import { matchesRankQuery } from '@/lib/search/fuzzySearch'; import React from 'react'; import type { Session } from '@opencode-ai/sdk/v2'; import type { SessionGroup, SessionNode, GroupSearchData } from '../types'; @@ -161,10 +162,10 @@ export const useSessionSidebarSections = (args: Args) => { section.groups.forEach((group) => { const filteredNodes = filterSessionNodesForSearch(group.sessions, normalizedSessionSearchQuery); const matchedSessionCount = countNodes(filteredNodes); - const groupMatches = buildGroupSearchText(group).includes(normalizedSessionSearchQuery); + const groupMatches = matchesRankQuery([buildGroupSearchText(group)], normalizedSessionSearchQuery); const scopeKey = normalizePath(group.directory ?? null); const scopeFolders = scopeKey ? (foldersMap[scopeKey] ?? []) : []; - const folderNameMatchCount = scopeFolders.filter((folder) => folder.name.toLowerCase().includes(normalizedSessionSearchQuery)).length; + const folderNameMatchCount = scopeFolders.filter((folder) => matchesRankQuery([folder.name], normalizedSessionSearchQuery)).length; result.set(group, { filteredNodes,