Created comprehensive SSR_FIRST_ALPINE_GUIDE.md to establish SSR-first
architecture principles for Alpine.js integration.
New Guide: SSR_FIRST_ALPINE_GUIDE.md
Covers:
- SSR-first principles (state in templates, no fetch in x-init for SSR pages)
- Three page type classifications:
* Type 1: 80% SSR (Collections, Conflicts) - backend provides all data
* Type 2: SSR + Interactive (Dashboard, Admin Library) - SSR + interactivity
* Type 3: 80% JavaScript (Analytics) - x-init fetches all data (intentional)
- The SSR data fetch problem (x-init replacing SSR content)
- DOMContentLoaded cleanup strategies
- Page-by-page strategy for each type
- Authentication & SSR (server-side token injection)
- Verification checklist and testing approach
- Architecture diagram showing data flow
Key Principles:
- ❌ 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
- ✅ State lives in template (x-data), not TypeScript
Updated: COLLECTIONS_CLEANUP_GUIDE.md
Changes:
- Added reference to SSR_FIRST_ALPINE_GUIDE.md as authority
- Removed two-option approach (no more choices)
- Documented that admin library SSR bug is already fixed (commit 1b9bc64)
- Simplified dashboard approach (wrap existing code in initDashboard)
- Simplified docs approach (simple setup, no data fetch)
- Updated summary to reflect completed work
- Added architecture section showing state location
Architecture Clarity:
- Templates: UI state (x-data, x-show)
- Backend: SSR data
- TypeScript: Business logic only
- No hybrid approach - follow SSR-first principles
References:
- SSR_FIRST_ALPINE_GUIDE.md - Complete SSR-first architecture
- ALPINE_COMPLETION_GUIDE.md - Full Alpine.js migration (future goal)
- PROJECT_GUIDELINES.md - Project standards
This establishes a single source of truth for SSR-first Alpine.js
architecture and removes confusion about which approach to use.
710 lines
17 KiB
Markdown
710 lines
17 KiB
Markdown
# 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
|