Files
book-comic-library/docs/superpowers/specs/2026-09-16-reader-revamp-design.md
T

13 KiB
Raw Blame History

阅读器改版(chrome 迁移 + 阅读新特性)设计 / Reader revamp design

日期 2026-09-16。分支 docs/reader-revamp-spec(设计);实施分支另建(建议 feat/reader-revamp)。状态:已获用户批准(会话内分节确认)。

本 spec 是项目优化四个子项目中的 ②(① 后端健壮性、④ 前端工程质量已合入 master;③ 功能增强独立 spec)。边界来源:2026-09-08-modern-redesign-shadcn-design.md 的「Phase 2」节;基建依赖 ④ 已交付(eslint/组件测试/e2e/axe 回归网)。

目标 / Goal

  1. 四阅读器(CBZ/Text/EPUB/PDF)chrome(头栏/工具条/目录/书签面板/主题卡)迁入 shadcn token + radix 原语,删除全部 rd-* 组件类与裸色类,删除 deprecated 的 components/ui.ts 与 components/icons.tsx。
  2. 「纸/米/夜」阅读主题重映射为语义 token,并实现与全局 light/dark 的「默认联动 + 手动覆盖」规则。
  3. 五个新特性:CBZ 横向翻页模式(长卷/单页/双页 + RTL)、txt/md 书内搜索、统一阅读设置面板(底部抽屉扩展)、阅读统计(localStorage + 可替换接口,书架顶部统计卡)、EPUB 排版设置(epubjs themes)。
  4. 回收 ④ 留下的收严清单:eslint reader 豁免 override(4 条 jsx-a11y warn)删除回 error、axe WAIVERS(reader-chrome color-contrast)清空重扫、TxtView/CbzReader 的行级 disable 重估。
  5. 顺带修复 ④ 终审遗留的 tsc 盲区(e2e/、*.config.ts 纳入类型检查)。

非目标见文末「范围外」。

决策记录 / Decisions

  • 范围 = chrome 迁移 + 新特性(用户选定,否决「纯迁移」与「迁移为主+逐个勾选」)。五特性由用户从候选清单多选圈定;快捷键速查面板落 backlog。
  • 实施策略 = 基建先行 + 按阅读器纵切(方案 1,用户选定):B0 横切基建 → B1 CBZ → B2 Text → B3 EPUB → B4 PDF+统计卡 → B5 清理收严。否决:两段式(迁移批过大、特性二次触碰)、特性先行(旧壳上写特性必返工)。
  • 主题联动 = 默认联动 + 手动覆盖(用户选定):themeMode:"auto" 时全局 dark→夜、light→纸;手动选卡记住手动值;设置面板提供「跟随全局」重置。否决:完全独立、完全同步。
  • 统一设置面板形态 = A 底部抽屉扩展(用户经视觉伴侣选定):现有沉浸底部 sheet 长大,常驻行 + 按格式条件渲染的「更多设置」展开区。否决:右侧 Sheet、工具条 Popover。
  • 书内搜索交互 = 导航抽屉第三 tab(用户选定),否决顶部 overlay 搜索栏。
  • 阅读统计 = localStorage 记录 + StatsStore 可替换接口(用户选定,③ 未来可换服务端实现);展示 = 书架顶部可折叠统计卡(用户选定),书卡不加元素。
  • CBZ 翻页 = 长卷/单页/双页 + RTL 独立开关(用户选定);双页为手动配对,不做自动跨页检测;RTL 仅对页模式生效。
  • EPUB 排版 = 字号/行距/边距 + 主题映射(用户选定),不引入自定义字体族/字体文件。
  • tsc 盲区修复纳入 ②(用户选定)作为 B0 的一个任务。
  • 无 localStorage 兼容要求(用户明确裁定):偏好统一收进单一 reader-prefs 新结构,旧独立键 cbz-continuous/cbz-prefetch 直接废弃(不读不写不迁移),老用户偏好重置为新默认值。服务端进度/书签 locator 格式本期不变(page/spread 与 {page} 天然兼容)。
  • 行为保留约束(继承 Phase 2 边界):翻页/翻屏、CBZ 章锁/连读/预读、书签 seek、进度上报的行为逻辑原样保留(只换壳 + 明确圈定的新特性);④ 期间的行为等价修复(渲染期重置等)不回退。

S0 批次总则 / Batches

六批依序实施、依序提交:B0 横切基建 → B1 CBZ → B2 Text → B3 EPUB → B4 PDF+统计卡 → B5 清理收严。

  • 每批验收底线:npm run check 绿 + npm run e2e 绿(既有 spec 不回归)+ 该批新增测试通过。
  • changelog:用户可见条目记 docs/CHANGELOG_web.md(双语同条、条目间空行、新在上);每批随批记。
  • 新依赖(如有)容器内安装并提交 lockfile;本期预期零新运行时依赖(radix/shadcn/lucide 已在),仅可能新增 @types/node(dev)。

S1 B0 横切基建 / Cross-cutting foundation

阅读主题 token 与联动

  • 保留 data-rd={paper|sepia|night} 驱动机制;--rd-bg/--rd-fg/--rd-link 扩展为完整 token 组(补 --rd-muted/--rd-accent/--rd-border),供迁移后 chrome 使用。阅读面用 --rd-* token,抽屉/弹层容器用 shadcn 语义 token。
  • themeMode: "auto" | "paper" | "sepia" | "night",默认 auto(全局 dark→night、light→paper,实时跟随);手动选主题卡即切换为手动值;设置面板「跟随全局」按钮重置回 auto。

readerPrefs 统一结构(无兼容负担)

  • 单一 localStorage 键 reader-prefs,新结构:{ themeMode, text:{sizeIdx,lineIdx,marginIdx}, cbz:{mode:"strip"|"page"|"spread", rtl:boolean, continuous:boolean, prefetch:0|1|2|3}, epub:{sizeIdx,lineIdx,marginIdx} };宽容解析(缺字段/坏值回默认)。旧 cbz-continuous/cbz-prefetch 键废弃。
  • useReaderPrefs 重构为该结构的读写 hook(现有消费点随各阅读器批次迁移)。

统一设置抽屉(形态 A)

  • 新增 shadcn 原语文件 ui/sheet.tsx、ui/tabs.tsx、ui/slider.tsx(基于已装的 radix-ui 包,零新 npm 依赖,模式对齐现有 ui/*.tsx)。
  • components/reader-settings.tsx:底部 sheet(radix Sheet 原语 + shadcn token),常驻行 = 进度滑条(shadcn Slider)+ 主题卡(含「跟随」态)+ A−/A+;「更多设置」展开区按格式条件渲染:CBZ = 翻页模式/方向/连读/预读;Text = 行距/边距;EPUB = 字号/行距/边距;PDF = 无。
  • 替代旧 rd-sheet/rd-btn/rd-range 与 rd-slider.tsx(组件在 B5 删除)。

统计记录层

  • lib/readingStats.ts:StatsStore 接口(record(seconds) / weekly() / streak())+ localStorage 实现(键 reading-stats,按日桶 { "YYYY-MM-DD": seconds })。
  • 记录时机:Reader 页可见期间 30s 心跳 + 卸载 flush(visibilitychange/pagehide)。纯函数(日桶聚合/周合计/连续天数)独立导出供单测。

tsc 盲区修复

  • frontend/tsconfig.json include 扩为 ["src", "test", "e2e", "*.config.ts"] + dev 依赖 @types/node;存量 e2e/config 代码先过一遍 tsc --noEmit 清零。

S2 B1 CBZ(迁移 + 翻页模式 + RTL)

chrome 迁移

  • pages/Reader.tsx 头栏:stone-950/btn/btnGhost/formatBadge → shadcn token + ui/button(ghost)+ ui/badge;IconArrowLeft → lucide ArrowLeft。
  • components/reader-nav.tsx → radix Sheet(side="left")+ shadcn Tabs 样式;Bookmarks.tsx 内容结构不变、rd-btn → ui/button。
  • 底部工具条 → S1 的 reader-settings。

翻页模式 + RTL

  • strip(长卷)= 现有渲染路径原样保留:章锁/连读/预读/滚动定位/章末卡片零改动。
  • page/spread 新渲染路径:按页索引显示当前页;spread 手动配对 [2i, 2i+1];翻页 = 索引步进,复用 PageImg 解码缓存预载相邻页;章锁在页模式 = 章末页后显示「本章完」卡片页,点击进下一章(锁章止步);continuous 在页模式 = 越过章末卡片自动进下一章;预读三模式通用。
  • RTL 独立开关:右缘点击 = 上一页、键盘 ←/→ 映射翻转、spread 右页为先;仅 page/spread 生效。
  • 点击区约定沿用:两侧翻页、中央收放 chrome。模式/方向切换保持当前页;书签 {page} locator 与进度上报(页索引→percent)兼容不变。
  • 双页配对与 RTL 方向映射抽纯函数进 lib/(单测)。

测试:配对/方向纯函数单测;模式切换组件测试;e2e 扩展单页模式翻页 + RTL 冒烟。CbzReader 迁移后其 eslint warn 豁免与 axe WAIVERS 应自然消除(B5 验证)。

S3 B2 Text(迁移 + 搜索 + 排版)

chrome 迁移:阅读面保留 rd-surface/data-rd token 机制(用 S1 扩展后变量);顶栏/章节选择器(含卷分组)/底部栏迁 shadcn + radix;主题卡/A−A+ 收进 reader-settings;书签抽屉复用 B1 的 reader-nav。分章/分卷/编码回退/进度恢复逻辑原样保留;TxtView 的 set-state disable(④ 遗留)迁移时重估:能以渲染期重置修复则修,否则保留注释并登记 B5 清单。

书内搜索(导航抽屉第三 tab,仅 Text)

  • 输入 250ms 防抖;对内存章节文本线性扫描(大小写不敏感),全局结果上限 500;结果行 = 章节标题 + snippet(命中 ±~20 字符,<mark> 高亮)。
  • 点击结果 → 复用章节跳转定位到章、滚动至命中处短暂高亮;不产生书签;locator 格式不变。
  • 扫描/snippet/高亮切分抽纯函数进 lib/search.ts(单测)。

排版设置:行距五档(1.2/1.4/1.6/1.8/2.0)+ 边距三档(窄/中/宽 → max-width),进 reader-settings,持久化 reader-prefs.text。

测试:lib/search.ts 单测;搜索 tab 组件测试;新增 e2e/fixtures/e2e-sample.txt + e2e 搜索冒烟(输入→结果→跳转断言)。

S4 B3 EPUB(迁移 + 排版设置)

  • chrome 迁移:顶栏/「更多阅读选项」dropdown → shadcn + radix DropdownMenu/Sheet;书签抽屉复用 reader-nav;CFI 进度/书签原样保留。
  • 排版:epubjs rendition.themes API——字号(复用 FONT_SIZES)/行距/边距 register 为 overrides;「纸/米/夜」映射 EPUB 背景/文字色,按 themeMode(含 auto 联动)themes.select;持久化 reader-prefs.epub。
  • 测试限制(如实记录):epubjs 渲染在 iframe 内,组件测试不可行;headless 稳定性差,本期不加 EPUB e2e/fixture。验收走手工清单:开书 → 改字号/行距/边距/主题各一次 → 视觉生效 → 进度恢复。

S5 B4 PDF(迁移)+ 书架统计卡

  • PDF:pdfjs 渲染/页码定位/书签/进度原样保留;顶栏/工具条 chrome → shadcn + radix;pdf.worker 配置不动;无新增排版设置。
  • 统计卡:Shelf.tsx 筛选栏上方可折叠卡(折叠态记忆 localStorage)——「本周阅读 X 小时 Y 分 · 连续 N 天」,展开显示近 7 日按日柱状图(纯 div 高度百分比,不引图表库);数据来自 StatsStore.weekly()/streak();书卡不加元素。
  • 测试:统计纯函数单测(B0 已列);统计卡组件测试用 fake StatsStore 注入;e2e 仅断言卡片存在(不断言时间敏感数值)。

S6 B5 清理与收严 / Cleanup & tightening

  • 删除 components/ui.ts(使用点先迁 ui/button/ui/badge)与 components/icons.tsx(→ lucide)。
  • index.css 删除全部 rd-* 组件类(rd-btn/rd-sheet/rd-range/rd-row/rd-highlight/rd-divider 等)与阅读器裸色类(stone-* 等);--rd-* token 变量与 data-rd 机制保留。验收:grep -rn 'rd-' src/ --include='*.tsx' 零命中(--rd-*/data-rd 除外)。
  • 收严清单回收:① eslint.config.js 删除 reader 豁免 override(4 条 jsx-a11y warn 回 error),迁移后代码必须真达标;② e2e/helpers/axe.ts 的 WAIVERS 清空,reader 相关页重扫零 critical/serious;③ TxtView/CbzReader 行级 disable 重估(修复或留带理由注释,AuthContext 的 disable 与阅读器无关、按 ④ 结论保留)。
  • 测试收口:设置抽屉组件测试(改字号→断言正文 style);e2e 补「设置面板冒烟」。
  • changelog:每批用户可见条目已在各批记;B5 补 Changed 条目(旧样式类删除/主题联动)。

S7 验收 / Definition of Done

  1. npm run check 绿(含 tsc 新覆盖 e2e/config);npm run e2e 绿(扩展后全部 spec)。
  2. 手工走查清单:四阅读器 × 三阅读主题(+auto 联动)× 375/768/1280 三宽 × light/dark;翻页/书签/进度恢复/沉浸收放行为与迁移前一致(新特性除外)。
  3. ui.ts/icons.tsx 已删;rd-* 组件类 grep 零命中;eslint reader override 与 axe WAIVERS 已移除。
  4. changelog(双语)与 lockfile 齐;prod 镜像无新依赖。

S8 范围外 / Out of scope

  • EPUB 书内搜索;CBZ 自动跨页检测;快捷键速查面板(backlog);
  • 阅读统计服务端化(③ 候选,本期仅留 StatsStore 接口缝);
  • PDF 排版设置;自定义字体族/字体文件;i18n;
  • 后端任何改动(API/迁移/契约零变化);
  • 书卡元素变更;书架布局改版;
  • localStorage 旧偏好兼容(用户裁定不需要)。

附录:backlog(本期未选,记录备查)

  • 快捷键速查面板(阅读页 ? 弹出);
  • CBZ 自动跨页检测(宽>高单显、相邻竖图配对);
  • EPUB 书内搜索(epubjs search API);
  • 阅读统计服务端化 + 跨设备(③ 联动);
  • ④ 遗留:--legacy-peer-deps 已落 .npmrc(闭合);vitest src/**/*.test.ts(非 tsx)静默缝隙——B0 顺手在 vitest.config 注释或 include 收紧。