docs(phase-01): add planning artifacts
This commit is contained in:
parent
0870996ed0
commit
6d196d64e5
102
.planning/phases/01-current-engine-boundaries/01-01-PLAN.md
Normal file
102
.planning/phases/01-current-engine-boundaries/01-01-PLAN.md
Normal 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>
|
||||||
107
.planning/phases/01-current-engine-boundaries/01-02-PLAN.md
Normal file
107
.planning/phases/01-current-engine-boundaries/01-02-PLAN.md
Normal 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>
|
||||||
31
.planning/phases/01-current-engine-boundaries/01-PATTERNS.md
Normal file
31
.planning/phases/01-current-engine-boundaries/01-PATTERNS.md
Normal 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 现状对齐。
|
||||||
224
.planning/phases/01-current-engine-boundaries/01-RESEARCH.md
Normal file
224
.planning/phases/01-current-engine-boundaries/01-RESEARCH.md
Normal 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>
|
||||||
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user