P1.U-subgraph-canvas · 子图画布 modal + 3 入口 + 双击编辑 + cyclic 检查
Worker:ClaudeA · 前端 / 部门:前端 P1-xilink / 预计:2.5d / 优先级:P1 / 状态:dispatched
🔍 触发与解锁链
- 触发:ADR-AIOS-08 v1.1 §2.5 议题④(2026-05-29 17:25 accepted)· 议题⑤ P1.U-dock-cleanup-9to4(
ad1e458)zombie 后议题④ RightDock 风格冲突预备条件解除 · 用户 2026-05-30 14:55 拍板 start。 - 依赖:P1.U-subgraph-schema-extend(0.5d 前置 · ClaudeA 同 worker 串行 · 必须先 zombie 才能开 Step 2)。本 fork prompt 落盘 = dispatched · 但 worker 实际执行步骤 2-5 必须等前置 fork zombie 后(grep 验证 types/subgraph.ts 已存在)。
- 解锁条件:本 fork zombie 后 ADR-08 议题④ 闭环 · 议题①②③④⑤ 全 zombie 后 ADR-08 整体 fulfilled。
任务定义(基于 ADR-AIOS-08 v1.1 §2.5 + §4.3 #8)
实施子图(subgraph)画布的完整 UI 交互 · 复用 P1.U-subgraph-schema-extend 落地的类型 + LinkSchema.subgraphDefs 字段。
🆕 3 个创建/编辑入口(ADR-08 §2.5 决议):
- 入口 1 · Toolbar 按钮:"新建子图" → 弹出空白子图画布(modal)
- 入口 2 · 多选封装:在父画布选中 ≥2 module → 右键 "封装为子图" → 自动推导接口端口(根据外部连线找出 inputPorts / outputPorts)
- 入口 3 · 双击编辑:双击父画布的子图节点 → 切换到子图画布(面包屑导航 Main / SubgraphA)
- 使用入口:ModuleLibrary "我的子图" 分类下 · 拖拽到任意画布(创建 SubgraphNodeInstance)
🆕 关键功能: - 子图画布 modal(ElDialog 或现有 stages/xilink modal 风格) - 面包屑导航(SubgraphBreadcrumb 组件 · 支持深 1 层 Main → SubgraphA) - cyclic 检查(useSubgraphCyclicCheck composable · 子图内部不能引用自身) - 多选封装算法(根据外部连线推导 inputPorts / outputPorts · 内部断开重连) - ModuleLibrary "我的子图" 分类 · 持久化在 linkStore.subgraphDefs
⚠️ 实施约束(ADR-08 §2.5 + §2.6 边界铁律):
- MVP 限定 2 层嵌套(主画布 → 1 层子图 · 子图内不能再嵌子图)
- 子图内部不能引用自身(cyclic 检查在创建时显式拒绝 · ElMessage error)
- 子图序列化 = 链路 schema 一部分(走 .xilink 文件持久化 · 无独立文件)
- 不允许在 stages/xilink/ 自行实装 meter / chart 组件(沿用 ADR-07 §2.3 边界铁律)
- ⭐ 全局红线 #1:响应式横竖屏 — SubgraphCanvas modal 必须支持横屏(>1024px)+ 竖屏(<768px)自适应布局,modal min-width 320px / max-width 90vw / max-height 80vh
- ⭐ 全局红线 #2:design-token 主题切换 — 所有颜色/字体/间距走 design-token(var(--color-*) / var(--font-*) / var(--spacing-*))· 不允许写死 #hex 或 px 数字 · 切换主题(亮/暗)时全部跟随
完整 prompt(直接复制粘贴 worker 终端)
[U-thread]: P1.U-subgraph-canvas
[部门]: 前端 P1-xilink
[Worker CWD]: d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies]: P1.K-xilink-canvas · P1.K-xilink-toolbar · P1.K-module-library
[优先级]: P1 · 2.5d
[隔离]: 🧵 file(同 worktree 同 branch · 文件正交并行)
[依赖]: P1.U-subgraph-schema-extend(前置 0.5d · ClaudeA 内部串行 · 必须先落 types/subgraph.ts + LinkSchema.subgraphDefs)
[ADR]: docs/08-implementation/40-aios/ADR/ADR-AIOS-08-xilink-stage-ux.md(v1.1)
[业务行为契约引用]: ADR-08 §2.5 议题④ 子图决议(三入口 + 双击编辑 + 2 层嵌套 + cyclic 检查 + 序列化走 .xilink)+ §2.6 边界铁律 #6
[参考文档]:
- ADR-AIOS-08 v1.1 §2.5(子图决议:三入口 + 嵌套 + cyclic + 序列化)+ §2.6 边界铁律 #6(子图必须可序列化)
- ADR-AIOS-07 §2.3 边界铁律 #2(严禁 stages/xilink/ 自行实装 meter)
- ADR-AIOS-08 全局红线(2026-05-30 12:53 用户加 · v3.1.16):
· 红线 #1 响应式横竖屏(modal 自适应 320-90vw / 320-80vh)
· 红线 #2 design-token 主题切换(全部 var(--*) · 无 #hex)
- 已就位代码(P1.U-subgraph-schema-extend zombie 后):
· frontend_vue3/src/types/subgraph.ts(SubgraphDefinition / SubgraphPort / SubgraphNodeInstance)
· frontend_vue3/src/types/link.ts(LinkSchema.subgraphDefs + LinkSchema.nodes 联合类型)
· frontend_vue3/src/stages/xilink/composables/useLinkLegacyMap.ts(LEGACY_LINK_FILE_MAP)
- 现有 stages/xilink/ 顶层 .vue + Toolbar / ModuleLibrary 真实路径(grep 锁定):
· grep "Toolbar|toolbar" frontend_vue3/src/stages/xilink/ → 锁定 Toolbar 入口文件
· grep "ModuleLibraryPanel|module-library" frontend_vue3/src/components/ → 锁定 ModuleLibrary 入口
· grep "ContextMenu|context-menu|@contextmenu" frontend_vue3/src/stages/xilink/ → 锁定右键菜单
- 不动 ADR-08 议题①/②/③/⑤ 已 zombie 或 dispatched 的:
· P1.U-dock-cleanup-9to4(ad1e458 · zombie · drawers/*)
· P1.UA8-link-error-check(BottomProblems · §2.2 dispatched)
· P1.UA8-perf-monitor-frontend(DrawerMetrics · §2.3 dispatched)
· P1.UA8-runtime-selector-ui(DrawerEngine · §2.4 dispatched)
- 不动 ADR-11 v1.3 fork 1-v3(stages/xitune/*)+ fork 1b-v2(backend_csharp/*)文件
【背景】
ADR-08 议题④ 子图(subgraph)是右 dock 5 议题中的中量级议题(2.5d 主体)。schema 已由 P1.U-subgraph-schema-extend 前置落地(types/subgraph.ts + LinkSchema.subgraphDefs + LEGACY_LINK_FILE_MAP)。本 fork 实施完整 UI 交互。
ADR-08 §2.5 决议给出三入口完整设计 · 本 fork 严格按决议落地 · 不擅自简化或扩展。MVP 限 2 层嵌套 + cyclic 检查 + 序列化走 .xilink 文件(无独立文件)。
⭐ **本 fork 含 UI · 必须满足全局 2 红线**(2026-05-30 12:53 v3.1.16 用户加):
- 红线 #1:SubgraphCanvas modal 响应式横竖屏(>1024px / <768px 自适应 · min-width 320px / max-width 90vw / max-height 80vh)
- 红线 #2:design-token 主题切换(全部颜色/字体/间距走 var(--color-*) / var(--font-*) / var(--spacing-*) · 切换亮/暗主题全部跟随)
【执行步骤】(7 步 · 2.5d)
Step 0 · 依赖核查(0.05d · 必跑)
- grep "interface SubgraphDefinition|export.*SubgraphDefinition" frontend_vue3/src/types/subgraph.ts → 必须存在(P1.U-subgraph-schema-extend zombie 才能继续)
- 若 types/subgraph.ts 不存在 → 暂停 · 等前置 fork zombie · 不准擅自实施 schema
- grep "subgraphDefs" frontend_vue3/src/types/link.ts → 必须存在
- 输出依赖核查结果到 commit message body
Step 1 · 真值核查(0.1d)
- grep "Toolbar" frontend_vue3/src/stages/xilink/ → 锁定 Toolbar 真实文件(可能 LinkToolbar.vue / TopToolbar.vue 等)
- grep "ModuleLibraryPanel" frontend_vue3/src/ → 锁定 ModuleLibraryPanel 真实路径
- grep "ContextMenu|@contextmenu" frontend_vue3/src/stages/xilink/ → 锁定右键菜单或确认需新建
- grep "ElDialog|el-dialog" frontend_vue3/src/stages/xilink/ → 学习现有 modal 风格惯例
- 输出真值清单到 commit message body
Step 2 · 新建 SubgraphCanvas.vue + 面包屑(0.6d)
- 路径:frontend_vue3/src/stages/xilink/SubgraphCanvas.vue
· ElDialog 或现有 modal 风格 · 全屏式或大尺寸(width="80%" max-height="80vh")
· ⭐ 红线 #1:响应式 — @media (max-width: 768px) → width="95%" / @media (min-width: 1024px) → width="80%" max-width 1280px
· ⭐ 红线 #2:全部用 design-token — bg/border/shadow 走 var(--color-bg-*) / var(--shadow-*)
· 内部嵌入现有 LinkCanvas 组件(grep 锁定 · 复用而非重写)
· 顶部 SubgraphBreadcrumb 组件(显示 "Main / SubgraphA")
· 底部 toolbar:保存子图 / 取消 / 命名输入框
- 路径:frontend_vue3/src/stages/xilink/SubgraphBreadcrumb.vue
· ElBreadcrumb 风格 · 第一级 "Main"(返回父画布)· 第二级当前子图名
· ⭐ MVP 限 2 层 · 不允许第三级渲染(本 fork 显式拒绝)
Step 3 · 实施 useSubgraph composable(0.4d · 核心逻辑)
- 路径:frontend_vue3/src/stages/xilink/composables/useSubgraph.ts
- export 函数:
· createSubgraphFromSelection(selectedNodeIds, edges) → SubgraphDefinition(自动推导 inputPorts / outputPorts · 多选封装算法)
· openSubgraphEditor(subgraphDefId) → 切换到子图画布(emit event 给上层)
· saveSubgraph(def) → linkStore.subgraphDefs.push 或 update
· deleteSubgraph(defId) → 检查父画布是否有 SubgraphNodeInstance 引用 · 有则拒绝
- 多选封装算法说明(写代码注释):
· 输入:selectedNodeIds[]、当前 LinkSchema.edges
· 找"外部 → 内部"连线 = inputPorts(target 在 selected · source 在外)
· 找"内部 → 外部"连线 = outputPorts(source 在 selected · target 在外)
· 内部连线保留(全在 selected 内)· SubgraphDefinition.edges
· 外部连线在父画布断开 · 重连到 SubgraphNodeInstance 的 inputPorts / outputPorts
- 路径:frontend_vue3/src/stages/xilink/composables/useSubgraphCyclicCheck.ts
· isCyclicSubgraph(def, allDefs) → boolean
· MVP:子图内部不能再含 SubgraphNodeInstance(2 层嵌套限制)
· 创建 / 编辑保存时调用 · 拒绝则 ElMessage error 并 return null
Step 4 · 实施 3 入口交互(0.6d)
- 入口 1 · Toolbar 按钮(改 stages/xilink/Toolbar 真实文件):
· 加按钮 "新建子图"(图标 + 文字)· @click 触发空白 SubgraphDefinition + 打开 SubgraphCanvas
· 不破坏现有 Toolbar 按钮顺序(在合理位置插入)
- 入口 2 · 多选封装(改 CanvasContextMenu.vue 或新建):
· 右键菜单加 "封装为子图"(仅在 selectedNodeIds.length ≥ 2 时启用 · disabled 否则)
· @click 触发 createSubgraphFromSelection · 成功则在父画布替换为 SubgraphNodeInstance
- 入口 3 · 双击编辑(改 LinkCanvas 节点 dblclick handler):
· 节点是 SubgraphNodeInstance 类型时 · @dblclick 触发 openSubgraphEditor(node.subgraphDefId)
· 普通 ModuleNode 双击保持现有行为
- 使用入口 · ModuleLibrary "我的子图":
· 改 ModuleLibraryPanel.vue · 加新分类 "我的子图"(ElTabs 或 ElCollapse 节)
· 数据源 linkStore.subgraphDefs.map(def => ({ id: def.id, name: def.name, type: 'subgraph' }))
· 拖拽到画布时创建 SubgraphNodeInstance(subgraphDefId 引用 def.id)
Step 5 · 集成 linkStore.subgraphDefs 持久化(0.3d)
- 改 frontend_vue3/src/stores/linkStore.ts:
· 加 actions:addSubgraphDef / updateSubgraphDef / removeSubgraphDef
· 序列化 .xilink 文件时含 subgraphDefs(已由 P1.U-subgraph-schema-extend 落地 schema)
· 加载 .xilink 文件时走 LEGACY_LINK_FILE_MAP(已落地 · 无 subgraphDefs 字段自动注入 [])
- 不动 linkStore 现有 errors / validate 等字段(P1.UA8-link-error-check 范围)
Step 6 · vitest 单测 + e2e 验证(0.4d)
- 新增 stages/xilink/__tests__/subgraph-canvas.spec.ts:
· case 1:Toolbar "新建子图" 按钮可见 · 点击打开 SubgraphCanvas modal
· case 2:多选 ≥2 module · 右键菜单 "封装为子图" 启用 · 点击成功推导 inputPorts / outputPorts
· case 3:双击 SubgraphNodeInstance 进入子图画布 · 面包屑显示 "Main / SubgraphA"
· case 4:ModuleLibrary "我的子图" 分类显示 linkStore.subgraphDefs · 拖拽到画布创建 SubgraphNodeInstance
· case 5:cyclic 检查:子图内部含 SubgraphNodeInstance 时保存被拒绝 · ElMessage error
· case 6:序列化往返:SubgraphDefinition + SubgraphNodeInstance 都能存 .xilink 文件 + 加载恢复
· case 7(响应式 · 红线 #1):mock window.innerWidth 320 / 768 / 1280 → SubgraphCanvas modal 宽度自适应
· case 8(主题 · 红线 #2):mock document.documentElement.dataset.theme = 'dark' → SubgraphCanvas 背景色跟随
- 不退化 frontend 现有 361 测试基线(P1.U-subgraph-schema-extend zombie 后 +5 = 361)· 预计本 fork +8 case → 369
Step 7 · 自查 + commit(0.05d)
- npm run typecheck 全绿(0 error)
- npm run test:unit 不退化(基线 361/3 · 新增 +8 → 预计 369/3)
- 检查:grep "self-implemented meter|new RMSMeter\(\)" 在 stages/xilink/SubgraphCanvas* 应为 0(沿用边界铁律)
- 检查 ⭐:grep "#[0-9a-fA-F]{6}|background-color:.*#" 在 SubgraphCanvas.vue 应为 0(红线 #2 · 全部走 var(--*))
- 检查 ⭐:在 SubgraphCanvas.vue 必须含 @media 响应式断点(红线 #1)
- 不动 ADR-08 议题①/②/③/⑤ 已 zombie 或 dispatched 的 P1.UA8-* 三 fork + dock-cleanup
- commit subject: feat(P1.U-subgraph-canvas): xilink subgraph canvas modal + 3 entries (toolbar/select/dblclick) + 2-level nesting + cyclic check + ModuleLibrary "我的子图" (ADR-08 #8)
- commit trailer 三元组:[step=7/7] [pid=P1] [uid=U-subgraph-canvas] [occupies=P1.K-xilink-canvas+P1.K-xilink-toolbar+P1.K-module-library] [files=stages/xilink/SubgraphCanvas.vue,SubgraphBreadcrumb.vue,Toolbar.vue,contextmenu/CanvasContextMenu.vue,composables/useSubgraph.ts,useSubgraphCyclicCheck.ts,components/module-library/ModuleLibraryPanel.vue,stores/linkStore.ts]
【验收】(stop 前必跑)
形式合规:
☐ npm run typecheck 全绿
☐ npm run test:unit 不退化(基线 361/3)+ 新增 +8 case 全过(预计 369/3)
☐ grep "#[0-9a-fA-F]{6}" stages/xilink/SubgraphCanvas* 为 0(红线 #2)
☐ grep "@media" stages/xilink/SubgraphCanvas.vue 至少 1 处(红线 #1)
☐ 不在 stages/xilink/ 自行实装 meter 组件(grep 验证 · 沿用 ADR-07 §2.3)
☐ 不动 P1.UA8-* 三 fork dispatched 文件 + P1.U-dock-cleanup-9to4 zombie 文件
业务行为契约(端到端真值 · 必跑):
☐ 入口 1 e2e:Toolbar "新建子图" → modal 打开 → 内部画布空白 → 命名保存 → linkStore.subgraphDefs 加 1 项
☐ 入口 2 e2e:父画布选 3 module + 1 connection → 右键 "封装为子图" → 父画布替换为 1 SubgraphNodeInstance · inputPorts / outputPorts 推导正确
☐ 入口 3 e2e:双击 SubgraphNodeInstance → 切换到子图画布 → 面包屑 "Main / SubgraphA" → 修改后保存返回父画布
☐ ModuleLibrary "我的子图" e2e:子图保存后在 ModuleLibrary 可见 → 拖拽到任意画布创建 SubgraphNodeInstance
☐ cyclic 检查 e2e:子图内部加 SubgraphNodeInstance → 保存被拒绝 ElMessage error · 不破坏画布状态
☐ 序列化 e2e:含子图的 .xilink 文件 → 保存 → 重启页面 → 加载 → SubgraphDefinition + SubgraphNodeInstance 完整恢复
☐ ⭐ 红线 #1 e2e:浏览器 dev tools 切换 320 / 768 / 1280 / 1920 宽度 · SubgraphCanvas modal 自适应 · 不破坏布局
☐ ⭐ 红线 #2 e2e:切换亮/暗主题(主菜单或 design-token toggle)· SubgraphCanvas 背景/字体/边框颜色全部跟随 · 无残留 hardcode 颜色
【commit】
- subject + 三元组 trailer 见 Step 7
- 7 天宽限期内三元组缺失仅 warning · 6/2 起 strict mode 硬拒
【禁止】(8 项红线)
1. ❌ 禁止在 stages/xilink/ 自行实装 RMSMeter / SpectrumChart / PhaseChart 等 meter 组件(沿用 ADR-07 §2.3 第 2 项铁律)
2. ❌ 禁止动 ADR-08 议题①/②/③ 已 dispatched 的 P1.UA8-* 三 fork 文件 + 已 zombie 的 P1.U-dock-cleanup-9to4 落地文件(drawers/* + RightDock.vue tab)
3. ❌ 禁止突破 MVP 2 层嵌套限制(子图内部不能再嵌子图 · cyclic 检查必须显式拒绝)
4. ❌ 禁止子图独立持久化文件(必须走 .xilink 文件嵌入 · 沿用 ADR-08 §2.6 边界铁律 #6)
5. ❌ 禁止跳过依赖核查(Step 0 必须确认 types/subgraph.ts 已存在 · 不存在则等前置 fork zombie)
6. ❌ 禁止扩展 SubgraphDefinition 字段(P1.U-subgraph-schema-extend 已锁定 · 仅消费不修改)
7. ❌ 禁止写死颜色 hex 值或 px 像素值(红线 #2 · 全部走 var(--*) design-token)
8. ❌ 禁止跳过响应式 @media 断点(红线 #1 · 必须支持 320/768/1024/1280 宽度自适应)
解锁链(本任务 zombie 后)
- ✅ ADR-08 §4.3 #8 实施清单闭环 · 议题④ 全部完成(进度 2/2)
- ✅ ADR-08 议题①②③④⑤ 全 zombie 后 → ADR-08 整体 fulfilled(剩 P1.UA8-* 三前端 + P_contracts.U-protocol-v2-bootstrap)
- ✅ 子图设计模式可被 XiTest stage / XiTune stage 借鉴(后续 ADR 复用)
风险评估
| 风险 | 缓解 |
|---|---|
| 前置 P1.U-subgraph-schema-extend 未 zombie 就进 Step 2+ | Step 0 强制依赖核查 · grep types/subgraph.ts 不存在则暂停 |
| 现有 Toolbar / ModuleLibrary / ContextMenu 真实路径与预期不符 | Step 1 grep 锁定真实文件 · 必要时调整路径 |
| 多选封装算法推导 inputPorts/outputPorts 错误 | Step 3 算法注释清晰 · Step 6 case 2 显式 e2e 验证推导正确性 |
| cyclic 检查覆盖不全(用户绕过 UI 直接改 linkStore) | Step 3 useSubgraphCyclicCheck 在 saveSubgraph + load .xilink 两处都调用 |
| 子图序列化后再加载字段丢失 | 复用 P1.U-subgraph-schema-extend 已落 LEGACY_LINK_FILE_MAP + Step 6 case 6 e2e 验证往返 |
| 红线 #1 / #2 落地不彻底(只在 SubgraphCanvas 落 · 子组件 hardcode) | Step 7 grep #[0-9a-fA-F]{6} 全 stages/xilink/SubgraphCanvas* 应为 0 |
| 与 ADR-11 fork 1-v3(ClaudeC stages/xitune/)+ ADR-08 P1.UA8- 三 fork 文件并发冲突 | 全文件路径正交(本 fork 仅动 stages/xilink/Subgraph + Toolbar + composables/useSubgraph + ModuleLibraryPanel + linkStore) |
历史
| 时间 | 事件 | hash |
|---|---|---|
| 2026-05-30 14:58 | dispatched · 用户拍板 start P1.U-subgraph-canvas(2.5d 议题④ 主体 · 依赖 P1.U-subgraph-schema-extend 0.5d 前置) | — |
| 2026-05-31 08:43 | zombie · ClaudeA 完成汇报"所有 7 步骤全部完成" · 与 P0.U-measurement-impulse-spectrogram 同 commit af945e2(P0 并发 agent 连带提交) · 🎉 ADR-08 议题④ 主体闭环(9/10 fork zombie · 仅 protocol-v2-bootstrap blocked) |
af945e2 |