diff --git a/.agents/fix-batch-1-9-handoff.md b/.agents/fix-batch-1-9-handoff.md new file mode 100644 index 0000000000..d2443e6351 --- /dev/null +++ b/.agents/fix-batch-1-9-handoff.md @@ -0,0 +1,79 @@ +# Fix Batch #1–#9 — Handoff Record (COMPLETED 2026-08-06) + +**Date**: 2026-08-06 (all groups implemented + verified) +**Branch**: `merge/original-sync-batch-c` (HEAD `9d0751ecf`, 52 commits ahead of origin/master) +**Goal**: Implement the 9 audit findings, then advance `origin/dev` to this branch. + +## ✅ ALL 9 ITEMS IMPLEMENTED + VERIFIED (2026-08-06) + +Final gates: type-check ✅ · lint 0 errors (7 pre-existing warnings) ✅ · format ✅ · **full suite 86 suites / 1435 tests** (baseline 84/1349 → +2 suites / +86 tests) · TTS wake-cycle 7/7 ✅ · TTS refill ✅. Working tree: 20 modified + 3 new files, **UNCOMMITTED** (commit decision pending user). + +- Group 1 (#1,#2,#4,#5): duplicate "Applies to" row removed; setVisibleCleanup re-inject (prop/per-novel/MMKV effects) + core.js restore-pristine on toggle-off; no-op write-back skipped; resolveTtsCleanupTarget strict gating. +- Group 2 (#3): Slider claims only horizontal-dominant gestures (`shouldClaimPanResponder`, 6px threshold), Pressable tap-to-jump kept, flicker fix intact, 9 new tests (16 total). +- Group 3 (#6): Menu fontSize/lineHeight uiScale-scaled (scaleDimension(16/20, uiScale) — pre-change value restored); setBarColor dead code removed + doc comment (upstream #1076 rationale); buildPaperTheme extracted, ErrorFallbackPaperProvider (no auto-backup hook, safe pre-DB). +- Group 4a (#7,#8): README deprecation overstatements corrected (2 spots); getNovelChaptersByNumber prefers chapterNumber with position fallback (+4 tests); JumpToChapterModal scrolls via findIndex (loadedChaptersRef) with clamped fallback; ReadButton/FAB fall back to chapters[0] when novel fully read. +- Group 4b (#9): useGithubUpdateChecker tests — first-launch check, 24h skip, ignoreVersion (6 new, 4 existing kept); transformThemeId exported + full 1-21→100-108 map pinned for light/dark in new useTheme.test.ts. + +Remaining: user decision on commit grouping, then advance `origin/dev` (clean fast-forward — 0 commits on origin/dev missing from HEAD). + +--- + +## Audit context + +Full verification report in the chat session (2026-08-05). Source of the 9 findings: +- POV-1 (TTS unify commit `1f4833064`) → items #1, #2, #4, #5 +- POV-3 (Batch C UI) → items #3, #6 +- POV-4 (Features/docs) → items #7, #8, #9 +- Gates baseline at start: type-check ✅ / lint 0 errors (7 pre-existing warnings) / format ✅ / **84 suites, 1349 tests** / TTS wake-cycle 7/7. + +## ✅ DONE — Group 1 (TTS pipeline): items #1, #2, #4, #5 + +**Uncommitted working-tree changes (6 files, +220/−41)** — implemented by worker subagent, diff reviewed + verified by main agent, quick gates pass (type-check ✅, lint 0 errors ✅, format ✅). Worker reported **116 targeted tests passing** before it was interrupted; **full suite NOT re-run yet** (pending — run before commit). + +| File | Change | +|---|---| +| `src/screens/settings/SettingsReaderScreen/Modals/TtsTextCleanupModal.tsx` | **#1** removed the duplicate "Applies to" Pressable row (the second, byte-identical block) — exactly one row remains | +| `android/app/src/main/assets/js/core.js` | **#2** `setVisibleCleanup(false)` now restores pristine text from `dataset.originalText` + deletes snapshots (toggle-off reverts DOM immediately). **#4** `applyVisibleCleanup` skips elements whose text is unchanged (never flattens `//` on no-op) | +| `src/screens/reader/components/WebViewReader.tsx` | **#2** added `webViewLoadedRef`, `lastVisibleCleanupInjectedRef`, `injectVisibleCleanupState()` callback; wired into prop effect (~L332), per-novel effect (~L427), MMKV listener (~L540). onLoadEnd (L1526-1533) sets `webViewLoadedRef = true` + seeds `lastVisibleCleanupInjectedRef` so no double-inject. **#4** `visible-cleanup` handler bails when `!shouldCleanVisibleText(settings)` or when cleaned === input (no change) | +| `src/utils/htmlParagraphExtractor.ts` | **#5** new `resolveTtsCleanupTarget(settings)` helper (valid: 'visible'/'both', anything else → 'tts'); used in `cleanTtsText`, `applyTtsTextCleanup`, `shouldCleanVisibleText` — all sites now agree on garbage values | +| `src/utils/__tests__/ttsTextCleanup.test.ts` (+44) | tests for resolveTtsCleanupTarget garbage-value matrix + 'both'/'visible' behavior | +| `src/screens/reader/components/__tests__/WebViewReader.integration.test.tsx` (+59) | tests for re-inject + no-op write-back skip | + +**Verification status**: 116 targeted tests pass (worker). Full suite pending. TTS wake-cycle/refill pending. + +## ✅ DONE — Group 2 (item #3): Slider vertical-scroll swallow + +**Evidence**: `src/components/Slider/Slider.tsx:176-177` — `onStartShouldSetPanResponder: () => !disabled`, `onMoveShouldSetPanResponder: () => !disabled` claim ALL touches → vertical swipes starting on the slider are swallowed, blocking scroll-through in ReaderTTSTab bottom sheet, AccessibilityTab, SettingsAppearanceScreen, Onboarding. +**Required behavior**: vertical drag on slider must scroll the parent; horizontal drag adjusts value; tap still jumps the handle. +**Approach (verified standard pattern)**: `onStartShouldSetPanResponder: () => false`; `onMoveShouldSetPanResponder: (_, g) => !disabled && Math.abs(g.dx) > threshold && Math.abs(g.dx) > Math.abs(g.dy)` (horizontal-dominant only; parent vertical ScrollView wins vertical gestures since child never claims at start). Preserve tap-to-jump (wrap root in Pressable with onPress → updateFromPosition(locationX), or equivalent — keep `updateFromPosition` reuse). Keep flicker fix (dragValue until controlled value catches up) and add/extend `Slider.test.tsx` (e.g. responder decision helper extracted as pure function: vertical → false, horizontal → true, tap path still works). + +## ✅ DONE — Group 3 (item #6): UI polish + +- **Menu text not uiScale-scaled**: `src/components/Menu/index.tsx:253` `menuItemText fontSize: 14` hardcoded → make uiScale-scaled (pre-change value was `scaleDimension(16, uiScale)`; `uiScale` already available in the component, styles memo depends on it). +- **Nav-bar background no longer set**: `src/theme/utils/setBarColor.ts:19` `//NavigationBar.setBackgroundColorAsync(color);` commented out (function name misleading). Decide: restore the call (check git history `git log -p -- src/theme/utils/setBarColor.ts` for why it was removed — if M3/edge-to-edge intentional, instead remove the dead call + rename or document; prefer restoring behavior to match the name unless there is a hard reason). +- **dbError ErrorFallback button not app-themed**: `App.tsx:109-115` dbError path renders `` with no PaperProvider → PaperButton falls back to paper defaults. Wrap with the same `ThemedPaperProvider` used in the main tree (ThemeProvider is MMKV-only, ThemedPaperProvider is pure UI — safe during dbError; verify `ThemedPaperProvider` doesn't touch DB). + +## ✅ DONE — Group 4a (items #7, #8a, #8b, #8c): docs + edge cases + +**Uncommitted (alongside Groups 1–3). Full suite: 85 suites / 1365 tests passing.** +- **#7 README**: both overclaims corrected (`README.md:55`, `:379`) — API-35 targeting true, third-party patches via pnpm, remaining in-app Kotlin deprecation warnings tracked in plan.md. +- **#8a `getNovelChaptersByNumber`** (`ChapterQueries.ts:441-457`): now async — tries `WHERE chapterNumber = ? ORDER BY position ASC` for valid positive finite numbers, falls back to legacy `position = chapterNumber - 1` when no chapterNumber match. New test file `ChapterQueries.byNumber.test.ts` (4 tests). +- **#8b JumpToChapterModal**: added `loadedChaptersRef` mirror + re-resolve scroll target by chapter id after `loadUpToBatch` (was raw `chap.position`), clamped fallback when still absent; empty-list guard. ChapterDrawer already findIndex-based (no change needed). +- **#8c ReadButton + FAB**: `ReadButton` gets `chapters?: ChapterInfo[]` prop; `targetChapter = lastRead ?? firstUnreadChapter ?? chapters[0]` (`ReadButton.tsx:26`); `NovelScreenList` FAB `continueReadingChapter = lastRead ?? firstUnreadChapter ?? chapters[0]` (`:249`). `NovelInfoHeader` passes `chapters` through. +- Gates: type-check ✅, lint 0 errors (7 pre-existing warnings) ✅, format ✅, full suite ✅. + +## ✅ DONE — Group 4b (items #9a, #9b): tests +- **#9a** `src/hooks/common/__tests__/useGithubUpdateChecker.test.ts` — add tests for `ignoreVersion`/IGNORED_UPDATE_VERSION + first-launch `shouldCheckForUpdate()` returning true when `!lastCheckTime`. +- **#9b** new/extended `useTheme` test pinning `transformThemeId` map (1-21 → 100-108, light/dark). `transformThemeId` currently NOT exported from `src/hooks/persisted/useTheme.ts:103` — may need a pure-helper export or test via ThemeProvider. + +## Final steps (after Group 4b) + +1. Sequential subagent per group (repo precedent: main agent reviews diff + runs gates between groups). One writer at a time. +2. Full gates: `pnpm run type-check`, `pnpm run lint` (0 errors), `pnpm run format:check`, `pnpm run test` (expect ≥ 84 suites / 1349 tests + new ones), TTS wake-cycle 7/7, `pnpm run test:tts-refill`. +3. `pnpm run format && git add .` then commit (repo workflow: format before commit). Ask user about commit grouping (one commit per group vs single batch) before committing. +4. Then advance `origin/dev` → this branch (currently `git rev-list --count HEAD..origin/dev` = 0, clean fast-forward) and push — confirm with user first. + +## Notes + +- Working tree currently has the Group 1-3 + 4a changes, UNCOMMITTED. Do NOT reset. +- `origin/dev` fully contained in HEAD (0 commits on origin/dev not on HEAD) — advancing is a clean fast-forward once fixes are committed. diff --git a/.agents/upstream-merge-memory.md b/.agents/upstream-merge-memory.md index 608a8c71cb..77d2137d51 100644 --- a/.agents/upstream-merge-memory.md +++ b/.agents/upstream-merge-memory.md @@ -6,8 +6,9 @@ applyTo: '**' - Original Repo: https://github.com/lnreader/lnreader - Fork Repo: https://github.com/bizzkoot/lnreader -- Last Sync Date: 2025-12-30 -- Last Sync Commit: 7c57b7421 (upstream 2e1a0ab96) +- Last Sync Date: 2026-08-05 +- Last Sync Commit: c3260e8e0 (upstream/master @ 2026-08-01) — analyzed 2026-08-03; batches A (29 fixes), B (7 features), C (7 theme/UX items) ported 2026-08-04/05 +- Divergence: full merge infeasible (3 architecture walls: Drizzle/op-sqlite, Nitro/WorkManager, Expo-managed) → selective cherry-pick / manual port strategy # Custom Modifications Registry @@ -29,13 +30,74 @@ applyTo: '**' - Custom TTS handling in WebViewReader - Background TTS Playback - Screen Wake Sync +- Batch A: 29 upstream safety fixes (DB/download/native-file/reader) — see Batch A history entry +- Batch B: Kitsu tracker, parallel library updates, first-unread FAB, jump-to-chapter, EPUB chapter numbers, skip-version updates, download cooldown — see Batch B history entry +- Batch C: context ThemeProvider + theme-ID migration, dynamic Material You colors, MD3 Slider, TopTabBar, standardized bottom sheet, modernized Menu — see Batch C history entry ## Removed/Deprecated - src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx (Upstream file removed in favor of ReaderTTSTab.tsx) +- @react-native-community/slider (replaced by MD3 Slider, Batch C-3) +- src/screens/settings/SettingsReaderScreen/components/TabBar.tsx (replaced by TopTabBar, Batch C-5) # Merge History +## 2026-08-03 - Upstream Merge Analysis (170 commits, PLANNED - no code changes) + +**Analysis Range**: upstream/master @ c3260e8e0 (2026-08-01) from merge base 467a97dcf (2025-12-24) +**Commits Analyzed**: 170 (66 fix, 56 feat, 17 chore, 14 refactor, 5 docs, 4 perf, 2 test, 1 ci) +**Method**: 4 parallel subagents (chronological groups) cross-referencing git diffs vs fork source; key claims spot-verified +**Strategy**: Selective cherry-pick / manual port (full merge still infeasible - 3 architecture walls) + +**Architecture Walls (unchanged since 2025-12-30, now deeper):** +1. DB: upstream Drizzle ORM + op-sqlite (#1735) vs fork expo-sqlite raw SQL + custom MigrationRunner +2. TTS/Native: upstream Nitro modules + MediaSession + WorkManager (#1889/#1896) vs fork custom Kotlin (TTSHighlightModule, TTSForegroundService, DoH) + background-actions +3. Build: upstream Expo 55 managed + rock CLI (#1885/#1812) vs fork Expo 54 bare + Gradle 9.2.0 + +**Outcome**: 76 of 170 commits worth porting (batches A-D). 94 skipped (CI/release/docs/infra/ALREADY-HAVE/Drizzle). (Updated 2026-08-04 after independent review - see below.) + +**Key findings:** +- Fork often carries byte-identical PRE-FIX code - has the same live bugs upstream just fixed +- Verified live fork bugs: deleteReadChaptersFromDb passes novelId as chapter id (wrong folder); deleteDownloads UPDATE without WHERE wipes all flags; ServiceManager.setMeta negative-delay throttle; WHERE sort=1 default-category breaks on reorder; TEXT page comparison in multi-page queries; TTS reads quotes aloud (normalizeText); unawaited runAsync inside withTransactionAsync (NovelQueries/helpers.tsx) +- ALREADY-HAVE (skip): DoH, MediaSession/media notification, WebView-reset fix, metro middleware, EPUB exporter, volume buttons +- TRAP commit: 72bfcad03 (lint cleanup touching WebViewReader/ReaderScreen/ChapterContext) - never cherry-pick +- Raw-SQL layer is a PORTABILITY ADVANTAGE for pre-Drizzle-era SQL fixes + +**Batches planned (details in specs/upstream-merge-analysis-2026-08/):** +- Batch A (29 fixes, half-day): 16 Tier-1 (8 DIRECT clean-apply + 8 MANUAL) + 6 SQL + 7 extended (incl. c3260e8e0 download-removal fix and 0cb9da9027 tx-concurrency fix) +- Batch B (7 features + 15 stretch): Kitsu tracker, ignorable updates, EPUB chapter numbers, parallel library updates, first-unread FAB, jump-to-chapter fix, download cooldown +- Batch C (7 + 2): theme refactor -> dynamic Material You colors -> MD3 slider/tabs/menu +- Batch D (5 core + 10 optional, separate PRs): time tracking, in-chapter search, stats overhaul, reader perf sprint, scheduled updates + +**2026-08-04 - Independent Review + Corrections Applied**: 4 review subagents verified the analysis (all 6 live-bug claims CONFIRMED, 9/9 ALREADY-HAVE SOUND, trap commit correct, PRDs SOUND). Corrections applied to manifest/PRDs/analysis/README: (1) 0cb9da9027 reclassified SKIP->PORT-A2 (pre-Drizzle expo-sqlite tx fix); (2) 5 DIRECT labels -> MANUAL (31cb4b99, 93bc5e5e, 2a919ec0, f1fdafd3, a062beee - fork rewrote those files, hunks fail apply); (3) 9783c4d5e3 reclassified SKIP->PORT-A1/low; (4) c3c891cea0 relabeled SKIP-CI->SKIP-INFRA; PRD-C now requires BOTH surfaceContainerLow+High keys; PRD-A 45c4ea8ca0 now includes epub/import.ts hunk. Full report: specs/upstream-merge-analysis-2026-08/REVIEW-2026-08-04.md + +**Docs**: specs/upstream-merge-analysis-2026-08/ (README.md, analysis.md, commit-manifest.csv, batches/PRD-A..D, REVIEW-2026-08-04.md) +**Status**: Batch A ✅ + Batch B ✅ COMPLETED 2026-08-04 on `merge/original-sync-batch-b` (head `275c106`, 32 commits ahead of master, PUSHED to origin 2026-08-04); Batch C ✅ COMPLETED 2026-08-05 on `merge/original-sync-batch-c` (pushed); Batch D pending. ✅ CORRECTED: dev has NO pending TTS work — TTS text-cleanup was merged to master via PR #18 (`a7c030a64`); the only dev-only commit is `6550a7884` (docs/merge review), already inside the batch branch history. + +**2026-08-04 - Batch A (Safety Fixes) ✅ COMPLETED**: 29/29 upstream fixes ported on `merge/original-sync-batch-b`. Validated: **73 suites / 1265 tests passing (zero regressions)**. Last item: `8f53550d2c` restore tracker search requests (commit `d61228002`). + +**2026-08-04 - Batch B (High-Value Features, core 7/7) ✅ COMPLETED**: ported via sequential subagents (orchestrator = main agent, checked each diff + ran gates). +- B-1 `799845426c` parallel library updates (`a1d23a0a2`, +2 unit tests) +- B-2 `c40edd5b2c` Kitsu tracker (`f7c4b65e8`, kitsu.ts byte-identical to upstream, 14 unit tests, kitsu.png extracted via `git cat-file`) +- B-3 `8f237909d5` first-unread-chapter button/FAB (`0c6a6dc96`) +- B-4 `4ad0639796` jump-to-chapter loads unloaded batches + drawer end-reach (`62091cb07`, console.error→novelLog) +- B-5 `345d084eba` EPUB chapter numbers (`032723bbc`, fork's cd-z epub-creator loop; strings/types regenerated) +- B-6 `c0877a9b06` skip-version update notifications (`7ecb98ab4`, HYBRID port: preserved fork's richer NewUpdateDialog, added ignoreVersion to useGithubUpdateChecker + fixed first-launch check bug) +- B-7 `5d996f1388` configurable download cooldown (`398ecb958`, modal adapted to fork scaling) +- Validated: type-check ✅, lint ✅ (5 pre-existing warnings), format ✅, **75 suites / 1281 tests passing**, TTS wake-cycle 7/7 ✅, TTS refill ✅. +- Stretch items (15) NOT ported — deferred. + +**2026-08-05 - Batch C (UX/Theme train, core 7/7) ✅ COMPLETED**: ported on `merge/original-sync-batch-c` (branch from batch-b head; sequential subagents, main agent checked each). +- C-1 `8f47e8fd1e` theme-switcher refactor → context `ThemeProvider` + legacy-id migration 1-21→100-108 (`b07e701bb`; SKIPPED react-native-theme-switch-animation native dep) +- C-2 `44c8e54ed8` dynamic Material You colors (`0f0dd626a`; ADDED @pchmn/expo-material3-theme dep + jest mock; Android 12+ only, graceful fallback) +- C-3 `0c8546188e` MD3 Slider replacing @react-native-community/slider (`b1b08f7f2`; migrated 7 fork consumers incl. ReaderTTSTab/AccessibilityTab; dep removed) +- C-4 `6d2a9f8e15` slider flicker fix (`2f27e8e`) +- C-5 `3d34658d0f` M3 top tab indicators (`d7f25f94a`; new TopTabBar + SettingsReaderScreen TabView rework; used theme.surface since surfaceContainer keys absent then) +- C-6 `17c891e133` bottom-sheet UX standardization (`a8e6a24ba`; added surfaceContainerLow+High to ThemeColors + computed colors; layout.ts; 9 consumers cleaned; upstream retains handleComponent={null}) +- C-7 `e9f6bdaa20` Menu modernization (`54d5d758a`; NativeModal outside-tap/back dismiss + M3 tokens, kept uiScale scaling) +- Validated: type-check ✅, lint ✅ (0 errors; 7 warnings pre-existing/Slider-inline), format ✅, **78 suites / 1294 tests passing**, TTS wake-cycle 7/7 ✅, TTS refill ✅. +- Optional C items (`098782d6aa`, `99c31d56bb`) NOT ported. + ## 2025-12-30 - Upstream Merge Analysis (FAILED - DIVERGENCE DETECTED) **Analysis Summary:** @@ -186,3 +248,45 @@ The fork is no longer merge-compatible with upstream due to fundamental architec - Merge Date: 2025-12-11 21:46:22 +0800 - Post-merge Checks (local): lint: 17 warnings (no errors); type-check: passed; unit tests: all passed - Merge Branch Deleted: Yes (origin branch `merge/dev-into-master-20251211-ba60ecf9` removed) + +## 2026-08-05 - Post-Port Audit of Batches A/B/C (43 commits ahead of origin/master) + 1 FIX + +**Method**: 4 parallel read-only review subagents (Batch A / Batch B / Batch C / fork-integrity cross-cut) + main-agent verification of the one MED finding. Gates run independently: type-check ✅, lint 0 errors ✅, **78 suites / 1294 tests passing** ✅. + +**Batch A (21 code commits) — 19 ✅ CORRECT / 1 ⚠️ MED / 2 ⚠️ LOW**: +- ✅ Faithful ports incl. edfdbaea6 (download-deletion scoping, exemplary tests), 261b379a1 (core.js TTS quote-strip, length-preserving, index-contract safe), b0b32fe28 (progress restore, no TTS interference), 969f088cb's await fix, d61228002 (tracker search submit). +- ⚠️ **MED — FIXED (commit fa5e10b76)**: 969f088cb ported `withExclusiveTransactionAsync` but kept `db.runAsync` inside callbacks. expo-sqlite's `withExclusiveTransactionAsync` opens a NEW connection (`useNewConnection: true`); statements must run on the `txn` object. `db.runAsync` inside executed OUTSIDE the transaction → atomicity silently lost for `restoreLibrary`, `_restoreNovelAndChapters`, `transactionAsync` (helpers.tsx), `migrateNovel`. Fix: all 6 sites now use `tx` (4 fixed + ChapterQueries/LibraryUpdateQueries already correct). Test mock updated (tx proxies shared runAsync mock; all 54 DB tests still pass). Verified: grep shows zero `db.runAsync` inside transaction callbacks. +- ⚠️ LOW (deferred): f4f8defdf (EPUB range export not reworked to LIMIT/OFFSET — pre-existing multi-page position-window quirk); cf5b5923e (julianday trigger half inert on existing installs — no trigger recreation on bootstrap). + +**Batch B (7 features) — all ✅ CORRECT, 0 regressions**: B-1 parallel updates (2 tests), B-2 Kitsu (byte-identical, 14 tests; upstream-inherited score-scale quirk noted), B-3 first-unread FAB, B-4 jump-to-chapter + drawer end-reach, B-5 EPUB chapter numbers (⚠️ minor: first-submit staleness reading setting from closure instead of payload — inherits pre-existing epubUseAppTheme pattern), B-6 skip-version (fork's richer dialog preserved; first-launch check bug fixed intentionally), B-7 download cooldown (service actually uses getChapterDownloadCooldownMs; no hard-coded sleep left). No TTS/DoH/MMKV files touched. + +**Batch C (7 theme/UX + 1 build patch) — all ✅ PASS, 0 regressions**: C-1 theme-ID migration 1-21→100-108 map verified complete, no stale consumers; C-2 Material You graceful fallback { }, []); if (dbError) { - return ; + return ( + + + + + + ); } if (!isDbReady) { @@ -113,21 +124,71 @@ const App = () => { return ( - - - - - - -
- - - - + + + + + + + +
+ + + + + ); }; +/** + * Builds the react-native-paper theme from the app theme + uiScale. + * Pure function (no hooks, no side effects) so it can also be used on the + * dbError path where DB-dependent hooks must not run. + */ +const buildPaperTheme = ( + appTheme: ReturnType, + uiScale: number, +) => { + const baseTheme = appTheme.isDark ? MD3DarkTheme : MD3LightTheme; + const scaledFonts = getScaledFonts(uiScale); + + return { + ...baseTheme, + fonts: scaledFonts, + colors: { + ...baseTheme.colors, + primary: appTheme.primary, + onPrimary: appTheme.onPrimary, + primaryContainer: appTheme.primaryContainer, + onPrimaryContainer: appTheme.onPrimaryContainer, + secondary: appTheme.secondary, + onSecondary: appTheme.onSecondary, + secondaryContainer: appTheme.secondaryContainer, + onSecondaryContainer: appTheme.onSecondaryContainer, + tertiary: appTheme.tertiary, + onTertiary: appTheme.onTertiary, + tertiaryContainer: appTheme.tertiaryContainer, + onTertiaryContainer: appTheme.onTertiaryContainer, + error: appTheme.error, + onError: appTheme.onError, + errorContainer: appTheme.errorContainer, + onErrorContainer: appTheme.onErrorContainer, + background: appTheme.background, + onBackground: appTheme.onBackground, + surface: appTheme.surface, + onSurface: appTheme.onSurface, + surfaceVariant: appTheme.surfaceVariant, + onSurfaceVariant: appTheme.onSurfaceVariant, + outline: appTheme.outline, + outlineVariant: appTheme.outlineVariant, + inverseSurface: appTheme.inverseSurface, + inverseOnSurface: appTheme.inverseOnSurface, + inversePrimary: appTheme.inversePrimary, + }, + }; +}; + /** * ThemedPaperProvider - Wraps PaperProvider with scaled fonts based on uiScale setting. * This is a separate component because hooks can only be used after DB initialization. @@ -144,45 +205,32 @@ const ThemedPaperProvider: React.FC<{ children: React.ReactNode }> = ({ checkAndTriggerBackup(); }, [checkAndTriggerBackup]); - const paperTheme = useMemo(() => { - const baseTheme = appTheme.isDark ? MD3DarkTheme : MD3LightTheme; - const scaledFonts = getScaledFonts(uiScale); + const paperTheme = useMemo( + () => buildPaperTheme(appTheme, uiScale), + [appTheme, uiScale], + ); - return { - ...baseTheme, - fonts: scaledFonts, - colors: { - ...baseTheme.colors, - primary: appTheme.primary, - onPrimary: appTheme.onPrimary, - primaryContainer: appTheme.primaryContainer, - onPrimaryContainer: appTheme.onPrimaryContainer, - secondary: appTheme.secondary, - onSecondary: appTheme.onSecondary, - secondaryContainer: appTheme.secondaryContainer, - onSecondaryContainer: appTheme.onSecondaryContainer, - tertiary: appTheme.tertiary, - onTertiary: appTheme.onTertiary, - tertiaryContainer: appTheme.tertiaryContainer, - onTertiaryContainer: appTheme.onTertiaryContainer, - error: appTheme.error, - onError: appTheme.onError, - errorContainer: appTheme.errorContainer, - onErrorContainer: appTheme.onErrorContainer, - background: appTheme.background, - onBackground: appTheme.onBackground, - surface: appTheme.surface, - onSurface: appTheme.onSurface, - surfaceVariant: appTheme.surfaceVariant, - onSurfaceVariant: appTheme.onSurfaceVariant, - outline: appTheme.outline, - outlineVariant: appTheme.outlineVariant, - inverseSurface: appTheme.inverseSurface, - inverseOnSurface: appTheme.inverseOnSurface, - inversePrimary: appTheme.inversePrimary, - }, - }; - }, [uiScale, appTheme]); + return {children}; +}; + +/** + * ErrorFallbackPaperProvider - PaperProvider for the pre-DB error screen. + * Uses the SAME scaled/full-color theme as the main tree via buildPaperTheme, + * but deliberately does NOT run ThemedPaperProvider's auto-backup hook (which + * triggers ServiceManager backup flows and must only mount after DB init). + * Hooks used here (useAppSettings/useTheme) are MMKV/context-only and safe + * before DB initialization — ErrorFallback itself already uses them. + */ +const ErrorFallbackPaperProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const { uiScale = 1.0 } = useAppSettings(); + const appTheme = useTheme(); + + const paperTheme = useMemo( + () => buildPaperTheme(appTheme, uiScale), + [appTheme, uiScale], + ); return {children}; }; diff --git a/README.md b/README.md index 910b7a4788..108976b647 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,10 @@ This fork builds on the original LNReader with enhanced features focused on acce - **System-Wide UI Scaling**: Adjustable layout scaling for better accessibility and consistent experience across devices - **Enhanced Backup System**: Versioned schema with migration pipeline, multi-location support (Local, Google Drive, Self-Hosted) - **Improved App Updates**: In-app download with automatic backup before update -- **Better Android Compatibility**: Support for API 35+ with all deprecation warnings resolved +- **Parallel Library Updates**: Concurrent multi-source updating (up to 3 sources at once) for significantly faster library refreshes +- **Expanded Trackers**: Kitsu added alongside AniList, MyAnimeList, and MangaUpdates +- **Material You & MD3 Design**: Dynamic wallpaper-derived color palettes and modern Material Design 3 components +- **Better Android Compatibility**: Support for API 35+ (Android 15+); third-party Gradle/deprecation issues addressed via patches — remaining in-app Kotlin deprecation warnings tracked in plan.md --- @@ -65,15 +68,15 @@ This fork builds on the original LNReader with enhanced features focused on acce - [TTS Engine Picker](#tts-engine-picker) - [TTS Text Cleanup](#tts-text-cleanup) - [Reader Experience](#reader-experience) + - [Library & Updates](#library--updates) + - [Trackers](#trackers) - [Network \& Security](#network--security) - [UI \& Accessibility](#ui--accessibility) - [Backup \& Sync](#backup--sync) - [What's New](#whats-new) - - [Network \& Security Enhancements](#network--security-enhancements) - - [UI \& Accessibility](#ui--accessibility-1) - - [TTS Enhancements](#tts-enhancements) - - [Stability \& Performance](#stability--performance) + - [Stability & Performance](#stability--performance) - [Platform Updates](#platform-updates) + - [Database & System Stability](#database--system-stability) - [Getting Started](#getting-started) - [First-Time Setup](#first-time-setup) - [Using TTS](#using-tts) @@ -81,6 +84,12 @@ This fork builds on the original LNReader with enhanced features focused on acce - [Using Continuous Scrolling](#using-continuous-scrolling) - [Backup \& Restore](#backup--restore) - [Architecture](#architecture) + - [Advanced TTS System](#advanced-tts-system) + - [System-Wide UI Scaling](#system-wide-ui-scaling) + - [Dynamic Material You Theme](#dynamic-material-you-theme) + - [Parallel Library Updater](#parallel-library-updater) + - [Robust Backup System](#robust-backup-system) + - [Network \& Security Infrastructure](#network--security-infrastructure) - [Plugins](#plugins) - [Building \& Contributing](#building--contributing) - [Quick Start](#quick-start) @@ -92,10 +101,15 @@ This fork builds on the original LNReader with enhanced features focused on acce

Screenshots

+
+📸 View screenshots +

+
+ --- ## Feature Highlights @@ -122,12 +136,15 @@ This fork includes extensive TTS enhancements for hands-free reading and accessi | 🔧 **TTS Engine Picker** | Select system or custom TTS engines with quality badges and persistent selection | | 🏷️ **Auto Chapter Title Prepend** | Auto-announces chapter title via TTS when not visibly present in content | | 🖱️ **Advanced Button Gestures** | Tap to toggle playback, hold 0.5s + swipe to adjust highlight offset, hold 2s + drag to move | -| 🧹 **TTS Text Cleanup** | Strip watermarks/corrupted text & fix pronunciations before TTS reads — declarative rules, one-tap presets, per-novel overrides, JSON import/export | +| 🧹 **TTS Text Cleanup** | Strip watermarks/corrupted text & fix pronunciations before TTS reads — declarative rules, one-tap presets, per-novel overrides, JSON import/export. **Also cleans visible reader text** (Applies to: TTS / Visible / Both) | #### TTS Feature Demo +
+▶️ Watch the demo & feature showcase +

🎵 Text-to-Speech in Action

@@ -150,8 +167,13 @@ This fork includes extensive TTS enhancements for hands-free reading and accessi
+
+ #### Enhanced TTS Media Notification (Android) +
+🎛️ See the media notification + This release introduces a 5-button Android MediaStyle notification for the TTS foreground service. It provides rich metadata (novel name, chapter title, and paragraph-based progress), lock-screen visibility, and a native ⇄ React Native TTS progress sync — all while preserving visibility of the 5 action buttons.
@@ -167,17 +189,22 @@ This release introduces a 5-button Android MediaStyle notification for the TTS f

+
+ --- #### TTS Engine Picker +
+🔊 Explore TTS engine options + Android devices can have multiple TTS engines installed. The default engine is often your manufacturer's (Google on most phones, Samsung on Galaxy devices), but you can install better ones. The **Engine Picker** (Settings → Reader → Accessibility → TTS Engine) lets you choose which engine powers LNReader's TTS, independently of your system default. **Why switch engines?** Voice quality varies dramatically between engines. Neural/TTS voices sound significantly more natural than older synthesizers — with better intonation, pacing, and clarity for long listening sessions.

- TTS Engine Picker + TTS Engine Picker
Engine Picker showing detected TTS engines

@@ -199,11 +226,16 @@ Android devices can have multiple TTS engines installed. The default engine is o > [!TIP] > **Google TTS with Neural2 voices** is the current best recommendation. Install Google TTS, open your device's TTS settings (Settings → Accessibility → Text-to-Speech → Preferred Engine), select "Google Text-to-Speech", then install the Neural2 voice packs. Switch LNReader to use Google TTS and you'll get the highest quality reading experience. +
+ --- #### TTS Text Cleanup -Sites like Novelight inject anti-scraper watermarks (spaced letters, unicode lookalikes, `u2014` corruption, "Do not rehost this novel" spam) that system TTS engines read aloud, and LN names/honorifics are commonly mispronounced. **Text Cleanup** fixes both with a declarative, **length-preserving** pipeline applied to every paragraph before it reaches the TTS engine — across all playback modes, including background playback. +Sites like Novelight inject anti-scraper watermarks (spaced letters, unicode lookalikes, `u2014` corruption, "Do not rehost this novel" spam) that system TTS engines read aloud, and LN names/honorifics are commonly mispronounced. **Text Cleanup** fixes both with a declarative, **length-preserving** pipeline applied to every paragraph before it reaches the TTS engine — across all playback modes, including background playback. The **same ruleset can also clean the visible reader text** (Applies to: **TTS audio only** / **Visible text only** / **Both**), so you maintain ONE ruleset for reading and listening instead of juggling a separate Custom JS script. + +
+🧹 View the cleanup pipeline diagram
@@ -213,6 +245,7 @@ flowchart LR classDef src fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d47a1,font-weight:bold classDef clean fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,color:#e65100,font-weight:bold classDef out fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1b5e20,font-weight:bold + classDef vis fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px,color:#6a1b9a,font-weight:bold %% Nodes PARA["📄 Every Paragraph
(any playback path)"]:::src @@ -220,18 +253,23 @@ flowchart LR RULES["🧹 2. Find & Replace Rules
(literal or regex, ordered)"]:::clean PHON["🗣️ 3. Phonetic Dictionary
(whole-word or substring)"]:::clean TTS["🔈 Native TTS Engine"]:::out + VDOM["👁️ Visible Reader DOM
(rules only — no phonetics/normalization)"]:::vis %% Flow PARA ==> NORM ==> RULES ==> PHON ==> TTS + PARA -. "Applies to: Both / Visible" .-> VDOM ```
+
+ - **Access**: Settings → Reader → Accessibility → **TTS Text Cleanup** (global), or Reader Bottom Sheet → TTS Tab → **Text Cleanup** (quick access, per-novel aware) +- **Applies to**: inside the cleanup editor, choose **TTS audio only** (default), **Visible text only**, or **Both**. In visible modes the find/replace rules also clean the reader DOM (once per chapter load, never removing paragraphs — emptied ones are padded invisibly to keep highlight/scroll/progress in sync). **Phonetic dictionary and Unicode normalization always stay TTS-only** — they are audio-oriented and would corrupt visible text - **Presets (one-tap)**: Curated templates — Novelight spaced watermark, `u2014` corruption, "(Official version)" tags, "Do not rehost" spam, math-bold lookalikes, LN name pronunciations, and CJK substring pairs. Presets are UI data only; applying copies them into your editable rules -- **Import / Export**: Share or restore your rule set as a versioned JSON envelope (`lnreader-tts-cleanup` v1) -- **Per-novel overrides**: With per-novel TTS settings enabled, cleanup can be overridden per novel -- **Safety**: Regex length cap + ReDoS-shape detection + invalid-regex skip; length-preserving (paragraph count never changes, so highlight/scroll stay in sync) +- **Import / Export**: Share or restore your rule set as a versioned JSON envelope (`lnreader-tts-cleanup` v1; v2 when a non-default target is set — v1 files still import as "TTS audio only") +- **Per-novel overrides**: With per-novel TTS settings enabled, cleanup (including the target mode) can be overridden per novel +- **Safety**: Regex length cap + ReDoS-shape detection + invalid-regex skip; length-preserving (paragraph count never changes, so highlight/scroll stay in sync). Custom JS (`Settings → Reader → Advanced`) is NOT recommended for text cleanup — it runs raw and can break paragraph indexing --- @@ -246,6 +284,8 @@ Enhanced features for smoother, more immersive reading. | 📜 **Continuous Scrolling** | Seamless chapter transitions with invisible DOM stitching | | ✅ **Auto-Mark Short Chapters** | Automatically mark short chapters as read to reduce clutter | | 🪡 **Configurable Stitch Threshold** | User-adjustable trigger for chapter merging | +| 🚀 **Jump to First Unread** | Read button & FAB navigate straight to the first unread chapter | +| 📚 **EPUB Range Export** | Export custom chapter ranges with chapter numbers preserved | | 🎨 **EPUB Style Preservation** | `` tags preserved for better styling | | 🧹 **EPUB Summary Improvements** | Clean summaries with HTML tag/entity stripping | @@ -253,6 +293,22 @@ Enhanced features for smoother, more immersive reading. --- +### Library & Updates + +Efficient library management with parallel updates and rate-limit friendly downloads. + +
+ +| Feature | Description | +| :------------------------------- | :----------------------------------------------------------------------- | +| ⚡ **Parallel Library Updates** | Update novels across multiple sources concurrently (up to 3 at once) | +| ⏱️ **Configurable Download Cooldown** | Adjustable delay between chapter downloads (Settings → General) | +| 🔔 **Skip-Version Updates** | Dismiss update notifications for a specific version | + +
+ +--- + ### Network & Security Privacy-focused networking features for enhanced access and security. @@ -282,6 +338,8 @@ System-wide improvements for better usability across devices. | 📏 **UI-Wide Scaling** | Single `uiScale` setting affects icons, paddings, and component dimensions | | 🔠 **AppText Component** | Text scaling support for better accessibility | | 🖼️ **Theme Customization** | Multiple theme options with consistent styling | +| 🎨 **Dynamic Material You** | Wallpaper-derived Material 3 color palettes (Android 12+) | +| 🎚️ **MD3 Sliders & Tabs** | Flicker-free native sliders, M3 top tab indicators, standardized sheets | @@ -308,48 +366,61 @@ Robust backup system with multiple options and versioned schema. --- -## What's New +### Trackers -### Network & Security Enhancements +Synchronize your reading progress, status, and scores with popular tracking services. -- **Cookie Management**: Automatic persistence for authentication-required sources with WebView sync -- **DNS-over-HTTPS**: Encrypted DNS queries via Cloudflare, Google, or AdGuard (Settings → Advanced) -- **Cloudflare Bypass**: Automated challenge solving for protected novel sources -- **Enhanced Network Resilience**: Infinite loop prevention (max 2 retry attempts), connection pooling, graceful timeout handling +
-### UI & Accessibility +| Tracker | Supported Features | +| :------------------ | :-------------------------------------------------- | +| 🦊 **Kitsu** | Search, status, chapter progress, and score sync | +| 🌸 **AniList** | Search, status, chapter progress, and score sync | +| 🟦 **MyAnimeList** | Search, status, chapter progress, and score sync | +| 📖 **MangaUpdates** | Search, status, chapter progress, and score sync | -- **Header Positioning Fix**: Resolved header overlap with Android status bar icons across all Settings and More screens -- **SafeAreaView Enhancement**: Proper inset handling following React Native best practices +
+ +> [!NOTE] +> **Kitsu** is the newest addition to the tracker lineup; all trackers share the same capabilities (search, status, chapter progress, and score). + +--- + +## What's New -### TTS Enhancements +
+📋 Expand the changelog -- **TTS Text Cleanup**: Declarative rule pipeline strips anti-scraper watermarks, corrupted text, and fixes LN name pronunciations before TTS reads (Settings → Reader → Accessibility → TTS Text Cleanup; quick access in the Reader TTS tab) -- **Cleanup Presets**: One-tap curated templates (Novelight watermark, `u2014` corruption, LN/CJK pronunciations, and more) — UI data only, copied into your editable rules -- **Cleanup Import/Export**: Share or restore cleanup rule sets as versioned JSON from the editor -- **TTS Engine Picker**: Custom engine selection with native Android integration, quality badges, and persistent selection across sessions -- **Per-Novel TTS Settings**: Isolated voice/speed/pitch per novel — changes no longer overwrite global defaults -- **TTS Resume Reliability**: Fixed resume playback failure and wrong engine audio output after interruptions -- **Engine Stability**: Improved engine stability, voice matching, and reactive live settings updates -- **Auto Chapter Title Prepend**: TTS auto-announces the chapter title when not visibly present in content +> [!NOTE] +> **Feature highlights** mirror the tables above — this section lists what's new that isn't already covered there: bug fixes, platform/tooling upgrades, and database hardening. For the complete changelog, see [RELEASE_NOTES.md](RELEASE_NOTES.md). ### Stability & Performance -- **MainActivity Startup Crash**: Resolved critical crash on app launch +- **MainActivity Startup Crash**: Resolved critical crash on app launch (`window.insetsController` accessed before `super.onCreate()`) +- **Header Positioning Fix**: Resolved header overlap with Android status bar icons across all Settings and More screens +- **SafeAreaView Enhancement**: Proper inset handling following React Native best practices - **TTS Progress Persistence**: Fixed stale closure preventing reliable saves - **Media Notification Sync**: Eliminated flicker and state desync during seeks - **Notification Position Restore**: Fixed position restoration when returning from pause - **Auto-Stop Reliability**: Resolved race conditions and state management issues - **TTS Position Restoration**: Fixed position when returning to reader after notification pause +- **EPUB Toggles**: Toggle values (appTheme/CSS/JS/chapterNumber) now carry through export instead of reading a stale closure +- **EPUB Range Export Correctness**: Fixed chapter-range export fetching wrong/duplicated chapters on multi-page novels ### Platform Updates - **EPUB Improvements**: Adopted upstream PRs for better EPUB rendering and summaries -- **Android SDK 35+ Support**: Resolved all Gradle deprecation warnings and API compatibility issues +- **Android SDK 35+ Support**: Targets API 35 with compile-time compatibility fixes; Gradle deprecation warnings resolved for third-party modules via pnpm patches — remaining in-app Kotlin deprecation warnings tracked in plan.md - **Modern Tooling**: Upgraded React Native to 0.82.1, Reanimated to 4.2.0 - **Build System**: Gradle 9.2.0 upgrade with OkHttp 4.12.0 for DoH support -View full changelog: [RELEASE_NOTES.md](RELEASE_NOTES.md) +### Database & System Stability + +- **Database Migration 004**: Recreates `julianday` triggers across all installs, fixing library sorting anomalies +- **Exclusive DB Transactions**: Write operations wrapped in exclusive transactions to prevent database locks +- **Ordering & Safety Fixes**: Date-correct library updates, numeric EPUB page ordering, and hardened native file operations + +
--- @@ -408,6 +479,9 @@ View full changelog: [RELEASE_NOTES.md](RELEASE_NOTES.md) ## Architecture +
+🏗️ Expand architecture deep-dive + ### Advanced TTS System > [!NOTE] > See `docs/TTS/TTS_DESIGN.md` for full design details. @@ -452,6 +526,12 @@ A custom scaling engine (`src/theme/scaling.ts`) ensures accessibility across al - **Safety Clamps**: Prevents broken layouts by clamping scale factors (0.8x - 1.3x). - **Component-Level Support**: Custom components like `AppText` and `Icon` automatically consume scaling tokens. +### Dynamic Material You Theme +Dynamic theming (`src/theme/dynamic.ts`) generates Material 3 palettes from the Android wallpaper via `@pchmn/expo-material3-theme` when the system theme is set to "Dynamic" (`DYNAMIC_THEME_ID`), with an automatic fallback color when dynamic theming is unsupported (pre-Android 12). + +### Parallel Library Updater +The library updater (`src/services/updates`) groups library novels by source and updates up to **3 sources concurrently** (`UPDATE_SOURCE_CONCURRENCY`). Within each source, novels update sequentially with a short cooldown — so a slow or rate-limited source never bottlenecks unrelated sources. + ### Robust Backup System The backup system (`src/services/backup`) prioritizes data safety and portability: - **Versioned Schema**: Uses a generic `Backup` interface that supports version migration (v1 → v2). @@ -548,6 +628,8 @@ The network stack has been enhanced to address three common challenges when acce **Cookie Lifetime**: `cf_clearance` typically valid for 15 min - 24 hours. After expiration, bypass re-triggers automatically. +
+ --- ## Plugins diff --git a/__mocks__/@pchmn/expo-material3-theme.js b/__mocks__/@pchmn/expo-material3-theme.js new file mode 100644 index 0000000000..189cea5aea --- /dev/null +++ b/__mocks__/@pchmn/expo-material3-theme.js @@ -0,0 +1,4 @@ +export const getMaterial3Theme = jest.fn(() => ({ light: {}, dark: {} })); +// Real module exports a plain boolean (`!!ExpoMaterial3ThemeModule && Platform.OS === 'android' && Platform.Version >= 31`). +// Keeping this a jest.fn would be truthy and make isDynamicThemeAvailable take the dynamic branch in tests. +export const isDynamicThemeSupported = false; diff --git a/android/app/src/main/assets/css/index.css b/android/app/src/main/assets/css/index.css index c327c0975b..2c8ed41efd 100644 --- a/android/app/src/main/assets/css/index.css +++ b/android/app/src/main/assets/css/index.css @@ -53,6 +53,10 @@ img { max-width: 100%; } +div:has(> table):not(#LNReader-chapter) { + overflow: auto; +} + table { background-color: var(--theme-onPrimary); border-collapse: collapse; @@ -83,8 +87,6 @@ td { margin-left: var(--readerSettings-padding); margin-right: var(--readerSettings-padding); border-radius: 50px; - border-width: 1; - color: var(--theme-onPrimary); background-color: var(--theme-primary); font-family: var(--readerSettings-fontFamily); font-size: 16px; @@ -93,6 +95,7 @@ td { } .next-button { + color: var(--theme-onPrimary); min-height: 40px; text-overflow: ellipsis; overflow: hidden; diff --git a/android/app/src/main/assets/js/core.js b/android/app/src/main/assets/js/core.js index edcc578d04..c4b039334f 100644 --- a/android/app/src/main/assets/js/core.js +++ b/android/app/src/main/assets/js/core.js @@ -441,6 +441,10 @@ window.reader = new (function () { ); } this.invalidateCache(); + // Visible cleanup: newly appended stitched content needs cleaning too + // (no-op when disabled; re-posts pristine snapshots for already-clean + // elements so the count contract stays intact). + this.requestVisibleCleanup(); if (DEBUG) { console.log( `[receiveChapterContent] After invalidateCache: loadedChapters = ${JSON.stringify(this.loadedChapters)}, length = ${this.loadedChapters.length}`, @@ -1285,6 +1289,96 @@ window.reader = new (function () { this._cacheInvalidated = true; }; + // ========================================================================== + // Visible-text cleanup (issue #19): the declarative TTS ruleset can also be + // applied to the READER DOM. core.js stays dumb — RN decides (reads the + // effective settings) and injects window.reader.applyVisibleCleanup(). + // ========================================================================== + + this._visibleCleanupEnabled = false; + + /** Enable/disable the visible-text cleanup pass (called by RN on load). */ + this.setVisibleCleanup = function (enabled) { + this._visibleCleanupEnabled = !!enabled; + if (this._visibleCleanupEnabled) { + this.requestVisibleCleanup(); + } else { + // Restore pristine pre-cleanup text for every element that was written + // back, so toggling the visible pass OFF reverts the DOM immediately + // (no stale cleaned text left behind until the next reload). + const elements = this.chapterElement ? this.getReadableElements() : []; + for (let i = 0; i < elements.length; i++) { + const el = elements[i]; + if (el.dataset && el.dataset.originalText) { + el.textContent = el.dataset.originalText; + delete el.dataset.originalText; + } + } + } + }; + + /** + * Capture pristine text (dataset.originalText) for every readable element + * and post the current texts to RN. Pristine is always what TTS reads, so + * the full TTS ruleset applies exactly once on the RN side. + */ + this.requestVisibleCleanup = function () { + if (!this._visibleCleanupEnabled || !this.chapterElement) { + return; + } + const elements = this.getReadableElements(); + if (elements.length === 0) { + return; + } + const texts = []; + for (let i = 0; i < elements.length; i++) { + const el = elements[i]; + if (!el.dataset.originalText) { + el.dataset.originalText = el.textContent; + } + texts.push(el.dataset.originalText); + } + this.post({ type: 'visible-cleanup', data: texts }); + }; + + /** + * Write cleaned texts back into the DOM. Aborts safely on count mismatch + * (paragraph-index contract). Element identity/count never changes. + */ + this.applyVisibleCleanup = function (cleaned) { + if (!Array.isArray(cleaned) || !this.chapterElement) { + return; + } + const elements = this.getReadableElements(); + if (cleaned.length !== elements.length) { + if (DEBUG) { + console.warn('[visible-cleanup] count mismatch — aborting write-back'); + } + return; + } + for (let i = 0; i < elements.length; i++) { + const el = elements[i]; + if (el.textContent === cleaned[i]) { + // No-op: never flatten inline markup (//...) when the + // clean pass produced no change for this element. + continue; + } + if (!el.dataset.originalText) { + el.dataset.originalText = el.textContent; + } + el.textContent = cleaned[i]; + } + }; + + /** + * Text to send to the native TTS engine: the pristine pre-cleanup snapshot + * when visible cleanup is active, otherwise the live textContent. + */ + this.getCleanTextForTTS = function (el) { + if (!el) return ''; + return (el.dataset && el.dataset.originalText) || el.textContent; + }; + this.getReadableElements = () => { // NEW: Check if chapterElement is still valid. If not, re-query it. if (!this.chapterElement || !this.chapterElement.isConnected) { @@ -2203,6 +2297,7 @@ window.tts = new (function () { this.normalizeText = text => { if (!text) return ''; return text + .replace(/^["'“”‘’]+|["'“”‘’]+$/g, '') .replace(/\s+/g, ' ') .replace(/\s*([.,!?;:])\s*/g, '$1 ') .trim(); @@ -2829,7 +2924,7 @@ window.tts = new (function () { } this.reading = true; - const text = this.currentElement.textContent; + const text = reader.getCleanTextForTTS(this.currentElement); if (text && text.trim().length > 0) { this.log('Speaking (locked)', text.substring(0, 20)); reader.post({ type: 'speak', data: text }); @@ -2871,7 +2966,7 @@ window.tts = new (function () { } // Use textContent to ensure indices match for highlighting - const text = this.currentElement.textContent; + const text = reader.getCleanTextForTTS(this.currentElement); if (text && text.trim().length > 0) { this.log('Speaking', text.substring(0, 20)); // Include paragraphIndex so RN can create utteranceId matching the batch format @@ -2910,7 +3005,7 @@ window.tts = new (function () { ) { const el = readableElements[i]; if (this.readable(el) && !this.isContainer(el)) { - nextTexts.push(el.textContent); + nextTexts.push(reader.getCleanTextForTTS(el)); } } if (nextTexts.length > 0) { @@ -4208,6 +4303,10 @@ document.addEventListener('message', __handleNativeMessage); reader.chapterElement.innerHTML = html; + // Visible cleanup: the rebuild wiped datasets + cleaned text, so re-run + // the cleanup pass (no-op when disabled). + reader.requestVisibleCleanup(); + // CRITICAL: Restore TTS state after DOM rebuild if (savedTTSState && savedTTSState.paragraphIndex >= 0) { // Invalidate cache to force re-scan diff --git a/android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt b/android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt index e2948ea639..43110e8caa 100644 --- a/android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt +++ b/android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt @@ -74,13 +74,21 @@ class NativeFile(context: ReactApplicationContext) : } override fun writeFile(path: String, content: String) { - val fw = FileWriter(path) - fw.write(content) - fw.close() + try { + val fw = FileWriter(path) + fw.write(content) + fw.close() + } catch (e: IOException) { + throw Exception("Failed to write file '$path': ${e.message}") + } } override fun readFile(path: String): String { - return File(path).bufferedReader().readText() + val file = File(path) + if (!file.exists()) { + throw Exception("File not found: '$path'") + } + return file.bufferedReader().readText() } override fun copyFile(filepath: String, destPath: String) { @@ -97,17 +105,27 @@ class NativeFile(context: ReactApplicationContext) : destPath: String, onDone: (() -> Unit)? = null, ) { - val inputStream = getInputStream(filepath) - val outputStream = getOutputStream(destPath) - val buffer = ByteArray(BUFFER_SIZE) - var length: Int - while (inputStream.read(buffer).also { length = it } > 0) { - outputStream.write(buffer, 0, length) - } - inputStream.close() - outputStream.close() - if (onDone != null) { - onDone() + try { + val inputStream = getInputStream(filepath) + try { + val outputStream = getOutputStream(destPath) + try { + val buffer = ByteArray(BUFFER_SIZE) + var length: Int + while (inputStream.read(buffer).also { length = it } > 0) { + outputStream.write(buffer, 0, length) + } + } finally { + outputStream.close() + } + } finally { + inputStream.close() + } + if (onDone != null) { + onDone() + } + } catch (e: IOException) { + throw Exception("Failed to copy file from '$filepath' to '$destPath': ${e.message}") } } @@ -132,7 +150,7 @@ class NativeFile(context: ReactApplicationContext) : override fun unlink(filepath: String) { val file = File(filepath) - if (!file.exists()) throw Exception("File does not exist") + if (!file.exists()) return deleteRecursive(file) } @@ -191,18 +209,21 @@ class NativeFile(context: ReactApplicationContext) : } override fun onResponse(call: Call, response: Response) { - if (!response.isSuccessful || response.body == null) { - promise.reject(Exception("Failed to download load: ${response.code}")) - return - } - try { - val inputStream = decompressStream(response.body!!.byteStream()) - FileOutputStream(destPath).use { fos -> - inputStream.copyTo(fos, BUFFER_SIZE) + response.use { + if (!it.isSuccessful || it.body == null) { + promise.reject(Exception("Failed to download: ${it.code}")) + return + } + try { + decompressStream(it.body!!.byteStream()).use { inputStream -> + FileOutputStream(destPath).use { fos -> + inputStream.copyTo(fos, BUFFER_SIZE) + } + } + promise.resolve(null) + } catch (e: Exception) { + promise.reject(e) } - promise.resolve(null) - } catch (e: Exception) { - promise.reject(e) } } }) diff --git a/assets/kitsu.png b/assets/kitsu.png new file mode 100644 index 0000000000..4d487aaad5 Binary files /dev/null and b/assets/kitsu.png differ diff --git a/jest.config.cjs b/jest.config.cjs index cd30b969ce..06b5349031 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -2,7 +2,7 @@ module.exports = { preset: 'react-native', setupFilesAfterEnv: ['/jest.setup.js'], transformIgnorePatterns: [ - 'node_modules/(?!(.pnpm|jest-?|react-native|@react-native|@react-native-community|@react-native-google-signin|expo(nent)?|@expo(nent)?|@expo-google-fonts|react-navigation|@react-navigation|@unimodules|unimodules|sentry-expo|native-base|react-native-svg|react-native-background-actions|color|lodash-es)/)', + 'node_modules/(?!(.pnpm|jest-?|react-native|@react-native|@react-native-community|@react-native-google-signin|expo(nent)?|@expo(nent)?|@expo-google-fonts|react-navigation|@react-navigation|@unimodules|unimodules|sentry-expo|native-base|react-native-svg|react-native-background-actions|color|lodash-es|@pchmn)/)', ], moduleNameMapper: { '^expo-localization$': '/__mocks__/expo-localization.js', @@ -22,5 +22,7 @@ module.exports = { '.*specs/NativeZipArchive$': '/__mocks__/NativeZipArchive.js', '.*specs/NativeEpub$': '/__mocks__/NativeEpub.js', '^react-native-mmkv$': '/__mocks__/react-native-mmkv.js', + '^@pchmn/expo-material3-theme$': + '/__mocks__/@pchmn/expo-material3-theme.js', }, }; diff --git a/package.json b/package.json index 126140cec5..2c1b89f5cf 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@cd-z/react-native-epub-creator": "^3.0.0", "@gorhom/bottom-sheet": "^5.2.8", "@legendapp/list": "^2.0.16", - "@react-native-community/slider": "^5.1.1", + "@pchmn/expo-material3-theme": "^1.4.0", "@react-native-cookies/cookies": "^6.2.1", "@react-native-google-signin/google-signin": "^16.0.0", "@react-native-vector-icons/common": "^12.4.0", @@ -127,6 +127,7 @@ "@react-native/typescript-config": "^0.82.1", "@testing-library/react-hooks": "8.0.1", "@testing-library/react-native": "^13.3.3", + "@types/better-sqlite3": "^9.6.0", "@types/color": "^4.2.0", "@types/jest": "^30.0.0", "@types/lodash-es": "^4.17.12", @@ -138,6 +139,7 @@ "babel-plugin-module-resolver": "^5.0.2", "babel-plugin-react-compiler": "19.1.0-rc.3", "babel-plugin-transform-remove-console": "^6.9.4", + "better-sqlite3": "^13.0.3", "eslint": "^9.39.1", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", diff --git a/patches/@pchmn__expo-material3-theme@1.4.0.patch b/patches/@pchmn__expo-material3-theme@1.4.0.patch new file mode 100644 index 0000000000..c449ac9671 --- /dev/null +++ b/patches/@pchmn__expo-material3-theme@1.4.0.patch @@ -0,0 +1,29 @@ +diff --git a/android/build.gradle b/android/build.gradle +index ec86c517e8c904a9e9fb7ddcb5f10f2f4df16db1..0f9b19e9b3a926be1a093e72f1682a3fbb971993 100644 +--- a/android/build.gradle ++++ b/android/build.gradle +@@ -51,6 +51,7 @@ afterEvaluate { + } + + android { ++ namespace = "expo.modules.material3theme" + compileSdkVersion safeExtGet("compileSdkVersion", 34) + + def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION +@@ -84,7 +85,7 @@ android { + versionName "0.1.0" + } + lintOptions { +- abortOnError false ++ abortOnError = false + } + publishing { + singleVariant("release") { +diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml +index 96b3dda64b8fce7d472eaa57540db99c7aeac936..bdae66c8f5a82773eaadc8f7befc05437e27cf25 100644 +--- a/android/src/main/AndroidManifest.xml ++++ b/android/src/main/AndroidManifest.xml +@@ -1,2 +1,2 @@ +- ++ + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98cf644a5b..3cc13eb8c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false patchedDependencies: + '@pchmn/expo-material3-theme@1.4.0': + hash: 6f6350cdbc103dffabac026886e397ff8609da8644af64154af0448031fbcc6b + path: patches/@pchmn__expo-material3-theme@1.4.0.patch '@react-native-cookies/cookies': hash: ec064f4a202698a202758abc5b5d8deafb70ffb31585c78608d257295d731b9a path: patches/@react-native-cookies__cookies.patch @@ -25,9 +28,9 @@ importers: '@legendapp/list': specifier: ^2.0.16 version: 2.0.16(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1) - '@react-native-community/slider': - specifier: ^5.1.1 - version: 5.1.1 + '@pchmn/expo-material3-theme': + specifier: ^1.4.0 + version: 1.4.0(patch_hash=6f6350cdbc103dffabac026886e397ff8609da8644af64154af0448031fbcc6b)(expo@54.0.25(@babel/core@7.28.5)(react-native-webview@13.15.0(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1))(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1))(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1) '@react-native-cookies/cookies': specifier: ^6.2.1 version: 6.2.1(patch_hash=ec064f4a202698a202758abc5b5d8deafb70ffb31585c78608d257295d731b9a)(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1)) @@ -257,6 +260,9 @@ importers: '@testing-library/react-native': specifier: ^13.3.3 version: 13.3.3(jest@30.2.0(@types/node@24.10.1))(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react-test-renderer@19.1.1(react@19.1.1))(react@19.1.1) + '@types/better-sqlite3': + specifier: ^9.6.0 + version: 9.6.0 '@types/color': specifier: ^4.2.0 version: 4.2.0 @@ -290,6 +296,9 @@ importers: babel-plugin-transform-remove-console: specifier: ^6.9.4 version: 6.9.4 + better-sqlite3: + specifier: ^13.0.3 + version: 13.0.3 eslint: specifier: ^9.39.1 version: 9.39.1 @@ -1419,6 +1428,9 @@ packages: react: '*' react-native: '*' + '@material/material-color-utilities@0.3.0': + resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==} + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -1437,6 +1449,13 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@pchmn/expo-material3-theme@1.4.0': + resolution: {integrity: sha512-XNt0gpZX5argOQ7JiFnNdiXHpHjmpwJoMQrlnd1kpb8ly/IWZN2hh011incmfZXV2Xs+p6Uo1BGDKv8hW4MQTQ==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1513,9 +1532,6 @@ packages: engines: {node: '>=20.19.4'} hasBin: true - '@react-native-community/slider@5.1.1': - resolution: {integrity: sha512-W98If/LnTaziU3/0h5+G1LvJaRhMc6iLQBte6UWa4WBIHDMaDPglNBIFKcCXc9Dxp83W+f+5Wv22Olq9M2HJYA==} - '@react-native-cookies/cookies@6.2.1': resolution: {integrity: sha512-D17wCA0DXJkGJIxkL74Qs9sZ3sA+c+kCoGmXVknW7bVw/W+Vv1m/7mWTNi9DLBZSRddhzYw8SU0aJapIaM/g5w==} peerDependencies: @@ -1795,6 +1811,9 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/better-sqlite3@9.6.0': + resolution: {integrity: sha512-ZEEwBSgMu7GYJOynoagg5X9JbxfL6dTJDsgViJIqh67jV44kyOr9RXfmFjLK5rzC4MWssP06t9hu/JwGDnUbCg==} + '@types/color-convert@2.0.4': resolution: {integrity: sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==} @@ -1953,41 +1972,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -2279,6 +2306,10 @@ packages: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} + better-sqlite3@13.0.3: + resolution: {integrity: sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==} + engines: {node: '>=22'} + big-integer@1.6.52: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} @@ -3920,24 +3951,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -4316,6 +4351,10 @@ packages: resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} engines: {node: '>=12.0.0'} + node-addon-api@8.9.1: + resolution: {integrity: sha512-4eUQWVPCUUUiBjLnHS3cXWeC6ryoPUc0U3rP7IuzapoGbzMqd/r6KKO0clr0b+snQhsrueFEhCZDdK+LK7hxKg==} + engines: {node: ^18 || ^20 || >= 21} + node-forge@1.3.2: resolution: {integrity: sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==} engines: {node: '>= 6.13.0'} @@ -7321,6 +7360,8 @@ snapshots: react-native: 0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1) use-sync-external-store: 1.6.0(react@19.1.1) + '@material/material-color-utilities@0.3.0': {} + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.7.1 @@ -7344,6 +7385,14 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@pchmn/expo-material3-theme@1.4.0(patch_hash=6f6350cdbc103dffabac026886e397ff8609da8644af64154af0448031fbcc6b)(expo@54.0.25(@babel/core@7.28.5)(react-native-webview@13.15.0(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1))(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1))(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1)': + dependencies: + '@material/material-color-utilities': 0.3.0 + color: 4.2.3 + expo: 54.0.25(@babel/core@7.28.5)(react-native-webview@13.15.0(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1))(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-native: 0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1) + '@pkgjs/parseargs@0.11.0': optional: true @@ -7501,8 +7550,6 @@ snapshots: - typescript - utf-8-validate - '@react-native-community/slider@5.1.1': {} - '@react-native-cookies/cookies@6.2.1(patch_hash=ec064f4a202698a202758abc5b5d8deafb70ffb31585c78608d257295d731b9a)(react-native@0.82.1(@babel/core@7.28.5)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@react-native/metro-config@0.82.1(@babel/core@7.28.5))(@types/react@19.1.17)(react@19.1.1))': dependencies: invariant: 2.2.4 @@ -7950,6 +7997,10 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@types/better-sqlite3@9.6.0': + dependencies: + '@types/node': 24.10.1 + '@types/color-convert@2.0.4': dependencies: '@types/color-name': 1.1.5 @@ -8527,6 +8578,10 @@ snapshots: dependencies: open: 8.4.2 + better-sqlite3@13.0.3: + dependencies: + node-addon-api: 8.9.1 + big-integer@1.6.52: {} bignumber.js@9.3.1: {} @@ -11110,6 +11165,8 @@ snapshots: nocache@3.0.4: {} + node-addon-api@8.9.1: {} + node-forge@1.3.2: {} node-int64@0.4.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4a31a6f11d..40a151bf68 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,5 +3,6 @@ onlyBuiltDependencies: - unrs-resolver patchedDependencies: + '@pchmn/expo-material3-theme@1.4.0': patches/@pchmn__expo-material3-theme@1.4.0.patch '@react-native-cookies/cookies': patches/@react-native-cookies__cookies.patch react-native-sha256: patches/react-native-sha256.patch diff --git a/specs/tts-text-cleanup/PRD.md b/specs/tts-text-cleanup/PRD.md index dcaa897611..fde5f1210e 100644 --- a/specs/tts-text-cleanup/PRD.md +++ b/specs/tts-text-cleanup/PRD.md @@ -207,9 +207,43 @@ All on branch `dev` (not yet pushed at time of writing). - **Unicode normalization + regex patterns**: when `normalizeUnicode` is enabled, text is NFD-normalized FIRST; regex patterns are matched verbatim against the already-normalized text. A regex containing precomposed characters (e.g. `é`) will NOT match the normalized form (`e` + combining accent). Literal (non-regex) patterns are NFD-normalized to match. This is by design — document it for users. - **Partial-object robustness**: the runtime (`cleanTtsText`) guards `settings.rules ?? []` / `settings.phoneticPairs ?? []` so partial legacy objects degrade safely. Settings written by this feature always include both arrays. - **Length preservation is contractual**: never drop or merge paragraphs in cleanup — the RN ↔ WebView paragraph index contract depends on it. +- **Visible-text cleanup (issue #19)**: applied via a one-time WebView → RN → WebView bridge per chapter load. core.js captures `dataset.originalText` BEFORE mutating; the three TTS text-read sites (`speak`, locked dialog, `tts-queue` lookahead) prefer the pristine snapshot so the full TTS ruleset applies exactly once. DOM rebuilds (bionic reading / `removeExtraParagraphSpacing`, stitched chapter append) wipe datasets — `requestVisibleCleanup()` re-runs there. Never remove/empty readable elements (count contract); emptied paragraphs are padded with `\u200B`. `el.textContent` write-back flattens inline markup (`//`) — accepted limitation. --- -**Last Updated**: 2026-08-02 +## 10. Visible Text Unification (issue #19) + +**Status**: ✅ IMPLEMENTED +**Issue**: [bizzkoot/lnreader#19](https://github.com/bizzkoot/lnreader/issues/19) — "Feature Request: Unify Visible Text and TTS Cleanup Logic" (filed against v2.1.3) + +### What it does + +Adds `applyTo: 'tts' | 'visible' | 'both'` (default `'tts'`) to `TtsTextCleanupSettings`. In `'visible'`/`'both'` the find/replace rules ALSO clean the visible reader DOM. Users retire their Custom JS TreeWalker scripts and keep ONE ruleset. + +### Design decisions + +- **Option A** (TTS ruleset → visible text) chosen; **Option B** (custom JS → TTS) rejected — DOM-context scripts cannot run against the RN string stream, and arbitrary JS violates the no-eval design constraint. +- **Rules-only visible pass**: phonetic pairs + Unicode normalization stay TTS-only (`cleanVisibleText` in `htmlParagraphExtractor.ts`). +- **Centralized gating**: `cleanTtsText`/`applyTtsTextCleanup` no-op when `applyTo === 'visible'` — all existing call sites inherit correct behavior. +- **Count preservation**: visible pass never drops/merges/empties readable elements; emptied text is padded with `VISIBLE_PAD_CHAR` (`\u200B`) which survives `.trim()` so `readable()` keeps the element. +- **Pristine snapshot**: `dataset.originalText` is captured pre-mutation; TTS always reads pristine + full ruleset (single application, no double-clean). +- **Envelope**: exports `lnreader-tts-cleanup` v1 for default targets, v2 when `applyTo` is non-default; import validates versions (accepts 1–2, rejects others) and maps missing/invalid `applyTo` → `'tts'`. Bare-settings-object imports keep working. + +### Files + +- `src/utils/htmlParagraphExtractor.ts` — `applyTo` type + default, `cleanVisibleText`, `shouldCleanVisibleText`, `VISIBLE_PAD_CHAR`, centralized gating +- `src/screens/reader/components/WebViewReader.tsx` — `visible-cleanup` message allowlist + handler (RN cleans + writes back), `setVisibleCleanup` inject on load +- `android/app/src/main/assets/js/core.js` — `setVisibleCleanup`/`requestVisibleCleanup`/`applyVisibleCleanup`/`getCleanTextForTTS`, re-run hooks after DOM rebuild + stitched append +- `src/screens/settings/SettingsReaderScreen/Modals/TtsTextCleanupModal.tsx` — "Applies to" picker + `normalizeSettings` carry-through +- `src/screens/settings/SettingsReaderScreen/Modals/ttsCleanupPresets.ts` — envelope v1/v2 + version validation +- UI copy: `AccessibilityTab.tsx`, `ReaderTTSTab.tsx`, `AdvancedTab.tsx` (hint in Custom JS editor) + +### Tests + +`cleanVisibleText` rules-only gating, count preservation, applyTo gating matrix, envelope version matrix, WebView bridge integration test (new message type + nonce). + +--- + +**Last Updated**: 2026-08-04 (issue #19 visible-text unification) **Session Utilization**: 100% **Completion**: 6/6 commits (100%) diff --git a/specs/upstream-merge-analysis-2026-08/README.md b/specs/upstream-merge-analysis-2026-08/README.md new file mode 100644 index 0000000000..5902a772b1 --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/README.md @@ -0,0 +1,54 @@ +# Upstream Merge Analysis — 2026-08 + +**Status**: 📋 PLANNED (analysis only — NO code changes made yet) +**Date**: 2026-08-03 +**Upstream analyzed**: `original` branch (= upstream/master @ `c3260e8e0`, 2026-08-01) +**Fork analyzed**: `dev` branch (HEAD `40c667679` v2.1.3, 2026-08-03) +**Merge base**: `467a97dcf` (2025-12-24) + +--- + +## What this folder contains + +| File | Purpose | +|---|---| +| `README.md` | This index + executive summary | +| `analysis.md` | Full synthesis report: the 3 architecture walls, tiers, per-commit classification rationale, skip list | +| `commit-manifest.csv` | Machine-readable classification of **all 170 upstream commits** (action, batch, value, risk, port, fork-overlap files, notes) | +| `batches/PRD-A-safety-fixes.md` | Batch A: 27 verified bug fixes (15 Tier-1 + 5 SQL + 7 extended) | +| `batches/PRD-B-high-value-features.md` | Batch B: 7 high-value features (+ 15 stretch) | +| `batches/PRD-C-ux-theme-train.md` | Batch C: theme refactor → dynamic colors → M3 components | +| `batches/PRD-D-project-scale-features.md` | Batch D: project-scale rewrites (time tracking, search, stats, perf) | +| `reference/upstream_commits_170.txt` | Raw commit list (hash\|author\|date\|subject) | +| `reference/commit_overlap_170.txt` | Per-commit fork-overlap files | +| `reference/fork_context.md` | Fork architecture context given to analysis agents | +| `reference/subagent/group1-4-report.md` | Full per-commit assessments from the 4 analysis agents | + +## Executive summary + +- **170 upstream commits analyzed**; **76 are worth porting** (in some form), **94 skipped**. +- Full merge remains **infeasible** (confirmed again): three hard architecture walls separate the repos (Drizzle/op-sqlite vs raw SQL; Nitro/WorkManager vs custom Kotlin + background-actions; Expo-managed/rock vs bare Expo 54/Gradle 9.2.0). +- The recommended strategy stays **selective cherry-pick / manual port** (per `.agents/upstream-merge-memory.md` 2025-12-30). +- **Highest-value insight**: the fork often carries the *byte-identical pre-fix code* that upstream just fixed — the fork has the same live bugs. Batch A is therefore almost pure risk-reduction with verified low conflict surface. +- **2026-08-04 update**: independent 4-subagent review applied corrections (see `REVIEW-2026-08-04.md`) — `0cb9da9027` reclassified to PORT-A2 (pre-Drizzle tx fix), 5 DIRECT labels → MANUAL, `9783c4d5e3` → PORT-A1, `c3c891cea0` relabeled SKIP-INFRA. + +## Suggested execution order + +1. **Batch A — Safety fixes** (half-day): 29 verified bug fixes → run `pnpm run test` + TTS wake-cycle tests. +2. **Batch B — High-value features** (2–3 days): Kitsu tracker, ignorable update notifications, EPUB chapter numbers, parallel library updates, first-unread FAB, jump-to-chapter fix, download cooldown. +3. **Batch C — UX/theme train**: theme refactor → dynamic Material You colors → M3 slider/tabs/menu. +4. **Batch D — Project-scale** (separate PRs): time tracking, in-chapter search, stats overhaul, reader perf sprint. +5. **Continuous**: re-scan `original` every 2–4 weeks for new fixes in pre-fix-era code patterns. + +## Preconditions before implementation + +- ⚠️ `dev` branch is currently **unpushed** (TTS text-cleanup work, per AGENTS.md). Commit/push current state before starting Batch A. +- Each PRD lists its own testing gates (`pnpm run type-check`, `lint`, `format`, `test`, TTS wake-cycle/refill suites). + +## Legend (manifest columns) + +- **action**: `PORT` (recommend porting) | `SKIP` (do not port) +- **batch**: A1/A2/A3 (safety), B/B-stretch, C/C-optional, D/D-stretch/D-optional, or SKIP-* category +- **value**: HIGH / MEDIUM / LOW / NONE — user-facing benefit to the fork +- **risk**: GREEN (new files / untouched files) / YELLOW (localized overlap) / RED (fork-critical files or upstream-only infra) +- **port**: DIRECT (cherry-pick) / MANUAL (adapt) / MANUAL-rewrite (reimplement on fork architecture) / NOT-FEASIBLE diff --git a/specs/upstream-merge-analysis-2026-08/REVIEW-2026-08-04.md b/specs/upstream-merge-analysis-2026-08/REVIEW-2026-08-04.md new file mode 100644 index 0000000000..6536dd22cf --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/REVIEW-2026-08-04.md @@ -0,0 +1,103 @@ +# Independent Review of Upstream Merge Analysis — 2026-08-04 + +**Reviewer**: 4 independent review subagents (fresh context) + parent spot-verification +**Subject**: `specs/upstream-merge-analysis-2026-08/` (README, analysis.md, commit-manifest.csv, batches/PRD-A..D) + `.agents/upstream-merge-memory.md` entry +**Date**: 2026-08-04 · **Fork HEAD**: `9984f90d9` · **Upstream**: `original` @ `c3260e8e0` · **Merge base**: `467a97dcf` + +--- + +## Verdict + +**The analysis is HIGH quality and substantially accurate.** The PORT-vs-SKIP axis, the 6 "verified live fork bugs," all 9 ALREADY-HAVE claims, the trap-commit warning, and the batch PRDs all hold up under independent verification. **2 real defects, 3 minor inconsistencies, and 4 PRD-level gaps** were found — none block implementation, but all should be corrected before Batch A starts. + +--- + +## 1. Verified CORRECT (independent confirmation) + +| Area | Result | Evidence | +|---|---|---| +| Manifest structure | 170 rows, 74 PORT / 96 SKIP — counts match analysis.md §3/§4 exactly | python CSV parse; 74+96=170 | +| Divergence snapshot | behind=170 ✓; files changed fork=596, upstream=1088, overlap=206 ✓ | `git rev-list`, `git diff --name-only` | +| Commit-type counts | 66 fix / 56 feat / 17 chore / 14 refactor / 5 docs / 4 perf / 2 test / 1 ci (minor: raw count gives 67 fix incl. "Fix:" variant — cosmetic) | `git log --format` | +| 6 live fork bugs (§5) | **ALL 6 CONFIRMED** in fork source: ChapterQueries.ts:180 novelId-twice, :173 WHERE-less UPDATE, ServiceManager.ts:152-153 inverted throttle/negative delay, `WHERE sort = 1` ×5 sites, `page TEXT` string ordering, core.js:2203 no quote-strip | reviewer 2 (file:line evidence) + parent spot-checks | +| ALREADY-HAVE (9/9) | All SOUND — fork has real equivalents: DoH (DoHManagerModule.kt), MediaSession (TTSForegroundService.kt + ttsNotification.ts), EPUB export (@cd-z/react-native-epub-creator), volume buttons, WebView-reset fix (injectJavaScript), metro middleware | reviewer 3 | +| Trap commit `72bfcad034` | Correctly flagged — touches WebViewReader/ReaderScreen/ChapterContext, patch does not apply | reviewer 1 + parent (`git apply --check` fails) | +| SKIP categories (13/16 samples) | Valid reasons (CI/release/infra/Drizzle/WorkManager/i18n/docs/dep) | reviewer 3 | +| Batch PRDs A-D | All **SOUND**: ~40 fork file paths verified, commit→file mappings accurate, testing gates present, A→B→C→D ordering sound, consistent with manifest | reviewer 4 | +| Direct ports (8/13) | `git apply --check` applies cleanly: 76106317b6, 65e8610030, 788f6cede7, 57b9d41b5c, 34be7a1234, 9e38ad114d, 45c4ea8ca0, 4088d5966b | reviewer 1 + parent spot-check | +| A1/A2/A3 bug claims (8/9) | Byte-identical pre-fix state verified for cited fork files | reviewer 1 | + +--- + +## 2. Defects found (should fix before implementation) + +### 🔴 D1 — `0cb9da9027` misclassified as SKIP-OTHER — **should be PORT-A2** +- `refactor: Replace withTransactionAsync with withExclusiveTransactionAsync for Improved Concurrency Handling (#1636)`, dated **2026-01-15** +- **The SKIP rationale was factually wrong**: it was labeled "Drizzle/op-sqlite transaction API," but `git show 0cb9da9027:src/database/db.ts` = `import * as SQLite from 'expo-sqlite'` — this is **pre-Drizzle** (Drizzle migration `b8c177bc5` is 2026-02-19). +- It touches exactly the fork-shared files: CategoryQueries.ts, NovelQueries.ts, StatsQueries.ts, helpers.tsx, migrateNovel.ts. +- The fork carries the identical pre-fix pattern: `NovelQueries.ts:180` unawaited `db.runAsync` inside `withTransactionAsync` (verified). Fork already uses `withExclusiveTransactionAsync` in ChapterQueries.ts:26 / LibraryUpdateQueries.ts:62 — API available. +- This is a real SQLITE_BUSY/concurrency fix that maps 1:1 onto fork raw-SQL code. + +### 🟠 D2 — port=DIRECT over-used (5/13 = 38% false positives) +These 5 rows are labeled `DIRECT`/`GREEN` but **fail `git apply --check`** on the fork tree (files were rewritten with fork infra — `useScaledDimensions`/`uiScale`, `eslint-disable` headers, `@env` config, custom .gitignore): + +| hash | cause of mismatch | should be | +|---|---|---| +| `31cb4b994a` | BottomTabBar rewritten (`getStyles` + scaled padding) | MANUAL-trivial, risk YELLOW | +| `93bc5e5e4b` | myAnimeList.ts diverged (`@env` client id, WebBrowser, pkceChallenger) | MANUAL, YELLOW | +| `2a919ec0f9` | .gitignore context differs (fork expo-cli block) | MANUAL-trivial, YELLOW | +| `f1fdafd37f` | BrowseScreen.tsx has eslint-disable header + no Color import | MANUAL, YELLOW | +| `a062beee87` | SetCategoriesModal.tsx rewritten with scaleDimension/AppText | MANUAL, YELLOW | + +All 5 remain valid PORTs — only the port-strategy label + risk level are wrong. + +### 🟠 D3 — `9783c4d5e3` SKIP-OTHER contradicts its own subagent report +- `fix: Normalize Repository Back Navigation (#1905)` — small safe deletion (−16/+2) in fork-shared SettingsRepositoryScreen.tsx. +- group1 subagent classified it LOW/YELLOW/MANUAL **and explicitly listed it in its batch-B port recommendation**. The manifest overrode to SKIP-OTHER without documenting why. +- Fork carries the identical pre-fix code (`useBackHandler` + unconditional `popTo`). Defensible as a low-value skip, but the override should be documented (or reclassified PORT-A1/low). + +--- + +## 3. Minor inconsistencies (cosmetic, non-blocking) + +| # | Item | Detail | +|---|---|---| +| M1 | Claim 1 attribution (analysis.md §5) | `c3260e8e0` "fixes" the novelId-twice bug in effect, but upstream actually corrected the typo earlier in the Drizzle migration `b8c177bc5`. c3260e8e0 remains the correct *porting vehicle* for the fork. | +| M2 | Claim 5 worked example (analysis.md §5) | `'10' sorts before '2'` is true for ASC (getNextChapter:453) but inverted for the DESC query cited (line 434; there the failure mode is `'9'` before `'10'`). Substance correct. | +| M3 | `c3c891cea0` category label | Labeled SKIP-CI but is a 40-file managed-workflow migration → should be SKIP-INFRA. Skip decision itself is correct. | +| M4 | `e8c5550256` borderline skip | Perf-only 2-line core.js regex twin of the ported `54fedc2f` (1-line, PORT-A1/HIGH). Defensible but inconsistent; fork carries the pre-fix regex at core.js:4133. | +| M5 | Ahead-count 306 vs 307 | Analysis was run at HEAD `40c667679`; `docs(readme)` landed after → 307 now. Snapshot drift, not an error. | +| M6 | Manifest note | Fork `ttsNotification.ts` defines 3 buttons; the "5-button" claim lives in native MediaSession/notification code. Cosmetic imprecision. | + +--- + +## 4. PRD-level gaps (from PRD review) + +| # | PRD | Gap | +|---|---|---| +| G1 | PRD-A `45c4ea8ca0` | Omits the 7-line `src/services/epub/import.ts` hunk (try/catch around `NativeFile.copyFile` — fork import.ts:129 is pre-fix). The commit's named "EPUB import crash loop" fix lives partly there; port must include both files. | +| G2 | PRD-C #6 (risk table) | Mentions only `surfaceContainerLow`; fork themes lack **both** `surfaceContainerHigh` and `surfaceContainerLow` (verified `src/theme/types/index.ts:39-41`, useTheme.ts:22-27, all 10 md3 variants). Both keys must be added to the type + computed-colors + variants. | +| G3 | PRD-A `0c3542836c` | Line refs off by ~8 (35/48/60 not ~43/56/69); INSERT trigger is recompute-style (not byte-identical to upstream pre-fix) — julianday change still applies, port valid. | +| G4 | PRD-A `31cb4b994a` | Says "drop paddingVertical: 6"; fork uses scaled `paddingVertical: padding.xs` — intent-equivalent, not literal. | +| G5 | PRD-A | README precondition "push `dev` before Batch A" not carried into PRD-A §5; fork-side unit tests should be authored for the `c3260e8e0` SQL fixes (upstream tests are Drizzle-era, not cherry-pickable). | + +--- + +## 5. Recommended corrections (priority order) + +1. Reclassify `0cb9da9027` → **PORT-A2** (SQL-layer concurrency fix) + add `NovelQueries.ts:180/321`, `helpers.tsx:133-137` unawaited-`runAsync` items to the audit follow-up list (they pair with `2cd5a97ee8` which the analysis already flagged). +2. Reclassify 5 rows from DIRECT/GREEN → MANUAL(-trivial)/YELLOW: `31cb4b994a`, `93bc5e5e4b`, `2a919ec0f9`, `f1fdafd37f`, `a062beee87`. +3. Document the `9783c4d5e3` skip override (or reclassify PORT-A1/low). +4. PRD-A: add `epub/import.ts` hunk to `45c4ea8ca0`; fix line refs for `0c3542836c`/`31cb4b994a`; carry the "push dev first" precondition + fork-side test note. +5. PRD-C: add `surfaceContainerHigh` alongside `surfaceContainerLow` in the theme-key risk item. +6. Cosmetic: analysis.md §5 claim 1 attribution nuance + claim 5 example direction; manifest note for `c3c891cea0` category. + +--- + +## 6. Bottom line + +- **Trustworthiness of analysis.md §5**: confirmed — all 6 fork-bug claims reproduced in source, all 7 cited upstream fix commits exist with the described corrections. +- **Manifest reliability**: PORT-vs-SKIP axis accurate across all sampled rows; only port-strategy labels (DIRECT) and one hidden-port misclassification (`0cb9da9027`) need fixing. +- **ALREADY-HAVE / SKIP logic**: sound (9/9, 13/16 spot-checks valid; the 3 outliers are documented above). +- **PRDs**: implementable as written; the 5 gaps above are small edits, not redesigns. +- **Net**: the analysis can be trusted as the implementation blueprint, with the ~6 corrections in §5 applied first. diff --git a/specs/upstream-merge-analysis-2026-08/analysis.md b/specs/upstream-merge-analysis-2026-08/analysis.md new file mode 100644 index 0000000000..689f9484ea --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/analysis.md @@ -0,0 +1,102 @@ +# Upstream Merge Analysis — Full Report + +**Date**: 2026-08-03 · **Upstream**: `original` @ `c3260e8e0` (2026-08-01) · **Fork**: `dev` @ `40c667679` (v2.1.3) · **Merge base**: `467a97dcf` (2025-12-24) + +**Method**: 170 commits classified by 4 parallel analysis agents (chronological groups of ~43), each cross-referencing `git show` diffs against fork source; key claims independently spot-verified against fork files (see verification log at the end). + +--- + +## 1. Divergence snapshot + +| Metric | Value | +|---|---| +| Commits behind `original` | 170 | +| Commits ahead (`dev` only) | 306 | +| Files changed by fork since merge base | 596 | +| Files changed upstream since merge base | 1088 | +| Files changed by BOTH (overlap surface) | 206 | +| Upstream commits already in fork (patch-id match) | 0 (nothing cherry-picked) | +| Commit types | 66 fix, 56 feat, 17 chore, 14 refactor, 5 docs, 4 perf, 2 test, 1 ci | + +## 2. The three architecture walls + +| Wall | Upstream | Fork | Porting consequence | +|---|---|---|---| +| **Database** | Drizzle ORM + op-sqlite (#1735) + `manager/` + `drizzle/migrations` | expo-sqlite raw SQL + custom `MigrationRunner` + `tables/*.ts` | Drizzle commits NOT-FEASIBLE; only their *intent* translates to raw SQL | +| **TTS & native modules** | Nitro modules (`nitro-tts`, `nitro-epub`), Android MediaSession, WorkManager background tasks (#1889, #1896) | Custom Kotlin (`TTSHighlightModule.kt`, `TTSForegroundService.kt`, `DoHManagerModule.kt`, `NativeFile.kt`), `ttsNotification.ts`, react-native-background-actions | All upstream TTS work is dead to the fork **by design** — fork's stack is its differentiator | +| **Build & workflow** | Expo 55 managed (#1885), rock CLI (#1812), `assets/reader/js/` layout, `scripts/generate-env-file.cjs`, config plugins | Expo 54 bare, RN community CLI + Gradle 9.2.0, `android/app/src/main/assets/js/` layout | Path-level diffs never apply; reader-JS commits must be re-diffed at function level | + +**State management also diverged**: upstream moved the novel screen to a zustand store (`useNovel`); fork uses its own `useNovel.ts` hook + `NovelContext`/`ChapterContext`. Perf refactors (`9805da63`, `3d102a52`) cannot transfer as code — only their perf *rationales*. + +## 3. Classification summary (all 170) + +| Batch | Count | Meaning | +|---|---|---| +| **A1** | 16 | Verified Tier-1 fixes — fork carries byte-identical pre-fix code; 8 verified clean-apply (DIRECT), a few need manual re-application | +| **A2** | 6 | SQL bug fixes — copy corrected raw SQL into fork's expo-sqlite layer | +| **A3** | 7 | Extended verified bug fixes — localized manual ports | +| **B** | 7 | High-value user-facing features (2–3 days) | +| **B-stretch** | 15 | Optional medium features | +| **C** | 7 | UX/theme train (prerequisite-ordered) | +| **C-optional** | 2 | Optional M3 polish | +| **D** | 5 | Project-scale rewrites (separate PRs) | +| **D-stretch** | 1 | Scheduled library updates (needs native work) | +| **D-optional** | 10 | Lower-priority project-scale candidates | +| **SKIP** | 94 | CI/release/docs/infra/ALREADY-HAVE/architecture-incompatible | + +**PORT total: 76 commits.** Full per-commit detail: `commit-manifest.csv` + `reference/subagent/*`. + +## 4. Skip categories & rationale + +| Category | Count | Reason | +|---|---|---| +| SKIP-CI | 19 | Fork has separate GitHub Actions; release automation (draft releases, semver bump, APK verification) is upstream-only | +| SKIP-INFRA | 13 | rock CLI, Expo 55, managed workflow, repo reorganization, `lneader` org rebranding (actively harmful — would point users at wrong repo) | +| SKIP-DRIZZLE | 9 | Migration SQL / op-sqlite APIs; fork's MigrationRunner already covers recovery + counters | +| SKIP-OTHER | 20 | No clear user value or overlaps fork-critical files (incl. **trap commit `72bfcad0`** — lint cleanup touching WebViewReader/ReaderScreen/ChapterContext, zero value, maximal conflict surface) | +| ALREADY-HAVE | 9 | Fork shipped its own equivalents, often earlier: DoH (`4d982479`), MediaSession/media notification (`cc04287`, `74808f08`, `67d2c7c0`), WebView-reset fix (`02b6b984`), metro middleware (`58db0352`), EPUB exporter (`4cb1890b`), volume buttons (`52914b97`), collapsed-TTS guard (`cbe7d70e`) | +| SKIP-RELEASE | 6 | Version bumps; fork has own v2.1.3 numbering | +| SKIP-DOCS | 5 | Docs only | +| SKIP-I18N | 4 | Crowdin syncs at `src/i18n/` — fork stores `strings/languages/` with custom keys; only per-key diffs, never overwrite | +| SKIP-DEP | 3 | `@legendapp/list` 3.3.3 patches (fork pins ^2.0.16), hermes pinning | +| SKIP-NATIVE | 5 | WorkManager/native-background-tasks/background-actions-removal commits; **`9babe167` (remove background-actions manifest customization) would break the fork — do NOT port** | +| SKIP-NITRO | 1 | op-sqlite DevTools adapter | + +## 5. Key findings + +1. **The fork is 4–6 weeks behind on upstream *fix* quality while ahead on TTS.** The 2026-02→08 upstream fix cluster (tracker search, genre crashes, download removal, category reorder, date sorting, NativeFile crashes) targets shared legacy code the fork still carries pre-fix — the highest risk/benefit ratio available. +2. **Raw-SQL layer is a portability advantage.** Pre-Drizzle-era upstream SQL fixes (numeric page ordering, next-chapter logic, category built-in IDs, EPUB page ordering) map ~1:1 onto fork's `db.getAllAsync` layer. +3. **`NativeFile.kt` is a shared, stable seam** — both trees carry the same TurboModule file; the most reliable direct cherry-pick surface (`34be7a12`, `45c4ea8`, `4088d59`). +4. **Translation drift** (`src/i18n/languages/` vs `strings/languages/`) makes every Crowdin commit a manual merge, not a cherry-pick. +5. **Verified live fork bugs** (spot-checked during analysis): + - `deleteReadChaptersFromDb` passes `chapter.novelId` twice → deletes the **wrong folder**; `deleteDownloads` runs `UPDATE Chapter SET isDownloaded = 0` **without WHERE** → wipes all download flags. (Upstream `c3260e8e0` ships the corrected functions and is the fork's porting vehicle; the novelId typo itself was first corrected upstream in the Drizzle migration `b8c177bc`.) + - `ServiceManager.setMeta` throttle computes `delay = 1000 - now - lastNotifUpdate` → negative-delay timers, no throttling. (Upstream `57b9d41b`.) + - Default-category resolution `WHERE sort = 1` breaks after category reorder. (Upstream `ee3a4f20`.) + - Multi-page chapter ordering compares `page` as TEXT — `'10'` sorts before `'2'` in the ASC query (`getNextChapter`); in the DESC query (`getPrevChapter`) the failure mode is `'9'` before `'10'`. (Upstream `d15cf98`, `f069320`, `52d5c99c`.) + - Unawaited `db.runAsync` inside `withTransactionAsync` (`NovelQueries.ts:180,321`, `helpers.tsx:133-137`) → SQLITE_BUSY/concurrency risk; the `withExclusiveTransactionAsync` + `await` fix is a **pre-Drizzle** expo-sqlite change that maps 1:1 onto fork code. (Upstream `0cb9da9027` — reclassified SKIP→PORT-A2 by review 2026-08-04.) + - `normalizeText` in core.js doesn't strip leading/trailing quotes → TTS reads them aloud. (Upstream `54fedc2f`.) + +## 6. Verification log (spot-checks performed during analysis) + +- `src/database/queries/ChapterQueries.ts:167-184` — download-removal bugs confirmed +- `src/services/ServiceManager.ts:152-153` — inverted throttle + negative delay confirmed +- `android/.../NativeFile/NativeFile.kt` — unlink `throw` pre-fix state confirmed +- `src/screens/Categories/CategoriesScreen.tsx:94` — index-based keyExtractor bug confirmed +- `src/screens/reader/hooks/useChapter.ts:125` — no DB progress hydration on open +- `src/screens/reader/components/WebViewReader.tsx:473+` — settings synced via injectJavaScript (upstream `02b6b984` already covered) +- `android/app/src/main/assets/js/core.js:2203` — normalizeText lacks quote-strip +- `src/database/queries/NovelQueries.ts:180,321` + `src/database/utils/helpers.tsx:133-137` — unawaited `runAsync` inside `withTransactionAsync` (concrete fix: upstream `0cb9da9027` → `withExclusiveTransactionAsync` + `await`; concept of upstream `2cd5a97` also applies) + +## 8. Review corrections (2026-08-04) + +Independent 4-subagent review (`REVIEW-2026-08-04.md`) confirmed the analysis's core claims and found the following corrections, all applied: + +1. **`0cb9da9027` reclassified SKIP-OTHER → PORT-A2** — pre-Drizzle expo-sqlite transaction-concurrency fix (verified `db.ts` at that commit imports expo-sqlite; dated 2026-01-15, before the Drizzle migration). Fork carries the identical pre-fix pattern. +2. **5 rows relabeled DIRECT/GREEN → MANUAL(-trivial)/YELLOW** (`31cb4b99`, `93bc5e5e`, `2a919ec0`, `f1fdafd3`, `a062beee`) — their upstream patches fail `git apply --check` on fork files that were rewritten (scaled dimensions, eslint headers, `@env` config). All remain valid PORTs. +3. **`9783c4d5e3` reclassified SKIP-OTHER → PORT-A1/low** — small safe deletion in shared SettingsRepositoryScreen.tsx; group1 subagent had recommended porting it. +4. **`c3c891cea0` relabeled SKIP-CI → SKIP-INFRA** — it is a 40-file managed-workflow migration, not CI tooling. +5. Counts updated accordingly: PORT 74→**76**, SKIP 96→**94**, A1 15→**16**, A2 5→**6**, SKIP-OTHER 22→**20**, SKIP-CI 20→**19**, SKIP-INFRA 12→**13**. + +## 7. Recommended execution order + +See `README.md` §Suggested execution order and the per-batch PRDs in `batches/`. diff --git a/specs/upstream-merge-analysis-2026-08/batches/PRD-A-safety-fixes.md b/specs/upstream-merge-analysis-2026-08/batches/PRD-A-safety-fixes.md new file mode 100644 index 0000000000..879063e548 --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/batches/PRD-A-safety-fixes.md @@ -0,0 +1,129 @@ +# PRD — Batch A: Safety Fixes + +**Status**: 📋 PLANNED (analysis complete; no code changes yet) +**Date**: 2026-08-03 +**Source**: `specs/upstream-merge-analysis-2026-08/analysis.md` · Manifest batch: A1, A2, A3 +**Estimated effort**: half-day (Phase 1) + 1 day (Phase 2) + +--- + +## 1. Objective + +Port **29 verified upstream bug fixes** into the fork. These fixes target code the fork often carries **byte-identical to the upstream pre-fix state**, meaning the fork has the same live bugs. Phase 1 is low-risk ports (8 verified clean-apply); Phase 2 is localized manual ports with verified identical pre-fix code. + +**Verified live fork bugs this batch fixes:** +- Deleting read chapters deletes the **wrong folder** (`deleteReadChaptersFromDb` passes `chapter.novelId` as chapter id) +- "Delete downloads" wipes `isDownloaded = 0` for **every chapter** in the DB (UPDATE without WHERE) +- Default category breaks after category reorder (`WHERE sort = 1`) +- TTS reads leading/trailing quotes aloud (`normalizeText`) +- Deleting a downloaded chapter whose files were removed externally **crashes the whole app** (NativeFile unlink `throw`) +- ServiceManager notification throttle computes **negative delays** (never throttles) +- Multi-page novels: chapters ordered by TEXT `page` comparison; wrong next/prev chapter +- Unawaited `db.runAsync` inside `withTransactionAsync` (NovelQueries.ts:180/321, helpers.tsx:133-137) → SQLITE_BUSY/concurrency risk +- Genre strings crash EditInfoModal/NovelInfoComponents on malformed data + +--- + +## 2. Phase 1 — Tier-1 verified fixes (16 commits: 8 DIRECT clean-apply + 8 MANUAL) + +| Commit | Subject | Port strategy | +|---|---|---| +| `34be7a1234` | fix(native-file): prevent fatal crash when unlinking missing files (#1832) | NativeFile.kt: `if (!file.exists()) throw` → `return` (1 line). **Top crash fix.** | +| `54fedc2f53` | fix(tts): remove leading/trailing quotes from normalized text (#1869) | `core.js` `normalizeText` (~line 2203): strip `"`/`'` at string ends. Manual apply (core.js diverged elsewhere). Run TTS tests after. | +| `45c4ea8ca0` | fix: handle IOException in NativeFile to prevent EPUB import crash loop (#1315) | Wrap `writeFile`/`readFile`/`copyFileContent` IOExceptions. Direct (NativeFile.kt). **ALSO includes the try/catch around `NativeFile.copyFile` in `src/services/epub/import.ts:129` (fork pre-fix) — the commit's named EPUB-import crash-loop fix lives partly there; port BOTH files.** | +| `4088d5966b` | fix: close OkHttp response and streams in NativeFile.downloadFile (#1740) | `response.body!!.byteStream()` → wrap in `use {}`. Direct. | +| `57b9d41b5c` | fix(service): correct notification throttle logic in ServiceManager.setMeta (#1873) | Fix inverted condition + `delay = 1000 - (now - lastNotifUpdate)`. Direct (fork `ServiceManager.ts:152-153`). | +| `9e38ad114d` | fix: sanitize EPUB filename (#1480) | `ExportNovelAsEpubButton.tsx`: sanitize `novel.name` for filesystem-unsafe chars. 1 line. Direct. | +| `93bc5e5e4b` | fix: handle missing MAL list entries and null progress (#1875) | `SetTrackChaptersDialog.tsx` null-safe `progress.toString()` + default-entry creation in `myAnimeList.ts`. **Manual** — fork `myAnimeList.ts` diverged (@env client id, WebBrowser, pkceChallenger); only the SetTrackChaptersDialog hunk ports cleanly. | +| `788f6cede7` | fix: prevent table overflow and clean up CSS (#1876) | `index.css`: `div:has(> table):not(#LNReader-chapter){overflow:auto}` + next-button cleanup. Direct. | +| `76106317b6` | fix: restore Midnight Dusk chip contrast (#1939) | `SelectableChip.tsx`: remove `selectedColor={theme.primary}` (1 line). Direct. | +| `31cb4b994a` | fix: align bottom navigation with Material 3 specs | `BottomTabBar/index.tsx`: `paddingBottom: 16 + insets`. **Manual** — fork BottomTabBar rewritten (`getStyles` + scaled `padding.xs`); upstream hunk fails `git apply --check`, apply the M3 intent with fork's scaled values. | +| `0ed7d878b4` | fix: prevent white reader drawer seam | `ReaderScreen.tsx`: `drawerStyle={{backgroundColor:'transparent'}}`. Tiny hunk in fork-critical file — manual apply. | +| `a062beee87` | fix: add max height to Set Categories modal (#1738) | `SetCategoriesModal.tsx`: FlatList `maxHeight: 40% window`. **Manual** — fork modal rewritten (scaleDimension/AppText); hunk fails apply, apply maxHeight intent manually. | +| `f1fdafd37f` | fix: prevent tab bar height jump and shadow flash on Browse screen | `BrowseScreen.tsx`: add `initialLayout` to TabView + border instead of shadow. **Manual** — fork has eslint-disable header + no Color import; hunk fails apply, apply intent manually. | +| `65e8610030` | refactor: replace bottom navigation shift with fade | `BottomNavigator.tsx:68`: `animation: 'shift'` → `'fade'`. Direct. | +| `2a919ec0f9` | chore: gitignore tsconfig.tsbuildinfo build cache (#1829) | 1-line `.gitignore`. **Manual-trivial** — fork .gitignore context differs (expo-cli block); append `tsconfig.tsbuildinfo` manually. | +| `9783c4d5e3` | fix: normalize repository back navigation (#1905) | `SettingsRepositoryScreen.tsx`: remove `useBackHandler` + unconditional `popTo` on back. Manual-trivial — fork carries identical pre-fix code; reclassified from SKIP by review 2026-08-04. | + +## 3. Phase 2a — SQL bug fixes (6 commits, MANUAL — copy corrected SQL) + +| Commit | Subject | Fork location | +|---|---|---| +| `d15cf98742` | fix: order chapter pages numerically (#1742) | `ChapterQueries.ts`: `CAST(page AS INTEGER)` in `getCustomPages`, `getPrevChapter`, `getNextChapter` | +| `f0693204e1` | fix: improve next chapter selection logic (#1744) | `ChapterQueries.ts:441` `getNextChapter`: correct position-0/page handling + `LIMIT 1`. Port together with `d15cf98` | +| `52d5c99c46` | fix: order EPUB chapters across paginated pages (#1762) | `ChapterQueries.ts:478` `getNovelDownloadedChapters`: `ORDER BY page ASC, position ASC` + range→limit/offset rework | +| `a421177f0c` | fix: improve category badge layout and drag reordering | `CategoriesScreen.tsx:94`: fix index-based `keyExtractor` (breaks drag reorder). Optional: IconButton `onPressIn` drag handle | +| `ee3a4f20bc` | fix: preserve default category after reordering (#1922) | `NovelQueries.ts` (4 sites) + `CategoryQueries.ts:47`: replace `WHERE sort = 1` with `BUILT_IN_CATEGORY_IDS` (default=1, local=2); `updateCategoryOrderQuery` writes positional `sort = index+1`. Fork already hardcodes id 2 for Local. | +| `0cb9da9027` | refactor: replace `withTransactionAsync` with `withExclusiveTransactionAsync` (#1636) | `CategoryQueries.ts`/`NovelQueries.ts`/`StatsQueries.ts` + `helpers.tsx` + `migrateNovel.ts`: swap to `withExclusiveTransactionAsync` + `await` all `runAsync` inside transactions. Fork has the identical unawaited pattern (`NovelQueries.ts:180,321`, `helpers.tsx:133-137`). **Pre-Drizzle** expo-sqlite fix — reclassified from SKIP by review 2026-08-04. | + +## 4. Phase 2b — Extended verified fixes (7 commits, MANUAL, localized) + +| Commit | Subject | Port strategy | +|---|---|---| +| `c3260e8e01` | fix: resolve chapter download queue and removal issues (#1937) | **Highest priority in this batch.** Fix `deleteReadChaptersFromDb` (pass `chapter.id` not `novelId`), `deleteDownloads` (add `WHERE id IN (...)`), await `db.execAsync`. Skip upstream backgroundTasks/useDownloadReconciliation halves (fork uses ServiceManager + MMKV queue). | +| `eb12bdfd62` | fix: prevent novel genre rendering crashes | Add `parseGenres()` util (split on comma, handle null/empty); apply to `EditInfoModal` + `NovelInfoComponents`. | +| `8f53550d2c` | fix: restore tracker search requests | `TrackSearchDialog.tsx`: reschedule search on open + event-based `onSubmitEditing` handler + `returnKeyType="search"`. | +| `0c3542836c` | fix: synchronize library updates and optimize import refreshes | `NovelTable.ts` triggers (lines 35/48/60): `julianday()` comparison (INSERT trigger is recompute-style in fork, not byte-identical to upstream — julianday change applies to the `lastUpdatedAt` sub-expression of all three triggers); `LibraryQueries` `lastUpdatedAt` sort; ISO-8601 inserted-chapter timestamps. | +| `25c93afabe` | fix(reader): restore saved chapter progress from db (#1850) | `useChapter.ts:125`: `dbChapter = await getDbChapter(chapter.id)` fallback (3 lines). Complements TTS Math.max reconciliation for non-TTS reader open. | +| `7ddd934c2b` | fix: handle missing tracker client IDs | Guard clauses in `src/services/Trackers/aniList.ts` + `myAnimeList.ts` (skip `scripts/generate-env-file.cjs` hunk — fork uses `.env`). | +| `e75f4fba5d` | feat: add more novel statuses and icons (#1931) | `NovelStatus` enum + `getStatusIcon` in `NovelInfoHeader.tsx` (skip `.husky/pre-push` hunk). | + +## 5. Implementation order + +1. Phase 1 (16 commits) — can be done in ~4 focused edit sessions, grouped by file: + - Native layer: `34be7a12`, `45c4ea8`, `4088d59` (NativeFile.kt) + - Services: `57b9d41b` (ServiceManager.ts) + - Reader/WebView: `54fedc2f` (core.js), `788f6ced` (index.css), `0ed7d878` (ReaderScreen) + - UI: `76106317`, `31cb4b99`, `a062bee`, `f1fdafd`, `65e86100` + - Export/MAL: `9e38ad1`, `93bc5e5e` + - Housekeeping: `2a919ec0` +2. Phase 2a (SQL): `d15cf98`+`f069320` together, then `52d5c99c`, `a421177f`, `ee3a4f20`, `0cb9da90` (transaction concurrency). +3. Phase 2b: `c3260e8e0` first (correctness), then `eb12bdfd6`, `8f53550d`, `0c354283`, `25c93afa`, `7ddd934c`, `e75f4fba`. + +Work on a branch: `merge/original-sync-batch-a`. + +> ⚠️ **Precondition**: commit/push the current unpushed `dev` work (TTS text-cleanup, per AGENTS.md) BEFORE starting this batch. + +## 6. Testing & validation + +```bash +pnpm run type-check +pnpm run lint:fix +pnpm run format +pnpm run test # 1235+ tests, zero regressions +pnpm run test -- --testPathPattern="ChapterQueries|CategoryQueries|NativeFile|ServiceManager|StatsQueries" +pnpm run test:tts-wake-cycle # core.js + TTS changes (54fedc2f) +pnpm run test:tts-refill +``` + +- After `54fedc2f` (core.js): run TTS wake-cycle + refill suites; verify highlighted text output unchanged (length-preserving). +- After `c3260e8e0`: manual test — download 2 novels, delete one novel's read chapters, confirm other novel's download flags + files intact. **Write fork-side unit tests for the two SQL fixes** (upstream's tests are Drizzle-era and not cherry-pickable). +- After `ee3a4f20`: reorder categories, add a novel, confirm it lands in the default category. +- After `0cb9da9027`: run DB query suites (NovelQueries/CategoryQueries/StatsQueries) — transaction semantics unchanged, no SQLITE_BUSY regressions. + +## 7. Acceptance criteria + +- [ ] All 29 commits ported; no TypeScript/ESLint regressions +- [ ] Download-flag wipe bug eliminated (UPDATE scoped by `id IN`) +- [ ] Read-chapter deletion removes correct folder +- [ ] TTS no longer reads quote characters +- [ ] Deleting a chapter with missing files doesn't crash the app +- [ ] Multi-page chapters navigate in correct numeric order +- [ ] Default category preserved after reorder +- [ ] Full test suite green (1235+ tests, zero regressions) + TTS suites green +- [ ] `pnpm run format` applied (Husky pre-commit) + +## 8. Risks & mitigations + +| Risk | Mitigation | +|---|---| +| `core.js` divergence (54fedc2f) | Manual 1-line apply at function level; TTS test gates | +| `c3260e8e0` touches DB + download flows | Scoped SQL, manual QA of multi-novel download state | +| `ee3a4f20` changes category resolution semantics | Built-in IDs already partially hardcoded in fork; run CategoryQueries tests | +| Cherry-pick conflicts on fork-modified files | All items in this batch were verified against fork source pre-analysis; apply manually where needed | + +## 9. Out of scope + +- Batch B features, Batch C theme work, Batch D rewrites +- Upstream's backgroundTasks/useDownloadReconciliation architecture +- Drizzle migration commits (SKIP-DRIZZLE, see manifest) diff --git a/specs/upstream-merge-analysis-2026-08/batches/PRD-B-high-value-features.md b/specs/upstream-merge-analysis-2026-08/batches/PRD-B-high-value-features.md new file mode 100644 index 0000000000..10a2ca5796 --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/batches/PRD-B-high-value-features.md @@ -0,0 +1,93 @@ +# PRD — Batch B: High-Value Features + +**Status**: 📋 PLANNED (analysis complete; no code changes yet) +**Date**: 2026-08-03 +**Source**: `specs/upstream-merge-analysis-2026-08/analysis.md` · Manifest batch: B (7) + B-stretch (15) +**Estimated effort**: 2–3 days (core B) · stretch items optional, ~1 day each + +--- + +## 1. Objective + +Port **7 high-value user-facing features** that the fork genuinely lacks, adapting each to the fork's architecture (raw-SQL DB, custom TTS, MMKV settings, `strings/languages/` i18n). All are MANUAL ports — none cherry-pick cleanly, but each is bounded and self-contained. + +## 2. Scope — core batch (7 commits) + +| # | Commit | Feature | Port notes | +|---|---|---|---| +| 1 | `c40edd5b2c` | **Kitsu tracker** (#416) | New service file `kitsu.ts` (~490 lines: auth, list sync, scoring) + score selectors + tracker cards + settings screen + login dialog. Fork's tracker infra exists (`src/services/Trackers/`, TrackerCards, ScoreSelectors, SetTrackScoreDialog, SettingsTrackerScreen). Best value/effort in batch. | +| 2 | `c0877a9b06` | **Ignorable app update notifications** | Long-requested (upstream #600/#1632): "ignore this version" persistence (MMKV) so update prompt never returns. Fork's `useGithubUpdateChecker.ts` is a near-clone of upstream's deleted hook — port `AppUpdateChecker` + ignore-version field; keep fork's repo URL (`bizzkoot/lnreader`) + `githubReleaseUtils.pickApkAsset`. | +| 3 | `345d084eba` | **Chapter numbers in EPUB titles** (#1405) | New `epubIncludeChapterNumber` setting (useSettings, additive) + numbered-title composition in export flow. Adapt to fork's `@cd-z/react-native-epub-creator` (JS) — NOT upstream's nitro-epub. Files: `ExportEpubModal`, `ExportNovelAsEpubButton`. | +| 4 | `799845426c` | **Parallel library updates across sources** | Fork's `services/updates/index.ts` is a sequential `for` + `sleep(1000)` loop. Port group-by-pluginId + `Promise.all` concurrency-3 pattern. No schema/native changes. Skip upstream `TaskNotificationFactory.kt` (fork shows progress via ServiceManager metadata). | +| 5 | `8f237909d5` | **First unread chapter in ReadButton/FAB** | "Start reading" jumps to first unread chapter instead of `chapters[0]`. New `getFirstUnreadChapter` SQL + exposure via `useNovel` + `ReadButton.tsx`/`NovelScreenList.tsx` wiring. Fork has no first-unread logic today. | +| 6 | `4ad0639796` | **Jump-to-chapter / drawer can't reach unloaded chapters** | New raw-SQL queries `getNovelChaptersByNumber` (`position = ?`) + `getNovelChaptersByName` (`name LIKE ?`) — map 1:1 onto fork expo-sqlite; `loadUpToBatch` in useNovel; wire `JumpToChapterModal.tsx` + `ChapterDrawer/index.tsx`. Skip commit's pnpm-lock churn. | +| 7 | `5d996f1388` | **Configurable chapter download cooldown** (#1834) | `chapterDownloadCooldownMs` AppSettings + modal + replace hardcoded `await sleep(1000)` in `downloadChapter.ts:93`. Helps users hitting 429 rate limits. | + +## 3. Scope — stretch batch (15 commits, optional) + +| Commit | Feature | Effort | +|---|---|---| +| `c30441b949` | Smart library update filters (skip read/completed/unstarted) | SQL filter trivial (fork schema has `status`/`lastReadAt`); `SmartUpdateDialog` UI in SettingsLibraryScreen | +| `8ac628b3de` | Per-novel history removal + confirm dialog | New `deleteNovelHistory` SQL + `useHistory`/`HistoryScreen`/`HistoryCard` UI | +| `732628c40b` | Date format + relative timestamp settings | New `utils/dateFormat.ts` + Appearance setting + `DateFormatModal`; touches ~8 screens (History, Downloads, ChapterItem, Updates, GoogleDriveModal…) | +| `d02291c22c` | Remember last used library category | Small setting + rehydration in `LibraryScreen` (bottom-tab index state) | +| `97be48dfe1` | Migration review options (cover/metadata source, redownload policy + review dialog) | Adapts to fork's raw-SQL `migrateNovel.ts` + `MigrationNovelList.tsx`; closes upstream #937/#1037 | +| `3e1e211098` | WebView address bar | ~11 lines in `WebviewScreen` Appbar (URL display + address bar) | +| `eac6ad584e` | Novel app bar + EPUB export UX polish | Re-port against fork's `NovelAppbar`, `ExportEpubModal`, `JumpToChapterModal`, `NovelScreenList` (different layouts/uiScale) | +| `b53ec3352f` | Plugin setting types: dropdown + checkboxes | Extend `src/plugins/types/index.ts` + `SourceSettings.tsx` (fork currently pre-fix) | +| `ae99b077d4` | `@noble/ciphers` for plugins (#1709) | Add dep + expose `crypto` surface in `pluginManager.ts` | +| `01596c045a` | Apply active filters to chapter count display (#1793) | Extend raw-SQL `getChapterCount` with filter arg; update `useNovel` call sites (105/270/291) | +| `d2b95ae316` | NativeFile for custom CSS/JS import (#1661) | Replace `StorageAccessFramework.readAsStringAsync` in `AdvancedTab.tsx:206` with NativeFile copy/read | +| `caed965b6b` | EPUB import DocumentPicker cache-cleanup crash (#1725) | `copyToCacheDirectory: false` in `LibraryScreen.tsx:167` | +| `9922d43940` | EPUB export missing-image cleanup (#1696) | Remove `
`/`` refs to deleted files during export | +| `138fdff2e5` | Prevent duplicate WebView bottom insets | Remove `paddingBottom: bottom` in `WebviewScreen.tsx` container (fork likely has same double-inset bug) | +| `4dc933ee5a` | Remove white flash during screen transitions (#1926) | ThemedRootView paints root bg + reader stack background; replace upstream config-plugin hunk with native `res/values` windowBackground | + +## 4. Feature-specific risks + +| Feature | Risk | +|---|---| +| Kitsu tracker | New OAuth/API surface — needs network mocking in tests; tracker login dialog rework touches shared components | +| Ignorable update notifications | Persistence key must not clash with existing MMKV `AppSettings` fields; keep fork repo URL | +| Parallel library updates | Concurrency changes update-ordering semantics — regression-test global update with >3 sources | +| Jump-to-chapter fix | `loadUpToBatch` touches fork-modified `useNovel` — isolate + run novel-screen tests | +| Download cooldown | AppSettings interface change ripples to any screen reading settings | + +## 5. Implementation order (core) + +1. `79984542` (parallel updates) — smallest, highest certainty +2. `c40edd5` (Kitsu) — new file + wiring, no conflicts +3. `8f23790` + `4ad0639` — reader/navigation cluster (share `useNovel`/ChapterQueries touchpoints) +4. `345d084e` + `c0877a9b` — settings + persistence cluster +5. `5d996f13` — downloader + +Work on a branch: `merge/original-sync-batch-b`. + +## 6. Testing & validation + +```bash +pnpm run type-check && pnpm run lint:fix && pnpm run format +pnpm run test +pnpm run test -- --testPathPattern="Tracker|NovelQueries|ChapterQueries|Settings" +pnpm run test:tts-wake-cycle # safety gate if ReaderScreen/useNovel touched +``` + +Manual QA: +- Kitsu: login → list sync → score sync → search +- Parallel updates: 3+ sources with many novels; observe parallel progress +- Jump-to-chapter: open chapter beyond preloaded batch from both modal and drawer +- Update ignore: tap "ignore version" → relaunch → no prompt + +## 7. Acceptance criteria + +- [ ] All 7 core features live with settings persisted in MMKV +- [ ] No regressions in existing tracker flows (MAL/AniList/MangaUpdates) +- [ ] Library updates complete faster (parallel) without duplicate/overlapping downloads +- [ ] 1235+ tests green; TTS suites green +- [ ] Stretch items each landed as separate small PRs with own tests + +## 8. Out of scope + +- Anything touching the TTS engine internals (WebViewReader TTS sections, TTSAudioManager, .kt TTS modules) +- Drizzle-based upstream implementations (rewrite to raw SQL) +- Batch C theme train and Batch D project-scale work diff --git a/specs/upstream-merge-analysis-2026-08/batches/PRD-C-ux-theme-train.md b/specs/upstream-merge-analysis-2026-08/batches/PRD-C-ux-theme-train.md new file mode 100644 index 0000000000..9f3d66b993 --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/batches/PRD-C-ux-theme-train.md @@ -0,0 +1,78 @@ +# PRD — Batch C: UX / Theme Train + +**Status**: 📋 PLANNED (analysis complete; no code changes yet) +**Date**: 2026-08-03 +**Source**: `specs/upstream-merge-analysis-2026-08/analysis.md` · Manifest batch: C (7) + C-optional (2) +**Estimated effort**: 1–2 days core · optional items ~0.5 day each + +--- + +## 1. Objective + +Port the Material-3 UX polish cluster from upstream. **Strict ordering matters**: the dynamic Material You colors feature (`44c8e54e`) depends on the theme-switcher refactor (`8f47e8fd`) landing first. The fork's `useTheme.ts` was verified to match the upstream pre-refactor base, so adaptation is localized. + +## 2. Core batch (7 commits, prerequisite-ordered) + +| Order | Commit | Feature | Port notes | +|---|---|---|---| +| 1 | `8f47e8fd1e` | **Theme switcher refactor** (#1839) | Context-based `useTheme` + `ThemePicker` + appearance-settings rework (~20 files). Fork's `useTheme.ts` matches the pre-refactor base (same imports, same MMKV hooks). **Prerequisite** for dynamic colors. | +| 2 | `44c8e54ed8` | **Dynamic Material You theme colors** (#1863) | Android 12+ wallpaper-derived colors via `@pchmn/expo-material3-theme` + new `src/theme/dynamic.ts` + useTheme/settings/onboarding wiring. High user appeal. Verify UI-scale + tests after. | +| 3 | `0c8546188e` | **MD3 Slider component** | New MD3 Slider (416 lines) replacing `@react-native-community/slider`. Coordinate `package.json` removal; fork's `TextSizeSlider`/`ReaderValueChange` (incl. `ReaderTTSTab` path — fork's TTS tab) must migrate. | +| 4 | `6d2a9f8e15` | Slider flicker fix | Only after #3 lands. | +| 5 | `3d34658d0f` | **M3 top tab indicators** | New `TopTabBar` component; replaces `SettingsReaderScreen/components/TabBar.tsx`; applies to 6 screens incl. ReaderBottomSheet. | +| 6 | `17c891e133` | **Standardize bottom sheet UX/styling** | BottomSheet component + `surfaceContainerLow` in `useTheme` + layout util; touches ReaderBottomSheet, NovelBottomSheet, LibraryBottomSheet, FilterBottomSheet, TrackSheet. Fork's useTheme needs the new surface color keys. | +| 7 | `e9f6bdaa20` | **Menu styling + outside-tap dismissal** | Menu component overhaul; fork has its own `src/components/Menu/index.tsx` used by reader/Webview appbars — portable pattern. | + +## 3. Optional (2 commits) + +| Commit | Feature | Notes | +|---|---|---| +| `098782d6aa` | Simplify settings layout + theme selection | Removes `react-native-theme-switch-animation` dep — verify fork's dependency graph before removing | +| `99c31d56bb` | MD3 outline variant for tab borders | Cosmetic across 6 screens incl. ReaderBottomSheet (fork: ReaderBottomSheetV2/ReaderTTSTab) | + +## 4. Risks + +| Risk | Mitigation | +|---|---| +| Theme refactor touches `useTheme` used everywhere | Land as standalone PR; snapshot screenshots before/after | +| Dynamic colors may conflict with fork's `uiScale`/computed color system | Keep fork's scaling; dynamic palette only feeds existing color roles | +| MD3 Slider removal of community slider ripples to reader value sliders | Migrate all consumers in same PR; check ReaderTTSTab (fork-custom) | +| `surfaceContainerLow` AND `surfaceContainerHigh` new color keys must exist in ALL fork themes (incl. TTS/reader themes). Verified: fork `ThemeColors` (`src/theme/types/index.ts:39-41`) + `addComputedColors` (`useTheme.ts:22-27`) contain NEITHER key | Add BOTH keys to the type + computed-colors function + all 10 `src/theme/md3/` variants in same PR | + +## 5. Implementation order + +1. `8f47e8fd1e` theme refactor → run full test suite (themes touch SettingsAppearanceScreen, useTheme, ThemePicker) +2. `44c8e54ed8` dynamic colors → manual QA on Android 12+ (wallpaper change → app colors follow) +3. `0c8546188e` + `6d2a9f8e15` slider train +4. `3d34658d`, `17c891e1`, `e9f6bdaa` component polish +5. Optional: `098782d6`, `99c31d56` + +Work on a branch: `merge/original-sync-batch-c`. + +## 6. Testing & validation + +```bash +pnpm run type-check && pnpm run lint:fix && pnpm run format +pnpm run test +pnpm run test:tts-wake-cycle # safety gate (ReaderBottomSheet/ReaderTTSTab touched) +``` + +Manual QA: +- Theme switcher: light/dark/amoled + per-app themes still function; TTS sheet readable in all +- Dynamic colors: enable on Android 12+, change wallpaper, confirm palette updates without app restart crash +- Slider: text size slider in reader + TTS tab; no flicker on release +- Bottom sheet standard: open/close each sheet; no layout jump + +## 7. Acceptance criteria + +- [ ] Theme switcher refactor landed with zero visual regressions (screenshot comparison) +- [ ] Dynamic Material You colors work on Android 12+ with toggle in Settings → Appearance +- [ ] MD3 slider replaces community slider app-wide; `@react-native-community/slider` removed from package.json +- [ ] Top tab indicators + bottom sheet standardization applied to all 6 target screens +- [ ] 1235+ tests green; TTS suites green + +## 8. Out of scope + +- Full M3 dialog standardization (`dfd620aa`, 51 files — deferred: low value-per-conflict) +- Any reader/TTS engine change +- Batch D rewrites diff --git a/specs/upstream-merge-analysis-2026-08/batches/PRD-D-project-scale-features.md b/specs/upstream-merge-analysis-2026-08/batches/PRD-D-project-scale-features.md new file mode 100644 index 0000000000..ffbd2d4eff --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/batches/PRD-D-project-scale-features.md @@ -0,0 +1,99 @@ +# PRD — Batch D: Project-Scale Features (Separate PRs) + +**Status**: 📋 PLANNED (analysis complete; no code changes yet) +**Date**: 2026-08-03 +**Source**: `specs/upstream-merge-analysis-2026-08/analysis.md` · Manifest batch: D (5) + D-stretch (1) + D-optional (10) +**Estimated effort**: each core item is its own multi-day PR + +--- + +## 1. Objective + +The highest-value upstream features that **cannot be cherry-picked** — every one depends on upstream-only infrastructure (Drizzle, zustand store, Nitro, WorkManager) or rewrites fork-critical files. Each must be **reimplemented on the fork's architecture** (raw-SQL + custom TTS + expo-sqlite) as an independent PR with its own test plan. + +## 2. Core projects (5) + +### D1. Time Tracking & Statistics — `89c43c1eef` (+ toggle `4a4208e336`) +- **User value**: reading-time stats per chapter/novel, sessions, statistics screen. Fork's StatsScreen covers library counts only — no `timeSpent`/session anywhere. +- **Reimplementation**: 1 new table via fork's `MigrationRunner` (raw SQL), extended raw-SQL `StatsQueries`, standalone `useTimeTracking` hook (~59 lines, portable from upstream), wiring into `useChapter`/`WebViewReader` (fork-critical — careful). +- **Order**: after `89c43c1e` lands, add `4a4208e3` (toggle + inactivity timeout). +- **Risks**: WebViewReader wiring interacts with TTS paragraph events — run full TTS suites. + +### D2. In-Chapter Search — `71da09ed44` (#1877) +- **User value**: best reader feature in the whole upstream set (word search inside current chapter). +- **Reimplementation**: upstream `search.js` (442 lines) + `ReaderSearchbar` (246) must be re-adapted against fork's diverged `core.js` (bionic reading, spacing rebuild, custom TTS speak/normalize) and its DOM rebuild lifecycle. ReaderScreen/ReaderAppbar/WebViewReader/useChapter wiring. +- **Risks**: HIGH — DOM index contract with TTS paragraph highlighting must stay intact; expect a day+ of work + TTS regression testing. + +### D3. Stats Overhaul: Genre Taxonomy + Charts — `96fb52df17` (#1919) +- **User value**: StatsScreen tabs (Overview/Time/Plugins), SVG donut charts, genre taxonomy screen with per-genre carousels (+2939/−719, 31 files). +- **Reimplementation**: needs new dep `react-native-svg` (fork lacks it); upstream query half is Drizzle → rewrite `StatsQueries.ts` in raw SQL. UI components are new files (low conflict). +- **Prerequisite**: D1 time-tracking provides the Time tab data. + +### D4. Reader Perf Sprint — `3d102a52a7` (#1899) +- **User value**: faster chapter open, no re-render stutter while reading, scroll restore ~100ms vs ~1.5s. +- **Strategy**: do NOT port code (touches every fork-critical reader file + upstream store/asset layout). Port **techniques** as a manual perf sprint on fork's `WebViewReader.tsx`/`useChapter.ts`/`core.js`: + - Defer adjacent-chapter resolution + - Cache sanitized HTML + - Lazy-mount drawer + settings sheet + - Memoized drawer rows + - Single coalesced scroll listener + - One mark-as-read per chapter +- **Risks**: HIGH conflict surface by nature; run TTS wake-cycle/refill after every sub-change. + +### D5. Scheduled Library Updates — `164f5d2592` (D-stretch) +- **User value**: automatic library updates at 12/24/48/72/168h intervals (fork has only `updateLibraryOnLaunch`). +- **Reimplementation**: upstream uses Expo-module `native-background-tasks`/WorkManager — fork would add a native scheduler (AlarmManager/WorkManager) OR adapt fork's ServiceManager/headless task; plus `automaticLibraryUpdateIntervalHours` in `AppSettings` + `SettingsLibraryScreen` dialog. +- **Risks**: native scheduling + battery/Doze behavior; moderate-to-high effort. + +## 3. Optional projects (10, lower priority) + +| Commit | Project | Note | +|---|---|---| +| `198b7443ae` | RTL language support (#1717) | 44 files incl. WebViewReader (84-line diff) — dedicated effort for Arabic/Urdu/Hebrew users | +| `a16ec87631` | Novel covers/lists/chapter-group perf | expo-image `NovelCoverImage`, virtualized `NovelChapterGroup` lists — large manual port + perf regression testing | +| `9805da63cf` | Novel screen perf/architecture | Re-implement perf goals (skeletons, memoized rows, lazy modals) on fork's useNovel stack | +| `5e62c36a9b` | Library settings + category-based global updates | Completes fork's TODO-stubbed `DefaultCategoryDialog` (SettingsLibraryScreen:18); Drizzle→SQL rewrite | +| `bd527a02ef` | Backup selection / plugin restore overhaul | +2021/−249, 31 files; port only selection/options/plugin-restore slices | +| `98a2a1d725` | Scanlator filtering (#1864) | Needs schema migration + plugin-pipeline change to populate column — only useful for aggregator sources. Do last, if at all | +| `2f45db659b` | Async DB ops (SQLITE_BUSY) | Fork-side incremental effort across queries/useNovel/library/downloadChapter/migrateNovel | +| `29bd6612e9` | Library screen perf + category index bug | Re-implement on fork's LibraryScreen/useLibrary | +| `4c7684a3a7` | Novel screen perf + download-state tracking | Port selectively (updateChapter state bug, Set-based tracking) | +| `11ff69d4f5` | Pre-fetch adjacent page chapters | Port only the LibraryUpdateQueries preload half; reader half touches fork-critical useChapter | + +## 4. Common risks + +- **Every D project touches fork-critical files** (WebViewReader, useChapter, useSettings, database, core.js). Each must be its own branch + PR with dedicated regression gates. +- **Drizzle→SQL translation** is required for all query work — fork's `MigrationRunner` + raw-SQL layer is the target. +- **TTS regression** is the #1 hazard (D1, D2, D4) — the TTS wake-cycle/refill suites are mandatory gates. + +## 5. Suggested sequencing + +1. D4 (perf sprint) — improves baseline for everything else; sub-item sized +2. D1 (time tracking) → D3 (stats overhaul) — data → presentation +3. D2 (in-chapter search) — biggest user win, heaviest risk +4. D5 (scheduled updates) — native scheduling as its own project +5. Optional projects — only after core D lands and `dev` is stable + +## 6. Testing & validation (per project) + +```bash +pnpm run type-check && pnpm run lint:fix && pnpm run format +pnpm run test +pnpm run test:tts-wake-cycle && pnpm run test:tts-refill # D1/D2/D4 mandatory +``` + +Manual QA per project (per acceptance criteria defined in each PR's own spec). + +## 7. Acceptance criteria (per project) + +- [ ] Feature fully functional on fork architecture (raw SQL, custom TTS, MMKV settings) +- [ ] Zero regressions in existing fork features (TTS, DoH, backup, per-novel settings) +- [ ] 1235+ tests green + TTS suites green +- [ ] Performance metrics measured before/after (for D3/D4) +- [ ] `pnpm run format` applied before commit + +## 8. Out of scope + +- Porting upstream's zustand store / Nitro / WorkManager architecture itself +- Batch A–C work (tracked in their PRDs) +- Any upstream commit marked SKIP in `commit-manifest.csv` diff --git a/specs/upstream-merge-analysis-2026-08/commit-manifest.csv b/specs/upstream-merge-analysis-2026-08/commit-manifest.csv new file mode 100644 index 0000000000..c98c91493a --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/commit-manifest.csv @@ -0,0 +1,171 @@ +hash,short,date,author,subject,action,batch,value,risk,port,fork-overlap-files,notes +c3260e8e01e4704b57137d787ffe9cfec7941cbc,c3260e8e01,2026-08-01,Rajarshee Chatterjee,fix: resolve chapter download queue and removal issues (closes #1937),PORT,A3,HIGH,YELLOW,MANUAL,,Chapter download queue/removal. Fork has live bugs: deleteReadChaptersFromDb passes novelId twice (wrong folder); deleteDownloads UPDATE without WHERE wipes all flags. +c0877a9b0656e22c3c788854006fb456c3ed704a,c0877a9b06,2026-08-01,Rajarshee Chatterjee,feat: add ignorable app update notifications,PORT,B,HIGH,YELLOW,MANUAL,,Ignorable app update notifications (ignore-version persistence in useGithubUpdateChecker-like hook). +96fb52df1730912c40ee6918cf6a6fcd74e31cc8,96fb52df17,2026-08-01,Patrick Loser,feat: enhance stats with genre taxonomy and charts (#1919),PORT,D,HIGH,RED,MANUAL-rewrite,,Stats overhaul: genre taxonomy + SVG charts (#1919) - needs react-native-svg + raw-SQL stats rewrite. +76106317b68e8aade12b868afb16dda4671ceb16,76106317b6,2026-07-31,Rajarshee Chatterjee,fix: restore midnight dusk chip contrast (closes #1939),PORT,A1,MEDIUM,GREEN,DIRECT,,Midnight Dusk chip contrast (1-line SelectableChip). +e75f4fba5da08e2317cfed2fe92b8de09457475c,e75f4fba5d,2026-07-29,K1ngfish3r,feat: add more novel statuses and icons (#1931),PORT,A3,MEDIUM,YELLOW,MANUAL,"src/plugins/types/index.ts,src/screens/novel/components/Info/NovelInfoHeader.tsx",More novel statuses + icons (NovelStatus enum + getStatusIcon; skip .husky hunk). +4dc933ee5a361eb0535120fa192c1eb51ba8cc70,4dc933ee5a,2026-07-29,Rajarshee Chatterjee,fix: remove white flash during screen transitions (closes #1926) (#1927),PORT,B-stretch,MEDIUM,YELLOW,MANUAL,App.tsx,Remove white flash during screen transitions; replace config-plugin hunk with native res windowBackground change. +bfb12b04aa31e3013f50503697253b549ee9618c,bfb12b04aa,2026-07-29,Rajarshee Chatterjee,feat: add configurable automatic backup scheduling,SKIP,SKIP-NATIVE,-,-,-,"src/hooks/persisted/useSettings.ts,src/screens/settings/SettingsBackupScreen/index.tsx",Automatic backup scheduling needs upstream BackgroundTaskScheduler/WorkManager + expo native-file changes - fork would need from-scratch reimplementation on background-actions. +b7639ab55a308c55617f0088c193052b9e0bd2b2,b7639ab55a,2026-07-29,Patrick Loser,feat: upload javascript source maps (#1924),SKIP,SKIP-CI,-,-,-,src/components/AppErrorBoundary/AppErrorBoundary.tsx,Dev-tooling/CI. +ecc5b84a64ac1aa841583acb43d7ac8aab5a72c5,ecc5b84a64,2026-07-29,github-actions[bot],chore: release v2.1.2,SKIP,SKIP-RELEASE,-,-,-,package.json,Version bump; fork has own numbering (v2.1.3). +eb12bdfd623bd28c2c3f88f585fefca9352711a4,eb12bdfd62,2026-07-29,Rajarshee Chatterjee,fix: prevent novel genre rendering crashes,PORT,A3,HIGH,YELLOW,MANUAL,"src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx","Novel genre rendering crash (parseGenres helper; fork has same split("","") pattern)." +9612bf6f893d0078c566f9651405097b7f8d1f24,9612bf6f89,2026-07-29,Rajarshee Chatterjee,chore: update translations (#1928),SKIP,SKIP-I18N,-,-,-,,Crowdin sync; fork strings at strings/languages with custom keys - manual per-key diff only. +0ed7d878b4b75e1f6ebc83d0978f05cad5920014,0ed7d878b4,2026-07-29,Rajarshee Chatterjee,fix: prevent white reader drawer seam,PORT,A1,LOW,YELLOW,MANUAL-trivial,src/screens/reader/ReaderScreen.tsx,White reader drawer seam; tiny hunk in fork-critical ReaderScreen. +5f45e4c7f2d4f77da3b73bdfdfcdb5b477fd6849,5f45e4c7f2,2026-07-28,Rajarshee Chatterjee,fix: recover interrupted chapter migrations,SKIP,SKIP-DRIZZLE,-,-,-,src/database/db.ts,Drizzle migration SQL; fork MigrationRunner already covers. +ca24671a2f847c5722009b6b8451e9c54d3a2c12,ca24671a2f,2026-07-28,Rajarshee Chatterjee,fix: support fork-safe preview builds,SKIP,SKIP-CI,-,-,-,,CI/preview flavor infra (fork has no preview variant). +ee3a4f20bc41ccf9e3f33ce37e7191cd5c7e6a17,ee3a4f20bc,2026-07-28,Rajarshee Chatterjee,fix: preserve default category after reordering (closes #1922),PORT,A2,HIGH,YELLOW,MANUAL,"src/database/queries/__tests__/CategoryQueries.test.ts,src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/CategoryQueries.ts,src/database/queries/NovelQueries.ts,src/screens/Categories/CategoriesScreen.tsx",Preserve default category after reorder (built-in IDs + positional sort); fork uses WHERE sort=1 in 4 places. +0c3542836c847c0e824e473c06394bd5219d6329,0c3542836c,2026-07-28,Rajarshee Chatterjee,fix: synchronize library updates and optimize import refreshes,PORT,A3,MEDIUM,YELLOW,MANUAL,"src/database/queries/ChapterQueries.ts,src/screens/library/hooks/useLibrary.ts",Library sync + import refresh (julianday trigger + lastUpdatedAt sort; fork triggers identical pre-fix). +f71c9c859471968684baef6e7f3da55a9d2f5821,f71c9c8594,2026-07-28,Patrick Loser,fix: ignore discord notifications outside the main repository (#1923),SKIP,SKIP-CI,-,-,-,,CI/notification infra. +f53546cc01d76b8b3f73ed4a3b88b374f775d746,f53546cc01,2026-07-28,github-actions[bot],chore: release v2.1.1,SKIP,SKIP-RELEASE,-,-,-,package.json,Version bump; fork has own numbering (v2.1.3). +594b078fad3308c284b3b9cd8484056b40d3c821,594b078fad,2026-07-28,Rajarshee Chatterjee,fix: harden database migration recovery,SKIP,SKIP-DRIZZLE,-,-,-,src/database/db.ts,Drizzle migration SQL; fork MigrationRunner already covers. +ac1b2f5ce446ae90db315be11d1494bc49539f40,ac1b2f5ce4,2026-07-27,Rajarshee Chatterjee,feat: add durable completion notifications,SKIP,SKIP-NATIVE,-,-,-,,Durable completion notifications built on upstream native-background-tasks/WorkManager - fork has no such infra. +648d21062f8812ae2a8526954a374424c0dede3c,648d21062f,2026-07-27,Rajarshee Chatterjee,feat: add parallel task queues with per-plugin download limits,SKIP,SKIP-NATIVE,-,-,-,src/services/migrate/migrateNovel.ts,Parallel task queues + per-plugin download limits depend on upstream BackgroundTaskDao/scheduler - would need reimplementation on ServiceManager queue. +89d715ce6bc22ea15885ff86b3359033f3253b4c,89d715ce6b,2026-07-27,Rajarshee Chatterjee,docs: replace beta badge with build preview status,SKIP,SKIP-DOCS,-,-,-,README.md,Documentation only. +bd527a02efa9d642b7168e432bfb965a0ae85f51,bd527a02ef,2026-07-27,Rajarshee Chatterjee,"feat: improve backup selection, notifications, and plugin restore",PORT,D-optional,HIGH,RED,MANUAL,"src/components/Checkbox/Checkbox.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/Components/SelfHostModal.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/services/backup/local/index.ts,src/services/backup/types.ts,src/services/backup/utils.ts","Backup selection/notifications/plugin-restore overhaul (+2021/-249, 31 files). Port only selection/options/plugin-restore slices as dedicated effort." +11117c1b5404820868683522242296d4f61d7642,11117c1b54,2026-07-27,Rajarshee Chatterjee,feat: add github-style embed for nightly notifications,SKIP,SKIP-CI,-,-,-,,GitHub-style embed for nightly notifications - CI workflow only. +ba25d5b8fe3af0b006ad09293a499b58cbee1479,ba25d5b8fe,2026-07-27,Patrick Loser,feat: add rozenite devtools integration (#1912),SKIP,SKIP-INFRA,-,-,-,"App.tsx,package.json,pnpm-lock.yaml",op-sqlite adapter + metro config; fork uses expo-sqlite. +31cb4b994a332bd6f99864ba792aaa988bf4720b,31cb4b994a,2026-07-27,Rajarshee Chatterjee,fix: align bottom navigation with material 3 specs,PORT,A1,LOW,YELLOW,MANUAL-trivial,src/components/BottomTabBar/index.tsx,Bottom nav M3 padding. Fork BottomTabBar rewritten (getStyles + scaled padding.xs) so upstream hunk fails git apply --check - manual port (review 2026-08-04). +72ef618af1bdb6a40783ab119649e302a89e12d8,72ef618af1,2026-07-27,Rajarshee Chatterjee,fix: preserve existing data during novel counter migration,SKIP,SKIP-DRIZZLE,-,-,-,,Drizzle migration SQL; fork MigrationRunner already covers. +936fd223bbde71c3e0496f22ea4f28c9714d7987,936fd223bb,2026-07-27,Rajarshee Chatterjee,feat: add discord notifications for nightly builds,SKIP,SKIP-CI,-,-,-,,CI/discord notification infra. +6d26438d2dd5b7faf21786bc390ae83b43aca6d5,6d26438d2d,2026-07-27,Rajarshee Chatterjee,test: update expected database migrations,SKIP,SKIP-DRIZZLE,-,-,-,AGENTS.md,Drizzle migration SQL; fork MigrationRunner already covers. +5eb5a55c4c654c046f984f1e324b36587389dfa8,5eb5a55c4c,2026-07-27,Rajarshee Chatterjee,fix: repair skipped novel counter migrations,SKIP,SKIP-DRIZZLE,-,-,-,,Drizzle migration SQL; fork MigrationRunner already covers. +6cd86d1422de47dc8e08c53e645c678b39ed66cc,6cd86d1422,2026-07-27,Rajarshee Chatterjee,chore: update translations (#1906),SKIP,SKIP-I18N,-,-,-,,Crowdin sync; fork strings at strings/languages with custom keys - manual per-key diff only. +9783c4d5e3a0f7e5736d1fa50a8dd360a760f76c,9783c4d5e3,2026-07-26,Patrick Loser,fix: normalize repository back navigation (#1905),PORT,A1,LOW,YELLOW,MANUAL-trivial,src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx,Reclassified PORT-A1 per review 2026-08-04: small safe deletion (remove useBackHandler + popToTop); group1 subagent had recommended porting; fork carries identical pre-fix code. +87f602a1fdaaf4456b1947a6097b5a7e65caa1a3,87f602a1fd,2026-07-26,github-actions[bot],chore: release v2.1.0,SKIP,SKIP-RELEASE,-,-,-,package.json,Version bump; fork has own numbering (v2.1.3). +3d32ddd6e933d5b5208f7813b99b8755e700d483,3d32ddd6e9,2026-07-26,Rajarshee Chatterjee,fix: improve release selection and apk verification,SKIP,SKIP-RELEASE,-,-,-,,Version bump; fork has own numbering (v2.1.3). +7ddd934c2b834ba8fbed6969dab89728e3316251,7ddd934c2b,2026-07-26,Rajarshee Chatterjee,fix: handle missing tracker client ids,PORT,A3,MEDIUM,YELLOW,MANUAL,,Handle missing tracker client IDs (guard clauses in aniList.ts/myAnimeList.ts only). +b53c91f748581227d63a28d28d33de87c1cbb296,b53c91f748,2026-07-26,Rajarshee Chatterjee,chore: separate jest and type check workflows,SKIP,SKIP-CI,-,-,-,package.json,Fork has separate CI. +9d106a371620a513f1f4cf57f511936866a77fd2,9d106a3716,2026-07-26,Rajarshee Chatterjee,feat: add semantic version bump selection,SKIP,SKIP-CI,-,-,-,,Release automation. +8b0a8dc4c07b6d77532ef9b070bbce288afd92d4,8b0a8dc4c0,2026-07-26,Patrick Loser,chore: update husky pre-commit and pre-push hooks,SKIP,SKIP-INFRA,-,-,-,package.json,Fork has own hooks; bundled volume-module change conflicts. +72bfcad03469440c1e9cf5ff88b5ff97a544c45e,72bfcad034,2026-07-26,Patrick Loser,fix: resolve type and lint issues,SKIP,SKIP-OTHER,-,-,-,"eslint.config.js,src/components/ErrorView/ErrorView.tsx,src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/__tests__/StatsQueries.test.ts,src/hooks/common/useGithubUpdateChecker.ts,src/navigators/types/index.ts,src/screens/browse/discover/AniListTopNovels.tsx,src/screens/browse/discover/MalTopNovels.tsx,src/screens/browse/migration/MigrationSourceItem.tsx,src/screens/browse/SourceNovels.tsx,src/screens/BrowseSourceScreen/useBrowseSource.ts,src/screens/GlobalSearchScreen/GlobalSearchScreen.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/more/About.tsx,src/screens/more/components/MoreHeader.tsx,src/screens/more/MoreScreen.tsx,src/screens/more/TaskQueueScreen.tsx,src/screens/reader/ChapterContext.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,src/screens/settings/SettingsRepositoryScreen/components/RepositoryCard.tsx,src/screens/settings/SettingsTrackerScreen.tsx,src/screens/WebviewScreen/components/Appbar.tsx,src/screens/WebviewScreen/components/Menu.tsx,src/screens/WebviewScreen/WebviewScreen.tsx","TRAP: lint/type cleanup touching WebViewReader/ReaderScreen/ChapterContext/useLibrary - zero value, maximal conflict surface. NEVER cherry-pick." +345d084eba5f1862c37d15c3790ce36610116d70,345d084eba,2026-07-26,Rajarshee Chatterjee,feat: add chapter numbers to epub titles (closes #1405),PORT,B,HIGH,YELLOW,MANUAL,"src/hooks/persisted/useSettings.ts,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx",Chapter numbers in EPUB titles (setting + title composition; adapt to @cd-z/react-native-epub-creator). +10b6042a9c671f47ba33f7810c47935a54706f40,10b6042a9c,2026-07-26,Rajarshee Chatterjee,feat: add epub export progress notifications,SKIP,SKIP-INFRA,-,-,-,"src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx",Expo infra commit. +bd7161172551568e579e60ece7072f8c5a05fa65,bd71611725,2026-07-26,Rajarshee Chatterjee,feat: improve chapter drawer and webview app bar,SKIP,SKIP-OTHER,-,-,-,"src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/ReaderScreen.tsx,src/screens/WebviewScreen/components/Appbar.tsx",No clear user value / overlaps fork-critical files or infra. See subagent report. +8f53550d2c6ec75b8d9cefe0142218aaec26f726,8f53550d2c,2026-07-26,Rajarshee Chatterjee,fix: restore tracker search requests,PORT,A3,HIGH,YELLOW,MANUAL,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,Restore tracker search requests (scheduleSearch bypass + stale searchText; fork shares broken flow). +52914b978df3267342b9cd3d18270bf19ae5a7cb,52914b978d,2026-07-26,Rajarshee Chatterjee,fix: migrate volume button events to expo listener api,SKIP,ALREADY-HAVE,-,-,-,src/screens/reader/hooks/useChapter.ts,Fork has own volume-button listener wiring. +ff12759a66ad68791d7b8270579b8f7d020e73f5,ff12759a66,2026-07-26,Rajarshee Chatterjee,ci: run preview builds daily when missing,SKIP,SKIP-CI,-,-,-,,CI/preview flavor infra (fork has no preview variant). +e62868b82ccd1784ad83cd00d338c47d2818df94,e62868b82c,2026-07-26,Rajarshee Chatterjee,feat: automate draft android releases,SKIP,SKIP-RELEASE,-,-,-,".gitignore,package.json,src/hooks/common/useGithubUpdateChecker.ts",Version bump; fork has own numbering (v2.1.3). +97be48dfe17b6a5261284cc3052ab5dcbab8d20d,97be48dfe1,2026-07-26,Rajarshee Chatterjee,feat: add migration review options,PORT,B-stretch,MEDIUM,YELLOW,MANUAL,"AGENTS.md,src/screens/browse/migration/MigrationNovelList.tsx,src/services/migrate/migrateNovel.ts",Migration review options (cover/metadata/redownload + review dialog). +cbe7d70e2df349db7ebf36a448e1f84b3903fb78,cbe7d70e2d,2026-07-26,Rajarshee Chatterjee,fix: restore collapsed tts button interaction,SKIP,ALREADY-HAVE,-,-,-,,Fork index.js already guards TTS controller element. +6548e9a6f8efc0bb21e46af005072d7599b326e9,6548e9a6f8,2026-07-26,Rajarshee Chatterjee,fix: improve paged reader layout and performance,SKIP,SKIP-OTHER,-,-,-,src/screens/reader/ReaderScreen.tsx,No clear user value / overlaps fork-critical files or infra. See subagent report. +138fdff2e57e55e02f9775d11dfb47482701f5c5,138fdff2e5,2026-07-26,Rajarshee Chatterjee,fix: prevent duplicate webview bottom insets,PORT,B-stretch,MEDIUM,YELLOW,MANUAL,src/screens/WebviewScreen/WebviewScreen.tsx,Prevent duplicate WebView bottom insets (fork likely same bug). +f1dd4b77a891e501b9345e5d0fe81b6405bce450,f1dd4b77a8,2026-07-26,Rajarshee Chatterjee,feat: improve empty chapter reporting,SKIP,SKIP-OTHER,-,-,-,"src/screens/reader/components/WebViewReader.tsx,src/screens/reader/utils/sanitizeChapterText.ts",No clear user value / overlaps fork-critical files or infra. See subagent report. +9b356ffd17c28d7f37285157f3f34488af67a266,9b356ffd17,2026-07-26,Rajarshee Chatterjee,fix: preserve client ids and improve environment generation,SKIP,SKIP-OTHER,-,-,-,,No clear user value / overlaps fork-critical files or infra. See subagent report. +5cf7e15d7631f3ada56d4cb979694214745bd10d,5cf7e15d76,2026-07-26,Rajarshee Chatterjee,feat: add reader setting descriptions,SKIP,SKIP-OTHER,-,-,-,"src/screens/settings/SettingsReaderScreen/tabs/AccessibilityTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/NavigationTab.tsx",No clear user value / overlaps fork-critical files or infra. See subagent report. +6a7e90c179b735521aa9d778e68f1727607e8ac5,6a7e90c179,2026-07-26,Rajarshee Chatterjee,fix: align advanced reader tabs and localize actions,SKIP,SKIP-OTHER,-,-,-,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,No clear user value / overlaps fork-critical files or infra. See subagent report. +99c31d56bb45c6e04fe96cff3f14c3ca55fccba4,99c31d56bb,2026-07-26,Rajarshee Chatterjee,fix: use md3 outline variant for tab borders,PORT,C-optional,LOW,YELLOW,MANUAL,"src/screens/browse/BrowseScreen.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx",MD3 outline variant for tab borders. +4d98247980c21f892df6cf0da19ecad8a6f0d136,4d98247980,2026-07-26,Rajarshee Chatterjee,feat: add android dns over https support,SKIP,ALREADY-HAVE,-,-,-,src/screens/settings/SettingsAdvancedScreen.tsx,Fork shipped own DoH (DoHManagerModule.kt) earlier. +164f5d259243a68f14a7e8a069b2e2cfc42aab84,164f5d2592,2026-07-25,Rajarshee Chatterjee,feat: add automatic library update scheduling,PORT,D-stretch,HIGH,YELLOW,MANUAL,src/hooks/persisted/useSettings.ts,Automatic library update scheduling - needs native scheduler on fork ServiceManager. +89cddf9e3d07d3f7a7c7cb71e8cc8e5e4e96fcc3,89cddf9e3d,2026-07-25,Rajarshee Chatterjee,fix: expand discover row ripple,SKIP,SKIP-OTHER,-,-,-,src/screens/browse/discover/DiscoverCard.tsx,No clear user value / overlaps fork-critical files or infra. See subagent report. +eac6ad584e85d4ed72cf76fafcde1c4d0551687d,eac6ad584e,2026-07-25,Rajarshee Chatterjee,feat: improve novel app bar and epub export ux,PORT,B-stretch,MEDIUM,YELLOW,MANUAL,"src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/NovelScreen.tsx",Novel app bar + EPUB export UX polish. +93fe04c28cc4776a96f86aa157b729ddf3c218c3,93fe04c28c,2026-07-25,Rajarshee Chatterjee,feat: polish reader controls and navigation,SKIP,SKIP-OTHER,-,-,-,"src/components/Common/ToggleButton.tsx,src/components/SegmentedControl/SegmentedControl.tsx,src/database/queries/ChapterQueries.ts,src/navigators/MoreStack.tsx,src/screens/browse/discover/DiscoverCard.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/reader/components/ReaderAppbar.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderFontPicker.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderSheetPreferenceItem.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderTextAlignSelector.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderThemeSelector.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderValueChange.tsx,src/screens/reader/components/ReaderBottomSheet/TextSizeSlider.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/ReaderFooter.tsx,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsTrackerScreen.tsx",Broad reader-controls polish incl. Drizzle ChapterQueries limit + every fork-critical file. Only selective sub-fixes portable - see group2 report. +c9358b3a9fc49ad6a98ac0293da46bf9eab39bf8,c9358b3a9f,2026-07-25,Rajarshee Chatterjee,feat: update android branding assets,SKIP,SKIP-CI,-,-,-,,Binary assets; cherry-picks but inert. +67d2c7c01b101d83d0033aad2c0a99247f04620e,67d2c7c01b,2026-07-25,Rajarshee Chatterjee,feat: add collapsible tts controls and stable notification progress,SKIP,ALREADY-HAVE,-,-,-,,Collapsible TTS controls; fork has own TTS UI + stable progress. +c30441b9493d5e75fbe82a608b034c6239fa493f,c30441b949,2026-07-25,Rajarshee Chatterjee,feat: add smart library update filters,PORT,B-stretch,MEDIUM,YELLOW,MANUAL,src/hooks/persisted/useSettings.ts,Smart library update filters (skip read/completed/unstarted). +6d2a9f8e1530604860375a9be3abb92b4502d01b,6d2a9f8e15,2026-07-25,Rajarshee Chatterjee,fix: prevent slider flicker after release,PORT,C,LOW,GREEN,MANUAL,,Slider flicker fix - only after MD3 Slider ported. +799845426c8f2e651de508d471d8319add41d2cc,799845426c,2026-07-25,Rajarshee Chatterjee,feat: add parallel library updates across sources,PORT,B,MEDIUM,YELLOW,MANUAL,,Parallel library updates (group by pluginId + Promise.all concurrency 3; fork updates/index.ts is sequential sleep(1000)). +8ac628b3deda367f68af93f9f43d7a1da8c80f9d,8ac628b3de,2026-07-25,Rajarshee Chatterjee,fix: improve history removal flow.,PORT,B-stretch,MEDIUM,YELLOW,MANUAL,"src/database/queries/__tests__/HistoryQueries.test.ts,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/history/HistoryScreen.tsx",Per-novel history removal + confirm dialog. +d02291c22c079e5faee9817b00c7bd87ed7f47da,d02291c22c,2026-07-25,Rajarshee Chatterjee,feat: remember last used library category,PORT,B-stretch,MEDIUM,YELLOW,MANUAL,"src/hooks/persisted/useSettings.ts,src/screens/library/LibraryScreen.tsx",Remember last used library category. +7af0911f5b17e1c606dbf60e0f1d1b8863c78221,7af0911f5b,2026-07-25,Rajarshee Chatterjee,feat: add high-resolution android flavor assets,SKIP,SKIP-CI,-,-,-,,Binary flavor assets. +098782d6aa9a188bf007402155bd66e3dbc14523,098782d6aa,2026-07-25,Rajarshee Chatterjee,refactor: simplify settings layout and theme selection,PORT,C-optional,LOW,YELLOW,MANUAL,"package.json,pnpm-lock.yaml,src/components/List/List.tsx,src/components/ThemePicker/ThemePicker.tsx,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,src/screens/settings/SettingsTrackerScreen.tsx",Simplify settings layout + theme selection (removes react-native-theme-switch-animation). +732628c40bb3f1afbbb0336577f9a56f97ef9115,732628c40b,2026-07-25,Rajarshee Chatterjee,feat: add date format and relative timestamp settings.,PORT,B-stretch,MEDIUM,YELLOW,MANUAL,"src/hooks/persisted/useSettings.ts,src/screens/history/HistoryScreen.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/updates/UpdatesScreen.tsx",Date format + relative timestamp settings. +5e62c36a9b0c0d7ca562383f2d9938e722216f00,5e62c36a9b,2026-07-25,Rajarshee Chatterjee,feat: add library settings and category-based global updates,PORT,D-optional,MEDIUM,RED,MANUAL-rewrite,"src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/NovelQueries.ts,src/hooks/persisted/useSettings.ts,src/navigators/MoreStack.tsx,src/navigators/types/index.ts,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,src/screens/settings/SettingsLibraryScreen/DefaultCategoryDialog.tsx,src/screens/settings/SettingsScreen.tsx",Library settings + category-based global updates; completes fork TODO-stubbed DefaultCategoryDialog but needs Drizzle->SQL rewrite. +3d102a52a7d780b096afd78c6d5214e46b670b47,3d102a52a7,2026-07-25,Rajarshee Chatterjee,perf(reader): cut chapter open latency and reading-time re-renders (#1899),PORT,D,HIGH,RED,MANUAL-rewrite,"src/hooks/persisted/useSettings.ts,src/screens/reader/ChapterContext.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/reader/components/ReaderFooter.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/reader/utils/sanitizeChapterText.ts,src/screens/WebviewScreen/WebviewScreen.tsx","Reader perf sprint (#1899) - port TECHNIQUES (deferred fetch, HTML cache, lazy mount, coalesced scroll) not code." +4dcf21303715793087ea3bd49a9d510943e30944,4dcf213037,2026-07-25,Rajarshee Chatterjee,fix: restore reader changes reverted during expo migration,SKIP,SKIP-OTHER,-,-,-,,Restores upstream assets/reader/js/search.js (442 lines) - fork has no in-reader search; feature = 71da09ed. Skip. +4de82472ee70fd0c36114f0ee2e213ff6bf81099,4de82472ee,2026-07-25,Rajarshee Chatterjee,fix: resolve require cycles and legendlist render updates,SKIP,SKIP-DEP,-,-,-,"pnpm-lock.yaml,pnpm-workspace.yaml,src/components/SearchbarV2/SearchbarV2.tsx,src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx,src/services/backup/utils.ts",LegendList v3 patch + Drizzle manager; version mismatch. +58db0352e6359d4b1c69391d260105abbe4ede60,58db0352e6,2026-07-25,Rajarshee Chatterjee,fix: restore metro reader asset middleware,SKIP,ALREADY-HAVE,-,-,-,,Fork metro.config.cjs already serves reader assets. +74808f086e5e3fbd479138681d700814c3370386,74808f086e,2026-07-24,Rajarshee Chatterjee,feat: replace legacy media controls with nitro-powered playback (#1896),SKIP,ALREADY-HAVE,-,-,-,"babel.config.js,package.json,src/hooks/persisted/useSettings.ts,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,tsconfig.json",Nitro media controls; fork has own 5-button ttsNotification.ts. +b96351ea8117f4a1e464b0a7ab1820a557172c92,b96351ea81,2026-07-24,amir-rezaei,docs: fix contraction and missing article in `testing.md` (#1895),SKIP,SKIP-DOCS,-,-,-,,Documentation only. +eb8dc6df368ba7628178d6481294fca1b30f9019,eb8dc6df36,2026-07-24,amir-rezaei,"docs: fix ""withoud"" typo in `contributing-nix.md` (#1894)",SKIP,SKIP-DOCS,-,-,-,CONTRIBUTING-NIX.md,Documentation only. +0c8546188eb33a0ac3be46596503cd38e4fb4d75,0c8546188e,2026-07-24,Rajarshee Chatterjee,feat: add md3 slider component.,PORT,C,MEDIUM,YELLOW,MANUAL,"package.json,pnpm-lock.yaml,src/screens/reader/components/ReaderBottomSheet/ReaderValueChange.tsx,src/screens/reader/components/ReaderBottomSheet/TextSizeSlider.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/settings/SettingsReaderScreen/ReaderTextSize.tsx",MD3 Slider component (replaces @react-native-community/slider; coordinate package.json). +e9f6bdaa203890fb7d69b261f31afc431fba429c,e9f6bdaa20,2026-07-24,Rajarshee Chatterjee,fix: modernize menu styling and outside-tap dismissal,PORT,C,MEDIUM,YELLOW,MANUAL,src/components/Menu/index.tsx,Menu outside-tap dismissal modernization. +3d34658d0faee3f1a1dce6ed77f745804851c4c0,3d34658d0f,2026-07-24,Rajarshee Chatterjee,feat: add material 3 top tab indicators,PORT,C,MEDIUM,YELLOW,MANUAL,"src/screens/browse/BrowseScreen.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/settings/SettingsReaderScreen/components/TabBar.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx",M3 top tab indicators (new TopTabBar). +d44ce7412ac512b392d35e33d6dc9d98587a2350,d44ce7412a,2026-07-24,amir-rezaei,"docs: fix trailing period, organization expansion, and comma in `contributing.md` (#1893)",SKIP,SKIP-DOCS,-,-,-,,Documentation only. +17c891e1334409fa29d4a365b9ba46014b81b591,17c891e133,2026-07-24,Rajarshee Chatterjee,refactor: standardize bottom sheet ux and styling,PORT,C,MEDIUM,YELLOW,MANUAL,"src/components/BottomSheet/BottomSheet.tsx,src/hooks/persisted/useTheme.ts,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/PageNavigationBottomSheet.tsx,src/screens/novel/components/Tracker/TrackSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx",Standardize bottom sheet UX/styling (surfaceContainerLow in useTheme). +9805da63cf23d58f0561b9335e0a07c1789e9a82,9805da63cf,2026-07-24,Rajarshee Chatterjee,refactor: optimize novel screen performance and architecture,PORT,D-optional,HIGH,RED,MANUAL-rewrite,"src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/NovelScreenButtonGroup/NovelScreenButtonGroup.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/SetCategoriesModal.tsx,src/screens/novel/components/Tracker/ScoreSelectors.tsx,src/screens/novel/components/Tracker/TrackSheet.tsx,src/screens/novel/NovelScreen.tsx,src/services/migrate/migrateNovel.ts","Novel screen perf/architecture (zustand store upstream) - re-implement perf goals (skeletons, memo, lazy modals) on fork useNovel stack." +afc20277e0f8a8d4b49d1ebd1c7689c599ba2e06,afc20277e0,2026-07-24,Rajarshee Chatterjee,fix: restore android build types for ci,SKIP,SKIP-CI,-,-,-,".gitignore,package.json",rock-CLI CI/build infra. +14061d207b77d8930c4d15af135189ccb4f4bd9e,14061d207b,2026-07-24,amir-rezaei,"docs: fix title case headings, github capitalization, and e.g. formatting in `contributing.md` (#1892)",SKIP,SKIP-DOCS,-,-,-,,Documentation only. +916374b5df3cfa3b09d464109340ad81bdbef457,916374b5df,2026-07-24,Rajarshee Chatterjee,refactor: reorganize repository structure,SKIP,SKIP-INFRA,-,-,-,"babel.config.js,package.json,scripts/generate-string-types.cjs,src/components/ColorPickerModal/ColorPickerModal.tsx,src/components/ConfirmationDialog/ConfirmationDialog.tsx,src/components/NewUpdateDialog.tsx,src/components/NovelCover.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/database/queries/CategoryQueries.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/navigators/BottomNavigator.tsx,src/screens/browse/BrowseScreen.tsx,src/screens/browse/discover/DiscoverCard.tsx,src/screens/browse/migration/Migration.tsx,src/screens/browse/migration/MigrationNovelList.tsx,src/screens/browse/settings/modals/ConcurrentSearchesModal.tsx,src/screens/browse/SourceNovels.tsx,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/Categories/CategoriesScreen.tsx,src/screens/Categories/components/AddCategoryModal.tsx,src/screens/Categories/components/DeleteCategoryModal.tsx,src/screens/GlobalSearchScreen/components/GlobalSearchResultsList.tsx,src/screens/GlobalSearchScreen/GlobalSearchScreen.tsx,src/screens/history/components/ClearHistoryDialog.tsx,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/history/HistoryScreen.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/LibraryScreen.tsx,src/screens/more/About.tsx,src/screens/more/components/RemoveDownloadsDialog.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/more/MoreScreen.tsx,src/screens/more/TaskQueueScreen.tsx,src/screens/novel/components/Chapter/ChapterDownloadButtons.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/DownloadCustomChapterModal.tsx,src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/NovelScreenButtonGroup/NovelScreenButtonGroup.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/NovelSummary/NovelSummary.tsx,src/screens/novel/components/SetCategoriesModal.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/novel/NovelScreen.tsx,src/screens/onboarding/OnboardingScreen.tsx,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderFontPicker.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderTextAlignSelector.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderThemeSelector.tsx,src/screens/reader/components/ReaderBottomSheet/TextSizeSlider.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/reader/utils/sanitizeChapterText.ts,src/screens/settings/components/ConnectionModal.tsx,src/screens/settings/SettingsAdvancedScreen.tsx,src/screens/settings/SettingsAppearanceScreen/LanguagePickerModal.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/Components/SelfHostModal.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/screens/settings/SettingsGeneralScreen/modals/DisplayModeModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/GridSizeModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/NovelBadgesModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/NovelSortModal.tsx,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,src/screens/settings/SettingsLibraryScreen/DefaultCategoryDialog.tsx,src/screens/settings/SettingsReaderScreen/Modals/FontPickerModal.tsx,src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx,src/screens/settings/SettingsReaderScreen/ReaderTextSize.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/tabs/AccessibilityTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/DisplayTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/NavigationTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/ThemeTab.tsx,src/screens/settings/SettingsRepositoryScreen/components/AddRepositoryModal.tsx,src/screens/settings/SettingsRepositoryScreen/components/DeleteRepositoryModal.tsx,src/screens/settings/SettingsRepositoryScreen/components/RepositoryCard.tsx,src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx,src/screens/settings/SettingsScreen.tsx,src/screens/settings/SettingsTrackerScreen.tsx,src/screens/StatsScreen/StatsScreen.tsx,src/screens/updates/UpdatesScreen.tsx,src/screens/WebviewScreen/components/Menu.tsx,src/services/backup/local/index.ts,src/services/backup/utils.ts,tsconfig.json","Pure repo shuffle (strings/ -> src/i18n/); zero value, huge conflicts." +0a2a6c96ca453fd68ce42d4723c0f42c0a730519,0a2a6c96ca,2026-07-24,Rajarshee Chatterjee,feat: add variant-specific android splash screens,SKIP,SKIP-CI,-,-,-,,Flavor-specific assets; fork has no variants. +65e86100304ae27ba8049937558b4e25b49252f6,65e8610030,2026-07-24,Rajarshee Chatterjee,refactor: replace bottom navigation shift with fade,PORT,A1,LOW,GREEN,DIRECT,src/navigators/BottomNavigator.tsx,Bottom nav shift->fade animation (1 line). +1322065290f63141633c30cd3013b7ba81c12fae,1322065290,2026-07-24,Rajarshee Chatterjee,feat: add variant-specific android app icons,SKIP,SKIP-CI,-,-,-,,Flavor-specific assets. +22a1efd1e5c548a27b3ab3b8c54909a5092c1e37,22a1efd1e5,2026-07-24,Rajarshee Chatterjee,perf: optimize react native skeleton loading components,SKIP,SKIP-DEP,-,-,-,"src/components/Skeleton/Skeleton.tsx,src/screens/browse/loadingAnimation/MalLoading.tsx,src/screens/browse/loadingAnimation/TrackerLoading.tsx,src/screens/history/components/HistorySkeletonLoading.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/LoadingAnimation/NovelScreenLoading.tsx,src/screens/updates/components/UpdatesSkeletonLoading.tsx",Reanimated shimmer rewrite; fork has own optimized Skeleton. +269606ebb8e352f72fbab401bd5d93ed15c1a7a2,269606ebb8,2026-07-24,Rajarshee Chatterjee,fix: resolve nitro epub import alias,SKIP,SKIP-NITRO,-,-,-,tsconfig.json,Nitro module infra; fork uses custom Kotlin/JS. NOT-FEASIBLE. +4cb1890b62eb57cc75ca0628a11be312c930ddc4,4cb1890b62,2026-07-24,Rajarshee Chatterjee,feat: add nitro epub exporter,SKIP,ALREADY-HAVE,-,-,-,".gitignore,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx,strings/languages/en/strings.json,strings/types/index.ts",Fork exports EPUB via @cd-z/react-native-epub-creator (JS). +a421177f0cb5b74e30b869c6595ed12c9263586a,a421177f0c,2026-07-24,Rajarshee Chatterjee,fix: improve category badge layout and drag reordering,PORT,A2,MEDIUM,YELLOW,MANUAL,"src/components/IconButtonV2/IconButtonV2.tsx,src/screens/Categories/CategoriesScreen.tsx,src/screens/Categories/components/CategoryCard.tsx",Category drag-reorder keyExtractor fix (fork CategoriesScreen.tsx:94 identical bug). +a16ec876311692a6cb3da9348d09e7cf287c9fb6,a16ec87631,2026-07-24,Rajarshee Chatterjee,"perf: optimize novel covers, lists, and chapter groups",PORT,D-optional,HIGH,RED,MANUAL-rewrite,"package.json,pnpm-lock.yaml,src/components/ErrorScreenV2/ErrorScreenV2.tsx,src/components/ListView.tsx,src/components/NovelCover.tsx,src/components/NovelList.tsx,src/database/queries/ChapterQueries.ts,src/database/types/index.ts,src/plugins/types/index.ts,src/screens/browse/discover/DiscoverNovelCard/index.tsx,src/screens/browse/globalsearch/GlobalSearchNovelCover.tsx,src/screens/GlobalSearchScreen/components/GlobalSearchResultsList.tsx,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/StatsScreen/StatsScreen.tsx,src/screens/updates/components/UpdateNovelCard.tsx,src/screens/updates/UpdatesScreen.tsx","Novel covers/lists/chapter-group perf (expo-image, virtualized lists)." +dfd620aa292fadd3b8798013701ae1bef954fe57,dfd620aa29,2026-07-23,Rajarshee Chatterjee,feat: standardize dialogs with material 3 patterns,SKIP,SKIP-OTHER,-,-,-,"src/components/ColorPickerModal/ColorPickerModal.tsx,src/components/ConfirmationDialog/ConfirmationDialog.tsx,src/components/DialogTitle/DialogTitle.tsx,src/components/NewUpdateDialog.tsx,src/hooks/persisted/useTheme.ts,src/screens/browse/migration/MigrationNovelList.tsx,src/screens/browse/settings/modals/ConcurrentSearchesModal.tsx,src/screens/Categories/components/AddCategoryModal.tsx,src/screens/Categories/components/DeleteCategoryModal.tsx,src/screens/history/components/ClearHistoryDialog.tsx,src/screens/history/HistoryScreen.tsx,src/screens/more/components/RemoveDownloadsDialog.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/novel/components/DownloadCustomChapterModal.tsx,src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/SetCategoriesModal.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/settings/components/ConnectionModal.tsx,src/screens/settings/SettingsAdvancedScreen.tsx,src/screens/settings/SettingsAppearanceScreen/LanguagePickerModal.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/Components/SelfHostModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/DisplayModeModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/GridSizeModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/NovelBadgesModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/NovelSortModal.tsx,src/screens/settings/SettingsLibraryScreen/DefaultCategoryDialog.tsx,src/screens/settings/SettingsReaderScreen/Modals/FontPickerModal.tsx,src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/screens/settings/SettingsRepositoryScreen/components/AddRepositoryModal.tsx,src/screens/settings/SettingsRepositoryScreen/components/DeleteRepositoryModal.tsx,src/screens/settings/SettingsTrackerScreen.tsx",No clear user value / overlaps fork-critical files or infra. See subagent report. +a5a6e8326f72d00271fd01cf8d6374cc35310147,a5a6e8326f,2026-07-23,Rajarshee Chatterjee,chore: update `@legendapp/list` to 3.3.3,SKIP,SKIP-DEP,-,-,-,"package.json,pnpm-lock.yaml,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelDrawer.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/PageNavigationBottomSheet.tsx,src/screens/novel/NovelScreen.tsx,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx",Patches @legendapp/list@3.3.3; fork pins ^2.0.16. +7f48a8ba8299180ffd5b07ba6314960213cd13cd,7f48a8ba82,2026-07-23,Rajarshee Chatterjee,refactor: improve plugins screen ux and code,SKIP,SKIP-OTHER,-,-,-,"src/components/IconButtonV2/IconButtonV2.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/components/Switch/Switch.tsx,src/components/Switch/SwitchItem.tsx,src/hooks/persisted/index.ts,src/navigators/BottomNavigator.tsx,src/navigators/types/index.ts,src/screens/browse/BrowseScreen.tsx,src/screens/browse/components/AvailableTab.tsx,src/screens/browse/components/DeferredPluginListItem.tsx,src/screens/browse/components/InstalledTab.tsx,src/screens/browse/components/Modals/SourceSettings.tsx,src/screens/browse/components/PluginListItem.tsx,src/screens/browse/components/PluginListItemSkeleton.tsx,src/screens/browse/discover/DiscoverCard.tsx,src/screens/browse/migration/Migration.tsx,src/screens/browse/migration/MigrationNovels.tsx,src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts",No clear user value / overlaps fork-critical files or infra. See subagent report. +ae34d7b4ec6be28f7ad5ae25c663c7d2b3db43ef,ae34d7b4ec,2026-07-23,Rajarshee Chatterjee,feat: add dynamic preview apk metadata,SKIP,SKIP-CI,-,-,-,,CI/preview flavor infra (fork has no preview variant). +44c8e54ed80bb47cc49c2461adca66ced94b2d1c,44c8e54ed8,2026-07-23,Rajarshee Chatterjee,feat: add dynamic material theme colors (closes #1863),PORT,C,HIGH,YELLOW,MANUAL,"App.tsx,package.json,pnpm-lock.yaml,src/hooks/persisted/useTheme.ts,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts",Dynamic Material You theme colors (#1863) via @pchmn/expo-material3-theme. +4a4208e33602d65ef9942d05e54a692addd87e2f,4a4208e336,2026-07-23,Ecasept,feat: add time-tracking toggle and inactivity timeout (#1890),PORT,D,MEDIUM,RED,MANUAL-rewrite,"src/hooks/persisted/useSettings.ts,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts",Time-tracking toggle + inactivity timeout (#1890) - after 89c43c1e. +d2385b8dc2a9cd911e7567e55d6b7dc2c43f2372,d2385b8dc2,2026-07-23,CD-Z,update build.yml,SKIP,SKIP-CI,-,-,-,,Fork has separate CI. +9babe1676dd053069d127743a61bc7a8bc11256c,9babe1676d,2026-07-22,Rajarshee Chatterjee,refactor: remove react native background actions manifest customization,SKIP,SKIP-NATIVE,-,-,-,package.json,Native background-task infra (WorkManager/Expo modules); fork uses background-actions + ServiceManager. +8c38f499e4b62de02157aa8dcd1c9207978033bf,8c38f499e4,2026-07-22,Rajarshee Chatterjee,chore: update appstate test setup and `pnpm` configuration,SKIP,SKIP-CI,-,-,-,,Test infra for feature fork lacks. +89c43c1eeffd5a9980b3aaebe647f3d94252f8b8,89c43c1eef,2026-07-22,Ecasept,feat: implement time tracking and statistics (#1886),PORT,D,HIGH,RED,MANUAL-rewrite,"src/database/queries/__tests__/StatsQueries.test.ts,src/database/queries/ChapterQueries.ts,src/database/queries/StatsQueries.ts,src/database/types/index.ts,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/StatsScreen/StatsScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts",Time tracking & statistics (#1886) - rewrite raw-SQL table + hook + screen (fork has no timeSpent). +c3c891cea084796b3640233826dacfc7aab6f8d6,c3c891cea0,2026-07-22,Patrick Loser,refactor: migrate to expo managed workflow (#1885),SKIP,SKIP-INFRA,-,-,-,".gitignore,android/app/build.gradle,android/app/proguard-rules.pro,android/app/src/main/AndroidManifest.xml,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainActivity.kt,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainApplication.kt,android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt,android/app/src/main/res/raw/loading.json,android/build.gradle,android/gradle.properties,android/gradle/wrapper/gradle-wrapper.properties,android/settings.gradle,App.tsx,babel.config.js,eslint.config.js,ios/LNReader/Images.xcassets/AppIcon.appiconset/Contents.json,ios/loading.json,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,src/components/BottomTabBar/index.tsx,src/components/EmptyView/EmptyView.tsx,src/components/ErrorScreenV2/ErrorScreenV2.tsx,src/components/IconButtonV2/IconButtonV2.tsx,src/components/NovelCover.tsx,src/components/NovelList.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/database/queries/CategoryQueries.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/hooks/persisted/useNovel.ts,src/plugins/helpers/fetch.ts,src/screens/browse/BrowseScreen.tsx,src/screens/browse/components/PluginListItem.tsx,src/screens/browse/loadingAnimation/MalLoading.tsx,src/screens/browse/loadingAnimation/TrackerLoading.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/novel/components/ExportNovelAsEpubButton.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/reader/components/ReaderFooter.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/services/backup/local/index.ts,src/services/backup/utils.ts,src/theme/utils/setBarColor.ts,strings/languages/en/strings.json,strings/types/index.ts,tsconfig.json",Fork has separate CI. Managed-workflow migration (40 files) - relabeled SKIP-INFRA per review 2026-08-04. +e4cf64a112dbfd9f9dd853abec99afbdc09f1460,e4cf64a112,2026-07-22,Rajarshee Chatterjee,refactor: migrate background tasks to native android scheduling (#1889),SKIP,SKIP-NATIVE,-,-,-,"android/app/build.gradle,android/app/src/main/AndroidManifest.xml,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainApplication.kt,android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt,App.tsx,package.json,pnpm-lock.yaml,src/database/db.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/hooks/index.ts,src/hooks/persisted/useNovel.ts,src/screens/browse/migration/MigrationNovelList.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx,src/screens/more/MoreScreen.tsx,src/screens/more/TaskQueueScreen.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/Components/SelfHostModal.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/screens/updates/UpdatesScreen.tsx,src/services/backup/local/index.ts,src/services/backup/utils.ts,src/services/migrate/migrateNovel.ts,src/services/ServiceManager.ts",Native background-task infra (WorkManager/Expo modules); fork uses background-actions + ServiceManager. +3e1e211098779264ba123121cc66bb48385fcf77,3e1e211098,2026-07-19,Luigi,feat: add address bar in webview (#1870),PORT,B-stretch,MEDIUM,YELLOW,MANUAL,"src/screens/WebviewScreen/components/Appbar.tsx,src/screens/WebviewScreen/WebviewScreen.tsx",WebView address bar (~11 lines WebviewScreen). +02b6b9848fb3349373de09a4343b6d273605b842,02b6b9848f,2026-07-19,Ecasept,fix: prevent webview reset when reader settings change (#1884),SKIP,ALREADY-HAVE,-,-,-,src/screens/reader/components/WebViewReader.tsx,Fork already syncs settings via injectJavaScript (no WebView reload). +71da09ed444707586eee6fc63f7f2ee182bb2cda,71da09ed44,2026-07-19,Shisii,feat(reader): add current chapter search (#1877),PORT,D,HIGH,RED,MANUAL-rewrite,"android/app/src/main/assets/css/index.css,android/app/src/main/assets/js/core.js,src/screens/reader/components/ReaderAppbar.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts",In-chapter search (#1877) - re-adapt 442-line search.js against fork core.js. +4404494ec9a57c3d2a8b1afb4734c9fc569773a2,4404494ec9,2026-07-19,Rajarshee Chatterjee,fix(database): repair migration history for scanlator column,SKIP,SKIP-DRIZZLE,-,-,-,src/database/db.ts,Drizzle migration SQL; fork MigrationRunner already covers. +98a2a1d725d51d333ea3969b74434b2917a92161,98a2a1d725,2026-07-15,Luigi,feat: implement scanlator filtering (#1864),PORT,D-optional,MEDIUM,RED,MANUAL-rewrite,"App.tsx,src/components/Checkbox/Checkbox.tsx,src/components/NovelCover.tsx,src/components/Switch/Switch.tsx,src/components/Switch/SwitchItem.tsx,src/database/queries/ChapterQueries.ts,src/database/types/index.ts,src/plugins/types/index.ts,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/more/MoreScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,strings/languages/en/strings.json,strings/types/index.ts",Scanlator filtering - needs schema migration + plugin pipeline change; aggregator sources only. +788f6cede7a5676a3d30cf0fd8e565cddd3c486c,788f6cede7,2026-07-13,Palloxin,fix: prevent table overflow and clean up css (#1876) (#1876),PORT,A1,MEDIUM,GREEN,DIRECT,android/app/src/main/assets/css/index.css,Reader table-overflow CSS fix (index.css). +7abee71ac33ab4b16c0c2a629640b1a7c2f5eb3b,7abee71ac3,2026-07-13,Rajarshee Chatterjee,test: generate debug environment variables in testing workflow,SKIP,SKIP-CI,-,-,-,,Fork has separate CI. +52d5c99c46b14148bd262c878701abf91c8b593b,52d5c99c46,2026-07-02,Rajarshee Chatterjee,fix: order epub chapters across paginated pages (closes #1762),PORT,A2,MEDIUM,YELLOW,MANUAL,src/database/queries/ChapterQueries.ts,"EPUB chapters page ordering (getNovelDownloadedChapters ORDER BY page ASC, position ASC); fork has page column." +93bc5e5e4bac14fea10913cb215a7d9c297d6c31,93bc5e5e4b,2026-07-02,Rajarshee Chatterjee,fix: handle missing mal list entries and null progress (closes #1875),PORT,A1,MEDIUM,YELLOW,MANUAL,,MAL null progress fix. myAnimeList.ts diverged in fork (@env client id, WebBrowser, pkceChallenger) - only SetTrackChaptersDialog hunk ports cleanly; MANUAL (review 2026-08-04). +5d996f13883219090ad39ddbe7b52e88e36107c7,5d996f1388,2026-07-01,Jeon JaeHyeong,feat(downloads): make chapter download cooldown configurable (#1834),PORT,B,MEDIUM,YELLOW,MANUAL,"src/hooks/persisted/useSettings.ts,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,strings/languages/en/strings.json",Configurable chapter download cooldown (replaces hardcoded sleep(1000) in downloadChapter.ts). +bbb2645a6f6c47f7cda8d83ef096c329e03ffa6a,bbb2645a6f,2026-07-01,Palloxin,chore: clean reader preview content (#1867),SKIP,SKIP-CI,-,-,-,,CI/preview flavor infra (fork has no preview variant). +54fedc2f531ecdda29dea74f3cc279a7a4525261,54fedc2f53,2026-07-01,Vikas Movva,fix(tts): remove leading and trailing quotes from normalized text (#1869),PORT,A1,HIGH,YELLOW,MANUAL-trivial,android/app/src/main/assets/js/core.js,"TTS normalizeText quote-strip (1 line, core.js:2203). Improves fork TTS output." +e8c5550256dfe06c5ae12c106801e8688173a830,e8c5550256,2026-07-01,Palloxin,perf: optimize regex for removing extra paragraph spacing (#1872),SKIP,SKIP-OTHER,-,-,-,android/app/src/main/assets/js/core.js,No clear user value / overlaps fork-critical files or infra. See subagent report. +57b9d41b5c3eacc9cbf5e38082c4efb2a1a11f5e,57b9d41b5c,2026-07-01,Ali Hamza Malik,fix(service): correct notification throttle logic in servicemanager.setmeta (#1873),PORT,A1,MEDIUM,GREEN,DIRECT,src/services/ServiceManager.ts,"ServiceManager.setMeta throttle (fork has inverted condition + negative delay, :152-153)." +34be7a123499cc7733161482fdf5d42e4c1bae51,34be7a1234,2026-05-24,Jeon JaeHyeong,fix(native-file): prevent fatal crash when unlinking missing files (#1832),PORT,A1,HIGH,GREEN,DIRECT,android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt,NativeFile.kt unlink crash fix; fork byte-identical pre-fix (throw->return). Top crash fix. +60f42ed6243dc5d4daa68c727d60c9fb5dd6576a,60f42ed624,2026-05-24,Jeon JaeHyeong,chore(build): patch @rock-js/plugin-metro for windows compatibility (#1830),SKIP,SKIP-INFRA,-,-,-,"android/settings.gradle,pnpm-lock.yaml,pnpm-workspace.yaml",rock CLI build system; fork uses community CLI + Gradle 9.2.0. +25c93afabe24d34f2130ee0fd7e0846a9b4861ff,25c93afabe,2026-05-24,Anthony Truong,fix(reader): restore saved chapter progress from db if available (#1850),PORT,A3,MEDIUM,YELLOW,MANUAL,src/screens/reader/hooks/useChapter.ts,Restore chapter progress from DB on reader open (3 lines useChapter.ts). +2cd5a97ee8e482e92b45e9b702c9ba974e06a092,2cd5a97ee8,2026-05-10,Gaurav Tupe,fix: await database operations inside transactions (#1845),SKIP,SKIP-DRIZZLE,-,-,-,"src/database/queries/CategoryQueries.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts",await DB ops in transactions (op-sqlite tx API) - NOT-FEASIBLE direct; CONCEPT applies to fork unawaited runAsync in NovelQueries (audit follow-up). +8f47e8fd1e1aedaf22df8275484f27b5d40b2a39,8f47e8fd1e,2026-05-07,Patrick Loser,theme switcher refactor (#1839),PORT,C,MEDIUM,YELLOW,MANUAL,"App.tsx,package.json,pnpm-lock.yaml,src/components/BottomTabBar/index.tsx,src/components/ColorPickerModal/ColorPickerModal.tsx,src/components/List/List.tsx,src/components/SegmentedControl/SegmentedControl.tsx,src/components/ThemePicker/ThemePicker.tsx,src/hooks/persisted/index.ts,src/hooks/persisted/useTheme.ts,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/settings/SettingsAppearanceScreen/LanguagePickerModal.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/theme/utils/setBarColor.ts",Theme switcher refactor (#1839) - prerequisite for dynamic colors; fork useTheme matches pre-refactor base. +eafa545b2a34320b5e1eb2d99ba61b6888838da8,eafa545b2a,2026-05-06,Patrick Loser,use novel refactor (#1838),SKIP,SKIP-INFRA,-,-,-,"package.json,pnpm-lock.yaml,src/database/db.ts,src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/hooks/persisted/index.ts,src/hooks/persisted/useNovel.ts,src/hooks/persisted/useTheme.ts,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/PageNavigationBottomSheet.tsx,src/screens/novel/NovelScreen.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/ReaderAppbar.tsx,src/screens/reader/components/ReaderFooter.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/settings/SettingsAdvancedScreen.tsx,src/screens/settings/SettingsReaderScreen/tabs/NavigationTab.tsx,src/screens/updates/UpdatesScreen.tsx,src/services/migrate/migrateNovel.ts,strings/languages/en/strings.json,strings/types/index.ts",useNovel zustand store refactor (75 files) - fork has own useNovel hook + raw SQL. Maintainability-only. +b680a07750f0b7650836c0573566e641d4aaa679,b680a07750,2026-05-03,Jeon JaeHyeong,fix(browse): use gesture-handler pressable in pluginlistitem (#1828),SKIP,SKIP-OTHER,-,-,-,src/screens/browse/components/PluginListItem.tsx,No clear user value / overlaps fork-critical files or infra. See subagent report. +2a919ec0f963a4d4613e3e0ebe99a16fa7e2dbe3,2a919ec0f9,2026-05-03,Jeon JaeHyeong,chore: gitignore tsconfig.tsbuildinfo build cache (#1829),PORT,A1,LOW,YELLOW,MANUAL-trivial,.gitignore,gitignore tsconfig.tsbuildinfo. Fork .gitignore context differs (expo-cli block) - manual append (review 2026-08-04). +01596c045a4637acf056f75cf87ffc0198d79899,01596c045a,2026-04-14,Definisi,fix: apply active filters to chapter count display (closes #1793) (#1797),PORT,B-stretch,MEDIUM,YELLOW,MANUAL,"src/database/queries/ChapterQueries.ts,src/hooks/persisted/useNovel.ts",Apply active filters to chapter count display. +3de93c5c093ab2fb7e0d509e367e4fec813082e2,3de93c5c09,2026-04-13,Patrick Loser,feat: migrate from rn community cli to rock (#1812),SKIP,SKIP-INFRA,-,-,-,".gitignore,android/app/build.gradle,android/settings.gradle,babel.config.js,package.json,pnpm-lock.yaml,src/screens/more/About.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,tsconfig.json",rock CLI build system; fork uses community CLI + Gradle 9.2.0. +306f5b27e2716f1a963fd992c06f5c961f44c926,306f5b27e2,2026-04-06,Patrick Loser,fix: install the right version of hermes compiler,SKIP,SKIP-INFRA,-,-,-,"package.json,pnpm-lock.yaml",Expo 55 toolchain pinning; N/A to fork Expo 54. +06f0f66e3e4659db14127b1b56202dd3a8fca521,06f0f66e3e,2026-04-05,Patrick Loser,fix: add hermes-compiler dev dep,SKIP,SKIP-INFRA,-,-,-,"package.json,pnpm-lock.yaml",Expo 55 toolchain pinning; N/A to fork Expo 54. +64921a054e9d4a11a0aad294b9703a3ab4c16b9f,64921a054e,2026-04-05,Patrick Loser,feat: expo 55 upgrade,SKIP,SKIP-INFRA,-,-,-,".gitignore,android/app/src/main/AndroidManifest.xml,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainApplication.kt,android/build.gradle,android/gradle/wrapper/gradle-wrapper.properties,android/settings.gradle,babel.config.js,package.json,pnpm-lock.yaml,src/components/BottomSheet/BottomSheet.tsx,src/components/List/List.tsx,src/database/db.ts,src/hooks/persisted/useNovel.ts,src/plugins/helpers/storage.ts,src/screens/Categories/components/CategoryCard.tsx,src/screens/more/About.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderSheetPreferenceItem.tsx,src/screens/settings/SettingsAdvancedScreen.tsx",Expo 55 upgrade - belongs to fork own upgrade decision. +44e035ad38b25f79741199b6aea64479831ccc14,44e035ad38,2026-03-12,Rajarshee Chatterjee,chore: update translations (#1683),SKIP,SKIP-I18N,-,-,-,,Crowdin sync; fork strings at strings/languages with custom keys - manual per-key diff only. +1803cd071d24a4d8ada8c55814a6ce62eaf98481,1803cd071d,2026-02-25,Patrick Loser,feat: add testing support for react code (#1763),SKIP,SKIP-CI,-,-,-,"__mocks__/react-native-mmkv.js,babel.config.js,package.json,pnpm-lock.yaml,src/hooks/persisted/useNovel.ts,src/services/migrate/migrateNovel.ts,tsconfig.json",CI/test infra. +e4d7ba306b4441b4743ab9edb7c96751d62e3cde,e4d7ba306b,2026-02-24,Patrick Loser,fix: safety level may not be changed inside a transaction error (#1758),SKIP,SKIP-DRIZZLE,-,-,-,src/database/db.ts,Drizzle db.ts PRAGMA handling. +cc0428752b2e9869677a6bd2dccc449f7a98a552,cc0428752b,2026-02-20,Rajarshee Chatterjee,feat: replace tts notifications with android mediasession controls (closes #1711),SKIP,ALREADY-HAVE,-,-,-,"android/app/build.gradle,android/app/src/main/assets/js/core.js,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainApplication.kt,App.tsx,src/screens/reader/components/WebViewReader.tsx",MediaSession controls; fork has own TTS media notification. +b8c177bc55016b5a8fb96cc120e8ac050a2f7c73,b8c177bc55,2026-02-19,Patrick Loser,refactor: migrate to drizzle and `op-sqlite` (#1735),SKIP,SKIP-DRIZZLE,-,-,-,".gitignore,App.tsx,babel.config.js,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,src/components/ListView.tsx,src/components/NovelCover.tsx,src/database/db.ts,src/database/migrations/002_add_novel_counters.ts,src/database/migrations/index.ts,src/database/queries/__tests__/CategoryQueries.test.ts,src/database/queries/__tests__/HistoryQueries.test.ts,src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/__tests__/RepositoryQueries.test.ts,src/database/queries/__tests__/StatsQueries.test.ts,src/database/queries/CategoryQueries.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/database/queries/StatsQueries.ts,src/database/tables/ChapterTable.ts,src/database/types/index.ts,src/database/utils/migrationRunner.ts,src/hooks/common/useDatabaseInitialization.ts,src/hooks/common/useGithubUpdateChecker.ts,src/hooks/index.ts,src/hooks/persisted/useNovel.ts,src/hooks/persisted/useSettings.ts,src/navigators/types/index.ts,src/screens/browse/components/AvailableTab.tsx,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/Categories/CategoriesScreen.tsx,src/screens/Categories/components/AddCategoryModal.tsx,src/screens/Categories/components/CategoryCard.tsx,src/screens/history/HistoryScreen.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/SetCategoriesModal.tsx,src/screens/novel/NovelScreen.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsAdvancedScreen.tsx,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,src/screens/settings/SettingsRepositoryScreen/components/DeleteRepositoryModal.tsx,src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx,src/screens/settings/SettingsTrackerScreen.tsx,src/screens/updates/components/UpdateNovelCard.tsx,src/services/backup/utils.ts,src/services/migrate/migrateNovel.ts,src/services/Trackers/mangaUpdates.ts,tsconfig.json",Drizzle/op-sqlite migration or API; fork uses raw SQL + MigrationRunner. NOT-FEASIBLE. +7083c51dea1be0af3bb0d53c341f19b32a35dca4,7083c51dea,2026-02-19,Rajarshee Chatterjee,fix: include selectedids in chapter list extradata for selection highlight (closes #1750),SKIP,SKIP-OTHER,-,-,-,src/screens/novel/components/NovelScreenList.tsx,No clear user value / overlaps fork-critical files or infra. See subagent report. +f0693204e1f8f961dff85b195f3c7f4f6f9bb0ce,f0693204e1,2026-02-18,tomsLt0,fix: improve next chapter selection logic (#1744),PORT,A2,MEDIUM,YELLOW,MANUAL,src/database/queries/ChapterQueries.ts,Next chapter selection logic + LIMIT 1; fork ChapterQueries.ts:441 identical pre-fix. Port with d15cf98. +3f8071e48ee603f906cc9e3910abe90d6e3874a5,3f8071e48e,2026-02-18,Rajarshee Chatterjee,chore: release v2.0.3,SKIP,SKIP-RELEASE,-,-,-,"android/app/build.gradle,package.json",Version bump; fork has own numbering (v2.1.3). +a1c5350e8e330e783b6537c366351c2082e99150,a1c5350e8e,2026-02-12,Rajarshee Chatterjee,fix: miscellaneous ui tweaks,SKIP,SKIP-OTHER,-,-,-,"src/components/List/List.tsx,src/screens/Categories/components/CategoryCard.tsx,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/updates/components/UpdateNovelCard.tsx",No clear user value / overlaps fork-critical files or infra. See subagent report. +8f237909d54310220d518000ace81b5fddbc3b5f,8f237909d5,2026-02-12,Rajarshee Chatterjee,feat: show first unread chapter in readbutton and fab when no last read,PORT,B,MEDIUM,YELLOW,MANUAL,"src/database/queries/ChapterQueries.ts,src/hooks/persisted/useNovel.ts,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/NovelScreenList.tsx",First unread chapter in ReadButton/FAB (getFirstUnreadChapter SQL + wiring). +11ff69d4f5b4a79286247ac6c40ca1eac72ad283,11ff69d4f5,2026-02-12,Rajarshee Chatterjee,fix: pre-fetch adjacent page chapters in reader and library updates,PORT,D-optional,MEDIUM,RED,MANUAL-rewrite,"src/components/BottomSheet/BottomSheet.tsx,src/screens/reader/hooks/useChapter.ts",Pre-fetch adjacent page chapters - port only LibraryUpdateQueries preload part; reader part touches fork-critical useChapter. +d15cf987425709f97ec168a46af8d936e944c29e,d15cf98742,2026-02-12,Rajarshee Chatterjee,fix: order chapter pages numerically in queries for accurate retrieval (closes #1742),PORT,A2,MEDIUM,YELLOW,MANUAL,src/database/queries/ChapterQueries.ts,Numeric page ordering CAST(page AS INTEGER) in getCustomPages/getPrevChapter/getNextChapter; fork has identical buggy SQL. +29bd6612e9a9950c61b9a569b761ffa1aaa5d4eb,29bd6612e9,2026-02-12,Rajarshee Chatterjee,refactor: optimize library screen performance and fix category bug,PORT,D-optional,MEDIUM,RED,MANUAL-rewrite,"src/components/NovelCover.tsx,src/components/NovelList.tsx,src/screens/GlobalSearchScreen/components/GlobalSearchResultsList.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx",Library screen perf + update-category index bug - re-implement on fork LibraryScreen/useLibrary. +ab1e36d280768be46acdb57557e09b3e2d37f12b,ab1e36d280,2026-02-08,Rajarshee Chatterjee,refactor: update components to follow material design 3 guidelines,SKIP,SKIP-OTHER,-,-,-,"src/components/Actionbar/Actionbar.tsx,src/components/BottomSheet/BottomSheet.tsx,src/components/BottomTabBar/index.tsx,src/components/Chip/Chip.tsx,src/components/ConfirmationDialog/ConfirmationDialog.tsx,src/components/IconButtonV2/IconButtonV2.tsx,src/components/List/List.tsx,src/components/Menu/index.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/components/SegmentedControl/SegmentedControl.tsx,src/components/Switch/Switch.tsx,src/components/Switch/SwitchItem.tsx,src/screens/more/MoreScreen.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderSheetPreferenceItem.tsx",No clear user value / overlaps fork-critical files or infra. See subagent report. +f1fdafd37f1f7d0816484c2e414e39d9885499d8,f1fdafd37f,2026-02-07,Rajarshee Chatterjee,fix: prevent tab bar height jump and shadow flash on browse screen,PORT,A1,LOW,YELLOW,MANUAL,src/screens/browse/BrowseScreen.tsx,Browse tab-bar height jump/shadow flash (initialLayout). Fork BrowseScreen.tsx has eslint-disable header + no Color import - hunk fails apply; MANUAL (review 2026-08-04). +4c7684a3a71e57fb27f910b3edfbdd902353dd47,4c7684a3a7,2026-02-07,Rajarshee Chatterjee,refactor: optimize novel screen performance and fix download state tracking,PORT,D-optional,MEDIUM,RED,MANUAL-rewrite,".gitignore,src/hooks/persisted/useNovel.ts,src/screens/novel/components/Chapter/ChapterDownloadButtons.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/PagePaginationControl.tsx,src/screens/novel/NovelScreen.tsx,src/screens/updates/components/UpdateNovelCard.tsx","Novel screen perf + download state tracking - port selectively (updateChapter state bug, Set-based download tracking)." +2f45db659b2fdfa77f9213023d9d2707cbc452e1,2f45db659b,2026-02-07,Rajarshee Chatterjee,fix: replace sync database operations with async to prevent database lock (closes #1716),PORT,D-optional,MEDIUM,RED,MANUAL-rewrite,"src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/hooks/persisted/useNovel.ts,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/Chapter/ChapterDownloadButtons.tsx,src/services/migrate/migrateNovel.ts",Async DB ops to prevent SQLITE_BUSY - fork-side incremental effort. +9e38ad114d182d39880a962cb7acefa55235310d,9e38ad114d,2026-02-06,Rajarshee Chatterjee,fix: sanitize epub filename to remove filesystem-unsafe characters (closes #1480),PORT,A1,MEDIUM,GREEN,DIRECT,src/screens/novel/components/ExportNovelAsEpubButton.tsx,EPUB filename sanitize (1 line ExportNovelAsEpubButton). +45c4ea8ca02031c803224b0d906a6045364d2817,45c4ea8ca0,2026-02-06,Rajarshee Chatterjee,fix: handle ioexception in nativefile to prevent epub import crash loop (closes #1315),PORT,A1,MEDIUM,GREEN,DIRECT,android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt,NativeFile.kt IOException wrap; prevents EPUB import crash loop. +d2b95ae3166f337d8d3597e1cc9a0bff65832d62,d2b95ae316,2026-02-06,Rajarshee Chatterjee,fix: use nativefile for custom css/js import to fix content uri read failure (closes #1661),PORT,B-stretch,MEDIUM,YELLOW,MANUAL,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,NativeFile for custom CSS/JS import (Content URI fix). +02121152b3e3cdbbaaff8a34b51cb8e9b2010538,02121152b3,2026-02-06,Rajarshee Chatterjee,fix: stabilize scroll handler references to prevent infinite re-render loop (closes #1679),SKIP,SKIP-OTHER,-,-,-,"src/screens/browse/discover/MalTopNovels.tsx,src/screens/novel/components/NovelScreenList.tsx",No clear user value / overlaps fork-critical files or infra. See subagent report. +caed965b6bad1b788eee3035daba42322454fe26,caed965b6b,2026-02-06,Rajarshee Chatterjee,fix: prevent epub import crash from documentpicker cache cleanup (closes #1725),PORT,B-stretch,MEDIUM,YELLOW,MANUAL,src/screens/library/LibraryScreen.tsx,EPUB import DocumentPicker cache-cleanup crash (copyToCacheDirectory:false). +4088d5966bbc5ec27801b1ddc553f5621b4b6d79,4088d5966b,2026-02-06,Rajarshee Chatterjee,fix: close okhttp response and streams in nativefile.downloadfile (closes #1740),PORT,A1,MEDIUM,GREEN,DIRECT,android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt,OkHttp response.use in NativeFile.downloadFile; leak fix. +a062beee87d4173383e3033eeacb9376642645e5,a062beee87,2026-02-06,Rajarshee Chatterjee,fix: add max height to set categories modal (closes #1738),PORT,A1,LOW,YELLOW,MANUAL,src/screens/novel/components/SetCategoriesModal.tsx,Set Categories modal maxHeight. Fork SetCategoriesModal rewritten (scaleDimension/AppText) - hunk fails apply; MANUAL (review 2026-08-04). +30958825162fbba2f7b805de6d41ab8e9183f28b,3095882516,2026-02-01,Patrick Loser,replace react-native-paper menus with custom implementation (#1733),SKIP,SKIP-OTHER,-,-,-,"src/components/Menu/index.tsx,src/screens/browse/components/Modals/SourceSettings.tsx,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/more/TaskQueueScreen.tsx,src/screens/novel/components/Chapter/ChapterDownloadButtons.tsx",No clear user value / overlaps fork-critical files or infra. See subagent report. +4ad06397960f1fbc633604ef06c2dfca193a220e,4ad0639796,2026-01-31,Patrick Loser,fix: chapter loading for jump to chapter modal and chapter drawer,PORT,B,MEDIUM,YELLOW,MANUAL,"pnpm-lock.yaml,src/database/queries/ChapterQueries.ts,src/hooks/persisted/useNovel.ts,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/NovelScreen.tsx,src/screens/reader/components/ChapterDrawer/index.tsx",Jump-to-chapter/drawer reach unloaded chapters (getNovelChaptersByNumber/Name raw SQL + loadUpToBatch). +b53ec3352f8ce9084e32ddaa352526b39ec6003c,b53ec3352f,2026-01-28,Rajarshee Chatterjee,feat: support additional plugin setting types (dropdown and checkboxes),PORT,B-stretch,MEDIUM,YELLOW,MANUAL,"src/plugins/types/index.ts,src/screens/browse/components/Modals/SourceSettings.tsx",Plugin setting types: dropdown + checkboxes. +c40edd5b2cc68c2b746ba588aa39e142ab99f01e,c40edd5b2c,2026-01-25,Rajarshee Chatterjee,feat: add kitsu tracker (closes #416),PORT,B,HIGH,YELLOW,MANUAL,"src/screens/novel/components/Tracker/ScoreSelectors.tsx,src/screens/novel/components/Tracker/TrackerCards.tsx,src/screens/settings/SettingsTrackerScreen.tsx",Kitsu tracker (new kitsu.ts service + tracker UI wiring). Best value/effort in batch. +bdd42caaaf3c3d5961d444aabc9150057d2de5d9,bdd42caaaf,2026-01-20,Rajarshee Chatterjee,chore: update repository links to use lneader instead of lnreader,SKIP,SKIP-INFRA,-,-,-,"README.md,src/screens/more/About.tsx,strings/languages/en/strings.json",Repo-link rebrand to upstream org - actively harmful for fork. +ab4eee31ceb82c3f89e459279bef8b56de5ec10c,ab4eee31ce,2026-01-20,Rajarshee Chatterjee,feat: allow all safe tags and attributes when sanitizing chapter html (closes #1715),SKIP,SKIP-OTHER,-,-,-,src/screens/reader/utils/sanitizeChapterText.ts,No clear user value / overlaps fork-critical files or infra. See subagent report. +3f9886a2e5300bb9913888872847727ab63597fc,3f9886a2e5,2026-01-17,Rajarshee Chatterjee,chore: update repository links to use lneader instead of lnreader,SKIP,SKIP-INFRA,-,-,-,"README.md,src/screens/more/About.tsx,strings/languages/en/strings.json,strings/types/index.ts",Repo-link rebrand to upstream org - actively harmful for fork. +198b7443ae9c0ee7cc3ff608c0fc89a88880fa84,198b7443ae,2026-01-20,kingstar1111,feat: add rtl (right-to-left) language support (#1717),PORT,D-optional,MEDIUM,RED,MANUAL-rewrite,"src/components/Checkbox/Checkbox.tsx,src/components/Chip/Chip.tsx,src/components/ColorPreferenceItem/ColorPreferenceItem.tsx,src/components/List/List.tsx,src/components/ListView.tsx,src/components/Menu/index.tsx,src/components/RadioButton.tsx,src/components/RadioButton/RadioButton.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/components/Switch/SwitchItem.tsx,src/components/ThemePicker/ThemePicker.tsx,src/hooks/common/useGithubUpdateChecker.ts,src/navigators/BottomNavigator.tsx,src/screens/browse/components/PluginListItem.tsx,src/screens/browse/components/PluginListItemSkeleton.tsx,src/screens/browse/migration/MigrationSourceItem.tsx,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/Categories/components/CategoryCard.tsx,src/screens/GlobalSearchScreen/components/GlobalSearchResultsList.tsx,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/PageNavigationBottomSheet.tsx,src/screens/novel/components/PagePaginationControl.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/novel/components/Tracker/TrackSheet.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/ReaderAppbar.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderFontPicker.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/components/TabBar.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/screens/settings/SettingsTrackerScreen.tsx,src/screens/updates/components/UpdateNovelCard.tsx",RTL language support (44 files incl. WebViewReader). +f9c5ca303d71830811a203f98a17c3dd4846f887,f9c5ca303d,2026-01-16,Rajarshee Chatterjee,chore: update indonesian translations (closes #1653),SKIP,SKIP-I18N,-,-,-,,Crowdin sync; fork strings at strings/languages with custom keys - manual per-key diff only. +9922d4394058a6da29ab2789cd8d7b75a37bf1bc,9922d43940,2026-01-15,Rajarshee Chatterjee,fix: remove missing image references during epub export (closes #1696),PORT,B-stretch,MEDIUM,YELLOW,MANUAL,src/screens/novel/components/ExportNovelAsEpubButton.tsx,EPUB export missing-image cleanup. +ae99b077d4e883e678b31ba0235a7abae88e1e94,ae99b077d4,2026-01-15,Rajarshee Chatterjee,feat: add `@noble/ciphers` for plugins (closes #1709),PORT,B-stretch,MEDIUM,YELLOW,MANUAL,"package.json,pnpm-lock.yaml",@noble/ciphers for plugins. +0cb9da90276c6a30d5d917557195f0f0b0ebc70c,0cb9da9027,2026-01-15,Rajarshee Chatterjee,refactor: replace `withtransactionasync` with `withexclusivetransactionasync` for improved concurrency handling (closes #1636),PORT,A2,MEDIUM,YELLOW,MANUAL,"src/database/queries/CategoryQueries.ts,src/database/queries/NovelQueries.ts,src/database/queries/StatsQueries.ts,src/database/utils/helpers.tsx,src/services/migrate/migrateNovel.ts",Reclassified PORT-A2 per review 2026-08-04: PRE-Drizzle expo-sqlite tx fix (withTransactionAsync -> withExclusiveTransactionAsync + await runAsync); fork has identical pre-fix pattern (NovelQueries.ts:180/321, helpers.tsx:133-137 unawaited runAsync); maps 1:1 onto fork raw SQL. diff --git a/specs/upstream-merge-analysis-2026-08/reference/commit_overlap_170.txt b/specs/upstream-merge-analysis-2026-08/reference/commit_overlap_170.txt new file mode 100644 index 0000000000..f89c2fdc4f --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/reference/commit_overlap_170.txt @@ -0,0 +1,170 @@ +c3260e8e01e4704b57137d787ffe9cfec7941cbc|fix: Resolve Chapter Download Queue and Removal Issues (Closes #1937)| +c0877a9b0656e22c3c788854006fb456c3ed704a|feat: Add Ignorable App Update Notifications| +96fb52df1730912c40ee6918cf6a6fcd74e31cc8|feat: Enhance Stats With Genre Taxonomy and Charts (#1919)| +76106317b68e8aade12b868afb16dda4671ceb16|fix: Restore Midnight Dusk Chip Contrast (Closes #1939)| +e75f4fba5da08e2317cfed2fe92b8de09457475c|feat: Add More Novel Statuses and Icons (#1931)|src/plugins/types/index.ts,src/screens/novel/components/Info/NovelInfoHeader.tsx +4dc933ee5a361eb0535120fa192c1eb51ba8cc70|fix: Remove White Flash During Screen Transitions (Closes #1926) (#1927)|App.tsx +bfb12b04aa31e3013f50503697253b549ee9618c|feat: Add Configurable Automatic Backup Scheduling|src/hooks/persisted/useSettings.ts,src/screens/settings/SettingsBackupScreen/index.tsx +b7639ab55a308c55617f0088c193052b9e0bd2b2|feat: Upload JavaScript Source Maps (#1924)|src/components/AppErrorBoundary/AppErrorBoundary.tsx +ecc5b84a64ac1aa841583acb43d7ac8aab5a72c5|chore: Release v2.1.2|package.json +eb12bdfd623bd28c2c3f88f585fefca9352711a4|fix: Prevent Novel Genre Rendering Crashes|src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx +9612bf6f893d0078c566f9651405097b7f8d1f24|chore: Update Translations (#1928)| +0ed7d878b4b75e1f6ebc83d0978f05cad5920014|fix: Prevent White Reader Drawer Seam|src/screens/reader/ReaderScreen.tsx +5f45e4c7f2d4f77da3b73bdfdfcdb5b477fd6849|fix: Recover Interrupted Chapter Migrations|src/database/db.ts +ca24671a2f847c5722009b6b8451e9c54d3a2c12|fix: Support Fork-Safe Preview Builds| +ee3a4f20bc41ccf9e3f33ce37e7191cd5c7e6a17|fix: Preserve Default Category After Reordering (Closes #1922)|src/database/queries/__tests__/CategoryQueries.test.ts,src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/CategoryQueries.ts,src/database/queries/NovelQueries.ts,src/screens/Categories/CategoriesScreen.tsx +0c3542836c847c0e824e473c06394bd5219d6329|fix: Synchronize Library Updates And Optimize Import Refreshes|src/database/queries/ChapterQueries.ts,src/screens/library/hooks/useLibrary.ts +f71c9c859471968684baef6e7f3da55a9d2f5821|fix: Ignore Discord Notifications Outside the Main Repository (#1923)| +f53546cc01d76b8b3f73ed4a3b88b374f775d746|chore: Release v2.1.1|package.json +594b078fad3308c284b3b9cd8484056b40d3c821|fix: Harden Database Migration Recovery|src/database/db.ts +ac1b2f5ce446ae90db315be11d1494bc49539f40|feat: Add Durable Completion Notifications| +648d21062f8812ae2a8526954a374424c0dede3c|feat: Add Parallel Task Queues With Per-Plugin Download Limits|src/services/migrate/migrateNovel.ts +89d715ce6bc22ea15885ff86b3359033f3253b4c|docs: Replace Beta Badge With Build Preview Status|README.md +bd527a02efa9d642b7168e432bfb965a0ae85f51|feat: Improve Backup Selection, Notifications, And Plugin Restore|src/components/Checkbox/Checkbox.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/Components/SelfHostModal.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/services/backup/local/index.ts,src/services/backup/types.ts,src/services/backup/utils.ts +11117c1b5404820868683522242296d4f61d7642|feat: Add GitHub-Style Embed For Nightly Notifications| +ba25d5b8fe3af0b006ad09293a499b58cbee1479|feat: Add Rozenite DevTools Integration (#1912)|App.tsx,package.json,pnpm-lock.yaml +31cb4b994a332bd6f99864ba792aaa988bf4720b|fix: Align Bottom Navigation With Material 3 Specs|src/components/BottomTabBar/index.tsx +72ef618af1bdb6a40783ab119649e302a89e12d8|fix: Preserve Existing Data During Novel Counter Migration| +936fd223bbde71c3e0496f22ea4f28c9714d7987|feat: Add Discord Notifications For Nightly Builds| +6d26438d2dd5b7faf21786bc390ae83b43aca6d5|test: Update Expected Database Migrations|AGENTS.md +5eb5a55c4c654c046f984f1e324b36587389dfa8|fix: Repair Skipped Novel Counter Migrations| +6cd86d1422de47dc8e08c53e645c678b39ed66cc|chore: Update Translations (#1906)| +9783c4d5e3a0f7e5736d1fa50a8dd360a760f76c|fix: Normalize Repository Back Navigation (#1905)|src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx +87f602a1fdaaf4456b1947a6097b5a7e65caa1a3|chore: Release v2.1.0|package.json +3d32ddd6e933d5b5208f7813b99b8755e700d483|fix: Improve Release Selection And APK Verification| +7ddd934c2b834ba8fbed6969dab89728e3316251|fix: Handle Missing Tracker Client IDs| +b53c91f748581227d63a28d28d33de87c1cbb296|chore: Separate Jest And Type Check Workflows|package.json +9d106a371620a513f1f4cf57f511936866a77fd2|feat: Add Semantic Version Bump Selection| +8b0a8dc4c07b6d77532ef9b070bbce288afd92d4|chore: Update Husky Pre-Commit and Pre-Push Hooks|package.json +72bfcad03469440c1e9cf5ff88b5ff97a544c45e|fix: Resolve Type and Lint Issues|eslint.config.js,src/components/ErrorView/ErrorView.tsx,src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/__tests__/StatsQueries.test.ts,src/hooks/common/useGithubUpdateChecker.ts,src/navigators/types/index.ts,src/screens/browse/discover/AniListTopNovels.tsx,src/screens/browse/discover/MalTopNovels.tsx,src/screens/browse/migration/MigrationSourceItem.tsx,src/screens/browse/SourceNovels.tsx,src/screens/BrowseSourceScreen/useBrowseSource.ts,src/screens/GlobalSearchScreen/GlobalSearchScreen.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/more/About.tsx,src/screens/more/components/MoreHeader.tsx,src/screens/more/MoreScreen.tsx,src/screens/more/TaskQueueScreen.tsx,src/screens/reader/ChapterContext.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,src/screens/settings/SettingsRepositoryScreen/components/RepositoryCard.tsx,src/screens/settings/SettingsTrackerScreen.tsx,src/screens/WebviewScreen/components/Appbar.tsx,src/screens/WebviewScreen/components/Menu.tsx,src/screens/WebviewScreen/WebviewScreen.tsx +345d084eba5f1862c37d15c3790ce36610116d70|feat: Add Chapter Numbers To EPUB Titles (Closes #1405)|src/hooks/persisted/useSettings.ts,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx +10b6042a9c671f47ba33f7810c47935a54706f40|feat: Add EPUB Export Progress Notifications|src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx +bd7161172551568e579e60ece7072f8c5a05fa65|feat: Improve Chapter Drawer And WebView App Bar|src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/ReaderScreen.tsx,src/screens/WebviewScreen/components/Appbar.tsx +8f53550d2c6ec75b8d9cefe0142218aaec26f726|fix: Restore Tracker Search Requests|src/screens/novel/components/Tracker/TrackSearchDialog.tsx +52914b978df3267342b9cd3d18270bf19ae5a7cb|fix: Migrate Volume Button Events to Expo Listener API|src/screens/reader/hooks/useChapter.ts +ff12759a66ad68791d7b8270579b8f7d020e73f5|ci: Run Preview Builds Daily When Missing| +e62868b82ccd1784ad83cd00d338c47d2818df94|feat: Automate Draft Android Releases|.gitignore,package.json,src/hooks/common/useGithubUpdateChecker.ts +97be48dfe17b6a5261284cc3052ab5dcbab8d20d|feat: Add Migration Review Options|AGENTS.md,src/screens/browse/migration/MigrationNovelList.tsx,src/services/migrate/migrateNovel.ts +cbe7d70e2df349db7ebf36a448e1f84b3903fb78|fix: Restore Collapsed TTS Button Interaction| +6548e9a6f8efc0bb21e46af005072d7599b326e9|fix: Improve Paged Reader Layout and Performance|src/screens/reader/ReaderScreen.tsx +138fdff2e57e55e02f9775d11dfb47482701f5c5|fix: Prevent Duplicate WebView Bottom Insets|src/screens/WebviewScreen/WebviewScreen.tsx +f1dd4b77a891e501b9345e5d0fe81b6405bce450|feat: Improve Empty Chapter Reporting|src/screens/reader/components/WebViewReader.tsx,src/screens/reader/utils/sanitizeChapterText.ts +9b356ffd17c28d7f37285157f3f34488af67a266|fix: Preserve Client IDs And Improve Environment Generation| +5cf7e15d7631f3ada56d4cb979694214745bd10d|feat: Add Reader Setting Descriptions|src/screens/settings/SettingsReaderScreen/tabs/AccessibilityTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/NavigationTab.tsx +6a7e90c179b735521aa9d778e68f1727607e8ac5|fix: Align Advanced Reader Tabs And Localize Actions|src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx +99c31d56bb45c6e04fe96cff3f14c3ca55fccba4|fix: Use MD3 Outline Variant For Tab Borders|src/screens/browse/BrowseScreen.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx +4d98247980c21f892df6cf0da19ecad8a6f0d136|feat: Add Android DNS Over HTTPS Support|src/screens/settings/SettingsAdvancedScreen.tsx +164f5d259243a68f14a7e8a069b2e2cfc42aab84|feat: Add Automatic Library Update Scheduling|src/hooks/persisted/useSettings.ts +89cddf9e3d07d3f7a7c7cb71e8cc8e5e4e96fcc3|fix: Expand Discover Row Ripple|src/screens/browse/discover/DiscoverCard.tsx +eac6ad584e85d4ed72cf76fafcde1c4d0551687d|feat: Improve Novel App Bar And EPUB Export UX|src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/NovelScreen.tsx +93fe04c28cc4776a96f86aa157b729ddf3c218c3|feat: Polish Reader Controls And Navigation|src/components/Common/ToggleButton.tsx,src/components/SegmentedControl/SegmentedControl.tsx,src/database/queries/ChapterQueries.ts,src/navigators/MoreStack.tsx,src/screens/browse/discover/DiscoverCard.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/reader/components/ReaderAppbar.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderFontPicker.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderSheetPreferenceItem.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderTextAlignSelector.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderThemeSelector.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderValueChange.tsx,src/screens/reader/components/ReaderBottomSheet/TextSizeSlider.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/ReaderFooter.tsx,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsTrackerScreen.tsx +c9358b3a9fc49ad6a98ac0293da46bf9eab39bf8|feat: Update Android Branding Assets| +67d2c7c01b101d83d0033aad2c0a99247f04620e|feat: Add Collapsible TTS Controls And Stable Notification Progress| +c30441b9493d5e75fbe82a608b034c6239fa493f|feat: Add Smart Library Update Filters|src/hooks/persisted/useSettings.ts +6d2a9f8e1530604860375a9be3abb92b4502d01b|fix: Prevent Slider Flicker After Release| +799845426c8f2e651de508d471d8319add41d2cc|feat: Add Parallel Library Updates Across Sources| +8ac628b3deda367f68af93f9f43d7a1da8c80f9d|fix: Improve History Removal Flow.|src/database/queries/__tests__/HistoryQueries.test.ts,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/history/HistoryScreen.tsx +d02291c22c079e5faee9817b00c7bd87ed7f47da|feat: Remember Last Used Library Category|src/hooks/persisted/useSettings.ts,src/screens/library/LibraryScreen.tsx +7af0911f5b17e1c606dbf60e0f1d1b8863c78221|feat: Add High-Resolution Android Flavor Assets| +098782d6aa9a188bf007402155bd66e3dbc14523|refactor: Simplify Settings Layout And Theme Selection|package.json,pnpm-lock.yaml,src/components/List/List.tsx,src/components/ThemePicker/ThemePicker.tsx,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,src/screens/settings/SettingsTrackerScreen.tsx +732628c40bb3f1afbbb0336577f9a56f97ef9115|feat: Add Date Format And Relative Timestamp Settings.|src/hooks/persisted/useSettings.ts,src/screens/history/HistoryScreen.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/updates/UpdatesScreen.tsx +5e62c36a9b0c0d7ca562383f2d9938e722216f00|feat: Add Library Settings And Category-Based Global Updates|src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/NovelQueries.ts,src/hooks/persisted/useSettings.ts,src/navigators/MoreStack.tsx,src/navigators/types/index.ts,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,src/screens/settings/SettingsLibraryScreen/DefaultCategoryDialog.tsx,src/screens/settings/SettingsScreen.tsx +3d102a52a7d780b096afd78c6d5214e46b670b47|perf(reader): Cut Chapter Open Latency and Reading-Time Re-Renders (#1899)|src/hooks/persisted/useSettings.ts,src/screens/reader/ChapterContext.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/reader/components/ReaderFooter.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/reader/utils/sanitizeChapterText.ts,src/screens/WebviewScreen/WebviewScreen.tsx +4dcf21303715793087ea3bd49a9d510943e30944|fix: Restore Reader Changes Reverted During Expo Migration| +4de82472ee70fd0c36114f0ee2e213ff6bf81099|fix: Resolve Require Cycles And LegendList Render Updates|pnpm-lock.yaml,pnpm-workspace.yaml,src/components/SearchbarV2/SearchbarV2.tsx,src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx,src/services/backup/utils.ts +58db0352e6359d4b1c69391d260105abbe4ede60|fix: Restore Metro Reader Asset Middleware| +74808f086e5e3fbd479138681d700814c3370386|feat: Replace Legacy Media Controls with Nitro-Powered Playback (#1896)|babel.config.js,package.json,src/hooks/persisted/useSettings.ts,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,tsconfig.json +b96351ea8117f4a1e464b0a7ab1820a557172c92|docs: Fix Contraction and Missing Article in `TESTING.md` (#1895)| +eb8dc6df368ba7628178d6481294fca1b30f9019|docs: Fix "Withoud" Typo in `CONTRIBUTING-NIX.md` (#1894)|CONTRIBUTING-NIX.md +0c8546188eb33a0ac3be46596503cd38e4fb4d75|feat: Add MD3 Slider Component.|package.json,pnpm-lock.yaml,src/screens/reader/components/ReaderBottomSheet/ReaderValueChange.tsx,src/screens/reader/components/ReaderBottomSheet/TextSizeSlider.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/settings/SettingsReaderScreen/ReaderTextSize.tsx +e9f6bdaa203890fb7d69b261f31afc431fba429c|fix: Modernize Menu Styling And Outside-Tap Dismissal|src/components/Menu/index.tsx +3d34658d0faee3f1a1dce6ed77f745804851c4c0|feat: Add Material 3 Top Tab Indicators|src/screens/browse/BrowseScreen.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/settings/SettingsReaderScreen/components/TabBar.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx +d44ce7412ac512b392d35e33d6dc9d98587a2350|docs: Fix Trailing Period, Organization Expansion, and Comma in `CONTRIBUTING.md` (#1893)| +17c891e1334409fa29d4a365b9ba46014b81b591|refactor: Standardize Bottom Sheet UX and Styling|src/components/BottomSheet/BottomSheet.tsx,src/hooks/persisted/useTheme.ts,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/PageNavigationBottomSheet.tsx,src/screens/novel/components/Tracker/TrackSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx +9805da63cf23d58f0561b9335e0a07c1789e9a82|refactor: Optimize Novel Screen Performance And Architecture|src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/NovelScreenButtonGroup/NovelScreenButtonGroup.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/SetCategoriesModal.tsx,src/screens/novel/components/Tracker/ScoreSelectors.tsx,src/screens/novel/components/Tracker/TrackSheet.tsx,src/screens/novel/NovelScreen.tsx,src/services/migrate/migrateNovel.ts +afc20277e0f8a8d4b49d1ebd1c7689c599ba2e06|fix: Restore Android Build Types For CI|.gitignore,package.json +14061d207b77d8930c4d15af135189ccb4f4bd9e|docs: Fix Title Case Headings, GitHub Capitalization, and E.g. Formatting in `CONTRIBUTING.md` (#1892)| +916374b5df3cfa3b09d464109340ad81bdbef457|refactor: Reorganize Repository Structure|babel.config.js,package.json,scripts/generate-string-types.cjs,src/components/ColorPickerModal/ColorPickerModal.tsx,src/components/ConfirmationDialog/ConfirmationDialog.tsx,src/components/NewUpdateDialog.tsx,src/components/NovelCover.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/database/queries/CategoryQueries.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/navigators/BottomNavigator.tsx,src/screens/browse/BrowseScreen.tsx,src/screens/browse/discover/DiscoverCard.tsx,src/screens/browse/migration/Migration.tsx,src/screens/browse/migration/MigrationNovelList.tsx,src/screens/browse/settings/modals/ConcurrentSearchesModal.tsx,src/screens/browse/SourceNovels.tsx,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/Categories/CategoriesScreen.tsx,src/screens/Categories/components/AddCategoryModal.tsx,src/screens/Categories/components/DeleteCategoryModal.tsx,src/screens/GlobalSearchScreen/components/GlobalSearchResultsList.tsx,src/screens/GlobalSearchScreen/GlobalSearchScreen.tsx,src/screens/history/components/ClearHistoryDialog.tsx,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/history/HistoryScreen.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/LibraryScreen.tsx,src/screens/more/About.tsx,src/screens/more/components/RemoveDownloadsDialog.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/more/MoreScreen.tsx,src/screens/more/TaskQueueScreen.tsx,src/screens/novel/components/Chapter/ChapterDownloadButtons.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/DownloadCustomChapterModal.tsx,src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/NovelScreenButtonGroup/NovelScreenButtonGroup.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/NovelSummary/NovelSummary.tsx,src/screens/novel/components/SetCategoriesModal.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/novel/NovelScreen.tsx,src/screens/onboarding/OnboardingScreen.tsx,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderFontPicker.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderTextAlignSelector.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderThemeSelector.tsx,src/screens/reader/components/ReaderBottomSheet/TextSizeSlider.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/reader/utils/sanitizeChapterText.ts,src/screens/settings/components/ConnectionModal.tsx,src/screens/settings/SettingsAdvancedScreen.tsx,src/screens/settings/SettingsAppearanceScreen/LanguagePickerModal.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/Components/SelfHostModal.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/screens/settings/SettingsGeneralScreen/modals/DisplayModeModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/GridSizeModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/NovelBadgesModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/NovelSortModal.tsx,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,src/screens/settings/SettingsLibraryScreen/DefaultCategoryDialog.tsx,src/screens/settings/SettingsReaderScreen/Modals/FontPickerModal.tsx,src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx,src/screens/settings/SettingsReaderScreen/ReaderTextSize.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/tabs/AccessibilityTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/DisplayTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/NavigationTab.tsx,src/screens/settings/SettingsReaderScreen/tabs/ThemeTab.tsx,src/screens/settings/SettingsRepositoryScreen/components/AddRepositoryModal.tsx,src/screens/settings/SettingsRepositoryScreen/components/DeleteRepositoryModal.tsx,src/screens/settings/SettingsRepositoryScreen/components/RepositoryCard.tsx,src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx,src/screens/settings/SettingsScreen.tsx,src/screens/settings/SettingsTrackerScreen.tsx,src/screens/StatsScreen/StatsScreen.tsx,src/screens/updates/UpdatesScreen.tsx,src/screens/WebviewScreen/components/Menu.tsx,src/services/backup/local/index.ts,src/services/backup/utils.ts,tsconfig.json +0a2a6c96ca453fd68ce42d4723c0f42c0a730519|feat: Add Variant-Specific Android Splash Screens| +65e86100304ae27ba8049937558b4e25b49252f6|refactor: Replace Bottom Navigation Shift With Fade|src/navigators/BottomNavigator.tsx +1322065290f63141633c30cd3013b7ba81c12fae|feat: Add Variant-Specific Android App Icons| +22a1efd1e5c548a27b3ab3b8c54909a5092c1e37|perf: Optimize React Native Skeleton Loading Components|src/components/Skeleton/Skeleton.tsx,src/screens/browse/loadingAnimation/MalLoading.tsx,src/screens/browse/loadingAnimation/TrackerLoading.tsx,src/screens/history/components/HistorySkeletonLoading.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/LoadingAnimation/NovelScreenLoading.tsx,src/screens/updates/components/UpdatesSkeletonLoading.tsx +269606ebb8e352f72fbab401bd5d93ed15c1a7a2|fix: Resolve Nitro EPUB Import Alias|tsconfig.json +4cb1890b62eb57cc75ca0628a11be312c930ddc4|feat: Add Nitro EPUB Exporter|.gitignore,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx,strings/languages/en/strings.json,strings/types/index.ts +a421177f0cb5b74e30b869c6595ed12c9263586a|fix: Improve Category Badge Layout And Drag Reordering|src/components/IconButtonV2/IconButtonV2.tsx,src/screens/Categories/CategoriesScreen.tsx,src/screens/Categories/components/CategoryCard.tsx +a16ec876311692a6cb3da9348d09e7cf287c9fb6|perf: Optimize Novel Covers, Lists, and Chapter Groups|package.json,pnpm-lock.yaml,src/components/ErrorScreenV2/ErrorScreenV2.tsx,src/components/ListView.tsx,src/components/NovelCover.tsx,src/components/NovelList.tsx,src/database/queries/ChapterQueries.ts,src/database/types/index.ts,src/plugins/types/index.ts,src/screens/browse/discover/DiscoverNovelCard/index.tsx,src/screens/browse/globalsearch/GlobalSearchNovelCover.tsx,src/screens/GlobalSearchScreen/components/GlobalSearchResultsList.tsx,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/StatsScreen/StatsScreen.tsx,src/screens/updates/components/UpdateNovelCard.tsx,src/screens/updates/UpdatesScreen.tsx +dfd620aa292fadd3b8798013701ae1bef954fe57|feat: Standardize Dialogs with Material 3 Patterns|src/components/ColorPickerModal/ColorPickerModal.tsx,src/components/ConfirmationDialog/ConfirmationDialog.tsx,src/components/DialogTitle/DialogTitle.tsx,src/components/NewUpdateDialog.tsx,src/hooks/persisted/useTheme.ts,src/screens/browse/migration/MigrationNovelList.tsx,src/screens/browse/settings/modals/ConcurrentSearchesModal.tsx,src/screens/Categories/components/AddCategoryModal.tsx,src/screens/Categories/components/DeleteCategoryModal.tsx,src/screens/history/components/ClearHistoryDialog.tsx,src/screens/history/HistoryScreen.tsx,src/screens/more/components/RemoveDownloadsDialog.tsx,src/screens/more/DownloadsScreen.tsx,src/screens/novel/components/DownloadCustomChapterModal.tsx,src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/ExportEpubModal.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/SetCategoriesModal.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/settings/components/ConnectionModal.tsx,src/screens/settings/SettingsAdvancedScreen.tsx,src/screens/settings/SettingsAppearanceScreen/LanguagePickerModal.tsx,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/Components/SelfHostModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/DisplayModeModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/GridSizeModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/NovelBadgesModal.tsx,src/screens/settings/SettingsGeneralScreen/modals/NovelSortModal.tsx,src/screens/settings/SettingsLibraryScreen/DefaultCategoryDialog.tsx,src/screens/settings/SettingsReaderScreen/Modals/FontPickerModal.tsx,src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/screens/settings/SettingsRepositoryScreen/components/AddRepositoryModal.tsx,src/screens/settings/SettingsRepositoryScreen/components/DeleteRepositoryModal.tsx,src/screens/settings/SettingsTrackerScreen.tsx +a5a6e8326f72d00271fd01cf8d6374cc35310147|chore: Update `@legendapp/list` to 3.3.3|package.json,pnpm-lock.yaml,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelDrawer.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/PageNavigationBottomSheet.tsx,src/screens/novel/NovelScreen.tsx,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx +7f48a8ba8299180ffd5b07ba6314960213cd13cd|refactor: Improve Plugins Screen UX and Code|src/components/IconButtonV2/IconButtonV2.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/components/Switch/Switch.tsx,src/components/Switch/SwitchItem.tsx,src/hooks/persisted/index.ts,src/navigators/BottomNavigator.tsx,src/navigators/types/index.ts,src/screens/browse/BrowseScreen.tsx,src/screens/browse/components/AvailableTab.tsx,src/screens/browse/components/DeferredPluginListItem.tsx,src/screens/browse/components/InstalledTab.tsx,src/screens/browse/components/Modals/SourceSettings.tsx,src/screens/browse/components/PluginListItem.tsx,src/screens/browse/components/PluginListItemSkeleton.tsx,src/screens/browse/discover/DiscoverCard.tsx,src/screens/browse/migration/Migration.tsx,src/screens/browse/migration/MigrationNovels.tsx,src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts +ae34d7b4ec6be28f7ad5ae25c663c7d2b3db43ef|feat: Add Dynamic Preview APK Metadata| +44c8e54ed80bb47cc49c2461adca66ced94b2d1c|feat: Add Dynamic Material Theme Colors (Closes #1863)|App.tsx,package.json,pnpm-lock.yaml,src/hooks/persisted/useTheme.ts,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts +4a4208e33602d65ef9942d05e54a692addd87e2f|feat: Add Time-Tracking Toggle and Inactivity Timeout (#1890)|src/hooks/persisted/useSettings.ts,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts +d2385b8dc2a9cd911e7567e55d6b7dc2c43f2372|Update build.yml| +9babe1676dd053069d127743a61bc7a8bc11256c|refactor: Remove React Native Background Actions Manifest Customization|package.json +8c38f499e4b62de02157aa8dcd1c9207978033bf|chore: Update AppState Test Setup And `pnpm` Configuration| +89c43c1eeffd5a9980b3aaebe647f3d94252f8b8|feat: Implement Time Tracking and Statistics (#1886)|src/database/queries/__tests__/StatsQueries.test.ts,src/database/queries/ChapterQueries.ts,src/database/queries/StatsQueries.ts,src/database/types/index.ts,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/StatsScreen/StatsScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts +c3c891cea084796b3640233826dacfc7aab6f8d6|refactor: Migrate to Expo Managed Workflow (#1885)|.gitignore,android/app/build.gradle,android/app/proguard-rules.pro,android/app/src/main/AndroidManifest.xml,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainActivity.kt,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainApplication.kt,android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt,android/app/src/main/res/raw/loading.json,android/build.gradle,android/gradle.properties,android/gradle/wrapper/gradle-wrapper.properties,android/settings.gradle,App.tsx,babel.config.js,eslint.config.js,ios/LNReader/Images.xcassets/AppIcon.appiconset/Contents.json,ios/loading.json,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,src/components/BottomTabBar/index.tsx,src/components/EmptyView/EmptyView.tsx,src/components/ErrorScreenV2/ErrorScreenV2.tsx,src/components/IconButtonV2/IconButtonV2.tsx,src/components/NovelCover.tsx,src/components/NovelList.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/database/queries/CategoryQueries.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/hooks/persisted/useNovel.ts,src/plugins/helpers/fetch.ts,src/screens/browse/BrowseScreen.tsx,src/screens/browse/components/PluginListItem.tsx,src/screens/browse/loadingAnimation/MalLoading.tsx,src/screens/browse/loadingAnimation/TrackerLoading.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/novel/components/ExportNovelAsEpubButton.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/reader/components/ReaderFooter.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/services/backup/local/index.ts,src/services/backup/utils.ts,src/theme/utils/setBarColor.ts,strings/languages/en/strings.json,strings/types/index.ts,tsconfig.json +e4cf64a112dbfd9f9dd853abec99afbdc09f1460|refactor: Migrate Background Tasks to Native Android Scheduling (#1889)|android/app/build.gradle,android/app/src/main/AndroidManifest.xml,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainApplication.kt,android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt,App.tsx,package.json,pnpm-lock.yaml,src/database/db.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/hooks/index.ts,src/hooks/persisted/useNovel.ts,src/screens/browse/migration/MigrationNovelList.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx,src/screens/more/MoreScreen.tsx,src/screens/more/TaskQueueScreen.tsx,src/screens/novel/components/ExportNovelAsEpubButton.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/settings/SettingsBackupScreen/Components/GoogleDriveModal.tsx,src/screens/settings/SettingsBackupScreen/Components/SelfHostModal.tsx,src/screens/settings/SettingsBackupScreen/index.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/screens/updates/UpdatesScreen.tsx,src/services/backup/local/index.ts,src/services/backup/utils.ts,src/services/migrate/migrateNovel.ts,src/services/ServiceManager.ts +3e1e211098779264ba123121cc66bb48385fcf77|feat: Add Address Bar in WebView (#1870)|src/screens/WebviewScreen/components/Appbar.tsx,src/screens/WebviewScreen/WebviewScreen.tsx +02b6b9848fb3349373de09a4343b6d273605b842|fix: Prevent WebView Reset When Reader Settings Change (#1884)|src/screens/reader/components/WebViewReader.tsx +71da09ed444707586eee6fc63f7f2ee182bb2cda|feat(reader): Add Current Chapter Search (#1877)|android/app/src/main/assets/css/index.css,android/app/src/main/assets/js/core.js,src/screens/reader/components/ReaderAppbar.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,strings/languages/en/strings.json,strings/types/index.ts +4404494ec9a57c3d2a8b1afb4734c9fc569773a2|fix(database): Repair Migration History For Scanlator Column|src/database/db.ts +98a2a1d725d51d333ea3969b74434b2917a92161|feat: Implement Scanlator Filtering (#1864)|App.tsx,src/components/Checkbox/Checkbox.tsx,src/components/NovelCover.tsx,src/components/Switch/Switch.tsx,src/components/Switch/SwitchItem.tsx,src/database/queries/ChapterQueries.ts,src/database/types/index.ts,src/plugins/types/index.ts,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/more/MoreScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,strings/languages/en/strings.json,strings/types/index.ts +788f6cede7a5676a3d30cf0fd8e565cddd3c486c|fix: Prevent Table Overflow and Clean Up CSS (#1876) (#1876)|android/app/src/main/assets/css/index.css +7abee71ac33ab4b16c0c2a629640b1a7c2f5eb3b|test: Generate Debug Environment Variables In Testing Workflow| +52d5c99c46b14148bd262c878701abf91c8b593b|fix: Order EPUB Chapters Across Paginated Pages (Closes #1762)|src/database/queries/ChapterQueries.ts +93bc5e5e4bac14fea10913cb215a7d9c297d6c31|fix: Handle Missing MAL List Entries and Null Progress (Closes #1875)| +5d996f13883219090ad39ddbe7b52e88e36107c7|feat(downloads): Make Chapter Download Cooldown Configurable (#1834)|src/hooks/persisted/useSettings.ts,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,strings/languages/en/strings.json +bbb2645a6f6c47f7cda8d83ef096c329e03ffa6a|chore: Clean Reader Preview Content (#1867)| +54fedc2f531ecdda29dea74f3cc279a7a4525261|fix(tts): Remove Leading and Trailing Quotes from Normalized Text (#1869)|android/app/src/main/assets/js/core.js +e8c5550256dfe06c5ae12c106801e8688173a830|perf: Optimize Regex for Removing Extra Paragraph Spacing (#1872)|android/app/src/main/assets/js/core.js +57b9d41b5c3eacc9cbf5e38082c4efb2a1a11f5e|fix(service): Correct Notification Throttle Logic in ServiceManager.setMeta (#1873)|src/services/ServiceManager.ts +34be7a123499cc7733161482fdf5d42e4c1bae51|fix(native-file): prevent fatal crash when unlinking missing files (#1832)|android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt +60f42ed6243dc5d4daa68c727d60c9fb5dd6576a|chore(build): patch @rock-js/plugin-metro for Windows compatibility (#1830)|android/settings.gradle,pnpm-lock.yaml,pnpm-workspace.yaml +25c93afabe24d34f2130ee0fd7e0846a9b4861ff|fix(reader): restore saved chapter progress from db if available (#1850)|src/screens/reader/hooks/useChapter.ts +2cd5a97ee8e482e92b45e9b702c9ba974e06a092|fix: await database operations inside transactions (#1845)|src/database/queries/CategoryQueries.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts +8f47e8fd1e1aedaf22df8275484f27b5d40b2a39|Theme switcher refactor (#1839)|App.tsx,package.json,pnpm-lock.yaml,src/components/BottomTabBar/index.tsx,src/components/ColorPickerModal/ColorPickerModal.tsx,src/components/List/List.tsx,src/components/SegmentedControl/SegmentedControl.tsx,src/components/ThemePicker/ThemePicker.tsx,src/hooks/persisted/index.ts,src/hooks/persisted/useTheme.ts,src/screens/onboarding/ThemeSelectionStep.tsx,src/screens/settings/SettingsAppearanceScreen/LanguagePickerModal.tsx,src/screens/settings/SettingsAppearanceScreen/SettingsAppearanceScreen.tsx,src/theme/utils/setBarColor.ts +eafa545b2a34320b5e1eb2d99ba61b6888838da8|Use novel refactor (#1838)|package.json,pnpm-lock.yaml,src/database/db.ts,src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/hooks/persisted/index.ts,src/hooks/persisted/useNovel.ts,src/hooks/persisted/useTheme.ts,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/PageNavigationBottomSheet.tsx,src/screens/novel/NovelScreen.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/ReaderAppbar.tsx,src/screens/reader/components/ReaderFooter.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/settings/SettingsAdvancedScreen.tsx,src/screens/settings/SettingsReaderScreen/tabs/NavigationTab.tsx,src/screens/updates/UpdatesScreen.tsx,src/services/migrate/migrateNovel.ts,strings/languages/en/strings.json,strings/types/index.ts +b680a07750f0b7650836c0573566e641d4aaa679|fix(browse): use gesture-handler Pressable in PluginListItem (#1828)|src/screens/browse/components/PluginListItem.tsx +2a919ec0f963a4d4613e3e0ebe99a16fa7e2dbe3|chore: gitignore tsconfig.tsbuildinfo build cache (#1829)|.gitignore +01596c045a4637acf056f75cf87ffc0198d79899|fix: Apply active filters to chapter count display (Closes #1793) (#1797)|src/database/queries/ChapterQueries.ts,src/hooks/persisted/useNovel.ts +3de93c5c093ab2fb7e0d509e367e4fec813082e2|feat: migrate from rn community cli to rock (#1812)|.gitignore,android/app/build.gradle,android/settings.gradle,babel.config.js,package.json,pnpm-lock.yaml,src/screens/more/About.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,tsconfig.json +306f5b27e2716f1a963fd992c06f5c961f44c926|fix: install the right version of hermes compiler|package.json,pnpm-lock.yaml +06f0f66e3e4659db14127b1b56202dd3a8fca521|fix: Add hermes-compiler dev dep|package.json,pnpm-lock.yaml +64921a054e9d4a11a0aad294b9703a3ab4c16b9f|feat: Expo 55 upgrade|.gitignore,android/app/src/main/AndroidManifest.xml,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainApplication.kt,android/build.gradle,android/gradle/wrapper/gradle-wrapper.properties,android/settings.gradle,babel.config.js,package.json,pnpm-lock.yaml,src/components/BottomSheet/BottomSheet.tsx,src/components/List/List.tsx,src/database/db.ts,src/hooks/persisted/useNovel.ts,src/plugins/helpers/storage.ts,src/screens/Categories/components/CategoryCard.tsx,src/screens/more/About.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderSheetPreferenceItem.tsx,src/screens/settings/SettingsAdvancedScreen.tsx +44e035ad38b25f79741199b6aea64479831ccc14|chore: Update Translations (#1683)| +1803cd071d24a4d8ada8c55814a6ce62eaf98481|feat: Add testing support for react code (#1763)|__mocks__/react-native-mmkv.js,babel.config.js,package.json,pnpm-lock.yaml,src/hooks/persisted/useNovel.ts,src/services/migrate/migrateNovel.ts,tsconfig.json +e4d7ba306b4441b4743ab9edb7c96751d62e3cde|fix: Safety level may not be changed inside a transaction error (#1758)|src/database/db.ts +cc0428752b2e9869677a6bd2dccc449f7a98a552|feat: Replace TTS Notifications with Android MediaSession Controls (Closes #1711)|android/app/build.gradle,android/app/src/main/assets/js/core.js,android/app/src/main/java/com/rajarsheechatterjee/LNReader/MainApplication.kt,App.tsx,src/screens/reader/components/WebViewReader.tsx +b8c177bc55016b5a8fb96cc120e8ac050a2f7c73|refactor: Migrate to Drizzle and `op-sqlite` (#1735)|.gitignore,App.tsx,babel.config.js,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,src/components/ListView.tsx,src/components/NovelCover.tsx,src/database/db.ts,src/database/migrations/002_add_novel_counters.ts,src/database/migrations/index.ts,src/database/queries/__tests__/CategoryQueries.test.ts,src/database/queries/__tests__/HistoryQueries.test.ts,src/database/queries/__tests__/NovelQueries.test.ts,src/database/queries/__tests__/RepositoryQueries.test.ts,src/database/queries/__tests__/StatsQueries.test.ts,src/database/queries/CategoryQueries.ts,src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/database/queries/StatsQueries.ts,src/database/tables/ChapterTable.ts,src/database/types/index.ts,src/database/utils/migrationRunner.ts,src/hooks/common/useDatabaseInitialization.ts,src/hooks/common/useGithubUpdateChecker.ts,src/hooks/index.ts,src/hooks/persisted/useNovel.ts,src/hooks/persisted/useSettings.ts,src/navigators/types/index.ts,src/screens/browse/components/AvailableTab.tsx,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/Categories/CategoriesScreen.tsx,src/screens/Categories/components/AddCategoryModal.tsx,src/screens/Categories/components/CategoryCard.tsx,src/screens/history/HistoryScreen.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/EditInfoModal.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/NovelBottomSheet.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/SetCategoriesModal.tsx,src/screens/novel/NovelScreen.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/hooks/useChapter.ts,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsAdvancedScreen.tsx,src/screens/settings/SettingsGeneralScreen/SettingsGeneralScreen.tsx,src/screens/settings/SettingsRepositoryScreen/components/DeleteRepositoryModal.tsx,src/screens/settings/SettingsRepositoryScreen/SettingsRepositoryScreen.tsx,src/screens/settings/SettingsTrackerScreen.tsx,src/screens/updates/components/UpdateNovelCard.tsx,src/services/backup/utils.ts,src/services/migrate/migrateNovel.ts,src/services/Trackers/mangaUpdates.ts,tsconfig.json +7083c51dea1be0af3bb0d53c341f19b32a35dca4|fix: Include selectedIds in Chapter List extraData for Selection Highlight (Closes #1750)|src/screens/novel/components/NovelScreenList.tsx +f0693204e1f8f961dff85b195f3c7f4f6f9bb0ce|fix: Improve Next Chapter Selection Logic (#1744)|src/database/queries/ChapterQueries.ts +3f8071e48ee603f906cc9e3910abe90d6e3874a5|chore: Release v2.0.3|android/app/build.gradle,package.json +a1c5350e8e330e783b6537c366351c2082e99150|fix: Miscellaneous UI Tweaks|src/components/List/List.tsx,src/screens/Categories/components/CategoryCard.tsx,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/updates/components/UpdateNovelCard.tsx +8f237909d54310220d518000ace81b5fddbc3b5f|feat: Show First Unread Chapter in ReadButton and FAB When No Last Read|src/database/queries/ChapterQueries.ts,src/hooks/persisted/useNovel.ts,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/NovelScreenList.tsx +11ff69d4f5b4a79286247ac6c40ca1eac72ad283|fix: Pre-Fetch Adjacent Page Chapters in Reader and Library Updates|src/components/BottomSheet/BottomSheet.tsx,src/screens/reader/hooks/useChapter.ts +d15cf987425709f97ec168a46af8d936e944c29e|fix: Order Chapter Pages Numerically in Queries for Accurate Retrieval (Closes #1742)|src/database/queries/ChapterQueries.ts +29bd6612e9a9950c61b9a569b761ffa1aaa5d4eb|refactor: Optimize Library Screen Performance and Fix Category Bug|src/components/NovelCover.tsx,src/components/NovelList.tsx,src/screens/GlobalSearchScreen/components/GlobalSearchResultsList.tsx,src/screens/library/components/LibraryBottomSheet/LibraryBottomSheet.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx +ab1e36d280768be46acdb57557e09b3e2d37f12b|refactor: Update Components to Follow Material Design 3 Guidelines|src/components/Actionbar/Actionbar.tsx,src/components/BottomSheet/BottomSheet.tsx,src/components/BottomTabBar/index.tsx,src/components/Chip/Chip.tsx,src/components/ConfirmationDialog/ConfirmationDialog.tsx,src/components/IconButtonV2/IconButtonV2.tsx,src/components/List/List.tsx,src/components/Menu/index.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/components/SegmentedControl/SegmentedControl.tsx,src/components/Switch/Switch.tsx,src/components/Switch/SwitchItem.tsx,src/screens/more/MoreScreen.tsx,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderSheetPreferenceItem.tsx +f1fdafd37f1f7d0816484c2e414e39d9885499d8|fix: Prevent Tab Bar Height Jump and Shadow Flash on Browse Screen|src/screens/browse/BrowseScreen.tsx +4c7684a3a71e57fb27f910b3edfbdd902353dd47|refactor: Optimize Novel Screen Performance and Fix Download State Tracking|.gitignore,src/hooks/persisted/useNovel.ts,src/screens/novel/components/Chapter/ChapterDownloadButtons.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/Info/NovelInfoComponents.tsx,src/screens/novel/components/Info/NovelInfoHeader.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/NovelScreenList.tsx,src/screens/novel/components/PagePaginationControl.tsx,src/screens/novel/NovelScreen.tsx,src/screens/updates/components/UpdateNovelCard.tsx +2f45db659b2fdfa77f9213023d9d2707cbc452e1|fix: Replace Sync Database Operations With Async to Prevent Database Lock (Closes #1716)|src/database/queries/ChapterQueries.ts,src/database/queries/NovelQueries.ts,src/hooks/persisted/useNovel.ts,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/Chapter/ChapterDownloadButtons.tsx,src/services/migrate/migrateNovel.ts +9e38ad114d182d39880a962cb7acefa55235310d|fix: Sanitize EPUB FileName to Remove Filesystem-Unsafe Characters (Closes #1480)|src/screens/novel/components/ExportNovelAsEpubButton.tsx +45c4ea8ca02031c803224b0d906a6045364d2817|fix: Handle IOException in NativeFile to Prevent EPUB Import Crash Loop (Closes #1315)|android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt +d2b95ae3166f337d8d3597e1cc9a0bff65832d62|fix: Use NativeFile for Custom CSS/JS Import to Fix Content URI Read Failure (Closes #1661)|src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx +02121152b3e3cdbbaaff8a34b51cb8e9b2010538|fix: Stabilize Scroll Handler References to Prevent Infinite Re-render Loop (Closes #1679)|src/screens/browse/discover/MalTopNovels.tsx,src/screens/novel/components/NovelScreenList.tsx +caed965b6bad1b788eee3035daba42322454fe26|fix: Prevent EPUB Import Crash from DocumentPicker Cache Cleanup (Closes #1725)|src/screens/library/LibraryScreen.tsx +4088d5966bbc5ec27801b1ddc553f5621b4b6d79|fix: Close OkHttp Response and Streams in NativeFile.downloadFile (Closes #1740)|android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt +a062beee87d4173383e3033eeacb9376642645e5|fix: Add Max Height to Set Categories Modal (Closes #1738)|src/screens/novel/components/SetCategoriesModal.tsx +30958825162fbba2f7b805de6d41ab8e9183f28b|Replace react-native-paper menus with custom implementation (#1733)|src/components/Menu/index.tsx,src/screens/browse/components/Modals/SourceSettings.tsx,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/more/TaskQueueScreen.tsx,src/screens/novel/components/Chapter/ChapterDownloadButtons.tsx +4ad06397960f1fbc633604ef06c2dfca193a220e|Fix: Chapter loading for jump to chapter modal and chapter drawer|pnpm-lock.yaml,src/database/queries/ChapterQueries.ts,src/hooks/persisted/useNovel.ts,src/screens/novel/components/JumpToChapterModal.tsx,src/screens/novel/NovelScreen.tsx,src/screens/reader/components/ChapterDrawer/index.tsx +b53ec3352f8ce9084e32ddaa352526b39ec6003c|feat: Support Additional Plugin Setting Types (Dropdown and Checkboxes)|src/plugins/types/index.ts,src/screens/browse/components/Modals/SourceSettings.tsx +c40edd5b2cc68c2b746ba588aa39e142ab99f01e|feat: Add Kitsu Tracker (Closes #416)|src/screens/novel/components/Tracker/ScoreSelectors.tsx,src/screens/novel/components/Tracker/TrackerCards.tsx,src/screens/settings/SettingsTrackerScreen.tsx +bdd42caaaf3c3d5961d444aabc9150057d2de5d9|chore: Update Repository Links to Use lneader Instead of LNReader|README.md,src/screens/more/About.tsx,strings/languages/en/strings.json +ab4eee31ceb82c3f89e459279bef8b56de5ec10c|feat: Allow All Safe Tags and Attributes When Sanitizing Chapter HTML (Closes #1715)|src/screens/reader/utils/sanitizeChapterText.ts +3f9886a2e5300bb9913888872847727ab63597fc|chore: Update Repository Links to Use lneader Instead of LNReader|README.md,src/screens/more/About.tsx,strings/languages/en/strings.json,strings/types/index.ts +198b7443ae9c0ee7cc3ff608c0fc89a88880fa84|feat: Add RTL (Right-to-Left) Language Support (#1717)|src/components/Checkbox/Checkbox.tsx,src/components/Chip/Chip.tsx,src/components/ColorPreferenceItem/ColorPreferenceItem.tsx,src/components/List/List.tsx,src/components/ListView.tsx,src/components/Menu/index.tsx,src/components/RadioButton.tsx,src/components/RadioButton/RadioButton.tsx,src/components/SearchbarV2/SearchbarV2.tsx,src/components/Switch/SwitchItem.tsx,src/components/ThemePicker/ThemePicker.tsx,src/hooks/common/useGithubUpdateChecker.ts,src/navigators/BottomNavigator.tsx,src/screens/browse/components/PluginListItem.tsx,src/screens/browse/components/PluginListItemSkeleton.tsx,src/screens/browse/migration/MigrationSourceItem.tsx,src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx,src/screens/Categories/components/CategoryCard.tsx,src/screens/GlobalSearchScreen/components/GlobalSearchResultsList.tsx,src/screens/history/components/HistoryCard/HistoryCard.tsx,src/screens/library/hooks/useLibrary.ts,src/screens/library/LibraryScreen.tsx,src/screens/novel/components/ChapterItem.tsx,src/screens/novel/components/NovelAppbar.tsx,src/screens/novel/components/PageNavigationBottomSheet.tsx,src/screens/novel/components/PagePaginationControl.tsx,src/screens/novel/components/Tracker/TrackSearchDialog.tsx,src/screens/novel/components/Tracker/TrackSheet.tsx,src/screens/reader/components/ChapterDrawer/index.tsx,src/screens/reader/components/ReaderAppbar.tsx,src/screens/reader/components/ReaderBottomSheet/ReaderFontPicker.tsx,src/screens/reader/components/ReaderBottomSheet/TTSTab.tsx,src/screens/reader/components/WebViewReader.tsx,src/screens/reader/ReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/components/TabBar.tsx,src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx,src/screens/settings/SettingsReaderScreen/tabs/AdvancedTab.tsx,src/screens/settings/SettingsTrackerScreen.tsx,src/screens/updates/components/UpdateNovelCard.tsx +f9c5ca303d71830811a203f98a17c3dd4846f887|chore: Update Indonesian Translations (Closes #1653)| +9922d4394058a6da29ab2789cd8d7b75a37bf1bc|fix: Remove Missing Image References During EPUB Export (Closes #1696)|src/screens/novel/components/ExportNovelAsEpubButton.tsx +ae99b077d4e883e678b31ba0235a7abae88e1e94|feat: Add `@noble/ciphers` for Plugins (Closes #1709)|package.json,pnpm-lock.yaml +0cb9da90276c6a30d5d917557195f0f0b0ebc70c|refactor: Replace `withTransactionAsync` With `withExclusiveTransactionAsync` for Improved Concurrency Handling (Closes #1636)|src/database/queries/CategoryQueries.ts,src/database/queries/NovelQueries.ts,src/database/queries/StatsQueries.ts,src/database/utils/helpers.tsx,src/services/migrate/migrateNovel.ts diff --git a/specs/upstream-merge-analysis-2026-08/reference/fork_context.md b/specs/upstream-merge-analysis-2026-08/reference/fork_context.md new file mode 100644 index 0000000000..d3c4a1b93d --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/reference/fork_context.md @@ -0,0 +1,45 @@ +# Fork Architecture Context (for merge-value analysis) + +## Fork (branch `dev`, HEAD 40c6676797 v2.1.3, 2026-08-03) +- React Native 0.82.1, React 19.1.1, Expo SDK 54 (bare workflow w/ custom Kotlin native modules) +- **TTS (fork's crown jewel, heavily customized)**: + - WebViewReader.tsx (controller), useTTSController.ts (state machine), TTSAudioManager.ts, TTSState.ts, ttsBridge.ts + - Native: TTSHighlightModule.kt, TTSForegroundService.kt (custom Kotlin, NOT Nitro) + - Features: background playback, per-paragraph highlight, sleep timer, smart rewind, text cleanup pipeline (htmlParagraphExtractor.ts), per-novel TTS overrides, 5-button media notification (ttsNotification.ts), chapter list live progress sync +- **Database**: expo-sqlite `SQLite.openDatabaseSync` + raw SQL table files (src/database/tables/*) + custom MigrationRunner (src/database/utils/migrationRunner.ts) + migrations/ — NOT Drizzle +- **Settings**: MMKV via src/hooks/persisted/useSettings.ts (AppSettings interface, ChapterGeneralSettings/ChapterReaderSettings) +- **Reader UI**: WebView (core.js at android/app/src/main/assets/js/core.js), ReaderBottomSheetV2 → ReaderTTSTab.tsx (custom) +- **UI lib**: react-native-paper 5.14.5 +- **Plugins**: src/plugins/pluginManager.ts dynamic loading, plugin types in src/plugins/types/ +- **Build**: Gradle 9.2.0, Java 17, pnpm; scripts dev:start / dev:android + +## Upstream (`original` = upstream/master, c3260e8e01, 2026-08-01) +- Migrated to **Expo managed workflow** (#1885), Expo 55, rock CLI (#1812) +- **Database**: **Drizzle ORM + op-sqlite** (#1735) — completely different layer (src/database/schema, manager/) +- **TTS**: **Nitro-powered playback** (#1896), Android **MediaSession** controls (#1711) — different native stack +- **Background tasks**: native Android scheduling (#1889, replaced react-native-background-actions) +- Repository reorganized (916374b5df) + +## Fork-critical files (heavily rewritten in fork — upstream changes here are RED risk) +- src/screens/reader/components/WebViewReader.tsx +- src/screens/reader/hooks/useTTSController.ts, useTTSUtilities.ts +- src/services/TTSAudioManager.ts, TTSState.ts, ttsBridge.ts, ttsNotification.ts +- src/hooks/persisted/useSettings.ts +- src/database/* (db.ts, tables/, queries/, utils/migrationRunner.ts) +- src/screens/reader/components/ReaderBottomSheet/ (fork: ReaderTTSTab.tsx; upstream file structure differs) +- android/app/src/main/assets/js/core.js, index.js +- android/app/src/main/java/com/rajarsheechatterjee/LNReader/*.kt +- android/app/build.gradle, package.json + +## Prior analysis (memory bank 2025-12-30) +Full merge infeasible (100+ conflicts). RECOMMENDED: selective cherry-pick / manual port of beneficial upstream features. + +## Assessment rules +For each commit classify: +- VALUE: HIGH (clear user benefit, missing in fork) / MEDIUM (nice-to-have) / LOW / NONE (chore/docs/infra) / ALREADY-HAVE (fork has own equivalent e.g. DoH, media notification, volume buttons, text cleanup, migration recovery) +- RISK if cherry-picked directly: GREEN (new files or files fork didn't touch) / YELLOW (some overlap, localized, portable) / RED (touches fork-critical files or depends on Drizzle/Nitro/Expo55/rock infrastructure) +- Portability: DIRECT (cherry-pick clean) / MANUAL (needs adaptation to fork architecture) / NOT-FEASIBLE (depends on upstream-only infra) +- One-line rationale each. + +Use `git show --stat` / `git show ` (read-only) when you need diff details. +Output as a markdown report with a table per commit + group summary (top valuable, notable risks). diff --git a/specs/upstream-merge-analysis-2026-08/reference/subagent/group1-report.md b/specs/upstream-merge-analysis-2026-08/reference/subagent/group1-report.md new file mode 100644 index 0000000000..04f0dfde65 --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/reference/subagent/group1-report.md @@ -0,0 +1,91 @@ +# Group 1 Upstream Commit Analysis — Selective Adoption for LNReader Fork (bizzkoot/lnreader) + +**Analysis date:** 2026-08-03 · **Branch:** dev @ 40c667679 (v2.1.3) · **Upstream analyzed:** original/master @ c3260e8e0 (2026-08-01) +**Method:** read-only `git show --stat` / `git show` of all 43 commits + direct inspection of fork's current source to verify ALREADY-HAVE, overlap, and bug-parity. + +**Headline verification result:** Several upstream "fix" commits were made against code that the fork still carries **byte-for-byte identical to the upstream pre-fix state** (verified: SelectableChip.tsx, NovelInfoHeader getStatusIcon, NovelStatus enum, deleteDownloads/deleteReadChaptersFromDb in ChapterQueries.ts, Category `sort = 1` lookups, NovelTable triggers `MAX(updatedTime)`). These fixes port trivially with high confidence. + +--- + +## Per-commit assessment (43 commits) + +| commit (short) | subject | VALUE | RISK | PORT | rationale | +|---|---|---|---|---|---| +| c3260e8e0 | fix: Resolve Chapter Download Queue and Removal Issues (#1937) | **HIGH** | YELLOW | MANUAL | Fork's `deleteDownloads` (src/database/queries/ChapterQueries.ts:167-173) has the exact pre-fix bugs: un-awaited `deleteDownloadedFiles`, and `UPDATE Chapter SET isDownloaded = 0` **without WHERE** (marks every chapter undownloaded). Worse, fork's `deleteReadChaptersFromDb` (line 177-184) passes `chapter.novelId` as the chapter id → **deletes the wrong folder**. Port the DB/file-deletion fixes (WHERE id IN, await Promise.all, correct chapter.id) + undownloaded-chapter ordering to raw SQL. Skip upstream's backgroundTasks/taskDefinitions/useDownloadReconciliation parts (fork uses ServiceManager + MMKV queue). | +| c0877a9b0 | feat: Add Ignorable App Update Notifications | **HIGH** | YELLOW | MANUAL | Long-requested (upstream #600/#1632): lets users permanently dismiss an update prompt. Fork's NewUpdateDialog has no dismiss option. Fork's `useGithubUpdateChecker.ts` is a near-clone of upstream's deleted hook (fork points at bizzkoot repo). Port AppUpdateChecker/useAppUpdateChecker + "ignore this version" MMKV persistence; keep fork's repo URL/asset picker (`githubReleaseUtils.pickApkAsset`). | +| 96fb52df1 | feat: Enhance Stats With Genre Taxonomy and Charts (#1919) | **HIGH** | RED | MANUAL | Major user-facing upgrade: StatsScreen tabs (Overview/Time/Plugins), SVG donut charts, genre taxonomy screen with per-genre novel carousels (31 files, +2939/-719). Requires new dep `react-native-svg` (fork lacks it) and a rewrite of fork's raw-SQL StatsQueries.ts (upstream part is Drizzle). UI components are new files (low conflict), but stats queries + i18n key types + navigators must be adapted. High effort, high payoff. | +| 76106317b | fix: Restore Midnight Dusk Chip Contrast (#1939) | MEDIUM | GREEN | DIRECT | 1-line deletion (`selectedColor={theme.primary}`) in SelectableChip.tsx. Fork's file is **byte-identical** to upstream pre-fix (verified). Cherry-picks cleanly. Cosmetic bugfix for dark theme chip contrast. | +| e75f4fba5 | feat: Add More Novel Statuses and Icons (#1931) | MEDIUM | YELLOW | MANUAL | Adds `STUB` + `Inactive` statuses and full status→icon map. Fork's NovelStatus enum + `getStatusIcon` (NovelInfoHeader.tsx:72-80) match upstream pre-fix exactly — small localized edits. Skip `.husky/pre-push` hunk (fork has own hook file). | +| 4dc933ee5 | fix: Remove White Flash During Screen Transitions (#1926) (#1927) | MEDIUM | YELLOW | MANUAL | ThemedRootView paints root with theme background; ReaderStack gives Chapter screen reader background. Fork App.tsx has same GestureHandlerRootView structure (line 115). The `plugins/withAndroidCustomizations.js` config-plugin hunk is upstream-only (fork is bare workflow, no `plugins/` dir) → replace with a native `android/app/src/main/res` windowBackground change. | +| bfb12b04a | feat: Add Configurable Automatic Backup Scheduling | **HIGH** | RED | NOT-FEASIBLE | Automatic scheduled backups = real user value (fork backup is manual-only). But native side is upstream-only infra: BackgroundTaskScheduler.kt / AutomaticBackupScheduler.kt / expo `native-file` module changes. `useSettings.ts` addition is additive (4 new fields) but the scheduler has no fork equivalent (fork uses react-native-background-actions + ServiceManager). Would need a from-scratch reimplementation to port. | +| b7639ab55 | feat: Upload JavaScript Source Maps (#1924) | LOW | YELLOW | MANUAL | Dev-facing crash-debugging aid (source map upload + AppErrorBoundary changes). Fork has its own AppErrorBoundary.tsx and different build/CI (Gradle 9.2.0, own workflows). Low value; not recommended. | +| ecc5b84a6 | chore: Release v2.1.2 | NONE | GREEN | NOT-FEASIBLE | Version bump + issue-template text. Fork is on its own v2.1.3 numbering. Skip. | +| eb12bdfd6 | fix: Prevent Novel Genre Rendering Crashes | **HIGH** | YELLOW | MANUAL | Crash fix: `parseGenres()` helper (splits on comma, handles null/empty) applied to EditInfoModal + NovelInfoComponents. Fork's components use the same fragile `genres?.split(',')` pattern (verified) → same crash risk on malformed genre data. Port util + hardened components. | +| 9612bf6f8 | chore: Update Translations (#1928) | LOW | YELLOW | MANUAL | 35 language JSON updates (Crowdin). Fork stores translations at `strings/languages/` (vs upstream `src/i18n/languages/`) → mechanical merge, not cherry-pickable. Only worthwhile if fork cares about non-English parity; conflicts with fork-local string keys possible. | +| 0ed7d878b | fix: Prevent White Reader Drawer Seam | LOW | YELLOW | MANUAL | 2-line fix: `drawerStyle={{backgroundColor:'transparent'}}` on reader Drawer. ReaderScreen.tsx is fork-critical but the addition is a tiny isolated hunk. Cheap cosmetic fix; safe to apply. | +| 5f45e4c7f | fix: Recover Interrupted Chapter Migrations | NONE | RED | NOT-FEASIBLE | Drizzle `migration.sql` + `src/database/db.ts`. Fork uses expo-sqlite + custom MigrationRunner and already has its own migration recovery (ALREADY-HAVE concept). No user value to port. | +| ca24671a2 | fix: Support Fork-Safe Preview Builds | NONE | GREEN | NOT-FEASIBLE | GitHub Actions workflow tweak (upstream's own CI). Fork has separate CI. Skip. | +| ee3a4f20b | fix: Preserve Default Category After Reordering (#1922) | **HIGH** | YELLOW | MANUAL | Real bug present in fork: default-category resolution via `(SELECT id FROM Category WHERE sort = 1)` in NovelQueries.ts:114,138,201,301 and CategoryQueries.ts:47 — breaks once categories are reordered; `updateCategoryOrderQuery` also writes `c.sort` values instead of positional. Port = introduce `BUILT_IN_CATEGORY_IDS` (default=1, local=2) and write `sort = index+1`. Verified fork hardcodes id 2 for Local already, so concept maps cleanly. | +| 0c3542836 | fix: Synchronize Library Updates And Optimize Import Refreshes | MEDIUM | YELLOW | MANUAL | Fixes text-date comparisons via `julianday()` in triggers + `lastUpdatedAt ASC/DESC` sort rewrite; ISO-8601 timestamp format for inserted chapters. Fork's NovelTable.ts triggers (lines 43/56/69) and LibraryQueries.ts sortOrder path are identical pre-fix (verified) → same latent bug when chapter updatedTime formats mix. Port trigger + sort-order hunks to raw SQL. | +| f71c9c859 | fix: Ignore Discord Notifications Outside Main Repository | NONE | GREEN | NOT-FEASIBLE | CI workflow 1-liner. Skip. | +| f53546cc0 | chore: Release v2.1.1 | NONE | GREEN | NOT-FEASIBLE | Version bump. Skip. | +| 594b078fa | fix: Harden Database Migration Recovery | NONE | RED | NOT-FEASIBLE | Drizzle `db.ts` + migration SQL. Fork has its own MigrationRunner + recovery (ALREADY-HAVE). No value. | +| ac1b2f5ce | feat: Add Durable Completion Notifications | LOW | RED | NOT-FEASIBLE | Built on upstream-only `native-background-tasks` Expo module (TaskNotificationFactory, WorkManager). Fork has no such infra (MMKV queue + ServiceManager). Low value; skip. | +| 648d21062 | feat: Add Parallel Task Queues With Per-Plugin Download Limits | LOW | RED | NOT-FEASIBLE | Depends on upstream native BackgroundTaskDao/scheduler + `src/services/backgroundTasks` contract changes (includes migrateNovel.ts 1-line hunk). Concept (per-plugin download limits) is nice but requires reimplementation on fork's ServiceManager queue. Not worth direct port. | +| 89d715ce6 | docs: Replace Beta Badge With Build Preview Status | NONE | GREEN | NOT-FEASIBLE | README text. Skip. | +| bd527a02e | feat: Improve Backup Selection, Notifications, And Plugin Restore | **HIGH** | RED | MANUAL | Backup include-options selection, plugin restore from backup, richer results — genuine user value. But +2021/-249 across 31 files heavily overlapping fork's backup stack (SettingsBackupScreen/index.tsx, GoogleDriveModal, SelfHostModal, Checkbox.tsx, services/backup/*), and notification parts depend on upstream backgroundTasks contracts. Port only the selection/options/plugin-restore logic; high effort. | +| 11117c1b5 | feat: Add GitHub-Style Embed For Nightly Notifications | NONE | GREEN | NOT-FEASIBLE | CI workflow only. Skip. | +| ba25d5b8f | feat: Add Rozenite DevTools Integration (#1912) | NONE | RED | NOT-FEASIBLE | op-sqlite adapter + metro config + 1828-line lockfile churn. Fork uses expo-sqlite (`SQLite.openDatabaseSync`) — no op-sqlite. Zero value. | +| 31cb4b994 | fix: Align Bottom Navigation With Material 3 Specs | LOW | GREEN | DIRECT | `paddingBottom: 16 + (insets?.bottom || 0)` + remove `paddingVertical: 6`. Fork's BottomTabBar/index.tsx:65 has identical pre-fix line (verified). 2-line cosmetic change; cherry-picks cleanly. | +| 72ef618af | fix: Preserve Existing Data During Novel Counter Migration | NONE | RED | NOT-FEASIBLE | Drizzle migration SQL. Fork's own `migrations/002_add_novel_counters.ts` already preserves data (ALREADY-HAVE). Skip. | +| 936fd223b | feat: Add Discord Notifications For Nightly Builds | NONE | GREEN | NOT-FEASIBLE | CI workflow. Skip. | +| 6d26438d2 | test: Update Expected Database Migrations | NONE | RED | NOT-FEASIBLE | Test/docs for Drizzle migration list (AGENTS.md hunk). Skip. | +| 5eb5a55c4 | fix: Repair Skipped Novel Counter Migrations | NONE | RED | NOT-FEASIBLE | Drizzle migration SQL + migrations.js. Fork's runner applies its own counter migration. Skip. | +| 6cd86d142 | chore: Update Translations (#1906) | LOW | YELLOW | MANUAL | Same as 9612bf6f8 — 33 language files, different path in fork (`strings/languages/`), mechanical merge only. | +| 9783c4d5e | fix: Normalize Repository Back Navigation (#1905) | LOW | YELLOW | MANUAL | Removes `useBackHandler` popToTop weirdness on repository screen. Fork's SettingsRepositoryScreen.tsx exists (overlap) and uses same navigator types. Small deletion; low value. | +| 87f602a1f | chore: Release v2.1.0 | NONE | GREEN | NOT-FEASIBLE | Version bump. Skip. | +| 3d32ddd6e | fix: Improve Release Selection And APK Verification | NONE | GREEN | NOT-FEASIBLE | Release workflow + scripts/resolve-release-version.cjs (fork doesn't have this script). Skip. | +| 7ddd934c2 | fix: Handle Missing Tracker Client IDs | MEDIUM | YELLOW | MANUAL | Guard clauses in aniList.ts / myAnimeList.ts so missing client ID throws a clear error instead of building a broken auth URL. Fork's tracker files exist (src/services/Trackers/) and share structure; `scripts/generate-env-file.cjs` hunk doesn't apply (fork uses `.env` + env.d.ts) → port only the two tracker-file guards. | +| b53c91f74 | chore: Separate Jest And Type Check Workflows | NONE | GREEN | NOT-FEASIBLE | CI + package.json script renames. Skip. | +| 9d106a371 | feat: Add Semantic Version Bump Selection | NONE | GREEN | NOT-FEASIBLE | Release workflow + missing script. Skip. | +| 8b0a8dc4c | chore: Update Husky Pre-Commit and Pre-Push Hooks | NONE | RED | NOT-FEASIBLE | package.json + .husky + NativeVolumeButtonListenerModule (fork uses its own volume module). Bundled 3-line useTimeTracking change is negligible. Skip. | +| 72bfcad03 | fix: Resolve Type and Lint Issues | LOW | RED | NOT-FEASIBLE | 40+ file lint/type cleanup including **WebViewReader.tsx (39 lines), ReaderScreen.tsx, ChapterContext.tsx, useLibrary.ts** — all fork-critical, heavily rewritten in fork. Zero user value, maximal conflict surface. **Do not port.** | +| 345d084eb | feat: Add Chapter Numbers To EPUB Titles (#1405) | **HIGH** | YELLOW | MANUAL | User-requested export feature: `epubIncludeChapterNumber` setting + numbered-title composition. Fork's ExportEpubModal/ExportNovelAsEpubButton exist with same structure but use `@cd-z/react-native-epub-creator` (JS) vs upstream nitro-epub → port the option plumbing + title logic only. `useSettings.ts` change is one additive field. | +| 10b6042a9 | feat: EPUB Export Progress Notifications | LOW | RED | NOT-FEASIBLE | nitro-epub C++ (HybridEpub.cpp/hpp, EpubExporter) + native background tasks. Fork's EPUB export is synchronous JS — different mechanism entirely. Progress-notification concept would need a new implementation. Skip. | +| bd7161172 | feat: Improve Chapter Drawer And WebView App Bar | MEDIUM | RED | MANUAL | Drawer UX improvements incl. a genuine bugfix: `chapters.findIndex(el => el.id === chapter.id) || 0` mis-fallbacks to 0 on chapter 0... actually returns 0 when index is 0 anyway; the real fix is the `index >= 0 ? index : 0` fallback + scroll-to-current math. But fork's ChapterDrawer uses `@legendapp/list` LegendList (upstream uses FlashList) and ReaderScreen.tsx is fork-critical → port only the index-calculation logic manually. App-bar hunk (WebviewScreen) is lower risk. | +| 8f53550d2 | fix: Restore Tracker Search Requests | **HIGH** | YELLOW | MANUAL | Fixes broken tracker search-on-open (scheduleSearch bypassed; submit handler used stale `searchText` state). Fork's TrackSearchDialog.tsx is fork-modified (overlap) and likely carries the same broken flow. Port the `useEffect` reschedule + `onSubmitEditing` event-based handler + `returnKeyType="search"`. | + +--- + +## Group Summary + +### (a) Top 5 most valuable commits for the fork + +1. **c3260e8e0 — Chapter Download Queue and Removal Issues** (HIGH). The fork literally contains the bug being fixed: `deleteReadChaptersFromDb` passes `chapter.novelId` as the chapter id (deletes the wrong folder), and `deleteDownloads` runs `UPDATE Chapter SET isDownloaded = 0` with no WHERE clause (wipes download flags across the whole DB). Users deleting one novel's downloads lose flags everywhere and orphan files. The DB part ports in ~20 lines of raw SQL. **Highest risk/benefit ratio in the group.** + +2. **ee3a4f20b — Preserve Default Category After Reordering** (HIGH). The fork resolves the default category via `WHERE sort = 1` in four places (NovelQueries.ts:114,138,201,301; CategoryQueries.ts:47) and writes `c.sort` on reorder. After any category reorder, new novels silently land in the wrong category. The fix (built-in IDs + positional sort) is small and maps directly onto the fork's hardcoded `id = 2` Local-category convention. + +3. **eb12bdfd6 — Prevent Novel Genre Rendering Crashes** (HIGH). A malformed/empty genre string crashes EditInfoModal and NovelInfoComponents in the fork today (same `split(',')` patterns as upstream pre-fix). `parseGenres()` helper + 3 component hunks is a small, safe crash fix. + +4. **345d084eb — Chapter Numbers in EPUB Titles** (HIGH). Explicit user feature request (upstream #1405). Fork's EPUB export path is different (`@cd-z/react-native-epub-creator`), so the setting + title composition must be adapted, but it is fully feasible in JS and has no native dependency. + +5. **c0877a9b0 — Ignorable App Update Notifications** (HIGH). Closes the long-standing "can't dismiss update prompt" complaint (upstream #600/#1632). Fork's update checker is a near-clone of the deleted upstream hook, so the port is mostly file-copy + repo-URL adaptation. + +Honorable mentions: **8f53550d2** (restores broken tracker search — likely broken in fork today) and **0c3542836** (library "last updated" sort/trigger correctness, identical pre-fix code verified). **96fb52df1** (stats overhaul) is the highest-value *feature*, but at RED risk with a full raw-SQL stats rewrite + new dependency — schedule as its own project. + +### (b) Notable risks / red flags + +- **72bfcad03 (lint/type cleanup) is a trap**: touches WebViewReader.tsx, ReaderScreen.tsx, ChapterContext.tsx, useLibrary.ts — the fork's most rewritten files. Lint-only value, guaranteed conflicts. Never cherry-pick. +- **bd7161172 (chapter drawer)** touches ReaderScreen.tsx and a drawer whose list component diverged (fork: `@legendapp/list` LegendList; upstream: `@shopify/flash-list`). Only the index-calc bugfix is safely portable. +- **bd527a02e (backup overhaul)** is +2000 lines into files the fork modified heavily (SettingsBackupScreen, both provider modals, Checkbox, services/backup/*). Port only the selection/plugin-restore slices, as a dedicated effort. +- **All native-scheduling/notification commits** (bfb12b04a, 648d21062, ac1b2f5ce, 10b6042a9, and the notification half of bd527a02e) depend on upstream-only Expo-module infrastructure (`modules/native-background-tasks`, `modules/native-file`, nitro-epub). NOT-FEASIBLE — the fork would have to reimplement on react-native-background-actions + ServiceManager. +- **All Drizzle migration commits** (5f45e4c7f, 594b078fa, 72ef618af, 5eb5a55c4, 6d26438d2) are NOT-FEASIBLE; the fork's own MigrationRunner already covers recovery/counters. +- **Release/chore/CI commits** (12 of 43) carry zero user value; the fork has its own version numbering, workflows, and husky hooks. + +### (c) Architecture-divergence insights + +1. **DB layer divergence is logic-level, not conceptual.** Upstream fixes (WHERE-scoped updates, julianday date math, positional sort, built-in category IDs) express pure SQL logic; the fork's raw-SQL layer accepts them almost 1:1. The porting cost is low because the *pre-fix* fork code is often byte-identical to upstream's pre-fix code (verified for SelectableChip, NovelInfoHeader, NovelStatus enum, triggers, deleteDownloads). +2. **The fork is 4–6 weeks behind on "fix" quality while being ahead on TTS.** The 2026-07-26→08-01 upstream fix cluster (tracker search, genre crashes, download removal, category reorder, date sorting) is exactly the batch the fork should take — these are regression-style fixes to shared legacy code, not features tied to upstream's new infra. +3. **Three hard upstream-only infrastructure pillars** make certain commits unportable regardless of value: (1) Drizzle/op-sqlite vs expo-sqlite raw SQL; (2) Expo-module background tasks + nitro modules vs react-native-background-actions/ServiceManager and custom Kotlin TTS; (3) Expo-managed config plugins (`plugins/`, `modules/`, `scripts/generate-env-file.cjs`) vs the fork's bare workflow. +4. **Translation path drift** (`src/i18n/languages/*` upstream vs `strings/languages/*` fork) plus fork-local string keys means all Crowdin commits are manual merges, not cherry-picks. +5. **Recommended execution order** for the fork: batch A (safe, high-value fixes): c3260e8e0, ee3a4f20b, eb12bdfd6, 8f53550d2, 0c3542836, 76106317b, 31cb4b994, 0ed7d878b, e75f4fba5, 7ddd934c2 → batch B (feature ports needing UI work): 345d084eb, c0877a9b0, 9783c4d5e, bd7161172 (index fix only) → batch C (project-scale): 96fb52df1, bd527a02e slices → skip everything else. diff --git a/specs/upstream-merge-analysis-2026-08/reference/subagent/group2-report.md b/specs/upstream-merge-analysis-2026-08/reference/subagent/group2-report.md new file mode 100644 index 0000000000..150de1874b --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/reference/subagent/group2-report.md @@ -0,0 +1,90 @@ +# Group 2 Upstream Commit Analysis — Selective Port Assessment + +**Fork:** branch `dev` @ 40c6676797 (v2.1.3) — bare-workflow Expo SDK 54, expo-sqlite raw SQL, custom Kotlin TTS stack (TTSHighlightModule.kt / TTSForegroundService.kt), MMKV settings, WebView reader (core.js at `android/app/src/main/assets/js/`), react-native-paper 5.14.5. +**Upstream:** `original` = upstream/master @ c3260e8e01 — Expo 55 managed, Drizzle ORM + op-sqlite, Nitro modules, rock CLI, zustand-based novel store. +**Prior decision:** full merge infeasible (100+ conflicts); strategy = selective cherry-pick/manual port (`.agents/upstream-merge-memory.md`, 2025-12-30). + +All 43 commits verified via `git show --stat` / `git show` / fork file inspection. + +--- + +## Per-commit assessment + +| # | commit (short) | subject | VALUE | RISK | PORT | rationale | +|---|---|---|---|---|---|---| +| 1 | 52914b978 | fix: Migrate Volume Button Events to Expo Listener API | ALREADY-HAVE | YELLOW | MANUAL | Fork has its own volume-button scroll in `src/screens/reader/hooks/useChapter.ts` (NativeVolumeButtonListener + NativeEventEmitter, connectVolumeButton). This is an upstream-internal API migration (expo `useEventListener`); fork's own listener wiring already works. No user-facing gain. | +| 2 | ff12759a | ci: Run Preview Builds Daily When Missing | NONE | GREEN | DIRECT | CI-only change to `.github/workflows/build.yml`. No app code; pointless for fork's build setup. | +| 3 | e62868b8 | feat: Automate Draft Android Releases | LOW | YELLOW | NOT-FEASIBLE | CI release automation + scripts + `useGithubUpdateChecker.ts` (fork has its own pointing at bizzkoot/lnreader). Depends on rock CLI `plugins/android/android-build-types.gradle` (upstream-only infra) and fork's release process differs. | +| 4 | 97be48df | feat: Add Migration Review Options | MEDIUM-HIGH | YELLOW | MANUAL | User-facing migration UX (choose cover/metadata source, redownload policy) + new MigrationReviewDialog. Fork's `migrateNovel.ts` is raw-SQL/ServiceManager-based and `MigrationNovelList.tsx` exists but differs; `backgroundTasks/contracts.ts` infra is upstream-only. Adaptable. Closes upstream #937/#1037. | +| 5 | cbe7d70e | fix: Restore Collapsed TTS Button Interaction | ALREADY-HAVE | GREEN | MANUAL | 1-line `??=` guard in upstream `assets/reader/js/index.js`. Fork's own index.js (android/app/src/main/assets/js/index.js, line ~548) already guards `if (!controllerElement) controllerElement = document.getElementById('TTS-Controller')` inside `ontouchstart`. Equivalent protection exists. | +| 6 | 6548e9a6 | fix: Improve Paged Reader Layout and Performance | MEDIUM | RED | NOT-FEASIBLE | ~218-line rewrite of upstream `core.js` + css + search.js. Fork's core.js lives at a different path and is heavily TTS-customized (137KB); ReaderScreen.tsx change references `window.pageReader?.movePage(0)` which fork's core.js does not expose. Paged-reader perf ideas portable only as a manual feature port. | +| 7 | 138fdff2 | fix: Prevent Duplicate WebView Bottom Insets | MEDIUM | YELLOW | MANUAL | Upstream removes `useSafeAreaInsets` padding from `WebviewScreen.tsx`. Fork's WebviewScreen.tsx (cookie-hardened, line 171) still has `containerStyle={{ paddingBottom: bottom }}` → likely has the same double-inset bug. Small localized fix; file is fork-modified (cookie parsing) so needs manual merge. | +| 8 | f1dd4b77 | feat: Improve Empty Chapter Reporting | MEDIUM | RED | MANUAL | Adds GitHub-issue report link + HTML-escaped empty-chapter message in WebViewReader.tsx + sanitizeChapterText.ts. Both files are fork-critical; fork's sanitizeChapterText.ts is only 1KB (no sanitizeOptions caching). i18n lives in fork at `strings/languages/*` not `src/i18n/*`. Feature is portable conceptually, needs full manual wiring. | +| 9 | 9b356ffd | fix: Preserve Client IDs And Improve Environment Generation | NONE | GREEN | NOT-FEASIBLE | Rewrites upstream-only `scripts/generate-env-file.cjs` (Expo managed env generation). Fork has no such script/infra. | +| 10 | 5cf7e15d | feat: Add Reader Setting Descriptions | LOW | YELLOW | MANUAL | Adds helper descriptions to AccessibilityTab/NavigationTab. Fork's AccessibilityTab (32.7KB) is heavily TTS-customized (text cleanup, per-novel overrides); descriptions are cosmetic. Low payoff, some merge friction. | +| 11 | 6a7e90c1 | fix: Align Advanced Reader Tabs And Localize Actions | LOW | YELLOW | MANUAL | AdvancedTab.tsx alignment + hardcoded strings → i18n. Fork has its own AdvancedTab.tsx; cosmetic. Fork strings structure differs. | +| 12 | 99c31d56 | fix: Use MD3 Outline Variant For Tab Borders | LOW | YELLOW | MANUAL | Cosmetic border variant across 6 screens incl. ReaderBottomSheet (fork's is ReaderBottomSheetV2/ReaderTTSTab). Fork uses paper 5.14.5; trivially portable but purely visual. | +| 13 | 4d982479 | feat: Add Android DNS Over HTTPS Support | ALREADY-HAVE | GREEN | NOT-FEASIBLE | Fork shipped its own DoH long ago (`DoHManagerModule.kt`, `DoHManager.ts`, SettingsAdvancedScreen DoH picker, persistence). Upstream implementation is an expo local module (`modules/native-doh`). Redundant. | +| 14 | 164f5d25 | feat: Add Automatic Library Update Scheduling | HIGH | YELLOW | MANUAL | User-facing: schedule library updates at 12/24/48/72/168h intervals. Fork has `updateLibraryOnLaunch` only. Requires a new native scheduler (WorkManager-like; upstream uses expo-module `native-background-tasks`) plus SettingsLibraryScreen UI and `useSettings` field. Fork's ServiceManager (react-native-background-actions) is a different paradigm — feasible but real native+JS work. | +| 15 | 89cddf9e | fix: Expand Discover Row Ripple | LOW | YELLOW | MANUAL | Pressable restructure in DiscoverCard.tsx. Fork's DiscoverCard takes `theme` prop and uses AppText/scaleDimension — different structure. Minor ripple-visual fix. | +| 16 | eac6ad58 | feat: Improve Novel App Bar And EPUB Export UX | MEDIUM | YELLOW | MANUAL | Large UX polish of NovelAppbar, ExportEpubModal, JumpToChapterModal, NovelScreenList + tests. Fork has its own versions of all four (different layouts, uiScale). Real user-facing value (jump-to-chapter + EPUB export improvements) but requires manual re-port against fork components. | +| 17 | 93fe04c2 | feat: Polish Reader Controls And Navigation | MEDIUM-HIGH | RED | NOT-FEASIBLE | Very broad: touches ChapterQueries.ts (Drizzle `limit` param), ReaderAppbar, all ReaderBottomSheet children, ReaderFooter, ReaderScreen, navigators (MoreStack/ReaderStack/Main), Slider/ToggleButton/SegmentedControl, DownloadsScreen. Fork-modified copies of nearly every file + Drizzle-only DB change. Selective sub-fixes (e.g., ChapterQueries limit → fork SQL) possible manually. | +| 18 | c9358b3a | feat: Update Android Branding Assets | LOW | GREEN | DIRECT | Binary-only icon/splash refresh in `debug`/`preview` flavors. Cherry-picks cleanly; fork has its own branding but assets are inert to TTS/DB. | +| 19 | 67d2c7c0 | feat: Add Collapsible TTS Controls And Stable Notification Progress | MEDIUM | RED | NOT-FEASIBLE | Touches nitro-tts Kotlin/Swift (`TtsMediaNotification.kt`) + upstream index.js/tts.css. Fork has its own TTS notification (`ttsNotification.ts`, TTSForegroundService.kt) and its own TTS controller UI. Nitro dependency = NOT-FEASIBLE; fork's "stable progress" equivalent already exists. | +| 20 | c30441b9 | feat: Add Smart Library Update Filters | MEDIUM-HIGH | YELLOW | MANUAL | Skip-completed/unstarted/unread novels in global updates + `getLibraryNovelsForGlobalUpdate` (Drizzle) + SmartUpdateDialog. Fork's `services/updates/index.ts` + raw-SQL LibraryQueries are structurally different but the filter concept ports cleanly to fork SQL (`status`, `lastReadAt` exist in fork schema). UseSettings overlap is small. | +| 21 | 6d2a9f8e | fix: Prevent Slider Flicker After Release | LOW | GREEN | MANUAL | Fix to the new MD3 Slider component (0c854618). Only relevant if MD3 Slider is ported first; fork currently uses `@react-native-community/slider`. | +| 22 | 79984542 | feat: Add Parallel Library Updates Across Sources | MEDIUM-HIGH | YELLOW | MANUAL | Concurrency-3 parallel updates across sources (grouped by pluginId). Fork's `updates/index.ts` is a sequential `for` + `sleep(1000)` loop; the same grouping/Promise.all pattern adapts directly. Native `TaskNotificationFactory.kt` part is upstream-only (fork shows progress via ServiceManager metadata). Real speed win for large libraries. | +| 23 | 8ac628b3 | fix: Improve History Removal Flow | MEDIUM | YELLOW | MANUAL | Per-novel history removal + confirm dialog + async flows. Fork has raw-SQL HistoryQueries + own useHistory/HistoryScreen/HistoryCard. New `deleteNovelHistory` SQL is trivial; UI adaption needed. | +| 24 | d02291c2 | feat: Remember Last Used Library Category | MEDIUM | YELLOW | MANUAL | Persists last library tab selection. Fork's useSettings LibrarySettings + LibraryScreen exist; small setting + rehydration. Fork uses bottom-tab navigation in LibraryScreen (index state) — adaptable. | +| 25 | 7af0911f | feat: Add High-Resolution Android Flavor Assets | LOW | GREEN | DIRECT | Binary-only res/mipmap/drawable additions. Cherry-picks cleanly; inert. | +| 26 | 098782d6 | refactor: Simplify Settings Layout And Theme Selection | LOW | YELLOW | MANUAL | Removes `react-native-theme-switch-animation` dep, simplifies ThemePicker/ThemeSelectionStep/SettingsAppearanceScreen. Fork has its own theme system (md3 theme dirs + uiScale) and its own package.json — dep removal must be verified against fork's dependency graph. Net user value low. | +| 27 | 732628c4 | feat: Add Date Format And Relative Timestamp Settings | MEDIUM | YELLOW | MANUAL | New `utils/dateFormat.ts` + Appearance setting + DateFormatModal; touches useSettings, HistoryScreen, DownloadsScreen, ChapterItem, NovelScreenList, UpdatesScreen, GoogleDriveModal. Fork uses dayjs already (translations.ts). User-facing (timestamp formatting choice); manual adaptation of ~8 fork screens needed. | +| 28 | 5e62c36a | feat: Add Library Settings And Category-Based Global Updates | MEDIUM-HIGH | RED | NOT-FEASIBLE | Adds `defaultCategoryId` + include/exclude category sets for global updates. Core logic in Drizzle NovelQueries/LibraryQueries (`getCategoryForNewNovel`, `getLibraryNovelsForGlobalUpdate`); fork's NovelQueries.ts is raw SQL and fork-critical. Fork's SettingsLibraryScreen already has a **TODO-stubbed** DefaultCategoryDialog (line 18) this feature would complete. Concept very valuable; port = rewrite in raw SQL + new SettingsLibrary UI. | +| 29 | 3d102a52 | perf(reader): Cut Chapter Open Latency (#1899) | HIGH | RED | NOT-FEASIBLE | Big perf pass: WebViewReader.tsx (−314/+), useChapter.ts (465 lines), core.js (107), index.js/search.js, useSettings, ChapterContext, ChapterDrawer, ReaderBottomSheet, ReaderFooter, SkeletonLines, sanitizeChapterText, WebviewScreen + useNovel zustand store. Touches every fork-critical reader file and depends on upstream store/asset layout. Cherry-pick = guaranteed conflict storm. Concepts (deferred adjacent-chapter fetch, sanitized-HTML cache, lazy drawer/sheet, memoized drawer rows, shared scroll listener) are excellent candidates for a manual perf port. | +| 30 | 4dcf2130 | fix: Restore Reader Changes Reverted During Expo Migration | MEDIUM | RED | NOT-FEASIBLE | Restores upstream `assets/reader/js/search.js` (442 new lines) + core.js/css tweaks. Fork's reader asset layout differs and fork has no in-reader search feature. Would require building the feature from scratch. | +| 31 | 4de82472 | fix: Resolve Require Cycles And LegendList Render Updates | LOW | RED | NOT-FEASIBLE | Patches `@legendapp__list@3.3.3` (fork pins `^2.0.16`), Drizzle `database/manager/liveQuery.ts`, backup/utils require-cycle. Version mismatch + Drizzle/op-sqlite manager = NOT-FEASIBLE. | +| 32 | 58db0352 | fix: Restore Metro Reader Asset Middleware | ALREADY-HAVE | GREEN | NOT-FEASIBLE | Fork's `metro.config.cjs` already implements an equivalent `enhanceMiddleware` serving `android/app/src/main` reader assets (paths mirror the fork's asset location). Redundant. | +| 33 | 74808f08 | feat: Replace Legacy Media Controls with Nitro (#1896) | ALREADY-HAVE | RED | NOT-FEASIBLE | Nitro-powered TTS + MediaSession. Fork has its own complete native TTS stack (TTSForegroundService.kt, TTSHighlightModule.kt, ttsNotification.ts 5-button media notification). Replacing it = throwing away fork's crown jewel. NOT-FEASIBLE by design; feature (media notification) already exists in fork. | +| 34 | b96351ea | docs: Fix Contraction in TESTING.md | NONE | GREEN | DIRECT | Docs typo. | +| 35 | eb8dc6df | docs: Fix "Withoud" Typo in CONTRIBUTING-NIX.md | NONE | GREEN | DIRECT | Docs typo. | +| 36 | 0c854618 | feat: Add MD3 Slider Component | MEDIUM | YELLOW | MANUAL | New MD3 Slider (416 lines) + removes `@react-native-community/slider`. Fork's TextSizeSlider/ReaderValueChange use community slider. Porting gives consistent M3 styling + fixes; touches fork's ReaderTTSTab path (upstream TTSTab.tsx). package.json change must be coordinated. | +| 37 | e9f6bdaa | fix: Modernize Menu Styling And Outside-Tap Dismissal | MEDIUM | YELLOW | MANUAL | Menu component overhaul (outside-tap dismiss, tests). Fork has its own Menu component (`src/components/Menu/index.tsx`) used by reader/Webview appbars; pattern is portable. | +| 38 | 3d34658d | feat: Add Material 3 Top Tab Indicators | MEDIUM | YELLOW | MANUAL | New TopTabBar component + replaces SettingsReaderScreen TabBar (fork has its own `SettingsReaderScreen/components/TabBar.tsx`). Visual M3 polish across 6 screens incl. ReaderBottomSheet. Moderate manual effort. | +| 39 | d44ce741 | docs: CONTRIBUTING.md | NONE | GREEN | DIRECT | Docs typo. | +| 40 | 17c891e1 | refactor: Standardize Bottom Sheet UX and Styling | MEDIUM | YELLOW | MANUAL | BottomSheet component + `useTheme` (adds `surfaceContainerLow`) + layout util; touches ReaderBottomSheet, NovelBottomSheet, LibraryBottomSheet, FilterBottomSheet, TrackSheet. Fork has its own BottomSheet + useTheme (fork's useTheme differs: computed colors, no surfaceContainerLow yet). Portable as a styling/UX backport. | +| 41 | 9805da63 | refactor: Optimize Novel Screen Performance And Architecture | HIGH | RED | NOT-FEASIBLE | Massive refactor onto upstream's zustand `useNovel` store + new hooks (useNovelRefresh, useNovelScreenActions, etc.), NovelScreen −209 lines, NovelInfoHeader split. Fork has its own `useNovel.ts` hook + NovelContext architecture. Perf goals (skeletons, memoized rows, lazy modals) are valuable but must be re-implemented on fork's stack. | +| 42 | afc20277 | fix: Restore Android Build Types For CI | NONE | GREEN | NOT-FEASIBLE | CI + rock-CLI `plugins/android/android-build-types.gradle`. Fork's Gradle 9.2.0/build setup is custom; no rock. | +| 43 | 14061d20 | docs: CONTRIBUTING.md | NONE | GREEN | DIRECT | Docs typo. | + +--- + +## Group Summary + +### (a) Top 5 most valuable commits for the fork + +1. **3d102a52 — perf(reader): Cut Chapter Open Latency (#1899)** — Highest user-visible payoff (faster chapter open, no re-render stutter while reading, scroll restore ~100ms vs ~1.5s). Cannot be cherry-picked (touches every fork-critical reader file + upstream store/asset layout) but its *techniques* are directly applicable: defer adjacent-chapter resolution, cache sanitized HTML, lazy-mount drawer + settings sheet, memoized drawer rows, single coalesced scroll listener, one mark-as-read per chapter. **Recommended as a manual perf sprint on fork's WebViewReader.tsx/useChapter.ts/core.js.** +2. **164f5d25 — Automatic Library Update Scheduling** — Genuinely missing user feature (scheduled background updates). Manual port: add a native scheduler (or adapt fork's ServiceManager/headless task + AlarmManager/WorkManager), `automaticLibraryUpdateIntervalHours` in useSettings AppSettings, SettingsLibraryScreen dialog. Moderate effort, high appreciation for a library-heavy userbase. +3. **79984542 — Parallel Library Updates Across Sources** — Simple, contained speed win. Fork's sequential `for` + `sleep(1000)` in `services/updates/index.ts` can adopt the same group-by-pluginId + `Promise.all` concurrency-3 pattern with no schema or native changes. Low-risk, near-certain benefit. +4. **c30441b9 — Smart Library Update Filters** — Skip-completed/unstarted/unread novels during global update. Fork's `Novel` table already has `status`/`lastReadAt`, so the SQL filter is trivial; UI (SmartUpdateDialog in SettingsLibraryScreen) adapts to fork's settings style. Good value/effort ratio. +5. **97be48df — Migration Review Options** — Migration is a core fork flow (own migrateNovel.ts); adding cover/metadata/redownload preferences + review dialog is user-visible and self-contained. Fork's raw-SQL migrate service makes it a moderate manual port. + +Honorable mentions: **5e62c36a** (default category + category-based updates — completes fork's TODO-stubbed DefaultCategoryDialog, but Drizzle→SQL rewrite needed), **732628c4** (date format/relative timestamps — broad but mechanical), **8ac628b3** (per-novel history removal), **eac6ad58** (novel screen + EPUB export UX). + +### (b) Notable risks / red flags + +- **RED, do not cherry-pick:** `3d102a52`, `93fe04c2`, `f1dd4b77`, `6548e9a6`, `4dcf2130`, `5e62c36a`, `9805da63`, `4de82472`, `098782d6` (package.json). Every one of these touches `WebViewReader.tsx`, `useChapter.ts`, `ChapterQueries.ts`/`NovelQueries.ts`, `core.js`, `ReaderScreen.tsx`, `ReaderFooter.tsx`, or `package.json` — all fork-rewritten. +- **Upstream-only infra (NOT-FEASIBLE):** Nitro (`74808f08`, `67d2c7c0`), Drizzle/op-sqlite (`4de82472`, parts of `93fe04c2`, `5e62c36a`, `c30441b9`, `8ac628b3`), Expo 55 managed workflow (env scripts `9b356ffd`, rock CLI `afc20277`, `e62868b8`), expo local modules (`4d982479`, `164f5d25` native half). +- **Version/toolchain mismatches:** `4de82472` patches `@legendapp__list@3.3.3` while fork pins `^2.0.16`; `58db0352` metro middleware already exists in fork (`metro.config.cjs`); `0c854618` removes a dependency the fork still uses. +- **i18n divergence:** upstream edits `src/i18n/languages/*/strings.json`; fork uses `strings/languages/*` + `translations.ts`. Every upstream commit touching strings (`f1dd4b77`, `93fe04c2`, `c30441b9`, `eac6ad58`, `6a7e90c1`) needs string-tree reconciliation, not a clean merge. +- **Asset-layout divergence:** upstream `assets/reader/js/*` vs fork `android/app/src/main/assets/js/*` — reader-JS commits (`cbe7d70e`, `6548e9a6`, `4dcf2130`, `67d2c7c0`) never apply to fork paths. + +### (c) Architecture-divergence insights + +1. **DB layer is the hardest seam.** Upstream is Drizzle ORM on op-sqlite; fork is expo-sqlite raw SQL with its own migration runner. Any upstream "query" commit must be re-expressed as fork SQL — a pure port (not a cherry-pick). Value-laden queries (global-update filters, limit params, per-novel history delete) are individually small in SQL, so a "port the feature, rewrite the query" pattern is the right workflow. +2. **TTS is a one-way street.** Fork's custom Kotlin TTS (foreground service + highlight module + WebView tts bridge + text cleanup) is its differentiator. All upstream TTS work now targets Nitro/MediaSession and is effectively dead to the fork except as UX inspiration. The fork's own `ttsNotification.ts` already delivers what upstream's notification work does. +3. **Reader UI lives in the fork's WebView, not upstream's.** Both sides independently evolved `core.js`/`index.js`; fork's is TTS-integrated and length-preserving. Upstream reader-perf and paged-reader changes should be treated as *specifications* to re-implement, not code to import. +4. **State management diverged.** Upstream moved novel screen onto a zustand store; fork uses `useNovel.ts` hook + `NovelContext`/`ChapterContext`. Perf refactors like `9805da63` and `3d102a52` cannot transfer; their perf *rationales* (memoization, deferred heavy work, lazy mounting) should be applied directly to fork components. +5. **Selective porting is confirmed viable.** The clean wins are the library-update cluster (`164f5d25`, `79984542`, `c30441b9`, `5e62c36a`), migration UX (`97be48df`), history removal (`8ac628b3`), date formatting (`732628c4`), and the M3 component polish set (`0c854618`, `3d34658d`, `17c891e1`, `e9f6bdaa`). These are the same cluster upstream shipped in late July 2026 and align with fork gaps (TODO-stubbed default category, sequential updates, no date-format control). + +--- +*Evidence base: `git show` for all 43 hashes; fork inspection of useChapter.ts, WebViewReader.tsx, sanitizeChapterText.ts, core.js/index.js, LibraryQueries/NovelQueries/ChapterQueries/HistoryQueries, useSettings.ts, useTheme.ts, ServiceManager.ts, updates/index.ts, metro.config.cjs, strings/ layout, package.json deps, novel/settings/library screen trees.* diff --git a/specs/upstream-merge-analysis-2026-08/reference/subagent/group3-report.md b/specs/upstream-merge-analysis-2026-08/reference/subagent/group3-report.md new file mode 100644 index 0000000000..40b8066d0c --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/reference/subagent/group3-report.md @@ -0,0 +1,100 @@ +# Group 3 — Upstream Commit Adoption Analysis (43 commits, 2026-05-03 → 2026-07-24) + +Fork: branch `dev` (v2.1.3, 2026-08-03) · Upstream: `original` = upstream/master (c3260e8e01, 2026-08-01) +Method: read-only inspection of each commit's diff (`git show`) cross-checked against fork source. Every HIGH/MEDIUM port recommendation below was verified against the fork's actual current file contents. + +--- + +## Per-commit assessment table + +| # | commit (short) | subject | VALUE | RISK | PORT | rationale | +|---|---|---|---|---|---|---| +| 1 | 916374b5df | refactor: Reorganize Repository Structure | NONE | RED | NOT-FEASIBLE | Pure churn: `strings/`→`src/i18n/` moves, 100+ import updates, removes `gradle/android-build-types.gradle`. Zero user value; would create hundreds of conflicts. | +| 2 | 0a2a6c96ca | feat: Add Variant-Specific Android Splash Screens | NONE | RED | NOT-FEASIBLE | Expo-managed `app.json` + `src/.../preview/res` flavor resources. Fork is bare workflow, single variant (build.gradle uses `customAppId` property only). | +| 3 | 65e8610030 | refactor: Replace Bottom Navigation Shift With Fade | LOW | YELLOW | DIRECT | 1-line `animation: 'shift'`→`'fade'`. Fork's BottomNavigator.tsx:68 still has `animation: 'shift'` (verified) → trivially portable, but pure visual preference. | +| 4 | 1322065290 | feat: Add Variant-Specific Android App Icons | NONE | RED | NOT-FEASIBLE | Flavor-scoped mipmap resources for upstream's `preview` variant. Fork has no build variants. No user value. | +| 5 | 22a1efd1e5 | perf: Optimize RN Skeleton Loading Components | LOW | YELLOW | MANUAL | Reanimated shimmer rewrite (new ShimmerPlaceholder.tsx, moves useLoadingColors to @utils). Fork has its own `Skeleton/Skeleton.tsx` (8K, own useLoadingColors.tsx) already optimized. Minor perf/a11y polish only. | +| 6 | 269606ebb8 | fix: Resolve Nitro EPUB Import Alias | NONE | RED | NOT-FEASIBLE | tsconfig path alias for the Nitro module the fork doesn't have. | +| 7 | 4cb1890b62 | feat: Add Nitro EPUB Exporter | ALREADY-HAVE | RED | NOT-FEASIBLE | Fork already exports EPUB via `@cd-z/react-native-epub-creator` (ExportNovelAsEpubButton.tsx:5). Upstream version is a C++ Nitro module (`modules/nitro-epub/`) — upstream-only infra. | +| 8 | a421177f0c | fix: Improve Category Badge Layout And Drag Reordering | MEDIUM | YELLOW | MANUAL | Fork's CategoriesScreen.tsx:94 has the identical buggy `keyExtractor={(item,index)=>`${item.id}-${index}`}` that breaks drag reordering — verified. Port the keyExtractor fix (1 line). Drag-handle switch to IconButton `onPressIn` is optional (fork's TouchableOpacity+onLongPress works). Badge overflow fix useful. | +| 9 | a16ec87631 | perf: Optimize Novel Covers, Lists, and Chapter Groups | HIGH | RED | MANUAL | New `NovelCoverImage` (expo-image), `NovelCoverLayoutContext`, virtualized `NovelChapterGroup`/`UpdateNovelChapterGroup` lists + `useUpdates` rework. Genuine perf win for library/updates/history. BUT touches ChapterQueries.ts, NovelCover, NovelList, many screen components the fork rewrote → large manual port, needs perf regression testing. | +| 10 | dfd620aa29 | feat: Standardize Dialogs with Material 3 Patterns | MEDIUM | RED | MANUAL | 51 files / 1603 insertions rework of every dialog incl. the fork's custom `ReaderTTSTab.tsx` and SettingsAdvancedScreen. Visual consistency only; low value-per-conflict. Defer. | +| 11 | a5a6e8326f | chore: Update `@legendapp/list` to 3.3.3 | LOW | YELLOW | MANUAL | Fork pins `^2.0.16`. Bump touches ~10 list-using screens; v3 API mostly compatible but needs testing. Marginal benefit. | +| 12 | 7f48a8ba82 | refactor: Improve Plugins Screen UX and Code | MEDIUM | RED | MANUAL | New PluginDetailsScreen, usePlugins selectors rework (434 lines). Fork has its own pluginManager/plugin UI (heavily diverged BrowseScreen, PluginListItem, SourceSettings) → heavy overlap, large manual port. | +| 13 | ae34d7b4ec | feat: Add Dynamic Preview APK Metadata | NONE | RED | NOT-FEASIBLE | build.yml + `gradle/android-build-types.gradle` flavor infra the fork deleted/never had. CI-only. | +| 14 | 44c8e54ed80 | feat: Add Dynamic Material Theme Colors (Closes #1863) | HIGH | YELLOW | MANUAL | Android 12+ Material You colors via `@pchmn/expo-material3-theme` + new `src/theme/dynamic.ts` + useTheme/settings/onboarding wiring. Fork's useTheme.ts matches upstream pre-refactor base (verified — same imports/structure) so adaptation is localized. Moderate effort, high user appeal. Needs UI-scale/testing verification. | +| 15 | 4a4208e336 | feat: Add Time-Tracking Toggle and Inactivity Timeout (#1890) | MEDIUM | RED | MANUAL | Builds on #1886 (time tracking) which the fork lacks; touches useSettings.ts (fork-critical), WebViewReader.tsx, useChapter.ts, and upstream's relocated `assets/reader/js/core.js` (fork keeps `android/app/src/main/assets/js/core.js`). Port only after #1886. | +| 16 | d2385b8dc2 | Update build.yml | NONE | — | NOT-FEASIBLE | CI-only for upstream's GitHub workflow; fork's CI is separate. | +| 17 | 9babe1676d | refactor: Remove React Native Background Actions Manifest Customization | NONE | RED | NOT-FEASIBLE | Fork still depends on `react-native-background-actions@^4.0.1` (ServiceManager.ts:1, package.json:89). Applying this would break the fork's background download/update tasks. Do NOT port. | +| 18 | 8c38f499e4 | chore: Update AppState Test Setup And `pnpm` Configuration | NONE | — | NOT-FEASIBLE | Test-infra for the time-tracking feature fork lacks. | +| 19 | 89c43c1eef | feat: Implement Time Tracking and Statistics (#1886) | HIGH | RED | MANUAL | New user-facing feature (reading time per chapter/novel, sessions, stats screen). Fork has StatsQueries.ts/StatsScreen but only library-count stats — no time tracking (verified: no timeSpent/session anywhere). Upstream implementation rides Drizzle migration + op-sqlite + useNovel zustand store → must be rewritten as: raw-SQL table via fork's MigrationRunner, raw-SQL StatsQueries, standalone useTimeTracking hook (59 lines, portable), WebViewReader/useChapter wiring (fork-critical). Big but bounded. | +| 20 | c3c891cea0 | refactor: Migrate to Expo Managed Workflow (#1885) | NONE | RED | NOT-FEASIBLE | Would invalidate fork's custom Kotlin native modules (TTSHighlightModule, TTSForegroundService, DoH, NativeFile, ZipArchive, VolumeButtonListener), Gradle 9.2.0 setup, and dev scripts. Anti-goal for the fork. | +| 21 | e4cf64a112 | refactor: Migrate Background Tasks to Native Android Scheduling (#1889) | MEDIUM | RED | NOT-FEASIBLE | WorkManager + headless-task stack (BackgroundTaskScheduler, LNReaderTaskWorker, NativeBackgroundTasks, 243-line NativeFile.kt rework, ServiceManager rework, batched downloads, checkpoint recovery). Fork's ServiceManager + background-actions + downloader are tightly coupled; this is a multi-week native+TS project. The "download checkpoint recovery" idea is worth noting as a follow-up though. | +| 22 | 3e1e211098 | feat: Add Address Bar in WebView (#1870) | MEDIUM | YELLOW | MANUAL | ~11-line addition to WebviewScreen Appbar (URL display + address bar). Fork's WebviewScreen.tsx (6K) + components/ differ (own Menu.tsx) → small manual adaptation. Nice UX for source webviews. | +| 23 | 02b6b9848f | fix: Prevent WebView Reset When Reader Settings Change (#1884) | ALREADY-HAVE | RED | MANUAL | Fork never reloads WebView on settings change: settings removed from memoizedHTML deps (TTS highlight-offset fix), settings synced via MMKV listener injectJavaScript (WebViewReader.tsx:473+), and useTTSController has its own CHAPTER_READER_SETTINGS listener (line 591). Only missing nicety: upstream's "restart TTS only when TTS-settings actually changed" optimization (`areTTSSettingsEqual`). Optional, low priority. | +| 24 | 71da09ed44 | feat(reader): Add Current Chapter Search (#1877) | HIGH | RED | MANUAL | In-chapter word search: new `search.js` (442 lines) + ReaderSearchbar (246) + core.js hooks + ReaderScreen/ReaderAppbar/WebViewReader/useChapter wiring. Fork's core.js has diverged heavily (bionic reading, spacing rebuild, custom TTS) — search must be re-adapted against fork's DOM/rebuild lifecycle. Best user-facing feature in this group after time-tracking; expect a day+ of manual work + TTS regression testing. | +| 25 | 4404494ec9 | fix(database): Repair Migration History For Scanlator Column | NONE | RED | NOT-FEASIBLE | Drizzle `__drizzle_migrations` bookkeeping for a scanlator column the fork doesn't have. Fork has its own MigrationRunner + migration-recovery mechanisms (ALREADY-HAVE in spirit). | +| 26 | 98a2a1d725 | feat: Implement Scanlator Filtering (#1864) | MEDIUM-HIGH | RED | MANUAL | Scanlator/group column + filter UI. Fork has NO scanlator column (verified — zero hits in src/database). Requires: new column via fork's MigrationRunner, plugin `Chapter` type + fetch pipeline changes to capture scanlator, 275-line ChapterQueries rework (Drizzle → SQL), filter UI in chapter list/bottom sheet. Large manual port; medium value (only relevant for aggregator sources that expose scanlator). | +| 27 | 788f6cede7 | fix: Prevent Table Overflow and Clean Up CSS (#1876) | MEDIUM | GREEN | DIRECT | `div:has(> table):not(#LNReader-chapter){overflow:auto}` + next-button CSS cleanup. Fork's index.css still has pre-fix `border-width: 1` and no overflow rule (verified lines 86-89). Small, safe, user-visible (wide tables no longer overflow reader). | +| 28 | 7abee71ac3 | test: Generate Debug Environment Variables In Testing Workflow | NONE | — | NOT-FEASIBLE | CI-only. | +| 29 | 52d5c99c46 | fix: Order EPUB Chapters Across Paginated Pages (Closes #1762) | MEDIUM | YELLOW | MANUAL | Fork's `getNovelDownloadedChapters` (ChapterQueries.ts:478) orders by `position` only; fork's ChapterTable HAS `page TEXT DEFAULT "1"` (verified). Manual SQL change: `ORDER BY page ASC, position ASC` + rework the position range filter to limit/offset semantics. Fixes paginated-EPUB downloaded-chapter order. | +| 30 | 93bc5e5e4b | fix: Handle Missing MAL List Entries and Null Progress (Closes #1875) | MEDIUM | GREEN | DIRECT | Fork has the identical pre-fix code: SetTrackChaptersDialog.tsx:17,21 `progress.toString()` (null-crash) and myAnimeList.ts `getUserListEntry` without default-entry creation (verified). Applies cleanly; adds a test file. | +| 31 | 5d996f1388 | feat(downloads): Make Chapter Download Cooldown Configurable (#1834) | MEDIUM | RED | MANUAL | New `chapterDownloadCooldownMs` AppSettings + modal + replaces `sleep(1000)` in downloadChapter.ts. Fork's downloadChapter.ts:93 has the same hardcoded `await sleep(1000)` (verified) but fork's useSettings AppSettings interface differs → manual port. Helps users hitting 429 rate limits. | +| 32 | bbb2645a6f | chore: Clean Reader Preview Content (#1867) | LOW | GREEN | DIRECT | Rewrites SettingsReaderScreen/utils.ts `dummyHTML` (soft-wrapped, table in div). Fork's utils.ts is identical to the pre-clean upstream file (verified — same hard-wrapped HTML + base64 img). Cosmetic/dev-facing. | +| 33 | 54fedc2f | fix(tts): Remove Leading and Trailing Quotes from Normalized Text (#1869) | HIGH | YELLOW | MANUAL (trivial) | 1-line addition to `normalizeText`. Fork's core.js:2203 normalizeText is byte-identical to upstream pre-fix (verified — lacks the quote-strip). Directly improves TTS output quality on the fork's crown-jewel feature. Manual 1-line apply (not a raw cherry-pick due to core.js divergence elsewhere). Test TTS pipeline. | +| 34 | e8c555025 | perf: Optimize Regex for Removing Extra Paragraph Spacing (#1872) | LOW | YELLOW | MANUAL | Fork's spacing block (core.js:4126-4139) has evolved (bionic/spacing rebuild). First `
`-strip pattern already equivalent (`\b[^>]*>`), second pattern at line 4139 is still the old `(?<=<\/?p>\s*
)`. Partially applicable; minor. | +| 35 | 57b9d41b5c | fix(service): Correct Notification Throttle Logic in ServiceManager.setMeta (#1873) | MEDIUM | GREEN | DIRECT | Fork's ServiceManager.ts:152-153 has the identical inverted-throttle bug (`> 1000` + wrong delay math, verified). The current code never throttles correctly and schedules negative-delay timers. 2-line fix; safe (fork still on old background-actions architecture, same as upstream pre-fix). | +| 36 | 34be7a1234 | fix(native-file): prevent fatal crash when unlinking missing files (#1832) | HIGH | GREEN | DIRECT | Fork's NativeFile.kt:133-135 is byte-identical (`if (!file.exists()) throw Exception(...)`, verified). This bug kills the whole process (uncaught exception on TurboModule thread) when deleting a downloaded chapter whose files were removed externally (e.g. backup restore). 1-line fix (`throw`→`return`). Top crash fix. | +| 37 | 60f42ed624 | chore(build): patch @rock-js/plugin-metro for Windows compatibility (#1830) | NONE | RED | NOT-FEASIBLE | rock-CLI workspace patch; fork doesn't use rock. | +| 38 | 25c93afabe | fix(reader): restore saved chapter progress from db if available (#1850) | MEDIUM | YELLOW | MANUAL | Fork's useChapter `getChapter` (useChapter.ts:125) uses `navChapter ?? chapter` with no DB progress hydration (verified). Add `dbChapter = await getDbChapter(chapter.id)` fallback (3 lines, same as upstream). Complements the fork's TTS Math.max reconciliation for the non-TTS reader-open path. | +| 39 | 2cd5a97ee8 | fix: await database operations inside transactions (#1845) | MEDIUM | RED | NOT-FEASIBLE | Drizzle `.run()` additions (op-sqlite tx API) — cannot port directly. CONCEPT applies: fork's NovelQueries.ts:180,321 use `withTransactionAsync` with non-awaited `runAsync(...).then(...)` (verified), a similar race-prone pattern. Recommend a manual audit of fork query transactions as a follow-up, not a cherry-pick. | +| 40 | 8f47e8fd1e | Theme switcher refactor (#1839) | MEDIUM | RED | MANUAL | Context-based useTheme + ThemePicker + appearance-settings rework (~20 files). Fork's useTheme.ts matches the upstream pre-refactor base (verified — imports defaultTheme, same MMKV hooks). Self-contained module; prerequisite for a clean port of #44c8e54ed80 (dynamic colors). Medium effort. | +| 41 | eafa545b2a | Use novel refactor (#1838) | MEDIUM | RED | NOT-FEASIBLE | 75 files / 6232 insertions: zustand novel store + MMKV adapter + Drizzle db.ts + LibraryUpdateQueries rewrite. Fork has its own useNovel.ts (16.6K) + raw-SQL layer. Maintainability-only value; too invasive. | +| 42 | b680a07750 | fix(browse): use gesture-handler Pressable in PluginListItem (#1828) | LOW | YELLOW | MANUAL | WSA mouse-tap fix. Fork's PluginListItem.tsx:2 still imports RN `Pressable` (verified). Trivial import swap; low value (Windows Subsystem for Android edge case). | +| 43 | 2a919ec0f9 | chore: gitignore tsconfig.tsbuildinfo build cache (#1829) | LOW | GREEN | DIRECT | 1-line .gitignore addition. Harmless housekeeping. | + +--- + +## Group summary + +### (a) Top 5 most valuable commits for the fork (value-per-effort) + +1. **34be7a1234 — NativeFile.kt unlink crash fix (HIGH, GREEN, DIRECT)** — Verified the fork has the exact same throwing code. Uncaught exceptions on the TurboModule thread kill the entire app when deleting a downloaded chapter whose files vanished (common after backup restore/external cleanup). One-line fix, zero risk, and it protects the Downloads "delete all" flow. Port first. +2. **54fedc2f — TTS leading/trailing-quote strip (HIGH, YELLOW, 1-line manual)** — Verified fork's `core.js normalizeText` lacks the strip. TTS currently reads stray quote characters aloud on every paragraph. Directly improves the fork's headline TTS feature; tiny change to a byte-identical function. +3. **57b9d41b5c — ServiceManager.setMeta throttle fix (MEDIUM, GREEN, DIRECT)** — Verified the fork has the identical inverted condition + wrong delay math (ServiceManager.ts:152-153). The notification throttle is effectively broken (negative-delay timers, no throttling). 2-line fix on an otherwise untouched file. +4. **89c43c1eef — Time Tracking & Statistics (HIGH, RED, MANUAL)** — The only genuinely *new* user-facing feature in this group that the fork completely lacks (fork's StatsScreen covers library counts only). Reading-time stats is a commonly requested feature. Must be re-implemented against the fork's raw-SQL MigrationRunner and useChapter/WebViewReader wiring (upstream version rides Drizzle + the useNovel zustand store). Bounded scope: one stats table, one hook (~59 lines), one screen. +5. **44c8e54ed80 — Dynamic Material Theme Colors (HIGH, YELLOW, MANUAL)** — Android 12+ Material You colors via an Expo module the fork can install. High appeal, and the fork's useTheme matches the upstream base so the adaptation is localized. Recommended order: port 8f47e8fd1e (theme refactor) first, then this. + +Honorable mentions (worth queueing): 71da09ed44 (in-chapter search — best reader feature but heavy core.js work), a16ec87631 (cover/list perf), 25c93afabe (DB progress restore), 93bc5e5e4b (MAL null fix, DIRECT), 52d5c99c46 (EPUB page ordering), 788f6cede7 (table overflow, DIRECT), 5d996f1388 (download cooldown), 3e1e211098 (webview address bar). + +### (b) Notable risks / red flags + +- **core.js divergence (54fedc2f, e8c555025, 71da09ed44):** The fork's `android/app/src/main/assets/js/core.js` has evolved independently (bionic reading, spacing rebuild, custom TTS speak/normalize). Only tiny additive edits are safe; the 442-line `search.js` feature must be re-integrated with the fork's DOM rebuild and TTS state machine. Any core.js change requires the fork's TTS wake-cycle/refill test suite. +- **Fork-critical file touches:** 89c43c1eef, 4a4208e336, 71da09ed44, 02b6b9848f, a16ec87631, 98a2a1d725, 5d996f1388 all touch WebViewReader.tsx / useChapter.ts / useSettings.ts / database — expect conflict-heavy manual ports. +- **Upstream-only infra (do NOT attempt):** c3c891cea0 (Expo managed), e4cf64a112 (WorkManager scheduling), eafa545b2a (zustand novel store), 2cd5a97ee8 + 4404494ec9 + 52d5c99c46 (Drizzle/op-sqlite APIs), 4cb1890b62 (Nitro C++), 916374b5df (repo shuffle), 9babe1676d (actively harmful — fork still needs background-actions). +- **Dep bumps (a5a6e8326f, 22a1efd1e5) need Android build + list virtualization regression checks.** +- **Value inflation trap:** 98a2a1d725 (scanlator) looks attractive but requires a schema migration + plugin-pipeline change to ever populate the column; only useful for aggregator sources. Do last, if at all. + +### (c) Architecture-divergence insights + +- **DB layer is the hard boundary.** Every upstream DB commit assumes Drizzle schema objects, `dbManager.write`, `tx.run()`, `drizzle/migrations/*/snapshot.json`. The fork's raw-SQL + custom MigrationRunner means these commits are never cherry-pickable — only their *intent* is portable (await-in-transaction, page ordering, scanlator column, stats tables). +- **WebView asset location differs.** Upstream moved reader JS to `assets/reader/js/core.js` (Expo-managed); fork keeps `android/app/src/main/assets/js/core.js`. Path-based diffs will never apply; diff at the function level instead. +- **Feature coupling upstream is a chain.** Time-tracking (#1886→#1890) and scanlator (#1864→#4404494ec9) are built on the useNovel zustand store (#1838) — a refactor the fork rightly rejects. Porting those features means unbundling them from the store and wiring them into the fork's ChapterContext/useChapter flow. +- **Fork equivalents confirmed (skip):** EPUB export (JS lib vs Nitro), WebView settings sync (fork's MMKV-listener + ref pattern), migration recovery (fork's MigrationRunner), background notification (fork's ServiceManager — but with the throttle bug above worth fixing), media notification (ttsNotification.ts), DoH, volume buttons, text cleanup. +- **Recommended batch order:** (1) trivial verified exact-match fixes 34be7a1234, 57b9d41b5c, 93bc5e5e4b, 788f6cede7, 54fedc2f, 25c93afabe, bbb2645a6f, 65e8610030, 2a919ec0f9; (2) small SQL/UI ports 52d5c99c46, a421177f0c, 3e1e211098, b680a07750, 5d996f1388; (3) themed feature train 8f47e8fd1e → 44c8e54ed80; (4) big features 89c43c1eef, 71da09ed44, a16ec87631 as separate PRs with TTS regression testing. + +--- + +## Verification evidence (spot-checks performed) + +- Fork `android/app/src/main/java/com/rajarsheechatterjee/NativeFile/NativeFile.kt:133-135` — byte-identical pre-fix unlink (matches #1832 context). +- Fork `src/services/ServiceManager.ts:152-153` — identical inverted throttle (`now - lastNotifUpdate > 1000`, `delay = 1000 - now - lastNotifUpdate`). +- Fork `src/screens/novel/components/Tracker/SetTrackChaptersDialog.tsx:17,21` + `src/services/Trackers/myAnimeList.ts` — identical pre-fix (no default-entry creation). +- Fork `src/screens/reader/components/WebViewReader.tsx:473-490` — settings applied via injectJavaScript without WebView reload; `useTTSController.ts:591` reads CHAPTER_READER_SETTINGS directly → #1884 already covered. +- Fork `android/app/src/main/assets/js/core.js:2203` normalizeText lacks quote-strip; `:4139` second spacing regex still pre-fix form. +- Fork `src/database/queries/ChapterQueries.ts:478` getNovelDownloadedChapters orders by position only; ChapterTable has `page TEXT` column. +- Fork `src/database/queries/NovelQueries.ts:180,321` — unawaited `runAsync` inside `withTransactionAsync` (concept of #1845 applies). +- Fork `src/screens/Categories/CategoriesScreen.tsx:94` — same index-based keyExtractor bug. +- Fork `src/screens/reader/hooks/useChapter.ts:125` — no DB progress hydration on open. +- Fork `src/screens/browse/components/PluginListItem.tsx:2` — RN Pressable (pre-fix #1828). +- Fork has NO scanlator column, NO timeSpent/session stats, NO dynamic theme, NO address bar, NO chapter search — features genuinely missing. diff --git a/specs/upstream-merge-analysis-2026-08/reference/subagent/group4-report.md b/specs/upstream-merge-analysis-2026-08/reference/subagent/group4-report.md new file mode 100644 index 0000000000..21eda22e2f --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/reference/subagent/group4-report.md @@ -0,0 +1,88 @@ +# Group 4 — Upstream Commit Analysis (41 commits) for Selective Porting + +**Scope**: commits from `original` (upstream/master), newest-first. Fork = branch `dev` (HEAD 40c6676797 v2.1.3), RN 0.82.1 / Expo 54 bare / expo-sqlite raw SQL / custom Kotlin TTS (TTSForegroundService.kt, TTSHighlightModule.kt) / react-native-paper 5.14.5. +All findings verified via read-only `git show`/`grep`/`read` against the fork tree. + +Legend — RISK: GREEN = new files or files fork didn't touch; YELLOW = some overlap, localized/portable; RED = fork-critical file or upstream-only infra. PORT: DIRECT / MANUAL / NOT-FEASIBLE. + +--- + +## Commit-by-Commit Table + +| # | commit (short) | subject | VALUE | RISK | PORT | rationale | +|---|---|---|---|---|---|---| +| 1 | 01596c0 | fix: Apply active filters to chapter count display (#1797) | MEDIUM | YELLOW | MANUAL | Real UX bug: `getChapterCount` ignores the unread/downloaded/bookmarked filter, so the header count stays at total. Upstream impl is Drizzle (`$count`+`and()`+`chapterFilterToSQL`); fork's raw-SQL `getChapterCount(novelId, page='1')` (ChapterQueries.ts:401, uses `getFirstSync`) needs a `filter` arg appended as SQL. `useNovel.ts` call sites (lines 105/270/291) are fork-modified → manual. | +| 2 | 3de93c5 | feat: migrate from rn community cli to rock (#1812) | NONE | RED | NOT-FEASIBLE | rock CLI build orchestration (rock.config.mjs, env scripts, package.json/build.gradle/tsconfig). Fork is RN community CLI + Gradle 9.2.0 bare workflow. No user-facing value. | +| 3 | 306f5b2 | fix: install the right version of hermes compiler | NONE | RED | NOT-FEASIBLE | Package/lock pinning for Expo 55 toolchain. N/A to fork's Expo 54 dependency set. | +| 4 | 06f0f66 | fix: Add hermes-compiler dev dep | NONE | RED | NOT-FEASIBLE | Same infra category; package.json/pnpm-lock only. | +| 5 | 64921a0 | feat: Expo 55 upgrade | NONE | RED | NOT-FEASIBLE | Upstream migrated to Expo managed workflow; 42 files incl. MainApplication.kt, build.gradle, db.ts, useNovel, ChapterItem, reader bottom sheet. Fork is Expo 54 bare with custom Kotlin. Upgrade decision belongs to the fork itself, not a cherry-pick. | +| 6 | 44e035a | chore: Update Translations (#1683) | LOW | GREEN | DIRECT | Crowdin sync across 34 string files. Fork's strings/languages are heavily customized (custom TTS strings); a raw overwrite would clobber fork strings. Only take if diffed per-key. | +| 7 | 1803cd0 | feat: Add testing support for react code (#1763) | LOW | YELLOW | MANUAL | Test infra (babel config, jest mocks, test-utils). Fork already runs 1235 tests with its own jest setup; useful only as inspiration for useNovel tests. | +| 8 | e4d7ba3 | fix: Safety level may not be changed inside a transaction error (#1758) | LOW | YELLOW | MANUAL | Splits PRAGMA execution out of the DB bootstrap transaction. Drizzle `db.ts` diff, but the *concept* (don't run `PRAGMA`/`safety_level` inside a transaction) could apply to fork's own db bootstrap. Not verified as a live fork bug → low priority. | +| 9 | cc04287 | feat: Replace TTS Notifications with Android MediaSession Controls (#1711) | ALREADY-HAVE | RED | NOT-FEASIBLE | Fork already ships a 5-button TTS media notification (ttsNotification.ts + DebugMediaButtonReceiver.kt). Upstream's MediaSession adds lock-screen controls/seekbar/album art but is built on its Nitro TTS stack and rewrites WebViewReader.tsx, core.js, MainApplication.kt, build.gradle. Fork's equivalent notification is functional; port not worth the risk. | +| 10 | b8c177b | refactor: Migrate to Drizzle and op-sqlite (#1735) | NONE | RED | NOT-FEASIBLE | Complete DB-layer rewrite (42 files). Fork deliberately uses expo-sqlite + raw SQL tables + custom MigrationRunner. Directly contrary to fork architecture. | +| 11 | 7083c51 | fix: Include selectedIds in Chapter List extraData (#1750) | LOW | YELLOW | MANUAL | LegendList selection-highlight re-render fix. Fork's NovelScreenList.tsx:329 already passes `selected.length` in extraData, so the fork is largely covered; verify selection toggle of equal-length sets before porting. | +| 12 | f069320 | fix: Improve Next Chapter Selection Logic (#1744) | MEDIUM | YELLOW | MANUAL | Bug fix: `getNextChapter` misbehaves when `position=0` across pages and lacks `LIMIT 1`. Fork's getNextChapter (ChapterQueries.ts:441) has the *identical* pre-fix SQL (`(position = 0 AND page > ?)` / `ORDER BY position ASC, page ASC`). Port = copy the corrected SQL. Port together with d15cf98. | +| 13 | 3f8071e | chore: Release v2.0.3 | NONE | YELLOW | NOT-FEASIBLE | Version bump (build.gradle, package.json, issue templates). Fork is v2.1.3 with its own versioning. | +| 14 | a1c5350 | fix: Miscellaneous UI Tweaks | LOW | YELLOW | MANUAL | Cosmetic tweaks; the NovelInfoHeader change is a 230-line rewrite that conflicts with fork's customized header (TTS/continue-reading FAB). Low payoff. | +| 15 | 8f23790 | feat: Show First Unread Chapter in ReadButton and FAB When No Last Read | MEDIUM | YELLOW | MANUAL | Nice UX: "start reading" jumps to first *unread* chapter instead of chapters[0]. Fork's ReadButton.tsx currently falls back to `chapters[0]` (possibly read). Port needs: new `getFirstUnreadChapter` SQL + useNovel exposure + ReadButton/NovelScreenList wiring. Fork has no first-unread logic today (verified). | +| 16 | 11ff69d | fix: Pre-Fetch Adjacent Page Chapters in Reader and Library Updates | MEDIUM | RED | MANUAL | Reader pagination UX + library update preload. Touches `useChapter.ts` — fork's reader hook is heavily customized for TTS progress sync (fork-critical). High conflict surface; only worth porting the LibraryUpdateQueries part. | +| 17 | d15cf98 | fix: Order Chapter Pages Numerically in Queries (#1742) | MEDIUM | YELLOW | MANUAL | Bug fix for multi-page novels: `getCustomPages`, `getPrevChapter`, `getNextChapter` now `CAST(page AS INTEGER)` sort/compare. Fork's versions (ChapterQueries.ts:337, 422, 441) have the identical buggy SQL. Direct SQL copy, localized. | +| 18 | 29bd661 | refactor: Optimize Library Screen Performance and Fix Category Bug | MEDIUM | RED | MANUAL | Library perf (SelectionContext, memoized items, Set lookups) + a real "Update Category" index bug. Fork's LibraryScreen/useLibrary are heavily rewritten (selection + updates integration), so this is a re-implementation, not a patch. Value real but effort high. | +| 19 | ab1e36d | refactor: Update Components to Follow Material Design 3 Guidelines | LOW | YELLOW | MANUAL | MD3 polish across 13 shared components (Switch, TabBar, Chip, BottomSheet…). Fork on paper 5.14.5 with custom-scaling components; cosmetic, risky churn for little user-visible gain. | +| 20 | f1fdafd | fix: Prevent Tab Bar Height Jump and Shadow Flash on Browse Screen | LOW | GREEN | DIRECT | Small, self-contained: add `initialLayout` to TabView + border instead of shadow in BrowseScreen.tsx. Fork's BrowseScreen still uses plain TabView (verified) → applies cleanly. Low value but safe. | +| 21 | 4c7684a | refactor: Optimize Novel Screen Performance and Fix Download State Tracking | MEDIUM | RED | MANUAL | Real wins (Set-based download tracking, memoized ChapterItem, state-mutation bug in updateChapter) but rewrites NovelScreen/ChapterItem/NovelInfoHeader/useNovel — all fork-modified. Re-implement selectively (e.g., the updateChapter state bug) rather than port wholesale. | +| 22 | 2f45db6 | fix: Replace Sync Database Operations With Async to Prevent Database Lock (#1716) | MEDIUM | RED | MANUAL | Correct stability direction (sync `getAllSync`/`getFirstSync` calls cause SQLITE_BUSY). Fork's DB layer still uses sync calls (e.g., getCustomPages, getPageChaptersBatched, getChapterCount). Broad change across queries/useNovel/library/downloadChapter/migrateNovel; worth doing incrementally as a fork-side effort, not a cherry-pick. | +| 23 | 9e38ad1 | fix: Sanitize EPUB FileName to Remove Filesystem-Unsafe Characters (#1480) | MEDIUM | GREEN | DIRECT | 1-line fix in ExportNovelAsEpubButton.tsx:140. Fork has the identical pre-fix line (`novel.name.replace(/\s/g,'')`). Filesystem-unsafe chars break EPUB export. Zero conflict. | +| 24 | 45c4ea8 | fix: Handle IOException in NativeFile to Prevent EPUB Import Crash Loop (#1315) | MEDIUM | GREEN | DIRECT | Fork's `android/.../NativeFile/NativeFile.kt` is byte-identical to upstream's pre-fix version (verified: `writeFile`/`readFile`/`copyFileContent` unwrapped). Wrapping IOExceptions stops native bridge crash loops on stale MMKV import tasks. Best cherry-pick candidate in the group. | +| 25 | d2b95ae | fix: Use NativeFile for Custom CSS/JS Import to Fix Content URI Read Failure (#1661) | MEDIUM | YELLOW | MANUAL | Fork's AdvancedTab.tsx:206 still uses `StorageAccessFramework.readAsStringAsync` (pre-fix). Fork's NativeFile exposes the same copyFile/readFile API, so the replacement is straightforward; verify fork's imported-file temp path handling. | +| 26 | 0212115 | fix: Stabilize Scroll Handler References to Prevent Infinite Re-render Loop (#1679) | LOW | YELLOW | MANUAL | useCallback/useRef stabilization in MalTopNovels + NovelScreenList. Fork has both files but with local modifications; low value, safe to skip or port the MalTopNovels half. | +| 27 | caed965 | fix: Prevent EPUB Import Crash from DocumentPicker Cache Cleanup (#1725) | MEDIUM | YELLOW | MANUAL | 1-line: `copyToCacheDirectory: false` in LibraryScreen.tsx:167 (fork still `true`, verified). Applies nearly directly despite fork's LibraryScreen differences. | +| 28 | 4088d59 | fix: Close OkHttp Response and Streams in NativeFile.downloadFile (#1740) | MEDIUM | GREEN | DIRECT | Fork's NativeFile.kt `downloadFile` matches upstream pre-fix exactly (verified: leaky `response.body!!.byteStream()` without `use`). Connection/memory leak fix for batch image downloads. Direct port. | +| 29 | a062bee | fix: Add Max Height to Set Categories Modal (#1738) | LOW | GREEN | DIRECT | Adds `maxHeight: 40% window` to the category FlatList. Fork's SetCategoriesModal.tsx lacks it (verified). Trivial, safe. | +| 30 | 3095882 | Replace react-native-paper menus with custom implementation (#1733) | LOW | YELLOW | MANUAL | Drops paper `Menu` for a custom one (4 files). Fork's Menu/index.tsx still imports paper `Portal` (verified). Cosmetic/architectural prep on upstream's path to de-papering; low fork value, medium churn. | +| 31 | 4ad0639 | Fix: Chapter loading for jump to chapter modal and chapter drawer | MEDIUM-HIGH | YELLOW | MANUAL | Real bug: jump-to-chapter modal and chapter drawer can't reach chapters beyond the preloaded batch. Upstream adds `getNovelChaptersByNumber`/`getNovelChaptersByName` (raw SQL — directly compatible with fork) + `loadUpToBatch` in useNovel + modal/drawer wiring. Fork has JumpToChapterModal.tsx and ChapterDrawer/index.tsx. Ignore the pnpm-lock churn in the commit. Best candidate among useNovel-touching commits. | +| 32 | b53ec33 | feat: Support Additional Plugin Setting Types (Dropdown and Checkboxes) | MEDIUM | YELLOW | MANUAL | Plugin-ecosystem feature: sources can declare dropdown/multi-checkbox settings. Fork's `src/plugins/types/index.ts` and SourceSettings.tsx are pre-fix (verified: no dropdown/checkbox types). Useful for richer sources; needs adaptation to fork's plugin types. | +| 33 | c40edd5 | feat: Add Kitsu Tracker (Closes #416) | HIGH | YELLOW | MANUAL | Biggest user-facing feature in the group: new Kitsu tracker service (kitsu.ts, ~490 lines) + score selectors, tracker cards, settings screen, login dialog rework. Fork has the full tracker infrastructure (aniList/MAL/MangaUpdates; TrackerCards/ScoreSelectors/SetTrackScoreDialog exist) with a different structure → port as new service + adapt UI wiring. High value for tracker users. | +| 34 | bdd42ca | chore: Update Repository Links to lneader Instead of LNReader | NONE | YELLOW | NOT-FEASIBLE | Rebranches repo links/org rename across 40 string files. Fork is a separate project (bizzkoot/lnreader) — adopting would point users at the wrong repo. Do not port. | +| 35 | ab4eee3 | feat: Allow All Safe Tags and Attributes When Sanitizing Chapter HTML (#1715) | MEDIUM | YELLOW | MANUAL | Renders tables/footnotes/del-ins/epub:type etc. in chapter HTML. Fork's sanitizeChapterText.ts is the pre-fix version + fork's own `span[style]` (adopted #1573). The change is additive; must reconcile with fork's `style`-preservation and check it doesn't bloat TTS paragraph extraction (htmlParagraphExtractor.ts consumes sanitized HTML). | +| 36 | 3f9886a | chore: Update Repository Links to lneader (dup) | NONE | YELLOW | NOT-FEASIBLE | Same as bdd42ca. Do not port. | +| 37 | 198b744 | feat: Add RTL (Right-to-Left) Language Support (#1717) | MEDIUM | RED | MANUAL | 44 files, 770 insertions: RTL layout across every screen incl. WebViewReader.tsx (fork-critical, 84-line diff), TTSTab.tsx (fork deleted/renamed → ReaderTTSTab.tsx), SettingsReaderScreen/AdvancedTab (fork-modified). Real feature for Arabic/Urdu/Hebrew users but requires a dedicated fork-side porting effort. Do not attempt as cherry-pick. | +| 38 | f9c5ca3 | chore: Update Indonesian Translations (Closes #1653) | LOW | GREEN | DIRECT | id_ID strings re-sync (444 changed lines). Fork's id_ID differs; diff per-key before adopting. | +| 39 | 9922d43 | fix: Remove Missing Image References During EPUB Export (#1696) | MEDIUM | YELLOW | MANUAL | Removes `
`/`` pointing at deleted files during EPUB export. Fork's ExportNovelAsEpubButton.tsx is at the pre-fix state (verified: `readFile` without image cleanup). Ports cleanly; watch fork's chapter path layout (uses same NOVEL_STORAGE layout). | +| 40 | ae99b07 | feat: Add `@noble/ciphers` for Plugins (Closes #1709) | MEDIUM | YELLOW | MANUAL | Adds cipher support (AES etc.) exposed to source plugins in pluginManager.ts. Fork's pluginManager.ts is custom (per-plugin sandboxing); adding the dep + `crypto` surface is a small manual job with clear ecosystem benefit. | +| 41 | 0cb9da9 | refactor: Replace `withTransactionAsync` With `withExclusiveTransactionAsync` (#1636) | NONE | RED | NOT-FEASIBLE | Drizzle/op-sqlite transaction API. Fork's raw-SQL transaction handling is entirely different. Skip. | + +--- + +## Group Summary + +### (a) Top 5 most valuable commits for the fork + +1. **c40edd5 — Kitsu tracker (HIGH)** — The only new user-facing feature in the group. Kitsu has a large dedicated readership community. The tracker service is a new file (kitsu.ts); the fork's tracker infra (ScoreSelectors, TrackerCards, SettingsTrackerScreen, login dialog) already exists, so the port is additive wiring rather than a rewrite. Best value-per-effort in this group. + +2. **4ad0639 — Jump-to-chapter / chapter-drawer loading (MEDIUM-HIGH)** — Fixes a genuinely broken UX: chapters not yet in the loaded batch are unreachable from the jump-to-chapter modal and the chapter drawer. Upstream's new queries are raw SQL (`position = ?`, `name LIKE ?`) that map 1:1 onto the fork's expo-sqlite layer. `loadUpToBatch` in useNovel is the only messy bit (fork-modified hook). Skip the commit's pnpm-lock churn. + +3. **45c4ea8 + 4088d59 — NativeFile.kt IOException wrap + OkHttp `response.use` (MEDIUM)** — Two real stability fixes (import crash-loop, connection/memory leak during batch image downloads). Verified the fork's `NativeFile.kt` is byte-identical to upstream's pre-fix state, so these cherry-pick **directly** — the lowest-risk high-value ports in the whole group. + +4. **d15cf98 + f069320 — Numeric page ordering / next-chapter SQL fixes (MEDIUM)** — Multi-page novels get wrong prev/next chapter navigation and unordered page lists because `page` is text-compared. The fork's `getCustomPages`/`getPrevChapter`/`getNextChapter` still carry the identical buggy SQL; porting is copying corrected SQL into ChapterQueries.ts (3 functions, no API change). Port both together since they touch the same lines. + +5. **9e38ad1 + 9922d43 — EPUB export filename sanitization + missing-image cleanup (MEDIUM)** — Export is currently broken for titles containing `/ : * ? " < > |` (9e38ad1 is a 1-line direct port) and produces broken image placeholders (9922d43). Both land in ExportNovelAsEpubButton.tsx which is unmodified in the fork. High value for EPUB users, near-zero risk. + +Also worth scheduling (not top-5 but valuable): **2f45db6** (async DB ops → SQLITE_BUSY stability) and **29bd661** (library category bug + perf) — both are MEDIUM value but RED-risk because they rewrite fork-modified files; treat as fork-side re-implementations, not cherry-picks. **01596c0** (filtered chapter count) is a nice small manual port. **b53ec33** (plugin dropdown/checkbox settings) and **ae99b07** (@noble/ciphers) strengthen the fork's plugin ecosystem. **d2b95ae** (CSS/JS import) and **caed965** (EPUB picker) are two small crash-fix manual ports that apply to fork files still in pre-fix state. + +### (b) Notable risks / red flags + +- **NOT-FEASIBLE infrastructure cluster**: `3de93c5` (rock CLI), `64921a0` (Expo 55), `b8c177b` (Drizzle+op-sqlite), `0cb9da9` (withExclusiveTransactionAsync), `e4d7ba3` (Drizzle db.ts), `306f5b2`/`06f0f66` (hermes pinning). These are upstream-only infra; attempting them drags the fork toward the managed/Drizzle architecture it deliberately diverged from. +- **RED on fork-critical files**: `11ff69d` (useChapter.ts — TTS progress sync hook), `2f45db6` (entire DB query layer + useNovel), `29bd661` (library screen), `4c7684a` (novel screen + ChapterItem + useNovel), `198b744` (RTL — 44 files incl. WebViewReader.tsx), `cc04287` (WebViewReader/core.js/native — ALREADY-HAVE feature anyway). +- **ALREADY-HAVE**: only `cc04287` (fork's own 5-button TTS media notification). The upstream MediaSession implementation adds seekbar/album art but rides on their Nitro TTS stack — do not port. +- **Do-not-port churn**: `bdd42ca`/`3f9886a` (repo-link rebrand to upstream's new org — actively wrong for a fork), `3f8071e` (release bump), translation syncs (`44e035a`, `f9c5ca3`) risk clobbering fork's customized string files. +- **Conflict magnet**: `useNovel.ts` is touched by 6+ commits in this group (01596c0, 8f23790, 4ad0639, 2f45db6, 4c7684a, 1803cd0). Any port touching it should be sequenced and regression-tested against the fork's 1235-test suite (TTS wake/refill tests especially). + +### (c) Architecture-divergence insights + +1. **The fork's raw-SQL layer is a *portability advantage***: upstream commits written against pre-Drizzle SQL (d15cf98, f069320, 4ad0639's new queries, 01596c0's concept) map directly onto fork's expo-sqlite `db.getAllAsync`. The fork is effectively pinned to the *pre-Drizzle* upstream codebase for DB logic — good news for these specific fixes. +2. **TTS is a hard wall**: every upstream commit touching playback/native TTS (cc04287) assumes the Nitro/MediaSession stack; the fork's ForegroundService + TTSHighlightModule + WebView bridge is a parallel universe. Only UI-layer fixes that don't cross the TTS boundary are safe. +3. **NativeFile.kt is a shared, stable seam**: both trees carry the same TurboModule file. It's the single most reliable place for direct cherry-picks (45c4ea8, 4088d59) and a good test case for validating the port workflow. +4. **Upstream is de-papering** (3095882 custom Menu → MD3 refactors); the fork remains on react-native-paper 5.14.5 with custom UI scaling. UI refactor commits will keep conflicting for the foreseeable future — port only user-visible fixes (f1fdafd, a062bee, 9e38ad1), not architecture churn. +5. **useNovel.ts / novel-screen files are the fork's second conflict hotspot** after TTS files; bundle their ports (8f23790, 4ad0639, 01596c0) into one coordinated change with dedicated tests. diff --git a/specs/upstream-merge-analysis-2026-08/reference/upstream_commits_170.txt b/specs/upstream-merge-analysis-2026-08/reference/upstream_commits_170.txt new file mode 100644 index 0000000000..631b933afe --- /dev/null +++ b/specs/upstream-merge-analysis-2026-08/reference/upstream_commits_170.txt @@ -0,0 +1,170 @@ +c3260e8e01e4704b57137d787ffe9cfec7941cbc|Rajarshee Chatterjee|2026-08-01|fix: Resolve Chapter Download Queue and Removal Issues (Closes #1937) +c0877a9b0656e22c3c788854006fb456c3ed704a|Rajarshee Chatterjee|2026-08-01|feat: Add Ignorable App Update Notifications +96fb52df1730912c40ee6918cf6a6fcd74e31cc8|Patrick Loser|2026-08-01|feat: Enhance Stats With Genre Taxonomy and Charts (#1919) +76106317b68e8aade12b868afb16dda4671ceb16|Rajarshee Chatterjee|2026-07-31|fix: Restore Midnight Dusk Chip Contrast (Closes #1939) +e75f4fba5da08e2317cfed2fe92b8de09457475c|K1ngfish3r|2026-07-29|feat: Add More Novel Statuses and Icons (#1931) +4dc933ee5a361eb0535120fa192c1eb51ba8cc70|Rajarshee Chatterjee|2026-07-29|fix: Remove White Flash During Screen Transitions (Closes #1926) (#1927) +bfb12b04aa31e3013f50503697253b549ee9618c|Rajarshee Chatterjee|2026-07-29|feat: Add Configurable Automatic Backup Scheduling +b7639ab55a308c55617f0088c193052b9e0bd2b2|Patrick Loser|2026-07-29|feat: Upload JavaScript Source Maps (#1924) +ecc5b84a64ac1aa841583acb43d7ac8aab5a72c5|github-actions[bot]|2026-07-29|chore: Release v2.1.2 +eb12bdfd623bd28c2c3f88f585fefca9352711a4|Rajarshee Chatterjee|2026-07-29|fix: Prevent Novel Genre Rendering Crashes +9612bf6f893d0078c566f9651405097b7f8d1f24|Rajarshee Chatterjee|2026-07-29|chore: Update Translations (#1928) +0ed7d878b4b75e1f6ebc83d0978f05cad5920014|Rajarshee Chatterjee|2026-07-29|fix: Prevent White Reader Drawer Seam +5f45e4c7f2d4f77da3b73bdfdfcdb5b477fd6849|Rajarshee Chatterjee|2026-07-28|fix: Recover Interrupted Chapter Migrations +ca24671a2f847c5722009b6b8451e9c54d3a2c12|Rajarshee Chatterjee|2026-07-28|fix: Support Fork-Safe Preview Builds +ee3a4f20bc41ccf9e3f33ce37e7191cd5c7e6a17|Rajarshee Chatterjee|2026-07-28|fix: Preserve Default Category After Reordering (Closes #1922) +0c3542836c847c0e824e473c06394bd5219d6329|Rajarshee Chatterjee|2026-07-28|fix: Synchronize Library Updates And Optimize Import Refreshes +f71c9c859471968684baef6e7f3da55a9d2f5821|Patrick Loser|2026-07-28|fix: Ignore Discord Notifications Outside the Main Repository (#1923) +f53546cc01d76b8b3f73ed4a3b88b374f775d746|github-actions[bot]|2026-07-28|chore: Release v2.1.1 +594b078fad3308c284b3b9cd8484056b40d3c821|Rajarshee Chatterjee|2026-07-28|fix: Harden Database Migration Recovery +ac1b2f5ce446ae90db315be11d1494bc49539f40|Rajarshee Chatterjee|2026-07-27|feat: Add Durable Completion Notifications +648d21062f8812ae2a8526954a374424c0dede3c|Rajarshee Chatterjee|2026-07-27|feat: Add Parallel Task Queues With Per-Plugin Download Limits +89d715ce6bc22ea15885ff86b3359033f3253b4c|Rajarshee Chatterjee|2026-07-27|docs: Replace Beta Badge With Build Preview Status +bd527a02efa9d642b7168e432bfb965a0ae85f51|Rajarshee Chatterjee|2026-07-27|feat: Improve Backup Selection, Notifications, And Plugin Restore +11117c1b5404820868683522242296d4f61d7642|Rajarshee Chatterjee|2026-07-27|feat: Add GitHub-Style Embed For Nightly Notifications +ba25d5b8fe3af0b006ad09293a499b58cbee1479|Patrick Loser|2026-07-27|feat: Add Rozenite DevTools Integration (#1912) +31cb4b994a332bd6f99864ba792aaa988bf4720b|Rajarshee Chatterjee|2026-07-27|fix: Align Bottom Navigation With Material 3 Specs +72ef618af1bdb6a40783ab119649e302a89e12d8|Rajarshee Chatterjee|2026-07-27|fix: Preserve Existing Data During Novel Counter Migration +936fd223bbde71c3e0496f22ea4f28c9714d7987|Rajarshee Chatterjee|2026-07-27|feat: Add Discord Notifications For Nightly Builds +6d26438d2dd5b7faf21786bc390ae83b43aca6d5|Rajarshee Chatterjee|2026-07-27|test: Update Expected Database Migrations +5eb5a55c4c654c046f984f1e324b36587389dfa8|Rajarshee Chatterjee|2026-07-27|fix: Repair Skipped Novel Counter Migrations +6cd86d1422de47dc8e08c53e645c678b39ed66cc|Rajarshee Chatterjee|2026-07-27|chore: Update Translations (#1906) +9783c4d5e3a0f7e5736d1fa50a8dd360a760f76c|Patrick Loser|2026-07-26|fix: Normalize Repository Back Navigation (#1905) +87f602a1fdaaf4456b1947a6097b5a7e65caa1a3|github-actions[bot]|2026-07-26|chore: Release v2.1.0 +3d32ddd6e933d5b5208f7813b99b8755e700d483|Rajarshee Chatterjee|2026-07-26|fix: Improve Release Selection And APK Verification +7ddd934c2b834ba8fbed6969dab89728e3316251|Rajarshee Chatterjee|2026-07-26|fix: Handle Missing Tracker Client IDs +b53c91f748581227d63a28d28d33de87c1cbb296|Rajarshee Chatterjee|2026-07-26|chore: Separate Jest And Type Check Workflows +9d106a371620a513f1f4cf57f511936866a77fd2|Rajarshee Chatterjee|2026-07-26|feat: Add Semantic Version Bump Selection +8b0a8dc4c07b6d77532ef9b070bbce288afd92d4|Patrick Loser|2026-07-26|chore: Update Husky Pre-Commit and Pre-Push Hooks +72bfcad03469440c1e9cf5ff88b5ff97a544c45e|Patrick Loser|2026-07-26|fix: Resolve Type and Lint Issues +345d084eba5f1862c37d15c3790ce36610116d70|Rajarshee Chatterjee|2026-07-26|feat: Add Chapter Numbers To EPUB Titles (Closes #1405) +10b6042a9c671f47ba33f7810c47935a54706f40|Rajarshee Chatterjee|2026-07-26|feat: Add EPUB Export Progress Notifications +bd7161172551568e579e60ece7072f8c5a05fa65|Rajarshee Chatterjee|2026-07-26|feat: Improve Chapter Drawer And WebView App Bar +8f53550d2c6ec75b8d9cefe0142218aaec26f726|Rajarshee Chatterjee|2026-07-26|fix: Restore Tracker Search Requests +52914b978df3267342b9cd3d18270bf19ae5a7cb|Rajarshee Chatterjee|2026-07-26|fix: Migrate Volume Button Events to Expo Listener API +ff12759a66ad68791d7b8270579b8f7d020e73f5|Rajarshee Chatterjee|2026-07-26|ci: Run Preview Builds Daily When Missing +e62868b82ccd1784ad83cd00d338c47d2818df94|Rajarshee Chatterjee|2026-07-26|feat: Automate Draft Android Releases +97be48dfe17b6a5261284cc3052ab5dcbab8d20d|Rajarshee Chatterjee|2026-07-26|feat: Add Migration Review Options +cbe7d70e2df349db7ebf36a448e1f84b3903fb78|Rajarshee Chatterjee|2026-07-26|fix: Restore Collapsed TTS Button Interaction +6548e9a6f8efc0bb21e46af005072d7599b326e9|Rajarshee Chatterjee|2026-07-26|fix: Improve Paged Reader Layout and Performance +138fdff2e57e55e02f9775d11dfb47482701f5c5|Rajarshee Chatterjee|2026-07-26|fix: Prevent Duplicate WebView Bottom Insets +f1dd4b77a891e501b9345e5d0fe81b6405bce450|Rajarshee Chatterjee|2026-07-26|feat: Improve Empty Chapter Reporting +9b356ffd17c28d7f37285157f3f34488af67a266|Rajarshee Chatterjee|2026-07-26|fix: Preserve Client IDs And Improve Environment Generation +5cf7e15d7631f3ada56d4cb979694214745bd10d|Rajarshee Chatterjee|2026-07-26|feat: Add Reader Setting Descriptions +6a7e90c179b735521aa9d778e68f1727607e8ac5|Rajarshee Chatterjee|2026-07-26|fix: Align Advanced Reader Tabs And Localize Actions +99c31d56bb45c6e04fe96cff3f14c3ca55fccba4|Rajarshee Chatterjee|2026-07-26|fix: Use MD3 Outline Variant For Tab Borders +4d98247980c21f892df6cf0da19ecad8a6f0d136|Rajarshee Chatterjee|2026-07-26|feat: Add Android DNS Over HTTPS Support +164f5d259243a68f14a7e8a069b2e2cfc42aab84|Rajarshee Chatterjee|2026-07-25|feat: Add Automatic Library Update Scheduling +89cddf9e3d07d3f7a7c7cb71e8cc8e5e4e96fcc3|Rajarshee Chatterjee|2026-07-25|fix: Expand Discover Row Ripple +eac6ad584e85d4ed72cf76fafcde1c4d0551687d|Rajarshee Chatterjee|2026-07-25|feat: Improve Novel App Bar And EPUB Export UX +93fe04c28cc4776a96f86aa157b729ddf3c218c3|Rajarshee Chatterjee|2026-07-25|feat: Polish Reader Controls And Navigation +c9358b3a9fc49ad6a98ac0293da46bf9eab39bf8|Rajarshee Chatterjee|2026-07-25|feat: Update Android Branding Assets +67d2c7c01b101d83d0033aad2c0a99247f04620e|Rajarshee Chatterjee|2026-07-25|feat: Add Collapsible TTS Controls And Stable Notification Progress +c30441b9493d5e75fbe82a608b034c6239fa493f|Rajarshee Chatterjee|2026-07-25|feat: Add Smart Library Update Filters +6d2a9f8e1530604860375a9be3abb92b4502d01b|Rajarshee Chatterjee|2026-07-25|fix: Prevent Slider Flicker After Release +799845426c8f2e651de508d471d8319add41d2cc|Rajarshee Chatterjee|2026-07-25|feat: Add Parallel Library Updates Across Sources +8ac628b3deda367f68af93f9f43d7a1da8c80f9d|Rajarshee Chatterjee|2026-07-25|fix: Improve History Removal Flow. +d02291c22c079e5faee9817b00c7bd87ed7f47da|Rajarshee Chatterjee|2026-07-25|feat: Remember Last Used Library Category +7af0911f5b17e1c606dbf60e0f1d1b8863c78221|Rajarshee Chatterjee|2026-07-25|feat: Add High-Resolution Android Flavor Assets +098782d6aa9a188bf007402155bd66e3dbc14523|Rajarshee Chatterjee|2026-07-25|refactor: Simplify Settings Layout And Theme Selection +732628c40bb3f1afbbb0336577f9a56f97ef9115|Rajarshee Chatterjee|2026-07-25|feat: Add Date Format And Relative Timestamp Settings. +5e62c36a9b0c0d7ca562383f2d9938e722216f00|Rajarshee Chatterjee|2026-07-25|feat: Add Library Settings And Category-Based Global Updates +3d102a52a7d780b096afd78c6d5214e46b670b47|Rajarshee Chatterjee|2026-07-25|perf(reader): Cut Chapter Open Latency and Reading-Time Re-Renders (#1899) +4dcf21303715793087ea3bd49a9d510943e30944|Rajarshee Chatterjee|2026-07-25|fix: Restore Reader Changes Reverted During Expo Migration +4de82472ee70fd0c36114f0ee2e213ff6bf81099|Rajarshee Chatterjee|2026-07-25|fix: Resolve Require Cycles And LegendList Render Updates +58db0352e6359d4b1c69391d260105abbe4ede60|Rajarshee Chatterjee|2026-07-25|fix: Restore Metro Reader Asset Middleware +74808f086e5e3fbd479138681d700814c3370386|Rajarshee Chatterjee|2026-07-24|feat: Replace Legacy Media Controls with Nitro-Powered Playback (#1896) +b96351ea8117f4a1e464b0a7ab1820a557172c92|amir-rezaei|2026-07-24|docs: Fix Contraction and Missing Article in `TESTING.md` (#1895) +eb8dc6df368ba7628178d6481294fca1b30f9019|amir-rezaei|2026-07-24|docs: Fix "Withoud" Typo in `CONTRIBUTING-NIX.md` (#1894) +0c8546188eb33a0ac3be46596503cd38e4fb4d75|Rajarshee Chatterjee|2026-07-24|feat: Add MD3 Slider Component. +e9f6bdaa203890fb7d69b261f31afc431fba429c|Rajarshee Chatterjee|2026-07-24|fix: Modernize Menu Styling And Outside-Tap Dismissal +3d34658d0faee3f1a1dce6ed77f745804851c4c0|Rajarshee Chatterjee|2026-07-24|feat: Add Material 3 Top Tab Indicators +d44ce7412ac512b392d35e33d6dc9d98587a2350|amir-rezaei|2026-07-24|docs: Fix Trailing Period, Organization Expansion, and Comma in `CONTRIBUTING.md` (#1893) +17c891e1334409fa29d4a365b9ba46014b81b591|Rajarshee Chatterjee|2026-07-24|refactor: Standardize Bottom Sheet UX and Styling +9805da63cf23d58f0561b9335e0a07c1789e9a82|Rajarshee Chatterjee|2026-07-24|refactor: Optimize Novel Screen Performance And Architecture +afc20277e0f8a8d4b49d1ebd1c7689c599ba2e06|Rajarshee Chatterjee|2026-07-24|fix: Restore Android Build Types For CI +14061d207b77d8930c4d15af135189ccb4f4bd9e|amir-rezaei|2026-07-24|docs: Fix Title Case Headings, GitHub Capitalization, and E.g. Formatting in `CONTRIBUTING.md` (#1892) +916374b5df3cfa3b09d464109340ad81bdbef457|Rajarshee Chatterjee|2026-07-24|refactor: Reorganize Repository Structure +0a2a6c96ca453fd68ce42d4723c0f42c0a730519|Rajarshee Chatterjee|2026-07-24|feat: Add Variant-Specific Android Splash Screens +65e86100304ae27ba8049937558b4e25b49252f6|Rajarshee Chatterjee|2026-07-24|refactor: Replace Bottom Navigation Shift With Fade +1322065290f63141633c30cd3013b7ba81c12fae|Rajarshee Chatterjee|2026-07-24|feat: Add Variant-Specific Android App Icons +22a1efd1e5c548a27b3ab3b8c54909a5092c1e37|Rajarshee Chatterjee|2026-07-24|perf: Optimize React Native Skeleton Loading Components +269606ebb8e352f72fbab401bd5d93ed15c1a7a2|Rajarshee Chatterjee|2026-07-24|fix: Resolve Nitro EPUB Import Alias +4cb1890b62eb57cc75ca0628a11be312c930ddc4|Rajarshee Chatterjee|2026-07-24|feat: Add Nitro EPUB Exporter +a421177f0cb5b74e30b869c6595ed12c9263586a|Rajarshee Chatterjee|2026-07-24|fix: Improve Category Badge Layout And Drag Reordering +a16ec876311692a6cb3da9348d09e7cf287c9fb6|Rajarshee Chatterjee|2026-07-24|perf: Optimize Novel Covers, Lists, and Chapter Groups +dfd620aa292fadd3b8798013701ae1bef954fe57|Rajarshee Chatterjee|2026-07-23|feat: Standardize Dialogs with Material 3 Patterns +a5a6e8326f72d00271fd01cf8d6374cc35310147|Rajarshee Chatterjee|2026-07-23|chore: Update `@legendapp/list` to 3.3.3 +7f48a8ba8299180ffd5b07ba6314960213cd13cd|Rajarshee Chatterjee|2026-07-23|refactor: Improve Plugins Screen UX and Code +ae34d7b4ec6be28f7ad5ae25c663c7d2b3db43ef|Rajarshee Chatterjee|2026-07-23|feat: Add Dynamic Preview APK Metadata +44c8e54ed80bb47cc49c2461adca66ced94b2d1c|Rajarshee Chatterjee|2026-07-23|feat: Add Dynamic Material Theme Colors (Closes #1863) +4a4208e33602d65ef9942d05e54a692addd87e2f|Ecasept|2026-07-23|feat: Add Time-Tracking Toggle and Inactivity Timeout (#1890) +d2385b8dc2a9cd911e7567e55d6b7dc2c43f2372|CD-Z|2026-07-23|Update build.yml +9babe1676dd053069d127743a61bc7a8bc11256c|Rajarshee Chatterjee|2026-07-22|refactor: Remove React Native Background Actions Manifest Customization +8c38f499e4b62de02157aa8dcd1c9207978033bf|Rajarshee Chatterjee|2026-07-22|chore: Update AppState Test Setup And `pnpm` Configuration +89c43c1eeffd5a9980b3aaebe647f3d94252f8b8|Ecasept|2026-07-22|feat: Implement Time Tracking and Statistics (#1886) +c3c891cea084796b3640233826dacfc7aab6f8d6|Patrick Loser|2026-07-22|refactor: Migrate to Expo Managed Workflow (#1885) +e4cf64a112dbfd9f9dd853abec99afbdc09f1460|Rajarshee Chatterjee|2026-07-22|refactor: Migrate Background Tasks to Native Android Scheduling (#1889) +3e1e211098779264ba123121cc66bb48385fcf77|Luigi|2026-07-19|feat: Add Address Bar in WebView (#1870) +02b6b9848fb3349373de09a4343b6d273605b842|Ecasept|2026-07-19|fix: Prevent WebView Reset When Reader Settings Change (#1884) +71da09ed444707586eee6fc63f7f2ee182bb2cda|Shisii|2026-07-19|feat(reader): Add Current Chapter Search (#1877) +4404494ec9a57c3d2a8b1afb4734c9fc569773a2|Rajarshee Chatterjee|2026-07-19|fix(database): Repair Migration History For Scanlator Column +98a2a1d725d51d333ea3969b74434b2917a92161|Luigi|2026-07-15|feat: Implement Scanlator Filtering (#1864) +788f6cede7a5676a3d30cf0fd8e565cddd3c486c|Palloxin|2026-07-13|fix: Prevent Table Overflow and Clean Up CSS (#1876) (#1876) +7abee71ac33ab4b16c0c2a629640b1a7c2f5eb3b|Rajarshee Chatterjee|2026-07-13|test: Generate Debug Environment Variables In Testing Workflow +52d5c99c46b14148bd262c878701abf91c8b593b|Rajarshee Chatterjee|2026-07-02|fix: Order EPUB Chapters Across Paginated Pages (Closes #1762) +93bc5e5e4bac14fea10913cb215a7d9c297d6c31|Rajarshee Chatterjee|2026-07-02|fix: Handle Missing MAL List Entries and Null Progress (Closes #1875) +5d996f13883219090ad39ddbe7b52e88e36107c7|Jeon JaeHyeong|2026-07-01|feat(downloads): Make Chapter Download Cooldown Configurable (#1834) +bbb2645a6f6c47f7cda8d83ef096c329e03ffa6a|Palloxin|2026-07-01|chore: Clean Reader Preview Content (#1867) +54fedc2f531ecdda29dea74f3cc279a7a4525261|Vikas Movva|2026-07-01|fix(tts): Remove Leading and Trailing Quotes from Normalized Text (#1869) +e8c5550256dfe06c5ae12c106801e8688173a830|Palloxin|2026-07-01|perf: Optimize Regex for Removing Extra Paragraph Spacing (#1872) +57b9d41b5c3eacc9cbf5e38082c4efb2a1a11f5e|Ali Hamza Malik|2026-07-01|fix(service): Correct Notification Throttle Logic in ServiceManager.setMeta (#1873) +34be7a123499cc7733161482fdf5d42e4c1bae51|Jeon JaeHyeong|2026-05-24|fix(native-file): prevent fatal crash when unlinking missing files (#1832) +60f42ed6243dc5d4daa68c727d60c9fb5dd6576a|Jeon JaeHyeong|2026-05-24|chore(build): patch @rock-js/plugin-metro for Windows compatibility (#1830) +25c93afabe24d34f2130ee0fd7e0846a9b4861ff|Anthony Truong|2026-05-24|fix(reader): restore saved chapter progress from db if available (#1850) +2cd5a97ee8e482e92b45e9b702c9ba974e06a092|Gaurav Tupe|2026-05-10|fix: await database operations inside transactions (#1845) +8f47e8fd1e1aedaf22df8275484f27b5d40b2a39|Patrick Loser|2026-05-07|Theme switcher refactor (#1839) +eafa545b2a34320b5e1eb2d99ba61b6888838da8|Patrick Loser|2026-05-06|Use novel refactor (#1838) +b680a07750f0b7650836c0573566e641d4aaa679|Jeon JaeHyeong|2026-05-03|fix(browse): use gesture-handler Pressable in PluginListItem (#1828) +2a919ec0f963a4d4613e3e0ebe99a16fa7e2dbe3|Jeon JaeHyeong|2026-05-03|chore: gitignore tsconfig.tsbuildinfo build cache (#1829) +01596c045a4637acf056f75cf87ffc0198d79899|Definisi|2026-04-14|fix: Apply active filters to chapter count display (Closes #1793) (#1797) +3de93c5c093ab2fb7e0d509e367e4fec813082e2|Patrick Loser|2026-04-13|feat: migrate from rn community cli to rock (#1812) +306f5b27e2716f1a963fd992c06f5c961f44c926|Patrick Loser|2026-04-06|fix: install the right version of hermes compiler +06f0f66e3e4659db14127b1b56202dd3a8fca521|Patrick Loser|2026-04-05|fix: Add hermes-compiler dev dep +64921a054e9d4a11a0aad294b9703a3ab4c16b9f|Patrick Loser|2026-04-05|feat: Expo 55 upgrade +44e035ad38b25f79741199b6aea64479831ccc14|Rajarshee Chatterjee|2026-03-12|chore: Update Translations (#1683) +1803cd071d24a4d8ada8c55814a6ce62eaf98481|Patrick Loser|2026-02-25|feat: Add testing support for react code (#1763) +e4d7ba306b4441b4743ab9edb7c96751d62e3cde|Patrick Loser|2026-02-24|fix: Safety level may not be changed inside a transaction error (#1758) +cc0428752b2e9869677a6bd2dccc449f7a98a552|Rajarshee Chatterjee|2026-02-20|feat: Replace TTS Notifications with Android MediaSession Controls (Closes #1711) +b8c177bc55016b5a8fb96cc120e8ac050a2f7c73|Patrick Loser|2026-02-19|refactor: Migrate to Drizzle and `op-sqlite` (#1735) +7083c51dea1be0af3bb0d53c341f19b32a35dca4|Rajarshee Chatterjee|2026-02-19|fix: Include selectedIds in Chapter List extraData for Selection Highlight (Closes #1750) +f0693204e1f8f961dff85b195f3c7f4f6f9bb0ce|tomsLt0|2026-02-18|fix: Improve Next Chapter Selection Logic (#1744) +3f8071e48ee603f906cc9e3910abe90d6e3874a5|Rajarshee Chatterjee|2026-02-18|chore: Release v2.0.3 +a1c5350e8e330e783b6537c366351c2082e99150|Rajarshee Chatterjee|2026-02-12|fix: Miscellaneous UI Tweaks +8f237909d54310220d518000ace81b5fddbc3b5f|Rajarshee Chatterjee|2026-02-12|feat: Show First Unread Chapter in ReadButton and FAB When No Last Read +11ff69d4f5b4a79286247ac6c40ca1eac72ad283|Rajarshee Chatterjee|2026-02-12|fix: Pre-Fetch Adjacent Page Chapters in Reader and Library Updates +d15cf987425709f97ec168a46af8d936e944c29e|Rajarshee Chatterjee|2026-02-12|fix: Order Chapter Pages Numerically in Queries for Accurate Retrieval (Closes #1742) +29bd6612e9a9950c61b9a569b761ffa1aaa5d4eb|Rajarshee Chatterjee|2026-02-12|refactor: Optimize Library Screen Performance and Fix Category Bug +ab1e36d280768be46acdb57557e09b3e2d37f12b|Rajarshee Chatterjee|2026-02-08|refactor: Update Components to Follow Material Design 3 Guidelines +f1fdafd37f1f7d0816484c2e414e39d9885499d8|Rajarshee Chatterjee|2026-02-07|fix: Prevent Tab Bar Height Jump and Shadow Flash on Browse Screen +4c7684a3a71e57fb27f910b3edfbdd902353dd47|Rajarshee Chatterjee|2026-02-07|refactor: Optimize Novel Screen Performance and Fix Download State Tracking +2f45db659b2fdfa77f9213023d9d2707cbc452e1|Rajarshee Chatterjee|2026-02-07|fix: Replace Sync Database Operations With Async to Prevent Database Lock (Closes #1716) +9e38ad114d182d39880a962cb7acefa55235310d|Rajarshee Chatterjee|2026-02-06|fix: Sanitize EPUB FileName to Remove Filesystem-Unsafe Characters (Closes #1480) +45c4ea8ca02031c803224b0d906a6045364d2817|Rajarshee Chatterjee|2026-02-06|fix: Handle IOException in NativeFile to Prevent EPUB Import Crash Loop (Closes #1315) +d2b95ae3166f337d8d3597e1cc9a0bff65832d62|Rajarshee Chatterjee|2026-02-06|fix: Use NativeFile for Custom CSS/JS Import to Fix Content URI Read Failure (Closes #1661) +02121152b3e3cdbbaaff8a34b51cb8e9b2010538|Rajarshee Chatterjee|2026-02-06|fix: Stabilize Scroll Handler References to Prevent Infinite Re-render Loop (Closes #1679) +caed965b6bad1b788eee3035daba42322454fe26|Rajarshee Chatterjee|2026-02-06|fix: Prevent EPUB Import Crash from DocumentPicker Cache Cleanup (Closes #1725) +4088d5966bbc5ec27801b1ddc553f5621b4b6d79|Rajarshee Chatterjee|2026-02-06|fix: Close OkHttp Response and Streams in NativeFile.downloadFile (Closes #1740) +a062beee87d4173383e3033eeacb9376642645e5|Rajarshee Chatterjee|2026-02-06|fix: Add Max Height to Set Categories Modal (Closes #1738) +30958825162fbba2f7b805de6d41ab8e9183f28b|Patrick Loser|2026-02-01|Replace react-native-paper menus with custom implementation (#1733) +4ad06397960f1fbc633604ef06c2dfca193a220e|Patrick Loser|2026-01-31|Fix: Chapter loading for jump to chapter modal and chapter drawer +b53ec3352f8ce9084e32ddaa352526b39ec6003c|Rajarshee Chatterjee|2026-01-28|feat: Support Additional Plugin Setting Types (Dropdown and Checkboxes) +c40edd5b2cc68c2b746ba588aa39e142ab99f01e|Rajarshee Chatterjee|2026-01-25|feat: Add Kitsu Tracker (Closes #416) +bdd42caaaf3c3d5961d444aabc9150057d2de5d9|Rajarshee Chatterjee|2026-01-20|chore: Update Repository Links to Use lneader Instead of LNReader +ab4eee31ceb82c3f89e459279bef8b56de5ec10c|Rajarshee Chatterjee|2026-01-20|feat: Allow All Safe Tags and Attributes When Sanitizing Chapter HTML (Closes #1715) +3f9886a2e5300bb9913888872847727ab63597fc|Rajarshee Chatterjee|2026-01-17|chore: Update Repository Links to Use lneader Instead of LNReader +198b7443ae9c0ee7cc3ff608c0fc89a88880fa84|kingstar1111|2026-01-20|feat: Add RTL (Right-to-Left) Language Support (#1717) +f9c5ca303d71830811a203f98a17c3dd4846f887|Rajarshee Chatterjee|2026-01-16|chore: Update Indonesian Translations (Closes #1653) +9922d4394058a6da29ab2789cd8d7b75a37bf1bc|Rajarshee Chatterjee|2026-01-15|fix: Remove Missing Image References During EPUB Export (Closes #1696) +ae99b077d4e883e678b31ba0235a7abae88e1e94|Rajarshee Chatterjee|2026-01-15|feat: Add `@noble/ciphers` for Plugins (Closes #1709) +0cb9da90276c6a30d5d917557195f0f0b0ebc70c|Rajarshee Chatterjee|2026-01-15|refactor: Replace `withTransactionAsync` With `withExclusiveTransactionAsync` for Improved Concurrency Handling (Closes #1636) diff --git a/src/components/BottomSheet/BottomSheet.tsx b/src/components/BottomSheet/BottomSheet.tsx index a1dd84b518..e3dfd6561a 100644 --- a/src/components/BottomSheet/BottomSheet.tsx +++ b/src/components/BottomSheet/BottomSheet.tsx @@ -1,6 +1,5 @@ import React, { RefObject, useCallback, useMemo, useRef } from 'react'; import { - // BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetModal, BottomSheetModalProps, @@ -9,11 +8,28 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useBackHandler } from '@hooks/index'; import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types'; import BottomSheetBackdrop from './BottomSheetBackdrop'; -import { useWindowDimensions } from 'react-native'; +import { StyleSheet, useWindowDimensions } from 'react-native'; +import { useTheme } from '@hooks/persisted'; +import { getBottomSheetLayout, normalizeBottomSheetSnapPoints } from './layout'; interface BottomSheetProps extends Omit< BottomSheetModalProps, - 'ref' | 'onChange' | 'snapPoints' + | 'backgroundComponent' + | 'backgroundStyle' + | 'backdropComponent' + | 'bottomInset' + | 'containerStyle' + | 'enableDynamicSizing' + | 'enableOverDrag' + | 'enablePanDownToClose' + | 'handleComponent' + | 'handleIndicatorStyle' + | 'handleStyle' + | 'onChange' + | 'ref' + | 'snapPoints' + | 'style' + | 'topInset' > { bottomSheetRef: RefObject; onChange?: (index: number) => void; @@ -24,13 +40,19 @@ const BottomSheet: React.FC = ({ bottomSheetRef, children, onChange, - containerStyle, snapPoints, ...otherProps }) => { const indexRef = useRef(null); const { bottom, top } = useSafeAreaInsets(); - const { height } = useWindowDimensions(); + const { height, width } = useWindowDimensions(); + const theme = useTheme(); + const { horizontalInset, maxHeight, topMargin } = getBottomSheetLayout({ + bottom, + height, + top, + width, + }); const renderBackdrop = useCallback( (backdropProps: BottomSheetBackdropProps) => ( @@ -46,30 +68,32 @@ const BottomSheet: React.FC = ({ }); const safeSnapPoints = useMemo(() => { - if (!snapPoints) { - return undefined; - } - const maxHeight = height - top; - return snapPoints - .sort((a, b) => a - b) - .map(point => { - if (point < maxHeight - 100) return point; - return maxHeight; - }); - }, [height, snapPoints, top]); + return normalizeBottomSheetSnapPoints(snapPoints, maxHeight); + }, [maxHeight, snapPoints]); return ( { onChange?.(index); indexRef.current = index; }} enableDynamicSizing={false} enableOverDrag={false} + enablePanDownToClose + topInset={top + topMargin} snapPoints={safeSnapPoints} {...otherProps} > @@ -79,3 +103,10 @@ const BottomSheet: React.FC = ({ }; export default React.memo(BottomSheet); + +const styles = StyleSheet.create({ + modal: { + borderTopLeftRadius: 28, + borderTopRightRadius: 28, + }, +}); diff --git a/src/components/BottomSheet/BottomSheetBackdrop.tsx b/src/components/BottomSheet/BottomSheetBackdrop.tsx index d2a1db10da..58d1c8f570 100644 --- a/src/components/BottomSheet/BottomSheetBackdrop.tsx +++ b/src/components/BottomSheet/BottomSheetBackdrop.tsx @@ -1,25 +1,41 @@ -import React, { memo } from 'react'; +import { memo } from 'react'; import { Pressable, StyleSheet } from 'react-native'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; import { useBottomSheet } from '@gorhom/bottom-sheet'; import { BottomSheetBackdropProps } from '@gorhom/bottom-sheet/lib/typescript/components/bottomSheetBackdrop/types'; +import Color from 'color'; + +import { useTheme } from '@hooks/persisted'; +import { getString } from '@strings/translations'; const styles = StyleSheet.create({ container: { - backgroundColor: 'rgba(0, 0, 0, 0.4)', + bottom: 0, + left: 0, + position: 'absolute', + right: 0, + top: 0, }, }); const AnimatedPressable = Animated.createAnimatedComponent(Pressable); const CustomBackdrop = ({ style }: BottomSheetBackdropProps) => { + const theme = useTheme(); const { close } = useBottomSheet(); + return ( close()} - entering={FadeIn.duration(100)} - exiting={FadeOut.duration(100)} - style={[styles.container, style]} + entering={FadeIn.duration(200)} + exiting={FadeOut.duration(150)} + style={[ + styles.container, + style, + { backgroundColor: Color(theme.scrim).alpha(0.32).string() }, + ]} /> ); }; diff --git a/src/components/BottomSheet/__tests__/layout.test.ts b/src/components/BottomSheet/__tests__/layout.test.ts new file mode 100644 index 0000000000..76ec73e7fd --- /dev/null +++ b/src/components/BottomSheet/__tests__/layout.test.ts @@ -0,0 +1,45 @@ +import { + getBottomSheetLayout, + normalizeBottomSheetSnapPoints, +} from '../layout'; + +describe('bottom sheet layout', () => { + it('preserves the Material 3 top margin on compact windows', () => { + expect( + getBottomSheetLayout({ + bottom: 16, + height: 800, + top: 24, + width: 400, + }), + ).toEqual({ + horizontalInset: 0, + maxHeight: 688, + topMargin: 72, + }); + }); + + it('centers sheets at the 640dp max width on large windows', () => { + expect( + getBottomSheetLayout({ + bottom: 16, + height: 800, + top: 24, + width: 1000, + }), + ).toEqual({ + horizontalInset: 180, + maxHeight: 704, + topMargin: 56, + }); + }); + + it('sorts and clamps snap points without mutating the caller array', () => { + const snapPoints = [720, 320, 480]; + + expect(normalizeBottomSheetSnapPoints(snapPoints, 600)).toEqual([ + 320, 480, 600, + ]); + expect(snapPoints).toEqual([720, 320, 480]); + }); +}); diff --git a/src/components/BottomSheet/layout.ts b/src/components/BottomSheet/layout.ts new file mode 100644 index 0000000000..94199caf92 --- /dev/null +++ b/src/components/BottomSheet/layout.ts @@ -0,0 +1,39 @@ +const MAX_WIDTH = 640; +const COMPACT_TOP_MARGIN = 72; +const LARGE_WINDOW_MARGIN = 56; + +interface BottomSheetLayoutInput { + bottom: number; + height: number; + top: number; + width: number; +} + +export const getBottomSheetLayout = ({ + bottom, + height, + top, + width, +}: BottomSheetLayoutInput) => { + const isLargeWindow = width > MAX_WIDTH; + const topMargin = isLargeWindow ? LARGE_WINDOW_MARGIN : COMPACT_TOP_MARGIN; + const horizontalInset = isLargeWindow + ? Math.max(LARGE_WINDOW_MARGIN, (width - MAX_WIDTH) / 2) + : 0; + + return { + horizontalInset, + maxHeight: Math.max(0, height - top - bottom - topMargin), + topMargin, + }; +}; + +export const normalizeBottomSheetSnapPoints = ( + snapPoints: number[] | undefined, + maxHeight: number, +) => + snapPoints + ? [...snapPoints] + .sort((a, b) => a - b) + .map(point => Math.min(point, maxHeight)) + : undefined; diff --git a/src/components/BottomTabBar/index.tsx b/src/components/BottomTabBar/index.tsx index 918bbbb996..b5933229f0 100644 --- a/src/components/BottomTabBar/index.tsx +++ b/src/components/BottomTabBar/index.tsx @@ -7,6 +7,7 @@ import { ThemeColors } from '@theme/types'; import Animated from 'react-native-reanimated'; import { useScaledDimensions } from '@hooks/useScaledDimensions'; import { useAppSettings } from '@hooks/persisted'; +import Color from 'color'; interface CustomBottomTabBarProps extends BottomTabBarProps { theme: ThemeColors; @@ -37,6 +38,7 @@ function CustomBottomTabBar({ () => getStyles(padding, margin, iconSize, borderRadius, uiScale), [padding, margin, iconSize, borderRadius, uiScale], ); + const transparentBg = Color(theme.primaryContainer).fade(1).rgb().toString(); const getLabelText = useCallback( (route: any) => { if (!showLabelsInNav && route.name !== state.routeNames[state.index]) { @@ -62,7 +64,7 @@ function CustomBottomTabBar({ styles.container, { backgroundColor: theme.surface2 || theme.surface, - paddingBottom: insets?.bottom || 0, + paddingBottom: 16 + (insets?.bottom || 0), }, ]} > @@ -113,7 +115,7 @@ function CustomBottomTabBar({ width: isFocused ? iconSize.lg + 20 : iconSize.lg, backgroundColor: isFocused ? theme.primaryContainer - : 'transparent', + : transparentBg, }, ]} > @@ -163,7 +165,7 @@ const getStyles = ( flex: 1, justifyContent: 'center', alignItems: 'center', - paddingVertical: padding.xs + 2, + paddingVertical: padding.xs, paddingHorizontal: padding.xs, position: 'relative', }, diff --git a/src/components/Chip/SelectableChip.tsx b/src/components/Chip/SelectableChip.tsx index 518dff71c9..15f7253aaf 100644 --- a/src/components/Chip/SelectableChip.tsx +++ b/src/components/Chip/SelectableChip.tsx @@ -33,7 +33,6 @@ const SelectableChip: React.FC = ({ showSelectedCheck={showCheckIcon} style={styles.chip} textStyle={{ fontFamily: customFontFamily }} - selectedColor={theme.primary} theme={{ colors: theme }} mode={selected ? 'flat' : mode} > diff --git a/src/components/ColorPickerModal/ColorPickerModal.tsx b/src/components/ColorPickerModal/ColorPickerModal.tsx index d9d85372a0..c880f153fb 100644 --- a/src/components/ColorPickerModal/ColorPickerModal.tsx +++ b/src/components/ColorPickerModal/ColorPickerModal.tsx @@ -3,16 +3,18 @@ import { FlatList, Pressable, StyleSheet, View } from 'react-native'; import { Text } from '@components/AppText'; import { Portal, TextInput } from 'react-native-paper'; -import { Modal } from '@components'; +import { Button, Modal } from '@components'; import { ThemeColors } from '../../theme/types'; +import { Row } from '@components/Common'; import { useAppSettings } from '@hooks/persisted/useSettings'; +import { getString } from '@strings/translations'; import { scaleDimension } from '@theme/scaling'; interface ColorPickerModalProps { visible: boolean; title: string; color: string; - onSubmit: (val: string) => void; + onSubmit: (val: string | undefined) => void; closeModal: () => void; theme: ThemeColors; showAccentColors?: boolean; @@ -52,6 +54,9 @@ const ColorPickerModal: React.FC = ({ }, flex: { flex: 1 }, marginBottom: { marginBottom: scaleDimension(8, uiScale) }, + row: { + justifyContent: 'flex-end', + }, }), [uiScale], ); @@ -76,6 +81,10 @@ const ColorPickerModal: React.FC = ({ setError('Enter a valid hex color code'); } }; + const onReset = () => { + onSubmit(undefined); + closeModal(); + }; const accentColors = [ '#EF5350', @@ -141,6 +150,10 @@ const ColorPickerModal: React.FC = ({ error={Boolean(error)} /> {error} + + - diff --git a/src/screens/settings/components/MangaUpdatesLoginDialog.tsx b/src/screens/settings/components/TrackerLoginDialog.tsx similarity index 92% rename from src/screens/settings/components/MangaUpdatesLoginDialog.tsx rename to src/screens/settings/components/TrackerLoginDialog.tsx index b043a88ae6..8b0f3185e5 100644 --- a/src/screens/settings/components/MangaUpdatesLoginDialog.tsx +++ b/src/screens/settings/components/TrackerLoginDialog.tsx @@ -6,16 +6,20 @@ import { Modal } from '@components'; import { useAppSettings, useTheme } from '@hooks/persisted'; import { scaleDimension } from '@theme/scaling'; -interface MangaUpdatesLoginDialogProps { +interface TrackerLoginDialogProps { visible: boolean; + trackerName: string; onDismiss: () => void; onSubmit: (username: string, password: string) => Promise; + usernameLabel?: string; } -const MangaUpdatesLoginDialog: React.FC = ({ +const TrackerLoginDialog: React.FC = ({ visible, + trackerName, onDismiss, onSubmit, + usernameLabel = 'Username', }) => { const theme = useTheme(); const { uiScale = 1.0 } = useAppSettings(); @@ -67,7 +71,7 @@ const MangaUpdatesLoginDialog: React.FC = ({ const handleSubmit = async () => { if (!username.trim() || !password.trim()) { - setError('Username and password are required'); + setError(`${usernameLabel} and password are required`); return; } @@ -97,7 +101,7 @@ const MangaUpdatesLoginDialog: React.FC = ({ - Login to MangaUpdates + Login to {trackerName} = ({ borderColor: theme.outline, }, ]} - placeholder="Username" + placeholder={usernameLabel} placeholderTextColor={theme.onSurfaceVariant} value={username} onChangeText={setUsername} @@ -167,4 +171,4 @@ const MangaUpdatesLoginDialog: React.FC = ({ ); }; -export default MangaUpdatesLoginDialog; +export default TrackerLoginDialog; diff --git a/src/services/ServiceManager.ts b/src/services/ServiceManager.ts index 30d245b2a6..46b8a672cf 100644 --- a/src/services/ServiceManager.ts +++ b/src/services/ServiceManager.ts @@ -149,8 +149,8 @@ export default class ServiceManager { taskList[0].task?.name !== 'DOWNLOAD_CHAPTER' ) { const now = Date.now(); - if (now - this.lastNotifUpdate > 1000) { - const delay = 1000 - now - this.lastNotifUpdate; + if (now - this.lastNotifUpdate < 1000) { + const delay = 1000 - (now - this.lastNotifUpdate); const id = ++this.currentPendingUpdate; setTimeout(() => { if (this.currentPendingUpdate !== id) { diff --git a/src/services/Trackers/__tests__/kitsu.test.ts b/src/services/Trackers/__tests__/kitsu.test.ts new file mode 100644 index 0000000000..14371b366b --- /dev/null +++ b/src/services/Trackers/__tests__/kitsu.test.ts @@ -0,0 +1,426 @@ +/** + * Kitsu tracker unit tests + * + * Tests the Kitsu tracker's search, library entry and score conversion + * logic by mocking the global fetch API. + */ + +import { authenticateWithCredentials, kitsuTracker } from '../kitsu'; + +const BASE_URL = 'https://kitsu.app/api/edge/'; +const LOGIN_URL = 'https://kitsu.app/api/oauth/token'; +const ALGOLIA_KEY_URL = 'https://kitsu.app/api/edge/algolia-keys/media/'; + +const originalFetch = global.fetch; +let mockFetch: jest.Mock; + +const jsonResponse = (status: number, body: any) => ({ + status, + json: jest.fn().mockResolvedValue(body), +}); + +const auth = { + accessToken: 'test-access-token', + refreshToken: 'test-refresh-token', + expiresAt: new Date(), + meta: { + userId: 'user-1', + refreshToken: 'test-refresh-token', + createdAt: 1, + }, +}; + +const getFetchCall = (predicate: (url: string, options: any) => boolean) => + mockFetch.mock.calls.find( + ([url, options]: any[]) => + typeof url === 'string' && predicate(url, options || {}), + ); + +describe('kitsuTracker', () => { + beforeEach(() => { + mockFetch = jest.fn(); + global.fetch = mockFetch as unknown as typeof fetch; + }); + + afterEach(() => { + global.fetch = originalFetch; + jest.clearAllMocks(); + }); + + describe('handleSearch', () => { + beforeEach(() => { + mockFetch.mockImplementation((url: string) => { + if (url === ALGOLIA_KEY_URL) { + return Promise.resolve( + jsonResponse(200, { media: { key: 'algolia-test-key' } }), + ); + } + return Promise.resolve(jsonResponse(200, { hits: [] })); + }); + }); + + it('should return only items with subtype "novel" mapped to SearchResult', async () => { + mockFetch.mockImplementation((url: string) => { + if (url === ALGOLIA_KEY_URL) { + return Promise.resolve( + jsonResponse(200, { media: { key: 'algolia-test-key' } }), + ); + } + return Promise.resolve( + jsonResponse(200, { + hits: [ + { + id: 1, + canonicalTitle: 'Novel One', + chapterCount: 12, + subtype: 'novel', + posterImage: { original: 'https://example.com/one.png' }, + synopsis: 'A novel', + }, + { + id: 2, + canonicalTitle: 'Manga Only', + chapterCount: 3, + subtype: 'manga', + posterImage: { original: 'https://example.com/two.png' }, + synopsis: 'Not a novel', + }, + { + id: 3, + canonicalTitle: 'Novel Two', + chapterCount: null, + subtype: 'novel', + posterImage: null, + synopsis: null, + }, + ], + }), + ); + }); + + const result = await kitsuTracker.handleSearch('test', auth); + + expect(result).toHaveLength(2); + expect(result[0]).toEqual({ + id: 1, + title: 'Novel One', + coverImage: 'https://example.com/one.png', + totalChapters: 12, + }); + expect(result[1]).toEqual({ + id: 3, + title: 'Novel Two', + coverImage: '', + totalChapters: undefined, + }); + expect(result[1].coverImage).toBe(''); + expect(result[1].totalChapters).toBeUndefined(); + }); + + it('should return an empty array when Algolia returns a non-200 status', async () => { + mockFetch.mockImplementation((url: string) => { + if (url === ALGOLIA_KEY_URL) { + return Promise.resolve( + jsonResponse(200, { media: { key: 'algolia-test-key' } }), + ); + } + return Promise.resolve(jsonResponse(500, { error: 'boom' })); + }); + + const result = await kitsuTracker.handleSearch('test', auth); + + expect(result).toEqual([]); + }); + + it('should return an empty array when the Algolia key fetch fails', async () => { + mockFetch.mockResolvedValue(jsonResponse(403, { error: 'forbidden' })); + + const result = await kitsuTracker.handleSearch('test', auth); + + expect(result).toEqual([]); + }); + }); + + describe('getUserListEntry', () => { + it('should map an existing library entry to normalized status, progress and score', async () => { + mockFetch.mockResolvedValue( + jsonResponse(200, { + data: [ + { + id: 'lib-1', + attributes: { + status: 'current', + progress: 10, + ratingTwenty: 16, + startedAt: null, + finishedAt: null, + }, + }, + ], + }), + ); + + const result = await kitsuTracker.getUserListEntry(42, auth); + + expect(result).toEqual({ status: 'CURRENT', progress: 10, score: 8 }); + }); + + it('should map on_hold to PAUSED and planned to PLANNING', async () => { + mockFetch.mockResolvedValue( + jsonResponse(200, { + data: [ + { + id: 'lib-2', + attributes: { + status: 'on_hold', + progress: 3, + ratingTwenty: null, + startedAt: null, + finishedAt: null, + }, + }, + ], + }), + ); + + const result = await kitsuTracker.getUserListEntry(42, auth); + + expect(result.status).toBe('PAUSED'); + expect(result.score).toBe(0); + }); + + it('should return default values when no entry exists', async () => { + mockFetch.mockResolvedValue(jsonResponse(200, { data: [] })); + + const result = await kitsuTracker.getUserListEntry(42, auth); + + expect(result).toEqual({ status: 'CURRENT', progress: 0, score: 0 }); + }); + }); + + describe('updateUserListEntry', () => { + it('should PATCH an existing library entry with score*2 as ratingTwenty and mapped status', async () => { + mockFetch.mockImplementation((url: string) => { + if (url.includes('library-entries?filter[manga_id]=')) { + return Promise.resolve( + jsonResponse(200, { + data: [ + { + id: 'lib-1', + attributes: { + status: 'current', + progress: 10, + ratingTwenty: 10, + startedAt: null, + finishedAt: null, + }, + }, + ], + }), + ); + } + return Promise.resolve(jsonResponse(200, { data: { id: 'lib-1' } })); + }); + + const result = await kitsuTracker.updateUserListEntry( + 42, + { status: 'COMPLETED', progress: 20, score: 8 }, + auth, + ); + + expect(result).toEqual({ status: 'COMPLETED', progress: 20, score: 8 }); + + const patchCall = getFetchCall(url => url.includes('library-entries/')); + expect(patchCall).toBeDefined(); + const [patchUrl, patchOptions] = patchCall!; + expect(patchUrl).toBe(`${BASE_URL}library-entries/lib-1`); + expect(patchOptions.method).toBe('PATCH'); + const patchBody = JSON.parse(patchOptions.body); + expect(patchBody.data.attributes).toEqual({ + status: 'completed', + progress: 20, + ratingTwenty: 16, + }); + }); + + it('should POST to create a new library entry when none exists', async () => { + mockFetch.mockImplementation((url: string) => { + if (url.includes('library-entries?filter[manga_id]=')) { + return Promise.resolve(jsonResponse(200, { data: [] })); + } + if (url === `${BASE_URL}library-entries`) { + return Promise.resolve( + jsonResponse(201, { data: { id: 'new-lib-1' } }), + ); + } + return Promise.resolve( + jsonResponse(200, { data: { id: 'new-lib-1' } }), + ); + }); + + const result = await kitsuTracker.updateUserListEntry( + 42, + { status: 'PLANNING', progress: 0, score: 0 }, + auth, + ); + + expect(result).toEqual({ status: 'PLANNING', progress: 0, score: 0 }); + + const postCall = getFetchCall( + (url, options) => + url === `${BASE_URL}library-entries` && options.method === 'POST', + ); + expect(postCall).toBeDefined(); + const postBody = JSON.parse(postCall![1].body); + expect(postBody.data.attributes).toEqual({ + status: 'planned', + progress: 0, + }); + expect(postBody.data.relationships.media.data).toEqual({ + id: '42', + type: 'manga', + }); + }); + + it('should PATCH the newly created entry with score when score is provided', async () => { + mockFetch.mockImplementation((url: string) => { + if (url.includes('library-entries?filter[manga_id]=')) { + return Promise.resolve(jsonResponse(200, { data: [] })); + } + if (url === `${BASE_URL}library-entries`) { + return Promise.resolve( + jsonResponse(201, { data: { id: 'new-lib-1' } }), + ); + } + return Promise.resolve( + jsonResponse(200, { data: { id: 'new-lib-1' } }), + ); + }); + + await kitsuTracker.updateUserListEntry( + 42, + { status: 'CURRENT', progress: 5, score: 4 }, + auth, + ); + + const patchCall = getFetchCall( + url => url === `${BASE_URL}library-entries/new-lib-1`, + ); + expect(patchCall).toBeDefined(); + const patchBody = JSON.parse(patchCall![1].body); + expect(patchBody.data.attributes.ratingTwenty).toBe(8); + }); + + it('should throw when the library update fails', async () => { + mockFetch.mockImplementation((url: string) => { + if (url.includes('library-entries?filter[manga_id]=')) { + return Promise.resolve( + jsonResponse(200, { + data: [ + { + id: 'lib-1', + attributes: { + status: 'current', + progress: 10, + ratingTwenty: 10, + startedAt: null, + finishedAt: null, + }, + }, + ], + }), + ); + } + return Promise.resolve(jsonResponse(500, { error: 'boom' })); + }); + + await expect( + kitsuTracker.updateUserListEntry( + 42, + { status: 'COMPLETED', progress: 20, score: 8 }, + auth, + ), + ).rejects.toThrow('Failed to update Kitsu entry'); + }); + }); + + describe('authenticateWithCredentials', () => { + it('should exchange credentials for a token and fetch the current user id', async () => { + mockFetch.mockImplementation((url: string) => { + if (url === LOGIN_URL) { + return Promise.resolve( + jsonResponse(200, { + access_token: 'access-1', + token_type: 'Bearer', + created_at: 1000, + expires_in: 3600, + refresh_token: 'refresh-1', + }), + ); + } + if (url.includes('users?filter[self]=true')) { + return Promise.resolve( + jsonResponse(200, { data: [{ id: 'user-1' }] }), + ); + } + return Promise.resolve(jsonResponse(404, {})); + }); + + const result = await authenticateWithCredentials( + 'test@example.com', + 'password', + ); + + expect(result.accessToken).toBe('access-1'); + expect(result.refreshToken).toBe('refresh-1'); + expect(result.meta?.userId).toBe('user-1'); + }); + + it('should throw with the error description on invalid credentials', async () => { + mockFetch.mockResolvedValue( + jsonResponse(400, { + error_description: 'Invalid username or password', + }), + ); + + await expect(authenticateWithCredentials('bad', 'bad')).rejects.toThrow( + 'Invalid username or password', + ); + }); + }); + + describe('revalidate', () => { + it('should refresh the session using the stored refresh token', async () => { + mockFetch.mockResolvedValue( + jsonResponse(200, { + access_token: 'access-2', + token_type: 'Bearer', + created_at: 2000, + expires_in: 3600, + refresh_token: 'refresh-2', + }), + ); + + const result = await kitsuTracker.revalidate!(auth); + + expect(result.accessToken).toBe('access-2'); + expect(result.refreshToken).toBe('refresh-2'); + expect(result.meta?.userId).toBe('user-1'); + expect(result.meta?.refreshToken).toBe('refresh-2'); + + const loginCall = getFetchCall((url, options) => url === LOGIN_URL); + expect(loginCall![1].body).toContain('grant_type=refresh_token'); + }); + + it('should throw when no refresh token is available', async () => { + await expect( + kitsuTracker.revalidate!({ + accessToken: 'access-1', + refreshToken: 'refresh-1', + expiresAt: new Date(), + meta: undefined, + }), + ).rejects.toThrow('No refresh token available'); + }); + }); +}); diff --git a/src/services/Trackers/aniList.ts b/src/services/Trackers/aniList.ts index d0e5eb9909..618021ee57 100644 --- a/src/services/Trackers/aniList.ts +++ b/src/services/Trackers/aniList.ts @@ -8,8 +8,6 @@ const clientId = ANILIST_CLIENT_ID; const redirectUri = Linking.createURL('tracker/AL'); -const authUrl = `https://anilist.co/api/v2/oauth/authorize?client_id=${clientId}&response_type=token`; - const searchQuery = `query($search: String) { Page { media(search: $search, type: MANGA, format: NOVEL, sort: POPULARITY_DESC) { @@ -46,6 +44,11 @@ const updateListEntryMutation = `mutation($id: Int!, $status: MediaListStatus, $ export const aniListTracker = { authenticate: async () => { + if (!clientId) { + throw new Error('AniList client ID is not configured'); + } + + const authUrl = `https://anilist.co/api/v2/oauth/authorize?client_id=${clientId}&response_type=token`; const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUri); if (result.type === 'success') { const { url } = result; diff --git a/src/services/Trackers/index.ts b/src/services/Trackers/index.ts index 736a614ed4..db5e00ec72 100644 --- a/src/services/Trackers/index.ts +++ b/src/services/Trackers/index.ts @@ -1,4 +1,4 @@ -export type TrackerName = 'AniList' | 'MyAnimeList' | 'MangaUpdates'; +export type TrackerName = 'AniList' | 'MyAnimeList' | 'MangaUpdates' | 'Kitsu'; /** * Normalized list status values. * Not all trackers will use all of these. diff --git a/src/services/Trackers/kitsu.ts b/src/services/Trackers/kitsu.ts new file mode 100644 index 0000000000..780fc999e4 --- /dev/null +++ b/src/services/Trackers/kitsu.ts @@ -0,0 +1,489 @@ +import { AuthenticationResult, Tracker, UserListStatus } from './index'; + +/** + * Kitsu Tracker Implementation + * + * Kitsu uses username/password authentication with OAuth2 client credentials. + * It supports refresh tokens for session renewal. + */ + +const BASE_URL = 'https://kitsu.app/api/edge/'; +const LOGIN_URL = 'https://kitsu.app/api/oauth/token'; +const ALGOLIA_KEY_URL = 'https://kitsu.app/api/edge/algolia-keys/media/'; + +const ALGOLIA_APP_ID = 'AWQO5J657S'; +const ALGOLIA_URL = `https://${ALGOLIA_APP_ID}-dsn.algolia.net/1/indexes/production_media/query/`; +const ALGOLIA_FILTER = + '&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=' + + '%5B%22synopsis%22%2C%22averageRating%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22' + + 'posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D'; + +/* Kitsu public OAuth credentials */ +const CLIENT_ID = + 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd'; +const CLIENT_SECRET = + '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151'; + +const VND_API_JSON = 'application/vnd.api+json'; + +/* Kitsu status mapping */ +const KITSU_STATUS = { + CURRENT: 'current', + COMPLETED: 'completed', + ON_HOLD: 'on_hold', + DROPPED: 'dropped', + PLAN_TO_READ: 'planned', +} as const; + +export const kitsuToNormalized: Record = { + current: 'CURRENT', + completed: 'COMPLETED', + on_hold: 'PAUSED', + dropped: 'DROPPED', + planned: 'PLANNING', +}; + +const normalizedToKitsu: Record = { + CURRENT: 'current', + COMPLETED: 'completed', + PAUSED: 'on_hold', + DROPPED: 'dropped', + PLANNING: 'planned', + REPEATING: 'current', +}; + +type KitsuAuthMeta = { + userId: string; + refreshToken: string; + createdAt: number; +}; + +type KitsuOAuth = { + access_token: string; + token_type: string; + created_at: number; + expires_in: number; + refresh_token: string; +}; + +type KitsuAlgoliaSearchItem = { + id: number; + canonicalTitle: string; + chapterCount: number | null; + subtype: string | null; + posterImage: { + original: string; + large?: string; + medium?: string; + small?: string; + tiny?: string; + } | null; + synopsis: string | null; +}; + +type KitsuLibraryEntry = { + id: string; + attributes: { + status: string; + progress: number; + ratingTwenty: number | null; + startedAt: string | null; + finishedAt: string | null; + }; +}; + +/** + * Authenticates with Kitsu using username and password. + * This function is called from the login dialog in settings. + */ +export async function authenticateWithCredentials( + username: string, + password: string, +): Promise> { + const formBody = new URLSearchParams({ + username, + password, + grant_type: 'password', + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + }).toString(); + + const response = await fetch(LOGIN_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: formBody, + }); + + if (response.status !== 200) { + const error = await response.json().catch(() => ({})); + throw new Error(error.error_description || 'Invalid username or password'); + } + + const oauth: KitsuOAuth = await response.json(); + + /* Get current user ID */ + const userId = await getCurrentUser(oauth.access_token); + + return { + accessToken: oauth.access_token, + refreshToken: oauth.refresh_token, + expiresAt: new Date((oauth.created_at + oauth.expires_in) * 1000), + meta: { + userId, + refreshToken: oauth.refresh_token, + createdAt: oauth.created_at, + }, + }; +} + +async function getCurrentUser(accessToken: string): Promise { + const url = `${BASE_URL}users?filter[self]=true`; + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: VND_API_JSON, + }, + }); + + if (response.status !== 200) { + throw new Error('Failed to get current user'); + } + + const data = await response.json(); + return data.data[0].id; +} + +async function getAlgoliaKey(accessToken: string): Promise { + const response = await fetch(ALGOLIA_KEY_URL, { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: VND_API_JSON, + }, + }); + + if (response.status !== 200) { + throw new Error('Failed to get Algolia key'); + } + + const data = await response.json(); + return data.media.key; +} + +async function algoliaSearch( + key: string, + query: string, +): Promise { + const jsonBody = JSON.stringify({ + params: `query=${encodeURIComponent(query)}${ALGOLIA_FILTER}`, + }); + + const response = await fetch(ALGOLIA_URL, { + method: 'POST', + headers: { + 'X-Algolia-Application-Id': ALGOLIA_APP_ID, + 'X-Algolia-API-Key': key, + 'Content-Type': 'application/json', + }, + body: jsonBody, + }); + + if (response.status !== 200) { + return []; + } + + const data = await response.json(); + return data.hits || []; +} + +async function findLibraryEntry( + mediaId: number | string, + userId: string, + accessToken: string, +): Promise<{ libraryId: string; entry: KitsuLibraryEntry } | null> { + const url = `${BASE_URL}library-entries?filter[manga_id]=${mediaId}&filter[user_id]=${userId}&include=manga`; + + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: VND_API_JSON, + }, + }); + + if (response.status !== 200) { + return null; + } + + const data = await response.json(); + + if (data.data && data.data.length > 0) { + return { + libraryId: data.data[0].id, + entry: data.data[0], + }; + } + + return null; +} + +async function addToLibrary( + mediaId: number | string, + userId: string, + status: string, + progress: number, + accessToken: string, +): Promise { + const body = JSON.stringify({ + data: { + type: 'libraryEntries', + attributes: { + status, + progress, + }, + relationships: { + user: { + data: { + id: userId, + type: 'users', + }, + }, + media: { + data: { + id: String(mediaId), + type: 'manga', + }, + }, + }, + }, + }); + + const response = await fetch(`${BASE_URL}library-entries`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': VND_API_JSON, + 'Accept': VND_API_JSON, + }, + body, + }); + + if (response.status !== 201 && response.status !== 200) { + throw new Error('Failed to add to library'); + } + + const data = await response.json(); + return data.data.id; +} + +async function updateLibraryEntry( + libraryId: string, + status: string, + progress: number, + ratingTwenty: number | null, + accessToken: string, +): Promise { + const body = JSON.stringify({ + data: { + type: 'libraryEntries', + id: libraryId, + attributes: { + status, + progress, + ratingTwenty, + }, + }, + }); + + const response = await fetch(`${BASE_URL}library-entries/${libraryId}`, { + method: 'PATCH', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': VND_API_JSON, + 'Accept': VND_API_JSON, + }, + body, + }); + + if (response.status !== 200) { + throw new Error('Failed to update library entry'); + } +} + +/** + * Converts a 0-10 score to Kitsu's ratingTwenty (0-20 scale). + * Kitsu uses a 20-point scale internally (2-20), displayed as 0.5-10.0. + */ +function scoreToRatingTwenty(score: number): number | null { + if (score <= 0) { + return null; + } + /* Convert 0-10 to 0-20, ensuring minimum of 2 if score > 0 */ + return Math.max(2, Math.round(score * 2)); +} + +/** + * Converts Kitsu's ratingTwenty (0-20 scale) to a 0-10 score. + */ +function ratingTwentyToScore(ratingTwenty: number | null): number { + if (ratingTwenty === null || ratingTwenty <= 0) { + return 0; + } + return ratingTwenty / 2; +} + +export const kitsuTracker: Tracker = { + authenticate: async () => { + /** + * Authentication is handled by authenticateWithCredentials + * which is called from the login dialog in settings. + */ + throw new Error('Please use the login dialog to authenticate with Kitsu'); + }, + + revalidate: async auth => { + if (!auth.meta?.refreshToken) { + throw new Error('No refresh token available'); + } + + const formBody = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: auth.meta.refreshToken, + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + }).toString(); + + const response = await fetch(LOGIN_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: formBody, + }); + + if (response.status !== 200) { + throw new Error('Session expired, please re-authenticate'); + } + + const oauth: KitsuOAuth = await response.json(); + + return { + accessToken: oauth.access_token, + refreshToken: oauth.refresh_token, + expiresAt: new Date((oauth.created_at + oauth.expires_in) * 1000), + meta: { + userId: auth.meta.userId, + refreshToken: oauth.refresh_token, + createdAt: oauth.created_at, + }, + }; + }, + + handleSearch: async (search, auth) => { + try { + const algoliaKey = await getAlgoliaKey(auth.accessToken); + const hits = await algoliaSearch(algoliaKey, search); + + /* Filter to only include novels (subtype === 'novel') */ + return hits + .filter(hit => hit.subtype === 'novel') + .map(hit => ({ + id: hit.id, + title: hit.canonicalTitle, + coverImage: hit.posterImage?.original || '', + totalChapters: hit.chapterCount || undefined, + })); + } catch (error) { + return []; + } + }, + + getUserListEntry: async (id, auth) => { + try { + const entry = await findLibraryEntry( + id, + auth.meta!.userId, + auth.accessToken, + ); + + if (entry) { + return { + status: kitsuToNormalized[entry.entry.attributes.status] || 'CURRENT', + progress: entry.entry.attributes.progress || 0, + score: ratingTwentyToScore(entry.entry.attributes.ratingTwenty), + }; + } + + /* Entry not found, return defaults */ + return { + status: 'CURRENT', + progress: 0, + score: 0, + }; + } catch (error) { + return { + status: 'CURRENT', + progress: 0, + score: 0, + }; + } + }, + + updateUserListEntry: async (id, payload, auth) => { + const status = payload.status + ? normalizedToKitsu[payload.status] + : KITSU_STATUS.CURRENT; + const progress = Math.round(payload.progress || 0); + const ratingTwenty = scoreToRatingTwenty(payload.score || 0); + + try { + /* Check if entry exists */ + const existingEntry = await findLibraryEntry( + id, + auth.meta!.userId, + auth.accessToken, + ); + + let libraryId: string; + + if (existingEntry) { + libraryId = existingEntry.libraryId; + await updateLibraryEntry( + libraryId, + status, + progress, + ratingTwenty, + auth.accessToken, + ); + } else { + /* Create new entry */ + libraryId = await addToLibrary( + id, + auth.meta!.userId, + status, + progress, + auth.accessToken, + ); + + /* Update with score if provided */ + if (ratingTwenty !== null) { + await updateLibraryEntry( + libraryId, + status, + progress, + ratingTwenty, + auth.accessToken, + ); + } + } + + return { + status: payload.status || 'CURRENT', + progress, + score: payload.score || 0, + }; + } catch (error) { + throw new Error('Failed to update Kitsu entry'); + } + }, +}; diff --git a/src/services/Trackers/myAnimeList.ts b/src/services/Trackers/myAnimeList.ts index 0b34b24f85..7b59691377 100644 --- a/src/services/Trackers/myAnimeList.ts +++ b/src/services/Trackers/myAnimeList.ts @@ -8,7 +8,6 @@ const baseOAuthUrl = 'https://myanimelist.net/v1/oauth2/authorize'; const tokenUrl = 'https://myanimelist.net/v1/oauth2/token'; const baseApiUrl = 'https://api.myanimelist.net/v2'; const challenge = pkceChallenger(); -const authUrl = `${baseOAuthUrl}?response_type=code&client_id=${clientId}&code_challenge_method=plain&code_challenge=${challenge}`; const redirectUri = Linking.createURL('tracker/MAL'); export const malToNormalized: Record = { @@ -29,6 +28,11 @@ const normalizedToMal: Record = { export const myAnimeListTracker: Tracker = { authenticate: async () => { + if (!clientId) { + throw new Error('MyAnimeList client ID is not configured'); + } + + const authUrl = `${baseOAuthUrl}?response_type=code&client_id=${clientId}&code_challenge_method=plain&code_challenge=${challenge}`; const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUri); if (result.type !== 'success') { diff --git a/src/services/download/downloadChapter.ts b/src/services/download/downloadChapter.ts index 52ec0d2162..ef77c9f815 100644 --- a/src/services/download/downloadChapter.ts +++ b/src/services/download/downloadChapter.ts @@ -6,6 +6,7 @@ import { getPlugin } from '@plugins/pluginManager'; import { getString } from '@strings/translations'; import { getChapter } from '@database/queries/ChapterQueries'; import { sleep } from '@utils/sleep'; +import { getChapterDownloadCooldownMs } from '@hooks/persisted/useSettings'; import { getNovelById } from '@database/queries/NovelQueries'; import { db } from '@database/db'; import { BackgroundTaskMetadata } from '@services/ServiceManager'; @@ -90,7 +91,7 @@ export const downloadChapter = async ( chapter.id, ]); - await sleep(1000); + await sleep(getChapterDownloadCooldownMs()); } else { throw new Error(getString('downloadScreen.chapterEmptyOrScrapeError')); } diff --git a/src/services/epub/import.ts b/src/services/epub/import.ts index 0ddfee06a4..6dc215155e 100644 --- a/src/services/epub/import.ts +++ b/src/services/epub/import.ts @@ -126,7 +126,13 @@ export const importEpub = async ( const epubFilePath = NativeFile.getConstants().ExternalCachesDirectoryPath + '/novel.epub'; - NativeFile.copyFile(uri, epubFilePath); + try { + NativeFile.copyFile(uri, epubFilePath); + } catch (error) { + throw new Error( + `Failed to read EPUB file "${filename}". The file may have been moved or deleted. Please try importing again.`, + ); + } const epubDirPath = NativeFile.getConstants().ExternalCachesDirectoryPath + '/epub'; if (NativeFile.exists(epubDirPath)) { diff --git a/src/services/migrate/migrateNovel.ts b/src/services/migrate/migrateNovel.ts index e789071e8c..3d4e1dfce4 100644 --- a/src/services/migrate/migrateNovel.ts +++ b/src/services/migrate/migrateNovel.ts @@ -74,8 +74,8 @@ export const migrateNovel = async ( toChapters = await getNovelChapters(toNovel.id); } - await db.withTransactionAsync(async () => { - await db.runAsync( + await db.withExclusiveTransactionAsync(async tx => { + await tx.runAsync( migrateNovelMetaDataQuery, fromNovel.cover || toNovel!.cover || '', fromNovel.summary || toNovel!.summary || '', @@ -86,12 +86,12 @@ export const migrateNovel = async ( toNovel!.id, ); - await db.runAsync( + await tx.runAsync( 'UPDATE OR IGNORE NovelCategory SET novelId = ? WHERE novelId = ?', toNovel!.id, fromNovel.id, ); - await db.runAsync('DELETE FROM Novel WHERE id = ?', fromNovel.id); + await tx.runAsync('DELETE FROM Novel WHERE id = ?', fromNovel.id); }); // When the old novel row is deleted, remove any novel-scoped overrides. diff --git a/src/services/updates/__tests__/index.test.ts b/src/services/updates/__tests__/index.test.ts new file mode 100644 index 0000000000..12207bb5bb --- /dev/null +++ b/src/services/updates/__tests__/index.test.ts @@ -0,0 +1,129 @@ +import { + getLibraryWithCategory, + getLibraryNovelsFromDb, +} from '@database/queries/LibraryQueries'; +import type { BackgroundTaskMetadata } from '@services/ServiceManager'; +import type { LibraryNovelInfo } from '@database/types'; +import { updateNovel } from '../LibraryUpdateQueries'; +import { updateLibrary } from '../index'; + +jest.mock('@database/queries/LibraryQueries', () => ({ + getLibraryNovelsFromDb: jest.fn(), + getLibraryWithCategory: jest.fn(), +})); +jest.mock('../LibraryUpdateQueries', () => ({ + updateNovel: jest.fn(), +})); +jest.mock('@utils/sleep', () => ({ + sleep: jest.fn(() => Promise.resolve()), +})); +jest.mock('@utils/showToast', () => ({ + showToast: jest.fn(), +})); +jest.mock('@utils/mmkv/mmkv', () => ({ + MMKVStorage: { set: jest.fn() }, + getMMKVObject: jest.fn(() => ({})), +})); +jest.mock('@hooks/persisted/useUpdates', () => ({ + LAST_UPDATE_TIME: 'LAST_UPDATE_TIME', +})); +jest.mock('@hooks/persisted/useSettings', () => ({ + APP_SETTINGS: 'APP_SETTINGS', +})); + +const mockedGetLibraryNovels = jest.mocked(getLibraryNovelsFromDb); +const mockedGetLibraryWithCategory = jest.mocked(getLibraryWithCategory); +const mockedUpdateNovel = jest.mocked(updateNovel); + +const novel = (id: number, pluginId: string, name: string): LibraryNovelInfo => + ({ + id, + pluginId, + name, + path: `/${name}`, + }) as LibraryNovelInfo; + +const flushPromises = () => new Promise(resolve => setImmediate(resolve)); + +describe('updateLibrary', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('updates at most three different sources while keeping each source sequential', async () => { + const novels = [ + novel(1, 'source-a', 'A One'), + novel(2, 'source-a', 'A Two'), + novel(3, 'source-b', 'B One'), + novel(4, 'source-c', 'C One'), + novel(5, 'source-d', 'D One'), + ]; + mockedGetLibraryNovels.mockReturnValue(novels); + + const completions = new Map void>(); + mockedUpdateNovel.mockImplementation( + (_pluginId, _path, novelId) => + new Promise(resolve => completions.set(novelId, resolve)), + ); + + let metadata: BackgroundTaskMetadata = { + name: 'Update library', + isRunning: false, + progress: undefined, + progressText: undefined, + }; + const progressSnapshots: BackgroundTaskMetadata[] = []; + const setMeta = ( + transform: (meta: BackgroundTaskMetadata) => BackgroundTaskMetadata, + ) => { + metadata = transform(metadata); + progressSnapshots.push(metadata); + }; + + const updatePromise = updateLibrary({}, setMeta); + await flushPromises(); + + expect(mockedUpdateNovel.mock.calls.map(([pluginId]) => pluginId)).toEqual([ + 'source-a', + 'source-b', + 'source-c', + ]); + expect(progressSnapshots[progressSnapshots.length - 1]?.progressText).toBe( + ['A One', 'B One', 'C One'].join('\n'), + ); + + completions.get(3)?.(); + await flushPromises(); + expect(mockedUpdateNovel).toHaveBeenCalledTimes(4); + expect(mockedUpdateNovel.mock.calls[3][0]).toBe('source-d'); + + completions.get(1)?.(); + await flushPromises(); + expect(mockedUpdateNovel).toHaveBeenCalledTimes(5); + expect(mockedUpdateNovel.mock.calls[4][0]).toBe('source-a'); + + completions.get(2)?.(); + completions.get(4)?.(); + completions.get(5)?.(); + await updatePromise; + + expect(metadata).toMatchObject({ + isRunning: false, + progress: 1, + progressText: undefined, + }); + }); + + it('uses the category query when an update is category-scoped', async () => { + mockedGetLibraryWithCategory.mockReturnValue([]); + + await updateLibrary({ categoryId: 4 }, jest.fn()); + + expect(mockedGetLibraryWithCategory).toHaveBeenCalledWith( + 4, + undefined, + true, + ); + expect(mockedGetLibraryNovels).not.toHaveBeenCalled(); + }); +}); diff --git a/src/services/updates/index.ts b/src/services/updates/index.ts index b4a539a932..ff2c005289 100644 --- a/src/services/updates/index.ts +++ b/src/services/updates/index.ts @@ -13,6 +13,20 @@ import dayjs from 'dayjs'; import { APP_SETTINGS, AppSettings } from '@hooks/persisted/useSettings'; import { BackgroundTaskMetadata } from '@services/ServiceManager'; +const UPDATE_SOURCE_CONCURRENCY = 3; + +const groupNovelsByPlugin = ( + novels: LibraryNovelInfo[], +): LibraryNovelInfo[][] => { + const groupedNovels = new Map(); + for (const novel of novels) { + const pluginNovels = groupedNovels.get(novel.pluginId); + if (pluginNovels) pluginNovels.push(novel); + else groupedNovels.set(novel.pluginId, [novel]); + } + return [...groupedNovels.values()]; +}; + const updateLibrary = async ( { categoryId, @@ -55,26 +69,50 @@ const updateLibrary = async ( if (libraryNovels.length > 0) { MMKVStorage.set(LAST_UPDATE_TIME, dayjs().format('YYYY-MM-DD HH:mm:ss')); - for (let i = 0; i < libraryNovels.length; i++) { + + const sourceQueues = groupNovelsByPlugin(libraryNovels); + const activeNovels = new Map(); + let completedNovels = 0; + let nextSourceQueue = 0; + + const publishProgress = () => { setMeta(meta => ({ ...meta, - progressText: libraryNovels[i].name, - progress: i / libraryNovels.length, + progressText: [...activeNovels.values()].join('\n') || undefined, + progress: completedNovels / libraryNovels.length, })); + }; + + const updateSourceQueue = async (sourceQueue: LibraryNovelInfo[]) => { + for (const novel of sourceQueue) { + activeNovels.set(novel.pluginId, novel.name); + publishProgress(); + try { + await updateNovel(novel.pluginId, novel.path, novel.id, options); + await sleep(1000); + } catch (error: any) { + showToast(novel.name + ': ' + error.message); + } finally { + completedNovels += 1; + activeNovels.delete(novel.pluginId); + publishProgress(); + } + } + }; - try { - await updateNovel( - libraryNovels[i].pluginId, - libraryNovels[i].path, - libraryNovels[i].id, - options, - ); - await sleep(1000); - } catch (error: any) { - showToast(libraryNovels[i].name + ': ' + error.message); - continue; + const updateNextSource = async () => { + while (nextSourceQueue < sourceQueues.length) { + const sourceQueue = sourceQueues[nextSourceQueue++]; + await updateSourceQueue(sourceQueue); } - } + }; + + await Promise.all( + Array.from( + { length: Math.min(UPDATE_SOURCE_CONCURRENCY, sourceQueues.length) }, + updateNextSource, + ), + ); } else { showToast("There's no novel to be updated"); } @@ -82,6 +120,7 @@ const updateLibrary = async ( setMeta(meta => ({ ...meta, progress: 1, + progressText: undefined, isRunning: false, })); }; diff --git a/src/theme/dynamic.ts b/src/theme/dynamic.ts new file mode 100644 index 0000000000..42ac1fb3f4 --- /dev/null +++ b/src/theme/dynamic.ts @@ -0,0 +1,26 @@ +import { + getMaterial3Theme, + isDynamicThemeSupported, + type Material3Theme, +} from '@pchmn/expo-material3-theme'; + +import { getString } from '@strings/translations'; +import type { ThemeColors } from '@theme/types'; + +export const DYNAMIC_THEME_ID = -1; +const DYNAMIC_THEME_FALLBACK_COLOR = '#0057CE'; + +export const isDynamicThemeAvailable = isDynamicThemeSupported; + +export const getSystemDynamicTheme = (): Material3Theme => + getMaterial3Theme(DYNAMIC_THEME_FALLBACK_COLOR); + +export const toDynamicThemeColors = ( + dynamicTheme: Material3Theme, + isDark: boolean, +): ThemeColors => ({ + ...(isDark ? dynamicTheme.dark : dynamicTheme.light), + id: DYNAMIC_THEME_ID, + name: getString('appearanceScreen.dynamicColors'), + isDark, +}); diff --git a/src/theme/md3/catppuccin.ts b/src/theme/md3/catppuccin.ts index d7ae348c00..4c530bcc80 100644 --- a/src/theme/md3/catppuccin.ts +++ b/src/theme/md3/catppuccin.ts @@ -2,7 +2,6 @@ import { getString } from '@strings/translations'; export const catppuccinTheme = { light: { - id: 20, name: getString('appearanceScreen.theme.catppuccin'), isDark: false, primary: 'rgb(136, 57, 239)', @@ -39,7 +38,6 @@ export const catppuccinTheme = { backdrop: 'rgba(196, 200, 208, 0.4)', }, dark: { - id: 21, name: getString('appearanceScreen.theme.catppuccin'), isDark: true, primary: 'rgb(203, 166, 247)', diff --git a/src/theme/md3/defaultTheme.ts b/src/theme/md3/defaultTheme.ts index 5fc18f6856..34f8657e7e 100644 --- a/src/theme/md3/defaultTheme.ts +++ b/src/theme/md3/defaultTheme.ts @@ -2,7 +2,6 @@ import { getString } from '@strings/translations'; export const defaultTheme = { light: { - id: 1, name: getString('appearanceScreen.theme.default'), isDark: false, primary: 'rgb(0, 87, 206)', @@ -39,7 +38,6 @@ export const defaultTheme = { backdrop: 'rgba(46, 48, 56, 0.4)', }, dark: { - id: 2, name: getString('appearanceScreen.theme.default'), isDark: true, primary: 'rgb(177, 197, 255)', diff --git a/src/theme/md3/index.ts b/src/theme/md3/index.ts index 044f09e097..b1378e34e3 100644 --- a/src/theme/md3/index.ts +++ b/src/theme/md3/index.ts @@ -8,6 +8,13 @@ import { takoTheme } from './tako'; import { catppuccinTheme } from './catppuccin'; import { yinyangTheme } from './yinyang'; +/** + * Exports for MD3 theme system + * + * IMPORTANT: + * IDs are auto-assigned, so new themes + * need to be added at the end of the list. + */ export const lightThemes = [ defaultTheme.light, midnightDusk.light, @@ -18,7 +25,7 @@ export const lightThemes = [ takoTheme.light, catppuccinTheme.light, yinyangTheme.light, -]; +].map((theme, i) => ({ ...theme, id: 100 + i })); export const darkThemes = [ defaultTheme.dark, midnightDusk.dark, @@ -29,4 +36,4 @@ export const darkThemes = [ takoTheme.dark, catppuccinTheme.dark, yinyangTheme.dark, -]; +].map((theme, i) => ({ ...theme, id: 100 + i })); diff --git a/src/theme/md3/lavender.ts b/src/theme/md3/lavender.ts index d3515ad1c9..e08094c0dc 100644 --- a/src/theme/md3/lavender.ts +++ b/src/theme/md3/lavender.ts @@ -2,7 +2,6 @@ import { getString } from '@strings/translations'; export const lavenderTheme = { light: { - id: 14, name: getString('appearanceScreen.theme.lavender'), isDark: false, primary: 'rgb(121, 68, 173)', @@ -39,7 +38,6 @@ export const lavenderTheme = { backdrop: 'rgba(52, 47, 55, 0.4)', }, dark: { - id: 15, name: getString('appearanceScreen.theme.lavender'), isDark: true, primary: 'rgb(221, 184, 255)', diff --git a/src/theme/md3/mignightDusk.ts b/src/theme/md3/mignightDusk.ts index 8a47708c98..0af08b6c4c 100644 --- a/src/theme/md3/mignightDusk.ts +++ b/src/theme/md3/mignightDusk.ts @@ -2,7 +2,6 @@ import { getString } from '@strings/translations'; export const midnightDusk = { light: { - id: 10, name: getString('appearanceScreen.theme.daybreakBloom'), isDark: false, primary: 'rgb(240, 36, 117)', @@ -39,7 +38,6 @@ export const midnightDusk = { backdrop: 'rgba(58, 45, 47, 0.4)', }, dark: { - id: 11, name: getString('appearanceScreen.theme.midnightDusk'), isDark: true, primary: 'rgb(240, 36, 117)', diff --git a/src/theme/md3/strawberry.ts b/src/theme/md3/strawberry.ts index 612992179f..b617ecbf5e 100644 --- a/src/theme/md3/strawberry.ts +++ b/src/theme/md3/strawberry.ts @@ -2,7 +2,6 @@ import { getString } from '@strings/translations'; export const strawberryDaiquiriTheme = { light: { - id: 16, name: getString('appearanceScreen.theme.strawberry'), isDark: false, primary: 'rgb(182, 30, 64)', @@ -39,7 +38,6 @@ export const strawberryDaiquiriTheme = { backdrop: 'rgba(59, 45, 46, 0.4)', }, dark: { - id: 17, name: getString('appearanceScreen.theme.strawberry'), isDark: true, primary: 'rgb(255, 178, 184)', diff --git a/src/theme/md3/tako.ts b/src/theme/md3/tako.ts index 2502c2f2cd..588fb5d63f 100644 --- a/src/theme/md3/tako.ts +++ b/src/theme/md3/tako.ts @@ -2,7 +2,6 @@ import { getString } from '@strings/translations'; export const takoTheme = { light: { - id: 18, name: getString('appearanceScreen.theme.tako'), isDark: false, primary: '#66577E', @@ -39,7 +38,6 @@ export const takoTheme = { backdrop: 'rgba(51, 47, 55, 0.4)', }, dark: { - id: 19, name: getString('appearanceScreen.theme.tako'), isDark: true, primary: '#F3B375', diff --git a/src/theme/md3/tealTurquoise.ts b/src/theme/md3/tealTurquoise.ts index f9bb458bf9..30619be2cd 100644 --- a/src/theme/md3/tealTurquoise.ts +++ b/src/theme/md3/tealTurquoise.ts @@ -2,7 +2,6 @@ import { getString } from '@strings/translations'; export const tealTurquoise = { light: { - id: 8, name: getString('appearanceScreen.theme.teal'), isDark: false, primary: 'rgb(0, 106, 106)', @@ -39,7 +38,6 @@ export const tealTurquoise = { backdrop: 'rgba(41, 50, 50, 0.4)', }, dark: { - id: 9, name: getString('appearanceScreen.theme.turquoise'), isDark: true, primary: 'rgb(76, 218, 218)', diff --git a/src/theme/md3/yinyang.ts b/src/theme/md3/yinyang.ts index 5f4b934d25..5b8b32e16f 100644 --- a/src/theme/md3/yinyang.ts +++ b/src/theme/md3/yinyang.ts @@ -2,7 +2,6 @@ import { getString } from '@strings/translations'; export const yinyangTheme = { light: { - id: 9, name: getString('appearanceScreen.theme.yinyang'), isDark: false, primary: '#000000', @@ -39,7 +38,6 @@ export const yinyangTheme = { backdrop: 'rgba(0, 0, 0, 0.4)', }, dark: { - id: 10, name: getString('appearanceScreen.theme.yinyang'), isDark: true, primary: '#FFFFFF', diff --git a/src/theme/md3/yotsuba.ts b/src/theme/md3/yotsuba.ts index 0105636e99..1b981769e2 100644 --- a/src/theme/md3/yotsuba.ts +++ b/src/theme/md3/yotsuba.ts @@ -2,7 +2,6 @@ import { getString } from '@strings/translations'; export const yotsubaTheme = { light: { - id: 12, name: getString('appearanceScreen.theme.yotsuba'), isDark: false, primary: 'rgb(174, 50, 0)', @@ -39,7 +38,6 @@ export const yotsubaTheme = { backdrop: 'rgba(59, 45, 41, 0.4)', }, dark: { - id: 13, name: getString('appearanceScreen.theme.yotsuba'), isDark: true, primary: 'rgb(255, 181, 158)', diff --git a/src/theme/types/index.ts b/src/theme/types/index.ts index eec0a863a1..ddf6e6a026 100644 --- a/src/theme/types/index.ts +++ b/src/theme/types/index.ts @@ -38,6 +38,8 @@ export interface MD3ThemeType { export interface ThemeColors extends MD3ThemeType { rippleColor?: string; + surfaceContainerLow?: string; + surfaceContainerHigh?: string; surface2?: string; overlay3?: string; surfaceReader?: string; diff --git a/src/theme/utils/setBarColor.ts b/src/theme/utils/setBarColor.ts index 3985da450c..256b1471d9 100644 --- a/src/theme/utils/setBarColor.ts +++ b/src/theme/utils/setBarColor.ts @@ -15,7 +15,16 @@ export const setStatusBarColor = (color: ThemeColors | Color) => { } }; +/** + * Sets the navigation-bar BUTTON style only. + * + * The nav-bar BACKGROUND is intentionally left to the system: setting it here + * was disabled upstream (commit 1ac6d116a, "Fix: Reader footer position & + * system bar", PR #1076) because a custom background broke the reader footer + * layout, and it is ignored/ineffective under Android 12+/API 35 edge-to-edge + * anyway. Restoring `NavigationBar.setBackgroundColorAsync` would risk + * regressing that fix for no user-visible benefit. + */ export const changeNavigationBarColor = (color: string, isDark = false) => { - NavigationBar.setBackgroundColorAsync(color); NavigationBar.setButtonStyleAsync(isDark ? 'light' : 'dark'); }; diff --git a/src/utils/__tests__/ttsTextCleanup.test.ts b/src/utils/__tests__/ttsTextCleanup.test.ts index efdee068ca..853b6bcdc7 100644 --- a/src/utils/__tests__/ttsTextCleanup.test.ts +++ b/src/utils/__tests__/ttsTextCleanup.test.ts @@ -1,11 +1,16 @@ import { cleanTtsText, applyTtsTextCleanup, + cleanVisibleText, + shouldCleanVisibleText, + resolveTtsCleanupTarget, + VISIBLE_PAD_CHAR, createTtsCleanupRule, createTtsPhoneticPair, normalizeUnicodeText, DEFAULT_TTS_CLEANUP_SETTINGS, TtsTextCleanupSettings, + TtsCleanupTarget, TTS_CLEANUP_MAX_REGEX_LENGTH, isPotentiallyCatastrophic, normalizeRegExpFlags, @@ -342,3 +347,188 @@ describe('applyTtsTextCleanup', () => { expect(applyTtsTextCleanup([], settings)).toEqual([]); }); }); + +describe('cleanVisibleText (issue #19 visible-text cleanup)', () => { + it('applies RULES ONLY — phonetic pairs and normalizeUnicode stay TTS-only', () => { + const settings = buildSettings({ + enabled: true, + applyTo: 'both', + normalizeUnicode: true, + rules: [createTtsCleanupRule('u2014', ' ')], + phoneticPairs: [createTtsPhoneticPair('Xianxia', 'Shee-an-shah')], + }); + // 'é' would NFD-normalize to 'e' and 'Xianxia' would be swapped by the + // TTS-only phonetic pass — neither may touch visible text. + const texts = ['u2014 hello', 'Xianxia é']; + expect(cleanVisibleText(texts, settings)).toEqual([' hello', 'Xianxia é']); + }); + + it('is count-preserving: pads paragraphs a rule would empty', () => { + const settings = buildSettings({ + enabled: true, + applyTo: 'both', + rules: [ + createTtsCleanupRule('\\s*\\(Official version\\)\\s*', '', true, 'g'), + ], + }); + const result = cleanVisibleText(['(Official version)', 'keep'], settings)!; + expect(result).toHaveLength(2); + expect(result[0]).toBe(VISIBLE_PAD_CHAR); + expect(result[1]).toBe('keep'); + // Padded element stays "readable": trim() of ZWSP is non-empty. + expect(result[0].trim().length).toBeGreaterThan(0); + }); + + it('no-ops when applyTo is tts or missing (default)', () => { + const texts = ['a', 'b']; + const ttsOnly = buildSettings({ + enabled: true, + applyTo: 'tts', + rules: [createTtsCleanupRule('a', 'b')], + }); + expect(cleanVisibleText(texts, ttsOnly)).toBe(texts); + const missing = buildSettings({ + enabled: true, + rules: [createTtsCleanupRule('a', 'b')], + }); + expect(cleanVisibleText(texts, missing)).toBe(texts); + }); + + it('no-ops when disabled or when only phonetic pairs exist', () => { + const texts = ['a']; + expect( + cleanVisibleText( + texts, + buildSettings({ enabled: false, applyTo: 'both' }), + ), + ).toBe(texts); + const phoneticsOnly = buildSettings({ + enabled: true, + applyTo: 'both', + phoneticPairs: [createTtsPhoneticPair('Xianxia', 'Shee-an-shah')], + }); + expect(cleanVisibleText(texts, phoneticsOnly)).toBe(texts); + }); + + it('handles undefined/null input', () => { + const settings = buildSettings({ + enabled: true, + applyTo: 'both', + rules: [createTtsCleanupRule('a', 'b')], + }); + expect(cleanVisibleText(undefined, settings)).toBeUndefined(); + expect(cleanVisibleText(null, settings)).toBeNull(); + }); + + it('reuses regex guardrails — invalid regex skipped silently', () => { + const settings = buildSettings({ + enabled: true, + applyTo: 'both', + rules: [ + createTtsCleanupRule('(unclosed', '', true, 'g'), + createTtsCleanupRule('u2014', ' '), + ], + }); + expect(cleanVisibleText(['unclosed u2014'], settings)).toEqual([ + 'unclosed ', + ]); + }); +}); + +describe('applyTo gating (issue #19)', () => { + it("'visible' mode: TTS pipeline no-ops, visible pipeline active", () => { + const settings = buildSettings({ + enabled: true, + applyTo: 'visible', + rules: [createTtsCleanupRule('u2014', ' ')], + }); + expect(cleanTtsText('u2014 x', settings)).toBe('u2014 x'); + expect(applyTtsTextCleanup(['u2014 x'], settings)).toEqual(['u2014 x']); + expect(cleanVisibleText(['u2014 x'], settings)).toEqual([' x']); + }); + + it("'both' mode: TTS pipeline AND visible pipeline active", () => { + const settings = buildSettings({ + enabled: true, + applyTo: 'both', + rules: [createTtsCleanupRule('u2014', ' ')], + }); + expect(cleanTtsText('u2014 x', settings)).toBe(' x'); + expect(applyTtsTextCleanup(['u2014 x'], settings)).toEqual([' x']); + expect(cleanVisibleText(['u2014 x'], settings)).toEqual([' x']); + }); + + it("'tts' (default) keeps historical behavior", () => { + const settings = buildSettings({ + enabled: true, + rules: [createTtsCleanupRule('u2014', ' ')], + }); + expect(cleanTtsText('u2014 x', settings)).toBe(' x'); + expect(cleanVisibleText(['u2014 x'], settings)).toEqual(['u2014 x']); + }); + + it('shouldCleanVisibleText reflects the target mode', () => { + expect( + shouldCleanVisibleText(buildSettings({ enabled: true, applyTo: 'both' })), + ).toBe(true); + expect( + shouldCleanVisibleText( + buildSettings({ enabled: true, applyTo: 'visible' }), + ), + ).toBe(true); + expect( + shouldCleanVisibleText(buildSettings({ enabled: true, applyTo: 'tts' })), + ).toBe(false); + expect(shouldCleanVisibleText(buildSettings({ enabled: true }))).toBe( + false, + ); + expect( + shouldCleanVisibleText( + buildSettings({ enabled: false, applyTo: 'both' }), + ), + ).toBe(false); + expect(shouldCleanVisibleText(undefined)).toBe(false); + }); + + it('garbage persisted applyTo values are treated as tts everywhere', () => { + for (const garbage of [ + 'sideways', + '', + 'BOTH', + 'none', + 'visible-text', + ] as const) { + const settings = buildSettings({ + enabled: true, + applyTo: garbage as TtsCleanupTarget, + rules: [createTtsCleanupRule('u2014', ' ')], + }); + // Audio pipeline still cleans (garbage -> 'tts'). + expect(cleanTtsText('u2014 x', settings)).toBe(' x'); + expect(applyTtsTextCleanup(['u2014 x'], settings)).toEqual([' x']); + // Visible pipeline stays off for garbage values. + expect(cleanVisibleText(['u2014 x'], settings)).toEqual(['u2014 x']); + expect(shouldCleanVisibleText(settings)).toBe(false); + } + }); + + it('resolveTtsCleanupTarget maps only valid values, everything else to tts', () => { + expect(resolveTtsCleanupTarget(buildSettings({ applyTo: 'visible' }))).toBe( + 'visible', + ); + expect(resolveTtsCleanupTarget(buildSettings({ applyTo: 'both' }))).toBe( + 'both', + ); + expect(resolveTtsCleanupTarget(buildSettings({ applyTo: 'tts' }))).toBe( + 'tts', + ); + expect( + resolveTtsCleanupTarget( + buildSettings({ applyTo: 'garbage' as TtsCleanupTarget }), + ), + ).toBe('tts'); + expect(resolveTtsCleanupTarget(buildSettings({}))).toBe('tts'); + expect(resolveTtsCleanupTarget(undefined)).toBe('tts'); + expect(resolveTtsCleanupTarget(null)).toBe('tts'); + }); +}); diff --git a/src/utils/htmlParagraphExtractor.ts b/src/utils/htmlParagraphExtractor.ts index b8d0f299c9..4fb52d0d48 100644 --- a/src/utils/htmlParagraphExtractor.ts +++ b/src/utils/htmlParagraphExtractor.ts @@ -221,6 +221,9 @@ export interface TtsPhoneticPair { matchMode?: 'whole-word' | 'substring'; } +/** Where TTS cleanup rules apply. */ +export type TtsCleanupTarget = 'tts' | 'visible' | 'both'; + /** Full user-configurable TTS text cleanup settings. */ export interface TtsTextCleanupSettings { /** Master switch: apply cleanup to TTS text. */ @@ -231,6 +234,14 @@ export interface TtsTextCleanupSettings { rules: TtsCleanupRule[]; /** Ordered phonetic pronunciation swaps. */ phoneticPairs: TtsPhoneticPair[]; + /** + * Where the ruleset applies. Defaults to 'tts' (audio only, historical + * behavior). 'visible' = reader DOM text only (rules-only pass); 'both' + * = visible text AND TTS audio. Phonetic pairs + Unicode normalization + * always stay TTS-only regardless. Optional so persisted settings that + * predate the field keep working unchanged (missing = 'tts'). + */ + applyTo?: TtsCleanupTarget; } export const DEFAULT_TTS_CLEANUP_SETTINGS: TtsTextCleanupSettings = { @@ -238,6 +249,7 @@ export const DEFAULT_TTS_CLEANUP_SETTINGS: TtsTextCleanupSettings = { normalizeUnicode: false, rules: [], phoneticPairs: [], + applyTo: 'tts', }; /** @@ -451,7 +463,12 @@ export function cleanTtsText( text: string, settings?: TtsTextCleanupSettings | null, ): string { - if (typeof text !== 'string' || !text || !settings?.enabled) { + if ( + typeof text !== 'string' || + !text || + !settings?.enabled || + resolveTtsCleanupTarget(settings) === 'visible' + ) { return text; } @@ -500,6 +517,7 @@ export function applyTtsTextCleanup( ): string[] | undefined | null { if ( !settings?.enabled || + resolveTtsCleanupTarget(settings) === 'visible' || !Array.isArray(paragraphs) || paragraphs.length === 0 ) { @@ -517,3 +535,86 @@ export function applyTtsTextCleanup( return paragraphs.map(paragraph => cleanTtsText(paragraph, settings)); } + +/** + * Zero-width space used to pad paragraphs that a display rule would otherwise + * empty. Keeps the paragraph COUNT invariant intact: core.js `readable()` + * filters out elements whose trimmed text is empty, so emptying an element's + * text entirely would drop it and shift every downstream paragraph index + * (highlight, scroll, progress). + */ +export const VISIBLE_PAD_CHAR = '\u200B'; + +/** + * Normalize the persisted `applyTo` value to one of the three valid targets. + * Any missing or invalid value (e.g. a corrupted/garbage string persisted to + * MMKV) maps to the historical 'tts' default so every consumption site agrees + * on the same behavior: audio cleans, the visible DOM does not. + */ +export function resolveTtsCleanupTarget( + settings?: TtsTextCleanupSettings | null, +): TtsCleanupTarget { + const target = settings?.applyTo; + return target === 'visible' || target === 'both' ? target : 'tts'; +} + +/** + * True when visible-text (reader DOM) cleanup is active for the given + * effective settings. + */ +export function shouldCleanVisibleText( + settings?: TtsTextCleanupSettings | null, +): boolean { + return !!settings?.enabled && resolveTtsCleanupTarget(settings) !== 'tts'; +} + +/** + * Clean paragraph texts for VISIBLE display (reader DOM). RULES ONLY: the + * phonetic dictionary and Unicode normalization stay TTS-only because they + * are audio-oriented — pronunciation spellings would corrupt visible text + * and NFD-stripping visibly mutates glyphs. + * + * COUNT-PRESERVING: the returned array always has the same length as the + * input; paragraphs a rule would empty are padded with a zero-width space so + * the DOM element stays readable and paragraph indices never shift. Reuses + * the exact rule engine + regex guardrails from the TTS pipeline (length + * cap, catastrophic-shape detection, compile-time try/catch, literal + * replacement semantics). + */ +export function cleanVisibleText( + paragraphs: string[] | undefined | null, + settings?: TtsTextCleanupSettings | null, +): string[] | undefined | null { + if ( + !shouldCleanVisibleText(settings) || + !Array.isArray(paragraphs) || + paragraphs.length === 0 || + !settings + ) { + return paragraphs; + } + + const hasRules = settings.rules?.some(rule => rule.enabled) ?? false; + if (!hasRules) { + return paragraphs; + } + + return paragraphs.map(paragraph => { + if (typeof paragraph !== 'string' || !paragraph) { + return paragraph; + } + let result = paragraph; + for (const rule of settings.rules ?? []) { + if (!rule.enabled) { + continue; + } + // normalizePattern=false: visible text is never NFD-normalized, so + // literal patterns match verbatim. + result = applyCleanupRule(result, rule, false); + } + if (!result || result.trim().length === 0) { + return VISIBLE_PAD_CHAR; + } + return result; + }); +} diff --git a/src/utils/translateEnum.ts b/src/utils/translateEnum.ts index e73bab9584..86d371707a 100644 --- a/src/utils/translateEnum.ts +++ b/src/utils/translateEnum.ts @@ -17,6 +17,12 @@ export const translateNovelStatus = (status?: NovelStatus | string) => { return getString('novelScreen.status.licensed'); case NovelStatus.PublishingFinished: return getString('novelScreen.status.publishingFinished'); + // New statuses are displayed verbatim; i18n keys intentionally not added + // to keep translation files untouched (see batch constraints). + case NovelStatus.STUB: + return 'STUB'; + case NovelStatus.Inactive: + return 'Inactive'; default: return status ?? ''; } diff --git a/strings/languages/en/strings.json b/strings/languages/en/strings.json index da486a68a9..fc0a43aa64 100644 --- a/strings/languages/en/strings.json +++ b/strings/languages/en/strings.json @@ -53,6 +53,7 @@ "appLanguageDefault": "Default", "appTheme": "App theme", "darkTheme": "Dark Theme", + "dynamicColors": "Dynamic", "hideBackdrop": "Hide backdrop", "lightTheme": "Light Theme", "navbar": "Navbar", @@ -286,6 +287,7 @@ "show": "Show", "signIn": "Sign in", "signOut": "Sign out", + "skipVersion": "Skip version", "sort": "Sort", "submit": "Submit", "loading": "Loading ...", @@ -339,6 +341,10 @@ "autoDownloadAmount": "Chapters to download", "autoDownloadAmountDesc": "Number of chapters to download when triggered", "bySource": "By source", + "chapterDownloadCooldown": "Chapter download cooldown", + "chapterDownloadCooldownDesc": "Wait between sequential chapter downloads (seconds). Helps avoid source rate limits during batch downloads.", + "chapterDownloadCooldownPlaceholder": "Cooldown in seconds (e.g. 1.5)", + "chapterDownloadCooldownWarning": "Very low cooldowns may trigger rate limiting or temporary IP bans from sources.", "chapterSort": "Default chapter sort", "desc": "(Descending)", "disableLoadingAnimations": "Disable loading animations", @@ -456,6 +462,7 @@ "downloadedChaptersOnly": "Only downloaded chapters will be included in the EPUB file", "endChapter": "End Chapter", "exportAll": "Export All Chapters", + "includeChapterNumber": "Include chapter number in chapter titles", "includeCustomCSS": "Include Custom CSS", "includeCustomJS": "Include Custom JS", "invalidRange": "Please enter valid chapter numbers", diff --git a/strings/types/index.ts b/strings/types/index.ts index eb6eaebc9f..478854cc4b 100644 --- a/strings/types/index.ts +++ b/strings/types/index.ts @@ -50,6 +50,7 @@ export interface StringMap { 'appearanceScreen.appLanguageDefault': 'string'; 'appearanceScreen.appTheme': 'string'; 'appearanceScreen.darkTheme': 'string'; + 'appearanceScreen.dynamicColors': 'string'; 'appearanceScreen.hideBackdrop': 'string'; 'appearanceScreen.lightTheme': 'string'; 'appearanceScreen.navbar': 'string'; @@ -257,6 +258,7 @@ export interface StringMap { 'common.show': 'string'; 'common.signIn': 'string'; 'common.signOut': 'string'; + 'common.skipVersion': 'string'; 'common.sort': 'string'; 'common.submit': 'string'; 'common.loading': 'string'; @@ -300,6 +302,10 @@ export interface StringMap { 'generalSettingsScreen.autoDownloadAmount': 'string'; 'generalSettingsScreen.autoDownloadAmountDesc': 'string'; 'generalSettingsScreen.bySource': 'string'; + 'generalSettingsScreen.chapterDownloadCooldown': 'string'; + 'generalSettingsScreen.chapterDownloadCooldownDesc': 'string'; + 'generalSettingsScreen.chapterDownloadCooldownPlaceholder': 'string'; + 'generalSettingsScreen.chapterDownloadCooldownWarning': 'string'; 'generalSettingsScreen.chapterSort': 'string'; 'generalSettingsScreen.desc': 'string'; 'generalSettingsScreen.disableLoadingAnimations': 'string'; @@ -388,6 +394,7 @@ export interface StringMap { 'novelScreen.exportEpubModal.downloadedChaptersOnly': 'string'; 'novelScreen.exportEpubModal.endChapter': 'string'; 'novelScreen.exportEpubModal.exportAll': 'string'; + 'novelScreen.exportEpubModal.includeChapterNumber': 'string'; 'novelScreen.exportEpubModal.includeCustomCSS': 'string'; 'novelScreen.exportEpubModal.includeCustomJS': 'string'; 'novelScreen.exportEpubModal.invalidRange': 'string';