Closed
Conversation
When manually linking a book to Hardcover, users can now select which edition to use for progress tracking (audiobook, ebook, hardcover, etc.). - Add get_book_editions() method to fetch all editions from Hardcover API - Add /api/hardcover/resolve endpoint with auto-match using ISBN/ASIN/title - Create hardcover_routes.py Blueprint for cleaner code organization - Add edition picker modal with dark theme styling - Support manual URL/ID input as fallback when auto-match fails Closes #83
- Return 502 when ABS metadata fetch fails - Return 404 when book not found on Hardcover - Use explicit None check for pages to preserve pages=0
…ests - Fix get_book_editions() to query release_date (release_year always null) - Extract year from date string with proper null handling - Add test_hardcover_routes.py with 10 tests covering: - /api/hardcover/resolve endpoint (missing params, not configured, 404s, success) - /link-hardcover endpoint (JSON flow, audiobook pages handling) - Year extraction from release_date
…known - Add get_book_author() method to query primary author from contributions - Update /api/hardcover/resolve to fetch and return Hardcover author - Prefer Hardcover author over ABS metadata for consistency - Add 3 tests for get_book_author edge cases
- Add Literata serif font for book titles - Improved layout with format icons for each edition type - Add loading spinner, gradient header, smooth animations - Edition cards show format icons, pages/duration with SVG icons - Better visual hierarchy with book info section and edition label - Refined hover states and selected state styling
- API returns linked_edition_id for existing Hardcover links - Pre-select and sort linked edition to top of list - Add "Linked" badge to indicate currently linked edition - Add Audible format to audiobook icon detection
…cker When a user manually links a different Hardcover book than the auto-matched one, reopening the modal now shows the linked book instead of re-auto-matching from ABS metadata.
Reduces "Unknown" format labels by inferring format when edition_format and physical_format are null: - audio_seconds > 0 → "Audiobook" - pages > 0 → "Book"
Feature: Unified SQLite Database Architecture & Sync Hardening
fix(abs-client): hardcode 30s timeout to prevent hangs
fix(smil): use word boundary matching in front matter filter
* feat: Implement Ebook Direct Match Priority Chain - Added CWAClient for OPDS support - Updated ABSClient for ebook search/download - Refactored LibraryService to acquire_ebook with priority logic - Added CWA settings to UI - Integrated into Sync and Background jobs
* fix: remove unused polisher normalization and tune alignment sensitivity * feat: implement alignment backfill strategy and fix smil extraction logic * feat: add smil duration failsafe to reject incomplete transcripts * fix: save ebook_filename to prevent cache cleanup deletion * fix: route DB_MANAGED books to AlignmentService in ABS Sync Client
The filesystem and Booklore scans in _try_find_epub_by_hash() were creating new KosyncDocument records for hashes that already existed in the DB (from device syncs). session.merge() then overwrote the existing percentage, progress, device, and linked_abs_id fields with defaults, resetting reading progress to 0%. Now checks for an existing record by hash before creating a new one, and only updates the metadata fields (filename, mtime, source).
Deduplicates the check-then-update-or-create pattern from the filesystem and Booklore scan paths in _try_find_epub_by_hash().
fix(kosync): prevent auto-discovery from overwriting progress data
… service, sync clients, UI templates, and database migrations.
…vices, and introduce web server.
…elines
- Replace single-rename atomic move with a two-step transfer: cross-device
shutil.move into a hidden .staging_ folder on the Storyteller volume, then
instant same-filesystem rename to reveal the folder to the scanner.
- Stage files in PROCESSING_DIR instead of directly inside the Storyteller
library, preventing the scanner from seeing partial content.
- Add hidden_staging_dir cleanup on transfer failure.
- After UUID detection, add a settle delay (30s for manual forge, 60s for
auto-forge) before breaking the poll loop, giving Storyteller time to
complete internal EPUB linking before trigger_processing is called.
- Fall back to b.get('id') when b.get('uuid') is absent in both poll loops.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves conflicts caused by dev's forge hardening patches (ba718ef) being applied to web_server.py after that logic had already been extracted into forge_service.py in this branch. All fixes from ba718ef (staging isolation, cross-device move, UUID fallback, settle delay) are fully covered by the Two-Step Atomic Transfer implementation in src/services/forge_service.py. - web_server.py: kept feature branch version (forge logic delegated to ForgeService) - BRANCH_STATUS.md: removed (matches dev's intent from dafd0fa) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…wnload After Storyteller artifact is downloaded and sanitized, _auto_forge_background_task now extracts the SMIL transcript and stores a BookAlignment record before marking the book active. This ensures alignment_service.get_time_for_text is immediately usable for sync requests without requiring a separate manual sync job. - ForgeService.__init__ accepts transcriber and alignment_service dependencies - EXTRACT & ALIGN block: fetches ABS chapters, extracts book text from artifact, calls transcribe_from_smil, then align_and_store; both failures raise exceptions so the book falls into error state rather than going active with no alignment map - di_container.py: injects transcriber and alignment_service into ForgeService provider Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
forge_service referenced transcriber before it was declared in the class body, causing a NameError at container class definition time. Moved the transcriber Singleton to before forge_service to fix evaluation order. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After _forge_background_task detects the Storyteller readaloud EPUB and completes the safety delay, it now runs the EXTRACT & ALIGN block before cleaning up source files. Uses readaloud_files[0] as the completed artifact, fetches ABS chapters, extracts book text, runs transcribe_from_smil, and calls align_and_store. Failures are logged but do not interrupt cleanup, so source files are always removed regardless of alignment outcome. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [Feature] Auto-Forge Pipeline & Match Refactor
* feat: Two-Step Atomic Transfer and database settle delay in forge pipelines
- Replace single-rename atomic move with a two-step transfer: cross-device
shutil.move into a hidden .staging_ folder on the Storyteller volume, then
instant same-filesystem rename to reveal the folder to the scanner.
- Stage files in PROCESSING_DIR instead of directly inside the Storyteller
library, preventing the scanner from seeing partial content.
- Add hidden_staging_dir cleanup on transfer failure.
- After UUID detection, add a settle delay (30s for manual forge, 60s for
auto-forge) before breaking the poll loop, giving Storyteller time to
complete internal EPUB linking before trigger_processing is called.
- Fall back to b.get('id') when b.get('uuid') is absent in both poll loops.
* feat: generate alignment map in auto-forge pipeline after artifact download
After Storyteller artifact is downloaded and sanitized, _auto_forge_background_task
now extracts the SMIL transcript and stores a BookAlignment record before marking
the book active. This ensures alignment_service.get_time_for_text is immediately
usable for sync requests without requiring a separate manual sync job.
- ForgeService.__init__ accepts transcriber and alignment_service dependencies
- EXTRACT & ALIGN block: fetches ABS chapters, extracts book text from artifact,
calls transcribe_from_smil, then align_and_store; both failures raise exceptions
so the book falls into error state rather than going active with no alignment map
- di_container.py: injects transcriber and alignment_service into ForgeService provider
* fix: move forge_service provider after transcriber in di_container
forge_service referenced transcriber before it was declared in the class
body, causing a NameError at container class definition time. Moved the
transcriber Singleton to before forge_service to fix evaluation order.
* feat: add alignment map extraction to manual forge cleanup monitor
After _forge_background_task detects the Storyteller readaloud EPUB and
completes the safety delay, it now runs the EXTRACT & ALIGN block before
cleaning up source files. Uses readaloud_files[0] as the completed artifact,
fetches ABS chapters, extracts book text, runs transcribe_from_smil, and
calls align_and_store. Failures are logged but do not interrupt cleanup,
so source files are always removed regardless of alignment outcome.
Previously, _run_alembic_migrations() logged the failure traceback at DEBUG level, causing silent schema mismatches (e.g. missing original_ebook_filename) when migrations failed undetected. This change logs the current DB revision before upgrading, elevates the success log to INFO, and promotes the failure traceback to ERROR so root causes are always visible at the default log level. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix/migration sync issue
[Db] Enforce Alembic Migrations & Schema Check
Fix(Database): Add safety migration for original_ebook_filename column
* fix(forge): increase auto-forge timeout to 20m for large library scans * [Feature] Path-based book detection for Forge pipeline * [Fix] Path detection now checks nested ebook/audiobook objects * [Cleanup] Remove schema discovery debug logs from path detection
…ration fix_original_filename
There was a second <head> tag open inside the existing <head> tag that broke rendering on my iPad. Pushing up a quick fix.
Fix nested head tags in index.html
* [Refactor] Standardize logging across all src/ modules * [Refactor] Complete logging standardization — final verification pass Finishes the repository-wide logging refactor across all src/ Python files. This commit covers Group 4 light-touch files and a comprehensive final verification sweep that caught and fixed remaining emoji/level mismatches. Changes: - storyteller_api.py: Full emoji pass, variable quoting - hardcover_client.py: Errors use empty, date-setting info calls use empty - hardcover_routes.py: Empty on all error/warning calls - config_loader.py: Fixed wrong emoji on two error-level calls - database_service.py: Full emoji pass - forge_service.py: Fixed 13 instances of wrong emoji on error/warning level - sync_manager.py: Fixed missing emojis, trailing period, daemon started - transcriber.py: Full error/warning emoji pass, fixed SMIL rejection warning - ebook_utils.py: Full emoji pass across all exception handlers - smil_extractor.py: Emoji fixes, variable quoting - cwa_client.py: Emoji fixes on remaining undecorated calls - kosync_server.py: Emoji fixes on all remaining error/warning calls - hardcover_sync_client.py: Fixed wrong emoji on warning level - migration_service.py: Emoji fixes on all error/warning calls - library_service.py, json_db.py, logging_utils.py: Emoji fixes Verification confirms zero bracket tags, zero level/emoji mismatches, all logger.warning start with the warning emoji, all logger.error start with the error emoji across the entire src/ directory. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * [Feature] Add checkmark badge on selected cards in batch match page * [Feature] Add Storyteller support to batch match (GET search, add_to_queue, process_queue tri-link logic) * [Feature] Unify card layout: resource-grid/resource-card system across match and batch match pages * Delete PLAN_OF_ACTION.md
…, fixes, and maintenance updates.
…anual hash overrides
[Fix] KOSync hash sync: prevent destructive progress push, preserve manual hash overrides
Databases created before Alembic was introduced have a 'books' table
but no 'alembic_version' table. On upgrade, Alembic would attempt to
run initial_database_schema and crash with 'table books already exists'.
- database_service.py: detect legacy DB (books exists, no alembic_version)
and stamp at revision 76886bc89d6e before running upgrade("head"), so
the initial migration is skipped and only newer migrations are applied
- alembic/env.py: add render_as_batch=True to run_migrations_online so
SQLite can safely handle ALTER TABLE in future migrations
- tests: add TestLegacyDatabaseMigration with 4 tests that simulate a
real legacy database and assert no crash, correct stamping, data
preservation, and fresh-install regression guard
[Fix] Prevent legacy database crash on startup via Alembic stamp
* [Fix] Improve ebook title formatting, remove UI truncation, fix card layouts, and enhance queue display ## Changes ### CSS Fixes (both templates) - Remove text truncation CSS properties from `.resource-title`, `.resource-subtitle`, `.audiobook-title`, and `.book-title` - Allow long titles and subtitles to wrap naturally instead of being cut off ### Card Layout Improvements - **Ebook Cards**: Show "Title: Subtitle" on top row, Authors on bottom row (both templates) - **Storyteller Cards**: Show "Title: Subtitle" on top row, Authors on bottom row (both templates) - Update match.html Storyteller loop variable from `book` to `st` for consistency ### EbookResult.display_name Property - Change format from "Author - Title: Subtitle" to "Title: Subtitle - Author" - Return clean title instead of raw filepath for filesystem sources ### Batch Match Queue Enhancements - Add `ebook_display_name` field to queue session storage - Add hidden form input for `ebook_display_name` - Add `data-display-name` attribute to ebook cards - Update `selectEbookCard()` JS function to capture display name - Update `init()` function to set display name on page load - Improve queue display with clean names and Storyteller indicators * [Cleanup] Remove noisy fallback warning for /api/me/progress - Remove unnecessary warning log when /api/me/progress returns 404 - This fallback to /api/me is normal for older ABS versions - Update comment to clarify this is expected behavior * [Feature] Improve dashboard book card display with ABS metadata ## Changes ### Backend Enhancements (src/web_server.py) - Add single ABS metadata fetch at top of index() route (not per-book) - Build `abs_metadata_by_id` lookup dictionary keyed by abs_id - Wrap entire ABS fetch in try/except to prevent dashboard failures - Enrich each mapping dict with `abs_subtitle` and `abs_author` fields - Fields default to empty strings when metadata unavailable ### Frontend Improvements (templates/index.html) - Update book card to show "Title: Subtitle" on top row - Replace filename with author name on sub-row - Fallback to ebook_filename if author metadata unavailable - Remove CSS truncation from .book-title to allow natural wrapping - Rename .book-filename to .book-author with better styling - Increase font size (10px → 11px) and brightness (0.4 → 0.55) ### Performance & UX - Single API call for all ABS metadata (not N+1 queries) - Graceful degradation when ABS metadata unavailable - Consistent card layout with match/batch_match screens - Better book identification with full titles and subtitles
…without restart (#133) Resolved configuration caching bug where ABSClient cached base_url and token at instantiation, preventing web UI settings changes from taking effect until container restart. Changes: - Converted base_url, token, and headers to @Property decorators (dynamic from os.environ) - Added _update_session_headers() helper called before all API requests - Added URL scheme validation to warn about missing http:// or https:// - Matches KoSyncClient pattern for consistency Impact: - Web UI ABS settings changes now take effect immediately (no restart needed) - Fixes "Invalid URL" and 401 auth errors after settings updates - No breaking changes - all existing code continues to work Also includes: - Hardcover token cleanup (strip whitespace and "Bearer " prefix)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.