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,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 task’s `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.