docs: remove redundant COLLECTIONS_CLEANUP_GUIDE.md

Delete the standalone cleanup guide as its content has been fully
consolidated into ALPINE_COMPLETION_GUIDE.md (Phase 0 and Phase 3).

All step-by-step instructions for dead export removal and DOMContentLoaded
cleanup are now in the main completion guide, creating a single source
of truth for Alpine.js migration.
This commit is contained in:
2026-03-12 20:01:43 -04:00
parent 442ace5e55
commit 5670f02c7f
-709
View File
@@ -1,709 +0,0 @@
# Collections and Alpine.js Cleanup Guide
## Overview
This guide provides fixes for Alpine.js issues while maintaining SSR-first architecture.
**References:**
- **`SSR_FIRST_ALPINE_GUIDE.md`** - Complete SSR-first architecture principles (READ THIS FIRST)
- **`ALPINE_COMPLETION_GUIDE.md`** - Full reactive Alpine.js migration (eventual goal)
### Problem Statement
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
4. **CRITICAL:** x-init functions that fetch data and replace SSR content
### SSR-First Principles
**See `SSR_FIRST_ALPINE_GUIDE.md` for complete documentation**
**Quick Summary:**
-**NEVER fetch data in x-init** if data is already SSR'd
- ✅ x-init ONLY for setup (event listeners, modals)
- ✅ Data fetch ONLY after user actions (create/delete/update)
- ✅ State lives in template (`x-data`), not in TypeScript
### Page Types
**Type 1: 80% SSR** (Collections, Conflicts, Queue)
- Backend provides all data
- Alpine for modals only
- No data fetch in x-init
**Type 2: SSR + Interactive** (Dashboard, Admin Library)
- Backend provides initial data
- Alpine for interactivity (drag-drop, CRUD)
- x-init sets up listeners only
**Type 3: 80% JavaScript** (Analytics)
- Backend renders empty shell
- x-init fetches ALL data (intentional)
### Review Status
**Pages methodically reviewed so far:**
-**Dashboard** - Completed
- 🔄 **Collections** - In progress (this guide)
- ⏸️ **Admin Library** - Fixed (see SSR_FIRST_ALPINE_GUIDE.md)
- ⏸️ **Other pages** - Not yet reviewed
**Goal:** Fix console errors and maintain 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 - SIMPLE SETUP ONLY
**File:** `web/src/docs.ts`
**Good news:** `initializeDocsSearch()` ONLY sets up an event listener - no data fetch!
**Current (around line 95-100):**
```typescript
document.addEventListener("DOMContentLoaded", () => {
initializeDocsSearch();
});
```
**Solution:** Remove DOMContentLoaded, use x-init
**Remove DOMContentLoaded:**
```typescript
// DELETE:
// document.addEventListener("DOMContentLoaded", () => {
// initializeDocsSearch();
// });
export { toggleSidebar, initializeDocsSearch };
Alpine.data("docs", () => ({
toggleSidebar,
initializeDocsSearch, // Keep as-is
}));
```
**Update template:**
```html
<!-- templates/docs.templ -->
<body x-data="docs" x-init="initializeDocsSearch" class="theme-{ user.Theme }">
```
**✅ Simple setup only**
**✅ No data fetch** (search is client-side)
**✅ x-init is appropriate here**
### Step 3.3: library.ts - ALREADY FIXED
**Status:****COMPLETED** - See commit 1b9bc64
**The SSR bug has been fixed:**
- Removed `void reloadLibraries()` from `initializeLibraryAdmin()`
- SSR provides initial library list (no fetch on page load)
- `reloadLibraries()` available for after CRUD operations
- See `SSR_FIRST_ALPINE_GUIDE.md` for complete SSR-first principles
**Current state (web/src/library.ts:653-655):**
```typescript
function initializeLibraryAdmin(): void {
// Setup event listeners
const librariesList = document.getElementById("libraries-list");
if (librariesList) {
librariesList.addEventListener("click", handleLibraryListClick);
}
// ... setup code ...
// ✅ FIXED: No data fetch - SSR provides initial library list
// reloadLibraries() is called AFTER create/delete/update operations only
}
```
**Template (templates/admin_library.templ:11):**
```html
<body x-data="library" x-init="initializeLibraryAdmin" class="theme-{ user.Theme }">
```
**✅ No changes needed** - SSR bug is already fixed.
### Step 3.4: dashboard.ts - WRAP EXISTING CODE
**File:** `web/src/dashboard.ts`
**Good news:** Dashboard already uses event delegation with `data-action` attributes!
**Current (lines 494-521):**
```typescript
document.addEventListener("DOMContentLoaded", () => {
initDragAndDrop();
document.addEventListener("click", (e: Event) => {
// ... event delegation with data-action ...
});
// ... library select setup ...
});
```
**Solution:** Wrap existing DOMContentLoaded code in `initDashboard()` function
**Create wrapper function at end of dashboard.ts:**
```typescript
function initDashboard() {
initDragAndDrop(); // Setup drag-drop
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": /* ... */ break;
// ... existing cases ...
}
});
document.addEventListener("input", (e: Event) => {
// ... existing input handler ...
});
// Load saved library on page load
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}`;
}
}
export { initDashboard };
Alpine.data("dashboard", () => ({
initDashboard,
}));
```
**Update template:**
```html
<!-- templates/dashboard.templ line 19 -->
<body x-data="dashboard" x-init="initDashboard" class="theme-{ user.Theme }">
```
**✅ Keeps event delegation pattern**
**✅ Wraps existing code (minimal changes)**
**✅ x-init does NOT fetch data (SSR provides initial dashboard)**
**✅ localStorage redirect is user preference, not data fetch**
**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
### Progress So Far
**Pages methodically reviewed so far:**
- ✅ **Dashboard** - Completed, working correctly with SSR
- ✅ **Admin Library** - SSR bug fixed (commit 1b9bc64)
- 🔄 **Collections** - In progress (this guide)
- ⏸️ **Other pages** - Not yet reviewed, will be done page-by-page
### Completed Changes
**TypeScript Files (3 files):**
- `web/src/collections.ts` - ✅ Removed dead Alpine.js exports
- `web/src/analytics.ts` - ✅ Removed DOMContentLoaded, SSR-first (intentional data fetch)
- `web/src/admin.ts` - ✅ Removed DOMContentLoaded, WebSocket moved to template
- `web/src/library.ts` - ✅ Fixed SSR bug, removed reloadLibraries() from init
**Template Files (1 file):**
- `templates/analytics.templ` - ✅ Added x-init="loadAnalytics" (JS-heavy page, correct)
### Pending Fixes
**Collections (This Guide):**
- Dead exports removed (Step 1)
- DOMContentLoaded cleanup (Step 3)
- x-init calls needed (Step 3)
**Dashboard:**
- Wrap existing code in `initDashboard()` function
- Add x-init to template
- Already uses event delegation correctly
**Docs:**
- Remove DOMContentLoaded
- Add x-init="initializeDocsSearch" to template
- Simple setup only, no data fetch
### What Was NOT Changed
- **"Add Books" modal** - Still client-side Alpine.js (quick fix decision)
- **WebSocket code** - Already moved to templates with server-side token injection
- **API endpoints** - Already exist and work correctly
- **HTMX modals** - Already implemented for collection CRUD
### SSR-First Principles
**See `SSR_FIRST_ALPINE_GUIDE.md` for complete documentation:**
✅ **Type 1 (80% SSR):** Collections, Conflicts - backend provides data, Alpine for modals only
✅ **Type 2 (SSR + Interactive):** Dashboard, Admin Library - SSR data + Alpine for interactivity
✅ **Type 3 (80% JS):** Analytics - x-init fetches data (intentional)
**Key Principle:**
- ❌ **NEVER fetch data in x-init** if data is already SSR'd
- ✅ x-init ONLY for setup (event listeners, modals)
- ✅ Data fetch ONLY after user actions (create/delete/update)
### Next Steps
1. **Complete collections fixes** (this guide)
2. **Continue page-by-page review** - one page at a time
3. **Reference `SSR_FIRST_ALPINE_GUIDE.md`** for SSR-first principles
4. **Reference `ALPINE_COMPLETION_GUIDE.md`** for full Alpine.js migration (future goal)
### Architecture
**State location:**
- Templates: UI state (`x-data`, `x-show`)
- Backend: SSR data
- TypeScript: Business logic only
**No hybrid approach:**
- ✅ Follow SSR-first principles
- ✅ Full Alpine.js is eventual goal (see ALPINE_COMPLETION_GUIDE.md)
- ✅ Fix issues methodically as you encounter them