Skip to content

发版前冒烟 Runbook(rb-debug MCP 驱动)

创建:2026-07-22 · roadmap 5-5(docs/plans/product-iteration-roadmap-2026h2.md §五) 性质:发版前对运行中的 dev app 跑一遍的半自动冒烟清单。不是 CI 门(CI 只 --lib 单测 + build); 也不是全自动 E2E(tauri-driver 不支持 macOS WKWebView,见 roadmap 5-5)。

执行环境:一个「Claude + rb-debug MCP」会话 + 运行中的 dev app(pnpm tauri dev)。 断言纪律:用客观只读探针rb_db_query / rb_state / rb_supabase_query / rb_logs)验证状态, 不靠截图肉眼rb_snapshot 最贵,只在纯视觉观感存疑时用;见 memory feedback_mcp_token_economy)。 驱动步骤分两类:🤖=MCP 可驱动(rb_click/rb_type/rb_scroll);🙋=需人工动作(如双击查词、提供账号)。


前置

  1. 启动 dev app:终端 pnpm tauri dev(首次编译数分钟)。
  2. 准备一个测试 Supabase 账号(sync 流程需要;勿用真实用户数据)。
  3. 准备一个本地 .epub 测试文件路径。
  4. 确认 rb-debug MCP 连通:rb_state store=tabs 返回而非 fetch failed

本 runbook 尚未在 dev app 上实跑验证(2026-07-22 写就时 app 未运行)。首次执行时若某条 断言/选择器与实际不符,就地修正本文件(它是活文档)。


Flow 0 — 启动 + 首页渲染 + 登录门

🤖 驱动rb_state store=tabs
✅ 断言返回成功;tabs 含 id:'home'(首位,type='web' url='')+ id:'review'(type='review-source',webviewLabel='content-review');activeTabId 存在
🤖 驱动rb_state store=auth
✅ 断言未登录时 SPA 门在登录页(session 为空);已登录则 session 非空
🤖 兜底首页结构存疑时 rb_dom selector="[data-testid=home], main"不要上来就 rb_snapshot)

Flow 1 — 登录 → 自动同步启动

🙋/🤖 驱动rb_type 填邮箱/密码输入框 + rb_click 登录按钮(选择器以 AuthPanel 实际为准)
✅ 断言rb_state store=auth → session/access_token 非空、auth_user_id 有值
✅ 断言rb_state store=auth → 同步生命周期启动(轮询状态字段,见 useAuthStore)
⚠️ 隐私用测试账号;截图会带账号信息,本流程避免 rb_snapshot

Flow 2 — 开网页 → 双击查词 → 保存生词 → 库可见(核心闭环)

🤖 驱动rb_type 地址栏输入一个英文网页 URL + 回车/rb_click 导航
✅ 断言rb_state store=tabs → 新增 web tab、activeTab 指向它;rb_logs tail=20 无红错
🙋 驱动在 content webview 里双击一个生词(content-script 的 lookup 挂 dblclick;rb_click 只合成单击,双击需人工
✅ 断言查词弹窗出现:rb_dom selector=".rb-word-popup, [data-rb-popup]"(选择器以 WordPopup 实际为准)
🙋 驱动点弹窗「加入生词本」(含语境来源的入口;见 memory project_notebook_requires_context
✅ 断言rb_db_querySELECT word, added_at FROM learning_entries WHERE deleted_at IS NULL ORDER BY added_at DESC LIMIT 3 → 目标词出现
✅ 断言语境落库:SELECT word, sentence FROM word_cloze_contexts WHERE deleted_at IS NULL ORDER BY created_at DESC LIMIT 3 → 该词的真实句入池
✅ 断言来源关联:SELECT wpl.word FROM word_page_links wpl WHERE wpl.deleted_at IS NULL ORDER BY rowid DESC LIMIT 3 + reading_pages 有对应源
🤖 驱动打开 Library/Vocab,rb_dom 确认该词行渲染(WordRow)

Flow 3 — 复习一张卡 → SM-2 状态推进

🤖 前置先取基线:rb_db_querySELECT word, repetitions, next_review_date, mastery_level, last_review_quality FROM learning_entries WHERE deleted_at IS NULL AND next_review_date <= datetime('now') ORDER BY next_review_date ASC LIMIT 1(实跑确认此查询有效,未复习词 next_review_date=epoch 即到期)
🤖 驱动 · 进复习模块✅ 2026-08-07 实测点通rb_click selector="nav[aria-label] button:nth-of-type(3)" webview=main → 断言 rb_state store=workspaceactiveModule: "review"位置选择器,不吃 locale(rail 顺序:1 发现 / 2 阅读 / 3 回顾 / 4 词汇与笔记 / 5 学习统计)。原「title/aria 中英混用不可靠」的障碍由此绕开——不要再用文案选。
🙋/🤖 驱动 · 开始 session + 揭晓picker 选组 → 点卡面揭晓。未固化(本轮无到期卡,没能实跑)。
🤖 驱动 · 评分从源码推导、待首跑验证:Hard/Easy 的文案是硬编码英文ReviewSession.tsx:296/302<div class="text-sm">Hard/Easy</div>),不吃 locale;两个按钮是评分条里仅有的 flex-1 + font-semibold 按钮 → button[class*="flex-1"][class*="font-semibold"],第 1 个 = Hard,第 2 个 = Easy。⚠️ 揭晓前它们 pointerEvents:none + aria-hidden必须先揭晓再点。⚠️ 会推进真实卡片排期(低危、正常使用)
✅ 断言同一 word 复查:repetitions 变化符合 SM-2(Easy 首次 reps→1;已推进则 +1);next_review_date 前移;last_review_quality 更新
✅ 断言与黄金向量一致性:结果应落在 docs/cross-end/sm2-golden-vectors.json 覆盖的行为域内(单测已锁纯函数,此处验端到端落库)
✅ 断言rb_state store=review → 卡片状态机推进(currentCard/reviewedCount)

Flow 4 — 本地 EPUB 打开 → 章节导航 → 划线持久化

⚠️ 2026-08-07 重写:原 Flow 4 叫「位置恢复」,断言「恢复到上次阅读位置(TOC/滚动位置)」。 roadmap 3-5 验收证明产品根本没有这个行为(书/章/章内偏移三层全不恢复addEpubTab 永远落 chapters[0];见 epub-longform-acceptance-handoff.md E7 + backlog B4)。 一条永远不可能通过的断言会让 smoke test 要么长期挂红、要么被习惯性忽略——两种都比没有更糟。 故本 flow 改为验「打开 → 章节导航 → 划线跨会话持久」这条真实存在的闭环; 位置恢复在 backlog B4 修复后再把断言加回来。

🙋 驱动地址栏 📂 → 选本地 .epub。只能人工:这是 macOS 原生文件面板,rb_click 只触发页面 JS 事件、碰不到原生窗口;地址栏也不接受本地路径(useNavigation.ts:78 一律按 URL 处理)
✅ 断言rb_state store=tabs → epub tab(type='epub'epubInfo.toc 非空,url 形如 rb-cache://localhost/epub/<hash>/…
✅ 断言rb_db_querySELECT source_ref, name, source_type, last_opened_at FROM reading_pages WHERE deleted_at IS NULL ORDER BY last_opened_at DESC LIMIT 3 → 该书 source_type='book'注:路径列名是 source_ref 不是 uri;标题是 name(2026-07-22 校正);source_ref 对 EPUB 带 #锚点(2026-08-07 补)
⚠️ 已知打开后落封面目录不自动开(backlog B1);reading_pages 的行只在存词/划线时才产生,光打开不建行MainApp.tsx:278 让 epub 跳过 logResourceOpen
🤖 驱动 · 开目录 + 跳章✅ 2026-08-07 实测点通:TOC 条目是 [class*="space-y-0"] > *:nth-child(N)(主 webview,div[role=button]),N 从 1 起。先用 rb_dom 同选择器读 span 文本确认落点,再 rb_click
✅ 断言内容 webview 到位:rb_dom selector="#<该章锚点 id>" webview=content-<tabId>found: true
🙋 驱动 · 划线选中一句 → 划高亮。未固化(选区 + 浮动工具栏,rb_click 不产生真实 Selection)
✅ 断言rb_db_querySELECT annotation_type, user_id, substr(source_text,1,40), created_at FROM page_annotations ORDER BY created_at DESC LIMIT 3 → 有 highlight 行、user_id = 当前登录用户
🙋 驱动 · 跨会话关掉该 tab → 从「最近打开」重开(不要点「继续阅读」——那条对 EPUB 是断的,backlog B3)
✅ 断言重新跳到同一章后 rb_dom selector=".rb-annotation" webview=content-<新 tabId>total_matches 与划线数一致(✅ 2026-08-07 实测得 2)。rb_logs 无恢复报错
💡 多标签对照法需要 A/B 时可开两个 epub tab 各自导航,再分别数 .rb-annotation——切标签用 rb_click selector="[title*=\"<书名片段>\"]" webview=main✅ 实测可用;同名多标签时用 :first-of-type 取靠前那个)

Flow 5 — sync 双向(本地 ↔ Supabase 真相对账)

🤖 驱动触发一次同步(登录后自动轮询,或手动入口 rb_click
✅ 断言 push(首选)rb_state store=authsyncStatus.pending_push == 0syncStatus.last_sync_at 晚于 Flow 2 存词时刻 → push 已完成
✅ 断言本地 synced_at 卫生:rb_db_querySELECT word, (synced_at IS NOT NULL) synced, (synced_at >= updated_at) clean FROM learning_entries WHERE deleted_at IS NULL ORDER BY added_at DESC LIMIT 5 → 全部 synced=1 clean=1
✅ 断言rb_logs tail=30 无 sync 错误(无 23503 FK / 无 watermark 异常)
⚠️ 环境限制rb_supabase_query(Supabase 真相直查)在 CN 网络稳定失败("error sending request")——MCP server 直连 supabase.co 不走系统代理(2026-07-22 实跑确认)。故 push 成功改用 app 自身 sync 状态断言(上面首选项);app 走自己的网络栈能同步。跨设备 pull 验证需第二端

通过标准

  • Flow 0/2/3/4 必过(核心闭环 + 阅读器 + 复习算法端到端)。
  • Flow 1/5 有测试账号时必过;无账号时记为 skipped 并在发版说明标注「sync 未冒烟」。
  • 任一 rb_logs 出现未预期红错 = 不通过,定位后再发版。

实跑校准记录

2026-07-22(首次实跑,账号 <测试账号 A>:只读断言层全部对真实数据校准通过;发现并修正:

  • Flow 4:reading_pages 列名 urisource_ref(URL/路径)、标题列 name(原 runbook 写错,已修)。真实数据含一本本地 EPUB(source_type='book')last_opened_at 有值 → EPUB 追踪闭环验证 ✓。
  • Flow 5:rb_supabase_query 在 CN 网络稳定失败(MCP server 不走代理)→ 改用 app sync 状态断言(pending_push=0 + synced_at≥updated_at),已验证 ✓。
  • Flow 0:review tab webviewLabel = content-review(已补)。
  • Flow 2:learning_entries / word_cloze_contexts 列名与查询全部正确,真实存词(unlock/spread/fungus)+ 语境池 + gloss 均落库 ✓。
  • Flow 3:due 卡基线查询有效 ✓;复习 UI 驱动选择器未固化(图标按钮,title 中英混用),下次实跑定位回填。
  • 未实跑的写驱动步骤(存词双击、复习评分):因改动真实数据 + 选择器待定,本次只校准断言层;写路径已由单测(roadmap 5-1)+ 黄金向量三端(5-2)锁死。

2026-08-07(roadmap 3-5 EPUB 验收同轮顺手固化,账号 <测试账号 B> / <test-user-id-A>

已实测点通的选择器(全部位置/结构型,不吃 locale——原「title 中英混用不可靠」的障碍由此绕开):

  • 进复习模块:nav[aria-label] button:nth-of-type(3)rb_state store=workspaceactiveModule:"review" ✓ (rail 顺序 1 发现 / 2 阅读 / 3 回顾 / 4 词汇与笔记 / 5 学习统计)
  • TOC 条目:[class*="space-y-0"] > *:nth-child(N)(主 webview 的 div[role=button])✓
  • 切标签页:[title*="<书名片段>"](同名多标签用 :first-of-type)✓
  • 划线计数:内容 webview 里的 .rb-annotationannotations.js:14 MARK_CLASS;持久化与临时标记同类名, 临时的多一个 rb-annotation-transient)✓
  • /debug/click(= rb_click)对主 webview 的 React onClick 有效(合成 click 事件会被 React 委托监听接住)✓

仍需人工的写驱动不是选择器问题,是能力边界,别再试):

  • 打开本地 .epub → macOS 原生文件面板,合成事件碰不到;地址栏不接受本地路径
  • 双击存词 / 选区划线 → 需要真实 Selection 与 dblclick,/debug/click 只发单次 click

Flow 4 已按 roadmap 3-5 结论重写:原「位置恢复」断言断言了一个产品不存在的行为 (三层全不恢复,见 epub-longform-acceptance-handoff.md E7),改为验「打开 → 章节导航 → 划线跨会话持久」; 位置恢复待 backlog B4 修复后再加回。

Flow 3 评分按钮:源码推导出 button[class*="flex-1"][class*="font-semibold"](文案硬编码英文 Hard/Easy, 不吃 locale),本轮无到期卡未能实跑,首跑时验证并把此行改为 ✓。

与其他质量门的关系

覆盖何时
CI(ci.yml)编译 + 纯函数/算法单测 + i18n 门每次 push/PR 自动
本 runbook真机端到端闭环(UI+webview+DB+sync)发版前手动一遍
/release skill打包 + cfg-gate + tag 双推发版编排

建议:/release 流程 Step 1 前先跑本 runbook。未来 flows 稳定后可把纯 DB 断言部分抽成脚本进一步自动化。