mirror of
https://github.com/panatgithub/AnkiHeadingSync.git
synced 2026-07-22 06:51:43 +00:00
- 将 marker、state、writeback 的 durable identity 改为 noteId - Switch marker, state, and writeback durable identity to noteId - 支持满足标题规则的旧 ID 卡片重接进并按当前 renderer 重渲染 - Reconnect legacy ID cards that match heading rules and rerender them with the current renderer - 补充 schema migration、测试、构建验证与插件同步 - Add schema migration, tests, build verification, and plugin sync
4.6 KiB
4.6 KiB
ID NoteId Mainline Decisions
Decision Summary
本轮实现严格按如下决策收口:
<!--ID: noteId-->是 manual-sync 主链唯一 marker 协议noteId是 durable identitysyncKey只用于一次 sync 运行内的关联,不落盘- 旧
IDmarker 卡片只在“满足当前标题级别规则”时才参与认领/重渲染 - 认领成功的旧卡一律使用当前 renderer 重渲染更新,不保留旧 HTML 风格
- 已有 AHS-marker 文件本轮不做兼容,不做迁移
Concrete Decisions
1. Marker service 直接重构,不保留 AHS dual mode
- 继续沿用
CardMarkerService这个文件名以控制改动范围 - 但服务语义改成 ID marker service
- 删除
generateCardId()及所有 AHS-specific create/parse 逻辑 create(noteId)永远输出<!--ID: <noteId>-->
原因:
- 本轮契约明确不支持 dual marker
- 保留 dual mode 只会把旧 AHS 分支继续带进主链
2. IndexedCard 保留 noteId?,新增 syncKey
- durable
cardId从IndexedCard删除 - 新增
syncKey = filePath + "\0" + blockStartLine + "\0" + rawBlockHash idMarkerState固定为:missing | present-valid | present-invalidnoteIdSource固定为:marker | state-recovery | pending-writeback | undefined
原因:
syncKey适合在 planner/executor/writeback 间做单次运行内关联- 不需要再把 cardId 作为系统稳定主键
3. Persisted state 全部切到 noteId 主身份
PluginState.cards保持字段名,但 key 改为String(noteId)CardState.noteId改为必填CardState.cardId删除FileState.cardIds改为noteIdsPendingWriteBackState改为:filePathblockStartLinerawBlockHashtargetNoteIdexpectedFileHashtargetMarker
原因:
- 这正好对应本轮 durable identity contract
- pending writeback 的匹配条件也必须改成块身份,而不是 cardId
4. Repository load 承担 schema migration
- 迁移点放在
DataJsonPluginStateRepository.load() - 旧
cards[cardId]如果带合法noteId,迁成cards[String(noteId)] - 旧
files[].cardIds迁成files[].noteIds - 旧
pendingWriteBack一律丢弃
原因:
- 这是最靠近持久化边界的位置
- 可避免把 schema upgrade 逻辑散落到 service 层
5. Identity resolution 按契约固定顺序实现
- 先 valid trailing ID marker
- 再唯一 same-file rawBlockHash state recovery
- 再 pending writeback 命中
- 否则
noteId = undefined
补充决策:
- rawBlockHash 基于剥离 trailing valid/invalid ID marker 后的正文计算
- duplicate noteId claim 时,只保留首次 claim,后续 claim 降级为
undefined
原因:
- 这能同时满足旧卡重接进、marker 被手动删掉后的恢复,以及创建路径的确定性
6. Diff planning 不引入额外兼容模式
toCreate、toUpdate、toChangeDeck、toRewriteMarker、toOrphan按用户契约原样实现- blocks not matching heading rules 在 index 阶段就不进入 planner
原因:
- 避免 planner 再承担“忽略不匹配标题块”的第二层分支
7. Executor 用 syncKey 做 run-time correlation
resolvedNoteIds改为Map<string, number | undefined>,key 是syncKeytouchedCardIds改名并改语义为 touched note/sync keys- marker write map 以
syncKey为 key - rendered cards map 也以
syncKey为 key
原因:
- 这能把“本次运行内引用”与“持久 identity”彻底分开
8. Reconnect success always means re-render
- 只要 block 满足当前标题级别规则
- 且
<!--ID: noteId-->合法且 Anki 中 note 存在 - 就认领该 note
- 然后用当前 renderer 重新计算 fields 并更新该 note
原因:
- 这是本轮明确的产品契约
- 不再引入 preserve legacy HTML mode
9. Not matching heading rules means total silence
- 不解析 marker
- 不 strip marker
- 不访问 Anki
- 不创建、不更新、不写回、不 warning
原因:
- 这是本轮必须实现的静默忽略语义
10. Test strategy
- 重写 marker/index/planner/writeback 的单元测试到新协议
- 为旧
IDmarker 接管行为补 integration tests - migration 行为至少覆盖 repository load 级别或 service 可观察级别
原因:
- 现有测试大多锁定 AHS/cardId 旧契约,不能继续作为主保护层
Intentional Non-Decisions
以下事项本轮不做延伸决策:
- AHS marker 文件后续怎么迁
- 是否需要独立 migration command
- 是否给 ignored
<!--ID: ...-->块做可视化提示 - 是否为旧样本提供 heading policy compatibility mode
这些都超出本轮范围。