Skip to content

17 — word_cloze_contexts 跨端同步:RVH 侧镜像完成确认

物理位置:本文件在 RVH 仓库 ~/reading_vocab_helper/docs/cross-end/17-rvh-cloze-context-sync-confirmation.md。 对接 RB 交接文档 ~/reading-browser/docs/cross-end/16-rb-cloze-context-sync-handoff.md(RB 先行,本文件回确认)。

创建:2026-07-10 · RVH 镜像完成 · 关联 schema v57


0. 一句话结论

RB 交接文档 §3 的三件事全部落地:① 本地建表(schema v57);② sync 双向适配(忠实复刻 RB 合并算法, 含 reconcile_cloze_pool);③ 复习卡消费真实语境(背面抽屉挖空块)。RVH 定位 pull-only 消费 (不新增本地采集路径,仍参与 reconcile 去重封顶 + 删词/切用户软删墓碑 push 传播收敛)。


1. RVH 侧已完成清单(对照 RB §3)

§3.1 本地建表

  • [x] 新建本地 word_cloze_contexts,schema 对齐 RB §1.1(assets/sql/01_create_tables.sql)。表数 10→11。
  • [x] idx_cloze_word(user_id, word COLLATE NOCASE, created_at DESC)02_create_indexes.sql)。
  • [x] wordCOLLATE NOCASE,与 learning_entries.word 同一归一口径(红线 #9,写入的是 lemmatizer.normalize 后的 lemma)。
  • [x] 无 UNIQUE、无 FK(多语境池 + v16 松耦合);source_platform(与远端一致)。
  • [x] _schemaVersion 56→57(app_database.dart);三个 SQL 头部 + schema.md + CHANGELOG 同步。开发期删库重建。

§3.2 sync 适配(lib/features/sync/data/repositories/sync_repository_impl.dart

  • [x] _pushWordClozeContexts:脏检查(synced_at IS NULL OR updated_at>synced_at OR 墓碑新增)+ mapped 10 列 (不含 source_platform)+ onConflict='user_id,word,sentence'(红线 #5f)+ markSynced by id。
  • [x] _pullWordClozeContexts:墓碑三分支(remote删+本地有→墓碑 / 本地已删+remote活→skip / 本地无+remote删→skip) + 双活 sense_gloss 非空回填(双非空不覆盖)+ 本地无+remote活→INSERT OR IGNORE(synced_at=created_at)。
  • [x] _reconcileClozePool(本表独有,忠实复刻 RB pull.rs:771):① 按句 ASCII 折叠去重(幸存=created_at 最早, gloss 同组回填,其余软删)② 去重后 >cap 软删最旧超额(correlated subquery LIMIT max(0,count-cap))。 去重键用 _asciiLower(仅 A–Z),匹配 SQLite COLLATE NOCASE / RB to_ascii_lowercase不用 Dart Unicode toLowerCase
  • [x] syncNow 编排:push 循环 + pull 段(均置尾,无 FK)+ observe() watermark;force-full-pull 计数 + getSyncStatus pending 计数各加 cloze 行。
  • [x] _clozePoolCap=5(与 RB CLOZE_POOL_CAP 同值)。

§3.2 撤销/删除路径软删

  • [x] 删词级联:notebook_datasource.deleteNotebookEntry 软删 learning_entry 后,按 (user_id, word) 软删其 cloze 语境 (镜像 RB clear_cloze_context_for)。软删而非硬删 → 墓碑 push 传播回 RB。
  • [x] 用户切换清理:auth_repository_impl.clearLearningDataForUserword_cloze_contexts(按 user_id)。

§3.3 复习卡消费("⑥ cloze v2" 主体)

  • [x] 批量加载:notebook_datasource 三个 due 查询(ByGroup/ByDate/ByDateRange)跑完后单次 IN 查询附着语境池 (镜像 RB srs.rs step 2,created_at ASC, id ASC 稳定序,按 lower(word) 分组)。
  • [x] ReviewWordDto / ReviewWordItemclozeContexts: List<ClozeContextInfo>;loader 两处构造点透传。
  • [x] ClozeBuilderdomain/services/cloze_builder.dart,移植 RB src/lib/cloze.ts):整词多形态挖空防同根泄露、 句长 8..220 护栏、pickInitialContextIdx = repetitions % n 确定性轮换、blankWidth 3–12。
  • [x] UX 落点 = 背面释义抽屉DefinitionDrawer):RVH 复习卡正面是「原文聚光灯」(图/文本上高亮词), 不套用 RB 的正面挖空范式。当词有可挖空语境 → 抽屉顶部渲染「你读到的原句」挖空块(下划线遮空 + 答案揭晓 + sense_gloss 非空时义项显示),其下照常渲染 pos_definitions 策展例句;无语境完全回落现状。
  • [x] 多语境轮换:对齐 RB 语义(repetitions % 语境数),从轮换起点环形扫描第一条可挖空语境。

2. 与 RB 的差异(有意)

维度RBRVH原因
采集content-script 双击查词入池无本地采集(pull-only)RVH 无阅读/双击查词流;交接 §3 未列采集任务
复习正面文本挖空句原文聚光灯(图/文本高亮词)RVH 既有复习范式;cloze 落背面抽屉
消费落点position list + 正面挖空背面抽屉挖空块见上;Phase 4 UX 决策

RVH pull-only 下 push 只传播 reconcile / 删词 / 切用户产生的软删墓碑——不会产生 RVH 独有的活语境行 (RVH 不采集)。收敛方向:RB 采集 → Supabase → RVH pull + reconcile → RVH push 墓碑 → RB pull 收敛。


3. 请 RB 侧跟进

  • RB 交接 §4 留的口子:RVH 镜像已完成,可把 word_cloze_contexts 加入 scripts/cross-end-check.shSHARED_SYNC 数组(此前为避免对 RVH 误报缺表未加)。
  • 前置:Supabase user_word_cloze_contexts 表 DDL 需已部署(RB 交接 §5)。RVH sync 依赖其存在;未部署时 RVH pull 报错。

4. RVH 侧验证状态

  • [x] flutter analyze — lib/ 0 error(test/ 既存债务与本次无关)。
  • [ ] 删本地 dev DB 重启 → schema replay 到 v57、word_cloze_contexts 建成(待设备验证)。
  • [ ] 登录触发 sync → rvh_supabase_query user_word_cloze_contexts 确认远端行 + rvh_db_query 确认本地落行/synced_at 回写/reconcile ≤5(待设备验证)。
  • [ ] 复习有 cloze 语境的词(尤其 RB 采集、RVH 无源图的词)→ 背面抽屉出现真实语境挖空块(待设备验证)。

落地代码见本批 commit + plan rvh/docs/plans/word-cloze-contexts-sync-rvh-plan.md