strings.Title has been deprecated since Go 1.18 because it does not
handle Unicode properly. Replace it with cases.Title from
golang.org/x/text which correctly handles language-specific title
casing. Applied to breadcrumb generation and document title formatting.
Simplify pointer creation in media scanner validation messages and worker
job timestamps by using inline new() instead of local variable + address-of.
In media_scanner.go this cleans up three validation error message returns
(manga/comics library format checks). In worker.go it simplifies StartedAt
and CompletedAt timestamp assignments.
Simplify pointer creation across kobo, koreader, and queue handlers by
replacing the two-step pattern (assign to local, then take address) with
inline new() calls. This reduces verbosity without changing behavior:
Before:
remaining := int(a - b)
pagesRemaining = &remaining
After:
pagesRemaining = new(int(a - b))
Covers page calculations, chapter/progress fields, UUID formatting,
and timestamp string conversions.
Replace the original 5-theme allowlist (light, sepia, dark, night,
high-contrast) with a richer 20-theme palette organized into tonal
families: neutrals (light, paper, slate, oled), warm tones (sepia,
parchment, warm, candlelight), cool tones (azure, sky, arctic, frost),
and evening tones (dusk, sunset, twilight, forest, moss, solarized).
The backend validator in UpdateSettings now accepts all 20 theme names,
and the frontend Tailwind build is updated to include the new theme CSS
variables and preflight reset.
- Remove Playwrite NZ Guides test font and all references (FONT_MAP,
FONT_FILES, reader-fonts.css, dropdown option, font files)
- Move settings panel from left sidebar to right sidebar (left sidebar
now only contains TOC)
- Move Restore Defaults button from top bar icon to a styled button
inside the settings panel, side by side with Done button
The paginator renders inside a sandboxed iframe that blocks @font-face
URL fetches. Fonts were never loading — all font-family rules fell back
to the generic 'serif' system font, making every reading font identical.
Fix: fetch font files on the parent page, create blob: URLs via
URL.createObjectURL(), and use those blob URLs in the @font-face rules
injected into the iframe via setStyles(). Blob URLs are always
same-origin with the creating document, so the sandboxed iframe can
access them with allow-same-origin.
Also added Playwrite NZ Guides as a test font for verifying font
switching works.
Fonts weren't loading because the paginator renders inside a sandboxed
iframe. @font-face declarations in the parent page's CSS are invisible
to the iframe's document. Even injecting @font-face rules via
setStyles() may not trigger font loading in sandboxed iframes.
Fix: inject a <link> to reader-fonts.css directly into the iframe's
document on each section load, so @font-face declarations are parsed
in the iframe's own document context where font-family rules can
reference them.
Also:
- Remove foliate-themes.css entirely (no longer needed)
- Set viewport background color directly via JS using THEME_COLORS map
- Remove reading theme CSS classes from viewport element
background: none on html/body exposed the iframe's default black
background in gaps around the content. Now uses background-color
matching the theme color, and stops forcing background on body *
which caused black bars around page margins.
The paginator renders book content inside a sandboxed iframe within a
closed shadow DOM. @font-face declarations from the parent page's
reader-fonts.css are NOT available inside the iframe's document context.
All font-family rules fell back to the generic 'serif' system font,
making every reading font look identical.
Fix: prepend all @font-face declarations (Literata, Crimson Pro,
Source Serif 4, EB Garamond, Libertinus Serif, Noto Serif, Charis SIL,
IBM Plex Serif) into the CSS string returned by getCSS(), so they're
injected into the iframe via renderer.setStyles().
Comic Sans MS is a system font not available on Linux. The cursive
fallback rendered as a script font, giving false negatives. All 8
loaded reading fonts are serif fonts loaded via @font-face, so they
intentionally look similar — font switching is confirmed working.
Studied grimmory-tools/grimmory's ebook-reader style.service.ts and adopted
their approach:
- Replace CSS custom property resolution (getComputedStyle) with a hardcoded
THEME_COLORS map containing concrete fg/bg/link values for all 18 themes
in both light and dark modes. No more variable resolution failures.
- Font family now targets body + body * with !important, overriding book CSS
on every element (matching grimmory's approach).
- Colors use grimmory's aggressive pattern:
html, body { color: ... !important; background: none !important; }
body * { color: inherit !important; background-color: ... !important; }
This forces reading theme colors on ALL book elements, overriding inline
styles and book stylesheets.
- Line height uses !important on p, li, blockquote, dd to override book CSS.
- Removed fragile getComputedStyle calls entirely. getCSS() now receives
themeName and themeMode parameters for direct color lookup.
- Add Comic Sans MS as a test font option to verify font switching works
- Add !important to background-color and color in getCSS() to prevent
book CSS from overriding user's reading theme colors
- Expand font size range from 12-24px to 10-40px, bump default to 18px
- Expand line height range from 1.0-2.5 to 0.8-3.0
- Add restoreDefaults() method that resets reading theme, font, size,
line height, and justify/hyphenate to sensible defaults
- Add ↩️ restore defaults button in top bar underneath the settings gear
Two root causes fixed:
1. Reading theme CSS variables were on document.body, leaking font/color
into chrome UI. Now scoped to #reader-viewport so chrome keeps its own
theme (system font, --text-primary colors) while the reading area uses
reading theme colors/background.
2. getCSS() never received font family, font size, or line height settings.
The settings UI (dropdowns, sliders) saved values but they were never
injected into the book's shadow DOM. Now getCSS() accepts all four
settings and generates proper CSS rules for them.
Changes:
- Wrap foliate-view in #reader-viewport div (absolute positioned between
chrome bars)
- getCSS() reads computed style from #reader-viewport, not document.body
- getCSS() params expanded: fontFamily, fontSize, lineHeight, justify,
hyphenate (removed unused 'spacing')
- Added FONT_MAP to translate setting keys to CSS font-family values
- applyTheme() targets #reader-viewport instead of document.body
- Removed dead #reader-viewport typography rules from foliate-themes.css
(shadow DOM doesn't inherit outer styles), kept only background-color
- Position foliate-view with absolute inset-x-0 top-[52px] bottom-[52px]
so book content renders between the fixed header and footer bars instead
of behind them
- Confirmed removal of foliate-themes.css base typography was correct:
body color now comes from chrome theme's --text-primary (light for
dark chrome themes like tokyo-night)
Two fixes:
1. Remove base typography block from foliate-themes.css. The html/body
rules were unlayered CSS that overrode the chrome theme's layered
body styles, causing dark reading theme text colors (--reader-text)
to apply to the outer chrome UI on dark backgrounds. These styles
are only meant for the shadow DOM, which getCSS() already handles.
2. Move missing typography rules (img, blockquote, a, p orphans/widows)
into getCSS() so the shadow DOM still gets them.
3. Add sun/moon emoji indicators to the light/dark toggle switch.
Add explicit light/dark mode toggle switch to the reading theme settings.
The reading mode defaults based on the chrome theme (dark chrome themes
like tokyo-night default to dark reading mode).
- Add readingMode property to readerShell Alpine component
- Add toggleReadingMode() method that toggles dark class on body
- Add detectChromeDarkMode() to infer default from chrome theme
- Update applyTheme() to add/remove dark class and persist reading_mode
- Add toggle switch UI in settings panel (blue pill style, next to
Reading Theme heading)
- Add reading_mode to default settings in settings-manager
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