;
}
}
// 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 `