diff --git a/BOOKSHELF_COLLECTIONS_FILTER_PLAN.md b/BOOKSHELF_COLLECTIONS_FILTER_PLAN.md deleted file mode 100644 index 9b72c23..0000000 --- a/BOOKSHELF_COLLECTIONS_FILTER_PLAN.md +++ /dev/null @@ -1,1154 +0,0 @@ -# Bookshelf & Collections Filter Implementation Plan - -## Executive Summary - -Restore bookshelf page as an advanced filtering interface and fix collections book picker modal. Both components will use the existing `/api/media-items/filtered` API with SSR-first architecture. - -**Status:** Orphaned code restoration + new feature implementation -**Priority:** High (core library browsing functionality) -**Estimated Time:** 8-10 hours -**Backend Changes:** None (API already exists) -**Frontend Changes:** New templates + TypeScript - ---- - -## Table of Contents - -1. [Current State](#current-state) -2. [Architecture](#architecture) -3. [Phase 1: Bookshelf Page](#phase-1-bookshelf-page) -4. [Phase 2: Collections Book Picker](#phase-2-collections-book-picker) -5. [Phase 3: Shared Components](#phase-3-shared-components) -6. [Phase 4: Integration & Testing](#phase-4-integration--testing) -7. [Success Criteria](#success-criteria) - ---- - -## Current State - -### What Exists (Backend) ✅ - -**API Endpoint:** `/api/media-items/filtered` -- Supports: author, series, genre, language, year range, cover images -- Sorting by any field -- Pagination (limit/offset) -- **Status:** Fully functional, unused - -### What's Broken (Frontend) ❌ - -**Bookshelf Page:** -- Template: `templates/bookshelf.templ` (exists, orphaned) -- TypeScript: `web/src/bookshelf.ts` (exists, imported but unused) -- Route: `/bookshelf` (removed in commit 2df2b2d) -- Status: Template/JS exist, no route registration - -**Collections Book Picker:** -- Button: Disabled in `templates/collections.templ:153-154` -- Functions: Deleted in commit 93710a1 -- Status: Feature completely non-functional - ---- - -## Architecture - -### SSR-First Principles (Per PROJECT_GUIDELINES.md & ALPINE_COMPLETION_GUIDE.md) - -**Page Type:** Type 2 (SSR + Interactive) - -**SSR (Server-Side Rendering):** -- ✅ Backend renders initial page with data -- ✅ Complete HTML sent to browser (no empty states) -- ✅ No data fetch on page load - -**Alpine.js (UI State):** -- ✅ Manages component state (modals, dropdowns, filters) -- ✅ `x-show` for visibility (NOT `class="hidden"`) -- ✅ `x-data` for state storage -- ✅ x-init ONLY for setup (event listeners) -- ❌ NEVER fetch data in x-init - -**HTMX (Dynamic Updates):** -- ✅ Swaps HTML fragments without page reload -- ✅ Server returns new HTML -- ✅ Client swaps only changed portion -- ✅ No Alpine state lost (uses Alpine.store) - -**TypeScript (Business Logic):** -- ✅ Pure functions -- ✅ API calls -- ✅ Data processing -- ❌ NO manual DOM manipulation -- ❌ NO `classList.add/remove("hidden")` -- ❌ NO `getElementById` for show/hide - -**When to Use What:** -- **`class="hidden"`**: Structural hiding only (forms, utilities) -- **`x-show`**: All stateful UI (modals, dropdowns, dynamic content) -- **`style="display: none;"`**: FOUC prevention with `x-show` (Alpine guide recommends this) - -### Component Design - -**Shared Filtering Component:** -- Reusable across bookshelf page and collections picker -- Alpine.js for state management -- Server-side initial render -- HTMX for filter updates (no page reload) - -**Data Flow (SSR → HTMX → Alpine → TypeScript):** - -``` -Initial Page Load (SSR): - Server fetches libraries & initial books (paginated) - → Renders complete HTML with data - → Sends to browser - → Alpine initialized for UI state - -Filter Change (HTMX → Alpine): - User types in filter field - → Alpine updates local state (if needed) - → HTMX fetches filtered books (HTML) - → HTMX swaps book grid innerHTML - → Alpine re-initializes (maintains store state) - → User sees filtered results - -Multi-Select (Alpine Store): - User clicks checkbox - → Alpine.store.bookPicker.toggleBook(id) - → Checkbox state persisted in store - → User changes filter (HTMX swap) - → Grid re-rendered with new books - → Alpine re-applies :checked from store - → Selection persists across filter changes - -Form Submission (HTMX → TypeScript): - User clicks "Add Books" - → TypeScript reads from Alpine.store - → API call with all selected books - → Success: modal closes, page updates -``` - ---- - -## Phase 1: Bookshelf Page - -### 1.1 Restore Route Registration - -**File:** `internal/router/frontend.go` - -**Location:** After line 204 (before dashboard route) - -**Add route handler:** - -```go -// Bookshelf page - advanced library filtering interface -frontendProtected.GET("/bookshelf", func(c *echo.Context) error { - user, err := getTemplateUserWithTheme(c, cfg) - if err != nil { - return renderErrorPage(c, "Error loading user", "user_load_error") - } - - var errorMsg string - - // Get library_id from query param or user's first library - libraryID := c.QueryParam("library_id") - if libraryID == "" { - userUUID, _ := uuid.Parse(user.ID) - libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID)) - if err == nil && len(libraries) > 0 { - libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16]) - libraryID = libUUID.String() - } else { - errorMsg = "No libraries available" - } - } - - // Get libraries for dropdown - userUUID, _ := uuid.Parse(user.ID) - libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID)) - if err != nil { - log.Printf("GetUserVisibleLibraries failed: %v", err) - libraries = []database.GetUserVisibleLibrariesRow{} - if errorMsg == "" { - errorMsg = "Error loading libraries" - } - } - - libData := make([]templates.LibraryData, len(libraries)) - for i, lib := range libraries { - libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16]) - libData[i] = templates.LibraryData{ - ID: libUUID.String(), - Name: lib.Name, - Description: getText(lib.Description), - TypeName: lib.TypeName, - } - } - - var buf bytes.Buffer - err = templates.BookShelf(user, libData, libraryID, errorMsg).Render(c.Request().Context(), &buf) - if err != nil { - return err - } - return c.HTML(http.StatusOK, buf.String()) -}) -``` - -**Why this approach:** -- SSR-first: Backend fetches libraries, not JavaScript -- Default library selection if none specified -- Error handling for no libraries -- Follows existing frontend.go patterns - ---- - -### 1.2 Update Bookshelf Template - -**File:** `templates/bookshelf.templ` - -**Complete rewrite (replace entire file):** - -```templ -package templates - -templ BookShelf(user User, libraries []LibraryData, currentLibraryID string, errorMessage string) { - - -
- - -{ errorMessage }
-