diff --git a/README.md b/README.md index 24aca6b0..d8328089 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Planned FeaturesScreenshotsDownloads • + DocumentationGetting StartedTroubleshootingSupport • @@ -114,6 +115,12 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion - Linux users can also install [Readest on Flathub][link-flathub]. - Web: Visit and use **Readest for Web** at [https://web.readest.com][link-web-readest]. +## Documentation + +Guides, tutorials, and FAQs for installing and using Readest live in the official documentation: + +📖 **[https://readest.com/docs][link-docs]** + ## Requirements - **Node.js** and **pnpm** for Next.js development @@ -350,6 +357,7 @@ We would also like to thank the [Web Chinese Fonts Plan](https://chinese-font.ne [link-website]: https://readest.com?utm_source=github&utm_medium=referral&utm_campaign=readme [link-flathub]: https://flathub.org/en/apps/com.bilingify.readest [link-web-readest]: https://web.readest.com +[link-docs]: https://readest.com/docs [link-gh-releases]: https://github.com/readest/readest/releases [link-gh-commits]: https://github.com/readest/readest/commits/main [link-gh-pulse]: https://github.com/readest/readest/pulse diff --git a/apps/readest-app/.claude/memory/MEMORY.md b/apps/readest-app/.claude/memory/MEMORY.md index cc9f5e93..526ecbb0 100644 --- a/apps/readest-app/.claude/memory/MEMORY.md +++ b/apps/readest-app/.claude/memory/MEMORY.md @@ -47,6 +47,7 @@ - [Android Open-with intent flow (#4521)](android-open-with-intent-flow.md) — "Open with"/"Send to" pipeline: `NativeBridgePlugin.kt::handleIntent` → `shared-intent` → `useAppUrlIngress` → `useOpenWithBooks` (VIEW=transient→reader, SEND=library+upload). Telegram fails where file-manager works on TWO axes: cold-start delivery (fixed by #4527, on dev NOT released v0.11.4) + foreign-private-file read (Telegram FileProvider non-persistable grant vs shared-storage FUSE real-path). adb MediaStore VIEW repro tests pipeline but CANNOT reproduce the read axis (MANAGE_EXTERNAL_STORAGE bypasses grant) ## Feature Notes +- [Reference Pages (#672+#4542, PR #4549)](reference-pages-672-4542.md) — 'reference' progressStyle from foliate `pageItem`/`book.pageList` (numeric-max total rule, roman-tail safe); per-book `referencePageCount` via skipGlobal save; verification EPUBs + dev-web synthetic drag-drop import trick; locale-tail rebase-conflict recipe (checkout --ours → re-extract → re-translate) - [OPDS Firefox strict-XML parse (#4479)](opds-firefox-strict-xml-4479.md) — MEK feed has junk after ``; Firefox DOMParser → `` (silent back-nav), Chrome lenient; `parseOPDSXML` slices root start→last close tag; jsdom mirrors Firefox; wired into page.tsx + validateOPDSURL + feedChecker (latter also #4181 `looksLikeXMLContent` swap) - [OPDS 2.0 JSON search greyed out (#4502)](opds2-json-search-4502.md) — `isSearchLink` ignored templated `application/opds+json` links → `hasSearch` false → disabled navbar input; add `MIME.OPDS2`+`templated`, `expandOPDSSearchTemplate` (foliate `uri-template.js`), handleSearch OPDS2 branch. Gotcha: `resolveURL` mangles `{?query}` braces — expand template BEFORE resolving - [OPDS HTML description (#4503)](opds-html-description-4503.md) — detail-view descriptions showed raw `

`/`"` tags; aggregator double-escapes `type="text"` summary + `PublicationView` dumped it into unsanitized `dangerouslySetInnerHTML`; fix = `getOPDSDescriptionHtml` (decode-one-level-iff-fully-escaped, then `sanitizeHtml`) @@ -74,6 +75,9 @@ - [Tauri menu append race (#4389)](tauri-menu-append-race-4389.md) — un-awaited `Menu.append()` (async IPC) in `BookshelfItem.tsx` → context-menu items shuffle order every open (native only, invisible in jsdom); fix = single `await Menu.new({ items })` of ordered `MenuItemOptions`; order/inclusion extracted to pure `getBookContextMenuItemIds` for unit testing - [TXT author recognition (#4390)](txt-author-recognition-4390.md) — 【】-titled Chinese web-novels show author missing/garbage; they're TXT→EPUB (title==full filename is the tell, check `txt.ts` not foliate-js); `extractTxtFilenameMetadata` only handled 《》 + greedy header capture grabbed metadata blobs; fix = `parseLabeledAuthor` for any filename + `isPlausibleAuthorName` guard +## Library Architecture +- [Book action platform surfaces](book-actions-platform-surfaces.md) — library context menu is **Tauri-desktop-only** (`hasContextMenu` false on web + iOS/Android); cross-platform book actions go in `BookDetailView`'s icon row. #4543 Goodreads search added both surfaces + a built-in web-search provider for highlighted-text lookup + ## Architecture Notes - foliate-js is a git submodule at `packages/foliate-js/` - Multiview paginator: loads adjacent sections in background, multiple View/Overlayer instances per book @@ -96,3 +100,4 @@ - [Never push on every change](feedback_dont_push_every_change.md) — hold pushes during active bug iteration; commit locally only until user confirms or work hits a clean done-state - [No test seams in production code](feedback_no_test_seams_in_prod.md) — production must never import or call `__reset*ForTests`; cross-module test resets belong in the test file's beforeEach/afterEach - [Dependabot transitive fixes](dependabot-pnpm-overrides.md) — pin patched min-version in `pnpm-workspace.yaml` `overrides:` (NOT package.json `pnpm.overrides`, which pnpm 9+ ignores); watch for existing too-low pins; alert#≠issue# so no `Closes #` (PR #4523) +- [CI/PR delivery + push keepalive](ci-pr-delivery-and-push.md) — package small PRs from a dirty dev tree via temp-index plumbing (no worktree); slow pre-push hook (~55s full suite) + SOCKS-proxy SSH → idle "Broken pipe", fixed with `ServerAliveInterval`; `--no-verify` safe once the hook already passed (always `git ls-remote` to confirm a push landed) diff --git a/apps/readest-app/.claude/memory/android-cdp-e2e-lane.md b/apps/readest-app/.claude/memory/android-cdp-e2e-lane.md index 99c23761..43171f90 100644 --- a/apps/readest-app/.claude/memory/android-cdp-e2e-lane.md +++ b/apps/readest-app/.claude/memory/android-cdp-e2e-lane.md @@ -7,7 +7,7 @@ metadata: originSessionId: 16f94822-04b0-4be3-a47e-8a2e3cab290a --- -New test tier (branch fix/android-hyphen-selection-1553): `pnpm test:android` → `scripts/test-android.sh` → `vitest.android.config.mts` (node env, serial, retry 1) → `src/__tests__/android/*.android.test.ts`. Helpers in `src/__tests__/android/helpers/`: `adb.ts` (tap/longPress/`motionGesture` = one-shell DOWN/MOVE/UP chain), `cdp.ts` (forward `webview_devtools_remote_`, node:http discovery with Host header, `CdpPage.evaluate` async-IIFE), `reader.ts` (fixture open + probes). Soft-skips without adb/device/app. Covers the [[android-hyphen-selection-bounds-1553]] cases: prone long-press → app handles, drag repair clamp, tap dismissal, handle-drag extension, mid-paragraph native handles, cross-page corner-dwell auto-turn. +New test tier (PR #4545, merged 2026-06-12): `pnpm test:android` → `scripts/test-android.sh` → `vitest.android.config.mts` (node env, serial, retry 1) → `src/__tests__/android/*.android.test.ts`. Helpers in `src/__tests__/android/helpers/`: `adb.ts` (tap/longPress/`motionGesture` = one-shell DOWN/MOVE/UP chain), `cdp.ts` (forward `webview_devtools_remote_`, node:http discovery with Host header, `CdpPage.evaluate` async-IIFE), `reader.ts` (fixture open + probes). Soft-skips without adb/device/app. Covers the [[android-hyphen-selection-bounds-1553]] cases: prone long-press → app handles, drag repair clamp, tap dismissal, handle-drag extension, mid-paragraph native handles, cross-page corner-dwell auto-turn. Design principles (per chrox): discover-don't-assume (find a hyphenated on-screen paragraph at runtime, start in main text via `gotoChapter('chapter\\s*4')`), force hyphenation by injecting `p{hyphens:auto!important;text-align:justify!important}` into section docs (app settings irrelevant), poll-don't-sleep (`waitFor`), fixture `sample-alice.epub` opened TRANSIENTLY via MediaStore VIEW intent. diff --git a/apps/readest-app/.claude/memory/android-hyphen-selection-bounds-1553.md b/apps/readest-app/.claude/memory/android-hyphen-selection-bounds-1553.md index f579795f..474694df 100644 --- a/apps/readest-app/.claude/memory/android-hyphen-selection-bounds-1553.md +++ b/apps/readest-app/.claude/memory/android-hyphen-selection-bounds-1553.md @@ -19,7 +19,7 @@ Key facts: - Auto-hyphens show up as separate ~0.3em rects in `Range.getClientRects()` on hyphenated lines — usable as a generated-hyphen detector. - **JS `removeAllRanges()+addRange()` does NOT hide already-visible touch handles synchronously** — the empty selection must commit through one painted frame (double-rAF in the iframe window) before re-adding; then handles stay hidden for all later JS selection updates. -Fix (branch fix/android-hyphen-selection-1553): detection utils in `src/utils/sel.ts` (`isHyphenHandleBugProneRange`, `repairJumpedSelectionRange`, `hasTrailingHyphenRectPattern`); gesture-initial anchor capture + touchend sanitize in `useTextSelector` (repair jumped anchor → suppress handles via empty-commit → `makeSelection(handlesSuppressed)`); `SelectionRangeEditor.tsx` renders custom drag handles (reuses `Handle` + extracted `buildRangeFromPoints`/`getHandlePositionsFromRange` in annotatorUtil) for suppressed selections. Gated to Android app + exact bug condition; flipping to always-custom-handles later = drop the proneness gate. +Fix (**PR #4545, MERGED 2026-06-12**; worktree cleaned): detection utils in `src/utils/sel.ts` (`isHyphenHandleBugProneRange`, `repairJumpedSelectionRange`, `hasTrailingHyphenRectPattern`); gesture-initial anchor capture + touchend sanitize in `useTextSelector` (repair jumped anchor → suppress handles via empty-commit → `makeSelection(handlesSuppressed)`); `SelectionRangeEditor.tsx` renders custom drag handles (reuses `Handle` + extracted `buildRangeFromPoints`/`getHandlePositionsFromRange` in annotatorUtil) for suppressed selections. Gated to Android app + exact bug condition; flipping to always-custom-handles later = drop the proneness gate. Gotcha: `input motionevent DOWN/MOVE/UP` (adb) simulates long-press-drag; `input swipe x y x y 700` simulates plain long-press. diff --git a/apps/readest-app/.claude/memory/book-actions-platform-surfaces.md b/apps/readest-app/.claude/memory/book-actions-platform-surfaces.md new file mode 100644 index 00000000..fdbbb421 --- /dev/null +++ b/apps/readest-app/.claude/memory/book-actions-platform-surfaces.md @@ -0,0 +1,14 @@ +--- +name: book-actions-platform-surfaces +description: Where to add a library book action so it reaches every platform (context menu is desktop-only) +metadata: + node_type: memory + type: project + originSessionId: 4efb7b40-cce1-4742-9730-7e93e643d196 +--- + +The library book **context menu** (`BookshelfItem.tsx::bookContextMenuHandler`, native `Menu.new`) only renders where `appService.hasContextMenu` is true — that is **Tauri desktop only** (`nativeAppService.ts`: `!(ios||android)`). It is **false on web AND on iOS/Android**. So a book action added only to the context menu (+ `getBookContextMenuItemIds` in `libraryUtils.ts`) never reaches phone/web users. + +The cross-platform home for book-level actions is the **`BookDetailView` action-icon row** (`src/components/metadata/BookDetailView.tsx`), shown in `BookDetailModal`, reachable on every platform (BookItem tap → details, `Bookshelf.tsx::handleShowDetailsBook`). That row is `flex-nowrap` inside a fixed `h-32` column and already holds up to ~5 icons (Edit/Delete/Download/Upload/Export) — adding more risks phone overflow; keep additions to one small icon. + +**Rule:** desktop-only fast path → context menu; must reach mobile → BookDetailView (or both). Example: the "Search on Goodreads" feature (#4543) added both — `searchGoodreads` context-menu id + a `FaGoodreads` button in BookDetailView, opening `getGoodreadsSearchUrl` via [[open-external-url-helper]]. In-reader highlighted-text Goodreads search is a built-in [[web-search-provider]] entry instead. diff --git a/apps/readest-app/.claude/memory/ci-pr-delivery-and-push.md b/apps/readest-app/.claude/memory/ci-pr-delivery-and-push.md new file mode 100644 index 00000000..e74abff2 --- /dev/null +++ b/apps/readest-app/.claude/memory/ci-pr-delivery-and-push.md @@ -0,0 +1,26 @@ +--- +name: ci-pr-delivery-and-push +description: Delivering small PRs from a dirty dev tree without a worktree; the slow pre-push hook + proxy SSH-drop and its keepalive fix +metadata: + node_type: memory + type: project + originSessionId: c1097233-8b53-422a-98ec-3f0146f32f6b +--- + +How CI/config PRs get delivered in this repo when `dev` has unrelated uncommitted WIP, and the push gotcha. + +**Packaging a commit onto a fresh PR branch WITHOUT `pnpm worktree:new`** (the worktree script does a full `pnpm install` + `tauri android init` + icon gen — disproportionate for a YAML/package.json-only PR, and you can't `git checkout` a branch in the dev tree because the user's WIP blocks it): +1. Edit the target files in the dev tree (only files NOT in the user's WIP set — verify with `git status --short -- `), `git add` just those, commit on `dev` (mirrors how the user wanted the pin committed). +2. Re-parent onto `origin/main` (or onto the existing PR-branch tip for a fast-forward add) via a temp index — no checkout, no worktree, dev working tree untouched: + ``` + export GIT_INDEX_FILE=$(mktemp); git read-tree + git update-index --cacheinfo 100644,$(git rev-parse HEAD:), # per changed file + TREE=$(git write-tree); unset GIT_INDEX_FILE + NEW=$(git log --format=%B -n1 HEAD | git commit-tree $TREE -p ) + git update-ref refs/heads/ $NEW + ``` +3. Verify `git diff --stat ..` shows ONLY the intended files, then push. + +This is how PR #4547 (pin `android-emulator-runner` + shard `test_web_app`) was built on top of `origin/main` while `dev` carried 49 files of unrelated dictionary/goodreads WIP. + +**Push gotcha (now fixed in `~/.ssh/config`):** `git push` opens the SSH connection BEFORE running the pre-push hook; the husky hook runs the FULL vitest suite (~55s, 5271 tests) + format + lint. The user pushes through a SOCKS proxy (`nc -x 127.0.0.1:8119` → `ssh.github.com:443`), so the idle connection got dropped during the hook → "Broken pipe", ref never transferred (remote stayed at old SHA — always `git ls-remote` to confirm). Fix added: `ServerAliveInterval 15` + `ServerAliveCountMax 60` under `Host github.com`. Also: **`--no-verify` is safe once the hook has already passed** on the same tree — re-running it just re-opens the idle window. See also [[feedback_dont_push_every_change]], [[feedback_use_worktree]]. diff --git a/apps/readest-app/.claude/memory/reference-pages-672-4542.md b/apps/readest-app/.claude/memory/reference-pages-672-4542.md new file mode 100644 index 00000000..75776b41 --- /dev/null +++ b/apps/readest-app/.claude/memory/reference-pages-672-4542.md @@ -0,0 +1,17 @@ +--- +name: reference-pages-672-4542 +description: "Reference Pages progress style (#672 +" +metadata: + node_type: memory + type: project + originSessionId: 2a51b785-c6db-4bab-86f1-4a9b086a0e48 +--- + +PR #4549 (2026-06-12) merged #4542 (manual page count) into #672 (page-list/page-map): `progressStyle: 'reference'` shows physical-book pages in `ProgressBar` + `DesktopFooterBar`. + +- **foliate-js already did the hard part**: `book.pageList` (EPUB3 nav page-list at `epub.js` parseNav; EPUB2 NCX `pageList` — only parsed when there's no navigable nav-doc TOC) and `view.js #pageProgress` emits `detail.pageItem` on every relocate. Readest never consumed it before. Adobe `page-map.xml` is NOT parsed, but books that ship it (Count Zero) also carry the NCX pageList, so it works anyway. Gap: EPUB3 nav-with-TOC-but-no-page-list never falls back to NCX pageList. +- **Total = highest numeric label**, not last entry (a trailing roman-numeral index page like "XII" after p553 would corrupt it — reported in #672 comments). All-roman page lists fall back to entry count. Logic in `getReferencePageInfo` (`utils/progress.ts`). +- **Per-book-only viewSettings field**: `referencePageCount` saved with `saveViewSettings(..., skipGlobal=true)` so a physical page count never leaks into `globalViewSettings` even when the user runs global settings. The merge `{...global, ...perBook}` keeps it. +- **Verification EPUBs** (issue #672 comments, in /tmp/issue672 while it lasts): Caleb's Crossing = EPUB3 nav page-list (419 pp); Count Zero = EPUB2 NCX pageList + page-map.xml (346 pp; `Text/c2.html` starts at `name="22"` — good exact-match oracle). +- **Web-import injection trick for dev-web e2e**: stage the epub in `public/`, then dispatch a synthetic `DragEvent('drop')` with a real `DataTransfer` (files added via `dt.items.add(new File(...))`) on `.library-page` — `useDragDropImport` takes it from there. The chrome-MCP `javascript_tool` has NO top-level await ("await only valid in async functions") and collects returned promises — use an async IIFE writing to `window.__result` and poll. +- **Locale-file rebase conflicts** (every feature PR appends keys to all 33 translation.json tails): don't hand-merge — `git checkout --ours -- public/locales`, re-run `pnpm i18n:extract`, re-run the translation script, `git add`, `git rebase --continue`.