Chapter navigation stays on the arrow keys. Bump to v0.2.14; reset BUILD=0 for release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
637 lines
41 KiB
Python
637 lines
41 KiB
Python
"""
|
||
Changelog data for Novela
|
||
"""
|
||
|
||
CHANGELOG = [
|
||
{
|
||
"version": "v0.2.14",
|
||
"date": "2026-06-03",
|
||
"summary": "Reader: Page Up / Page Down now scroll within the page instead of switching chapters — chapter navigation stays on the arrow keys.",
|
||
"sections": [
|
||
{
|
||
"title": "Improvements",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Reader: Page Up and Page Down now scroll within the current page instead of switching chapters. Chapter navigation remains on the Left/Right arrow keys, so accidentally pressing Page Down while reading no longer jumps to the next chapter and loses your place.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.13",
|
||
"date": "2026-06-01",
|
||
"summary": "Backup & Restore overhaul: database-stored books can now be restored individually, a full-database restore from a dump, and token-free local & upload restore — all with automatic pre-restore safety snapshots and rollback. The sidebar build version is now display-only.",
|
||
"sections": [
|
||
{
|
||
"title": "New features",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Backup: database-stored books (those kept inside the database rather than as files on disk) are now included in snapshots and can be restored one by one from the Restore screen, just like file books. They previously existed only inside the full database dump and could not be restored individually. Note: they appear in snapshots created after this update; older database books are recovered via the full database restore below.",
|
||
"Backup: new Full Database Restore — restore the entire database from any Dropbox database dump. This recovers everything, including all database-stored books, reading progress, tags and settings, and is guarded behind a double confirmation.",
|
||
"Backup: token-free restore. Every restore now keeps a local pre-restore safety copy of the database on the config volume, so you can restore or roll back without a Dropbox token. You can also upload a .sql database dump (e.g. one downloaded manually from Dropbox) and restore it directly. Regular scheduled backups stay Dropbox-only.",
|
||
],
|
||
},
|
||
{
|
||
"title": "Improvements",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Backup: restores now take a safety snapshot of the current database first and automatically roll back to it if the dump fails to load, so a failed restore can no longer leave the database empty or broken.",
|
||
"Backup: a full restore tolerates PostgreSQL version differences (such as a newer dump's transaction_timeout setting) instead of aborting on them, while still failing on genuine errors.",
|
||
"Sidebar: the build version indicator at the bottom is now a plain display instead of a clickable link — it exists only to show that the running build has been updated.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.12",
|
||
"date": "2026-06-01",
|
||
"summary": "New Sepia reading theme in the reader, a build-version indicator in the sidebar, and a current-chapter-only scope option for the editor's Find & Replace.",
|
||
"sections": [
|
||
{
|
||
"title": "New features",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Reader: new Sepia theme alongside Dark, for easier long-form reading — a warm paper background with dark brown text instead of light-on-black. A Theme toggle (Dark / Sepia) sits at the top of the reading settings drawer; the choice is saved per device. Text colour is now stored per theme, so each theme keeps its own tint, and the saved theme is applied before paint to avoid a flash.",
|
||
"Sidebar: the running build version is now shown at the bottom of the sidebar (e.g. v0.2.12 for releases, v0.2.12.3 for dev builds) and links to the changelog page.",
|
||
"Editor: Find & Replace gained a Current chapter only option. When checked, search/replace runs against the open chapter instead of every chapter in the book; it stays unchecked by default, so the existing all-chapters behaviour is unchanged.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.11",
|
||
"date": "2026-05-10",
|
||
"summary": "Subheading styling now also applies when the wrapper contains a block element (e.g. <div class=\"subheading\"><p>…</p></div>) — the inner <p>/<h*> color rule no longer wins.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Reader: subheading styling now applies to descendants too — previously the inner <p> (or <h*>) had the same specificity as .subheading but applied more directly, so the wrapper's color was overridden and the paragraph kept the default text color. CSS now reads #chapter-content .subheading, #chapter-content .subheading * { … }, and the same change was made to .chat for safety.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.10",
|
||
"date": "2026-05-10",
|
||
"summary": "Subheading and chat styling now also renders when the wrapper is a <div> instead of a <span> (e.g. when applied around or inside a heading).",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Editor / Reader: subheading (S) and chat (C) styling now also applies when the wrapper is a <div> instead of a <span>. wrapSpan() in editor.js falls back to <div class=\"…\"> whenever the selection contains a block element (e.g. <h1..6>, <p>, <div>) to keep the HTML valid; the previous CSS in reader.html only matched the <span> form, so anything wrapped around or inside a heading silently lost its color/weight. CSS selectors are now class-only (#chapter-content .subheading / .chat), matching both span and div wrappers.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.9",
|
||
"date": "2026-05-09",
|
||
"summary": "Reading position is now monotonic across devices — only advances, never rewinds; explicit Mark as read/unread still resets.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Reader: reading position is now monotonic across devices — when the same book is read on multiple devices, the saved position only advances and never rewinds; previously, opening the book on a device with an older stored position would overwrite the further progress made on another device. The progress endpoint compares the incoming (chapterIndex, scrollFrac) to the stored value and only writes when the new position is strictly further. Explicit Mark as read / Mark as unread still clears the row, so deliberate restarts work as before.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.8",
|
||
"date": "2026-04-22",
|
||
"summary": "Newly converted books from the grabber show up in the New view again.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Grabber: newly converted books now appear in the New view again — both the DB-storage and file-EPUB branches in routers/grabber.py now persist needs_review=True on upsert_book (was False); the New view filters on needs_review, so previously grabbed books never showed up there",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.7",
|
||
"date": "2026-04-22",
|
||
"summary": "Consecutive scene-break images are now collapsed to a single break, and TECHNICAL.md is brought up to date with recent FlareSolverr, Book Info and editor changes.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Break detection: runs of 2+ consecutive scene-break images are now collapsed to a single break — the reader and exported EPUBs no longer show multiple identical break images directly after each other",
|
||
],
|
||
},
|
||
{
|
||
"title": "Internal",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"New helper collapse_consecutive_breaks() in xhtml.py matches 2+ consecutive break-image <center> lines (with optional whitespace between) and replaces them with one; applied in normalize_wysiwyg_html() (editor save path) and in routers/grabber.py on both the preview converted_xhtml and the per-chapter content_html produced during scraping",
|
||
"docs/TECHNICAL.md updated to cover previously missing changes: POST /api/edit/intro/{filename} and the title field on file-EPUB chapter save; FlareSolverr sidecar and BaseScraper.close(); AwesomeDudeScraper uses FlareSolverr; make_epub(include_intro=…) and epub_utils.build_book_info_body_html; grabber DB flow stores Book Info as chapter 0; 'Book Info' h1-strip skip in reader; new env vars (FLARESOLVERR_URL, FLARESOLVERR_TIMEOUT_MS, NOVELA_PORT, ADMINER_PORT); collapse_consecutive_breaks() helper",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.6",
|
||
"date": "2026-04-22",
|
||
"summary": "FlareSolverr sidecar lets the scraper bypass Cloudflare 'Just a moment…' challenges (awesomedude.org), with per-book sessions so chapters after the first are much faster.",
|
||
"sections": [
|
||
{
|
||
"title": "New features",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Scrapers: Cloudflare-protected sites (e.g. awesomedude.org, now fully behind a 'Just a moment…' JavaScript challenge) can be scraped again via a new FlareSolverr sidecar service that solves the challenge in a headless browser; the novela container uses FlareSolverr for both the book-info page and every chapter fetch",
|
||
"Per-book FlareSolverr sessions: the scraper creates one browser session at the start of a book, reuses it across all chapters (Cloudflare cookies stay warm), and destroys it on completion — only the first request pays the full challenge-solve cost and subsequent chapters are much faster",
|
||
],
|
||
},
|
||
{
|
||
"title": "Internal",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"stack/stack.yml adds a flaresolverr service (image ghcr.io/flaresolverr/flaresolverr:latest, internal-only, on novela-net); novela gains FLARESOLVERR_URL=http://flaresolverr:8191/v1 and depends_on: flaresolverr",
|
||
"Host port mappings in stack/stack.yml are now driven by ${NOVELA_PORT} and ${ADMINER_PORT}, defaulted in stack/novela.env to 8099 / 8098, so production stacks can override without diverging from the repo",
|
||
"New helpers in scrapers/base.py: flaresolverr_get(url, timeout_ms=None, session=None) returns a SimpleNamespace(text, url) as a drop-in for httpx.Response attributes; flaresolverr_session_create() and flaresolverr_session_destroy(sid) manage browser sessions; configurable via FLARESOLVERR_URL and FLARESOLVERR_TIMEOUT_MS env vars",
|
||
"BaseScraper gained an async close() method (default no-op) so scrapers can release scoped resources",
|
||
"scrapers/awesomedude.py creates a FlareSolverr session in fetch_book_info, reuses it in every fetch_chapter call, and destroys it in close()",
|
||
"routers/grabber.py wraps all three scraper usages (debug_run, preview, _run_scrape) in try/finally: await scraper.close() so FlareSolverr sessions are always released, even on errors",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.5",
|
||
"date": "2026-04-22",
|
||
"summary": "Book Info page generator in the editor, editable chapter titles for file EPUBs, and Book Info page auto-inserted during DB-storage conversions.",
|
||
"sections": [
|
||
{
|
||
"title": "New features",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Editor: Info page button in the chapter editor toolbar generates a gayauthors-style book-info page (title, author, genres, sub-genres, tags, description, source, updated) and inserts it as the first chapter; empty metadata fields are skipped; no duplicate detection — clicking it again will add another page",
|
||
"Editor: chapter titles are now editable for file-EPUB books (DB books already supported this); the chapter-title input works for both storage types, and for file EPUBs the matching NCX navPoint is updated on save so the table of contents reflects the new title",
|
||
"Grabber: DB-storage conversions now persist the Book Info page as a real stored chapter at index 0, so it is visible in the editor and reader (EPUB-storage conversions continue to produce intro.xhtml via make_epub as before)",
|
||
],
|
||
},
|
||
{
|
||
"title": "Internal",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"New endpoint POST /api/edit/intro/{filename} — for DB books shifts existing chapter_index values up by one via a two-step negation and inserts 'Book Info' at index 0; for file EPUBs writes a new intro_<hex>.xhtml via make_intro_xhtml, adds a manifest item, places the itemref at the start of the spine, and inserts a navPoint at the top of the NCX with renumbered playOrder",
|
||
"POST /api/edit/chapter/{index}/{filename} for file EPUBs now accepts a title field alongside content and updates the matching NCX navPoint text when it changes",
|
||
"make_epub gained an include_intro: bool = True parameter; DB → EPUB export (reader.py) calls it with include_intro=False because the stored chapter 0 is now the single source of truth for the info page",
|
||
"reader.py leading-h-tag stripping (get_chapter_html and DB→EPUB export) is skipped when title == 'Book Info', so the <h1>{book title}</h1> in that chapter's body survives",
|
||
"New helper epub_utils.build_book_info_body_html(title, author, info) returns the inner-body HTML fragment for DB storage; skips empty fields and separates description and source/updated blocks with <hr/>",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.4",
|
||
"date": "2026-04-21",
|
||
"summary": "Backup: separate Scanned vs Uploaded counts, and live phase indicator on the backup page.",
|
||
"sections": [
|
||
{
|
||
"title": "Improvements",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Backup: status and history now clearly distinguish Scanned (library files inspected) from Uploaded (objects actually sent to Dropbox — library + snapshot + pg_dump); previously only the upload count was shown, which was confusing when most files were already deduplicated",
|
||
"Backup page: live phase indicator shown under the Run buttons while a backup is running (scanning library, uploading library objects, uploading snapshot, uploading pg_dump), so it is clear the process is not stuck at N/N while snapshot and pg_dump are uploaded",
|
||
],
|
||
},
|
||
{
|
||
"title": "Internal",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Migration backup_log_scanned_files adds a scanned_files column to backup_log; /api/backup/status and /api/backup/history return uploaded_files and scanned_files (the old files_count key was renamed to uploaded_files)",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.3",
|
||
"date": "2026-04-21",
|
||
"summary": "Backup: Dropbox upload timeout and chunk size tuned to prevent read-timeout errors.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Backup: Dropbox uploads no longer fail with 'HTTPSConnectionPool ... Read timed out. (read timeout=120)' — the Dropbox client timeout was raised from 120s to 300s and the upload chunk size was reduced from 100 MB to 16 MB so each chunk completes comfortably within the timeout window",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.2",
|
||
"date": "2026-04-16",
|
||
"summary": "Four inline formatting buttons in the chapter editor: subheading, chat, indent, and comment block.",
|
||
"sections": [
|
||
{
|
||
"title": "New features",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Editor: four inline formatting buttons added to the chapter editor toolbar — S (subheading, red bold), C (chat, orange), →| (indented paragraph), [ ] (comment block with blue left border); each button wraps the selected text or inserts an empty tag at the cursor; wrap logic automatically uses a <div> when the selection contains block elements to keep the HTML valid",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.1",
|
||
"date": "2026-04-16",
|
||
"summary": "Migration progress now visible in Docker logs at startup.",
|
||
"sections": [
|
||
{
|
||
"title": "Improvements",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Startup: migration progress is now visible in Docker logs — each migration logs whether it was skipped or executed (with duration in ms); a summary line shows either 'all already applied' or how many were executed",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.2.0",
|
||
"date": "2026-04-15",
|
||
"summary": "Deferred chapter save in the editor, startup performance, ETag accuracy, scraper encoding fixes, and internal hardening.",
|
||
"sections": [
|
||
{
|
||
"title": "New features",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Editor: chapter add and delete are now deferred — structural changes are no longer saved immediately; they are applied in the correct order when the Save button is pressed",
|
||
"Operations: GET /health endpoint — returns {\"ok\": true} when the database is reachable; suitable for container health checks and monitoring",
|
||
],
|
||
},
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Editor: adding a chapter to a DB-stored book no longer fails with a UniqueViolation — PostgreSQL was checking the unique constraint on (filename, chapter_index) mid-update; fixed with a two-step index shift",
|
||
"Scraper: Codey's World pages now decode correctly — pages are read as Windows-1252 (cp1252), which correctly maps the 0x80–0x9F byte range; characters like …, ', \", — no longer appear as replacement characters",
|
||
"XHTML conversion: followed by a regular space no longer produces a double space — non-breaking spaces are normalized to regular spaces and consecutive spaces are collapsed; applies to all scrapers",
|
||
],
|
||
},
|
||
{
|
||
"title": "Improvements",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Startup: each database migration now runs only once — a schema_migrations tracking table prevents heavy migrations from re-running on every container restart; startup connection overhead reduced from 37 separate connections to 1",
|
||
"Library API: ETag now reflects changes to tags and reading progress — tag edits and progress updates correctly invalidate the client cache",
|
||
"CBR/CBZ reader: page list is cached per file and modification time — avoids opening the archive twice per page request",
|
||
"Grabber and backup: in-memory job dicts are capped at 50 entries to prevent unbounded memory growth",
|
||
],
|
||
},
|
||
{
|
||
"title": "Internal",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Shared epub_utils.py module eliminates near-identical EPUB helper functions that existed across reader.py, editor.py, and common.py; fixes a double-escaped regex in the old OPF path lookup",
|
||
"pdf_cover_thumb no longer writes a temporary file — cover thumbnail generated fully in-memory, eliminating a race condition under concurrent requests",
|
||
"security.py: hardcoded fallback encryption key removed; raises a clear error at startup when no key is configured; Fernet instance cached per process",
|
||
"builder.py: all explicit conn.commit() calls replaced with with conn: context manager",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.12",
|
||
"date": "2026-04-15",
|
||
"summary": "Font size slider in the reader settings drawer.",
|
||
"sections": [
|
||
{
|
||
"title": "New features",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Reader: font size slider in the reading settings drawer — adjust text size from 80% to 150%; setting is saved per device so iPad and desktop each remember their own preference",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.11",
|
||
"date": "2026-04-13",
|
||
"summary": "Comma-separated values in genre, subgenre and tag inputs are now split into individual tags.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Edit metadata: pasting or typing a comma-separated list in the genre, subgenre or tag input now adds each value as a separate tag instead of one combined tag",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.10",
|
||
"date": "2026-04-12",
|
||
"summary": "Series navigation in the reader, series_volume support for annual comics, archive a series in one click, and a TedLouis scraper fix.",
|
||
"sections": [
|
||
{
|
||
"title": "New features",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Reader: prev/next volume buttons in the header for books that are part of a series — buttons appear automatically when the book has adjacent volumes; tooltip shows the volume number and title; marking a book as read redirects directly to the next volume in the reader instead of the book detail page",
|
||
"Comics: series_volume field for annual series where issue numbers restart each year (e.g. Donald Duck (1982) [15]) — stored in the database and EPUB OPF; displayed as '(year)' after the series name on the book detail page; sorting respects series_volume before series_index; supported in Bulk Import via %series_volume% placeholder and 'Year/Vol.' shared field",
|
||
"Library: archive or unarchive an entire series in one click — 'Archive series' / 'Unarchive series' button in the series detail view; updates all books in the series via a single SQL UPDATE and recalculates sidebar counters without a page reload",
|
||
],
|
||
},
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"TedLouis scraper: title extraction no longer includes the 'Back' button text or the author byline — only direct text nodes of the title heading are used",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.9",
|
||
"date": "2026-04-08",
|
||
"summary": "Five new scrapers (Nifty, codeysworld.org, iomfats.org, tedlouis.com), break image settings, and bug fixes.",
|
||
"sections": [
|
||
{
|
||
"title": "New features",
|
||
"type": "feature",
|
||
"changes": [
|
||
"New scraper: Nifty.org (classic) — scrapes plain-text email-format stories; email headers stripped, boilerplate paragraphs auto-detected and hidden, scene-break patterns converted to break images",
|
||
"New scraper: new.nifty.org — scrapes the Next.js version of Nifty; reads chapter content from RSC payload when the static HTML does not include it; boilerplate detection shared with classic Nifty",
|
||
"New scraper: codeysworld.org — single-file and multi-chapter stories; title and author extracted from heading elements; category from URL path stored as tag; navigation links and audio links stripped from chapter content",
|
||
"New scraper: iomfats.org — all stories are listed on a single author page; provide any chapter URL and the scraper finds the correct story automatically; supports single stories and multi-part series (series name, book title, and series index derived from the page structure)",
|
||
"New scraper: tedlouis.com — all pages use opaque token-based routing (?t=TOKEN); provide the story index URL and the scraper collects all chapter links from the three-column chapter list",
|
||
"Settings: break image upload — upload a custom PNG/JPG/WebP to use as the scene break image in all converted books; stored in the imagestore and applied to both DB-stored and EPUB-format books",
|
||
"Settings: develop mode toggle — shows a DEVELOP banner and updates the page title across all pages when enabled",
|
||
],
|
||
},
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Break images were not displayed in DB-stored books — the image path '../Images/break.png' is a relative EPUB path that does not exist for DB content; DB mode now uses '/static/break.png'",
|
||
"Break images were silently lost during import — the image was decomposed before element_to_xhtml ran, leaving an empty wrapper; the wrapper is now replaced with <hr> so the break is correctly rendered",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.8",
|
||
"date": "2026-04-06",
|
||
"summary": "Cover upload for DB-stored books, and rating moved to the Edit metadata panel.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Library: cover upload now works for DB-stored books — the upload endpoint previously returned 'File not found' because DB books have no file on disk; the cover is now stored directly in the cover cache",
|
||
],
|
||
},
|
||
{
|
||
"title": "Improvements",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Book detail: rating moved from clickable stars to a dropdown in the Edit metadata panel — avoids touch-input issues on iPad where hover state caused all stars to appear filled",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.7",
|
||
"date": "2026-04-06",
|
||
"summary": "Search filter for unread novels/shorts, Dropbox chunked upload fix, and underscores in new filenames.",
|
||
"sections": [
|
||
{
|
||
"title": "New feature",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Search: filter on unread novels or unread shorts — a second toggle row (All / Unread novels / Unread shorts) restricts results to books with no reading history; filter is preserved in the URL",
|
||
],
|
||
},
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Backup: files larger than 148 MB now upload correctly — chunked upload session (100 MB per chunk) replaces the single-call upload that hit Dropbox's payload size limit",
|
||
],
|
||
},
|
||
{
|
||
"title": "Improvements",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"File paths: spaces in new filenames are now replaced with underscores (publisher, author, title, series segments); series separator changed from ' - ' to '_-_'",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.6",
|
||
"date": "2026-04-05",
|
||
"summary": "Bug fixes: double chapter titles in exported EPUBs, and authors/publishers with only archived books now remain visible.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Export EPUB: double chapter titles fixed — same heading-stripping logic as the reader now applied before passing content to the chapter builder",
|
||
"Library: authors and publishers with only archived books now remain visible in the Authors and Publishers list views",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.5",
|
||
"date": "2026-04-04",
|
||
"summary": "Bug fixes: double chapter titles in pandoc-style EPUB content, and search now requires words in order (phrase match).",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Reader: double chapter titles for pandoc-converted books — headings wrapped in a <section> element were not stripped by the previous regex; now also removes the first heading found directly inside an opening <section> or <div>",
|
||
"Search: multi-word queries no longer match chapters where the words appear far apart — switched to phraseto_tsquery so all words must appear in order",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.4",
|
||
"date": "2026-04-04",
|
||
"summary": "Bug fixes: double chapter titles in the reader for DB-stored books, and archived books now shown in author/publisher detail with an indicator badge.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Reader: double chapter titles in DB-stored books — the chapter endpoint now strips all leading headings from stored content before prepending its own chapter title; affects books scraped before front-matter stripping was added",
|
||
"Library: archived books were missing from author and publisher detail views — detail views now include all books (active and archived); archived books have a badge on their cover so they remain distinguishable",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.3",
|
||
"date": "2026-04-03",
|
||
"summary": "DB-stored books: chapters stored in PostgreSQL with full-text search, EPUB conversion, export, and a storage toggle in the grabber.",
|
||
"sections": [
|
||
{
|
||
"title": "New feature",
|
||
"type": "feature",
|
||
"changes": [
|
||
"DB-stored books: scraped books are now stored as chapters in PostgreSQL instead of EPUB files on disk — full-text search, content deduplication, and backup coverage are all handled automatically",
|
||
"Grabber stores chapters in book_chapters and images in a content-addressed imagestore (sha256-based, automatic deduplication across all books)",
|
||
"EPUB-to-DB conversion: Convert to DB button on any EPUB book detail page — extracts chapters, migrates all metadata and child rows (tags, progress, bookmarks, cover), removes the EPUB file",
|
||
"DB-to-EPUB export: Export EPUB button on DB-stored books — builds and streams a standards-compliant EPUB without writing a file to disk",
|
||
"Full-text search (/search): searches across all DB-stored chapter content via PostgreSQL FTS (tsvector / plainto_tsquery), returns highlighted snippets with direct links to the chapter position in the reader",
|
||
"Chapter editor supports DB-stored books: Monaco-based editor reads and writes book_chapters directly; chapter titles editable inline; title-only changes correctly included in Save All",
|
||
"Grabber: storage toggle on the Convert page — choose between DB storage and EPUB file before converting",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.2",
|
||
"date": "2026-04-02",
|
||
"summary": "Restore functionality on the Backup page.",
|
||
"sections": [
|
||
{
|
||
"title": "New feature",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Restore functionality on the Backup page: browse any available Dropbox snapshot, see which files are currently missing from disk, and restore individual books or a selection back to the library — file is written to disk and immediately re-indexed",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.1",
|
||
"date": "2026-03-31",
|
||
"summary": "Bug fixes, volume-aware duplicate detection, shared code cleanup, and a new Changelog page.",
|
||
"sections": [
|
||
{
|
||
"title": "Bug fixes",
|
||
"type": "bugfix",
|
||
"changes": [
|
||
"Duplicates view crashed on load due to a TypeError (g.books.length was undefined); counter was stale and the view never rendered",
|
||
"Duplicate detection was too aggressive: different volumes of the same series (same title + author, different volume) were incorrectly grouped as duplicates — now keyed on title + author + volume",
|
||
"Grabber preload: same volume-aware fix — only flags a duplicate when title, author, and volume all match; falls back to title + author when no volume is known",
|
||
"Bulk Import duplicate check: different volumes of the same series are no longer flagged as duplicates",
|
||
],
|
||
},
|
||
{
|
||
"title": "Improvements",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Search changed from search-as-you-type (250 ms debounce) to Enter-to-search — prevents the iPad keyboard from locking up on large collections",
|
||
"CBR reader: archive format now detected via magic bytes instead of file extension — .cbr files that are actually ZIP or 7-zip archives open correctly; added 7-zip support via py7zr",
|
||
"Docker: replaced unrar-free with proprietary unrar (RARLAB v6.2.6) — fixes failures on RAR archives using newer compression methods",
|
||
],
|
||
},
|
||
{
|
||
"title": "New feature",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Changelog page (/changelog): structured release history with version, date, and categorised change lists",
|
||
],
|
||
},
|
||
{
|
||
"title": "Code quality",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Shared CSS (theme.css): single :root block with all global CSS custom properties; loaded on every page — no more duplicate inline :root blocks across templates",
|
||
"Shared JS (books.js): book helpers (bookTitle, bookAuthor, bookGenres, bookSubgenres, bookPlainTags, filterBooks) and search input wiring extracted into one shared file",
|
||
"Shared JS (conversion.js): SSE/EventSource logic (connectConversionStream, addLog) extracted from Convert and Grabber pages into one shared file",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
"version": "v0.1.0",
|
||
"date": "2026-03-29",
|
||
"summary": "First release of Novela: a self-hosted personal library for EPUB, PDF, CBR, and CBZ files.",
|
||
"sections": [
|
||
{
|
||
"title": "Library",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Grid and List view for all books and New books, with column visibility filter and persistent view mode",
|
||
"Sidebar navigation: All books, Want to Read, New, Incomplete, Series, Authors, Publishers, Archived, Bookmarks, Rated, Duplicates, Statistics — all with live counters",
|
||
"1–5 star ratings stored in the database and written back to EPUB OPF and CBZ ComicInfo.xml",
|
||
"Publication status: Complete, Ongoing, Temporary Hold, Long-Term Hold",
|
||
"Status and want-to-read badges on grid covers, always readable regardless of cover colour",
|
||
"Duplicate detection: groups books by title and author with a sidebar counter",
|
||
"Incomplete view: all non-archived books where publication status is not Complete",
|
||
"Rated view: non-archived books with a star rating, sorted by rating",
|
||
"Bulk delete in All books List view with multi-select and Shift+click range selection",
|
||
"Disk usage warning in sidebar (amber ≥ 85%, red ≥ 95% or low free space)",
|
||
"Autocomplete for Author, Publisher, and Series in the book edit panel",
|
||
"Series volume suffix support (e.g. 21a, 21b) and volume 0 for prequels and specials",
|
||
"Cover upload for EPUB books with cover cache for fast subsequent loads",
|
||
],
|
||
},
|
||
{
|
||
"title": "Reader",
|
||
"type": "feature",
|
||
"changes": [
|
||
"EPUB reader with chapter navigation, scroll progress, and bookmarks",
|
||
"PDF reader with page-image rendering and page navigation",
|
||
"CBR/CBZ reader with page-image rendering; format detection via magic bytes (supports ZIP, RAR, and 7-zip archives)",
|
||
"Reader text colour: 5 warm-tone presets, persisted per browser",
|
||
"Content width slider (30–100 vw), persisted per browser",
|
||
"Bookmarks: save position with optional note; navigate back via sidebar or bookmark list",
|
||
],
|
||
},
|
||
{
|
||
"title": "Import & Convert",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Single-file import: drag-and-drop or file picker for EPUB, PDF, CBR, CBZ",
|
||
"Bulk Import: batch import with %placeholder% filename pattern parsing, shared metadata, live preview table, and duplicate detection",
|
||
"Convert: scrape web fiction and convert to EPUB; warns if title and author already exist in the library",
|
||
"Grabber with credentials manager for site-specific login",
|
||
],
|
||
},
|
||
{
|
||
"title": "Book Builder",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Create EPUB books from scratch via a WYSIWYG editor",
|
||
"Chapters with contenteditable editing; toolbar: bold, italic, underline, blockquote, author note, scene break, normalize",
|
||
"Autosave every 30 seconds and Ctrl+S; publish produces a standards-compliant EPUB 2.0 added directly to the library",
|
||
],
|
||
},
|
||
{
|
||
"title": "Following",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Track external author URLs on the Following page",
|
||
"Two tabs: Following (authors with URL set) and All Authors",
|
||
"Inline URL editing with Enter/Escape support; Visit opens URL in a new tab",
|
||
"Sidebar counter shows number of followed authors",
|
||
],
|
||
},
|
||
{
|
||
"title": "Backup",
|
||
"type": "feature",
|
||
"changes": [
|
||
"Dropbox backup with versioned snapshots and object-store deduplication",
|
||
"OAuth2 refresh token flow (does not expire); legacy access token supported as fallback",
|
||
"Configurable backup root, snapshot retention, and scheduled interval",
|
||
"Live backup progress in sidebar (file count and phase); backup status indicator with time-ago",
|
||
"PostgreSQL dump included in each backup run",
|
||
],
|
||
},
|
||
{
|
||
"title": "Performance",
|
||
"type": "improvement",
|
||
"changes": [
|
||
"Library loads instantly for large collections: ETag 304 Not Modified, lazy cover loading via IntersectionObserver, single DOM pass rendering, SQL tag aggregation",
|
||
"Fast-path API (database-only); full disk rescan only on demand",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
]
|