Skip to content

Commit 35a8613

Browse files
committed
docs(v0.14.1): 笔记组 CRUD 与画布连线/布局选项设计定稿
1 parent 6a0d54e commit 35a8613

3 files changed

Lines changed: 305 additions & 0 deletions

File tree

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# v0.14.1 设计规格:笔记组 CRUD + 知识画布连线/布局选项
2+
3+
> 状态:✅ 已批准(2026-08-29,逐项确认;用户裁决:删除语义=影响面确认后级联;偏好记忆=按体系持久化;布局/连线候选全纳入)
4+
> 所属版本:v0.14.1(体验与质量攻坚系列续作;v0.14 D4 ROI 接线为本版另一挂账项,不在本设计范围)
5+
> 范围:① 笔记组新建/重命名/删除(前端接线 + 新增删除命令)② 知识画布连线样式(直线/曲线/圆角折线/硬折线 + 箭头开关)与布局算法(6 种)选择 + 按体系持久化
6+
7+
## 1. 背景与目标
8+
9+
### 1.1 现状与痛点
10+
11+
| # | 痛点 | 现状根因 |
12+
|---|------|----------|
13+
| G1 | 笔记组只能由会话自动生成,用户不能主动新建/改名 | 后端 `create_topic_group` / `rename_note_group`(v0.11.0)已存在,前端零接线;组侧栏(v0.14 C1 重构)无任何新建/编辑入口 |
14+
| G2 | 组无法删除——误生成的组永久存在 | 无 `delete_note_group` 命令;ⓘ 弹层只有改判/管理动作 |
15+
| G3 | 画布连线只有圆角折线一种 | v0.13.8 规格曾记 smoothstep/bezier 候选(`docs/archive/2026-08-24/2026-08-24-v0.13.8-knowledge-canvas-design.md`),实现仅 smoothstep |
16+
| G4 | 自动排列只有辐射布局 | v0.13.8 后画布仍是唯一 BFS 辐射算法;用户拖拽/多根/深树场景下无备选 |
17+
18+
用户反馈(2026-08-29):**① 笔记组要能添加、编辑(新建/编辑等操作逻辑);② 知识体系要有直线/曲线的选择,布局上要有其他选择(头脑风暴)**。
19+
20+
### 1.2 目标
21+
22+
1. **组管理闭环**:手动新建(名称+领域+颜色)/ 行内重命名 / 影响面确认后级联删除
23+
2. **画布连线样式**:直线 / 曲线 / 圆角折线(现状默认)/ 硬折线 + 可选箭头
24+
3. **画布布局算法**:辐射(现状默认)/ 双翼思维导图 / 水平逻辑树 / 垂直组织树 / 鱼骨图(简化版)/ 双环(浮动项贴树)
25+
4. **按体系持久化**:连线样式 + 布局算法 + 箭头开关存 `knowledge_canvas_states`(每体系独立记忆)
26+
27+
### 1.3 约束(守既有裁决,不越界)
28+
29+
- **力导向不入**:ADR-024 不做清单"力导向图"与 v0.13.8"画布=手动画布非自动图"纪律保持——布局只作为「自动排列」算法菜单,用户显式触发才覆盖已存位置(规格 §4.4 纪律)
30+
- **画布不做清单保持**:不新建/编辑节点、不手动连线、不导出图片、不做框选批量移动(v0.13.8 §九)
31+
- **组模型不变**:无父子组;三条设计契约(粒度对齐 DomainTag / 组是学习单元 / 组结算防沼泽)延续
32+
- **零破坏迁移**:`ALTER TABLE ADD COLUMN` 幂等模式(v0.14 B 同款,已验证);旧库无新列 → 默认值兜底
33+
34+
## 2. 方案选型记录
35+
36+
### 2.1 组删除语义(已裁决:影响面确认后级联)
37+
38+
| 变体 | 结论 |
39+
|---|---|
40+
| A. 影响面确认后级联 | ✅ 采纳——先展示数量(笔记/碎片→移入"全部";闪卡/结算/周契约→级联删除;体系引用→清理悬空),用户勾选确认后单事务执行 |
41+
| B. 有闪卡先禁删 | 否决——闪卡无法迁到无组状态(GROUP_ID NOT NULL),操作繁琐 |
42+
| C. 改为归档语义 | 否决——用户原意是删除;归档组与现状软归档体系语义混叠 |
43+
44+
### 2.2 偏好记忆方式(已裁决:按体系持久化)
45+
46+
| 变体 | 结论 |
47+
|---|---|
48+
| 按体系持久化 | ✅ 采纳——`knowledge_canvas_states`(system_id 1:1)加列;不同体系可不同风格 |
49+
| 全局一次设置 | 否决——体系间风格无法区分 |
50+
| 仅会话内 | 否决——切页/重启丢失 |
51+
52+
### 2.3 布局算法候选(用户全选 6 项;力导向被否决)
53+
54+
| 算法 | 适配 | 说明 |
55+
|---|---|---|
56+
| 辐射 `radial` | 现状默认 | 现有 `layoutRadial` 纯函数不动(回归安全) |
57+
| 双翼思维导图 `mindmap` | 浅/中树 | XMind 默认心智:核心/根居中,根节点左右分翼,翼内树向下展开 |
58+
| 水平逻辑树 `treeRight` | 深树 | XMind「逻辑图(向右)」:根最左,整树向右单向展开 |
59+
| 垂直组织树 `org` | 层级浏览 | 根最上,子层向下逐层展开 |
60+
| 鱼骨图 `fishbone` | 根分支多 | **简化版**(用户确认):主干=体系根水平线,一级根上下交替为刺,二级沿刺展开;完整鱼骨因果语义与知识树不匹配,不做 |
61+
| 双环 `dualRing` | 概念/模型多 | 问题树辐射布局 + 概念/模型浮动项独占「树最深环+1」贴树环(修复现状"浮动项总落最外环远离树") |
62+
63+
### 2.4 连线样式候选(用户全选)
64+
65+
| 样式 | RF edge type | 说明 |
66+
|---|---|---|
67+
| 直线 `straight` | `straight` | 紧凑无歧义,交叉多时易乱 |
68+
| 曲线 `bezier` | `bezier`(RF 默认) | 视觉柔和,边多时如面条 |
69+
| 圆角折线 `smoothstep` | `smoothstep` | **现状默认**——横平竖直干线,最像树/流程图 |
70+
| 硬折线 `step` | `step` | 直角无圆角,"电路图"感 |
71+
72+
附加开关:**箭头**(`edge_arrows`,默认 false 保持现状);**分类边样式**:本次只保证结构不变——父子边按所选样式实线、体系根卡边按所选样式虚线(现状仅根卡虚),不引入新边类型/着色(预留扩展位)。
73+
74+
## 3. 部分一:笔记组 CRUD
75+
76+
### 3.1 数据层(db_note_groups.rs,无新表)
77+
78+
外键已就绪(`db.rs` 每连接 `PRAGMA foreign_keys=ON`,v0.11.0 建表即定):`notes.group_id`/`fragments.group_id` ON DELETE SET NULL;`flashcards`/`settlements`/`contracts`.group_id ON DELETE CASCADE。新增:
79+
80+
- `group_delete_impact(id) -> Result<GroupDeleteImpact>`:影响面计数(只读,无副作用)
81+
- `delete_group(id) -> Result<bool>`:**单事务**——① 显式 `DELETE FROM knowledge_links WHERE target_type='note_group' AND target_id=?`(该表无 FK 到 note_groups,必须手动清悬空引用)② `DELETE FROM note_groups WHERE id=?`(笔记/碎片 SET NULL 与闪卡/结算/契约 CASCADE 由 FK 自动生效)③ 组不存在 → 返回 false(command 层转错误)
82+
83+
`GroupDeleteImpact`(types.rs,serde camelCase 与前端同口径):
84+
85+
```rust
86+
pub struct GroupDeleteImpact {
87+
pub notes: i64, // 笔记数 → 移入「全部」
88+
pub fragments: i64, // 碎片数 → 移入「全部」
89+
pub cards: i64, // 闪卡数 → 将级联删除
90+
pub settlements: i64, // 结算记录数 → 将级联删除
91+
pub contracts: i64, // 周契约数 → 将级联删除
92+
pub system_refs: i64, // 体系引用数 → 引用将解除
93+
}
94+
```
95+
96+
### 3.2 命令层(commands_groups.rs)
97+
98+
- `get_group_delete_impact(id: i64) -> Result<GroupDeleteImpact>`:id>0 + 存在性校验
99+
- `delete_note_group(id: i64) -> Result<bool>`:id>0 + 存在性校验;调 `db.delete_group`
100+
101+
删除**不区分组来源**(container/feed、课程/主题/独立皆可删——误生成纠错是主诉求);
102+
删除前**必须先调 impact 再调 delete**(两步——确认弹窗数据源与执行分离,防确认后数据漂移造成"以为删的是 A 实际删的是 B")。
103+
104+
### 3.3 前端
105+
106+
| 入口 | 行为 | 命令调用 |
107+
|---|---|---|
108+
| 侧栏头「+ 新建组」(GroupSidebar 头部工具条) | `GroupCreateDialog.tsx` 弹窗:名称(必填)+ 领域标签(15 类下拉,必填——契约一,`DOMAIN_OPTIONS` 自 RouteInfoPopover 迁至 `utils/domainOptions.ts` 共享常量)+ 颜色(可选,复用 `NoteColorPicker`) | `create_topic_group` → `update_group_color` → `onChanged` |
109+
| 组行 hover「✎」(GroupSidebarRow) | 名称变内联输入框(受控编辑态;行单击过滤/色点/ⓘ 全部 stopPropagation);Enter 保存 / Esc 取消;空白名拒绝 | `rename_note_group` → `onChanged` |
110+
| ⓘ 弹层新增「组管理·危险操作」区(RouteInfoPopover 第五区) | 「删除组」按钮 → `GroupDeleteConfirm.tsx` 确认弹窗:loading 展示影响面六项 → 勾选「我了解后果」(有级联项时必勾;无级联项时点按钮即执行)→ 执行 | `get_group_delete_impact` → `delete_note_group` → `onChanged` |
111+
112+
## 4. 部分二:知识画布连线/布局偏好
113+
114+
### 4.1 迁移(db_migrations.rs)
115+
116+
```sql
117+
ALTER TABLE knowledge_canvas_states ADD COLUMN edge_style TEXT NOT NULL DEFAULT 'smoothstep';
118+
ALTER TABLE knowledge_canvas_states ADD COLUMN edge_arrows INTEGER NOT NULL DEFAULT 0;
119+
ALTER TABLE knowledge_canvas_states ADD COLUMN layout_algorithm TEXT NOT NULL DEFAULT 'radial';
120+
```
121+
122+
- 幂等模式:迁移器按列探测(v0.14 B 同款 `column_exists` 手法)
123+
- `CREATE TABLE IF NOT EXISTS knowledge_canvas_states` 同步补三列(新库直接建齐)
124+
- 表仍 1:1(system_id 主键),**行可能不存在**(首开无视口)——读偏好返回 `Option`,前端缺省回落默认值
125+
126+
### 4.2 数据层(db_knowledge_canvas.rs)
127+
128+
- `CanvasPrefs { edge_style: String, edge_arrows: bool, layout_algorithm: String }`(types.rs;serde camelCase)
129+
- `get_canvas_prefs(system_id) -> Result<Option<CanvasPrefs>>`
130+
- `save_canvas_prefs(system_id, edge_style, edge_arrows, layout_algorithm) -> Result<bool>`:**upsert**(ON CONFLICT(system_id) DO UPDATE——与 save_canvas_viewport 同款)
131+
132+
### 4.3 命令层(commands_knowledge_canvas.rs)
133+
134+
- `get_canvas_prefs(system_id)` / `save_canvas_prefs(system_id, edgeStyle, edgeArrows, layoutAlgorithm)`
135+
- 白名单校验(前后端枚举同口径):
136+
137+
```rust
138+
const EDGE_STYLES: [&str; 4] = ["straight", "bezier", "smoothstep", "step"];
139+
const LAYOUT_ALGORITHMS: [&str; 6] = ["radial", "mindmap", "treeRight", "org", "fishbone", "dualRing"];
140+
```
141+
142+
非法值拒绝(防任意字符串入库);system_id>0 + 体系存在性校验。
143+
144+
### 4.4 布局算法纯函数(app/src/utils/)
145+
146+
统一接口(与 `layoutRadial` 同构——纯函数零副作用零随机):
147+
148+
```ts
149+
type LayoutFn = (input: { hasCore: boolean; items: CanvasLayoutItem[] }) => Map<string, CanvasPoint>;
150+
```
151+
152+
- `layoutRadial.ts`:**行为零变化**——公共件(`CANVAS_BBOX`/`polar`/碰撞检测)迁出至新建
153+
`layoutShared.ts`(layoutRadial 改为 import 引用;输出/签名不变,回归安全)
154+
- 新增纯函数文件(各 ≤300 行:`layoutShared.ts` + 5 个算法 + 分发器):
155+
- `layoutMindmap.ts`:中心=core 卡(hasCore)或第一根;根节点按序分左右两翼(交替分配),翼内树向下展开;x=深度步长、y=翼内顺序步长(紧凑层叠,无重叠——复用碰撞外推)
156+
- `layoutTreeRight.ts`:根 x=0;深度决定 x;同层 y 按父顺序紧凑下排
157+
- `layoutOrg.ts`:根 y=0;深度决定 y;同层 x 水平展开
158+
- `layoutFishbone.ts`:简化版——主干 y=0 水平线;一级根上下交替(y=±step)、沿 x 等距;二级沿所属骨刺向下展开;其余(三级+)沿骨刺继续延伸
159+
- `layoutDualRing.ts`:问题树按 radial 分层(核心近心);概念/模型浮动项独占 `最深环+1` 环(半径=外圈,贴树不分离);孤儿沿用 golden angle 兜底(与 radial 一致)
160+
- `layoutCanvas.ts`:分发器 `layoutCanvas(input, algorithm)`——未知算法名回退 `radial`(前端枚举同口径 + 防御)
161+
- 确定性要求:相同输入恒相同输出(单测可精确断言);每个算法保证所有输入 item 的 key 都有位置(孤儿/未知 parent 兜底)
162+
163+
### 4.5 连线样式(canvasElements.ts)
164+
165+
- `buildCanvasElements` 输入增加 `edgeStyle: EdgeStyle` + `edgeArrows: boolean`
166+
- 父子边与根卡虚边 `type` 按映射输出(straight/bezier/smoothstep/step);根卡边保持 `strokeDasharray` 虚线 + 所选 type
167+
- `edgeArrows=true` → 全部边加 `markerEnd: { type: MarkerType.ArrowClosed }`(与主题色一致)
168+
- 三自定义问题/概念/模型节点 + core 节点的四边 Handle 已齐备(v0.13.9)——所有边类型均从 handle 接线,`resolveEdgeHandles` 不变
169+
170+
### 4.6 画布 UI(KnowledgeCanvasView.tsx)
171+
172+
右上 Panel 调整(替换现行「✨ 自动排列」「⛶ 适配视图」按钮组):
173+
174+
| 控件 | 行为 |
175+
|---|---|
176+
| 「✨ 布局 ▾」下拉(6 项 + 简述) | 选中 = 用该算法立即重排(覆盖已存位置,规格 §4.4 显式触发语义)+ `save_canvas_prefs` + `fitView` |
177+
| 「〰 连线 ▾」下拉(4 项 + ✓箭头勾选) | 选中 = 即时重渲染 + `save_canvas_prefs` |
178+
| 「⛶ 适配视图」 | 保留 |
179+
180+
- 初始渲染:连线样式/箭头读 prefs(缺省默认);布局算法读 prefs(缺省 `radial`)——首次打开缺位节点的初始化位置用**已存算法**而非固定辐射
181+
- 「自动排列」按钮语义并入「布局 ▾」(原按钮删除——避免双入口语义分歧)
182+
183+
## 5. 错误处理与边界
184+
185+
- 组删除:impact 读取失败 → 确认弹窗展示错误不执行;delete 失败 → 弹窗内状态行展示错误,组列表不变(onChanged 不触发)
186+
- 新建组:领域必填(后端 `create_topic_group` 已强制);同领域同地形已有主题组 → 后端幂等复用返回既有组(前端提示"已存在,已选中")
187+
- 重命名冲突/非法名 → `normalize_title` 兜底(后端已有);空名拒绝在前端层
188+
- 画布偏好:枚举非法值命令层拒绝;states 行缺失 → 默认值;`save_canvas_prefs` 失败 → 状态行展示错误,视觉选项仍生效(会话内),下次打开回落旧值
189+
- layout 纯函数输入异常(空 items、全部孤儿、hasCore 但无根)→ 不 panic,输出空/兜底位置
190+
191+
## 6. 测试策略
192+
193+
**Rust(cargo test,内存库)**:
194+
195+
| 模块 | 用例(AAA) |
196+
|---|---|
197+
| db_note_groups | impact 六项计数正确(含零项);delete 后 notes/fragments 组置 NULL、flashcards/settlements/contracts 归零、knowledge_links 悬空清理、再查组不存在;不存在组 delete → false |
198+
| commands_groups | id≤0/不存在组拒绝;非法领域拒绝(沿用现有校验链) |
199+
| db_knowledge_canvas | 无行 get → None;save 后 get 全字段回读;upsert 覆盖;第二体系互不影响 |
200+
| commands_knowledge_canvas | 枚举白名单(合法 4+6 + 非法拒绝);system_id 校验;camelCase 契约回读 |
201+
202+
**前端(Vitest)**:
203+
204+
| 模块 | 用例 |
205+
|---|---|
206+
| layout 纯函数 ×5 + 分发器 | 所有 key 有位置;确定性(两跑一致);无重叠(碰撞断言);mindmap 左右翼/树深 x 正确;treeRight x 随深度;org y 随深度;fishbone 一二级上下交替;dualRing 浮动项在最深环+1;未知算法回退 radial |
207+
| canvasElements | edge type 按 4 枚举映射;edgeArrows → markerEnd;根卡边保持虚线 |
208+
| GroupCreateDialog | 领域必填校验;invoke 序列(create → color → onChanged) |
209+
| GroupSidebarRow 重命名 | ✎ 进入编辑态;Enter 保存/Esc 取消;与行单击不冲突 |
210+
| GroupDeleteConfirm | impact 展示六项;级联>0 时未勾选禁执行;勾选后调 delete |
211+
| KnowledgeCanvasView | 布局下拉选中 → autoLayout + save_canvas_prefs;连线下拉 → 重渲染 + save;初始读 prefs |
212+
213+
## 7. 文件清单
214+
215+
**Rust(app/src-tauri/src/)**:
216+
217+
| 文件 | 变化 |
218+
|---|---|
219+
| `types.rs` | +`GroupDeleteImpact`、+`CanvasPrefs` |
220+
| `db_migrations.rs` | +canvas_states 三列迁移(幂等)+ CREATE 语句同步 |
221+
| `db_note_groups.rs` | +`group_delete_impact`/`delete_group`;`db_note_groups_tests.rs` 补用例 |
222+
| `commands_groups.rs` | +2 命令;`commands_groups_tests.rs`(如无则新建) |
223+
| `db_knowledge_canvas.rs` | +`get_canvas_prefs`/`save_canvas_prefs`;`db_knowledge_canvas_tests.rs` 补用例 |
224+
| `commands_knowledge_canvas.rs` | +2 命令 + 白名单;`commands_knowledge_canvas_tests.rs` 补用例 |
225+
| `lib.rs` | +4 命令注册 |
226+
227+
**前端(app/src/)**:
228+
229+
| 文件 | 变化 |
230+
|---|---|
231+
| `utils/layoutMindmap.ts` / `layoutTreeRight.ts` / `layoutOrg.ts` / `layoutFishbone.ts` / `layoutDualRing.ts` / `layoutCanvas.ts` / `layoutShared.ts` | 新增纯函数 + 单测 |
232+
| `utils/layoutRadial.ts` | 公共件迁出至 layoutShared 后的 import 调整(行为零变化) |
233+
| `utils/canvasElements.ts` | +`edgeStyle`/`edgeArrows` 入参与映射 |
234+
| `components/KnowledgeCanvasView.tsx` | 布局/连线下拉 + prefs 读写 |
235+
| `components/GroupSidebar.tsx` | 「+ 新建组」+ 重命名态接线 |
236+
| `components/GroupSidebarRow.tsx` | ✎ 按钮 + 内联编辑态 |
237+
| `components/GroupCreateDialog.tsx` | 新建弹窗(新增) |
238+
| `components/GroupDeleteConfirm.tsx` | 删除影响面确认(新增) |
239+
| `components/RouteInfoPopover.tsx` | 第五区「删除组」 |
240+
| 相应 `.test.tsx` | 交互测试 |
241+
242+
## 8. 验收标准
243+
244+
1. 组侧栏「+ 新建组」→ 填名称/领域/颜色 → 新组出现在「主题」分区且色点正确
245+
2. 组行 ✎ → 改名 → Enter 后行显示新名(刷新后保持)
246+
3. ⓘ 弹层「删除组」→ 影响面弹窗数字正确 → 勾选确认 → 组消失;组内笔记留在「全部笔记」;闪卡/结算/契约消失;体系页引用该组处消失
247+
4. 画布「布局 ▾」选水平逻辑树 → 整树右向展开且节点不重叠;选回辐射 → 恢复圆形排布
248+
5. 画布「连线 ▾」切曲线 → 亲子连线变曲线;勾选箭头 → 边末端出现箭头;硬折线/直线同理
249+
6. 切换体系再切回 → 布局/连线偏好保持上次选择;重启应用后保持
250+
7. 旧库升级(无新列)→ 默认 smoothstep + radial,功能正常
251+
8. 单测全绿(新增用例数预计:Rust ~20、前端 ~35)

‎docs/versions/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
| v0.12.8 | [v0.12.8.md](./v0.12.8.md) — 笔记列表级批量删除:行内勾选 + 底部批量操作栏(全选三态/计数/批量删除),与会话管理台同操作逻辑,无需先打开笔记 | 版本文档(代码已交付 2026-08-23) |
4141
| v0.13 | [v0.13.md](./v0.13.md) — 大目标版本系列:知识体系层(v0.13.0 理念修订批/ADR-024 / v0.13.1 体系基建 / v0.13.2 概念双面体 / v0.13.3 决策与应用 / v0.13.4 审计与整合;方案 B:全局+领域双层、组仍唯一容器、REQ-202~212;v0.13.7 体系上手路径:入口补全+具象化;v0.13.8 画布可视化:React Flow 节点式无限画布) | 系列文档(v0.13.1 代码已交付 2026-08-23,真机验收待执行;v0.13.2~4 未启动,REQ-146 门控;v0.13.7 代码已交付 2026-08-24;v0.13.8 代码已交付 2026-08-24,真机验收待执行) |
4242
| v0.14 | [v0.14.md](./v0.14.md) — 体验与质量攻坚系列:A 编辑器基础体验(CodeMirror 6)/ B 视觉系统(四级颜色)/ C 知识体系架构(Obsidian 式组侧栏 + 多关系分层图谱 + 深度联动 v2)/ D 采集质量(章节级混合形态 + 质量门控 + 版面重建/行合并/跨帧增量 + 平台模板适配) | 版本文档(2026-08-27~28 头脑风暴定稿;**A 代码已交付 2026-08-28**,真机验收待执行;B/C/D 设计完成待实施) |
43+
| v0.14.1 | [v0.14.1.md](./v0.14.1.md) — 笔记组 CRUD(新建/行内重命名/影响面确认后级联删除)+ 知识画布连线/布局选项(连线 4 样式+箭头开关、布局 6 算法、按体系持久化) | 版本文档(2026-08-29 设计定稿,待实施) |
4344

4445
## 沉淀规则
4546

0 commit comments

Comments
 (0)