diff --git a/apps/readest-app/.claude/memory/MEMORY.md b/apps/readest-app/.claude/memory/MEMORY.md index d719ac7f..c36298db 100644 --- a/apps/readest-app/.claude/memory/MEMORY.md +++ b/apps/readest-app/.claude/memory/MEMORY.md @@ -65,12 +65,15 @@ - [Android hyphen selection (#1553)](android-hyphen-selection-bounds-1553.md) — Blink paints start handle on last hyphen; repair anchor + custom handles - [NativeFile vs RemoteFile I/O](android-nativefile-remotefile-io.md) — NativeFile slow; RemoteFile can't replace (asset Range broken); handle-reuse 2.3× - [Window-state sanitizer (#4398)](window-state-sanitize-4398.md) — invalid `.window-state.json` crashes WebView2; sanitizer plugin before window-state -- [Android Open-with intent (#4521)](android-open-with-intent-flow.md) — VIEW=transient→reader, SEND=library; Telegram fails cold-start + foreign-file read +- [Android Open-with intent (#4521)](android-open-with-intent-flow.md) — VIEW/SEND pipeline; VIEW routing now gated by `autoImportBooksOnOpen` (#4747, mobile default ON → import); Telegram cold-start + foreign-file read - [Dict lookup browser hijack (#4559)](dict-lookup-browser-hijack-4559.md) — missing `` ACTION_PROCESS_TEXT sdk36; filter browsers in `decideLookupDispatch` - [Large-PDF OOM range flood (#3470)](pdf-oom-range-flood-3470.md) — makePDF fires all ranges un-awaited → OOM; MAX_CONCURRENT_RANGES=6 - [Android themed icon (#4733)](android-themed-icon-4733.md) — `tauri icon` emits no monochrome → force-commit; tint=SRC_IN, negative-space gap ## Reader Features & UI +- [Search modes #4560 + spoiler-bound bug](search-modes-4560-and-spoiler-bound-bug.md) — regex + nearby-words in foliate search.js (phased); deferred cache/searchBook; lookupPassage spoiler bound (page vs chunk ordinal) already wrong +- [OPDS groups carousel (#4750)](opds-groups-carousel-4750.md) — >=2 groups → horizontal react-virtuoso carousel; lazy covers; `handle.scrollBy` vertical-only so page by `scrollToIndex`; card covers rounded + badge dropped (kept in detail) +- [Image zoom trackpad flicker (#4742)](image-zoom-trackpad-flicker-4742.md) — macOS trackpad pinch = `ctrl+wheel` stream; `0.05s` transform transition restarts each event → flicker; `isWheelZooming` debounce gates transition off - [Instant Highlight ate tap/swipe (Android)](instant-highlight-tap-paginate.md) — `handlePointerDown` preventDefault killed tap-paginate; touch still-hold gate `INSTANT_HOLD_MS=300` - [Keyboard selection adjust (#4728)](keyboard-selection-adjust-4728.md) — Shift+←/→ char, Ctrl/Alt+Shift word; `onAdjustTextSelection` in useBookShortcuts - [Annotator onLoad listener leak (#4735)](annotator-onload-listener-leak-paragraph-mode.md) — per-section onLoad leaked listeners; `useRendererInputListeners` once-per-view @@ -90,6 +93,7 @@ - [Biometric app-lock (#4645)](biometric-app-lock-4645.md) — read flag from `appLockStore` not settingsStore; plugin `cfg(mobile)` - [Reference Pages (#4542)](reference-pages-672-4542.md) — 'reference' progressStyle from foliate pageList; per-book `referencePageCount` - [Share intent + toolbar (#4014)](annotation-share-toolbar-4014.md) — Share tool gated mobile+macOS; drag-drop customizer; `annotationToolbarItems` +- [Customize Toolbar global (serializeConfig) #4760](customize-toolbar-global-serializeconfig.md) — was per-book; root cause `serializeConfig` `!==` ref-compares array viewSettings → always stored as stale per-book override shadowing global; fix = value compare (`isSameViewSettingValue`) in viewSettings reduce only; field stays `annotationToolbarItems` in AnnotatorConfig (no exception/rename/move); limitation: pre-existing v0.11.12 overrides not retro-cleared - [Native iOS TTS (#4676)](native-ios-tts-4676.md) — AVSpeechSynthesizer plugin; pause==stop, never `end` on didCancel; rate `pow^(1/2.5)` - [Native TTS offline halt (#4613)](native-tts-offline-autoadvance-4613.md) — `#speak` advances only on `end`; native SKIP-on-error via `forward()` + cap - [Edge TTS word highlight (#4017)](edge-tts-word-highlighting-4017.md) — `audio.metadata` WordBoundary synced by rAF; gates on UA not Origin @@ -109,6 +113,7 @@ - [OPDS Firefox strict-XML (#4479)](opds-firefox-strict-xml-4479.md) — junk after `` → parsererror; `parseOPDSXML` slices to last close tag - [OPDS2 JSON search greyed (#4502)](opds2-json-search-4502.md) — `isSearchLink` ignored templated opds+json; expand `{?query}` BEFORE resolveURL - [OPDS HTML description (#4503)](opds-html-description-4503.md) — double-escaped into unsanitized HTML; decode-once + sanitize +- [OPDS self-link metadata (#4749)](opds-self-link-metadata-4749.md) — summary pubs need `rel:self` deref (`opds-publication+json`); JSON `description` is HTML → `getOPDSDescriptionHtml(content ?? description)` - [D-pad Navigation](dpad-navigation.md) — Android TV remote / arrow-key nav design + pitfalls - [koplugin cover upload (#4374)](koplugin-cover-upload.md) — local covers uploaded blank; `extractLocalCover` via `getCoverImage` @@ -119,6 +124,7 @@ - [TXT chapter measure-word FP (#4658)](txt-chapter-measure-word-4658.md) — strong `[章节回讲篇话]` vs weak `[卷本册部封]` needs separator - [Cover stale (in-place mutation)](cover-stale-inplace-mutation-memo.md) — mutated book in place → memo skip; pure `getBookWithUpdatedMetadata` - [Series/author back no-op (#4437)](series-folder-back-noop-4437.md) — Next 16.2 empty-search `router.replace` no-op; `handleBack` `group=''` workaround +- [Library/reader separate texture (#4743)](library-reader-separate-texture-4743.md) — shared `#background-texture` style; `libraryBackground*` device-local + inherit; `none` must unmount; store inits `{}` ## Architecture & Patterns - foliate-js is a git submodule at `packages/foliate-js/`; multiview paginator preloads adjacent sections (multiple View/Overlayer per book) diff --git a/apps/readest-app/.claude/memory/android-open-with-intent-flow.md b/apps/readest-app/.claude/memory/android-open-with-intent-flow.md index e8b82b28..26d7199d 100644 --- a/apps/readest-app/.claude/memory/android-open-with-intent-flow.md +++ b/apps/readest-app/.claude/memory/android-open-with-intent-flow.md @@ -11,7 +11,7 @@ Android "Open with Readest" / "Send to Readest" file-intent pipeline and the #45 **Pipeline (ACTION_VIEW = "Open with", ACTION_SEND = "Share"):** - `NativeBridgePlugin.kt::handleIntent` is the real handler (NOT `MainActivity.kt` — its ACTION_SEND branch is legacy/redundant). → `emitSharedIntent("VIEW"|"SEND", uris)` → JS `useAppUrlIngress` `shared-intent` plugin listener → `app-incoming-url` event → `useOpenWithBooks`. -- VIEW → `openTransient` → straight to reader (ephemeral book, `deletedAt` set, `filePath` = the content:// URI, no library write/upload). SEND → `window.OPEN_WITH_FILES` → `library/page.tsx::processOpenWithFiles` (full ingest + force cloud upload on mobile). +- VIEW routing is now gated by `autoImportBooksOnOpen` (PR #4747, issue #4746): `shouldOpenTransient(action, autoImportBooksOnOpen)` in `helpers/openWith.ts` → only `VIEW` with the setting OFF goes `openTransient` (ephemeral book, `deletedAt` set, `filePath` = content:// URI, no library write/upload); `VIEW` with it ON falls through to the SEND path. SEND (and VIEW-with-import-on) → `window.OPEN_WITH_FILES` → `library/page.tsx::processOpenWithFiles` (full ingest + force cloud upload on mobile). The setting defaults TRUE on mobile (`DEFAULT_MOBILE_SYSTEM_SETTINGS`, desktop default still false) and its "Auto Import on File Open" toggle is now shown on mobile too. `useOpenWithBooks.handle` reads it via `appService.loadSettings()` (disk), NOT the settings store — the store is unhydrated during the cold-start intent replay and would wrongly fall back to transient. So the Telegram default is now import-to-library (persists past the dying URI grant); transient is opt-out. - content:// read: `nativeAppService.openFile` → if URI contains `com.android.externalstorage` → direct `NativeFile` (real path); else `copyURIToPath` → `contentResolver.openInputStream` → copy to Cache → `NativeFile`. `basename` here is LEXICAL (`@tauri-apps/api/path`), not a ContentResolver `DISPLAY_NAME` query — but EPUB format is sniffed by zip magic (`document.ts isZip()`), so an extension-less content URI still opens. - The Tauri deep-link plugin's `getCurrent()`/`onOpenUrl` only fire for configured deep-link domains (`https://web.readest.com`, `readest:`); `content://`/`file://` VIEW intents are filtered out by `DeepLinkPlugin.isDeepLink()`, so file opens flow ONLY through the native `shared-intent` channel, never the deep-link plugin. diff --git a/apps/readest-app/.claude/memory/customize-toolbar-global-serializeconfig.md b/apps/readest-app/.claude/memory/customize-toolbar-global-serializeconfig.md new file mode 100644 index 00000000..dccf5d42 --- /dev/null +++ b/apps/readest-app/.claude/memory/customize-toolbar-global-serializeconfig.md @@ -0,0 +1,48 @@ +--- +name: customize-toolbar-global-serializeconfig +description: Customize Toolbar applied per-book not global; root cause = serializeConfig compared viewSettings by reference (!==) so array values were always stored as stale per-book overrides +metadata: + node_type: memory + type: project + originSessionId: c6601464-9463-4ac3-99c0-e7527e4051b5 +--- + +Customize Toolbar (annotation bar, #4014, shipped v0.11.12) changes only applied +to the book where edited, not globally. Fixed in PR #4760 (MERGED, squashed onto +main as 7da5f8321). + +**Root cause:** `serializeConfig` (`src/utils/serializer.ts`) decides which per-book +viewSettings to persist as overrides via `globalViewSettings[key] !== value` — a +*reference* compare. It deep-clones the config first (`JSON.parse(JSON.stringify)`), +so any **array/object** viewSettings value (`annotationToolbarItems`, and latently +`paragraphMode`, `proofreadRules`, `ttsHighlightOptions`, `noteExportConfig`) is a +fresh reference ≠ global → stored as a per-book override on **every** save (progress +autosave serializes with settings each relocate). On reopen the merge +`{ ...globalViewSettings, ...perBookOverrides }` lets the stale override shadow +global → a global toolbar change never reaches already-saved books. + +**Fix (final — minimal, general, no special-casing):** compare viewSettings values +by content, not reference. Added `isSameViewSettingValue(a,b) = a===b || +JSON.stringify(a)===JSON.stringify(b)`, used in the viewSettings reduce ONLY +(searchConfig left on `!==` — it holds functions / large `results`). The field +stays `annotationToolbarItems` in `AnnotatorConfig` (normal per-book viewSettings, +honors the isGlobal "Apply to This Book" toggle). PR diff is just serializer.ts + +serializer.test.ts. + +**Iteration history (user steered):** (1) a `GLOBAL_ONLY_VIEW_SETTINGS` exception +forcing global save + strip/ignore per-book — rejected "don't make it an exception"; +(2) move field to `SystemSettings.globalReadSettings` — rejected "too much"; +(3) rename `annotationToolbarItems`→`annotationToolbar` for a clean slate — rejected, +keep the original name (it's synced in globalViewSettings). Landing point: keep the +name, fix only the serializer reference-compare bug. + +**Known limitation (no rename clean-slate):** existing books may carry a per-book +`annotationToolbarItems` override from the buggy v0.11.12 build. The value compare +stops new ones and drops an existing one on next save when it matches global, but +does NOT retroactively clear an override whose content differs from current global — +those books keep the stale toolbar until re-saved while equal to global. A follow-up +one-time migration (clear persisted per-book toolbar overrides) would close this if +needed. + +Tests: `src/__tests__/utils/serializer.test.ts` — array setting equal to global is +not persisted; differing array still persisted. diff --git a/apps/readest-app/.claude/memory/image-zoom-trackpad-flicker-4742.md b/apps/readest-app/.claude/memory/image-zoom-trackpad-flicker-4742.md new file mode 100644 index 00000000..e6686cdb --- /dev/null +++ b/apps/readest-app/.claude/memory/image-zoom-trackpad-flicker-4742.md @@ -0,0 +1,16 @@ +--- +name: image-zoom-trackpad-flicker-4742 +description: "Trackpad pinch-zoom flickered the image viewer; macOS pinch = ctrl+wheel stream, disable CSS transition during continuous gestures" +metadata: + node_type: memory + type: project + originSessionId: affbfa14-0152-4d69-8fce-f7e0b9ee97a3 +--- + +ImageViewer (`src/app/reader/components/ImageViewer.tsx`) flickered when zooming an open image with a MacBook trackpad pinch (#4742, PR #4748). + +**Root cause:** on macOS a trackpad pinch-to-zoom is delivered to the WebView as a rapid stream of `wheel` events with `ctrlKey: true` (NOT touch events), so it flows through `handleWheel`. The zoomed `` kept its `transition: transform 0.05s ease-out` whenever `isDragging` was false. Pinch wheel events fire faster than 50ms apart, so each event restarted the in-flight transition from its interpolated mid-point — the transform constantly lagged and caught up = visible flicker. Same root cause as the #4451 pan flicker, which only fixed the pan path and (via `isDragging` set in `onTouchStart`) the touch-pinch path; the wheel-zoom path was the only continuous gesture left with the transition on. That's why touch pinch on iPhone was smooth but trackpad pinch flickered. + +**Fix:** added an `isWheelZooming` state set on each `handleWheel` event and cleared on a 200ms debounce (wheel has no explicit gesture-end). Transition is `isDragging || isWheelZooming ? 'none' : 'transform 0.05s ease-out'`. Discrete zoom (buttons, double-click, keyboard) keeps the smoothing. + +**General pattern:** never run a CSS `transition` on a transform that's being updated by a high-frequency continuous input stream (drag, touch pinch, trackpad/`ctrl+wheel` pinch) — the interrupted-transition restart flickers. Gate the transition off for the duration of the gesture. Maintainer couldn't repro on macOS 15.6.1 (WebKit) while reporter hit it on macOS 26.5.1 / WebKit 605.1.15; the fix is version-independent. Related: [[instant-highlight-tap-paginate]]. diff --git a/apps/readest-app/.claude/memory/library-reader-separate-texture-4743.md b/apps/readest-app/.claude/memory/library-reader-separate-texture-4743.md new file mode 100644 index 00000000..1347531d --- /dev/null +++ b/apps/readest-app/.claude/memory/library-reader-separate-texture-4743.md @@ -0,0 +1,18 @@ +--- +name: library-reader-separate-texture-4743 +description: "Separate library vs reader background texture (#4743); shared-style-element + two gotchas" +metadata: + node_type: memory + type: project + originSessionId: dfdb7b38-1869-4fb4-b869-c32301c80128 +--- + +#4743: library and reader shared one background texture; split so each is set independently. + +**Architecture**: ONE global `