P1.A21.F4-phase-module-frontend · XiLink Phase Module 前端实装(plugin 注册 + DockModule + IP 库入口)
Worker:ClaudeA · 部门:前端 P1-xilink · 预计:1.5d · 优先级:P0 · 状态:dispatched · isolation:🧵 file(同 worktree 同 branch · 与 F6 transfer-module-frontend / F2 fft-scope-controls-enhance 文件正交可并行)
🔍 触发与解锁链
触发:用户 2026-06-13 18:09 三连 stop F1+F3+F5 算法层闭环 → 18:18 反馈"前端 IP 库还是没看到 phase/transfer 组件" → 18:26 拍板三连 start F4+F6+F2。
用户原话(verbatim · ADR-21 §1.1):
"4. 然后是 phase 控件 · 同样需要在右侧 dock 来显示 phase 信号(可以参考 fft 内的实现)· 在线条曲线显示的形态下:由于 phase 是 1khz 1ms 这种单位 · 1khz 1ms 也都属于 360°一个周期 · 都属于 wrap;...phase 模块和 fft 模块基本类似 · 只是不需要做积分平均"
解锁条件(双解锁已满足):
- ✅ F1 a5b52de zombie · DockHost.vue + DockNodeSelector.vue + 5 类节点 selector(含 xilink-module)+ useDockScale + useDockChannelMask + useChainNodeMetadata
- ✅ F3 8eaaf40 zombie · dsp_algo/modules/analysis/phase_module + dspalgo_dll.c export phase_module_get_phase(9 参数 P/Invoke 签名)+ 6 单测 10/10 · 502/508 基线零回归
架构契约(ADR-21 §3.3 · 业务契约 5 必填段全填):
- 输入:用户在 IP 库拖拽 phase-module 节点到 chain · DockHost 顶部 selector 选目标节点
- DLL 调用:phase_module_get_phase(handle, instanceId, displayMode, sampleRateHz, pOut, maxChannels, maxBins, pActualChannels, pActualBins) · displayMode 0=wrapped 1=unwrapped 2=groupDelayMs
- 输出 schema(MeterFrame_Phase · 复用 ADR-12 §3.4):{freqs[], phaseWrapped[][], phaseUnwrapped[][], groupDelay[][], averagedCount, resolution}(channelCount × binCount)
- 性能基线:30fps · 4 通道 1024 bin · canvas 重绘 < 33ms
- 5 类失败回退:DLL 调用失败 fallback 红 X / 弱信号 fftReady=0 显示"Wait for signal" / channelCount 突变重置 mask / displayMode 切换无缝(不重置 averaging)/ NaN/Inf clamp 红 X
解锁链(本任务 zombie 后): - F7 e2e 部分解锁(blocked-by-F2+F4+F6 · 还需 F2/F6 zombie 才完整 ready)
任务定义(基于 ADR-21 §3.3)
子任务 ① · usePhaseFrame.ts composable + DLL P/Invoke(0.4d)
Step 1.1:NEW frontend_vue3/src/stages/xilink/modules/phase/usePhaseFrame.ts:
- 接口:usePhaseFrame(instanceId: Ref<string>, options: { displayMode: Ref<0|1|2>; sampleRateHz: number; refreshFps?: number }) → reactive { frame: MeterFrame_Phase | null; error: string | null; isReliable: boolean }
- WS 订阅 /api/dspalgo/frame?moduleType=0x100E0004&instanceId=${instanceId} 拉取 phase frame(后端 ADR-18 protocol-v1 已就位)
- 30fps 节流 · 切 displayMode 时不重置 frame · 仅切显示通道
- 弱信号判定:fftReady=0 时 isReliable=false(对齐 F3 phase_module.c 阈值)
Step 1.2:类型扩展 frontend_vue3/src/types/meterFrame.ts:
- 新增 MeterFrame_Phase 接口(对齐 ADR-12 §3.4 ① schema · 不破坏现有 RMS/FFT/Scope/Transfer frame 类型)
- 字段:freqs: number[] / phaseWrapped: number[][] / phaseUnwrapped: number[][] / groupDelay: number[][] / averagedCount: number / resolution: number / fftReady: boolean
子任务 ② · PhaseChart.vue canvas 渲染(0.5d)
Step 2.1:NEW frontend_vue3/src/stages/xilink/modules/phase/PhaseChart.vue:
- props:{ frame: MeterFrame_Phase | null; channelMask: boolean[]; displayMode: 0|1|2; scaleX: {min,max}; scaleY: {min,max} }
- canvas 时频域 line chart(参考 FftChart 实现 · 但 Y 轴单位 = ° 或 ms · 不是 dB)
- 多通道 stack(channelMask 过滤)+ 颜色复用 ADR-18 通道配色
- displayMode 0 wrapped:Y 轴 [-180°, +180°] · 跨边界画 line break 不画连线
- displayMode 1 unwrapped:Y 轴动态 auto fit · 单调连线
- displayMode 2 groupDelayMs:Y 轴 ms · F3 algo 输出 numpy.gradient 等价
- 弱信号 fftReady=false:全屏覆盖灰底 + "Wait for signal" 文字
- 性能:RAF 节流 + 仅 redrawKey 变化时重绘 + 30fps clamp
Step 2.2:复用 F1 useDockScale 的 redrawKey 信号 · 滚轮 zoom 自动重绘
子任务 ③ · PhaseDockModule.vue 接入 DockHost(0.3d)
Step 3.1:NEW frontend_vue3/src/stages/xilink/modules/phase/PhaseDockModule.vue:
- 模板:<DockHost :module-id="instanceId" :supported-node-kinds="['source','sink','log_module_v1','xilink-module']" :default-node-id="props.defaultNodeId" :unit="displayUnit">
- <template #canvas="{ scaleX, scaleY }"> → <PhaseChart :frame :channel-mask :display-mode :scale-x :scale-y />
- <template #controls> → 3 单选按钮 wrapped/unwrapped/groupDelay + averaging slider(0/4/16/64)
- 内部:useChainNodeMetadata(selectedNodeId) 拿 channelCount + sampleRateHz · usePhaseFrame(instanceId, { displayMode, sampleRateHz })
- displayUnit 计算属性:displayMode 0|1 → '°' · displayMode 2 → 'ms'
子任务 ④ · plugin 注册 + IP 库入口(0.2d) 🔥 用户痛点直接修复
Step 4.1:frontend_vue3/src/stages/xilink/modules/phase/index.ts:
- export phaseModulePlugin: XiLinkModulePlugin = { typeId: 0x100E0004, name: 'phase', displayName: 'Phase', icon: '∠', component: PhaseDockModule, defaultNodeKind: 'sink-pre', category: 'analysis' }
Step 4.2:更新 frontend_vue3/src/stages/xilink/registry/moduleRegistry.ts:
- 加 import { phaseModulePlugin } from '@/stages/xilink/modules/phase'
- 加 register(phaseModulePlugin) 到 default exports
- 确认 IP 库面板(ADR-18 F5 769405a)能枚举到 phase 节点(category='analysis' 类目)
Step 4.3:确认 plugin 在 IP 库面板可见 + 拖拽到 chain 后右 dock 自动加 PhaseDockModule(F1 DockHost 流程)
子任务 ⑤ · 测试(0.1d)
Step 5.1:vitest 加 ≥ 4 case(ADR-21 §3.3 ⑤ playwright e2e 模板对齐): - usePhaseFrame WS 订阅 + 30fps 节流 + 弱信号 isReliable=false - PhaseChart wrapped 模式跨边界 line break(±180° 不连线) - PhaseChart unwrapped 模式单调连线 - PhaseDockModule controls 3 单选切 displayMode + averaging 持久化 - 单独加 1 case 浏览器实测覆盖:plugin 注册到 IP 库可见 + 拖拽生成 chain 节点 + DockHost 渲染 PhaseDockModule
Step 5.2:vue-tsc + build + 全测试基线零回归(F1 11 case + ADR-18 F5 152 case + ADR-17 F6 基线)
完整 prompt(直接复制粘贴 ClaudeA 终端)
[U-thread] P1.A21.F4-phase-module-frontend · ADR-21 §3.3 phase-module 前端实装(IP 库添加 phase 节点)
[部门] 前端 P1-xilink
[Worker CWD] d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies] P1.K-shared-xilink-dock + P0.K-shared-types
[优先级] P0(1.5d · 解锁 F7 e2e 1/3 路径 · 用户痛点直接修复)
[ADR] docs/08-implementation/40-aios/ADR/ADR-AIOS-21-xilink-dock-and-analysis-modules.md(必读 §3.3 phase 业务契约 + §1.4 边界铁律)
[isolation] file(同 worktree 同 branch · 与 F6 transfer / F2 fft-scope 文件正交可并行)
[参考文档绝对路径]
- 业务契约:ADR-21 §3.3 完整 5 必填段(① MeterFrame_Phase schema + DLL P/Invoke 9 参数 / ② 30fps + < 33ms 重绘 / ③ 5 类失败回退 / ④ 5 步操作流 / ⑤ playwright e2e 模板)
- 用户 2026-06-13 09:55 拍板原话(ADR-21 §1.1):phase 控件 + 1kHz 1ms wrap · 同 fft 类似但不积分
- 范式 commits(worker 必读 · 强制 read 全文):
* a5b52de P1.A21.F1 DockHost 通用化(本任务接入此 host 插槽 · 复用 useChainNodeMetadata + useDockScale + useDockChannelMask)
* 8eaaf40 P_dsp.A21.F3 phase_module 算法 + dspalgo_dll.c phase_module_get_phase export(本任务 P/Invoke 调用此 9 参数 export)
* 769405a P0.A18.F5 IP 库 + 4 popup(本任务 plugin 注册到此 IP 库体系 · 类目 'analysis')
* 78dc17c P0.A17.F6 widget endpoint selector(F1 已扩 5 类 · 本任务复用 selector 选 phase 节点)
- 现状参考:
* frontend_vue3/src/stages/xilink/dock/DockHost.vue(F1 a5b52de · 本任务接入插槽)
* frontend_vue3/src/stages/xilink/registry/moduleRegistry.ts(plugin 注册中心 · 加 phaseModulePlugin)
* frontend_vue3/src/components/popups/FftModulePopup.vue(F5 769405a · canvas 渲染 + WS 订阅范式参考)
* frontend_vue3/src/types/meterFrame.ts(扩 MeterFrame_Phase 类型 · 不破坏现有 RMS/FFT/Scope/Transfer)
* dsp_algo/dll/dspalgo_dll.c(F3 8eaaf40 · phase_module_get_phase export 9 参数 · P/Invoke 签名真值源)
- 三层分工:ADR-07 §1.3.4(L3 前端零数学 · 本任务纯 P/Invoke + canvas 渲染 · 不做 FFT/atan2/unwrap 计算)
[文件正交策略](.clinerules §任务隔离类型分配准则 v1.4):
isolation: file · 同 worktree 同 branch · 与 F6 / F2 并行 push 拉合并
本任务文件:frontend_vue3/src/stages/xilink/modules/phase/* (4 文件 NEW) + registry/moduleRegistry.ts(append-only 注册 1 plugin) + types/meterFrame.ts(append-only 加 MeterFrame_Phase 类型)
F6 文件:frontend_vue3/src/stages/xilink/modules/transfer/* + registry/moduleRegistry.ts(append-only 注册 transferModulePlugin)+ types/meterFrame.ts(append-only 加 MeterFrame_Transfer)
F2 文件:frontend_vue3/src/stages/xilink/modules/fft/* + scope/* 控件增强(displayMode/peakHold/trigger/persistence · 不动 phase/transfer)
⚠️ moduleRegistry.ts + meterFrame.ts 是 F4/F6 共享文件 · 但都是 append-only 模式(各 export 独立 const + 各自加注册行)· git pull --no-rebase 可自动合并 · 若冲突由 worker 手动 resolve(简单 + 拼接)
【背景】
用户 2026-06-13 18:09 三连 stop F1+F3+F5(a5b52de+8eaaf40+eb84bab)算法层闭环。
18:18 用户反馈"前端 xilink IP 库还是没看到 phase/transfer 组件" · Cline 真值核查证实是预期(F1+F3+F5 仅完成底座+算法 · F4+F6 才注册到 IP 库)· 用户 18:26 拍板三连 start F4+F6+F2 直接修复。
F4 本任务核心:① usePhaseFrame composable 调 F3 DLL export ② PhaseChart canvas 渲染(参考 fft · 但 Y 轴 °/ms)③ PhaseDockModule 接入 F1 DockHost 插槽 ④ plugin 注册到 IP 库(用户痛点直接修复点 🔥)
【架构关键约束】
⚡ DLL P/Invoke 9 参数严格对齐 F3 8eaaf40 dspalgo_dll.c phase_module_get_phase 签名(handle/instanceId/displayMode/sampleRateHz/pOut/maxChannels/maxBins/pActualChannels/pActualBins)· P0.K-shared-types 共享 schema
🎨 canvas 渲染参考 FftModulePopup(769405a)· 但:① Y 轴单位 ° / ms(不是 dB)② wrapped 模式跨 ±180° 边界 line break(不画连线)③ unwrapped 模式动态 auto fit(无 fixed 上下限)
📋 displayMode 切换:wrapped(0 默认)/ unwrapped(1)/ groupDelayMs(2)· controls 3 单选按钮 + averaging 0/4/16/64 slider · 切换无缝(不重置 averaging counter)
📋 三层分工:L3 前端零数学(纯 P/Invoke 接收 frame + canvas 渲染 · 不做 FFT/atan2/unwrap/groupDelay 计算)· F3 已在 dsp_algo 完成
⚡ ADR-21 §3.3 ① schema 锁定:MeterFrame_Phase 字段 freqs/phaseWrapped/phaseUnwrapped/groupDelay/averagedCount/resolution/fftReady · 复用 ADR-12 §3.4 ① 标杆
📋 plugin 注册:typeId=0x100E0004(F3 已锁)· category='analysis'(IP 库面板分组)· defaultNodeKind='sink-pre'(默认监听点)
🔥 IP 库添加 phase 节点是用户实际痛点(18:18 反馈)· 本任务必须确保 plugin 在 IP 库面板可见 + 拖拽生成节点正确
【执行步骤】
Step 0 · 文件注入真值核查(强制门槛 · F1 教训承接)
- read frontend_vue3/src/stages/xilink/dock/DockHost.vue(F1 a5b52de · 确认 supportedNodeKinds + 3 插槽 props 真签名)
- read dsp_algo/dll/dspalgo_dll.c phase_module_get_phase export 9 参数(F3 8eaaf40 · P/Invoke 真签名)
- read frontend_vue3/src/stages/xilink/registry/moduleRegistry.ts(F5 769405a · plugin 注册接口 + 现有 4 plugin 范式)
- read frontend_vue3/src/components/popups/FftModulePopup.vue(F5 769405a · canvas 渲染范式参考)
- read frontend_vue3/src/composables/useChainNodeMetadata.ts(F1 a5b52de · channelCount 字段查询)
- read frontend_vue3/src/types/meterFrame.ts(现有 MeterFrame_RMS/FFT/Scope · 不破坏)
- 留 commit log:Step 0 六层核查记录(ADR + DLL + registry + popup + composable + type)
Step 1 · usePhaseFrame composable + 类型 0.4d(子任务 ①)
- NEW usePhaseFrame.ts · WS 订阅 + 30fps 节流 + 弱信号判定
- 扩 MeterFrame_Phase 类型(append-only · 不破坏现有 frame 类型)
- DLL P/Invoke 9 参数对齐 F3 dspalgo_dll.c 真签名
Step 2 · PhaseChart.vue canvas 渲染 0.5d(子任务 ②)
- 参考 FftModulePopup.vue · 改 Y 轴单位 °/ms · wrapped 边界 line break · unwrapped auto fit
- 多通道 stack + channelMask 过滤
- 弱信号灰底 + "Wait for signal"
- RAF 节流 + redrawKey 触发重绘
Step 3 · PhaseDockModule.vue 接入 DockHost 0.3d(子任务 ③)
- 模板用 F1 DockHost · supportedNodeKinds 4 类(去掉 xitune-module · phase 不需要)
- canvas 插槽 → PhaseChart · controls 插槽 → 3 单选 + averaging slider
- useChainNodeMetadata + useDockScale + useDockChannelMask 复用
Step 4 · plugin 注册 + IP 库入口 0.2d 🔥(子任务 ④)
- NEW index.ts · export phaseModulePlugin = { typeId, name, displayName: 'Phase', icon: '∠', component, defaultNodeKind, category: 'analysis' }
- 更新 moduleRegistry.ts · register(phaseModulePlugin)
- 浏览器实测:IP 库面板 'analysis' 类目可见 phase + 拖拽到 chain 后右 dock 自动加 PhaseDockModule
Step 5 · 测试 0.1d(子任务 ⑤)
- vitest +4 case
- vue-tsc --noEmit 0 errors
- npm run build 0 errors
- npm run test 全过(基线 + 4 新增)
Step 6 · 浏览器实测 + commit
- 启动 backend(dotnet run · 加载 F3 8eaaf40 dspalgo_dll.c phase export)+ frontend(npm run dev)
- 验收点(ADR-21 §3.3 ④ 用户操作流):
☐ 进入 xilink stage · IP 库面板 "analysis" 类目可见 "Phase" 节点(图标 ∠)🔥
☐ 拖拽 Phase 到 chain · 右 dock 自动加 PhaseDockModule
☐ Module 顶部 selector 默认 sink-pre · 切到 log_module_v1 · channelCount 适配
☐ 注入物理 1kHz @ -20dBFS sine · wrapped 模式显 0° 附近窄带相位曲线
☐ 切到 unwrapped · 单调连线 · 切到 groupDelayMs · Y 轴换 ms 单位
☐ averaging slider 切 0/4/16/64 · 抖动随平均次数减小
☐ 弱信号(无注入)· 显示 "Wait for signal" 灰底
- git add . && git commit -m "feat(xilink/phase): P1.A21.F4 phase-module 前端实装(plugin 注册 + DockModule + IP 库入口)
用户 2026-06-13 18:09 三连 stop F1+F3+F5 算法层闭环 · 18:18 反馈 IP 库无 phase/transfer · 18:26 拍板三连 start F4+F6+F2。
F4 本任务(ADR-21 §3.3):
① usePhaseFrame.ts WS 订阅 + 30fps 节流 + 弱信号 isReliable + DLL P/Invoke 9 参数(对齐 F3 8eaaf40)
② MeterFrame_Phase 类型(append-only · 不破坏现有 RMS/FFT/Scope/Transfer)
③ PhaseChart.vue canvas 渲染(参考 FftModulePopup · Y 轴 °/ms · wrapped 边界 line break · unwrapped auto fit)
④ PhaseDockModule.vue 接入 F1 DockHost 4 插槽 + 3 单选 displayMode + averaging slider
⑤ plugin 注册 typeId=0x100E0004 + category='analysis' + IP 库 'Phase' ∠ 图标可见 🔥
⑥ vitest +4 case · vue-tsc + build 全绿 · F1 11 + ADR-18 F5 152 + ADR-17 F6 基线零回归
解锁 F7 e2e 部分(还需 F6 + F2 zombie · 三齐后 F7 ready)
[step=6/6] [pid=P1] [uid=P1.A21.F4-phase-module-frontend] [type=fork] [isolation=file]
[occupies=P1.K-shared-xilink-dock+P0.K-shared-types] [files=6] [ipc=rest+ws]
[adr=ADR-AIOS-21 §3.3 phase-module 前端实装(#4 类 fft 多通道相位)]"
【验收】
☐ Step 0 文件注入真值核查通过(read 6 真值源 + commit log 留痕)
☐ Step 1 usePhaseFrame 30fps 节流 + 弱信号 + DLL 9 参数对齐 F3
☐ Step 2 PhaseChart wrapped 边界 line break + unwrapped 单调 + groupDelayMs ms 单位 + 弱信号灰底
☐ Step 3 PhaseDockModule 接入 F1 DockHost 4 插槽 + 3 单选 + averaging slider
☐ Step 4 phaseModulePlugin 注册 typeId=0x100E0004 + IP 库 'analysis' 类目 'Phase' ∠ 可见 🔥
☐ Step 5 vitest +4 case 全过 · vue-tsc + build 0 errors · 基线零回归
☐ Step 6 浏览器实测 7 验收点全过(IP 库可见 + 拖拽 + 3 displayMode + averaging + 弱信号)
☐ commit message 含 7 元组 trailer + ADR §3.3 引用
【禁止】
❌ 禁止跳过 Step 0 文件注入核查(F1 教训:派发前必须 read 6 真值源)
❌ 禁止在前端做 FFT/atan2/unwrap/groupDelay 计算(三层分工 · 这些都在 F3 dsp_algo · 本任务仅 P/Invoke + canvas 渲染)
❌ 禁止偏离 F3 dspalgo_dll.c phase_module_get_phase 9 参数 P/Invoke 签名(typeId 0x100E0004 + displayMode 0/1/2 严格)
❌ 禁止破坏 ADR-12 §3.4 MeterFrame_Phase schema(只能 append-only 加新字段 · 不能改现有字段语义)
❌ 禁止破坏 F1 11 + ADR-18 F5 152 + ADR-17 F6 已锁基线(零回归是硬门槛)
❌ 禁止跳过 vitest +4 case(验收硬门槛)
❌ 禁止 commit 缺三元组 trailer(.clinerules v1.6 铁律)
❌ 禁止嵌入完整 SFC > 60 行 / TS interface > 5 行(.clinerules v1.6)· 拆 child component 或独立类型文件
解锁链(本任务 zombie 后)
- ✅ xilink IP 库面板 'analysis' 类目可见 Phase ∠ 节点(用户 18:18 痛点直接修复 🔥)
- ✅ 拖拽 Phase 节点到 chain 后右 dock 自动加 PhaseDockModule(F1 DockHost 流程)
- ✅ 3 displayMode 实测可切(wrapped / unwrapped / groupDelayMs)+ averaging 持久化
- ✅ MeterFrame_Phase 类型契约固化(F7 e2e 有 schema 真值源)
- ⏳ F7 e2e 解锁 ⅓ 路径(还需 F6 transfer-module-frontend + F2 fft-scope-controls-enhance zombie · 三齐后 F7 ready)
风险评估
| 风险 | 缓解 |
|---|---|
| F3 dspalgo_dll.c phase_module_get_phase 9 参数与现有前端 P/Invoke 框架不兼容 | Step 0 强制 read F3 8eaaf40 dspalgo_dll.c 真签名 + grep 现有 RMS/FFT P/Invoke 范式 · 若框架不对齐则 fork 内补丁(append-only) |
| MeterFrame_Phase schema 与 ADR-12 §3.4 ① 不一致 | Step 1.2 严格对齐 ADR-12 §3.4 ① schema + ADR-21 §3.3 ① · 不允许字段重写 |
| canvas 渲染 wrapped 模式 ±180° 边界 line break 算法易错 | Step 2 用 sample-to-sample diff > π 检测边界 · 单测 1kHz sine 4 周期边界点正确 |
| plugin 注册到 IP 库面板但 'analysis' 类目缺失 | Step 0 read moduleRegistry.ts 现有 category 枚举 · 若缺则 fork 内补 'analysis' enum + IP 库面板 group 渲染分支 |
| moduleRegistry.ts + meterFrame.ts 与 F6 transfer-module 同时 push 冲突 | append-only 模式(各加各的 const + register)+ git pull --no-rebase 自动合并 · F4/F6 worker 都遵守此约定 |
| F1 DockHost.vue 插槽签名变更与本任务接入不兼容 | Step 0 强制 read F1 a5b52de DockHost.vue 真签名 + 严格用 supportedNodeKinds + 3 插槽 props · 不允许修改 F1 文件 |
| ClaudeA 三连 dispatched 排队膨胀(F4 1.5 + F6 2.0 + F2 1.5 = 5.0d 串行) | F4/F6/F2 文件正交 · 可同时 dispatched · ClaudeA 自行排期(推荐顺序:F4 1.5d → F6 2.0d → F2 1.5d 总 5.0d) |
| dsp_algo backend 加载 F3 8eaaf40 phase export 失败 | Step 6 浏览器实测前必须 backend(dotnet run)启动正常 + 后端日志确认 phase_module_get_phase 加载成功 |
历史
| 时间 | 事件 | hash |
|---|---|---|
| 2026-06-13 18:26 | dispatched(用户 18:26 拍板三连 start F4+F6+F2 · ClaudeA 1.5d · F1+F3 双解锁满足 · IP 库添加 phase 直接修复用户 18:18 反馈痛点) | — |