docs(phase-01): add planning artifacts

This commit is contained in:
shen 2026-05-21 22:00:41 +08:00
parent 0870996ed0
commit 6d196d64e5
5 changed files with 539 additions and 0 deletions

View File

@ -0,0 +1,102 @@
---
phase: 01-current-engine-boundaries
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md
autonomous: true
requirements:
- COMP-02
user_setup: []
must_haves:
truths:
- 审计文档准确描述 `RDURLReaderController -> RDEPUBReaderController -> readingProfile -> text/native or web` 的真实分流链路。
- 审计文档明确 `webFixedLayout``webInteractive` 继续走 `WKWebView`,不纳入原生重构。
- 审计文档明确 `RDReaderView` 是必须保持稳定的翻页容器。
artifacts:
- .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md
key_links:
- `RDEPUBReaderController.paginatePublication()``RDEPUBParser.readingProfile()` 的关系被写清楚。
- `RDEPUBTextBookBuilder` / `RDEPUBDTCoreTextRenderer` / `RDEPUBTextContentView` 之间的数据流被写清楚。
---
<objective>
产出一份基于当前代码的 `.textReflowable` 审计文档。
Purpose: 为后续 Phase 2/3 提供“当前旧引擎真实是什么”的事实基线,避免围绕错误边界重构。
Output: `.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md`
</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/01-current-engine-boundaries/01-RESEARCH.md
@.planning/phases/01-current-engine-boundaries/01-PATTERNS.md
@Sources/RDReaderView/RDURLReaderController.swift
@Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
@Sources/RDReaderView/EPUBCore/RDEPUBParser+ReadingProfile.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift
@Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift
@Sources/RDReaderView/RDReaderView.swift
</context>
<tasks>
<task type="auto">
<name>Task 1: 梳理原生 reflowable 调用链与数据模型</name>
<files>.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md</files>
<read_first>Sources/RDReaderView/RDURLReaderController.swift, Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/EPUBCore/RDEPUBParser+ReadingProfile.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextPaginationSupport.swift</read_first>
<action>新建 `01-reflowable-audit.md`,写清楚 `RDURLReaderController` 如何把 `.epub` 交给 `RDEPUBReaderController``RDEPUBParser.readingProfile()` 如何把内容分为 `textReflowable` / `webFixedLayout` / `webInteractive`,以及 `RDEPUBReaderController.paginatePublication()``textReflowable` 分支内如何调用 `RDEPUBTextBookBuilder`、`RDEPUBDTCoreTextRenderer`、`ss_pageRanges(size:)`、`RDEPUBTextBook` / `RDEPUBTextPage`。必须包含关键类型名、关键方法名和每层输入输出,不能只写概念描述。</action>
<verify>rg -n "RDURLReaderController|RDEPUBReaderController|RDEPUBTextBookBuilder|RDEPUBDTCoreTextRenderer|ss_pageRanges|RDEPUBTextBook|RDEPUBTextPage" .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md</verify>
<acceptance_criteria>
- `.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md` 存在
- 文档包含 `RDURLReaderController`、`RDEPUBReaderController`、`RDEPUBTextBookBuilder`、`RDEPUBDTCoreTextRenderer`、`RDEPUBTextContentView`
- 文档明确 `ss_pageRanges(size:)` 仍是当前分页核心
</acceptance_criteria>
<done>审计文档把当前 native reflowable 路径的入口、渲染、分页、展示和位置数据模型全部串起来。</done>
</task>
<task type="auto">
<name>Task 2: 写清 WebView 边界与容器不变约束</name>
<files>.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md</files>
<read_first>Sources/RDReaderView/EPUBCore/RDEPUBParser+ReadingProfile.swift, Sources/RDReaderView/EPUBCore/RDEPUBPaginator.swift, Sources/RDReaderView/EPUBCore/RDEPUBWebView+FixedLayout.swift, Sources/RDReaderView/EPUBCore/RDEPUBWebView+Reflowable.swift, Sources/RDReaderView/RDReaderView.swift</read_first>
<action>在同一份审计文档中追加“边界与排除项”章节,明确 `webFixedLayout``webInteractive` 为什么继续依赖 `WKWebView``RDEPUBPaginator` 在当前架构中的职责,以及 `RDReaderView` 的 page curl / scroll / dual-page / orientation 逻辑为什么必须保持不变。把这些结论落成可以直接供后续 phase 引用的约束条目。</action>
<verify>rg -n "WKWebView|webFixedLayout|webInteractive|RDEPUBPaginator|RDReaderView|不修改" .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md</verify>
<acceptance_criteria>
- 文档包含 `WKWebView`、`webFixedLayout`、`webInteractive`、`RDEPUBPaginator`、`RDReaderView`
- 文档有单独的边界或排除项章节
- 文档明确写出“`RDReaderView` 不修改”或等价表述
</acceptance_criteria>
<done>审计文档既说明当前原生引擎,也说明哪些路径和容器不属于本次重构。</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `test -f .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md`
- [ ] `rg -n "textReflowable|webFixedLayout|webInteractive|RDReaderView" .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md`
- [ ] 文档内容可直接对应到当前源码文件,而不是只复述 roadmap 目标
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- No errors or warnings introduced
- 审计文档能支撑后续 Phase 2/3 不再重新摸索当前引擎边界
</success_criteria>
<output>
After completion, create `.planning/phases/01-current-engine-boundaries/01-01-SUMMARY.md`
</output>

View File

@ -0,0 +1,107 @@
---
phase: 01-current-engine-boundaries
plan: 02
type: execute
wave: 2
depends_on:
- "01-01"
files_modified:
- .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md
autonomous: true
requirements:
- COMP-02
user_setup: []
must_haves:
truths:
- 策略文档给出“旧引擎直接演进”而不是“双引擎并存”的切入方案。
- 策略文档明确 Fixed Layout / 交互式 EPUB 继续走 `WKWebView`
- 策略文档明确 `RDReaderView` 与现有翻页容器契约保持不变。
- 策略文档指出 Phase 2 与 Phase 3 分别应优先触碰的代码触点。
artifacts:
- .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md
key_links:
- `Doc/WXRead/analysis/*` 的目标能力被映射到当前 `EPUBTextRendering` 触点。
- `01-reflowable-audit.md` 中确认的边界被转写为后续阶段的实施约束。
---
<objective>
基于当前审计结果和 WXRead 资料,产出一份可执行的旧引擎直接演进策略文档。
Purpose: 把“从哪里动、先动什么、哪些外部契约不能动”固定下来,作为 Phase 2/3 的实现基线。
Output: `.planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md`
</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/01-current-engine-boundaries/01-RESEARCH.md
@.planning/phases/01-current-engine-boundaries/01-PATTERNS.md
@.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md
@Doc/WXRead/analysis/01_核心架构与数据流图.md
@Doc/WXRead/analysis/02_符号恢复与核心算法.md
@Doc/WXRead/analysis/EPUB渲染管线详解.md
@Doc/WXRead/analysis/DTCoreText自定义修改分析.md
@Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md
@Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift
@Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift
@Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift
@Sources/RDReaderView/RDReaderView.swift
</context>
<tasks>
<task type="auto">
<name>Task 1: 提炼 WXRead 目标能力到当前引擎触点</name>
<files>.planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md</files>
<read_first>.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md, Doc/WXRead/analysis/EPUB渲染管线详解.md, Doc/WXRead/analysis/DTCoreText自定义修改分析.md, Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift, Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift</read_first>
<action>新建 `01-refactor-entry-strategy.md`,把 WXRead 资料中的五层 CSS、DTCoreText 自定义属性、页面级元数据、块级避免断页、layout frame / layouter 责任拆解为当前仓库可落地的触点映射。至少给出 `RDEPUBDTCoreTextRenderer`、`RDEPUBTextBookBuilder`、`RDEPUBTextPaginationSupport`、`RDEPUBTextPage` / `RDEPUBTextChapter` 这些具体落点,并说明每个落点在 Phase 2 或 Phase 3 中承担什么职责。</action>
<verify>rg -n "RDEPUBDTCoreTextRenderer|RDEPUBTextBookBuilder|RDEPUBTextPaginationSupport|RDEPUBTextPage|RDEPUBTextChapter|CSS|属性|分页" .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md</verify>
<acceptance_criteria>
- 文档包含明确的“WXRead 能力 -> 当前源码触点”映射
- 文档点名 `RDEPUBDTCoreTextRenderer`、`RDEPUBTextBookBuilder`、`RDEPUBTextPaginationSupport`
- 文档区分 Phase 2 与 Phase 3 的切入重心
</acceptance_criteria>
<done>策略文档不再停留在抽象“参考 WXRead”而是把目标能力落成当前仓库里的具体改造入口。</done>
</task>
<task type="auto">
<name>Task 2: 固化范围边界和兼容性保留链路</name>
<files>.planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md</files>
<read_first>.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md, Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md, Sources/RDReaderView/EPUBCore/RDEPUBParser+ReadingProfile.swift, Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift, Sources/RDReaderView/RDReaderView.swift</read_first>
<action>在策略文档中追加“非目标与兼容面”章节,明确列出以下内容:`webFixedLayout` 与 `webInteractive` 保持 `WKWebView``RDReaderView` 与现有翻页模式不改,阅读位置/高亮/搜索/主题切换后的重分页必须继续通过现有 `RDEPUBReaderController` 契约工作;同时给出后续实施顺序建议,说明为什么必须先增强 renderer/typesetter再升级分页语义最后再回接 reader 能力链路。</action>
<verify>rg -n "WKWebView|RDReaderView|阅读位置|高亮|搜索|主题|重分页|实施顺序" .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md</verify>
<acceptance_criteria>
- 文档明确写出 `WKWebView` 保留边界
- 文档明确写出 `RDReaderView` 不修改
- 文档明确列出阅读位置、高亮、搜索、主题切换等兼容项
- 文档包含一个按阶段排序的切入顺序
</acceptance_criteria>
<done>策略文档既给出切入点,也把必须保留的外层契约固定为后续 phase 的硬约束。</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `test -f .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md`
- [ ] `rg -n "WKWebView|RDReaderView|RDEPUBDTCoreTextRenderer|RDEPUBTextBookBuilder" .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md`
- [ ] 文档结论与 `01-reflowable-audit.md` 不冲突,并明确拒绝双引擎方案
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- No errors or warnings introduced
- 策略文档足以直接指导 Phase 2/3 进入实现,不再重复做边界澄清
</success_criteria>
<output>
After completion, create `.planning/phases/01-current-engine-boundaries/01-02-SUMMARY.md`
</output>

View File

@ -0,0 +1,31 @@
# Phase 1: 对齐现状、边界与重构切入点 - Pattern Map
## Goal
为本阶段执行提供“先看哪里、按什么证据写文档”的最短路径。Phase 1 不改业务代码,主要产出审计和策略文档,因此模式映射聚焦在源码证据与现有方案文档之间的对应关系。
## Planned Outputs
| Planned file | Role | Primary evidence | Why this is the right analog |
|--------------|------|------------------|------------------------------|
| `.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md` | 当前引擎审计文档 | `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`, `Sources/RDReaderView/EPUBTextRendering/*.swift`, `Sources/RDReaderView/EPUBCore/RDEPUBParser+ReadingProfile.swift` | 这些文件构成真实 `.textReflowable` 调用链和 fixed/interactive 分流边界。 |
| `.planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md` | 直接演进策略文档 | `Doc/WXRead/analysis/*.md`, `Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md` | 这些文档定义了 WXRead 参考能力与当前项目已有方案草案。 |
## Code Evidence Map
| Concern | Closest source of truth | Evidence to extract |
|---------|-------------------------|---------------------|
| URL 入口如何进入阅读器 | `Sources/RDReaderView/RDURLReaderController.swift` | EPUB vs TXT 分流、`RDEPUBReaderController` 嵌入方式、TXT fallback。 |
| `.textReflowable` 何时生效 | `Sources/RDReaderView/EPUBCore/RDEPUBParser+ReadingProfile.swift` | `.webFixedLayout` / `.webInteractive` / `.textReflowable` 判定。 |
| 原生 reflowable 渲染入口 | `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift` | `paginatePublication()` 内的 `RDEPUBTextBookBuilder` 分支。 |
| 章节 HTML 如何转富文本 | `Sources/RDReaderView/EPUBTextRendering/RDEPUBDTCoreTextRenderer.swift` | `DTHTMLAttributedStringBuilder`、`dtOptions`、fragment marker 注入。 |
| 章节如何切页 | `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift` and `RDEPUBTextPaginationSupport.swift` | `ss_pageRanges(size:)`、`RDEPUBTextPage` offsets、chapter/page flattening。 |
| 文本页如何接回 reader UI | `Sources/RDReaderView/EPUBUI/RDEPUBTextContentView.swift` and `RDEPUBReaderController.swift` | 高亮、搜索、selection、page number、location persistence。 |
| 哪些链路必须留在 WebView | `Sources/RDReaderView/EPUBCore/RDEPUBWebView+FixedLayout.swift`, `RDEPUBWebView+Reflowable.swift`, `RDEPUBPaginator.swift` | Fixed-layout spread loading、interactive / web pagination、presentation script。 |
| 哪些容器代码不能动 | `Sources/RDReaderView/RDReaderView.swift` | page curl / scroll container、dual-page、orientation handling。 |
## Writing Guidance
- 审计文档优先引用源码事实,再引用 `.planning/codebase/ARCHITECTURE.md` 作为补充解释。
- 策略文档优先从 `Doc/WXRead/analysis/EPUB渲染管线详解.md`、`DTCoreText自定义修改分析.md` 提炼目标能力,再映射回当前 `EPUBTextRendering` 触点。
- 若引用旧实现差异,优先查看 `Sources/RDReaderView/LegacyRDReaderController/RDEPUBTextPaging.swift`,但不要把 legacy 行为当成自动保留项,必须与新 controller 现状对齐。

View File

@ -0,0 +1,224 @@
# Phase 1: 对齐现状、边界与重构切入点 - Research
**Researched:** 2026-05-21
**Domain:** iOS EPUB reader architecture / DTCoreText-based native reflowable rendering
**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 |
|------------|-------------|----------------|-----------|
| EPUB URL entry and format routing | Browser/Client | — | `RDURLReaderController` decides epub vs txt and embeds the correct reader controller. |
| EPUB structure parsing and reading-profile split | Browser/Client | — | `RDEPUBParser` and `RDEPUBPublication` classify `.textReflowable` vs `webFixedLayout` / `webInteractive`. |
| Reflowable native chapter rendering | Browser/Client | — | `RDEPUBDTCoreTextRenderer` and `RDEPUBTextBookBuilder` own HTML to `NSAttributedString` and page slicing. |
| Fixed-layout and interactive content rendering | Browser/Client | — | `RDEPUBWebView` and `RDEPUBPaginator` keep `WKWebView` as the rendering and measurement path. |
| Page container and interaction shell | Browser/Client | — | `RDReaderView` is the stable presentation container that Phase 1 must not modify. |
</architectural_responsibility_map>
<research_summary>
## Summary
当前仓库已经存在一条独立的原生 reflowable 路径,而不是“所有 EPUB 都走 WebView”。`RDEPUBReaderController.paginatePublication` 会在 `publication.readingProfile == .textReflowable` 时改走 `RDEPUBTextBookBuilder`,使用 `RDEPUBDTCoreTextRenderer` 把章节 HTML 转成 `NSAttributedString`,再通过 `ss_pageRanges(size:)` 基于 CoreText 可见范围切页。该路径最终产出 `RDEPUBTextBook` / `RDEPUBTextPage`,并由 `RDEPUBTextContentView` 在现有 `RDReaderView` 容器内展示。
Fixed Layout 与交互式 EPUB 的边界已经在代码里清晰存在。`RDEPUBParser.readingProfile()` 将 `metadata.layout == .fixed` 归类为 `.webFixedLayout`,并把包含脚本/多媒体/iframe/form 等交互内容的资源归类为 `.webInteractive`。这两类内容继续走 `RDEPUBWebView` / `RDEPUBPaginator` / `WKWebView` 体系,不应被纳入本次原生重构。
WXRead 参考资料说明,目标不是“再包一层轻量 renderer”而是把当前 DTCoreText 路径升级为完整的 typesetter + layouter + layout frame 体系:五层 CSS 级联、自定义 DTCoreText 属性键、块级避免断页、页面级元数据、图片与背景等页面语义。对本仓库来说,最稳妥的切入点是保留 `RDURLReaderController -> RDEPUBReaderController -> RDReaderView` 外层契约不变,把增强集中在 `EPUBTextRendering` 内部及其与 reader state 的对接点。
**Primary recommendation:** Phase 1 应先把当前 `.textReflowable` 调用链、WebView 边界、兼容性约束和可演进触点写成明确文档,再以这些文档作为 Phase 2/3 的实现基线。
</research_summary>
<standard_stack>
## Standard Stack
### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| UIKit | iOS 15+ SDK | Reader container, controllers, text view presentation | 现有 reader UI 与 `RDReaderView` 全部建立在 UIKit 上。 |
| CoreText | System | Native pagination and text measurement | 当前 `ss_pageRanges(size:)` 直接使用 `CTFramesetterCreateFrame` / `CTFrameGetVisibleStringRange`。 |
| DTCoreText | 1.6.28 | HTML/CSS to `NSAttributedString` | 当前原生 reflowable 渲染核心已经依赖它,后续定制也必须围绕它展开。 |
| WebKit | System | Fixed-layout / interactive EPUB rendering and pagination measurement | 非 reflowable-native 路径仍依赖 `WKWebView`。 |
### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| DTFoundation | 1.7.19 | DTCoreText supporting parser/runtime pieces | DTCoreText HTML 解析与附件支持所需。 |
| ZIPFoundation | 0.9.20 | EPUB archive handling | EPUB 解压与资源访问基础设施。 |
| SnapKit | 5.7.1 | UI layout helper | Reader UI 周边布局已有依赖,但不是本次内核切入点。 |
### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| Directly evolving `EPUBTextRendering` | A second native engine alongside the existing one | 会增加长期双轨维护成本,并破坏 roadmap 已锁定的“旧引擎直接演进”原则。 |
| DTCoreText-based native rendering | Full `WKWebView` for all EPUB modes | 无法满足 WXRead-style page metadata / page-frame semantics / native selection control goals。 |
| Keeping `RDReaderView` stable | Reworking page container and gesture stack together | 改动半径过大Phase 1 明确要求把翻页容器排除出本次重构。 |
**Installation:**
```bash
pod install
```
</standard_stack>
<architecture_patterns>
## Architecture Patterns
### System Architecture Diagram
```text
RDURLReaderController
-> RDEPUBReaderController
-> RDEPUBParser.parse()
-> RDEPUBPublication / readingProfile()
-> textReflowable
-> RDEPUBTextBookBuilder
-> RDEPUBDTCoreTextRenderer
-> DTHTMLAttributedStringBuilder
-> NSAttributedString
-> RDEPUBTextPaginationSupport.ss_pageRanges()
-> RDEPUBTextBook / RDEPUBTextPage
-> RDEPUBTextContentView
-> RDReaderView
-> webFixedLayout
-> RDEPUBWebView.loadFixedSpread()
-> RDReaderView
-> webInteractive
-> RDEPUBPaginator + RDEPUBWebView.loadPage()
-> RDReaderView
```
### Recommended Project Structure
```text
Sources/RDReaderView/
├── EPUBCore/ # Parser, publication, web pagination, render requests
├── EPUBTextRendering/ # Native reflowable rendering and pagination
├── EPUBUI/ # Reader controller, content views, settings
└── RDReaderView.swift # Stable page container and gestures
```
### Pattern 1: Preserve the outer reader contract
**What:** Keep `RDURLReaderController`, `RDEPUBReaderController`, persistence, location mapping, and `RDReaderView` as the stable shell while replacing internals inside `EPUBTextRendering`.
**When to use:** Any phase that upgrades the reflowable engine without expanding scope into the page container.
**Example:** `RDEPUBReaderController.paginatePublication` already branches into text vs web rendering without changing the reader container contract.
### Pattern 2: Keep rendering and presentation separate
**What:** Produce rich `RDEPUBTextBook` / `RDEPUBTextPage` models first, then let `RDEPUBTextContentView` render those models.
**When to use:** When introducing page-level metadata, selection offsets, image semantics, or new pagination logic.
**Example:** `RDEPUBTextBookBuilder` owns chapter rendering and page slicing, while `RDEPUBTextContentView` only paints attributed substrings and overlays highlights/search state.
### Pattern 3: Branch by publication profile early
**What:** Decide native reflowable vs fixed vs interactive before rendering work starts.
**When to use:** Any future entry-point or repagination logic.
**Example:** `RDEPUBParser.readingProfile()` uses `layout == .fixed` and interactive-content detection to keep `WKWebView` modes out of the native path.
### Anti-Patterns to Avoid
- **Touching `RDReaderView` during engine migration:** Phase 1 explicitly excludes page curl / scroll / gesture container changes.
- **Building a parallel renderer shell:** The roadmap already rejects a “new engine beside old engine” approach.
- **Mixing WebView-only semantics into the native path:** `textReflowable` should evolve around `NSAttributedString` and CoreText, not around hidden `WKWebView` dependencies.
</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 | 仓库已经围绕 DTCoreText 建立基础能力,重写 parser 成本高且风险大。 |
| EPUB mode classification | Ad-hoc view-layer checks | `RDEPUBParser.readingProfile()` | 当前 profile 分流已经覆盖 fixed / interactive / text reflowable。 |
| Reader shell replacement | A new page container | Existing `RDReaderView` contract | 现有 container 已承担翻页模式、双页与方向逻辑,超出本次范围。 |
| Location/highlight transport | A second mapping format | Existing `RDEPUBLocation` + `RDEPUBTextOffsetRangeInfo` | 这些类型已被 highlights / search / restore location 链路依赖。 |
**Key insight:** 应该复用现有阅读器契约和数据模型,把新增复杂度集中在旧引擎内部,而不是在外围重造一层基础设施。
</dont_hand_roll>
<common_pitfalls>
## Common Pitfalls
### Pitfall 1: Confusing native reflowable with the WebView paginator
**What goes wrong:** 把 `RDEPUBPaginator` 当成所有 reflowable 的基础,导致 Phase 2/3 方案偏向继续强化 WebView。
**Why it happens:** 当前工程同时存在 `textReflowable` 和基于 `WKWebView` 的分页/展示路径。
**How to avoid:** 在所有 Phase 1 输出里明确:`RDEPUBPaginator` 服务于 fixed 或 interactive/web paths原生重构主战场是 `EPUBTextRendering`
**Warning signs:** 计划任务把 `RDEPUBPaginator` 作为 CSS 五层或页面元数据的主要落点。
### Pitfall 2: Breaking `RDReaderView` assumptions while changing pagination
**What goes wrong:** 新分页结果不再满足现有 page index / container reuse / orientation repagination 约定。
**Why it happens:** 容易把引擎升级和容器升级绑在一起处理。
**How to avoid:** 把 `RDEPUBTextBook.pages` / `RDEPUBTextPage` 视为 Phase 2/3 的兼容面,并在策略中显式约束 `RDReaderView` 不改。
**Warning signs:** 方案需要修改 `RDReaderView.DataSource`、翻页方向、双页逻辑或 content view reuse 协议。
### Pitfall 3: Introducing richer layout semantics without preserving location mapping
**What goes wrong:** 新分页器能排版复杂元素,但阅读位置、高亮、搜索结果定位失效。
**Why it happens:** 只关注排版帧,不关注 `pageStartOffset/pageEndOffset``RDEPUBLocation` 的传递。
**How to avoid:** 把 offset continuity、fragment offset、selection range transport 作为 Phase 2/3 的第一类兼容目标。
**Warning signs:** 设计里没有说明新页面元数据如何回写 `RDEPUBTextBook`、`RDEPUBSelection`、`RDEPUBHighlight`。
</common_pitfalls>
<code_examples>
## Code Examples
### Current reflowable split
```swift
if publication.readingProfile == .textReflowable {
let renderer = resolvedTextRenderer()
let builder = RDEPUBTextBookBuilder(renderer: renderer)
let textBook = try builder.build(
parser: parser,
publication: publication,
pageSize: pageSize,
style: renderStyle
)
}
```
### Current fixed vs interactive boundary
```swift
public func readingProfile() -> RDEPUBReadingProfile {
if metadata.layout == .fixed {
return .webFixedLayout
}
return hasInteractiveContent() ? .webInteractive : .textReflowable
}
```
### Current native pagination primitive
```swift
while visibleRange.location + visibleRange.length < length {
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(location, 0), path, nil)
visibleRange = CTFrameGetVisibleStringRange(frame)
ranges.append(NSRange(location: location, length: visibleRange.length))
location += visibleRange.length
}
```
</code_examples>
<sota_updates>
## State of the Art (2024-2025)
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Generic HTML to attributed string only | HTML to attributed string plus page-level layout semantics | 已反映在 WXRead 参考资料中 | 需要把 CSS/HTML 处理与分页语义一起设计,而不是只换 builder options。 |
| Simple visible-range page slicing | Block-aware layout frames and custom pagination metadata | 已反映在 WXRead 参考资料中 | 当前 `CTFrameGetVisibleStringRange` 模式会成为主要替换对象。 |
| Styling as a small option bag | Multi-layer stylesheet cascade | 已反映在 WXRead 参考资料中 | `dtOptions` 需要被更明确的 stylesheet pipeline 取代或扩展。 |
**New tools/patterns to consider:**
- DTCoreText attribute extension points: 适合承接 WXRead 风格自定义属性键。
- Page-level layout model: 需要在 `RDEPUBTextChapter` / `RDEPUBTextPage` 附近引入更丰富的元数据,而不是只保存范围。
**Deprecated/outdated:**
- “Simple DTCoreText renderer + pageRanges” 作为长期方案:对复杂块元素、页面语义和 CSS 分层都不够。
- “为原生重构同时替换翻页容器”:与当前范围控制相冲突。
</sota_updates>
<open_questions>
## Open Questions
- `LegacyRDReaderController/RDEPUBTextPaging.swift` 与新 `EPUBTextRendering` 之间哪些实现差异仍值得迁移,哪些应直接废弃,需要在 Phase 1 执行阶段做一次并列审计。
- `Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md` 与当前源码是否已经出现偏差,需要在 Plan 01-02 中显式核对。
- 未来自定义 DTCoreText 属性应优先挂在 `NSAttributedString`、页模型,还是单独的 layout frame 对象上,需要在策略文档中做出建议但不提前定版实现。
</open_questions>

View File

@ -0,0 +1,75 @@
---
phase: 1
slug: current-engine-boundaries
status: draft
nyquist_compliant: false
wave_0_complete: false
created: 2026-05-21
---
# Phase 1 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | shell assertions + Xcode build smoke |
| **Config file** | none — current repo has no dedicated XCTest target for this phase |
| **Quick run command** | `test -f .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md` |
| **Full suite command** | `xcodebuild -workspace ReadViewDemo/ReadViewDemo.xcworkspace -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 15' build` |
| **Estimated runtime** | ~60 seconds |
---
## Sampling Rate
- **After every task commit:** Run `test -f .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md` or the matching strategy-doc existence/source assertions for the touched artifact.
- **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 and both phase documents must exist.
- **Max feedback latency:** 60 seconds
---
## Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| 01-01-01 | 01 | 1 | COMP-02 | — | 审计文档准确区分 `.textReflowable``WKWebView` 路径 | source | `rg -n "textReflowable|webFixedLayout|webInteractive|RDReaderView" .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md` | ❌ W0 | ⬜ pending |
| 01-01-02 | 01 | 1 | COMP-02 | — | 审计文档列出真实调用链与关键源码入口 | source | `rg -n "RDURLReaderController|RDEPUBReaderController|RDEPUBTextBookBuilder|RDEPUBDTCoreTextRenderer" .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md` | ❌ W0 | ⬜ pending |
| 01-02-01 | 02 | 2 | COMP-02 | — | 策略文档明确固定版式/交互式 EPUB 继续走 `WKWebView` | source | `rg -n "WKWebView|webFixedLayout|webInteractive" .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md` | ❌ W0 | ⬜ pending |
| 01-02-02 | 02 | 2 | COMP-02 | — | 策略文档明确 `RDReaderView` 不修改且采用旧引擎直接演进 | source | `rg -n "RDReaderView|直接重构|不修改" .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md` | ❌ W0 | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
- [ ] No new test target required for Phase 1 documentation work.
- [ ] Executor must confirm `ReadViewDemo/ReadViewDemo.xcworkspace` and `ReadViewDemo` scheme still open/build before closing the phase.
---
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Audit conclusions match the actual repo structure | COMP-02 | 需要阅读源码与文档交叉确认,纯自动化难以判断“结论是否误导” | 打开 `01-reflowable-audit.md`,逐项对照源码路径和 reading-profile 分流逻辑。 |
| Refactor entry strategy is actionable for Phase 2/3 | COMP-02 | 需要判断切入顺序是否真的减少风险 | 阅读 `01-refactor-entry-strategy.md`,确认触点、保留边界、后续 phase 依赖关系清晰。 |
---
## 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 < 60s
- [ ] `nyquist_compliant: true` set in frontmatter
**Approval:** pending