Commit Graph
302 Commits
Author SHA1 Message Date
john-okeefe e04eb90d53 fix: Rename duplicate extractFB2Metadata in fb2-parser
Fix infinite recursion bug where exported function called itself.
Renamed to getFB2Metadata to avoid duplicate declaration.
2026-04-04 14:22:34 -04:00
john-okeefe c1477659c1 fix: Remove duplicate functions in epub-parsers.ts
Clean up duplicate implementations of resolvePath and
calculateTotalCharacters that were causing syntax errors.
2026-04-04 14:22:16 -04:00
john-okeefe 3e48989e83 fix: Rename duplicate extractHTMLMetadata in html-parser
Fix infinite recursion bug where exported function called itself.
Renamed to getHTMLMetadata to avoid duplicate declaration.
2026-04-04 14:21:54 -04:00
john-okeefe bf455dbf97 fix: Rename duplicate extractTXTMetadata in txt-parser
Fix infinite recursion bug where exported function called itself.
Renamed to getTXTMetadata to avoid duplicate declaration.
2026-04-04 14:21:35 -04:00
john-okeefe 7eee7c8e39 fix: Rename image-parter.ts to image-parser.ts
Reader shell expects image-parser module. Renamed file to match
import expectations.
2026-04-04 14:21:04 -04:00
john-okeefe bb586984d1 feat: Add missing feature files for build
Create panel-dock-system.ts and navigator-panel.ts in features/
directory with proper init() implementations. Copy offline-manager
and reading-speed-tracker to features/ as well.
2026-04-04 14:20:48 -04:00
john-okeefe 6649d0526d fix: Store context at module level in gestures.ts
Fix bug where context parameter was unused in init() but referenced
throughout the file. Now properly stored as module-level variable
so all gesture handler functions can access it.
2026-04-04 14:16:24 -04:00
john-okeefe 4b2b7ee66a feat: Add ebook view modes with Feature Registration Pattern
Convert view-modes.ts to init(context) pattern with:
- Paginated mode with pagination controls
- Scrolled mode for continuous reading
- Single and double column layouts
- Event-based navigation and mode switching
2026-04-04 13:48:47 -04:00
john-okeefe d03ac20f66 refactor: Convert all reader features to Feature Registration Pattern
Complete the Feature Registration Pattern refactoring across all reader
modules. Each feature now exports an init(context) function and uses the
event-based architecture for loose coupling.

## Comic Features (6 files)
- background-color.ts: Background color picker with toggle
- chapter-markers.ts: Visual chapter indicators
- page-cache.ts: 5-page ahead prefetch with cleanup
- page-order.ts: Auto-detect Japanese vs Western order
- page-scrubber.ts: Quick navigation slider
- panel-gap.ts: Adjustable panel gap controls

## Ebook Features (6 files)
- copy-handler.ts: Text copying with citation
- dictionary-popup.ts: Word lookup integration
- font-loader.ts: 8 bundled libre fonts
- search.ts: Full-text search across spine
- typography-engine.ts: Font rendering and hyphenation

## Manga Features (4 files)
- reading-direction.ts: RTL/LTR/vertical detection
- rtl-navigator.ts: Reversed page turn direction
- settings.ts: Webtoon mode and transitions
- vertical-scroll-mode.ts: Infinite scroll with lazy loading

## PDF Features (3 files)
- pdf-navigation.ts: Page turning, zoom, fit modes
- pdf-text-selection.ts: Highlight creation via backend
- annotation-layer.ts: Render highlights and notes

## Root-Level Features (3 files)
- offline-manager.ts: PWA service worker and sync
- reading-speed-tracker.ts: Pages/words per minute tracking
- settings-manager.ts: Per-user settings with localStorage fallback

## Core Infrastructure (1 file)
- parser-manager.ts: Fixed import paths for all parsers

## Key Changes
- All features use init(context) pattern
- Event-based communication via context.events.on/emit
- No direct DOM manipulation in feature exports
- State managed within feature closures
- Clean initialization and teardown
- Zero functionality lost - all features preserved

Total: 23 files converted to unified architecture
2026-04-04 13:48:18 -04:00
john-okeefe c3cf4717db refactor(reader): remove old callback-based feature files
- Delete gestures.ts: replaced by features/gestures.ts
- Delete keyboard-shortcuts.ts: replaced by features/keyboard-shortcuts.ts
- Delete progress-indicator.ts: replaced by features/progress-indicator.ts
- Old files used callback pattern, new files use init(context) pattern
2026-04-04 13:38:28 -04:00
john-okeefe 13347a9eed feat(reader): add reader module to main bundle
- Import reader/reader-shell to load reader functionality
- Ensures reader JavaScript is included in main.js bundle
- Reader features and Alpine components are now initialized
2026-04-04 13:38:19 -04:00
john-okeefe 08564ed8b1 refactor(reader): implement unified reader shell with feature orchestration
- Replace callback-based architecture with Feature Registration Pattern
- Add feature registry for modular feature initialization
- Create createNavigationAPI() for unified page/chapter navigation
- Implement renderCurrentPage() for PDF/comic/manga rendering
- Add updateProgress() to sync reading progress to backend
- Support ebook, PDF, comic, and manga readers in single interface
- Remove 600+ lines of callback wiring code
- reader-shell now acts as lightweight orchestrator
- Delegates specific functionality to feature modules
- Parse ebooks using parser-manager with format routing
- Initialize readers based on library type from metadata
2026-04-04 13:38:14 -04:00
john-okeefe f51749243a feat(reader): add PDF reader implementation
- Update pdfjs-wrapper.ts: add initializePDFReader() function
- Configure PDF.js worker, standard fonts, and cmaps
- Load PDF documents and extract metadata
- Implement page rendering with canvas
- Support PDF page navigation and zoom
- Return PDFReader with current page tracking
- Integrates with feature-based reader architecture
2026-04-04 13:38:09 -04:00
john-okeefe 6e41e5c33f feat(reader): add ebook reader features
- Update copy-handler.ts: export enableContextMenuCopy for text selection
- Update typography-engine.ts: export applyTypography and getFontStack
- Update view-modes.ts: export setViewMode and getCurrentViewMode
- Enable text copying with citation for ebooks
- Add typography engine with font loading and text formatting
- Add view mode support (paginated, scrolled, single/dual column)
- These features integrate with settings-manager for customization
2026-04-04 13:38:06 -04:00
john-okeefe 3c9a661941 feat(reader): implement comic and manga reader support
- Add image-parser.ts: CBZ archive parser for comics/manga
- Add image-parter.ts: image splitting utility for comic pages
- Update panel-detector.ts: export Panel interface
- Update panel-editor.ts: export functions for panel editing
- Implement initializeComicReader() and initializeMangaReader()
- Support page navigation for image-based readers
- Parse comic archives using JSZip with natural sort order
2026-04-04 13:38:01 -04:00
john-okeefe 6c876c3e19 feat(reader): add reader API integration
- Add getReaderMetadata() to fetch media item metadata
- Add updateReadingProgress() to sync reading progress to backend
- Export ReaderMetadata and ReadingProgress types
- Enable reader to communicate with Go backend
2026-04-04 13:37:56 -04:00
john-okeefe e076b0d0b2 feat(reader): implement gestures and keyboard-shortcuts as feature modules
- Move gestures.ts to features/ with init(context) pattern
- Move keyboard-shortcuts.ts to features/ with init(context) pattern
- Remove callback-based architecture
- Features now subscribe to events via ReaderContext
- Support touch gestures (swipe, tap, double-tap, pinch-to-zoom)
- Support keyboard shortcuts (navigation, zoom, fullscreen, bookmarks)
- Add panel-aware navigation for comics/manga
- Keyboard shortcuts include chapter navigation
2026-04-04 13:37:52 -04:00
john-okeefe 96d90db036 feat(reader): add core infrastructure for feature-based architecture
- Add reader-context.ts: defines ReaderContext interface and factory
- Add reader-events.ts: event bus for feature communication
- Add reader-state.ts: centralized state management
- Add reader-navigation.ts: unified navigation and rendering API
- Add reader-services.ts: shared services (progress, chapters)
- Establishes foundation for Feature Registration Pattern
2026-04-04 13:37:47 -04:00
john-okeefe 0c199963b9 Add universal reader features: gestures, keyboard, navigator, offline
- gestures.ts: Touch gesture controls (swipe, pinch, tap, double-tap)
- keyboard-shortcuts.ts: Keyboard navigation (arrows, space, page up/down, etc)
- navigator-panel.ts: Affinity-style minimap for page navigation
- offline-manager.ts: Service worker registration and online status
- reading-speed-tracker.ts: Track and sync reading speed to database
- Add web manifest.json for PWA support
2026-04-04 01:01:08 -04:00
john-okeefe 2046960563 Add ebook reader features: view modes, dictionary, copy handler
- view-modes.ts: Paginated, scrolled, single-column, double-column modes
- dictionary-popup.ts: Dictionary lookup with popup definitions
- copy-handler.ts: Copy text with automatic citation formatting
- Add en-US.json dictionary for offline word lookups
2026-04-04 01:01:01 -04:00
john-okeefe c94679ec1e Add comprehensive PDF reader enhancements
- pdf-page-sizes.ts: Dynamic page sizing and layout detection
- pdf-rotation.ts: Page rotation with orientation detection
- pdf-minimap.ts: Thumbnail minimap for navigation
- pdf-dual-page.ts: Dual page spread support
- pdf-links.ts: Clickable link handling
- pdf-clipbooard.ts: Copy to clipboard functionality
- pdf-bookmarks.ts: Bookmark management
- pdf-outline.ts: Document outline/TOC integration
- pdf-text-selection.ts: Text selection and highlighting
- page-cache.ts: PDF page caching system
- pdf-search.ts: Full-text search within PDF
- pdf-navigation.ts: Navigation controls and history
- annotation-layer.ts: PDF annotation rendering
- text-layer-renderer.ts: Text layer overlay rendering
- pdfjs-wrapper.ts: PDF.js wrapper with utilities
2026-04-04 01:00:49 -04:00
john-okeefe d60469a757 Add manga reading support with RTL and vertical scroll modes
- settings.ts: Manga reading settings and configuration
- reading-direction.ts: Right-to-left reading direction support
- vertical-scroll-mode.ts: Webtoon/vertical scroll reading mode
- rtl-navigator.ts: RTL navigation for manga
2026-04-04 01:00:45 -04:00
john-okeefe c36d8e6b57 Add comic panel detection system with multi-tier fallback
- panel-detection.service.ts: Main orchestration with OpenCV → ML → Grid fallback chain
- panel-detection.opencv.ts: Edge detection using OpenCV for 80% of comics
- panel-detection.ml.ts: COCO-SSD object detection for irregular layouts
- panel-detector.ts: Unified detector interface
- panel-editor.ts: Manual panel editor UI for user corrections
- panel-ml-detector.ts: TensorFlow.js integration for ML detection
- page-cache.ts: Efficient page caching for large comics
- background-color.ts: Auto-detect comic background color
- chapter-markers.ts: Chapter detection and navigation
- page-scrubber.ts: Fast page scrubbing/thumbnails
- page-order.ts: RTL/LTR page ordering support
- panel-gap.ts: Panel gap detection
2026-04-04 01:00:43 -04:00
john-okeefe 2eddc1b92b Add font loader and update reader template for font loading
- Add font-loader.ts for managing 8 reading fonts with preload optimization
- Include fonts: Literata, Crimson Text, Source Serif 4, EB Garamond,
  Libertinus Serif, Noto Serif, Charis SIL, IBM Plex Serif
- Update reader.templ to include reader-fonts.css stylesheet
2026-04-04 01:00:23 -04:00
john-okeefe d52937e578 Update font files with simplified naming and add reader font CSS
- Rename font files from VariableFont_wght/opsz,wght to Variable format
- Add reader-fonts.css with @font-face definitions for 10 serif fonts
- Fonts: Crimson Pro, EB Garamond, Literata, Noto Serif, Source Serif 4
- Each font includes regular and italic variants
2026-04-04 01:00:13 -04:00
john-okeefe a387efad22 Add reading fonts with variable font support for premium typography
Add 8 libre font families optimized for extended reading:

Variable fonts (continuous weight range):
- Literata: Modern book typeface for Google Books
- Crimson Pro: Screen-optimized serif
- Source Serif 4: Adobe's professional serif with optical size axis
- EB Garamond: Classic elegance with smooth italics

Static fonts (multiple weights):
- Libertinus Serif: Academic/technical with excellent math support
- Noto Serif: Maximum language coverage
- Charis SIL: Multilingual specialist with extensive Latin support
- IBM Plex Serif: IBM's corporate serif family

All fonts provided in WOFF2 format for optimal compression. Variable fonts
offer continuous weight ranges (200-900) while static fonts provide specific
weights for predictable rendering. Total footprint: ~7.5MB.

These fonts provide excellent readability for extended reading sessions
across all supported languages and scripts.
2026-04-03 22:29:54 -04:00
john-okeefe d8bb5ff68a Implement client-side ebook parsers for EPUB, FB2, TXT, and HTML formats
- epub-parser.ts: EPUB2/EPUB3 parsing with container, encryption, and navigation support
- fb2-parser.ts: FictionBook 2.0/XML parser with metadata and TOC extraction
- txt-parser.ts: Plain text parser with encoding detection and chapter detection
- html-parser.ts: HTML document parser with metadata and structure extraction

All parsers convert their respective formats to the Common Intermediate Format (CIF)
for universal handling. Client-side parsing provides instant access without server
processing for common ebook formats.

Phase 1 focuses on these client-side parsers. Server-side parsers for MOBI, AZW3,
DOCX, and RTF will be implemented in Phase 2.5.
2026-04-03 22:29:33 -04:00
john-okeefe 1e47b0e459 Implement ebook reader with HTML rendering, typography engine, and search
- html-renderer.ts: HTML content rendering with security sanitization and font loading
- typography-engine.ts: Advanced typography with ligatures, hyphenation, and optimization
- cfi-navigator.ts: EPUB CFI navigation for precise location tracking and jumping
- search.ts: Full-text search with highlighting across ebook content

The ebook reader provides a premium reading experience with:
- Clean HTML rendering with XSS protection
- Publisher-quality typography with custom fonts
- Precise CFI-based navigation for EPUBs
- Fast full-text search with result highlighting

This handles EPUB, FB2, TXT, and HTML ebook formats client-side.
2026-04-03 22:29:25 -04:00
john-okeefe d3d84a8318 Implement core reader TypeScript modules for shell and UI management
- reader-shell.ts: Main initialization, Alpine.js integration, media type detection
- progress-indicator.ts: Reading progress tracking and display components
- settings-manager.ts: User settings persistence and retrieval
- panel-dock-system.ts: Dockable panel management with drag/drop and collapse
- parser-manager.ts: Parser selection and format detection system

These core modules provide the foundation for all reader types with
shared functionality for progress tracking, settings management, and
the flexible panel docking system.
2026-04-03 22:29:20 -04:00
john-okeefe 0191d36dc9 Add TypeScript type definitions for reader feature
Define comprehensive TypeScript interfaces for reader functionality:
- Common Intermediate Format (CIF) for universal ebook representation
- Parser capabilities and format detection types
- Reader metadata and chapter structure interfaces
- Panel layout and dock system types
- Reading progress and settings interfaces
- Dictionary and search functionality types

These types provide the foundation for type-safe reader implementation
across all media types (ebook, comic, manga, pdf).
2026-04-03 22:29:11 -04:00
john-okeefe 55ae91a147 api: add comic metadata fields to API responses
Update API layer to expose all 14 new comic metadata fields in media item
responses for both list and detail endpoints.

Handler Changes (internal/handlers/media.go):
- Added encoding/json import for JSON unmarshaling
- Updated ListMediaItems() to include 15 new fields in JSON response
- Updated GetMediaItem() to include 15 new fields in JSON response
- Added numericToFloat() helper: Convert pgtype.Numeric to float64
- Added jsonBytesToMap() helper: Convert JSONB []byte to map[string]interface{}
- Field name mapping: WebUrl (not WebURL), proper pgtype handling

TypeScript Types (web/src/types/api.d.ts):
- Updated MediaItemSummary interface with 15 new optional fields
- manga_type: 'unknown' | 'no' | 'yes' | 'yes_and_right_to_left'
- reading_direction: 'auto' | 'ltr' | 'rtl' | 'vertical'
- Universal fields: series_count, volume, imprint, age_rating, web_url, metadata_notes, community_rating
- Comic-specific: story_arc, is_black_and_white, alternate_info (nested type), scan_information, summary
- Proper TypeScript typing with string literals for type safety

API Response Fields Added:
Reading Direction:
- manga_type: Raw Manga field from ComicInfo.xml
- reading_direction: Computed direction (auto/ltr/rtl/vertical)

Universal Metadata (all formats):
- series_count, volume, imprint, age_rating, web_url, metadata_notes, community_rating

Comic-Specific:
- story_arc, is_black_and_white, alternate_info (JSONB object), scan_information, summary

Part of Phase 5: API Layer Updates
Implementation: IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md
2026-03-29 19:12:05 -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 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 d9bb0834cd feat: add theme-aware tristate button styles with dynamic state rendering
CSS changes for bookshelf page:

Tristate button styles (input.css):
- Add .tristate-btn base class with transition effects
- Three state-specific classes with dynamic colors:
  - .state-null (Any): Neutral style with --text-secondary
  - .state-true (Has Cover): Green success style using color-mix()
  - .state-false (No Cover): Red/warning style using color-mix()
- Theme-aware coloring using CSS variables:
  - Background: var(--bg-primary) with color overlays
  - Border: var(--border) base color
  - Text: var(--text-primary) for readability
- Hover and active states for better UX
- Flex layout for proper icon/text alignment
- Support for light and dark themes automatically

Style.css update:
- Minor adjustment for compatibility

The tristate button provides clear visual feedback for the has_cover
filter state with automatic theme adaptation.
2026-03-28 00:46:18 -04:00
john-okeefe 486214d8a5 fix: refactor filter loading and clearing to prevent stale field data
Major refactoring of bookshelf filter logic:

Filter loading improvements:
- Add clearFormWithoutSubmit() helper to reset form without submission
- Refactor clearFilters() to reuse clearFormWithoutSubmit() helper
  Reduces code duplication from 38 lines to 8 lines
- Update loadFilter() to call clearFormWithoutSubmit() before populating
  This ensures all stale data from previous filter is cleared
- Move has_cover handling before empty value check
  Fixes issue where has_cover=false was being skipped
- Remove automatic HTMX trigger note from cycleHasCover()

Fixed issues:
- Author field staying populated when switching to filter without author
- has_cover tristate button not updating when switching between filters
- has_cover button not updating from "Has Cover" to "Any" when loading filter without has_cover
- General stale data retention when loading different saved filters

HTMX event handling:
- Add event listener in initBookshelf() for htmx:afterSwap events
- Listens on #saved-filters-list element (the swap target)
- Calls afterFilterSave() to close modal and show success toast
- Properly handles Alpine component state access

All filter operations now work correctly with proper state management
and no visual artifacts from previous filters.
2026-03-28 00:46:14 -04:00
john-okeefe 01b1f0de79 fix: improve Clear Filters button functionality
Updated the clearFilters() function to properly reset the filter form
and trigger form submission.

Changes:
- Use form.reset() instead of manually clearing each input for
  cleaner, more reliable form reset
- Manually reset pagination hidden inputs (limit=50, offset=0) after
  form.reset() to ensure pagination state is properly cleared
- Changed HTMX trigger from "change" to "submit" to match the new
  visible form structure
- Simplified loadFilter function to not clear the form before
  populating, just update existing field values

The previous implementation was manually iterating through all inputs
and resetting them one by one, which was error-prone and didn't
properly handle the pagination state. The new implementation uses
the browser's native form.reset() for reliable form clearing.

This fix ensures that clicking "Clear" properly resets all filters
and pagination, allowing users to start fresh with their search.
2026-03-27 18:08:09 -04:00
john-okeefe 4ea110cfeb refactor: replace genre with tags in frontend TypeScript
- Add fetchTagValues() function in bookshelf.ts
- Update custom-section-builder field id from "genre" to "tags"
- Genre code preserved as comments for easy restoration if needed
- collection-rules.ts already supports both genre and tags

Updates the frontend TypeScript to use tags instead of genre for filtering.
Genre code is preserved in comments for future use if the genre field
is populated.

Relates to IMPLEMENTATION_TAGS_FILTER.md Phase 4
2026-03-25 20:38:21 -04:00
john-okeefe d3783eca8b feat: add autocomplete dropdown support for filter fields
- Add fetchFieldValues helper function for API calls
- Add fetchAuthorValues for author autocomplete
- Add fetchGenreValues for genre autocomplete
- Add fetchSeriesValues for series autocomplete
- Add fetchLanguageValues for language autocomplete
- Functions use native DOM manipulation to populate datalist elements
- No Alpine.js reactive state (simple pattern, not reactive)
- Functions registered as methods in Alpine.data("bookshelf") component
- Triggers on input with 300ms debounce after 2 characters minimum
- Updates include count in option text (e.g., "Asimov, Isaac (47)")

Uses /api/media-items/search with field-specific params (author=value, genre=value, etc.).
2026-03-23 22:38:03 -04:00
john-okeefe 0cfd0bad52 feat: implement saved filter loading via API endpoint
Implement Phase 8 of GET_SAVED_FILTER_BY_ID_IMPLEMENTATION.md:
Frontend integration for loading saved filters via GET /:id endpoint.
Completes the saved filters feature with full CRUD functionality.

Changes to web/src/bookshelf.ts:
- Convert loadFilter() from synchronous to async function
- Fetch filter details from GET /api/saved-filters/:id endpoint
- Parse filters JSON (handles both string and object formats)
- Populate hidden #filter-form fields with filter values
- Update visible form fields for user feedback
- Trigger HTMX change event to apply filter
- Show loading, success, and error toasts
- Close filters dropdown after applying filter
- Proper error handling (404, network errors, auth errors)

User Flow:
1. User clicks saved filter in dropdown (server-rendered list)
2. Alpine.js calls GET /api/saved-filters/:id API
3. Receives filter object with filters JSONB
4. Populates form fields (hidden + visible)
5. Triggers HTMX to submit form
6. Books grid updates instantly (no page reload)

SSR-First Compliance:
-  Initial page load: Server renders everything (no API calls)
-  User interaction only: API called when user clicks filter
-  Hybrid approach: Alpine fetches data, HTMX applies it
-  No async x-init data fetching
-  Progressive enhancement maintained
-  Matches dashboard pattern for interactions

Error Handling:
- 404: Filter not found (deleted by another session)
- 401: Not authenticated
- Network errors: Show error toast
- Form not found: Show error toast

Benefits:
- Complete saved filters CRUD functionality
- Instant filter application (no page reload)
- User feedback with toast notifications
- Follows HTMX + Alpine hybrid pattern
- Type-safe TypeScript with proper error handling

Implements Phase 8 from GET_SAVED_FILTER_BY_ID_IMPLEMENTATION.md.
2026-03-21 23:04:05 -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 535097cefb style(web): fix async/await formatting in library.ts
Minor code formatting improvements to improve readability and
consistency with TypeScript best practices.

Changes (web/src/library.ts):

1. Fix async/await formatting (line 46-48):
   Before: const result = handleResponse(response) as unknown as LibrariesResponse;
   After:  const result = (await handleResponse(response)) as unknown as LibrariesResponse;

   Properly wraps the async handleResponse call in parentheses before
   the type assertion, making the await precedence explicit.

2. Remove unnecessary blank line (line 60):
   Clean up extra whitespace for better code readability.

These are pure formatting changes with no functional impact.
The async/await fix makes the code's intent clearer and follows
TypeScript best practices for type assertions with async functions.

TypeScript: Type assertions with async functions
2026-03-21 01:24:34 -04:00
john-okeefe ea2463fe44 fix(frontend): remove jarring forced reload on dashboard navigation
Remove unnecessary full page reload that occurred on dashboard load
when localStorage library preference didn't match URL parameter.

Changes (web/src/dashboard.ts):

1. Remove forced reload logic (lines 514-520, deleted):
   - Deleted: window.location.href redirect on library mismatch
   - Removed: localStorage.getItem("selectedLibrary") check
   - Removed: URL parameter comparison logic

2. Fix localStorage key inconsistency (line 158):
   - Changed: "selectedLibraryId" → "selectedLibrary"
   - Now matches: switchLibrary() function (line 230)
   - Now matches: storage.ts utility (getSelectedLibrary/setSelectedLibrary)
   - Ensures consistency across entire application

User Experience Impact:

Before:
- Dashboard loads → Checks localStorage vs URL → Forces reload if mismatch 
- User switches library → switchLibrary() runs smoothly → But next interaction triggers reload 
- Jarring full page reload disrupts UX 

After:
- Dashboard loads → SSR provides fresh data (no reload) 
- User switches library → switchLibrary() fetches fresh data with smooth fade animation 
- No forced reloads → Smooth, seamless navigation 

Technical Details:

The removed code was attempting to restore the user's last-selected library
when returning to the dashboard. However, this was redundant because:

1. SSR already provides fresh dashboard data on navigation
2. switchLibrary() function already fetches fresh data via API
3. Library select has change event listener that calls switchLibrary()
4. Forced reload happened BEFORE smooth switching could work

The reload logic was added to preserve library selection across sessions,
but it caused more UX problems than it solved. Users now get smooth
navigation while still maintaining library selection via the dropdown.

Browser Testing:
- Navigate to /dashboard → Smooth load
- Switch library dropdown → Smooth fade transition
- Navigate away and back → No forced reload
- No console errors

Related: Dashboard navigation smoothness
User Impact: Eliminates jarring full page reloads
2026-03-21 01:24:21 -04:00
john-okeefe d1625fe231 feat(frontend): update bookshelf to use saved filters API
Update bookshelf page to use new generic saved filters endpoint
for persisting and loading user filter presets.

API Endpoint Changes:
- loadSavedFilters(): Use /api/saved-filters?resource_type=media-items
  (OLD: /api/bookshelf/filters - removed endpoint)
- saveFilter(): Include resource_type: "media-items" in request body

Filter Persistence:
- Filters saved to backend instead of localStorage only
- Supports multiple resource types (extensible design)
- Maintains existing Alpine.js store integration
- Automatic reload after saving filters

User Experience:
- No breaking changes to UI
- Same save/load workflow for users
- Better data persistence (server-side storage)
- Cross-device filter sync (future enhancement)

Error Handling:
- Toast notifications for save success/failure
- Proper error logging to console
- Graceful handling of missing authentication

Migration:
- Fully backward compatible with existing UI
- No changes to HTML template needed
- Alpine store remains unchanged

Part of: Saved Filters Implementation (Phase 3: Frontend)
Related: #saved-filters-feature
2026-03-21 00:16:14 -04:00
john-okeefe 86444ec7ae refactor: remove duplicate HTMX type declaration
Delete web/src/types/htmx.d.ts - HTMX is already declared in web/src/alpine.ts.
Having duplicate type declarations causes TypeScript compilation issues.

The Window interface extension in alpine.ts:
```typescript
declare global {
  interface Window {
    htmx: any;
  }
}
```

This is the canonical location for HTMX types. Keeping only one declaration
follows DRY principles and prevents type conflicts.
2026-03-20 22:58:21 -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 3c85a59c9e chore: rebuild Tailwind CSS with latest changes
Regenerate style.css with tailwindcss build process.
Includes updated utility classes for bookshelf and collections UI.
2026-03-20 11:51:11 -04:00
john-okeefe 17fd86aad3 refactor: update main.ts imports for page-specific bookshelf loading
Change from global to page-specific JavaScript loading:

Remove:
- import "./bookshelf" (loaded globally on every page)

Add:
- import "./bookPicker" (needed globally for collections)

This change supports page-specific script loading strategy:
- Bookshelf: Loaded via <script> tag in bookshelf.templ only
- BookPicker: Loaded globally for collections page usage

Reduces JavaScript bundle size for pages that don't need bookshelf.
Matches SSR-first principle of progressive enhancement.
2026-03-20 11:51:11 -04:00