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
}
}
子任务 ③ · 单击行为不变 + 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 自管串行节奏) | — |