Lead fetches with the primary file, give Book Dock its library's rules and read-along media,
default audiobook fields to sources that carry them, and flag providers that never fill a field.
Joining a format now fills gaps only and respects locks; a move picks its primary as a scan would.
Make the field rules table one grid with each row as a subgrid, so the
merge and status columns size to the widest control instead of fixed
272px and 116px tracks. The four-option genres control and translated
labels now fit, and the global scope drops the empty status gap.
Link two ordinary requests as companions, filed into one book, with a Both search,
grouped list rows, a tabbed release picker and a book-page request for the missing
format. Retire comic requests, keeping old rows readable. File Book Dock covers by the
field rules with a shape check, and re-pick a moved book's primary by its new library.
Pick the face slot from the primary file's medium instead of the library's
ratio, and send that frame per book on cards, detail, notebook labels and
recommendations. Grids keep uniform tiles, and the library setting becomes
Grid shape.
Keep text and narration bookmarks independent when opening and navigating EPUBs.
Use the narrated section for sentence bookmarks and attribute delayed page moves to narration.
Recover stalled WebKit blob frames with a bounded fallback that preserves XHTML location structure.
Cover restoration, narration attribution, and iframe fallback behavior with regression tests.
Closes#1708
Return matching item counts with create and update responses so the sidebar
can display the saved scope count without requiring a page reload.
Reuse the listing count logic and verify visibility and shared sidebar state.
Carry ISBN-10 and ISBN-13 independently from accepted providers through
Auto-fill previews, saves and background refreshes. Preserve existing
identifiers and field locks, validate provider checksums and avoid
combining different editions.
Classify embedded ISBNs correctly and support ISBN-10 fallback in
search, Book Dock confidence and file naming.
Add account-scoped reminders for the metadata fields users care about.
Treat either ISBN as complete, accept a publication year or date, and
skip page-count reminders for audio-only books.
Place reminders after the genre blocklist and allow customization from
the editor without discarding unsaved changes.
Let users inspect missing fields without launching provider searches.
Keep metadata searches manual by default and expose a remembered
opt-in beside Search.
The walker sorted a book's files by file name, so tracks in disc folders that reuse names, such as
CD 1/01.mp3 and CD 2/01.mp3, interleaved in playback, merged chapters, and written track numbers.
Order them by their whole path instead, everywhere a track order is chosen.
A rescan that only reorders a book's existing tracks now moves every audio bookmark and the
listening progress percentage to the same moment of the same track, and rebuilds merged chapters.
Disc folders are never skipped by incremental scans, so existing libraries are fixed by their next
scan.
Also inventory the notebook and dashboard highlights routes in the authorization matrix, and pin the
app locale in two date tests that depended on the machine's locale.
Books often carry several files, such as an EPUB beside a PDF or an audiobook split into tracks.
Write-back reached only the primary file and the audio tracks, so the other formats kept stale
metadata.
Add an opt-in per-library mode that writes every content file of a book. Target selection,
capability reporting and the Files tab now share one role-aware selector, covers resolve per
medium, and results report per-format counts. Read-along EPUBs get their own toggle, off by
default, and their own size limit of 1000 MB, so they no longer follow the EPUB settings.
Live testing on copies of real libraries surfaced write-path bugs, fixed here as well:
- tag-only writes to chaptered M4B files failed because FFmpeg copied the chapter data track
- an undecodable cover failed every file instead of falling back to metadata only
- failure reasons stored the FFmpeg command line and absolute paths; they are now sanitized
- size limits used the stored size, so an unknown size bypassed them; writes now check the disk
- atomic replace dropped the file mode and ownership
- the EPUB temp name failed on long file names and could clash with a user file
- tracks in disc folders got duplicate titles
- a PDF worker that exited without a result hung its write forever; it now fails and times out
- null on a NOT NULL library setting returned 500 instead of 400
Generates migrations 0104 and 0105. Every new API field is optional, and released iOS builds
decode none of them.
Ref #694
Remove redundant destination choices when only one book library or folder is available.
Preserve existing Dock targets and cover loading, selection, and failure states with unit tests.
Closes#1362
Register the identity of delivered bytes so Kobo conversions and audio
removal continue matching their source books in KOReader. Keep download
registration recoverable without allowing database lock waits to stall it.
Preserve manual corrections across cached device identities, make review
semantics explicit, and keep cache invalidation bounded for large libraries.
Add unit, database, lifecycle and scale regressions with one generated migration.
Closes#1697
Convert complete source CFIs on the server to avoid chapter-start resets
and browser-generated locators that disagree with CREngine. Preserve
engine whitespace, Unicode offsets, element anchors and long text nodes.
Handle prefixed OPF namespaces and retry outdated annotation conversions.
Closes#1621Closes#1288
Keep dashboard layouts browser-local by default and let users opt in to account storage.
Group library selection and shelf sync in one dropdown, preserve legacy client settings,
and block sync changes for demo-restricted accounts.
Fixes#1646
The three-column layout left the editor 270px wide at 1280 and broke resolved paths mid-word.
Draw the pattern one folder per line, preview it against long-name books, and list only what
missing metadata changes. Add resolvePlaceholders to the shared resolver to report fallbacks.
Keep TypeScript at 6.0.3 while moving the remaining server and client packages onto their current supported releases.
Allow the PWA asset generator 2 peer range because vite-plugin-pwa still declares the compatible version 1 range.
Keep long request titles from widening the auto-layout table and hiding row actions. Guard the intrinsic-sizing chain with focused component regression tests.
Fixes#1613
Generate RFC 4122 UUIDs with getRandomValues when randomUUID is unavailable so plain HTTP deployments can save playback state and bookmarks.
Fixes#1608
Cut long paths from the start, name layouts and schedules the way the editor
does, and switch to cards when the ledger would scroll sideways. Rows now
toggle automation in place with undo, open recent scan history, show live
scan counts, flag failed or never-scanned libraries, and can seed a new
library from another's settings.
Bump every direct dependency to its newest same-major release, moving the
embedpdf patches and packageExtensions to 2.15.1 and deduping the lockfile.
Hold jsdom at 30.0 because 30.1 breaks vitest's URL.createObjectURL shim,
and skip releases still inside pnpm's minimum release age.
Search and Compare Metadata become one two-pane sheet, shared with the Book Dock. Results are
ranked by match strength beside a field-by-field comparison with Keep/Use per row, word-level
diffs, other sources inline, a cover strip, and picks that survive switching results.
The search runs on open and a single source can be retried. No API change.
Replace tinted modal veils with dedicated scrim tokens so dialogs, sheets, and image lightboxes dim consistently in both light and dark themes. Enforce scrim token usage in style validation to prevent backdrops that wash out in dark mode.
Replace the seven-step wizard with a sheet whose section rail summarizes each
setting. Format priority shows only the formats a library holds, folders are
checked as they are added, and people can be granted access while creating.
Expose media-overlay-aware naming across file creation and delivery paths while preserving existing client contracts. Keep settings router mocks compatible with the application router exports.
Closes#1590
Save the library and folder shown in the metadata sheet before closing it. Avoid duplicate target updates after explicit selection and keep the sheet open when persistence fails.
Fixes#1573
Clients that download an audiobook get its files from the book detail and its playback order from
the manifest, and had to pair the two by position. The two lists are ordered differently, so an
index pairing can attach the wrong file to an asset. Add the book_files id to each manifest asset
so the pairing can be made by id.
The field is additive and the server always sends it. The manifest revision is hashed from file
and chapter data, not from the response, so existing revisions do not change and clients holding
one do not see a 412 from this deploy.
The iOS app consumes this field to pair downloaded files with manifest assets; the web client
does not read it yet, so a grep of the client alone will show it as unused.
Every non-OK playback-state response marked the write dirty and retried it every five seconds,
so a stale manifest (412) or a position past the rounded track duration (400) looped for the rest
of the session. Each attempt also minted a new operationId, which defeated the server's replay
protection, and capturedAt was stamped at flush time rather than when the position was taken.
Capture the timestamp and one operationId with each position and reuse them across retries.
Retry only network failures, 5xx and 429, and drop other rejected writes. On 409, reload the
server state and keep the local write only if it is newer. On 412, hold the write and have the
player reload the manifest, resending once the revision changes. Clamp positions to the track
duration from the manifest.
The web audiobook player shared the reading session composable, which never sent a
sessionType, so the server stored every listening session as a read session and counted
audiobook time as reading in activity stats. Let callers pass an optional sessionType and
have the audiobook view send listen. Other readers keep omitting the field, so their
payloads are unchanged. Existing rows are not backfilled.
Let users hide request entry points and notifications while existing requests continue running. Keep the setting optional so older clients retain their current behavior.
Ref #1476
Allow 0.05% adjustments while preserving existing integer settings and the 98% default. Keep finish detection stable when progress is stored as PostgreSQL real.
* fix(auth): recover from expired edge auth proxy sessions
The service worker answered every page load from the precached app shell, so once an edge auth
proxy's session cookie (e.g. Cloudflare Access, OAuth2 Proxy) expired, the proxy never saw the
navigation and could not redirect to its login page. The app loaded from cache, its API calls were
redirected cross-origin and failed, and it fell back to its own login screen with no way out short
of clearing site data.
Serve navigations network-first and fall back to the precached shell only when offline.
vite-plugin-pwa defaults navigateFallback to index.html, which would register a cache-only route
ahead of this one, so disable it explicitly. Cache only 200 responses so a proxy's redirect is never
stored as the page.
Request API redirects manually so a proxy redirect is detected directly and triggers a full reload,
which the proxy can then send to its login page. Cap these reloads to one per 15 seconds so a reload
answered from cache again surfaces an error instead of looping.
Fixes#642
* fix(auth): harden edge auth proxy recovery against loops and cache growth
Force manual redirects after caller options so no request can opt back into following a proxy
redirect. Skip the reload when session storage is unavailable, since the loop guard cannot work
without it. Drop the navigation network timeout so a slow proxy redirect is never replaced by the
cached page, and cap the app shell cache, which stores one entry per visited URL.
Ref #642
* test(auth): isolate useLoginOptions module state between tests
The composable keeps its cached options, error, and in-flight request at module level, so they
carried over between tests in the file. Reset the module and import it fresh for each test so a
future test cannot pass or fail depending on what ran before it.
Ref #642
* fix(auth): let edge proxy handle sign-in navigations
Keep the offline shell under a distinct precache URL so Workbox cannot serve
root navigations before the proxy sees them. Route public auth requests through
the redirect-aware fetch path so expired edge sessions open reauthentication.
Ref #642
* fix(auth): keep offline shell off root URLs
Prevent Workbox from mapping root query variants to the precached offline shell so expired proxy sessions reach the network.
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Refresh the web access cookie after a media load failure and resume from the last observed position. Limit retries so unrelated media errors surface promptly.
Closes#1536
Add a reusable pagination control and cover Book Dock navigation with focused tests. Align the client PDF dependency and lockfile for clean CI verification.
Use the browser origin when APP_URL points to a local address so setup links work through a public proxy. Preserve public canonical URLs and HTTPS.
Closes#1548
Every format chip, edition row and format menu now builds from shared helpers: content files
only, a read-along EPUB kept apart from a plain one, in the library's format priority, which
gains a read-along entry. Labels, colours and the chip each have one source, and cards, OPDS,
email, Kobo and the dashboard rank files the same way. Book detail file order is unchanged
because iOS builds its track queue from it.
The plugin Set up form reused the new-source editor, which always showed
an Indexer type select listing only torznab and newznab. Changing it
saved a torznab feed under the plugin's name, which is how a MyAnonaMouse
feed ended up pointed at the tracker's website.
"Read from here" and the page start resolved the range to the nearest element with any id and
matched SMIL entries against that id. Storyteller keeps the book's inline elements inside its
sentence spans with their ids intact (Kobo's kobo.N.N, calibre's calibre_link-N), so in those
books the nearest id named no entry and narration restarted at the top of the chapter, while
playback kept highlighting the same sentences correctly.
Start at the first SMIL entry whose element overlaps the range instead, looking each entry up the
way foliate resolves the highlight. Foliate already walks entries in reading order through the
start filter, so the vendored engine is unchanged.
Fixes#1528
Keep one cover slot per medium and show the one each surface needs: the
library's shape for the face, square art on audio screens, the ebook slot
for Kobo and OPDS. Legacy covers convert on upgrade or on first read, and a
write that names no slot keeps working for released iOS builds. Also read
the dev client origin from one constant.
Run the dev API on 6262 and Vite on 6263 with strictPort; production
defaults stay on 3000. Define the dev client origin once and read the
typed appConfig instead of repeating its fallback in every gateway.