Skip to content

fix+feat(vocab): A2/B1 富字段回填 + A1 取数 API 化(ADR-0014) - #61

Merged
ROM4n2 merged 5 commits into
masterfrom
fix/rich-fields-backfill
Sep 17, 2026
Merged

ROM4n2 merged 5 commits into
masterfrom
fix/rich-fields-backfill

Conversation

@ROM4n2

@ROM4n2 ROM4n2 commented Sep 16, 2026

Copy link
Copy Markdown
Owner

按 ADR-0014 顺序执行:A(富字段回填)→ C(ADR 归档)→ B(A1 取数统一)。

解决的问题

1)A2/B1「只有部分词有例句」——根因是字段冻结,不是缺词。
sync{A2,B1}CardsFromServer 原为 append-only(if (!existingIds.has(wid)) 无 else),存量词条首次同步后就再不会被更新 → 永远停在 ex:[]
修:新建分支逐字不变,新增 else 只补空字段ex/ipa,B1 另含 gender/plural)——不覆盖非空(保护手编例句)、只动词表、不碰 cards/log/wrong、幂等。进 A2/B1 档即自愈。
另:备考域卡片例句块改条件渲染(空值不再输出孤立标签 + 空行)。

2)A1 取数统一(ADR-0014 §4-2)

  • S1:输出契约 11 → 12 字段(+letter)。必须下发而非前端派生:实测 letterOf 会在 10 条上漂移(O↔ÖU↔ÜK↔-A↔S)。
  • S2:a1WordFromApi / a1WordsFromInline / bootstrapA1Words——API 优先 → 内联兜底 → localStorage 缓存wb.a1.src.v1(server/inline)让「可稍后刷新重试」真实可重试,升级时按「只增 + 只补空」合并,不覆盖用户手编、不动 FSRS;挂起期 renderA1BootLoading() 占位(不误报空态/已完成)。
  • S3:裁决保留内联作离线 fallback(不删)——file:// 直开是 ADR-0011 明示保护的能力,本地服务下 124KB 收益近零。由 G1/G2/G3 三条守卫钉死「内联绝不当主路径」。

关键决策(ADR-0014)

  • 词表源头不入库:不可变/版本化/全量读/随发布走 → 代码常量;可变/事务/增量 → SQLite。唯一例外 = FTS5 派生索引(源头仍为代码)。
  • 词表同步语义固化:服务端 → 本地 MUST「只增 + 只补空 + 幂等 + 不碰 cards/log/wrong」。

门禁

  • 定向 134 passed;分半 A(--ignore=test_server)759 passed(2 条 no such table: exam_trials 为既有 Windows 环境失败,已用无关文件组合复现证实);分半 B test_server 228 passed + 1 skipped
  • 全探针零漂移(14 个 tools/*.mjs
  • ruff 全绿 / mypy 120 文件 0 error
  • 新探针均接入 pytest / CIwb_rich_backfill_probe.mjs(19 场景)、wb_a1_bootstrap_probe.mjs(10 场景)
  • 变异验证:去挂载起守卫 / 改整体覆盖 / 内联重回主路径 → 对应测试必红

⚠️ 合并后必做

改动含 static/Android 必须覆盖安装(桌面端即时生效)。点检:A1 五档可见、A2/B1 例句音标齐全、进度零丢失、字母分组一致。ADR-0014 §6-S4。

- 根因:sync{A2,B1}CardsFromServer 为 append-only(if (!existingIds.has(wid)) 无 else),
  存量词条(富字段上线前首次同步)永远停在 ex:[] → 用户看到「只有部分词有例句」
- 修复:新建分支行为逐字不变;新增 else 分支「只补空字段」(ex/ipa,B1 另含 gender/plural),
  绝不覆盖非空(保护手编例句/音标),不碰 hw/gloss/tags/id,只动词表(FSRS 进度零接触),幂等
- 进 A2/B1 档即自愈(wordFilters.scope 不持久化,切换必然触发 sync)
- a1_cards.js:备考域卡片背面/列表卡片例句块改条件渲染(空值不输出孤立标签+空行)
- 探针 tools/wb_rich_backfill_probe.mjs(真实切片 + node:vm 真跑,19 场景,--json 模式)
  已由 pytest 驱动接入 CI(test_rich_backfill_probe_reports_no_failures)
- 回归:749 passed(--ignore=test_server,2 条为既有 Windows 环境失败)+ test_server 228 passed/1 skipped
  = 977 passed + 1 skipped;13 探针零漂移;ruff/mypy 全绿
- Vault 01-ADR/0014:词表源头不入库(FTS5 仅派生索引)/ A1 取数 API 化 / 同步语义固化
- PROJECT_OVERVIEW 增 ADR-0014 引用 + Task A(32a8f0f)门禁数据
- _contract_item 增 letter 形参/输出(唯一构造点);reader 分支由内联 dict 收敛到它
- A1 分支逐字透传 seed 原值(不派生:letterOf 会在 10 条上漂移 O<->Ö / U<->Ü / K<->- / A<->S)
- 非 A1 分支(A2/B1/reader)取 hw 首字母大写,与前端现行派生逐字一致
- 同步 4 处字段集守卫(contract_uniform / sources / a1_workbench_source / test_server)
- 新增 3 条 TDD:A1 seed 逐条等价 / 非 A1 口径 / 反向守卫防派生
- 回归 752 passed(--ignore=test_server,2 条既有环境失败)+ test_server 228 passed 1 skipped;ruff/mypy 全绿
- 新增纯函数 a1WordFromApi(服务端 12 字段→前端词对象,逐条 normalizeWord,letter 取原值不派生)
  与 a1WordsFromInline(旧内联建表原样抽出,全文件唯一一份)
- loadAll 首装分支改为置空表 + 挂起标记;async bootstrapA1Words:fetch 成功覆盖/合并,
  失败回退内联并 toast;挂起期集中式 renderA1BootLoading 占位(不误报空态/已完成)
- 黄牌② 引入来源标记 wb.a1.src.v1(server/inline):曾兜底的设备下次启动真实重试,
  成功后按 ADR-0014 只增+只补空合并(mergeA1ServerWords,不覆盖手编、不碰 cards/log/wrong)
- 红牌① 三处启动 saveWords 加挂起守卫 + saveWords 顶部统一兜底闸(挡挂起+空表,
  不误挡首装成功路径)+ bootstrap 补跑 migrateSeedIdAliases
- 黄牌③ bootstrap 尾部 try/catch/finally + 调用点 catch,杜绝永久加载态
- 探针 tools/wb_a1_bootstrap_probe.mjs(真实切片 + node:vm,8 场景,含挂起不落盘 / inline 重试)
  经 pytest 驱动接入 CI;变异验证:去守卫→场景 A 红、改覆盖→场景 B 红
- 回归 756 passed(--ignore=test_server,2 条既有环境失败)+ test_server 228 passed 1 skipped;ruff/mypy 全绿
- 裁决:不删 SEED_WORDS/CORE_CUSTOM_WORDS(file:// 直开是 ADR-0011 明示保护的离线能力)
- 两常量上方注释标注「离线 fallback,非主路径数据源」+ 指向 ADR-0014 §6-S3
- 新增守卫 G1/G2/G3:loadAll 体内无 SEED_WORDS.map(;全文件 a1WordsFromInline( 恰 2 处
  且调用点只在 bootstrapA1Words 内;成功路径不引用内联(三个变异实跑验证非死断言)
- 探针新增场景「内联仅作离线兜底」:服务端可用 inlineCalls=0 / 服务不可用 inlineCalls=1
- ADR-0014 §6-S3 同步改写;PROJECT_OVERVIEW 实施状态更新(S1-S3 完成,S4 点检待做)
- 134 passed(定向)+ 全探针零漂移 + ruff/mypy 全绿
@ROM4n2
ROM4n2 merged commit ce854c5 into master Sep 17, 2026
1 check passed
@ROM4n2
ROM4n2 deleted the fix/rich-fields-backfill branch September 17, 2026 05:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant