From 6d196d64e5fc98152a333d755ac0c3a901bae0ac Mon Sep 17 00:00:00 2001 From: shen <> Date: Thu, 21 May 2026 22:00:41 +0800 Subject: [PATCH] docs(phase-01): add planning artifacts --- .../01-01-PLAN.md | 102 ++++++++ .../01-02-PLAN.md | 107 +++++++++ .../01-PATTERNS.md | 31 +++ .../01-RESEARCH.md | 224 ++++++++++++++++++ .../01-VALIDATION.md | 75 ++++++ 5 files changed, 539 insertions(+) create mode 100644 .planning/phases/01-current-engine-boundaries/01-01-PLAN.md create mode 100644 .planning/phases/01-current-engine-boundaries/01-02-PLAN.md create mode 100644 .planning/phases/01-current-engine-boundaries/01-PATTERNS.md create mode 100644 .planning/phases/01-current-engine-boundaries/01-RESEARCH.md create mode 100644 .planning/phases/01-current-engine-boundaries/01-VALIDATION.md diff --git a/.planning/phases/01-current-engine-boundaries/01-01-PLAN.md b/.planning/phases/01-current-engine-boundaries/01-01-PLAN.md new file mode 100644 index 0000000..4ab45b3 --- /dev/null +++ b/.planning/phases/01-current-engine-boundaries/01-01-PLAN.md @@ -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` 之间的数据流被写清楚。 +--- + + +产出一份基于当前代码的 `.textReflowable` 审计文档。 + +Purpose: 为后续 Phase 2/3 提供“当前旧引擎真实是什么”的事实基线,避免围绕错误边界重构。 +Output: `.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md` + + + +@$HOME/.codex/get-shit-done/workflows/execute-plan.md +@$HOME/.codex/get-shit-done/templates/summary.md + + + +@.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 + + + + + + Task 1: 梳理原生 reflowable 调用链与数据模型 + .planning/phases/01-current-engine-boundaries/01-reflowable-audit.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 + 新建 `01-reflowable-audit.md`,写清楚 `RDURLReaderController` 如何把 `.epub` 交给 `RDEPUBReaderController`,`RDEPUBParser.readingProfile()` 如何把内容分为 `textReflowable` / `webFixedLayout` / `webInteractive`,以及 `RDEPUBReaderController.paginatePublication()` 在 `textReflowable` 分支内如何调用 `RDEPUBTextBookBuilder`、`RDEPUBDTCoreTextRenderer`、`ss_pageRanges(size:)`、`RDEPUBTextBook` / `RDEPUBTextPage`。必须包含关键类型名、关键方法名和每层输入输出,不能只写概念描述。 + rg -n "RDURLReaderController|RDEPUBReaderController|RDEPUBTextBookBuilder|RDEPUBDTCoreTextRenderer|ss_pageRanges|RDEPUBTextBook|RDEPUBTextPage" .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md + + - `.planning/phases/01-current-engine-boundaries/01-reflowable-audit.md` 存在 + - 文档包含 `RDURLReaderController`、`RDEPUBReaderController`、`RDEPUBTextBookBuilder`、`RDEPUBDTCoreTextRenderer`、`RDEPUBTextContentView` + - 文档明确 `ss_pageRanges(size:)` 仍是当前分页核心 + + 审计文档把当前 native reflowable 路径的入口、渲染、分页、展示和位置数据模型全部串起来。 + + + + Task 2: 写清 WebView 边界与容器不变约束 + .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md + 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 + 在同一份审计文档中追加“边界与排除项”章节,明确 `webFixedLayout` 与 `webInteractive` 为什么继续依赖 `WKWebView`,`RDEPUBPaginator` 在当前架构中的职责,以及 `RDReaderView` 的 page curl / scroll / dual-page / orientation 逻辑为什么必须保持不变。把这些结论落成可以直接供后续 phase 引用的约束条目。 + rg -n "WKWebView|webFixedLayout|webInteractive|RDEPUBPaginator|RDReaderView|不修改" .planning/phases/01-current-engine-boundaries/01-reflowable-audit.md + + - 文档包含 `WKWebView`、`webFixedLayout`、`webInteractive`、`RDEPUBPaginator`、`RDReaderView` + - 文档有单独的边界或排除项章节 + - 文档明确写出“`RDReaderView` 不修改”或等价表述 + + 审计文档既说明当前原生引擎,也说明哪些路径和容器不属于本次重构。 + + + + + +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 目标 + + + + +- All tasks completed +- All verification checks pass +- No errors or warnings introduced +- 审计文档能支撑后续 Phase 2/3 不再重新摸索当前引擎边界 + + + +After completion, create `.planning/phases/01-current-engine-boundaries/01-01-SUMMARY.md` + diff --git a/.planning/phases/01-current-engine-boundaries/01-02-PLAN.md b/.planning/phases/01-current-engine-boundaries/01-02-PLAN.md new file mode 100644 index 0000000..23421ed --- /dev/null +++ b/.planning/phases/01-current-engine-boundaries/01-02-PLAN.md @@ -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` 中确认的边界被转写为后续阶段的实施约束。 +--- + + +基于当前审计结果和 WXRead 资料,产出一份可执行的旧引擎直接演进策略文档。 + +Purpose: 把“从哪里动、先动什么、哪些外部契约不能动”固定下来,作为 Phase 2/3 的实现基线。 +Output: `.planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md` + + + +@$HOME/.codex/get-shit-done/workflows/execute-plan.md +@$HOME/.codex/get-shit-done/templates/summary.md + + + +@.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 + + + + + + Task 1: 提炼 WXRead 目标能力到当前引擎触点 + .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md + .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 + 新建 `01-refactor-entry-strategy.md`,把 WXRead 资料中的五层 CSS、DTCoreText 自定义属性、页面级元数据、块级避免断页、layout frame / layouter 责任拆解为当前仓库可落地的触点映射。至少给出 `RDEPUBDTCoreTextRenderer`、`RDEPUBTextBookBuilder`、`RDEPUBTextPaginationSupport`、`RDEPUBTextPage` / `RDEPUBTextChapter` 这些具体落点,并说明每个落点在 Phase 2 或 Phase 3 中承担什么职责。 + rg -n "RDEPUBDTCoreTextRenderer|RDEPUBTextBookBuilder|RDEPUBTextPaginationSupport|RDEPUBTextPage|RDEPUBTextChapter|CSS|属性|分页" .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md + + - 文档包含明确的“WXRead 能力 -> 当前源码触点”映射 + - 文档点名 `RDEPUBDTCoreTextRenderer`、`RDEPUBTextBookBuilder`、`RDEPUBTextPaginationSupport` + - 文档区分 Phase 2 与 Phase 3 的切入重心 + + 策略文档不再停留在抽象“参考 WXRead”,而是把目标能力落成当前仓库里的具体改造入口。 + + + + Task 2: 固化范围边界和兼容性保留链路 + .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md + .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 + 在策略文档中追加“非目标与兼容面”章节,明确列出以下内容:`webFixedLayout` 与 `webInteractive` 保持 `WKWebView`,`RDReaderView` 与现有翻页模式不改,阅读位置/高亮/搜索/主题切换后的重分页必须继续通过现有 `RDEPUBReaderController` 契约工作;同时给出后续实施顺序建议,说明为什么必须先增强 renderer/typesetter,再升级分页语义,最后再回接 reader 能力链路。 + rg -n "WKWebView|RDReaderView|阅读位置|高亮|搜索|主题|重分页|实施顺序" .planning/phases/01-current-engine-boundaries/01-refactor-entry-strategy.md + + - 文档明确写出 `WKWebView` 保留边界 + - 文档明确写出 `RDReaderView` 不修改 + - 文档明确列出阅读位置、高亮、搜索、主题切换等兼容项 + - 文档包含一个按阶段排序的切入顺序 + + 策略文档既给出切入点,也把必须保留的外层契约固定为后续 phase 的硬约束。 + + + + + +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` 不冲突,并明确拒绝双引擎方案 + + + + +- All tasks completed +- All verification checks pass +- No errors or warnings introduced +- 策略文档足以直接指导 Phase 2/3 进入实现,不再重复做边界澄清 + + + +After completion, create `.planning/phases/01-current-engine-boundaries/01-02-SUMMARY.md` + diff --git a/.planning/phases/01-current-engine-boundaries/01-PATTERNS.md b/.planning/phases/01-current-engine-boundaries/01-PATTERNS.md new file mode 100644 index 0000000..2f00a92 --- /dev/null +++ b/.planning/phases/01-current-engine-boundaries/01-PATTERNS.md @@ -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 现状对齐。 diff --git a/.planning/phases/01-current-engine-boundaries/01-RESEARCH.md b/.planning/phases/01-current-engine-boundaries/01-RESEARCH.md new file mode 100644 index 0000000..8e8e203 --- /dev/null +++ b/.planning/phases/01-current-engine-boundaries/01-RESEARCH.md @@ -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 (from CONTEXT.md) + +No user constraints - all decisions at the agent's discretion. + + + +## 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. | + + + + +## 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 的实现基线。 + + + +## 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 +``` + + + +## 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. + + + +## 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:** 应该复用现有阅读器契约和数据模型,把新增复杂度集中在旧引擎内部,而不是在外围重造一层基础设施。 + + + +## 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`。 + + + +## 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 +} +``` + + + +## 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 分层都不够。 +- “为原生重构同时替换翻页容器”:与当前范围控制相冲突。 + + + +## Open Questions + +- `LegacyRDReaderController/RDEPUBTextPaging.swift` 与新 `EPUBTextRendering` 之间哪些实现差异仍值得迁移,哪些应直接废弃,需要在 Phase 1 执行阶段做一次并列审计。 +- `Doc/FeatureSolution/ReflowableEPUB_WXReadRenderer_Design.md` 与当前源码是否已经出现偏差,需要在 Plan 01-02 中显式核对。 +- 未来自定义 DTCoreText 属性应优先挂在 `NSAttributedString`、页模型,还是单独的 layout frame 对象上,需要在策略文档中做出建议但不提前定版实现。 + diff --git a/.planning/phases/01-current-engine-boundaries/01-VALIDATION.md b/.planning/phases/01-current-engine-boundaries/01-VALIDATION.md new file mode 100644 index 0000000..4258b57 --- /dev/null +++ b/.planning/phases/01-current-engine-boundaries/01-VALIDATION.md @@ -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 `` 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