Spaces:
Running
UI Components
Architecture Overview
MiniSearch uses a PubSub-based reactive architecture. Components subscribe to state changes via channels rather than props drilling or Context API.
PubSub Pattern
// Component subscribes to state
const query = usePubSub(queryPubSub);
// Any module can update state
queryPubSub.set('new query');
// All subscribers automatically re-render
Benefits:
- No prop drilling
- Decoupled components
- Easy to add new subscribers
- Performance: Only subscribers to changed channel re-render
PubSub Channel Reference
All state channels are defined in client/modules/pubSub.ts:
| Channel | Type | Description | Primary Consumers |
|---|---|---|---|
queryPubSub |
string |
Current search query | SearchForm, SearchButton |
responsePubSub |
string |
AI response content (throttled: 12/sec) | AiResponseSection |
settingsPubSub |
Settings |
Application settings | SettingsForm, various components |
textSearchResultsPubSub |
TextSearchResults |
Text search results | SearchResultsSection |
imageSearchResultsPubSub |
ImageSearchResults |
Image search results | ImageResultsSection |
textGenerationStatePubSub |
TextGenerationState |
AI generation state | AiResponseSection, StatusIndicators |
chatMessagesPubSub |
ChatMessage[] |
Chat conversation | ChatSection, ChatInput |
conversationSummaryPubSub |
{id, summary} |
Rolling conversation summary | TextGeneration module |
textSearchStatePubSub |
SearchState |
Search loading/error state | SearchResultsSection, LoadingIndicators |
Component Hierarchy
App
βββ AccessPage (if access keys enabled)
βββ MainPage
βββ SearchForm
β βββ SearchInput
β βββ SearchButton
βββ SettingsDrawer
β βββ AISettings
β βββ SearchSettings
β βββ HistorySettings
βββ SearchResultsSection
β βββ TextResultsList
β β βββ SearchResultCard (ΓN)
β βββ ImageResultsGrid
β βββ ImageResultCard (ΓN)
βββ AiResponseSection
β βββ ResponseContent
β βββ CitationsPanel
β βββ ChatSection
β βββ ChatMessages
β βββ ChatInput
βββ HistoryDrawer
β βββ HistoryList
β βββ SearchStats
β βββ HistoryActions
βββ AnalyticsPanel
Key Components
App (client/components/App/)
Responsibility: Application shell and routing
Logic:
// App.tsx
const accessKeyValidated = usePubSub(accessKeyValidatedPubSub);
if (!accessKeyValidated) {
return <AccessPage />;
}
return (
<MantineProvider>
<MainPage />
</MantineProvider>
);
Subscribes to: accessKeyValidatedPubSub
SearchForm (client/components/Search/Form/)
Responsibility: Query input and search initiation
PubSub:
- Subscribes:
queryPubSub,textSearchStatePubSub - Updates:
queryPubSub(on type), triggerssearchAndRespond()(on submit)
Logic:
const SearchForm: React.FC = () => {
const [query, setQuery] = usePubSub(queryPubSub);
const searchState = usePubSub(textSearchStatePubSub);
const handleSubmit = () => {
searchAndRespond();
};
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button disabled={searchState.loading}>
{searchState.loading ? 'Searching...' : 'Search'}
</button>
</form>
);
};
SearchResultsSection (client/components/Search/Results/)
Responsibility: Display search results (text and images)
PubSub:
- Subscribes:
textSearchResultsPubSub,imageSearchResultsPubSub,textSearchStatePubSub
Logic:
const SearchResultsSection: React.FC = () => {
const textResults = usePubSub(textSearchResultsPubSub);
const imageResults = usePubSub(imageSearchResultsPubSub);
const searchState = usePubSub(textSearchStatePubSub);
if (searchState.loading) return <LoadingSkeleton />;
if (searchState.error) return <ErrorMessage error={searchState.error} />;
return (
<>
<TextResultsList results={textResults} />
{settings.enableImageSearch && <ImageResultsGrid results={imageResults} />}
</>
);
};
AiResponseSection (client/components/AiResponse/)
Responsibility: AI response display and chat interface
PubSub:
- Subscribes:
responsePubSub,textGenerationStatePubSub,chatMessagesPubSub
States:
idle: No response yetloadingModel: Downloading/loading AI modelawaitingSearchResults: Waiting for search before generatinggenerating: Streaming responsecompleted: Full response receivedfailed: Error occurred
Logic:
const AiResponseSection: React.FC = () => {
const response = usePubSub(responsePubSub);
const state = usePubSub(textGenerationStatePubSub);
const messages = usePubSub(chatMessagesPubSub);
return (
<section>
<ResponseContent content={response} />
<GenerationStatus state={state} />
<CitationsPanel results={textResults} />
<ChatSection messages={messages} />
</section>
);
};
SettingsDrawer (client/components/Pages/Main/Menu/)
Responsibility: Application settings UI
Sub-components:
- AISettings: Model selection, inference type, temperature
- SearchSettings: Result limits, image search toggle
- HistorySettings: Retention days, max entries
PubSub:
- Subscribes/Updates:
settingsPubSub(full settings object)
Persistence:
// Settings automatically persisted to localStorage
settingsPubSub.subscribe(newSettings => {
localStorage.setItem('settings', JSON.stringify(newSettings));
});
HistoryDrawer (client/components/Search/History/)
Responsibility: Search history display and management
PubSub:
- Subscribes: History loaded from IndexedDB (not via PubSub, via custom hook)
Hook: useSearchHistory()
const {
searches,
groupedSearches,
deleteSearch,
pinSearch,
restoreSearch,
searchHistory
} = useSearchHistory();
Features:
- Fuzzy search through history
- Date-based grouping (Today, Yesterday, Last Week, etc.)
- Pin/unpin searches
- Restore previous search (re-runs query)
- Analytics: Search frequency, cache hit rate
State Flow Examples
Search Flow
User types query
β
SearchForm updates queryPubSub
β
User submits
β
searchAndRespond() called
β
searchText() updates textSearchStatePubSub β 'loading'
β
SearchResultsSection shows loading skeleton
β
API returns results
β
textSearchResultsPubSub updated with results
β
textSearchStatePubSub β 'idle'
β
SearchResultsSection renders results
AI Response Flow
Search results ready
β
canStartResponding() β true
β
textGenerationStatePubSub β 'loadingModel'
β
AiResponseSection shows "Loading AI model..."
β
Model loaded
β
textGenerationStatePubSub β 'generating'
β
Response tokens stream in
β
responsePubSub updated (throttled 12/sec)
β
AiResponseSection updates content
β
Generation complete
β
textGenerationStatePubSub β 'completed'
Chat Flow
User sends message
β
Message added to chatMessagesPubSub
β
generateChatResponse() called
β
Token budget calculated
β
If overflow: generate summary β conversationSummaryPubSub
β
Inference API called
β
Response tokens stream to responsePubSub
β
Full response added to chatMessagesPubSub
β
Saved to IndexedDB
Custom Hooks
usePubSub
Subscribes to a PubSub channel:
const [value, setValue] = usePubSub(channel);
useSearchHistory
Manages search history from IndexedDB:
const { searches, groupedSearches, deleteSearch, restoreSearch } = useSearchHistory();
useLocalStorage
Syncs state with localStorage:
const [value, setValue] = useLocalStorage('key', defaultValue);
Styling
Framework: Mantine UI v8
Theme Configuration:
// client/main.tsx
<MantineProvider
theme={{
primaryColor: 'blue',
defaultRadius: 'md',
fontFamily: 'system-ui, sans-serif',
}}
>
Responsive Breakpoints:
xs: 0-576pxsm: 576-768pxmd: 768-992pxlg: 992-1200pxxl: 1200px+
Dark Mode:
- Automatic based on system preference
- Toggle available in settings
- All components support dark mode via Mantine
Accessibility
Standards: WCAG 2.1 AA compliance
Features:
- All interactive elements keyboard accessible
- ARIA labels on all buttons and inputs
- Focus management in drawers and modals
- Screen reader announcements for loading states
- Reduced motion support
Implementation:
// Using Mantine's accessibility props
<Button aria-label="Search the web">
<SearchIcon />
</Button>
<TextInput
label="Search query"
aria-describedby="search-help"
/>
<span id="search-help">Enter keywords to search</span>
Component Design Principles
- Self-Contained: Each component folder includes component, styles, tests, and types
- Single Responsibility: Components do one thing well
- PubSub-First: Use channels for cross-component communication
- Lazy Loading: Route-level components use
React.lazy()for code splitting - Error Boundaries: Each major section wrapped in error boundary
File Organization
client/components/
βββ ComponentName/
β βββ index.tsx # Main component
β βββ ComponentName.tsx # Component implementation
β βββ ComponentName.module.css # Scoped styles
β βββ ComponentName.test.tsx # Unit tests
β βββ types.ts # Component-specific types
Related Topics
- Search Module:
docs/search-history.md- History implementation - AI Integration:
docs/ai-integration.md- Text generation flow - State Management:
docs/overview.md- PubSub architecture - Design:
docs/design.md- UI/UX principles