Parse calibre's embedded user metadata (custom columns) from the OPF
in foliate-js, store it on BookMetadata.calibreColumns, render the
columns in the book details view, and match column names and values
in the library search so a value like a recommends tag can be found
by typing it.
Closes#4811
In the library list view, surface each book's series and series number on
their own line, in addition to the description. Previously series info was
only visible by grouping by series or opening a book's details.
- Add `formatSeries(series, seriesIndex)` helper ("Series #N", trims the
name, omits a zero/NaN/negative index) with unit tests.
- In list mode, render a dedicated single-line "Series #N" line above the
description when the book has series metadata.
- Clamp every list line (incl. title) to one line and tighten the row gap
to `gap-1` so the extra line fits the fixed-height row without clipping.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(library): refresh book cover after editing metadata
Editing a book's cover in Book Details and saving showed the old cover
until a full reload, in two render paths:
- Library grid: handleUpdateMetadata mutated the book object in place,
so the memoized <BookCover> compared fields off the same (mutated)
reference and skipped re-rendering. Build a new book object via the
new getBookWithUpdatedMetadata helper instead of mutating.
- Book Details view: BookDetailView renders cover/title/author from the
modal's `book` prop, which the parent never re-passed after save.
BookDetailModal now tracks the saved book locally (displayBook) and
renders the view from it.
Adds a unit test for the immutable helper, a BookDetailModal regression
test (edit cover -> save -> view reflects it), and a sample-alice.txt
fixture for TXT import testing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(agent): add cover-refresh stale-render memory
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(cbz,i18n): ComicInfo metadata + CBZ page count + WebDAV i18n
Closes#4253 (ComicInfo.xml not read) and #4255 (CBZ shows "1 page left").
CBZ / ComicInfo (foliate-js submodule + Readest derivation):
- comic-book.js: find ComicInfo.xml in subdirectories too, parse
description / subject / identifier / published / series fields
beyond the prior name+position pair. Series Count populates the
canonical `belongsTo.series.total`; no top-level duplication.
- bookService.ts / readerStore.ts: derive `metadata.seriesTotal`
from `belongsTo.series.total` in parallel to the existing
series / seriesIndex derivation.
- ProgressBar / FooterBar / DesktopFooterBar: drop the hard-coded
`pagesLeft = 1` for fixed-layout books and compute it from
`section.total - section.current`. FooterBar uses
`FIXED_LAYOUT_FORMATS.has(bookFormat)` so CBZ picks `section`
(correct image count) instead of `pageinfo` (locations).
- ProgressBar: switch the remaining-pages text to "in book" for
fixed-layout titles (no chapter structure) and keep
"in chapter" for reflowable books.
WebDAV refactor for translation coverage:
- WebDAVBrowsePane / SyncHistoryPanel called `t(...)` (passed as a
prop) instead of `_(...)`. The i18next-scanner only looks for
`_`, so ~53 strings were unreachable and shipped in English to
every locale. Switched both components to call
`useTranslation()` themselves; helpers that aren't React FCs
take `_: TranslationFunc` so the scanner sees the literal calls.
- WebDAVClient.checkConnection now returns a `code` discriminator
(`SERVER_URL_REQUIRED` / `AUTH_FAILED` / `ROOT_NOT_FOUND` /
`UNEXPECTED_STATUS` / `NETWORK`); raw English `message` is
reserved for the dev console. New `formatConnectError` and
`formatSyncError` helpers in WebDAVForm translate via a switch
where each branch is a literal `_('...')`. Same treatment for
the sync-failure path that previously surfaced raw e.message.
- "Syncing 0 / {{total}}" is now parameterized as
"Syncing {{n}} / {{total}}" with n=0 at startup so the digit
formats naturally and the template can be reused mid-sync.
- "Cleanup · {{count}} book(s)" hard-coded options used unsupported
ternary; rewrote as plural-aware key.
i18n scanner fix (i18next-scanner.config.cjs):
- vinyl-fs walked into directories whose names end in source-file
extensions (Next.js route folder `runtime-config.js/`, Playwright
screenshot folder `*.test.tsx/`) and crashed with EISDIR.
Resolved by expanding globs via `fs.globSync` and filtering to
files only before handing to the scanner.
TypeScript-syntax sites that broke esprima during extraction:
- WebDAVBrowsePane / WebDAVForm: `(e as Error).message` and
`failed[0]!.title` inside `_(..., options)` arguments. Replaced
with `e instanceof Error ? e.message : String(e)` and
`failed[0]?.title ?? ''` — also runtime-safer.
User-facing em-dash cleanup:
- Removed em-dashes from translation keys across SyncHistoryPanel /
WebDAVForm / WebDAVBrowsePane / SyncPassphraseSection / send/page /
replicaCryptoMiddleware / AIPanel. Tagline in `layout.tsx` kept.
Locale translations:
- ~2400 translations applied across all 33 locales for the keys
that were either newly extractable, freshly worded, or
pre-existing but untranslated. Zero `__STRING_NOT_TRANSLATED__`
remain after the run.
Misc:
- next.config.mjs: drop `eslint.ignoreDuringBuilds: true` so build
runs the same lint as CI.
- Collection type: add `total?: string` for ComicInfo series count.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci(test): fix vitest invocation, run with 4 workers
`pnpm test:pr:web` was chaining `pnpm test -- --watch=false`, which
pnpm expanded into:
dotenv -e .env -e .env.test.local -- vitest -- --watch=false
The second `--` made vitest treat `--watch=false` as a positional
file pattern, not a flag. Vitest then fell back to defaults (in CI's
non-TTY env that still meant a one-shot run, so the suite passed),
but the worker pool was effectively serialized for big chunks of the
243-file run — wall ~90 s on a 4-vCPU runner where the parallel-sum
of phases was ~236 s (≈2.6× effective parallelism).
Replace the chained pnpm invocation with a direct call to
`vitest run --maxWorkers=4`, matching the 4 vCPUs the GH Actions
ubuntu-latest runner provides.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace Prettier with Biome for formatting JS/TS/JSX/CSS/JSON. The CI
format check drops from ~23s to ~0.4s.
- Unify config into a single root biome.json (formatter + linter); the
former apps/readest-app/biome.json was linter-only
- Mirror the old .prettierrc.json style: 100 line width, 2-space indent,
LF, single quotes, trailing commas
- Enable the CSS tailwindDirectives parser for @apply in globals.css
- Convert // prettier-ignore comments to // biome-ignore format:
- Root scripts and lint-staged now run biome; apps/readest-app lint runs
`biome lint` (lint-only) so formatting stays a separate CI step
- Drop prettier + prettier-plugin-tailwindcss dependencies
Markdown/YAML are no longer format-checked (Biome does not format them)
and Tailwind class sorting is no longer enforced.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(toc): cache TOC + section fragments per book
Moves the TOC regrouping and section-fragment computation out of
foliate-js/epub.js #updateSubItems into the readest client as
computeBookNav / hydrateBookNav in utils/toc.ts. The result is
persisted to Books/{hash}/nav.json — capturing the book's full
navigable structure (TOC hierarchy + sections with hierarchical
fragments). Compute once, persist locally, hydrate on subsequent
opens. Designed to serve current human-facing navigation (TOC
sidebar, progress math) and future agentic navigation (LLM-driven
seeking by structural location).
Versioned by BOOK_NAV_VERSION for forward invalidation. Existing
books regenerate transparently on next open.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update worktree scripts
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [Issue #1799](https://github.com/readest/readest/issues/1799): Changing the name order when sorting the book by Author by placing the last name first in Arabic, Tibetan, German, English, Spanish, French, Hindi, Italian, Dutch, Polish, Portuguese, Russian, Thai, Turkish and Ukrainian languages
* refactor author sort
---------
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
* Add H and L keyboard shortcuts for back and forward navigation, consistent with Vimium
* Fallback bookmark text to page number if current page has no text, closes#51
* Add delete and edit buttons for highlights and booknotes in the sidebar, closes#57
* Each book now has its own sidebar default tab