- Export getDefaultSettings function for use in reader-navigation
- Minor formatting improvements in settings-manager.ts
- Add tabindex and ebook-content class to reader-content in template
- Add pageCalculationResult and currentScrollPosition to UniversalReader type
- Add settings:changed event type for settings change notifications
- Call initializePageCalculation after reader ready in reader-shell.ts
- Add tabindex to reader-content for keyboard navigation focus
- Import getCurrentPageFromScroll for viewport-based page calculation
- Replace spine index with actual calculated page numbers when available
- Fix bug where currentPage was assigned to itself (no-op)
- Add fallback to estimated pages while calculation is in progress
- Add page calculation state and initialization function
- Import page calculator functions for dynamic page tracking
- Update nextPage/previousPage to use getScrollPositionForPage for chapter navigation
- Add scroll tracking in renderSpineItem for real-time page updates
- Emit progressUpdated with dynamic page count instead of spine index
- Fix applyReaderTheme to use classList.add instead of className overwrite
- Remove unused state variables from applyReaderTheme/applyTypography
- Create page-calculator.ts module with viewport-based pagination
- Implement calculatePagesForEbook to measure rendered content
- Add getCurrentPageFromScroll for scroll position to page mapping
- Add getScrollPositionForPage for page to scroll position mapping
- Include calculateProgressPercentage for progress tracking
- Create documentation with full implementation spec
- Add readerReady event listener to progress-indicator.ts to ensure
progress displays correctly when reader finishes loading
- Update bruno environment variable values for testing
- Move bluemonday from indirect to direct dependency in go.mod
- Clean up unused indirect dependencies in go.sum
- Add Feature Registry system in reader-shell.ts to track features
- Update format detection to use format_group with switch statement
- Add manga detection via library_type_name or manga_type fields
- Import and register features via init() functions on load
- Fix parser imports in parser-manager.ts (add missing function imports)
- Simplify epub-parsers and fb2-parser by removing unused imports
- Add init(context) exported function to each feature module
- Store module-level context reference for event handlers
- Fix gestures.ts to use library_type_name instead of library_type
- Features: gestures, keyboard-shortcuts, navigator-panel, offline-manager,
panel-dock-system, progress-indicator, reading-speed-tracker
- Move ReaderMetadata from api.ts to reader.d.ts as global type
- Add format_group field for format detection (reflowable/fixed_layout/comic_archive)
- Add library_type_name for library type (ebooks/comics/manga/pdf)
- Add manga_type for manga-specific reading direction metadata
- Remove duplicate mime_type field and fix typo (unkown -> unknown)
- Add reader.ts as second entry point alongside main.ts in vite.config.ts
- Set assetsDir to empty string to output chunks to /static/ root
- Remove reader-shell import from main.ts (loaded separately via reader.js)
- Update .gitignore to exclude web/static/assets/*.js chunks
- Update Bruno environment with new test IDs
Delete old versions of features that are now in /reader/features/:
- offline-manager.ts (moved to features/)
- reading-speed-tracker.ts (moved to features/)
- panel-dock-system.ts (replaced by converted version in features/)
- navigator-panel.ts (replaced by converted version in features/)
All imports now use the /reader/features/ directory. This eliminates
duplication and makes it clear which files are the active ones.
Create panel-dock-system.ts and navigator-panel.ts in features/
directory with proper init() implementations. Copy offline-manager
and reading-speed-tracker to features/ as well.
Fix bug where context parameter was unused in init() but referenced
throughout the file. Now properly stored as module-level variable
so all gesture handler functions can access it.
Convert view-modes.ts to init(context) pattern with:
- Paginated mode with pagination controls
- Scrolled mode for continuous reading
- Single and double column layouts
- Event-based navigation and mode switching
Complete the Feature Registration Pattern refactoring across all reader
modules. Each feature now exports an init(context) function and uses the
event-based architecture for loose coupling.
## Comic Features (6 files)
- background-color.ts: Background color picker with toggle
- chapter-markers.ts: Visual chapter indicators
- page-cache.ts: 5-page ahead prefetch with cleanup
- page-order.ts: Auto-detect Japanese vs Western order
- page-scrubber.ts: Quick navigation slider
- panel-gap.ts: Adjustable panel gap controls
## Ebook Features (6 files)
- copy-handler.ts: Text copying with citation
- dictionary-popup.ts: Word lookup integration
- font-loader.ts: 8 bundled libre fonts
- search.ts: Full-text search across spine
- typography-engine.ts: Font rendering and hyphenation
## Manga Features (4 files)
- reading-direction.ts: RTL/LTR/vertical detection
- rtl-navigator.ts: Reversed page turn direction
- settings.ts: Webtoon mode and transitions
- vertical-scroll-mode.ts: Infinite scroll with lazy loading
## PDF Features (3 files)
- pdf-navigation.ts: Page turning, zoom, fit modes
- pdf-text-selection.ts: Highlight creation via backend
- annotation-layer.ts: Render highlights and notes
## Root-Level Features (3 files)
- offline-manager.ts: PWA service worker and sync
- reading-speed-tracker.ts: Pages/words per minute tracking
- settings-manager.ts: Per-user settings with localStorage fallback
## Core Infrastructure (1 file)
- parser-manager.ts: Fixed import paths for all parsers
## Key Changes
- All features use init(context) pattern
- Event-based communication via context.events.on/emit
- No direct DOM manipulation in feature exports
- State managed within feature closures
- Clean initialization and teardown
- Zero functionality lost - all features preserved
Total: 23 files converted to unified architecture
- Delete gestures.ts: replaced by features/gestures.ts
- Delete keyboard-shortcuts.ts: replaced by features/keyboard-shortcuts.ts
- Delete progress-indicator.ts: replaced by features/progress-indicator.ts
- Old files used callback pattern, new files use init(context) pattern
- Import reader/reader-shell to load reader functionality
- Ensures reader JavaScript is included in main.js bundle
- Reader features and Alpine components are now initialized
- Replace callback-based architecture with Feature Registration Pattern
- Add feature registry for modular feature initialization
- Create createNavigationAPI() for unified page/chapter navigation
- Implement renderCurrentPage() for PDF/comic/manga rendering
- Add updateProgress() to sync reading progress to backend
- Support ebook, PDF, comic, and manga readers in single interface
- Remove 600+ lines of callback wiring code
- reader-shell now acts as lightweight orchestrator
- Delegates specific functionality to feature modules
- Parse ebooks using parser-manager with format routing
- Initialize readers based on library type from metadata
- Update pdfjs-wrapper.ts: add initializePDFReader() function
- Configure PDF.js worker, standard fonts, and cmaps
- Load PDF documents and extract metadata
- Implement page rendering with canvas
- Support PDF page navigation and zoom
- Return PDFReader with current page tracking
- Integrates with feature-based reader architecture
- Update copy-handler.ts: export enableContextMenuCopy for text selection
- Update typography-engine.ts: export applyTypography and getFontStack
- Update view-modes.ts: export setViewMode and getCurrentViewMode
- Enable text copying with citation for ebooks
- Add typography engine with font loading and text formatting
- Add view mode support (paginated, scrolled, single/dual column)
- These features integrate with settings-manager for customization
- Add getReaderMetadata() to fetch media item metadata
- Add updateReadingProgress() to sync reading progress to backend
- Export ReaderMetadata and ReadingProgress types
- Enable reader to communicate with Go backend
- Move gestures.ts to features/ with init(context) pattern
- Move keyboard-shortcuts.ts to features/ with init(context) pattern
- Remove callback-based architecture
- Features now subscribe to events via ReaderContext
- Support touch gestures (swipe, tap, double-tap, pinch-to-zoom)
- Support keyboard shortcuts (navigation, zoom, fullscreen, bookmarks)
- Add panel-aware navigation for comics/manga
- Keyboard shortcuts include chapter navigation
- Add reader-context.ts: defines ReaderContext interface and factory
- Add reader-events.ts: event bus for feature communication
- Add reader-state.ts: centralized state management
- Add reader-navigation.ts: unified navigation and rendering API
- Add reader-services.ts: shared services (progress, chapters)
- Establishes foundation for Feature Registration Pattern
- settings.ts: Manga reading settings and configuration
- reading-direction.ts: Right-to-left reading direction support
- vertical-scroll-mode.ts: Webtoon/vertical scroll reading mode
- rtl-navigator.ts: RTL navigation for manga
- panel-detection.service.ts: Main orchestration with OpenCV → ML → Grid fallback chain
- panel-detection.opencv.ts: Edge detection using OpenCV for 80% of comics
- panel-detection.ml.ts: COCO-SSD object detection for irregular layouts
- panel-detector.ts: Unified detector interface
- panel-editor.ts: Manual panel editor UI for user corrections
- panel-ml-detector.ts: TensorFlow.js integration for ML detection
- page-cache.ts: Efficient page caching for large comics
- background-color.ts: Auto-detect comic background color
- chapter-markers.ts: Chapter detection and navigation
- page-scrubber.ts: Fast page scrubbing/thumbnails
- page-order.ts: RTL/LTR page ordering support
- panel-gap.ts: Panel gap detection
- 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
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.
- 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.
- 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.
- 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.
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).
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.
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)
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
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.
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.
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.