Conversation
…tions 4-subagent review verified the 2026-08 upstream merge analysis (170 commits): - All 6 live fork bug claims CONFIRMED, 9/9 ALREADY-HAVE claims SOUND, trap commit 72bfcad correctly flagged, PRDs A-D rated SOUND Corrections applied: - 0cb9da9 reclassified SKIP->PORT-A2 (pre-Drizzle expo-sqlite tx-concurrency fix) - 5 DIRECT labels -> MANUAL (31cb4b9, 93bc5e5, 2a919ec, f1fdafd, a062bee) - 9783c4d reclassified SKIP->PORT-A1 (safe back-nav deletion) - c3c891c relabeled SKIP-CI -> SKIP-INFRA - PRD-A: 29 fixes (16 Tier-1 + 6 SQL + 7 extended), epub/import.ts hunk added, precondition + fork-side test notes - PRD-C: require BOTH surfaceContainerLow+High theme keys - Counts: PORT 74->76, SKIP 96->94 Report: specs/upstream-merge-analysis-2026-08/REVIEW-2026-08-04.md
- unlink: return instead of throwing when file already missing (34be7a1) - wrap writeFile/readFile/copyFileContent in try/catch so IOException serializes across the TurboModule bridge instead of crashing the app process (45c4ea8) - guard EPUB import copyFile with a user-friendly error (45c4ea8) - close OkHttp response body and streams in downloadFile to prevent OOM during batch downloads (4088d59)
- ServiceManager.setMeta: invert throttle condition (< 1000) and compute delay as 1000 - (now - lastNotifUpdate) so the notification update is actually debounced by 1s instead of firing immediately (57b9d41) - ExportNovelAsEpubButton: strip filesystem-unsafe chars from epub filename with a safe fallback (9e38ad1)
normalizeText now removes surrounding quote characters (straight and curly) so TTS no longer reads them aloud. Length-preserving - only strips quote chars at string edges, paragraph index contract intact (54fedc2)
Transparent drawer background so the reader drawer doesn't flash a white seam over the chapter background (0ed7d87)
…l height - BottomTabBar: add 16px bottom padding per M3 spec and trim pressable vertical padding (intent of 31cb4b9; fork uses scaled padding.xs) - BrowseScreen: initialLayout for TabView prevents tab bar height jump; replace shadow with border to stop shadow flash (f1fdafd) - SetCategoriesModal: cap category list at 40% window height so long lists don't overflow the modal (a062bee)
String(trackItem.progress ?? 0) instead of progress.toString() so a null/undefined progress value no longer crashes the dialog (93bc5e5; myAnimeList.ts hunk skipped - fork already null-handles)
Ignore *.tsbuildinfo (2a919ec)
Remove useBackHandler + unconditional popTo so back always respects the navigation stack (9783c4d)
withExclusiveTransactionAsync opens a new connection (useNewConnection), so db.runAsync inside the callback executes OUTSIDE the transaction, silently losing atomicity for multi-statement operations (restoreLibrary, _restoreNovelAndChapters, transactionAsync, migrateNovel). Route statements through the txn parameter, matching the pre-existing pattern in ChapterQueries/LibraryUpdateQueries and upstream 0cb9da9. Test mock updated so tx proxies the shared runAsync mock (assertions on db.runAsync remain valid).
… theming, mock fidelity - fix(epub): getNovelDownloadedChapters range branch → LIMIT/OFFSET over flat (page, position) order. position is per-page (resets in insertChapters), so the old position-window query matched every page for multi-page novels → wrong/duplicated chapter set on chapter-range EPUB export. 6 new tests pin single-page parity, multi-page correct set, overrun clamping, and isDownloaded filter. - fix(epub): carry LIVE toggle values through the onSubmit payload (EpubExportOptions) instead of reading stale useChapterReaderSettings() closure at export time. First export after toggling was one submit behind for all 4 toggles (appTheme/CSS/JS/chapterNumber). Persistence call kept. 4 new tests on the pure payload builder. - fix(ui): render ErrorFallback inside ThemeProvider on the dbError path and move ThemeProvider above AppErrorBoundary so boundary-caught errors also render themed (was unstyled white screen + console.error for dark/AMOLED users). - test(mock): isDynamicThemeSupported is a plain boolean false in the material-you jest mock, matching the real module (a jest.fn was truthy, silently steering future dynamic-theme tests into the wrong branch). Validated: type-check clean, eslint 0 errors, 80 suites / 1304 tests passing (+10 vs baseline 1294). No PR created.
Triggers were only created in createInitialSchema (user_version === 0), so existing v2/v3 installs kept stale pre-julianday trigger bodies forever (CREATE TRIGGER IF NOT EXISTS is a no-op when the trigger exists). - New migration004: column guard (SQLite doesn't validate trigger-body column refs at CREATE time — error only on first fire) → DROP ×4 → CREATE ×4 from the shared NovelTable/CategoryTable constants (single source of truth, fresh + migrated installs converge) → julianday lastUpdatedAt backfill (same subquery the triggers use). - Perf fix in scope: update trigger now has 'AFTER UPDATE OF isDownloaded, unread, readTime, updatedTime' — previously fired on every Chapter UPDATE incl. per-paragraph TTS progress saves (pure waste); verified semantically safe (no computed column derives from progress). - add_category trigger included (pre-migration-system installs never had it → NULL sort → wrong category ordering). - Fresh installs keep user_version=2 (004 runs as idempotent no-op, matching the 003 precedent); db.ts untouched. - Tests: real better-sqlite3 engine via ExpoLikeDb adapter + real MigrationRunner (better-sqlite3 chosen over node:sqlite — CI pins Node 20). 28 tests: trigger recreation/drift invariant, fire behavior, lexicographic-vs-chronological divergence, backfill, column-list non-firing, idempotency, column guards, upgrade paths v0/v1/v2/v3, large-novel set, LibraryQueries sort rewrite. Validated: type-check clean, eslint 0 errors, 84 suites / 1332 tests passing (+28 vs baseline). No PR created.
extend the fork-maintained pnpm patch to use 'abortOnError = false' assignment syntax (was deprecated space-assignment 'abortOnError false'). This was the only fork-actionable warning in the release build; all other warnings are third-party node_modules or AGP-internal multi-string dependency declarations (Gradle 10 removals, non-blocking on Gradle 9.2.0). patch hash updated in pnpm-lock.yaml (f0171c0e → 6f6350cd) via pnpm patch-commit; verified applied in node_modules and release build warning count for this module is now zero.
The declarative TTS Text Cleanup ruleset can now also clean the visible reader DOM, so users maintain ONE ruleset instead of juggling a parallel Custom JS TreeWalker script (issue #19, filed against v2.1.3). - Settings: applyTo?: 'tts'|'visible'|'both' on TtsTextCleanupSettings (default 'tts' — zero behavior change for existing installs) - Engine: cleanVisibleText() rules-only, count-preserving visible pass (phonetic dictionary + Unicode normalization stay TTS-only); centralized gating so cleanTtsText/applyTtsTextCleanup no-op when applyTo==='visible'; emptied paragraphs padded with VISIBLE_PAD_CHAR (U+200B) to keep the RN <-> WebView paragraph-index contract intact - core.js bridge: setVisibleCleanup/requestVisibleCleanup/applyVisibleCleanup with pristine dataset.originalText snapshot captured pre-mutation; all three TTS text-read sites (speak, locked dialog, tts-queue lookahead) prefer the pristine snapshot so the full ruleset applies exactly once; re-run hooks after bionic/spacing DOM rebuild and stitched chapter append - WebViewReader: 'visible-cleanup' message allowlist + handler (RN cleans, injects write-back), setVisibleCleanup inject on load; effective settings read from ref (per-novel overrides honored) - UI: 'Applies to' picker (TTS audio only / Visible text only / Both) in the cleanup modal with TTS-only disclaimer; contextual copy in Accessibility + Reader TTS tabs; Custom JS editor hint - Envelope: exports lnreader-tts-cleanup v2 when applyTo non-default (v1 otherwise); import now validates versions (accepts 1-2, rejects others); missing/invalid applyTo maps to 'tts'; bare-object imports keep working - Tests: cleanVisibleText rules-only gating + count preservation, applyTo gating matrix, envelope version matrix, WebView bridge integration test; harden flaky 'uses MMKV saved position' test (macrotask flush after rerender + waitFor retry, same assertion) - Docs: README feature table + TTS Text Cleanup section + mermaid diagram; PRD Phase 10 (visible text unification)
- add sections for parallel updates, material you design, and trackers - document technical implementation of dynamic theme and concurrent updater - update "what's new" with stability fixes, platform updates, and db hardening - fix image link encoding for tts engine picker screenshot - restructure table of contents for improved navigation
TTS text cleanup pipeline (issue #19 follow-ups): - Remove duplicate 'Applies to' row in TTS cleanup modal (#1) - Re-inject setVisibleCleanup on effective-settings change so visible cleanup applies without reload; toggle-off restores pristine DOM text from dataset snapshots (#2) - Skip no-op visible write-back so inline markup is never flattened by a rule-less pass (#4) - Strict resolveTtsCleanupTarget gating: garbage persisted applyTo values behave as 'tts' everywhere (#5) UI/theme: - Slider claims only horizontal-dominant gestures so vertical swipes scroll the parent; tap-to-jump preserved; flicker fix intact (#3) - Menu item text uiScale-scaled (pre-change value restored); nav-bar dead code removed with rationale documented (#6) - Theme ErrorFallback on the dbError path via pure buildPaperTheme + ErrorFallbackPaperProvider (no DB-bound auto-backup hook) (#6) Docs & novel edge cases: - Correct README deprecation-warning overstatements (API 35 true; cleanup partial, tracked in plan.md) (#7) - getNovelChaptersByNumber prefers real chapterNumber match with position fallback; jump-to-chapter scroll target filter-safe (findIndex + clamp); fully-read novels keep Read button/FAB via chapters[0] fallback (#8) Tests: - useGithubUpdateChecker: first-launch check, 24h skip, ignoreVersion - theme-ID migration map 1-21 -> 100-108 pinned for light/dark - transformThemeId exported for testing Gates: 86 suites / 1435 tests (baseline 84/1349), TTS wake-cycle 7/7, type-check clean, lint 0 errors.
5 tasks
- add missing architecture links to table of contents - fold screenshots, demos, and changelog into collapsible sections - hide verbose architecture and tts configuration details by default
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. Summary
The 2026-08 upstream merge sync: a selective port of upstream
lnreader/lnreaderfixes and features (170 upstream commits analyzed, 76 worth porting, 94 skipped — see §2), delivered as 53 verified commits across four porting batches plus an independent audit fix batch:juliandaytriggers) + TTS visible-text & cleanup ruleset unification (issue #19)e9198c4c3)Every commit was verified against its claimed intent (not just its message): a 4-agent multi-POV audit cross-checked each port against the fork's actual architecture, found 3 initially-incomplete ports (fixed in follow-ups, see §4), found 1 real defect during the audit itself (fixed on the spot), and the 9-finding fix batch closed out the remaining MED/LOW items.
2. Context — why selective porting (not a full merge)
A full upstream merge remains infeasible — three architecture walls separate the repos:
TTSHighlightModule,TTSForegroundService, DoH) + background-actions.Strategy: selective cherry-pick / manual port, prioritizing (a) fixes where the fork carries byte-identical pre-fix code (i.e. the fork has the same live bugs), (b) high-user-value features that port cleanly, and (c) theme/UX modernization with zero TTS/DoH/per-novel-settings impact.
3. What this PR does
3.1 Batch A — 29 upstream safety fixes (key highlights)
edfdbaea6—deleteDownloadspreviously ranUPDATEwith noWHERE, wipingisDownloadedfor all chapters; now scopedWHERE id IN (…). Also fixeddeleteReadChaptersFromDbpassingnovelIdtwice (deleted the wrong disk folder).95f79626d/153038d70— chapter pages compared as strings ('10' < '2'); nowCAST(page AS INTEGER)everywhere.cf5b5923e—lastUpdatedAtcompared lexicographically in SQLite triggers; nowjulianday()(completed by Migration 004, §3.4).969f088cb→fa5e10b76— exclusive transactions: callbacks now run on thetxnobject (expo-sqlite opens a new connection), restoring true atomicity for library restore/migrations.91c10cf83— default-category lookup replaced hardcodedsort = 1.f352ec1dfnative-file ops wrapped in try/catch + stream close (top native crash class);ac1cb9153/9a4110de9tracker null guards;ac92f34e7genre parser.261b379a1strips leading/trailing quotes from normalized TTS text (length-preserving);b0b32fe28restores saved chapter progress from DB;a7eb69d09table overflow scroll + chip contrast;45bd7b0afdrawer seam.ce69ec0e2notification throttle no longer computes negative delays; EPUB filenames sanitized.d61228002restores search requests on submit.3.2 Batch B — 7 high-value features
f7c4b65e8— Kitsu tracker (4th tracker; OAuth + GraphQL search/score,kitsu.tsbyte-identical to upstream, 14 unit tests).a1d23a0a2— Parallel library updates (concurrency ≤ 3 sources, rate-limit safe, 2 tests).0c6a6dc96— Start Reading jumps to first unread chapter (Read button + FAB).62091cb07— Jump-to-chapter loads unloaded batches (novels with thousands of chapters) + drawer end-reach batch loading.032723bbc— EPUB chapter numbers option (epubIncludeChapterNumber).7ecb98ab4— Skip-version update notifications (preserved the fork's richer update dialog; also fixed the first-launch check bug).398ecb958— Configurable chapter download cooldown (service actually consumesgetChapterDownloadCooldownMs(); no hard-coded sleep).3.3 Batch C — UX/theme train (7 items + 2 build patches)
b07e701bb— context-based ThemeProvider with legacy theme-ID migration (1–21 → 100–108), instant re-render; fork'suiScaledensity scaling preserved.0f0dd626a— dynamic Material You colors (@pchmn/expo-material3-theme), Android 12+ only, graceful fallback.b1b08f7f2+2f27e8e65— custom MD3 Slider replacing@react-native-community/slider(dep removed; all 7 consumers migrated, TTS min/max/step preserved; release-flicker fixed).d7f25f94a— M3 top-tab indicators (TopTabBar) across 6 tab views.a8e6a24ba— standardized bottom sheets (surfaceContainerLow/Hightokens, 9 consumers).54d5d758a— modernized Menu (outside-tap + back-button dismissal, M3 tokens,uiScalekept).494ad9707+f716159ff— expo-material3-theme gradle namespace + abortOnError patches (required under Gradle 9.2.0 / AGP 8.12.0; verified applied innode_modules).3.4 Batch D PR#1 — Migration 004 + TTS issue #19
b6b367df6— DB Migration 004: recreates the 4 SQLitejuliandaytriggers on ALL installs (they were inert on existing installs), narrows the Chapter update trigger toAFTER UPDATE OF isDownloaded, unread, readTime, updatedTime(no longer fires on per-paragraph TTS progress saves), includesadd_category, with column guards +juliandaybackfill. 28 migration tests incl. full upgrade-path matrix (fresh/v1/v2/v3/empty/large).1f4833064— TTS: unify visible text and TTS cleanup rulesets (issue Feature Request: Unify Visible Text and TTS Cleanup Logic #19) — one declarative ruleset can now also clean the visible reader DOM (applyTo: 'tts' | 'visible' | 'both', default'tts'= zero behavior change). Count-preserving (U+200Bpadding keeps the RN↔WebView paragraph-index contract), pristine-snapshot TTS reads, envelope v2 export with version-validated import.3.5 Audit fix batch (
e9198c4c3) — 9 findings resolvedIndependent 4-POV verification of all 52 commits (before this one) found 2 MED UX defects + 1 MED UX regression risk + 6 LOW items. All resolved:
PressablesetVisibleCleanupon settings change; toggle-off restores pristine DOM from snapshotsshouldClaimPanResponder, 6px threshold); tap-to-jump preserved; flicker fix intactapplyTobehaved as'both'resolveTtsCleanupTarget()— garbage →'tts'everywherescaleDimension(16/20, uiScale); dead call removed + documented (upstream lnreader#1076 rationale); purebuildPaperTheme+ErrorFallbackPaperProvider(no DB-bound hook)chapterNumber-first query w/ position fallback;findIndexscroll target;chapters[0]fallbacktransformThemeIdexported + full 1–21→100–108 map pinned4. Gaps found & resolved during porting
Three Batch-A ports were initially incomplete and are fully resolved within this PR (verified):
f4f8defdfEPUB range orderingebea3c75a—LIMIT ? OFFSET ?over flat(page, position)order (+6 tests)969f088cbexclusive transactionsdb.runAsync(outside the txn's new connection) → atomicity silently lostfa5e10b76— all 6 sites rebound totx.runAsync(grep-verified zerodb.*in txn callbacks)cf5b5923ejuliandaytriggersCREATE TRIGGER IF NOT EXISTSnever recreated triggers on existing installsb6b367df6— Migration 004 drop/recreate + backfill (+28 tests)5. Verification
5.1 Automated gates (all green at HEAD
e9198c4c3)pnpm run testpnpm run type-checkpnpm run lintpnpm run format:checkpnpm run test:tts-wake-cyclepnpm run test:tts-refill5.2 Verification methodology (independent, per-commit)
git showdiffs + live code withfile:lineevidence; the 2 MED + regression-risk findings were re-verified by the main agent before fixing.uiScalescaling.@react-native-community/slider+@pchmn/expo-material3-themeonly.5.3 Manual test checklist
6. Files changed (high-level)
153 files, +7,079 / −1,195 across: database migrations/queries (+~1,900 lines incl. 28 migration tests), theme/UI components (new
Slider.tsx,TopTabBar.tsx,layout.ts), tracker (newkitsu.ts+ 14 tests), updates service (parallel runner + 2 tests), TTS cleanup engine (visible-text unification, +190 test lines), native (NativeFile.kthardening,core.jsvisible-cleanup bridge), plus the audit fix batch. Full list in the commit log (git log 6550a7884..e9198c4c3).7. Out of scope / deferred (tracked in
.agents/upstream-merge-memory.md)lastReadAtstill lexicographic for non-padded imported data; Migration 004 backfill is O(novels×chapters) one-time cost on very large libraries; remaining in-app Kotlin deprecation warnings tracked inplan.md.8. Commit list (53, oldest → newest)
6550a7884docs(merge) independent review ·f352ec1dfnative-file hardening ·ce69ec0e2throttle+epub filenames ·a7eb69d09table overflow/chip/nav ·261b379a1TTS quote-strip ·45bd7b0afdrawer seam ·341cf16c3M3 bottom nav ·9a4110de9tracker null-safe ·7d0d0a1bfgitignore ·c2a10f08frepo back nav ·95f79626dnumeric pages ·f4f8defdfepub ordering ·153038d70numeric cast ·da8b1b337category reorder ·969f088cbexclusive txn ·edfdbaea6download scoping ·91c10cf83default category ·cf5b5923ejulianday ·b0b32fe28progress restore ·ac1cb9153tracker client ids ·ac92f34e7genres ·ca9261308statuses ·d61228002tracker search ·f0fb47943docs Batch A ·a1d23a0a2parallel updates ·f7c4b65e8Kitsu ·0c6a6dc96first-unread ·62091cb07jump-to-chapter ·032723bbcepub numbers ·7ecb98ab4skip-version ·398ecb958cooldown ·275c1069edocs Batch B ·7b47bb66bdocs TTS note ·b07e701bbThemeProvider ·0f0dd626aMaterial You ·b1b08f7f2MD3 slider ·2f27e8e65slider flicker ·d7f25f94atop tabs ·a8e6a24babottom sheets ·54d5d758amenu ·84a59e970docs Batch C ·494ad9707gradle patch ·2e5b119b6docs registry ·fa5e10b76txn-object fix ·131179d6edocs audit ·ebea3c75aquick-fix batch ·2eca9c1c9docs quick-fix ·b6b367df6migration 004 ·4bdc18bb9docs Batch D PR1 ·f716159ffabortOnError patch ·1f4833064TTS visible-cleanup unify (issue #19) ·9d0751ecfdocs readme ·e9198c4c3audit fix batch (9 findings)9. How to test this PR
Manual smoke: launch on Android 12+ (dynamic colors), open a chapter (TTS + visible cleanup), Library → Update (parallel), a novel page (first-unread, jump-to-chapter, EPUB export), Settings → Trackers (Kitsu).