panatgithub_AnkiHeadingSync/docs/qa-group-12-decisions.md
Dusk 4845c721d5 feat: add #anki-list QA Group 12 route / 新增 #anki-list QA Group 12 路线
Add a parallel QA Group 12 sync path with GI writeback, fixed model management, and GroupId recovery semantics.

新增并行 QA Group 12 同步路径,支持 GI 写回、固定模型管理,以及基于 GroupId 的恢复语义。
2026-04-20 13:42:30 +08:00

191 lines
No EOL
5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# QA Group 12 Decisions
## Decision Summary
基于真实仓库结构,本轮锁定如下实现决策:
1. 保留现有 atomic/basic、cloze、semantic-qa 路线不变。
2. 新增一条并行的 QA Group 路线,使用单独 marker`qaGroupMarker`。
3. 新路线不强塞进 `IndexedCard` / `DiffPlannerService` / `AnkiBatchExecutor` 的单卡单 note 主链。
4. group route 使用专用 block model、state、write-back 和 sync executor。
5. `GroupId` 是 GI 丢失恢复的主锚点;`Src` 只做辅锚点。
6. v1 固定目标 model`ObsiAnki QA Group 12`。
7. v1 不做 group model 的 settings field mapping UI。
8. v1 的 `A` 只取第一条二级列表项文本。
## Concrete Decisions
### 1. New setting name and validation
新增设置:
- `qaGroupMarker`
默认值:
- `#anki-list`
校验规则:
- 必须符合与 semantic marker 同级的 hashtag token 规则
- 不允许等于 `semanticQaMarker`
实现上复用现有 hashtag-style validation helper而不是创建第二套正则协议。
### 2. Routing priority
QA heading 路由顺序固定为:
1. 命中 `qaGroupMarker` -> QA Group route
2. 命中 `semanticQaMarker` -> existing semantic-qa route
3. 其他命中 QA level 的 heading -> existing basic route
这样能确保两个 marker 可并存且互不吞噬。
### 3. Group route data model stays parallel
新增并行模型,不复用 `IndexedCard` 作为 group block 的核心表示。仓库中应存在这些概念:
- group marker representation
- group item representation
- indexed group block representation
- group note payload builder
- recovered group state
原因:
- 当前 `IndexedCard` 强绑定单 `noteId`
- 当前 `PluginState.cards` 强绑定 noteId-keyed atomic state
- Group 12 需要在一条 note 内维护 slot identity
### 4. Parser rules for v1
v1 固定规则:
- `Stem` = heading 去 marker 后的文本
- `Q` = 一级列表项文本
- `A` = 第一条二级列表项文本
- deeper nested content 不进入 `A`
- fenced code blocks 不能被误判成列表
- 块尾允许存在一条 `GI`
无效一级项定义:
- 没有二级列表项
处理策略:
- 直接跳过,不生成 group item
### 5. GI protocol is explicit and strict
采用目标格式:
```md
<!--GI:n=999;i=a1:1,b2:2,c3:3;f=-->
```
语义锁定:
- `n` = noteId
- `i` = itemId -> slot
- `f` = free slots
附加规则:
- GI 固定写在 group block 尾部
- group write-back 只保留一条 GI
- 命中 group route 的块会清理内部旧 `<!--ID: ...-->`
### 6. Item identity strategy
首次进入组时,插件为每个 item 分配短 token `itemId`
本轮采用:
- 轻量 deterministic short token generator
- 一旦分配并写入 GI/state后续重排不改 `itemId`
slot 规则:
- 首次 `1..N`
- 删除中间项时进入 `freeSlots`
- 新增项优先复用最小 free slot
- 重排不改 slot
- 超过 12 项直接阻止同步
### 7. Recovery order
GI 丢失恢复顺序固定:
1. 先查本地 group state by `GroupId`
2. 若本地无充分信息,再查询 Anki 中同 model note 的 `GroupId`
3. `Src` 仅用于辅助缩小范围和校验唯一性
4. 唯一命中可重建 GI
5. 多命中返回 warning/error不自动认领
6. 未命中则走首次创建
### 8. Model management strategy
`AnkiGateway` 扩展 group model 管理接口,至少支持:
- ensure model exists
- inspect fields
- inspect templates
- create model
- add fields
- add templates
- update templates
- update styling
- query note ids for recovery
- read note fields for recovery
但这些能力只为 group route 服务,不反向改造 basic/cloze/semantic-qa 的 note field mapping 工作流。
### 9. Execution integration point
group route 的执行入口放在 `ManualSyncService` 中,与现有 atomic sync 并列。
原因:
- `ManualSyncService` 已持有 index / render / execute / write-back / save state 的总编排能力
- 在这里并联 group 流最容易共享 deck / vault / repository / gateway 能力
- 也最容易保证旧路线不受影响
### 10. Settings UI scope control
只新增最小设置项:
- `QA Group marker`
本轮不新增:
- group model selector
- group field mapping panel
- group preview complex panel
原因:
- v1 模型固定
- 字段协议固定
- 复杂 UI 会扩大 scope 而不增加正确性
## Intentional Deviations From The Reference Plan
### 1. 不把 group route 接进现有 `DiffPlannerService`
这是对参考方案的结构性调整。
原因:当前仓库的 planner/executor/state 都是单卡单 note 语义;强行复用会显著增加回归风险。
### 2. parser 层不引入大而全 warning 总线
原因:当前仓库没有统一 parser warnings 模型。本轮只在需要阻止同步的地方返回结构化错误,其余无效项直接跳过。
### 3. `A` 只取第一条二级列表项纯文本/markdown 内容
原因:这是最符合当前仓库复杂度预算的 v1 规则,也更容易保证 slot 稳定与恢复逻辑清晰。
### 4. 不做 group field mapping UI
原因:目标 model 固定,直接生成完整 field payload 更适合当前代码结构。