6b403d019e
* feat(calibre): add Readest calibre plugin to push books and metadata (#4863) Add apps/readest-calibre-plugin, a calibre GUI plugin that uploads selected books with their metadata into the user's Readest cloud library, modeled on BookFusion's open-source plugin. - Selective manual push from the calibre toolbar with per-book status (uploaded / updated / up to date / failed) and quota handling - Books are content-addressed with the same partial MD5 as the apps, so re-pushing updates the existing entry instead of duplicating; metadata edits re-push without re-uploading the file - Metadata mapping includes series, tags, identifiers and optional calibre custom columns; carries over server-side fields (progress, reading status, grouping, cover) that POST /sync would null out - Auth mirrors readest.koplugin and the desktop app: email/password plus browser OAuth (Google/Apple/GitHub/Discord) through a localhost callback server with the fragment-to-query relay - Pure-logic modules (api.py, wire.py, oauth.py) are calibre-free and covered by 56 unit tests (make test); make zip builds the plugin Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(release): package readest-calibre-plugin in releases Mirror the KOReader plugin packaging: a build-calibre-plugin job stamps PLUGIN_VERSION in __init__.py with the release version from apps/readest-app/package.json, builds the zip via make, and uploads Readest-<version>.calibre-plugin.zip to the GitHub release. The version committed in git stays a development placeholder. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(agent): add calibre plugin project memory Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(calibre): embed metadata in OPF and dedupe by calibre uuid Rework book identity so metadata can be embedded into the uploaded file without creating duplicates, as requested in review: - Embed calibre metadata (including custom columns) into a temporary copy of the book file at upload time via calibre's set_metadata; the library file is never modified - Dedupe by the calibre book uuid carried in the entry's metadata identifier, which survives file-byte changes, with a live-row preference when both hash and uuid match rows - Detect file content changes via calibreSourceHash, the raw library file fingerprint stored in the pushed metadata, so detection works from any machine; v1 rows fall back to book_hash which equals the raw hash for them - A changed file now replaces the old entry in one sync push (new row with carried-over reading status, grouping, progress and created date, plus a tombstone for the old row) and deletes the old cloud files to reclaim quota - Metadata-only edits still update the library entry without re-uploading the file Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(calibre): set copyright holder to Bilingify LLC Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
16 KiB
16 KiB
Readest Project Memory
Key Reference Documents (aggregators)
- Bug Patterns · CSS & Style — EPUB CSS + style.ts · TTS
- Layout & UI — insets/z-index · Platform Compat — Android/iOS/Linux/macOS · Annotator & Reader
Safety & Security
- In-place delete wiped originals — never
fs.removeFileexternalsource; - Backup zip Windows paths (#4703) —
\entry names broke restore; normalize - download_file scope Android (#4639) — strict
is_allowedbroke - Security advisories 2026-06 — 4 GHSA #4638 (OPDS SSRF, storage key, Stripe
Paginator & Scroll
- Reading ruler line-aware — snaps to real lines; iframe frame-offset map
- TOC expand + auto-scroll — #4059 collapse-default breaks scroll-to-current
- BooknoteView auto-scroll (#4352) — reload via
initializedref; - TOC current-position row — synthetic row; insert AFTER to keep flatItems index
- Swipe page-turn bg flash — static
#background;computeBackgroundSegmentsper-rAF - Paginated texture occlusion (#4399) — opaque bg occludes texture; `hasTexture
- Background overflows column (#4394) — bg bled into gutter; clamp to
- Inline-block column overflow — body can't fragment;
#demoteUnfragmentableBoxes - FXL fit-width scroll reset (#4683) — scrollTop not reset;
- PDF spread 1px seam (#4587) — fractional dpr truncates; pin
canvas.style=viewport - PDF scrolled wheel double (#4727) — drop manual
scrollByover native - Scrolled header title center (#4436) — view covering
renderedStart + size/2 - Duokan fullscreen cover scroll — #4379 cover collapses; gate on
this.#column - TOC table heading clip (#4439) — #4400 scroll-wrapper
overflow:autoclips - Page-turn bg-replace reflow (#4785) —
#replaceBackgroundrebuilt context per
Critical Files (Most Bug-Prone)
src/utils/style.tsEPUB CSS hub ·packages/foliate-js/paginator.jslayout/image/bg ·src/services/tts/TTSController.tsTTS state machinesrc/hooks/useSafeAreaInsets.tsinsets ·src/app/reader/components/FoliateViewer.tsxview orchestration ·.../annotator/Annotator.tsxannotation lifecycle
Sync Notes
- Grimmory native sync — Booklore-fork REVERTED; id by ISBN/ASIN + koreader-hash
- KOSync: CFI spine resolution convert via CFI's own spine; connect() false-positive #4692
- Empty-start CFI sync —
isMalformedLocationCfi→ discard synced value - Custom fonts vanish on sync (#4410) — CRDT remove-wins; re-import
- koplugin: note deletion
recordDeletiontombstone; stats #4666 statistics.sqlite3 delta LuaSettings; bulk download #4751 - Statusless books re-pin top (#4677) —
(a??null)!==(b??null) - Pull cursor via synced_at (#4678) — books
synced_at+ BEFORE trigger - WebDAV metadata sync (#4756) — PR #4776; LWW on
book.updatedAt+ - File-sync refactor (#4784) —
FileSyncEngine/FileSyncProvider/merge.ts; - Third-party library auto-sync (#4835) —
useLibraryFileSyncparity w/ - WebDAV connect nullified (#4780) — catch+finally saved STALE closure;
- WebDAV credential sync (#4810) —
webdav.*missing from - Multi-window settings clobber (#4580) — stale window overwrites shared
- Google Drive: research Drive as
FileSyncProvidertoken-persist+resumable; multi-PR status - Hardcover progress edition_id (#4792) —
edition_idfell back to
Build, Testing & CI
- format:check separate gate —
pnpm format:checkown gate before push - Worktree rebase submodule drift — rebase leaves foliate-js submodule old; `git
- Android CDP e2e lane —
pnpm test:androidadb+CDP; CI KVM emulator - CDP Android WebView profiling — adb+CDP JS probes; locked-device freezes fetch
- Tauri Rust↔JS parser parity — #4369 cross-check;
dcterms:modified→published - TTS browser e2e harness — seed
settings.globalViewSettingsor getMergedRules crashes - TTS paragraph+RSVP sync (#3235) — TTS-is-clock; overlay CLONE via CSS Custom
- fastlane App Store — keep
APPLE_API_KEY_PATHout of macOS build env - Turbopack cache OOM (#4619) — partial cache freeze; gate on
- Deps override workflow — overrides in
pnpm-workspace.yaml; tauri-plugins - pdfjs vendor wasm — pdfjs 5.7 moved JBIG2 to
jbig2.wasm; copywasm/* - CI/PR delivery + push keepalive — temp-index plumbing; SSH
ServerAliveInterval
Platform Compat
- Android hyphen selection (#1553) — Blink start handle on last hyphen;
- NativeFile vs RemoteFile I/O — NativeFile slow; asset Range broken;
- Window-state sanitizer (#4398) — invalid json crashes WebView2; sanitize before
- Android Open-with intent (#4521) — VIEW gated by
autoImportBooksOnOpen - Dict lookup browser hijack (#4559) — missing
<queries>sdk36; filter in - Large-PDF OOM range flood (#3470) — un-awaited ranges OOM; MAX_CONCURRENT_RANGES=6
- Android themed icon (#4733) — no monochrome → force-commit; tint=SRC_IN
Reader Features & UI
- Mobile reading widgets (#1602/PR#4842) — home-screen widget iOS+Android; gotchas: iOS
- PDF scrolled-mode lag (#4795/#4031) — 415ms/page render vs 50% margin; widen to
- Scrolled-PDF pinch-zoom (#4817) — live pinch + no-shift (gap×
--scroll-zoom+ - Search modes #4560 + spoiler-bound bug — regex + nearby-words;
- OPDS groups carousel (#4750) — >=2 groups → virtuoso carousel;
scrollToIndex - WebDAV browser sort + search (#4724) — PR #4786;
- Image zoom trackpad flicker (#4742) — macOS pinch=
ctrl+wheel; - Instant Highlight ate tap/swipe — preventDefault killed tap-paginate;
- Keyboard selection adjust (#4728) — Shift+←/→ char, Ctrl/Alt+Shift word;
- Double-click word select — orphaned
iframe-double-click→ - Cross-page selection auto-turn (#4741) —
useAutoPageTurndwell; - Annotator onLoad listener leak (#4735) — `useRendererInputListener
- Paragraph mode toggle/resume (#4717) — snapshot live Set; resume from fresh
- Paragraph-mode accidental exit (#4474) —
paragraph-show-controls; bar - #4584 tap-death — UNFIXED;
isPopupedRED HERRING; likely WebView-148 - Dblclick-drag turns page (#4524) —
isMouseDowngatespostSingleClick - Tap to open image/table (#4600) —
iframe-open-media+detectMediaTarget - PDF/CBZ Contrast view-menu — per-book
contrast; ONEfilter:(invert+contrast); - iOS instant-dict double popup — once-per-gesture latch;
isLongPressHold300ms - Dict: popup font size #4443
--dict-font-scaleMDict::part(dict-content); lemmatization #4574 - Word Lens: inline gloss CFI-safe
<ruby cfi-skip>…<rt cfi-inert>TTS/search isolation; en-en - Stripe highest-active plan (#4694) —
plans.plan= MAX over active subs - Save image to gallery (#4680) — MediaStore; sharekit 0-byte self-copy
- Webtoon Mode (#3647) — no-gap scrolled images; FXL fit-width;
--scroll-page-gap - Biometric app-lock (#4645) — flag from
appLockStore; plugincfg(mobile) - Reference Pages (#4542) — 'reference' progressStyle;
referencePageCount - E-ink screen refresh page-turner (#4687) — bindable 'refresh' action;
- Share intent + toolbar (#4014) — Share gated mobile+macOS;
- Instant highlight delete orphan (#4773) — stale memoized index + in-place
- Empty highlight leak on annotate cancel (#4791) — Annotate
- Customize Toolbar global (#4760) —
serializeConfigref-compare → - Customize Toolbar e-ink black bar (#4839) — preview Zone copied
- Native TTS: iOS #4676 AVSpeechSynthesizer pause==stop rate
pow^(1/2.5); offline halt #4613 - Edge TTS: word highlight #4017
audio.metadataWordBoundary by rAF gate on UA; drift - TTS highlight granularity setting — Word/Sentence; gate
prepareSpeakWords - TTS start-from-selection — use last mark at/before sel; cloneRange+deselect
- Reuse TTS session on mode entry —
redispatchPosition()+tts-sync-request - RSVP: control-bar overlap REVERT #4585 fixed/#4589 stale-reverted; font face/family #4519
getBaseFontFamilyoverlay top doc; RTL word #4630 - Overlay z-index scale — RSVP 100 / Settings 110 / ModalPortal 120 / toast 130 / app-lock
- Global annotation page-turn lag (#4575) —
globalre-fanned every turn; - Overlayer splitRange text nodes —
'p,h1-h4'droppedli; walk text nodes + - Android image callout freeze —
.no-context-menuon ANCESTOR - Table dark-mode tint (#4419) —
blockquote, table *tint gated onoverrideColor - Footnote aside border line (#4438) — @font-face before @namespace; hoist
- Proofread: enhancements #4700 regex UI/Ctrl+P reuse/
wholeWordno-op; per-book CRDT #4781 - Russian hanging-preposition NBSP (#4769) — generic
nbsp+ - OPDS: Firefox strict-XML #4479 junk after
</feed>slice to last close; JSON search #4502 expand{?query}BEFORE resolveURL; HTML desc #4503 decode-once+sanitize; self-link meta #4749rel:selfderef; popular dedup #4782 - D-pad Navigation — Android TV remote / arrow-key nav
- koplugin cover upload (#4374) —
extractLocalCoverviagetCoverImage - Calibre plugin push #4863 apps/readest-calibre-plugin; OAuth localhost relay + /sync carry-over
Library Fixes
- Book action platform surfaces — context menu Tauri-desktop-only; cross-platform
- Tauri menu append race (#4389) — single
await Menu.new({ items }) - TXT: author recognition #4390
parseLabeledAuthor+isPlausibleAuthorName; chapter measure-word FP #4658 - Cover stale (in-place mutation) — pure
getBookWithUpdatedMetadata - Series/author back no-op (#4437) — Next 16.2 empty-search no-op;
handleBack - Library/reader separate texture (#4743) —
libraryBackground* - List view series overflow (#4796) — fixed
h-28clipped series+description - Recently-read shelf (#3797) — PR #4829; reuse
BookItem+ shareduseOpenBook; flex
Architecture & Patterns
- foliate-js submodule at
packages/foliate-js/; multiview paginator preloads adjacent sections - Markdown .md support (#774) — in-memory foliate book (no EPUB) in
src/utils/md.ts; - Style:
getLayoutStyles()always,getColorStyles()when overriding color;transformStylesheet()rewrites EPUB CSS at load - TTS independent section tracking (
#ttsSectionIndex); safe insets: native plugin → useSafeAreaInsets → styles; DropdownsDropdownContext - Stale settings closure: store-hook
settingsstale acrossawait; persistuseSettingsStore.getState().settings(#4780 - Foliate touch-listener capture phase — suppress gestures via
- iframe cross-realm instanceof —
instanceof Elementfalse; duck-type `'closest' - Virtuoso + OverlayScrollbars — useOverlayScrollbars for mobile webviews
- Design system → DESIGN.md — never
pl/pr/ml/mr/text-left/right(RTL)
Workflow & Feedback
- Commit messages English-only — English only (no CJK, no em/en dashes)
- Test file filter —
pnpm test <path>without--runs one file - Rebase before PR — rebase onto origin/main before PRs
- New branch per PR — fresh branch from main per PR/issue
- Use worktree — never
git worktree add; alwayspnpm worktree:new - Never push every change — commit locally until user confirms
- No test seams in prod — prod never imports
__reset*ForTests - No lookbehind regex — never
(?<=)/(?<!); build check rejects - en plurals manual — only plural variants + proper nouns;
_one/_other - i18n:extract prunes keys —
removeUnusedKeysdeletes valid keys; revert churn, add - Dependabot transitive fixes — pin in
pnpm-workspace.yamloverrides:; alert#≠issue# - Upgrade gstack locally — upgrade from project
.claude/skills/gstack