# 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<Response> { ... }
export async function apiPost(url: string, data?: unknown): Promise<Response> { ... }
export async function handleResponse<T>(response: Response): Promise<T> { ... }
```

```typescript
// library.ts (consumes api.ts)
import { apiGet, handleResponse } from "./api";

const response = await apiGet("/libraries");
const result = await handleResponse<LibrariesResponse>(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
<!-- Before -->
<button onclick="apiPost('/api/save', data)">Save</button>

<!-- After -->
<button @click="api.post('/api/save', data)">Save</button>
```

### 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<typeof setTimeout> | 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 =
        '<p class="p-2 text-sm" style="color: var(--text-secondary)">Search index not loaded</p>';
      searchResults.classList.remove("hidden");
      return;
    }

    const results = idx.search(query);

    if (results.length === 0) {
      searchResults.innerHTML =
        '<p class="p-2 text-sm" style="color: var(--text-secondary)">No results found</p>';
    } else {
      searchResults.innerHTML = results
        .slice(0, 10)
        .map((result: { ref: string }) => {
          const doc = docsData[result.ref];
          if (!doc) return "";

          return `
            <a href="${result.ref}" class="block p-2 hover:bg-opacity-50 transition-colors" style="background-color: var(--bg-secondary)">
              <p class="font-medium text-sm" style="color: var(--text-primary)">${doc.title || result.ref}</p>
              ${doc.section ? `<p class="text-xs" style="color: var(--text-secondary)">${doc.section}</p>` : ""}
            </a>
          `;
        })
        .join("");
    }

    searchResults.classList.remove("hidden");
  } catch (error) {
    console.error("Search error:", error);
    searchResults.innerHTML =
      '<p class="p-2 text-sm" style="color: var(--text-secondary)">Search error</p>';
    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
<script src="/static/htmx.min.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/dom.js"></script>
<script src="/static/admin.js"></script>
```

**After**:
```templ
<script src="/static/htmx.min.js"></script>
<script src="/static/main.js"></script>
```

**Note**: Keep htmx.min.js separate (loaded before main.js)

#### Step B: Convert onclick to @click

**Before**:
```templ
<button onclick="toggleThemeDropdown()">Theme</button>
<div id="theme-dropdown" class="hidden">
  <button onclick="changeThemeTo('tokyo-night')">Tokyo Night</button>
</div>
```

**After**:
```templ
<button @click="header.toggleThemeDropdown()">Theme</button>
<div x-show="themeDropdownOpen" @click.outside="themeDropdownOpen = false" x-transition>
  <button @click="header.changeThemeTo('tokyo-night')">Tokyo Night</button>
</div>
```

#### Step C: Add Alpine State for UI Components

For modals, dropdowns, and any UI with show/hide state:

**Before**:
```templ
<button onclick="showAddBooksModal()">Add Books</button>
<div id="add-books-modal" class="hidden">
  ...modal content...
  <button onclick="hideAddBooksModal()">Cancel</button>
</div>
```

**After**:
```templ
<div x-data="{ addBooksModalOpen: false }">
  <button @click="addBooksModalOpen = true">Add Books</button>
  <div x-show="addBooksModalOpen"
       x-transition
       @click.self="addBooksModalOpen = false"
       class="fixed inset-0 ...">
    ...modal content...
    <button @click="addBooksModalOpen = false">Cancel</button>
  </div>
</div>
```


**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
<button onclick="showToast.success('Saved!')">Save</button>
<button onclick="apiPost('/api/save', data)">Save</button>
```

**After**:
```templ
<button @click="showToast.success('Saved!')">Save</button>
<button @click="api.post('/api/save', data)">Save</button>
```

### 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
<!-- templates/docs.templ -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    window.Alpine.docs.initializePage();
  });
</script>
```

#### header.templ - Most Critical (Used in 17 Places)

This template is included in 17 other templates. Test thoroughly:

```templ
<!-- Before -->
<button onclick="toggleThemeDropdown()">Theme</button>
<div id="theme-dropdown" class="hidden">
  <button onclick="changeThemeTo('tokyo-night')">Tokyo Night</button>
  ...11 more themes...
  <button onclick="changeWoodPaneling('none')">None</button>
  <button onclick="changeWoodPaneling('wood-light')">Wood Light</button>
</div>

<button onclick="toggleUserMenu()">
<div id="user-menu" class="hidden">
  <button onclick="logout()">Logout</button>
</div>
```

```templ
<!-- After -->
<div x-data="{ themeDropdownOpen: false, userMenuOpen: false }">
  <button @click="themeDropdownOpen = !themeDropdownOpen">Theme</button>
  <div x-show="themeDropdownOpen"
       @click.outside="themeDropdownOpen = false"
       x-transition:enter="transition ease-out duration-200"
       x-transition:enter-start="opacity-0 scale-95"
       x-transition:enter-end="opacity-100 scale-100"
       class="absolute right-0 mt-2 w-64 rounded-lg shadow-lg z-50">
    <button @click="header.changeThemeTo('tokyo-night'); themeDropdownOpen = false">Tokyo Night</button>
    ...11 more themes...
    <button @click="woodPaneling.change('none'); themeDropdownOpen = false">None</button>
    <button @click="woodPaneling.change('wood-light'); themeDropdownOpen = false">Wood Light</button>
  </div>

  <button @click="userMenuOpen = !userMenuOpen">
  <div x-show="userMenuOpen"
       @click.outside="userMenuOpen = false"
       x-transition>
    <button @click="header.logout(); userMenuOpen = false">Logout</button>
  </div>
</div>
```

### 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 `<script src="/static/main.js"></script>`
- 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**: `<script src="/static/htmx.min.js"></script>`

### Chart.js
- **Status**: Keep on CDN
- **Reason**: 3.4MB minified, only used on analytics page
- **Location**: `<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>` 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
<!-- Before -->
<button onclick="namespace.functionName()">

<!-- After -->
<button @click="namespace.functionName()">
```

**Add State with Alpine**:
```html
<div x-data="{ modalOpen: false }">
  <button @click="modalOpen = true">Open</button>
  <div x-show="modalOpen" x-transition>
    Modal content
  </div>
</div>
```

---

## 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.
