Ariver
2026-06-03 f66a79670c444010a59b8513c9f61257ebc7d9b6
Prepare 2.1.20 Round 2 QA package
8 files modified
3 files added
253 ■■■■ changed files
02-P-NBL/round-2/logs/go-test-all-20260603-2.1.20.log 22 ●●●●● patch | view | raw | blame | history
02-P-NBL/round-2/logs/npm-run-build-20260603-2.1.20.log 38 ●●●●● patch | view | raw | blame | history
02-P-NBL/round-2/qa-report-current.md 121 ●●●●● patch | view | raw | blame | history
02-P-NBL/round-2/todo-list.md 36 ●●●● patch | view | raw | blame | history
CHANGELOG.md 18 ●●●●● patch | view | raw | blame | history
privatevoice.src/app.go 4 ●●●● patch | view | raw | blame | history
privatevoice.src/build/config.yml 2 ●●● patch | view | raw | blame | history
privatevoice.src/build/darwin/Info.plist 4 ●●●● patch | view | raw | blame | history
privatevoice.src/build/windows/wails.exe.manifest 2 ●●● patch | view | raw | blame | history
privatevoice.src/frontend/package-lock.json 4 ●●●● patch | view | raw | blame | history
privatevoice.src/frontend/package.json 2 ●●● patch | view | raw | blame | history
02-P-NBL/round-2/logs/go-test-all-20260603-2.1.20.log
New file
@@ -0,0 +1,22 @@
?       voicesnap    [no test files]
?       voicesnap/internal/audio    [no test files]
ok      voicesnap/internal/config    1.315s
ok      voicesnap/internal/correctioncsv    3.369s
?       voicesnap/internal/dock    [no test files]
?       voicesnap/internal/engine    [no test files]
ok      voicesnap/internal/history    2.037s
?       voicesnap/internal/hotkey    [no test files]
?       voicesnap/internal/input    [no test files]
ok      voicesnap/internal/language    2.710s
?       voicesnap/internal/logger    [no test files]
ok      voicesnap/internal/model    4.887s
?       voicesnap/internal/overlay    [no test files]
?       voicesnap/internal/paths    [no test files]
?       voicesnap/internal/permissions    [no test files]
?       voicesnap/internal/singleinstance    [no test files]
?       voicesnap/internal/sound    [no test files]
?       voicesnap/internal/startup    [no test files]
?       voicesnap/internal/textproc    [no test files]
?       voicesnap/internal/updater    [no test files]
ok      voicesnap/internal/userdict    4.164s
?       voicesnap/services    [no test files]
02-P-NBL/round-2/logs/npm-run-build-20260603-2.1.20.log
New file
@@ -0,0 +1,38 @@
> privatevoice-input-frontend@2.1.20 build
> vite build
vite v6.4.1 building for production...
transforming...
11:46:45 AM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:28:0 Elements with the 'dialog' interactive role must have a tabindex value
https://svelte.dev/e/a11y_interactive_supports_focus
26:
27: <!-- svelte-ignore a11y_click_events_have_key_events -->
28: <div class="overlay" onclick={onclose} role="dialog">
          ^
29:   <!-- svelte-ignore a11y_click_events_have_key_events -->
30:   <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
11:46:45 AM [vite-plugin-svelte] src/components/update/UpdateDialog.svelte:30:2 Non-interactive element `<div>` should not be assigned mouse or keyboard event listeners
https://svelte.dev/e/a11y_no_noninteractive_element_interactions
28: <div class="overlay" onclick={onclose} role="dialog">
29:   <!-- svelte-ignore a11y_click_events_have_key_events -->
30:   <div class="dialog" onclick={(e) => e.stopPropagation()} role="document">
            ^
31:     <h2 class="title">{t('update.title')}</h2>
32:     <p class="version">{t('update.version', { version: info.version })}</p>
11:46:45 AM [vite-plugin-svelte] src/components/shared/ToggleSwitch.svelte:17:0 Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute
https://svelte.dev/e/a11y_consider_explicit_label
15: </script>
16:
17: <button
           ^
18:   class="toggle"
19:   class:active={checked}
✓ 163 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html                      0.41 kB │ gzip:  0.28 kB
dist/assets/app-icon-Bs-pS5hx.png  400.98 kB
dist/assets/index-p3GQqwqB.css      33.39 kB │ gzip:  5.73 kB
dist/assets/index-yZ25Q-mY.js      137.08 kB │ gzip: 45.83 kB
✓ built in 783ms
02-P-NBL/round-2/qa-report-current.md
New file
@@ -0,0 +1,121 @@
# Round 2 QA 报告:语言检测与语言版本设置
状态:功能性 QA PASS,可进入下一步交付门禁
日期:2026-06-03
测试人:Codex
## 1. 测试对象
| 项目 | 内容 |
|---|---|
| worktree | `/Users/ar/Projects/VoiceSnap-2.1.1-Round2` |
| branch | `codex/round2-language-settings` |
| 实现基线 commit | `516375d` |
| 最终测试包 commit | 以 `RELEASE_MANIFEST.md` 为准 |
| version | `2.1.20` |
| build | `20260603.1145` |
| 本地 App | `privatevoice.src/build/local/arm64/PrivateVoice Input.app` |
| 本地 DMG | 最终路径以 `RELEASE_MANIFEST.md` 为准 |
| DMG SHA256 | 最终 SHA256 以 `RELEASE_MANIFEST.md` 为准 |
## 2. 自动化与构建结果
| 项目 | 结果 | 证据 |
|---|---|---|
| `npm run build` | PASS | `logs/npm-run-build-20260603.log` |
| `go test ./... -count=1` | PASS | `logs/go-test-all-20260603.log` |
| 本地 App 构建与签名 | PASS | `logs/qa-runtime/build-local-round2-20260603.014931.log` |
| 本地 DMG verify | PASS | `logs/qa-runtime/build-local-round2-20260603.014931.log` |
说明:前端构建仍有既有 Svelte a11y warning,来源为旧组件 `UpdateDialog.svelte`、`ToggleSwitch.svelte`,本轮未新增阻断。
## 3. 真机测试结果
| ID | 场景 | 结果 | 证据 |
|---|---|---|---|
| R2-QA-001 | 老配置升级,无 `LanguageMode` / `LanguageID` | PASS | baseline config 无语言字段;Round 2 App 正常启动 |
| R2-QA-002 | 中文系统 auto | PASS | `config.json` 默认 `LanguageMode=auto`、`LanguageID=zh-CN` |
| R2-QA-003 | English 系统 auto | PASS by automated test | `go test ./...` 覆盖 detector normalize |
| R2-QA-004 | 其他系统语言 auto fallback | PASS by automated test | `go test ./...` 覆盖 fallback 到 English |
| R2-QA-005 | 手动中文 | PASS | `round2-screen-20260603.014931-zh-manual.png`;配置写入 `manual/zh-CN` |
| R2-QA-006 | 手动 English | PASS | `round2-screen-20260603.014931-english6.png`;配置写入 `manual/en` |
| R2-QA-007 | 切回自动 | PASS | `round2-screen-20260603.014931-chinese.png`;配置写入 `auto/zh-CN` |
| R2-QA-008 | 重启持久化 | PASS | 重启后配置仍为 `manual/zh-CN` |
| R2-QA-009 | 非法 LanguageMode fallback | PASS by automated test | `go test ./...` |
| R2-QA-010 | 非法 LanguageID fallback | PASS by automated test | `go test ./...` |
| R2-QA-011 | 语言切换不改模型 | PASS | `SelectedModelID` 始终为 `sensevoice-zh` |
| R2-QA-012 | 已有模型时语言切换不下载模型 | PASS | 真实用户模型 hash 不变;日志未出现因语言切换触发下载 |
| R2-QA-013 | 语言切换不删模型 | PASS | `model.int8.onnx`、`tokens.txt` hash 与 baseline 一致 |
| R2-QA-014 | 词库保留 | PASS | `userdict.json` hash 与 baseline 一致;replacement 数量 149 |
| R2-QA-015 | 历史保留 | PASS | `history.json` hash 与 baseline 一致;entries 数量 1708 |
| R2-QA-016 | 热键保留 | PASS | config 热键字段未丢失;断网真实语音输入已用 `R-Alt` 触发成功 |
| R2-QA-017 | 断网已有模型识别 | PASS | `logs/qa-runtime/offline-voice-final-20260603.014931.log`;断网期间识别并写入 history |
| R2-QA-018 | UI 不暴露新模型 | PASS | 设置页只显示现有 SenseVoice 引擎 |
| R2-QA-019 | English UI 文案 | PASS | `round2-screen-20260603.014931-english6.png` |
| R2-QA-020 | 中文 UI 文案 | PASS | `round2-screen-20260603.014931-zh-manual.png` |
## 4. 数据完整性
测试前已备份真实用户数据:
`/Users/ar/Projects/VoiceSnap-2.1.1-Round2/QA-backups/round2-pre-qa-20260603.014931`
语言切换阶段完成后,以下文件与 baseline SHA256 完全一致:
- `userdict.json`
- `history.json`
- `models/sensevoice/model.int8.onnx`
- `models/sensevoice/tokens.txt`
`config.json` 有预期变化:新增/写入 `LanguageMode`、`LanguageID`、`SelectedModelID`。
断网真实语音输入复测后,`history.json` 出现预期变化:新增一条识别记录。
## 5. 新用户测试
使用临时 HOME 启动本地 App,未挪动真实 App Support:
`logs/qa-runtime/new-user-home-20260603.014931`
结果:
- 新用户默认配置为 `LanguageMode=auto`、`LanguageID=zh-CN`、`SelectedModelID=sensevoice-zh`。
- 干净用户首次启动会自动下载 SenseVoice;下载发生在临时 QA 目录,不影响真实用户模型目录。
- 下载完成后 ASR engine ready。
## 6. 断网启动检查
证据:`logs/qa-runtime/offline-start-check-20260603.014931.log`
结果:
- Wi-Fi 从 On 切到 Off。
- `curl https://www.apple.com` 返回 `Could not resolve host`,确认断网。
- 断网状态下重启本地 App。
- App 使用已有真实 SenseVoice 模型启动,日志显示 `ASR engine ready: SenseVoice · CoreML (Apple Neural Engine)`。
- Wi-Fi 已恢复为 On。
## 7. 断网真实语音输入复测
证据:`logs/qa-runtime/offline-voice-final-20260603.014931.log`
结果:
- Wi-Fi 从 On 切到 Off。
- 断网探测返回 `Could not resolve host: www.apple.com`。
- 用户在断网窗口内按住 `R-Alt` 说测试句。
- 日志显示 2026-06-03 11:40:36 开始录音,2026-06-03 11:40:41 停止录音并完成识别。
- 识别文本:`这是 mac 电脑用 SenseVoice 做离线识别。`
- `history.json` 条目数从 1710 增加到 1711。
- `history.json` 最新条目 `entries[0]` 为:`这是 mac 电脑用 SenseVoice 做离线识别。`
- Wi-Fi 已恢复为 On,并通过联网探测。
## 8. 阻断项
无。
## 9. 当前结论
Round 2 的语言设置、i18n 切换、持久化、新用户默认值、老用户数据保护、模型不破坏、断网启动均通过。
Round 2 功能性 QA PASS。
02-P-NBL/round-2/todo-list.md
@@ -1,6 +1,6 @@
# Round 2 To Do List
状态:待启动
状态:功能性 QA PASS;等待交付门禁/测试包流程
所属子项目:`02-P-NBL`
Round:Round 2
日期:2026-06-03
@@ -30,24 +30,24 @@
## 3. QA 准备
- [ ] 准备老用户升级测试数据。
- [ ] 准备新用户干净安装测试数据。
- [ ] 准备 detector mock 或测试开关。
- [ ] 准备非法 config 测试样本。
- [ ] 准备断网识别回归步骤。
- [x] 准备老用户升级测试数据。
- [x] 准备新用户干净安装测试数据。
- [x] 准备 detector mock 或测试开关。
- [x] 准备非法 config 测试样本。
- [x] 准备断网识别回归步骤。
- [x] 准备 QA 预检记录。
- [ ] 准备 QA 报告模板。
- [x] 准备 QA 报告模板。
## 4. 验收
- [ ] 中文系统 auto 解析为中文。
- [ ] English 系统 auto 解析为 English。
- [ ] 其他系统语言 auto 解析为 English。
- [ ] 手动中文持久化。
- [ ] 手动 English 持久化。
- [ ] 语言切换不触发模型下载。
- [ ] 语言切换不破坏 SenseVoice 模型。
- [ ] 语言切换不破坏词库。
- [ ] 语言切换不破坏历史。
- [ ] 断网识别回归通过。
- [ ] QA 报告确认功能性 PASS。
- [x] 中文系统 auto 解析为中文。
- [x] English 系统 auto 解析为 English。
- [x] 其他系统语言 auto 解析为 English。
- [x] 手动中文持久化。
- [x] 手动 English 持久化。
- [x] 语言切换不触发模型下载。
- [x] 语言切换不破坏 SenseVoice 模型。
- [x] 语言切换不破坏词库。
- [x] 语言切换不破坏历史。
- [x] 断网识别回归通过。
- [x] QA 报告确认功能性 PASS。
CHANGELOG.md
@@ -1,5 +1,23 @@
# Changelog
## v2.1.20 (2026-06-03)
### QA 候选包
- **Round 2 语言版本设置**:设置页新增“语言版本”,支持自动、中文、English 三种模式。
- **系统语言解析**:新增语言 profile 和 resolver,中文系统默认中文,English 系统默认 English,其他语言默认 English。
- **前端 i18n 切换**:语言切换后设置页、侧边栏、关于页等关键文案立即切换。
- **老用户兼容**:旧 `config.json` 缺少 `LanguageMode` / `LanguageID` 时可正常启动,并自动 fallback。
- **模型隔离**:本轮语言切换不引入 Moonshine / Parakeet / Qwen3-ASR,不改写或删除 SenseVoice 模型。
- **QA 资料**:新增 `02-P-NBL/round-2` 下 PRD、研发拆解、QA 计划、QA 报告和验收记录。
### 构建
- build: `20260603.1145`
- 说明: 本版本为 Round 2 QA 通过后的测试包候选,已完成老用户升级、新用户临时 HOME、语言切换、重启持久化、断网启动和断网真实语音输入复测。
---
## v2.1.19 (2026-06-02)
### QA 候选包
privatevoice.src/app.go
@@ -27,8 +27,8 @@
)
const (
    appVersion        = "2.1.19"
    appBuild          = "20260602.2022"
    appVersion        = "2.1.20"
    appBuild          = "20260603.1145"
    appDisplayVersion = appVersion + " (build " + appBuild + ")"
    appName           = "PrivateVoice Input"
privatevoice.src/build/config.yml
@@ -7,7 +7,7 @@
  description: "Private offline voice input tool"
  copyright: "(c) 2026, PrivateVoice"
  comments: "Private.Offline"
  version: "2.1.19"
  version: "2.1.20"
dev_mode:
  root_path: .
privatevoice.src/build/darwin/Info.plist
@@ -15,9 +15,9 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>2.1.19</string>
    <string>2.1.20</string>
    <key>CFBundleVersion</key>
    <string>20260602.2022</string>
    <string>20260603.1145</string>
    <key>LSMinimumSystemVersion</key>
    <string>11.0</string>
    <key>NSMicrophoneUsageDescription</key>
privatevoice.src/build/windows/wails.exe.manifest
@@ -3,7 +3,7 @@
  <assemblyIdentity
    type="win32"
    name="PrivateVoice.Input"
    version="2.1.19.0"
    version="2.1.20.0"
    processorArchitecture="*"/>
  <dependency>
    <dependentAssembly>
privatevoice.src/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
  "name": "privatevoice-input-frontend",
  "version": "2.1.19",
  "version": "2.1.20",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "privatevoice-input-frontend",
      "version": "2.1.19",
      "version": "2.1.20",
      "dependencies": {
        "@wailsio/runtime": "latest"
      },
privatevoice.src/frontend/package.json
@@ -1,7 +1,7 @@
{
  "name": "privatevoice-input-frontend",
  "private": true,
  "version": "2.1.19",
  "version": "2.1.20",
  "type": "module",
  "scripts": {
    "dev": "vite dev",