Skip to content

In-chapter search corrupts reading progress and loses position on close #22

Description

@bizzkoot

Steps to reproduce

  1. Open any chapter on origin/master v2.1.5 (be0106d47) — scroll mode (same in paged reader).
  2. Note the saved position, e.g. 35% at paragraph ~120.
  3. Tap the search (magnifier) in the reader appbar → type a query with many hits (e.g. the, he said).
  4. Step through matches with next/prev (chevron-down / chevron-up) — each step scrolls the WebView via window.readerSearch.next() / previous().
  5. Close search via X, hardware back, or tapping the magnifier again.
  6. Reopen the chapter / check Library progress / inspect MMKV chapter_progress_{chapterId}.

Related repro for layout:
7. On a device where StatusBar.currentHeight is null (some Android builds / split-screen), open search and observe the searchbar overlapping the status bar.

Expected behavior

  • Searching is a transient lookup — it must never overwrite the saved last-read position while the search overlay is open. The position should stay at the anchor where search was opened until the user explicitly chooses to stay.
  • On close, the user should be offered a way back to that anchor (with a preference: auto-return after a short countdown, return immediately, or stay at the found match). When TTS is active, return should prioritize the active TTS highlight; in paged mode it should return to the anchored page; otherwise to the anchored scroll offset.
  • The searchbar should be inset below the status bar on all devices and behave as an overlay (no layout shift), with animated enter/exit, a clear button, correct disabled states, and a truncation-aware counter.

Actual behavior

1) Progress is mutated while searching
src/screens/reader/components/WebViewReader.tsx on origin/master has no isSearchActive guard around the save handler. Every scroll caused by stepping through matches fires saveProgress(percent, paragraphIndex) via the onMessagesave path, so chapter_progress_{chapterId} and DB progress are overwritten to the current match location even though the user is only searching.

2) Position is lost on close
src/screens/reader/ReaderScreen.tsx on origin/master:

  • handleCloseSearch / handleToggleSearch only do setSearchVisible(false) + window.readerSearch.clear() — no anchor is captured on open (window.__searchAnchorY / __searchAnchorPage / __searchAnchorPIdx / __isSearching do not exist).
  • Closing search leaves the viewport at the last match with no affordance to return. Because (1) already mutated progress, even reopening the chapter lands at the wrong place.

3) No user preference
src/hooks/persisted/useSettings.ts has no ChapterGeneralSettings.searchReturnBehavior — users who want to stay at the match and users who want to return cannot both be satisfied.

4) Searchbar layout/UX regressions

  • src/screens/reader/components/ReaderSearchbar.tsx reads StatusBar.currentHeight directly — null on several Android builds ⇒ bar renders under the status bar. It is a normal flex row in the hidden-header branch (not an absolute overlay), so it shifts layout and has no elevation.
  • No enter/exit animation, no clear (×) button, onSubmitEditing calls onNext() even with no results, no elevation, font/counter sizes not scaled via uiScale.
  • Counter renders only current/total — WebView caps highlights at MAX_RENDERED_MATCHES (1500) but renderedTotal / isTruncated (already in src/screens/reader/types/index.ts) are ignored, so truncation is silent.
  • Missing accessibility labels (closeSearch / clearSearch / previousMatch / nextMatch, Match X/Y announcement).
  • Input is bound to the parent searchResult.query (WebView round-trip) with a fragile debounce — typing feels laggy.

LNReader version

2.1.5 (origin/master be0106d47 — the v2.1.5 release containing 932638119 feat(reader): add in-chapter search)

Android version

Android 14 (reproducible on Android 13–15; not OS-specific — bug is in origin/master reader logic, status-bar overlap also seen on Android 12 where StatusBar.currentHeight returns null)

Device

Pixel 7 (also reproduced on generic emulator — any device; status-bar overlap is device/ROM-specific where StatusBar.currentHeight == null)

Other details

Impact

  • Data loss: Search moves the permanent last-read position — users lose their place without realizing it.
  • Navigation: Deep search → close → manual scroll back with no anchor.
  • Layout: Searchbar overlaps status bar on ROMs where StatusBar.currentHeight is null.

Proposed solution (already proven working on local dev)

The fix is implemented and verified on local dev (3 commits ahead of origin/master: 74404ed43 feat(reader): enhance in-chapter search UX, fbf73b4e3 fix(reader): pass status bar height to searchbar, dd53eeb3e fix(reader): handle back press for return banner; pnpm run type-check + pnpm run test pass, manual QA in scroll/paged/TTS).

A) Make search non-destructive

  • On open, capture window.__searchAnchorY / __searchAnchorPage / __searchAnchorPIdx and __isSearching = true before any search scroll; unhide header if hidden.
  • Add isSearchActive (searchVisible || showReturnBanner) prop to WebViewReaderisSearchActiveRef guard that early-returns from the save message handler (save-ignore-while-searching). On close paths: clear anchors/__isSearching; when staying, call window.reader.saveProgress() to commit the current position, otherwise just jump — the anchor is last-read.

B) Configurable return-on-close

  • New ChapterGeneralSettings.searchReturnBehavior: 'countdown' | 'immediate' | 'stay' (default countdown), exposed in Settings → Reader → Navigation via SearchReturnBehaviorModal (3 radio options, persisted with setChapterGeneralSettings).
  • countdown: close → bottom banner “Returning to last read position in 5s” with Return now / Stay here, auto-return after 5s (setInterval, cleared on unmount / chapter.id change / dismiss / explicit close). immediate: instant executeReturnToAnchor(). stay: no jump, and commits current position. Toggle-close (magnifier while open) dismisses without banner — intentional “leave me here”.
  • Return priority: active TTS highlight (window.tts.scrollToElement) → paged reader (pageReader.movePage) → scroll (window.scrollTo({ behavior: 'smooth' })). Hardware back priority: searchVisible → return banner → drawer.

C) Searchbar polish

  • Receive statusBarHeight from NovelContext (ChapterContentReaderSearchbar) with fallback to StatusBar.currentHeight, render as position: absolute; top:0; zIndex:2; elevation:4 overlay with paddingTop: statusBarHeight + padding.xs. Add react-native-reanimated enter/exit. Use controlled local inputValue with 150ms debounce, clear (×) button, Keyboard.dismiss() on close, onSubmitEditing only when hasResults, correct disabled stepper states, scaleDimension(..., uiScale) sizing, truncation-aware counter current/renderedTotal+ + (N total) when isTruncated, and accessibility labels.

No DB migration needed; safe default. Files: src/hooks/persisted/useSettings.ts, src/screens/reader/ReaderScreen.tsx, src/screens/reader/components/ReaderSearchbar.tsx, src/screens/reader/components/WebViewReader.tsx, src/screens/settings/SettingsReaderScreen/Modals/SearchReturnBehaviorModal.tsx + strings.

Repro after fix (expected)

  1. Open chapter → search → step through matches → progress unchanged until close.
  2. Close with default countdown → banner → auto-return (or Return now / Stay here / hardware back all behave); immediate → instant return; stay → no return.
  3. Works in scroll, paged, and TTS-active chapters; survives chapter change/rotation; correctly inset when StatusBar.currentHeight == null.

Acknowledgements

  • I have searched the existing issues and this is a new ticket, NOT a duplicate or related to another open or closed issue.
  • I have written a short but informative title.
  • If this is an issue with a plugin, I should be opening an issue in the plugins repository.
  • I have updated the app to version 2.0.2.
  • I will fill out all of the requested information in this form.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions