feat(03-01): add SearchResults component with highlighting, scope toggle, and CommunicationHub integration

- Create SearchResults component with virtualized list, term highlighting via <mark>, keyboard navigation, loading/empty states
- Add search scope toggle (All Channels / This Channel) with radio group accessibility
- Integrate debounced (300ms) IPC search into CommunicationHub with stale response discard via requestId
- Update CommunicationSlice with search state (query, scope, results, loading, requestId)
- Modify FilterBar to accept scopeToggle prop and Escape key handler
- AnimatePresence cross-fade between SearchResults and MessageList views
This commit is contained in:
ZacharyZhang-NY committed 2026-03-22 14:34:35 -04:00
1 parent 43cb97ac6d
commit 7d5d1e5b30
4 files changed
+407 -12

No files matched your search

+43 -5
View File
@@ -1,10 +1,18 @@
import type { StateCreator } from "zustand";
import type { AgentMessageRecord } from "@shared/types";
import type { AppState } from "../store";
export interface CommunicationSlice {
// Placeholder for future message state -- this slice will be populated
// during component decomposition. The communication component currently
// manages its own internal state.
searchQuery: string;
searchScope: "global" | "channel";
searchResults: AgentMessageRecord[];
searchLoading: boolean;
searchRequestId: number;
setSearchQuery: (query: string) => void;
setSearchScope: (scope: "global" | "channel") => void;
setSearchResults: (results: AgentMessageRecord[], requestId: number) => void;
setSearchLoading: (loading: boolean) => void;
clearSearch: () => void;
}
export const createCommunicationSlice: StateCreator<
@@ -12,6 +20,36 @@ export const createCommunicationSlice: StateCreator<
[["zustand/immer", never]],
[],
CommunicationSlice
> = () => ({
// Empty state for now
> = (set) => ({
searchQuery: "",
searchScope: "global",
searchResults: [],
searchLoading: false,
searchRequestId: 0,
setSearchQuery: (query) =>
set((state) => {
state.searchQuery = query;
state.searchRequestId += 1;
}),
setSearchScope: (scope) =>
set((state) => {
state.searchScope = scope;
}),
setSearchResults: (results, requestId) =>
set((state) => {
if (requestId === state.searchRequestId) {
state.searchResults = results;
state.searchLoading = false;
}
}),
setSearchLoading: (loading) =>
set((state) => {
state.searchLoading = loading;
}),
clearSearch: () =>
set((state) => {
state.searchQuery = "";
state.searchResults = [];
state.searchLoading = false;
}),
});