docs: add Alpine.js migration guide for global() → store() API

Add comprehensive migration documentation for transitioning from the invalid
Alpine.global() API to the correct Alpine.store() API.

This guide addresses:
- Critical issue: Alpine.global() does not exist in Alpine.js v3.15.8
- 26 TypeScript files requiring updates
- Step-by-step migration instructions
- Template syntax changes (namespace.function() → $store.namespace.function())
- Testing checklist and troubleshooting guide

The migration will fix the "p.global is not a function" error currently
breaking the theme switcher and all Alpine namespaces.

Part 1 of 2 - covers TypeScript and template file updates.
This commit is contained in:
2026-03-09 21:23:07 -04:00
parent 647644fdce
commit 0a5042e130
3 changed files with 398 additions and 34 deletions
+387
View File
@@ -0,0 +1,387 @@
# Alpine.js `Alpine.global()` → `Alpine.store()` Migration Guide - Part 1
## 🔴 Critical Issue Identified
**Problem**: Your codebase uses `Alpine.global()` which **DOES NOT EXIST** in Alpine.js v3.15.8. This is causing the error:
```
Uncaught TypeError: p.global is not a function
```
## 📋 Root Cause Analysis
1. **Invalid API Usage**: `Alpine.global()` is not a valid Alpine.js v3 method
2. **26 Occurrences**: Found across 26 TypeScript files
3. **Breaking Impact**: Theme switcher and all Alpine namespaces are broken
## ✅ Solution Overview
Replace all `Alpine.global()` calls with `Alpine.store()`, the correct Alpine.js v3 API for registering global utilities.
**Current Pattern (Broken)**:
```typescript
Alpine.global("namespace", {
function1: () => { ... },
function2: () => { ... }
});
```
**New Pattern (Correct)**:
```typescript
Alpine.store("namespace", {
function1: () => { ... },
function2: () => { ... }
});
```
**Template Usage Changes**:
- **Old**: `@click="namespace.function()"``@click="$store.namespace.function()"`
---
## 📁 Files Requiring Changes (26 TypeScript files)
### Step 1: Update TypeScript Files
For each file, replace `Alpine.global()` with `Alpine.store()`:
| File | Line | Namespace | Action |
|------|------|-----------|--------|
| `admin.ts` | 399 | `admin` | Replace `Alpine.global` with `Alpine.store` |
| `api.ts` | 102 | `api` | Replace `Alpine.global` with `Alpine.store` |
| `api-explorer-docs.ts` | 135 | `apiExplorerDoc` | Replace `Alpine.global` with `Alpine.store` |
| `bookshelf.ts` | 199 | `bookshelf` | Replace `Alpine.global` with `Alpine.store` |
| `collection-rules.ts` | 411 | `collectionRules` | Replace `Alpine.global` with `Alpine.store` |
| `collections.ts` | 917 | `collections` | Replace `Alpine.global` with `Alpine.store` |
| `conflicts.ts` | 219 | `conflicts` | Replace `Alpine.global` with `Alpine.store` |
| `device-management.ts` | 559 | `devices` | Replace `Alpine.global` with `Alpine.store` |
| `docs.ts` | 96, 103 | `docs` | Replace `Alpine.global` with `Alpine.store` (2 occurrences) |
| `header.ts` | 35 | `header` | Replace `Alpine.global` with `Alpine.store` |
| `index.ts` | 46 | `index` | Replace `Alpine.global` with `Alpine.store` |
| `library.ts` | 683 | `library` | Replace `Alpine.global` with `Alpine.store` |
| `linking.ts` | 204 | `linking` | Replace `Alpine.global` with `Alpine.store` |
| `login.ts` | 25 | `login` | Replace `Alpine.global` with `Alpine.store` |
| `password_validation.ts` | 192 | `validation` | Replace `Alpine.global` with `Alpine.store` |
| `profile.ts` | 44 | `profile` | Replace `Alpine.global` with `Alpine.store` |
| `profile-modal.ts` | 29 | `profileModal` | Replace `Alpine.global` with `Alpine.store` |
| `queue.ts` | 167 | `queue` | Replace `Alpine.global` with `Alpine.store` |
| `register.ts` | 14 | `register` | Replace `Alpine.global` with `Alpine.store` |
| `search.ts` | 310 | `search` | Replace `Alpine.global` with `Alpine.store` |
| `themeDropdown.ts` | 24 | `themeDropdown` | Replace `Alpine.global` with `Alpine.store` |
| `toast.ts` | 229 | `showToast` | Replace `Alpine.global` with `Alpine.store` |
| `toast-error.ts` | 33 | `toastError` | Replace `Alpine.global` with `Alpine.store` |
| `unlinked_books.ts` | 430 | `unlinkedBooks` | Replace `Alpine.global` with `Alpine.store` |
| `woodPaneling.ts` | 85 | `woodPaneling` | Replace `Alpine.global` with `Alpine.store` |
### Step 2: Update Template Files
For each `.templ` file that uses Alpine namespaces, update the syntax:
**Old Syntax**:
```html
<div x-data="{}">
<button @click="namespace.function()">Click</button>
</div>
```
**New Syntax**:
```html
<div x-data="{}">
<button @click="$store.namespace.function()">Click</button>
</div>
```
**Templates Requiring Updates** (to be identified by searching for namespace usage):
- All templates using `@click="admin."`
- All templates using `@click="api."`
- All templates using `@click="header."`
- All templates using `@click="showToast."`
- All templates using any other namespace from the list above
---
## 🔧 Detailed Step-by-Step Instructions
### Phase 1: Update TypeScript Source Files
#### Step 1.1: Backup Current State
```bash
cd /home/nymusicman/Code/bookhoard
git add -A
git commit -m "Backup before Alpine.global() → Alpine.store() migration"
```
#### Step 1.2: Find All `Alpine.global` Occurrences
```bash
cd /home/nymusicman/Code/bookhoard/web/src
grep -rn "Alpine\.global" . | tee alpine-global-occurrences.txt
```
Expected output: 26 occurrences across 26 files
#### Step 1.3: Replace All `Alpine.global` with `Alpine.store`
**Option A: Manual Replacement (Recommended for Understanding)**
For each file in the table above:
1. Open the file
2. Find the `Alpine.global()` call
3. Replace `Alpine.global` with `Alpine.store`
4. Save the file
**Example** (`web/src/toast.ts:229`):
```typescript
// BEFORE (Line 229):
Alpine.global("showToast", {
error: (message: string, duration?: number) =>
showToast(message, "error", duration),
success: (message: string, duration?: number) =>
showToast(message, "success", duration),
info: (message: string, duration?: number) =>
showToast(message, "info", duration),
});
// AFTER (Line 229):
Alpine.store("showToast", {
error: (message: string, duration?: number) =>
showToast(message, "error", duration),
success: (message: string, duration?: number) =>
showToast(message, "success", duration),
info: (message: string, duration?: number) =>
showToast(message, "info", duration),
});
```
**Option B: Automated Replacement (Faster)**
```bash
cd /home/nymusicman/Code/bookhoard/web/src
find . -name "*.ts" -type f -exec sed -i 's/Alpine\.global(/Alpine.store(/g' {} \;
```
⚠️ **Warning**: Automated replacement will change all 26 occurrences at once. Verify with grep first:
```bash
grep -rn "Alpine\.global" . # Should return nothing after replacement
grep -rn "Alpine\.store" . # Should show all 26 occurrences
```
#### Step 1.4: Verify Changes
```bash
cd /home/nymusicman/Code/bookhoard/web/src
grep -c "Alpine\.store" *.ts | grep -v ":0"
```
Expected: Each of the 26 files should show `1` occurrence (except `docs.ts` which should show `2`)
#### Step 1.5: Rebuild TypeScript
```bash
cd /home/nymusicman/Code/bookhoard/web
npm run build:ts
```
Expected output:
```
> bookhoard@1.0.0 build:ts
> cp node_modules/htmx.org/dist/htmx.min.js web/static/htmx.min.js && esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --minify
web/static/main.js 167.9kb
web/static/main.js.map 538.7kb
⚡ Done in 18ms
```
---
### Phase 2: Update Template Files
#### Step 2.1: Find All Namespace Usage in Templates
```bash
cd /home/nymusicman/Code/bookhoard/templates
grep -rn '@click="' .
```
This will show all click handlers that use namespaces. Look for patterns like:
- `@click="admin.deleteConfirm"`
- `@click="api.getUrl"`
- `@click="header.changeThemeTo"`
- `@click="showToast.error"`
- etc.
#### Step 2.2: Create Mapping Document
Create a text file with the old → new namespace mappings:
```
admin.function() → $store.admin.function()
api.function() → $store.api.function()
apiExplorerDoc.function() → $store.apiExplorerDoc.function()
bookshelf.function() → $store.bookshelf.function()
collectionRules.function() → $store.collectionRules.function()
collections.function() → $store.collections.function()
conflicts.function() → $store.conflicts.function()
devices.function() → $store.devices.function()
docs.function() → $store.docs.function()
header.function() → $store.header.function()
index.function() → $store.index.function()
library.function() → $store.library.function()
linking.function() → $store.linking.function()
login.function() → $store.login.function()
validation.function() → $store.validation.function()
profile.function() → $store.profile.function()
profileModal.function() → $store.profileModal.function()
queue.function() → $store.queue.function()
register.function() → $store.register.function()
search.function() → $store.search.function()
themeDropdown.function() → $store.themeDropdown.function()
showToast.function() → $store.showToast.function()
toastError.function() → $store.toastError.function()
unlinkedBooks.function() → $store.unlinkedBooks.function()
woodPaneling.function() → $store.woodPaneling.function()
```
#### Step 2.3: Update Templates (Iterative Approach)
**For each namespace found in templates:**
1. **Search for all usages**:
```bash
cd /home/nymusicman/Code/bookhoard/templates
grep -rn 'namespace\.' . | grep "@click"
```
Replace `namespace` with the actual namespace name (e.g., `admin`, `header`, etc.)
2. **Replace in each file** manually or using find/replace:
- Find: `@click="namespace.functionName"`
- Replace: `@click="$store.namespace.functionName"`
**Example for `header.templ`:**
**Before**:
```templ
@click="header.changeThemeTo('wood-light')"
@click="header.logout()"
```
**After**:
```templ
@click="$store.header.changeThemeTo('wood-light')"
@click="$store.header.logout()"
```
#### Step 2.4: Critical Template Priority
Update templates in this order (most critical first):
1. **`header.templ`** - Theme switcher (currently broken)
2. **`index.templ`** - Main page
3. **`library.templ`** - Core functionality
4. **`collections.templ`** - Collection management
5. **`bookshelf.templ`** - Book display
6. **`api_explorer.templ`** - API testing
7. **`admin.templ`** - Admin panel
8. **`profile.templ`** - User profile
9. **`login.templ`** - Authentication
10. **`register.templ`** - Registration
11. **All other templates** with namespace usage
---
### Phase 3: Test the Migration
#### Step 3.1: Rebuild Container
```bash
cd /home/nymusicman/Code/bookhoard
docker-compose build
```
#### Step 3.2: Restart Container
```bash
docker-compose down
docker-compose up -d
```
#### Step 3.3: Verify Bundle
```bash
podman exec bookhoard grep -c "\.store(" /root/web/static/main.js
```
Expected: Should show all `Alpine.store()` calls (not `Alpine.global()`)
#### Step 3.4: Test Theme Switcher (Critical)
1. Open `http://localhost:8765` in browser
2. Hard refresh: `Ctrl+Shift+R`
3. Open browser console (F12)
4. Click theme dropdown
5. Select a theme
6. **Expected**: Theme changes without error
7. **Expected Console**:
```javascript
Alpine.store('showToast') // Should return object, not undefined
Alpine.store('header') // Should return object, not undefined
```
#### Step 3.5: Verify No Errors
Check browser console for:
- ✅ No `p.global is not a function` errors
- ✅ No `Uncaught TypeError` messages
- ✅ Alpine version shows: `3.15.8`
- ✅ Stores are accessible via `$store`
---
## 📊 Testing Checklist
After completing the migration, verify:
- [ ] No `Alpine.global` in any `.ts` file
- [ ] All 26 `Alpine.store` registrations present
- [ ] Bundle size approximately 168KB
- [ ] Browser console shows no errors
- [ ] Theme switcher works correctly
- [ ] All dropdown menus open/close properly
- [ ] Toast notifications display correctly
- [ ] All forms submit without errors
- [ ] All click handlers work as expected
- [ ] Alpine directives (`x-show`, `@click`, etc.) work correctly
---
## 🐛 Troubleshooting
### Error: "Cannot read property 'function' of undefined"
**Cause**: Template still using old syntax `namespace.function()`
**Fix**: Change to `$store.namespace.function()`
### Error: "Alpine.store is not a function"
**Cause**: TypeScript build didn't complete or using cached main.js
**Fix**: Run `npm run build:ts` and rebuild container
### Error: "p.global is not a function" (still)
**Cause**: Some files still have `Alpine.global()`
**Fix**: Run `grep -rn "Alpine\.global" web/src` to find remaining occurrences
### Theme switcher still broken
**Cause**: `header.templ` not updated with new syntax
**Fix**: Update all `@click="header."` to `@click="$store.header."`
---
## 📝 Next Steps (After This Guide)
**Part 2** will cover:
1. Advanced Alpine store patterns
2. Reactive state management with stores
3. Testing strategies for all templates
4. Performance optimization
5. Rollback plan if needed
---
## 🔗 References
- [Alpine.js Stores Documentation](https://alpinejs.dev/globals/alpine_store.html)
- [Alpine.js Magic Properties ($store)](https://alpinejs.dev/magics/$store.html)
- [Migration from Alpine v2 to v3](https://alpinejs.dev/upgrade-guide)
---
**Created**: 2026-03-09
**Alpine.js Version**: 3.15.8
**Status**: Ready for Implementation
+11 -1
View File
@@ -1,3 +1,6 @@
import "./alpine";
import { Alpine } from "./alpine";
import "./admin";
import "./api";
import "./analytics";
@@ -23,7 +26,14 @@ import "./profile";
import "./profile-modal";
import "./queue";
import "./register";
import "./register-alpine";
import "./search";
import "./storage";
import "./themeDropdown";
import "./theme";
import "./toast";
import "./toast-error";
import "./unlinked_books";
import "./woodPanelingInit";
import "./woodPaneling";
Alpine.start();
-33
View File
@@ -1,33 +0,0 @@
import { Alpine } from "./alpine";
// Import all modules that register Alpine globals
// These imports trigger their Alpine.global() calls
import "./admin";
import "./api";
import "./analytics";
import "./api-explorer-docs";
import "./bookshelf";
import "./collection-rules";
import "./collections";
import "./conflicts";
import "./custom-section-builder";
import "./dashboard";
import "./device-management";
import "./docs";
import "./header";
import "./index";
import "./library";
import "./login";
import "./linking";
import "./password_validation";
import "./profile";
import "./profile-modal";
import "./queue";
import "./register";
import "./search";
import "./themeDropdown";
import "./toast";
import "./toast-error";
import "./unlinked_books";
import "./woodPaneling";
// NOW start Alpine after all registrations complete
Alpine.start();