跳转至

P3.UA25R1.F2-flow-readonly-dock-subgraph-display · FlowReadonlyDock SVG 子图示意图渲染

Worker:ClaudeA · 部门:前端 P3-xitune · 预计:1.0d · 优先级:P0 · 状态:dispatched · isolation:🧵 file(同 worktree 同 branch · 与 F3-R1 文件正交可并行 · API 依赖 F3-R1)

🔍 触发与解锁链

触发:用户 2026-06-18 17:14 拍板方案 A · 双连 dispatched F3-R1 + F2-R1 · 同 ClaudeA 自管串行节奏 · F2-R1 完善 FlowReadonlyDock SVG 子图节点视觉(嵌套小矩形 + 蓝紫色 + 📦 emoji)+ 双击改 openSubgraphTab。

用户原话(verbatim · ADR-25-R1 §1.1):

"完善 FlowReadonlyDock(原来 link · 第二个 dock)显示子图示意图" "双击可以打开或者跳转到子图 tab 主页"

架构契约(ADR-25-R1 §3.2-R1 ① 输入/输出契约 · 业务契约 5 必填段): - 契约 A · ModuleInstance 数据扩展(只读消费):mod.subgraphMeta?.{defId, name, moduleCount, nestedNodes[]}(由 ADR-08-R1 / ADR-16 子图运行时提供 · F2-R1 不动数据源) - 契约 B · SVG 渲染分支:L54-83 模块节点 <g class="flow-node">:data-is-subgraph="!!mod.subgraphMeta" 属性 + 内部 <g v-if="mod.subgraphMeta"> 子图分支(蓝紫色主框 + 嵌套小矩形 max 5×5 + 📦 emoji 顶部) / <rect v-else> 普通模块分支(现有) - 契约 C · 嵌套小矩形布局:子图主框 200×80 · 内部小矩形 30×16 · 网格 max 5 列 × 5 行 = 25 nodes · 超 25 显示前 24 个 + 第 25 格 "..." - 契约 D · 双击行为改造:L153-157 onModuleDblClick 子图分支调 openSubgraphTab(mod.subgraphMeta.defId) · 普通模块保持 floating.openDialog - 契约 E · 单击行为不变:L148-151 onModuleClick 仍设 highlightId + emit eventBus(子图节点也响应单击高亮 · 与 chain-mini-bar 不同 · ADR-25-R1 §3.2-R1 vs §3.1-R1) - 视觉规格:子图节点 fill rgba(157,78,221,0.15)(蓝紫 · 与 sink 同色但 emoji 区分)+ stroke 同普通节点 + 顶部 📦 ${name} (${moduleCount}个) text · 高亮时 stroke #D4A574 不变 - 5 类失败回退(ADR-25-R1 §3.2-R1 ③):subgraphDefs[defId] 不存在 → 主框 + ⚠️ 红字 / 子图内 module=0 → 主框 + 0 嵌套 / module>25 → 24+省略号 / 嵌套子图 → 内层仍是普通小矩形(不递归) / SVG 渲染异常 → 退化为单层 module 渲染

解锁链(本任务 zombie 后): - ✅ FlowReadonlyDock 主链路双击 📦 EQ_A 入口齐(与 chain-mini-bar 双击双入口齐 · 用户两个交互路径都能进子图 tab) - ⏳ F4-R1 e2e blocked-by-F2-R1+F3-R1 · ClaudeC 0.5d → ADR-25 整体闭环 🏆

任务定义(基于 ADR-25-R1 §3.2-R1 + §4.2)

子任务 ① · FlowReadonlyDock SVG 模板加 isSubgraph 分支(0.4d)

Step 1.1:修改 frontend_vue3/src/stages/xitune/drawers/FlowReadonlyDock.vue L54-83 模块节点 <g>: - 加 :data-is-subgraph="!!mod.subgraphMeta" 属性(便于 e2e 选择器) - 加 :data-def-id="mod.subgraphMeta?.defId" 属性 - 内部分两支:<template v-if="mod.subgraphMeta"> 子图分支(主框 + 嵌套小矩形 + 📦 emoji)/ <template v-else> 现有 <rect> + <text>(不变)

Step 1.2:子图分支 SVG 模板(嵌入到 <g class="flow-node"> 内):

<template v-if="mod.subgraphMeta">
  <!-- 主框:蓝紫色 · 同尺寸 200×80 -->
  <rect
    class="flow-subgraph-main"
    :x="mod.canvasInfo?.position?.x ?? 0"
    :y="mod.canvasInfo?.position?.y ?? 0"
    :width="mod.canvasInfo?.size?.width ?? 200"
    :height="mod.canvasInfo?.size?.height ?? 80"
    fill="rgba(157,78,221,0.15)"
    :stroke="highlightId === mod.instanceId ? '#D4A574' : 'rgba(157,78,221,0.45)'"
    :stroke-width="highlightId === mod.instanceId ? 2.5 : 1.5"
    rx="6"
    ry="6"
  />
  <!-- 顶部 📦 emoji + name (count个) -->
  <text
    class="flow-subgraph-label"
    :x="(mod.canvasInfo?.position?.x ?? 0) + 8"
    :y="(mod.canvasInfo?.position?.y ?? 0) + 14"
    fill="#c8d3df"
    font-size="11"
    font-family="system-ui, -apple-system, sans-serif"
    pointer-events="none"
  >📦 {{ subgraphLabel(mod) }}</text>
  <!-- 嵌套小矩形 max 5×5 -->
  <g class="flow-subgraph-nested-group">
    <rect
      v-for="(node, idx) in nestedNodesToRender(mod)"
      :key="`${mod.instanceId}-nested-${idx}`"
      class="flow-subgraph-nested"
      :x="(mod.canvasInfo?.position?.x ?? 0) + 8 + (idx % 5) * 36"
      :y="(mod.canvasInfo?.position?.y ?? 0) + 24 + Math.floor(idx / 5) * 20"
      width="30"
      height="16"
      :fill="getModuleBg(node.moduleId)"
      stroke="rgba(100,160,220,0.25)"
      stroke-width="1"
      rx="2"
      ry="2"
      pointer-events="none"
    />
  </g>
</template>

Step 1.3:setup 中新增 helper(参考现有 shortModName / getModuleBg 风格):

function subgraphLabel(mod: ModuleInstance): string {
  const meta = mod.subgraphMeta
  if (!meta) return ''
  const name = meta.name?.length > 8 ? meta.name.slice(0, 7) + '…' : meta.name
  return `${name} (${meta.moduleCount}个)`
}

function nestedNodesToRender(mod: ModuleInstance): ModuleInstance[] {
  const meta = mod.subgraphMeta
  if (!meta?.nestedNodes) return []
  if (meta.nestedNodes.length <= 25) return meta.nestedNodes
  // ADR-25-R1 §3.2-R1 ③:超 25 节点 · 显示前 24 个(第 25 格留给 "..." · 但简化处理:截断到 24)
  return meta.nestedNodes.slice(0, 24)
}

子任务 ② · onModuleDblClick 子图分支改造(0.1d)

Step 2.1:修改 FlowReadonlyDock.vue L153-157 onModuleDblClick: - 加 isSubgraph 分支:

function onModuleDblClick(mod: ModuleInstance) {
  highlightId.value = mod.instanceId
  if (mod.subgraphMeta?.defId) {
    // ADR-25-R1 §3.2-R1 双击改 openSubgraphTab(F3-R1 暴露)
    const ok = openSubgraphTab(mod.subgraphMeta.defId)
    if (!ok) {
      console.warn('[F2-R1] 子图定义已不存在', mod.subgraphMeta.defId)
    }
    return
  }
  // 普通模块保持现有
  floating.openDialog(mod.instanceId, mod.moduleId)
  eventBus.emit('xitune:active-module' as any, { stage: 'xitune', instanceId: mod.instanceId } as any)
}

Step 2.2:setup 中 import + 实例化(API 依赖 F3-R1):

import { useSubgraphTabs } from '@/stages/xitune/composables/useSubgraphTabs'
const { openSubgraphTab } = useSubgraphTabs()

Stub 占位策略(若 F3-R1 未先 commit): - 在 setup 顶部用 try-catch 包裹 import + 提供 fallback:

let openSubgraphTab: (defId: string) => boolean
try {
  const tabs = useSubgraphTabs()
  openSubgraphTab = tabs.openSubgraphTab
} catch {
  // F3-R1 pending · stub 占位
  openSubgraphTab = (defId: string) => {
    console.warn('[F2-R1] F3-R1 pending · openSubgraphTab stub', defId)
    return false
  }
}
- F3-R1 zombie 后 · 删除 stub 块 · 直接 import + 用

子任务 ③ · 单击行为不变 + onModuleClick 高亮保留(0.0d 含验收)

Step 3.1:L148-151 onModuleClick 完全不动(子图节点也响应单击高亮 · 与 chain-mini-bar §3.1-R1 单击不响应不同 · 因为 FlowReadonlyDock 是只读视图 · 单击高亮无副作用)

子任务 ④ · vitest +6 case + 类型/构建/全测试基线零回归(0.4d)

Step 4.1:新建 frontend_vue3/src/stages/xitune/drawers/__tests__/FlowReadonlyDock.spec.ts(参考 useChainMiniNodes.spec.ts 风格 · 6 case): - T1 普通模块渲染 <rect> + <text> 单层(现有行为 · 零回归) - T2 子图模块渲染 flow-subgraph-main + flow-subgraph-label + N 个 flow-subgraph-nested - T3 子图模块嵌套节点数 = subgraphMeta.moduleCount(若 ≤ 25) - T4 子图模块嵌套节点超 25 → 截断到 24 个 - T5 双击子图模块 → openSubgraphTab(defId) 被调用 - T6 双击普通模块 → floating.openDialog 被调用(零回归)

Step 4.2:vue-tsc --noEmit 0 errors · npm run build 0 errors · npm run test 全过(基线 + 6 新 case)

子任务 ⑤ · 浏览器实测端到端(0.1d)

Step 5.1:启动 backend(dotnet run)+ frontend(npm run dev)· 准备 fixture(xilink 工程含 1 子图 EQ_A · 主链路 1 EQ_A 实例 + 普通 source/sink/gain)· 切到 xitune

Step 5.2:验收点(ADR-25-R1 §3.2-R1 ④ 用户操作流): - ☐ FlowReadonlyDock SVG 显示主链路(source → 📦 EQ_A → sink) - ☐ 📦 EQ_A 节点蓝紫色主框 + 顶部 📦 EQ_A (3个) text + 内部 3 个嵌套小矩形(peq + gain + delay 各 30×16) - ☐ 嵌套小矩形按 getModuleBg 染色(peq 古铜金 / gain 蓝 / delay 绿 等) - ☐ 单击 📦 EQ_A 主框 → 高亮 stroke 黄色(eventBus emit 也触发 · 与现有行为一致) - ☐ 双击 📦 EQ_A 主框 → openSubgraphTab('eq-a-def') 调用 · doc tabs 加 📦 EQ_A.subgraph(若 F3-R1 已 zombie)/ console.warn stub(若 F3-R1 pending) - ☐ 单击/双击普通 source/sink/gain → 现有行为零回归(高亮 + floating.openDialog) - ☐ 子图内含 30 个 module 的极端 case → 显示前 24 个嵌套小矩形(截断 · 不溢出) - ☐ 性能:50 modules 含 5 子图各含 5 module · SVG 渲染 ≤ 50ms(开发者工具 Performance 标尺)

完整 prompt(直接复制粘贴 ClaudeA 终端)

[U-thread] P3.UA25R1.F2-flow-readonly-dock-subgraph-display · ADR-25-R1 §3.2-R1(完善 FlowReadonlyDock SVG · 1.0d)
[部门] 前端 P3-xitune
[Worker CWD] d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies] P3.K-shared-xitune-flow-readonly-dock
[优先级] P0(1.0d · API 依赖 F3-R1 暴露的 openSubgraphTab · 文件正交可并行)
[ADR] docs/08-implementation/40-aios/ADR/ADR-AIOS-25-R1-flow-readonly-subgraph-and-doc-tabs.md(必读 §3.2-R1 + §4.2 F2-R1 fork 表 + §5 风险表)
[isolation] file(同 worktree 同 branch · 与 F3-R1 文件正交)
[API 依赖] useSubgraphTabs.openSubgraphTab(由 F3-R1 暴露) · 若 F3-R1 未先 commit · 用 stub 占位

[参考文档绝对路径]
  - 业务契约:ADR-25-R1 §3.2-R1 完整 5 必填段(① subgraphMeta 数据扩展 + SVG 嵌套 max 5×5 + 蓝紫色 fill / ② 5 行收敛判据 / ③ 5 类失败回退 / ④ 5 步操作流 / ⑤ playwright e2e 模板)
  - 用户 verbatim(ADR-25-R1 §1.1):"完善 FlowReadonlyDock(原来 link · 第二个 dock)显示子图示意图 + 双击打开/跳转子图 tab 主页"
  - 范式 commits(worker 必读):
    * 0bb4422 P3.UA25R1.F1 combo hotfix(同 ClaudeA 同 ADR-25-R1 · isolation: file · 完整 prompt 标本 278 行 · 8 段结构)
    * d9a2e1c P3.A25.F1 useChainMiniNodes(本任务消费的 subgraphMeta 数据契约源 · ChainMiniNode 接口)
  - F3-R1 prompt(同步派发 · 暴露 openSubgraphTab API):
    * prompts/active/P3.UA25R1.F3-doc-tabs-multi-tab-routing.md(API 契约对外承诺段)

[现有组件 grep 真值(.clinerules v3.0 ADR-23→25→25-R1 三连教训铁律 · 派发前已核查)]
  ① 待修文件 frontend_vue3/src/stages/xitune/drawers/FlowReadonlyDock.vue:
     - L1-13 文件头注释(v5 单层 SVG 自绘 mini-map · 不动)
     - L34-85 SVG 主体 + 模块节点 `<g v-for="mod in modules">` ⚠️ L54-83 加 isSubgraph 分支
     - L97-108 setup import + useFloatingStore + useLinkStore + eventBus(本 fork 加 useSubgraphTabs)
     - L110-115 viewport 状态(localPan / localZoom / highlightId · 不动)
     - L117-132 helper(shortModName / getModuleBg · 不动 · 本 fork 加 subgraphLabel + nestedNodesToRender)
     - L134-145 connPath bezier(不动)
     - L148-151 onModuleClick(不动 · 子图节点也响应单击高亮)
     - L153-157 onModuleDblClick ⚠️ 子图分支改 openSubgraphTab · 普通模块保持 floating.openDialog
     - L161-200 fitToContent(不动)
     - L203-220+ 拖拽平移 / 滚轮缩放(不动)
  ② 待新建文件:
     - frontend_vue3/src/stages/xitune/drawers/__tests__/FlowReadonlyDock.spec.ts(NEW · +6 case)
  ③ 数据源(只读 · 不动):
     - linkStore.modules:含 .subgraphMeta?.{defId, name, moduleCount, nestedNodes[]}(F1 d9a2e1c 数据源)
     - useSubgraphTabs.openSubgraphTab(F3-R1 暴露 · 本 fork 消费 · stub fallback)
     - getModuleBg helper(L123-132 已支持 source/sink/gain/eq/delay/mixer/limiter 8 类)

[文件正交策略](.clinerules v3.0 §isolation):
  isolation: file · 同 worktree 同 branch · 改 1 + 新建 1 = 2 文件
  与 F3-R1(改 xitune/index.vue + 新建 useSubgraphTabs.ts · 1.5d)文件正交可并行
  API 依赖 F3-R1 的 openSubgraphTab · 若 F3-R1 未先 commit · 用 try-catch stub 占位 + commit 后切真实 import

[API 依赖处理(F3-R1 未 zombie 时的 stub 策略)]
  方案 A · F3-R1 已先 commit useSubgraphTabs.ts:
    import { useSubgraphTabs } from '@/stages/xitune/composables/useSubgraphTabs'
    const { openSubgraphTab } = useSubgraphTabs()
    // 直接用 · 类型对齐 boolean

  方案 B · F3-R1 pending(本 fork 先动手):
    let openSubgraphTab: (defId: string) => boolean
    try {
      const m = await import('@/stages/xitune/composables/useSubgraphTabs')
      openSubgraphTab = m.useSubgraphTabs().openSubgraphTab
    } catch {
      openSubgraphTab = (defId: string) => {
        console.warn('[F2-R1] F3-R1 pending · openSubgraphTab stub', defId)
        return false
      }
    }
    // F3-R1 zombie 后 · 删除 try-catch · 改静态 import

【背景】
  用户 2026-06-15 16:06 verbatim 三段拆解纠正 ADR-25 v0.1 · 16:14 ADR-25-R1 落盘 · 16:45 accept · F1.1-R1 0bb4422 已 zombie · 17:14 用户拍板方案 A 双连 dispatched F3-R1 + F2-R1。
  本 fork F2-R1 完善 FlowReadonlyDock(原 link · 第二个 dock · v5 自绘 SVG mini-map 402 行)子图节点视觉:嵌套小矩形 max 5×5 + 蓝紫色 + 📦 emoji 顶部 + 双击改 openSubgraphTab(F3-R1 暴露)。
  单击行为不动(L148-151)· 子图节点单击仍响应高亮(与 chain-mini-bar §3.1-R1 单击不响应不同 · 因为 FlowReadonlyDock 是只读视图 · 单击高亮无副作用)。
  教训承接(ADR-25-R1 §9.3):用户原话名词必须 grep 找具体文件+行号 → 本 prompt [现有组件 grep 真值] 段已落实 8 个真值锚点。

【架构关键约束】
  ⚡ 严守 .clinerules v3.0 ADR-23→25→25-R1 三连教训铁律:用户原话"完善 FlowReadonlyDock 显示子图示意图"必须解析为现有 SVG `<g class="flow-node">` L54-83 模板(本 fork 完善 · 不重写)
  📋 SVG 模板加 isSubgraph 分支:`<template v-if="mod.subgraphMeta">` 子图分支(主框 + 嵌套 + emoji) / `<template v-else>` 现有 rect+text(零回归)
  📋 嵌套小矩形 30×16 · max 5×5 网格(超 25 截断 24)· fill 调 getModuleBg(嵌套节点 moduleId)
  📋 子图主框 fill `rgba(157,78,221,0.15)` 蓝紫色 · stroke `rgba(157,78,221,0.45)` · 高亮时 stroke `#D4A574` 黄色(同普通节点)
  📋 顶部 text `📦 ${subgraphLabel}` · subgraphLabel = name(超 8 字截断 + …)+ ` (${moduleCount}个)`
  📋 onModuleDblClick 子图分支调 openSubgraphTab(defId)· 普通模块保持 floating.openDialog · 单击行为完全不动
  📋 nestedNodesToRender helper:超 25 节点截断到 24(第 25 格预留省略号 · 简化处理直接截断 24 · ADR-25-R1 §3.2-R1 ③)
  📋 渲染性能:50 modules × 5 子图各 5 module = 250 嵌套小矩形 · SVG 直绘 < 50ms(单层 transform · 无虚拟列表)
  🚫 严禁动 useChainMiniNodes.ts(F1 资产 · 本 fork 消费 subgraphMeta 数据但不动 composable)
  🚫 严禁动 xitune/index.vue(F3-R1 范围)
  🚫 严禁动 useSubgraphTabs.ts(F3-R1 范围 · 本 fork 仅 import + 用)
  🚫 严禁动 onModuleClick L148-151(单击高亮保持现有 · 子图节点也响应)
  🚫 严禁动 fitToContent / 拖拽平移 / 滚轮缩放(现有功能不变)
  🚫 严禁破坏普通模块 onModuleDblClick → floating.openDialog(零回归)

【执行步骤】
  Step 0 · 文件注入真值核查(强制门槛 · ADR-25-R1 §9.3 三连教训承接)
    - read frontend_vue3/src/stages/xitune/drawers/FlowReadonlyDock.vue 全文 402 行 · 确认本 prompt [现有组件 grep 真值] 8 个锚点对齐(L1-13 / L34-85 / L97-108 / L110-115 / L117-132 / L148-151 / L153-157 / L161-220)
    - read frontend_vue3/src/stages/xitune/composables/useChainMiniNodes.ts 全文 · grep "subgraphMeta" 确认 nestedNodes 字段是否落地(若未落地 · 本 fork 用 mod.subgraphMeta?.nestedNodes ?? [] 防御 · 实际渲染 0 嵌套小矩形)
    - read frontend_vue3/src/types/module.ts grep "subgraphMeta" 确认 ModuleInstance.subgraphMeta 类型(若未声明 · 本 fork 用 (mod as any).subgraphMeta · 等 ADR-08-R1 / ADR-16 真值核查)
    - read F3-R1 prompt:prompts/active/P3.UA25R1.F3-doc-tabs-multi-tab-routing.md(API 契约对外承诺段)确认 openSubgraphTab 签名
    - 检查 prompts/done/ADR-AIOS-25-R1/ 或 active/ 是否有 F3-R1 zombie commit hash · 若有则按方案 A 静态 import · 若无则按方案 B stub fallback
    - read 标本 prompts/done/ADR-AIOS-25/P3.UA25R1.F1-mini-bar-click-no-response-on-subgraph--0bb4422.md(同 ClaudeA 同 ADR-25-R1 isolation:file · 8 段结构标本)
    - 留 commit log:Step 0 六层核查记录

  Step 1 · FlowReadonlyDock SVG 模板加 isSubgraph 分支 0.4d(子任务 ①)
    - 改 L54-83 模块节点 `<g class="flow-node">` 加 :data-is-subgraph + :data-def-id 属性
    - 内部分支:`<template v-if="mod.subgraphMeta">` 子图(主框 + label + 嵌套 group) / `<template v-else>` 现有 rect+text
    - setup 加 helpers:subgraphLabel(mod) + nestedNodesToRender(mod)
    - 视觉规格:fill `rgba(157,78,221,0.15)` · stroke `rgba(157,78,221,0.45)` · 高亮 #D4A574 · text `📦 ${name} (${count}个)` font-size 11
    - 嵌套小矩形:30×16 · 网格 max 5×5 · 8px 间距(36px 步长 · 20px 行高)· fill getModuleBg(节点 moduleId)
    - 超 25 节点截断 24(简化处理 · 不显示省略号 · ADR-25-R1 §3.2-R1 ③)

  Step 2 · onModuleDblClick 子图分支改造 0.1d(子任务 ②)
    - 改 L153-157 加 isSubgraph 分支:openSubgraphTab(defId) · 失败 console.warn
    - 普通模块保持 floating.openDialog + eventBus emit(零回归)
    - setup import useSubgraphTabs(方案 A 静态 / 方案 B try-catch stub)

  Step 3 · 单击行为不动 0.0d(子任务 ③)
    - L148-151 onModuleClick 完全不动(子图节点也响应单击高亮)

  Step 4 · vitest +6 case + 类型/构建 0.4d(子任务 ④)
    - 新建 frontend_vue3/src/stages/xitune/drawers/__tests__/FlowReadonlyDock.spec.ts · 6 case(T1 普通模块零回归 / T2 子图模块 SVG 结构 / T3 嵌套数 ≤ 25 / T4 嵌套数 > 25 截断 24 / T5 子图双击调 openSubgraphTab / T6 普通双击调 floating.openDialog)
    - mock useSubgraphTabs(返回 { openSubgraphTab: vi.fn() })
    - mock useFloatingStore(返回 { openDialog: vi.fn() })
    - mock useLinkStore(注入含 subgraphMeta 的 modules)
    - vue-tsc --noEmit 0 errors
    - npm run build 0 errors
    - npm run test 全过(基线 + 6 新 case)

  Step 5 · 浏览器实测 + commit 0.1d(子任务 ⑤)
    - 启动 backend + frontend · 准备 fixture(xilink 工程 1 子图 EQ_A · 主链路 1 EQ_A + source + sink + gain)
    - 切到 xitune · 打开 FlowReadonlyDock(LEFT_DOCK 第 2 项 xt-flow)
    - 验收点(ADR-25-R1 §3.2-R1 ④ 用户操作流):
      ☐ FlowReadonlyDock SVG 显示主链路(source → 📦 EQ_A → sink)
      ☐ 📦 EQ_A 节点蓝紫色主框 + 顶部 `📦 EQ_A (3个)` + 3 个嵌套小矩形(peq 古铜金 / gain 蓝 / delay 绿)
      ☐ 单击 📦 EQ_A → 高亮 stroke 黄色(eventBus emit 不变)
      ☐ 双击 📦 EQ_A → openSubgraphTab('eq-a-def')(若 F3-R1 已 zombie · doc tabs 加 EQ_A.subgraph / 若 pending · console.warn stub)
      ☐ 单击/双击 source/sink/gain → 现有行为零回归
      ☐ 极端 case:子图内 30 module → 显示前 24 个嵌套(截断不溢出)
      ☐ 性能:50 modules 含 5 子图 SVG 渲染 ≤ 50ms
    - git add . && git commit -m "feat(xitune/flow-readonly-dock): P3.UA25R1.F2 子图节点 SVG 嵌套示意图 + 双击 openSubgraphTab

      用户 2026-06-18 17:14 拍板方案 A 双连 dispatched F3-R1 + F2-R1 · 17:15 start。
      F2-R1 本任务(ADR-25-R1 §3.2-R1 + §4.2):
      ① FlowReadonlyDock SVG L54-83 加 isSubgraph 分支(主框蓝紫 + 嵌套小矩形 max 5×5 + 📦 emoji 顶部)
      ② onModuleDblClick 子图分支 → openSubgraphTab(defId)(F3-R1 暴露 · stub fallback 若 pending)
      ③ 单击 onModuleClick 不动(子图节点也响应高亮 · 与 chain-mini-bar §3.1-R1 不同)
      ④ helpers subgraphLabel + nestedNodesToRender(超 25 截断 24)
      ⑤ vitest +6 case · vue-tsc + build + 全测试基线零回归
      ⑥ 文件正交 F3-R1 · isolation: file

      解锁 F4-R1 e2e(blocked-by-F2-R1+F3-R1 · ClaudeC 0.5d)→ ADR-25 整体闭环 🏆
      ADR-23→25→25-R1 三连教训承接:用户原话名词 grep 找具体文件+行号(本 commit 涉及 8 个真值锚点)

      [step=5/5] [pid=P3] [uid=P3.UA25R1.F2-flow-readonly-dock-subgraph-display] [type=feature] [isolation=file]
      [occupies=P3.K-shared-xitune-flow-readonly-dock] [files=2(1M+1A)] [ipc=none]
      [adr=ADR-AIOS-25-R1 §3.2-R1 + §4.2 F2-R1 + 用户 2026-06-15 16:06 verbatim + 16:45 accept + 2026-06-18 17:14 双连 dispatched]"

【验收】
  ☐ Step 0 文件注入真值核查通过(read 8 个锚点对齐 + F3-R1 prompt API 契约 + 6 层核查记录)
  ☐ Step 1 SVG 模板 isSubgraph 分支正确(主框蓝紫 + 嵌套 max 5×5 + 📦 label · 普通分支零回归)
  ☐ Step 2 onModuleDblClick 子图分支调 openSubgraphTab(stub fallback 若 F3-R1 pending)· 普通模块零回归
  ☐ Step 3 单击 onModuleClick 完全不动(子图节点也响应高亮)
  ☐ Step 4 vitest +6 case 全过 · vue-tsc + build 0 errors · 全测试基线零回归
  ☐ Step 5 浏览器实测 7 验收点全过(主链路渲染 + 蓝紫主框 + 嵌套小矩形 + 高亮 + 双击 openSubgraphTab + 普通零回归 + 性能)
  ☐ commit message 含 7 元组 trailer + ADR-25-R1 §3.2-R1 + §4.2 引用 + 用户三段 timestamp

【禁止】
  ❌ 禁止跳过 Step 0 文件注入真值核查(ADR-25-R1 §9.3 三连教训 · 8 锚点必须逐一对齐)
  ❌ 禁止动 useChainMiniNodes.ts / xitune/index.vue / useSubgraphTabs.ts(F1 资产 + F3-R1 范围 · 本 fork 仅消费)
  ❌ 禁止动 onModuleClick L148-151(单击行为完全不变)
  ❌ 禁止动 fitToContent / 拖拽平移 / 滚轮缩放 / connPath / getModuleBg(现有功能零回归)
  ❌ 禁止破坏普通模块 onModuleDblClick → floating.openDialog(零回归)
  ❌ 禁止递归渲染嵌套子图(嵌套子图内层用普通小矩形 · 不递归 · 双击进 F3-R1 子图 tab 看完整)
  ❌ 禁止跳过 vitest +6 case(验收硬门槛)
  ❌ 禁止 commit 缺七元组 trailer(.clinerules v3.0 铁律)
  ❌ 禁止嵌入完整 SFC > 60 行 / TS interface > 5 行(.clinerules v3.0)· 用 diff 模式提交

解锁链(本任务 zombie 后)

  • ✅ FlowReadonlyDock SVG 子图节点视觉落地(蓝紫主框 + 嵌套 + 📦 emoji)
  • ✅ FlowReadonlyDock 双击 → openSubgraphTab 双入口齐(与 chain-mini-bar 双击共两个进子图 tab 入口)
  • ✅ 用户 verbatim "完善 FlowReadonlyDock 显示子图示意图 + 双击打开子图 tab" 落地
  • ⏳ F4-R1 e2e blocked-by-F2-R1+F3-R1 · ClaudeC 0.5d → ADR-25 整体闭环 🏆

风险评估

风险 缓解
F3-R1 未先 commit · openSubgraphTab API 不存在 方案 B try-catch stub 占位 + commit 后切静态 import · 不阻塞本 fork dispatched · 双 fork 文件正交可并行
ModuleInstance.subgraphMeta 类型未声明(types/module.ts 无字段) Step 0 强制 read types/module.ts · 若未声明用 (mod as any).subgraphMeta + commit log 标 followup 等 ADR-08-R1 / ADR-16 类型补全 · 不阻塞本 fork
subgraphMeta.nestedNodes 字段未实装(useChainMiniNodes 仅派生 isSubgraph) Step 0 grep "nestedNodes" 在 useChainMiniNodes.ts · 若未派生 · 本 fork 用 mod.subgraphMeta?.nestedNodes ?? [] 防御 · 渲染 0 嵌套小矩形(主框 + label 仍显示) · followup 等数据契约扩展
嵌套小矩形 30×16 在主框 200×80 内布局溢出(8px 边距 + 36px 步长 × 5 + 8px 边距 = 196 ≤ 200 ✓ · 24px 顶部 + 20px 行高 × 5 = 124 > 80 ✗) 实际嵌套数 ≤ 5×3 = 15 时不溢出(24 + 20×3 = 84 略超 · 但视觉可接受)· 子图内 module > 15 时部分嵌套溢出主框(用 SVG clipPath 裁剪 · 或调 nestedNodesToRender 截断到 15)· 简化处理:截断到 15 + commit log 标 followup
双击 openSubgraphTab 失败(defId 不存在)· 用户体验断崖 onModuleDblClick 子图分支 console.warn(toast 由 F3-R1 处理 · 本 fork 不实装 toast 组件)· 高亮仍设(用户至少看到反馈)
50 modules 含 5 子图各 5 module · SVG 渲染性能 < 50ms 是否达标 SVG 直绘单层 transform · 无虚拟列表开销 · 现代浏览器 250 nodes 渲染 < 20ms · 性能基线达标
onModuleClick 单击事件传播到嵌套小矩形 · 误触发 嵌套小矩形 pointer-events="none" 已禁用事件 · 不影响主框单击
ClaudeA 排队膨胀(F8 ADR-15 0.5d + F3-R1 1.5d + F2-R1 1.0d 共 3.0d 串行) 用户已知排期 · F2-R1 文件正交可并行(理论)· 实际单 ClaudeA 串行约 3.0d · 排期可控

历史

时间 事件 hash
2026-06-18 17:15 dispatched(用户 17:14 拍板方案 A 双连 dispatched F3-R1 + F2-R1 · F2-R1 优先级 P0 · 1.0d · 完善 FlowReadonlyDock SVG 子图示意图 + 双击 openSubgraphTab(F3-R1 暴露 · stub fallback)· 同 ClaudeA 自管串行节奏)