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,92 @@
---
phase: 02-typesetter-css
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextStyleSheetBuilder.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift
autonomous: true
requirements:
- REND-02
user_setup: []
must_haves:
truths:
- native renderer has an explicit chapter preprocessing / stylesheet assembly layer rather than only a flat DTCoreText option bag.
- CSS precedence is deterministic and documented as `default / replace / dark / epub / user`.
- chapter baseURL and resource context are preserved through preprocessing.
artifacts:
- .planning/phases/02-typesetter-css/02-01-SUMMARY.md
key_links:
- `RDEPUBTextBookBuilder` remains the chapter boundary and provides render context.
- `RDEPUBStyleSheetBuilder` is not repurposed for the native path.
---
<objective>
设计并实现旧引擎中的 WXRead 风格 stylesheet builder / HTML 预处理增强。
Purpose: 把章节 HTML、baseURL、资源引用和用户/主题/EPUB 样式合成为一个明确的 native renderer 输入层。
Output: `Sources/RDReaderView/EPUBTextRendering/` 中新增或重构的 stylesheet / preprocessing helper,以及与其对接的 renderer entry points。
</objective>
<execution_context>
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
@$HOME/.codex/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-typesetter-css/02-RESEARCH.md
@.planning/phases/02-typesetter-css/02-PATTERNS.md
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift
@Sources/RDReaderView/EPUBUI/RDEPUBReaderConfiguration.swift
@Sources/RDReaderView/EPUBUI/RDEPUBReaderTheme.swift
@Sources/RDReaderView/EPUBCore/RDEPUBPreferences.swift
@Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift
</context>
<tasks>
<task type="auto">
<name>Task 1: 建立 native stylesheet / preprocessing helper</name>
<files>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift</files>
<read_first>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift, Sources/RDReaderView/EPUBCore/RDEPUBPreferences.swift, Sources/RDReaderView/EPUBUI/RDEPUBReaderConfiguration.swift, Sources/RDReaderView/EPUBUI/RDEPUBReaderTheme.swift</read_first>
<action>在 `EPUBTextRendering` 内新增或重构一个明确的 native stylesheet/preprocessing helper:它需要把 `RDEPUBPreferences` / `RDEPUBReaderTheme` / `RDEPUBTextRenderStyle` 里的输入翻译成可叠加的 CSS 层,并在 chapter import 前完成 HTML 规范化、style tag 组装和必要的资源上下文占位。实现时要把层级顺序写死为 `default / replace / dark / epub / user`,并把它暴露成可复用的 renderer input,而不是继续在 `dtOptions` 里堆零散参数。</action>
<verify>rg -n "default|replace|dark|epub|user|stylesheet|preprocess|chapter|baseURL" Sources/RDReaderView/EPUBTextRendering</verify>
<acceptance_criteria>
- native renderer 侧出现独立的 stylesheet / preprocessing 责任
- CSS 层级顺序是显式且可读的
- chapter baseURL 与资源上下文没有被抹掉
</acceptance_criteria>
<done>章节样式输入不再是一个扁平 option bag,而是一个可追踪、可验证的 native 样式层管线。</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `test -f Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift`
- [ ] `rg -n "default|replace|dark|epub|user|preprocess|stylesheet" Sources/RDReaderView/EPUBTextRendering`
- [ ] 计划文档明确拒绝复用 WebView 专用的 `RDEPUBStyleSheetBuilder`
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- No errors or warnings introduced
- native renderer 的样式层输入足以支撑 Phase 2 后半段的 renderer 接线
</success_criteria>
<output>
After completion, create `.planning/phases/02-typesetter-css/02-01-SUMMARY.md`
</output>
@@ -0,0 +1,68 @@
---
phase: 02-typesetter-css
plan: 01
subsystem: epub-text-rendering
tags: [epub, dtcoretext, css, stylesheet, preprocessing]
requires: []
provides:
- "native renderer 的章节级 stylesheet / preprocessing 输入层"
- "显式的 CSS 五层顺序 default / replace / dark / epub / user"
- "章节 baseURL 与资源诊断信息"
affects: [phase-02, phase-03, reflowable-engine]
tech-stack:
added: []
patterns: ["chapter render request", "stylesheet layering", "resource-aware preprocessing"]
key-files:
created: []
modified:
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift
key-decisions:
- "不复用 WebView 专用的 `RDEPUBStyleSheetBuilder`,而是在 native path 中建立独立的 chapter preprocessing pipeline。"
- "把 EPUB 外链 CSS 预处理为原位内联样式,并在内联时重写 `url(...)` 资源路径。"
patterns-established:
- "章节输入先做 HTML 规范化、baseURL 注入、stylesheet 分层,再交给 renderer。"
requirements-completed: []
duration: 35min
completed: 2026-05-22
---
# Phase 02 Plan 01: Typesetter Input Layer Summary
**为 native reflowable renderer 建立了独立的 chapter stylesheet / preprocessing 层,输入不再只是扁平 DTCoreText options。**
## Performance
- **Duration:** 35 min
- **Completed:** 2026-05-22
- **Tasks:** 1
- **Files modified:** 2
## Accomplishments
- 在 [RDEPUBTextRenderer.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift) 中引入了 `RDEPUBTextChapterContext``RDEPUBTextChapterRenderRequest``RDEPUBTextStyleSheetPackage` 与资源诊断模型。
- 在 [RDEPUBTextRendererSupport.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift) 中实现了章节 HTML 规范化、`<base>` 注入、linked CSS 内联、`url(...)` 重写,以及 `default / replace / dark / epub / user` 五层 CSS 组装。
- 样式层顺序被硬编码进 preprocessing 管线,native renderer 现在消费的是明确的 chapter render request,而不是一组分散选项。
## Files Created/Modified
- [RDEPUBTextRenderer.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift) - 定义 chapter context、stylesheet package 与 resource diagnostics
- [RDEPUBTextRendererSupport.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift) - 实现 preprocessing、CSS 分层、资源解析与 HTML 注入
## Decisions Made
- 明确拒绝把 WebView 分页路径的 `RDEPUBStyleSheetBuilder` 拉回 native renderer。
- EPUB 外链 CSS 保持为 EPUB 层,但通过预处理内联进入统一管线,以确保 user layer 始终处于最后覆盖位。
## Verification
- `rg -n "default|replace|dark|epub|user|stylesheet|preprocess|chapter|baseURL" Sources/RDReaderView/EPUBTextRendering`
- `xcodebuild` simulator build passed via `ReadViewDemo` scheme
## Next Phase Readiness
- Renderer 已经有显式 chapter context 和 stylesheet outputPlan 02 可以直接把 `RDEPUBDTCoreTextRenderer` 接到这个新契约上。
---
*Phase: 02-typesetter-css*
*Completed: 2026-05-22*
@@ -0,0 +1,93 @@
---
phase: 02-typesetter-css
plan: 02
type: execute
wave: 2
depends_on:
- "02-01"
files_modified:
- Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift
- Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift
- Sources/RDReaderView/EPUBCore/RDEPUBParser.swift
autonomous: true
requirements:
- REND-01
- REND-02
user_setup: []
must_haves:
truths:
- `RDEPUBDTCoreTextRenderer` receives explicit chapter context and stylesheet output.
- `RDEPUBTextBookBuilder` continues to own chapter boundary and page model assembly.
- reflowable EPUB input is no longer just a small builder option bag.
artifacts:
- .planning/phases/02-typesetter-css/02-02-SUMMARY.md
key_links:
- `RDEPUBParser.readingProfile()` still keeps `webFixedLayout` and `webInteractive` out of this path.
- `RDEPUBResourceResolver` remains the authority for baseURL/resource normalization.
---
<objective>
改造 `RDEPUBDTCoreTextRenderer` 与相邻渲染链路,使其承接新的样式分层与章节上下文。
Purpose: 让 native renderer 真正消费 Phase 2.1 产出的 style pipeline,并保持章节边界、资源上下文和 fallback 行为稳定。
Output: 对 renderer 和 book builder 的接线调整,使 chapter render path 具备明确的输入契约。
</objective>
<execution_context>
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
@$HOME/.codex/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-typesetter-css/02-RESEARCH.md
@.planning/phases/02-typesetter-css/02-PATTERNS.md
@.planning/phases/02-typesetter-css/02-01-PLAN.md
@Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift
@Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift
@Sources/RDReaderView/EPUBCore/RDEPUBParser.swift
</context>
<tasks>
<task type="auto">
<name>Task 1: 接入 chapter context 并简化 renderer 输入</name>
<files>Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift</files>
<read_first>Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift</read_first>
<action>把 chapter 级上下文显式传入 renderer:包括 `href``baseURL`、样式层输出、以及后续资源解析所需的最小元数据。`RDEPUBTextBookBuilder` 继续负责拼接章节、构造 `RDEPUBTextChapter` / `RDEPUBTextPage`,但不再只传一个“扁平 style”给 renderer。`RDEPUBDTCoreTextRenderer` 应该从“DTCoreText option bag 的薄包装”转成“章节 typesetter 的统一入口”。</action>
<verify>rg -n "chapter context|baseURL|stylesheet|RDEPUBDTCoreTextRenderer|RDEPUBTextBookBuilder|DTHTMLAttributedStringBuilder" Sources/RDReaderView/EPUBTextRendering</verify>
<acceptance_criteria>
- renderer 入参能表达 chapter 级上下文
- renderer 仍保留 fallback path
- book builder 仍然是 page model 的唯一构造点
</acceptance_criteria>
<done>native renderer 的输入契约足够清楚,后续分页语义重构可以直接接在它后面。</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `test -f Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift`
- [ ] `rg -n "chapter context|baseURL|stylesheet|RDEPUBDTCoreTextRenderer|RDEPUBTextBookBuilder" Sources/RDReaderView/EPUBTextRendering`
- [ ] 文档与 Phase 2.1 计划不冲突,并且没有把 WebView 路径拉回 native renderer
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- No errors or warnings introduced
- renderer 与 book builder 的职责边界足够稳定,Phase 2.3 可以直接用真实样本验证资源解析
</success_criteria>
<output>
After completion, create `.planning/phases/02-typesetter-css/02-02-SUMMARY.md`
</output>
@@ -0,0 +1,70 @@
---
phase: 02-typesetter-css
plan: 02
subsystem: epub-text-rendering
tags: [epub, dtcoretext, renderer, chapter-context]
requires: [02-01]
provides:
- "renderer 接收显式 chapter context"
- "book builder 到 renderer 的稳定输入契约"
- "resource diagnostics 沿渲染链路透传"
affects: [phase-02, phase-03, reflowable-engine]
tech-stack:
added: []
patterns: ["chapter-scoped renderer contract", "book-builder-owned pagination boundary"]
key-files:
created: []
modified:
- Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
- Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift
key-decisions:
- "`RDEPUBTextBookBuilder` 继续是 chapter boundary 与 page model assembly 的唯一持有者。"
- "`RDEPUBResourceResolver` 扩展为 chapter-relative 资源标准化的权威入口。"
patterns-established:
- "renderer 从 request.context 读取 href/baseURL/stylesheet/resource diagnostics。"
requirements-completed: []
duration: 20min
completed: 2026-05-22
---
# Phase 02 Plan 02: Renderer Wiring Summary
**把新的 chapter stylesheet / preprocessing pipeline 正式接入 `RDEPUBDTCoreTextRenderer` 与 `RDEPUBTextBookBuilder`。**
## Performance
- **Duration:** 20 min
- **Completed:** 2026-05-22
- **Tasks:** 1
- **Files modified:** 3
## Accomplishments
- [RDEPUBDTCoreTextRenderer.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift) 现在首先消费 `RDEPUBTextChapterRenderRequest`,再根据 request 中的 `baseURL` 与 style 生成 DTCoreText 输入。
- [RDEPUBTextBookBuilder.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift) 在 chapter boundary 处组装 render request,并保留分页、chapter/page model 组装职责不变。
- [RDEPUBResourceResolver.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift) 新增 chapter-relative `normalizedHref` / `fileURL(forReference:relativeToHref:)`,让资源标准化继续集中在 resolver 层。
## Files Created/Modified
- [RDEPUBDTCoreTextRenderer.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift) - renderer 改为 chapter-scoped 输入契约
- [RDEPUBTextBookBuilder.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift) - builder 负责组装 request,并保留 page model 边界
- [RDEPUBResourceResolver.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift) - chapter-relative 资源标准化与文件定位
## Decisions Made
- 保留 `renderChapter(html:baseURL:style:)` 兼容入口,但统一转发到新的 request-based renderer 契约。
- 资源诊断跟随 render result 一起回到 builder,便于后续 demo/sample validation 使用真实 chapter 数据做 spot-check。
## Verification
- `rg -n "chapter context|baseURL|stylesheet|RDEPUBDTCoreTextRenderer|RDEPUBTextBookBuilder" Sources/RDReaderView/EPUBTextRendering`
- `xcodebuild` simulator build passed via `ReadViewDemo` scheme
## Next Phase Readiness
- 真实样本验证现在可以直接读取 builder 的 `lastBuildResourceDiagnostics`,无需再创建并行 reader shell。
---
*Phase: 02-typesetter-css*
*Completed: 2026-05-22*
@@ -0,0 +1,93 @@
---
phase: 02-typesetter-css
plan: 03
type: execute
wave: 3
depends_on:
- "02-02"
files_modified:
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift
- Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift
- Sources/RDReaderView/EPUBCore/RDEPUBParser.swift
- ReadViewDemo/ReadViewDemo/ViewController.swift
autonomous: true
requirements:
- REND-01
- REND-02
user_setup: []
must_haves:
truths:
- sample EPUBs with images and linked CSS can still resolve chapter resources through the new native pipeline.
- baseURL and resource normalization are verified against real EPUB files, not just code review.
- the validation path stays on the current demo app and does not require a new reader shell.
artifacts:
- .planning/phases/02-typesetter-css/02-03-SUMMARY.md
key_links:
- `RDEPUBResourceResolver` and `RDEPUBParser` are the source of truth for normalized chapter/resource paths.
- sample books in `ReadViewDemo/ReadViewDemo/book/` are the regression corpus.
---
<objective>
验证章节级图片/CSS/基础资源在新渲染输入下可正常解析。
Purpose: 证明 Phase 2.1 和 Phase 2.2 的接线在真实 EPUB 上没有破坏资源加载和章节样式解析。
Output: sample EPUB spot-checks、验证记录和必要的诊断补充。
</objective>
<execution_context>
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
@$HOME/.codex/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-typesetter-css/02-RESEARCH.md
@.planning/phases/02-typesetter-css/02-PATTERNS.md
@.planning/phases/02-typesetter-css/02-01-PLAN.md
@.planning/phases/02-typesetter-css/02-02-PLAN.md
@Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift
@Sources/RDReaderView/EPUBCore/RDEPUBParser.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@ReadViewDemo/ReadViewDemo/ViewController.swift
@ReadViewDemo/ReadViewDemo/book/
</context>
<tasks>
<task type="auto">
<name>Task 1: 用真实样本书验证 chapter resource resolution</name>
<files>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift</files>
<read_first>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift, Sources/RDReaderView/EPUBCore/RDEPUBParser.swift, ReadViewDemo/ReadViewDemo/ViewController.swift</read_first>
<action>使用 `ReadViewDemo/ReadViewDemo/book/` 下的 EPUB 作为验证集,确认新 pipeline 下章节 baseURL、相对 CSS、图片和其他资源仍能正确解析。必要时增加最小的诊断输出或 debug hook,但不要引入新的 reader shell。验证重点是“资源真的能被定位和加载”,不是只看渲染结果是否大致正常。</action>
<verify>rg -n "baseURL|resource|css|image|stylesheet|normalized" Sources/RDReaderView/EPUBCore Sources/RDReaderView/EPUBTextRendering ReadViewDemo/ReadViewDemo</verify>
<acceptance_criteria>
- sample EPUB 资源加载没有断裂
- relative href / baseURL 解析保持稳定
- demo app 仍然是验证入口
</acceptance_criteria>
<done>Phase 2 的 stylesheet / preprocessing 改造可以被真实书籍证实没有把资源寻址弄坏。</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `test -d ReadViewDemo/ReadViewDemo/book`
- [ ] `rg -n "baseURL|resource|css|image|stylesheet|normalized" Sources/RDReaderView/EPUBCore Sources/RDReaderView/EPUBTextRendering`
- [ ] demo app 仍然只依赖现有 reader 主流程,没有新增并行引擎
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- No errors or warnings introduced
- 至少一套 sample EPUB 能证明章节资源和样式层在 native renderer 中是可工作的
</success_criteria>
<output>
After completion, create `.planning/phases/02-typesetter-css/02-03-SUMMARY.md`
</output>
@@ -0,0 +1,67 @@
---
phase: 02-typesetter-css
plan: 03
subsystem: demo-validation
tags: [epub, resources, validation, demo]
requires: [02-02]
provides:
- "真实 EPUB 样本资源验证"
- "demo 首页中的验证状态展示"
- "运行时日志里的验证结果"
affects: [phase-02, phase-05, readviewdemo]
tech-stack:
added: []
patterns: ["sample-corpus validation", "demo-surfaced diagnostics"]
key-files:
created: []
modified:
- ReadViewDemo/ReadViewDemo/ViewController.swift
key-decisions:
- "继续使用现有 demo app 作为验证入口,不新增 reader shell。"
- "以 `RDEPUBTextBookBuilder.lastBuildResourceDiagnostics` 为资源完整性判定依据。"
patterns-established:
- "demo 启动时后台扫描 EPUB corpus,并把 pass/fail 汇总写到状态标签与运行时日志。"
requirements-completed: []
duration: 20min
completed: 2026-05-22
---
# Phase 02 Plan 03: Sample EPUB Validation Summary
**用 demo 中的真实 EPUB 样本验证了新 native pipeline 的章节资源定位,并把结果直接暴露给 demo UI 与 runtime log。**
## Performance
- **Duration:** 20 min
- **Completed:** 2026-05-22
- **Tasks:** 1
- **Files modified:** 1
## Accomplishments
- 在 [ViewController.swift](/Users/shen/Work/Code/ReadViewSDK/ReadViewDemo/ReadViewDemo/ViewController.swift) 中增加后台 EPUB validation pass,自动解析 demo corpus、构建 `RDEPUBTextBook`,并检查 `lastBuildResourceDiagnostics` 是否存在缺失资源。
- 状态标签现在会显示 `EPUB 资源验证:X/Y 通过`,运行时日志也会输出同样的汇总,便于 simulator 回归。
- simulator 运行日志记录了:`[ReadViewDemo] EPUB 资源验证:2/2 通过`,证明两本 `.textReflowable` 样本书在新 pipeline 下资源解析通过。
## Files Created/Modified
- [ViewController.swift](/Users/shen/Work/Code/ReadViewSDK/ReadViewDemo/ReadViewDemo/ViewController.swift) - 样本校验、UI 文案展示、runtime log 输出
## Decisions Made
- 只把 `.textReflowable` 样本纳入这轮自动验证;`webFixedLayout` / `webInteractive` 仍由原有 WebView 路径负责。
- 验证关注点限定为“资源是否被正确定位并进入渲染链路”,不在本阶段引入新的视觉比对基础设施。
## Verification
- `test -d ReadViewDemo/ReadViewDemo/book`
- `rg -n "baseURL|resource|css|image|stylesheet|normalized" Sources/RDReaderView/EPUBCore Sources/RDReaderView/EPUBTextRendering ReadViewDemo/ReadViewDemo`
- simulator runtime log: `[ReadViewDemo] EPUB 资源验证:2/2 通过`
## Next Phase Readiness
- Phase 3 可以在这个资源稳定输入层之上直接推进 attributed string 元数据和复杂分页器,不需要再回头补资源寻址契约。
---
*Phase: 02-typesetter-css*
*Completed: 2026-05-22*
@@ -0,0 +1,32 @@
# Phase 2: 重构 typesetter 与 CSS 分层 - Pattern Map
## Goal
为本阶段执行提供“先看哪里、按什么证据写代码”的最短路径。Phase 2 的核心不是分页,而是把章节 HTML、baseURL、资源解析和 CSS 层级变成明确的 native renderer 输入。
## Planned Outputs
| Planned file | Role | Primary evidence | Why this is the right analog |
|--------------|------|------------------|------------------------------|
| `.planning/phases/02-typesetter-css/02-01-PLAN.md` | 章节样式层与 HTML 预处理实现计划 | `Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift`, `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift`, `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift` | 这些文件已经是 native renderer 的真实入口,最适合承接 stylesheet pipeline。 |
| `.planning/phases/02-typesetter-css/02-02-PLAN.md` | renderer 与 chapter context 接线计划 | `Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift`, `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift`, `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift` | 这三个文件共同构成 HTML 导入、章节边界和富文本输出链路。 |
| `.planning/phases/02-typesetter-css/02-03-PLAN.md` | 资源解析与样本书验证计划 | `Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift`, `Sources/RDReaderView/EPUBCore/RDEPUBParser.swift`, `ReadViewDemo/ReadViewDemo/book/*.epub` | 资源解析必须靠真实 EPUB 资源和 chapter baseURL 证实,不宜只看代码。 |
## Code Evidence Map
| Concern | Closest source of truth | Evidence to extract |
|---------|-------------------------|---------------------|
| 章节输入如何进入 native renderer | `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift` | chapter `href``baseURL``renderChapter(...)` 入参、页模型构造点。 |
| 当前 renderer 输入到底有多薄 | `Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift` | `DTHTMLAttributedStringBuilder``dtOptions(baseURL:style:)`、fallback path。 |
| 现有 HTML 预处理做了什么 | `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift` | fragment marker 注入、reading attribute normalization、fallback attributed string。 |
| 用户样式与主题从哪里来 | `Sources/RDReaderView/EPUBUI/RDEPUBReaderConfiguration.swift`, `Sources/RDReaderView/EPUBUI/RDEPUBReaderTheme.swift`, `Sources/RDReaderView/EPUBCore/RDEPUBPreferences.swift` | font size / line spacing / theme colors 如何进入 renderer 输入。 |
| 哪些 CSS helper 不能直接复用 | `Sources/RDReaderView/EPUBCore/RDEPUBStyleSheetBuilder.swift` | 这是 WebView 量测/分页 helper,不是 native DTCoreText stylesheet pipeline。 |
| 资源如何定位 | `Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift` | 相对路径、manifest item、OPF directory、normalized href。 |
| 哪些样本书最有代表性 | `ReadViewDemo/ReadViewDemo/book/*.epub` | 含图片、复杂段落、外链 CSS 的样本最适合验证 chapter-scoped styles。 |
## Writing Guidance
- 计划文档先把新的 native stylesheet pipeline 命名清楚,再把它挂到现有 `RDEPUBDTCoreTextRenderer``RDEPUBTextBookBuilder` 上。
- 任何涉及样式 precedence 的描述,都要写出 `default / replace / dark / epub / user` 的顺序,而不是只说“增加 CSS 支持”。
- 资源验证优先写 chapter baseURL 和相对资源引用,不要只验证纯文本章节。
- 如果要提到 WebView helper,只能作为“不要复用”的反例。
@@ -0,0 +1,177 @@
# Phase 2: 重构 typesetter 与 CSS 分层 - Research
**Researched:** 2026-05-21
**Domain:** iOS EPUB reader architecture / native typesetting and chapter-scoped CSS layering
**Confidence:** HIGH
<user_constraints>
## User Constraints (from CONTEXT.md)
No user constraints - all decisions at the agent's discretion.
</user_constraints>
<architectural_responsibility_map>
## Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|------------|-------------|----------------|-----------|
| 章节 HTML 预处理与 CSS 层级组装 | Browser/Client | EPUBTextRendering | 这是 Phase 2 的主战场,必须在 native reflowable 路径里完成。 |
| 章节级 baseURL、资源寻址与样式注入 | Browser/Client | EPUBCore | `RDEPUBTextBookBuilder` 负责提供 chapter boundary 和 baseURL`RDEPUBResourceResolver` 负责资源定位。 |
| 章节 HTML to `NSAttributedString` 导入 | Browser/Client | EPUBTextRendering | `RDEPUBDTCoreTextRenderer` 仍是 DTCoreText 入口,但不能再只依赖一个扁平 option bag。 |
| 主题、字号、行高、背景色等用户输入 | EPUBUI | Browser/Client | `RDEPUBReaderConfiguration` / `RDEPUBPreferences` 仍是用户态入口,但它们必须被转换成可叠加的样式层。 |
| WebView 渲染与测量辅助 | Browser/Client | EPUBCore | `RDEPUBStyleSheetBuilder` 只服务 `WKWebView` 路径,不应直接搬到 native renderer 里。 |
</architectural_responsibility_map>
<research_summary>
## Summary
当前 native reflowable 路径已经有章节边界,但样式输入仍然太薄。`RDEPUBTextBookBuilder` 只负责取出 spine HTML、给出 chapter baseURL,然后把原始 HTML 直接交给 `RDEPUBDTCoreTextRenderer`。后者再用 `DTHTMLAttributedStringBuilder` 加一个小型 option bag 导入,最后靠 `normalizeReadingAttributes` 做字体和行距修正。这种结构能跑通基础排版,但不具备 WXRead 风格的显式 CSS 分层,也没有把章节资源、publication 级样式和用户样式拆成可验证的输入层。
Phase 2 的正确切入点不是重写 parser,也不是把 `RDEPUBStyleSheetBuilder` 从 WebView 路径直接复用到 native 路径。更稳妥的做法是:在 `EPUBTextRendering` 内部建立一个明确的 stylesheet / preprocessing pipeline,先把章节 HTML 规范化、补齐 baseURL 和资源引用上下文,再按 `default / replace / dark / epub / user` 的顺序合成样式层,最后把合成后的 HTML/CSS 交给 DTCoreText。
这个阶段的目标是把 renderer 输入从“`DTCoreText` 默认 builder + 少量 options”升级成“章节上下文 + 样式层 + 资源上下文 + 规范化 HTML”。这样后续 Phase 3 才能在不重做入口的前提下,引入更重的页面级属性和分页语义。
**Primary recommendation:** Phase 2 应先把 native stylesheet pipeline 和 chapter preprocessing 固化在 `EPUBTextRendering`,然后再把 `RDEPUBDTCoreTextRenderer` / `RDEPUBTextBookBuilder` 接到这个 pipeline 上,保持 `RDReaderView``WKWebView` 路径不动。
</research_summary>
<standard_stack>
## Standard Stack
### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| UIKit | iOS 15+ SDK | Reader UI and style input | 当前 reader 配置、主题与视图层仍然由 UIKit 提供。 |
| Foundation | System | HTML normalization, URL handling, string processing | 章节级 baseURL、样式拼接与资源路径修正都依赖它。 |
| CoreText | System | Pagination fallback and text measurement | Native book builder 仍需要它做后续分页验证。 |
| DTCoreText | 1.6.28 | HTML/CSS to `NSAttributedString` | Phase 2 的目标是把它从“简单导入器”提升成“可配置的章节 typesetter”。 |
| WebKit | System | Fixed-layout / interactive EPUB only | 只作为对照边界存在,不进入 Phase 2 的 native 分层。 |
### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| ZIPFoundation | 0.9.20 | EPUB archive/resource access | 资源是否能被 chapter baseURL 正确解析,需要它提供底层文件。 |
| DTFoundation | 1.7.19 | DTCoreText support | DTCoreText 解析 HTML / 附件 / 图片资源仍依赖它。 |
### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| Explicit native CSS layering | Continue passing a small `dtOptions` bag | 不能表达 `default / replace / dark / epub / user` 的层级关系,也难验证覆盖顺序。 |
| Chapter-scoped preprocessing in `EPUBTextRendering` | Reuse `RDEPUBStyleSheetBuilder` from WebView path | 会把 WebView 测量语义带进 native renderer,职责混淆。 |
| DTCoreText-based native evolution | Replacing DTCoreText with a custom HTML parser | 成本和风险都过高,会偏离当前 roadmap 的“直接重构旧引擎”原则。 |
**Installation:**
```bash
pod install
```
</standard_stack>
<architecture_patterns>
## Architecture Patterns
### System Architecture Diagram
```text
RDEPUBReaderConfiguration / RDEPUBPreferences
-> RDEPUBTextRenderStyle
-> RDEPUBTextBookBuilder
-> chapter HTML + baseURL + resource context
-> native stylesheet / preprocessing pipeline
-> RDEPUBDTCoreTextRenderer
-> DTHTMLAttributedStringBuilder
-> NSAttributedString
-> RDEPUBTextBook / RDEPUBTextPage
```
### Recommended Project Structure
```text
Sources/RDReaderView/
├── EPUBCore/ # Parser, publication, resource resolution, web pagination
├── EPUBTextRendering/ # Native reflowable rendering, preprocessing, pagination
├── EPUBUI/ # Reader configuration, theme, controller wiring
└── RDReaderView.swift # Stable page container and gestures
```
### Pattern 1: Keep chapter context explicit
**What:** Every chapter render should know its `href`, `baseURL`, publication-scoped styles, and user style inputs before DTCoreText import begins.
**When to use:** When a renderer must resolve relative CSS/image/font URLs reliably.
**Example:** `RDEPUBTextBookBuilder` already knows chapter `href` and `baseURL`; Phase 2 should make that context first-class instead of implicit.
### Pattern 2: Make CSS precedence explicit
**What:** Build a deterministic layer order: `default -> replace -> dark -> epub -> user`.
**When to use:** When theme, publication, and user preferences can all change the same visual property.
**Example:** Dark theme text/background colors should be the highest-priority visible override, while EPUB resources remain below user settings.
### Pattern 3: Separate preprocessing from pagination
**What:** Normalize HTML and resolve resources before chapter import; keep pagination logic in the next stage.
**When to use:** When `RDEPUBDTCoreTextRenderer` must remain responsible for HTML-to-attributed-string conversion, not page slicing.
**Example:** Phase 2 should leave `ss_pageRanges(size:)` untouched and focus only on renderer input quality.
### Anti-Patterns to Avoid
- **Using `RDEPUBStyleSheetBuilder` as the native stylesheet engine:** It is a WebView measurement helper and already encodes column/layout semantics that do not belong in native DTCoreText import.
- **Treating `RDEPUBTextRenderStyle` as the final style model:** It currently carries font, line spacing, and colors, but Phase 2 needs a richer chapter-scoped stylesheet pipeline around it.
- **Pushing CSS layering into `RDReaderView`:** The page container should remain a consumer of rendered pages, not a CSS assembly point.
</architecture_patterns>
<dont_hand_roll>
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| HTML parsing | A brand-new EPUB HTML parser | DTCoreText builder extension points | The repo already depends on DTCoreText for native text rendering. |
| Resource resolution | Ad-hoc string replacement for relative URLs | `RDEPUBResourceResolver` and `baseURL` context | Resource resolution must remain chapter-scoped and deterministic. |
| Theme plumbing | Copying WebView CSS injection logic into native renderer | Reader configuration + native stylesheet layers | Native renderer should own native style semantics. |
| Pagination semantics | Page slicing changes in Phase 2 | Keep current `ss_pageRanges(size:)` boundary | Phase 3 owns the heavy pagination rewrite. |
**Key insight:** Phase 2 should make chapter input richer, not make pagination heavier. The win is in the renderer input contract, not in a new page model yet.
</dont_hand_roll>
<common_pitfalls>
## Common Pitfalls
### Pitfall 1: Reusing the WebView stylesheet helper too early
**What goes wrong:** The native path accidentally inherits WebView-only column and viewport semantics.
**Why it happens:** `RDEPUBStyleSheetBuilder` looks like a ready-made stylesheet utility, but it is purpose-built for `WKWebView`.
**How to avoid:** Build a separate native stylesheet helper under `EPUBTextRendering`.
### Pitfall 2: Letting style precedence stay implicit
**What goes wrong:** Theme, user preferences, and EPUB CSS fight each other with no stable priority.
**Why it happens:** `dtOptions` is too small to represent the full cascade.
**How to avoid:** Make layer ordering an explicit API and document it in the renderer contract.
### Pitfall 3: Breaking resource URLs while normalizing HTML
**What goes wrong:** Images, linked stylesheets, and fonts stop loading once HTML is rewritten.
**Why it happens:** Preprocessing can easily strip or relocate nodes without preserving `baseURL`.
**How to avoid:** Keep chapter `baseURL` and resource resolution visible in the pipeline and verify with sample EPUBs.
</common_pitfalls>
<sota_updates>
## State of the Art (2024-2025)
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Flat HTML import with a small option bag | Explicit chapter-scoped preprocessing plus layered CSS | Already implied by WXRead reference material | Needed to support publication/user/theme overrides in a predictable order. |
| Theme colors applied after import | Style inputs assembled before or during import | Already visible in current repo boundary | Reduces post-processing ambiguity and makes resource/style verification easier. |
| One-size-fits-all renderer input | Chapter context with separate resource/style concerns | Required by Phase 2 scope | Makes baseURL and CSS resolution testable. |
**New tools/patterns to consider:**
- Chapter-scoped stylesheet assembly: keeps `href`, `baseURL`, and style precedence together.
- Pre-import HTML normalization: a safe place to inject style scaffolding without changing page slicing.
**Deprecated/outdated:**
- “Simple DTCoreText renderer + post-hoc normalization” as a long-term design.
- “Borrow WebView CSS helpers for native rendering” as a direct implementation strategy.
</sota_updates>
<open_questions>
## Open Questions
- Should `default` and `replace` layers be generated from engine code, or should some defaults live in bundled EPUB/native CSS assets?
- Where should publication-scoped CSS be sourced from first: OPF manifest, linked chapter CSS files, or a normalized in-memory layer?
- Do we need a dedicated native context type for chapter render input, or can `RDEPUBTextBookBuilder` carry all inputs through existing method parameters?
- How much of the current `RDEPUBTextRenderStyle` should remain public API versus moving into an internal stylesheet model?
</open_questions>
@@ -0,0 +1,76 @@
---
phase: 2
slug: typesetter-css
status: draft
nyquist_compliant: false
wave_0_complete: false
created: 2026-05-21
---
# Phase 2 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | shell assertions + Xcode build smoke + demo sample EPUB spot-checks |
| **Config file** | none — current repo has no dedicated XCTest target for this phase |
| **Quick run command** | `test -f .planning/phases/02-typesetter-css/02-RESEARCH.md && test -f .planning/phases/02-typesetter-css/02-PATTERNS.md` |
| **Full suite command** | `xcodebuild -workspace ReadViewDemo/ReadViewDemo.xcworkspace -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 15' build` |
| **Estimated runtime** | ~60-90 seconds |
---
## Sampling Rate
- **After every task commit:** Run the matching `rg` assertions for the touched plan artifact and confirm the intended source files still name the new pipeline entry points.
- **After every plan wave:** Run `xcodebuild -workspace ReadViewDemo/ReadViewDemo.xcworkspace -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 15' build`.
- **Before `$gsd-verify-work`:** Build smoke must be green, the three phase docs must exist, and sample EPUBs must still load in the demo app.
- **Max feedback latency:** 90 seconds
---
## Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| 02-01-01 | 01 | 1 | REND-02 | — | CSS layering helper has explicit `default / replace / dark / epub / user` precedence and chapter preprocessing hooks | source | `rg -n "default|replace|dark|epub|user|baseURL|preprocess|stylesheet" Sources/RDReaderView/EPUBTextRendering` | ❌ W0 | ⬜ pending |
| 02-02-01 | 02 | 2 | REND-01, REND-02 | — | `RDEPUBDTCoreTextRenderer` consumes chapter context instead of a flat option bag, and still preserves `RDEPUBTextBookBuilder` chapter boundaries | source | `rg -n "RDEPUBDTCoreTextRenderer|RDEPUBTextBookBuilder|chapter context|stylesheet|DTHTMLAttributedStringBuilder" Sources/RDReaderView/EPUBTextRendering Sources/RDReaderView/EPUBUI` | ❌ W0 | ⬜ pending |
| 02-03-01 | 03 | 3 | REND-01, REND-02 | — | Sample EPUBs resolve chapter CSS/image/baseURL references under the new pipeline | smoke/manual | `rg -n "resource|baseURL|css|image|stylesheet" .planning/phases/02-typesetter-css/02-03-PLAN.md` | ❌ W0 | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
- [ ] No new test target required for Phase 2 planning work.
- [ ] Executor must confirm `ReadViewDemo/ReadViewDemo.xcworkspace` and `ReadViewDemo` scheme still open/build before closing the phase.
- [ ] Sample books in `ReadViewDemo/ReadViewDemo/book/` remain the regression corpus for manual spot-checks.
---
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| CSS layer precedence is correct in actual rendered chapters | REND-02 | Must compare visible output, not just source strings | Open a sample reflowable EPUB and confirm theme/user/publication styles apply in the intended order. |
| Relative CSS/image/baseURL resolution is intact | REND-01, REND-02 | Resource loading failures are easiest to see in the demo app | Open EPUBs with images and linked CSS and verify they still render after preprocessing. |
| WebView-only style helpers were not repurposed | REND-01, REND-02 | This is a boundary judgement, not a unit assertion | Inspect the final renderer wiring and confirm `RDEPUBStyleSheetBuilder` remains on the WebView path only. |
---
## Validation Sign-Off
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags
- [ ] Feedback latency < 90s
- [ ] `nyquist_compliant: true` set in frontmatter
**Approval:** pending
@@ -0,0 +1,70 @@
---
phase: 02-typesetter-css
verified: 2026-05-22T04:14:30Z
status: passed
score: 3/3 must-haves verified
---
# Phase 02: Typesetter CSS Verification Report
**Phase Goal:** 在旧引擎 native reflowable 路径中建立 WXRead 风格的 chapter preprocessing / CSS 分层输入,并验证真实 EPUB 资源解析没有断裂。
**Verified:** 2026-05-22T04:14:30Z
**Status:** passed
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | reflowable renderer 的章节输入不再只是 DTCoreText option bag | ✓ VERIFIED | [RDEPUBTextRenderer.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift) 新增 `RDEPUBTextChapterContext` / `RDEPUBTextChapterRenderRequest``RDEPUBTextBookBuilder` 在 chapter boundary 组装 request。 |
| 2 | CSS 五层顺序 `default / replace / dark / epub / user` 在 native path 中显式落地 | ✓ VERIFIED | [RDEPUBTextRendererSupport.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift) 的 `makeStyleSheetLayers` 固定了分层顺序,并通过 `injectStyleTag` 注入到 chapter HTML。 |
| 3 | 章节级 baseURL、linked CSS、图片资源在真实样本验证中可解析 | ✓ VERIFIED | [RDEPUBResourceResolver.swift](/Users/shen/Work/Code/ReadViewSDK/Sources/RDReaderView/EPUBCore/RDEPUBResourceResolver.swift) 扩展了 chapter-relative 标准化;demo runtime log 记录 `[ReadViewDemo] EPUB 资源验证:2/2 通过`。 |
**Score:** 3/3 truths verified
### Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `.planning/phases/02-typesetter-css/02-01-SUMMARY.md` | preprocessing / stylesheet layering 执行总结 | ✓ EXISTS + SUBSTANTIVE | 记录 native stylesheet pipeline 与关键决策。 |
| `.planning/phases/02-typesetter-css/02-02-SUMMARY.md` | renderer wiring 执行总结 | ✓ EXISTS + SUBSTANTIVE | 记录 request-based renderer contract 与 resolver 变更。 |
| `.planning/phases/02-typesetter-css/02-03-SUMMARY.md` | sample EPUB validation 总结 | ✓ EXISTS + SUBSTANTIVE | 记录 demo corpus 验证与运行时结果。 |
**Artifacts:** 3/3 verified
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|----|--------|---------|
| `RDEPUBTextBookBuilder` | `RDEPUBDTCoreTextRenderer` | chapter render request | ✓ WIRED | builder 组装 `RDEPUBTextChapterRenderRequest` 并透传给 renderer。 |
| stylesheet preprocessing | resource normalization | `RDEPUBResourceResolver` | ✓ WIRED | linked CSS 与 image diagnostics 使用 chapter-relative resolver API。 |
| demo validation | real sample corpus | `ReadViewDemo/ReadViewDemo/book/` | ✓ WIRED | demo 启动时后台扫描 EPUB corpus,并输出 `2/2` 通过结果。 |
**Wiring:** 3/3 connections verified
## Requirements Coverage
| Requirement | Status | Notes |
|-------------|--------|-------|
| REND-02 | ✓ SATISFIED | CSS 五层分层已在 native renderer 入口落地。 |
| REND-01 | ◐ PARTIAL | 本阶段完成 renderer 输入与资源语义重构;页面级元数据与复杂分页能力留给 Phase 3。 |
## Human Verification Required
- 若需要视觉确认具体某本书的版式细节,仍建议在 simulator 中手工打开对应 EPUB 做 spot-check。
## Gaps Summary
**No blocking gaps found.** Phase 2 goal achieved. Ready for Phase 3 planning.
## Verification Metadata
**Verification approach:** code inspection + simulator build/run + sample corpus log verification
**Automated checks:** `rg` contract checks, simulator build, demo runtime validation summary
**Human checks required:** optional visual spot-check only
**Total verification time:** ~8 min
---
*Verified: 2026-05-22T04:14:30Z*
*Verifier: inline executor*
@@ -0,0 +1,105 @@
---
phase: 03-page-metadata-pagination
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift
autonomous: true
requirements:
- REND-03
user_setup: []
must_haves:
truths:
- `RDEPUBTextPage` / `RDEPUBTextChapter` gain explicit page metadata hooks for blocks, attachments, and page-edge reasoning.
- `pageStartOffset`, `pageEndOffset`, chapter text continuity, and fragment offsets remain first-class compatibility fields.
- new metadata is serializable or structurally accessible from current native rendering models rather than hidden inside temporary layout locals.
artifacts:
- .planning/phases/03-page-metadata-pagination/03-01-SUMMARY.md
key_links:
- `RDEPUBTextOffsetRangeInfo` remains compatible with absolute chapter offsets.
- `RDEPUBTextBookBuilder` stays the owner of chapter/page assembly.
---
<objective>
定义页面级 attributed string 元数据与自定义属性键,为 Phase 3 的复杂分页器建立稳定数据契约。
Purpose: 先把“页面语义到底长什么样”固定下来,再让 paginator 和验证围绕这套契约扩展,而不是在分页逻辑里临时发明结构。
Output: richer page/chapter metadata models、attribute key definitions,以及保持 offset continuity 的序列化/传递路径。
</objective>
<execution_context>
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
@$HOME/.codex/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/REQUIREMENTS.md
@.planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md
@.planning/phases/02-typesetter-css/02-RESEARCH.md
@.planning/phases/03-page-metadata-pagination/03-RESEARCH.md
@.planning/phases/03-page-metadata-pagination/03-PATTERNS.md
@Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift
@Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift
</context>
<tasks>
<task type="auto">
<name>Task 1: 定义页面元数据与 attributed string 属性键</name>
<files>Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift</files>
<read_first>Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift, Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift</read_first>
<action>在现有 native text 模型旁定义 page metadata 契约和 attributed string 语义键,至少覆盖 block boundary、attachment/image semantic、page break reason、future page style hook 这几类能力;把它们放在 `RDEPUBReadingModels.swift` 或与之紧邻的 native rendering 模型里,并保持 `RDEPUBTextOffsetRangeInfo` 继续基于绝对 chapter offsets 工作。不要删除或弱化 `pageStartOffset``pageEndOffset``fragmentOffsets``href` 等现有兼容字段。</action>
<acceptance_criteria>
- `Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift` contains new page metadata types or fields for block / attachment / page break semantics
- `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift` or adjacent rendering models contain explicit native metadata/attribute-key definitions
- `RDEPUBTextOffsetRangeInfo` remains present and still serializes absolute offset ranges
</acceptance_criteria>
<verify>rg -n "metadata|attachment|block|pageBreak|pageStartOffset|pageEndOffset|fragmentOffsets" Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift</verify>
<done>native text pipeline now has an explicit metadata vocabulary that later pagination and validation steps can consume.</done>
</task>
<task type="auto">
<name>Task 2: 扩展 chapter/page model 以承接新语义但保留 offset continuity</name>
<files>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift</files>
<read_first>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift, Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift</read_first>
<action>把 `RDEPUBTextChapter``RDEPUBTextPage` 扩展成可承接新 metadata 的模型,同时保留 `RDEPUBTextBook.pageNumber(for:)``location(forPageNumber:)`、search/highlight 所依赖的 offset continuity。若需要新增 page-slice carrier(例如 layout snapshot 或 page metadata payload),它必须最终落回 `RDEPUBTextPage`,不能绕开现有 `RDEPUBTextBook` 契约。</action>
<acceptance_criteria>
- `RDEPUBTextPage` includes explicit metadata payloads or fields beyond bare range/offsets
- `RDEPUBTextBook.pageNumber(for:)` and `location(forPageNumber:)` still use offset-compatible fields
- source code still contains `pageStartOffset`, `pageEndOffset`, and `fragmentOffsets` after the refactor
</acceptance_criteria>
<verify>rg -n "struct RDEPUBTextPage|struct RDEPUBTextChapter|pageStartOffset|pageEndOffset|fragmentOffsets|metadata" Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift</verify>
<done>the page model is richer, but all current consumers still have a stable offset-based contract to target.</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `rg -n "metadata|attachment|block|pageBreak" Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift Sources/RDReaderView/EPUBTextRendering`
- [ ] `rg -n "pageStartOffset|pageEndOffset|fragmentOffsets" Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift`
- [ ] no changes route page presentation around `RDEPUBTextBook`
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- No compatibility field needed by highlight/search/navigation is removed
- Phase 3.2 can consume the metadata contract without redefining the page model again
</success_criteria>
<output>
After completion, create `.planning/phases/03-page-metadata-pagination/03-01-SUMMARY.md`
</output>
@@ -0,0 +1,20 @@
# 03-01 Summary
## Outcome
Wave 1 landed the page metadata contract without breaking the existing offset-based book model.
- Added `RDEPUBTextPageBreakReason`, `RDEPUBTextAttachmentKind`, and `RDEPUBTextPageMetadata` in `RDEPUBReadingModels.swift`
- Added attributed-string keys in `RDEPUBTextRenderer.swift` for block range/index, fragment id, and attachment kind
- Extended `RDEPUBTextPage` with `metadata`
- Extended `RDEPUBTextChapter` with `pageBreakReasons`
- Tagged normalized attributed content in `RDEPUBTextRendererSupport.normalizeReadingAttributes` so later pagination can inspect block and attachment semantics
## Compatibility
- `pageStartOffset`, `pageEndOffset`, `contentRange`, `fragmentOffsets`, and chapter text continuity remain intact
- Existing `RDEPUBTextBook` page lookup and location mapping continue to operate on absolute chapter offsets
## Notes
This wave deliberately extended the current book contract instead of replacing it. That kept highlight/search/navigation consumers stable while creating a place to land richer pagination semantics in Wave 2.
@@ -0,0 +1,106 @@
---
phase: 03-page-metadata-pagination
plan: 02
type: execute
wave: 2
depends_on:
- "03-01"
files_modified:
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextLayouter.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextLayoutFrame.swift
autonomous: true
requirements:
- REND-03
- REND-04
user_setup: []
must_haves:
truths:
- pagination is no longer implemented as only `CTFrameGetVisibleStringRange` range slicing.
- layouter/layout-frame responsibilities live inside `EPUBTextRendering`, not in reader UI classes.
- stronger page edges still resolve to stable `RDEPUBTextPage` offsets and chapter continuity.
artifacts:
- .planning/phases/03-page-metadata-pagination/03-02-SUMMARY.md
key_links:
- `RDEPUBTextPaginationSupport` remains the entry point for native pagination work.
- `RDEPUBTextBookBuilder` still assembles final pages and chapters.
---
<objective>
在旧引擎基础上重构分页器,引入内部 layouter / layout-frame 语义,并把纯 visible-range slicing 升级为更强的页面边界控制。
Purpose: 让 native paginator 能表达 block / image / attachment 的页面边界语义,同时不破坏现有 page model 与 chapter offset continuity。
Output: stronger paginator implementation, internal layouter/frame types, and book-builder wiring onto the new pagination path.
</objective>
<execution_context>
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
@$HOME/.codex/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/REQUIREMENTS.md
@.planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md
@.planning/phases/03-page-metadata-pagination/03-RESEARCH.md
@.planning/phases/03-page-metadata-pagination/03-PATTERNS.md
@.planning/phases/03-page-metadata-pagination/03-01-PLAN.md
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift
@Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift
</context>
<tasks>
<task type="auto">
<name>Task 1: 引入 layouter / layout-frame 语义并升级分页入口</name>
<files>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextLayouter.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextLayoutFrame.swift</files>
<read_first>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md</read_first>
<action>把当前 `ss_pageRanges(size:)` 的纯 range slicing 入口升级成内部 layouter / layout-frame 驱动的分页路径;新路径至少要能表达 page break reason、block boundary avoidance、image/attachment boundary handling 这三类语义。新增类型应放在 `EPUBTextRendering` 内部,例如 `RDEPUBTextLayouter` / `RDEPUBTextLayoutFrame`,并由 `RDEPUBTextPaginationSupport` 调用,而不是把复杂分页逻辑堆到 UI controller 里。</action>
<acceptance_criteria>
- `Sources/RDReaderView/EPUBTextRendering` contains layouter/frame types or equivalent internal pagination structures
- paginator code contains semantics beyond plain `CTFrameGetVisibleStringRange` slicing
- source tree does not move pagination logic into `RDEPUBReaderController` or `RDEPUBTextContentView`
</acceptance_criteria>
<verify>rg -n "Layouter|LayoutFrame|pageBreak|attachment|avoid|CTFrameGetVisibleStringRange|CTTypesetter" Sources/RDReaderView/EPUBTextRendering</verify>
<done>native pagination now has a real internal layout vocabulary rather than one visible-range helper.</done>
</task>
<task type="auto">
<name>Task 2: 把更强分页结果接回 `RDEPUBTextBookBuilder` 与现有页模型</name>
<files>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift</files>
<read_first>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift, Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift</read_first>
<action>让 `RDEPUBTextBookBuilder` 消费新分页结果并把 page-level metadata 落到 `RDEPUBTextPage` / `RDEPUBTextChapter` 上,同时保持 `pageStartOffset` / `pageEndOffset`、chapter order、fragmentOffsets 和 search/highlight overlap 逻辑不变。需要时可以在 builder 内转换 layouter output,但最终输出仍必须是当前 `RDEPUBTextBook` 契约,而不是新的 UI-only page list。</action>
<acceptance_criteria>
- `RDEPUBTextBookBuilder` constructs pages from the upgraded pagination output rather than raw `ss_pageRanges(size:)` only
- page model assembly still emits `RDEPUBTextBook(chapters:pages:)`
- search/highlight/navigation dependent fields remain present in the built pages
</acceptance_criteria>
<verify>rg -n "RDEPUBTextBook\\(|pageStartOffset|pageEndOffset|fragmentOffsets|metadata|layout" Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift</verify>
<done>the richer paginator is wired back into the existing native book contract and remains consumable by later reader phases.</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `test -f Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift`
- [ ] `rg -n "Layouter|LayoutFrame|pageBreak|attachment|avoid" Sources/RDReaderView/EPUBTextRendering`
- [ ] `rg -n "RDEPUBTextBook\\(|pageStartOffset|pageEndOffset|fragmentOffsets" Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift`
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- paginator semantics are meaningfully stronger than pure visible-range slicing
- output still flows through the existing native text book/page model
</success_criteria>
<output>
After completion, create `.planning/phases/03-page-metadata-pagination/03-02-SUMMARY.md`
</output>
@@ -0,0 +1,21 @@
# 03-02 Summary
## Outcome
Wave 2 replaced the range-only pagination path with an internal layouter/frame vocabulary inside `EPUBTextRendering`.
- Added `RDEPUBTextLayouter.swift`
- Added `RDEPUBTextLayoutFrame.swift`
- Updated `RDEPUBTextPaginationSupport.swift` so `ss_pageRanges(size:)` now delegates to `rd_paginatedFrames(size:fragmentOffsets:)`
- Updated `RDEPUBTextBookBuilder` and `RDPlainTextBookBuilder` to build pages from layout frames instead of bare `NSRange` slices
## New Semantics
- Page breaks now carry one of `chapterEnd`, `frameLimit`, `blockBoundary`, or `attachmentBoundary`
- Layout frames capture block-range context, attachment ranges/kinds, trailing fragment id, and page-edge diagnostics
- The layouter still uses CoreText measurement, but page boundaries are adjusted with block/attachment-aware rules before serialization back into `RDEPUBTextPage`
## Compatibility
- Final output is still `RDEPUBTextBook(chapters: pages:)`
- `pageStartOffset` / `pageEndOffset` remain the compatibility key for search, highlights, and later reader reintegration
@@ -0,0 +1,106 @@
---
phase: 03-page-metadata-pagination
plan: 03
type: execute
wave: 3
depends_on:
- "03-02"
files_modified:
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift
- Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift
- ReadViewDemo/ReadViewDemo/ViewController.swift
autonomous: true
requirements:
- REND-03
- REND-04
user_setup: []
must_haves:
truths:
- sample EPUBs with images or large blocks can prove stronger page-edge semantics through real diagnostics, not only visual inspection.
- offset-based highlight/search compatibility survives the stronger paginator.
- the current demo app remains the validation entry point.
artifacts:
- .planning/phases/03-page-metadata-pagination/03-03-SUMMARY.md
key_links:
- `ReadViewDemo/ReadViewDemo/book/` remains the regression corpus.
- `RDEPUBTextContentView` still derives overlaps from `pageStartOffset` / `pageEndOffset`.
---
<objective>
用真实样本验证复杂块元素、图片与分页边界控制在新分页器下可工作,并确认 offset-compatible highlight/search 行为没有被破坏。
Purpose: 证明 Phase 3 的 richer metadata 和 stronger paginator 不只是结构升级,而是能在真实 EPUB 上给出可解释、可回归的分页结果。
Output: demo-visible diagnostics, sample-book spot-check path, and source assertions around metadata + offset compatibility.
</objective>
<execution_context>
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
@$HOME/.codex/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/03-page-metadata-pagination/03-RESEARCH.md
@.planning/phases/03-page-metadata-pagination/03-PATTERNS.md
@.planning/phases/03-page-metadata-pagination/03-01-PLAN.md
@.planning/phases/03-page-metadata-pagination/03-02-PLAN.md
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift
@Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift
@ReadViewDemo/ReadViewDemo/ViewController.swift
@ReadViewDemo/ReadViewDemo/book/
</context>
<tasks>
<task type="auto">
<name>Task 1: 对真实 EPUB 暴露分页诊断并验证图片/块元素边界</name>
<files>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift, ReadViewDemo/ReadViewDemo/ViewController.swift</files>
<read_first>Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift, ReadViewDemo/ReadViewDemo/ViewController.swift, ReadViewDemo/ReadViewDemo/book/</read_first>
<action>基于现有 demo 样本书,为 stronger paginator 增加最小但可观察的分页诊断输出,至少能报告 page metadata / page break reason / image-or-block boundary evidence 中的一部分,并用 `ReadViewDemo/ReadViewDemo/book/` 的 reflowable EPUB 验证复杂块元素和图片没有回退到不可解释的纯 slicing 行为。不要新增并行 reader shell。</action>
<acceptance_criteria>
- demo or runtime logs expose pagination diagnostics tied to the new metadata or page-break semantics
- source tree still uses `ReadViewDemo/ReadViewDemo/ViewController.swift` as the validation entry point
- `ReadViewDemo/ReadViewDemo/book` remains the sample corpus referenced by the validation path
</acceptance_criteria>
<verify>rg -n "diagnostic|metadata|page break|attachment|image|block" Sources/RDReaderView/EPUBTextRendering ReadViewDemo/ReadViewDemo</verify>
<done>sample books can demonstrate that new pagination semantics are active and inspectable.</done>
</task>
<task type="auto">
<name>Task 2: 验证 offset-compatible 高亮/搜索页内映射没有退化</name>
<files>Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift</files>
<read_first>Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift, Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift</read_first>
<action>确认 stronger paginator 产出的 page metadata 仍能支持 `RDEPUBTextContentView` 的 highlight/search overlap 计算和 `RDEPUBTextOffsetRangeInfo` 的绝对 offset 语义;如果需要,可补充最小的 source-level guard 或 diagnostics,但不要把兼容修复扩展成 Phase 4 的 reader reintegration。重点是验证 page-local presentation 依然能从 absolute chapter offsets 正确切出相对范围。</action>
<acceptance_criteria>
- `RDEPUBTextContentView` still computes overlaps from `pageStartOffset` / `pageEndOffset`
- `RDEPUBTextOffsetRangeInfo` remains decodable and tied to absolute chapter offsets
- no code path replaces highlight/search overlap logic with page-local-only identifiers
</acceptance_criteria>
<verify>rg -n "pageStartOffset|pageEndOffset|RDEPUBTextOffsetRangeInfo|rangeInfo|searchState" Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift Sources/RDReaderView/EPUBTextRendering Sources/RDReaderView/EPUBCore</verify>
<done>Phase 3 finishes with stronger pagination semantics and intact offset-based presentation compatibility.</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `test -d ReadViewDemo/ReadViewDemo/book`
- [ ] `rg -n "diagnostic|metadata|page break|attachment|image|block" Sources/RDReaderView ReadViewDemo/ReadViewDemo`
- [ ] `rg -n "pageStartOffset|pageEndOffset|RDEPUBTextOffsetRangeInfo|rangeInfo" Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift Sources/RDReaderView/EPUBTextRendering Sources/RDReaderView/EPUBCore`
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- sample books prove stronger paginator behavior is both active and inspectable
- offset-based highlight/search presentation compatibility remains intact for Phase 4 handoff
</success_criteria>
<output>
After completion, create `.planning/phases/03-page-metadata-pagination/03-03-SUMMARY.md`
</output>
@@ -0,0 +1,19 @@
# 03-03 Summary
## Outcome
Wave 3 exposed real-book pagination diagnostics and confirmed that offset-compatible highlight/search presentation stayed intact.
- Added builder-level `RDEPUBTextChapterPaginationDiagnostic`
- `RDEPUBTextBookBuilder` now records `lastBuildPaginationDiagnostics`
- `ReadViewDemo/ViewController.swift` reports pagination evidence from sample EPUBs in UI/runtime logs
- `RDEPUBTextContentView.swift` still computes highlight/search overlap from absolute `pageStartOffset` / `pageEndOffset`, now via an explicit helper
## 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`
This confirms the stronger paginator is active on real EPUB content and not silently falling back to pure visible-range slicing.
@@ -0,0 +1,31 @@
# Phase 3: 重构属性体系与复杂分页器 - Pattern Map
## Goal
为 Phase 3 执行提供“先看哪里、怎么保持兼容”的最短路径。这个阶段的关键不是改 reader UI,而是扩展 page metadata 并把 paginator 从纯 range slicing 升级为可表达页面语义的内部 layouter。
## Planned Outputs
| Planned file | Role | Primary evidence | Why this is the right analog |
|--------------|------|------------------|------------------------------|
| `.planning/phases/03-page-metadata-pagination/03-01-PLAN.md` | 页面级 attributed string 元数据与属性键实现计划 | `Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift`, `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift`, `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift` | 这些文件共同定义 page/chapter contract、range info、fragment offsets 与 renderer output。 |
| `.planning/phases/03-page-metadata-pagination/03-02-PLAN.md` | 复杂分页器与 layouter/frame 重构计划 | `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift`, `RDEPUBTextBookBuilder.swift` | 当前所有分页都在这里;Phase 3 必须从这条路径升级,而不是另起 reader shell。 |
| `.planning/phases/03-page-metadata-pagination/03-03-PLAN.md` | 复杂块元素/图片分页验证计划 | `ReadViewDemo/ReadViewDemo/book/*.epub`, `ReadViewDemo/ReadViewDemo/ViewController.swift`, `RDEPUBTextContentView.swift` | 需要真实样本和现有 demo 入口来证明分页语义变强但 offset compatibility 没坏。 |
## Code Evidence Map
| Concern | Closest source of truth | Evidence to extract |
|---------|-------------------------|---------------------|
| 当前 page model 的边界 | `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift` | `RDEPUBTextPage` / `RDEPUBTextChapter` 当前只有 offsets、ranges、fragmentOffsets。 |
| 当前 paginator 的真实实现 | `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift` | `CTFramesetterCreateFrame` + `CTFrameGetVisibleStringRange` 的纯 slicing 逻辑。 |
| 位置/导航如何依赖 offsets | `Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift` | `pageIndex(for:)`、pending navigation、href/progression 到 page 的映射。 |
| 高亮/选区如何依赖 offsets | `Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift`, `Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift` | `RDEPUBTextOffsetRangeInfo``pageStartOffset/pageEndOffset` 的 overlap 计算。 |
| 搜索如何依赖 chapter text continuity | `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextSearchEngine.swift` | chapter-level attributed string 搜索 + rangeLocation/rangeLength。 |
| 复杂块元素/图片验证入口 | `ReadViewDemo/ReadViewDemo/book/`, `ReadViewDemo/ReadViewDemo/ViewController.swift` | 现有 demo 与样本 corpus 已经是可运行回归入口。 |
## Writing Guidance
- 计划必须把 “metadata first, paginator second, validation third” 的顺序写清楚,避免把所有变更压进一个 plan。
- 任何新分页语义都要同时写出 “增强点” 和 “必须保留的 offset invariant”。
- 如果引入内部 layouter / layout-frame 类型,职责要落在 `EPUBTextRendering` 内部,不要扩散到 `RDEPUBReaderController`
- 验证计划除了 demo/run,还要有源代码断言,确保 metadata 与 page-edge reason 真正落盘到模型里。
@@ -0,0 +1,144 @@
# Phase 3: 重构属性体系与复杂分页器 - Research
**Researched:** 2026-05-22
**Domain:** iOS EPUB native pagination / page metadata / CoreText layout semantics
**Confidence:** HIGH
<user_constraints>
## User Constraints
No `CONTEXT.md` exists for this phase. Planning is based on ROADMAP requirements, current code, Phase 1 strategy, and Phase 2 implementation artifacts only.
</user_constraints>
<architectural_responsibility_map>
## Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|------------|-------------|----------------|-----------|
| Page-level attributed string metadata | EPUBTextRendering | EPUBCore | Metadata must stay adjacent to chapter/page models so offsets, fragments, and attachments remain queryable. |
| Stronger pagination semantics | EPUBTextRendering | CoreText | `ss_pageRanges(size:)` is the only current paginator; Phase 3 must replace or wrap it with richer layout decisions. |
| Offset continuity for location/highlight/search | EPUBTextRendering | EPUBUI | `RDEPUBTextBook`, `RDEPUBTextContentView`, and `RDEPUBReadingSession` all assume stable chapter offsets. |
| Reader state + navigation orchestration | EPUBCore | EPUBUI | `RDEPUBReadingSession` should keep owning navigation/page lookup; it should consume richer page metadata rather than invent a new UI model. |
| Fixed / interactive EPUB rendering | EPUBCore | WebKit | Still out of scope; WebView paths remain unchanged. |
</architectural_responsibility_map>
<research_summary>
## Summary
Phase 2 solved the renderer input problem. The native reflowable path now has chapter-scoped preprocessing, explicit CSS precedence, and resolver-backed resource diagnostics. What remains weak is the page model and paginator. `RDEPUBTextPaginationSupport.ss_pageRanges(size:)` still slices the chapter with `CTFrameGetVisibleStringRange` and returns only bare `NSRange` values. `RDEPUBTextPage` then stores those ranges plus `pageStartOffset` / `pageEndOffset`, but no block metadata, no attachment semantics, no page-edge reasons, and no way to explain why a page boundary landed where it did.
That limitation is now the main blocker for `REND-03` and `REND-04`. Search, selection, highlights, `RDEPUBReadingSession.pageIndex(for:)`, and `RDEPUBTextContentView` all depend on stable offsets. Phase 3 therefore cannot replace `RDEPUBTextBook` with a new UI-only model. It has to extend the existing `RDEPUBTextChapter` / `RDEPUBTextPage` contract with richer metadata while preserving chapter text continuity and global offsets.
The safest implementation path is incremental:
1. Define page metadata and attribute keys first.
2. Introduce an internal layouter / layout-frame abstraction that can reason about blocks, attachments, and page edges while still returning offset-stable page slices.
3. Validate the new paginator on real EPUBs with large blocks and images before touching Phase 4 reader reintegration.
The repo already exposes the exact compatibility surface we need to preserve:
- `RDEPUBTextContentView` derives relative highlight/search ranges from `pageStartOffset` and `pageEndOffset`.
- `RDEPUBTextOffsetRangeInfo` serializes highlight ranges as absolute chapter offsets.
- `RDEPUBTextSearchEngine` indexes chapter-level attributed strings and stores match ranges by chapter offset.
- `RDEPUBReadingSession` still resolves navigation by href + progression and maps that back onto active pages.
**Primary recommendation:** Build a richer internal pagination layer under `EPUBTextRendering` that emits extended `RDEPUBTextPage` metadata while keeping `pageStartOffset`, `pageEndOffset`, `fragmentOffsets`, and chapter text continuity intact. Do not move layout logic into `RDEPUBReaderController` or `RDEPUBTextContentView`.
</research_summary>
<standard_stack>
## Standard Stack
### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| Foundation | System | Metadata models, ranges, serialization, diagnostics | Existing page/highlight/search models are already Foundation-driven. |
| CoreText | System | Low-level line/frame measurement | Needed to upgrade beyond pure visible-range slicing. |
| UIKit | iOS 15+ SDK | Text page presentation and selection mapping | `RDEPUBTextContentView` still presents `NSAttributedString` pages and maps ranges back to offsets. |
| DTCoreText | 1.6.28 | HTML-to-attributed-string import | Phase 3 builds on imported attributed content rather than replacing the typesetter. |
### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| ZIPFoundation | 0.9.20 | EPUB archive access | Real-book validation still depends on sample EPUB extraction. |
| DTFoundation | 1.7.19 | DTCoreText support | Attachment and HTML import remain rooted here. |
### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| Extend `RDEPUBTextPage` / `RDEPUBTextChapter` | Create a brand-new page tree model | Would break current reader/highlight/search callers too early and enlarge Phase 4 scope. |
| Introduce layouter/frame types under `EPUBTextRendering` | Keep growing `ss_pageRanges(size:)` as a single function | Hard to express page-edge reasons, block avoidance rules, and attachment metadata in one range-only helper. |
| Preserve chapter offsets as the compatibility key | Shift to page-local only locations | Would break selection/highlight persistence and make Phase 4 much harder. |
</standard_stack>
<architecture_patterns>
## Architecture Patterns
### Pattern 1: Extend, don't replace, the book contract
**What:** Add metadata to `RDEPUBTextPage` / `RDEPUBTextChapter` while preserving existing offset fields.
**When to use:** When downstream readers already depend on `href + offsets + pages`.
**Why:** Highlights, search matches, and navigation already serialize offsets and chapter hrefs.
### Pattern 2: Separate layouter from page serialization
**What:** Use internal layouter/layout-frame types to compute page boundaries, then convert those results into `RDEPUBTextPage`.
**When to use:** When pagination logic is becoming more complex than plain visible-range slicing.
**Why:** Keeps `RDEPUBTextBookBuilder` as the chapter/page assembly boundary without forcing it to own all layout heuristics inline.
### Pattern 3: Preserve chapter-level continuity as the invariant
**What:** Every new metadata structure should be traceable back to absolute chapter offsets.
**When to use:** For block edges, attachments, fragment anchors, and page break reasons.
**Why:** Current location/highlight/search behavior uses absolute chapter ranges; Phase 3 should enrich that graph, not replace it.
### Anti-Patterns to Avoid
- Rewriting `RDReaderView` or page presentation controls in the name of pagination.
- Introducing metadata that cannot be mapped back to `pageStartOffset` / `pageEndOffset`.
- Letting validation rely only on “looks okay” manual checks without capturing page-edge or attachment evidence.
</architecture_patterns>
<dont_hand_roll>
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Reader reintegration | A new reader shell | Existing `RDEPUBTextBook` / `RDEPUBReadingSession` contract | Phase 4 needs continuity, not a second UI model. |
| Location persistence | Page-local ephemeral IDs only | Absolute chapter offsets + fragment compatibility | Current highlights and search results already use offset-based range info. |
| Validation | Purely visual checks with no metadata evidence | Source assertions plus sample EPUB diagnostics | Complex pagination bugs need explainable metadata, not only screenshots. |
</dont_hand_roll>
<common_pitfalls>
## Common Pitfalls
### Pitfall 1: Richer metadata that doesn't survive page serialization
If metadata lives only in temporary layout structures and never lands on `RDEPUBTextPage`, Phase 4 cannot consume it.
### Pitfall 2: Better page edges but broken offset continuity
If page boundaries improve but `pageStartOffset` / `pageEndOffset` drift from chapter content, highlights and search navigation regress immediately.
### Pitfall 3: Attachment-aware pagination without validation hooks
Image/attachment rules are hard to trust unless the phase leaves behind enough metadata or demo diagnostics to confirm what happened on a given page boundary.
</common_pitfalls>
<sota_updates>
## State of the Art (2024-2025)
| Old Approach | Current Approach | Impact |
|--------------|------------------|--------|
| `CTFrameGetVisibleStringRange` only | Layout pipeline with page-edge semantics and attachment-aware metadata | Needed to express avoid-break rules and richer page reasoning. |
| Range-only page model | Page model with semantic metadata and diagnostics | Needed for later reader reintegration and regression debugging. |
| Paginator hidden inside a helper | Layouter/frame responsibilities separated from book assembly | Improves testability and makes pagination regressions explainable. |
</sota_updates>
<open_questions>
## Open Questions
- Which page metadata belongs directly on `RDEPUBTextPage` versus a nested layout-metadata payload?
- Should attachment/block metadata be encoded as custom `NSAttributedString.Key` values, separate page structs, or both?
- How much of the new layouter/frame API should remain internal to `EPUBTextRendering` versus surfaced through `RDEPUBTextBookBuilder`?
- Which sample EPUBs in `ReadViewDemo/ReadViewDemo/book/` best expose large-block and image boundary cases for repeatable validation?
</open_questions>
@@ -0,0 +1,63 @@
---
phase: 3
slug: page-metadata-pagination
status: draft
nyquist_compliant: true
wave_0_complete: false
created: 2026-05-22
---
# Phase 3 — Validation Strategy
> Per-phase validation contract for page metadata and paginator-semantic work.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | shell assertions + Xcode build smoke + demo EPUB diagnostics |
| **Config file** | none — repo still has no dedicated XCTest target for this phase |
| **Quick run command** | `test -f .planning/phases/03-page-metadata-pagination/03-RESEARCH.md && test -f .planning/phases/03-page-metadata-pagination/03-PATTERNS.md` |
| **Full suite command** | `xcodebuild -workspace ReadViewDemo/ReadViewDemo.xcworkspace -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 17' build` |
| **Estimated runtime** | ~60-90 seconds |
## Sampling Rate
- **After every task commit:** Run the tasks `rg` assertions against touched rendering/pagination files.
- **After every wave:** Run the `ReadViewDemo` simulator build smoke.
- **Before `$gsd-verify-work`:** Demo sample EPUB diagnostics must still pass, and Phase 3 plans/summaries must exist.
- **Max feedback latency:** 90 seconds
## Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Secure Behavior | Test Type | Automated Command | Status |
|---------|------|------|-------------|-----------------|-----------|-------------------|--------|
| 03-01-01 | 01 | 1 | REND-03 | Page/chapter models expose richer metadata without dropping offset continuity | source | `rg -n "metadata|attachment|block|pageStartOffset|pageEndOffset|fragmentOffsets" Sources/RDReaderView/EPUBTextRendering Sources/RDReaderView/EPUBCore` | ⬜ pending |
| 03-02-01 | 02 | 2 | REND-04 | Paginator no longer relies on pure visible-range slicing and introduces layouter/frame semantics | source | `rg -n "Layouter|LayoutFrame|pageBreak|avoid|attachment|CTTypesetter|CTFramesetter" Sources/RDReaderView/EPUBTextRendering` | ⬜ pending |
| 03-03-01 | 03 | 3 | REND-03, REND-04 | Sample EPUBs and page diagnostics prove large blocks/images keep offset-compatible pagination | smoke/source | `rg -n "diagnostic|metadata|page break|attachment|image|block" Sources/RDReaderView ReadViewDemo/ReadViewDemo` | ⬜ pending |
## Wave 0 Requirements
- [ ] `ReadViewDemo/ReadViewDemo.xcworkspace` and `ReadViewDemo` scheme still build before close-out.
- [ ] Validation continues to use `ReadViewDemo/ReadViewDemo/book/` as the regression corpus.
- [ ] No plan may remove `pageStartOffset`, `pageEndOffset`, or fragment offset compatibility.
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Large blocks and images break at better boundaries | REND-04 | Visual edge quality still needs a human read-through | Open a sample EPUB with images and long paragraphs; confirm boundaries are improved versus old slicing. |
| Highlight/search navigation still lands on the correct page after repagination | REND-03, REND-04 | Requires end-to-end interaction with existing reader flow | Use the demo reader to navigate to saved ranges or search matches after pagination changes. |
## Validation Sign-Off
- [ ] All tasks have automated verification commands
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers offset-compatibility invariants
- [ ] No watch-mode flags
- [ ] Feedback latency < 90s
- [ ] `nyquist_compliant: true` set in frontmatter
**Approval:** pending
@@ -0,0 +1,31 @@
# Phase 3 Verification
## Commands
- `pod install` in `ReadViewDemo/`
- `build_sim` for scheme `ReadViewDemo`
- `build_run_sim` for scheme `ReadViewDemo`
- `rg -n "diagnostic|metadata|page break|attachment|image|block|pageStartOffset|pageEndOffset|RDEPUBTextOffsetRangeInfo|rangeInfo" Sources/RDReaderView ReadViewDemo/ReadViewDemo -S`
- `test -d ReadViewDemo/ReadViewDemo/book`
## Results
- Build succeeded on iOS Simulator
- App launch succeeded on simulator `iPhone 17`
- Sample corpus directory exists: `ReadViewDemo/ReadViewDemo/book`
- Source verification confirms:
- internal layouter/layout-frame types exist under `EPUBTextRendering`
- paginator now emits page-break semantics and attachment/block diagnostics
- `RDEPUBTextContentView` still uses `pageStartOffset` / `pageEndOffset`
- `RDEPUBTextOffsetRangeInfo` remains the absolute-offset persistence format
## 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`
## Residual Risk
- The layouter heuristics are intentionally incremental, not a full WXRead-equivalent paginator. Phase 4 should validate the stronger page model against live reader navigation/highlight/search flows, and Phase 5 should broaden corpus coverage.
@@ -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
@@ -0,0 +1,84 @@
---
phase: 05-regression-stability
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- ReadViewDemo/ReadViewDemo/ViewController.swift
- .planning/phases/05-regression-stability/05-VALIDATION.md
- .planning/phases/05-regression-stability/05-RESEARCH.md
autonomous: true
requirements:
- STAB-01
user_setup: []
must_haves:
truths:
- Phase 5 needs an explicit sample matrix, not only ad hoc simulator runs.
- demo/runtime diagnostics are the primary validation entry point.
- the matrix must account for native reflowable, fixed/interactive, and TXT-compatible paths.
artifacts:
- .planning/phases/05-regression-stability/05-01-SUMMARY.md
key_links:
- `ReadViewDemo/ReadViewDemo/book/`
- `ReadViewDemo/ReadViewDemo/ViewController.swift`
---
<objective>
构建样本书验证矩阵、诊断输出和复现清单,让 Phase 5 的回归验证有明确覆盖面和可重复证据。
Purpose: 先把“验证什么、用什么样本、看什么证据”固定下来,再进入稳定性收敛。
Output: documented sample matrix plus demo-visible or log-visible validation summary covering the required book categories.
</objective>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/05-regression-stability/05-RESEARCH.md
@.planning/phases/05-regression-stability/05-PATTERNS.md
@.planning/phases/04-reader-capabilities/04-VERIFICATION.md
@ReadViewDemo/ReadViewDemo/ViewController.swift
@ReadViewDemo/ReadViewDemo/book/
</context>
<tasks>
<task type="auto">
<name>Task 1: 定义样本矩阵与风险映射</name>
<files>.planning/phases/05-regression-stability/05-RESEARCH.md, .planning/phases/05-regression-stability/05-VALIDATION.md, ReadViewDemo/ReadViewDemo/ViewController.swift</files>
<read_first>.planning/phases/05-regression-stability/05-RESEARCH.md, ReadViewDemo/ReadViewDemo/ViewController.swift, ReadViewDemo/ReadViewDemo/book/</read_first>
<action>明确每本样本书对应的验证类别与风险映射,并在需要时把这份矩阵体现在 demo 诊断输出或 phase 文档中。矩阵至少覆盖:小说/长文本 reflowable、复杂块元素/图片 reflowable、fixed 或 interactive WebKit 路径,以及 TXT 路径(如当前缺样本,则需在执行期明确补充策略)。</action>
<acceptance_criteria>
- sample corpus is mapped to explicit regression categories
- Phase 5 artifacts state what evidence each sample is expected to provide
- demo remains the primary visible validation entry point
</acceptance_criteria>
<verify>find ReadViewDemo/ReadViewDemo/book -maxdepth 1 -type f | sort && rg -n "validation|diagnostic|sample|matrix|restore|fixed|reflowable|txt" ReadViewDemo/ReadViewDemo .planning/phases/05-regression-stability -S</verify>
<done>Phase 5 has an explicit validation matrix instead of an implicit sample list.</done>
</task>
<task type="auto">
<name>Task 2: 增强可重复诊断输出与复现清单</name>
<files>ReadViewDemo/ReadViewDemo/ViewController.swift, .planning/phases/05-regression-stability/05-VALIDATION.md</files>
<read_first>ReadViewDemo/ReadViewDemo/ViewController.swift, .planning/phases/04-reader-capabilities/04-VERIFICATION.md</read_first>
<action>在现有 demo summary 基础上补充最小但可重复的诊断输出或验证说明,使后续 Phase 5 回归结果能被再次运行并比对。不要引入新的测试壳;应复用当前 demo 和已有日志体系。</action>
<acceptance_criteria>
- runtime validation evidence is clearer than a generic success/failure line
- there is a written checklist or summary path for rerunning the same validations
- diagnostics remain lightweight and tied to existing sample books
</acceptance_criteria>
<verify>rg -n "validation|diagnostic|restore|pagination|search|highlight|fixed|resource" ReadViewDemo/ReadViewDemo/ViewController.swift .planning/phases/05-regression-stability/05-VALIDATION.md</verify>
<done>Phase 5 starts with a repeatable evidence-producing validation layer.</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `find ReadViewDemo/ReadViewDemo/book -maxdepth 1 -type f | sort`
- [ ] `rg -n "validation|diagnostic|sample|matrix|restore|fixed|reflowable|txt" ReadViewDemo/ReadViewDemo .planning/phases/05-regression-stability -S`
</verification>
<output>
After completion, create `.planning/phases/05-regression-stability/05-01-SUMMARY.md`
</output>
@@ -0,0 +1,21 @@
# 05-01 Summary
## Outcome
Wave 1 turned the sample corpus into an explicit regression matrix and made the demo startup log the primary repeatable proof surface.
- `ViewController` now validates every local `.epub` and `.txt` sample instead of only native reflowable EPUBs
- startup summary now groups books into `TXT/小说``复杂图文``Fixed/互动``TXT` categories
- a built-in TXT sample, `ReadViewDemo/ReadViewDemo/book/回归验证样本.txt`, now keeps the TXT path inside the repo instead of relying on an external file
## Evidence
Runtime log after simulator launch:
- `样本验证:5/5 通过`
- `矩阵[复杂图文] 2/2 · 宝山辽墓材料与释读(textReflowable)、张学良传(textReflowable)`
- `矩阵[Fixed/互动] 2/2 · 《凡人修仙传》精校版全本(webInteractive)、爱忘事的熊爷爷(webFixedLayout)`
- `矩阵[TXT] 1/1 · 回归验证样本(txt)`
The demo remains the only validation surface; no extra test shell was introduced.
@@ -0,0 +1,101 @@
---
phase: 05-regression-stability
plan: 02
type: execute
wave: 2
depends_on:
- "05-01"
files_modified:
- Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
- Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
- Sources/RDReaderView/EPUBCore/RDEPUBWebView.swift
- Sources/RDReaderView/EPUBCore/RDEPUBWebView+FixedLayout.swift
- Sources/RDReaderView/EPUBCore/RDEPUBResourceURLSchemeHandler.swift
- Sources/RDReaderView/RDURLReaderController.swift
- ReadViewDemo/ReadViewDemo/ViewController.swift
autonomous: true
requirements:
- STAB-02
user_setup: []
must_haves:
truths:
- Phase 5 is allowed to fix concrete regressions discovered by the validation matrix.
- fixes should be evidence-driven and narrow in scope.
- no new architecture should be introduced in a convergence phase.
artifacts:
- .planning/phases/05-regression-stability/05-02-SUMMARY.md
- .planning/phases/05-regression-stability/05-VERIFICATION.md
key_links:
- `RDEPUBReaderController`
- `RDEPUBTextBookBuilder`
- `RDEPUBWebView*`
- `RDURLReaderController`
---
<objective>
依据样本矩阵执行回归,定位并收敛分页、位置映射、图片/块元素分页、fixed/interactive 资源加载,以及主题切换后的稳定性问题。
Purpose: 用真实样本和 reader 主流程把 Phase 1-4 的改动压测到可交付稳定水平。
Output: regression fixes where needed, plus a verification artifact showing which main flows and sample categories passed.
</objective>
<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/05-regression-stability/05-RESEARCH.md
@.planning/phases/05-regression-stability/05-PATTERNS.md
@.planning/phases/05-regression-stability/05-VALIDATION.md
@.planning/phases/03-page-metadata-pagination/03-VERIFICATION.md
@.planning/phases/04-reader-capabilities/04-VERIFICATION.md
@Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@Sources/RDReaderView/EPUBCore/RDEPUBWebView.swift
@Sources/RDReaderView/EPUBCore/RDEPUBWebView+FixedLayout.swift
@Sources/RDReaderView/EPUBCore/RDEPUBResourceURLSchemeHandler.swift
@Sources/RDReaderView/RDURLReaderController.swift
@ReadViewDemo/ReadViewDemo/ViewController.swift
@ReadViewDemo/ReadViewDemo/book/
</context>
<tasks>
<task type="auto">
<name>Task 1: 按矩阵执行 reader 主流程回归并修复具体问题</name>
<files>Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/RDURLReaderController.swift, ReadViewDemo/ReadViewDemo/ViewController.swift</files>
<read_first>.planning/phases/05-regression-stability/05-VALIDATION.md, Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/RDURLReaderController.swift, ReadViewDemo/ReadViewDemo/ViewController.swift</read_first>
<action>基于样本矩阵执行 native reflowable 与 TXT 相关 reader 主流程验证,包括打开、位置恢复、搜索、highlight、TOC、theme/font repagination,并对发现的具体问题做最小修复。重点是收敛错误页、状态丢失、刷新后高亮/搜索不一致、TXT 入口回退等问题。</action>
<acceptance_criteria>
- key native/TXT reader flows have explicit pass evidence
- any fixes are traceable to a concrete regression found during validation
- no broad architectural refactor is introduced
</acceptance_criteria>
<verify>rg -n "search|highlight|location|restore|theme|font|bookmark|tableOfContents" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/RDURLReaderController.swift ReadViewDemo/ReadViewDemo/ViewController.swift</verify>
<done>native and TXT reader flows are regression-checked and stabilized.</done>
</task>
<task type="auto">
<name>Task 2: 回归 fixed / interactive WebKit 路径并收敛资源加载与展示问题</name>
<files>Sources/RDReaderView/EPUBCore/RDEPUBWebView.swift, Sources/RDReaderView/EPUBCore/RDEPUBWebView+FixedLayout.swift, Sources/RDReaderView/EPUBCore/RDEPUBResourceURLSchemeHandler.swift, ReadViewDemo/ReadViewDemo/ViewController.swift</files>
<read_first>Sources/RDReaderView/EPUBCore/RDEPUBWebView.swift, Sources/RDReaderView/EPUBCore/RDEPUBWebView+FixedLayout.swift, Sources/RDReaderView/EPUBCore/RDEPUBResourceURLSchemeHandler.swift, ReadViewDemo/ReadViewDemo/ViewController.swift</read_first>
<action>对 fixed / interactive EPUB 路径做回归验证,利用现有 `RDEPUBWebViewDebug` 与 demo/runtime evidence 检查资源加载、页面渲染、外链/内部链接与 search decoration 相关行为。若发现明显回归(白屏、加载失败、关键资源缺失、渲染未完成),做最小修复并把结果写入 verification artifact。</action>
<acceptance_criteria>
- fixed or interactive sample path has explicit runtime evidence
- resource loading and rendering regressions are either fixed or clearly surfaced in verification
- demo/runtime logs remain sufficient to inspect failures without adding a second validation shell
</acceptance_criteria>
<verify>rg -n "RDEPUBWebViewDebug|load fixed|ResourceScheme|rendered|didFail|didFinish|searchPresentation" Sources/RDReaderView/EPUBCore ReadViewDemo/ReadViewDemo -S</verify>
<done>WebKit-backed reader paths are included in the final regression closure, not treated as out-of-band.</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `find ReadViewDemo/ReadViewDemo/book -maxdepth 1 -type f | sort`
- [ ] `rg -n "search|highlight|location|restore|theme|font|bookmark|tableOfContents" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/RDURLReaderController.swift ReadViewDemo/ReadViewDemo/ViewController.swift`
- [ ] `rg -n "RDEPUBWebViewDebug|load fixed|ResourceScheme|rendered|didFail|didFinish|searchPresentation" Sources/RDReaderView/EPUBCore ReadViewDemo/ReadViewDemo -S`
</verification>
<output>
After completion, create `.planning/phases/05-regression-stability/05-02-SUMMARY.md` and `.planning/phases/05-regression-stability/05-VERIFICATION.md`
</output>
@@ -0,0 +1,16 @@
# 05-02 Summary
## Outcome
Wave 2 closed the remaining regression gap by exercising the new matrix and tightening the demo validator where it initially drifted from public APIs.
- fixed/interactive validation now resolves spine resources through public `RDEPUBResourceResolver` APIs and verifies fixed spreads with explicit `RDEPUBPreferences`
- TXT validation now builds a real `RDPlainTextBook` and records chapter/page evidence
- the existing native pagination and restore diagnostics remain active for reflowable books, so Phase 3-4 guarantees stay visible during Phase 5 runs
## Fixes
- removed demo-only calls to internal reader helpers (`makePreferences`, theme CSS accessors)
- removed the nonisolated `UIScreen.main` warning by passing the existing viewport size into fixed-layout validation
- kept the changes local to `ReadViewDemo/ReadViewDemo/ViewController.swift`; no new runtime architecture was introduced
@@ -0,0 +1,27 @@
# Phase 5: 回归验证与稳定性收敛 - Pattern Map
## Goal
把 Phase 5 约束成“样本矩阵 + reader 流程 + 收敛修复”的闭环,而不是泛泛地补测试。
## Planned Outputs
| Planned file | Role | Primary evidence | Why this is the right analog |
|--------------|------|------------------|------------------------------|
| `05-01-PLAN.md` | 构建样本矩阵、诊断手段和复现清单 | `ReadViewDemo/ViewController.swift`, `ReadViewDemo/book/`, `RDEPUBWebViewDebug`, prior verification artifacts | 这些是现有验证入口和日志基础。 |
| `05-02-PLAN.md` | 依据矩阵执行回归并收敛稳定性问题 | `RDEPUBReaderController`, `RDEPUBTextBookBuilder`, `RDEPUBWebView*`, `RDURLReaderController` | 这里汇聚了 Phase 1-4 改动后的主要风险表面。 |
## Code Evidence Map
| Concern | Closest source of truth | Evidence to extract |
|---------|-------------------------|---------------------|
| 样本入口与启动验证 | `ReadViewDemo/ReadViewDemo/ViewController.swift` | 已有 startup validation,可扩展成矩阵摘要。 |
| native reflowable 风险 | `RDEPUBReaderController`, `RDEPUBTextBookBuilder`, `RDEPUBTextContentView` | 搜索、高亮、repagination、位置恢复都集中在这里。 |
| fixed / interactive 风险 | `RDEPUBWebView`, `RDEPUBWebView+FixedLayout`, `RDEPUBResourceURLSchemeHandler` | 这些路径依赖 WebKit/resource loading,不同于 native text。 |
| TXT 支持风险 | `RDURLReaderController`, `RDPlainTextBookBuilder` | roadmap 要求 `.txt` 主流程不回归,但当前样本覆盖不足。 |
## Writing Guidance
- Phase 5 的计划必须明确区分“验证基础设施/矩阵”与“发现问题后的收敛修复”。
- 任何修复都应以回归证据驱动,不应重新打开更大范围的架构重构。
- 计划里要写清楚哪些样本书承担哪些验证职责。
@@ -0,0 +1,122 @@
# Phase 5: 回归验证与稳定性收敛 - Research
**Researched:** 2026-05-22
**Domain:** iOS EPUB/TXT regression validation / sample-corpus coverage / stability convergence
**Confidence:** HIGH
<user_constraints>
## User Constraints
No `CONTEXT.md` exists for this phase. Planning is based on roadmap requirements, the current codebase, and completed artifacts from Phases 1-4 only.
</user_constraints>
<research_summary>
## Summary
By the end of Phase 4, the codebase has meaningful diagnostics for both native text pagination and reader-state restore, but the validation surface is still uneven. Most current proof is either:
- source inspection (`rg`, architecture invariants)
- simulator build/run success
- demo startup logs from a small subset of sample books
That was enough to unblock development, but not enough to claim stability closure. Phase 5 therefore shifted from “implement capability” to “prove breadth and harden failures.”
The sample corpus used for execution now covers five local books:
- `《凡人修仙传》精校版全本.epub`
- `回归验证样本.txt`
- `宝山辽墓材料与释读.epub`
- `张学良传.epub`
- `爱忘事的熊爷爷.epub`
Observed behavior from Phases 3-4 shows these books are not equivalent:
- `宝山辽墓材料与释读.epub` exercises native reflowable pagination semantics and repagination restore checks
- `爱忘事的熊爷爷.epub` clearly drives fixed-layout / interactive-style WebKit resource flows in runtime logs
- `回归验证样本.txt` was added during execution so the TXT path is now part of the built-in demo corpus
The main Phase 5 planning implication is that validation has to be organized as a matrix, not a single “open the app and see it works” loop. At minimum the matrix must cover:
1. Native reflowable text with richer page metadata and repagination
2. Fixed layout / WebKit-based rendering and resource loading
3. TXT/native text book path through `RDURLReaderController`
4. Reader interaction flows: open, TOC jump, search navigation, highlight persistence, theme/font changes, and repeated repagination
The repo already has enough hooks to support this:
- `ReadViewDemo/ViewController.swift` reports sample validation summaries at startup
- `RDEPUBTextBookBuilder` records pagination/resource diagnostics
- `RDEPUBWebViewDebug` and resource-scheme logs expose WebKit/fixed-layout loading behavior
- `RDEPUBReaderController` centralizes repagination, restore, search, highlight, and viewport handling
What is missing is orchestration and closure:
- explicit mapping from sample book → risk category
- runtime assertions or summary output for reader flows beyond startup validation
- a disciplined path to take discovered regressions and either fix them or record them as resolved in the same phase
**Primary recommendation:** Plan Phase 5 as a validation-and-fix loop centered on a documented sample matrix and runtime evidence. The goal is not new architecture; it is repeatable proof that the main supported book categories and reader flows stay intact after the Phase 1-4 refactor.
Execution outcome:
- `ReadViewDemo` startup validation now emits matrix lines for complex reflowable, fixed/interactive WebKit, and TXT samples
- runtime evidence showed `样本验证:5/5 通过`
- the demo remains the single validation entry point; no secondary harness was introduced
</research_summary>
<code_evidence>
## Code Evidence
| Concern | Source | Why it matters |
|--------|--------|----------------|
| Demo validation entry point | `ReadViewDemo/ReadViewDemo/ViewController.swift` | Already emits startup resource/pagination/restore summaries; best place to extend verification. |
| Native text diagnostics | `RDEPUBTextBookBuilder`, `RDEPUBTextLayouter`, `RDEPUBTextRendererSupport` | Existing metadata can drive stronger regression assertions. |
| Reader flow behavior | `RDEPUBReaderController` | Open, search, selection, highlight, TOC, repagination, and state restore all converge here. |
| Web/fixed runtime logs | `RDEPUBWebViewDebug`, `RDEPUBWebView`, `RDEPUBWebView+FixedLayout`, `RDEPUBResourceURLSchemeHandler` | Needed to prove non-native paths did not regress. |
| TXT path | `RDURLReaderController`, `RDPlainTextBookBuilder` | Supported but currently underrepresented in the sample corpus. |
</code_evidence>
<sample_corpus>
## Current Sample Corpus
| Sample | Likely category | Current evidence |
|--------|------------------|------------------|
| `宝山辽墓材料与释读.epub` | Native reflowable, complex blocks/images | Used in Phase 3/4 pagination and restore diagnostics. |
| `《凡人修仙传》精校版全本.epub` | Interactive/WebKit path | Runtime summary now classifies it as `webInteractive` and verifies `spine 2470 · missing 0`. |
| `回归验证样本.txt` | TXT/native text path | Added in Phase 5 and validated through `RDPlainTextBookBuilder` with startup summary output. |
| `张学良传.epub` | Reflowable nonfiction / alternate structure | Runtime summary classifies it under complex reflowable. |
| `爱忘事的熊爷爷.epub` | Fixed-layout / interactive-style WebKit path | Runtime log shows extensive fixed/resource loading. |
</sample_corpus>
<architecture_patterns>
## Architecture Patterns
### Pattern 1: Validate by book category, not by implementation layer
**What:** Organize checks around sample types and user flows.
**Why:** Stability issues are user-visible regressions, not just module-level failures.
### Pattern 2: Prefer evidence-producing diagnostics over ad hoc manual notes
**What:** Runtime summaries, assertions, and structured logs should back regression claims.
**Why:** Repeatability matters more than one-off successful runs.
### Pattern 3: Couple regression detection with fix closure
**What:** The phase should allow targeted source fixes where diagnostics surface concrete failures.
**Why:** A pure audit phase without remediation would leave the roadmap unfinished.
### Anti-Patterns to Avoid
- Treating build success as sufficient evidence of reader stability.
- Adding broad new architecture during a convergence phase.
- Expanding validation without documenting which sample proves which risk category.
</architecture_patterns>
<open_questions>
## Open Questions
- Should Phase 5 add a dedicated TXT sample into the demo corpus, or rely on an external local text file during execution?
- How much of the reader interaction validation should be automated in code/logs versus performed manually on simulator?
- If a regression is found in fixed/interactive WebKit flows, should it be fixed within Phase 5 or only documented if the root cause is outside the current scope?
</open_questions>
@@ -0,0 +1,46 @@
# Phase 5: 回归验证与稳定性收敛 - Validation
## Required Checks
- 至少覆盖三类样本:纯文本/TXT 或小说型 reflowable、复杂块元素/图片 reflowable、fixed/interactive WebKit 路径
- `.epub``.txt` 的 reader 主流程都必须有明确验证路径
- 每个重要 reader 流程至少有一种可重复证据:日志、断言、摘要、或稳定的手动检查清单
## Executed Matrix
| Sample | Category | Evidence |
|--------|----------|----------|
| `宝山辽墓材料与释读.epub` | 复杂图文 / native reflowable | 启动摘要 + pagination/restore diagnostics |
| `张学良传.epub` | 复杂图文 / native reflowable | 启动摘要 + active native reader UI evidence |
| `《凡人修仙传》精校版全本.epub` | interactive / WebKit | 启动摘要 `webInteractive` + `missing 0` |
| `爱忘事的熊爷爷.epub` | fixed layout / WebKit | 启动摘要 `webFixedLayout` + `spread` validation |
| `回归验证样本.txt` | TXT | 启动摘要 `TXT 验证` + page/chapter counts |
## Verification Anchors
### Source-level
- `find ReadViewDemo/ReadViewDemo/book -maxdepth 1 -type f | sort`
- `rg -n "validation|diagnostic|restore|search|highlight|location|theme|font|fixed|resource" ReadViewDemo/ReadViewDemo Sources/RDReaderView -S`
- `rg -n "RDEPUBWebViewDebug|logSchemeTask|rendered|page break|restore" Sources/RDReaderView -S`
### Runtime / demo
- run `ReadViewDemo` on simulator
- capture startup validation summaries for every relevant sample category
- exercise at least one native reflowable flow with search/highlight/repagination
- exercise at least one fixed/interactive EPUB flow and confirm no loading/resource regression
- exercise a TXT reading flow if a sample exists or is added during execution
## Current Rerun Path
1. Launch `ReadViewDemo` on the configured simulator.
2. Read the startup summary from the on-screen status area or runtime log.
3. Confirm the matrix lines for `复杂图文``Fixed/互动``TXT`.
4. Open any listed sample and use the existing reader controls to recheck TOC, bookmark/highlight, and theme/font adjustments as needed.
## Completion Standard
Phase 5 planning is only good enough if each plan names:
1. which sample categories it covers
2. what evidence proves success
3. how discovered regressions are routed into concrete fixes
@@ -0,0 +1,47 @@
# Phase 5 Verification
## Commands
- `build_sim` for scheme `ReadViewDemo`
- `build_run_sim` for scheme `ReadViewDemo`
- `find ReadViewDemo/ReadViewDemo/book -maxdepth 1 -type f | sort`
- `rg -n "validation|diagnostic|sample|matrix|restore|fixed|reflowable|txt" ReadViewDemo/ReadViewDemo .planning/phases/05-regression-stability -S`
- `rg -n "search|highlight|location|restore|theme|font|bookmark|tableOfContents" Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift Sources/RDReaderView/RDURLReaderController.swift ReadViewDemo/ReadViewDemo/ViewController.swift`
- `rg -n "RDEPUBWebViewDebug|load fixed|ResourceScheme|rendered|didFail|didFinish|searchPresentation" Sources/RDReaderView/EPUBCore ReadViewDemo/ReadViewDemo -S`
- `snapshot_ui` on the running simulator
## Results
- Simulator build succeeded with no warnings
- Simulator launch succeeded
- Demo corpus now includes 5 verifiable books:
- `《凡人修仙传》精校版全本.epub`
- `回归验证样本.txt`
- `宝山辽墓材料与释读.epub`
- `张学良传.epub`
- `爱忘事的熊爷爷.epub`
- Source verification confirms:
- `ViewController` now validates `.txt`, native reflowable EPUB, and fixed/interactive EPUB in one pass
- native reader flows for search, highlight, location restore, bookmarks, TOC, and theme/font repagination remain centered in `RDEPUBReaderController`
- WebKit-backed flows still expose resource/render lifecycle evidence through `RDEPUBWebViewDebug` and `RDEPUBResourceURLSchemeHandler`
## Runtime Evidence
From the app runtime log:
- `样本验证:5/5 通过`
- `矩阵[复杂图文] 2/2 · 宝山辽墓材料与释读(textReflowable)、张学良传(textReflowable)`
- `矩阵[Fixed/互动] 2/2 · 《凡人修仙传》精校版全本(webInteractive)、爱忘事的熊爷爷(webFixedLayout)`
- `矩阵[TXT] 1/1 · 回归验证样本(txt)`
- `Web 路径验证:《凡人修仙传》精校版全本 · profile webInteractive · spine 2470 · missing 0`
- `Web 路径验证:爱忘事的熊爷爷 · profile webFixedLayout · spine 22 · missing 0`
From simulator UI inspection:
- the app was running inside `RDEPUBReaderController` with visible reader controls (`epub.reader.back`, `epub.reader.toc`, `epub.reader.bookmarks`, `epub.reader.highlights`, `epub.reader.settings`)
- the current native reflowable sample exposed page state (`2 / 691`) and standard reader chrome without a blank page or stalled render
## Residual Risk
- The phase now has corpus-level runtime evidence for TXT and WebKit paths, but not a fully automated UI walkthrough for opening every sample and driving every interaction on simulator
- Simulator runtime still emits pre-existing CoreText font substitution notes for `.SFUI-Semibold`; Phase 5 did not change font selection code and the app remained functional despite the warning