Books were failing to load because foliate-js fetches the file URL
without auth headers, getting rejected by JWT middleware. Also,
reading themes were not being applied because getCSS() didn't
inject background/text colors into the book iframe.
- Fetch book file with Bearer token, pass as File (not URL) to
view.open() so foliate-js can detect format via filename extension
- Add reading theme class to body so foliate-themes.css activates
the correct --reader-bg/--reader-text CSS variables
- Update getCSS() to read theme colors from outer page and embed
them in the iframe CSS string (background-color, color, link
color, selection color)
- Fix Alpine.start() deadlock: move call outside the alpine:init
listener so Alpine actually initializes
- Remove unused tocItem from relocate handler destructuring
- Replace apiGet/apiPut with direct fetch in settings-manager to
fix /api prefix mismatch (reader routes are at /readers/*, not
/api/readers/*)
The foliate-view custom element had no height, causing its shadow DOM
content to collapse to 0px. Books were loading but invisible.
- Add h-screen overflow-hidden to body for full viewport height
- Add block w-full h-full to foliate-view element
- Replace flex-grow with Tailwind grow class on progress slider
- Remove #progress-slider CSS rule from inline style block
(replaced by Tailwind grow utility)
Update NewDevDBSetup collection to create Ebook, Comic, and Manga
libraries with separate IDs (ebook_library_id, comic_library_id,
manga_library_id) instead of a single library_id.
- Fix CreateComicLibrary and CreateMangaLibrary to use correct
names, descriptions, and types instead of duplicating Ebook values
- Update NewDB.sh to run the full setup sequence: register user,
create all three libraries, add folders, then scan all
- Add AddEbookLibraryFolder, AddComicLibraryFolder, and
AddMangaLibraryFolder requests with per-type subfolder paths
- Add ScanAllLibraries request using bru.sendRequest() to scan
each library sequentially via the /api/scanner/scan endpoint
- Update Get Libraries (Admin) to save all three library IDs
- Update List Media Items requests to use ebook_library_id
- Rename library_id to ebook_library_id in Create Library and
Add Library Folder requests
- Add comic_library_id and manga_library_id to environment
Major rewrite of the web reader to properly interface with
@bookhoard/foliate-js, replacing the abandoned panel-detection
architecture with direct pan and zoom support built into the
foliate-js FixedLayout renderer.
Template (reader.templ):
- Fix critical bug: x-init config was using literal strings
'{ readerData.X }' inside a quoted attribute, which templ
treated as raw text and never interpolated. Values were never
actually passed to JavaScript. Now uses fmt.Sprintf() with
templ's expression attribute syntax ={ }.
- Pass fileUrl from server so foliate-js can open books directly.
- Redesign bottom bar with foliate-js parity: left/right navigation
buttons, progress slider with tick marks, and zoom controls
(zoom out, percentage display, zoom in, magnifier, pan/select
mode toggle for PDFs).
- Remove panel editor button and enablePanelDetection config.
- Add SVG icon styles for consistent reader controls.
Go types (templates/types.go):
- Expand ReaderMetadata with FormatGroup, MangaType,
ReadingDirection, FileURL, and LibraryID fields needed by
the reader frontend.
Router (internal/router/reader.go):
- Populate new ReaderMetadata fields from database values.
- Construct FileURL from library ID and file path for the
/uploads/library-{id}/* file serving route.
Reader JS (reader.ts):
- Full rewrite modeled on foliate-js Reader class, adapted for
Alpine.js. Opens books via view.open(fileUrl), accesses
view.renderer for zoom/pan/navigation, and wires up keyboard
shortcuts (+/-/0 for zoom, arrows for nav, Escape for magnifier).
- Uses view.isFixedLayout instead of importing FixedLayout class,
avoiding a TypeScript module resolution issue with the Vite alias.
Settings manager (settings-manager.ts):
- Remove dependency on deleted ReaderContext event bus.
- Export loadSettings/saveSettings/syncSettings directly as
standalone async functions.
Cleanup:
- Delete reader-context.ts and reader-events.ts (over-engineered
event system replaced by direct function calls).
- Remove panel_zoom_enabled from ReaderSettings type.
Update all library API request files to the current Bruno format
with proper settings blocks, updated sequence numbers, and cleaner
YAML formatting.
Add NewDevDBSetup collection with requests for creating comic, ebook,
and manga libraries, user registration, and folder configuration to
speed up development environment setup.
The string_to_array call in detect_fixed_layout_epub() had an extra
closing parenthesis after the '<img' delimiter, causing a SQL syntax
error that prevented the database container from initializing:
IF array_length(string_to_array(opf_content, '<img')), 1) - 1 > 50
Fixed to:
IF array_length(string_to_array(opf_content, '<img'), 1) - 1 > 50
Update the reader template to support dynamic configuration and manga metadata:
templates/reader_templ.go:
- Remove direct foliate-js/view.js script tag (integrated into reader.js)
- Add foliate-themes.css stylesheet for theming support
- Update initReader() call to accept configuration object with:
- mediaItemId: Unique media item identifier
- title: Media item title
- enablePanelDetection: Boolean for comic/manga panel detection
- libraryType: Media type for reader initialization
- formatGroup: Format category (ebook, comic, manga)
- mangaType: Manga subtype for specialized handling
- readingDirection: RTL/LTR/vertical reading direction
- Simplify theme to always use tokyo-night (theme handled in JS)
These changes enable the reader to dynamically configure itself based on
media item metadata, supporting enhanced manga reading features and
panel detection for comic formats.
Remove large dependencies that are not actively used in the codebase:
- jszip: Unused ZIP processing library
- pdfjs-dist: PDF rendering (handled by external library)
- @techstark/opencv-js: Computer vision operations
- @tensorflow/tfjs: TensorFlow.js machine learning framework
- @tensorflow-models/coco-ssd: COCO-SSD object detection model
These dependencies were related to experimental features that have been
replaced or moved to external processing. Removing them significantly
reduces bundle size and simplifies the dependency tree.
Retain only actively used dependencies like htmx, chart.js, lunr,
and the @bookhoard/foliate-js fork with panel detection support.
Apply Go 1.18+ language features and modern style:
internal/services/collection_service.go:
- Use map[string]any instead of map[string]interface{} (Go 1.18+)
- Use range clause with single variable for iteration-only loops
- Replace if-else chains with switch statements for better readability
- Remove explicit type initialization for zero values
internal/services/filters.go:
- Add Err prefix to custom error variable for error naming convention
internal/router/library.go:
- Use cfg.ProcessingIssuesHandler instead of local processingIssuesHandler variable
- Ensures proper dependency injection through router config
These changes follow current Go best practices and improve code readability.
Add frontend route /admin/libraries/:id/issues to display processing issues
management page for a specific library.
internal/router/frontend.go:
- Register GET /admin/libraries/:id/issues with admin middleware
- Fetch processing issue stats from database
- List processing issues for the library
- Convert database models to template types
- Render AdminProcessingIssues template with issues and stats
This provides the admin UI for viewing and managing processing errors that
occur during media scanning and import workflows.
Wire up the ProcessingIssuesHandler throughout the application:
cmd/server/main.go:
- Remove obsolete commented-out getTemplateUserWithTheme function
- Instantiate ProcessingIssuesHandler with database queries
- Add handler to router Config (with field alignment cleanup)
internal/router/router.go:
- Add ProcessingIssuesHandler field to router Config struct
- Reformat Config struct for better field alignment
This enables the processing issues API endpoints for listing and getting
statistics about issues within libraries, integrated with the admin UI.
Fix ResolveProcessingIssue and DeleteProcessingIssue methods to use proper
parameter structs instead of individual arguments.
Changes:
- ResolveProcessingIssue: Use database.ResolveProcessingIssueParams struct
with ID and MediaItemID fields instead of separate arguments
- DeleteProcessingIssue: Wrap issueID in pgtype.UUID struct
- Use map[string]any instead of map[string]interface{} for JSON responses
These changes align with the sqlc-generated database interface and ensure
type-safe parameter passing to the database layer.
Add admin_processing_issues_templ.go template for managing processing issues
in the admin dashboard. This template provides:
- List view of all processing issues with filtering by severity
- Issue details display (file path, error type, description)
- Actions to resolve or dismiss issues
- Integration with the ProcessingIssuesHandler API endpoints
This UI enables administrators to monitor and address processing errors that
occur during media scanning and import workflows.
Add ProcessingIssueData and IssueStats types to templates/types.go for use
in the processing issues management UI. These types support:
- ProcessingIssueData: Individual issue details including ID, media item,
file path, format, issue type, severity, and timestamps
- IssueStats: Aggregated counts of issues by severity (error, warning, info)
These types enable the admin UI to display processing issues from the database
and provide statistics for the issues dashboard.
Add Go package documentation comments to clarify the purpose and scope of:
- internal/handlers/: HTTP request/response handlers for authentication,
libraries, media items, reading, collections, dashboards, devices,
analytics, and system features
- internal/services/: Core business logic layer including media scanning,
library management, search, analytics, and conversion services
These doc comments improve code discoverability and help developers understand
the architectural separation between HTTP handling (handlers) and business
logic (services).
Remove READER_REFACTOR_MODULARIZATION_AND_PAGINATION.md as the modularization
and pagination refactoring has been completed and integrated into the codebase.
This document outlined:
- Modular reader architecture by format (reflowable, pdf, comic, manga)
- Page-based pagination using word count estimation
- CFI-based progress tracking for reflowable formats
- Format-agnostic UI components
The implementation has been completed, so this design document is no longer needed.
Added comprehensive integration tests for the new processing issues API
endpoints that track EPUB format mismatches in manga/comics libraries.
Test Coverage:
- Authentication & authorization (no auth, invalid auth, non-admin, admin)
- Input validation (malformed UUIDs, path traversal, SQL injection attempts)
- Response structure validation (fields, types, content-type)
- Cross-library isolation (ensures issues don't leak between libraries)
- All library types (ebooks, comics, manga, audiobooks)
- Edge cases and error conditions
Endpoints Tested:
- GET /api/libraries/:id/issues/list - Lists unresolved processing issues
- GET /api/libraries/:id/issues/stats - Returns error/warning/info counts
Test Implementation:
- 522 lines, 9 test functions, 30+ subtests
- Uses setupTestServer() helper for server setup
- Uses setupDeviceTest() helper for library creation
- Follows PROJECT_GUIDELINES.md requirements
- Table-driven tests with t.Run() for comprehensive coverage
- Tests all three user contexts: no user, regular user, admin
This ensures the processing issues feature is properly tested before
integration with the media scanner service.
- Add enablePanelDetection, libraryType, formatGroup state
- Add panelDetector instance for dynamic loading
- Update initReader to accept and process configuration
- Conditionally load panel detection only when enabled
- Use dynamic import for foliate-js/panel-detection.js
- Add error handling for panel detection loading
- Update reader initialization to pass panel detection configuration
- Include enablePanelDetection, libraryType, formatGroup params
- Add mangaType and readingDirection for proper manga rendering
- Change theme class to theme-tokyo-night for consistent styling
- Add ProcessingIssuesHandler with List and GetStats methods
- Add AdminProcessingIssues template for issues dashboard
- Display error/warning/info stats cards
- Sort issues by severity and creation date
- Add dismiss functionality for warnings and info items
- Add navigate to media item functionality
- Show issue type, description, and media details
- Add GET /admin/libraries/:id/issues/list for listing issues
- Add GET /admin/libraries/:id/issues/stats for issue statistics
- Integrate processing issues handler with library routes
- Add shouldEnablePanelDetection to determine when to enable panel detection
- Enable for manga/comics libraries with fixed_layout or comic_archive formats
- Fetch library type info using GetLibraryWithType query
- Pass panel detection config to reader initialization
- Include format_group, manga_type, and reading_direction in reader response
- Add ProcessingIssues model struct
- Update Querier interface with processing issues methods
- Add generated query implementations for CreateProcessingIssue, ListProcessingIssuesByLibrary, GetProcessingIssueStats, ResolveProcessingIssue, DeleteProcessingIssue
- Add GetLibraryWithType query for fetching library with type information
- Add CreateProcessingIssue with upsert for recording/renewing issues
- Add ListProcessingIssuesByLibrary with severity ordering and media item details
- Add GetProcessingIssueStats for error/warning/info counts
- Add ResolveProcessingIssue for marking issues as resolved
- Add DeleteProcessingIssue for removing resolved issues
- Add GetLibraryWithType for fetching library with type info for validation
- Add processing_issues table to track media items that cannot be properly processed in their assigned library
- Include fields for issue type, description, severity, and resolution status
- Add indexes for efficient querying by library and severity
- Support tracking format mismatches and other processing problems
- Unique constraint on media_item_id and issue_type to prevent duplicates
Remove unused Epubcfi and Percentage fields from UpdateReadingProgressParams
struct to align with the new foliate-js based reader implementation.
The foliate-js library handles CFI tracking and percentage calculation
internally, so these parameters are no longer needed in the update API.
The reader now relies on foliate-js's built-in progress tracking mechanisms.
This change aligns the database layer with the foliate-js integration completed
in commit c7a9098 (feat: Replace foliate-js submodule with npm git dependency).
Changes:
- Remove Epubcfi field from UpdateReadingProgressParams struct
- Remove Percentage field from UpdateReadingProgressParams struct
- UpdateReadingProgress function now uses simplified parameter set
This document provides a complete, phased implementation plan for:
- Enabling manga EPUBs in manga library (not just CBZ/CBR)
- Detecting fixed-layout EPUBs vs reflowable EPUBs
- Processing issue tracking for format mismatches
- Universal panel detection for manga and comics libraries
- Smart panel detection that works for PDF comics but not PDF ebooks
Key features:
- All changes follow existing code patterns with exact line numbers
- 9 implementation phases in correct dependency order
- Code-around context for every change (before/after)
- Testing checklist and rollback plan
- Database schema changes, scanner enhancements, new handlers, frontend updates
Panel detection logic:
- Manga library + fixed_layout/comic_archive → panel detection ON
- Comics library + fixed_layout/comic_archive → panel detection ON
- Ebooks library + any format → panel detection OFF
- Comics library + PDF → panel detection ON
- Ebooks library + PDF → panel detection OFF
Implementation addresses the constraint that manga EPUBs live in /manga/
directory physically but must be filtered to only show fixed-layout EPUBs
in the manga library (not reflowable novels).
This is a planning document only - no code changes yet.
Migrate from git submodule to npm package management for better
developer experience and simplified deployment.
Changes:
- Add @bookhoard/foliate-js from GitHub fork
(john-okeefe/foliate-js#bookhoard-panel-detection)
- Update vite alias to point to node_modules instead of vendor
- Delete .gitmodules (no submodules tracked)
- Remove scripts/setup-git-hooks.sh (no longer needed)
- Delete web/vendor/foliate-js/ submodule directory
- Remove sc-commit git alias (submodule-specific)
Benefits:
- Standard npm workflow (npm install / npm update)
- No authentication issues for end users (public GitHub)
- Simpler deployment (npm ci in containers)
- foliate-js protected in node_modules (AI won't rewrite)
- Independent project management
- Cleaner git history
Technical details:
- Import remains unchanged: import "foliate-js/view.js"
- Vite alias maps "foliate-js" to "/node_modules/@bookhoard/foliate-js"
- Build verified working (reader.js includes foliate-js)
- Package installed from git branch: bookhoard-panel-detection
- Add chmod +x to ensure pre-push hook is executable after creation
- Add global git alias 'sc-commit' for committing to all submodules at once
- Improve user feedback with detailed explanation of installed components
- Better code organization with clearer comments
This makes the setup script more robust by ensuring the hook has proper permissions and provides a convenient command for bulk submodule commits.
This script installs a pre-push hook that prevents pushing commits when submodules have uncommitted changes, helping avoid accidental commits with dirty submodule states.
The hook checks all submodules for uncommitted changes before allowing a push, protecting against pushing incomplete work that includes submodule modifications.
- Add web/static/*.mjs to ignore compiled JavaScript modules
- Add web/static/text_layer_builder*.css for PDF.js text layer CSS files
- Add web/static/annotation_layer_builder*.css for PDF.js annotation layer CSS
These files are generated during the PDF.js build process and should not be tracked in version control.
Create minimal Alpine.js integration for foliate-js reader. This file
serves as the entry point that imports foliate-js and provides basic
navigation controls.
Implementation:
1. Import foliate-js/view.js:
- Registers <foliate-view> custom element globally
- Makes foliate reader functional when element is added to DOM
- No explicit EPUB imports needed (foliate detects format automatically)
2. Alpine.js integration:
- Create readerShell data object for UI state management
- Provide nextPage() and previousPage() methods for button controls
- Methods access <foliate-view> custom element's API (next(), prev())
- Simple, functional approach (no OOP, follows project guidelines)
3. Init placeholder:
- initReader() method for future initialization logic
- Currently just logs for debugging
- Will be extended with theme switching, progress sync, etc.
Design Choices:
- Follow project guidelines: No classes, functional/procedural style
- Use Alpine.js for UI state (consistent with rest of application)
- Defer book loading to foliate's internal format detection
- Minimal footprint: Only what's needed to make <foliate-view> work
Next Steps (Future Commits):
- Theme switching logic (apply CSS custom properties)
- Progress sync to API (listen to foliate's relocate event)
- Book loading integration (open book path, handle CFI locations)
- Settings persistence (save theme, font, spacing preferences)
File: web/src/reader/reader.ts (31 lines)
- Clean separation: Foliate handles rendering, Alpine handles UI state
- Type-safe with @ts-ignore for foliate custom element API access
Add comprehensive reading theme system with 18 themes organized into 5 categories.
All themes include light and dark mode variants, optimized for readability and
eye comfort during long reading sessions.
Theme Categories:
1. Classic Reading (6 themes)
- Light, Paper, Sepia, Parchment, Warm, Candlelight
- Time-tested, comfortable for general reading
2. Sky & Atmosphere (4 themes)
- Azure, Sky, Arctic, Frost
- Open, airy, contemplative feel with blue tones
3. Sunset & Warmth (3 themes)
- Dusk, Sunset, Twilight
- Warm, energizing colors for evening reading
4. Nature & Earth (3 themes)
- Forest, Moss, Slate
- Grounded, natural, calming greens and grays
5. High Performance (2 themes)
- OLED, Solarized
- Optimized for specific use cases (battery saving, precision design)
Theme Features:
- All themes pass WCAG AAA contrast standards (7:1 ratio)
- Each theme has light and dark mode variants
- CSS custom properties for dynamic theme switching
- Optimized color temperatures for different lighting conditions
- Inspired by best practices from e-readers (Kindle, Kobo) and community projects (Grimmory)
Design Principles:
- Readability first: Avoid pure black on pure white (causes eye strain)
- Color temperature: Warm tones for evening, cool tones for daytime focus
- Typography support: Works seamlessly with 9 bundled libre fonts
- Progressive enhancement: Themes work without JavaScript
File: web/static/foliate-themes.css (301 lines)
- CSS custom properties for each theme variant
- Typography base styles (font-family, font-size, line-height, margins)
- Link, selection, and image handling styles
- Orphan/widow prevention for better text flow
Update Vite configuration to support foliate-js library integration:
1. Add import alias for foliate-js:
- Maps 'foliate-js' imports to web/vendor/foliate-js submodule
- Allows clean imports: import { View } from 'foliate-js/view.js'
2. Update build target to ESNext:
- Change from 'es2020' to 'esnext' to support top-level await
- Required by foliate-js pdf.js which uses top-level await
- ES2022+ support is excellent in all modern browsers (Chrome 112+, Firefox 115+, Safari 16.4+)
These changes enable Vite to bundle foliate-js into reader.js without
requiring a separate build step for the library.
Remove 60+ files from the old reader implementation that relied on
CSS columns pagination, which was fundamentally broken. This includes:
- Comic/Manga format handlers (panel detection, reading direction)
- PDF rendering, bookmarks, annotations, outlines
- Reflowable content pagination (EPUB, FB2, TXT, HTML parsers)
- UI components (gestures, keyboard shortcuts, panel dock system)
- Core navigation and state management
The old implementation used CSS columns for EPUB pagination, but this
approach is fundamentally incompatible with horizontal book layouts
because CSS columns fill vertically first, then wrap horizontally.
This causes only 1 column to be created instead of the expected 92+.
This cleanup prepares the codebase for foliate-js integration, which
uses JavaScript-driven pagination with CFI-based positioning that
actually works for book reading.
Files removed:
- formats/: comic/, manga/, pdf/, reflowable/ (65 files)
- parsers/: EPUB, FB2, TXT, HTML (4 files)
- ui/: gestures, keyboard shortcuts, panel dock, progress tracker (8 files)
- core/: parser-manager, reader-navigation, reader-services, reader-state (4 files)
- reader-shell.ts: Main reader orchestrator (565 lines)
Total: 9,361 lines removed
Reader functionality will be restored via foliate-js integration.
TYPE SAFETY IMPROVEMENTS:
1. manga/reading-direction.ts
- Create MangaMetadata interface to replace 'any' type
- Remove redundant 'as any' casts inside detectFromMetadata()
- Add proper typing for manga_type and reading_direction fields
- Function signature now properly typed
2. ui/gestures.ts
- Remove 'as any' cast for comic/manga reader
- TypeScript already knows the type after type guard checks
- Improves type safety and enables better autocomplete
3. ui/keyboard-shortcuts.ts
- Remove 'as any' cast for comic/manga reader
- Type guard on lines 22-24 narrows the type correctly
- No cast needed, TypeScript infers ComicReader | MangaReader
4. reader-shell.ts
- Remove 'as any' cast for comic/manga images array access
- Type checking after format checks ensures correct type
- Change: (state.currentReader as any).images.length
- To: state.currentReader.images.length
BENEFITS:
✅ Compiler catches property name mismatches (e.g., pageCalculationResult)
✅ Better IDE autocomplete and inline documentation
✅ Prevents runtime type errors that would slip through with 'any'
✅ Code becomes self-documenting with explicit types
✅ Easier refactoring with compiler assistance
This change improves overall type safety in the reader codebase by
removing unnecessary type casts that were bypassing TypeScript's
type checking. The 'as any' casts were hiding bugs and preventing
the compiler from catching errors at compile time.
Related to: Type system improvements, bug prevention
CRITICAL BUG FIX:
The UniversalReader interface had a duplicate spine index issue:
- Top-level property: currentSpineIndex: number
- Nested property: position.spineIndex: number
When navigation updated the position object, only position.spineIndex
was updated, but the top-level currentSpineIndex remained unchanged.
This caused a mismatch that broke navigation across spine boundaries.
ROOT CAUSE:
- Navigation functions update position (which contains spineIndex)
- updateCurrentPosition() only spread position, not currentSpineIndex
- progress-indicator.ts reads from currentSpineIndex to get spine info
- Result: Trying to access spine 0 when actually on spine 1, etc.
THE FIX:
Add currentSpineIndex to the returned object in updateCurrentPosition():
return {
...book,
position,
currentSpineIndex: position.spineIndex, // Keep them in sync
};
IMPACT:
✅ Navigation works correctly across spine boundaries (e.g., cover → content)
✅ Progress indicator displays accurate chapter information
✅ Content rendering uses correct spine data
✅ No more state corruption when navigating between spines
This fix ensures that both spine index properties stay synchronized,
preventing the navigation failures that occurred when crossing from
one spine item to another (e.g., from cover.xhtml to Frankenstein.xhtml).
Related to: Spine boundary navigation, state synchronization
BUG FIXES:
1. Fix incorrect property access causing page counter to show wrong totals
- Changed: reader.pageCalculationResult → reader.pagination
- The pageCalculationResult property doesn't exist on UniversalReader
- This caused fallback to metadata.total_pages (299) instead of
calculated pagination.totalPages (119)
2. Fix non-existent chapterMap property access
- chapterMap doesn't exist on PaginationData interface
- Changed to use spineMap which provides the same information
- Calculate chapter start page from spine.pages[0].pageIndex
- Calculate chapter pages from spine.pages.length
TYPE SAFETY IMPROVEMENTS:
3. Remove 'as any' type cast for ebook reader
- Changed: const reader = state.currentReader as any
- To: const reader = state.currentReader
- TypeScript already knows the type after type check
4. Remove 'as any' type casts for comic/manga readers
- Added proper type narrowing with if statement
- Changed: (state.currentReader as any).currentPage
- To: reader.currentPage with type guard
- Improves type safety and enables better IDE autocomplete
IMPACT:
✅ Page counter shows correct total (119 instead of 299)
✅ Chapter progress displays accurately
✅ No TypeScript errors for missing properties
✅ Better type safety prevents similar bugs in the future
✅ IDE autocomplete works correctly for reader properties
This fix resolves the pagination data access issues that caused the
page counter to display incorrect totals and improves overall type
safety in the progress indicator component.
Related to: Page counter display, type safety improvements
CRITICAL BUG FIX:
The updatePageFromScroll() function was calculating page numbers based on
CSS column scroll position and OVERWRITING the EPUB pagination page number.
This caused navigation to fail after the first scroll event.
ROOT CAUSE:
- Function calculated currentPage = Math.floor(scrollTop / viewportHeight) + 1
- This CSS column-based page number replaced the EPUB pagination page number
- Navigation functions expected EPUB page numbers but got CSS column numbers
- Result: canGoNext() checks failed, navigation broke after scrolling
SYMPTOMS:
- Navigation worked initially but stopped after page 2
- Page counter showed incorrect totals (e.g., 1/299 instead of 1/119)
- Cover became blank when navigating back
- State corruption made navigation unpredictable
THE FIX:
- Remove currentPage calculation based on CSS columns
- Only update currentScrollPosition for progress tracking
- Let navigation functions (nextPage/previousPage/goToPage) be the
single source of truth for currentPage
- Emit progressUpdated event with correct EPUB page numbers
ADDITIONAL IMPROVEMENTS:
- Add progressUpdated events to nextPage and previousPage functions
- Ensure page counter updates during navigation
- Remove unused totalPages and contentHeight from event payload
- Add diagnostic logging for canGoNext() failures
IMPACT:
✅ Navigation works correctly beyond page 2
✅ Page counter displays accurate EPUB page numbers
✅ Scroll position tracking works without corrupting navigation state
✅ Cover renders correctly when navigating back
This fix resolves the core issue where scroll tracking and EPUB pagination
were using different coordinate systems, causing state corruption and
navigation failures.
Related to: Scroll tracking, navigation state management
CRITICAL BUG FIX:
The previous pagination system used a linear mapping between word positions
and character positions, which is incorrect for HTML content. This caused
page boundaries to cut through HTML tags, resulting in:
- Missing content (e.g., headings like "Letter 1" skipped entirely)
- Truncated text starting mid-sentence
- Incorrect page breaks that didn't respect HTML structure
Example of the problem:
Plain text: "Letter 1\nTo Mrs. Saville" (25 chars, 5 words)
HTML: "<p>Letter 1</p><p>To Mrs. Saville" (85 chars)
Old calculation: (3 / 5) * 85 = 51 chars (wrong - cuts in middle of tag)
Correct mapping: ~45 chars (respects HTML structure)
SOLUTION:
- Add buildTextNodeMapping() function to traverse HTML DOM
- Track character positions for both HTML source and plain text
- Create mapWordToHtmlChar() to accurately map word positions to HTML positions
- Account for HTML tags, attributes, and element boundaries
TECHNICAL DETAILS:
- Introduce TextNodeInfo interface to track node positions
- Recursively traverse DOM to build accurate character position mapping
- Calculate word positions within each text node separately
- Map word ranges to precise HTML character positions
IMPACT:
✅ Content renders correctly without truncation
✅ Page boundaries respect HTML structure
✅ All text and headings display in correct order
✅ Character positions accurately reflect HTML content
This fix resolves the core issue where pagination was calculated based
on plain text word positions but applied to HTML source, causing
systematic content loss and incorrect page breaks.
Related to: EPUB pagination, content rendering accuracy