panatgithub_AnkiHeadingSync/docs/2026-04-17PLAN3.md

5.7 KiB
Raw Blame History

模块 2将 Anki noteId 回写到标题块末尾,使用短标记 <!-- AHS:123 -->

Summary

  • 新增卡片同步成功后,把返回的 Anki noteId 写回对应标题块的块尾,格式固定为 <!-- AHS:<noteId> -->
  • 以后该标题块再次同步时,优先按这个 noteId 更新原卡,不再依赖 标题 + 行号 + 路径
  • 标记放在块尾而不是标题下方,用来明确卡片边界。
  • 老卡不迁移,只对以后新增的卡生效;老卡继续走当前 cardKey 逻辑。
  • 如果块尾 noteId 在 Anki 中不存在,自动重建新卡并替换为新的 noteId
  • 如果同一标题块从 basic 切到 cloze 或反过来,直接报错并要求用户重建。

Key Changes

1. 标记语法与块尾规则

  • 唯一支持的标记格式:
    • <!-- AHS:<正整数> -->
  • 标记位置固定:
    • 当前标题块最后一个非空内容行之后
    • 下一个同级或更高层级标题之前
  • 写回规则固定:
    • 有正文时:写在正文末尾
    • 无正文时:写在标题下一行
    • 标记本身视为块尾终止标记
  • 读取规则固定:
    • 只识别当前标题块块尾的最后一个合法 AHS 标记
    • 标记不进入 bodyMarkdown
    • 同一标题块多个合法 AHS 标记时报错
    • 非法 AHS 格式时报错,不静默忽略

2. 提取与渲染

  • CardDraft 增加 embeddedNoteId?: number
  • SourceLocation 增加块尾定位信息,至少包括:
    • blockEndLine
    • contentEndLine
    • markerLine?: number
  • CardExtractionService 负责:
    • 识别块尾 <!-- AHS:... -->
    • 从正文中剔除该标记
    • embeddedNoteId 放入 CardDraft
  • CardRenderingService 不处理身份标记,继续只处理标题和正文渲染

3. 同步身份与本地状态

  • 同步身份模式固定为三种:
    • embedded-note-id
    • legacy-card-key
    • pending-note-id-write
  • SyncRecord 增加:
    • identityMode: "embedded-note-id" | "legacy-card-key" | "pending-note-id-write"
    • legacyCardKey?: string
    • noteId: number
    • filePath
    • sourceHash
    • lastSyncedAt
    • orphan
  • SyncRegistry 必须支持:
    • findByNoteId(noteId)
    • get(cardKey) 保留兼容
  • 新卡首次成功写回 AHS 标记后,记录保存为 embedded-note-id
  • 老卡无标记时,继续保存为 legacy-card-key
  • 建卡成功但标记写回失败时,保存为 pending-note-id-write 并保留 legacyCardKey

4. 规划逻辑

  • 匹配优先级固定:
    1. embeddedNoteId 时,按 noteId
    2. embeddedNoteId 时,按当前 legacy cardKey
  • embeddedNoteId 卡片:
    • registry 有同 noteId 记录:按 sourceHash 判断是否更新
    • registry 无记录:仍视为已有卡,可直接更新
  • orphan 判定拆成两套:
    • embedded-note-id 按本轮看到的 noteId
    • legacy-card-key 按本轮看到的 cardKey
  • 老卡不自动补写 AHS,只保留当前行为

5. 执行逻辑

  • AnkiGateway 增加:
    • getNoteSummaries(noteIds: number[]): Promise<Array<{ noteId: number; modelName: string }>>
  • 执行前先校验所有 embeddedNoteId
    • note 存在且模型一致:updateNote
    • note 不存在:自动重建
    • note 存在但模型不一致:报错并停止该卡同步
  • 自动重建流程固定:
    1. addNote
    2. 用新的 noteId 替换块尾旧 AHS 标记
    3. registry 更新为新的 embedded-note-id
  • 新卡首次同步流程固定:
    1. AHS 标记,先 addNote
    2. 把返回的 noteId 写到块尾
    3. registry 保存为 embedded-note-id

6. Markdown 写回

  • VaultGateway 增加:
    • replaceMarkdownFile(path: string, expectedContent: string, nextContent: string): Promise<void>
  • 写回采用 optimistic concurrency
    • 文件内容仍等于扫描时内容才允许写回
  • 块尾写回算法固定:
    • 定位当前标题块结束边界
    • 移除旧 AHS 标记(若存在)
    • 将新标记写到块内最后一个非空内容行之后
    • 不允许落到下一个标题之后
  • addNote 成功但写回失败:
    • 不删除新建 note
    • registry 记为 pending-note-id-write
    • 以后只要还能按 legacy key 命中,就继续尝试补写 AHS
  • 补写成功后,记录转为 embedded-note-id

Public Interfaces / Types

  • CardDraft.embeddedNoteId?: number
  • SourceLocation.contentEndLine: number
  • SourceLocation.markerLine?: number
  • SyncRecord.identityMode
  • SyncRecord.legacyCardKey?: string
  • VaultGateway.replaceMarkdownFile(...)
  • AnkiGateway.getNoteSummaries(...)
  • HeadingSyncMarker
    • noteId: number
    • raw: string
    • lineIndex: number

Test Plan

  • 块尾 <!-- AHS:123 --> 能被正确识别
  • AHS 标记不会进入 bodyMarkdown
  • 同一标题块多个 AHS 标记时报错
  • 非法 AHS 格式时报错
  • 新卡创建后,AHS 被写到块尾
  • 空正文标题块时,AHS 写在标题下一行
  • AHS 的卡在改标题、改正文、块内移动、文件移动后仍更新原 note
  • registry 丢失时,带 AHS 的卡仍能按 noteId 更新
  • noteId 在 Anki 不存在时会自动重建,并替换块尾 AHS
  • basic/cloze 类型切换时报错
  • 老卡无 AHS 时继续走 legacy cardKey
  • addNote 成功但写回失败时,生成 pending-note-id-write
  • 下次命中后可补写 AHS 并转正
  • orphan 检测对 embedded-note-idlegacy-card-key 都正确

Assumptions

  • 第一版只支持块尾短标记 <!-- AHS:<noteId> -->
  • 不兼容旧的 ID: 行、长注释格式、frontmatter、block ID
  • 老卡不迁移;只有以后新增的卡获得稳定 noteId 身份
  • basiccloze 的跨类型切换第一版不自动处理
  • 允许插件改写 Markdown 文件,在标题块末尾插入或替换 AHS 标记