# 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
```typescript
// 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:**
```typescript
// App.tsx
const accessKeyValidated = usePubSub(accessKeyValidatedPubSub);
if (!accessKeyValidated) {
return ;
}
return (
);
```
**Subscribes to:** `accessKeyValidatedPubSub`
### SearchForm (`client/components/Search/Form/`)
**Responsibility:** Query input and search initiation
**PubSub:**
- **Subscribes:** `queryPubSub`, `textSearchStatePubSub`
- **Updates:** `queryPubSub` (on type), triggers `searchAndRespond()` (on submit)
**Logic:**
```typescript
const SearchForm: React.FC = () => {
const [query, setQuery] = usePubSub(queryPubSub);
const searchState = usePubSub(textSearchStatePubSub);
const handleSubmit = () => {
searchAndRespond();
};
return (
);
};
```
### SearchResultsSection (`client/components/Search/Results/`)
**Responsibility:** Display search results (text and images)
**PubSub:**
- **Subscribes:** `textSearchResultsPubSub`, `imageSearchResultsPubSub`, `textSearchStatePubSub`
**Logic:**
```typescript
const SearchResultsSection: React.FC = () => {
const textResults = usePubSub(textSearchResultsPubSub);
const imageResults = usePubSub(imageSearchResultsPubSub);
const searchState = usePubSub(textSearchStatePubSub);
if (searchState.loading) return ;
if (searchState.error) return ;
return (
<>
{settings.enableImageSearch && }
>
);
};
```
### AiResponseSection (`client/components/AiResponse/`)
**Responsibility:** AI response display and chat interface
**PubSub:**
- **Subscribes:** `responsePubSub`, `textGenerationStatePubSub`, `chatMessagesPubSub`
**States:**
- `idle`: No response yet
- `loadingModel`: Downloading/loading AI model
- `awaitingSearchResults`: Waiting for search before generating
- `generating`: Streaming response
- `completed`: Full response received
- `failed`: Error occurred
**Logic:**
```typescript
const AiResponseSection: React.FC = () => {
const response = usePubSub(responsePubSub);
const state = usePubSub(textGenerationStatePubSub);
const messages = usePubSub(chatMessagesPubSub);
return (
);
};
```
### 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:**
```typescript
// 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()`
```typescript
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:
```typescript
const [value, setValue] = usePubSub(channel);
```
### useSearchHistory
Manages search history from IndexedDB:
```typescript
const { searches, groupedSearches, deleteSearch, restoreSearch } = useSearchHistory();
```
### useLocalStorage
Syncs state with localStorage:
```typescript
const [value, setValue] = useLocalStorage('key', defaultValue);
```
## Styling
**Framework:** Mantine UI v8
**Theme Configuration:**
```typescript
// client/main.tsx
```
**Responsive Breakpoints:**
- `xs`: 0-576px
- `sm`: 576-768px
- `md`: 768-992px
- `lg`: 992-1200px
- `xl`: 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:**
```typescript
// Using Mantine's accessibility props
Enter keywords to search
```
## Component Design Principles
1. **Self-Contained:** Each component folder includes component, styles, tests, and types
2. **Single Responsibility:** Components do one thing well
3. **PubSub-First:** Use channels for cross-component communication
4. **Lazy Loading:** Route-level components use `React.lazy()` for code splitting
5. **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