;
+ }
+}
+
+// Use without type casts
+const idx = window.lunrIndex;
+const doc = window.docsData?.[result.ref];
+```
+
+**Option B: Data attributes** (cleaner, requires template changes)
+
+```go
+// In Go template
+
+```
+
+```typescript
+// In TypeScript
+const container = document.getElementById('search-container')!;
+const searchIndex = JSON.parse(container.dataset.searchIndex!);
+const docsData = JSON.parse(container.dataset.docsData!);
+```
+
+**Option C: Loader module** (best, most maintainable)
+
+```typescript
+// web/src/search-data.ts (NEW FILE)
+let lunrIndex: any = null;
+let docsData: Record = {};
+
+export async function loadSearchData() {
+ const response = await fetch('/docs/search-index.json');
+ const data = await response.json();
+
+ // Build lunr index
+ lunrIndex = lunr(function() {
+ this.use(lunr.flex);
+ this.ref('id');
+ this.field('title', {boost: 10});
+ this.field('content', {boost: 1});
+ data.forEach(doc => this.add(doc));
+ });
+
+ docsData = data;
+}
+
+export { lunrIndex, docsData };
+```
+
+```typescript
+// web/src/docs.ts
+import { lunrIndex, docsData, loadSearchData } from './search-data';
+
+document.addEventListener('DOMContentLoaded', async () => {
+ await loadSearchData();
+ initializeDocsSearch();
+});
+
+function performDocsSearch(query: string) {
+ // Use imported data, no globals
+ const idx = lunrIndex;
+ const doc = docsData[result.ref];
+}
+```
+
+#### Type 3: Function Exports for HTML (Should use event listeners)
+
+**Example**: `(window as any).toggleSidebar = toggleSidebar;`
+
+**Why**: Template uses `onclick="toggleSidebar()"`
+
+**Fix**: Replace `onclick` attributes with `addEventListener`
+
+```typescript
+// Remove: (window as any).toggleSidebar = toggleSidebar;
+
+// Add event listener
+document.addEventListener('click', (e) => {
+ const button = e.target.closest('[data-action="toggle-sidebar"]');
+ if (button) toggleSidebar();
+});
+```
+
+```html
+
+
+```
+
+### Recommended Approach
+
+**Quick fix** (Types 1 + 2A):
+1. Import libraries directly (`import lunr from 'lunr'`)
+2. Add proper TypeScript declarations for SSR globals
+3. Keep function exports for now (can fix later)
+
+**Best practice** (All types):
+1. Import libraries directly
+2. Use Option C (loader module) for SSR data
+3. Replace all `onclick` with `addEventListener`
+
+---
+
+## Import Strategy
+
+### Principle: Import Where Used
+
+**❌ DON'T**: Import everything in main.ts
+
+```typescript
+// main.ts - DON'T DO THIS
+import './collections';
+import './docs';
+import 'htmx.org';
+import 'alpinejs';
+import 'lunr';
+import 'highlight.js/lib/common';
+// ... 29 more imports
+```
+
+This overwhelms main.ts and makes it hard to see what each file needs.
+
+**✅ DO**: Import in files that use it
+
+```typescript
+// main.ts - Keep simple!
+import './collections';
+import './toast';
+import './theme';
+// ... all page modules (29 total)
+```
+
+```typescript
+// web/src/collections.ts - Import what YOU use
+import Alpine from 'alpinejs';
+
+// Use Alpine for checkbox state
+document.addEventListener('alpine:init', () => {
+ Alpine.data('bookSelection', () => ({
+ selectedBooks: new Set(),
+ toggle(id: string) { /* ... */ }
+ }));
+});
+```
+
+```typescript
+// web/src/docs.ts - Import what YOU use
+import lunr from 'lunr';
+import hljs from 'highlight.js/lib/common';
+import 'highlight.js/lib/languages/bash';
+import 'highlight.js/lib/languages/go';
+import 'highlight.js/lib/languages/sql';
+import 'highlight.js/lib/languages/http';
+import 'highlight.js/lib/languages/json';
+
+// Use lunr for search
+const idx = lunr(function() { /* ... */ });
+
+// Make hljs available globally (needed for docs template)
+if (typeof window !== 'undefined') {
+ (window as any).hljs = hljs;
+}
+```
+
+### ESBuild Handles the Rest
+
+When you run `esbuild web/src/main.ts --bundle`:
+
+1. Starts at `main.ts`
+2. Follows all imports through all modules
+3. Collects all dependencies
+4. Bundles everything into `main.js`
+5. Handles deduplication automatically
+6. Tree-shakes unused code
+
+**Result**: Single `main.js` with everything you need, nothing you don't.
+
+---
+
+## Template Updates
+
+### Current State
+
+Templates have multiple script tags:
+
+```html
+
+
+
+
+
+
+
+
+
+
+```
+
+### Update to Single Script Tag
+
+```html
+
+
+
+
+
+
+
+
+```
+
+### Files to Update
+
+**1. collections.templ**
+- Remove ``
+
+**3. All other templates** (25 files)
+- Replace all ``
+
+### Pattern
+
+**Before**:
+```html
+
+
+
+
+```
+
+**After**:
+```html
+
+```
+
+---
+
+## Build and Test
+
+### Step 1: Install Dependencies
+
+```bash
+cd /home/nymusicman/Code/bookhoard
+npm install
+```
+
+**Expected output**:
+- `node_modules/htmx.org/` exists
+- `node_modules/alpinejs/` exists
+- `node_modules/lunr/` exists
+- `node_modules/highlight.js/` exists
+
+### Step 2: Build Bundle
+
+```bash
+npm run build:ts
+```
+
+**Expected output**:
+- `web/static/main.js` created (~100KB)
+- `web/static/main.js.map` created (sourcemap)
+
+### Step 3: Verify Build
+
+```bash
+# Check file size
+ls -lh web/static/main.js
+
+# Verify bundle contents (should see all modules)
+head -20 web/static/main.js
+
+# Check no 404 errors
+grep -r "script src" templates/ | grep -v "main.js"
+```
+
+**Expected**:
+- main.js is ~80-120KB (minified)
+- No references to htmx.min.js, lunr.min.js, etc. in templates
+- Bundle contains all your code + dependencies
+
+### Step 4: Test Application
+
+```bash
+# Restart Go server
+podman compose down
+podman compose up -d
+```
+
+**Test pages**:
+1. http://localhost:8765/collections - Should load without 404 errors
+2. http://localhost:8765/collections/{id} - Test checkbox functionality
+3. http://localhost:8765/docs - Search should work, syntax highlighting should work
+4. http://localhost:8765/dashboard - Should load normally
+
+**Check browser console**:
+- ✅ No 404 errors for .js files
+- ✅ No "f is not a function" or "l is not a function" errors
+- ✅ HTMX loaded and working (check `typeof htmx !== 'undefined'`)
+- ✅ Alpine.js loaded if used (check `typeof Alpine !== 'undefined'`)
+- ✅ Lunr loaded on docs page (check `typeof lunr !== 'undefined'`)
+- ✅ Highlight.js loaded on docs page (check `typeof hljs !== 'undefined'`)
+
+### Step 5: Clean Up (Optional)
+
+After confirming everything works:
+
+```bash
+cd /home/nymusicman/Code/bookhoard/web/static
+
+# Remove old separate .js files
+rm admin.js analytics.js api-explorer.js api.js bookshelf.js
+rm collections.js conflicts.js custom-section-builder.js dashboard.js
+rm device-management.js dom.js events.js header.js library.js
+rm linking.js password_validation.js queue.js search.js storage.js
+rm theme.js themeDropdown.js toast.js woodPaneling.js woodPanelingInit.js
+```
+
+**Keep**:
+- `main.js` (new bundle)
+- `main.js.map` (sourcemap)
+- `style.css` (Tailwind output)
+- `input.css` (Tailwind input)
+- `placeholder-book.svg` (asset)
+- `highlight-dark.min.css` (CSS only)
+
+---
+
+## Verification Checklist
+
+### Before Declaring Complete
+
+- [ ] `npm run build:ts` completes without errors
+- [ ] `web/static/main.js` exists (~80-120KB)
+- [ ] `web/static/main.js.map` exists (sourcemap)
+- [ ] No 404 errors in browser console for .js files
+- [ ] HTMX working (check network tab for AJAX requests)
+- [ ] Docs page search working
+- [ ] Docs page syntax highlighting working
+- [ ] Collections page loads correctly
+- [ ] No `(window as any)` type casts in TypeScript files (unless for SSR data)
+- [ ] All npm imports are in files that use them (not main.ts)
+- [ ] `main.ts` only imports page modules (not dependencies)
+- [ ] Templates updated to use single `