跳转至

P0.UA13-storage-engine-v2-captures · ADR-13 SmartStorageEngine v2 升级(议题 4 capture 持久化)

Worker:ClaudeA · 前端 / 预计:1.5d / 优先级:P1 / 状态:dispatched 隔离:🧵 文件隔离(同 worktree 同 branch · 与 ADR-13 fork ½/¾/5 + ADR-09 4 active fork 文件路径完全正交)


🔍 触发与解锁链

触发 状态 影响
ADR-AIOS-13 v1.0 accepted(2026-06-01 19:21) ✅ user accept 8 fork ready · 用户首批 start fork 1+6
真值核查 #3(subagent · 2026-06-01 18:25) ✅ SmartStorageEngine 5ea9806 双层就位 · IDB v1 仅 2 store(snapshots+workspaces) 本 fork 在其上扩 v2 + 2 新 store(captures+testProjects)
ADR-12 §3.7 Recorder 真业务 zombie 🏆 ✅ 824b2a5 收尾 多 widget snapshot schema 范式已就位 · 本 fork 复用

→ 本 fork zombie 解锁:Phase 3 fork 7(capture toolbar 弹框 + 多 widget 同 groupId 抓取 + LeftDock § Captures 列表)。


任务定义(基于 ADR-AIOS-13 v1.0 §2.7 + §3.4 + §6.1)

升级 SmartStorageEngine v1(5ea9806 已落地双层 localStorage + IndexedDB)→ v2 · 加 2 新 store + LEGACY 迁移 + types schema:

核心范围: 1. types schema:CaptureRecord(7 widgetType 物理分目录 + captureGroupId + description Ximind 字段)+ TestProject(name + description + capturesByWidgetType 索引)+ SerializedCurveData union(7 kind:fft/rms/transfer/phase/waveform/electrical/recorder) 2. IDB schema 升级 v1→v2:加 captures store(keyPath: id · index byTestProject/byWidgetType/byCaptureGroup)+ testProjects store(keyPath: id · index byCreatedAt) 3. 新 API 方法(扩 storage-engine.ts): - saveCapture(record) / listCaptures(testProjectId, filter?) / loadCapture(id) / updateCapture(id, partial) / deleteCapture(id) - saveTestProject(project) / listTestProjects() / loadTestProject(id) / deleteTestProject(id)(级联删 captures) 4. LEGACY 迁移 7 天宽限(ADR §6.1):detect 旧 xitest:ws:* localStorage key → 自动迁移到 testProject "default" → 显示 banner(banner 实施留 fork 7 · 本 fork 仅落 migration 函数 + flag) 5. Ximind 兼容性 5 项(ADR §11):description 字段(CaptureRecord/TestProject)/ structured error / 自描述字段名 camelCase

不在本 fork 范围: - ❌ 不写 capture 弹框 UI(fork 7)/ recapture 同名替换(fork 7)/ 多曲线叠加渲染(fork 7) - ❌ 不动 ADR-12 §3 7 类 MeasurementNode 业务行为契约(只扩 § Storage 子能力) - ❌ 不动 LeftDock § Workspace § Test Project + § Captures 列表 UI(fork 7) - ❌ 不动后端任何代码(本 fork 0 后端依赖)


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

[U-thread]   P0.UA13-storage-engine-v2-captures
[部门]       前端 P0-xishell · 推荐 skill: vuejs-typescript-best-practices
[Worker CWD] d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies]   P0.K-storage-engine(写 · IDB v1→v2 升级 + 2 新 store API + LEGACY 迁移)
[隔离]       🧵 文件隔离 · 仅写:
             - frontend_vue3/src/storage/storage-engine.ts(扩展 · 不重写 · IDB upgrade(2) 加 2 store + 8 新方法)
             - frontend_vue3/src/types/capture.ts(新建 · CaptureRecord + SerializedCurveData union)
             - frontend_vue3/src/types/test-project.ts(新建 · TestProject)
             - frontend_vue3/src/types/storage.ts(扩展 · 加 captures/testProjects 接口段 · 行号正交不动 v1 段)
             - frontend_vue3/src/storage/legacy-migration.ts(新建 · 7 天宽限期 detectAndMigrate)
             - frontend_vue3/src/storage/__tests__/storage-engine-v2.spec.ts(新建 vitest)
             - frontend_vue3/src/storage/__tests__/legacy-migration.spec.ts(新建 vitest)
             与 ADR-13 fork 1/2/3(后端 backend_csharp/)+ fork 4(stores/realtimeRunStore.ts + stages/xitest/index.vue)+ fork 5(stages/xitest/drawers/EnginePanel.vue)+ ADR-09 4 active 文件路径完全正交
[优先级]     P1 · 1.5d · Phase 2 唯一无后端依赖 fork · 解锁 Phase 3 fork 7
[ADR]        d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/ADR/ADR-AIOS-13-xitest-realtime-dual-mode.md
             (必读 §2.7 SmartStorageEngine 扩展 schema + §3.4 议题 4 业务行为契约 + §6.1 LEGACY 迁移 + §11 Ximind 兼容性)
[业务行为契约引用] ADR-13 §3.4(① CaptureRecord/TestProject schema + ② 落盘成功判据 + ③ 失败回退 + ④ 用户操作流 + ⑤ e2e 真值)
[参考文档](绝对路径 · worker 必读)
  - ADR-13:d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/ADR/ADR-AIOS-13-xitest-realtime-dual-mode.md
    · §2.7 完整 CaptureRecord/TestProject/SerializedCurveData schema(本 fork 抄)
    · §2.8 LeftDock § Workspace 扩展 UI 草图(fork 7 实施 · 本 fork 仅看 schema 是否对齐)
    · §3.4 议题 4 业务行为契约 5 子段
    · §6.1 LEGACY 7 天宽限期(detect xitest:ws:* localStorage key → 迁移 testProject "default")
    · §11.1+§11.2 Ximind 可读状态 + 可写操作(storageEngine.listCaptures 含 description 字段)
  - 范式参考(已 zombie · ClaudeA 同部门标本):
    · d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/done/P0.U-bottom-dock-storage-engine--5ea9806.md
      (SmartStorageEngine v1 实施范式 · idb v8.0.3 双层架构 · ⭐ 本 fork 直接对应升级 v2)
    · d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/done/P0.U-measurement-recorder-real--824b2a5.md
      (Recorder snapshot 多 track + Marker · waveform widget data schema 参考)
  - 现状必读(主仓真值核查 · Step 1 必跑):
    · frontend_vue3/src/storage/storage-engine.ts(全文必读 · 重点看 v1 IDB upgrade 逻辑 · IDB_VERSION 常量 · 现有 saveSnapshot/saveWorkspace API 风格)
    · frontend_vue3/src/types/storage.ts(全文必读 · 看 v1 接口 · 行号正交规划)
    · frontend_vue3/src/types/snapshot.ts(必读 · 看 SnapshotType union 7 种 · 与本 fork SerializedCurveData 7 kind 对齐)
    · frontend_vue3/package.json(确认 idb v8.0.3 已装 · 本 fork 不引入新依赖)

【背景】
ADR-AIOS-13 v1.0 accepted(2026-06-01 19:21)。议题 4 决议:capture 类 Smaart 范式(顶栏 📸+🔄 + 物理分目录 + 弹框命名 · 用户拍板 Q4.1=A4)。

3 路 subagent 主仓真值核查(2026-06-01 18:25)发现:
  ① SmartStorageEngine `5ea9806` 已落地 v1 · idb v8.0.3 双层架构(localStorage<100KB → IndexedDB≥100KB)
  ② IDB v1 仅 2 store:`snapshots`(keyPath: id)+ `workspaces`(keyPath: id)
  ③ WorkspaceData schema 极简 5 字段 · 无 capture/testProject 概念
  ④ SnapshotType union 已含 'fft'|'rms'|'transfer' 等 7 种(与本 fork SerializedCurveData 对齐)
  ⑤ WorkspacePresetPanel 仅 4 套 Preset 切换 · 无 testProject 目录概念(fork 7 扩)

本 fork 核心:升级 IDB v1→v2 · 加 captures + testProjects 2 store · 暴露 8 新 API 方法 · LEGACY 7 天宽限自动迁移 · 0 破坏 v1 行为(snapshots/workspaces store 完全保留)。

【执行步骤】(7 步 · 总 1.5d)

Step 1 · 真值核查 + git pull(必跑 · 0.1d)
  - git status / git pull origin xistudio --no-rebase
  - cat frontend_vue3/src/storage/storage-engine.ts(全文 · 输出关键段:IDB_VERSION 常量值 + upgrade(db, oldVersion) 函数当前实现 + saveSnapshot/saveWorkspace 当前签名)
  - cat frontend_vue3/src/types/storage.ts(全文 · 输出 v1 接口结构 · 规划 v2 段插入位置)
  - cat frontend_vue3/src/types/snapshot.ts | head -30(SnapshotType union 当前值)
  - 输出真值核查到 commit message body:
    · v1 IDB_VERSION = ???
    · v1 upgrade 函数当前签名 = ???
    · v1 saveSnapshot 当前签名 = ???
    · types/storage.ts v1 接口名 = ???

Step 2 · 落 types schema(0.2d)
  - frontend_vue3/src/types/capture.ts(新建 · 抄 ADR §2.7 schema):
    export type WidgetType = 'fft'|'rms'|'transfer'|'phase'|'waveform'|'electrical'|'recorder'
    export type SerializedCurveData =
      | { kind: 'fft', freqs: number[], magsDb: number[], averagedCount: number }
      | { kind: 'rms', timestamps: number[], rmsDb: number[][], peakDb: number[][] }
      | { kind: 'transfer', freqs: number[], magnitudeDb: number[], phaseDeg: number[], coherence: number[], delayMs: number }
      | { kind: 'phase', freqs: number[], phaseDeg: number[] }
      | { kind: 'waveform', samples: number[][], sampleRate: number }
      | { kind: 'electrical', metric: 'thd'|'thdN'|'sinad'|'snr', value: number, freq: number }
      | { kind: 'recorder', samples: number[][], duration: number, markers: { at: number, label: string }[] }

    export interface CaptureRecord {
      id: string                       // uuid
      captureGroupId: string           // 同一次 capture 的多曲线共享(fork 7 一次抓 4 widget = 4 record · 同 groupId)
      testProjectId: string
      widgetType: WidgetType
      measurementNodeRef: string       // ADR-12 MeasurementNode id
      name: string                     // 默认 `<timestamp>__<widgetType>__<userName>` · fork 7 弹框可改
      description?: string             // ⭐ Ximind 自然语言描述
      color?: string                   // design-token 名(fork 7 用)· 不写 hex
      capturedAt: number
      modifiedAt: number               // recapture 时更新
      physicalPath: string             // `<testProjectId>/captures/<widgetType>/<filename>.json`(本 fork 写字段 · 真物理落盘 fork 7 决定是否实装)
      data: SerializedCurveData
      metadata: {
        sampleRate: number
        activeMode: 'hardware' | 'loopback'
        inputDevice: string
        outputDevice?: string
        signalConfig?: { type: string; frequency?: number; amplitudeDbfs: number }
      }
    }

  - frontend_vue3/src/types/test-project.ts(新建):
    import type { WidgetType } from './capture'
    export interface TestProject {
      id: string
      name: string                     // 用户输入(eg. "Speaker A · After Tuning")
      description?: string             // ⭐ Ximind
      createdAt: number
      modifiedAt: number
      capturesByWidgetType: Partial<Record<WidgetType, string[]>>  // CaptureRecord ids
    }

  - frontend_vue3/src/types/storage.ts(扩展 · 不动 v1 接口 · 在末尾追加):
    // v2 新增(ADR-13 §2.7)
    export interface CaptureFilter { widgetType?: WidgetType; captureGroupId?: string }
    export interface StorageEngineV2 extends StorageEngine {
      saveCapture(record: CaptureRecord): Promise<void>
      listCaptures(testProjectId: string, filter?: CaptureFilter): Promise<CaptureRecord[]>
      loadCapture(id: string): Promise<CaptureRecord | null>
      updateCapture(id: string, partial: Partial<Pick<CaptureRecord, 'name'|'description'|'color'|'data'|'modifiedAt'>>): Promise<void>
      deleteCapture(id: string): Promise<void>
      saveTestProject(project: TestProject): Promise<void>
      listTestProjects(): Promise<TestProject[]>
      loadTestProject(id: string): Promise<TestProject | null>
      deleteTestProject(id: string): Promise<void>     // 级联删 captures
    }

Step 3 · 升级 storage-engine.ts IDB v1→v2(0.4d)
  - 改 IDB_VERSION 常量:1 → 2
  - upgrade(db, oldVersion) 函数加 if-block(不动 v1):
    if (oldVersion < 2) {
      const captures = db.createObjectStore('captures', { keyPath: 'id' })
      captures.createIndex('byTestProject', 'testProjectId')
      captures.createIndex('byWidgetType', 'widgetType')
      captures.createIndex('byCaptureGroup', 'captureGroupId')

      const testProjects = db.createObjectStore('testProjects', { keyPath: 'id' })
      testProjects.createIndex('byCreatedAt', 'createdAt')
    }
  - 8 新方法实施(都走 IDB · captures+testProjects 通常 ≥ 100KB · 不走 localStorage 分支)
  - listCaptures 用 byTestProject index · 按需 filter widgetType / captureGroupId(filter 二次内存过滤)
  - deleteTestProject 走事务:① cursor 扫 captures byTestProject 删全部 · ② delete testProjects[id]
  - 错误处理:每个 API 用 try/catch · 失败 throw structured error `{ code: 'STORAGE_QUOTA_EXCEEDED'|'TEST_PROJECT_NOT_FOUND'|..., message, recoveryHints[] }`(ADR §11.5 风格 · 前端版)

Step 4 · 落 LEGACY 迁移(0.2d · ADR §6.1)
  - frontend_vue3/src/storage/legacy-migration.ts(新建):
    export const LEGACY_GRACE_DAYS = 7
    export const LEGACY_MIGRATION_FLAG_KEY = 'xitest:legacy-migrated-at'

    export interface LegacyMigrationReport {
      migratedKeys: string[]            // 旧 localStorage key 列表
      defaultTestProjectId: string      // 创建的默认 testProject id
      bannerMessage: string             // fork 7 banner 文案("已迁移 N 个旧 workspace 到默认测试项目")
    }

    export async function detectAndMigrateLegacy(engine: StorageEngineV2): Promise<LegacyMigrationReport | null> {
      // 1) 若 LEGACY_MIGRATION_FLAG_KEY 已 set 且 ≤ 7 天 → 跳过(返 null)
      // 2) 扫 localStorage · 找 'xitest:ws:*' key
      // 3) 若 0 个 → set flag · 返 null
      // 4) 若 ≥ 1 个 → 创建默认 testProject(id="default-migrated"+ts · name="(已迁移)旧 workspace")
      //    解析每个 ws:* value · 包装为 CaptureRecord(尽力解析 · 解析失败的 key 跳过)
      //    全部 saveCapture · saveTestProject · 不删原 localStorage(7 天后再清 · 留观察期)
      // 5) set flag · 返 LegacyMigrationReport
    }

    export async function cleanupLegacyAfterGracePeriod(): Promise<void> {
      // flag set ≥ 7 天 → 清旧 'xitest:ws:*' localStorage key
    }
  - 注:本 fork 仅落 detectAndMigrateLegacy + cleanupLegacyAfterGracePeriod 函数 · banner 显示 + 用户确认在 fork 7 实施

Step 5 · vitest 单测(0.3d)
  - frontend_vue3/src/storage/__tests__/storage-engine-v2.spec.ts(用 fake-indexeddb mock IDB):
    · IDB upgrades v1 to v2 · creates 2 new stores
    · saveCapture + loadCapture · roundtrip 完整字段保留
    · listCaptures(testProjectId) · 返该 testProject 全部 captures
    · listCaptures filter widgetType=fft · 仅返 fft kind
    · listCaptures filter captureGroupId · 验证多 widget 同 groupId 查询
    · updateCapture · 仅改 name/description/data/color · 不改 id/captureGroupId
    · deleteCapture · 删除后 loadCapture 返 null
    · saveTestProject + listTestProjects · 多个排序 byCreatedAt
    · deleteTestProject · 级联删 captures(用 listCaptures 验证返 0)
    · CaptureRecord description 字段 Ximind 字段保留(不丢)
  - frontend_vue3/src/storage/__tests__/legacy-migration.spec.ts:
    · 0 legacy key · 返 null + flag set
    · 3 legacy key · migratedKeys.length=3 · 创建 1 default testProject · captures 落盘
    · flag 存在且 ≤ 7 天 · 二次调用跳过(返 null)
    · 旧 localStorage value 解析失败 · skip 不抛异常
  - 期望 +20~25 用例

Step 6 · build + test + 浏览器手动验证(0.2d)
  - cd frontend_vue3
  - npm run typecheck(零错误)
  - npm run test:unit(基线 356/3 · 本 fork +20~25 = 376~381/3)
  - 浏览器手动:
    · npm run dev · 打开 xitest stage
    · DevTools → Application → IndexedDB → 验证 db 升级到 v2 · captures + testProjects store 存在
    · console:`storageEngine.saveCapture({...})` 手动调 · loadCapture 取回验证
    · F5 后再 listCaptures · 验证持久化
  - 不依赖后端启动(本 fork 0 后端依赖)

Step 7 · commit + push(0.1d)
  - git add frontend_vue3/src/storage/ frontend_vue3/src/types/capture.ts frontend_vue3/src/types/test-project.ts frontend_vue3/src/types/storage.ts
  - git commit(三元组 trailer 见下) · git push origin xistudio

【验收】

形式合规:
- [ ] npm run typecheck 零错误
- [ ] npm run test:unit 全绿(基线 356 → +20~25)
- [ ] 仅 7 文件修改(2 storage + 3 types + 2 测试)
- [ ] 不动 v1 行为(snapshots/workspaces store 旧 API 完全保留 · IDB v1 → v2 平滑升级)
- [ ] 不引入新 NPM 依赖(idb v8.0.3 已装 · fake-indexeddb dev dep 已装)
- [ ] 不动 ADR-12 §3 7 类 MeasurementNode 实施(只扩 § Storage 子能力对接)
- [ ] 不动后端 / contracts / dsp_algo 任何代码

业务行为契约(端到端真值 · 必跑 · ADR §3.4):
- [ ] saveCapture + loadCapture roundtrip 数据完整(含 description / metadata 全字段)
- [ ] listCaptures(testProjectId, {widgetType:'fft'}) 返仅 fft 类 record
- [ ] listCaptures(testProjectId, {captureGroupId:'g1'}) 返同一次 capture 的多 widget records(fork 7 一次抓 4 widget = 4 record · 必须能查到)
- [ ] updateCapture(id, {name, description, data}) modifiedAt 更新 · capturedAt 不变(recapture 语义)
- [ ] deleteTestProject 级联删 captures(listCaptures 返 0)
- [ ] F5 重新加载浏览器 · 数据持久化保留
- [ ] LEGACY 迁移 detect 'xitest:ws:*' key → 迁移到 default testProject · flag set · 二次调用 skip

Ximind 兼容性(ADR §11 · 必跑):
- [ ] CaptureRecord.description 字段保留(自然语言描述测量目的)
- [ ] TestProject.description 字段保留(自然语言描述项目)
- [ ] 字段名 camelCase 自描述(captureGroupId / capturedAt / modifiedAt 等 · 不缩写)
- [ ] 错误抛 structured(code + message + recoveryHints[])

【commit】
subject:`feat(P0.UA13-storage-engine-v2-captures): IDB v1→v2 + captures/testProjects + LEGACY migration · ADR-13 §2.7 §3.4 §6.1 §11`

trailer(必须精确 · 三元组):
[step=7/7] [pid=P0] [uid=UA13-storage-engine-v2-captures] [occupies=P0.K-storage-engine]
[files=frontend_vue3/src/storage/storage-engine.ts, frontend_vue3/src/types/capture.ts, frontend_vue3/src/types/test-project.ts, frontend_vue3/src/types/storage.ts, frontend_vue3/src/storage/legacy-migration.ts, frontend_vue3/src/storage/__tests__/storage-engine-v2.spec.ts, frontend_vue3/src/storage/__tests__/legacy-migration.spec.ts]
[isolation] file(同 worktree 同 branch · 与 ADR-13 fork 1/2/3/4/5 + ADR-09 4 active 文件路径完全正交)
[derived_from] ADR-AIOS-13 v1.0 §2.7 + §3.4 + §6.1 + §11 · parent zombie 5ea9806(SmartStorageEngine v1)
[truth-check] v1 IDB_VERSION=??? · v1 upgrade 签名=??? · v1 saveSnapshot 签名=???(Step 1 输出真值)
[acceptance] 8 新 API roundtrip 全过 · LEGACY 迁移 4 用例全过 · F5 持久化 OK · vitest +20~25 全绿
[ximind] 5 项检查清单全过(description 字段 · camelCase · structured error · 自描述字段名)

【禁止】(7 条红线)
1. ❌ 不修改 v1 IDB schema(snapshots/workspaces store 旧 API + 旧 keyPath/index 完全保留)
2. ❌ 不实施 capture 弹框 UI(fork 7 范围)
3. ❌ 不实施多曲线叠加渲染(fork 7 范围)
4. ❌ 不实施 LeftDock § Workspace § Test Project / § Captures 列表 UI(fork 7 范围)
5. ❌ 不写 banner 显示 / 用户确认(fork 7 范围 · 本 fork 仅落 detectAndMigrateLegacy 函数 + 返报告)
6. ❌ 不动后端 / contracts / dsp_algo 任何代码(本 fork 纯前端 · 0 后端依赖)
7. ❌ 不省略 Step 1 真值核查报告(commit body 必含 v1 现状)

解锁链(本任务 zombie 后)

  • ✅ Phase 3 fork 7 P0.UA13-capture-toolbar-multi-widget(直接调 storageEngine.saveCapture/listCaptures/updateCapture/deleteTestProject API)
  • ✅ Phase 3 fork 8 P_e2e.UA13-truth(议题 4 e2e:capture 4 widget 同 groupId + F5 恢复)
  • ✅ ADR-13 §3.4 议题 4 业务行为契约 5 子段全可验收

风险评估

风险 缓解
⚠️ IDB v1→v2 升级 · 用户已存数据破坏风险 upgrade(db, oldVersion) 严格 if(oldVersion<2) 块 · 只 createObjectStore 不动 v1 数据 · vitest 写 v1→v2 升级用例验证
⚠️ idb v8.0.3 API 与范式 5ea9806 不一致 Step 1 真值核查必跑 · 严格模仿 5ea9806 IDB 调用风格(open + tx + objectStore)
⚠️ LEGACY localStorage 值解析失败 解析失败的 key 跳过(不抛异常)· 记录到 LegacyMigrationReport.failedKeys[] · fork 7 banner 提示
⚠️ deleteTestProject 级联失败导致部分 captures 残留 单事务包住 · 失败回滚 · vitest 用例验证
⚠️ description 字段超长(用户写大段)致 IDB 写入慢 不限制长度(用户原话保留)· 若 ≥ 100KB 自动走 SmartStorageEngine 双层 IDB 分支(v1 已实施)
⚠️ 与 fork 7 schema 对齐风险(本 fork 写 schema · fork 7 用) 本 fork schema 完全对齐 ADR §2.7 文本 · fork 7 prompt 已引用同段 · 错位概率低

历史

时间 事件 hash
2026-06-01 19:30 dispatched · 用户首批 start 4 fork 中前端独立 fork 6 · ADR-13 v1.0 accepted 9 分钟后派发 · ClaudeA 1.5d 🧵 file 与 fork ½/¾/5 + ADR-09 4 active 文件正交 · 0 后端依赖可立即派 · parent zombie 5ea9806(SmartStorageEngine v1)