From 287e526e047f5bea5bc58bb80228918bfa5420e9 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 11 Mar 2026 11:37:54 -0400 Subject: [PATCH] docs: Archive obsolete Alpine.js and ESBuild migration plans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Archives 6 migration planning documents that are now complete or obsolete: Completed Migrations (Safe to Delete): - ALPINE_GLOBAL_FIX_PART1.md: Alpine.global() → Alpine.store() migration COMPLETE - ESBUILD_MIGRATION_PLAN.md: ESBuild bundling and ES modules migration COMPLETE - ESBUILD_IMPORT_FIXES.md: ESBuild import corrections APPLIED Obsolete Reference Documents (Safe to Delete): - ESBUILD_SETUP_OLD.md: Superseded by ESBUILD_MIGRATION_PLAN.md - ESBUILD_README.md: Quick reference for completed migration Future Work (Retained as Reference): - ALPINE_COMPLETION_GUIDE.md: Reactive Alpine.js migration (OPTIONAL, not started) Migration Status Summary: ✅ Alpine.store() migration: All 26 files converted, 25 stores registered ✅ ESBuild bundling: main.ts imports all modules, 168KB bundle working ✅ Template integration: All function calls verified and working ✅ Build system: TypeScript compiles cleanly, no errors All critical migrations complete. App is in stable working baseline. Future reactive migration (ALPINE_COMPLETION_GUIDE.md) is optional and can be pursued later for smoother animations and modern patterns. --- ALPINE_GLOBAL_FIX_PART1.md | 387 ------- ESBUILD_IMPORT_FIXES.md | 97 -- ESBUILD_MIGRATION_PLAN.md | 2040 --------------------------------- ESBUILD_README.md | 210 ---- ESBUILD_SETUP_OLD.md | 2199 ------------------------------------ 5 files changed, 4933 deletions(-) delete mode 100644 ALPINE_GLOBAL_FIX_PART1.md delete mode 100644 ESBUILD_IMPORT_FIXES.md delete mode 100644 ESBUILD_MIGRATION_PLAN.md delete mode 100644 ESBUILD_README.md delete mode 100644 ESBUILD_SETUP_OLD.md diff --git a/ALPINE_GLOBAL_FIX_PART1.md b/ALPINE_GLOBAL_FIX_PART1.md deleted file mode 100644 index fee9d01..0000000 --- a/ALPINE_GLOBAL_FIX_PART1.md +++ /dev/null @@ -1,387 +0,0 @@ -# Alpine.js `Alpine.global()` → `Alpine.store()` Migration Guide - Part 1 - -## 🔴 Critical Issue Identified - -**Problem**: Your codebase uses `Alpine.global()` which **DOES NOT EXIST** in Alpine.js v3.15.8. This is causing the error: - -``` -Uncaught TypeError: p.global is not a function -``` - -## 📋 Root Cause Analysis - -1. **Invalid API Usage**: `Alpine.global()` is not a valid Alpine.js v3 method -2. **26 Occurrences**: Found across 26 TypeScript files -3. **Breaking Impact**: Theme switcher and all Alpine namespaces are broken - -## ✅ Solution Overview - -Replace all `Alpine.global()` calls with `Alpine.store()`, the correct Alpine.js v3 API for registering global utilities. - -**Current Pattern (Broken)**: -```typescript -Alpine.global("namespace", { - function1: () => { ... }, - function2: () => { ... } -}); -``` - -**New Pattern (Correct)**: -```typescript -Alpine.store("namespace", { - function1: () => { ... }, - function2: () => { ... } -}); -``` - -**Template Usage Changes**: -- **Old**: `@click="namespace.function()"` → `@click="$store.namespace.function()"` - ---- - -## 📁 Files Requiring Changes (26 TypeScript files) - -### Step 1: Update TypeScript Files - -For each file, replace `Alpine.global()` with `Alpine.store()`: - -| File | Line | Namespace | Action | -|------|------|-----------|--------| -| `admin.ts` | 399 | `admin` | Replace `Alpine.global` with `Alpine.store` | -| `api.ts` | 102 | `api` | Replace `Alpine.global` with `Alpine.store` | -| `api-explorer-docs.ts` | 135 | `apiExplorerDoc` | Replace `Alpine.global` with `Alpine.store` | -| `bookshelf.ts` | 199 | `bookshelf` | Replace `Alpine.global` with `Alpine.store` | -| `collection-rules.ts` | 411 | `collectionRules` | Replace `Alpine.global` with `Alpine.store` | -| `collections.ts` | 917 | `collections` | Replace `Alpine.global` with `Alpine.store` | -| `conflicts.ts` | 219 | `conflicts` | Replace `Alpine.global` with `Alpine.store` | -| `device-management.ts` | 559 | `devices` | Replace `Alpine.global` with `Alpine.store` | -| `docs.ts` | 96, 103 | `docs` | Replace `Alpine.global` with `Alpine.store` (2 occurrences) | -| `header.ts` | 35 | `header` | Replace `Alpine.global` with `Alpine.store` | -| `index.ts` | 46 | `index` | Replace `Alpine.global` with `Alpine.store` | -| `library.ts` | 683 | `library` | Replace `Alpine.global` with `Alpine.store` | -| `linking.ts` | 204 | `linking` | Replace `Alpine.global` with `Alpine.store` | -| `login.ts` | 25 | `login` | Replace `Alpine.global` with `Alpine.store` | -| `password_validation.ts` | 192 | `validation` | Replace `Alpine.global` with `Alpine.store` | -| `profile.ts` | 44 | `profile` | Replace `Alpine.global` with `Alpine.store` | -| `profile-modal.ts` | 29 | `profileModal` | Replace `Alpine.global` with `Alpine.store` | -| `queue.ts` | 167 | `queue` | Replace `Alpine.global` with `Alpine.store` | -| `register.ts` | 14 | `register` | Replace `Alpine.global` with `Alpine.store` | -| `search.ts` | 310 | `search` | Replace `Alpine.global` with `Alpine.store` | -| `themeDropdown.ts` | 24 | `themeDropdown` | Replace `Alpine.global` with `Alpine.store` | -| `toast.ts` | 229 | `showToast` | Replace `Alpine.global` with `Alpine.store` | -| `toast-error.ts` | 33 | `toastError` | Replace `Alpine.global` with `Alpine.store` | -| `unlinked_books.ts` | 430 | `unlinkedBooks` | Replace `Alpine.global` with `Alpine.store` | -| `woodPaneling.ts` | 85 | `woodPaneling` | Replace `Alpine.global` with `Alpine.store` | - -### Step 2: Update Template Files - -For each `.templ` file that uses Alpine namespaces, update the syntax: - -**Old Syntax**: -```html -
- -
-``` - -**New Syntax**: -```html -
- -
-``` - -**Templates Requiring Updates** (to be identified by searching for namespace usage): -- All templates using `@click="admin."` -- All templates using `@click="api."` -- All templates using `@click="header."` -- All templates using `@click="showToast."` -- All templates using any other namespace from the list above - ---- - -## 🔧 Detailed Step-by-Step Instructions - -### Phase 1: Update TypeScript Source Files - -#### Step 1.1: Backup Current State -```bash -cd /home/nymusicman/Code/bookhoard -git add -A -git commit -m "Backup before Alpine.global() → Alpine.store() migration" -``` - -#### Step 1.2: Find All `Alpine.global` Occurrences -```bash -cd /home/nymusicman/Code/bookhoard/web/src -grep -rn "Alpine\.global" . | tee alpine-global-occurrences.txt -``` - -Expected output: 26 occurrences across 26 files - -#### Step 1.3: Replace All `Alpine.global` with `Alpine.store` - -**Option A: Manual Replacement (Recommended for Understanding)** -For each file in the table above: -1. Open the file -2. Find the `Alpine.global()` call -3. Replace `Alpine.global` with `Alpine.store` -4. Save the file - -**Example** (`web/src/toast.ts:229`): -```typescript -// BEFORE (Line 229): -Alpine.global("showToast", { - error: (message: string, duration?: number) => - showToast(message, "error", duration), - success: (message: string, duration?: number) => - showToast(message, "success", duration), - info: (message: string, duration?: number) => - showToast(message, "info", duration), -}); - -// AFTER (Line 229): -Alpine.store("showToast", { - error: (message: string, duration?: number) => - showToast(message, "error", duration), - success: (message: string, duration?: number) => - showToast(message, "success", duration), - info: (message: string, duration?: number) => - showToast(message, "info", duration), -}); -``` - -**Option B: Automated Replacement (Faster)** -```bash -cd /home/nymusicman/Code/bookhoard/web/src -find . -name "*.ts" -type f -exec sed -i 's/Alpine\.global(/Alpine.store(/g' {} \; -``` - -⚠️ **Warning**: Automated replacement will change all 26 occurrences at once. Verify with grep first: -```bash -grep -rn "Alpine\.global" . # Should return nothing after replacement -grep -rn "Alpine\.store" . # Should show all 26 occurrences -``` - -#### Step 1.4: Verify Changes -```bash -cd /home/nymusicman/Code/bookhoard/web/src -grep -c "Alpine\.store" *.ts | grep -v ":0" -``` - -Expected: Each of the 26 files should show `1` occurrence (except `docs.ts` which should show `2`) - -#### Step 1.5: Rebuild TypeScript -```bash -cd /home/nymusicman/Code/bookhoard/web -npm run build:ts -``` - -Expected output: -``` -> bookhoard@1.0.0 build:ts -> cp node_modules/htmx.org/dist/htmx.min.js web/static/htmx.min.js && esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --minify - - web/static/main.js 167.9kb - web/static/main.js.map 538.7kb -⚡ Done in 18ms -``` - ---- - -### Phase 2: Update Template Files - -#### Step 2.1: Find All Namespace Usage in Templates -```bash -cd /home/nymusicman/Code/bookhoard/templates -grep -rn '@click="' . -``` - -This will show all click handlers that use namespaces. Look for patterns like: -- `@click="admin.deleteConfirm"` -- `@click="api.getUrl"` -- `@click="header.changeThemeTo"` -- `@click="showToast.error"` -- etc. - -#### Step 2.2: Create Mapping Document - -Create a text file with the old → new namespace mappings: - -``` -admin.function() → $store.admin.function() -api.function() → $store.api.function() -apiExplorerDoc.function() → $store.apiExplorerDoc.function() -bookshelf.function() → $store.bookshelf.function() -collectionRules.function() → $store.collectionRules.function() -collections.function() → $store.collections.function() -conflicts.function() → $store.conflicts.function() -devices.function() → $store.devices.function() -docs.function() → $store.docs.function() -header.function() → $store.header.function() -index.function() → $store.index.function() -library.function() → $store.library.function() -linking.function() → $store.linking.function() -login.function() → $store.login.function() -validation.function() → $store.validation.function() -profile.function() → $store.profile.function() -profileModal.function() → $store.profileModal.function() -queue.function() → $store.queue.function() -register.function() → $store.register.function() -search.function() → $store.search.function() -themeDropdown.function() → $store.themeDropdown.function() -showToast.function() → $store.showToast.function() -toastError.function() → $store.toastError.function() -unlinkedBooks.function() → $store.unlinkedBooks.function() -woodPaneling.function() → $store.woodPaneling.function() -``` - -#### Step 2.3: Update Templates (Iterative Approach) - -**For each namespace found in templates:** - -1. **Search for all usages**: - ```bash - cd /home/nymusicman/Code/bookhoard/templates - grep -rn 'namespace\.' . | grep "@click" - ``` - Replace `namespace` with the actual namespace name (e.g., `admin`, `header`, etc.) - -2. **Replace in each file** manually or using find/replace: - - Find: `@click="namespace.functionName"` - - Replace: `@click="$store.namespace.functionName"` - -**Example for `header.templ`:** - -**Before**: -```templ -@click="header.changeThemeTo('wood-light')" -@click="header.logout()" -``` - -**After**: -```templ -@click="$store.header.changeThemeTo('wood-light')" -@click="$store.header.logout()" -``` - -#### Step 2.4: Critical Template Priority - -Update templates in this order (most critical first): - -1. **`header.templ`** - Theme switcher (currently broken) -2. **`index.templ`** - Main page -3. **`library.templ`** - Core functionality -4. **`collections.templ`** - Collection management -5. **`bookshelf.templ`** - Book display -6. **`api_explorer.templ`** - API testing -7. **`admin.templ`** - Admin panel -8. **`profile.templ`** - User profile -9. **`login.templ`** - Authentication -10. **`register.templ`** - Registration -11. **All other templates** with namespace usage - ---- - -### Phase 3: Test the Migration - -#### Step 3.1: Rebuild Container -```bash -cd /home/nymusicman/Code/bookhoard -docker-compose build -``` - -#### Step 3.2: Restart Container -```bash -docker-compose down -docker-compose up -d -``` - -#### Step 3.3: Verify Bundle -```bash -podman exec bookhoard grep -c "\.store(" /root/web/static/main.js -``` - -Expected: Should show all `Alpine.store()` calls (not `Alpine.global()`) - -#### Step 3.4: Test Theme Switcher (Critical) -1. Open `http://localhost:8765` in browser -2. Hard refresh: `Ctrl+Shift+R` -3. Open browser console (F12) -4. Click theme dropdown -5. Select a theme -6. **Expected**: Theme changes without error -7. **Expected Console**: - ```javascript - Alpine.store('showToast') // Should return object, not undefined - Alpine.store('header') // Should return object, not undefined - ``` - -#### Step 3.5: Verify No Errors -Check browser console for: -- ✅ No `p.global is not a function` errors -- ✅ No `Uncaught TypeError` messages -- ✅ Alpine version shows: `3.15.8` -- ✅ Stores are accessible via `$store` - ---- - -## 📊 Testing Checklist - -After completing the migration, verify: - -- [ ] No `Alpine.global` in any `.ts` file -- [ ] All 26 `Alpine.store` registrations present -- [ ] Bundle size approximately 168KB -- [ ] Browser console shows no errors -- [ ] Theme switcher works correctly -- [ ] All dropdown menus open/close properly -- [ ] Toast notifications display correctly -- [ ] All forms submit without errors -- [ ] All click handlers work as expected -- [ ] Alpine directives (`x-show`, `@click`, etc.) work correctly - ---- - -## 🐛 Troubleshooting - -### Error: "Cannot read property 'function' of undefined" -**Cause**: Template still using old syntax `namespace.function()` -**Fix**: Change to `$store.namespace.function()` - -### Error: "Alpine.store is not a function" -**Cause**: TypeScript build didn't complete or using cached main.js -**Fix**: Run `npm run build:ts` and rebuild container - -### Error: "p.global is not a function" (still) -**Cause**: Some files still have `Alpine.global()` -**Fix**: Run `grep -rn "Alpine\.global" web/src` to find remaining occurrences - -### Theme switcher still broken -**Cause**: `header.templ` not updated with new syntax -**Fix**: Update all `@click="header."` to `@click="$store.header."` - ---- - -## 📝 Next Steps (After This Guide) - -**Part 2** will cover: -1. Advanced Alpine store patterns -2. Reactive state management with stores -3. Testing strategies for all templates -4. Performance optimization -5. Rollback plan if needed - ---- - -## 🔗 References - -- [Alpine.js Stores Documentation](https://alpinejs.dev/globals/alpine_store.html) -- [Alpine.js Magic Properties ($store)](https://alpinejs.dev/magics/$store.html) -- [Migration from Alpine v2 to v3](https://alpinejs.dev/upgrade-guide) - ---- - -**Created**: 2026-03-09 -**Alpine.js Version**: 3.15.8 -**Status**: Ready for Implementation diff --git a/ESBUILD_IMPORT_FIXES.md b/ESBUILD_IMPORT_FIXES.md deleted file mode 100644 index 3d8658e..0000000 --- a/ESBUILD_IMPORT_FIXES.md +++ /dev/null @@ -1,97 +0,0 @@ -# Import Fixes Applied to Migration Plan - -## Summary - -All imports in the ESBUILD_MIGRATION_PLAN.md have been corrected to use individual function imports instead of namespace objects, matching your actual module exports. - -## Changes Made - -### 1. TypeScript Imports (Phase 1) -Changed from namespace imports to individual function imports: - -**Before** (WRONG): -```typescript -import { api } from "./api"; -import { dom } from "./dom"; -import { events } from "./events"; -``` - -**After** (CORRECT): -```typescript -import { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError } from "./api"; -import { querySelector, querySelectorAll, getElementById, createElement, ... } from "./dom"; -import { onDelegatedClick, onDelegatedSubmit, onDelegatedChange, ... } from "./events"; -``` - -### 2. Function Calls in TypeScript -Changed from namespaced calls to direct function calls: - -**Before** (WRONG): -```typescript -const response = await api.get("/libraries"); -showToast.success("Loaded!"); -const el = dom.getElementById("id"); -``` - -**After** (CORRECT): -```typescript -const response = await apiGet("/libraries"); -showToast("Loaded!", "success"); -const el = getElementById("id"); -``` - -### 3. Alpine/Template Calls (Phase 3) -Keep namespace objects (Alpine creates these): - -**CORRECT for Templates**: -```html - - -``` - -## Key Distinction - -### TypeScript Code -- **Imports**: Individual functions -- **Calls**: Direct function calls with arguments -- **Example**: `import { apiGet }` → `apiGet("/url")` - -### Template/Alpine Code -- **Imports**: None (Alpine handles this) -- **Calls**: Namespaced via Alpine.global() -- **Example**: `@click="api.post()"` (Alpine namespace) - -## Files Affected - -All TypeScript file examples in Phase 1 now show correct imports: -- ✅ dashboard.ts -- ✅ analytics.ts -- ✅ library.ts -- ✅ collections.ts -- ✅ bookshelf.ts -- ✅ api-explorer.ts -- ✅ All others - -## Verification - -```bash -# Verify no namespace imports remain in TypeScript sections -grep "import { api } from\|import { dom } from\|import { events } from" ESBUILD_MIGRATION_PLAN.md | grep -v "//" | wc -l -# Result: 0 ✅ - -# Verify Alpine namespace calls in templates -grep '@click="api\.' ESBUILD_MIGRATION_PLAN.md | wc -l -# Result: 3 ✅ - -# Verify TypeScript individual imports -grep "import { apiGet" ESBUILD_MIGRATION_PLAN.md | wc -l -# Result: 9 ✅ -``` - -## Plan Status - -✅ **Ready to execute** - All imports corrected -✅ **Consistent throughout** - TypeScript vs Alpine distinction clear -✅ **Matches your actual exports** - No namespace objects exported from modules - -You can now start Phase 0 with confidence! diff --git a/ESBUILD_MIGRATION_PLAN.md b/ESBUILD_MIGRATION_PLAN.md deleted file mode 100644 index b43b577..0000000 --- a/ESBUILD_MIGRATION_PLAN.md +++ /dev/null @@ -1,2040 +0,0 @@ -# ESBuild Migration Plan: TypeScript → ES Modules + Alpine - -## Executive Summary - -**Goal**: Migrate from individual JavaScript files with window globals to a single bundled `main.js` using ESBuild, ES modules for internal dependencies, and Alpine.js for template interactivity. - -**Architecture Principles**: -1. **ES Modules** for all TypeScript → TypeScript dependencies -2. **Alpine.js** ONLY as a bridge to templates (onclick → @click) -3. **SSR-first** - Server renders complete HTML, client-side JS only for interactivity -4. **No window globals** - everything through proper imports/exports -5. **Incremental migration** - Each phase is complete and testable - -**Target Bundle**: ~100-130KB minified (ES2020 target = Chrome 80+, Firefox 72+, Safari 13.1+, Edge 80+) - ---- - -## Current State Analysis - -### Problems with Current Architecture - -1. **274 window global references** across 21 TypeScript files -2. **193+ internal TypeScript dependencies** via window (should be ES imports) -3. **27 template files** with 150+ unique onclick handlers -4. **Function wrapping** (themeDropdown.ts wraps header.ts functions) -5. **Individual .js files** loaded in each template (not bundled) -6. **Mixed patterns** - some files have ES exports, some don't - -### Files Requiring Migration - -**TypeScript Source Files** (21 files): -- api.ts, toast.ts, storage.ts, events.ts, dom.ts -- theme.ts, header.ts, themeDropdown.ts, woodPaneling.ts -- library.ts, collections.ts, conflicts.ts, queue.ts -- dashboard.ts, admin.ts, bookshelf.ts, search.ts -- device-management.ts, analytics.ts, custom-section-builder.ts -- docs.ts, api-explorer.ts, password_validation.ts - -**Template Files** (27 files): -- dashboard.templ, collections.templ, header.templ, admin.templ -- docs.templ, bookshelf.templ, devices.templ, queue.templ -- conflicts.templ, analytics.templ, login.templ, register.templ -- profile.templ, settings.templ, stats.templ, sync.templ -- progress.templ, custom_section.templ, collection_rules.templ -- admin_library.templ, admin_users.templ, api_explorer.templ -- Plus 8 more modal/component templates - ---- - -## Architecture Principles - -### 1. ES Module Exports (TypeScript → TypeScript) - -Every utility module exports functions for other TypeScript files to import: - -```typescript -// api.ts -export async function apiGet(url: string): Promise { ... } -export async function apiPost(url: string, data?: unknown): Promise { ... } -export async function handleResponse(response: Response): Promise { ... } -``` - -```typescript -// library.ts (consumes api.ts) -import { apiGet, handleResponse } from "./api"; - -const response = await apiGet("/libraries"); -const result = await handleResponse(response); -``` - -### 2. Alpine.js Bridge (Templates → TypeScript) - -Alpine is ONLY used to expose functions to templates, not for internal TS dependencies: - -```typescript -// api.ts (bottom of file) -import { Alpine } from "./alpine"; - -Alpine.global("api", { - get: apiGet, - post: apiPost, - put: apiPut, - delete: apiDelete, - handleResponse, - handleVoidResponse, - handleError, -}); -``` - -Template usage: -```html - - - - - -``` - -### 3. SSR-First with Progressive Enhancement - -- Server renders complete HTML with data -- Client-side JavaScript only for interactivity (modals, dropdowns, forms) -- Data loaded via fetch() APIs (like docs search index) -- No window globals for data injection - ---- - -## Migration Strategy: Incremental with No Legacy Code - -### Why This Strategy Works - -1. **No breaking changes during migration** - App works at every phase -2. **Can ship anytime** - Partial migrations are functional -3. **No legacy code** - Each file completely migrated, no half-states -4. **Easy rollback** - If issues arise, revert individual files/templates -5. **Testable at each step** - Clear verification criteria - ---- - -## Phase 0: Foundation - Add ES Module Exports - -**Goal**: Add proper ES exports to all utility modules WITHOUT breaking existing functionality. - -**Files**: 8 utility modules - -**Duration**: 1-2 hours - -### Step 0.1: Add Exports to Utility Modules - -For each file, add `export` statements while keeping window exports temporarily: - -**web/src/api.ts** (Already has Alpine, just needs ES exports) -```typescript -// Add at bottom (after Alpine.global block): -export { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError }; -``` - -**web/src/toast.ts** (Already has Alpine, just needs ES exports) -```typescript -// Add at bottom (after Alpine.global block): -export { showToast }; -export type { ToastType }; -``` - -**web/src/storage.ts** (Already has exports! ✅) -```typescript -// Already has: -// export { getToken, setToken, ... }; -// No changes needed! -``` - -**web/src/events.ts** -```typescript -// Add at bottom: -export { - onDelegatedClick, - onDelegatedSubmit, - onDelegatedChange, - onDelegatedKeydown, - getDataAttribute, - setDataAttribute, - onClick, - onSubmit, - onChange, - onKeydown, - onInput, - preventDefault, - stopPropagation, -}; -``` - -**web/src/dom.ts** (Already has exports! ✅) -```typescript -// Already has: -// export { escapeHtml, querySelector, ... }; -// No changes needed! -``` - -**web/src/theme.ts** -```typescript -// Add at bottom: -export { applyTheme, loadTheme, changeTheme, loadUserTheme, initializeTheme }; -export type { ThemeType }; -``` - -**web/src/header.ts** -```typescript -// Add at bottom: -export { toggleThemeDropdown, toggleUserMenu, changeThemeTo, logout }; -``` - -**web/src/woodPaneling.ts** -```typescript -// Add at bottom: -export { changeWoodPaneling, loadWoodPaneling, updateWoodPanelingIndicators }; -``` - -### Step 0.2: Verify Build - -```bash -npm run build:ts -``` - -**Expected**: Build succeeds, no errors - -**Why this works**: We're adding exports without changing anything else. App still uses window globals, so functionality is unchanged. - ---- - -## Phase 1: Internal TypeScript Dependencies - -**Goal**: Convert all internal `window.XXX` reads to proper ES module imports. - -**Files**: 13 consumer files - -**Duration**: 4-6 hours - -### 1.1: Simple Consumers (Low Risk) - -Files with minimal window dependencies: - -**web/src/dashboard.ts** -```typescript -// Add at top: -import { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError } from "./api"; -import { showToast } from "./toast"; - -// Replace ALL instances: -// Line 22: (window as any).showToast.error("msg") → showToast("msg", "error") -// Line 37: (window as any).showToast.error("msg") → showToast("msg", "error") -// Line 128: (window as any).api.put → apiPut -// Line 135: (window as any).showToast.success("msg") → showToast("msg", "success") -// ... (10 more replacements) -``` - -**web/src/search.ts** -```typescript -// No changes needed! ✅ -// Already uses localStorage directly, doesn't read from window -// Only exports selectLibraryAndBook -``` - -**web/src/device-management.ts** -```typescript -// Add at top: -import { showToast } from "./toast"; - -// Replace ALL instances: -// Lines 32-34, 39-42, 78-82, 89-92: const toast = (window as any).showToast → showToast -``` - -**web/src/password_validation.ts** -```typescript -// No window reads found, only exports initPasswordValidation -// No changes needed -``` - -### 1.2: Moderate Consumers - -Files with multiple window dependencies: - -**web/src/admin.ts** -```typescript -// Add at top: -import { showToast } from "./toast"; - -// Replace ALL instances (14 occurrences): -// Lines 12-13, 17-18, 23-24, 40-41, 45-46, 53-54, 119-120, 152-153, 161-162 -// (window as any).showToast.success("msg") → showToast("msg", "success") -// (window as any).showToast.error → showToast.error -``` - -**web/src/queue.ts** -```typescript -// Add at top: -import { showToast } from "./toast"; - -// Replace ALL instances (8 occurrences): -// Lines 30-32, 37-39, 56-58, 63-65, 82-84, 89-91 -``` - -**web/src/conflicts.ts** -```typescript -// Add at top: -import { showToast } from "./toast"; - -// Replace ALL instances (6 occurrences): -// Lines 39-41, 45-48, 53-55, 78-80, 85-87 -``` - -**web/src/linking.ts** -```typescript -// Add at top: -import { showToast } from "./toast"; - -// Replace ALL instances: -// (window as any).showToast.success/error("msg") → showToast("msg", "success"/"error") -``` - -**web/src/custom-section-builder.ts** -```typescript -// Add at top: -import { showToast } from "./toast"; -import { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError } from "./api"; - -// Replace ALL instances (9 occurrences): -// Lines 7, 10, 13, 16, 19, 22, 25, 28 -``` - -**web/src/analytics.ts** -```typescript -// Add at top: -import { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError } from "./api"; -import { querySelector, querySelectorAll, getElementById, createElement, showElement, hideElement, toggleElement, addClass, removeClass, toggleClass, hasClass, setTextContent, setInnerHTML, escapeHtml } from "./dom"; - -// Replace ALL instances: -// (window as any).api.get → apiGet -// (window as any).dom.getElementById → getElementById -``` - -**web/src/bookshelf.ts** -```typescript -// Add at top: -import { showToast } from "./toast"; -import { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError } from "./api"; -import { onDelegatedClick, onDelegatedSubmit, onDelegatedChange, onDelegatedKeydown, getDataAttribute, setDataAttribute, onClick, onSubmit, onChange, onKeydown, onInput, preventDefault, stopPropagation } from "./events"; - -// Replace ALL instances -``` - -**web/src/api-explorer.ts** -```typescript -// Add at top: -import { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError } from "./api"; -import { querySelector, querySelectorAll, getElementById, createElement, showElement, hideElement, toggleElement, addClass, removeClass, toggleClass, hasClass, setTextContent, setInnerHTML, escapeHtml } from "./dom"; - -// Replace ALL instances -``` - -### 1.3: Complex Consumers (High Risk) - -Files with heavy window dependencies: - -**web/src/library.ts** (55 occurrences!) -```typescript -// Add at top: -import { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError } from "./api"; -import { showToast } from "./toast"; -import { querySelector, querySelectorAll, getElementById, createElement, showElement, hideElement, toggleElement, addClass, removeClass, toggleClass, hasClass, setTextContent, setInnerHTML, escapeHtml } from "./dom"; - -// Systematic replacement: -// (window as any).api.get → apiGet -// (window as any).api.handleResponse → handleResponse -// (window as any).api.handleError → handleError -// (window as any).showToast.success("msg") → showToast("msg", "success") -// (window as any).dom.createElement → createElement -``` - -**web/src/collections.ts** (54 occurrences!) -```typescript -// Add at top: -import { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError } from "./api"; -import { showToast } from "./toast"; -import { querySelector, querySelectorAll, getElementById, createElement, showElement, hideElement, toggleElement, addClass, removeClass, toggleClass, hasClass, setTextContent, setInnerHTML, escapeHtml } from "./dom"; - -// Systematic replacement of all 54 instances -``` - -### 1.4: Restructure Function Wrapping - -This section eliminates the anti-pattern of wrapping functions from other files. Currently, `themeDropdown.ts` overrides `header.ts` functions by reading from window and reassigning them. We'll convert this to proper ES module composition. - ---- - -#### 1.4.1: web/src/themeDropdown.ts - Complete Rewrite - -**Current Problem (Lines 1-32):** -```typescript -// Anti-pattern: Reading and reassigning window globals -const originalToggleThemeDropdown = (window as any).toggleThemeDropdown; -(window as any).toggleThemeDropdown = () => { - originalToggleThemeDropdown(); - updateThemeIndicators(); - (window as any).updateWoodPanelingIndicators?.(); -}; -``` - -**Step-by-Step Conversion:** - -**1. Add ES Module Imports (at top of file, after line 1):** -```typescript -import { toggleThemeDropdown } from "./header"; -import { updateWoodPanelingIndicators } from "./woodPaneling"; -``` - -**2. Remove window read/override (delete lines 1-8):** -```typescript -// DELETE THESE LINES: -// const originalToggleThemeDropdown = (window as any).toggleThemeDropdown; -// (window as any).toggleThemeDropdown = () => { -// originalToggleThemeDropdown(); -// updateThemeIndicators(); -// (window as any).updateWoodPanelingIndicators?.(); -// }; -``` - -**3. Create new composed function (replace deleted code with):** -```typescript -// Compose the theme dropdown behavior -export function initializeThemeDropdown(): void { - // Call original function from header.ts - toggleThemeDropdown(); - // Then update theme indicators - updateThemeIndicators(); - // Update wood paneling indicators - updateWoodPanelingIndicators(); -} -``` - -**4. Keep existing updateThemeIndicators function (no change needed)** - -**5. Add Alpine registration at bottom of file (after existing code):** -```typescript -import { Alpine } from "./alpine"; - -// Register composed function with Alpine -Alpine.global("themeDropdown", { - initialize: initializeThemeDropdown, - updateIndicators: updateThemeIndicators, -}); - -// Also keep the window export temporarily (remove in Phase 2) -(window as any).initializeThemeDropdown = initializeThemeDropdown; -(window as any).updateThemeIndicators = updateThemeIndicators; -``` - -**Complete File After Conversion:** -```typescript -// web/src/themeDropdown.ts - Rewritten version - -import { toggleThemeDropdown } from "./header"; -import { updateWoodPanelingIndicators } from "./woodPaneling"; -import { Alpine } from "./alpine"; - -// Composed theme dropdown initialization -export function initializeThemeDropdown(): void { - // Call original function from header.ts - toggleThemeDropdown(); - // Then update theme indicators - updateThemeIndicators(); - // Update wood paneling indicators - updateWoodPanelingIndicators(); -} - -// Update theme indicator elements (existing function, keep as-is) -export function updateThemeIndicators(): void { - const themeSelect = document.getElementById("theme-select") as HTMLSelectElement; - const themeIndicator = document.getElementById("current-theme"); - - if (themeSelect && themeIndicator) { - const theme = themeSelect.value as ThemeType; - themeIndicator.textContent = theme.charAt(0).toUpperCase() + theme.slice(1); - } - - // Update wood paneling button - const woodPanelingButton = document.getElementById("wood-paneling-button"); - if (woodPanelingButton) { - const woodPaneling = localStorage.getItem("woodPaneling") || "none"; - woodPanelingButton.textContent = woodPaneling === "none" ? "🪵" : "✓"; - } -} - -// Register with Alpine for template access -Alpine.global("themeDropdown", { - initialize: initializeThemeDropdown, - updateIndicators: updateThemeIndicators, -}); - -// Temporary window exports (remove in Phase 2) -(window as any).initializeThemeDropdown = initializeThemeDropdown; -(window as any).updateThemeIndicators = updateThemeIndicators; -``` - -**Changes Summary:** -- ✅ Added 3 ES imports (toggleThemeDropdown, updateWoodPanelingIndicators, Alpine) -- ✅ Deleted 8 lines of window read/override code -- ✅ Created 1 new composed function (initializeThemeDropdown) -- ✅ Added Alpine.global() registration (7 lines) -- ✅ Kept window exports temporarily (2 lines, remove in Phase 2) - ---- - -#### 1.4.2: web/src/docs.ts - Window Export Conversion Only - -**⚠️ IMPORTANT:** Search functionality changes (Lunr removal, backend API) are handled by a **separate DOCS_SEARCH_IMPLEMENTATION.md task**. This section ONLY covers ESBuild migration changes (window export → Alpine.global()). - ---- - -**Current Issue (Line 91):** -```typescript -(window as any).toggleSidebar = toggleSidebar; -``` - -This is the only window global that needs to be converted for the ESBuild migration. - -**Note:** The search functionality (`performDocsSearch`, `initializeDocsSearch`, Lunr imports, etc.) will be addressed separately by the backend API search implementation (see DOCS_SEARCH_IMPLEMENTATION.md). - ---- - -**Step-by-Step Conversion:** - -**1. Add Alpine Import (after line 1):** -```typescript -// BEFORE: -import * as lunr from "lunr"; - -// AFTER: -import * as lunr from "lunr"; -import { Alpine } from "./alpine"; -``` - -**2. Remove Window Export and Add Alpine Registration (replace line 91):** -```typescript -// DELETE line 91: -// (window as any).toggleSidebar = toggleSidebar; - -// REPLACE with: -// Register with Alpine for template access -Alpine.global("docs", { - toggleSidebar, - initializeSearch: initializeDocsSearch, -}); -``` - -**3. Add ES Module Export (at end of file):** -```typescript -export { toggleSidebar, initializeDocsSearch }; -``` - ---- - -**Complete Converted File (ESBuild changes only):** -```typescript -// web/src/docs.ts - ESBuild migration changes - -import * as lunr from "lunr"; -import { Alpine } from "./alpine"; - -function toggleSidebar(): void { - const sidebar = document.getElementById("docs-sidebar"); - const overlay = document.getElementById("docs-overlay"); - - if (sidebar && overlay) { - sidebar.classList.toggle("translate-x-0"); - sidebar.classList.toggle("-translate-x-full"); - overlay.classList.toggle("hidden"); - } -} - -function initializeDocsSearch(): void { - const searchInput = document.getElementById( - "docs-search", - ) as HTMLInputElement; - const searchResults = document.getElementById("docs-search-results"); - - if (!searchInput || !searchResults) return; - - let docsSearchTimeout: ReturnType | null = null; - - searchInput.addEventListener("input", () => { - const query = searchInput.value.trim(); - - if (docsSearchTimeout) { - clearTimeout(docsSearchTimeout); - } - - if (query.length < 2) { - searchResults.innerHTML = ""; - searchResults.classList.add("hidden"); - return; - } - - docsSearchTimeout = setTimeout(() => { - performDocsSearch(query); - }, 300); - }); -} - -function performDocsSearch(query: string): void { - const searchResults = document.getElementById("docs-search-results"); - if (!searchResults) return; - - try { - const idx = lunr.Index.load(lunrIndexData); - if (!idx) { - searchResults.innerHTML = - '

Search index not loaded

'; - searchResults.classList.remove("hidden"); - return; - } - - const results = idx.search(query); - - if (results.length === 0) { - searchResults.innerHTML = - '

No results found

'; - } else { - searchResults.innerHTML = results - .slice(0, 10) - .map((result: { ref: string }) => { - const doc = docsData[result.ref]; - if (!doc) return ""; - - return ` - -

${doc.title || result.ref}

- ${doc.section ? `

${doc.section}

` : ""} -
- `; - }) - .join(""); - } - - searchResults.classList.remove("hidden"); - } catch (error) { - console.error("Search error:", error); - searchResults.innerHTML = - '

Search error

'; - searchResults.classList.remove("hidden"); - } -} - -document.addEventListener("DOMContentLoaded", () => { - initializeDocsSearch(); -}); - -// Register with Alpine for template access -Alpine.global("docs", { - toggleSidebar, - initializeSearch: initializeDocsSearch, -}); - -// ES module exports -export { toggleSidebar, initializeDocsSearch }; -``` - ---- - -**ESBuild Migration Changes Only:** -- ✅ Added 1 import (Alpine from "./alpine") -- ✅ Removed 1 line (window export) -- ✅ Added 7 lines (Alpine.global() registration) -- ✅ Added 1 line (ES module export) - -**Search Functionality:** Unchanged (handled by separate DOCS_SEARCH_IMPLEMENTATION.md task) - -**Note:** The `lunrIndexData` and `docsData` undefined variables will be fixed when the backend API search is implemented (see DOCS_SEARCH_IMPLEMENTATION.md). - ---- - -#### 1.4.3: No Other Files Need Function Wrapping Changes - -**Confirmed Analysis:** -- ✅ `header.ts` - Only exports functions, no wrapping -- ✅ `woodPaneling.ts` - Only exports functions, no wrapping -- ✅ All other consumer files - Only import/use functions, no wrapping - -**Only 2 files affected:** themeDropdown.ts and docs.ts - ---- - -#### 1.4.4: Verification Steps - -After completing both file conversions: - -```bash -# Build TypeScript -npm run build:ts - -# Run application -go run . - -# Test in browser: -# 1. Navigate to dashboard -# 2. Click theme dropdown - should work and update indicators -# 3. Navigate to /docs -# 4. Search in docs - should work without console errors -``` - -**Expected Results:** -- ✅ Theme dropdown opens and updates indicators -- ✅ Wood paneling indicators update correctly -- ✅ Docs search works (lunr loaded via ES import) -- ✅ No console errors about missing window.lunr -- ✅ No console errors about missing window.lunrIndex -- ✅ No console errors about missing window.docsData - -**Common Issues:** -- If theme dropdown doesn't open: Check if toggleThemeDropdown import is correct -- If indicators don't update: Check if updateWoodPanelingIndicators is called -- If docs search fails: Check if lunr is imported correctly (not via window) -- If data missing: Check if fetch() API is working (not using window globals) - -### Step 1.5: Remove Window Exports from Consumer Files (If Present) - -After converting all reads to imports, check if consumer files have window exports to remove. - -**Note:** Not all files have window exports. Some files (like dashboard.ts) use event delegation and don't export to window. - -**Files WITH window exports to remove:** - -**web/src/library.ts** (lines 690-704) -```typescript -// DELETE these window exports: -(window as any).deleteLibrary = deleteLibrary; -(window as any).showLibraryFolders = showLibraryFolders; -(window as any).addLibraryFolder = addLibraryFolder; -(window as any).removeLibraryFolder = removeLibraryFolder; -(window as any).setLibraryVisibility = setLibraryVisibility; -(window as any).loadUserVisibility = loadUserVisibility; -(window as any).editLibrary = editLibrary; -(window as any).handleCreateLibrarySubmit = handleCreateLibrarySubmit; -(window as any).showFolderBrowser = showFolderBrowser; -(window as any).navigateFolderBrowser = navigateFolderBrowser; -(window as any).selectBrowseFolder = selectBrowseFolder; -(window as any).hideFolderBrowser = hideFolderBrowser; -(window as any).showDeleteModal = showDeleteModal; -(window as any).hideDeleteModal = hideDeleteModal; -(window as any).confirmDeleteLibrary = confirmDeleteLibrary; -``` - -**web/src/collections.ts** (multiple locations: lines 204-208, 243, 315-317, 472-476, 912-923) -```typescript -// DELETE all window exports (26 functions total) -// Examples: -(window as any).loadCollections = loadCollections; -(window as any).loadCollectionRules = loadCollectionRules; -// ... etc (all 26 exports) -``` - -**web/src/bookshelf.ts** (lines 127-130) -```typescript -// DELETE these window exports: -(window as any).selectLibrary = selectLibrary; -(window as any).loadBookshelf = loadBookshelf; -(window as any).selectBook = selectBook; -(window as any).changePage = changePage; -``` - -**web/src/api-explorer.ts** (lines 186-189) -```typescript -// DELETE these window exports: -(window as any).sendApiRequest = sendApiRequest; -(window as any).loadFromHistory = loadFromHistory; -(window as any).copyCurl = copyCurl; -(window as any).formatJson = formatJson; -``` - -**Files WITHOUT window exports (already clean):** -- ✅ **dashboard.ts** - Uses event delegation, no window exports -- ✅ **admin.ts** - Check if it has exports -- ✅ **queue.ts** - Check if it has exports -- ✅ **conflicts.ts** - Check if it has exports -- ✅ **analytics.ts** - Check if it has exports -- ✅ **device-management.ts** - Check if it has exports -- ✅ **linking.ts** - Check if it has exports -- ✅ **custom-section-builder.ts** - Check if it has exports - -**Verification:** -For each file, search for `(window as any).` at the end of the file. If present, remove those lines. - -**Only keep Alpine.global()** for functions that templates call directly. - -### Step 1.6: Verify Build and Test - -```bash -npm run build:ts -go run . -``` - -**Verification**: -- Build succeeds -- All pages still work (dashboard, library, collections, etc.) -- Console shows no errors -- All onclick handlers still work - -**Why this works**: Internal dependencies now use imports, but window exports still exist for templates. App is fully functional. - ---- - -## Phase 2: Dual Exports - ES Modules + Alpine Bridge - -**Goal**: Establish Alpine.js as the bridge to templates while keeping ES exports for TypeScript. - -**Files**: All files that export functions used by templates - -**Duration**: 2-3 hours - -### Step 2.1: Ensure Alpine Initialization File - -**web/src/alpine.ts** (Already exists, verify it's correct) -```typescript -import Alpine from "alpinejs"; - -// Extend Window interface -declare global { - interface Window { - Alpine: typeof Alpine; - } -} - -// Initialize Alpine -window.Alpine = Alpine; -Alpine.start(); - -// Re-export for other modules -export { Alpine }; -``` - -### Step 2.2: Register Template Functions with Alpine - -For each file that templates call, register functions with Alpine: - -**web/src/api.ts** (Already done ✅) -```typescript -Alpine.global("api", { - get: apiGet, - post: apiPost, - // ... etc -}); -``` - -**web/src/toast.ts** (Already done ✅) -```typescript -Alpine.global("showToast", { - error: (message: string, duration?: number) => showToast(message, "error", duration), - success: (message: string, duration?: number) => showToast(message, "success", duration), - info: (message: string, duration?: number) => showToast(message, "info", duration), -}); -``` - -**web/src/header.ts** -```typescript -Alpine.global("header", { - logout, - toggleThemeDropdown, - toggleUserMenu, - changeThemeTo: (theme: string) => { - changeThemeTo(theme); - updateThemeIndicators(); // Call themeDropdown function - }, -}); -``` - -**web/src/library.ts** -```typescript -Alpine.global("library", { - deleteLibrary, - showLibraryFolders, - addLibraryFolder, - removeLibraryFolder, -}); -``` - -**web/src/collections.ts** -```typescript -Alpine.global("collections", { - loadCollections, - createRule, - deleteRule, - testRule, - navigateToCollection, - selectColor, - closeCollectionModal, - initColorSelection, - selectIcon, - filterIcons, - showAllIcons, - populateIconGrid, - initIconSelection, - initCollectionDetail, - showAddBooksModal, - hideAddBooksModal, - searchBooksForCollections, - toggleBookSelection, - addbooksToAdd, - removeBook, - toggleBookForRemoval, - updateSelectedCount, - removebooksToAdd, - filterCollectionBooks, - backToCollections, -}); -``` - -**web/src/admin.ts** -```typescript -Alpine.global("admin", { - triggerLibraryScan, - triggerQuickScan, - loadSystemStats, - scanAllLibraries, - loadWatchStatus, - hideScanProgress, - stopScanStatusPolling, -}); -``` - -**web/src/queue.ts** -```typescript -Alpine.global("queue", { - refreshQueue, - processPendingItems, - clearFailedItems, - clearAllItems, - retryQueueItem, - deleteQueueItem, -}); -``` - -**web/src/conflicts.ts** -```typescript -Alpine.global("conflicts", { - refreshConflicts, - resolveConflict, - bulkResolve, - bulkDismiss, - dismissAllResolved, - showResolveModal, - hideResolveModal, - handleResolveSubmit, -}); -``` - -**web/src/docs.ts** -```typescript -Alpine.global("docs", { - toggleSidebar, - initializeSearch: initializeDocsSearch, -}); -``` - -**web/src/search.ts** -```typescript -Alpine.global("search", { - selectLibraryAndBook, -}); -``` - -**web/src/device-management.ts** -```typescript -Alpine.global("devices", { - copyToClipboard, - regenerateDeviceToken, -}); -``` - -**web/src/password_validation.ts** -```typescript -Alpine.global("validation", { - initPasswordValidation, -}); -``` - -### Step 2.3: Remove Old Window Exports - -After Alpine registration, REMOVE the old window export lines: - -**Before**: -```typescript -(window as any).api = { ... }; -Alpine.global("api", { ... }); -``` - -**After**: -```typescript -Alpine.global("api", { ... }); -``` - -Systematically search and remove these patterns from all files. - -### Step 2.4: Verify Build - -```bash -npm run build:ts -go run . -``` - -**Verification**: -- Build succeeds -- All pages still work -- Alpine is loaded (check browser DevTools: `window.Alpine` should be defined) -- Console shows no errors - -**Why this works**: Functions now registered with Alpine instead of window, but templates still use onclick="func()" so they still work. - ---- - -## Phase 3: Template Migration (Incremental) - -**Goal**: Migrate templates one-by-one from onclick handlers to Alpine directives. - -**Strategy**: One template at a time, test each, can ship after each migration. - -**Duration**: 1-2 hours per template (27 templates = 27-54 hours total) - -### Template Migration Pattern - -For each template file: - -#### Step A: Remove Individual Script Tags - -**Before**: -```templ - - - - - - -``` - -**After**: -```templ - - -``` - -**Note**: Keep htmx.min.js separate (loaded before main.js) - -#### Step B: Convert onclick to @click - -**Before**: -```templ - - -``` - -**After**: -```templ - -
- -
-``` - -#### Step C: Add Alpine State for UI Components - -For modals, dropdowns, and any UI with show/hide state: - -**Before**: -```templ - - -``` - -**After**: -```templ -
- -
- ...modal content... - -
-
-``` - - -**Important**: Alpine uses namespace objects (`api.post`, `showToast.success`), -**while TypeScript code uses individual functions** (`apiPost`, `showToast`). -**This is correct** - see Phase 2 for how Alpine creates these namespace objects. - -#### Step D: Update Namespace Calls - -**Before**: -```templ - - -``` - -**After**: -```templ - - -``` - -### Template Order (Low Risk to High Risk) - -#### Batch 1: Simple Pages (No complex state) -1. **login.templ** - Only has showToast, api -2. **register.templ** - Only has showToast, api, validation -3. **profile.templ** - Only has showToast, api -4. **settings.templ** - Only has showToast, api - -#### Batch 2: Pages with Simple State -5. **stats.templ** - Only has showToast, api, events -6. **sync.templ** - Only has showToast, api -7. **progress.templ** - Only has showToast, api, events -8. **custom_section.templ** - Has modal, but simple - -#### Batch 3: Pages with Moderate State -9. **collection_rules.templ** - Has showToast, api, events, some state -10. **queue.templ** - Has queue namespace, multiple modals -11. **conflicts.templ** - Has conflicts namespace, modal -12. **admin.templ** - Has admin namespace, modal -13. **admin_library.templ** - Has library namespace, multiple modals -14. **admin_users.templ** - Has header functions, simple - -#### Batch 4: Complex Pages -15. **dashboard.templ** - Has theme dropdown, wood paneling, collections -16. **bookshelf.templ** - Has search, book viewing, shelf mappings -17. **devices.templ** - Has devices namespace, multiple modals -18. **analytics.templ** - Keep Chart.js on CDN, has analytics namespace -19. **api_explorer.templ** - Has explorer namespace - -#### Batch 5: Most Complex (Shared Components) -20. **collections.templ** - Has collections namespace, multiple modals, color/icon pickers -21. **docs.templ** - Has sidebar toggle, search, inline JS to migrate -22. **header.templ** - Used in 17 templates! Theme dropdown, user menu - -#### Batch 6: Modal/Component Templates -23. **collection_modal.templ** - Shared component -24. **restore_system_collection_modal.templ** - Shared component -25. **profile_modal.templ** - Shared component -26. **profile_form.templ** - Shared component -27. **toast.templ** - Shared component (if using template for toast) - -### Special Cases - -#### docs.templ - Migrate Inline JavaScript - -**Current** (lines 124-515): Large inline script block - -**New Approach**: Move to docs.ts - -```typescript -// web/src/docs.ts -export function initializeDocsPage() { - // Move toggleSection, toggleSidebar functions here - // Move search initialization here - // Keep using fetch for data loading (SSR-compatible) -} - -Alpine.global("docs", { - initializePage: initializeDocsPage, - toggleSection, - toggleSidebar, -}); -``` - -```templ - - -``` - -#### header.templ - Most Critical (Used in 17 Places) - -This template is included in 17 other templates. Test thoroughly: - -```templ - - - - - - -``` - -```templ - -
- -
- - ...11 more themes... - - -
- - -
- -``` - -### Testing Strategy for Each Template - -After migrating each template: - -1. **Build**: `npm run build:ts && templ generate` -2. **Run**: `go run .` -3. **Test**: Visit the page, test all interactions: - - All buttons work - - Modals open/close - - Dropdowns work - - Forms submit (via HTMX) - - Toast notifications appear - - No console errors -4. **Verify**: Check Alpine DevTools (if installed) for reactive state - -### Rollback Strategy - -If a migrated template has issues: - -```bash -# Revert the template file -git checkout templates/PROBLEM_TEMPLATE.templ - -# Rebuild -templ generate -go run . -``` - -The template is now using old onclick handlers, but the bundle still has the functions registered. Everything works. - ---- - -## Phase 4: Data Injection (Server → Client) - -**Goal**: Ensure server-to-client data flow works correctly without window globals. - -**Files**: docs.templ, any future templates that need data injection - -**Duration**: 1-2 hours - -### Current Situation - -**docs.templ** currently loads data via fetch (correct approach): -```javascript -// In template inline JS (lines 228-285) -fetch('/docs/search-index.json') - .then(r => r.json()) - .then(data => { searchDocs = data; }) -``` - -**Go handler provides**: -```go -// internal/docs/http_handler.go line 417 -func (h *HTTPHandler) ServeSearchIndex(c *echo.Context) error { - index, err := h.docs.GenerateSearchIndex(); - return c.JSON(http.StatusOK, index) -} -``` - -### Migration Strategy - -Keep this pattern! It's already correct: - -1. **Server provides JSON endpoints** for data -2. **Client fetches data** via APIs -3. **No window globals** needed - -### Future Templates (Dashboard Pattern) - -For new pages that follow the dashboard pattern: - -1. **Server renders HTML** with initial data (SSR) -2. **Client fetches updates** via API when needed -3. **No data injection into window** - -Example: -```go -// Go handler -func (h *Handler) ShowDashboard(c *echo.Context) error { - // Fetch data from database - collections := h.queries.GetCollections(c.request().Context()) - - // Render template with data - return templates.Dashboard(collections).Render(c) -} -``` - -```typescript -// Client-side (if needed) -import { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError } from "./api"; - -async function refreshCollections() { - const response = await apiGet("/collections"); - const data = await handleResponse(response); - // Update DOM -} -``` - ---- - -## Phase 5: Cleanup - -**Goal**: Remove all legacy code and old .js files. - -**Duration**: 1-2 hours - -### Step 5.1: Remove Individual .js Files - -After all 27 templates are migrated and verified working: - -```bash -cd /home/nymusicman/Code/bookhoard/web/static - -# Remove old individual JS files -rm -f api.js toast.js events.js dom.js storage.js -rm -f theme.js header.js themeDropdown.js woodPaneling.js -rm -f search.js docs.js collections.js conflicts.js -rm -f dashboard.js admin.js admin_library.js admin_users.js -rm -f analytics.js bookshelf.js devices.js -rm -f index.js login.js profile.js -rm -f queue.js custom_section.js progress.js -rm -f register.js settings.js stats.js sync.js -rm -f testing_templ.js obsidian.js whatsapp.js midnight.js sunset.js -rm -f password_validation.js api_explorer.js - -# Keep only: -# - main.js (bundled) -# - main.js.map (sourcemap) -# - htmx.min.js (separate load) -# - highlight.min.js (for docs) -# - style.css (Tailwind) -``` - -### Step 5.2: Remove All Window Exports - -From all TypeScript files, remove any remaining `(window as any)` exports: - -```bash -# Search for remaining window exports -cd /home/nymusicman/Code/bookhoard/web/src -grep -rn "window as any" *.ts -``` - -Should only find: -- Type assertions being removed -- Comments referencing old pattern - -Remove all: -```typescript -// DELETE these lines: -(window as any).functionName = functionName; -``` - -### Step 5.3: Update Build Scripts - -Verify package.json scripts are correct: - -```json -{ - "scripts": { - "build:css": "tailwindcss -i ./web/static/input.css -o ./web/static/style.css --watch", - "build:css:prod": "tailwindcss -i ./web/static/input.css -o ./web/static/style.css --minify", - "build:ts": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --minify", - "build:ts:dev": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020", - "build:ts:watch": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --watch", - "build": "npm run build:ts && npm run build:css:prod", - "dev": "npm run build:ts:dev && templ generate && go run ." - } -} -``` - -### Step 5.4: Final Verification - -```bash -# Complete build -npm run build -templ generate -go build - -# Check bundle size -ls -lh web/static/main.js -# Expected: ~120-150KB - -# Run tests (if any) -go test ./... - -# Manual testing -go run . -# Test all pages, verify functionality -``` - -### Step 5.5: Update Documentation - -Update or remove any references to old build process in documentation. - ---- - -## Success Criteria - -### Phase 0 Completion -✅ All utility modules have ES exports -✅ Build succeeds -✅ No functionality broken - -### Phase 1 Completion -✅ All 193+ internal window reads converted to imports -✅ All consumer files use ES module imports -✅ Function wrapping eliminated (themeDropdown.ts restructured) -✅ Build succeeds -✅ All pages still work - -### Phase 2 Completion -✅ All template functions registered with Alpine -✅ Old window exports removed -✅ Build succeeds -✅ Alpine loaded and functional -✅ All pages still work (onclick still works) - -### Phase 3 Completion -✅ All 27 templates migrated to @click -✅ All individual script tags replaced with single main.js -✅ Inline JS migrated to TypeScript where appropriate -✅ All onclick handlers converted to @click -✅ All UI state uses x-data/x-show -✅ All templates tested and working - -### Phase 4 Completion -✅ Server data injection uses API endpoints (not window) -✅ Docs search still works -✅ Future pages follow SSR-first pattern - -### Phase 5 Completion -✅ All old .js files removed -✅ No window globals remain -✅ Only main.js and main.js.map exist -✅ Bundle size ~120-150KB minified -✅ Clean codebase ready for launch - ---- - -## Estimated Effort - -| Phase | Duration | Risk | Can Ship After | -|-------|----------|------|----------------| -| Phase 0 | 1-2 hours | Low | ✅ Yes | -| Phase 1 | 4-6 hours | Medium | ✅ Yes | -| Phase 2 | 2-3 hours | Low | ✅ Yes | -| Phase 3 | 27-54 hours | Medium-High | ✅ Yes (per template) | -| Phase 4 | 1-2 hours | Low | ✅ Yes | -| Phase 5 | 1-2 hours | Low | ❌ No (final cleanup) | -| **Total** | **36-69 hours** | | | - -**Recommended Schedule**: -- Week 1: Phases 0-2 (Foundation) - 7-11 hours -- Week 2-4: Phase 3 (Templates) - 5-10 templates per week -- Week 5: Phases 4-5 (Finalize) - 2-4 hours - ---- - -## Rollback Procedures - -### If Phase 0 or Phase 1 Fails - -```bash -# Revert TypeScript changes -git checkout web/src/ - -# Rebuild -npm run build:ts -go run . -``` - -### If Phase 2 Fails - -```bash -# Revert to Phase 1 state (window exports still present) -git checkout web/src/ - -# Rebuild -npm run build:ts -go run . -``` - -### If Phase 3 (Template Migration) Fails - -```bash -# Revert specific problematic template -git checkout templates/PROBLEM_TEMPLATE.templ - -# Regenerate templates -templ generate - -# Rebuild -go run . -``` - -All other migrated templates continue working. - ---- - -## Testing Checklist - -### After Each Phase - -- [ ] Build succeeds (`npm run build:ts`) -- [ ] Go build succeeds (`go build`) -- [ ] Application starts (`go run .`) -- [ ] Homepage loads -- [ ] Login works -- [ ] Dashboard loads -- [ ] No console errors -- [ ] Network tab shows no 404s for .js files - -### After Phase 1 (Internal Dependencies) - -- [ ] Dashboard works -- [ ] Library management works -- [ ] Collections work -- [ ] Admin functions work -- [ ] Queue works -- [ ] Conflicts work -- [ ] All toast notifications work -- [ ] All API calls work - -### After Phase 3 (Each Template) - -- [ ] Page loads -- [ ] All buttons work -- [ ] Modals open/close -- [ ] Dropdowns work -- [ ] Forms submit via HTMX -- [ ] Toast notifications appear -- [ ] No console errors -- [ ] Alpine DevTools shows reactive state (if installed) - ---- - -## Files Modified Summary - -### New Files Created -- None (alpine.ts already exists) - -### Source Files Modified (21 files) -- web/src/main.ts (verify imports are correct) -- web/src/alpine.ts (verify initialization is correct) -- web/src/api.ts (add ES exports, Alpine already present) -- web/src/toast.ts (add ES exports, Alpine already present) -- web/src/storage.ts (already has exports ✅) -- web/src/events.ts (add ES exports and Alpine) -- web/src/dom.ts (already has exports ✅) -- web/src/theme.ts (add ES exports and Alpine) -- web/src/header.ts (add ES exports and Alpine, restructure) -- web/src/woodPaneling.ts (add ES exports and Alpine) -- web/src/themeDropdown.ts (RESTRUCTURE to eliminate wrapping) -- web/src/library.ts (convert 55 window reads to imports, add Alpine) -- web/src/collections.ts (convert 54 window reads to imports, add Alpine) -- web/src/dashboard.ts (convert 10 window reads to imports, add Alpine) -- web/src/admin.ts (convert 14 window reads to imports, add Alpine) -- web/src/queue.ts (convert 8 window reads to imports, add Alpine) -- web/src/conflicts.ts (convert 6 window reads to imports, add Alpine) -- web/src/linking.ts (convert window reads to imports, add Alpine) -- web/src/custom-section-builder.ts (convert window reads to imports, add Alpine) -- web/src/analytics.ts (convert window reads to imports, add Alpine) -- web/src/bookshelf.ts (convert window reads to imports, add Alpine) -- web/src/api-explorer.ts (convert window reads to imports, add Alpine) -- web/src/device-management.ts (convert window reads to imports, add Alpine) -- web/src/search.ts (add Alpine registration) -- web/src/docs.ts (remove window globals, move inline JS to module, add Alpine) -- web/src/password_validation.ts (add Alpine registration) - -### Template Files Modified (27 files) -All templates updated to: -- Remove individual script tags -- Use single `` -- Replace `onclick` with `@click` -- Add `x-data` for stateful components (modals, dropdowns) -- Keep htmx.min.js separate - -### Generated Files -- web/static/main.js (bundled output with Alpine) -- web/static/main.js.map (sourcemap) - -### Files Deleted (Phase 5) -All individual .js files in web/static/ (except main.js, main.js.map, htmx.min.js, highlight.min.js, style.css) - ---- - -## External Dependencies (Not Bundled) - -### htmx.org -- **Status**: Keep as separate script tag -- **Reason**: Core framework, needs to load before main.js -- **Location**: `` - -### Chart.js -- **Status**: Keep on CDN -- **Reason**: 3.4MB minified, only used on analytics page -- **Location**: `` in analytics.templ only - -### highlight.js -- **Status**: Bundled via ESBuild -- **Reason**: Used in docs, small enough (~5KB gzipped) -- **Import**: `import hljs from "highlight.js";` - -### lunr -- **Status**: Bundled via ESBuild -- **Reason**: Used in docs search, small enough (~10KB gzipped) -- **Import**: `import * as lunr from "lunr";` - ---- - -## Architecture Decision Records - -### ADR-001: ES Modules over Window Globals - -**Decision**: Use ES module imports/exports for all TypeScript-to-TypeScript dependencies. - -**Rationale**: -- Standard JavaScript module system -- Better type safety with TypeScript -- Clear dependency chains -- Tree-shaking support -- No global namespace pollution - -**Consequences**: -- Positive: Cleaner code, better IDE support, easier refactoring -- Positive: Standard pattern, easier for new developers -- Neutral: Requires build step (already using ESBuild) - -### ADR-002: Alpine.js for Template Interactivity Only - -**Decision**: Use Alpine.js ONLY as a bridge between templates and TypeScript, not for internal TypeScript dependencies. - -**Rationale**: -- Alpine is designed for template directives (@click, x-show) -- Clean separation: ES modules for code, Alpine for templates -- Avoids over-engineering simple function calls -- Keeps bundle size smaller - -**Consequences**: -- Positive: Clean template syntax -- Positive: Progressive enhancement works -- Positive: Easy to understand data flow -- Neutral: Need to learn Alpine basics (simple) - -### ADR-003: SSR-First with Progressive Enhancement - -**Decision**: Server renders complete HTML with data, client-side JavaScript only for interactivity. - -**Rationale**: -- Faster initial page load -- Better SEO (if needed) -- Works without JavaScript (degrades gracefully) -- Simpler state management -- Aligns with HTMX philosophy - -**Consequences**: -- Positive: Better performance -- Positive: More resilient -- Positive: Easier to debug -- Neutral: Slightly more server work (acceptable) - -### ADR-004: Function Wrapping Elimination - -**Decision**: Eliminate function wrapping (themeDropdown.ts wraps header.ts functions) in favor of proper module composition. - -**Rationale**: -- Clearer code flow -- Better testability -- Easier to understand -- Standard pattern -- App not yet deployed, can refactor - -**Consequences**: -- Positive: Cleaner architecture -- Positive: Easier to maintain -- Negative: More work upfront (acceptable) -- Negative: Need to restructure (acceptable) - ---- - -## Troubleshooting - -### Build Errors - -**Error**: "Cannot find module './xxx'" - -**Solution**: -- Check import path is correct (relative, case-sensitive) -- Check file has `export {}` statements -- Run `npm run build:ts` with clean build - -**Error**: "Alpine is not defined" - -**Solution**: -- Check alpine.ts is imported in main.ts: `import "./alpine";` -- Check Alpine.start() is called -- Check window.Alpine is set - -**Error**: "Cannot read property 'xxx' of undefined" - -**Solution**: -- Check Alpine.global() is called after Alpine.start() -- Check namespace is correct (e.g., `api.post` not `window.api.post`) -- Check template uses correct namespace: `@click="api.post()"` - -### Runtime Errors - -**Error**: "@click handler not working" - -**Possible Causes**: -1. Alpine not loaded - - Check browser console: `window.Alpine` should be defined - - Check main.js is loaded - - Check alpine.ts imports Alpine and starts it - -2. Function not registered with Alpine - - Check source file has `Alpine.global("namespace", { ... })` - - Check namespace matches template usage - -3. Template syntax error - - Check @click syntax: `@click="namespace.function()"` - - Check for typos - -**Error**: "x-show not working" - -**Possible Causes**: -1. Missing x-data parent - - Add `x-data="{ varName: false }"` to parent element - -2. Variable name mismatch - - Check x-data variable name matches x-show variable - -3. Alpine not loaded - - See above - -### Template Errors - -**Error**: "templ generate fails" - -**Solution**: -- Check template syntax (missing closing tags, etc.) -- Check for invalid templ syntax -- Check template file encoding (UTF-8) - -**Error**: "Page not rendering correctly after migration" - -**Solution**: -- Check all script tags are removed except main.js -- Check main.js is loaded -- Check browser console for errors -- Check Alpine DevTools for state -- Verify onclick → @click conversion is correct - -### Performance Issues - -**Issue**: "Bundle size too large (>200KB)" - -**Possible Causes**: -- Check if Chart.js accidentally bundled (should be CDN) -- Check if duplicate dependencies -- Run `esbuild --analyze` to see bundle contents - -**Issue**: "Page load slow" - -**Possible Causes**: -- Check main.js is minified in production -- Check sourcemap not loaded in production -- Check server compression enabled -- Check browser caching headers - ---- - -## Development Workflow - -### During Migration (Phases 0-2) - -```bash -# Terminal 1: Watch TypeScript -npm run build:ts:watch - -# Terminal 2: Watch Templates -templ generate -watch - -# Terminal 3: Run Server -go run . -``` - -### After Migration (All Phases Complete) - -```bash -# Development -npm run dev - -# Production Build -npm run build -templ generate -go build -``` - ---- - -## FAQ - -### Q: Why not put everything in main.ts? - -**A**: Main.ts imports other modules. This keeps code: -- Organized (one file per concern) -- Maintainable (easy to find code) -- Testable (can test individual modules) -- Tree-shakeable (unused code eliminated) - -### Q: Why Alpine.js instead of vanilla JS event listeners? - -**A**: Alpine provides: -- Cleaner template syntax (@click vs onclick) -- Built-in state management (x-show, x-data) -- Better progressive enhancement -- SSR-friendly -- Smaller bundle than React/Vue - -### Q: Why keep HTMX if we have Alpine? - -**A**: They serve different purposes: -- **HTMX**: Server communication (form submissions, API calls) -- **Alpine**: Client-side state (modals, dropdowns, UI) - -They work great together. - -### Q: Can I use React/Vue instead of Alpine? - -**A**: You could, but: -- **Larger bundle size**: React = ~40KB gzipped, Alpine = ~15KB gzipped -- **More complexity**: Need JSX compilation, more build tools -- **Overkill**: For this app's needs, Alpine is sufficient -- **HTMX synergy**: Alpine works better with HTMX - -### Q: What if I need to add a new page? - -**A**: Follow the dashboard pattern: -1. Create Go handler that renders template with data -2. Create .templ file with SSR data -3. Use Alpine for any client-side interactivity -4. Import TypeScript modules in main.ts -5. Register functions with Alpine if templates call them - -### Q: How do I debug issues? - -**A**: -1. **Browser DevTools Console**: Check for errors -2. **Network Tab**: Check main.js loads, no 404s -3. **Alpine DevTools**: Install browser extension to inspect state -4. **Sourcemaps**: Use main.js.map to debug original TypeScript -5. **Go Logs**: Check server logs for errors - ---- - -## Glossary - -- **ES Modules**: Standard JavaScript module system (import/export) -- **ESBuild**: Fast JavaScript bundler -- **Alpine.js**: Lightweight JavaScript framework for UI interactivity -- **HTMX**: Library for dynamic web pages using HTML attributes -- **Templ**: Go templating language that compiles to Go code -- **SSR**: Server-Side Rendering - server generates complete HTML -- **Progressive Enhancement**: Page works without JavaScript, enhanced with it -- **Tree-shaking**: Removing unused code from bundle -- **Sourcemap**: File that maps bundled code back to source code for debugging -- **Window globals**: Variables attached to window object (old pattern) -- **Namespace**: Grouping related functions (e.g., api.post, api.get) - ---- - -## Appendix A: Quick Reference - -### Common Patterns - -**Import ES Module**: -```typescript -import { functionName } from "./module"; -``` - -**Export from Module**: -```typescript -export { functionName1, functionName2 }; -export default function mainFunction() { ... } -``` - -**Register with Alpine**: -```typescript -import { Alpine } from "./alpine"; - -Alpine.global("namespace", { - functionName1, - functionName2, -}); -``` - -**Use in Template**: -```html - - -
- Modal content -
- -``` - ---- - -## Appendix B: File-by-File Checklist - -### Phase 0: Add ES Exports - -- [ ] api.ts - Add exports -- [ ] toast.ts - Add exports -- [ ] storage.ts - Already has exports ✅ -- [ ] events.ts - Add exports -- [ ] dom.ts - Already has exports ✅ -- [ ] theme.ts - Add exports -- [ ] header.ts - Add exports -- [ ] woodPaneling.ts - Add exports - -### Phase 1: Internal Dependencies - -- [ ] dashboard.ts - Convert 10 window reads -- [ ] search.ts - No changes needed -- [ ] device-management.ts - Convert 4 window reads -- [ ] admin.ts - Convert 14 window reads -- [ ] queue.ts - Convert 8 window reads -- [ ] conflicts.ts - Convert 6 window reads -- [ ] linking.ts - Convert window reads -- [ ] custom-section-builder.ts - Convert window reads -- [ ] analytics.ts - Convert window reads -- [ ] bookshelf.ts - Convert window reads -- [ ] api-explorer.ts - Convert window reads -- [ ] library.ts - Convert 55 window reads -- [ ] collections.ts - Convert 54 window reads -- [ ] themeDropdown.ts - RESTRUCTURE to eliminate wrapping -- [ ] docs.ts - Remove window globals - -### Phase 2: Alpine Registration - -- [ ] api.ts - Already registered ✅ -- [ ] toast.ts - Already registered ✅ -- [ ] storage.ts - Add Alpine.global -- [ ] events.ts - Add Alpine.global -- [ ] dom.ts - Add Alpine.global -- [ ] theme.ts - Add Alpine.global -- [ ] header.ts - Add Alpine.global -- [ ] woodPaneling.ts - Add Alpine.global -- [ ] library.ts - Add Alpine.global -- [ ] collections.ts - Add Alpine.global -- [ ] admin.ts - Add Alpine.global -- [ ] queue.ts - Add Alpine.global -- [ ] conflicts.ts - Add Alpine.global -- [ ] docs.ts - Add Alpine.global -- [ ] search.ts - Add Alpine.global -- [ ] device-management.ts - Add Alpine.global -- [ ] password_validation.ts - Add Alpine.global - -### Phase 3: Template Migration - -- [ ] login.templ -- [ ] register.templ -- [ ] profile.templ -- [ ] settings.templ -- [ ] stats.templ -- [ ] sync.templ -- [ ] progress.templ -- [ ] custom_section.templ -- [ ] collection_rules.templ -- [ ] queue.templ -- [ ] conflicts.templ -- [ ] admin.templ -- [ ] admin_library.templ -- [ ] admin_users.templ -- [ ] dashboard.templ -- [ ] bookshelf.templ -- [ ] devices.templ -- [ ] analytics.templ -- [ ] api_explorer.templ -- [ ] collections.templ -- [ ] docs.templ -- [ ] header.templ -- [ ] collection_modal.templ -- [ ] restore_system_collection_modal.templ -- [ ] profile_modal.templ -- [ ] profile_form.templ -- [ ] toast.templ - -### Phase 4: Data Injection - -- [ ] Verify docs search still works -- [ ] Verify no data in window globals -- [ ] Document SSR-first pattern for future pages - -### Phase 5: Cleanup - -- [ ] Remove all old .js files -- [ ] Remove all window exports -- [ ] Update build scripts -- [ ] Final verification -- [ ] Update documentation - ---- - -## End of Migration Plan - -This plan provides a complete, incremental path from the current window-globals architecture to a modern ES modules + Alpine architecture, with clear phases, testing strategies, and rollback procedures. - -**Key Points**: -- Each phase is complete and testable -- Can ship after any phase (except final cleanup) -- No legacy code remains -- Clean architecture ready for launch -- SSR-first for future pages - -**Next Step**: Begin Phase 0 - Add ES module exports to utility modules. diff --git a/ESBUILD_README.md b/ESBUILD_README.md deleted file mode 100644 index 5c075e1..0000000 --- a/ESBUILD_README.md +++ /dev/null @@ -1,210 +0,0 @@ -# ESBuild Migration - Quick Reference - -## Status: Ready to Execute - -**Created**: Complete migration plan (ESBUILD_MIGRATION_PLAN.md) -**Archived**: Old incomplete plan (ESBUILD_SETUP_OLD.md) - -## The Problem - -- **274 window global references** across 21 TypeScript files -- **193+ internal TypeScript dependencies** using window instead of ES imports -- **27 template files** with 150+ unique onclick handlers -- **Function wrapping** creating tight coupling -- **Individual .js files** instead of single bundle - -## The Solution - -**Incremental migration** with no legacy code: - -1. **Phase 0**: Add ES exports (1-2 hours) - Can ship ✅ -2. **Phase 1**: Convert internal dependencies (4-6 hours) - Can ship ✅ -3. **Phase 2**: Alpine.js bridge (2-3 hours) - Can ship ✅ -4. **Phase 3**: Template migration (27-54 hours) - Can ship per template ✅ -5. **Phase 4**: Data injection (1-2 hours) - Can ship ✅ -6. **Phase 5**: Cleanup (1-2 hours) - Final step - -**Total**: 36-69 hours over ~5 weeks - -## Key Architecture Decisions - -### 1. ES Modules for TypeScript → TypeScript -```typescript -// library.ts -import { api } from "./api"; -import { showToast } from "./toast"; - -const response = await api.get("/libraries"); -showToast.success("Loaded!"); -``` - -### 2. Alpine.js for Templates → TypeScript ONLY -```typescript -// api.ts (bottom of file) -import { Alpine } from "./alpine"; - -Alpine.global("api", { - get: apiGet, - post: apiPost, - // ... -}); -``` - -```html - - -``` - -### 3. SSR-First with Progressive Enhancement -- Server renders complete HTML with data -- Client-side JavaScript only for interactivity -- Data loaded via fetch() APIs -- No window globals for data - -## File Changes - -### 21 TypeScript Files -All will have: -- ES module exports (`export { functions }`) -- ES module imports (`import { functions } from "./module"`) -- Alpine registration (`Alpine.global("namespace", { ... })`) -- NO window exports (cleaned up in Phase 5) - -### 27 Template Files -All will have: -- Single script tag: `` -- Alpine directives: `@click` instead of `onclick` -- Alpine state: `x-data` for modals/dropdowns -- NO individual .js file loads - -## Quick Start - -### Right Now: Start Phase 0 - -```bash -# 1. Open web/src/api.ts -# 2. Add at bottom (after Alpine.global block): -export { apiGet, apiPost, apiPut, apiDelete, apiPatch, handleResponse, handleVoidResponse, handleError }; - -# 3. Repeat for other utility modules (toast.ts, events.ts, theme.ts, header.ts, woodPaneling.ts) - -# 4. Build and test -npm run build:ts -go run . -``` - -### After Phase 0 Complete - -Move to Phase 1: Convert internal dependencies (see full plan) - -## Critical Path - -**Must complete in order**: -1. Phase 0 (ES exports) - Enables Phase 1 -2. Phase 1 (Internal imports) - Enables Phase 2 -3. Phase 2 (Alpine registration) - Enables Phase 3 -4. Phase 3 (Template migration) - Can do incrementally -5. Phase 4 (Data injection) - Verify pattern -6. Phase 5 (Cleanup) - Final polish - -## Safety Features - -✅ **Incremental**: Each phase is complete and testable -✅ **Ship anytime**: Can deploy after Phases 0-2, or during Phase 3 -✅ **Easy rollback**: Revert individual files if issues -✅ **No legacy**: Each file fully migrated, no half-states -✅ **Testing**: Clear verification criteria for each phase - -## Common Patterns - -### Before Migration -```typescript -// TypeScript -function doWork() { ... } -(window as any).doWork = doWork; - -// Another file -const result = (window as any).doWork(); -``` - -```html - - -``` - -### After Migration -```typescript -// work.ts -export function doWork() { ... } -import { Alpine } from "./alpine"; -Alpine.global("work", { doWork }); - -// Another file -import { doWork } from "./work"; -const result = doWork(); -``` - -```html - - -``` - -## Testing - -### After Each Phase -```bash -# Build -npm run build:ts - -# Run -go run . - -# Test -# - Homepage loads -# - Login works -# - Dashboard loads -# - No console errors -# - No 404s for .js files -``` - -## Rollback - -If any phase has issues: -```bash -# Revert changes -git checkout web/src/ # For TypeScript issues -git checkout templates/PROBLEM.templ # For template issues - -# Rebuild -npm run build:ts -templ generate -go run . -``` - -## Next Steps - -1. **Read** the full plan: `ESBUILD_MIGRATION_PLAN.md` -2. **Start** Phase 0: Add ES exports (1-2 hours) -3. **Test** thoroughly after each phase -4. **Track** progress using checklist in plan -5. **Ask** questions if anything is unclear - -## Resources - -- **Full Plan**: ESBUILD_MIGRATION_PLAN.md (this document) -- **Old Plan**: ESBUILD_SETUP_OLD.md (archived, incomplete) -- **Alpine Docs**: https://alpinejs.dev/ -- **ESBuild Docs**: https://esbuild.github.io/ -- **Templ Docs**: https://github.com/a-h/templ - -## Support - -If you encounter issues: -1. Check the "Troubleshooting" section in the full plan -2. Review the "Testing Checklist" for your phase -3. Use the "Rollback Procedures" if needed -4. Reference the "Quick Reference" patterns - ---- - -**Remember**: This is an incremental migration with clear phases. Take it one phase at a time, test thoroughly, and you'll have a clean, modern codebase ready for launch. diff --git a/ESBUILD_SETUP_OLD.md b/ESBUILD_SETUP_OLD.md deleted file mode 100644 index e0e4f61..0000000 --- a/ESBUILD_SETUP_OLD.md +++ /dev/null @@ -1,2199 +0,0 @@ -# ESBuild Setup Guide - -## Overview - -Migrate to a single bundled `main.js` using ESBuild for better performance, simplified deployment, and broader browser compatibility (ES2020 target = Chrome 80+, Firefox 72+, Safari 13.1+, Edge 80+). - -**Target Bundle Size**: ~100KB minified + gzip -**Browser Support**: ES2020 (Chrome 80+, Firefox 72+, Safari 13.1+, Edge 80+, Ubuntu 22.04 LTS browsers) -**Architecture**: SSR-first, procedural TypeScript, progressive enhancement (per PROJECT_GUIDELINES.md) - ---- - -## Status: What's Already Done ✅ - -### 1. package.json Dependencies (Already Correct) - -**File**: `package.json` -**Status**: ✅ Already configured - NO CHANGES NEEDED - -Dependencies (lines 14-26): - -- `htmx.org`, `alpinejs`, `lunr`, `highlight.js` already in dependencies -- `esbuild`, `typescript`, TailwindCSS tooling already in devDependencies - -### 2. Build Scripts (Already Correct) - -**File**: `package.json` -**Status**: ✅ Already configured - NO CHANGES NEEDED - -Lines 8-10: - -```json -"build:ts": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --minify", -"watch:ts": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020", -"dev": "concurrently \"npm run watch:css\" \"npm run watch:ts\" \"npm run watch:templ\"", -``` - -Already using `--target=es2020` for broad browser compatibility. - -### 3. Entry Point (Already Exists) - -**File**: `web/src/main.ts` -**Status**: ✅ Already exists - NO CHANGES NEEDED - -Already imports all 29 modules correctly (24 lines). Main.ts stays simple - imports happen in individual files where used. - ---- - -## Phase 1: Clean Up docs.ts - -**File**: `web/src/docs.ts` (99 lines) -**Status**: Imports already added ✅ -**Lines to modify**: 50, 56, 73 (remove window globals), 99 (replace with Alpine) - -**Note**: Lines 3-4 already have the correct imports: - -```typescript -import * as lunr from "lunr"; -import hljs from "highlight.js"; -``` - -### Step 1: Remove (window as any).lunr check - -**Location**: Line 50 - -**Current** (lines 49-53): - -```typescript -if (!(window as any).lunr) { - console.warn("Lunr.js not loaded"); - return; -} -``` - -**Change to**: - -```typescript -// Lunr now bundled via ESBuild -``` - -### Step 2: Replace (window as any).lunrIndex with direct lunr usage - -**Location**: Line 56 - -**Current** (lines 55-62): - -```typescript -const idx = (window as any).lunrIndex; -if (!idx) { - searchResults.innerHTML = - '

Search index not loaded

'; - searchResults.classList.remove("hidden"); - return; -} -``` - -**Change to**: - -```typescript -const idx = lunr.Builder.loadJs(searchIndex); -if (!idx) { - searchResults.innerHTML = - '

Search index not loaded

'; - searchResults.classList.remove("hidden"); - return; -} -``` - -### Step 3: Replace (window as any).docsData with direct import - -**Location**: Line 73 - -**Current** (lines 70-76): - -```typescript - .map((result: { ref: string }) => { - const doc = (window as any).docsData?.[result.ref]; - if (!doc) return ""; -``` - -**Change to**: - -```typescript - .map((result: { ref: string }) => { - const doc = docs[result.ref]; - if (!doc) return ""; -``` - -### Step 4: Replace window export with Alpine global - -**Location**: Line 99 - -**Current** (lines 98-99): - -```typescript -document.addEventListener("DOMContentLoaded", () => { - initializeDocsSearch(); -}); - -(window as any).toggleSidebar = toggleSidebar; -``` - -**Change to**: - -```typescript -document.addEventListener("DOMContentLoaded", () => { - initializeDocsSearch(); - - // Register with Alpine globally - if (typeof window.Alpine !== "undefined") { - window.Alpine.effect(() => { - window.Alpine.global("docs", { - toggleSidebar, - }); - }); - } -}); -``` - -**Why Alpine.global()**: Makes `toggleSidebar()` available to Alpine templates via `@click="docs.toggleSidebar()"` - ---- - -## Phase 2: Migrate TypeScript Files to Alpine Registration - -**Approach**: Replace `(window as any)` exports with Alpine.js global registration - -### Architecture Note: Alpine.js for Client-Side State - -**Why Alpine over window exports**: - -- Modern, reactive framework (already in package.json) -- Clean template syntax: `@click` instead of `onclick="window.func()"` -- Built-in state management: `x-data`, `x-show`, `x-model` -- Works with SSR (progressive enhancement) -- No global namespace pollution - -**Hybrid approach**: - -- **Alpine**: Client-side state (modals, dropdowns, theme, forms) -- **HTMX**: Server calls (already using for form submissions) - -### Step 1: Create Alpine Registration Helper - -**New file**: `web/src/alpine.ts` - -```typescript -import Alpine from "alpinejs"; - -// Initialize Alpine -window.Alpine = Alpine; -Alpine.start(); - -// Re-export Alpine for other modules to use -export { Alpine }; -``` - -**Add to main.ts**: Append this line at the end of `web/src/main.ts`: - -```typescript -import "./alpine"; -``` - -### Step 2: Update TypeScript Files to Register with Alpine - -#### Pattern: Object Registration (Multiple Related Functions) - -**Example**: `web/src/toast.ts` - -**Current** (lines 229-236): - -```typescript -(window as any).showToast = { - error: (message: string, duration?: number) => - showToast(message, "error", duration), - success: (message: string, duration?: number) => - showToast(message, "success", duration), - info: (message: string, duration?: number) => - showToast(message, "info", duration), -}; -``` - -**Change to**: - -```typescript -import { Alpine } from "./alpine"; - -// Register toast API with Alpine -Alpine.global("showToast", { - error: (message: string, duration?: number) => - showToast(message, "error", duration), - success: (message: string, duration?: number) => - showToast(message, "success", duration), - info: (message: string, duration?: number) => - showToast(message, "info", duration), -}); -``` - -**Usage in templates**: - -```html - - - - - - - -``` - -#### Pattern: Namespace Registration (Grouping Related Functions) - -**Example**: `web/src/api.ts` - -**Current** (lines 90-99): - -```typescript -(window as any).api = { - get: apiGet, - post: apiPost, - put: apiPut, - delete: apiDelete, - patch: apiPatch, - handleResponse, - handleVoidResponse, - handleError, -}; -``` - -**Change to**: - -```typescript -import { Alpine } from "./alpine"; - -Alpine.global("api", { - get: apiGet, - post: apiPost, - put: apiPut, - delete: apiDelete, - patch: apiPatch, - handleResponse, - handleVoidResponse, - handleError, -}); -``` - -**Usage in templates**: - -```html - - - - - -``` - -#### Pattern: Stateful Components (Dropdowns, Modals) - -**Example**: `web/src/header.ts` (theme dropdown) - -**Current approach**: Multiple window exports - -**New approach**: Create Alpine component with state - -**Add to `web/src/header.ts`**: - -```typescript -import { Alpine } from "./alpine"; - -// Register theme dropdown component -Alpine.data("themeDropdown", () => ({ - open: false, - - toggle() { - this.open = !this.open; - }, - - changeTheme(theme: string) { - changeThemeTo(theme); // Reuse existing function - this.open = false; - }, - - init() { - // Load saved theme on init - applyTheme(loadTheme()); - }, -})); -``` - -**Usage in templates**: - -```html - - - - - -
- -
- -
-
-``` - -### Files Requiring Alpine Migration - -**High priority** (used by 10+ templates): - -1. `web/src/toast.ts` - Register `showToast` -2. `web/src/api.ts` - Register `api` object -3. `web/src/events.ts` - Register event functions -4. `web/src/storage.ts` - Register storage helpers - -**Medium priority** (stateful components): 5. `web/src/header.ts` - Create `themeDropdown` component 6. `web/src/woodPaneling.ts` - Create `woodPaneling` component 7. `web/src/collections.ts` - Register collection functions 8. `web/src/conflicts.ts` - Register conflict functions 9. `web/src/search.ts` - Register search functions 10. `web/src/dom.ts` - Register DOM helpers - -**Lower priority** (page-specific): 11. `web/src/dashboard.ts` - Dashboard-specific functions 12. `web/src/devices.ts` - Device management functions 13. `web/src/queue.ts` - Queue management functions 14. `web/src/analytics.ts` - Analytics functions (Chart.js) 15. `web/src/admin.ts` - Admin functions - -**Keep as-is for now** (already working): - -- Lunr/Highlight.js imports (docs.ts) - already handled -- Chart.js usage - already loaded via CDN - ---- - -## Phase 3: Update Templates to Alpine Directives - -### Template Migration Strategy - -**151 onclick handlers need migration** across 27 templates. Use this approach: - -1. **Add `x-data` component** to sections with state -2. **Replace `onclick`** with `@click` -3. **Replace `class="hidden"`** with `x-show="!open"` -4. **Add transitions** with `x-transition` -5. **Use `x-model`** for form inputs - -### Template Changes (27 files) - ---- - -#### 1. templates/collections.templ - -**File**: `templates/collections.templ` (271 lines) - -**Remove script blocks** (lines 12-13, 111-112): -DELETE all individual ` -``` - -**Change to**: - -```templ - -``` - -**Update onclick handlers**: - -**Line 63** - Collection card: - -```templ - -
- - -
-``` - -**Line 119** - Back button: - -```templ - - - -``` - -**Change to Alpine**: - -```templ -
- - -
- -
- - - - - -
-
-
-``` - -**Update user menu** (lines 57-60, 65): - -```templ - - -``` - -**Change to**: - -```templ -
- -
-
-``` - ---- - -#### 7. templates/analytics.templ - -**File**: `templates/analytics.templ` (100 lines) - -**Remove first script block** (lines 10-14): - -```templ - - - - - -``` - -DELETE these lines entirely. - -**Note**: Chart.js remains on CDN (intentionally not bundled - 3.4MB, only used on 1 page). - -**Replace analytics.js script** (line 97): - -```templ - -``` - -**Change to**: - -```templ - - -``` - ---- - -#### 8. templates/bookshelf.templ - -**File**: `templates/bookshelf.templ` (266 lines) - -**Remove first script block** (lines 10-13): - -```templ - - - - -``` - -DELETE these lines entirely. - -**Replace bookshelf.js script** (line 63): - -```templ - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 9. templates/devices.templ - -**File**: `templates/devices.templ` (870 lines) - -**Replace script block** (lines 12-16): - -```templ - - - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 10. templates/index.templ - -**File**: `templates/index.templ` (165 lines) - -**Remove first script block** (lines 10-12): - -```templ - - - -``` - -DELETE these lines entirely. - -**Replace index.js script** (line 138): - -```templ - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 11. templates/login.templ - -**File**: `templates/login.templ` (81 lines) - -**Replace script block** (lines 10-12): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 12. templates/profile.templ - -**File**: `templates/profile.templ` (68 lines) - -**Replace script block** (lines 9-11): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 13. templates/queue.templ - -**File**: `templates/queue.templ` (169 lines) - -**Remove first script block** (lines 12-15): - -```templ - - - - -``` - -DELETE these lines entirely. - -**Replace queue.js script** (line 166): - -```templ - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 14. templates/conflicts.templ - -**File**: `templates/conflicts.templ` (239 lines) - -**Remove first script block** (lines 12-15): - -```templ - - - - -``` - -DELETE these lines entirely. - -**Replace conflicts.js script** (line 234): - -```templ - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 15. templates/custom_section.templ - -**File**: `templates/custom_section.templ` (168 lines) - -**Replace script block** (lines 10-13): - -```templ - - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 16. templates/admin_users.templ - -**File**: `templates/admin_users.templ` (145 lines) - -**Replace script block** (lines 9-12): - -```templ - - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 17. templates/collection_rules.templ - -**File**: `templates/collection_rules.templ` (420 lines) - -**Replace script block** (lines 10-12): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 18. templates/progress.templ - -**File**: `templates/progress.templ` (114 lines) - -**Replace script block** (lines 12-14): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 19. templates/register.templ - -**File**: `templates/register.templ` (97 lines) - -**Replace script block** (lines 10-12): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 20. templates/settings.templ - -**File**: `templates/settings.templ` (157 lines) - -**Replace script block** (lines 9-11): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 21. templates/stats.templ - -**File**: `templates/stats.templ` (162 lines) - -**Replace script block** (lines 9-11): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 22. templates/sync.templ - -**File**: `templates/sync.templ` (184 lines) - -**Replace script block** (lines 9-12): - -```templ - - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 23. templates/testing_templ.templ - -**File**: `templates/testing_templ.templ` (115 lines) - -**Replace script block** (lines 9-11): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 24. themes/obsidian.templ - -**File**: `themes/obsidian.templ` (91 lines) - -**Replace script block** (lines 9-11): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 25. themes/whatsapp.templ - -**File**: `themes/whatsapp.templ` (86 lines) - -**Replace script block** (lines 9-11): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 26. themes/midnight.templ - -**File**: `themes/midnight.templ` (91 lines) - -**Replace script block** (lines 9-11): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -#### 27. themes/sunset.templ (same pattern as other themes) - -**File**: `themes/sunset.templ` (86 lines) - -**Replace script block** (lines 9-11): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -### Remaining Templates (7-26) - -For templates **7-26**, follow this pattern: - -1. **Remove all individual script tags** (usually 2-5 scripts) -2. **Replace with single script**: `` -3. **Replace `onclick` with `@click`** -4. **Add `x-data` for stateful components** (modals, dropdowns) - -**Quick find-replace patterns**: - -```bash -# In each template file: -onclick="funcName()" → @click="module.funcName()" -onclick="func('param')" → @click="module.func('param')" -class="hidden" → x-show="!isOpen" (with x-data parent) -``` - -**Common stateful patterns**: - -**Modal pattern**: - -```templ - - - - - -
- -
- -
-
-``` - -**Confirm delete pattern**: - -```templ - - - - - -``` - -**Form submission** (keep HTMX for server calls): - -```templ - -
- - - Name required -
-``` - -**File**: `themes/sunset.templ` (86 lines) - -**Replace script block** (lines 9-11): - -```templ - - - -``` - -**Change to**: - -```templ - -``` - ---- - -## Phase 4: Build and Verify - -### Step 1: Create Alpine Registration File - -**File**: `web/src/alpine.ts` (already exists, needs update) - -**Current file** (missing window.Alpine): - -```typescript -import Alpine from "alpinejs"; - -// Initialize Alpine -Alpine.start(); - -// Re-export Alpine for other modules to use -export { Alpine }; -``` - -**Change to** (add window.Alpine + type declaration): - -```typescript -import Alpine from "alpinejs"; - -// Extend Window interface to include Alpine -declare global { - interface Window { - Alpine: typeof Alpine; - } -} - -// Initialize Alpine -window.Alpine = Alpine; -Alpine.start(); - -// Re-export Alpine for other modules to use -export { Alpine }; -``` - -**Why type declaration**: Fixes TypeScript error "Property 'Alpine' does not exist on type 'Window'" - -**Why `window.Alpine`**: Required for Alpine DevTools browser extension to work. - -### Step 2: Update main.ts - -**File**: `web/src/main.ts` - -**Add at end** (after all other imports): - -```typescript -import "./alpine"; -``` - -### Step 3: Build the Bundle - -```bash -npm run build:ts -``` - -**Expected output**: Creates `web/static/main.js` (~350-450KB unminified, ~120-150KB minified with Alpine bundled) + `main.js.map` sourcemap - -**Why larger**: Alpine.js adds ~15KB gzipped to bundle (worth it for cleaner code). - -### Step 4: Regenerate Templates - -```bash -templ generate -``` - -**Expected output**: Regenerates all .templ files with updated Alpine directives - -### Step 5: Verify Alpine is Loaded - -```bash -head -n 50 web/static/main.js | grep -i alpine -``` - -**Check for**: Alpine initialization code present in bundle. - -### Step 6: Test in Browser - -1. Start dev server: `go run .` -2. Open browser DevTools (F12) → Console -3. Verify Alpine loaded: - - ```javascript - typeof window.Alpine !== "undefined"; // should be true - ``` - -4. Test key pages: - - **Dashboard** (tests theme dropdown, wood paneling) - - **Docs page** (tests lunr search, sidebar toggle) - - **Collections** (tests modal, multiple onclick handlers) - - **Header** (tests theme dropdown, user menu) - - **Devices** (tests multiple modals) - -5. **Check Alpine DevTools** (optional): - - Install Alpine DevTools browser extension - - Inspect `x-data` components in DevTools panel - - Verify reactive state changes - -### Step 7: Verify Functionality - -**In browser console, test Alpine globals**: - -```javascript -// Should all be accessible via Alpine -Alpine.stores?.toast; // Toast store -Alpine.global("showToast"); // Global function -``` - -**Test interactions**: - -- Theme dropdown opens/closes smoothly -- Modals open with transitions -- Toast notifications appear -- Forms still submit via HTMX -- Search works on docs page -- All 151 onclick handlers work with `@click` - -### Step 8: Check Bundle Size - -```bash -ls -lh web/static/main.js -``` - -**Expected**: ~120-150KB (minified with Alpine included) - -**Breakdown**: - -- App code: ~100KB -- Alpine.js: ~15KB gzipped -- Lunr: ~10KB gzipped -- Highlight.js: ~5KB gzipped -- **Total**: ~130KB / ~40KB gzipped - ---- - -## Phase 5: Migrate TypeScript Files to Alpine (Step-by-Step) - -### Priority 1: Core Utilities (Do These First) - -#### 1. web/src/toast.ts - -**Current state**: Already has Alpine import at line 1, but not using it yet - -**Add import** (already at line 1): - -```typescript -import { Alpine } from "./alpine"; -``` - -**Replace export** (lines 229-236): - -```typescript -// Before -(window as any).showToast = { - error: (message: string, duration?: number) => - showToast(message, "error", duration), - success: (message: string, duration?: number) => - showToast(message, "success", duration), - info: (message: string, duration?: number) => - showToast(message, "info", duration), -}; -``` - -**Change to**: - -```typescript -// After - Register toast API with Alpine -Alpine.global("showToast", { - error: (message: string, duration?: number) => - showToast(message, "error", duration), - success: (message: string, duration?: number) => - showToast(message, "success", duration), - info: (message: string, duration?: number) => - showToast(message, "info", duration), -}); -``` - -**Template usage**: - -```html - - - - - -``` - -#### 2. web/src/api.ts - -**Add import** (at top): - -```typescript -import { Alpine } from "./alpine"; -``` - -**Replace export** (lines 90-99): - -```typescript -// Before -(window as any).api = { - get: apiGet, - post: apiPost, - put: apiPut, - delete: apiDelete, - patch: apiPatch, - handleResponse, - handleVoidResponse, - handleError, -}; -``` - -**Change to**: - -```typescript -// After - Register API with Alpine -Alpine.global("api", { - get: apiGet, - post: apiPost, - put: apiPut, - delete: apiDelete, - patch: apiPatch, - handleResponse, - handleVoidResponse, - handleError, -}); -``` - -**Also update line 85-87** (toast error calls): - -```typescript -// Before -if ((window as any).showToast?.error) { - (window as any).showToast.error(message); -} - -// After (will work once toast.ts is migrated) -if (window.Alpine?.stores?.toast?.error) { - window.Alpine.stores.toast.error(message); -} -// Or keep using window.showToast during migration -``` - -**Template usage**: - -```html - - - - - -``` - -#### 3. web/src/storage.ts - -**Add import** (at top): - -```typescript -import { Alpine } from "./alpine"; -``` - -**Replace export** (lines 53-67): - -```typescript -// Before -(window as any).storage = { - getToken, - setToken, - removeToken, - getRefreshToken, - setRefreshToken, - removeRefreshToken, - getTheme, - setTheme, - getSelectedLibrary, - setSelectedLibrary, - getSelectedBook, - setSelectedBook, - clearAll, -}; -``` - -**Change to**: - -```typescript -// After - Register storage helpers with Alpine -Alpine.global("storage", { - getToken, - setToken, - removeToken, - getRefreshToken, - setRefreshToken, - removeRefreshToken, - getTheme, - setTheme, - getSelectedLibrary, - setSelectedLibrary, - getSelectedBook, - setSelectedBook, - clearAll, -}); -``` - -**Template usage**: - -```html - - - - - -``` - -### Priority 2: Stateful Components - -#### 4. web/src/header.ts (Theme Dropdown) - -**Add import** (at top): - -```typescript -import { Alpine } from "./alpine"; -import { changeThemeTo, loadTheme, applyTheme } from "./theme"; -``` - -**Add Alpine component** (at end of file): - -```typescript -Alpine.data("themeDropdown", () => ({ - open: false, - - toggle() { - this.open = !this.open; - }, - - changeTheme(theme: string) { - changeThemeTo(theme); - this.open = false; - }, - - init() { - const savedTheme = loadTheme(); - if (savedTheme) applyTheme(savedTheme); - }, -})); -``` - -**Template usage**: - -```html -
- -
- -
-
-``` - -#### 5. web/src/woodPaneling.ts - -**Add Alpine component** (at end of file): - -```typescript -import { Alpine } from "./alpine"; - -Alpine.data("woodPaneling", () => ({ - current: localStorage.getItem("woodPaneling") || "none", - - change(style: string) { - this.current = style; - localStorage.setItem("woodPaneling", style); - // Apply logic here... - }, -})); -``` - -**Template usage**: - -```html -
- - -
-``` - -### Priority 3: Page-Specific Functions - -#### 6. web/src/collections.ts - -**Add import**: - -```typescript -import { Alpine } from "./alpine"; -``` - -**Register as namespace** (at end): - -```typescript -Alpine.global("collections", { - back: backToCollections, - showAddModal: showAddBooksModal, - hideAddModal: hideAddBooksModal, - addSelected: addSelectedBooks, - removeBook: removeBook, - navigate: navigateToCollection, -}); -``` - -#### 7. web/src/devices.ts, queue.ts, conflicts.ts - -**Follow same pattern as collections.ts** - register as namespace with Alpine.global() - ---- - -## Phase 6: Clean Up (Optional) - -### Remove Individual JS Files - -After verifying everything works, remove old individual .js files: - -```bash -rm web/static/api.js -rm web/static/events.js -rm web/static/dom.js -rm web/static/toast.js -rm web/static/storage.js -rm web/static/theme.js -rm web/static/header.js -rm web/static/themeDropdown.js -rm web/static/woodPaneling.js -rm web/static/search.js -rm web/static/docs.js -rm web/static/collections.js -rm web/static/conflicts.js -rm web/static/dashboard.js -rm web/static/admin*.js -rm web/static/analytics.js -rm web/static/bookshelf.js -rm web/static/devices.js -rm web/static/index.js -rm web/static/login.js -rm web/static/profile.js -rm web/static/queue.js -rm web/static/custom_section.js -rm web/static/progress.js -rm web/static/register.js -rm web/static/settings.js -rm web/static/stats.js -rm web/static/sync.js -rm web/static/testing_templ.js -rm web/static/obsidian.js -rm web/static/whatsapp.js -rm web/static/midnight.js -rm web/static/sunset.js -``` - -**Note**: Keep `main.js` and `main.js.map` - ---- - -## Troubleshooting - -### Issue: "Alpine is not defined" - -**Cause**: Alpine not initialized or alpine.ts not imported in main.ts -**Fix**: Ensure `import './alpine';` is at end of main.ts and Alpine.start() is called - -### Issue: "Alpine.global is not a function" - -**Cause**: Using old Alpine syntax -**Fix**: Use `Alpine.data()` for components or register globals before `Alpine.start()` - -### Issue: "lunr is not defined" - -**Cause**: Missing import in docs.ts -**Fix**: Ensure `import * as lunr from 'lunr';` is at line 4 - -### Issue: "hljs is not defined" - -**Cause**: Missing import in docs.ts -**Fix**: Ensure `import hljs from 'highlight.js';` is at line 5 - -### Issue: `@click` handlers not working - -**Cause**: Alpine not loaded or syntax error in directive -**Fix**: Check browser console, verify Alpine initialized, check directive syntax - -### Issue: `x-show` elements always visible - -**Cause**: Missing `x-cloak` CSS or Alpine not loaded before DOM ready -**Fix**: Add `[x-cloak] { display: none !important; }` to CSS, ensure Alpine loads early - -### Issue: Bundle too large (>200KB minified) - -**Cause**: Check if large dependencies accidentally bundled -**Fix**: Verify Chart.js is NOT bundled (should remain CDN link in analytics.templ) - -### Issue: 404 errors for .js files - -**Cause**: Script tags not updated in templates -**Fix**: Check that all template script tags point to `/static/main.js` - -### Issue: Theme dropdown doesn't close - -**Cause**: Missing `@click.away` directive -**Fix**: Add `@click.away="open = false"` to dropdown element - -### Issue: Search not working on docs page - -**Cause**: lunr not properly imported or bundled -**Fix**: Check docs.ts imports and rebuild with `npm run build:ts` - -### Issue: Modals don't open - -**Cause**: `x-show` variable not reactive or parent `x-data` missing -**Fix**: Ensure modal is wrapped in `x-data="{ modalOpen: false }"` and button uses `@click="modalOpen = true"` - -### Issue: HTMX forms stopped working after Alpine migration - -**Cause**: Alpine event handlers conflicting with HTMX -**Fix**: HTMX and Alpine work together - ensure `hx-post` is on form, `@click` is on buttons (not form) - ---- - -## External Dependencies (Not Bundled) - -### Chart.js - -**File**: `templates/analytics.templ` (line 14) -**Reason**: 3.4MB minified, only used on 1 page (analytics) -**Approach**: Keep as CDN link: `` - -### htmx.org - -**File**: Loaded via separate script tag in base templates -**Reason**: Core framework, needs to load before main.js -**Approach**: Keep as separate script tag (not bundled), works great with Alpine - ---- - -## Architecture Notes - -### Why Alpine.js Over Window Exports? - -**Alpine is the modern approach** for this codebase: - -1. **Reactive state management**: `x-data`, `x-show`, `x-model` instead of manual DOM manipulation -2. **Clean templates**: `@click` instead of `onclick="window.func()"` -3. **Component-based**: `Alpine.data()` for reusable components -4. **SSR-friendly**: Works with progressive enhancement -5. **No global pollution**: Functions registered in Alpine scope, not window -6. **Better DX**: Alpine DevTools for debugging reactive state - -### Hybrid Approach: Alpine + HTMX - -- **Alpine**: Client-side state (modals, dropdowns, theme, form validation) -- **HTMX**: Server calls (form submissions, API calls, data fetching) -- **Why both**: HTMX excels at server communication, Alpine excels at client-side state - -### ESBuild + Alpine Benefits - -- **Single bundle**: Alpine bundled with app code (~15KB gzipped) -- **No CDN dependency**: Faster load, no network request for Alpine -- **Tree-shaking**: Unused Alpine features not included -- **ES2020 target**: Works on Chrome 80+, Firefox 72+, Safari 13.1+ - -### Migration Path: Window → Alpine - -**Old approach**: - -```typescript -(window as any).showToast = showToast; -``` - -```html - -``` - -**New approach**: - -```typescript -Alpine.global("showToast", showToast); -``` - -```html - -``` - -### Why Not Put Everything in main.ts? - -Main.ts stays simple (24 lines) because: - -- **Imports where used**: docs.ts imports lunr, not main.ts -- **Tree-shaking**: Only what's used gets bundled -- **Maintainability**: Each file has its own dependencies -- **Testing**: Can test individual modules independently -- **Alpine registration**: Each file registers its own Alpine globals/components - ---- - -## Success Criteria - -✅ Single `main.js` bundle (~130KB minified with Alpine) -✅ All 27 templates use single script tag -✅ No 404 errors for missing .js files -✅ Alpine.js loaded and functional -✅ No `(window as any)` usage in TypeScript -✅ Lunr search works on docs page -✅ Syntax highlighting works on docs page -✅ Theme dropdown opens/closes with Alpine -✅ Modals use `x-show`/`x-transition` -✅ All `@click` handlers work (151 migrated) -✅ Toast notifications work via Alpine -✅ HTMX forms still submit correctly -✅ Chart.js loads on analytics page (CDN) -✅ Browser console shows no errors -✅ Alpine DevTools shows reactive components -✅ Bundle targets ES2020 for broad compatibility -✅ Sourcemap generated for debugging - ---- - -## Post-Migration: Development Workflow - -### Watch Mode (Development) - -```bash -npm run dev -``` - -Watches both TypeScript and templates, rebuilds on changes. - -### Production Build - -```bash -npm run build:ts -templ generate -go build -``` - -Creates minified bundle, regenerates templates, builds Go binary. - -### Debugging - -Use `main.js.map` sourcemap in browser DevTools to debug original TypeScript sources. - ---- - -## Files Modified Summary - -### New Files Created - -- `web/src/alpine.ts` (new) - Alpine initialization and exports - -### Configuration Files - -- ✅ package.json (already correct - no changes needed) -- ✅ tsconfig.json (already ES2020 - no changes needed) - -### Source Files Modified - -- `web/src/main.ts` (add `import './alpine'` at end) -- `web/src/docs.ts` (remove 4 window globals, add Alpine registration) -- `web/src/toast.ts` (replace window export with Alpine.global) -- `web/src/api.ts` (replace window export with Alpine.global) -- `web/src/storage.ts` (replace window export with Alpine.global) -- `web/src/events.ts` (replace window export with Alpine.global) -- `web/src/header.ts` (add Alpine.data themeDropdown component) -- `web/src/woodPaneling.ts` (add Alpine.data component) -- `web/src/collections.ts` (register namespace with Alpine.global) -- `web/src/conflicts.ts` (register namespace with Alpine.global) -- `web/src/devices.ts` (register namespace with Alpine.global) -- `web/src/queue.ts` (register namespace with Alpine.global) -- `web/src/search.ts` (register namespace with Alpine.global) -- `web/src/dom.ts` (register namespace with Alpine.global) -- Plus 10+ more TypeScript files (register functions with Alpine) - -### Template Files Modified (27 files) - -All templates updated to: - -1. Remove individual script tags -2. Use single `` -3. Replace `onclick` with `@click` -4. Add `x-data` for stateful components (modals, dropdowns) - -Templates: - -1. templates/collections.templ (migrate 7 onclick handlers) -2. templates/docs.templ (migrate sidebar toggle to Alpine) -3. templates/admin.templ -4. templates/admin_library.templ -5. templates/dashboard.templ (theme dropdown, wood paneling) -6. templates/header.templ (theme dropdown, user menu - KEY FILE) -7. templates/analytics.templ (keep Chart.js CDN) -8. templates/bookshelf.templ -9. templates/devices.templ (multiple modals) -10. templates/index.templ -11. templates/login.templ -12. templates/profile.templ -13. templates/queue.templ -14. templates/conflicts.templ -15. templates/custom_section.templ -16. templates/admin_users.templ -17. templates/collection_rules.templ -18. templates/progress.templ -19. templates/register.templ -20. templates/settings.templ -21. templates/stats.templ -22. templates/sync.templ -23. templates/testing_templ.templ -24. themes/obsidian.templ -25. themes/whatsapp.templ -26. themes/midnight.templ -27. themes/sunset.templ - -### Generated Files - -- `web/static/main.js` (new bundled output with Alpine) -- `web/static/main.js.map` (new sourcemap) - -### Files to Delete (After Verification) - -All individual .js files in `web/static/` (except main.js and main.js.map): - -- api.js, events.js, dom.js, toast.js, storage.js -- theme.js, header.js, themeDropdown.js, woodPaneling.js -- docs.js, search.js, collections.js, conflicts.js -- dashboard.js, admin\*.js, analytics.js, bookshelf.js -- devices.js, index.js, login.js, profile.js, queue.js -- custom_section.js, progress.js, register.js, settings.js -- stats.js, sync.js, testing_templ.js -- obsidian.js, whatsapp.js, midnight.js, sunset.js - ---- - -## Migration Checklist - -Use this checklist to track progress: - -### Phase 1: Clean Up docs.ts - -- [ ] Add imports for lunr and hljs (lines 3-4) -- [ ] Remove `(window as any).lunr` check (line 50) -- [ ] Replace `(window as any).lunrIndex` with `lunr.Builder.loadJs` (line 56) -- [ ] Replace `(window as any).docsData` with `docs` import (line 73) -- [ ] Replace window export with Alpine registration (line 99) - -### Phase 2: Create Alpine Infrastructure - -- [ ] Create `web/src/alpine.ts` with Alpine initialization -- [ ] Add `import './alpine'` to `web/src/main.ts` -- [ ] Build and verify Alpine is loaded (`npm run build:ts`) - -### Phase 3: Migrate Core TypeScript Files (Priority 1) - -- [ ] `web/src/toast.ts` - Register with Alpine.global -- [ ] `web/src/api.ts` - Register with Alpine.global -- [ ] `web/src/storage.ts` - Register with Alpine.global -- [ ] `web/src/events.ts` - Register with Alpine.global -- [ ] `web/src/dom.ts` - Register with Alpine.global - -### Phase 4: Migrate Stateful Components (Priority 2) - -- [ ] `web/src/header.ts` - Create themeDropdown Alpine.data component -- [ ] `web/src/woodPaneling.ts` - Create woodPaneling Alpine.data component -- [ ] Test theme dropdown in browser -- [ ] Test wood paneling in browser - -### Phase 5: Migrate Page-Specific Files (Priority 3) - -- [ ] `web/src/collections.ts` - Register namespace -- [ ] `web/src/devices.ts` - Register namespace -- [ ] `web/src/queue.ts` - Register namespace -- [ ] `web/src/conflicts.ts` - Register namespace -- [ ] `web/src/search.ts` - Register namespace - -### Phase 6: Update Templates (27 files) - -- [ ] templates/header.templ (CRITICAL - theme dropdown) -- [ ] templates/docs.templ (sidebar toggle) -- [ ] templates/dashboard.templ (wood paneling) -- [ ] templates/collections.templ (7 onclick handlers) -- [ ] templates/admin.templ through themes/sunset.templ (23 remaining) -- [ ] Run `templ generate` after template changes - -### Phase 7: Test and Verify - -- [ ] Build: `npm run build:ts` -- [ ] Check bundle size: ~130KB minified -- [ ] Start dev server: `go run .` -- [ ] Test all 151 onclick handlers -- [ ] Verify Alpine DevTools shows components -- [ ] Test HTMX forms still work -- [ ] Check browser console for errors -- [ ] Test on Chrome, Firefox, Safari (ES2020 compatibility) - -### Phase 8: Clean Up - -- [ ] Delete all individual .js files from web/static/ -- [ ] Commit changes -- [ ] Deploy and test in production - ---- - -## Additional Resources - -### Alpine.js Documentation - -- Official docs: -- Essentials guide: -- `x-data` docs: -- `@click` docs: -- `x-show` docs: -- `x-transition` docs: - -### Alpine + HTMX Integration - -- Blog post: -- Both work together seamlessly - Alpine for client state, HTMX for server calls - -### ESBuild Documentation - -- Official docs: -- API: -- Bundling: - -### Migration Tips - -1. **Start small**: Migrate 1-2 files at a time, test frequently -2. **Use Alpine DevTools**: Install browser extension for debugging -3. **Keep HTMX for forms**: Don't replace `hx-post` with Alpine fetch -4. **Test in browser**: Console errors will show missing Alpine globals -5. **Progressive migration**: Can use window exports AND Alpine during transition - -### Configuration - -- ✅ package.json (already correct) -- ✅ tsconfig.json (already ES2020) - -### Source Files - -- `web/src/docs.ts` (add imports, remove 4 window globals) - -### Template Files (27 files) - -All templates updated to use single `` tag: - -1. templates/collections.templ -2. templates/docs.templ -3. templates/admin.templ -4. templates/admin_library.templ -5. templates/dashboard.templ -6. templates/header.templ -7. templates/analytics.templ -8. templates/bookshelf.templ -9. templates/devices.templ -10. templates/index.templ -11. templates/login.templ -12. templates/profile.templ -13. templates/queue.templ -14. templates/conflicts.templ -15. templates/custom_section.templ -16. templates/admin_users.templ -17. templates/collection_rules.templ -18. templates/progress.templ -19. templates/register.templ -20. templates/settings.templ -21. templates/stats.templ -22. templates/sync.templ -23. templates/testing_templ.templ -24. themes/obsidian.templ -25. themes/whatsapp.templ -26. themes/midnight.templ -27. themes/sunset.templ - -### Generated Files - -- `web/static/main.js` (new bundled output) -- `web/static/main.js.map` (new sourcemap) - -### Files to Delete (After Verification) - -All individual .js files in `web/static/` (except main.js and main.js.map) - ---- - -## Summary: What We're Achieving - -### The Problem - -- 151 inline `onclick` handlers using `window.funcName()` -- 20+ TypeScript files exporting to `(window as any)` -- 27 template files with 3-5 script tags each -- Manual DOM manipulation for state (modals, dropdowns) -- No reactive state management -- Global namespace pollution - -### The Solution - -1. **Bundle with ESBuild**: Single ~130KB minified bundle (ES2020 target) -2. **Alpine.js for state**: Reactive components with `x-data`, `@click`, `x-show` -3. **Keep HTMX for forms**: Server-side communication remains unchanged -4. **Modern patterns**: No more `(window as any)`, clean templates - -### Benefits - -- ✅ **Performance**: Single HTTP request for all JS, better caching -- ✅ **Maintainability**: Alpine components vs imperative DOM manipulation -- ✅ **Type safety**: No more `window as any` hacks -- ✅ **Developer experience**: Clean templates, Alpine DevTools, sourcemaps -- ✅ **User experience**: Smooth transitions, reactive UI, faster loads -- ✅ **Bundle size**: Only ~40KB gzipped (was 30+ HTTP requests before) - -### Key Architecture Decisions - -1. **Alpine over vanilla event listeners**: Reactive state is cleaner -2. **Alpine over React/Vue**: Lightweight, SSR-friendly, works with HTMX -3. **Bundle Alpine**: No CDN dependency, faster load, tree-shaking -4. **Keep HTMX**: Don't fix what works - HTMX excels at server communication -5. **Progressive migration**: Migrate incrementally, can mix old/new during transition - -### Migration Effort - -- **TypeScript files**: 20+ files (5-10 min each) = ~3 hours -- **Templates**: 27 files (10-15 min each) = ~5 hours -- **Testing**: 2-3 hours -- **Total**: ~10-12 hours for complete migration - -### Maintenance Going Forward - -- Adding new feature? Create Alpine component or register function -- New page? Single `