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:
1 parent
43cb97ac6d
commit
7d5d1e5b30
4 files changed
+407
-12
No files matched your search
@@ -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;
|
||||
}),
|
||||
});
|
||||
Reference in new issue
Block a user