chore: checkpoint current milestone work

This commit is contained in:
shen
2026-05-22 13:28:53 +08:00
parent 6d196d64e5
commit 5698aeaead
68 changed files with 5694 additions and 3291 deletions
@@ -0,0 +1,89 @@
---
phase: 04-reader-capabilities
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
- Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift
- Sources/RDReaderView/RDURLReaderController.swift
autonomous: true
requirements:
- COMP-01
user_setup: []
must_haves:
truths:
- native text pages remain rendered through the existing `RDEPUBReaderController` + `RDReaderView` shell.
- no new page-turn container or parallel native reader shell is introduced.
- `RDEPUBTextBook` remains the native reflowable backend contract.
artifacts:
- .planning/phases/04-reader-capabilities/04-01-SUMMARY.md
key_links:
- `RDEPUBReaderController.paginatePublication`
- `RDEPUBReaderController.pageContentView`
- `RDEPUBReaderController.restoreReadingLocation`
---
<objective>
把 Phase 3 的 native text backend 稳定接回现有 reader 主流程,让 `RDEPUBReaderController``RDEPUBTextBook` 路径的分页、展示、恢复行为更完整,但不修改 `RDReaderView`
Purpose: 让 reader 主流程明确把 native reflowable path 当成正式 backend,而不是零散的特殊分支。
Output: reader-controller/native-text integration cleanup with clearer state handoff and no changes to the page-turn container.
</objective>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-reader-capabilities/04-RESEARCH.md
@.planning/phases/04-reader-capabilities/04-PATTERNS.md
@.planning/phases/03-page-metadata-pagination/03-RESEARCH.md
@.planning/phases/03-page-metadata-pagination/03-VERIFICATION.md
@Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift
@Sources/RDReaderView/RDURLReaderController.swift
</context>
<tasks>
<task type="auto">
<name>Task 1: 收敛 native text backend 的分页完成与展示入口</name>
<files>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift</files>
<read_first>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift</read_first>
<action>整理 `paginatePublication``applyTextBook``finishPagination``currentVisibleLocation``restoreReadingLocation` 的 native text 行为,让 `RDEPUBTextBook` 路径拥有清晰的完成/恢复入口,必要时补充最小状态桥接,但不要改 `RDReaderView` 的翻页接口,也不要引入新的 reader shell。</action>
<acceptance_criteria>
- `RDEPUBReaderController` remains the single entry point for native text and web reading
- text-book pagination completion and restore logic are explicit and internally coherent
- no code moves page-turn logic into `RDEPUBTextBookBuilder` or `RDEPUBTextContentView`
</acceptance_criteria>
<verify>rg -n "paginatePublication|applyTextBook|finishPagination|restoreReadingLocation|currentVisibleLocation" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift</verify>
<done>native text pagination is reconnected as a first-class reader backend.</done>
</task>
<task type="auto">
<name>Task 2: 保持 page container 兼容并清理 native text 初始化入口</name>
<files>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift, Sources/RDReaderView/RDURLReaderController.swift</files>
<read_first>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift, Sources/RDReaderView/RDURLReaderController.swift</read_first>
<action>确认并收敛 `pageContentView(readerView:pageNum:containerView:)`、TXT 入口和 native content view 配置路径,使 `RDReaderView` 继续只消费 UIView 页面内容而不用理解新的 page metadata。若需要暴露更多 page context,优先通过现有 content-view/controller 接口完成。</action>
<acceptance_criteria>
- `RDReaderView` does not gain native metadata-specific branching
- `RDEPUBTextContentView` remains the native page presenter
- TXT/native-text initialization path remains compatible with the same reader shell
</acceptance_criteria>
<verify>rg -n "pageContentView|RDEPUBTextContentView|RDURLReaderController|textBook" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/RDURLReaderController.swift Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift</verify>
<done>reader presentation stays container-compatible while native text becomes a stable backend.</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `rg -n "paginatePublication|applyTextBook|finishPagination|restoreReadingLocation|currentVisibleLocation" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
- [ ] `rg -n "pageContentView|RDEPUBTextContentView|RDURLReaderController|textBook" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/RDURLReaderController.swift Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift`
</verification>
<output>
After completion, create `.planning/phases/04-reader-capabilities/04-01-SUMMARY.md`
</output>
@@ -0,0 +1,16 @@
# 04-01 Summary
## Outcome
Wave 1 reconnected the native text backend as a first-class reader path inside the existing shell.
- `RDEPUBReaderController` now builds a real native snapshot from `RDEPUBTextBook` instead of leaving `activePages` / `activeChapters` empty
- Native restore and current-location resolution now go through shared controller helpers instead of scattered text-book special cases
- Native page turns update `RDEPUBReadingSession` with page/spine/chapter context, so reader state stays coherent with the visible page
- `RDReaderView` remained unchanged; it still only consumes content views and page counts
## Compatibility
- No new reader shell or page-turn container was introduced
- TXT and EPUB native text flows still use `RDEPUBReaderController` and `RDEPUBTextContentView`
- `RDEPUBTextBook` remains the backend contract for native reflowable pages
@@ -0,0 +1,90 @@
---
phase: 04-reader-capabilities
plan: 02
type: execute
wave: 2
depends_on:
- "04-01"
files_modified:
- Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
- Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift
- Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift
- Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift
autonomous: true
requirements:
- COMP-03
user_setup: []
must_haves:
truths:
- native text highlight/search behavior remains grounded in absolute chapter offsets.
- location mapping and page-number lookup stay coherent after the reader integration cleanup.
- no page-local-only persistence identifier replaces `RDEPUBTextOffsetRangeInfo`.
artifacts:
- .planning/phases/04-reader-capabilities/04-02-SUMMARY.md
key_links:
- `RDEPUBReaderController.normalizedTextSelection`
- `RDEPUBReaderController.search`
- `RDEPUBReadingSession.pageIndex(for:)`
---
<objective>
修复并验证 native text reader 下的位置映射、高亮、选区、搜索和 TOC/跳转兼容能力,确保 Phase 3 的 richer paginator 不会破坏这些用户能力。
Purpose: 把 offset-compatible 的底层契约真正接回 reader 行为链路。
Output: source fixes and verification around native location lookup, selection normalization, search navigation, and persisted highlights.
</objective>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-reader-capabilities/04-RESEARCH.md
@.planning/phases/04-reader-capabilities/04-PATTERNS.md
@Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
@Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift
@Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift
@Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift
</context>
<tasks>
<task type="auto">
<name>Task 1: 收敛 native text 的 location / selection / highlight 兼容语义</name>
<files>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift, Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift</files>
<read_first>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift, Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift</read_first>
<action>验证并修复 native text selection/highlight 链路,重点围绕 `normalizedTextSelection``RDEPUBTextOffsetRangeInfo``pageStartOffset` / `pageEndOffset` overlap 计算和 reader persistence 语义展开。若要补充 guard 或 normalization,必须继续以 chapter href + absolute offsets 为核心,而不是引入 page-local-only id。</action>
<acceptance_criteria>
- `RDEPUBTextOffsetRangeInfo` remains the persisted native text range format
- native selection/highlight restore still maps through absolute chapter offsets
- `RDEPUBTextContentView` still derives visible ranges from page offsets
</acceptance_criteria>
<verify>rg -n "RDEPUBTextOffsetRangeInfo|normalizedTextSelection|pageStartOffset|pageEndOffset|rangeInfo" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift Sources/RDReaderView/EPUBCore</verify>
<done>native text highlight and selection persistence remain offset-compatible.</done>
</task>
<task type="auto">
<name>Task 2: 修复并验证 native text 搜索 / TOC / 跳转页号解析</name>
<files>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift</files>
<read_first>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift</read_first>
<action>核对 native text path 的 `pageNumber(for:)``search``navigateToCurrentSearchMatch`、TOC 页号解析与内部链接跳转,确保 richer page model 下这些 reader feature 仍能落到正确页。需要时可以增强 lookup/normalization,但不要把 Web 路径逻辑硬搬到 native path。</action>
<acceptance_criteria>
- native text search still produces usable page navigation
- TOC/page-number resolution stays coherent for `RDEPUBTextBook`
- reader state and page lookup are not duplicated into a second custom navigation stack
</acceptance_criteria>
<verify>rg -n "search\\(|navigateToCurrentSearchMatch|pageNumber\\(for: searchMatch\\)|flattenedTableOfContentsItems|pageIndex\\(for:" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift</verify>
<done>native text search, TOC, and jumps remain believable after reintegration.</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `rg -n "RDEPUBTextOffsetRangeInfo|normalizedTextSelection|pageStartOffset|pageEndOffset|rangeInfo" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift Sources/RDReaderView/EPUBCore`
- [ ] `rg -n "search\\(|navigateToCurrentSearchMatch|pageNumber\\(for: searchMatch\\)|flattenedTableOfContentsItems|pageIndex\\(for:" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift`
</verification>
<output>
After completion, create `.planning/phases/04-reader-capabilities/04-02-SUMMARY.md`
</output>
@@ -0,0 +1,15 @@
# 04-02 Summary
## Outcome
Wave 2 tightened native location, highlight, selection, search, and TOC compatibility around the Phase 3 offset contract.
- `normalizedTextSelection` now preserves exclusive end semantics more carefully when rebuilding progressions from `RDEPUBTextOffsetRangeInfo`
- Native highlight filtering now compares normalized hrefs, reducing mismatch risk after resolver normalization
- Search page lookup now prefers exact `rangeLocation` offset matching before falling back to progression-based page resolution
## Compatibility
- `RDEPUBTextOffsetRangeInfo` remains the native persisted range format
- `RDEPUBTextContentView` still derives visible overlap from `pageStartOffset` / `pageEndOffset`
- No page-local-only identifier replaced the absolute chapter offset contract
@@ -0,0 +1,90 @@
---
phase: 04-reader-capabilities
plan: 03
type: execute
wave: 3
depends_on:
- "04-02"
files_modified:
- Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
- Sources/RDReaderView/EPUBUI/RDEPUBReaderConfiguration.swift
- Sources/RDReaderView/RDURLReaderController.swift
- ReadViewDemo/ReadViewDemo/ViewController.swift
autonomous: true
requirements:
- COMP-04
user_setup: []
must_haves:
truths:
- repagination after font, line-height, theme, or viewport changes restores semantic reading state.
- the demo app remains the primary runtime verification entry point.
- `RDReaderView` does not need modification to validate these flows.
artifacts:
- .planning/phases/04-reader-capabilities/04-03-SUMMARY.md
key_links:
- `RDEPUBReaderController.configuration.didSet`
- `RDEPUBReaderController.handleViewportChangeIfNeeded`
- `RDEPUBReaderController.rebuildExternalTextBook`
---
<objective>
验证并收敛字体、行高、主题和 viewport 变化导致的重新分页与状态恢复,让 reader 在 Phase 4 完成后可以稳定承接 repagination。
Purpose: 证明新 native backend 在真实 reader 交互下能够重新分页而不丢失位置、搜索和高亮上下文。
Output: re-pagination/state-restore fixes, plus demo-visible validation hooks or diagnostics where needed.
</objective>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-reader-capabilities/04-RESEARCH.md
@.planning/phases/04-reader-capabilities/04-PATTERNS.md
@.planning/phases/03-page-metadata-pagination/03-VERIFICATION.md
@Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
@Sources/RDReaderView/EPUBUI/RDEPUBReaderConfiguration.swift
@Sources/RDReaderView/RDURLReaderController.swift
@ReadViewDemo/ReadViewDemo/ViewController.swift
</context>
<tasks>
<task type="auto">
<name>Task 1: 收敛 repagination 触发器与状态恢复逻辑</name>
<files>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBUI/RDEPUBReaderConfiguration.swift, Sources/RDReaderView/RDURLReaderController.swift</files>
<read_first>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBUI/RDEPUBReaderConfiguration.swift, Sources/RDReaderView/RDURLReaderController.swift</read_first>
<action>围绕 `configuration.didSet``requiresRepagination``refreshVisibleContentPreservingLocation``rebuildExternalTextBook``handleViewportChangeIfNeeded` 和 TXT/native 入口收敛 repagination 行为,使 reader 在设置变化与 viewport 变化后恢复到语义位置,而不是错误页或空状态。</action>
<acceptance_criteria>
- font size / line height changes trigger repagination through semantic location restore
- theme changes refresh visible content without dropping current reader state
- viewport changes do not require `RDReaderView` changes to preserve location
</acceptance_criteria>
<verify>rg -n "requiresRepagination|refreshVisibleContentPreservingLocation|rebuildExternalTextBook|handleViewportChangeIfNeeded|configuration" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/EPUBUI/RDEPUBReaderConfiguration.swift Sources/RDReaderView/RDURLReaderController.swift</verify>
<done>repagination triggers are stable and state-preserving.</done>
</task>
<task type="auto">
<name>Task 2: 在 demo 中验证 reader-level repagination 与状态恢复</name>
<files>ReadViewDemo/ReadViewDemo/ViewController.swift, Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift</files>
<read_first>ReadViewDemo/ReadViewDemo/ViewController.swift, Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, ReadViewDemo/ReadViewDemo/book/</read_first>
<action>用现有 demo 入口补充最小但可观察的 reader-level 验证路径或日志,证明 Phase 4 关注的 repagination / location restore / search-highlight continuity 可以被检查。不要新建并行 demo shell。</action>
<acceptance_criteria>
- demo remains the validation entry point
- runtime checks can confirm at least part of the repagination/state-restore behavior
- sample corpus under `ReadViewDemo/ReadViewDemo/book/` remains the verification source
</acceptance_criteria>
<verify>test -d ReadViewDemo/ReadViewDemo/book && rg -n "restore|repagination|search|highlight|location|theme|font" ReadViewDemo/ReadViewDemo Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift</verify>
<done>Phase 4 finishes with a real runtime path to prove state-preserving repagination.</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `rg -n "requiresRepagination|refreshVisibleContentPreservingLocation|rebuildExternalTextBook|handleViewportChangeIfNeeded|configuration" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/EPUBUI/RDEPUBReaderConfiguration.swift Sources/RDReaderView/RDURLReaderController.swift`
- [ ] `test -d ReadViewDemo/ReadViewDemo/book`
- [ ] `rg -n "restore|repagination|search|highlight|location|theme|font" ReadViewDemo/ReadViewDemo Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
</verification>
<output>
After completion, create `.planning/phases/04-reader-capabilities/04-03-SUMMARY.md`
</output>
@@ -0,0 +1,19 @@
# 04-03 Summary
## Outcome
Wave 3 validated repagination and semantic location restore with real sample books.
- The demo now reports a restore diagnostic alongside the pagination diagnostic
- Validation rebuilds native text books with a larger font/line spacing and a theme-only style change, then checks whether the same semantic location still resolves correctly
- Runtime evidence confirmed stable href restoration and theme-stable page mapping on the sampled reflowable EPUB
## Demo Evidence
Runtime log from the simulator:
- `EPUB 资源验证:2/2 通过`
- `分页诊断:宝山辽墓材料与释读 · 章节 10 · attachment 页 54 · semantic break 页 57 · reasons [attachmentBoundary:31, blockBoundary:26, frameLimit:17, chapterEnd:4] · page break: blockBoundary`
- `恢复诊断:宝山辽墓材料与释读 · base 39 · font-shift 44 · theme-stable yes · href-stable yes · progression-delta 0.004`
This shows the native reader backend can repaginate to a different page number under font changes while preserving the same semantic reading location.
@@ -0,0 +1,29 @@
# Phase 4: 接回现有 reader 能力链路 - Pattern Map
## Goal
为 Phase 4 提供“先接哪里、怎么验证没把 reader 行为搞坏”的最短路径。重点不是继续改 paginator,而是把已经完成的 native text book/page model 稳定接回 reader 能力链路。
## Planned Outputs
| Planned file | Role | Primary evidence | Why this is the right analog |
|--------------|------|------------------|------------------------------|
| `04-01-PLAN.md` | native text backend 接回 reader 主流程 | `RDEPUBReaderController`, `RDEPUBTextBookBuilder`, `RDEPUBTextContentView`, `RDURLReaderController` | 这些文件定义当前 native text 打开、分页、展示和 page container 接口。 |
| `04-02-PLAN.md` | 位置映射 / 高亮 / 搜索兼容收敛 | `RDEPUBReadingSession`, `RDEPUBReaderController`, `RDEPUBTextSearchEngine`, `RDEPUBReadingModels` | 这些文件共同定义 reader state、search matches、selection normalization 与绝对 offset 持久化语义。 |
| `04-03-PLAN.md` | 主题 / 字号 / 行高 / viewport 变化后的重新分页与状态恢复验证 | `RDEPUBReaderController`, `RDEPUBReaderConfiguration`, `RDURLReaderController`, `ReadViewDemo` | 当前所有重新分页触发器和 demo 验证入口都在这里。 |
## Code Evidence Map
| Concern | Closest source of truth | Evidence to extract |
|---------|-------------------------|---------------------|
| native text page flow接入点 | `RDEPUBReaderController.paginatePublication`, `applyTextBook`, `pageContentView` | 现有 controller 已有 native/web 双路径,但需要收敛行为。 |
| 恢复位置与可见位置 | `restoreReadingLocation`, `currentVisibleLocation`, `finishPagination` | 这里决定 repagination 后回到哪里。 |
| 绝对 offset 高亮/选区兼容 | `RDEPUBTextContentView`, `normalizedTextSelection`, `RDEPUBTextOffsetRangeInfo` | 这些是 Phase 4 不能破坏的硬约束。 |
| 搜索跳转与页号解析 | `search`, `navigateToCurrentSearchMatch`, `pageNumber(for:)`, `RDEPUBTextSearchEngine` | 需要保证 native text path 仍能定位正确页。 |
| 设置变更触发重排 | `configuration.didSet`, `requiresRepagination`, `refreshVisibleContentPreservingLocation`, `handleViewportChangeIfNeeded` | 字号/行高/主题/旋转都是 Phase 4 的状态恢复验证入口。 |
## Writing Guidance
- 计划必须把“主流程接回、兼容能力收敛、重排恢复验证”拆成三个独立 plan,不要把所有 reader 能力挤进一个任务。
- 任何修复都要优先围绕 `RDEPUBReaderController``RDEPUBReadingSession` 的现有责任边界展开,不要借机重写 `RDReaderView`
- 验证步骤要明确要求真实的 reader 行为检查,而不是只做 build pass。
@@ -0,0 +1,123 @@
# Phase 4: 接回现有 reader 能力链路 - Research
**Researched:** 2026-05-22
**Domain:** iOS EPUB reader reintegration / location mapping / highlight-search-state continuity
**Confidence:** HIGH
<user_constraints>
## User Constraints
No `CONTEXT.md` exists for this phase. Planning is based on ROADMAP requirements, current code, and completed artifacts from Phases 1-3 only.
</user_constraints>
<architectural_responsibility_map>
## Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|------------|-------------|----------------|-----------|
| Reflowable native pagination output | EPUBTextRendering | EPUBCore | Phase 3 already moved richer page semantics into `RDEPUBTextBook` / `RDEPUBTextPage`. |
| Reader orchestration and restore logic | EPUBUI | EPUBCore | `RDEPUBReaderController` owns `paginatePublication`, `finishPagination`, restore, search navigation, selection, and configuration updates. |
| Page lookup and navigator state | EPUBCore | EPUBUI | `RDEPUBReadingSession` remains the non-UI state owner for fixed/web flows and should keep that role rather than pushing state into `RDReaderView`. |
| Highlight/search persistence semantics | EPUBCore | EPUBUI | `RDEPUBTextOffsetRangeInfo` and normalized locations are the durable compatibility contract. |
| Page-turn UI and display modes | RDReaderView | EPUBUI | Explicitly out of scope to change; Phase 4 must adapt to existing `RDReaderView` APIs. |
</architectural_responsibility_map>
<research_summary>
## Summary
Phase 3 strengthened the native reflowable engine but did not yet make that richer page model a first-class citizen across the whole reader capability chain. The main Phase 4 work is therefore not “more pagination”; it is reconnecting the existing reader shell to the upgraded `RDEPUBTextBook` path without changing `RDReaderView` or introducing a second native reader model.
The current code already shows the exact reintegration seams:
- `RDEPUBReaderController.paginatePublication` branches between native `RDEPUBTextBookBuilder`, fixed-layout snapshots, and `RDEPUBPaginator` WebKit measurement.
- `applyTextBook`, `finishPagination`, `restoreReadingLocation`, and `currentVisibleLocation` already contain a parallel text-book path that bypasses `RDEPUBReadingSession.pageIndex(for:)`.
- `pageContentView(readerView:pageNum:containerView:)` already swaps between `RDEPUBTextContentView` and `RDEPUBWebContentView`.
- Search uses `RDEPUBTextSearchEngine` when `textBook` exists and `RDEPUBHTMLSearchEngine` otherwise.
- Selection/highlight persistence for native text still depends on `RDEPUBTextOffsetRangeInfo` absolute offsets and chapter href normalization.
That means Phase 4 should stabilize and tighten an already-existing split path rather than invent a new architecture. The right outcome is:
1. Reflowable native pages remain rendered through `RDEPUBTextBook`.
2. `RDEPUBReaderController` keeps one coherent restore/navigation/search/highlight API no matter whether the source page is native text or Web content.
3. Repagination triggered by font size, line height, viewport changes, or theme changes preserves reader state instead of resetting to page 1 or dropping search/highlight context.
The riskiest compatibility areas are also already visible:
- `RDEPUBReaderController.restoreReadingLocation` and `currentVisibleLocation` use separate logic for text-book vs snapshot pages.
- `normalizedTextSelection` recomputes progression from absolute offset payloads; regressions here would silently break persisted highlights.
- `refreshVisibleContentPreservingLocation` and `rebuildExternalTextBook` can preserve location only if the text-book lookup contract remains stable after repagination.
- `flattenedTableOfContentsItems` and `pageNumber(for:)` need page-number continuity for TOC and search to stay believable.
**Primary recommendation:** Treat Phase 4 as a reader-state consolidation phase. Strengthen `RDEPUBReaderController` and adjacent state surfaces so the Phase 3 native pagination model behaves like a first-class reader backend while preserving the existing `RDReaderView` page-turn container and the fixed / interactive WebKit paths.
</research_summary>
<code_evidence>
## Code Evidence
| Concern | Source | Why it matters |
|--------|--------|----------------|
| Native-vs-web pagination entry split | `RDEPUBReaderController.paginatePublication` | Defines Phase 4s main re-integration entry point. |
| Native restore and current-location logic | `RDEPUBReaderController.restoreReadingLocation`, `currentVisibleLocation` | Text-book navigation already bypasses `RDEPUBReadingSession`; this must stay coherent. |
| Reader state owner for non-native flows | `RDEPUBReadingSession` | Existing navigation/session state should remain centralized instead of leaking into UI code. |
| Native selection/highlight normalization | `RDEPUBReaderController.normalizedTextSelection`, `RDEPUBTextContentView`, `RDEPUBReadingModels` | Absolute offsets must survive reader interactions and persistence. |
| Search backend switching | `RDEPUBReaderController.resolvedSearchMatches`, `RDEPUBTextSearchEngine` | Search behavior must remain uniform from the users perspective across render backends. |
| Repagination triggers | `RDEPUBReaderController.configuration.didSet`, `handleViewportChangeIfNeeded`, `rebuildExternalTextBook`, `repaginatePreservingCurrentLocation` | These are the operational triggers most likely to break restore/state continuity. |
</code_evidence>
<architecture_patterns>
## Architecture Patterns
### Pattern 1: Consolidate reader behavior at the controller boundary
**What:** Keep branching localized in `RDEPUBReaderController`, but make navigation, restore, search, and persistence semantics consistent across text-book and web paths.
**Why:** The UI entry point already exists there; moving branching elsewhere would enlarge scope.
### Pattern 2: Keep offsets as the native compatibility key
**What:** Native text selection, search, and highlight flows should continue to resolve through chapter href + absolute offset payloads.
**Why:** Phase 3 deliberately preserved this invariant to make Phase 4 possible.
### Pattern 3: Repaginate by restoring location, not page number
**What:** Font/theme/viewport changes should always preserve a semantic location and rebuild around it.
**Why:** Page numbers are not stable under re-pagination; href + progression + offset-compatible selection are.
### Anti-Patterns to Avoid
- Refactoring `RDReaderView` to understand native page metadata directly.
- Creating a second navigation state machine just for the native text path.
- Switching highlights/search to page-local identifiers that cannot survive repagination.
</architecture_patterns>
<dont_hand_roll>
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Native reader shell | A new dedicated native-text controller | Existing `RDEPUBReaderController` split path | Scope stays contained and existing app entry points remain valid. |
| Restore state | Page-number-only recovery | `RDEPUBLocation` + native page lookup + offset contract | Page numbers drift after repagination. |
| Search compatibility | Separate search UI semantics for text pages | Existing `RDEPUBSearchState` and `RDEPUBSearchMatch` flow | User-facing search behavior should stay unified. |
</dont_hand_roll>
<common_pitfalls>
## Common Pitfalls
### Pitfall 1: Letting native and web reader paths diverge in user-visible behavior
If search, selection, or TOC navigation behaves differently based on backend, the reader becomes unpredictable.
### Pitfall 2: Preserving page numbers instead of semantic locations during repagination
This causes subtle wrong-page restores after font, theme, or viewport changes.
### Pitfall 3: Refreshing visible content without reapplying current search/highlight state
Native text pages are recreated from attributed substrings, so state must be re-derived every time.
</common_pitfalls>
<open_questions>
## Open Questions
- Should any of the new Phase 3 page metadata be surfaced to reader delegates in Phase 4, or remain internal until Phase 5 diagnostics?
- Is `RDEPUBReadingSession` worth extending to cover native `RDEPUBTextBook` page lookup as well, or is the current controller-local split still the smallest safe change?
- Which search/highlight and TOC navigation paths are most important to validate interactively in the demo during execution?
</open_questions>
@@ -0,0 +1,29 @@
# Phase 4: 接回现有 reader 能力链路 - Validation
## Required Checks
- `RDEPUBReaderController` 仍是 native text / web reader 的统一入口
- `RDReaderView` 不需要新增 native page metadata 感知逻辑
- native text 的高亮、搜索、TOC、恢复位置仍通过 `RDEPUBLocation` 与绝对 offset 兼容语义工作
- 字号、行高、主题、viewport 变化后的 repagination 不会把阅读状态重置为错误位置
## Verification Anchors
### Source-level
- `rg -n "paginatePublication|applyTextBook|restoreReadingLocation|currentVisibleLocation|finishPagination|pageContentView" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
- `rg -n "RDEPUBTextOffsetRangeInfo|normalizedTextSelection|search\\(|pageNumber\\(for: searchMatch\\)" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/EPUBCore Sources/RDReaderView/EPUBTextRendering`
- `rg -n "requiresRepagination|refreshVisibleContentPreservingLocation|handleViewportChangeIfNeeded|rebuildExternalTextBook" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/RDURLReaderController.swift`
### Runtime / demo
- open a reflowable EPUB in `ReadViewDemo`
- navigate via TOC and ensure the target page remains correct on the native text path
- create or load a highlight, then verify it still paints after page refresh / repagination
- run a search and verify current match navigation lands on the right page
- change font size, line height, and theme; confirm the reader restores to the same semantic location
## Completion Standard
Phase 4 planning is only good enough if each executable plan names both:
1. the code seam it will modify
2. the reader behavior it must preserve or prove
@@ -0,0 +1,32 @@
# Phase 4 Verification
## Commands
- `build_sim` for scheme `ReadViewDemo`
- `build_run_sim` for scheme `ReadViewDemo`
- `test -d ReadViewDemo/ReadViewDemo/book`
- `rg -n "paginatePublication|applyTextBook|finishPagination|restoreReadingLocation|currentVisibleLocation|RDEPUBTextOffsetRangeInfo|normalizedTextSelection|pageStartOffset|pageEndOffset|search\\(|navigateToCurrentSearchMatch|pageNumber\\(for: searchMatch\\)|flattenedTableOfContentsItems|pageIndex\\(for:|requiresRepagination|refreshVisibleContentPreservingLocation|rebuildExternalTextBook|handleViewportChangeIfNeeded|restore|repagination|highlight|location|theme|font" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift Sources/RDReaderView/EPUBCore Sources/RDReaderView/RDURLReaderController.swift ReadViewDemo/ReadViewDemo -S`
## Results
- Simulator build succeeded
- Simulator launch succeeded
- Sample corpus directory exists
- Source verification confirms:
- native text pagination now feeds a real reader snapshot and location helpers
- native selection/highlight compatibility still depends on absolute chapter offsets
- native search page lookup now uses exact offset evidence when available
- repagination and restore hooks remain centered in `RDEPUBReaderController`, not `RDReaderView`
## Runtime Evidence
From the app runtime log:
- `EPUB 资源验证:2/2 通过`
- `分页诊断:宝山辽墓材料与释读 · 章节 10 · attachment 页 54 · semantic break 页 57 · reasons [attachmentBoundary:31, blockBoundary:26, frameLimit:17, chapterEnd:4] · page break: blockBoundary`
- `恢复诊断:宝山辽墓材料与释读 · base 39 · font-shift 44 · theme-stable yes · href-stable yes · progression-delta 0.004`
## Residual Risk
- The restore validation is still corpus-level and not a full interactive UI automation pass through TOC/highlight/search actions
- The native and web paths still branch inside `RDEPUBReaderController`; Phase 5 should focus on broader regression and stability coverage rather than further architectural expansion