跳转至

ADR-12 #7 P0.U-measurement-electrical-recorder-stub(729327c)仅落 RecorderNode ① schema + UI stub · ②-⑤ 真业务延后。 用户 2026-05-31 14:26 拍板方向 3 三 fork 全派(#10 + #11 + 本 fork Recorder 真业务 · 总 5.5d)。 本 fork 升级 #7 stub 到真业务实施 · 严守 §4.3 Non-Goals(.wav 多轨文件持久化留下季度 · 仅内存 ring buffer + Multi-track + Marker + AB Compare)。 ClaudeA 排队 2.5d(议题⑤ 1.0d + cabin-3d 1.0d + 缓冲)+ 本 fork 1.5d = 4.0d 可控 · 与 #10 ClaudeC + #11 ClaudeD 文件正交并行。 estimated: 1.5d preempts: [] preempted_by: null unblocks: [ADR-12 §3.7 Recorder Node ②-⑤ 5 必填段全填 · 配合 #11 BottomDock § Task Queue 显示录音任务] related_zombie: - P0.U-measurement-electrical-recorder-stub (729327c · §3.7 ① schema + UI stub · 本 fork 升级 ②-⑤) - P0.U-measurement-thd-snr (554ddd1 · ClaudeA 同 worker 设计语言标杆) - P0.U-measurement-rms-fft-phase (8379de2 · measurement-shared 风格标杆)


P0.U-measurement-recorder-real · ADR-12 RecorderNode 真业务实施(内存 ring buffer + Multi-track + AB Compare)

Worker:ClaudeA · 前端 / 部门:前端 P0-xishell / 预计:1.5d / 优先级:P1 / 状态:ready

🔍 触发与解锁链

  • 触发:ADR-12 #7(729327c)仅落 RecorderNode ① schema + UI stub · 用户拍板方向 3 三 fork 全派 · 本 fork 升级真业务。
  • 解锁条件:本 fork zombie 后,ADR-12 §3.7 Recorder Node 5 必填段全填 · 配合 #11 BottomDock § Task Queue 显示录音任务。

任务定义(基于 ADR-AIOS-12 v2.3 §3.7 + §A.14 + §A.20 + §2.11 全局红线)

将 #7 落地的 RecorderNode stub 升级为真业务实施(§3.7 ②-⑤ 5 必填段全填):

核心范围(对齐 ADR-12 §3.7 Recorder Node): 1. 5 必填段全填 ②-⑤(收敛判据 + 失败回退 + 用户操作流 + e2e 真值)· ① schema 已在 729327c 落 2. 内存 ring buffer 真业务:Multi-track(8 通道并行)+ Loop 录制 + Marker 添加 + AB Compare(2 buffer 切换播放)+ Buffer Cache(快照供 widget 复用) 3. §A.14 Recording 组件 + §A.20 Recordings 工程文件: - §A.14:Multi-track / Loop / Marker / AB Compare / Snapshot / Buffer Cache 6 项 · 本 fork 全实施(除文件持久化) - §A.20:回放 / 分析 / Overlay / Compare / Offline FFT(本 fork 实施回放 + Overlay · Offline FFT 留下季度) 4. 文件持久化(.wav 多轨)严守 Non-Goals(§4.3):仅内存 ring buffer · 浏览器关闭丢失 · 文件持久化留 ADR-AIOS-13 候选

🚨 全局红线对齐(ADR-12 §2.11 第 8+9 项): - 响应式横竖屏:RecorderWidget 主图(波形显示)+ 控制条响应式 · 横屏=波形左+控制右 / 竖屏=波形上+控制下 - 主题色系切换:严禁硬编码 hex · 必须 var(--xs-recorder-*) design-token

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

[U-thread]: P0.U-measurement-recorder-real
[部门]: 前端 P0-xishell
[Worker CWD]: d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies]: P0.K-shared-meter-dock · P5.K-meter-tap(read)
[优先级]: P1 · 1.5d
[ADR]: docs/08-implementation/40-aios/ADR/ADR-AIOS-12-xitest-realtime-arch.md(v2.3)
[业务行为契约引用]: 严格对齐 ADR §3.7 Recorder Node 5 必填段(① schema 已在 729327c · 本 fork 落 ②-⑤)+ §2.11 第 8+9 项全局红线 + §4.3 Non-Goals(.wav 文件持久化严守不实施)
[参考文档]:
  - ADR-AIOS-12 §3.7 Recorder Node + §A.14 Recording 组件 + §A.20 Recordings(本 fork 主依据)
  - ADR-AIOS-12 §2.11 第 8+9 项全局红线 + v2.3 前后端责任边界
  - ADR-AIOS-12 §4.3 Non-Goals(.wav 多轨文件持久化留下季度 · 严守不实施)
  - 已就位 stub(729327c):frontend_vue3/src/components/measurement-shared/RecorderStub.vue + types/recording-session.ts
  - 已就位标杆(554ddd1):components/measurement-shared/ElectricalMeter.vue · ClaudeA 同 worker 设计语言(数值卡片 + 控制条)
  - 已就位标杆(8379de2):components/measurement-shared/RMSMeter.vue · 数值显示 + ring buffer 风格
  - 已就位 P5 WS(48cf0ba):MeterTapService toolKind=recorder 已支持(本 fork 直接消费 · 不动 P5)

【背景】
ADR-12 #7(`729327c`)落地 RecorderNode ① 输入输出契约 schema(stub 占位)· 本 fork 升级到 ②-⑤ 5 必填段真业务。

§A.14 Recording 组件 6 项:Multi-track(本 fork 8 通道并行)+ Loop(可循环覆盖录制)+ Marker(用户标记关键点)+ AB Compare(2 buffer 切换)+ Snapshot(转 widget Snapshot)+ Buffer Cache(供其他 widget 复用)

§A.20 Recordings 工程文件 5 项:回放 / 分析 / Overlay / Compare / Offline FFT
- ✅ 本 fork 实施:回放(WaveformChart 渲染 buffer)+ Overlay(双 buffer 叠加显示)
- ❌ 留下季度:Offline FFT(需 P7 端点)+ Compare 跨 widget(需 #11 Storage Engine 持久化)

⚠️ §4.3 Non-Goals 严守:**禁止 .wav 多轨文件持久化**(浏览器关闭后录音丢失 · 用户期望文件保存留 ADR-AIOS-13)

【执行步骤】(6 步)

Step 1 · types schema 升级(0.1d)
  - 扩 types/recording-session.ts(已落):
    interface RecordingBuffer {
      session: RecordingSession            // 已落 · ADR §3.7 ①
      tracks: Float32Array[]               // 8 通道 PCM 数据(ring buffer · 浏览器内存)
      currentSize: number                  // 已录长度(samples)
      maxSize: number                      // ring buffer 上限(默认 30s @ 48kHz × 8 = ~46MB)
    }
    interface RecorderState {
      buffers: Map<string, RecordingBuffer>  // 多 buffer · key=sessionId
      activeBufferA?: string                  // AB Compare A 槽
      activeBufferB?: string                  // AB Compare B 槽
      playbackBuffer?: string                 // 当前回放 · null=实时
      isRecording: boolean
      isPlaying: boolean
    }
  - 不动 729327c 已落地 RecordingSession schema(仅扩展)

Step 2 · RecorderNode 主组件升级(0.5d · 替换 729327c stub)
  - 路径:frontend_vue3/src/components/measurement-shared/Recorder.vue(替换 RecorderStub.vue)
  - 主图:WaveformChart 渲染当前 buffer 8 通道波形(复用 8379de2 风格)· 实时模式滚动 / 回放模式静态
  - 控制条 · 6 段(对齐 §A.14 6 项):
    - [⏺ Record] / [⏸ Pause] / [⏹ Stop](ring buffer 录入)
    - [⏮ Prev Marker] / [⏭ Next Marker]([🚩 Add Marker] 按当前 cursor 加 marker)
    - [▶ Play] / [⏸ Pause Play] / [⏹ Stop Play](选 buffer 回放 · 走 Web Audio API)
    - [💾 As Snapshot]([📤 Export to Widget] 转给 widget · 走 #10 SnapshotsPanel 接口)
    - [🔄 AB] · [A] / [B] 槽切换 · Compare 模式叠加波形
    - [🔁 Loop] · 循环覆盖录制(到 maxSize 自动从头覆盖)
  - 数值显示:Buffer Size(MB)· Duration(s)· Markers(N)· Tracks(8)· Status(Recording/Playing/Idle)

Step 3 · ring buffer + AB Compare 核心逻辑(0.4d · 本 fork 主体)
  - useRecorder.ts composable:
    - subscribeMeterTap(toolKind='recorder'):订阅 P5 WS 帧 · 推 PCM 到 currentBuffer.tracks
    - addMarker(label):在 currentBuffer 当前 cursor 加 marker
    - switchABBuffer(slot, sessionId):切 A/B 槽 · 触发 Compare 模式
    - playback(sessionId, options):用 Web Audio API AudioBufferSourceNode 回放(纯前端 · 不走后端)
    - exportToWidget(targetWidgetId, sessionId):调用 #10 SnapshotsPanel 接口(若 #10 未 zombie · fallback console.warn `[need:engine-session-snapshots]`)
  - ring buffer 滚动覆盖逻辑:Loop 模式下达到 maxSize 后从 index 0 重新写(不分配新内存)

Step 4 · 响应式横竖屏 + 主题色系(0.2d · 全局红线 #1#2)
  - 响应式横竖屏(全局红线 #1):用 ResizeObserver 监听根容器 · aspectRatio
    - 横屏(宽 ≥ 高):波形主图占 70% 宽 + 控制条侧栏 30%
    - 竖屏(高 > 宽):波形主图占 70% 高 + 控制条 30% 在下方
  - 主题色系(全局红线 #2):design-tokens.css 新增:
    --xs-recorder-bg / --xs-recorder-track-fill / --xs-recorder-cursor / --xs-recorder-marker
    --xs-recorder-status-recording / --xs-recorder-status-playing / --xs-recorder-status-idle
    --xs-recorder-ab-a / --xs-recorder-ab-b / --xs-recorder-ab-compare-overlap
  - 全组件 grep "#[0-9a-fA-F]\{3,8\}" 0 命中

Step 5 · 业务行为契约 5 必填段 e2e 真值(0.2d)
  - 新增 e2e/recorder-real-truth.spec.ts(对齐 §3.1 RMSMeter e2e 风格 · 8379de2 标杆)
  - case 1:注入 1kHz -10dBFS 1s 正弦 · 触发 Record → Stop → 断言 buffer.tracks[0] 长度 ≈ 48000 samples · 数值有效(非全 0)
  - case 2:Record + Add Marker × 3 → 断言 markers 数组长度 = 3 · 时间戳递增
  - case 3:回放 buffer A → Web Audio API audio context running · 实测时长 ≈ 1s
  - case 4:AB Compare:buffer A(粉噪)+ buffer B(白噪)→ 双波形叠加渲染 · 颜色用 ab-compare-overlap token
  - case 5:Loop 模式覆盖录制 → 达到 maxSize 后 currentSize 不再增长 · index 从 0 重置(ring buffer 验证)
  - case 6:横竖屏切换布局正确(全局红线 #1)+ 主题切换浅色↔深色(全局红线 #2)

Step 6 · 自查 + commit(0.1d)
  - npm run typecheck 全绿
  - npm run test:unit 不退化(基线 356/3)+ 新增 6 vitest case 全过
  - npm run test:e2e -- recorder-real-truth 全过
  - grep `#[0-9a-fA-F]{3,8}` 在 Recorder.vue 中 0 命中(主题切换硬约束)
  - grep `\.wav|writeFile|node:fs|writeFileSync` 在本 fork 改动文件 0 命中(严守 §4.3 Non-Goals · .wav 文件持久化留下季度)
  - grep `audio-data|audio-buffer|fft|numpy|deconv` 在本 fork 改动文件 0 命中(沿用 ADR-07 §1.3.4 三层分工 · 数学全交 P7 · Web Audio API 仅播放不算"数学")
  - 不动 729327c 已落地的 RecordingSession schema(仅扩展 RecordingBuffer + RecorderState)
  - commit subject: feat(P0.U-measurement-recorder-real): upgrade RecorderNode to real business (Multi-track ring buffer + Marker + AB Compare + Web Audio playback + responsive landscape/portrait + design-token theme · ADR-12 §3.7 ②-⑤ + §A.14 6 features)
  - commit trailer 三元组:[step=6/6] [pid=P0] [uid=U-measurement-recorder-real] [occupies=P0.K-shared-meter-dock+P5.K-meter-tap-read] [files=components/measurement-shared/Recorder.vue,types/recording-session.ts,composables/useRecorder.ts,e2e/recorder-real-truth.spec.ts,styles/design-tokens.css]

【验收】(stop 前必跑)

形式合规:
  ☐ npm run typecheck 全绿(0 error)
  ☐ npm run test:unit 不退化 + 新增 6 case 全过
  ☐ npm run test:e2e -- recorder-real-truth 6 case 全过
  ☐ grep `#[0-9a-fA-F]{3,8}` 在 Recorder.vue / 新增 .ts 中 0 命中
  ☐ grep `\.wav|writeFile|node:fs` 0 命中(严守 §4.3 Non-Goals)
  ☐ grep `audio-data|audio-buffer|fft|numpy|deconv` 0 命中(沿用三层分工铁律)
  ☐ 不动 729327c 已落地的 RecordingSession schema 既有字段(仅扩展)
  ☐ design-tokens.css 已加 --xs-recorder-* 9 项 token

业务行为契约(端到端真值 · 必跑):
  ☐ 1kHz 1s 正弦录入 → buffer 长度 ≈ 48000 · 数值有效非全 0
  ☐ Add Marker × 3 → markers.length = 3 · 时间戳递增
  ☐ 回放 buffer A · Web Audio API context running · 时长 ≈ 1s
  ☐ AB Compare:粉噪 + 白噪 双波形叠加 · 颜色 token 正确
  ☐ Loop 模式覆盖录制 · ring buffer 不增长 · index 重置
  ☐ 横屏 1920×1080 vs 竖屏 1080×1920 布局切换(全局红线 #1)
  ☐ 主题切换浅色 ↔ 深色 · Recorder 颜色变化(全局红线 #2)

【commit】
- subject + 三元组 trailer 见 Step 6
- 7 天宽限期内三元组缺失仅 warning · 6/2 起 strict mode 硬拒

【禁止】(8 项红线)
1. ❌ 禁止实施 .wav 多轨文件持久化(严守 ADR-12 §4.3 Non-Goals · 留 ADR-AIOS-13 候选)
2. ❌ 禁止在前端实装 FFT / Offline 数学计算(沿用 ADR-07 §1.3.4 三层分工 · 数学全交 P7)
3. ❌ 禁止硬编码 hex 色值(必须 var(--xs-recorder-*) design-token · 全局红线 #2)
4. ❌ 禁止跳过响应式横竖屏 e2e(必须 viewport 1920×1080 vs 1080×1920 各跑一次 · 全局红线 #1)
5. ❌ 禁止动 729327c 已落地的 types/recording-session.ts 既有字段(仅扩展 RecordingBuffer + RecorderState)
6. ❌ 禁止改 P5 后端任何接口(对齐 ADR-12 §2.11 v2.3 前后端责任边界 · 现有数据契约不变)
7. ❌ 禁止跳过 e2e 真值(必须 6 case · 含全局红线 #1#2 · 不允许仅 typecheck/test 形式合规通过就 commit)
8. ❌ 禁止越界引入 stages/ 子目录代码(本 fork 仅在 components/measurement-shared/ + composables/ + types/ + e2e/ + styles/design-tokens.css)

解锁链(本任务 zombie 后)

  • ✅ ADR-12 §3.7 Recorder Node 5 必填段全填(对齐 §A.14 6 features)
  • ✅ 配合 #11 BottomDock § Task Queue 显示录音任务进度(若 #11 已 zombie · 自动联动)
  • ✅ 配合 #10 SnapshotsPanel:exportToWidget 把录音 buffer 转 Snapshot · 跨 widget 复用
  • ✅ Workspace Preset 'Recording' 默认 widget 第 1 格就位(Waveform / Spectrogram / Markers · 本 fork 提供 Recorder 主体)

风险评估

风险 缓解
ring buffer 30s × 8 通道 × 48kHz × 4byte ≈ 46MB 内存占用过大 Step 3 用 SharedArrayBuffer + Float32Array views(不复制 · 仅引用)· 浏览器单 widget < 100MB 可接受 · 用户可调 maxSize 默认 30s
Web Audio API AudioBufferSourceNode 跨浏览器兼容(Edge/Chrome/Firefox) Step 3 用 vueuse @vueuse/sound 兼容封装 · IE 不支持(MDN 规范无要求 IE)· Electron 26+ 100% 支持
AB Compare 双波形渲染性能(8 通道 × 48000 samples × 2 buffer) Step 2 WaveformChart 用 Canvas 降采样(峰值法 · 每像素 1 数据点)· 60fps 可达
Marker 时间戳与 ring buffer 滚动覆盖冲突(Loop 模式下旧 marker 失效) Step 3 useRecorder.addMarker 在 Loop 覆盖时自动清理过期 marker · 保留最近 N 条
#10 SnapshotsPanel 未 zombie 时 exportToWidget 调用失败 Step 3 useRecorder fallback console.warn [need:engine-session-snapshots] · 不阻塞本 fork zombie · 用户在 #10 zombie 后切真接口

历史

时间 事件 hash
2026-05-31 14:30 dispatched · 用户拍板方向 3 三 fork 全派(#10 + #11 + 本 fork)· ClaudeA 排队 4.0d 末尾