diff --git a/ONBOARDING.md b/ONBOARDING.md
new file mode 100644
index 0000000..07cb788
--- /dev/null
+++ b/ONBOARDING.md
@@ -0,0 +1,704 @@
+# Smart Search — Onboarding Guide
+
+Welcome to the **Smart Search** package (`@portal/smart-search`). This is a
+healthcare-focused semantic search application for querying patient clinical
+documents. It runs inside the Clinical Portal shell via module federation.
+
+---
+
+## Table of Contents
+
+- [What is Smart Search?](#what-is-smart-search)
+- [Monorepo Context](#monorepo-context)
+- [Getting Started](#getting-started)
+- [Architecture Overview](#architecture-overview)
+- [Component Hierarchy](#component-hierarchy)
+- [State Management](#state-management)
+- [API Layer](#api-layer)
+- [Data Flow](#data-flow)
+- [Directory Structure](#directory-structure)
+- [Key Files Reference](#key-files-reference)
+- [Healthcare Domain Concepts](#healthcare-domain-concepts)
+- [Token Size Estimates](#token-size-estimates)
+- [Common Development Tasks](#common-development-tasks)
+- [Testing](#testing)
+- [Troubleshooting](#troubleshooting)
+
+---
+
+## What is Smart Search?
+
+Smart Search enables clinicians to semantically search across a patient's
+clinical documents. Key features:
+
+- **Free-text search** with match highlighting across documents
+- **SNOMED disorder concept** filtering (passage concepts)
+- **Timeline visualization** of document distribution over time (D3 bar charts)
+- **Category/Author/Service/Date** filtering
+- **Infinite-scroll** paginated results
+- **Document viewer** (iframe-based) for reading full documents
+- **Privacy/Break-the-Seal** support for protected health information (PHI)
+
+---
+
+## Monorepo Context
+
+Smart Search lives inside a Lerna + Yarn Workspaces monorepo. Understanding
+where it fits is important:
+
+```mermaid
+graph TB
+ subgraph "Monorepo Root"
+ ROOT["smart-search-ui/"]
+ end
+
+ ROOT --> PKG["packages/"]
+ ROOT --> DEPLOY["deployments/"]
+
+ subgraph "Packages"
+ PKG --> SS["smart-search ← YOU ARE HERE"]
+ PKG --> CC["common-components"]
+ PKG --> CORE["component-core"]
+ PKG --> STYLED["styled"]
+ PKG --> HOST["host-environment"]
+ PKG --> MLC["module-loader-core"]
+ PKG --> MOCK["mock-apis"]
+ PKG --> E2E["portal-e2e"]
+ end
+
+ subgraph "Deployment"
+ DEPLOY --> SEM["semantic-search/
Webpack → CDN"]
+ end
+
+ SS -->|"imports"| CC
+ SS -->|"imports"| CORE
+ SS -->|"imports"| STYLED
+ SEM -->|"bundles"| SS
+```
+
+| Package | What it provides to Smart Search |
+| -------------------- | -------------------------------------------------------- |
+| `component-core` | `bootstrap()`, Redux store setup, i18n, Sentry, RTK |
+| `common-components` | `PrivacyModal`, shared UI utilities |
+| `styled` | `Box`, `Flex`, `Typography`, `css` — MUI + SC wrappers |
+| `host-environment` | Base styles (Blueprint, Font Awesome) |
+| `mock-apis` | Development API server (`connect-api-mocker`) |
+| `portal-e2e` | Dev server that hosts the app + Playwright/Cypress tests |
+| `module-loader-core` | Dynamic module loading in production |
+
+---
+
+## Getting Started
+
+```bash
+# 1. Install all dependencies (from repo root)
+yarn install
+
+# 2. Start the development server (hosts smart-search + mock APIs)
+yarn start
+
+# 3. Open http://localhost:18443/en/home in your browser
+
+# 4. Run unit tests for smart-search only
+npx jest packages/smart-search
+
+# 5. Start Storybook for component development
+yarn storybook
+
+# 6. Lint a specific file after editing
+npx eslint packages/smart-search/src/path/to/file.js
+```
+
+---
+
+## Architecture Overview
+
+Smart Search follows a standard React + Redux Toolkit pattern with RTK Query for
+API calls. It is bootstrapped as a **module federation remote** via
+`component-core`'s `bootstrap()` function.
+
+```mermaid
+graph LR
+ subgraph "Bootstrap Layer"
+ MOUNT["mount()"] --> BOOTSTRAP["component-core bootstrap"]
+ BOOTSTRAP --> REDUX["Redux Store"]
+ BOOTSTRAP --> I18N["i18n (react-intl)"]
+ BOOTSTRAP --> SENTRY["Sentry"]
+ BOOTSTRAP --> THEME["Theme Provider"]
+ end
+
+ REDUX --> APP_SLICE["application slice"]
+ REDUX --> DOC_SLICE["documents slice"]
+ REDUX --> RTK_CACHE["RTK Query cache"]
+
+ BOOTSTRAP --> REACT["React Component Tree"]
+ REACT --> APP[""]
+```
+
+**How the app boots:**
+
+1. `deployments/semantic-search/` bundles the package via Webpack
+2. `src/index.js` exports `mount()` — the module federation entry point
+3. `mount()` calls `bootstrap()` from `component-core` with reducers,
+ middleware, and theme
+4. The `` component initializes by dispatching parallel API calls:
+ - `searchForDocuments()` — fetch initial document set
+ - `getAllPassageConcepts()` — load SNOMED disorder concepts
+ - `getFilterValues()` — load available filter options (authors, categories,
+ services)
+
+---
+
+## Component Hierarchy
+
+The UI is organized as a **three-column layout**:
+
+```
+┌──────────────────────────────────────────────────────────────────┐
+│ Smart Search │
+├────────────┬───────────────────────────┬─────────────────────────┤
+│ │ ControlBar │ │
+│ │ ┌──────────┬────────────┐ │ │
+│ │ │TextFilter│FilterPopov.│ │ │
+│ Category │ └──────────┴────────────┘ │ │
+│ Viewer │ FilterSummary badges │ Document Viewer │
+│ ├───────────────────────────┤ (iframe) │
+│ ┌────────┐ │ Timeline Bars (D3) │ │
+│ │ICD-10 │ │ ▇▇▇ ▇▇ ▇▇▇▇ ▇▇ ▇▇▇ │ │
+│ │Groups │ ├───────────────────────────┤ ┌───────────────────┐ │
+│ │ │ │ Search Results │ │ Full document │ │
+│ │ ○ Tag1 │ │ ┌─────────────────────┐ │ │ content rendered │ │
+│ │ ○ Tag2 │ │ │ DocumentCard │ │ │ in iframe │ │
+│ │ ○ Tag3 │ │ │ title, author, ... │ │ │ │ │
+│ │ │ │ │ [Passages tags] │ │ │ │ │
+│ └────────┘ │ └─────────────────────┘ │ └───────────────────┘ │
+│ │ ┌─────────────────────┐ │ │
+│ │ │ DocumentCard ... │ │ │
+│ │ └─────────────────────┘ │ │
+│ │ ∞ infinite scroll │ │
+└────────────┴───────────────────────────┴─────────────────────────┘
+```
+
+```mermaid
+graph TD
+ subgraph "App (components/index.js)"
+ direction TB
+ APP[""]
+ end
+
+ APP --> CV["CategoryViewer
Left sidebar — ICD-10 concept groups"]
+ APP --> CC["CenterColumn"]
+ APP --> DV["DocumentViewer
Right panel — iframe"]
+
+ CC --> CB["ControlBar"]
+ CC --> TB["TimelineBars
D3 stacked bar chart"]
+ CC --> SR["SearchResults
Infinite scroll list"]
+
+ CB --> TF["TextFilter
react-select creatable"]
+ CB --> FP["FilterPopover
Date, Author, Category, Service"]
+ CB --> FS["FilterSummary
Active filter badges"]
+
+ SR --> DC["DocumentCard × N"]
+ DC --> PAS["Passages
SNOMED concept tags"]
+
+ TB --> HINT["Hint Popover
Top 10 disorders on hover"]
+```
+
+### Component Descriptions
+
+| Component | File | Purpose |
+| ------------------ | -------------------------------------------- | ----------------------------------------------------------------- |
+| **App** | `components/index.js` | Root layout, initialization, privacy modal integration |
+| **CategoryViewer** | `components/category-viewer/index.js` | Left sidebar, SNOMED disorder concepts grouped by ICD-10 |
+| **ControlBar** | `components/control-bar/index.js` | Search input + filter UI + timeline toggle + error display |
+| **TextFilter** | `components/control-bar/text-filter.js` | Creatable select for search terms + passage concept autocomplete |
+| **FilterPopover** | `components/control-bar/filter-popover.js` | Date range picker + author/category/service dropdowns |
+| **FilterSummary** | `components/control-bar/filter-summary.js` | Active filter badges display with edit/remove/clear-all |
+| **TimelineBars** | `components/timeline-bars/index.js` | D3-based stacked bar chart of document distribution over time |
+| **Hint** | `components/timeline-bars/hint.js` | Hover tooltip showing top 10 disorders for a time bucket |
+| **SearchResults** | `components/search-results/index.js` | Infinite-scroll result list with sort, count, and privacy banners |
+| **DocumentCard** | `components/search-results/document-card.js` | Individual result card: metadata, match highlights, passages |
+| **Passages** | `components/search-results/passages.js` | SNOMED concept tags for a document (main + supplemental) |
+| **DocumentViewer** | `components/document-viewer.js` | Iframe displaying full document content with BTS privacy |
+
+---
+
+## State Management
+
+The Redux store has three top-level slices:
+
+```mermaid
+graph TD
+ subgraph "Redux Store"
+ direction TB
+ AS["application
Filters, search options, search result tracking"]
+ DS["documents
Document entities, privacy, selection"]
+ RTKQ["api (RTK Query)
Request cache & lifecycle"]
+ end
+```
+
+### Application Slice (`store/application/slice.js`)
+
+Manages search state, filters, and tracks which search results map to which URL.
+
+```javascript
+// State shape:
+{
+ activeSearchId: string | null, // URL-based search identifier (bookmarkable)
+ availableFilters: {
+ authorFilters: [], // From API
+ categoryFilters: [], // From API
+ serviceFilters: [], // From API
+ },
+ activeAuthor: 'All', // Current filter selections
+ activeCategory: 'All',
+ activeService: 'All',
+ activeTimeFilter: [null, null], // [Date | null, Date | null]
+ sortOrder: 'dateTime', // dateTime | author | title | relevance
+ searchOptions: [], // Array of free-text + passage concept terms
+ height: undefined, // Container height for responsive layout
+
+ // Entity Adapter — search result collections keyed by URL:
+ entities: {
+ 'https://...?sort=-dateTime': {
+ id: 'https://...',
+ containsMatches: false,
+ documentIds: ['doc1', 'doc2'],
+ minDocumentIds: ['doc1', 'doc2', 'doc3'],
+ next: 'https://...?cursor=...',
+ }
+ },
+ ids: ['https://...?sort=-dateTime']
+}
+```
+
+**Key concept**: `activeSearchId` is a URL string that acts as a unique
+identifier for the current search. Filter changes update this URL. The entity
+adapter stores document ID lists keyed by these URLs, so switching back to a
+previous search can reuse cached results.
+
+### Documents Slice (`store/documents/slice.js`)
+
+Stores actual document data entities and privacy state.
+
+```javascript
+// State shape:
+{
+ entities: {
+ 'doc-uuid-1': {
+ id: 'doc-uuid-1',
+ title: 'Discharge Summary',
+ author: 'Dr. Smith',
+ category: 'Clinical Note',
+ service: 'Cardiology',
+ dateTime: '2025-01-15T10:30:00Z',
+ passageConcepts: [...], // SNOMED concepts found in document
+ match: { ... }, // Highlighted match data (only for searches)
+ _links: { ... }, // HAL links
+ }
+ },
+ ids: ['doc-uuid-1', ...],
+ btsLink: '', // "Break The Seal" privacy link
+ privacyLevel: 'NONE', // NONE | LIST_MORE
+ privacyResolutionError: false,
+ selectedDocumentId: null, // Currently viewed document
+}
+```
+
+**Two document modes:**
+
+1. **Minimal** — only IDs and timestamps (for counting/timeline). Accept header:
+ `application/vnd.orchestral.document.index.minimal.1_0+json`
+2. **Full** — complete content with match highlights. Accept header:
+ `application/vnd.orchestral.document.index.1_0+json`
+
+### Selectors Pattern
+
+Selectors follow a two-tier pattern:
+
+```
+store/application/selectors.js — Application-domain selectors + hooks
+store/documents/selectors.js — Document-domain selectors + hooks
+store/selectors.js — Re-exports both + cross-domain selectors
+```
+
+Each selector file exports both raw selectors (for thunks) and `use*` React
+hooks (for components). Example:
+
+```javascript
+// Raw selector (used in thunks/actions)
+export const selectActiveCategory = (state) => state.application.activeCategory;
+
+// React hook (used in components)
+export const useActiveCategory = () => useSelector(selectActiveCategory);
+```
+
+---
+
+## API Layer
+
+RTK Query endpoints are defined in `store/api.js`. The API communicates with a
+**Document Index** backend.
+
+### Endpoints
+
+| Endpoint | Method | URL Pattern | Accept Header | Purpose |
+| ---------------------- | ------ | ------------------------------------------------------------ | ------------- | --------------------------------------- |
+| `getFilterValues` | GET | `/document-index/patients/{path}/documents/filter-values` | full | Available authors, categories, services |
+| `getPassageConcepts` | GET | `/document-index/patients/{path}/documents/passage-concepts` | full | SNOMED disorder concepts (paginated) |
+| `getDocumentIds` | GET | `/document-index/patients/{path}/documents/` | minimal | Document IDs only (fast) |
+| `getDocuments` | GET | `/document-index/patients/{path}/documents/` | full/minimal | Full documents or paginated |
+| `getSpecificDocuments` | GET | `/document-index/patients/{path}/documents/?documentId=...` | full | Specific documents by ID |
+
+### Custom API Pattern
+
+This app uses `createInitiateWithCallbacks` from `component-core` which wraps
+RTK Query endpoints with `onSuccess`/`onError` callback patterns. This enables
+chaining async flows in thunks:
+
+```javascript
+const initiate = createInitiateWithCallbacks(api.endpoints.getDocuments);
+dispatch(
+ initiate({
+ ...args,
+ onSuccess: (response) => {
+ /* handle success */
+ },
+ onError: () => {
+ /* handle error */
+ },
+ }),
+);
+```
+
+---
+
+## Data Flow
+
+### Initialization Sequence
+
+```mermaid
+sequenceDiagram
+ participant App as mount
+ participant Actions as Thunk Actions
+ participant API as Backend API
+ participant Store as Redux Store
+
+ App->>Actions: initializeApplication()
+ Actions->>Store: resetApiState() + clearDocuments()
+
+ par Three parallel requests
+ Actions->>API: GET /documents/ (minimal)
+ API-->>Store: setDocuments(IDs)
+ Note right of API: Recursively pages all IDs
+ and
+ Actions->>API: GET /passage-concepts
+ API-->>Store: setPassageConcepts()
+ Note right of API: Recursively pages all concepts
+ and
+ Actions->>API: GET /filter-values
+ API-->>Store: setFilters()
+ end
+
+ Store-->>App: Re-render with data
+```
+
+### Search Flow
+
+When a user types a search term or applies a filter:
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant UI as Component
+ participant Actions as actions.js
+ participant Store as Redux Store
+ participant API as Backend
+
+ User->>UI: Enter search term
+ UI->>Actions: applySearchTerm({ value: "diabetes" })
+ Actions->>Store: setSearchOptions([...terms])
+ Actions->>Actions: searchForDocuments()
+
+ Actions->>API: getDocumentIds(q=diabetes, minimal)
+ API-->>Store: Document IDs for timeline
+
+ Actions->>API: getDocuments(q=diabetes, full)
+ API-->>Store: Documents with match highlights
+
+ Store-->>UI: Re-render results with highlights
+```
+
+### Search Optimization Logic
+
+The `searchForDocumentsIfNecessary()` thunk is critical — it decides whether a
+new API call is needed:
+
+- **Skips search** when: switching sort order or filters if all documents are
+ already known (cached)
+- **Forces search** when: any free-text or passage concept terms are active
+ (match highlights are volatile)
+
+### Two-phase Document Fetching
+
+For non-search browsing (no search terms):
+
+1. **Phase 1**: Fetch all document IDs (minimal, fast, paginated) → populates
+ timeline bars
+2. **Phase 2**: Fetch first page of full documents → populates result cards
+3. **Phase 3** (if needed): Fetch specific unknown documents by ID
+
+For searches (with search terms):
+
+1. **Phase 1**: Fetch all document IDs (minimal) → timeline bars
+2. **Phase 2**: Fetch full documents with match data → result cards with
+ highlights
+
+---
+
+## Directory Structure
+
+```
+packages/smart-search/
+├── package.json # Dependencies and scripts
+├── src/
+│ ├── index.js # mount() entry point (bootstrap)
+│ ├── constants.js # ICD-10 groups, page sizes, dimensions
+│ ├── types.js # PropTypes definitions
+│ │
+│ ├── components/
+│ │ ├── index.js # — main 3-column layout
+│ │ ├── concept-tag.js # Reusable SNOMED concept pill
+│ │ ├── document-viewer.js # Right panel iframe viewer
+│ │ ├── loading-overlay.js # Spinner overlay
+│ │ ├── non-ideal-states.js # Empty/loading states
+│ │ ├── pending-retrieval.js # Pending state
+│ │ │
+│ │ ├── control-bar/ # Search & filter controls
+│ │ │ ├── index.js # Container component
+│ │ │ ├── text-filter.js # Search input (react-select)
+│ │ │ ├── filter-popover.js # Advanced filter modal
+│ │ │ └── filter-summary.js # Active filter badges
+│ │ │
+│ │ ├── search-results/ # Document result list
+│ │ │ ├── index.js # Infinite scroll container
+│ │ │ ├── document-card.js # Single result card
+│ │ │ └── passages.js # Concept tags display
+│ │ │
+│ │ ├── timeline-bars/ # D3 timeline visualization
+│ │ │ ├── index.js # Stacked bar chart
+│ │ │ ├── hint.js # Hover tooltip
+│ │ │ ├── skeleton.js # Loading skeleton
+│ │ │ └── utils.js # D3 scale/bucketing helpers
+│ │ │
+│ │ └── category-viewer/ # Left sidebar
+│ │ ├── index.js # Concept category sidebar
+│ │ ├── concept-tag-section.js # Category group section
+│ │ └── utils.js # Grouping utilities
+│ │
+│ ├── store/ # Redux state management
+│ │ ├── actions.js # Thunk action creators (778 lines)
+│ │ ├── actions.test.js # Action tests
+│ │ ├── api.js # RTK Query endpoint definitions
+│ │ ├── middleware.js # RTK Query middleware
+│ │ ├── reducer.js # Root reducer combiner
+│ │ ├── selectors.js # Cross-domain selectors + hooks
+│ │ ├── application/
+│ │ │ ├── slice.js # Application state (filters, search)
+│ │ │ └── selectors.js # Application selectors + hooks
+│ │ └── documents/
+│ │ ├── slice.js # Document entities + privacy
+│ │ └── selectors.js # Document selectors + hooks
+│ │
+│ ├── lib/ # Utility functions
+│ │ ├── documents.js # Document enhancement/processing
+│ │ ├── format-duration.js # Duration → human string
+│ │ ├── get-filters.js # Filter extraction helpers
+│ │ ├── utils.js # URL manipulation, sanitization
+│ │ └── utils.test.js # Utility tests
+│ │
+│ └── theme/ # Theming
+│ ├── index.js # Theme configuration
+│ └── colors.js # Color palette constants
+```
+
+---
+
+## Key Files Reference
+
+**Start here** when working on a feature:
+
+| Task | Read these files first |
+| ----------------------- | --------------------------------------------------------------------------- |
+| Add a new filter | `store/application/slice.js`, `store/actions.js`, `components/control-bar/` |
+| Modify search behavior | `store/actions.js` (thunks), `store/api.js` (endpoints) |
+| Change document display | `components/search-results/document-card.js` |
+| Modify timeline bars | `components/timeline-bars/index.js`, `timeline-bars/utils.js` |
+| Add a new selector | `store/selectors.js` or domain-specific `store/*/selectors.js` |
+| Change concept tags | `components/concept-tag.js`, `components/search-results/passages.js` |
+| Privacy/BTS changes | `components/document-viewer.js`, `store/documents/slice.js` |
+| Styling/theming | `theme/colors.js`, `theme/index.js` |
+
+---
+
+## Healthcare Domain Concepts
+
+### SNOMED Disorders & Passage Concepts
+
+Documents are analyzed by an NLP pipeline that extracts **SNOMED CT** medical
+concepts (disorders/conditions) from the text. These are called **passage
+concepts**. They are:
+
+- Grouped by **ICD-10 categories** (e.g., "Diseases of the respiratory system")
+- Displayed in the **CategoryViewer** sidebar and on **DocumentCards**
+- Toggleable as search filters
+
+### ICD-10 Groups
+
+The app uses 21+ ICD-10 disease classification groups defined in `constants.js`
+(e.g., "Infectious diseases", "Neoplasms", "Circulatory system diseases"). Each
+passage concept is mapped to one of these groups.
+
+### Privacy / Break The Seal (BTS)
+
+Clinical documents may be protected by privacy levels:
+
+- **NONE** — full access
+- **LIST_MORE** — some documents hidden; user sees a "List More" banner and can
+ request access via a Break The Seal (BTS) modal
+- **Resolution Error** — privacy service couldn't resolve; error banner shown
+
+The `PrivacyModal` from `common-components` handles the BTS workflow. After
+breaking the seal, the entire application re-initializes.
+
+---
+
+## Token Size Estimates
+
+Approximate **LLM token counts** for each part of the smart-search package
+(estimated at ~3.5 characters per token for JavaScript code):
+
+### By Area
+
+| Area | Lines | Bytes | ~Tokens | Notes |
+| --------------------------------- | ----- | ------- | ------- | ----------------------------- |
+| **Store (top-level)** | 1,361 | 38,855 | ~11,100 | actions, api, selectors, etc. |
+| **Store — application/** | 298 | 10,303 | ~2,940 | slice + selectors |
+| **Store — documents/** | 165 | 6,024 | ~1,720 | slice + selectors |
+| **Components (top-level)** | 484 | 13,073 | ~3,735 | index, viewers, overlays |
+| **Components — control-bar/** | 1,636 | 59,047 | ~16,870 | Largest area (incl. tests) |
+| **Components — search-results/** | 670 | 25,708 | ~7,345 | Cards, passages, stories |
+| **Components — timeline-bars/** | 907 | 31,780 | ~9,080 | D3 charts, stories, tests |
+| **Components — category-viewer/** | 373 | 11,215 | ~3,205 | ICD groups sidebar |
+| **Lib (utilities)** | 274 | 9,209 | ~2,630 | documents, utils, filters |
+| **Theme** | 76 | 1,609 | ~460 | Colors, theme config |
+| **Entry + types + constants** | 264 | 7,735 | ~2,210 | index.js, constants, types |
+| **TOTAL** | 6,767 | 214,558 | ~61,300 | Entire smart-search/src |
+
+### Individual Key Files
+
+| File | Lines | ~Tokens | Role |
+| ------------------------------------------ | ----- | ------- | ----------------------------- |
+| `store/actions.js` | 778 | ~7,800 | **Largest** — all thunk logic |
+| `components/control-bar/index.js` | 395 | ~3,950 | Search & filter container |
+| `components/timeline-bars/index.js` | 436 | ~4,360 | D3 chart rendering |
+| `components/search-results/index.js` | 318 | ~3,180 | Result list + infinite scroll |
+| `components/control-bar/filter-summary.js` | 266 | ~2,660 | Filter badge display |
+| `components/control-bar/text-filter.js` | 247 | ~2,470 | Search input |
+| `store/api.js` | 225 | ~2,250 | RTK Query config |
+| `store/application/slice.js` | 220 | ~2,200 | Application state |
+| `components/control-bar/filter-popover.js` | 224 | ~2,240 | Advanced filter modal |
+
+### Takeaways
+
+- The **store/actions.js** file (~7,800 tokens) is the single most complex file
+ — it orchestrates all search logic. Budget extra context when working with it.
+- The **control-bar** directory (~16,870 tokens) is the largest component area
+ due to complex UI interactions.
+- The **entire package** (~61,300 tokens) fits within most LLM context windows
+ but not all at once with conversation history.
+- For focused work, individual directories (2,000–9,000 tokens) fit easily in
+ context.
+
+---
+
+## Common Development Tasks
+
+### Adding a New Filter
+
+1. Add filter state to `store/application/slice.js` (initial state + reducer)
+2. Create a selector in `store/application/selectors.js`
+3. Add the filter to the search query in
+ `store/actions.js → searchForDocuments()`
+4. Add UI controls in `components/control-bar/`
+5. Add the filter badge in `components/control-bar/filter-summary.js`
+
+### Modifying Search Behavior
+
+All search logic lives in `store/actions.js`. Key thunks:
+
+- `searchForDocuments()` — full search pipeline (lines ~305–435)
+- `searchForDocumentsIfNecessary()` — cache-aware search (lines ~440–470)
+- `getNextDocuments()` — pagination (lines ~275–300)
+- `toggleConcept()` — concept selection toggle (lines ~535–585)
+
+### Adding a New Component
+
+Follow existing patterns:
+
+1. Create component in appropriate `components/` subdirectory
+2. Use `useDispatchActions()` hook for actions
+3. Use `use*` selector hooks for state
+4. Add PropTypes for all props
+5. Create `*.stories.js` for Storybook
+6. Create `*.test.js` using `@testing-library/react` + `composeStories`
+
+### Styling Guide
+
+Priority order:
+
+1. **Blueprint** — buttons, popovers, selects, cards (`@blueprintjs/core`)
+2. **Styled (MUI)** — `Box`, `Flex`, `Typography`, `css` from `@portal/styled`
+3. **Styled-components** — custom `styled()` components for unique needs
+4. **Theme colors** — import from `../theme/colors.js`, never hardcode
+
+---
+
+## Testing
+
+```bash
+# Run smart-search unit tests only
+npx jest packages/smart-search
+
+# Run a specific test file
+npx jest packages/smart-search/src/store/actions.test.js
+
+# Run Playwright E2E tests
+AI_AGENT_MODE=true yarn playwright test --project=smart-search
+
+# Lint a file after editing
+npx eslint packages/smart-search/src/path/to/file.js
+npx prettier --check packages/smart-search/src/path/to/file.js
+```
+
+**Testing conventions:**
+
+- Use `@testing-library/react` for component tests
+- Use Storybook stories + `composeStories` for story-driven testing
+- Avoid `data-testid`; prefer user-centric locators
+- Mock APIs via `connect-api-mocker` (in `mock-apis` package)
+- High coverage threshold: **98%**
+
+---
+
+## Troubleshooting
+
+| Problem | Solution |
+| --------------------------------- | ----------------------------------------------------------------- |
+| App shows spinner forever | Check mock-apis are running; check browser console for CORS |
+| Tests fail after store change | Run `npx jest packages/smart-search --clearCache` |
+| Storybook doesn't reflect changes | Restart Storybook; check for import path issues |
+| Lint errors on save | Run `npx eslint --fix` |
+| Privacy modal not showing | Check `_links` in API response for BTS link presence |
+| Timeline bars empty | Check `getDocumentIds` response; ensure documents have `dateTime` |
+| "Module not found" errors | Run `yarn install` from repo root |