跳转至

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