Commit Graph
100 Commits
Author SHA1 Message Date
john-okeefe 169a8e7143 Add ML dependencies for panel detection
- Add @techstark/opencv-js for OpenCV-based edge detection
- Add @tensorflow/tfjs for ML model inference
- Add @tensorflow-models/coco-ssd for object detection fallback
- All packages are lazy-loaded to optimize initial load time
2026-04-04 01:00:35 -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 c1bc9dceb5 Add complete manga reader implementation section to plan
Added Section 8: Manga Reader Implementation with 5 subsections:

8.1 RTL Navigator (Right-to-Left Reading)
- Reverse page turn direction for traditional manga
- Proper key bindings for RTL navigation
- Progress tracking adapted for RTL

8.2 Vertical Scroll Mode (Webtoon Style)
- Infinite vertical scroll for webtoons/manhwa
- Lazy loading with threshold-based prefetching
- Scroll position to page number mapping
- Memory-efficient blob URL cleanup

8.3 Reading Direction Detection
- Auto-detection from metadata (manga_type, reading_direction)
- Filename-based heuristics (manga, manhwa, webtoon)
- User preference support with fallback
- Integration with database schema

8.4 Manga Settings Integration
- Reading direction preference (auto/ltr/rtl/vertical)
- Vertical scroll speed control
- RTL page transition effects
- Settings persistence via API

8.5 Manga Page Cache (shared with comics)
- 5-page ahead prefetching
- Memory management with cleanup
- Shared caching strategy for comics and manga

This fills the gap where manga was architecturally planned but had no
implementation details. Database schema, types, and UI already supported
manga - now the implementation is documented.

Plan now has complete coverage for all 4 media types: ebook, comic, manga, pdf
2026-04-03 23:38:46 -04:00
john-okeefe 104a6a1a3d Remove duplicate and incomplete sections from reader implementation plan
Removed 3 duplicate/incomplete sections:
- Section 5.8 (incomplete typography-engine.ts without font support)
- Section 5.12 (duplicate search.ts - kept version in section 5.9)
- Section 5.10.4 (incomplete reader.templ template - kept complete version in section 12.1)

The plan now contains only complete, implementation-ready sections:
- Complete typography-engine.ts with getFontStack() and all 8 bundled fonts
- Complete search.ts matching actual implementation with export functions
- Complete reader.templ SSR template with Alpine.js integration

This resolves confusion about which version to follow and ensures the plan
matches the actual codebase implementation.
2026-04-03 23:13:06 -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 fa2d4c0fd8 Clarify reader implementation plan data flow and SSR responsibilities
- Update Section 1.4 with detailed data flow diagram showing SSR skeleton approach
- Add Section 1.5 documenting SSR vs Client-Side responsibilities
- Clarify that SSR provides HTML shell with initial data, TypeScript handles UI
- Document specific examples of what SSR renders vs what TypeScript manages
- Remove confusing 'embedded JSON' terminology for clearer hybrid approach

These changes make the implementation plan more precise and implementation-ready
by clearly separating SSR (Go template) from client-side (TypeScript) concerns.
2026-04-03 22:29:05 -04:00
john-okeefe e7ce0cbe94 Implement backend reader infrastructure with SSR route and library access control
- Fix function signatures in reader.go (c echo.Context -> c *echo.Context)
- Replace non-existent UserHasLibraryAccess with GetUserVisibleLibraries pattern
- Implement SSR reader route in router/reader.go with proper access control
- Add inline library access checking following existing codebase patterns
- Fix ReadingProgress struct to use LastReadAt instead of CreatedAt/UpdatedAt
- Ensure all reader endpoints use consistent library access validation

This provides the backend foundation for the reader feature with proper
access control and SSR rendering capabilities.
2026-04-03 22:29:01 -04:00
john-okeefe 2e0779bec8 chore: add generated reader_templ.go file
This is the generated Go code from templ for the reader template.
The source template reader.templ generates this file during the build process.
2026-04-03 17:21:50 -04:00
john-okeefe b49c036010 chore: remove generated reader_templ.go file
This file is auto-generated by templ from reader.templ source file.
It should not be tracked in version control as it can be regenerated.
2026-04-03 17:21:19 -04:00
john-okeefe afbe7f7220 feat: add reader.templ source file for templ code generation
- Add templates/reader.templ with Go templ syntax
- Contains Reader component with dockable panels
- Panel components: ReaderChrome, Settings, TOC, Navigator, Bookmarks, DictionaryPopup
2026-04-03 17:20:40 -04:00
john-okeefe d91e5fac3d fix: update reader service and handler
- Add panel_layout to getDefaultSettings() for dockable panels
- Remove template rendering from ShowReader (router handles SSR)
- Fix pgtype.Int4 marshaling to JSON (no explicit int conversion)
- Remove unused strings import from handlers
2026-04-03 17:20:23 -04:00
john-okeefe e8544ac8f4 feat: add UpdateMediaItemChapterMetadata query
- Add SQL query to update chapter metadata in media_items table
- Enables caching of detected chapter structures
2026-04-03 17:20:18 -04:00
john-okeefe bef57f7acd feat: add reader template with dockable panels
- Create templates/reader_templ.go with main Reader() function
- Implement reader shell with top/bottom chrome bars
- Add dockable panels: TOC, Settings (left side)
- Add dockable panels: Navigator, Bookmarks (right side)
- Include panel lock and window-shade toggle buttons
- Support all media types: ebook, comic, manga, pdf
- Initialize reader shell via Alpine.js data attribute
2026-04-03 17:20:16 -04:00
john-okeefe 6dbcc8c8c3 feat: add reader types to templates package
- Add ReaderMetadata struct with media item details
- Add ReadingProgress struct for progress tracking
- Add Bookmark struct for user bookmarks
2026-04-03 17:20:13 -04:00
john-okeefe 5550b730ee docs: update reader implementation plan with dockable panels
- Add modular dockable panel architecture with:
  - Panel dock system (drag, lock, snap-back, window-shade)
  - TOC, Settings, Navigator, Bookmarks as dockable components
  - Lock toggle to prevent accidental moves
  - Snap-back to last valid position if dropped in invalid area
  - Per-user layout stored in reader_settings JSONB

- Update TypeScript types with PanelLayoutSettings and PanelState
- Add panel-dock-system.ts implementation to plan
- Add navigator-panel.ts for Affinity-style page navigation
- Update reader template with dockable panels and lock buttons

- Add Section 2.4 with database/Go implementation issues and fixes
- Document schema changes (DECIMAL -> REAL)
- Document all type fixes needed in reader.go
2026-04-03 16:53:41 -04:00
john-okeefe 74575d9a86 feat: add reader service, handler, and router 2026-04-03 16:53:37 -04:00
john-okeefe dd621956b9 database: change reading_speed columns from DECIMAL to REAL
- Changed words_per_minute, pages_per_minute, total_reading_minutes from DECIMAL(6,2) / DECIMAL(8,2) to REAL
- REAL (pgtype.Float4) is sufficient for reading statistics and simplifies Go code
- No precision loss for typical reading speed values (200-400 wpm, 0.5-3.0 pages/min)
2026-04-03 16:52:53 -04:00
john-okeefe 3228b62ea3 docs: update reader plan - Phase 0 prerequisites section
Update READER_IMPLEMENTATION_PLAN.md to include comprehensive Phase 0
setup and verification guide.

## Phase 0: Prerequisites & Setup (NEW Section)

### 0.1 Database Schema Setup
- References Section 2.1 for SQL (no duplication)
- Step-by-step instructions for schema.sql modifications
- Database recreation vs manual migration options
- sqlc generate commands for Go code regeneration

### 0.2 Database Queries Setup
- 12 new SQL queries for reader functionality
- Complete query definitions with parameter types
- Instructions for regeneration with sqlc

### 0.3 Frontend Dependencies
- jszip@^3.10.1 for EPUB/comic archive parsing
- pdfjs-dist@^3.11.174 for PDF rendering
- npm install instructions

### 0.4 Directory Structure
- mkdir commands for reader components (ebook, comic, manga, pdf)
- Font directory for bundled reading fonts
- Bruno API test directory

### 0.5 Pre-Implementation Checklist
- 10-item verification checklist
- Database, frontend, and structure items

### 0.6 Verification Commands
- Database table existence check
- Query generation verification
- Frontend dependency verification
- Go build verification
- Directory structure verification

### 0.7 Troubleshooting
- Common setup problems and solutions
- sqlc generate failures
- Database table issues
- npm install problems
- Go compilation errors

## Rationale

This prevents developers from starting implementation without
the necessary foundation, which would result in:
- Compilation errors from missing database queries
- Runtime errors from missing database tables
- Frontend build errors from missing dependencies
- Lost time from having to stop and fix prerequisites

## Changes

- Added Phase 0 section (~415 lines)
- Updated database references
- Added verification commands
- Added troubleshooting guide

Total: 1 file changed, 24 insertions(+), 37 deletions(-)

Phase 0 is now complete and ready for implementation to begin.
2026-04-02 21:02:08 -04:00
john-okeefe 0667cad8a9 feat: add reader infrastructure - Phase 0 database schema and queries
Implement Phase 0 prerequisites for reader functionality including
database schema, SQL queries, and frontend dependencies.

## Database Schema (5 New Tables + 1 Column Addition)

### New Tables Added:
1. **panel_data** - Comic/manga panel detection results
   - Stores detected panel boundaries (x, y, width, height)
   - Supports grid, ML, and manual detection methods
   - JSONB storage for flexible panel structures

2. **reading_speed** - User reading speed statistics
   - Tracks pages per minute and total reading time
   - Per-user per-media-item tracking
   - Enables progress estimation and analytics

3. **dictionary_cache** - Offline dictionary word definitions
   - Caches external dictionary lookups
   - Reduces API calls and improves performance
   - Supports offline reading functionality

4. **reader_settings** - User reader preferences (per-user)
   - Stores typography, theme, and display settings
   - JSONB storage for flexible configuration
   - Per-user customization (fonts, margins, themes)

5. **media_bookmarks** - Enhanced bookmarks with chapter/CFI support
   - Unified bookmarking for ebooks, comics, manga, PDFs
   - Supports page_number, chapter_number, and epubcfi_position
   - Includes notes field for annotations
   - Unique constraint on (media_item_id, user_id, title)

### Column Addition:
- **media_items.chapter_metadata** (JSONB) - Caches detected chapter structure
  - Stores TOC/chapter detection results
  - Prevents re-parsing files on every read
  - Populated by ReaderService.DetectChapters()

## Database Queries (12 New Queries)

Added queries for all reader functionality:
- Panel data: GetPanelData, UpsertPanelData
- Reading speed: GetReadingSpeed, CreateReadingSpeed, UpdateReadingSpeed
- Dictionary: GetDictionaryEntry, CreateDictionaryEntry, UpdateDictionaryAccessed
- Settings: GetReaderSettings, UpsertReaderSettings
- Bookmarks: GetMediaBookmarks, CreateMediaBookmark, DeleteMediaBookmark, UpdateMediaBookmark

## Frontend Dependencies

Added to package.json:
- jszip@^3.10.1 - EPUB/comic archive parsing (client-side)
- pdfjs-dist@^3.11.174 - PDF rendering library (Mozilla PDF.js)

## Generated Code

Ran `sqlc generate` to regenerate:
- models.go - Go structs for new tables (55 lines added)
- querier.go - Database query methods (14 lines added)
- queries.sql.go - Compiled SQL queries (504 lines added)

## Implementation Status

Phase 0 prerequisites now complete:
 Database schema (5 tables + 1 column)
 SQL queries (12 queries)
 Frontend dependencies (2 packages)
 Generated Go code (sqlc)
 Database recreated with new schema

Ready for Phase 1: Infrastructure & Basic Reader implementation.

Related to: Universal web reader for ebooks, comics, manga, PDFs
2026-04-02 21:01:45 -04:00
john-okeefe 40f519ce6e docs: add Phase 0 prerequisites section to reader implementation plan
Add comprehensive setup and verification guide to ensure all
dependencies and database schema are in place before beginning
reader implementation.

## Phase 0: Prerequisites & Setup (NEW)

### 0.1 Database Schema Setup
- References Section 2.1 for actual SQL (no duplication)
- Clear step-by-step instructions for schema.sql modifications
- Instructions for both database recreation and manual migration
- sqlc generate commands for Go code regeneration

### 0.2 Database Queries Setup
- 12 new SQL queries for reader functionality:
  * Panel data: GetPanelData, UpsertPanelData
  * Reading speed: GetReadingSpeed, CreateReadingSpeed, UpdateReadingSpeed
  * Dictionary: GetDictionaryEntry, CreateDictionaryEntry, UpdateDictionaryAccessed
  * Settings: GetReaderSettings, UpsertReaderSettings
  * Bookmarks: GetMediaBookmarks, CreateMediaBookmark, DeleteMediaBookmark, UpdateMediaBookmark

### 0.3 Frontend Dependencies
- jszip@^3.10.1 for EPUB/comic archive parsing
- pdfjs-dist@^3.11.174 for PDF rendering
- npm install instructions

### 0.4 Directory Structure
- web/src/reader/{ebook,comic,manga,pdf}/
- web/static/fonts/ for reading fonts
- bruno/reader/ for API tests

### 0.5 Pre-Implementation Checklist
- 10-item verification checklist
- Database schema and queries
- Frontend dependencies
- Directory creation
- Go compilation verification

### 0.6 Verification Commands
- Database table existence check
- Query generation verification
- Frontend dependency verification
- Go build verification
- Directory structure verification

### 0.7 Troubleshooting
- Common setup problems and solutions
- sqlc generate failures
- Database table issues
- npm install problems
- Go compilation errors

## Rationale

This prevents developers from starting implementation without
the necessary foundation, which would result in:
- Compilation errors from missing database queries
- Runtime errors from missing database tables
- Frontend build errors from missing dependencies
- Lost time from having to stop and fix prerequisites

By completing Phase 0 first, implementation proceeds smoothly
with all infrastructure in place.

## File Changes
- READER_IMPLEMENTATION_PLAN.md: +415 lines (Phase 0 section)

Total: 1 file changed, 415 insertions(+)
2026-04-02 20:09:43 -04:00
john-okeefe 925228f7cf docs: comprehensive reader implementation plan updates
Major changes:
- Convert all 22 OOP classes to procedural TypeScript functions
- Add complete PDF reader implementation (15 subsections, 6.1-6.15)
- Add 8 technical textbook features for PDFs (TOC, bookmarks, clipboard,
  dual-page, mini-map, rotation, variable page sizes)
- Remove duplicate sections and fix section numbering (1-19 sequential)
- Add 7 new comic/manga features (7.4-7.10):
  * Page order presets with auto-detect and user override
  * Extended keyboard shortcuts (Space, PageUp/Down, +/-, B, F, 1-9)
  * Page slider/scrubber for quick navigation
  * Touch gesture controls (swipe, pinch-to-zoom, tap, double-tap)
  * Panel gap controls with adjustable spacing and borders
  * Background color options (black/white/gray/sepia)
  * Chapter markers with visual boundaries

Architecture improvements:
- Universal reader + pluggable parser pipeline (all ebooks → CIF)
- Client-side parsers: EPUB, FB2, TXT, HTML
- Server-side parsers: MOBI, AZW3, DOCX, RTF (Go backend)
- ~500 KB total dependencies vs 182 MB Calibre

Database schema updates:
- Rename pdf_bookmarks to media_bookmarks
- Add chapter_number and cfi_position fields for all media types
- Unified bookmarks for ebooks, comics, manga, and PDFs

Preserved features:
- 8 bundled libre reading fonts with complete setup scripts
- 5 reading-optimized themes (light, sepia, dark, night, high-contrast)
- Hybrid theming strategy (11 themes for UI, 5 for ebook text)
- Panel detection (grid-based + ML + manual override)
- All 4 media types: ebook, comic, manga, PDF

All code follows PROJECT_GUIDELINES.md:
- No OOP (no classes, no this-capture)
- Procedural/imperative style
- Backend handles all position calculations (CFI, character offsets, etc.)
- Frontend receives positions from backend, no duplication
2026-04-02 16:58:45 -04:00
john-okeefe 9dd217afb6 docs: Complete Reader Implementation Plan overhaul with PDF support and universal reader architecture
Major Architecture Changes:
- Added PDF support (Mozilla pdf.js) with text selection, highlights, search, bookmarks
- Universal reader with pluggable parser pipeline for all reflowable ebooks
- Common Intermediate Format (CIF) to standardize ebook parsing
- Server-side parsing for complex formats (MOBI, AZW3, DOCX, RTF)
- Client-side parsing for simple formats (EPUB, FB2, TXT, HTML)
- PDF-specific features: TOC navigation, bookmarks, dual-page view, mini-map

Procedural TypeScript:
- Refactored all code to follow PROJECT_GUIDELINES.md (no classes, no OOP)
- Functions and modules instead of classes
- Functional techniques where appropriate

New Components:
- Parser manager (router) to detect format and route to appropriate parser
- CIF types for universal ebook representation
- PDF reader with full feature set (text-layer, annotation, search, etc.)
- Server-side Go handlers for MOBI/AZW3/DOCX/RTF parsing

Database Schema:
- Added pdf_bookmarks table for custom PDF bookmarks

API Routes:
- Added PDF outline/TOC endpoint
- Added PDF thumbnail endpoint for mini-map

Theming:
- Added PDF-specific reading themes (5 options: light, sepia, dark, night, high-contrast)
- Hybrid approach maintained: chrome_theme for UI, reading_theme for content
2026-04-02 16:54:34 -04:00
john-okeefe 9f7dffd292 docs: add comprehensive reader implementation plan
Add detailed 8-week implementation plan for ebook, comic, and manga reader
with hybrid architecture, panel detection, offline support, and libre fonts.

Key Features:
- Hybrid reader architecture: Shared infrastructure + specialized components
- Ebook reader: EPUB parsing, HTML rendering, CFI navigation, typography engine
- Comic reader: Grid-based + ML + manual panel detection, zoom animations
- Manga reader: RTL navigation, vertical scroll mode
- Offline support: PWA with service worker, cached pages
- Dictionary: Offline-capable word lookup
- Annotations: Bookmarks, highlights, notes (reuses existing DB schema)
- Reading statistics: Speed tracking, time estimation
- KOReader-style progress: Pages, chapter-relative, percentage, time-left

Theming Strategy:
- UI Chrome: All 11 Bookhoard themes
- Ebook text: 5 reading-optimized themes (light, sepia, dark, night, high-contrast)
- Comics/manga: All 11 themes (visual content works well with colors)

Typography:
- 8 bundled libre fonts (~1.2MB WOFF2): Literata, Crimson Text,
  Source Serif 4, EB Garamond, Libertinus, Noto Serif, Charis SIL, IBM Plex
- Standard weights only (400, 400i, 700, 700i)
- Font acquisition script included
- Offline-ready (no network requests for fonts)

Database Schema:
- panel_data: Hybrid panel detection (grid/ML/manual)
- reading_speed: Per-user reading speed tracking
- dictionary_cache: Offline dictionary support
- reader_settings: Per-user preferences (DB + localStorage fallback)

API Endpoints:
- 10 new reader routes (/api/readers/*)
- Lazy-loading page cache (5-page ahead)
- WebSocket integration for real-time sync
- Bruno API tests included

Phased Timeline:
- Week 1-2: Infrastructure & basic reader
- Week 3-4: Comic/manga features
- Week 5-6: Advanced features
- Week 7: Offline support
- Week 8: Polish & testing

Code Reuse:
- Reuses existing WebSocket sync, progress tracking, annotations
- Surgical code edits (extend, don't duplicate)
- Follows existing Bookhoard patterns and conventions

Includes:
- Database schema changes (SQL)
- API endpoint specifications
- TypeScript type definitions
- Frontend architecture (SSR-first with Alpine.js)
- Integration test patterns
- Bruno OpenCollection YAML tests
- User and developer documentation locations

Estimated total implementation time: 8 weeks
2026-04-01 21:10:34 -04:00
john-okeefe 84eb52df43 Add markdownlint directive to DOCS_SEARCH_IMPLEMENTATION.md
Add <!-- markdownlint-disable MD013 --> comment at the top to prevent
vim from loading diagnostics for this file, matching the pattern used
in other documentation files.
2026-03-31 21:28:01 -04:00
john-okeefe faba20db28 Remove completed implementation plan documents
- Remove FRONTEND_IMPLEMENTATION_PLAN_COMIC_METADATA.md
- Remove IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md

These planning documents outlined the implementation of comic metadata
fields and reading direction support. Since the features are now
implemented, these planning documents are no longer needed.

The implementation included:
- Database schema for comic/manga metadata (19 ComicInfo.xml fields)
- Reading direction detection (auto, ltr, rtl, vertical)
- Smart metadata merging (Calibre + ComicInfo.xml)
- Universal fields (age_rating, series_count, volume, imprint, web_url)
- Comic-specific fields (story_arc, scan_information, alternate_info, etc.)
- Genre/tag processing for all formats
2026-03-31 21:27:12 -04:00
john-okeefe 7beddb829c chore: remove obsolete ISSUES.md file
Remove the issues tracking file that contained two remaining items:
1. Filter UUID showing in dropdown as text
2. Load Filter functionality not populating fields

These issues appear to have been resolved or are tracked elsewhere.
2026-03-31 20:53:48 -04:00
john-okeefe db4f93af34 test: fix HTML entity encoding assertion in metadata notes test
The test was checking for hexadecimal entity &#x27; but templ actually outputs
the decimal entity &#39; for apostrophes. This commit updates the assertion to
match the actual HTML output from the templ library.
2026-03-31 20:53:46 -04:00
john-okeefe d26174fec0 chore: remove obsolete community rating type change documentation
Remove COMMUNITY_RATING_TYPE_CHANGE.md as this was a planning document
for the community rating field type change (FLOAT4 → FLOAT8) which has
now been completed and integrated into the main implementation.

The community rating field is now:
- Type: DOUBLE PRECISION (FLOAT8) in database schema
- Range: 0.0-10.0 (not 0-5 like user ratings)
- Displayed on frontend with star rating and numeric score
- Properly distinguished from user ratings

This documentation is no longer needed since the feature is complete.
2026-03-31 17:09:43 -04:00
john-okeefe 9b322e854e test: add integration tests for comic metadata display features
Add comprehensive integration tests for all 8 comic metadata display steps
on the book detail page, ensuring frontend rendering works correctly with
real database data.

## Test Coverage

### Step Tests (8 individual tests)
1. Reading Direction Badge - Tests RTL, LTR, vertical, and auto-hide behavior
2. Community Rating Display - Validates star rendering and numeric score
3. Comic-Specific Badges - Tests age rating, B&W, and story arc badges
4. Universal Series Info - Tests series count, volume, and imprint display
5. Comic-Specific Metadata - Tests manga type, scan info, alternate series
6. Summary Section - Tests ComicInfo.xml summary rendering
7. Metadata Notes Section - Tests technical notes display
8. Web URL Link - Tests external link rendering with security attributes

### Test Case Scenarios (4 complete scenarios)
1. Japanese Manga - Complete metadata display (RTL + all badges)
2. Western Comic - LTR direction with story arc
3. Webtoon/Manhwa - Vertical reading direction
4. Regular Ebook - No comic metadata (minimal display)

### Authentication Tests (2 tests)
- Anonymous users are denied access (401)
- Regular users can view metadata (same as admins)

### Edge Case Tests (2 tests)
- Minimal Metadata - Only required fields (no optional metadata)
- All Fields Together - Comprehensive metadata display

## Test Infrastructure

- Uses setupTestServer() helper for isolated test environment
- Uses createComicMediaItem() helper for flexible test data creation
- Uses createLibrary() helper with automatic cleanup
- Tests use pgtype types matching production code
- All tests run with admin authentication by default
- Tests check both structure and content in rendered HTML

## Test Details

- 21 total subtests covering all metadata display features
- Tests verify HTML structure, content presence, and proper escaping
- Uses t.Run() for organized test output
- Tests clean up resources automatically with t.Cleanup()
- Checks for proper HTML entity encoding (e.g., apostrophes)
- Validates conditional rendering (hide when values not set)

## Known Issues

- Metadata Notes content validation uses partial string matching to handle
  HTML escaping variations
- Reading Direction test checks specific direction strings (RTL/LTR/VERTICAL)
  to avoid false positives from emoji appearing elsewhere in the UI
- Community Rating test uses colon ("Community Rating:") to avoid matching
  HTML comments

Related: Template implementation commit (562ca53)
2026-03-31 17:09:38 -04:00
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 3a7c8e18c9 docs: add frontend implementation plan for comic metadata fields
Add comprehensive planning document for displaying new ComicInfo.xml
metadata fields on the book detail page.

New fields to be added:
- Reading direction (RTL/LTR/vertical) badge
- Community rating display (0-10 scale)
- Universal fields: series count, volume, imprint, age rating
- Comic-specific: manga type, story arc, scan info, B&W flag

Implementation approach:
- SSR-first rendering (no client-side fetching)
- TailwindCSS only (no custom CSS)
- Conditional display based on field validity
- Follows existing template patterns

This document provides step-by-step implementation guidance
with code examples and testing scenarios for the frontend team.

Related: IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md
2026-03-30 21:29:08 -04:00
john-okeefe eda79a1f92 Fix dashboard integration test: use title case collection names
Update TestRestoreSystemCollection_ValidNames to use the correct
title case format for system collection names.

The API handler validates these specific collection names:
- "Continue Reading"
- "Recently Added"
- "Recently Read"
- "Not Started"

The test was previously using kebab-case names (e.g., "continue-reading")
which were being rejected by the validation logic with 400 Bad Request.

This aligns the test with the updated collection name format used
throughout the application.
2026-03-30 21:22:44 -04:00
john-okeefe 158b15c1d8 Fix comic metadata tests: UUID handling, test isolation, and defaults
This commit fixes multiple issues in the comic metadata test suite that were causing test failures:

1. UUID Byte-Order Corruption
   - Fixed byte-order corruption when converting library IDs
   - Previously used [16]byte(uuid.MustParse(libraryID)) which corrupted bytes
   - Now parse UUID once and reuse the parsed UUID variable
   - Matches pattern used successfully in calibre_integration_test.go

2. Test Isolation
   - Each sub-test now creates its own isolated library
   - Previously all sub-tests shared one library, causing cross-test pollution
   - ListMediaItemsByLibrary returns items from previous tests
   - New libraries: "RTL Manga Test Library", "Western Comic Test Library", "Minimal Metadata Test Library"

3. Query Function Selection
   - Replaced SearchMediaItems with ListMediaItemsByLibrary
   - SearchMediaItems requires search_pattern parameter which was missing
   - ListMediaItemsByLibrary is simpler and more appropriate for these tests

4. Explicit Default Values
   - MangaType and ReadingDirection now explicitly set to expected defaults
   - Database defaults not applied when pgtype fields have Valid: false
   - "Comic with minimal metadata" test now sets: MangaType="unknown", ReadingDirection="auto"

5. Library Naming for Cleanup
   - All library names now include "Test" for proper cleanup
   - Test cleanup deletes libraries with "test" in name (case-insensitive)
   - Prevents orphaned libraries from accumulating in database

All tests in TestComicMetadataExtraction now pass:
- CBZ with RTL manga ✓
- CBZ with Western comic ✓
- Comic with minimal metadata ✓
2026-03-30 21:22:39 -04:00
john-okeefe 0ff34a683a feat(media_scanner): add genre field to MediaMetadata
Add Genre field to MediaMetadata struct to support genre information
extraction from media files during scanning.
2026-03-30 17:51:08 -04:00
john-okeefe 81c7c9e5cc fix: update type handling for schema changes
- Fix pgtype.UUID usage in test files by properly converting string UUIDs to pgtype.UUID
- Update numericToFloat to use pgtype.Float8 instead of pgtype.Numeric for DOUBLE PRECISION support
- Fix field name from WebURL to WebUrl to match current schema

These changes align with the recent community_rating type change to DOUBLE PRECISION
and ensure consistent type handling across the codebase.
2026-03-30 17:51:06 -04:00
john-okeefe 5860887b26 docs: add community_rating type change documentation
Document rationale for changing community_rating from DECIMAL(3,1) to DOUBLE PRECISION.

Contents:
- Comparison of original DECIMAL choice vs DOUBLE PRECISION benefits
- Code simplicity analysis (complex pgtype.Numeric vs simple pgtype.Float8)
- Migration notes for existing databases (manual ALTER TYPE command)
- Impact analysis (no API or UI changes, only internal Go code)

Provides complete justification for the type change and migration instructions
for anyone reviewing the git history or schema changes.

Relates to: Schema change commit (DOUBLE PRECISION for community_rating)
2026-03-29 21:12:28 -04:00
john-okeefe 008706a7fa docs: update implementation plan with DOUBLE PRECISION and complete integration tests
Phase 6.2 implementation: Add comprehensive integration test code to plan.

Documentation Updates:
- Changed community_rating from DECIMAL(3,1) to DOUBLE PRECISION throughout plan
- Fixed function name references: processNewMediaItems → processMediaFile (correct name)
- Added complete integration test implementation (TestComicMetadataExtraction,
  TestReadingDirectionAPI, TestUniversalMetadataFields, TestComicSpecificFields)
- Replaced skeleton TestMergeMetadata with actual test code using setupTestServer
- Added context-based location markers (3 lines before/after) for easier code navigation
- Removed TODO comment reference (doesn't exist in current code)
- Updated all line number references and added plan update summary
- Verified test helper usage: setupDeviceTest for library creation

Integration Tests Added:
- Full comic metadata field testing (manga_type, reading_direction, series_count, volume,
  imprint, age_rating, community_rating as pgtype.Float8, story_arc, is_black_and_white)
- Reading direction API testing (rtl, ltr, auto filtering)
- Universal field testing for both comic and ebook libraries
- Comic-specific field testing (alternate_info JSONB, scan_information, summary, metadata_notes)

All tests use proper test_helpers pattern with setupDeviceTest and verify DOUBLE PRECISION
storage for CommunityRating field.

Relates to: Phase 6.2 integration testing documentation
2026-03-29 21:12:27 -04:00
john-okeefe fd74415a4a test: add unit tests for comic metadata processing
Phase 6.1 implementation: Unit tests for metadata helper functions.

Test Coverage:
- TestNormalizeMangaType: Verify Manga field normalization to database enum values
  (unknown, no, yes, yes_and_right_to_left)
- TestDetermineReadingDirection: Test reading direction computation heuristics
  (explicit Manga field, Japanese language, webtoon/manhwa genre tags, Western default)
- TestNormalizeAgeRating: Verify age rating standardization
  (Everyone, Teen, Mature, Adult with various input formats)

These tests ensure the helper functions correctly normalize ComicInfo.xml data
before storage in the database.

Relates to: Phase 6.1 unit testing
2026-03-29 21:12:24 -04:00
john-okeefe 831668a07d feat(media_scanner): implement smart metadata merging and ComicInfo.xml v2.0 support
Phase 2-3 implementation: Complete ComicInfo.xml parsing with intelligent Calibre merging.

Data Structure Updates:
- ComicInfo struct: Add 19 ComicInfo.xml v2.0 fields (Manga, LanguageISO, Count,
  AlternateSeries, AlternateNumber, AlternateCount, Summary, Imprint, StoryArc,
  SeriesGroup, AgeRating, CommunityRating, MainCharacterOrTeam, Review,
  BlackAndWhite, ScanInformation, Characters, Teams, Locations)
- MediaMetadata struct: Add 14 fields for reading direction and universal/comic metadata

New Functions:
- mergeMetadata(): Smart merging with priority: Calibre metadata.opf → ComicInfo.xml →
  embedded metadata. Extracts reading direction even when metadata.opf exists.
- normalizeMangaType(): Normalize ComicInfo.xml Manga field to database enum
- determineReadingDirection(): Compute reading direction from Manga + language + genre heuristics
- normalizeAgeRating(): Standardize age rating values (Everyone, Teen, Mature, Adult)
- processGenresAndTags(): Universal genre/tag processing for all formats
- extractGenreTagsFromEPUB(): Extract all <dc:subject> values from EPUB
- extractGenreTagsFromComicInfo(): Extract genres from Genre + Tags + Characters + Teams + Locations
- containsTag(): Helper to prevent duplicate tags

Logic Changes:
- extractMetadata(): Now calls mergeMetadata() for smart metadata combination
- processMediaFile(): Updated CreateMediaItem call with all 14 new fields
- Removed duplicate comic metadata extraction (now handled by mergeMetadata)
- CommunityRating uses simple pgtype.Float8 (DOUBLE PRECISION) instead of pgtype.Numeric

This enables complete ComicInfo.xml v2.0 support with 19 fields plus 5 universal fields
that apply to all media formats (ebooks, audiobooks, comics).

Relates to: Phase 2 (data structures), Phase 3 (smart merging), Phase 4 (media item creation)
2026-03-29 21:12:23 -04:00
john-okeefe dd81dc08a2 db: update SQL queries and regenerate models for comic metadata
Phase 1 implementation: Update CreateMediaItem query to support 14 new comic metadata fields.

Changes:
- Add 14 new columns to CreateMediaItem INSERT statement
- Regenerate sqlc models with new fields
- CommunityRating now maps to pgtype.Float8 (was pgtype.Numeric)
- All new comic and universal metadata fields included

New fields supported:
- Reading direction: manga_type, reading_direction
- Universal: series_count, volume, imprint, age_rating, web_url
- Comic-specific: story_arc, is_black_and_white, metadata_notes,
  community_rating, alternate_info, scan_information, summary

Generated models verified:
- MediaItems struct includes all 14 new fields
- CreateMediaItemParams has correct parameter count (42 total)
- CommunityRating is pgtype.Float8 (not pgtype.Numeric)

Relates to: Phase 4.1-4.2 database layer implementation
2026-03-29 21:12:20 -04:00
john-okeefe 54559a6e91 schema: change community_rating to DOUBLE PRECISION for simpler type mapping
Change community_rating from DECIMAL(3,1) to DOUBLE PRECISION to:
- Eliminate awkward pgtype.Numeric conversion in Go code
- Enable direct pgtype.Float8 mapping from ComicInfo.xml float64
- Simplify code by matching natural types (XML float64 → PostgreSQL DOUBLE PRECISION → Go pgtype.Float8)
- Remove need for string formatting and Scan() method calls

The floating-point precision error (< 0.00001%) is negligible for 0-10 rating scale.
This simplifies Phase 4 implementation significantly.

Column comment updated to reflect DOUBLE PRECISION type.

Relates to: Phase 1 database schema changes for comic metadata support
2026-03-29 21:12:18 -04:00
john-okeefe 76648e1a54 docs: add comprehensive comic metadata implementation plan
Add detailed implementation plan for comic and manga metadata support including
reading direction detection, smart Calibre + ComicInfo.xml merging, and universal
metadata fields that apply to all media formats.

Implementation Plan (IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md):
- 10 phases covering database, data structures, merging logic, API, testing, and documentation
- Phase 1: Database schema (14 new columns, 8 indexes)
- Phase 2: Data structure updates (ComicInfo 18→29 fields, MediaMetadata +15 fields)
- Phase 3: Smart metadata merging (mergeMetadata, 8 helper functions)
- Phase 4: Media item creation (database integration)
- Phase 5: API layer updates (TypeScript types, Go handlers)
- Phase 6-10: Testing, Bruno tests, docs, build, commits
- Genre/tag processing strategy (Option A: reuse existing genre column)
- 5 universal fields apply to all formats: age_rating, series_count, volume, imprint, web_url
- Reading direction: manga_type (raw) + reading_direction (computed)

Key Design Decisions:
- Smart merging: metadata.opf priority + ComicInfo.xml fills gaps
- Genre processing: All <dc:subject> and comic genre tags go to tags array
- Reading direction computed from Manga field + language + genre heuristics
- Supports manga (RTL), webtoons/manhwa (vertical), Western comics (LTR)
- JSONB for alternate_info (flexible schema for alternate series data)

Documentation Deleted:
- BOOK_DETAIL_IMPLEMENTATION.md (superseded by new comprehensive plan)

Plan Status:
- Phases 1-5: COMPLETE
- Phases 6-10: Pending (testing, bruno tests, documentation, build, git commits)

Total: 1,970 lines of detailed implementation guidance with code examples.
2026-03-29 19:12:10 -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 9e3e0ff931 db: regenerate sqlc models with comic metadata fields
Auto-generated by sqlc from updated schema.sql
- MediaItems struct now includes all 14 new comic metadata fields
- CreateMediaItemParams struct updated with new parameters
- All SELECT queries now include new columns in RETURNING clauses
- Properly typed with pgtype.Text, pgtype.Int4, pgtype.Bool, pgtype.Numeric, []byte

New Fields in MediaItems:
- MangaType pgtype.Text
- ReadingDirection pgtype.Text
- SeriesCount pgtype.Int4
- Volume pgtype.Int4
- Imprint pgtype.Text
- AgeRating pgtype.Text
- WebUrl pgtype.Text
- StoryArc pgtype.Text
- IsBlackAndWhite pgtype.Bool
- MetadataNotes pgtype.Text
- CommunityRating pgtype.Numeric
- AlternateInfo []byte (JSONB)
- ScanInformation pgtype.Text
- Summary pgtype.Text

Part of Phase 1: Database Schema Changes
Implementation: IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md
2026-03-29 19:11:51 -04:00
john-okeefe 2ffd8e032b schema: add comic metadata and reading direction support
Add 14 new columns to media_items table for comprehensive comic and manga
metadata support, including reading direction fields and universal metadata
that applies to all media formats.

New Columns:
- Reading direction: manga_type (raw ComicInfo.xml field), reading_direction (computed)
- Universal series: series_count, volume (apply to ebooks, audiobooks, comics)
- Publisher info: imprint, age_rating (all formats)
- Comic-specific: story_arc, is_black_and_white, alternate_info, scan_information, summary
- Additional metadata: metadata_notes, community_rating, web_url

Constraints:
- manga_type CHECK: unknown, no, yes, yes_and_right_to_left
- reading_direction CHECK: auto, ltr, rtl, vertical

Indexes (8 new):
- idx_media_items_reading_direction, idx_media_items_manga_type
- idx_media_items_story_arc, idx_media_items_imprint
- idx_media_items_age_rating, idx_media_items_series_count, idx_media_items_volume
- idx_media_items_alternate_info_gin (GIN index for JSONB queries)

Documentation:
- Added COMMENT ON COLUMN for all 14 new fields
- Distinctions between comic-specific and universal fields clearly documented

This supports the ComicInfo.xml v2.0 standard with 29 fields and enables
proper reading direction detection for manga, webtoons, and Western comics.

Part of Phase 1: Database Schema Changes
Implementation: IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md
2026-03-29 19:11:48 -04:00
john-okeefe 1b632a1b6e docs: add implementation plan for comprehensive comic metadata support
This document provides a complete implementation plan for extracting ALL 19
ComicInfo.xml v2.0 fields with smart metadata merging and universal metadata
support across all media types (ebooks, audiobooks, comics).

Key features planned:
- Complete ComicInfo.xml v2.0 support (all 19 fields)
- Smart metadata merging: Calibre metadata.opf + ComicInfo.xml + folder structure
- Universal metadata fields (5 fields apply to ALL formats):
  * age_rating: Content classification (Everyone, Teen, Mature, Adult)
  * series_count: Total items in series
  * volume: Collected edition/omnibus number
  * imprint: Publisher subdivision
  * web_url: Info page URLs (Goodreads, Audible, ComicVine, etc.)
- Comic-specific fields (10 fields):
  * manga_type, reading_direction: Reading direction detection
  * story_arc, scan_information, alternate_info, summary, etc.
- Genre/tag processing: ALL genres appear in tags array without duplication
  * Uses existing 'genre' column for primary genre
  * processGenresAndTags() ensures no duplicate genres in tags
  * Works for EPUB, ComicInfo.xml, PDF metadata
- 15 new database columns, 8 indexes
- Full-stack support: database → Go → API → TypeScript → frontend
- Comprehensive testing and documentation

Plan organization:
- 10 phases: Database schema → Data structures → Smart merging → API updates
- Includes detailed commit strategy with sequential git add/commit/push commands
- Testing checklist with manual verification steps
- Bruno API test specifications
- Documentation updates (user + API reference)

This plan follows PROJECT_GUIDELINES.md requirements:
- No new services or handlers (reuses existing media_scanner.go)
- Uses setupTestServer() helper for integration tests
- Includes Bruno YAML files for API testing
- Sequential git commits with detailed messages
- Reuses existing code patterns and database schema
2026-03-29 12:05:06 -04:00
john-okeefe 7f6c6d903e test: update Bruno test data for media items
Update Bruno API test collections with latest test data:

Bookhoard.yml environment:
- Update media_item_id to current database value after recent
  database recreation/schema updates

Create Media Rating test:
- Change test rating from 8 to 7 to test different rating value
  for validation

These are test infrastructure updates only, no application code changes.
2026-03-29 00:27:28 -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 e1723d60bd deps: add bluemonday HTML sanitizer
Add github.com/microcosm-cc/bluemonday for safe HTML sanitization.
This library is industry-standard, actively maintained, and has zero
telemetry/network calls.

License: BSD-3-Clause (compatible with project's GPL-3 license)
Used for sanitizing book description HTML before rendering.
2026-03-28 23:54:30 -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 542940c2ff docs: add book detail page implementation guide
Add comprehensive implementation guide for /media/:uuid book detail page.

Features documented:
- SSR-first template with Alpine.js for modals
- Cover image (left) + metadata (right) layout
- Reading progress tracking with conflict detection
- Sync progress modal (comparison only, manual resolution via /conflicts)
- Notes & highlights counter with placeholder modal
- Collections display as clickable badges
- External service links (Goodreads, Open Library, Google Books, Amazon)
- Smart URL fallback: ID → ISBN → Title+Author search

Technical approach:
- Embeds database.MediaItems struct for zero duplication
- Uses existing database queries (GetMediaItem, GetMediaRating, etc.)
- Follows existing pattern: inline handlers in router/frontend.go
- Keeps json tags in struct for API endpoint compatibility
- Separate routes: /media/:uuid (HTML) vs /api/media-items/:id (JSON)

Files to create:
- internal/handlers/media_detail.go (data structure)
- templates/book_detail.templ (SSR template)
- templates/book_detail_modals.templ (modals)
- web/src/book-detail.ts (Alpine.js integration)

Files to modify:
- internal/router/frontend.go (add route)
- web/src/main.ts (import module)
- templates/utils.go (helper functions)

See BOOK_DETAIL_IMPLEMENTATION.md for complete implementation details.
2026-03-28 22:03:01 -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 0c1a55d6e3 chore: remove obsolete documentation and API collection files
Cleanup of project files:
- Remove CALIBRE_OPF_IMPLEMENTATION.md (obsolete documentation)
- Remove Bruno API collection files for field value searches:
  - Field Values Search - Authors.yml
  - Field Values Search - Genres.yml
  - Field Values Search - Languages.yml
  - Field Values Search - Series.yml

These files are no longer needed as the functionality has been
implemented and the API has evolved.
2026-03-28 00:46:20 -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 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 d399a110ca feat: add HTMX support for saving filters with HTML response handling
Router changes for saved filters API:
- Add CreateFilterHTML handler to return HTML for HTMX requests
- Extract CreateFilterHTML function to handle filter creation logic
- Add wrapper for POST /api/saved-filters to detect HTMX requests
  - HTMX requests: Return HTML via CreateFilterHTML
  - Regular requests: Return JSON via existing handler
- Add collectFilterFormData helper to gather form data from #filter-form
- Improve error handling for duplicate filter names (409 Conflict)
- HTML responses include error messages for better UX

This enables the save filter modal to work without page refresh,
providing a smoother user experience with immediate visual feedback.
2026-03-28 00:46:07 -04:00
john-okeefe 0e11c9263c fix: add unique constraint for saved filter names and fix search sort ordering
Database changes:
- Add unique index on saved_filters(user_id, name, resource_type)
  Prevents duplicate filter names while allowing same name across
  different users or different resource types

Search functionality fix:
- Remove DISTINCT ON (mi.id) from SearchMediaItemsUnified query
- Remove mi.id from ORDER BY clause (was required by DISTINCT ON)
- This allows user-selected sort field to be primary sort criteria
- Previously results were always sorted by ID first, making sort
  dropdown ineffective
- Relevance score and title remain as fallback sorts

This fixes the sort dropdown functionality on the bookshelf page
where changing the sort option appeared to have no effect.
2026-03-28 00:46:03 -04:00
john-okeefe 99f95d2ff1 test: update search API request parameters and sequence numbers
- Update Combined Search and Filters: change has_cover to true, tags_filter to fic
- Update sequence numbers for all search requests (1-7)
- Ensure consistent request ordering in search folder
2026-03-27 21:14:36 -04:00
john-okeefe af3c3019cf refactor: reorganize Bruno API collection for better structure
- Remove obsolete scenarios/ folder and move requests to root media-items/
- Create new filters/ folder for field value autocomplete searches
- Move Field Values Search requests from search/ to filters/ for clarity
- Move Search Media Items from scenarios/ to search/ for consistency
- Update sequence numbers across all media-items requests (2-13)
- Remove redundant folder configuration files
- Improve API collection organization for better discoverability
2026-03-27 21:14:34 -04:00
john-okeefe c9e085c164 feat: automate library_id extraction from Get Libraries API response
- Update library_id in Bookhoard environment configuration
- Add post-response script to Get Libraries (Admin) endpoint
- Script automatically extracts and saves first library_id from response
- Enables seamless API testing without manual variable updates
2026-03-27 21:14:31 -04:00
john-okeefe 3f36d99783 added issues to finish up
added issues to finish up on /bookshelf.
2026-03-27 18:09:10 -04:00
john-okeefe bac77312b4 test: update Bruno API collection for has_cover filter
Updated the Bruno API test collection to include tests for the new
3-state has_cover filter functionality.

Changes:
- Added test cases for has_cover=true, has_cover=false, and has_cover
  not specified to verify all three states work correctly
- Updated environment configuration to support the new filter parameter

These tests verify that the has_cover filter properly handles:
- NULL (not specified): Returns all books
- TRUE: Returns only books with cover images
- FALSE: Returns only books without cover images

This ensures the 3-state boolean implementation works correctly across
all scenarios and prevents regression of the bug where searches were
returning 0 results.
2026-03-27 18:08:18 -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 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 ba243c223d fix: implement proper 3-state boolean handling in backend
Updated the backend services and handlers to properly detect and pass
the has_cover parameter's validity state to the database layer.

Changes:
- services/search.go: Changed HasCover type from bool to pgtype.Bool
  to support 3-state logic (NULL, TRUE, FALSE)
- handlers/media.go: Fixed 3-state detection by checking if has_cover
  exists in query params before setting Valid flag
- router/search.go: Fixed 3-state detection to match media.go logic
- router/frontend.go: Use pgtype.Bool{Valid: false} for SSR initial
  load to ensure no filtering occurs on first page load

The key fix is detecting whether the has_cover parameter was actually
sent in the request:
- Parameter not sent → pgtype.Bool{Bool: false, Valid: false}
- Parameter sent as "true" → pgtype.Bool{Bool: true, Valid: true}
- Parameter sent as "false" → pgtype.Bool{Bool: false, Valid: true}

Previously, media.go was hardcoding Valid: true, which meant it was
always filtering by has_cover=false (only books without covers) when
the parameter wasn't sent, causing searches to incorrectly return
0 results for queries like "1984".

This ensures consistency between the JSON API endpoint (media.go) and
the HTML endpoint (search.go), and fixes the critical bug where SSR
was returning 0 books on initial page load.
2026-03-27 18:07:51 -04:00
john-okeefe 36ae781765 fix: implement proper 3-state boolean logic for has_cover filter
Fixed the SearchMediaItemsUnified query to properly handle the has_cover
parameter in three states:
- NULL (not specified): Show all books
- TRUE: Show only books with cover images
- FALSE: Show only books without cover images

Changes:
- Added explicit boolean casting (::bool) to sqlc.narg('has_cover')
  to resolve PostgreSQL type inference error (SQLSTATE 42P08)
- Replaced single AND condition with OR'd logic to handle all three
  states without mutual exclusion
- Used IS NULL check to detect when parameter is not specified
- Used IS TRUE/IS FALSE to explicitly check boolean states

The previous implementation had mutually exclusive AND conditions that
prevented any records from matching when has_cover was explicitly set
to TRUE or FALSE, causing the filter to block all searches.

This fix resolves the issue where searches were returning 0 results
regardless of other filter parameters when has_cover was included in
the query.
2026-03-27 18:07:41 -04:00
john-okeefe 4dab581f33 fix(router): use service layer for SSR book loading
Replace direct database call with service layer to fix SSR
returning 0 books on initial page load.

Root Cause:
- SSR was calling cfg.Queries.SearchMediaItemsUnified directly
- API was using MediaHandler.ExecuteSearch via service layer
- Both code paths had different parameter structures

Solution:
- Use same MediaHandler.ExecuteSearch handler as API
- Build services.SearchParams struct (same as API path)
- Convert user.ID string to pgtype.UUID for service layer
- Remove unused books variable

Changes:
- Parse user.ID to UUID before building search params
- Build services.SearchParams with empty filters for SSR
- Call cfg.MediaHandler.ExecuteSearch instead of direct DB
- Use textToString helper (already exists in router package)
- Remove unused books variable declaration

Both SSR and API now use identical search logic, ensuring
consistent behavior. HTMX search continues working as before.

Fixes: Issue #1 - SSR returns 0 books on initial load
Related: Issue #2 - Search/filter returning JSON instead of HTML
2026-03-27 15:52:14 -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 417685e9a7 feat(router): implement dual-mode search endpoint (HTML/JSON)
Rewrite /api/media-items/search endpoint to detect HTMX requests and return appropriate response format. The endpoint now checks for HX-Request header and routes to HTML renderer or JSON handler accordingly.

- Check HX-Request header to detect HTMX requests
- Return HTML via BooksGrid template for HTMX requests
- Return JSON for API clients (existing behavior)
- Add handleSearchHTML function for HTML rendering
- Use shared MediaHandler.ExecuteSearch method
- Eliminates previous issue where JSON was rendered in browser
2026-03-27 14:50:36 -04:00
john-okeefe b1446f15f8 feat(media): add ExecuteSearch wrapper and update SearchMediaItems
Add public ExecuteSearch method to MediaHandler that delegates to SearchService. Update SearchMediaItems to use the new shared service method instead of calling SearchMediaItemsUnified directly.

- Add ExecuteSearch wrapper method (line 160-162)
- Update SearchMediaItems to use searchService.ExecuteSearch
- Maintains existing JSON API behavior while enabling shared logic
2026-03-27 14:50:28 -04:00
john-okeefe a33d521492 feat(search): add ExecuteSearch method to SearchService
Add shared search method that returns results with count. This method will be used by both JSON API endpoints and HTML rendering for HTMX, avoiding duplicate business logic.

- Extracts common search logic into reusable service method
- Returns search results with total count for pagination
- Follows DRY principle by eliminating duplicated search code
2026-03-27 14:50:21 -04:00
john-okeefe d8c63b8b8e test: update Bruno API collection with current database test values
Update test data values across Bruno API collection to reflect current
database state and improve test parameter relevance:

- Environment variables: Refresh library_id and job_id UUIDs to current
  database values for accurate testing
- Combined Search test: Update tags_filter from "scifi" to "fict" for
  broader genre coverage and extend year_max from 2000 to 2026 for
  modern title inclusivity
- Fuzzy Author Filter test: Change author_filter from "Conan" to
  "orwell" for consistent author search testing

These updates ensure API tests use valid reference data that matches
the development database state.
2026-03-26 21:01:47 -04:00
john-okeefe be31cc88f1 feat: enhance search with date-prioritized year filtering and true exact matching
Improve media item search functionality with two key enhancements:

1. Date-prioritized year filtering:
   - Prioritize date_published over copyright_year for year range queries
   - Fall back to copyright_year when date_published is NULL
   - Extract year from date_published timestamp for comparison

2. True exact search matching:
   - Replace ILIKE pattern matching with exact equality for quoted queries
   - Use search_query directly instead of wildcard pattern for exact matches
   - Remove SearchPattern parameter and related wildcard logic
   - Add COALESCE handling for author/series NULL values in exact matches

These changes make year filtering more accurate with published dates
and provide genuine exact matching when users wrap queries in quotes.

Refs internal/database/queries/queries.sql:475, internal/services/search.go:62
2026-03-26 15:35:31 -04:00
john-okeefe 6b518462f9 test: update Bruno API collection with new test data values
Update test environment and request files to use different test data:
- Update job_id variable to new test job UUID
- Change search test queries from Foundation/Asimov to 1984/Orwell
- Change series search from Foundation to Haley
- Add force parameter to scanner test

These updates provide fresh test data for API testing and
demonstrate search functionality with different media items.
2026-03-26 15:35:25 -04:00
john-okeefe a3fe47ac21 Update and consolidate implementation documentation
Clean up documentation by removing obsolete implementation notes and
updating the Calibre OPF implementation guide.

Changes:
- Update CALIBRE_OPF_IMPLEMENTATION.md with namespace URL approach
- Remove IMPLEMENTATION_TAGS_FILTER.md (superseded by unified search)
- Remove UNIFIED_SEARCH_IMPLEMENTATION.md (implementation complete)

The Calibre OPF documentation now reflects the corrected approach using
full Dublin Core namespace URLs (http://purl.org/dc/elements/1.1/)
instead of namespace prefixes, which were found to not work with Go's
XML decoder.

Documentation: #docs-cleanup
2026-03-26 14:38:31 -04:00
john-okeefe 0298c589b1 Fix library_id filter test for dev database compatibility
Update TestCollectionSearchLibraryFilter to check for specific test
books rather than exact counts, making tests resilient to changing
dev database data.

Changes:
- Modified "no filter" test case to check both test books are present
- Enhanced shouldContain to support comma-separated book ID lists
- Added strings import for ID list processing
- Skip exact count check when expectedCount is 0

Rationale:
The library_id filter was working correctly. The test failure was due
to running against a dev database with pre-existing data. When no
library_id filter is provided, the API correctly returns all visible
books across all libraries, not just test-created books.

This validates that the filter works correctly while being resilient
to dynamic dev database content.

Fixes: #test-isolation-library-filter
2026-03-26 14:38:26 -04:00
john-okeefe a900c78faf Add Calibre metadata.opf sidecar file support to media scanner
Implement sidecar-first metadata extraction approach that prioritizes
Calibre metadata.opf files over embedded metadata when available.

Key Features:
- Sidecar-first approach: Check for metadata.opf before extracting embedded
- Full Dublin Core namespace support: Use complete namespace URLs
- Calibre-specific meta tags: Extract series, series_index from <meta> tags
- Graceful degradation: Fall back to embedded metadata on parse failure
- Identifier extraction: Support ISBN and ASIN from Dublin Core identifiers
- Date parsing: Handle ISO 8601 timestamps and simple date formats

Implementation Details:
- Added extractCalibreSidecar() to check for and parse metadata.opf
- Added parseCalibreMetadataOPF() with full Dublin Core namespace handling
- Modified extractMetadata() to try sidecar first, fallback to embedded
- Added CalibreOPFMetadata struct for intermediate parsing
- Cover image support: findSidecarCover() for sidecar metadata

Tests:
- Unit tests for parseCalibreMetadataOPF() with real Calibre file examples
- Integration tests for Calibre library scanning

This allows users with Calibre-managed libraries to import their curated
metadata (series, tags, custom covers) into Bookhoard.

Fixes: #calibre-opf-support
2026-03-26 14:38:20 -04:00
john-okeefe 902e878341 docs: enhance implementation plan with copy-paste ready code
Update CALIBRE_OPF_IMPLEMENTATION.md to be implementation-ready with detailed, copy-paste code for all functions.

Major enhancements:
- Add 4 detailed implementation steps with emoji markers (📝 STEP 1-4)
- Include complete, ready-to-copy code for all functions:
  * CalibreOPFMetadata struct (STEP 1)
  * parseCalibreMetadataOPF() function ~130 lines (STEP 2)
  * extractCalibreSidecar() function ~25 lines (STEP 3)
  * extractMetadata() modification showing exact lines to change (STEP 4)
- Add comprehensive unit test file (~200 lines) with test cases
- Add optional integration test (~100 lines)
- Add required imports section (encoding/xml)
- Add verification & testing checklist (Phase 4)
- Add troubleshooting guide for common issues
- Add success criteria checklist

Plan now provides:
- Exact line numbers and locations for all changes
- Complete functions ready to copy/paste
- Clear before/after code for modifications
- Test data and expected outputs
- Build verification commands
- Manual testing procedures

Implementation plan is now detailed enough for direct implementation by copy-pasting code sections.

Total plan: 1,113 lines (up from 427 lines)
New code templates: ~450 lines of production + test code
Time estimate: 2-2.5 hours for complete implementation
2026-03-26 11:54:07 -04:00
john-okeefe 13db38e881 docs: simplify Calibre OPF implementation approach
Update developer documentation to reflect simplified implementation approach based on user feedback.

Key changes:
- Rename extractMetadataFromCalibreSidecar() to extractCalibreSidecar()
- Simplify function signature: return *MediaMetadata instead of (*MediaMetadata, error)
- Replace wrapper function pattern with direct modification of extractMetadata()
- Add code example showing simple if-check at top of extractMetadata()
- Document benefits of simplified approach (40% less code, 0 call site changes)
- Add implementation note explaining the simplification

Benefits of simplified approach:
- ~150 lines of code vs. ~250 lines (40% reduction)
- No wrapper function needed
- No call site changes required
- Clearer single entry point for metadata extraction
- Better testability
- Easier to maintain

This change simplifies the implementation while maintaining all functionality. The sidecar-first approach remains the same, but implementation is cleaner and more straightforward.

See: CALIBRE_OPF_IMPLEMENTATION.md Decision 4 for full rationale
2026-03-26 10:42:32 -04:00
john-okeefe f7001dac4b docs: add Calibre metadata.opf implementation plan
Add comprehensive implementation plan for Calibre metadata.opf sidecar file support in the media scanner.

Key features:
- Sidecar-first approach: Calibre metadata.opf takes precedence over embedded metadata
- Complete database schema mapping (no schema changes required - all fields exist)
- Dublin Core and Calibre-specific field support
- Simplified implementation: modify existing extractMetadata() instead of wrapper pattern
- Works for all library types and file types
- Comprehensive testing strategy

Implementation details:
- ~150 lines of new code (2 new functions + 1 modification)
- No call site changes required
- Graceful degradation on malformed XML
- Performance target: <5% scan time increase

This plan reflects simplified approach based on user feedback to directly modify extractMetadata() rather than creating wrapper functions.

Related: User guide and developer docs added in separate commits
2026-03-26 10:41:58 -04:00
john-okeefe 53046f5499 test: improve Bruno API collection formatting and automation
This commit improves the Bruno API collection with better formatting,
updated test environment variables, and automation scripts for easier
API testing workflow.

## Environment Updates

- bruno/environments/Bookhoard.yml: Updated test IDs for library_id
  and job_id to reflect latest test database state

## Formatting Improvements

Updated all Bruno collection files with consistent formatting:
- bruno/highlights/Create Media Highlight.yml
- bruno/highlights/Update Media Highlight.yml
- bruno/library/Add Library Folder.yml
- bruno/library/Create Library.yml
- bruno/library/Delete Library.yml
- bruno/library/Set Library Visibility.yml
- bruno/media-items/Create Media Item.yml
- bruno/media-items/Create Media Rating.yml
- bruno/media-items/Update Media Item.yml
- bruno/media-items/Update Media Rating.yml
- bruno/media-items/search/Combined Search and Filters.yml
- bruno/notes/Create Media Note.yml
- bruno/notes/Update Media Note.yml
- bruno/progress/Update Reading Progress.yml
- bruno/user/admin/Register Admin User.yml
- bruno/user/auth/Logout User.yml
- bruno/user/auth/Refresh Token.yml

Improvements include:
- Consistent YAML structure and indentation
- Proper multiline string format for JSON bodies
- Moved auth: inherit after headers for consistency
- Added descriptive comments in request bodies

## Automation Features

Added runtime scripts to Create Library.yml:
- after-response script automatically extracts and saves library_id
  from API response to environment variables
- Persists library_id for use in subsequent requests
- Reduces manual copy-paste workflow during testing

Updated request bodies with example data:
- Create Media Item.yml: Added complete example with library_id
  variable reference, title, author, file_path, file_size, mime_type
- Other files: Updated with proper JSON formatting

## Benefits

- More consistent API collection structure
- Automated workflow reduces manual steps
- Better readability with proper YAML formatting
- Example data makes requests easier to understand
2026-03-26 10:34:23 -04:00
john-okeefe 22fd28c7db docs: add comprehensive Calibre integration documentation
This commit adds complete documentation for the planned Calibre
metadata.opf sidecar file support feature.

## New Documentation

### Implementation Planning
- CALIBRE_OPF_IMPLEMENTATION.md: Detailed implementation plan with
  requirements, architecture, database mapping, and step-by-step
  implementation guide for adding Calibre metadata.opf support

### Technical Documentation
- docs/development/calibre-opf-implementation.md: Technical implementation
  details including:
  - Scanner pipeline architecture with sidecar-first approach
  - Data structures (CalibreOPFMetadata, MediaMetadata)
  - Function signatures and logic for parseCalibreMetadataOPF()
  - Database schema mapping (no changes required)
  - Testing strategy (unit and integration tests)
  - Error handling and performance considerations
  - Code examples and benchmarking approach

### User Documentation
- docs/user/calibre-integration.md: Comprehensive user guide covering:
  - What is Calibre and how Bookhoard integrates with it
  - Automatic metadata import from metadata.opf sidecar files
  - Supported metadata fields (Dublin Core + Calibre-specific)
  - Setup instructions for Calibre libraries
  - Workflow examples (fresh library, mixed library, updating metadata)
  - Troubleshooting common issues
  - Best practices for Calibre + Bookhoard workflow
  - FAQ and resources

## Updated Documentation

- README.md: Added Calibre integration feature to media management section
- docs/user/user-guide.md: Added link to Calibre integration guide
- docs/developer/development.md: Added link to Calibre implementation guide

## Feature Summary

The Calibre integration feature will allow Bookhoard to automatically
import curated metadata from Calibre's metadata.opf sidecar files,
including titles, authors, series, tags, descriptions, publishers,
identifiers (ISBN/ASIN), and contributors. Uses sidecar-first approach:
metadata.opf → embedded metadata → folder structure → filename.

All database fields already exist; no schema changes required.
2026-03-26 10:34:15 -04:00
john-okeefe 333f4ae026 chore: update test library_id in Bruno environment
- Update library_id variable in Bookhoard.yml environment
- Changed to dd03d719-76c8-4398-93ec-9258d2becf85
- Refreshes test environment with current library ID

Updates the Bruno API testing environment to use a current
library ID for testing media items and search functionality.
2026-03-25 21:13:35 -04:00
john-okeefe 80d423663b test: fix type assertion in autocomplete test
- Change type assertion from []map[string]interface{} to []interface{}
- JSON unmarshal into interface{} creates []interface{}, not typed slices
- Fixes panic: interface conversion error in test

The response["results"] field needs to be asserted as []interface{}
when the parent is unmarshaled into map[string]interface{}.
This matches Go's JSON unmarshaling behavior for interface{} types.
2026-03-25 21:01:21 -04:00
john-okeefe fcc8b38c52 chore: remove queries.sql.go.backup file
Remove outdated backup file that is no longer needed.
The generated Go code is maintained in queries.sql.go.
2026-03-25 21:00:26 -04:00
john-okeefe 6a8d2e0e3b test: fix autocomplete test to match API response structure
- Update test to unmarshal response object before extracting results array
- API returns {"results": [...], "total": N}, not a bare array
- Fixes "cannot unmarshal object into Go value of type []map" error
- Test now correctly handles the structured autocomplete response

The handleFieldValuesSearch endpoint returns a structured response
with metadata (results array + total count), not a bare array.
This aligns the test with the actual API response format.
2026-03-25 20:59:26 -04:00