Merged
Conversation
- Add configurable search-close return behavior and position preservation. - Improve search bar controls, animation, accessibility, and match counters. - Validate formatting before local commit.
- Inject `statusBarHeight` from `NovelContext` to `ChapterContent` and `ReaderSearchbar` to ensure consistent layout. - Replace native `StatusBar.currentHeight` usage with the injected prop value. - Refactor return banner dismissal to explicitly clear timer and reset countdown state. - Add elevation to the search bar container for visual layering.
- intercept back press when return banner is visible to dismiss it - update back handler priority comment to reflect new logic - add missing clearReturnTimer dependency
… anchor fixes - search.js: replace cloneContents bottleneck with bounded DOM walk (AUD-SRCH-06) - search.js: per-text-node wrapping to avoid cross-tag transplant and inline node deletion (AUD-SRCH-02) - search.js: store all match positions and lazily render beyond MAX_RENDERED_MATCHES, wrap-consistent focus (AUD-SRCH-05) - ReaderSearchbar: enable cyclic stepper navigation to match WebView wrapping (AUD-SRCH-05) - ReaderScreen: restrict return-to-anchor TTS priority to actively reading state (AUD-SRCH-01) - ReaderScreen+WebViewReader: allow paragraph-indexed saves during search and bypass Stay Here race (AUD-SRCH-03/04)
…ress flush - fix AUD-TIME-01: replace window-bound this.post/initialX in swipe IIFE with reader.post and closure vars to prevent TypeError on touchstart - fix AUD-PERS-01: guard flushPendingProgressSave with hasPerformedInitialScroll OR suppressSaveOnScroll to block 0% save before initial scroll/calculatePages completes - fix AUD-RTL-01: mirror pageReader translateX, drag preview, tap zones and swipe thresholds when dir=rtl so pages no longer shift off-screen - fix AUD-RTL-02: invert continuous chapter swipe next/prev mapping when dir=rtl - fix AUD-RTL-03: invert horizontal seekbar ratio, anchor thumb via right:100% and right-fill progress under dir=rtl, clamp ratio 0-1; mirror off-screen transition chapter position - keep LTR behavior unchanged; validated type-check and existing tests
- useTimeTracking: exclude idle window from inactivity flush via lastActivityAt (AUD-TIME-02)
- cap durations to 12h and drop non-monotonic negatives to guard NTP/zone skew (AUD-TIME-05)
- periodic 60s checkpoint with 30s min to survive SIGKILL/LMK without duplicate sessions (AUD-TIME-04)
- cap TTS inactive-poll to background-enter time to avoid Doze drift counting paused hours (AUD-TIME-03)
- isolate chapter-change starter timer from unrelated re-renders to prevent cleared starter (AUD-TIME-06)
- DistributionBar: use visibleEntries single-full Circle fallback to avoid 360° SVG arc collapse (AUD-STAT-01)
- TimeTab: guard velocity cph >50 or minsPerChapter <0.5 to avoid lifetime/post-migration distortion (AUD-STAT-02)
- normalizeGenre: Unicode-aware \p{L}\p{N} preserve CJK/Cyrillic/accented genres (AUD-STAT-03)
- PluginsTab: memoize entries and palette, reduce-based max to avoid stack/extra recompute (AUD-STAT-04/05)
- GenreSection: reduce-based max to avoid spread stack (AUD-STAT-05)
- add migration 007 idx_novel_inLibrary for WHERE inLibrary=1 seek (AUD-STAT-06)
- update useTimeTracking and migration tests to match new semantics
Validation: pnpm run type-check passed, pnpm run format passed, jest useTimeTracking/StatsQueries/migration tests passed (1666+ passed, 7 migration version expectations updated to 7)
- defer slider responder claim to move to unblock vertical scroll, allow termination when not actively dragging, interlock ViewPager swipe while dragging to avoid half-commits, fallback-clear dragValue when parent rejects value (AUD-GEST-01/02/03) - gate progress DB writes with rejection handling and document best-effort MMKV fallback for background process freeze without ForegroundService (AUD-PERS-02/03) - AUD-GEST-01: onStartShouldSetPanResponder false, shouldClaimPanResponder on move, terminationRequest dynamic - AUD-GEST-02: sliderDragState global flag, ReaderBottomSheet disables TabView swipe while dragging, terminate reverts without committing - AUD-GEST-03: keep dragValue until controlled catches up with 400ms fallback to avoid permanent thumb freeze - AUD-PERS-02: document multi-hop IPC limitation, MMKV synchronous write first in saveProgress, WebView flush note - AUD-PERS-03: attach .catch to updateChapterProgress/MarkRead via useNovel and direct ChapterQueries callers - tests: update Slider start-claim expectation, keep released-value-visible behavior Validation: pnpm run type-check passed, pnpm jest Slider.test 16 passed
- Add readingChapters aggregate counting distinct chapter IDs from reading sessions of library novels - Compute reading velocity from readingChapters instead of lifetime chaptersRead and drop implausible-velocity heuristic guards - Flush manual reading sessions when the user is inactive during checkpoint ticks - Cache lazily rendered search marks and return the last mark to avoid duplicate wrapping - Force RTL layout when an RTL locale is selected and prompt an app restart - Remove search save bypass window from reader anchor clearing - Brace single-statement ifs and use const in search script for consistency
- Format sub-minute reading time as seconds ("20s") instead of "<1m" so short sessions are visible in the Time tab
- Add a seconds field to formatTotalTimeParts and a seconds unit box to TimeTab, backed by a new statsScreen.secs string
- Refetch stats on screen focus via useFocusEffect so returning from the reader picks up newly recorded ReadingSession rows
- Skip the full-screen loader on refocus after the first load to avoid a UI flash during silent refreshes
- Add reading-time simulation tests covering manual scroll, TTS (including background), and multi-checkpoint sessions end-to-end
- Add StatsScreen focus-refetch tests and update TimeTab/utils tests for seconds precision
- replace flawed AUD-TIME-03 background-enter truncation with heartbeat-based polling check in useTimeTracking - preserve full background TTS playback time when paused via lockscreen notification or Bluetooth headset - protect periodic checkpoints and inactive flushes against Doze drift by bounding to confirmed heartbeat window - add simulation and unit tests covering background pause, screen-off starts, and Doze suspension drift capping - validate with type-check, linter, Prettier, and all 91 Stats and reading-time tests passing
- specify bundleConfig in react block to explicitly resolve metro.config.cjs - fix createBundleReleaseJsAndAssets SHA-1 failure with pnpm symlink resolution - validate with successful assembleRelease generating app-release.apk
- Update test metrics to 1,684 passing tests across 118 test suites in AGENTS.md and GEMINI.md. - Document post-merge hardening for in-chapter search UX, RTL inversion, reading time tracking, gesture arbitration, and Android packaging. - Note translation key additions in English strings and queued downstream sync for secondary locales. - Confirm branch health and zero regressions across all ahead commits. - Validation: passed Prettier formatting and TypeScript type-check with zero errors.
- Add monotonic speaking-time clock to TTSForegroundService (segment per utterance-active span) with getTtsPlaybackClock bridge. - Snapshot the native clock on app-background entry and top up unattested time on foreground/unmount, capped by wall-clock and native attestation. - Gate post-foreground TTS restart on native speaking state to prevent phantom sessions; fail open when the clock is unavailable. - Validation: 1688 jest tests pass (+4 reconcile), 14 native unit tests pass, type-check and lint clean, release build succeeds.
Owner
Author
|
Close #22 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
1. Executive Summary
This PR stacks 16 hardening and polish commits on top of v2.1.5 (
be0106d47). No new top-level features — it takes the features already merged intomaster(in-chapter search, RTL support, reading-time tracking, stats overhaul, scheduled updates) and fixes the defects found by follow-up adversarial audits plus one real-world data-loss bug:cloneContentstransplant bugs), and virtualized match navigation capped at 200 rendered marks.2. What Changed (by Theme)
2.1. In-Chapter Search UX & Engine Fixes (4 commits)
74404ed43featSearchReturnBehaviorModal+NavigationTabsetting + MMKV persistence), position preservation, animated return banner, improved match counters/animation/accessibility, newenstringsfbf73b4e3fixstatusBarHeightfromNovelContextinstead ofStatusBar.currentHeightfor consistent searchbar layout; explicit return-timer cleanup; searchbar elevationdd53eeb3efixclearReturnTimerdep addeda11e42bf1fixcloneContentsbottleneck; per-text-node wrapping (no cross-tag transplant, no inline-node deletion); lazy rendering beyondMAX_RENDERED_MATCHES(200) with wrap-consistent focus; cyclic stepper inReaderSearchbar; return-to-anchor TTS priority restricted to actively-reading state; paragraph-indexed saves allowed during searchFiles:
ReaderScreen.tsx,ReaderSearchbar.tsx,WebViewReader.tsx,search.js,SearchReturnBehaviorModal.tsx(new),NavigationTab.tsx,useSettings.ts,en/strings.json2.2. RTL Hardening & Completion (2 commits + part of §2.3)
111fa901afixpageReadertranslateX, drag preview, tap zones and swipe thresholds underdir=rtl; invert continuous-chapter swipe next/prev mapping; invert seekbar ratio, anchor thumb viaright:100%with right-fill progress, clamp 0–1, mirror off-screen transition position. Also fixes swipethis.postTypeError (closure vars, AUD-TIME-01) and blocks 0% progress save before initial scroll (AUD-PERS-01). LTR unchangeda8895e63cfixI18nManager.isRTL→arrow-rightinSearchbarV2,ReaderAppbar,ReaderSearchbar); scheduler isolation — category-only updates no longer advance the globalLAST_UPDATE_TIMEtimestampcf76883ae(part)LanguagePickerModal) with app-restart promptFiles:
core.js,index.js,pageReader.css,toolWrapper.css,SearchbarV2.tsx,ReaderAppbar.tsx,updates/index.ts,LanguagePickerModal.tsx2.3. Stats & Time-Tracking Integrity (5 commits)
09a966a35fixDistributionBarsingle-full-circle fallback (no 360° SVG collapse), velocity guard (cph > 50), Unicode-aware genre normalization (CJK/Cyrillic/accented preserved), memoizedPluginsTab, reduce-based max (no spread-stack risk). Migration 007:idx_novel_inLibraryindex forWHERE inLibrary=1seekscf76883aefeatreadingChaptersaggregate (distinct chapter IDs from sessions of library novels); velocity computed from active chapters instead of lifetimechaptersRead, implausible-velocity heuristics dropped; manual sessions flush on inactive checkpoint ticks317e6e1cefeat"20s"not"<1m", newstatsScreen.secsstring); stats refetch on screen focus viauseFocusEffect(no full-loader flash on silent refresh); end-to-end reading-time simulation tests (manual scroll, TTS incl. background, multi-checkpoint)b30abcc12fix26348b2c1fixTTSForegroundServicegains a speaking-time clock (segment opens on utteranceonStart, closes on drain/stop/pause;speak/speakBatchboundaries flush stale segments), bridged viaTTSHighlightModule.getTtsPlaybackClock()as{spokenMs, speaking}.useTimeTrackingsnapshots the clock on app-background and tops up unattested time on foreground/unmount (wall-clock capped, 12h sanitized, <1s dropped). Post-foreground restart requires nativespeaking(no phantom sessions); fail-open when clock is null/unbound (identical to old behavior). NativeTTSSpeakingClockTest4/4 via RobolectricFiles:
useTimeTracking.ts,StatsQueries.ts,TimeTab.tsx,StatsScreen.tsx,utils.ts,DistributionBar.tsx,GenreSection.tsx,PluginsTab.tsx, migration007_*,TTSForegroundService.kt,TTSHighlightModule.kt,TTSSpeakingClockTest.kt(new),useTimeTracking.reconcile.test.ts(new),readingTime.simulation.test.ts2.4. Slider Gestures & Progress Persistence (3 commits)
d16880e17fixsliderDragStateglobal flag interlocks bottom-sheet TabView swipe while dragging, 400ms fallback keepsdragValueuntil controlled value catches up (no permanent thumb freeze). Persistence (AUD-PERS-02/03): MMKV-first synchronous write insaveProgress,.catchon allupdateChapterProgress/MarkReadpaths (incl.useNovel,useChapter,useTTSController)05c5e24f4fix31e0f1d54fixonTouchStart/onTouchEndtap detection (panGestureRef+HORIZONTAL_CLAIM_THRESHOLD) firesonSlidingCompletefor taps that never become pan gestures; flush-guard condition corrected to `Files:
Slider.tsx,sliderDragState.ts(new),ReaderBottomSheet.tsx,WebViewReader.tsx,useChapter.ts,useTTSController.ts,useNovel.ts,Slider.test.tsx,WebViewReader.backgroundFlush.test.tsx2.5. Build & Docs (2 commits)
9e737c9f4buildandroid/app/build.gradle(release packaging fix, 1 line)b7bd96318docsAGENTS.md/GEMINI.mdcontext refresh (test counts, post-merge hardening status)3. Branch Health Check (this review)
feat/fix/build/docs); audit-tagged fixes reference their finding IDs (AUD-SRCH/GEST/TIME/STAT/PERS/RTL).tsc --noEmit: 0 errors (re-verified at26348b2c1on 2026-09-05).console.log/debugger/TODO/FIXMEin added lines (excluding__tests__).en/strings.jsongains: search return-behavior keys,statsScreen.secs, RTL restart notes. Secondary locales intentionally deferred to a downstream localization pass.fbf73b4e3,dd53eeb3e,05c5e24f4,31e0f1d54) directly follow their parents — no squashing needed, history reads chronologically.26348b2c1touches native Kotlin + adds a Robolectric test — needs an Android release-build sanity check if CI doesn't cover it (commit message attests release build succeeds).4. Verification & Test Coverage
TTSSpeakingClockTest4/4 (Robolectric) + 10 existing media-session teststsc --noEmit)26348b2c1validation)Manual QA smoke checklist
5. Chronological Commit Log (16 Commits,
be0106d47..26348b2c1)6. How to Test & Review This PR