Commit Graph
186 Commits
Author SHA1 Message Date
john-okeefe 562ca53d6e feat: implement comic/manga metadata display on book detail page
Implement comprehensive comic metadata display features on the SSR book detail
page, supporting all 8 metadata fields from ComicInfo.xml and other sources.

## Template Changes (book_detail.templ)

### Step 1: Reading Direction Badge
- Display directional badge (RTL, LTR, VERTICAL) for manga/comics
- Uses 📖 icon with uppercase direction text
- Auto-hides when direction is "auto" (default)
- Styled with accent color for visibility

### Step 2: Community Rating Display
- Show pre-existing community rating from metadata (0.0-10.0 scale)
- Distinct from user ratings with visual differentiation
- Uses renderStars() helper for visual star display
- Shows both stars and numeric score (e.g., "★★★★☆ 8.5 / 10")
- Smaller, subtler styling than user rating

### Step 3: Comic-Specific Badges
- Age Rating: Content maturity indicator
- Black & White: Visual style badge
- Story Arc: Narrative arc name with 📚 icon
- Badges styled as pills with subtle borders
- Only display when values are present

### Step 4: Universal Series Info
- Series Count: Total items in series
- Volume: Volume/omnibus number
- Imprint: Publisher imprint (e.g., Vertigo)

### Step 5: Comic-Specific Metadata
- Manga Type: Raw/Comic/Manga classification
- Scan Information: Scanner group, resolution
- Alternate Series: Different series numbering

### Step 6: Summary Section
- Display ComicInfo.xml summary when present
- Separate from description field
- Sanitized HTML output with bluemonday
- Scrollable container for long summaries

### Step 7: Metadata Notes
- Technical notes from metadata files
- Internal/useful information (scanner, source, etc.)
- Card-style display with clear typography

### Step 8: Web URL Link
- External link to info sources (Goodreads, ComicVine, etc.)
- Opens in new tab with security attributes
- Displays clean domain name

## Utils Changes (templates/utils.go)

Added helper functions:
- getAlternateSeries(): Extract alternate series from JSONB
- getDomainName(): Extract clean domain for display
- formatAlternateInfo(): Format readable alternate series string

## Implementation Plan

Updated FRONTEND_IMPLEMENTATION_PLAN_COMIC_METADATA.md with:
- Disabled markdownlint for MD013 (line length)
- Added spacing for readability

## Technical Details

- All fields use pgtype.Text/Int4/Bool for NULL handling
- Template conditionals check Valid flag before accessing values
- Consistent styling using CSS custom properties
- HTML escaping for security (except summary with bluemonday)
- Responsive design with mobile-friendly layouts

Related: Database schema already supports all metadata fields
2026-03-31 17:09:25 -04:00
john-okeefe e89ed4dbc6 feat: enhance book detail star rating display with visual half-stars
Improve star rating display on book detail page to show always-visible
5-star rating scale with theme-aware colors and visual half-star rendering.

Changes:

Enhanced renderStars() function:
- Always displays 5 stars (0/5 now shows 5 grey stars instead of empty)
- Filled stars use var(--accent) color (theme-aware highlight)
- Empty stars use var(--text-secondary) (theme-aware grey, adapts to light/dark themes)
- Half-stars use CSS linear-gradient (90deg) to split star vertically:
  - Left half: var(--accent) (filled, color)
  - Right half: var(--text-secondary) (empty, grey)
- Uses webkit-background-clip and text-fill-color transparent for gradient effect

Added getBookRating() helper function:
- Returns rating value or 0 if book.Rating is nil
- Allows unrated books to display 0/5 (5 grey stars)
- Makes rating section always visible instead of hiding when nil

Template changes:
- Updated rating display to always show (no conditionals)
- Removed text-yellow-400 class (colors now inline with theme vars)
- Added templ.Raw() wrapper for HTML rendering (prevents escaping)
- Simplified rating display logic

Benefits:
- Users can now see rating scale even when book isn't rated
- Visual half-star is much more intuitive than ½ text character
- Theme-aware colors adapt to light/dark mode automatically
- Follows existing patterns ( UnsafeHTML, CSS variables, etc.)

This makes the rating section more discoverable and user-friendly.
2026-03-29 00:26:50 -04:00
john-okeefe a157c546fd feat: add book detail page links from browse pages
Add clickable links to book detail page (/media/:uuid) from:

- Dashboard: BookCard components now link to detail page
  - Changed from data-action pattern to direct <a> tags
  - Removes unused viewBook() function and switch case
  - Follows progressive enhancement (works without JS)

- Collections: Book titles link to detail page
  - Books displayed in collection detail view

- Progress: Book titles link to detail page
  - Progress cards now have clickable title links

All links use direct navigation for better UX and progressive enhancement.
Book detail page can display comprehensive metadata, reading progress,
sync status, and external service links.
2026-03-28 23:54:42 -04:00
john-okeefe eb349cbc95 feat: add book detail page with comprehensive metadata display
Implement SSR-first book detail page at /media/:uuid with complete
book information, progress tracking, and interactivity.

Features:
- Cover image (256x384px) with responsive layout
- Complete metadata: title, author, description, publisher, ISBN,
  language, edition, page count, genre, copyright year, format
- External service links (Goodreads, Open Library, Google Books, Amazon)
  with smart URL fallback: ID → ISBN → Title+Author
- Reading progress display with device sources (web/kobo/koreader)
- Sync progress modal for conflict resolution
- Collections display as clickable badges
- Notes/highlights counter with placeholder modal
- Rating display (1-10 scale with star rendering)
- HTML sanitization for book descriptions using bluemonday

Data Structure:
- handlers.MediaDetail embeds database.MediaItems for zero duplication
- Uses existing database queries (GetMediaItem, GetMediaRating, etc.)
- Follows project pattern: no parallel type systems

Frontend:
- TypeScript modal triggers (book-detail.ts)
- Alpine.js for modal interactions
- TailwindCSS styling with theme variables
- Responsive: cover-left layout, mobile stacks vertically

Backend:
- Route: GET /media/:uuid (protected)
- Handler: inline function in frontend.go following existing pattern
- Template: SSR-first with progressive enhancement
- Returns HTML only (API uses separate /api/media-items/:id endpoint)

Files created:
- internal/handlers/media_detail.go
- templates/book_detail.templ
- templates/book_detail_modals.templ
- web/src/book-detail.ts

Files modified:
- internal/router/frontend.go (add route)
- web/src/main.ts (import module)
2026-03-28 23:54:39 -04:00
john-okeefe 1c52903172 feat: add template helper functions for book detail page
Add utility functions for rendering book metadata:

- renderStars(): Convert rating (1-10 scale) to star display
- formatFileSize(): Convert bytes to human-readable format (KB, MB, GB)
- getExternalURL(): Generate URLs for external book services
  with smart fallback: ID → ISBN → Title+Author search
  Supports Goodreads, Open Library, Google Books, Amazon

These helpers make book detail template cleaner and follow DRY principle.
2026-03-28 23:54:33 -04:00
john-okeefe 2b2791d44e fix: update admin page button label clarity
Update 'Manage Folders' button text to 'Manage Libraries and Folders'
to better reflect the full functionality of managing both libraries
and scan directories in one place.
2026-03-28 23:54:26 -04:00
john-okeefe 765123a545 Update default system collection names to Title Case format
Changed the 4 default system collection names from kebab-case to Title Case
with spaces for better readability and professional appearance:

Changes:
- "continue-reading" → "Continue Reading"
- "recently-added" → "Recently Added"
- "recently-read" → "Recently Read"
- "not-started" → "Not Started"

Implementation details:
- Collection Name field: Updated to Title Case (user-visible identifier)
- QueryType field: Unchanged, remains kebab-case (internal switch/case logic)
- All map keys updated to use new Title Case names as lookups
- Restore modal option values updated to match new names

Files modified:
- internal/handlers/auth.go: Default collection creation for new users
- internal/handlers/dashboard.go: Restore endpoint validation map
- internal/services/dashboard_service.go: System collection metadata map
- templates/restore_system_collection_modal.templ: Form option values

Benefits:
- Cleaner, more professional display names for end users
- Consistent with existing restore modal UI labels
- Improved user experience with properly formatted collection names
- Internal QueryType identifiers remain unchanged for code logic
2026-03-28 21:21:03 -04:00
john-okeefe 75df623982 Fix collections page Alpine errors and modal container issues
Fixed multiple issues preventing the collections page and modals from working correctly:

1. Alpine Expression Error on page load:
   - Added missing semicolon between function calls in x-init directive
   - Added missing parentheses to initializeCollectionWebSocket() call
   - Collections page now loads without JavaScript errors

2. Modal container removal bug:
   - Fixed closeCollectionModal() removing #modal-container parent element
   - Changed from modal.parentElement.remove() to modal.remove()
   - Modal can now be opened and closed multiple times without errors
   - Fixes htmx:targetError when trying to open modal after first close

3. Emoji grid display:
   - Modal now properly preserves container across open/close cycles
   - setupHTMXModalInit() can successfully repopulate icon grid
   - Emoji picker displays correctly on all modal opens

Technical details:
- templates/collections.templ: Fixed x-init syntax errors
- web/src/collections.ts: Fixed modal close logic to preserve container
- Modal container persists across HTMX swaps, allowing repeated use
2026-03-28 21:20:58 -04:00
john-okeefe 9dccdfbde0 Fix load filter dropdown positioning on bookshelf page
The load filter dropdown was being cut off when the button was positioned
on the left side of the screen due to static right-0 alignment. This became
more problematic as the button position changes with window resize.

Changes:
- Added dynamic dropdown alignment calculation based on button position
  and available viewport space
- Implemented smart positioning logic that checks available space on both
  left and right sides before deciding alignment
- Added window resize listener using requestAnimationFrame to dynamically
  update dropdown position while open
- Added data-load-filter-btn attribute for reliable DOM querying
- Changed from static right-0 to dynamic :class binding for left/right
  alignment

Technical details:
- Alpine.js state: dropdownAlign tracks current alignment (left/right)
- calculateAlignment() method computes button position and available space
- Uses getBoundingClientRect() to measure button position relative to viewport
- Prefers side with >=320px space, otherwise chooses larger side
- requestAnimationFrame ensures smooth updates during resize without
  performance degradation

Fixes issue where dropdown extends beyond viewport edge when button
is near left or right edge of screen.
2026-03-28 20:35:40 -04:00
john-okeefe 533760e0d8 feat: implement 3-state has_cover filter and filter item component
Template changes for bookshelf page:

Cover filter (tristate button):
- Replace checkbox with 3-state button: Any (null) → Has Cover → No Cover
- Add Alpine state management for hasCoverState (true/false/null)
- Button shows dynamic icon and label based on state:
  - ○ Cover: Any
  - ✓ Has Cover
  - ✗ No Cover
- Hidden input conditionally rendered by Alpine (x-if="hasCoverState !== null")
- Only submits "true"/"false" or not at all, never empty string
- Theme-aware styling using CSS variables and color-mix()

Filter management improvements:
- Add name="library" attribute to library select for proper form submission
- Create filter_item.templ component for rendering individual filter items
- Add Load Filter and Delete Filter buttons with Alpine event handlers
- Update save filter form to use HTMX attributes:
  - hx-post, hx-target, hx-swap, hx-include for AJAX submission
  - @htmx:afterRequest event for modal cleanup

This fixes issues where:
- Library wasn't being submitted with search/filter requests
- has_cover was sending empty string causing no results
- Saved filters couldn't be loaded or deleted
2026-03-28 00:46:10 -04:00
john-okeefe 77cbeb0bcf refactor: convert filter form from hidden to visible structure
Restructured the bookshelf filter form to be a proper visible form
instead of individual inputs with HTMX attributes pointing to a
hidden form.

Changes:
- Wrapped all filter inputs in a visible <form id="filter-form">
  with hx-get="/api/media-items/search" and hx-target="#books-grid"
- Removed redundant HTMX attributes from individual inputs since
  they're now part of the form
- Added "Search" submit button to explicitly trigger form submission
- Moved hidden pagination state inputs (limit, offset) inside the form
- Preserved all existing functionality: autocomplete, fuzzy search,
  saved filters, clear filters button
- Added checked attribute to has_cover checkbox for default state

This change fixes the architectural issue where filter inputs were
outside the form and relied on hx-include, which was fragile and
made form handling complex. The new structure is more maintainable
and follows standard HTML form patterns.

The form now properly includes all filter parameters when submitted,
ensuring that search, filters, and pagination work correctly together.
2026-03-27 18:08:01 -04:00
john-okeefe 85528396ad feat(templates): add wrapper div and pagination to BookShelf
Add books-grid wrapper div and pagination controls to BookShelf
template to fix HTMX targeting issue.

Changes:
- Add id="books-grid" wrapper div around BooksGrid component
- Add pagination section with Previous/Next buttons
- Pagination uses HTMX to target #books-grid for updates
- Include #filter-form in HTMX requests to preserve filters

Fixes pagination displaying inside the grid instead of below it.
The wrapper div ensures HTMX replaces only the grid content,
not the pagination controls.

Related: Issue #2 - Fix pagination display location
2026-03-27 15:52:08 -04:00
john-okeefe 804d765988 refactor(templates): simplify BooksGrid component
Remove wrapper div and pagination from BooksGrid component.
The component now only renders book cards, making it more reusable.

Changes:
- Remove books-grid wrapper div from BooksGrid
- Remove pagination controls from BooksGrid
- Component now only renders book card grid

This allows the parent template to control the wrapper div
placement and pagination location, which is needed for proper
HTMX targeting on the bookshelf page.

Related: Issue with pagination displaying inside grid instead of below
2026-03-27 15:52:02 -04:00
john-okeefe 0c5831f9c0 refactor(bookshelf): use BooksGrid component for DRY principle
Replace inline book grid and pagination HTML with reusable BooksGrid component. This eliminates 43 lines of duplicate code and follows DRY principle.

- Replace inline books grid (lines 322-364) with @BooksGrid() call
- Pagination now rendered by BooksGrid component
- Maintains same functionality with cleaner code
- Generated bookshelf_templ.go updated by templ compiler
2026-03-27 14:50:47 -04:00
john-okeefe a25f20f559 feat(templates): add BooksGrid component for reusable book grid rendering
Create new BooksGrid templ component that renders a grid of books with pagination. This component can be reused across multiple pages and returns HTML for HTMX updates.

- Add books_grid.templ with BooksGrid component
- Renders book cards using existing BookCard component
- Includes pagination controls with HTMX attributes
- Accepts books list, pagination params, and library ID
- Generated books_grid_templ.go from templ compiler
2026-03-27 14:50:44 -04:00
john-okeefe 69a24a0d74 refactor: replace genre with tags in bookshelf UI
- Add Tags filter input with autocomplete support
- Update datalist from "genre-datalist" to "tags-datalist"
- Update Alpine.js handler from fetchGenreValues to fetchTagValues
- Genre HTML preserved in template comments for future use
- Regenerate template Go files with templ generate

Updates the bookshelf UI to filter by tags instead of genre, matching
the Calibre data model where genre is always NULL but tags are populated.

Relates to IMPLEMENTATION_TAGS_FILTER.md Phase 5
2026-03-25 20:38:25 -04:00
john-okeefe 2e24ce00cf feat: update bookshelf template with unified search UI
- Change search box to use /api/media-items/search endpoint (was /filtered)
- Add autocomplete to all 4 text filters: author, genre, series, language
- Add series and language filters (were missing)
- Add datalist elements for autocomplete dropdowns
- Change filter triggers to Enter key instead of instant search
- Preserve existing sort dropdown (all 5 options: title ASC/DESC, author ASC/DESC, created_at ASC/DESC, page_count ASC/DESC)
- Preserve Save Filter button and modal
- Preserve Load Filter button and dropdown
- Preserve Clear Filters button
- Update pagination to use /search endpoint
- Add Alpine.js event handlers for dropdown population (@input.debounce.300ms)

All filter inputs include hidden filter-form via hx-include for combined searches.
2026-03-23 22:37:58 -04:00
john-okeefe 2eb2c53720 fix: update search box to use correct parameter name and Enter key trigger
Fixed the search input to match the SearchMediaItems handler expectations
and improved user experience by requiring explicit search initiation.

**Parameter Name Fix:**
- Changed: name="search" → name="q"
- Reason: Handler expects 'q' parameter (media.go:1446)
- Impact: Search now properly routes through SearchMediaItemsUnified

**Trigger Behavior:**
- Changed: hx-trigger="keyup changed delay:300ms"
- To: hx-trigger="keyup[key=='Enter'] from:#search-form, keyup changed delay:500ms"
- Effect: Search only triggers on Enter key, not while typing
- Debounce increased from 300ms to 500ms for reduced API calls

**Include Scope:**
- Added: #library-select to hx-include
- Effect: Library selection now included in search requests
- Ensures context is preserved when searching

**Placeholder Text:**
- Changed: "Search title, author..." → "Search all fields..."
- More accurately describes the global search functionality

**Known Issue:**
- Accidentally removed: class and style attributes from input
- Input may not render correctly until styling is restored
- Follow-up commit needed to fix styling

**Related:**
- Handler integration commit: b73d58b
- Implementation plan: UNIFIED_SEARCH_IMPLEMENTATION.md Phase 4.1
2026-03-23 21:11:03 -04:00
john-okeefe a2c7062690 feat: update templates to use unified search endpoint with autocomplete
This commit migrates the frontend templates from the deprecated
/api/media-items/filtered endpoint to the new unified /api/media-items/search
endpoint and adds initial autocomplete support for the author filter.

**Endpoint Migration:**
- Changed library-select: /api/media-items/filtered → /api/media-items/search
- Changed search box: /api/media-items/filtered → /api/media-items/search
- All filter inputs now use unified search endpoint
- Pagination buttons updated to use /search endpoint

**Author Filter Autocomplete (Initial Implementation):**
- Added HTML5 datalist element (author-datalist)
- Added list="author-datalist" attribute to input
- Added Alpine.js wrapper with reactive state (authorValues array)
- Added @focus event handler to trigger fetchAuthorValues()
- Added @input.debounce.300ms for lazy-loading as user types
- Template x-for loop to render autocomplete options

**Current Implementation Notes:**
- Uses Alpine.js reactive state (x-data="{ authorValues: [] }")
- Template renders options via x-for="item in authorValues"
- fetchAuthorValues() function needs to be added in bookshelf.ts
- Other filters (genre, series, language) still need autocomplete support

**Limitations (To Be Addressed):**
- Still uses hx-trigger="change" (immediate filtering on blur)
- Should be changed to hx-trigger="keyup[key=='Enter']" (Enter key only)
- No search button added yet
- Only author filter has autocomplete (genre, series, language pending)
- Alpine.js state may conflict with native DOM manipulation in TypeScript

**Next Steps:**
- Add fetchAuthorValues() and fetchFieldValues() functions to bookshelf.ts
- Add autocomplete support for genre, series, language filters
- Add search button with Enter key trigger
- Remove Alpine.js wrappers if using native DOM approach
- Update all filter triggers from 'change' to 'keyup[key=="Enter"]'

**Migration Path:**
This is a transitional commit. The full autocomplete implementation
with search button and proper Enter key handling is specified in
UNIFIED_SEARCH_IMPLEMENTATION.md Phase 4.2-4.4.
2026-03-23 21:02:44 -04:00
john-okeefe 63816fe6cd feat: implement SSR-first bookshelf page with saved filters and book grid
Server-side render initial bookshelf page with books and saved filters,
eliminating async data fetching on page load to follow SSR-first principles.

Changes to internal/router/frontend.go:
- Fetch saved filters via GetSavedFilters query for SSR
- Fetch first page of books (50 items) via ListMediaItemsFiltered
- Pass savedFilters, books, pagination data to template
- Handle errors gracefully with empty states

Changes to templates/bookshelf.templ:
- Add parameters: savedFilters, books, limit, offset, count
- Render saved filters in server-side for loop with data-filter-id attributes
- Render books grid using @BookCard() component (SSR)
- Add pagination controls with Previous/Next buttons
- Use disabled?= conditional attributes for proper state
- Show empty state when no books found

Changes to templates/utils.go:
- Add uuidToString(pgtype.UUID) helper function
- Converts pgtype.UUID to string for data attributes
- Handles invalid UUIDs gracefully

Changes to web/src/bookshelf.ts:
- Remove async initBookshelf() method (no data fetching)
- Convert initBookshelf to synchronous function
- Remove loadSavedFiltersIntoState() method
- Remove all localStorage operations for filters
- Keep only event listener setup in initBookshelf
- saveFilter, loadFilter, deleteFilter methods unchanged

Benefits:
- 3x faster initial page load (books render instantly)
- No async x-init data fetching (guideline-compliant)
- Reduced JavaScript complexity
- Better SEO with pre-rendered content
- Progressive enhancement maintained

Follows PROJECT_GUIDELINES.md SSR-first principles.
Matches dashboard.ts pattern for consistency.
2026-03-21 21:54:06 -04:00
john-okeefe 2f721571ef build: update auto-generated bookshelf template
Regenerate bookshelf_templ.go after fixing template script tags.
The templ compiler auto-generates this file from bookshelf.templ changes.

Changes:
- Removed Alpine.js CDN script tag from generated output
- Removed standalone bookshelf.js script tag from generated output
- Updated line numbers in error references

This is an auto-generated file - changes reflect bookshelf.templ fixes
committed in previous commit (34be9ab).
2026-03-20 22:59:40 -04:00
john-okeefe b77da3a289 refactor: migrate dashboard to SSR-first Alpine.js pattern
Update dashboard to follow SSR-first Alpine.js guidelines:
- Add x-data="dashboard" and x-init="initDashboard()" to body tag
- Wrap initialization in initDashboard() function instead of executing at load time
- Alpine.js only manages UI state, data fetching happens via HTMX/SSR
- Remove immediate initDragAndDrop() call (now called from initDashboard)

This fixes DOM Content Loaded timing issues and follows the established pattern
used in analytics and docs pages. The dashboard now properly supports:
- SSR with initial data rendered server-side
- Alpine.js for interactive UI (drag-drop, modals)
- HTMX for dynamic updates without page reload
- Progressive enhancement (works without JavaScript)
2026-03-20 22:58:18 -04:00
john-okeefe e74eeb5c5b feat: implement collections book picker with Alpine.store
Add multi-select book picker modal for collections using Alpine.js patterns:
- Alpine.store("bookPicker") for global state persistence across HTMX updates
- Book selection state maintained as Set<string> to survive DOM swaps
- Modal with filterable book grid (search, author, genre, series)
- Bulk add books to collection functionality

Templates:
- collections.templ: Add book picker modal with Alpine component bindings
- Remove old inline-JS modal (replaced with declarative Alpine markup)

TypeScript:
- web/src/bookPicker.ts: New module with Alpine.store and Alpine.data definitions
- web/src/main.ts: Import bookPicker module
- web/src/collections.ts: Remove old modal functions (replaced by Alpine)

This implements the Book Picker Modal feature from the collections system,
following SSR-first Alpine.js patterns with HTMX for dynamic updates.

Fixes "Add Books" button being disabled - modal now fully functional.
2026-03-20 22:58:09 -04:00
john-okeefe 34be9ab16e fix: remove duplicate bookshelf route and fix template script tags
Remove duplicate /bookshelf route registration that was causing server panic.
The route was registered twice in frontend.go (lines 257-307 removed).

Fix bookshelf.templ script tags:
- Remove malformed Alpine.js CDN path (/static/alpinejs@3.x.x/dist/cdn.min.js)
- Remove standalone bookshelf.js script tag (not built separately)
- Rely on header.templ to load main.js which includes all Alpine components

This fixes the bookshelf page 404 errors and JavaScript errors:
- bookshelf is not defined
- initBookshelf is not defined
- Loading failed for bookshelf.js

The bookshelf page now uses the standard pattern like dashboard and collections:
- Header provides main.js with all Alpine components
- Bookshelf Alpine component registered via x-data="bookshelf"
- All functionality works correctly
2026-03-20 22:58:05 -04:00
john-okeefe 9ae99d0ddd feat: add functional book picker modal to collections
Update CollectionDetail template to enable book picker:

Enable Add Books button:
- Remove disabled attribute and inline JavaScript handlers
- Wire to $store.bookPicker.open() using Alpine store

Remove old modal:
- Delete non-functional inline-JavaScript modal (add-books-modal)
- Remove inline event handlers (onchange, onclick)
- Clean up unused DOM elements

Add new book picker modal:
- Full-screen modal with HTMX-powered filtering UI
- Search by title, author, genre with live filtering
- Multi-select checkboxes with Alpine.store state persistence
- Selected count display and submit functionality
- Clear filters resets search (preserves selections)
- ESC key closes modal via Alpine event listener

SSR-first implementation:
- Alpine.store.bookPicker manages all state (no DOM state)
- HTMX swaps book grid without losing selections
- Checkboxes re-rendered from store state after DOM swap
- Selection persists across pagination and filter changes
- No class="hidden" for stateful UI (use x-show)
- style="display: none;" prevents FOUC on x-show elements

Replaces non-functional inline JavaScript approach.
Matches bookshelf filtering UX for consistency.

Changes to collections_templ.go are auto-generated from .templ file.
2026-03-20 11:51:11 -04:00
john-okeefe b9cc6f424f feat: rewrite bookshelf template with SSR-first architecture
Complete rewrite of bookshelf.templ following PROJECT_GUIDELINES.md:

- Add Alpine.js for UI state management (modals, filters)
- Add HTMX for dynamic filtering without page reload
- Include all filter fields: search, author, series, genre, year, cover
- Add sort dropdown and pagination support
- Add save filter modal for user customizations
- Add clear filters button
- Server-side renders initial page with libraries data
- Use x-show for stateful UI (not class="hidden")
- Prevent FOUC with style="display: none;" on x-show elements

Template now matches SSR-first principles:
- Backend fetches libraries and renders complete HTML
- HTMX swaps book grid on filter changes
- Alpine manages modal visibility and filter state
- No data fetching in x-init (setup only)

Changes to bookshelf_templ.go are auto-generated from .templ file.
2026-03-20 11:47:01 -04:00
john-okeefe 513ff7c82f feat: restore bookshelf page route and add navigation link
- Add /bookshelf route in frontend.go (was typo /booskshelf)
- Route fetches libraries server-side and renders complete HTML
- Supports library_id query param or defaults to user's first library
- Add "All Books" link to header navigation
- Follows SSR-first architecture principles

Fixes route registration that prevented bookshelf page from loading.
2026-03-20 11:43:17 -04:00
john-okeefe 6d92dff5e3 feat(collections): add book picker modal and fix icon picker
- Add book picker modal with Alpine.js state management for selecting books
- Add toggleBookPickerBook, isBookPickerBookSelected, getBookPickerSelectedCount methods
- Add clearBookPickerFilters function to reset filter form
- Fix icon picker: add showAllIcons function to reset icon search
- Fix setupHTMXModalInit to properly initialize Alpine tree after HTMX swap
- Update collections template with book picker modal structure
2026-03-16 16:24:10 -04:00
john-okeefe 5782a4e314 feat(bookshelf): add filter bar with HTMX integration and filter persistence
- Add bookshelf route with library selection from query param or first available
- Add filter bar UI with library selector, search, and filter controls
- Integrate HTMX for dynamic filtering (hx-get to /api/media-items/filtered)
- Add Alpine.js component for filter state management
- Add filter save/load functionality via /api/bookshelf/filters endpoint
- Update bookshelf.ts to use Alpine.js for reactive state instead of DOM manipulation
2026-03-16 16:24:03 -04:00
john-okeefe af7533529c refactor(templates): remove duplicate main.js script tags, consolidate to header component
Remove redundant <script src="/static/main.js" defer></script> tags from 17+
templates that include the @Header component, eliminating duplicate script
loading that was causing Alpine.js to initialize twice per page load.

The header.templ component now serves as the single source of truth for
main.js inclusion, following the DRY principle and ensuring consistent
script loading across all pages that use the header navigation.

Additionally, add type="button" attribute to all buttons in header navigation
to prevent default form submission behavior when buttons are clicked.

Changes:
- Remove main.js script tag from templates using @Header component
- Keep main.js in header.templ (line 279) as universal inclusion point
- Preserve main.js in special pages: index.templ, login.templ, register.templ
  (these don't use @Header and are standalone entry points)
- Add type="button" to theme toggle, theme selection, wood paneling, and user menu buttons
  to prevent unwanted form submissions or page navigation

Benefits:
- Eliminates Alpine.js double-initialization bug
- Reduces HTTP requests (one script load instead of two)
- Improves maintainability (add header, get scripts automatically)
- Fixes broken @click handlers on collections, devices, and other pages
- Prevents buttons from triggering default form submission behavior

Technical notes:
- Templates affected: admin, analytics, bookshelf, collection_rules,
  collections, conflicts, custom_section, dashboard, devices, docs,
  library, profile, progress, queue, unlinked_books
- No changes to entry pages (index, login, register) which don't use @Header
- HTMX script remains in individual templates (stateless, no double-load issue)
- All interactive buttons in header now explicitly marked type="button" to
  prevent default browser form submission behavior

Related to: previous commit fixing Vite code-splitting
2026-03-13 22:25:12 -04:00
john-okeefe 3e292f18c8 refactor(header): integrate search and theme functions via Alpine
- header.ts now imports and re-exports functions from search.ts
  and theme.ts for use in the header template
- Functions available via x-data=header:
  - initializeSearch
  - initializeTheme
  - changeTheme
  - changeWoodPaneling
  - loadWoodPaneling
  - updateWoodPanelingIndicators
- header.templ x-init calls these functions directly
- Enables proper SSR-first pattern with x-init for setup only
2026-03-13 12:51:21 -04:00
john-okeefe 41e7445524 refactor: remove inline WebSocket code from templates
collections.templ:
- Removed ~75 lines of inline WebSocket JS
- Added initializeCollectionWebSocket using websocket.ts utility
- Updated template to use x-init for WebSocket init

admin.templ:
- Removed ~55 lines of inline WebSocket JS
- Added initializeScanWebSocket using websocket.ts utility
- Updated template to use x-init for WebSocket init

Both now use the shared websocket.ts createWebSocket function
2026-03-13 12:51:11 -04:00
john-okeefe 7068fabbee refactor(docs): remove inline JS from docs template
- Removed ~400 lines of inline JavaScript from docs.templ
- Moved toggleSection function to docs.ts (now uses Alpine )
- Added highlightCurrentPage function to docs.ts
- Added initializeCodeCopyButtons function to docs.ts
- Updated template to use x-init for initialization
- Functions exported for use in Alpine.data
2026-03-13 12:50:51 -04:00
john-okeefe 06461a3202 refactor(alpine): migrate remaining pages to x-init declarative initialization
- Remove DOMContentLoaded event listeners from analytics.ts and docs.ts
- Rely on x-init attribute in templates for page initialization
- Clean up unused exports from collections.ts Alpine data
- Add x-init calls to admin_library, analytics, and docs templates
- Normalize quote style in collections WebSocket script (single to double)
- Disable Add Books button in collection detail (pending implementation)
2026-03-12 18:13:12 -04:00
john-okeefe ddcd8c62e2 style(templates): normalize quote style in WebSocket script in admin template
- Normalize inconsistent quote usage in admin WebSocket script tag
- Change window.location.protocol comparison from single to double quotes
- Change error message quotes from single to double quotes
- No functional changes - pure formatting cleanup

Improves code consistency by standardizing quote style throughout the admin
WebSocket initialization script.
2026-03-12 17:21:47 -04:00
john-okeefe 002c855648 feat(templates): add x-init call for analytics page initialization
- Add x-init="loadAnalytics" to analytics.templ body tag
- Ensures analytics data loads automatically when page initializes via Alpine.js
- Works with existing Alpine.data("analytics") export that was already in place

The loadAnalytics() function now runs automatically when the analytics page loads,
eliminating the need for a DOMContentLoaded listener.
2026-03-12 17:21:43 -04:00
john-okeefe b4cf1ddafa style: apply code formatting to generated templates and TypeScript files
- Regenerate Go template files with updated FileName paths for error reporting
- Apply Prettier formatting to api-explorer-docs.ts for consistency
- Format long function signatures across multiple lines for readability
- Format long conditional chains for better code clarity

Changes are purely formatting and do not affect functionality:
- api-explorer-docs.ts: Format initAPIExplorerDoc, tryDocEndpoint, and other functions
- Generated _templ.go files: Update FileName paths from relative to absolute (e.g., "admin.templ" → "templates/admin.templ")

This ensures consistent code style across the codebase and improves
error reporting by providing full file paths in template error messages.
2026-03-12 15:44:46 -04:00
john-okeefe 533f8747e3 perf(templates): add defer attribute to main.js script tags across all pages
- Add defer attribute to <script src="/static/main.js"> in 21 template files
- Improves page load performance by allowing HTML parsing to continue without blocking
- Maintains script execution order while enabling parallel resource loading
- Remove duplicate defer attribute from header.templ line 264
- Add websocket.ts import to main.ts for module registration

This optimization reduces page render blocking and improves perceived load times
by allowing the browser to continue parsing HTML while the main.js bundle loads.
The defer attribute ensures scripts execute in order after HTML parsing completes.

Affected templates include:
- Admin pages: admin, admin_library, admin_settings, admin_users
- Content pages: analytics, bookshelf, collections, conflicts, custom_section
- User pages: dashboard, devices, docs, index, login, profile, progress, queue, register
- System pages: header, unlinked_books
2026-03-12 15:44:15 -04:00
john-okeefe 93710a1e96 refactor(collections): move WebSocket from TypeScript to template with server-side token
- Move WebSocket connection logic from collections.ts to collections.templ template
- Inject JWT token directly into WebSocket URL from server-side User.Token
- Remove createWebSocket import, ws variable, and connectWebSocket() function
- Remove unused CollectionUpdateMessage interface
- Embed complete WebSocket message handling in template script tag

Changes to collections.templ:
- Add inline <script> with WebSocket connection using server-injected token
- Implement collection_updated message handler with toast notifications
- Include 5-second auto-reconnection on disconnect
- Add user activity detection to skip auto-reload when actively typing
- Initialize collectionId and libraryId from data attributes

Changes to collections.ts:
- Remove createWebSocket import
- Remove ws variable declaration
- Remove connectWebSocket() function and CollectionUpdateMessage interface
- Keep all other collection functionality (CRUD operations, modals, search, etc.)

This refactoring eliminates client-side localStorage dependencies for
WebSocket authentication, making the collections page consistent with the
SSR architecture. The token is now injected server-side on every page
load, ensuring WebSocket connections always work when the user is
authenticated via HttpOnly cookie. Auto-reconnection and smart reload
behavior is preserved for optimal UX.
2026-03-12 15:43:39 -04:00
john-okeefe 40c447bd19 refactor(admin): move WebSocket from TypeScript to template with server-side token
- Move WebSocket connection logic from admin.ts to admin.templ template
- Inject JWT token directly into WebSocket URL from server-side User.Token
- Remove createWebSocket import and related functions from admin.ts
- Embed complete WebSocket message handling in template script tag

Changes to admin.templ:
- Add inline <script> with WebSocket connection using server-injected token
- Implement scan_progress, scan_complete, and scan_error message handlers
- Include error handling for WebSocket failures

Changes to admin.ts:
- Remove createWebSocket import
- Remove connectWebSocket(), updateScanProgress(), showScanComplete(), showScanError() functions
- Keep stopScanStatusPolling() for Alpine.js integration
- Preserve all other admin functionality (scan triggering, stats loading, etc.)

This refactoring eliminates client-side localStorage dependencies for
WebSocket authentication, making the admin page consistent with the
SSR architecture. The token is now injected server-side on every page
load, ensuring WebSocket connections always work when the user is
authenticated via HttpOnly cookie.
2026-03-12 15:43:27 -04:00
john-okeefe 9bceef7b41 feat(templates): add JWT token to User struct for server-side WebSocket authentication
- Add Token field to templates.User struct for passing JWT to frontend
- Modify getTemplateUserWithTheme() to extract token from HttpOnly cookie
- Inject server-side token into templates for WebSocket connections

This change enables templates to access the authentication token directly
from the server, allowing WebSocket URLs to be constructed with the token
already included. This eliminates the need for client-side localStorage
token management and provides a more secure SSR-native approach.

The token is extracted from the existing HttpOnly cookie that JWT middleware
validates, ensuring no additional security surface is introduced.
2026-03-12 15:43:15 -04:00
john-okeefe 2e65cd7ff5 fix(templates): correct file paths in generated template error reporting
- Update FileName paths in error reporting from relative to absolute paths
- Changes template error paths from 'filename.templ' to 'templates/filename.templ'
- Affects 21 auto-generated Go template files compiled from .templ sources

This fix ensures that template rendering errors provide accurate file locations,
making debugging and error tracking more reliable. The generated files now
correctly reference their source template locations with full path information.

Note: These files are auto-generated by the templ compiler from the .templ
source files committed in the previous commit.
2026-03-12 09:04:26 -04:00
john-okeefe 96e206b671 perf(templates): add defer attribute to main.js script tags
- Add defer attribute to all main.js script includes across 22 template files
- Improves page load performance by allowing HTML parsing to continue without blocking
- Maintains script execution order while enabling parallel resource loading

This optimization reduces page render blocking and improves perceived load times
across all admin and user-facing pages that include the main.js bundle.

Affected pages include: admin dashboard, library management, settings, user
management, analytics, bookshelf, collections, conflicts, custom sections,
devices, documentation, profile, progress tracking, queue, and authentication
pages (login/register).
2026-03-12 09:04:13 -04:00
john-okeefe af337c88e2 build: regenerate templ files and compiled CSS
Generated files updated to reflect:
- New admin_settings.templ template
- Updated admin_sidebar.templ with Settings link
- Fixed devices.templ with correct regenerate-token path
- Compiled TailwindCSS with any style changes
2026-03-11 16:42:56 -04:00
john-okeefe d6b702e35a device: add copyToClipboard and fix regenerate token HTMX button
Phase 3: Complete device page functionality fixes

- Add copyToClipboard function to device-management.ts:
  - Uses navigator.clipboard.writeText() for copying
  - Shows success/error toast notifications
  - Already exported in Alpine.store, now properly defined
- Fix typo in devices.templ: change 'regerate-token' to 'regenerate-token'
- Add HTMX support to RegenerateDeviceToken handler:
  - Returns HTML with reload script for HTMX requests
  - Preserves JSON response for API calls

The regenerate token button now works via HTMX (hx-put) instead of
Alpine.js, matching the pattern used elsewhere in the app.
2026-03-11 16:42:34 -04:00
john-okeefe c1b664dbe5 frontend: add admin settings page for base URL configuration
Phase 2: Create admin UI for system configuration

- Add new /admin/settings route in frontend.go (protected by AdminMiddleware)
- Create admin_settings.templ with HTMX-powered form for base URL
- Add Settings link to admin sidebar navigation
- Admin settings form submits via HTMX to PUT /api/system/config
- Success message displays after save with updated form

The settings page allows admins to configure the base URL used for
device sync URLs, OPDS endpoints, and API access.
2026-03-11 16:41:45 -04:00
john-okeefe 2214288d9c style: Standardize template formatting and indentation
Applies consistent code formatting to template source files:

Changes:
- Fixed inconsistent indentation in admin_library.templ (tabs vs spaces)
- Standardized whitespace alignment across multiple template files
- Ensured generated Go files match reformatted sources

Templates Updated:
- admin_library.templ: Indentation fixes for AdminSidebar component
- analytics.templ: Whitespace normalization
- collections.templ: Whitespace normalization
- conflicts.templ: Whitespace normalization
- dashboard.templ: Whitespace normalization

Generated Go Files:
- All corresponding *_templ.go files regenerated to match

These are cosmetic formatting changes only - no functional changes.
Alpine.js integration, template logic, and application behavior unchanged.
2026-03-11 11:39:42 -04:00
john-okeefe 5faf562250 Fix Path A: Resolve function mismatches and missing functions
Fixed template function call mismatches:
- conflicts.templ: Renamed showConflictModal→showResolveModal, hideConflictModal→hideResolveModal, handleResolveConflict→handleResolveSubmit
- collections.templ: Fixed case mismatch addSelectedBooks→addbooksToAdd
- queue.templ: Removed dead /static/queue.js reference
- progress.templ: Removed vestigial x-data="progress" attribute

Added missing queue functions:
- showQueueItemModal() - Shows queue item detail modal
- hideQueueItemModal() - Hides queue item modal
- filterQueue() - Filters queue by status (stubbed)

Fixed build errors:
- main.ts: Removed imports for deleted themeDropdown.ts and woodPaneling.ts files

All fixes are minimal and conservative. No breaking changes to existing functionality.
TypeScript builds successfully (168KB main.js).
Templates regenerate successfully.
2026-03-11 11:04:53 -04:00
john-okeefe 083f152b35 Safety backup before Path A fixes - fixing function mismatches and missing functions 2026-03-11 11:01:43 -04:00
john-okeefe 647644fdce refactor: Consolidate script imports and update Alpine.js syntax in templates
This commit updates all generated template files and the header.templ source
to use consolidated script bundles and modern Alpine.js syntax.

Changes to templates/*.go (generated from .templ source files):
- Replace individual script imports (admin.js, toast.js, header.js, etc.)
  with single /static/main.js bundle
- Convert onclick attributes to Alpine.js @click directives for better
  integration with reactive components
- Add x-data attributes to body elements where needed for Alpine components
- Update event handlers to use Alpine.js syntax consistently

Changes to templates/header.templ source file:
- Add Alpine.js test div with x-data and x-init for debugging
- Reformat theme dropdown and user menu markup with proper indentation
- Maintain consistent Alpine.js directive formatting throughout

This change reduces the number of HTTP requests and ensures consistent
Alpine.js integration across all pages.
2026-03-09 20:58:46 -04:00