13 KiB
阅读器改版(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
- 四阅读器(CBZ/Text/EPUB/PDF)chrome(头栏/工具条/目录/书签面板/主题卡)迁入 shadcn token + radix 原语,删除全部
rd-*组件类与裸色类,删除 deprecated 的components/ui.ts与components/icons.tsx。 - 「纸/米/夜」阅读主题重映射为语义 token,并实现与全局 light/dark 的「默认联动 + 手动覆盖」规则。
- 五个新特性:CBZ 横向翻页模式(长卷/单页/双页 + RTL)、txt/md 书内搜索、统一阅读设置面板(底部抽屉扩展)、阅读统计(localStorage + 可替换接口,书架顶部统计卡)、EPUB 排版设置(epubjs themes)。
- 回收 ④ 留下的收严清单:eslint reader 豁免 override(4 条 jsx-a11y warn)删除回 error、axe
WAIVERS(reader-chrome color-contrast)清空重扫、TxtView/CbzReader 的行级 disable 重估。 - 顺带修复 ④ 终审遗留的 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.jsoninclude 扩为["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→ lucideArrowLeft。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.themesAPI——字号(复用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
npm run check绿(含 tsc 新覆盖 e2e/config);npm run e2e绿(扩展后全部 spec)。- 手工走查清单:四阅读器 × 三阅读主题(+auto 联动)× 375/768/1280 三宽 × light/dark;翻页/书签/进度恢复/沉浸收放行为与迁移前一致(新特性除外)。
ui.ts/icons.tsx已删;rd-* 组件类 grep 零命中;eslint reader override 与 axe WAIVERS 已移除。- 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(闭合);vitestsrc/**/*.test.ts(非 tsx)静默缝隙——B0 顺手在 vitest.config 注释或 include 收紧。