跳转至

P3.UA25R1.F3-doc-tabs-multi-tab-routing · doc tabs 多 tab 路由 + 子图 tab 主页 chain-mini-bar 复用

Worker:ClaudeA · 部门:前端 P3-xitune · 预计:1.5d · 优先级:P0 关键路径起点 · 状态:dispatched · isolation:🧵 file(同 worktree 同 branch · 与 F2-R1 文件正交可并行)

🔍 触发与解锁链

触发:用户 2026-06-18 17:14 拍板方案 A · 双连 dispatched F3-R1 + F2-R1 · 同 ClaudeA 自管串行节奏 · F3-R1 优先暴露 useSubgraphTabs API。

用户原话(verbatim · ADR-25-R1 §1.1 第 1+2 段):

"在主页面的 mini-node 中双击子图模块可以打开或者跳转到子图 tab 主页" "进入子图 tab 主页 → chain-mini-bar 显示子图内部 modules · 单击调音 / 双击悬浮窗与 Main tab 一致"

架构契约(ADR-25-R1 §3.3-R1 ① 输入/输出契约 · 业务契约 5 必填段): - 契约 A · useSubgraphTabs composable(NEW):暴露 openSubgraphTab(defId) / closeSubgraphTab(defId) / activeTabId ref / subgraphTabs computed - 契约 B · buildDocTabs 扩展:返回 [main, ...subgraphTabs.value.map(t => ({key:'subgraph-${defId}', label:t.name+'.subgraph', icon:'📦', dirty, closeable:true}))] · 主 tab 永居 [0] - 契约 C · currentTabModules computed:activeDocTab='main' → linkStore.modules / activeDocTab='subgraph-${defId}' → linkStore.subgraphDefs[defId].nodes - 契约 D · useChainMiniNodes 签名改造:接收 Ref<ModuleInstance[]>(子图 tab 时传 currentTabModules · 主 tab 时传 orderedModules · 实际就是统一传 currentTabModules) - 契约 E · onMiniNodeDblclick 子图分支:console.log 占位 → 改 openSubgraphTab(node.subgraphMeta.defId) - dirty 标记:复用 ADR-08-R1 R1.5(linkStore.subgraphDefs[defId].dirty) - 5 类失败回退(ADR-25-R1 §3.3-R1 ③):defId 不存在 toast / 同 defId 重开 idempotent / 关闭 active tab 切回 main / 主 tab 拒关 closeable=false / 刷新丢 tab(状态不持久化)

解锁链(本任务 zombie 后): - ✅ F2-R1 P3.UA25R1.F2-flow-readonly-dock-subgraph-display API 依赖满足(可消费 openSubgraphTab) - ⏳ F4-R1 P_e2e.A25R1.F4-truth-e2e-subgraph-r1(blocked-by-F2-R1+F3-R1 · ClaudeC 0.5d → ADR-25 整体闭环 🏆)

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

子任务 ① · useSubgraphTabs composable 实装(0.4d)

Step 1.1:新建 frontend_vue3/src/stages/xitune/composables/useSubgraphTabs.ts(参考 useChainMiniNodes 风格):

interface SubgraphTab {
  key: string                  // `subgraph-${defId}`
  defId: string
  name: string
  icon: '📦'
  dirty: boolean               // 来自 linkStore.subgraphDefs[defId].dirty(ADR-08-R1 R1.5)
  closeable: true
}

export function useSubgraphTabs() {
  const linkStore = useLinkStore()
  const _openTabs = ref<SubgraphTab[]>([])  // 已打开的子图 tab 数组
  const activeTabId = ref<string>('main')

  function openSubgraphTab(defId: string) {
    const def = linkStore.subgraphDefs[defId]
    if (!def) {
      // ADR-25-R1 §3.3-R1 ③ 失败回退:def 不存在
      // toast 由调用方处理(本 composable 仅返回 false)
      console.warn('[F3-R1] openSubgraphTab: defId not found', defId)
      return false
    }
    // idempotent:同 defId 不重开 · 切 active 即可
    const key = `subgraph-${defId}`
    if (!_openTabs.value.find(t => t.key === key)) {
      _openTabs.value.push({ key, defId, name: def.name ?? defId, icon: '📦', dirty: def.dirty ?? false, closeable: true })
    }
    activeTabId.value = key
    return true
  }

  function closeSubgraphTab(defId: string) {
    const key = `subgraph-${defId}`
    _openTabs.value = _openTabs.value.filter(t => t.key !== key)
    if (activeTabId.value === key) {
      activeTabId.value = 'main'  // 关闭 active tab 时切回主 tab
    }
  }

  const subgraphTabs = computed(() => _openTabs.value)

  return { openSubgraphTab, closeSubgraphTab, activeTabId, subgraphTabs }
}

Step 1.2:导出类型 SubgraphTab interface(供 buildDocTabs 类型对齐)

子任务 ② · index.vue buildDocTabs 扩展(0.2d)

Step 2.1:修改 frontend_vue3/src/stages/xitune/index.vue L221-228 buildDocTabs(): - 在 setup 顶部 import + 实例化 const { openSubgraphTab, closeSubgraphTab, activeTabId: subgraphActiveTabId, subgraphTabs } = useSubgraphTabs() - 改 buildDocTabs 返回:

function buildDocTabs(): DocTab[] {
  const main = { key: 'main', label: name + '.xitune', icon: '🎚', dirty: false /* closeable 默认 false · shell 已支持 */ }
  const subTabs = subgraphTabs.value.map(t => ({
    key: t.key,
    label: t.name + '.subgraph',
    icon: t.icon,
    dirty: t.dirty,
    closeable: t.closeable,
  }))
  return [main, ...subTabs]
}
- 改 refreshDocTabs(L230-232):用 shellSlots.setDocTabs(buildDocTabs(), shellSlots.activeDocTab ?? 'main') 保留当前 active(避免切 tab 时被刷成 main) - 添加 watch 监听 subgraphTabs 变化 → refreshDocTabs(子图 tab 增删时自动刷)+ watch subgraphActiveTabIdshellSlots.setDocTabs(buildDocTabs(), subgraphActiveTabId.value) 同步切 active

子任务 ③ · currentTabModules computed + useChainMiniNodes 改造(0.4d)

Step 3.1:在 index.vue setup 中(L391 useChainMiniNodes 调用之前)新增 computed:

const currentTabModules = computed<ModuleInstance[]>(() => {
  const activeKey = shellSlots.activeDocTab ?? 'main'
  if (activeKey === 'main') {
    return orderedModules.value  // 主链路 BFS 拉平(现有)
  }
  // 子图 tab:从 subgraphDefs 取 nodes
  const tab = subgraphTabs.value.find(t => t.key === activeKey)
  if (!tab) return orderedModules.value  // fallback:防御性
  const def = linkStore.subgraphDefs[tab.defId]
  return def?.nodes ?? []
})

Step 3.2:修改 useChainMiniNodes 调用(L391-392): - 把 useChainMiniNodes(orderedModules) 改为 useChainMiniNodes(currentTabModules) - ⚠️ 检查 useChainMiniNodes.ts 签名:若现有签名是 useChainMiniNodes(modules: Ref<ModuleInstance[]>) 则无需改 composable;若是 useChainMiniNodes(modules: ComputedRef<ModuleInstance[]>) 也兼容(Vue ref/computed 互通) - ⚠️ 不要修改 useChainMiniNodes.ts 内部逻辑(F1 d9a2e1c 资产 · ADR-25-R1 §2.2 部分保留矩阵)· 仅改调用入参

子任务 ④ · onMiniNodeDblclick 子图分支改造(0.1d)

Step 4.1:修改 index.vue L482-495 onMiniNodeDblclick: - 删除 L487-493 console.log 占位(F1 d9a2e1c 占位代码) - 改为:

if (node?.isSubgraph) {
  if (node.subgraphMeta?.defId) {
    const ok = openSubgraphTab(node.subgraphMeta.defId)
    if (!ok) {
      // ADR-25-R1 §3.3-R1 ③ defId 不存在 toast(用 alert 占位 · 后续可换 toast 组件)
      console.warn('[F3-R1] 子图定义已不存在', node.subgraphMeta.defId)
    }
  }
  return
}
floating.openDialog(instanceId, moduleId)  // 普通模块保持现有

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

Step 5.1:新建 frontend_vue3/src/stages/xitune/composables/__tests__/useSubgraphTabs.spec.ts(参考 useChainMiniNodes.spec.ts 风格 · 8 case): - T1 openSubgraphTab(defId) 新建 tab + 切 active - T2 openSubgraphTab(同 defId) 重复 idempotent · 不重开 · 仅切 active - T3 openSubgraphTab(不存在 defId) 返回 false + 不动 _openTabs - T4 closeSubgraphTab 删 tab + 若是 active 切回 main - T5 closeSubgraphTab 不是 active · activeTabId 不变 - T6 subgraphTabs computed 返回数组只读快照 - T7 dirty 字段从 linkStore.subgraphDefs[defId].dirty 派生 - T8 多 tab 顺序保持插入顺序(FIFO 不重排)

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

子任务 ⑥ · 浏览器实测端到端(0.0d 含在 ⑤ 验收时间)

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

Step 6.2:验收点(ADR-25-R1 §3.3-R1 ④ 用户操作流 · F2-R1 未 zombie 时部分操作走 stub): - ☐ 主 tab 🎚 工程名.xitune(closeable=false)正常显示 - ☐ chain-mini-bar 双击 📦 EQ_A → doc tabs 加 📦 EQ_A.subgraph(可关闭) + 自动切 active - ☐ 子图 tab 主页 chain-mini-bar 显示子图内部 modules(currentTabModules 切换生效) - ☐ 子图 tab 内单击普通 module → activeInstanceId 切到子图内 instanceId · 主区右侧加载对应 InlinePanel - ☐ 子图 tab 关闭 → 切回 main + activeInstanceId 恢复 main 之前选择 - ☐ 同 defId 双击 2 次 → 不重开(idempotent)+ 切 active

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

[U-thread] P3.UA25R1.F3-doc-tabs-multi-tab-routing · ADR-25-R1 §3.3-R1(关键路径 · 1.5d · 暴露 API)
[部门] 前端 P3-xitune
[Worker CWD] d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies] P3.K-shared-xitune-doc-tabs + P3.K-shared-xitune-mini-bar
[优先级] P0(关键路径起点 · 1.5d · 暴露 useSubgraphTabs API 给 F2-R1 消费)
[ADR] docs/08-implementation/40-aios/ADR/ADR-AIOS-25-R1-flow-readonly-subgraph-and-doc-tabs.md(必读 §3.3-R1 + §4.2 F3-R1 fork 表 + §5 风险表)
[isolation] file(同 worktree 同 branch · 与 F2-R1 文件正交并行)
[API 暴露] useSubgraphTabs() composable · 供 F2-R1 onModuleDblClick + 本 fork onMiniNodeDblclick 共同消费

[参考文档绝对路径]
  - 业务契约:ADR-25-R1 §3.3-R1 完整 5 必填段(① useSubgraphTabs API + buildDocTabs 扩展 + currentTabModules / ② 8 行收敛判据 / ③ 5 类失败回退 / ④ 6 步操作流 / ⑤ playwright e2e 模板)
  - 用户 verbatim(ADR-25-R1 §1.1):"双击可以打开或跳转到子图 tab 主页 + chain-mini-bar 显示子图内部 modules · 单击调音 / 双击悬浮窗与 Main tab 一致"
  - 范式 commits(worker 必读):
    * 0bb4422 P3.UA25R1.F1 combo hotfix(同 ClaudeA 同 ADR-25-R1 · isolation: file · 完整 prompt 标本 278 行 · 8 段结构)
    * d9a2e1c P3.A25.F1 useChainMiniNodes(本任务消费的 ChainMiniNode + isSubgraph + subgraphMeta 数据源)
    * a5b52de P1.A21.F1 dock-host-generalize(同 ClaudeA 前端 · 完整 prompt 8 段 + commit 七元组标本)

[现有组件 grep 真值(.clinerules v3.0 ADR-23→25→25-R1 三连教训铁律 · 派发前已核查)]
  ① 待新建文件:
     - frontend_vue3/src/stages/xitune/composables/useSubgraphTabs.ts(NEW · 参考 useChainMiniNodes.ts 风格)
     - frontend_vue3/src/stages/xitune/composables/__tests__/useSubgraphTabs.spec.ts(NEW · +8 case)
  ② 待修文件 frontend_vue3/src/stages/xitune/index.vue:
     - L221-228 buildDocTabs() 单 tab 现状 → 加 ...subgraphTabs.value.map(...)(主 tab 永居首位)
     - L230-232 refreshDocTabs() 用 shellSlots.setDocTabs(buildDocTabs(), 'main') → 改保留当前 active
     - L391-392 useChainMiniNodes(orderedModules) → 改 useChainMiniNodes(currentTabModules)
     - L482-495 onMiniNodeDblclick 子图分支 console.log 占位 → 改 openSubgraphTab(node.subgraphMeta.defId)
     - L494 普通模块 floating.openDialog 不动
  ③ 数据源(只读 · 不动):
     - linkStore.subgraphDefs[defId]:含 .name / .nodes / .dirty(ADR-08-R1 R1.5 dirty 标记)
     - useChainMiniNodes.ts ChainMiniNode 接口:含 .isSubgraph + .subgraphMeta.defId(F1 d9a2e1c 资产)
     - shellSlots.activeDocTab / shellSlots.docTabs / shellSlots.setDocTabs(shell 框架已支持 closeable)

[文件正交策略](.clinerules v3.0 §isolation):
  isolation: file · 同 worktree 同 branch · 改 1 + 新建 2 = 3 文件
  与 F2-R1(改 FlowReadonlyDock.vue · 1.0d)文件正交可并行 · 但 F2-R1 onModuleDblClick 子图分支需调本 fork 暴露的 openSubgraphTab API · 建议本 fork 优先 commit useSubgraphTabs.ts(API 骨架)再继续其他改造 · F2-R1 可早期 import

[API 契约对外承诺(F2-R1 消费方依赖)]
  export function useSubgraphTabs(): {
    openSubgraphTab(defId: string): boolean   // 返回 true=成功 / false=defId 不存在
    closeSubgraphTab(defId: string): void
    activeTabId: Ref<string>                   // 'main' | 'subgraph-${defId}'
    subgraphTabs: ComputedRef<SubgraphTab[]>
  }
  - F2-R1 在 FlowReadonlyDock.vue setup 中需:
    import { useSubgraphTabs } from '@/stages/xitune/composables/useSubgraphTabs'
    const { openSubgraphTab } = useSubgraphTabs()
    // onModuleDblClick 子图分支调 openSubgraphTab(mod.subgraphMeta?.defId)

【背景】
  用户 2026-06-15 16:06 verbatim 三段拆解纠正 ADR-25 v0.1 · 16:14 ADR-25-R1 落盘 · 16:45 accept · F1.1-R1 0bb4422 已 zombie(单击不响应 + F2 文件清理)· 17:14 用户拍板方案 A 双连 dispatched F3-R1 + F2-R1。
  本 fork F3-R1 是关键路径起点(1.5d · 优先级 P0)· 暴露 useSubgraphTabs composable 给 F2-R1 双击行为消费 · 同时改造 buildDocTabs 派生子图 tab + currentTabModules 切换 main / 子图 tab 内 chain-mini-bar 数据源。
  教训承接(ADR-25-R1 §9.3):用户原话名词必须 grep 找具体文件+行号 → 本 prompt [现有组件 grep 真值] 段已落实 9 个真值锚点(L221-228 / L230-232 / L391-392 / L482-495 / linkStore.subgraphDefs / useChainMiniNodes / shellSlots / ChainMiniNode / dirty)。

【架构关键约束】
  ⚡ 严守 .clinerules v3.0 ADR-23→25→25-R1 三连教训铁律:用户原话"打开/跳转子图 tab 主页"必须解析为现有 shellSlots.docTabs + setDocTabs 框架(本 fork 完善 · 不新建)
  📋 buildDocTabs 主 tab 永居 [0]:closeable=false(shell 默认行为)· 子图 tab closeable=true
  📋 currentTabModules computed:activeKey='main' → orderedModules / activeKey='subgraph-${defId}' → subgraphDefs[defId].nodes
  📋 useChainMiniNodes 签名兼容:本 fork 仅改调用入参(orderedModules → currentTabModules)· 不动 composable 内部逻辑(F1 d9a2e1c 资产)
  📋 onMiniNodeDblclick 子图分支:console.log 占位 → openSubgraphTab(defId)· 普通模块 floating.openDialog 不动
  📋 dirty 标记:复用 linkStore.subgraphDefs[defId].dirty(ADR-08-R1 R1.5)· 不新增 store 字段
  📋 idempotent:同 defId openSubgraphTab 不重开 · 仅切 active(防御快速双击竞态)
  🚫 严禁动 useChainMiniNodes.ts / useChainMiniNodes.spec.ts(F1 资产 · 仅消费)
  🚫 严禁动 FlowReadonlyDock.vue(F2-R1 范围)
  🚫 严禁动 linkStore.ts schema(仅消费 subgraphDefs · 不动 store)
  🚫 严禁破坏 onMiniNodeClickWithDelay(F1.1-R1 0bb4422 已落地 isSubgraph 早 return · 本 fork 不动)
  🚫 严禁删除主 tab(closeable=false 默认 · shell 已拒绝)

【执行步骤】
  Step 0 · 文件注入真值核查(强制门槛 · ADR-25-R1 §9.3 三连教训承接)
    - read frontend_vue3/src/stages/xitune/index.vue L221-232 / L391-400 / L460-500 · 确认本 prompt [现有组件 grep 真值] 9 个锚点对齐
    - read frontend_vue3/src/stages/xitune/composables/useChainMiniNodes.ts 全文(F1 资产 · 派生 isSubgraph + subgraphMeta 数据源)
    - read frontend_vue3/src/stores/linkStore.ts grep "subgraphDefs" 确认 schema(.name / .nodes / .dirty 三字段)
    - read frontend_vue3/src/composables/useShellSlots(或类似)grep "setDocTabs|activeDocTab|DocTab" 确认 closeable 字段已支持
    - 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 · 新建 useSubgraphTabs composable 0.4d(子任务 ①)
    - 新建 frontend_vue3/src/stages/xitune/composables/useSubgraphTabs.ts
    - 实装 SubgraphTab interface + useSubgraphTabs() composable
    - 暴露 4 项 API:openSubgraphTab(defId): boolean / closeSubgraphTab(defId) / activeTabId Ref / subgraphTabs ComputedRef
    - idempotent 检查:同 defId 不重开 · 仅切 active
    - 失败回退:defId 不存在 → 返回 false + console.warn(toast 由调用方处理)
    - 关闭 active tab → 切回 'main'

  Step 2 · index.vue buildDocTabs 扩展 0.2d(子任务 ②)
    - 在 setup 顶部 import useSubgraphTabs + 实例化
    - 改 L221-228 buildDocTabs:返回 [main, ...subgraphTabs.value.map(t => ({key, label, icon, dirty, closeable}))]
    - 改 L230-232 refreshDocTabs:shellSlots.setDocTabs(buildDocTabs(), shellSlots.activeDocTab ?? 'main')
    - watch subgraphTabs → refreshDocTabs(增删自动刷)
    - watch activeTabId → shellSlots.setDocTabs(buildDocTabs(), activeTabId.value)(同步切 active)

  Step 3 · currentTabModules computed + useChainMiniNodes 改造 0.4d(子任务 ③)
    - 新增 currentTabModules computed(activeKey === 'main' ? orderedModules : subgraphDefs[defId].nodes)
    - 改 L391-392 useChainMiniNodes(orderedModules) → useChainMiniNodes(currentTabModules)
    - 不动 useChainMiniNodes.ts 内部
    - 不动 sourceModules / nonSourceOrSingleSourceModules computed(基于 chainMiniNodes 派生 · 跟随 currentTabModules 自动响应)

  Step 4 · onMiniNodeDblclick 子图分支改造 0.1d(子任务 ④)
    - 改 L482-495 子图分支:console.log 占位 → openSubgraphTab(node.subgraphMeta.defId)
    - 失败回退(defId 不存在):openSubgraphTab 返回 false + console.warn
    - 普通模块 floating.openDialog 不动

  Step 5 · vitest +8 case + 类型/构建 0.4d(子任务 ⑤)
    - 新建 useSubgraphTabs.spec.ts · 8 case(T1 open / T2 idempotent / T3 not-found / T4 close+切回main / T5 close非active / T6 computed / T7 dirty 派生 / T8 FIFO 顺序)
    - mock useLinkStore · 注入 subgraphDefs[mock-defId] = { name, nodes, dirty }
    - vue-tsc --noEmit 0 errors
    - npm run build 0 errors
    - npm run test 全过(基线 + 8 新 case)

  Step 6 · 浏览器实测 + commit
    - 启动 backend(dotnet run)+ frontend(npm run dev)
    - 准备 fixture:xilink 工程含 1 子图 EQ_A(3 modules)+ 主链路 1 EQ_A 实例 · 切到 xitune
    - 验收点(ADR-25-R1 §3.3-R1 ④ · F2-R1 未 zombie 时双击主链路 📦 EQ_A 仍可触发本 fork 的 onMiniNodeDblclick → openSubgraphTab):
      ☐ 主 tab `🎚 工程名.xitune`(closeable=false)正常显示
      ☐ chain-mini-bar 双击 📦 EQ_A → doc tabs 加 `📦 EQ_A.subgraph(可关闭)` + 自动切 active
      ☐ 子图 tab 主页 chain-mini-bar 显示子图内部 3 个 modules(peq + gain + delay)
      ☐ 子图 tab 内单击 peq → activeInstanceId 切到子图内 instanceId · 主区加载 GEQTuningDialog inline
      ☐ 子图 tab 关闭 → 切回 main + activeInstanceId 恢复
      ☐ 同 defId 双击 2 次 → 不重开 + 切 active(idempotent)
    - git add . && git commit -m "feat(xitune/doc-tabs): P3.UA25R1.F3 useSubgraphTabs composable + 多 tab 路由

      用户 2026-06-18 17:14 拍板方案 A 双连 dispatched F3-R1 + F2-R1 · 17:15 start。
      F3-R1 本任务(ADR-25-R1 §3.3-R1 + §4.2 关键路径起点):
      ① 新建 useSubgraphTabs composable · 暴露 openSubgraphTab/closeSubgraphTab/activeTabId/subgraphTabs API
      ② index.vue buildDocTabs 扩展 · 主 tab 永居 [0] + 子图 tab 派生(closeable=true · dirty 复用 ADR-08-R1)
      ③ currentTabModules computed · activeKey='main' → orderedModules / 'subgraph-${defId}' → subgraphDefs[defId].nodes
      ④ useChainMiniNodes 调用入参改 currentTabModules(F1 资产不动)
      ⑤ onMiniNodeDblclick 子图分支 · console.log 占位 → openSubgraphTab(defId)
      ⑥ vitest +8 case · vue-tsc + build + 全测试基线零回归
      ⑦ 暴露 API 给 F2-R1 消费(FlowReadonlyDock onModuleDblClick 子图分支同样调 openSubgraphTab)

      解锁 F2-R1 API 依赖(可消费 openSubgraphTab)+ F4-R1 e2e
      ADR-23→25→25-R1 三连教训承接:用户原话名词 grep 找具体文件+行号(本 commit 涉及 9 个真值锚点)

      [step=6/6] [pid=P3] [uid=P3.UA25R1.F3-doc-tabs-multi-tab-routing] [type=feature] [isolation=file]
      [occupies=P3.K-shared-xitune-doc-tabs+mini-bar] [files=3(2A+1M)] [ipc=none]
      [adr=ADR-AIOS-25-R1 §3.3-R1 + §4.2 F3-R1 + 用户 2026-06-15 16:06 verbatim + 16:45 accept + 2026-06-18 17:14 双连 dispatched]"

【验收】
  ☐ Step 0 文件注入真值核查通过(read 9 个锚点对齐 + 5 层核查记录)
  ☐ Step 1 useSubgraphTabs composable 实装 · 4 API 全暴露 · idempotent + failure-fallback 落地
  ☐ Step 2 buildDocTabs 主 tab 永居 [0] · 子图 tab 派生正确 · refreshDocTabs 保留 active
  ☐ Step 3 currentTabModules computed 切换正确 · useChainMiniNodes 接收 currentTabModules · sourceModules 跟随响应
  ☐ Step 4 onMiniNodeDblclick 子图分支调 openSubgraphTab · 普通模块 floating.openDialog 零回归
  ☐ Step 5 vitest +8 case 全过 · vue-tsc + build 0 errors · 全测试基线零回归
  ☐ Step 6 浏览器实测 6 验收点全过(主 tab + 双击切 active + 子图内 mini-bar + 单击调音 + 关闭切回 + idempotent)
  ☐ commit message 含 7 元组 trailer + ADR-25-R1 §3.3-R1 + §4.2 引用 + 用户三段 timestamp

【禁止】
  ❌ 禁止跳过 Step 0 文件注入真值核查(ADR-25-R1 §9.3 三连教训 · 9 锚点必须逐一对齐)
  ❌ 禁止动 useChainMiniNodes.ts / useChainMiniNodes.spec.ts(F1 d9a2e1c 资产 · 仅改调用入参)
  ❌ 禁止动 FlowReadonlyDock.vue(F2-R1 范围)
  ❌ 禁止动 linkStore.ts schema 或 subgraphDefs 字段(仅消费)
  ❌ 禁止破坏 onMiniNodeClickWithDelay(F1.1-R1 0bb4422 资产 · 本 fork 不动 isSubgraph 早 return 分支)
  ❌ 禁止删除主 tab 或改 closeable=true(主 tab 必须不可关闭)
  ❌ 禁止持久化子图 tab 状态(刷新丢 tabs 是预期行为 · ADR-25-R1 §3.3-R1 ③)
  ❌ 禁止跳过 vitest +8 case(验收硬门槛)
  ❌ 禁止 commit 缺七元组 trailer(.clinerules v3.0 铁律)
  ❌ 禁止嵌入完整 SFC > 60 行 / TS interface > 5 行(.clinerules v3.0)· 用 diff 模式提交

解锁链(本任务 zombie 后)

  • ✅ useSubgraphTabs composable API 暴露(F2-R1 可 import + 调用 openSubgraphTab)
  • ✅ doc tabs 多 tab 路由落地(主 tab + N 子图 tab · 主 tab 永居 [0])
  • ✅ 子图 tab 主页 chain-mini-bar 复用(currentTabModules 切换数据源)
  • ✅ 用户 verbatim "双击打开/跳转子图 tab 主页" 落地
  • ⏳ F2-R1 文件正交并行 zombie 后 → 主 tab 双击 + FlowReadonlyDock 双击双入口齐
  • ⏳ F4-R1 e2e blocked-by-F2-R1+F3-R1 · ClaudeC 0.5d → ADR-25 整体闭环 🏆

风险评估

风险 缓解
useChainMiniNodes.ts 现有签名是 Ref<ModuleInstance[]> 还是 ComputedRef 不确定 Step 0 强制 read composable 全文确认 · 若签名只接受 Ref 则用 toRef/computed 转换 · 若兼容则直传 currentTabModules
linkStore.subgraphDefs[defId].dirty 字段是否实际存在(ADR-08-R1 R1.5 落地深度未知) Step 0 强制 grep "subgraphDefs.*dirty" 在 linkStore.ts · 若字段未实装则 dirty=false 占位(不阻塞本 fork)+ commit log 标注 followup 等 ADR-08-R1 R1.5 真值核查
shellSlots.setDocTabs 是否支持 activeKey 参数(切 active) Step 0 强制 read shell composable + grep "setDocTabs" 函数签名 · 若不支持需先扩 shell 框架(超出本 fork 范围 · 升级到 ADR · 暂用 setDocTabs(tabs) + 单独 watch 触发 setActiveDocTab)
sourceModules / nonSourceOrSingleSourceModules 派生在子图 tab 切换后行为不一致 子图 tab 内通常无 source(子图是模块组合 · 非完整链路)· 该 computed 自然返回空数组 · 子图 tab chain-mini-bar 不显示源组(预期行为)
子图 tab 内单击 module 切换 activeInstanceId · 但浮窗不在子图 tab 显示(隔离不到位) activeInstanceId 是全局 ref · 切回 main tab 时仍指向子图内 instanceId(可能错乱)· 需 watch activeDocTab 切 main 时清 activeInstanceId(但用户期望"切回 main 时恢复之前选择" · 需缓存机制)· 本 fork 范围:暂用 watch activeDocTab → activeInstanceId.value = ''(简化)· 缓存机制留 followup
快速双击同 defId(防抖竞态) useSubgraphTabs.openSubgraphTab idempotent 检查(同 defId 不重开)+ Vue 响应式批处理 · 不会出现重复 tab
F2-R1 同时 dispatched 但 API 未就绪 F2-R1 prompt 已说明依赖关系 · 建议 ClaudeA 先做本 fork(P0 关键路径起点)再接 F2-R1 · 或 F2-R1 worker 先 stub import { openSubgraphTab } from './stub' 待本 fork commit 后改 import 路径
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 · F3-R1 优先级 P0 关键路径起点 1.5d · 暴露 useSubgraphTabs API 给 F2-R1 消费 · 同 ClaudeA 自管串行节奏)