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

**Change to**:

```typescript
const idx = lunr.Builder.loadJs(searchIndex);
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;
}
```

### 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
<!-- Before -->
<button onclick="showToast.success('Saved!')">Save</button>
<button onclick="showToast.error('Failed!')">Retry</button>

<!-- After -->
<button @click="showToast.success('Saved!')">Save</button>
<button @click="showToast.error('Failed!')">Retry</button>
```

#### 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
<!-- Before -->
<button onclick="api.post('/api/save', data)">Save</button>

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

#### 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
<!-- Before -->
<button onclick="toggleThemeDropdown()">Theme</button>
<div id="theme-dropdown" class="hidden">
  <button onclick="changeThemeTo('tokyo-night')">Tokyo Night</button>
</div>

<!-- After -->
<div x-data="themeDropdown">
  <button @click="toggle()">Theme</button>
  <div x-show="open" @click.away="open = false" x-transition>
    <button @click="changeTheme('tokyo-night')">Tokyo Night</button>
  </div>
</div>
```

### 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 `<script>` tags.

**Replace final script** (line 269):

```templ
<script src="/static/collections.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

**Update onclick handlers**:

**Line 63** - Collection card:

```templ
<!-- Before -->
<div onclick="navigateToCollection(this)" data-href={...}>

<!-- After -->
<div @click="collections.navigate(this)" data-href={...} x-data>
```

**Line 119** - Back button:

```templ
<!-- Before -->
<button onclick="backToCollections()" class="btn-secondary">

<!-- After -->
<button @click="collections.back()" class="btn-secondary">
```

**Line 156** - Show add books modal:

```templ
<!-- Before -->
<button onclick="showAddBooksModal()" class="btn-primary">

<!-- After -->
<button @click="collections.showAddModal()" class="btn-primary">
```

**Lines 231, 252** - Hide modal:

```templ
<!-- Before -->
<button onclick="hideAddBooksModal()">

<!-- After -->
<button @click="collections.hideAddModal()">
```

**Line 255** - Add selected books:

```templ
<!-- Before -->
<button type="button" onclick="addSelectedBooks()">

<!-- After -->
<button type="button" @click="collections.addSelected()">
```

---

#### 2. templates/docs.templ

**File**: `templates/docs.templ` (518 lines)

**Remove external scripts** (lines 16-18):

```templ
<script src="https://cdn.jsdelivr.net/npm/lunr@2.3.9/lunr.min.js"></script>
<script src="/static/lunr-flex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
```

DELETE these lines entirely.

**Note**: `lunr-flex.min.js` doesn't exist - this causes 404 errors.

**Replace docs.js script** (line 517):

```templ
<script src="/static/docs.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

**Update sidebar toggle** (line 23-27):

```templ
<!-- Before -->
<button
  class="lg:hidden fixed top-4 left-4 z-50 bg-background-secondary border border-border rounded p-2"
  onclick="toggleSidebar()"
  aria-label="Toggle menu">
```

**Change to**:

```templ
<!-- After -->
<div x-data="{ sidebarOpen: false }">
  <button
    class="lg:hidden fixed top-4 left-4 z-50 bg-background-secondary border border-border rounded p-2"
    @click="sidebarOpen = !sidebarOpen"
    aria-label="Toggle menu">
```

**Add Alpine state to sidebar** (line 37):

```templ
<!-- Before -->
<div class="sidebar fixed left-0 top-0 bottom-0 w-72 bg-background-secondary ...">
```

**Change to**:

```templ
<!-- After -->
<div class="sidebar fixed left-0 top-0 bottom-0 w-72 bg-background-secondary ..."
     :class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'"
     x-show="sidebarOpen"
     x-transition.opacity>
```

---

#### 3. templates/admin.templ

**File**: `templates/admin.templ` (125 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/api.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/admin.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 4. templates/admin_library.templ

**File**: `templates/admin_library.templ` (163 lines)

**Replace script block** (lines 156-160):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/dom.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/admin_library.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 5. templates/dashboard.templ

**File**: `templates/dashboard.templ` (310 lines)

**Remove first script block** (lines 15-19):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/dom.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/storage.js"></script>
```

DELETE these lines entirely.

**Replace dashboard.js script** (line 80):

```templ
<script src="/static/dashboard.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 6. templates/header.templ

**File**: `templates/header.templ` (188 lines)

**Replace script block** (lines 184-187):

```templ
<script src="/static/storage.js"></script>
<script src="/static/theme.js"></script>
<script src="/static/header.js"></script>
<script src="/static/themeDropdown.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

**Update theme dropdown** (lines 48-93):
This is a perfect Alpine use case - replace with Alpine component.

**Current structure** (simplified):

```templ
<button onclick="toggleThemeDropdown()">Theme</button>
<div id="theme-dropdown" class="hidden">
  <button onclick="changeThemeTo('tokyo-night')">Tokyo Night</button>
  <button onclick="changeThemeTo('dracula')">Dracula</button>
  <!-- ... more themes ... -->
</div>
```

**Change to Alpine**:

```templ
<div x-data="themeDropdown">
  <button @click="toggle()">Theme</button>

  <div x-show="open"
       @click.away="open = 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-56 rounded-lg shadow-lg z-50"
       :class="open ? '' : 'hidden'">

    <div class="py-1">
      <button @click="changeTheme('tokyo-night')"
              class="block w-full text-left px-3 py-2 rounded hover:opacity-80">
        Tokyo Night
      </button>

      <button @click="changeTheme('dracula')"
              class="block w-full text-left px-3 py-2 rounded hover:opacity-80">
        Dracula
      </button>

      <!-- ... more themes ... -->
    </div>
  </div>
</div>
```

**Update user menu** (lines 57-60, 65):

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

**Change to**:

```templ
<div x-data="{ userMenuOpen: false }">
  <button @click="userMenuOpen = !userMenuOpen">

  <div x-show="userMenuOpen"
       @click.away="userMenuOpen = false"
       x-transition>
    <button @click="api.post('/api/auth/logout').then(() => window.location.href = '/login')">
      Logout
    </button>
  </div>
</div>
```

---

#### 7. templates/analytics.templ

**File**: `templates/analytics.templ` (100 lines)

**Remove first script block** (lines 10-14):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/dom.js"></script>
<script src="/static/toast.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
```

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
<script src="/static/analytics.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
```

---

#### 8. templates/bookshelf.templ

**File**: `templates/bookshelf.templ` (266 lines)

**Remove first script block** (lines 10-13):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/dom.js"></script>
```

DELETE these lines entirely.

**Replace bookshelf.js script** (line 63):

```templ
<script src="/static/bookshelf.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 9. templates/devices.templ

**File**: `templates/devices.templ` (870 lines)

**Replace script block** (lines 12-16):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/dom.js"></script>
<script src="/static/devices.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 10. templates/index.templ

**File**: `templates/index.templ` (165 lines)

**Remove first script block** (lines 10-12):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/toast.js"></script>
```

DELETE these lines entirely.

**Replace index.js script** (line 138):

```templ
<script src="/static/index.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 11. templates/login.templ

**File**: `templates/login.templ` (81 lines)

**Replace script block** (lines 10-12):

```templ
<script src="/static/api.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/login.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 12. templates/profile.templ

**File**: `templates/profile.templ` (68 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/api.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/profile.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 13. templates/queue.templ

**File**: `templates/queue.templ` (169 lines)

**Remove first script block** (lines 12-15):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/dom.js"></script>
```

DELETE these lines entirely.

**Replace queue.js script** (line 166):

```templ
<script src="/static/queue.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 14. templates/conflicts.templ

**File**: `templates/conflicts.templ` (239 lines)

**Remove first script block** (lines 12-15):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/dom.js"></script>
```

DELETE these lines entirely.

**Replace conflicts.js script** (line 234):

```templ
<script src="/static/conflicts.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 15. templates/custom_section.templ

**File**: `templates/custom_section.templ` (168 lines)

**Replace script block** (lines 10-13):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/custom_section.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 16. templates/admin_users.templ

**File**: `templates/admin_users.templ` (145 lines)

**Replace script block** (lines 9-12):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/admin_users.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 17. templates/collection_rules.templ

**File**: `templates/collection_rules.templ` (420 lines)

**Replace script block** (lines 10-12):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/collection_rules.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 18. templates/progress.templ

**File**: `templates/progress.templ` (114 lines)

**Replace script block** (lines 12-14):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/progress.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 19. templates/register.templ

**File**: `templates/register.templ` (97 lines)

**Replace script block** (lines 10-12):

```templ
<script src="/static/api.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/register.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 20. templates/settings.templ

**File**: `templates/settings.templ` (157 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/api.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/settings.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 21. templates/stats.templ

**File**: `templates/stats.templ` (162 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/stats.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 22. templates/sync.templ

**File**: `templates/sync.templ` (184 lines)

**Replace script block** (lines 9-12):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/sync.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 23. templates/testing_templ.templ

**File**: `templates/testing_templ.templ` (115 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/api.js"></script>
<script src="/static/events.js"></script>
<script src="/static/testing_templ.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 24. themes/obsidian.templ

**File**: `themes/obsidian.templ` (91 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/api.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/obsidian.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 25. themes/whatsapp.templ

**File**: `themes/whatsapp.templ` (86 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/api.js"></script>
<script src="/static/toast.js"></script>
<script src="/static/whatsapp.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 26. themes/midnight.templ

**File**: `themes/midnight.templ` (91 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/toast.js"></script>
<script src="/static/api.js"></script>
<script src="/static/midnight.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

#### 27. themes/sunset.templ (same pattern as other themes)

**File**: `themes/sunset.templ` (86 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/toast.js"></script>
<script src="/static/api.js"></script>
<script src="/static/sunset.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

### 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**: `<script src="/static/main.js"></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
<!-- Before -->
<button onclick="showModal()">Open</button>
<div id="modal" class="hidden">
  <button onclick="hideModal()">Close</button>
</div>

<!-- After -->
<div x-data="{ modalOpen: false }">
  <button @click="modalOpen = true">Open</button>
  <div x-show="modalOpen" x-transition>
    <button @click="modalOpen = false">Close</button>
  </div>
</div>
```

**Confirm delete pattern**:

```templ
<!-- Before -->
<button onclick="confirmDelete()">Delete</button>

<!-- After -->
<button @click="if(confirm('Are you sure?')) module.deleteItem()">Delete</button>
```

**Form submission** (keep HTMX for server calls):

```templ
<!-- Keep HTMX for forms -->
<form hx-post="/api/save" hx-target="#result">
  <!-- Alpine for client-side validation -->
  <input x-model="formData.name" @input="validate = true">
  <span x-show="validate && !formData.name" class="error">Name required</span>
</form>
```

**File**: `themes/sunset.templ` (86 lines)

**Replace script block** (lines 9-11):

```templ
<script src="/static/toast.js"></script>
<script src="/static/api.js"></script>
<script src="/static/sunset.js"></script>
```

**Change to**:

```templ
<script src="/static/main.js"></script>
```

---

## 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
<!-- Before -->
<button onclick="showToast.success('Saved!')">Save</button>

<!-- After -->
<button @click="showToast.success('Saved!')">Save</button>
```

#### 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
<!-- Before -->
<button onclick="api.post('/api/save', data)">Save</button>

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

#### 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
<!-- Before -->
<button onclick="const token = storage.getToken()">Get Token</button>

<!-- After -->
<button @click="const token = storage.getToken()">Get Token</button>
```

### 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
<div x-data="themeDropdown">
  <button @click="toggle()">Theme</button>
  <div x-show="open" @click.away="open = false" x-transition>
    <button @click="changeTheme('tokyo-night')">Tokyo Night</button>
  </div>
</div>
```

#### 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
<div x-data="woodPaneling">
  <button @click="change('none')" :class="current === 'none' ? 'active' : ''">
    None
  </button>
  <button
    @click="change('wood-light')"
    :class="current === 'wood-light' ? 'active' : ''"
  >
    Wood Light
  </button>
</div>
```

### 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: `<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>`

### 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
<button onclick="showToast('Saved!')">Save</button>
```

**New approach**:

```typescript
Alpine.global("showToast", showToast);
```

```html
<button @click="showToast('Saved!')">Save</button>
```

### 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 `<script src="/static/main.js"></script>`
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: <https://alpinejs.dev/>
- Essentials guide: <https://alpinejs.dev/essentials/start>
- `x-data` docs: <https://alpinejs.edu/directives/data>
- `@click` docs: <https://alpinejs.edu/directives/on>
- `x-show` docs: <https://alpinejs.edu/directives/show>
- `x-transition` docs: <https://alpinejs.edu/directives/transition>

### Alpine + HTMX Integration

- Blog post: <https://htmx.org/examples/blog/>
- Both work together seamlessly - Alpine for client state, HTMX for server calls

### ESBuild Documentation

- Official docs: <https://esbuild.github.io/>
- API: <https://esbuild.github.io/api/>
- Bundling: <https://esbuild.github.io/api/#bundle>

### 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 `<script src="/static/main.js"></script>` 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 `<script src="/static/main.js">` tag
- Need state? `x-data="{ open: false }"`
- Server call? `hx-post="/api/endpoint"` (HTMX)

---

## Quick Reference: Alpine Syntax

| Old (Window)                 | New (Alpine)                                                    |
| ---------------------------- | --------------------------------------------------------------- |
| `onclick="func()"`           | `@click="func()"`                                               |
| `onclick="obj.method()"`     | `@click="obj.method()"`                                         |
| `class="hidden"`             | `x-show="!open"`                                                |
| Manual DOM toggle            | `x-data="{ open: false }"` + `@click="open = !open"`            |
| `onclick="window.api.get()"` | `@click="api.get()"`                                            |
| Form validation              | `x-model="formData.name"` + `<span x-show="!name">Error</span>` |

---

**Last Updated**: March 2026  
**ESBuild Version**: Latest (via npm)  
**Alpine.js Version**: Latest (via npm)  
**Target**: ES2020 (Chrome 80+, Firefox 72+, Safari 13.1+)
