ooodc a7883dbed9 refactor(todo): 重构待办事项管理逻辑及更新状态规则
- 移除 TodoItem 中的 priority、created_at 和 updated_at 字段
- 强制每个任务都必须有唯一 id,且由用户负责生成
- 修改合并模式逻辑,merge=true 下保留未提及的旧任务
- 支持已完成和已取消任务重新激活(状态改回 pending 或 in_progress)
- 禁止 in_progress 状态退回到 pending,必须标记为 completed 或 cancelled
- 优化状态转换校验,允许特定状态间合法切换
- 简化任务变更消息,移除详细的新增/更新/移除统计
- 更新文档和示例,明确 id 必须由用户生成和使用
- 修复和补充测试,增强状态转换和合并模式验证
- 调整任务时间戳生成逻辑,统一使用当前时间及索引
- 该变更提供更合理的任务状态机械及管理模式,提升稳定性和易用性
2026-06-13 09:22:33 +08:00

372 lines
20 KiB
Markdown
Raw 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.

# 泳道图Swimlane
适用于:跨角色/跨系统的端到端流程(用户/网关/服务/存储/回调)、多泳道协作流程、系统交互链路图。
支持两种方向:
- **水平泳道**:泳道为横向条带(自上而下排列),流程从左到右推进
- **垂直泳道**:泳道为纵向列(自左向右排列),流程从上到下推进
## Content 约束
- 泳道数lanes建议 3-7超过 7 会显著降低可读性;如必须更多泳道,优先合并同类或拆成两张图
- 阶段数stages建议 4-8超过 8 优先合并相邻阶段或改成“代表性阶段”
- 每个阶段在每条泳道中最多放 1 个“主步骤卡片”如同一阶段需要多个步骤放在同一格内做纵向堆叠2-3 个为上限)
- 节点文本 1-2 行为主;长文本用 `\n` 手动换行,避免单行超长导致卡片过宽
- 仅画必要连线:泳道图的结构已经表达了“属于哪个角色/系统 + 发生顺序”,连线只用于表达跨泳道交互、关键因果关系或异步事件流
## Layout 选型
| 模式 | 适用条件 | 特征 |
|------|---------|------|
| **水平泳道** | 默认推荐;流程天然左→右推进 | lanes=行stages=列;跨泳道同一阶段严格 x 对齐 |
| **垂直泳道** | 用户明确要求竖版、或画布更适合纵向滚动阅读 | lanes=列stages=行;跨泳道同一阶段严格 y 对齐 |
## Layout 规则
### 通用规则(两种方向都适用)
1. **网格对齐是第一优先级**:跨泳道同一阶段必须严格对齐(水平对齐 x垂直对齐 y。对齐通过“共享阶段标尺stage ruler / stage slots”实现不靠肉眼估算也不靠逐节点随意手写坐标
2. **只生成真实节点**:为保证跨泳道阶段严格对齐,所有阶段统一保留透明的 **stage cell**;仅在真实阶段的 cell 内生成卡片节点,并按阶段索引映射到对应槽位
3. **泳道底色**:为了增强层级感同时保持界面整洁,**强烈建议所有泳道容器统一使用极浅灰色背景**(如 `fillColor: "#F8F9FA"``"#FCFCFC"`)。边框使用浅灰色细虚线(`borderDash: "dashed"`, `borderWidth: 1`, `borderColor: "#DEE0E3"`)以明确边界。
4. **步骤卡片**:使用 `rect`。为建立清晰的视觉层级,卡片**必须填充浅色背景**(参考 `elements/style.md` 中的浅色板,如极浅的主题色),边框使用对应的主题主色(`borderWidth: 1-2`),文字使用深色(如 `#1F2329`)以确保可读性。统一圆角;宽高以可读为先,避免过窄导致换行过多
5. **间距**:只要存在 connector 连线,卡片之间的主轴间距必须满足 `gap >= 40`
### 子节点对齐
- **同一阶段必须严格对齐**:所有泳道复用同一套 stage slots不允许靠卡片自身宽度或肉眼估算来对齐
- **卡片宽度一致**:同一泳道中的步骤卡片应保持统一宽度;推荐使用统一固定宽度,或严格复用同一槽位宽度
- **统一使用 stack 容器**:有内容的阶段统一使用 `layout: "vertical"` 的 stack frame纵向堆叠 1-3 张卡片);空阶段不生成 stack/卡片,但保留透明 cell 保证对齐
- **垂直居中但不影响对齐**stage cell 默认 `alignItems: "stretch"`,可用 `justifyContent: "center"` 让卡片在 cell 内居中,以确保左右边界严格对齐
- **不靠底色区分行/列**:阶段网格默认不需要背景色;如需“轻微”的行/列边界提示,优先给 stage cell 加 1px 细边框(`fillColor: "transparent"` 仍保持视觉透明)
### Flex 栅格模式(默认)
- lane body 使用 Flex 布局:水平泳道用 `layout: "horizontal"`,垂直泳道用 `layout: "vertical"`
- 为每个阶段生成一个 **stage cell**(占位单元格);空阶段的 cell 透明但保留cell 内用 `layout: "vertical"` 的 stack 承载 1-3 张卡片
- 统一参数:`slotWidth: 180-220`(水平泳道 cell 宽度)、`slotHeight: 64-104`(垂直泳道 cell 高度建议档)、`gap: 40-56`(有连线时必须 ≥40`stackGap: 8``lanePadding: 16`
- 对齐规则:所有泳道复用同一组 `slotWidth/slotHeight/gap`;同一阶段在各泳道上使用相同的 cell 索引保证严格对齐
- 尺寸语义lane body `width/height``"fit-content"`Yoga 自适应);卡片 `height: "fit-content"`Flex 容器内不写子节点 `x/y`
- 内容密度:卡片文字 1-2 行;同阶段堆叠上限 2-3超过上限优先拆分到相邻阶段或缩短文本
### 跨泳道间距lanesGap
- 根容器承载所有泳道:水平泳道用 `layout: "vertical"`,垂直泳道用 `layout: "horizontal"`
- 缩减跨泳道主轴间距 `lanesGap`(建议 `16-24`),以保持整体图表的紧凑性。避免 `lanesGap` 设置为 `0` 导致边框重叠变粗,也避免间距过大导致视觉涣散。
- 每条泳道作为根容器的子 frame内部再使用上述 Flex 栅格的 stage cell 布局
- `lanesGap``lanePadding/stackGap` 独立lane 内容增减不应影响跨泳道间距
- 4px 基线对齐:`lanesGap``lanePadding`、cell 尺寸建议按 4 的倍数对齐
### 水平泳道lanes=行stages=列)
- 根容器:`layout: "vertical"``gap: lanesGap` 固定;`alignItems: "stretch"`,标题在最上方
- 每条泳道:一个可见 frame分组容器内部用 `layout: "horizontal"` 分成两块:
- 左侧 lane label固定宽度 text如 100-140垂直居中左对齐`textAlign: "left"`title 需要比步骤卡片更醒目,优先通过 `fontSize: 18-20` + `fontWeight: "bold"` + 与泳道边框一致的 `textColor` 实现
- 右侧 lane body`layout: "horizontal"`,包含完整的阶段 **stage cell** 数组cell 宽度固定为 `slotWidth`,相邻 cell 间 `gap` 统一;空阶段 cell 透明但保留
- 步骤卡片:推荐统一卡片宽度(如 160-220并在所有泳道复用同一组 `slotWidth / gap`,保证跨泳道阶段严格 x 对齐
### 垂直泳道lanes=列stages=行)
- 根容器:`layout: "horizontal"``gap: lanesGap` 固定;`alignItems: "stretch"`,标题在最上方
- 每条泳道:一个可见 frame分组容器内部 `layout: "vertical"`
- 顶部 lane label必须放在单独的 `lane label frame`label frame 使用 `width: "fill-container"``alignItems: "center"``justifyContent: "center"`,并通过 `paddingTop` 留出与泳道上边的 gap推荐 `12-16`,按 4px 基线取值,如 `padding: [12, 8, 8, 8]`);内部 text 使用 `width: "fill-container"` + `textAlign: "center"`,确保 title 在整条泳道顶部**水平居中**
- lane body`layout: "vertical"`,包含完整的阶段 **stage cell** 数组cell 高度固定为 `slotHeight`,相邻 cell 间 `gap` 统一;空阶段 cell 透明但保留
- 内容居中对齐stage cell 建议 `alignItems: "center"` + `justifyContent: "center"`,让卡片在每个 cell 内水平/垂直居中;卡片宽度不超过 `slotWidth`(或固定宽度),避免被 `"fill-container"` 拉伸导致“看起来不居中”
- 步骤卡片:推荐统一卡片高度或统一 `slotHeight / gap`,保证跨泳道阶段严格 y 对齐
- 泳道外层容器必须显式写 `fillColor: "#F8F9FA"`(极浅灰)、`borderDash: "dashed"``borderWidth: 1``borderColor: "#DEE0E3"`(统一浅灰色),否则会被编译为虚拟 frame 导致不渲染
- 统一高度Flex 自适应,可选):根容器使用 `alignItems: "stretch"`,每个泳道外层 frame 使用 `height: "fill-container"`;泳道内部仍保持 lane label + lane body 的结构
示例:
```json
{
"version": 2,
"nodes": [
{
"type": "frame",
"id": "lanes-root",
"x": 40, "y": 40,
"layout": "horizontal",
"gap": 16,
"alignItems": "stretch",
"children": [
{
"type": "frame",
"id": "lane-left",
"layout": "vertical",
"width": "fit-content",
"height": "fill-container",
"fillColor": "#F8F9FA",
"borderDash": "dashed",
"borderWidth": 1,
"borderColor": "#DEE0E3",
"children": [
{ "type": "frame", "id": "lane-left-label-wrap", "layout": "vertical", "width": "fill-container", "height": "fit-content",
"alignItems": "center", "justifyContent": "center", "padding": [12, 8, 8, 8], "children": [
{ "type": "text", "id": "lane-left-label", "text": "Lane Left", "width": "fill-container", "height": "fit-content",
"textAlign": "center", "verticalAlign": "middle", "fontSize": 18, "fontWeight": "bold", "textColor": "#5178C6" }
] },
{ "type": "frame", "id": "lane-left-body", "layout": "vertical",
"gap": 40, "padding": 16,
"children": [
{ "type": "frame", "id": "stage-1-cell-left", "layout": "vertical", "width": 220, "height": 80, "alignItems": "center", "justifyContent": "center",
"children": [{ "type": "rect", "id": "c-s1", "width": 200, "height": "fit-content", "fillColor": "#E1EAFA", "borderColor": "#5178C6", "borderWidth": 2, "borderRadius": 8 }] },
{ "type": "frame", "id": "stage-2-cell-left", "layout": "vertical", "width": 220, "height": 80, "alignItems": "center", "justifyContent": "center", "children": [] }
] }
]
},
{
"type": "frame",
"id": "lane-right",
"layout": "vertical",
"width": "fit-content",
"height": "fill-container",
"fillColor": "#F8F9FA",
"borderDash": "dashed",
"borderWidth": 1,
"borderColor": "#DEE0E3",
"children": [
{ "type": "frame", "id": "lane-right-label-wrap", "layout": "vertical", "width": "fill-container", "height": "fit-content",
"alignItems": "center", "justifyContent": "center", "padding": [12, 8, 8, 8], "children": [
{ "type": "text", "id": "lane-right-label", "text": "Lane Right", "width": "fill-container", "height": "fit-content",
"textAlign": "center", "verticalAlign": "middle", "fontSize": 18, "fontWeight": "bold", "textColor": "#8569CB" }
] },
{ "type": "frame", "id": "lane-right-body", "layout": "vertical",
"gap": 40, "padding": 16,
"children": [
{ "type": "frame", "id": "stage-1-cell-right", "layout": "vertical", "width": 220, "height": 80, "alignItems": "center", "justifyContent": "center", "children": [] },
{ "type": "frame", "id": "stage-2-cell-right", "layout": "vertical", "width": 220, "height": 80, "alignItems": "center", "justifyContent": "center",
"children": [{ "type": "rect", "id": "d-s2", "width": 200, "height": "fit-content", "fillColor": "#EAE6F3", "borderColor": "#8569CB", "borderWidth": 2, "borderRadius": 8 }] }
] }
]
}
]
},
{ "type": "connector", "connector": { "from": "c-s1", "to": "d-s2",
"lineShape": "polyline", "lineColor": "#BBBFC4", "lineWidth": 2, "endArrow": "arrow" } }
]
}
```
### 泳道配色(默认色板)
- **泳道背景**:所有泳道容器统一使用极浅灰色(如 `fillColor: "#F8F9FA"``"#FCFCFC"`),以增强物理容器的层级感,并突出内部的彩色卡片。
- **泳道边框**:所有泳道外层容器统一使用浅灰色细虚线(`borderColor: "#DEE0E3"`, `borderWidth: 1`, `borderDash: "dashed"`)。
- **泳道标题**:按 `elements/style.md` 经典色板为每条泳道分配不同的主题色,泳道 title 的 `textColor` 使用该主题色。
- **内容节点rect**:采用“浅色底 + 主题色边框”策略。`fillColor` 使用与该泳道主题色对应的极浅色(如浅蓝、浅紫等),`borderColor` 使用对应的主题色,文字 `textColor` 统一使用深色 `#1F2329`
- **连线connector**:连线颜色固定为灰色 `#BBBFC4`,不随泳道颜色变化。当连线带有文字(`label`)时,为防止文字压在边框上难以阅读,必须为连线文字设置纯白背景(`labelFillColor: "#FFFFFF"`)遮挡底纹。
提醒:避免创建“虚拟 frame”`elements/schema.md` 的说明。lane 外层必须具有可见属性以避免在编译时被跳过。
## 连线规则(强制参考 connectors.md
泳道图中所有连线的选择与写法必须严格遵循 `elements/connectors.md`,尤其是:
- `connector` 必须放在 `WBDocument.nodes` 顶层,不能嵌套在 `children`
- 默认优先使用自动绕线:`lineShape: "polyline"` / `"rightAngle"`,且不写 `waypoints`
- 未指定 `lineShape` 时默认使用 `"rightAngle"`
- 只有在必要时才强制锚点方向;锚点选择必须与节点相对位置一致
- 有连线时卡片间距必须满足 `gap >= 40`;如果连线包含文字(`label`),主轴间距必须 `gap >= 64`
- 带文字的连线必须设置 `labelFillColor: "#FFFFFF"` 遮挡底纹
泳道图语境下的落地约束:
- **默认不写锚点**,交给引擎自动推断;只有需要强制“左→右推进 / 上→下推进”时才写
- 需要表达“异步/事件流/推送”(如 SSE/Chunk使用 `lineStyle: "dashed"` 并配合 `label` 说明语义;其他参数仍按 connectors.md
- 避免连接“仅用于布局且可能被优化掉的虚拟 frame”尽量连接具体步骤卡片的节点 id参考 `elements/schema.md` 的虚拟 frame 陷阱)
## 骨架示例
> 示例展示布局的结构与对齐方法;实际节点的样式满足当前布局规则的前提下参考 `elements/style.md`
- 水平泳道示例:
```json
{
"version": 2,
"nodes": [
{
"type": "frame",
"id": "lanes-root",
"x": 40,
"y": 40,
"layout": "vertical",
"gap": 16,
"alignItems": "stretch",
"padding": 0,
"width": "fit-content",
"height": "fit-content",
"children": [
{
"type": "frame",
"id": "lane-a",
"layout": "horizontal",
"gap": 40,
"padding": 16,
"width": "fit-content",
"height": "fill-container",
"fillColor": "#F8F9FA",
"borderDash": "dashed",
"borderWidth": 1,
"borderColor": "#DEE0E3",
"children": [
{
"type": "text",
"id": "lane-a-label",
"text": "Lane A",
"width": 120,
"height": "fit-content",
"textAlign": "left",
"verticalAlign": "middle",
"fontSize": 18,
"fontWeight": "bold",
"textColor": "#5178C6"
},
{
"type": "frame",
"id": "stage-1-cell-a",
"layout": "vertical",
"gap": 8,
"padding": 0,
"width": 200,
"height": "fit-content",
"fillColor": "transparent",
"alignItems": "stretch",
"justifyContent": "center",
"children": [
{
"type": "rect",
"id": "a-s1",
"width": "fill-container",
"height": "fit-content",
"fillColor": "#E1EAFA",
"borderColor": "#5178C6",
"borderWidth": 2,
"borderRadius": 8,
"text": "[阶段 1 节点]",
"fontSize": 14,
"textColor": "#1F2329",
"textAlign": "center",
"verticalAlign": "middle"
}
]
},
{
"type": "frame",
"id": "stage-2-cell-a",
"layout": "vertical",
"gap": 8,
"padding": 0,
"width": 200,
"height": "fit-content",
"fillColor": "transparent",
"alignItems": "stretch",
"justifyContent": "center",
"children": []
}
]
},
{
"type": "frame",
"id": "lane-b",
"layout": "horizontal",
"gap": 40,
"padding": 16,
"width": "fit-content",
"height": "fill-container",
"fillColor": "#F8F9FA",
"borderDash": "dashed",
"borderWidth": 1,
"borderColor": "#DEE0E3",
"children": [
{
"type": "text",
"id": "lane-b-label",
"text": "Lane B",
"width": 120,
"height": "fit-content",
"textAlign": "left",
"verticalAlign": "middle",
"fontSize": 18,
"fontWeight": "bold",
"textColor": "#8569CB"
},
{
"type": "frame",
"id": "stage-1-cell-b",
"layout": "vertical",
"gap": 8,
"padding": 0,
"width": 200,
"height": "fit-content",
"fillColor": "transparent",
"alignItems": "stretch",
"justifyContent": "center",
"children": []
},
{
"type": "frame",
"id": "stage-2-cell-b",
"layout": "vertical",
"gap": 8,
"padding": 0,
"width": 200,
"height": "fit-content",
"fillColor": "transparent",
"alignItems": "stretch",
"justifyContent": "center",
"children": [
{
"type": "rect",
"id": "b-s2",
"width": "fill-container",
"height": "fit-content",
"fillColor": "#EAE6F3",
"borderColor": "#8569CB",
"borderWidth": 2,
"borderRadius": 8,
"text": "[阶段 2 节点]",
"fontSize": 14,
"textColor": "#1F2329",
"textAlign": "center",
"verticalAlign": "middle"
}
]
}
]
}
]
},
{
"type": "connector",
"connector": {
"from": "a-s1",
"to": "b-s2",
"lineShape": "polyline",
"lineColor": "#BBBFC4",
"lineWidth": 2,
"endArrow": "arrow",
"label": "[跨泳道交互]",
"labelFillColor": "#FFFFFF"
}
}
]
}
```
- 垂直泳道示例:见上文“垂直泳道”
- 全泳道统一 `slotWidth/slotHeight/gap`,并为每个阶段生成占位 **stage cell**(空阶段 cell 透明但保留)
- Flex 容器内不写子节点 `x/y`;对齐通过 cell 索引与统一尺寸实现
- 只有真实阶段才在对应 cell 内生成卡片;空阶段不生成卡片但保留 cell 保证网格完整
- 连线必须放在 `nodes` 顶层,并连接具体步骤卡片 id不要连接 `lane-*-body` 这类布局容器
- **水平泳道**:根容器用 `layout: "vertical"` 固定 `lanesGap`lane body 用 `layout: "horizontal"`cell 固定宽度 `slotWidth`;主轴 `gap` 统一
- **垂直泳道**:根容器用 `layout: "horizontal"` 固定 `lanesGap`lane body 用 `layout: "vertical"`cell 固定高度 `slotHeight`;主轴 `gap` 统一
- **泳道 title**title 比步骤卡片更醒目,但仍只用字号、字重、文字色强调;不要给泳道 title 额外加背景条
## 陷阱
- **各泳道复用的 stage slots 不一致**:会导致同阶段错位;`slotWidth / slotHeight / gap` 必须全泳道统一
- **把 connector 放进 children**:会导致 schema 报错或无法连线(见 connectors.md
- **把辅助容器画成可见元素**lane body 或其他支撑 frame 必须保持 `fillColor: "transparent"`,除泳道分组容器外不要额外加边框
- **手写 waypoints 过早**:先让引擎自动绕线;只有在必要时才通过 waypoints 接管
- **连线过多**:按 connectors.md 的连线数量策略降采样,否则跨泳道线会互相遮挡导致不可读