P0.A17R1.F3 · Waveform Scope UI + Trigger(canvas 时域波形 + level/edge/preroll + freeze + Snapshot A/B)
worker:ClaudeA 部门:前端 P0 预计:1.5d(并行路径 · 与 F1 文件正交) 优先级:P0 状态:ready
🔍 触发与解锁链
- 触发:ADR-17-R1 accepted(2026-06-07 11:03 · 用户拍板)解锁 + 用户 11:03 拍板
start P0.A17R1.F3-waveform-scope-ui-and-trigger - 前置:ClaudeA ADR-18 F5(1.0d 业务式 IP 库)zombie 后接 F3 · 后端 P5 buffer 抓取已 ADR-13 H1(
bcd9a74)就位 · 不依赖 F1 phase 算法 - 解锁:F6 e2e ≥ 12 case 真值断言 🏆(等 F1+F2+F3+F4+F5 全 zombie · scope 真值 case = "1kHz 方波 trigger 锁后 buffer 包含完整周期")
任务定义(基于 ADR-17-R1 §3.5.3 完整 5 段 + 引用 ADR-12 §3.5 line 805-862)
实现 Waveform Scope UI + Trigger 完整闭环:NEW WaveformScopePanel.vue(canvas 时域波形渲染 · 多通道叠加 · 颜色区分)+ trigger UI(level/edge/preroll 配置)+ freeze 按钮(冻结当前 buffer)+ Snapshot A/B 对比(localStorage 持久化)+ NEW useWaveformScope composable(订阅 P5 buffer 抓取 endpoint · 解析 PCM samples · canvas 60fps 渲染)+ 单元测试覆盖业务契约 §3.5.3 ② 2 收敛判据 + ③ 2 失败回退 · 严守三层分工铁律(L3 前端零数学 · 仅 canvas 渲染 + Float32Array 时域绘图 · trigger 检测在前端做轻量阈值比对 = 非 DSP 数学 · samples 物理意义解析由后端 buffer endpoint 提供)。
完整 prompt(直接复制粘贴 worker 终端)
[U-thread] P0.A17R1.F3-waveform-scope-ui-and-trigger
[部门] 前端 (P0-xishell + P4-xitest realtime stage)
[Worker CWD] d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies] P0.K-shared-xitest + P0.K-shared-meter-dock
[优先级] P0 · R1 并行路径(与 F1 phase 文件正交并行)
[ADR] docs/08-implementation/40-aios/ADR/ADR-AIOS-17-R1-realtime-widgets-algorithm-impl.md
[参考文档]
- 业务契约:ADR-17-R1 §3.5.3 完整 5 段(① MeterFrame_Waveform schema · ② 2 收敛判据 · ③ 2 失败回退 · ④ Snapshot A/B 对比操作流 · ⑤ 1kHz 方波 trigger e2e 真值)
- 业务契约 ① 引用 ADR-12 §3.5 line 805-825(MeterFrame_Waveform 字段定义:channels / bufferSize 100ms-5s / samples[] / triggered / triggerLevel / triggerEdge / preRollPercent / frozen)
- fork 描述:ADR-17-R1 §4 fork 表 F3 行
- F4 标本(同 ClaudeA · 同部门前端 · 同 ADR-17 系列):prompts/done/ADR-AIOS-17/F4-loopback-generator-ui--c2cbeb8.md(LoopbackGeneratorPanel.vue NEW 477 行 + form state + REST 调用 + 8 case TDD)· **沿用其 vue 组件 + composable + 单测风格**
- F6 标本(同 ClaudeA · widget endpoint selector + RMS/scope 弹窗):prompts/done/P0.A17.F6-widget-endpoint-selector-4types--78dc17c.md(scope-module 弹窗 component canvas raw-sample 零数学 · 12 case TDD)· **复用 canvas 渲染风格**
- 三层分工:ADR-07 §1.3.4(前端零数学 · 数学全交 P7 · F3 仅做 canvas 时域绘图 + form 编辑 + buffer endpoint 调用)
- 现有代码参考:frontend_vue3/src/stages/xitest/(Realtime stage 入口 · Step 0 grep 真值)+ frontend_vue3/src/components/realtime/(F4 LoopbackGeneratorPanel + 现有 widget panel)+ frontend_vue3/src/composables/useMeterWs.ts(WS 订阅模式参考)
- 后端 buffer 抓取 endpoint:ADR-13 H1(`bcd9a74`)已就位 · GET /api/realtime/buffer-capture?bufferSize=&trigger= · 返 MeterFrame_Waveform schema(samples[] + triggered)
【背景】
ADR-17 7 fork 全 zombie 但 §3.4 line 517-519 假设 ADR-12 时代 7 widget 算法已 fulfilled · 实际 ADR-13 H1/H2/H3 触底证明 scope widget 仍 stub 占位(canvas 渲染 zeros 数组 · 无 trigger 逻辑 · 无 Snapshot A/B 对比)。
用户 2026-06-04 12:37 + 2026-06-06 21:09 双次反馈"phase transfer 等都不显示 · 测量插件只有 fft+rms 可用"症状原话完全相同 · scope 是 5 widget 中的一个 stub。
ADR-17-R1 v0.1 accepted · 6 fork 12d 跨栈实装 5 widget · 本 fork 是前端 UI 层并行路径(与 F1 phase 后端算法层文件正交)· 不依赖 F1 zombie 即可起手。
后端 P5 buffer 抓取 endpoint 已 ADR-13 H1 就位 · samples 物理意义由后端解析 · 本 fork 仅做 canvas 时域绘图 + trigger UI + Snapshot A/B 对比。
【架构关键约束】
- ⚡ **三层分工铁律**:L3 前端零数学(本 fork 范围)· trigger level 阈值比对 = 简单 if-else 非 DSP 数学 · samples 物理意义(物理单位 / 采样率 / 通道映射)由后端 P5 buffer endpoint 解析后提供 · 前端仅做 canvas Path2D / lineTo 时域绘图
- 🎨 **F3 范围**:NEW WaveformScopePanel.vue + useWaveformScope composable + Snapshot A/B 对比 UI + 8 case 单测 · **不做后端 buffer endpoint**(ADR-13 H1 已就位)· 不做 P5 路由扩展 · 不做 trigger 算法的复杂数字信号处理(那是 P7 未来扩展)
- 📋 **F1 文件正交并行**:F3 改前端 .vue + .ts 文件 · F1 改 P5 backend_csharp + pysidecar/ + protocol/ · 完全文件正交 · 可同时跑(ClaudeA 跑 F3 · ClaudeB 跑 F1)
- 🚨 **拒绝 stub 占位**(ADR-13 触底教训):scope canvas 必须用真 PCM samples 数据(从后端 buffer endpoint 拉)· 不允许 zeros 数组 · 不允许随机数 · F6 e2e 真值断言会验"1kHz 方波 trigger 锁后 buffer 包含完整周期"(48 samples @ 48kHz)
- 🎯 **Step 0 强制门槛**(F2 教训承接):必须 grep + read 校验 WaveformScopePanel.vue 真注入到 Realtime stage 激活的 panel · 不允许凭文件名推断
【执行步骤】
Step 0 · 文件注入真值核查(F2 教训承接 · 强制门槛)
- grep "Realtime" 在 frontend_vue3/src/stages/xitest/ 中找入口文件(可能 RealtimeMode.vue / index.vue / 类似)
- grep "WaveformChart" 在 frontend_vue3/src/components/ 中找现有 stub(ADR-13 H1 时代留的)· 如果存在 · 评估"扩展现有 stub" vs "净新增 WaveformScopePanel.vue"
- grep "buffer-capture" 在 backend_csharp/ 中确认 ADR-13 H1 已就位 + 端点签名(等 ClaudeA 实测核查 · 不凭描述推断)
- read 入口 vue 文件确认 Realtime mode 真激活的 panel 是哪个 · 是否复用 F4 LoopbackGeneratorPanel 的嵌入位置
- ⚠️ 不允许凭文件名推断 · 必须 grep import + 看 router/stage activate 逻辑
- 输出 internal todo:确认 WaveformScopePanel.vue 嵌入路径 + 是否替换/扩展现有 WaveformChart stub + buffer endpoint 真签名
Step 1 · 起 K-thread 占用 + 读 §3.5.3 + ADR-12 §3.5
- 读 ADR-17-R1 §3.5.3 完整 5 段(收敛/失败/操作流/e2e)
- 读 ADR-12 §3.5 line 805-862(MeterFrame_Waveform 字段 · Snapshot A/B 操作流)
- 读 F4 / F6 标本(canvas 渲染 / form state / 单测风格)
Step 2 · NEW WaveformScopePanel.vue(净新增)
- frontend_vue3/src/components/realtime/WaveformScopePanel.vue(或 stages/xitest/realtime/ 下 · Step 0 真值)
- props:bufferSize(number · 100-5000ms · 默认 1000)+ channels(number · 自动协商)+ sampleRate(number · 48000/44100/96000)+ source(WidgetEndpoint · 复用 F6 endpoint selector)
- 内部 form state:
- trigger: { enabled: boolean, level: number (-60~0 dBFS), edge: 'rising'|'falling', preRollPercent: number (0-100) }
- frozen: boolean(冻结按钮控制)
- snapshotA / snapshotB: MeterFrame_Waveform | null(localStorage 持久化)
- canvas 渲染:
- 多通道叠加(颜色区分 · 复用 F6 颜色 picker 风格)
- x 轴:时间(0 → bufferSize ms)
- y 轴:幅值(-1.0 ~ 1.0 normalized)
- trigger 锁定后显示 trigger 时刻竖线 + preRoll 阴影区
- frozen=true 时停止刷新 + 显示 "FROZEN" badge
- Snapshot A/B 对比模式:
- [💾 Save Snapshot A] / [💾 Save Snapshot B] 按钮 · 写 localStorage
- [↔ Compare A vs B] toggle · canvas 双曲线叠加(A 蓝 · B 红)
- 操作流对齐 ADR-12 §3.5 ④ Snapshot A/B 对比
Step 3 · NEW useWaveformScope composable
- frontend_vue3/src/composables/useWaveformScope.ts
- 订阅模式(看后端 endpoint 真值):
- 若 ADR-13 H1 是 REST poll(GET /api/realtime/buffer-capture):用 setInterval 定周期拉(默认 1Hz 拉一次 · trigger 后停拉)
- 若是 WS subscription:复用 useMeterWs 模式 · subscribe topic `waveform-scope:<chainId>:<channelId>`
- trigger 检测逻辑(前端轻量):
- 后端 frame.triggered=true 时 · 锁定 frame 不再更新
- 用户改 trigger config 后重新拉 buffer
- frozen 状态:停止订阅 / 拉取 · 保留当前 frame
- reactive return:{ currentFrame, snapshotA, snapshotB, isTriggered, isFrozen, saveSnapshot(slot), compare() }
Step 4 · 嵌入到 Realtime stage(Step 0 确认的真激活 panel)
- v-if="loopbackEnabled || hardwareSourceConnected" 控制显隐
- 业务契约 §3.5.3 ④ 嵌入位置:Realtime stage 右侧 widget pane(复用 F6 widget endpoint selector 入口)· 或独立 widget(看 Step 0 现有 stub 注入位置)
- props 传递:source = widget endpoint(用户在顶部 [▼ Endpoint] selector 选 sink-pre / physical-input / log-module)
Step 5 · 错误处理(对齐 §3.5.3 ③ 2 失败回退)
- trigger 等待超时(60s 未触发)→ toast warn "等待触发超时 · 降低 trigger level"+ 自动回到 free-run 模式
- 数据丢帧(后端 P5 报告 buffer underrun)→ 顶栏红警 + XRUN 计数 +1(复用 F8 ADR-15 status bar 模式)
Step 6 · 单元测试(vitest · ≥ 8 case)
- tests/components/realtime/WaveformScopePanel.spec.ts(≥ 6 case)
- case1: 默认 bufferSize=1000 · 渲染空 canvas + "等待信号" placeholder
- case2: 注入 mock samples (1kHz square wave · 48 samples) · canvas Path2D 路径正确(verify lineTo count)
- case3: trigger.enabled=true + level=-10 + edge='rising' · 触发后 isTriggered=true + canvas 显竖线
- case4: frozen=true · canvas 停止刷新(verify mock setInterval 不再调)
- case5: Save Snapshot A → localStorage 写入 · UI 显示 "A saved"
- case6: Compare A vs B · canvas 双曲线渲染(verify 2 path2D)
- tests/composables/useWaveformScope.spec.ts(≥ 2 case)
- case7: 订阅 WS frame waveform_scope_v1(若是 WS)· 解析 samples 正确
- case8: trigger 等待超时 60s · toast warn
Step 7 · 验收 + commit
- vue-tsc --noEmit ✓ exit 0
- npm run build ✓ 0 错误
- npm run test:unit ✓ 至少 +8 case 全过(基线 +8)
- 用户实测路径:Realtime stage → 选 source(loopback / hardware)→ Waveform Scope widget 显示 → 注入 1kHz square 信号 → trigger 锁后 canvas 显完整方波周期(48 samples @ 48kHz · ≈ 1ms)
- F4 LoopbackGeneratorPanel + F6 widget endpoint selector + RMS/scope 弹窗 module 不修改
- commit message 见下方 trailer
【验收】
- ☐ 4-5 文件改/新建:components/realtime/WaveformScopePanel.vue(净新增)+ composables/useWaveformScope.ts(净新增)+ Step 0 确认的 Realtime panel(嵌入 WaveformScopePanel)+ tests/components/realtime/WaveformScopePanel.spec.ts + tests/composables/useWaveformScope.spec.ts
- ☐ 测试新增 ≥ 8 case · 基线 +8
- ☐ vue-tsc + build + test:unit 全绿
- ☐ Step 0 文件注入核查通过(WaveformScopePanel 真嵌入到 Realtime stage 激活 panel · 不复发 F2 修错陷阱)
- ☐ ADR-17-R1 §3.5.3 ② 2 收敛判据全过(triggered=true · samples.length===bufferSize)
- ☐ §3.5.3 ③ 2 失败回退覆盖(trigger 等待超时 · 数据丢帧 XRUN)
- ☐ Snapshot A/B 对比操作流完整(localStorage 持久化 · toggle 双曲线渲染)
- ☐ 三层分工铁律严守(前端零 DSP 数学 · samples 数据由后端 endpoint 提供)
- ☐ 拒绝 stub 占位 · canvas 用真 PCM samples 渲染(F6 e2e 真值断言会验"1kHz 方波 trigger 锁后 buffer 包含完整周期 48 samples @ 48kHz")
- ☐ commit message 含三元组 trailer
【commit】
subject: feat(P0.A17R1.F3/waveform-scope-ui-and-trigger): NEW WaveformScopePanel.vue (canvas 时域波形 + trigger level/edge/preroll + freeze + Snapshot A/B) + useWaveformScope composable + 8 case TDD
trailer:
[step=7/7] [pid=P0] [uid=P0.A17R1.F3-waveform-scope-ui-and-trigger] [occupies=P0.K-shared-xitest+P0.K-shared-meter-dock]
[adr=ADR-AIOS-17-R1 §3.5.3] [files=components/realtime/WaveformScopePanel.vue,composables/useWaveformScope.ts,<step0确认的 Realtime panel>,tests/components/realtime/WaveformScopePanel.spec.ts,tests/composables/useWaveformScope.spec.ts]
[ipc=rest+ws-ready]
【禁止】
- ❌ 禁止跳过 Step 0 文件注入核查(F2 教训 · 必须 grep + read 校验目标 vue 文件真注入到激活 stage · 不能凭文件名推断)
- ❌ 禁止在前端做任何 DSP 数学(三层分工 · trigger level 阈值比对 = 简单 if-else 非数学 · 任何 RMS/FFT/correlation 留 P7)
- ❌ 禁止超前实施后端 buffer endpoint(ADR-13 H1 已就位 · 仅调用 · 不动 schema)
- ❌ 禁止超前实施 F1 phase / F2 transfer / F4 spectrogram / F5 electrical(那是 R1 其他 fork 范围)
- ❌ 禁止动 ADR-17 已 zombie 的 5 fork(F1/F3/F4/F5/F6 · 本 fork 仅扩 Realtime stage 嵌入位置)
- ❌ 禁止 canvas 渲染 zeros 数组 / 随机数 / mock 占位数据(ADR-13 触底教训)· 必须用真 PCM samples
- ❌ 禁止破坏 contract-v1.0 frozen(仅调用现有 buffer endpoint · 不动 schema)
- ❌ 禁止跳过单元测试(≥ 8 case · 验收硬门槛 · F6 e2e 真值断言会验数据准确性)
- ❌ 禁止 commit 缺三元组 trailer
解锁链(本任务 zombie 后)
- ✅ R1 F6
P_e2e.A17R1.F6-truth-e2e-5-widgets-algorithm🏆(等 F1+F2+F3+F4+F5 全 zombie · ClaudeC 2.0d · scope 真值 case = "1kHz 方波 trigger 锁后 buffer 包含完整周期 48 samples @ 48kHz") - ✅ 用户实测路径就位:Realtime stage scope widget 真显波形 + trigger 锁定 + Snapshot A/B 对比(彻底解决"phase transfer 等都不显示"症状的 ⅕)
风险评估
| 风险 | 缓解 |
|---|---|
| Step 0 文件注入真值核查不通过(F2 教训复发) | 强制门槛 · grep "Realtime" + grep "WaveformChart" + read 入口 + 看 import/mount · 不允许凭文件名推断 |
| 现有 WaveformChart stub 与新 WaveformScopePanel 冲突(ADR-13 H1 时代留的 stub) | Step 0 评估"扩展现有 stub" vs "净新增 + 替换" · 倾向净新增 + 替换 stub(stub 是 zeros 占位 · 拒绝继承)· 用户可在 commit message 注明替换路径 |
| canvas 渲染性能(48kHz × 5s buffer = 240k samples · 多通道叠加) | Path2D 用 lineTo 批量 · 限频 60fps requestAnimationFrame · 大于屏幕宽度时降采样(每 N samples 取 max+min) |
| trigger 检测逻辑边界(rising edge · level=-10dBFS · 跨样本) | 前端仅做 frame.triggered=true 锁定 · trigger 算法在后端 P5 buffer endpoint 实现(ADR-13 H1 已就位)· case3 验前端锁定逻辑 |
| Snapshot A/B localStorage 持久化跨 session 一致性 | localStorage key 用 waveform-snapshot-{slot}-{chainId}-{channelId} · 跨 session 自动读取 · case5 验持久化 |
| ADR-13 H1/H2/H3 触底复发(stub 占位) | F6 e2e 真值断言"1kHz 方波 trigger 锁后 buffer 包含完整周期"(verifyScopePeriodicity 过零点法误差<5%)· 不允许 stub 走 e2e |
历史
| 时间 | 事件 | hash |
|---|---|---|
| 2026-06-07 11:03 | dispatched(ADR-17-R1 accepted 解锁 + 用户 11:03 拍板 start F1+F3 · 与 F1 文件正交并行 · ClaudeA 当前 ADR-18 F5 跑中 · F5 zombie 后接) | (待 commit) |