refactor(library): fix SSR bug by removing data fetch from init function

CRITICAL FIX: initializeLibraryAdmin() was calling reloadLibraries()
which fetched data from the API and replaced the SSR-rendered library
list on page load, defeating the purpose of server-side rendering.

Changes in web/src/library.ts:
- Remove DOMContentLoaded listener (now uses Alpine x-init in template)
- Remove void reloadLibraries() call from initializeLibraryAdmin()
- Add comment explaining SSR provides initial data
- Add initializeLibraryAdmin to export statement
- Add initializeLibraryAdmin to Alpine.data() registration
- Keep reloadLibraries() as standalone function for use after CRUD ops

Rationale:
- SSR provides fast initial page load with library list
- x-init should ONLY setup event listeners, not fetch data
- reloadLibraries() is called after create/delete/update operations
- Follows SSR-first architecture: different pages have different
  SSR/JS ratios (analytics is 80% JS, most pages are 80% SSR)

Documentation:
- Update COLLECTIONS_CLEANUP_GUIDE.md with SSR-first strategy
- Document page-by-page review status (dashboard ✓, collections 🔄)
- Fix template references (library.templ → admin_library.templ)
- Explain why analytics fetches data (intentional for dynamic page)

This ensures the admin library page maintains SSR benefits while
still providing interactive features via Alpine.js.
This commit is contained in:
2026-03-12 17:58:36 -04:00
parent ddcd8c62e2
commit 1b9bc64b28
2 changed files with 152 additions and 86 deletions
+126 -55
View File
@@ -1,11 +1,52 @@
# Collections Cleanup: Fix Alpine.js and DOMContentLoaded Issues # Alpine.js and SSR Architecture Cleanup Guide
## Overview ## Overview
This guide fixes console errors caused by: This guide provides a systematic, page-by-page approach to fixing Alpine.js issues while maintaining SSR-first architecture.
### Problem Statement
Console errors caused by:
1. Dead Alpine.js exports (functions that don't exist) 1. Dead Alpine.js exports (functions that don't exist)
2. DOMContentLoaded listeners running on wrong pages 2. DOMContentLoaded listeners running on wrong pages
3. Missing x-init calls in templates 3. Missing x-init calls in templates
4. **CRITICAL:** x-init functions that fetch data and replace SSR content
### SSR-First Alpine.js Strategy
**Different pages have different SSR/JavaScript ratios:**
- **Analytics Page (80% JavaScript):** Dynamic charts/data that fetch on page load
-`x-init="loadAnalytics"` fetches data and replaces content
- ✅ This is intentional - analytics is a JavaScript-heavy page
- **Dashboard Page (SSR + Interactive):** SSR for speed, JS for drag-drop/library switching
- ✅ SSR provides initial dashboard data
- ✅ x-init ONLY sets up event listeners (drag-drop, modals)
- ✅ x-init does NOT fetch data (data already SSR'd)
-`switchLibrary()` fetches only when user changes library dropdown
- **Admin Library Page (SSR + Modals/CRUD):** SSR list, JS for management
- ✅ SSR provides initial library list
- ✅ x-init ONLY sets up event listeners and modals
- ❌ BUG: `initializeLibraryAdmin()` was calling `reloadLibraries()` which fetches data
- ❌ This replaces the SSR content on page load - MUST FIX
- **Most Pages (80% SSR):** Server-rendered with interactive areas
- ✅ Use x-init for setup only (event listeners, modals)
- ❌ NEVER fetch data in x-init if data is already SSR'd
**Key Principle:**
- ✅ DO in x-init: Set up event listeners, initialize modals, setup drag-drop
- ❌ DON'T in x-init: Fetch data via API, replace SSR innerHTML
### Review Status
**Pages methodically reviewed so far:**
-**Dashboard** - Completed, working correctly
- 🔄 **Collections** - In progress (fixes in progress)
- ⏸️ **Admin Library** - Identified bug, fix documented in this guide
- ⏸️ **Other pages** - Not yet reviewed, will be done page-by-page
**Goal:** Clean, working Alpine.js integration with proper SSR architecture. **Goal:** Clean, working Alpine.js integration with proper SSR architecture.
@@ -326,44 +367,61 @@ Alpine.data("docs", () => ({
<body x-data="docs" x-init="initializeSearch" class="theme-{ user.Theme }"> <body x-data="docs" x-init="initializeSearch" class="theme-{ user.Theme }">
``` ```
### Step 3.3: library.ts ### Step 3.3: library.ts - SSR-FIRST FIX REQUIRED
**⚠️ CRITICAL SSR BUG:** The `initializeLibraryAdmin()` function currently calls `reloadLibraries()` which fetches data from the API and replaces the SSR-rendered library list. This defeats SSR for the admin library page!
**File:** `web/src/library.ts` **File:** `web/src/library.ts`
**Current (around line 655-662):** **Problem (around line 653-655):**
```typescript ```typescript
// Initialize on DOM ready function initializeLibraryAdmin(): void {
if (document.readyState === "loading") { // Setup event listeners
document.addEventListener("DOMContentLoaded", initializeLibraryAdmin); const librariesList = document.getElementById("libraries-list");
} else { if (librariesList) {
initializeLibraryAdmin(); librariesList.addEventListener("click", handleLibraryListClick);
}
// ... more setup ...
// ❌ BUG: This fetches data and replaces SSR content on page load!
void reloadLibraries();
} }
// ... later in file ...
``` ```
**Change to:** **Fix - Remove data fetch from init:**
```typescript ```typescript
// REMOVE this entire block: function initializeLibraryAdmin(): void {
// if (document.readyState === "loading") { // Setup event listeners
// document.addEventListener("DOMContentLoaded", initializeLibraryAdmin); const librariesList = document.getElementById("libraries-list");
// } else { if (librariesList) {
// initializeLibraryAdmin(); librariesList.addEventListener("click", handleLibraryListClick);
// } }
// ... keep all existing setup code ...
// ✅ REMOVED: void reloadLibraries();
// SSR provides initial library list - no need to fetch on page load
// reloadLibraries() is still available to call AFTER create/delete/update operations
}
``` ```
**Template Update:** `templates/library.templ` **Why this fix matters:**
- SSR renders the library list on the server for fast initial load
- Calling `reloadLibraries()` in x-init replaces this with a slower API call
- `reloadLibraries()` should ONLY be called after CRUD operations (create/delete/update)
- The `initializeLibraryAdmin()` should ONLY setup event listeners and modals
**Find the `<body>` tag** and add x-data and x-init: **Template Update:** `templates/admin_library.templ`
**Current state (line 11):**
```html ```html
<!-- BEFORE --> <body x-data="library" x-init="initializeLibraryAdmin" class="theme-{ user.Theme }">
<body class="theme-{ user.Theme }">
<!-- AFTER -->
<body class="theme-{ user.Theme }" x-data="library" x-init="reloadLibraries">
``` ```
**Status:** ✅ Already correct - template already has x-data and x-init setup
**Action:** No template change needed - just fix library.ts to not fetch data in init
### Step 3.4: dashboard.ts ### Step 3.4: dashboard.ts
**File:** `web/src/dashboard.ts` **File:** `web/src/dashboard.ts`
@@ -556,48 +614,61 @@ go build ./cmd/server
## Summary ## Summary
### Changes Made ### Progress So Far
**TypeScript Files (5 files):** **Pages methodically reviewed:**
- `web/src/collections.ts` - Removed dead exports - ✅ **Dashboard** - Completed (drag-drop works, SSR maintained)
- `web/src/analytics.ts` - Removed DOMContentLoaded - 🔄 **Collections** - In progress (dead exports removed, more fixes needed)
- `web/src/docs.ts` - Removed DOMContentLoaded - ⏸️ **Admin Library** - SSR bug identified, fix documented in Step 3.3
- `web/src/library.ts` - Removed DOMContentLoaded - ⏸️ **All other pages** - Not yet reviewed, will be done page-by-page
- `web/src/dashboard.ts` - Created initDashboard wrapper, removed DOMContentLoaded
**Template Files (5 files):** ### Completed Changes
- `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:** **TypeScript Files (3 files):**
- All `_templ.go` files regenerated - `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
**Template Files (1 file):**
- `templates/analytics.templ` - ✅ Added x-init="loadAnalytics" (JS-heavy page, correct)
### Pending Critical Fixes
**SSR Bug in library.ts (Step 3.3):**
- ❌ `initializeLibraryAdmin()` calls `reloadLibraries()` which fetches data and replaces SSR
- ✅ Fix documented: Remove data fetch from init, only setup event listeners
- ⚠️ Template already correct: `templates/admin_library.templ` has x-data/x-init
### What Was NOT Changed ### What Was NOT Changed
- **"Add Books" modal** - Still client-side Alpine.js (quick fix decision) - **"Add Books" modal** - Still client-side Alpine.js (quick fix decision)
- **WebSocket code** - Already moved to templates (previous commit) - **WebSocket code** - Already moved to templates with server-side token injection
- **API endpoints** - Already exist and work correctly - **API endpoints** - Already exist and work correctly
- **HTMX modals** - Already implemented for collection CRUD - **HTMX modals** - Already implemented for collection CRUD
- **Search** - Skipped pending user's planned revamp
### Result ### SSR-First Principles Applied
No more Alpine.js errors **Analytics (80% JS):** x-init fetches data - intentional for dynamic page
No more DOMContentLoaded pollution **Dashboard (SSR + JS):** x-init ONLY sets up event listeners, no data fetch
Pages run only their own initialization code **Admin Library:** Will fix to only setup event listeners, SSR provides data
Console is clean **Collections:** SSR provides data, JS for interactivity (in progress)
✅ SSR architecture maintained
### Next Steps (Optional) ### Result So Far
If you want to convert the "Add Books" modal to HTMX (future enhancement), that would require: ✅ Collections dead exports removed
✅ Analytics loads correctly with SSR-first approach
✅ Admin WebSocket fixed with server-side token injection
⏳ Admin library SSR bug identified, fix ready to implement
⏳ Other pages will be reviewed methodically, one by one
1. Create `templates/add_books_modal.templ` ### Next Steps
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. **Immediate (Critical SSR Bug):**
1. Fix `web/src/library.ts` Step 3.3 - Remove `reloadLibraries()` from `initializeLibraryAdmin()`
2. Test admin library page to verify SSR content is not replaced
**Continue Page-by-Page Review:**
3. Complete collections page fixes
4. Review and fix remaining pages one at a time
5. Update this guide as each page is completed
+26 -31
View File
@@ -650,50 +650,45 @@ function initializeLibraryAdmin(): void {
createLibraryForm.addEventListener("submit", handleCreateLibrarySubmit); createLibraryForm.addEventListener("submit", handleCreateLibrarySubmit);
} }
// Load libraries from API on page load // SSR provides initial library list - no need to fetch on page load
void reloadLibraries(); // reloadLibraries() is called after create/delete/update operations
}
// Initialize on DOM ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initializeLibraryAdmin);
} else {
initializeLibraryAdmin();
} }
// Export functions for global access // Export functions for global access
export { export {
deleteLibrary,
showLibraryFolders,
addLibraryFolder, addLibraryFolder,
removeLibraryFolder, confirmDeleteLibrary,
setLibraryVisibility, deleteLibrary,
loadUserVisibility,
editLibrary, editLibrary,
handleCreateLibrarySubmit, handleCreateLibrarySubmit,
showFolderBrowser,
navigateFolderBrowser,
selectBrowseFolder,
hideFolderBrowser,
showDeleteModal,
hideDeleteModal, hideDeleteModal,
confirmDeleteLibrary, hideFolderBrowser,
initializeLibraryAdmin,
loadUserVisibility,
navigateFolderBrowser,
removeLibraryFolder,
selectBrowseFolder,
setLibraryVisibility,
showDeleteModal,
showFolderBrowser,
showLibraryFolders,
}; };
Alpine.data("library", () => ({ Alpine.data("library", () => ({
deleteLibrary,
showLibraryFolders,
addLibraryFolder, addLibraryFolder,
removeLibraryFolder, confirmDeleteLibrary,
setLibraryVisibility, deleteLibrary,
loadUserVisibility,
editLibrary, editLibrary,
handleCreateLibrarySubmit, handleCreateLibrarySubmit,
showFolderBrowser,
navigateFolderBrowser,
selectBrowseFolder,
hideFolderBrowser,
showDeleteModal,
hideDeleteModal, hideDeleteModal,
confirmDeleteLibrary, hideFolderBrowser,
initializeLibraryAdmin,
loadUserVisibility,
navigateFolderBrowser,
removeLibraryFolder,
selectBrowseFolder,
setLibraryVisibility,
showDeleteModal,
showFolderBrowser,
showLibraryFolders,
})); }));