|
| 1 | +# v0.13.2 设计规格:概念双面体(模型卡 × 概念) |
| 2 | + |
| 3 | +> 状态:**已批准**(2026-08-23 上层审斡通过,含修正:升格锚点追加为独立行 `\n→ 概念「name」`,与 back_has_anchor 整行前缀判定一致防重复回链) |
| 4 | +> 依据 [ADR-024](../../adr/ADR-024-knowledge-system-layer.md) + [整合分析 §四.3/§六/§七](../../Foresight/personal-knowledge-system-layer-integration.md) + [v0.13 系列文档](../../versions/v0.13.md);需求登记 REQ-206~207 |
| 5 | +> 范围:v0.13.2 概念双面体——**不含** v0.13.3+(决策/应用/审计/交叉点)、AI 能力、概念→卡反向 |
| 6 | +
|
| 7 | +## 一、总架构 |
| 8 | + |
| 9 | +自底向上四层(AGENTS.md §3 纪律)——与 v0.13.1 同构,仅新增一原子模块与一命令模块: |
| 10 | + |
| 11 | +``` |
| 12 | +原子层:knowledge_pure.rs(已交付不动——promote_rules 本版只接线) |
| 13 | + knowledge_card.rs(新增:format_model_card_back / parse_model_card_back / back_has_anchor) |
| 14 | +业务层:db_flashcards.rs(+list_cards_by_group / find_card_by_front / update_card_back——零 schema) |
| 15 | + db_knowledge_concepts.rs / db_knowledge_links.rs / db_knowledge_systems.rs(已交付不动) |
| 16 | +系统层:commands_knowledge_cards.rs(create_model_card / list_group_cards / promote_card_to_concept) |
| 17 | +UI 层:KnowledgePage 概念库三问一用卡片 + 组侧 model 卡入口 + 复习面/列表「纳入体系」按钮 |
| 18 | +``` |
| 19 | + |
| 20 | +硬约束(写入每层实现): |
| 21 | +- **组仍是唯一容器**:model 卡在组内(flashcards.group_id),概念在体系;升格不搬运内容,只建引用与锚点。 |
| 22 | +- **双面体单向升格**:组内 model 卡(记忆面)→ 概念(思辨面)单向;概念→卡不反向(杜绝"概念卡与闪卡重复记账")。 |
| 23 | +- **只引用不收纳**:卡↔概念经 `knowledge_links(target_type=flashcard)`;**不新增** `flashcards.concept_id` 列(裁决见 §二)。 |
| 24 | +- **零破坏**:本版无建表/补列;`flashcards.kind`/`metrics_events.kind` 均是无 CHECK 的"补值"级增量(§二)。 |
| 25 | +- **命名纪律**:card/model_card/concept/promotion 术语贯穿全栈;Tauri command 与 TS 类型同名(camelCase 契约)。 |
| 26 | + |
| 27 | +## 二、数据层(零破坏——补值级增量,无迁移) |
| 28 | + |
| 29 | +**结论:本版无需任何 `ensure_column`,也无需新表。** 理由(逐条): |
| 30 | +- `flashcards.kind` 无 CHECK 约束(建表 `kind TEXT NOT NULL DEFAULT 'fact'`)——存 `'model'` 只是新增补值,命令层透传即可,无需迁移/补列。 |
| 31 | +- `metrics_events.kind` 无约束(已存在 `add_metric_event(kind, payload)`)——`add_metric_event("concept_promoted", …)` 直接可用,与 v0.13.1 侧埋其余指标同口径。 |
| 32 | +- **不加 `flashcards.concept_id` 列(裁决)**:卡↔概念关联唯一走 `knowledge_links(target_type='flashcard', target_id=card_id, concept_id=…)`——与"体系只引用、不收纳(引用是唯一通道)"纪律一致;引用反向可查(`list_knowledge_links(concept_id)` 能列出挂了哪些卡);避免"卡"与"线索"双轨记账;加列则破坏唯一引用通道,且与 v0.13.4 交叉点/失效检查的引用口径不一致。 |
| 33 | + |
| 34 | +**db_flashcards.rs 仅新增三个方法(零 schema,其余不动):** |
| 35 | +```rust |
| 36 | +list_cards_by_group(group_id, kind: Option<&str>) -> Vec<Flashcard> // 组卡列表(kind 过滤;模型卡列表数据源) |
| 37 | +find_card_by_front(group_id, front: &str) -> Option<Flashcard> // 幂等 create 取既有卡 |
| 38 | +update_card_back(id, back: &str) -> bool // 升格锚点追加 |
| 39 | +``` |
| 40 | + |
| 41 | +## 三、原子层:纯函数(knowledge_card.rs,TDD golden 先行) |
| 42 | + |
| 43 | +**1. 卡面契约(compose 与 parse 互为逆)** ——back 标签行: |
| 44 | +``` |
| 45 | +本质:<essence> |
| 46 | +边界:<boundary> |
| 47 | +联系:<relation> |
| 48 | +``` |
| 49 | +- 用 full-width `:`(半角 `:` 容错);字段值可含换行/首尾多余空白(trim 折叠首尾空白,保留内部换行);缺字段/空字段 → 该问 = None。 |
| 50 | +- `format_model_card_back(essence, boundary, relation) -> String`:按上述契约组合 back(空字段留 `标签:` 空行)。golden:全填/任一问空。 |
| 51 | +- `parse_model_card_back(back_text) -> ModelCardBack{essence, boundary, relation}`:按标签行扫描,后续非标签行并入当前字段;同名标签后出现者覆盖;标签不存在 → None。**golden(≥6)**: |
| 52 | + 1. 标准三行 → 三问全取 |
| 53 | + 2. 缺边界 → boundary=None |
| 54 | + 3. 空字段(`本质:`)→ essence=None |
| 55 | + 4. 值首尾多余空白 → trim |
| 56 | + 5. 值含换行 → 保留内部换行(拼接) |
| 57 | + 6. 无任何标签 → 三问全 None |
| 58 | + 7. 半角冒号(`本质:A`)→ 容错解析 |
| 59 | +- `back_has_anchor(back_text) -> bool`:任一行(trim 后)以 `→ 概念「` 开头即视为已锚定(升格锚点=回链标记)。**golden(≥3)**: |
| 60 | + 1. 无锚点(仅三问)→ false |
| 61 | + 2. 末尾含 `→ 概念「安全边际」` → true |
| 62 | + 3. 标记出现在字段值中部(如联系里提 `→ 概念「凯利」`)→ false(非整行锚点,不误判) |
| 63 | + |
| 64 | +**2. 升格锚点追加幂等**:在升格事务内 `back_has_anchor(back)` 为 true 则不追加;追加格式为**独立行** `back + "\n→ 概念「name」"`(保证行首匹配——与 back_has_anchor 的整行前缀判定一致,防重复回链)。 |
| 65 | + |
| 66 | +**3. promote_rules 只接线不重写**:`knowledge_pure::promote_rules` 已交付(v0.13.1 §三),本版在 `promote_card_to_concept` 内调用其决策,**不改函数**(接线语义见 §四)。 |
| 67 | + |
| 68 | +线宽:knowledge_card.rs 预估 ≤300 行;卡面为独立域,不进 knowledge_pure.rs(避免该文件超 300 行)。 |
| 69 | + |
| 70 | +## 四、系统层:Tauri commands(commands_knowledge_cards.rs) |
| 71 | + |
| 72 | +入参校验沿用 v0.13.1 口径:`require_id`/`normalize_text`;错误中文业务语义。`*_inner(db,…)` 纯函数 + 薄 `#[tauri::command]` 壳。 |
| 73 | + |
| 74 | +| 命令 | 参数要点 | 返回值 | 校验 | |
| 75 | +|---|---|---|---| |
| 76 | +| `create_model_card` | group_id, name, essence?, boundary?, relation? | Flashcard(kind='model') | 组存在;name 归一化非空 ≤2000;卡面契约(compose back);幂等 `find_card_by_front`→已有则返回既有卡 | |
| 77 | +| `list_group_cards` | group_id, kind? | Vec\<Flashcard\> | id>0;kind ∈ {fact, action, model}(模型卡现读) | |
| 78 | +| `promote_card_to_concept` | card_id, target_system_id? | PromoteResult | 卡存在且 kind='model';target 体系存在;**空名拒绝**(promote_rules 空名 panic 前置——command 归一化 name=front 非空后再调 pure) | |
| 79 | + |
| 80 | +**`PromoteResult` 结构体**(camelCase 序列化):`action: String`(created/merged/hinted/already)+ `decision: PromoteDecision` + `concept: KnowledgeConcept` + `link: Option<KnowledgeLink>`(created/merged=新建;hinted/already=None——未落库/既有)。 |
| 81 | + |
| 82 | +**`promote_card_to_concept` 接线语义**: |
| 83 | +1. 校验卡存在;`kind` 必须 `model`(非 model → 报错"仅概念卡(model 卡)可纳入体系")。 |
| 84 | +2. `target_system_id` 未给 → `find_global_system()`;无全局体系 → 报错引导先建全局体系。 |
| 85 | +3. **AlreadyLinked 免重复(调用方判定)**:已有 `list_knowledge_links` 中 `target_type=flashcard, target_id=card_id, concept_id 非空` → `action="already"`,返回既有 concept+link,不重跑(幂等免重复)。 |
| 86 | +4. 组装 `PromoteInput { card_name=front, existing=[find_concept_by_name(front)?].map(|c|(c.id, c.name, c.system_id)).unwrap_or(vec![]), target_system_id }`;调 `promote_rules`。 |
| 87 | +5. 按决策分派: |
| 88 | + - **Create**:`add_knowledge_concept(target_system_id, name, parse 三问 from back)` → `link_knowledge_target(system, concept_id, flashcard, card_id)` → 锚点追加(back_has_anchor 幂等)→ `add_metric_event("concept_promoted", payload{cardId, conceptId})` → `action="created"`。 |
| 89 | + - **Merge**:`link_knowledge_target` 既有概念(同体系)→ 锚点追加 → 同 metric → `action="merged"`。 |
| 90 | + - **CrossSystemHint**:**仅提示不落库**(v0.13.4 交叉点数据源)——返回 cross-system 命中概念 + decision,无 link、无 metric、无锚点追加 → `action="hinted"`。 |
| 91 | + - **AlreadyLinked**:见①(decision 透传 AlreadyLinked)。 |
| 92 | +6. 锚点只在 Create/Merge 追加;CrossSystemHint/Already 不动 back(不劫持跨体系线索,也不重复写)。 |
| 93 | + |
| 94 | +**注册**:lib.rs `mod commands_knowledge_cards;` + invoke_handler 追加三命令。线宽:新模块集中 v0.13.2 命令,避免 commands_flashcards(266 行)/commands_knowledge_core(474 行,已豁免)再膨胀。 |
| 95 | + |
| 96 | +## 五、UI 层 |
| 97 | + |
| 98 | +- **概念库三问一用卡片(中栏概念列表升级)**:KnowledgePage 概念 tab 每行显示——name + 三问摘要(essence 单行 ellipsis,缺则显示边界/联系)+ 最近应用标签(`lastAppliedAt` 有值→"最近应用 · 距今天数",无值→"从未应用"——v0.13.3 前恒为未应用)+ 状态徽标(沿用 conceptStatusLabel)。点击进详情。 |
| 99 | +- **详情面板补 `lastAppliedAt`**:概念编辑区下新增"最近应用"只读行(秒级格式化;null → "从未应用")。v0.13.3 接线"记一次使用"后才有数据,本版只展示占位。 |
| 100 | +- **model 卡创建入口(组侧最小方案)**:复用 NotesPage/GroupSidebar 组件模式——组卡详情/组卡列表加"+ 概念卡"按钮,弹单字段表单(name + 三问可选);提交调 `create_model_card`。仅在选定组内创建;**不建体系侧入口**(体系只引用,不造卡)。 |
| 101 | +- **model 卡「纳入体系」按钮**:出现于组卡列表/复习面(ReviewSessionOverlay 对 `kind==='model'` 卡显示)。点击 → 弹目标体系选择(下拉,默认全局,可切换领域体系)→ 调 `promote_card_to_concept` → 结果提示:created「已纳入·创建概念」/ merged「已关联既有概念」/ hinted「该概念属其他体系,暂不落库」/ already「已纳入(免重复)」。 |
| 102 | +- **延迟揭示=必要难度(确认现状)**:ReviewSessionOverlay 现行机制——先显示 front(线索)→ 用户点「回忆完成 · 查看答案」→ 展开 back(验证)→ 四档评分。model 卡 front=概念名、back=三问,**沿用此机制不改**——用户凭概念名回忆三问再揭示(提取优先,必要难度)。 |
| 103 | +- 前端文件:`types/knowledge.ts` 补 `PromoteResult`/`ModelCardBack`;`pages/KnowledgePage.tsx` 概念列表升级;`components/KnowledgeDetailPanel.tsx` 补 `lastAppliedAt`;`components/ModelCardCreateDialog.tsx`(新);`components/PromoteCardButton.tsx`(新,含目标体系选择);`ReviewSessionOverlay.tsx` 给 model 卡加按钮。 |
| 104 | + |
| 105 | +## 六、测试策略 |
| 106 | + |
| 107 | +- 纯函数全 TDD(AAA + golden):§三 列出 `parse_model_card_back` 7、`back_has_anchor` 3、`format_model_card_back` 2。 |
| 108 | +- 数据层 `:memory:`:`list_cards_by_group`(kind 过滤/无过滤)、`find_card_by_front`(命中/未命中)、`update_card_back`。 |
| 109 | +- command 校验用例:组不存在 → 报错;卡非 model → 报错;target 体系不存在 → 报错;空名 → 报错(不触发 promote_rules panic);全局体系不存在且未传 target → 报错。 |
| 110 | +- 接线用例:Create/Merge/CrossSystemHint/AlreadyLinked 四分支;CrossSystemHint 不落库(link 无新增、metric 不增、back 无锚点);目标体系切换。 |
| 111 | +- 前端 vitest:概念卡片渲染(三问摘要/最近应用标签/状态徽标);model 卡「纳入体系」按钮流(mock invoke 四分支);目标体系选择默认全局。 |
| 112 | +- 零依赖验证:`cargo test --test app_lib_tests` + `tsc --noEmit` + vitest + vite build。 |
| 113 | + |
| 114 | +## 七、交付批次(每批原子提交,沿 v0.13.1 命名风格) |
| 115 | + |
| 116 | +| 批 | 内容 | 提交 | |
| 117 | +|---|---|---| |
| 118 | +| M1 | knowledge_card.rs 纯函数(golden 全绿)+ db_flashcards.rs 三方法(:memory: 测试) | `feat(knowledge): 模型卡卡面纯函数与数据层` | |
| 119 | +| M2 | commands_knowledge_cards 三命令 + promote_rules 接线 + lib.rs 注册 + 校验/接线用例 | `feat(knowledge): 模型卡创建与升格 commands` | |
| 120 | +| M3 | 前端概念库三问一用卡片 + model 卡入口 + 纳入体系按钮(目标体系选择)+ vitest | `feat(knowledge): 概念双面体 UI` | |
| 121 | + |
| 122 | +## 八、验收(真机走查) |
| 123 | + |
| 124 | +1. 组内建 model 卡(name+三问)→ 组卡列表/复习面可见 `kind='model'` 徽标。 |
| 125 | +2. 复习该卡:front=概念名先显,点「回忆完成·查看答案」后展 back 三问(延迟揭示不改)。 |
| 126 | +3. 点「纳入体系」(默认全局)→ created;全局概念库出现该概念,三问与卡 back 一致;卡 back 尾部追加「→ 概念「name」」。 |
| 127 | +4. 再次点「纳入体系」→ already(免重复,back 锚点不二次追加)。 |
| 128 | +5. 切目标为另一领域且该名已存在于他体系 → hinted(不落库、无新引用)。 |
| 129 | +6. 该名已存在于目标体系 → merged(关联既有概念,无新概念)。 |
| 130 | +7. 概念库:卡片显示三问摘要/最近应用(暂无→"从未应用")/状态徽标;详情面板显示最近应用只读行。 |
| 131 | +8. 回归:notes/组/闪卡/复习面全链路不变(零破坏验证);既有 fact/action 卡不出现「纳入体系」按钮。 |
| 132 | + |
| 133 | +## 九、不做清单(本版) |
| 134 | + |
| 135 | +概念→卡反向 · AI 回填三问(AI 可选增强默认关——本版纯人工/解析回填)· 决策/应用记录(v0.13.3)· 审计/交叉点提示(v0.13.4——CrossSystemHint 仅提示不落库)· 交叉点提示开关 · 图谱可视化 · 概念失效检查 UI · 组结算增强 · AI 能力任何实现 · `flashcards.concept_id` 列。 |
| 136 | + |
| 137 | +## 十、参考 |
| 138 | + |
| 139 | +- [ADR-024](../../adr/ADR-024-knowledge-system-layer.md) · [整合分析 §四.3/§六/§七](../../Foresight/personal-knowledge-system-layer-integration.md)(升格链路/防线:记账负担/假燃料) |
| 140 | +- [v0.13 系列](../../versions/v0.13.md)(v0.13.2 行 + 关键决策):双面体单向升格/只引用不收纳/无新北极星/AI 测量人类见证 |
| 141 | +- [v0.13.1 设计规格](./2026-08-23-v0.13.1-knowledge-system-foundation-design.md)(§一~§十 同构;promote_rules 已交付) |
| 142 | +- 指南附录B(概念三问一用卡片)[源文档](../../Foresight/source-personal-knowledge-system-guide-v2.md) |
| 143 | +- 已交付接口:`knowledge_pure.rs`(promote_rules)· `db_knowledge_{concepts,links,systems}.rs` · `db_flashcards.rs`(`add_metric_event(kind,payload)`)· `commands_flashcards.rs` · `ReviewSessionOverlay.tsx` |
0 commit comments