From 5466b385d10fd49c2875bf8c398a454c2d08df67 Mon Sep 17 00:00:00 2001 From: remy Date: Sun, 8 Nov 2026 07:34:25 +1000 Subject: [PATCH] feat(workbench): unify device entry, pull large recordings over Wi-Fi - Single device entry and navigation; overview page folded into the new layout - Device service with transport selection (Wi-Fi first, BLE fallback), network gate that pauses network-bound jobs while on the recorder hotspot - Layered error envelope; details shown on demand - Settings split into sub-pages; diagnostics route and runtime probe - README: Floatboat community group section - Unit tests for device service/transport/sync/errors/network gate/router and new verify-openminutes e2e step files Real-device Wi-Fi acceptance is still pending (switches system network). Co-Authored-By: Claude Opus 5.5 --- .claude/skills/verify-openminutes/SKILL.md | 17 +- .../verify-openminutes/features/README.md | 13 +- .../features/batch-success.steps.json | 20 +- .../features/calendar-link.md | 26 + .../features/calendar-link.steps.json | 28 + .../features/device-connection.md | 40 + .../features/device-connection.steps.json | 33 + .../features/device-fallback.steps.json | 18 + .../features/diagnostics.md | 24 + .../features/diagnostics.steps.json | 25 + .../features/directory-import.md | 32 +- .../features/error-detail.md | 26 + .../features/error-detail.steps.json | 28 + .../features/import-dialog.steps.json | 22 + .../features/import-failed-detail.steps.json | 16 + .../features/library-batch-all.steps.json | 25 + .../features/library-search.md | 31 +- .../features/live-busy.steps.json | 13 + .../features/live-connecting.steps.json | 11 + .../verify-openminutes/features/navigation.md | 27 + .../features/navigation.steps.json | 38 + .../recording-detail-actions.steps.json | 46 ++ .../features/recording-detail.md | 23 +- .../features/transcribe-organize.md | 14 +- .../features/transcribe-success.md | 12 +- .../features/transcribe-success.steps.json | 14 +- .../features/voice-import.steps.json | 17 +- .../recipes/host-batch.json | 23 +- .../recipes/host-correct.json | 2 +- .../verify-openminutes/recipes/host-off.json | 19 +- .../recipes/host-organize.json | 6 +- .../recipes/host-single.json | 15 +- .../reports/2026-11-08-workbench-unify.md | 37 + .../scripts/capability-gate.test.mjs | 8 +- .../verify-openminutes/scripts/doctor.mjs | 6 + .../verify-openminutes/scripts/launch.mjs | 41 +- AGENTS.md | 22 + DESIGN.md | 66 +- ENGINEERING.md | 135 +++- README.md | 169 ++-- README.zh-CN.md | 171 ++-- REQUIREMENTS.md | 16 +- docs/images/floatboat-wechat-group.png | Bin 0 -> 159534 bytes migrations/0007_device_service.sql | 6 + scripts/verify-calendar.mjs | 34 +- src/contracts/app.ts | 78 +- src/contracts/device.ts | 149 ++++ src/contracts/errors.ts | 73 ++ src/contracts/preflight.ts | 7 + src/server/auto-connect.ts | 72 -- src/server/bridge.ts | 223 ----- src/server/calendar-digest.ts | 13 +- src/server/device/auto-connect.ts | 63 ++ src/server/device/errors.ts | 58 ++ src/server/device/fake-device.ts | 164 ++++ src/server/device/service.ts | 731 +++++++++++++++++ src/server/device/sync.ts | 434 ++++++++++ src/server/device/transport.ts | 150 ++++ src/server/device/worker-script.ts | 765 ++++++++++++++++++ src/server/device/worker.ts | 217 +++++ src/server/environment-repair.ts | 16 +- src/server/http.ts | 56 +- src/server/index.ts | 27 +- src/server/ingest.ts | 16 +- src/server/live-asr.ts | 41 +- src/server/live-script.ts | 4 +- src/server/live.ts | 111 ++- src/server/network-gate.ts | 97 +++ src/server/pipeline.ts | 99 ++- src/server/preflight.ts | 110 ++- src/server/pull.ts | 122 --- src/server/queue.ts | 26 +- src/server/repo.ts | 37 +- src/server/routes/calendar.ts | 12 +- src/server/routes/device.ts | 249 ++---- src/server/routes/diagnostics.ts | 43 + src/server/routes/environment.ts | 9 +- src/server/routes/ingest.ts | 46 +- src/server/routes/live.ts | 5 +- src/server/routes/pipeline.ts | 24 +- src/server/routes/recordings.ts | 10 +- src/server/routes/settings.ts | 11 +- src/server/runtime-probe.ts | 23 + src/server/sdk-script.ts | 57 -- src/server/transcribe.ts | 5 +- src/ui/App.tsx | 121 +-- src/ui/api.ts | 43 +- src/ui/calendar.css | 4 - src/ui/components/CalendarAssociation.tsx | 32 - src/ui/components/DeviceCard.tsx | 263 ++++++ src/ui/components/DeviceHero.tsx | 151 ---- src/ui/components/DeviceRecordingList.tsx | 60 ++ src/ui/components/EnvironmentBanner.tsx | 16 + src/ui/components/EnvironmentPanel.tsx | 146 +++- src/ui/components/ErrorNotice.tsx | 70 ++ src/ui/components/EventLinker.tsx | 104 +++ src/ui/components/ImportDialog.tsx | 124 +++ src/ui/components/PendingPullList.tsx | 49 -- src/ui/components/SdkCommandTable.tsx | 11 +- src/ui/components/recording-parts.tsx | 12 +- src/ui/components/ui.tsx | 73 +- src/ui/format.ts | 15 + src/ui/pages/Calendar.tsx | 187 +++-- src/ui/pages/Categories.tsx | 28 +- src/ui/pages/Detail.tsx | 230 ++++-- src/ui/pages/Device.tsx | 539 +++--------- src/ui/pages/Library.tsx | 195 +++-- src/ui/pages/Live.tsx | 73 +- src/ui/pages/Overview.tsx | 200 ----- src/ui/pages/Settings.tsx | 138 ++-- src/ui/pages/settings/DiagnosticsSection.tsx | 220 +++++ src/ui/pages/settings/ManifestTool.tsx | 79 ++ src/ui/router.ts | 86 ++ src/ui/style.css | 292 ++++--- src/ui/useApi.ts | 9 +- src/ui/useDevice.ts | 117 +++ tests/calendar-routes.test.ts | 14 +- tests/calendar.test.ts | 2 +- tests/device-errors.test.ts | 57 ++ tests/device-routes.test.ts | 146 ++++ tests/device-service.test.ts | 615 ++++++++++++++ tests/device-sync.test.ts | 282 +++++++ tests/device-transport.test.ts | 159 ++++ tests/e2e-preflight.mjs | 23 +- tests/environment-repair.test.ts | 2 +- tests/http-envelope.test.ts | 73 ++ tests/mobile-preflight.mjs | 9 +- tests/network-gate.test.ts | 112 +++ tests/pipeline-errors.test.ts | 41 + tests/pipeline-routes.test.ts | 20 +- tests/preflight.test.ts | 14 +- tests/router.test.ts | 47 ++ tests/sdk.test.ts | 509 ++++++++++-- tests/support/device-fixture.ts | 263 ++++++ 134 files changed, 9142 insertions(+), 2801 deletions(-) create mode 100644 .claude/skills/verify-openminutes/features/calendar-link.md create mode 100644 .claude/skills/verify-openminutes/features/calendar-link.steps.json create mode 100644 .claude/skills/verify-openminutes/features/device-connection.md create mode 100644 .claude/skills/verify-openminutes/features/device-connection.steps.json create mode 100644 .claude/skills/verify-openminutes/features/device-fallback.steps.json create mode 100644 .claude/skills/verify-openminutes/features/diagnostics.md create mode 100644 .claude/skills/verify-openminutes/features/diagnostics.steps.json create mode 100644 .claude/skills/verify-openminutes/features/error-detail.md create mode 100644 .claude/skills/verify-openminutes/features/error-detail.steps.json create mode 100644 .claude/skills/verify-openminutes/features/import-dialog.steps.json create mode 100644 .claude/skills/verify-openminutes/features/import-failed-detail.steps.json create mode 100644 .claude/skills/verify-openminutes/features/library-batch-all.steps.json create mode 100644 .claude/skills/verify-openminutes/features/live-busy.steps.json create mode 100644 .claude/skills/verify-openminutes/features/live-connecting.steps.json create mode 100644 .claude/skills/verify-openminutes/features/navigation.md create mode 100644 .claude/skills/verify-openminutes/features/navigation.steps.json create mode 100644 .claude/skills/verify-openminutes/features/recording-detail-actions.steps.json create mode 100644 .claude/skills/verify-openminutes/reports/2026-11-08-workbench-unify.md create mode 100644 docs/images/floatboat-wechat-group.png create mode 100644 migrations/0007_device_service.sql create mode 100644 src/contracts/device.ts create mode 100644 src/contracts/errors.ts delete mode 100644 src/server/auto-connect.ts delete mode 100644 src/server/bridge.ts create mode 100644 src/server/device/auto-connect.ts create mode 100644 src/server/device/errors.ts create mode 100644 src/server/device/fake-device.ts create mode 100644 src/server/device/service.ts create mode 100644 src/server/device/sync.ts create mode 100644 src/server/device/transport.ts create mode 100644 src/server/device/worker-script.ts create mode 100644 src/server/device/worker.ts create mode 100644 src/server/network-gate.ts delete mode 100644 src/server/pull.ts create mode 100644 src/server/routes/diagnostics.ts create mode 100644 src/server/runtime-probe.ts delete mode 100644 src/server/sdk-script.ts delete mode 100644 src/ui/components/CalendarAssociation.tsx create mode 100644 src/ui/components/DeviceCard.tsx delete mode 100644 src/ui/components/DeviceHero.tsx create mode 100644 src/ui/components/DeviceRecordingList.tsx create mode 100644 src/ui/components/EnvironmentBanner.tsx create mode 100644 src/ui/components/ErrorNotice.tsx create mode 100644 src/ui/components/EventLinker.tsx create mode 100644 src/ui/components/ImportDialog.tsx delete mode 100644 src/ui/components/PendingPullList.tsx delete mode 100644 src/ui/pages/Overview.tsx create mode 100644 src/ui/pages/settings/DiagnosticsSection.tsx create mode 100644 src/ui/pages/settings/ManifestTool.tsx create mode 100644 src/ui/router.ts create mode 100644 src/ui/useDevice.ts create mode 100644 tests/device-errors.test.ts create mode 100644 tests/device-routes.test.ts create mode 100644 tests/device-service.test.ts create mode 100644 tests/device-sync.test.ts create mode 100644 tests/device-transport.test.ts create mode 100644 tests/http-envelope.test.ts create mode 100644 tests/network-gate.test.ts create mode 100644 tests/pipeline-errors.test.ts create mode 100644 tests/router.test.ts create mode 100644 tests/support/device-fixture.ts diff --git a/.claude/skills/verify-openminutes/SKILL.md b/.claude/skills/verify-openminutes/SKILL.md index 91151e8..3f420e6 100644 --- a/.claude/skills/verify-openminutes/SKILL.md +++ b/.claude/skills/verify-openminutes/SKILL.md @@ -14,7 +14,7 @@ description: 在本机隔离启动 OpenMinutes,用无头 Chrome/Edge 验证导 - **默认复用桌面 Floatboat 的 Host,不另起 Host。** launch 检查 Floatboat 进程、本 appId 最新连接配置、监听端口的进程归属,以及只读 `capabilities.list`。任一步失败便停止,不自动降级;显式 `--no-host` 或 `VERIFY_HOST_MODE=none` 才禁用 Host。 - **Host 地址属于本机敏感信息。** 只从 `%APPDATA%/floatboat/workbench/connections/*.json` 读取连接配置,只复制 `hostConnection.url` 到本 run 的 `workbench-config.json`。不复制真实数据目录、端口或其它启动字段;不打印地址、不把配置放入证据、不提交它。cleanup 删除临时配置。 - **真实 ASR/LLM 消耗额度。** 使用 `speech.mjs` 的短语音,每条只点一次转录/归类;失败后先读证据,不盲目重跑配方。`evidence.mjs` 从隔离库统计 `asr.start` 和 `llm.*`,包含自动重试/回退。能力探测不发起计费调用,也不能证明上游模型一定可用。实时字幕/BLE 不在本配方覆盖范围。 -- **BLE 设备不要碰。** 设备页的「连接并检测」「读取清单」「下载」会通过本机 Python + bleak 真正连无线电。launch 已关闭 `auto_connect` / `auto_pull`,驱动时也不要点这几个按钮。 +- **真实录音笔不要碰。** 录音笔页的「连接」「同步录音」「断开连接」「清理已同步的录音」以及列表里的「同步」会通过本机 Python + bleak 真正连无线电;同步大录音还会**真实切换本机 Wi-Fi**(连上录音笔热点、断开外网),清理会删除录音笔上的文件。**禁止对真实录音笔执行同步**,e2e 只用 `--fake-device`。launch 已关闭 `auto_connect` / `auto_pull`;没有 `--fake-device` 时驱动也不要点这些按钮。真机验收交给用户按清单执行。 - **不要驱动不是本次 run 启动的实例。** 用户的 Floatboat 里可能开着同一个应用(数据在 `%APPDATA%\floatboat\workbench\data\workbench.3bb3830b-…`),那是真实数据,绝不读写、绝不 kill。 - 多个 run 可以并行:端口随机、数据目录 / 收件目录 / 归档目录 / 浏览器 profile 都按 runId 隔离。 @@ -24,8 +24,11 @@ description: 在本机隔离启动 OpenMinutes,用无头 Chrome/Edge 验证导 node .claude/skills/verify-openminutes/scripts/build.mjs # ~10s;严格类型检查 + 打包到 dist/,失败时打印诊断并 exit 1 node .claude/skills/verify-openminutes/scripts/launch.mjs # runId 省略则生成 UUID node .claude/skills/verify-openminutes/scripts/launch.mjs --no-host # 显式验证无 Host 失败路径 +MEMOTALK_TEST_PYTHON=<装了 memotalk-sdk 的解释器> node .claude/skills/verify-openminutes/scripts/launch.mjs --no-host --fake-device=wifi # 假录音笔:wifi | ble | absent ``` +- `--fake-device=` 设置 `OPENMINUTES_FAKE_DEVICE`,把 `python_path` 写成 `MEMOTALK_TEST_PYTHON`(缺少时报错退出),并用 ffmpeg 在 `$R/fake-device/` 生成 3 段真实 mp3(约 2.1 MiB、1.2 MiB、160 KiB)作为录音笔里的文件。`wifi` 走无线快传(内存 FTP),`ble` 模拟入网失败以验证「已改用蓝牙」,`absent` 模拟找不到录音笔。假设备 prelude 会让任何 `netsh` / `nmcli` / `networksetup` 调用直接抛错,保证不改动本机网络。模式写进 `run.json`,doctor 会输出当前模式。 + - 要验收另一个 worktree 或分支的代码时,给每条命令都加上 `OPENMINUTES_ROOT=<那个 checkout 的绝对路径>`。build、run 目录和证据都会落在那个 checkout 里,不需要往它里面复制 skill。 - 每次使用全新 runId;即使 cleanup 已执行,旧证据目录仍然保留,launch 会拒绝复用该 id,防止读取旧日志的 ready 地址或混合不同运行的证据。 - `build.mjs` 调用 `C:/Program Files/Floatboat/resources/workbench-toolchain/compiler.mjs`(可用 `FLOATBOAT_TOOLCHAIN` 覆盖),产物与 Host 构建一致。改了 `src/` 之后必须重新 build 再 launch,否则验证的是旧代码(doctor 会报 `dist/ is older than src/`)。 @@ -42,7 +45,7 @@ node .claude/skills/verify-openminutes/scripts/launch.mjs --no-host # node .claude/skills/verify-openminutes/scripts/doctor.mjs ``` -只读检查,exit 0 才能继续驱动。它用 run 的 nonce 请求 `/__workbench/health`,确认端口上的进程就是本 run 启动的(pid、generation 一致);确认隔离设置仍然生效(导入/归档目录指向本 run,四个 auto 开关为 `0`);确认 `dist/` 不比 `src/` 旧。任何异常(页面空白、接口 404、数据对不上)都先跑它。如果报告设置被改,说明刚才的操作(例如设备页「保存路径」)覆盖了隔离,这个 run 应该 cleanup 后重来。 +只读检查,exit 0 才能继续驱动。它用 run 的 nonce 请求 `/__workbench/health`,确认端口上的进程就是本 run 启动的(pid、generation 一致);确认隔离设置仍然生效(导入/归档目录指向本 run,四个 auto 开关为 `0`);确认 `dist/` 不比 `src/` 旧。任何异常(页面空白、接口 404、数据对不上)都先跑它。如果报告设置被改,说明刚才的操作(例如在「设置 › 通用」里保存了目录)覆盖了隔离,这个 run 应该 cleanup 后重来。 Host 模式另检查 Floatboat 监听归属,并发送一次 `capabilities.list`。输出 `host: {mode, available, probe}`,不输出连接地址或能力响应原文;显式无 Host 模式显示 `mode: "none", available: false`,不调用 Host。Floatboat 重启后旧地址可能失效,此时 cleanup 后重新 launch。 @@ -55,15 +58,17 @@ node .claude/skills/verify-openminutes/scripts/browse.mjs