File size: 10,348 Bytes
10d1fd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# 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 <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), triggers `searchAndRespond()` (on submit)

**Logic:**
```typescript
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:**
```typescript
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 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 (
    <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:**
```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
<MantineProvider
  theme={{
    primaryColor: 'blue',
    defaultRadius: 'md',
    fontFamily: 'system-ui, sans-serif',
  }}
>
```

**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
<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

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