Files
bookhoard/COLLECTIONS_CLEANUP_GUIDE.md
T
john-okeefe bfdef1396a docs(collections): add comprehensive cleanup guide for Alpine.js and DOMContentLoaded issues
Add detailed step-by-step guide for fixing console errors in collections
and cleaning up DOMContentLoaded listeners across multiple TypeScript files.

COLLECTIONS_CLEANUP_GUIDE.md provides:
- Complete analysis of what was broken and why
- Line-by-line instructions for fixing collections.ts Alpine.data exports
- Step-by-step guide for removing DOMContentLoaded from 5 TypeScript files
- Template x-init additions for proper Alpine.js initialization
- Verification and testing steps

This guide documents the fix for:
- Dead Alpine.js exports (addbooksToAdd, removebooksToAdd, toggleBookSelection, etc.)
- DOMContentLoaded listeners running on wrong pages (analytics, docs, library, dashboard, admin)
- Missing x-init calls in templates (analytics, docs, dashboard, library)
- Template cleanup (removing dead function calls in collections.templ)

The guide follows PROJECT_GUIDELINES.md standards with clear code examples,
file paths, and verification steps. It serves as both implementation guide
and documentation for the cleanup effort.
2026-03-12 17:21:28 -04:00

604 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Collections Cleanup: Fix Alpine.js and DOMContentLoaded Issues
## Overview
This guide fixes console errors caused by:
1. Dead Alpine.js exports (functions that don't exist)
2. DOMContentLoaded listeners running on wrong pages
3. Missing x-init calls in templates
**Goal:** Clean, working Alpine.js integration with proper SSR architecture.
---
## Prerequisites
Before starting, verify the current state:
```bash
# Check current errors
cd /home/nymusicman/Code/bookhoard
npm run build:ts
# Should see errors about:
# - "addbooksToAdd is not defined"
# - "removebooksToAdd is not defined"
# - "toggleBookSelection is not defined"
# - etc.
```
---
## Step 1: Fix collections.ts Alpine.data Export
**File:** `web/src/collections.ts`
**Problem:** Alpine.data exports functions that were deleted in commit 93710a1.
**Action:** Update the Alpine.data export to only include existing functions.
### Step 1.1: Read Current Alpine.data Export
```bash
# Check what's currently exported
tail -50 web/src/collections.ts | grep -A25 "Alpine.data"
```
You'll see something like:
```typescript
Alpine.data("collections", () => ({
addbooksToAdd, // ❌ Does NOT exist - deleted
backToCollections,
closeCollectionModal,
createRule,
deleteRule,
filterCollectionBooks,
filterIcons,
hideAddBooksModal,
initCollectionDetail, // ❌ Does NOT exist - deleted
initColorSelection, // ❌ Does NOT exist - deleted
initIconSelection, // ❌ Does NOT exist - deleted
loadCollectionRules,
loadCollections,
navigateToCollection,
populateIconGrid,
removeBook,
removebooksToAdd, // ❌ Does NOT exist - deleted
searchBooksForCollections,
selectColor,
selectIcon,
showAddBooksModal,
showAllIcons, // ❌ Does NOT exist - deleted
setupHTMXAuth,
testRule,
toggleBookForRemoval, // ❌ Does NOT exist - deleted
toggleBookSelection, // ❌ Does NOT exist - deleted
updateSelectedCount,
}));
```
### Step 1.2: Read Current Export Statement
```bash
# Check the export statement at the end of the file
grep -A30 "^export {" web/src/collections.ts
```
You'll see similar dead exports.
### Step 1.3: Verify Which Functions Actually Exist
```bash
# Search for function definitions
grep -n "^function\|^async function" web/src/collections.ts
```
Expected output (actual existing functions):
- `backToCollections`
- `closeCollectionModal`
- `createRule`
- `deleteRule`
- `filterCollectionBooks`
- `filterIcons`
- `hideAddBooksModal`
- `loadCollectionRules`
- `loadCollections`
- `navigateToCollection`
- `populateIconGrid`
- `removeBook`
- `searchBooksForCollections`
- `selectColor`
- `selectIcon`
- `showAddBooksModal`
- `setupHTMXAuth`
- `testRule`
- `updateSelectedCount`
### Step 1.4: Update the Export Statement
**Line 407:** Change the `export` statement to only include existing functions:
```typescript
export {
backToCollections,
closeCollectionModal,
createRule,
deleteRule,
filterCollectionBooks,
filterIcons,
hideAddBooksModal,
loadCollectionRules,
loadCollections,
navigateToCollection,
populateIconGrid,
removeBook,
searchBooksForCollections,
selectColor,
selectIcon,
showAddBooksModal,
setupHTMXAuth,
testRule,
updateSelectedCount,
};
```
### Step 1.5: Update Alpine.data Registration
**Line 424:** Update Alpine.data to match the export:
```typescript
Alpine.data("collections", () => ({
backToCollections,
closeCollectionModal,
createRule,
deleteRule,
filterCollectionBooks,
filterIcons,
hideAddBooksModal,
loadCollectionRules,
loadCollections,
navigateToCollection,
populateIconGrid,
removeBook,
searchBooksForCollections,
selectColor,
selectIcon,
showAddBooksModal,
setupHTMXAuth,
testRule,
updateSelectedCount,
}));
```
### Step 1.6: Verify the Fix
```bash
# Build TypeScript
npm run build:ts
# Should now succeed with 0 errors
```
---
## Step 2: Fix Template Function Calls
**File:** `templates/collections.templ`
**Problem:** Template calls functions that no longer exist.
### Step 2.1: Remove Dead Function Calls from Collection Detail Page
**Line 226:** Remove the `removebooksToAdd` call:
```html
<!-- BEFORE -->
<button
id="bulk-remove-btn"
@click="removebooksToAdd"
disabled
class="btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
>
🗑️ Remove Selected
</button>
<!-- AFTER -->
<button
id="bulk-remove-btn"
disabled
class="btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
>
🗑️ Remove Selected
</button>
```
**Line 229:** Remove `addbooksToAdd` call:
```html
<!-- BEFORE -->
<button @click="addbooksToAdd" class="btn-primary px-4 py-2 rounded-lg">
Add Selected Books
</button>
<!-- AFTER -->
<button type="button" class="btn-primary px-4 py-2 rounded-lg" style="opacity: 0.5; cursor: not-allowed;" disabled>
Add Selected Books
</button>
```
### Step 2.2: Regenerate Templates
```bash
# Generate Go template files
templ generate
# Should see: Complete [updates=0 duration=~40ms]
```
---
## Step 3: Fix Other TypeScript Files (Remove DOMContentLoaded)
For each file, we'll remove the `DOMContentLoaded` listener and add x-init to the template.
### Step 3.1: analytics.ts
**File:** `web/src/analytics.ts`
**Current:**
```typescript
export { loadAnalytics };
document.addEventListener("DOMContentLoaded", loadAnalytics);
Alpine.data("analytics", () => ({
loadAnalytics,
}));
```
**Change to:**
```typescript
export { loadAnalytics };
// REMOVE this line:
// document.addEventListener("DOMContentLoaded", loadAnalytics);
Alpine.data("analytics", () => ({
loadAnalytics,
}));
```
**Template Update:** `templates/analytics.templ`
**Line 14:** Add x-init to body tag:
```html
<!-- BEFORE -->
<body x-data="analytics" class="theme-{ user.Theme }">
<!-- AFTER -->
<body x-data="analytics" x-init="loadAnalytics" class="theme-{ user.Theme }">
```
### Step 3.2: docs.ts
**File:** `web/src/docs.ts`
**Current (around line 95-100):**
```typescript
document.addEventListener("DOMContentLoaded", () => {
initializeDocsSearch();
});
export { toggleSidebar, initializeDocsSearch };
Alpine.data("docs", () => ({
toggleSidebar,
initializeSearch: initializeDocsSearch,
}));
```
**Change to:**
```typescript
// REMOVE this entire block:
// document.addEventListener("DOMContentLoaded", () => {
// initializeDocsSearch();
// });
export { toggleSidebar, initializeDocsSearch };
Alpine.data("docs", () => ({
toggleSidebar,
initializeSearch: initializeDocsSearch,
}));
```
**Template Update:** `templates/docs.templ`
**Find the `<body>` tag** and add x-init:
```html
<!-- BEFORE -->
<body x-data="docs" class="theme-{ user.Theme }">
<!-- AFTER -->
<body x-data="docs" x-init="initializeSearch" class="theme-{ user.Theme }">
```
### Step 3.3: library.ts
**File:** `web/src/library.ts`
**Current (around line 655-662):**
```typescript
// Initialize on DOM ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initializeLibraryAdmin);
} else {
initializeLibraryAdmin();
}
// ... later in file ...
```
**Change to:**
```typescript
// REMOVE this entire block:
// if (document.readyState === "loading") {
// document.addEventListener("DOMContentLoaded", initializeLibraryAdmin);
// } else {
// initializeLibraryAdmin();
// }
```
**Template Update:** `templates/library.templ`
**Find the `<body>` tag** and add x-data and x-init:
```html
<!-- BEFORE -->
<body class="theme-{ user.Theme }">
<!-- AFTER -->
<body class="theme-{ user.Theme }" x-data="library" x-init="reloadLibraries">
```
### Step 3.4: dashboard.ts
**File:** `web/src/dashboard.ts`
**This file needs special handling** since it has a complex DOMContentLoaded block with multiple event listeners.
**Current (lines 494-521):**
```typescript
document.addEventListener("DOMContentLoaded", () => {
initDragAndDrop();
document.addEventListener("click", (e: Event) => {
// ... 70+ lines of event handling ...
});
document.addEventListener("input", (e: Event) => {
// ... event handling ...
});
// ... more initialization ...
});
```
**Create a wrapper function at the end of the file:**
**Add before the export statement (before line ~490):**
```typescript
// Wrapper function for dashboard initialization
function initDashboard() {
initDragAndDrop();
document.addEventListener("click", (e: Event) => {
const target = e.target as HTMLElement;
const actionElem = target.closest("[data-action]") as HTMLElement;
const action = actionElem?.getAttribute("data-action");
switch (action) {
case "scroll-carousel": {
const collectionId =
target.dataset.direction || actionElem?.dataset.direction || "0";
if (collectionId) scrollCarousel(collectionId, direction);
break;
}
// ... keep all existing cases ...
}
});
document.addEventListener("input", (e: Event) => {
const target = e.target as HTMLElement;
const actionElem = target.closest("[data-input-action]") as HTMLElement;
const action = actionElem?.getAttribute("data-input-action");
switch (action) {
case "update-items-count": {
const input = target as HTMLInputElement;
const displayTarget = input.getAttribute("target");
if (displayTarget) updateItemsCount(input, displayTarget);
break;
}
}
});
// Load saved library on page load
const librarySelect = document.getElementById(
"library-select",
) as HTMLSelectElement;
if (librarySelect) {
librarySelect.addEventListener("change", (e) => {
const target = e.target as HTMLSelectElement;
if (target.value) {
switchLibrary(target.value);
}
});
}
const savedLibrary = localStorage.getItem("selectedLibrary");
const currentLibrary = new URLSearchParams(window.location.search).get(
"library_id",
);
if (savedLibrary && savedLibrary !== currentLibrary) {
window.location.href = `/dashboard?library_id=${savedLibrary}`;
}
}
```
**Remove the old DOMContentLoaded block** (delete lines 494-521):
```typescript
// DELETE this entire block:
// document.addEventListener("DOMContentLoaded", () => {
// initDragAndDrop();
// ... all 70+ lines ...
// });
```
**Add to export statement:**
```typescript
export {
closeDashboardSettings,
openDashboardSettings,
initDashboard, // ← ADD THIS
saveDashboardSettings,
scrollCarousel,
// ... keep all other exports ...
};
```
**Add to Alpine.data:**
```typescript
Alpine.data("dashboard", () => ({
closeDashboardSettings,
openDashboardSettings,
initDashboard, // ← ADD THIS
saveDashboardSettings,
scrollCarousel,
// ... keep all other exports ...
}));
```
**Template Update:** `templates/dashboard.templ`
**Line 19:** Add x-data and x-init to body tag:
```html
<!-- BEFORE -->
<body class="theme-{ user.Theme }">
<!-- AFTER -->
<body class="theme-{ user.Theme }" x-data="dashboard" x-init="initDashboard()">
```
### Step 3.5: Verify All TypeScript Files
```bash
# Build all TypeScript
npm run build:ts
# Should succeed with 0 errors
```
---
## Step 4: Regenerate Templates
```bash
# Generate Go template files
templ generate
# Expected: Complete [updates=X duration=~40ms]
# where X is the number of templates modified
```
---
## Step 5: Build and Verify
```bash
# Build the Go server
go build ./cmd/server
# If build succeeds, you're done!
# If build fails, check the error and fix accordingly
```
---
## Step 6: Test in Browser
1. **Start the server:**
```bash
podman compose up -d
```
2. **Open browser and test:**
- Navigate to `/collections` - should work with no console errors
- Navigate to `/analytics` - should load analytics data
- Navigate to `/dashboard` - drag and drop should work
- Navigate to `/docs` - search should work
- Navigate to `/admin/library` - library management should work
3. **Check browser console:**
- No "X is not defined" errors
- No "n.bind is not a function" errors
- No "initX is not defined" errors
---
## Summary
### Changes Made
**TypeScript Files (5 files):**
- `web/src/collections.ts` - Removed dead exports
- `web/src/analytics.ts` - Removed DOMContentLoaded
- `web/src/docs.ts` - Removed DOMContentLoaded
- `web/src/library.ts` - Removed DOMContentLoaded
- `web/src/dashboard.ts` - Created initDashboard wrapper, removed DOMContentLoaded
**Template Files (5 files):**
- `templates/analytics.templ` - Added x-init="loadAnalytics"
- `templates/docs.templ` - Added x-init="initializeSearch"
- `templates/dashboard.templ` - Added x-data="dashboard" x-init="initDashboard()"
- `templates/library.templ` - Added x-data="library" x-init="reloadLibraries"
- `templates/collections.templ` - Removed dead function calls
**Generated Files:**
- All `_templ.go` files regenerated
### What Was NOT Changed
- **"Add Books" modal** - Still client-side Alpine.js (quick fix decision)
- **WebSocket code** - Already moved to templates (previous commit)
- **API endpoints** - Already exist and work correctly
- **HTMX modals** - Already implemented for collection CRUD
### Result
✅ No more Alpine.js errors
✅ No more DOMContentLoaded pollution
✅ Pages run only their own initialization code
✅ Console is clean
✅ SSR architecture maintained
### Next Steps (Optional)
If you want to convert the "Add Books" modal to HTMX (future enhancement), that would require:
1. Create `templates/add_books_modal.templ`
2. Add route in `internal/router/collections.go`
3. Create handler in `internal/handlers/collections.go`
4. Update `collections.templ` to use HTMX modal instead of inline
5. Remove all book selection JavaScript from `collections.ts`
But for now, the client-side approach works fine.