This document outlines the plan to fix the broken /collections/:id page
which has an inline JavaScript bug, and add library_id support to the
search API.
Key changes planned:
- Remove 265+ lines of inline JavaScript from collections.templ template
- Add minimal TypeScript module (~180 lines) in web/src/collections.ts
- Add optional library_id parameter to SearchMediaItems API endpoint
- Add library filter toggle UI to the Add Books modal
- Update template to accept libraryID parameter
The implementation uses a hybrid approach: minimal TypeScript for
client-only features while maintaining HTMX-like patterns for CRUD
operations. This reduces maintenance burden and improves code
organization.
Steps detailed:
1. Update Search API to accept optional library_id parameter
2. Add library_id filter to SQL query if not present
3. Remove inline JS from template, add data attributes
4. Add toggle UI for filtering books by library
5. Add TypeScript functions for modal, search, and book management
6. Update handler to pass libraryID to template
7. Update template function signature
Testing checklist included to verify:
- Page loads without JS errors
- Library filter toggle visibility
- Search results with/without library filtering
- Add/remove books functionality
- Client-side search filtering
The GetPreferences API was returning 404 when no preferences existed
for a library, breaking the dashboard settings modal. Now returns
default preferences (empty hidden_collections, empty collection_order,
20 items_per_section) when no preferences are found, matching the
behavior of the frontend dashboard page.
- Update TestGetViewAllURL_SystemCollections to use collectionID and libraryID parameters
- Test both with and without library_id in URL
- Update TestBuildSections_ConvertsServiceTypesToHandlerTypes expected values
- All collections now link to /collections/{id} (system and user treated equally)
- Add library_id parameter to BuildSections and getViewAllURL functions
- Update dashboard handler to pass libraryID when building sections
- Add library_id query param support to collection detail page handler
- When library_id is provided, filter collection items by that library
- When no library_id, show all books (backward compatible)
- Reuses GetCollectionItemsForDashboard query for filtered results
- Preserves context when navigating from dashboard to collection detail
Add comprehensive step-by-step plan for implementing library-aware
filtering on collection detail pages.
Purpose:
- Preserve dashboard context when navigating to collection details
- Support both filtered (single library) and unfiltered (all libraries) views
- Maintain backward compatibility with existing URLs
Plan includes:
- Detailed code changes for dashboard.go, frontend.go, dashboard_test.go
- Line-by-line modifications with before/after code snippets
- Implementation order with 10 steps
- Testing checklist for verification
- Documentation requirements
Follows PROJECT_GUIDELINES.md:
- No cascading fix-up edits
- Sequential implementation order
- Post-edit verification steps
- Test-driven approach with additions to dashboard_test.go
- Documentation updates for user-facing feature
This is a planning document only - no implementation changes yet.
Ensure consistent JSON responses by converting nil slices to empty arrays
in the GetPreferences handler. This prevents null values from being
returned to the client for hidden_collections and collection_order fields,
making the API response more predictable and easier to consume.
Update RestoreSystemCollection handler to support form-encoded requests from HTMX:
- Add 'form' struct tags to CollectionName and ResetType fields to enable binding
from both JSON payloads and form submissions (required for HTMX compatibility)
- Add conditional HTMX redirect handling that sets HX-Redirect header when
the request originates from HTMX, directing users to /collections after
successful restoration
This change enables the system collection restore functionality to work seamlessly
with HTMX-based modal forms, improving the user experience by providing proper
navigation after the restore operation completes without requiring JavaScript
redirect logic.
Add *.map pattern to .gitignore to exclude JavaScript sourcemap files
from version control. These files are generated during the build process
and are not needed in the repository, matching the existing pattern for
TypeScript declaration maps (*.d.ts.map).
This prevents accidentally committing generated sourcemap files like
collections.js.map that provide debugging information but are not
necessary for deployment or source control.
Add comprehensive documentation tracking the HTMX Server-Side Rendering
implementation for the Collections page.
Document contents:
- Summary of completed implementation (March 2025)
- Detailed list of all files created and modified
- Step-by-step workflow for each CRUD operation
(Create, Edit, Delete, Restore System Collection)
- Verification instructions
- Key discoveries and lessons learned:
* Templ syntax limitations in conditionals
* Route registration order requirements
* HTMX fragment theming inheritance
* Color handling best practices
* Browser caching considerations
Purpose:
- Historical record of implementation approach
- Reference for future developers
- Documentation of project patterns and conventions
- Guide for troubleshooting similar features
Add comprehensive TypeScript utilities for collections page functionality.
1. HTMX Authentication (setupHTMXAuth):
- Adds Authorization header to all HTMX requests automatically
- Listens for htmx:configRequest event on document.body
- Injects Bearer token from localStorage
- Eliminates need for hx-headers attributes on individual elements
2. Smart Card Navigation (navigateToCollection):
- Implements event delegation to distinguish button clicks from card clicks
- Checks event.target to determine what user clicked
- Returns early if button clicked (lets HTMX handle button actions)
- Navigates to collection detail page only when card body clicked
- Uses data-href attribute for navigation target
3. Color Selection Helpers:
- selectColor(): Updates hidden input and visual selection state
- closeCollectionModal(): Removes modal from DOM after HTMX swap
- initColorSelection(): Applies border color classes to collection cards
using borderClasses mapping (blue→border-blue-500, etc.)
4. Icon Picker with Search:
- Hardcoded iconData object: 30 emojis with searchable keywords
(e.g., "📚": ["book", "books", "library", "read", "reading"])
- populateIconGrid(): Dynamically generates icon buttons from iconData
- selectIcon(): Updates hidden input with selected emoji
- filterIcons(): Real-time search filtering by emoji OR keywords
- showAllIcons(): Clears search filter
- initIconSelection(): Auto-initializes after HTMX modal swap
(listens for htmx:afterSwap event on #modal-container)
5. HTMX Modal Initialization:
- setupHTMXModalInit(): Listens for modal loads via HTMX
- Auto-initializes icon picker when modal content swapped into
#modal-container
All functions exported to window object for onclick attribute access.
Auto-initializes on DOMContentLoaded or immediately if DOM ready.
Pattern consistency:
- Follows same pattern as toast.js (global exports, auto-init)
- Uses TypeScript type annotations
- No OOP (functional style per project guidelines)
- Server-side rendering with HTMX (no AJAX data fetching)
Add auto-generated Go code for new modal templates:
- collection_modal_templ.go (from collection_modal.templ)
- restore_system_collection_modal_templ.go (from restore_system_collection_modal.templ)
These files are generated by templ compiler and contain the Render()
implementations. Do not edit manually.
Regenerate with: templ generate
Add two new template components:
1. CollectionModal(collection CollectionData)
- Reusable modal for both creating and editing collections
- When collection.ID is empty: shows "Create Collection" form
- When collection.ID is set: shows "Edit Collection" form with pre-filled data
- Features:
* Name and description fields
* Icon picker with search input and emoji grid
(grid populated dynamically by JavaScript)
(supports typing emoji directly or searching by keywords)
* Color selection buttons (blue/red/yellow/green/purple)
* HTMX form submission (hx-post for create, hx-put for update)
- HX-Redirect to /collections after successful submission
2. RestoreSystemCollectionModal()
- Modal for restoring deleted system collections
- Dropdown with options: Continue Reading, Recently Added,
Recently Read, Not Started
- HTMX form submission to /api/dashboard/restore-system-collection
- HX-Redirect to /collections after restoration
Both modals:
- Use fixed inset-0 positioning with black/70 backdrop
- Inherit theme from parent page (no html/head/body tags)
- Include close button (✕) that calls closeCollectionModal()
- Follow existing card styling conventions
- Use CSS custom properties for theming (--bg-secondary, --text-primary, etc.)
Add three new frontend routes to support HTMX-powered modal dialogs:
1. GET /collections/create-modal
- Renders empty collection creation modal
- Uses CollectionModal template with empty CollectionData
2. GET /collections/:id/edit-modal
- Fetches collection by ID from database
- Pre-populates modal with existing collection data
- Returns 400 for invalid UUID, 404 if collection not found
3. GET /collections/restore-modal
- Renders system collection restoration modal
- Allows users to restore deleted system collections
Route registration order:
- /collections/:id/edit-modal must be registered before /collections/:id
to avoid path conflicts in Echo's router
These routes enable the collections page to load modals dynamically via
HTMX (hx-get) instead of embedding modal HTML in the base page.
Add form:"" tags to CreateCollectionRequest and UpdateCollectionRequest
structs to enable proper form data binding with Echo's c.Bind().
This change aligns with the pattern used in auth handlers where both
form:"" and json:"" tags are present, allowing the same request structs
to work with both JSON payloads (API) and form data (HTMX).
Changes:
- Add form:"name", form:"description", form:"color", form:"icon",
form:"auto_assign_rules", and form:"view_settings" tags to both
CreateCollectionRequest and UpdateCollectionRequest
Additionally, add HTMX redirect support to CreateCollection and
UpdateCollection handlers:
- Add HX-Redirect header for HTMX requests after successful create/update
- Add HTML redirect response to DeleteCollection for HTMX requests
(follows pattern from auth.go: inline script with window.location.href)
This ensures HTMX form submissions properly redirect to /collections
after successful operations, while maintaining API compatibility for
JSON requests.
- Update wood-light border from harsh black (#2a2a2a) to lighter warm brown (#8b5a2b) for better harmony with light background
- Update wood-dark border from #5c3317 to #7a5228 (slightly lighter medium brown) for improved visibility on dark backgrounds
- Update wood-mahogany border from #5c3317 to #8b3a3a (medium red-brown) to enhance mahogany's characteristic reddish tones
- Reduce background blend opacity from 60% to 40% to create more subtle text area background that complements new border colors
These changes improve visual consistency between border colors and their respective wood paneling backgrounds while maintaining good text contrast across all wood themes.
Update test files to work with recent backend refactoring changes.
Test changes in internal/services/dashboard_service_test.go:
- Fix method name casing for FilterHiddenCollections
- Change from filterHiddenCollections (lowercase 'f')
- Change to FilterHiddenCollections (uppercase 'F')
- Matches exported method signature in DashboardService
- Line 57: Update test call to use correct exported method
Test changes in internal/handlers/dashboard_test.go:
- Update getViewAllURL test to match simplified function signature
- Remove queryType parameter from test call
- Function now only takes collectionName parameter
- Aligns with refactoring to use /collections/{id} routing
- Line 178: Update test call to use new signature
These fixes ensure tests compile and run correctly after the
collection detail page refactoring where:
1. getViewAllURL() was simplified to return /collections/{id}
2. System collections now use the same routing as user collections
Add default library ID functionality to improve library targeting
during media scans.
Service changes in internal/services/media_scanner.go:
- Add defaultLibraryID field to MediaScanner struct
- Add SetLibraryID() method to set default library
- Modify processMediaFile() to use defaultLibraryID when set
- Prioritizes defaultLibraryID over folder-based library detection
- Provides explicit library targeting for scans
Service changes in internal/services/worker.go:
- Add libraryUUID conversion from string to pgtype.UUID
- Call scanner.SetLibraryID() before ScanFolders()
- Ensures scanner respects the job's library ID
These changes enable more precise library targeting during media scans,
allowing scans to be directed to specific libraries rather than relying
solely on folder-based detection.
Change library ordering in dropdown from DESC to ASC to display
libraries in creation order (oldest first).
Database changes in internal/database/queries/queries.sql:
- Modify GetUserLibraries query ORDER BY clause
- Change from ORDER BY l.created_at DESC to ASC
- Displays oldest libraries first in dropdown
This provides a more intuitive ordering where users see their
first-created libraries at the top of the list.
Fix multiple issues with wood paneling background image display
affecting overscroll area and page-specific rendering.
CSS changes in web/static/input.css:
- Add background-attachment: fixed to all wood paneling classes
- Prevents wood paneling from moving during page scroll
- Ensures wood paneling extends into overscroll area
- Applied to bg-wood-dark, bg-wood-light, bg-wood-mahogany
- Fix body and container selectors for wood paneling
- Ensure proper selector targeting for wood paneling application
- Use background-position: center for better alignment
- Use background-size: cover for full coverage
TypeScript changes in web/src/woodPanelingInit.ts:
- Add page detection to prevent wood paneling on collections page
- Check if #collections-container exists in DOM
- Only apply wood paneling on dashboard, not collections page
- Prevents ID collision between dashboard and collections containers
Template changes in templates/header.templ:
- No functional changes, only reformatting
These fixes ensure that:
1. Wood paneling displays consistently across the entire viewport
2. Wood paneling extends into the overscroll area when scrolling past content
3. Wood paneling is properly aligned and centered
4. Wood paneling doesn't interfere with collections page rendering
5. Both dashboard and collections pages can coexist without visual conflicts
Fix multiple issues with dashboard customization modal and slider
not working correctly per library.
Frontend changes in web/src/dashboard.ts:
- Fix openDashboardSettings() to use current library ID
- Add library_id parameter to dashboard preferences API call
- Show toast error message on API failure instead of opening modal
- Prevent opening modal with stale/inaccurate data
- Fix slider query parameter mismatch
- Change from 'libraryId' to 'library_id' to match backend API
- Fix DOM query from collectionList.querySelector to document.querySelector
- Ensure slider targets correct input element
- Fix saveDashboardSettings() to refresh current library
- Fetch current library data before saving preferences
- Use library_id from current library, not from URL
- Show toast error message on save failure
- Keep modal open on error for user to retry
- Add localStorage persistence for selected library
- Store selectedLibrary in localStorage after switching
- Enables persistence across page refreshes
- Improve switchLibrary() with fade transitions
- Add fade-out (150ms) before data fetch
- Add fade-in (300ms) after rendering new library
- Provide smooth visual feedback during library switches
- Apply preferences dynamically to modal
- Use applyPreferencesToModal() to update slider and toggles
- Ensure modal reflects current library's settings
Backend changes in internal/router/dashboard.go:
- Update GetDashboardPreferences to use library_id query parameter
- Matches frontend API call parameter naming
Template changes in templates/dashboard.templ:
- Remove duplicate renderDashboardCollections() inline script
- Functionality now handled by dashboard.ts
These fixes ensure that:
1. Dashboard settings work correctly per library
2. Slider reflects and updates the correct library's item limit
3. Toggles show accurate visibility state for each library
4. Library switches provide smooth visual feedback
5. Errors are properly surfaced to users via toast messages
Add comprehensive collection detail page that works for both system collections
(continue-reading, recently-added, not-started) and user collections.
Backend changes:
- Add new /collections/:id route in internal/router/frontend.go
- Fetches collection using GetCollection with UUID parameter
- Determines collection type from QueryType field
- Resolves library_id for system collections
- Converts database.MediaItems to handlers.BookInfo for display
- Renders CollectionDetail template with collection and books data
- Update SectionData struct in internal/handlers/collections.go
- Add CollectionID string field for view all links
- Update BuildSections() in internal/handlers/dashboard.go
- Pass CollectionID to SectionData for proper link generation
- Simplify getViewAllURL() in internal/handlers/dashboard.go
- Return /collections/{collectionID} instead of /section/{type}
- Works uniformly for both system and user collections
Frontend changes:
- Fix CollectionDetail template in templates/collections.templ
- Fix broken div nesting causing compilation error
- Add null check for CoverImagePath to prevent broken images
- Update aspect ratio to modern aspect-[3/4] syntax
- Use responsive widths (w-16 sm:w-20) for mobile/desktop
- Improve card layout with horizontal flex structure
- Add placeholder image fallback for books without covers
- Remove erroneous renderBooks() function call
This change aligns with the backend update where system collections are
now pre-made user collections in the database with query_type fields.
All collections can now use the same CollectionDetail template for a
consistent viewing experience.
- Update validation from minutes (15-1440) to seconds (1-3600)
- Clarify behavior: real-time file watching with polling fallback
- Remove scheduler references from development docs
- Update migration notes for the new implementation
- Add unit tests for MediaScanner.GetPollInterval and GetAutoScanEnabled
- Add integration tests for scan-settings API endpoints
- Update validation test cases to use seconds (1-3600) instead of minutes
- Fix worker.go to use new NewMediaScanner signature
- Update validation to use scan_poll_interval_seconds field (1-3600 seconds)
- Update all test cases and assertions to use new field name
- Update integration test to reflect new field name
- Add check for auto_scan_enabled setting in router before starting watch mode
- Update StartScanner handler to verify auto-scan is enabled
- Switch scanner to use watchModeCtx/watchModeCancel instead of ctx/cancel
- Update StartWatchModeForLibrary to use new MediaScanner signature
- Add GetPollInterval() method to MediaScanner to read from database
- Add GetAutoScanEnabled() method to check if auto-scan is enabled
- Remove ScanPollIntervalSeconds from config (now DB-driven)
- Update NewMediaScanner signature to not require interval parameter
- Remove SCAN_POLL_INTERVAL_SECONDS from docker-compose env var
- Delete scheduler.go and scheduler_test.go (no longer needed)
- Simplify App struct by removing Handler interface dependency
- Remove StartScheduler/StopScheduler from app lifecycle
- Update main.go to not pass handler to app constructor
- Remove scheduler mock from app tests, simplify test coverage
- Update API field from scan_frequency_minutes to scan_poll_interval_seconds
- Update database schema default value key
- Update Bruno API collection requests and documentation
- Update OpenAPI documentation examples and field descriptions
- Rename SCAN_POLL_INTERVAL_MINUTES to SCAN_POLL_INTERVAL_SECONDS in config
- Update MediaScanner to accept interval in seconds instead of minutes
- Adjust default polling interval from 3 minutes to 30 seconds for faster response
- Add debug logging for fsnotify events to aid troubleshooting file watching
This change improves media file detection responsiveness by reducing the
polling interval from minutes to seconds, while maintaining the file
watcher as the primary detection mechanism.
- Implement event queue with 3-second debouncing for file system events
- Add configurable polling fallback (default 3 min) via SCAN_POLL_INTERVAL_MINUTES
- Add SyncFilesystemWithDatabase to detect orphaned DB entries and new files
- Integrate utils.ResolveMediaURL for consistent media file path resolution
- Add COOKIE_SECURE env var with SameSite=LaxMode for session cookies
- Update media handler to properly decode URL paths for file serving
- Refactor scanner initialization to accept poll interval configuration
Remove IMPLEMENTATION_PLAN.md as the implementation phase has been completed
and the document is no longer needed for reference. The changes described in
the plan have been successfully integrated into the codebase.
Update the DownloadBook function to properly resolve media file paths using
the LibraryService.ResolveMediaPath method instead of directly accessing the
FilePath field. This ensures correct file resolution after the migration to
relative path storage.
The change affects three code paths in the download handler:
- KEPUB conversion path
- Direct file serve path (non-EPUB with conversion service)
- Default EPUB path
Error handling added to return 404 when path resolution fails, preventing
potential errors when accessing non-existent files.
This fixes potential file access issues after the relative path storage
implementation.
Remove the FilePath field from book metadata responses in the GetShelf endpoint.
This change improves security by not exposing internal file paths to API clients,
as the application now uses relative path storage with URL resolution via the
library service.
Changes:
- Remove FilePath field from BookPreview struct in GetShelf response
- Remove FilePath field from shelf items response
Related to previous commit implementing relative path storage.
- Correct indentation in goroutine leak test setup block
- Align struct field tags in BookMatch and all matching methods for
consistent column-style formatting (media_item_id, bookhoard_uuid,
confidence, match_method)
- Improves code readability and adheres to project indentation guidelines
This commit updates third-party static library files:
- highlight.min.js: Updated to latest version (syntax highlighting)
- highlight-dark.min.css: Dark theme for syntax highlighting
- htmx.min.js: Updated to latest version (HTMX library for AJAX)
- lunr.min.js: Updated to latest version (full-text search)
- input.css: Updated Tailwind CSS input styles
- style.css: Updated main application styles
These are third-party library updates that provide improved
functionality and bug fixes for the frontend.
Add explicit guideline specifying 2-space indentation for all code files
unless the language prohibits it. This ensures consistent formatting across
the entire codebase and prevents debates about tab vs space preferences.
The guideline is placed in the General section alongside other coding
convention rules to maintain consistency in project standards.
- Convert indentation from 4 spaces to 2 spaces (matching project style)
- Standardize quotes to double quotes for consistency
- Reformat template literals for improved readability
This file contains the core bookshelf functionality including:
- Library selection and persistence
- Book rendering with cover images
- Pagination for large book collections
- Add libraryService dependency to CollectionHandler and OPDSHandler for centralized path resolution
- Create internal/utils/mediaurl.go with ResolveMediaURL() function as single source of truth
- Update GetMediaItem and ListMediaItems handlers to return resolved URLs in API responses
- Update collection handlers (GetCollection, TestRules, PreviewCollection) to use resolved cover URLs
- Update progress handler (GetAllProgress) to use resolved cover URLs
- Add library_id to GetCollectionItems SQL query to enable URL resolution
- Refactor media scanner to store relative paths instead of absolute filesystem paths
- Add ResolveMediaPath() to LibraryService for resolving relative paths to absolute paths
- Add ServeFile endpoint at /uploads/library-:id/* for authenticated file serving
- Add MimeTypes map to library_service.go for consistent MIME type handling
- Update DownloadBook handler to use resolved filesystem paths
- Add getRelativePath() helper to MediaScanner for converting absolute to relative paths
- Use strings.EqualFold for case-insensitive path comparisons in zip extraction
This change enables the application to work with relative paths stored in the
database, making it portable across different server environments while
maintaining backward compatibility with existing absolute paths.
Extract Handler struct, constructor, and shared utilities from scanner.go
into a new commonhandlers.go file for better code organization.
Changes:
- Move Handler struct definition to commonhandlers.go
- Move NewHandler constructor to commonhandlers.go
- Move SetupRoutes function to commonhandlers.go
- Move parseDate utility function to commonhandlers.go
- Remove unused imports from scanner.go
- Create dedicated commonhandlers.go for shared HTTP handler code
This refactoring improves code maintainability by separating
concerns between scanner-specific logic and common handler utilities,
making it easier to understand and extend the handlers package.
Update Bruno collection test documentation to reflect the new unified static-style
URL strategy for cover images and media downloads.
Changes:
- Update cover image endpoint from /api/covers/{id} to /uploads/library-{id}/{path}
- Update download endpoint from /api/media-items/{id}/download to /uploads/library-{id}/{path}
- Document JWT authentication for static endpoints (same as API endpoints)
- Add clarification that resolved URLs come from API responses
- Update status codes to reflect new endpoint behavior
- Rename 'Download Media Item.yml' to 'EPUB Download.yml' for clarity
This documentation aligns with the unified URL strategy where all file access
goes through a consistent /uploads/library-{id}/ pattern with JWT-based
authentication, eliminating separate API endpoints for file serving.