| | |
| | | ## Project |
| | | |
| | | - Product: Wails v3 / Go desktop dictation app. |
| | | - Current branch purpose: Windows technical preview based on `v2.1.38-build20260629.2014-diagnostic`. |
| | | - Protected macOS behavior: existing macOS App Store / local DMG build lines are not changed by this branch. |
| | | - Git root: `/Users/ar/Projects/PrivateVoice/03-O`. |
| | | - Source root: `/Users/ar/Projects/PrivateVoice/03-O/C1.source/privatevoice.src`. |
| | | - Current branch purpose: R221 recovery freeze for `2.2.2 build20260703.0335`, restoring reviewed `2.2.1` fixes into the current active source and producing four real-user test candidate packages. |
| | | - Protected behavior: existing macOS App Store, local DMG, Monterey compatibility, Windows preview, model download safety, and paste/clipboard protection must not be changed without a scoped task and QA plan. |
| | | |
| | | ## Automatic Codebase Memory |
| | | |
| | | - Status: `enabled`. |
| | | - Tool: `codebase-memory-mcp` 0.8.1. |
| | | - Current project name after path unification: `Users-ar-Projects-PrivateVoice-03-O`. |
| | | - Current artifact note: `.codebase-memory/artifact.json` must record project `Users-ar-Projects-PrivateVoice-03-O` before each freeze / release package. |
| | | - Shared artifact: `.codebase-memory/graph.db.zst`; metadata: `.codebase-memory/artifact.json`. |
| | | - Scope: `/Users/ar/Projects/PrivateVoice/03-O`. |
| | | - Refresh command: `codebase-memory-mcp cli index_repository '{"repo_path":"/Users/ar/Projects/PrivateVoice/03-O"}'`. |
| | | - Current `indexed_at`, commit, node count, and edge count are recorded in `.codebase-memory/artifact.json`. |
| | | - Excluded by indexer: `.git`, `K3.运营与发布资料/legacy-运营资料/PrivateVoice-26.0604上架`, `K3.运营与发布资料/legacy-运营资料/运营文档`, `C1.source/privatevoice-2.2.1-recovered.src`, `C1.source/privatevoice.src/frontend/dist`, `C1.source/privatevoice.src/frontend/node_modules`. |
| | | - Query example after regenerating graph from Git root: `codebase-memory-mcp cli search_graph '{"project":"Users-ar-Projects-PrivateVoice-03-O","query":"model registry","limit":5}'`. |
| | | - This automatic graph is an exploration aid only; it does not replace source reading, tests, QA evidence, or this `CODEGRAPH.md`. |
| | | - Refresh rule: regenerate `.codebase-memory/` before each source freeze, version freeze, formal rollback-capable test package, or release package; daily Coder work-in-progress changes do not force a refresh. |
| | | - Freeze record requirement: record the refresh command, `.codebase-memory/artifact.json` path, indexed commit, freeze commit, and whether the two commits match or differ only by `.codebase-memory/` artifact updates. |
| | | |
| | | ## Core Entrypoints |
| | | |
| | | - `privatevoice.src/main.go`: process startup, data migration, logger, single-instance guard. |
| | | - `privatevoice.src/app.go`: Wails app wiring, settings window, tray, recorder, hotkey loop, ASR init, paste orchestration. |
| | | - `privatevoice.src/embed.go`: embeds frontend assets and app/tray icons. |
| | | - `C1.source/privatevoice.src/main.go`: process startup, data migration, logger, single-instance guard. Unix single-instance locking starts from the existing temp lock path, but falls back to the app support directory when sandbox runtime denies temp lock access; a genuinely held temp lock remains authoritative and must not be bypassed. |
| | | - `C1.source/privatevoice.src/app.go`: Wails app wiring, settings window, tray, recorder, hotkey loop, ASR init, paste orchestration. |
| | | - `C1.source/privatevoice.src/embed.go`: embeds frontend assets and app/tray icons. |
| | | - `C1.source/privatevoice.src/frontend/package.json` / `package-lock.json`: frontend dependency graph. `@wailsio/runtime` is pinned to `3.0.0-alpha.79`; App Store / release candidate preparation must not use `latest` or a lockfile / `node_modules` version drift. |
| | | |
| | | ## Language And Model Registry |
| | | |
| | | - `C1.source/privatevoice.src/internal/model/profile.go`: model and language IDs, model profile shape, install status types. |
| | | - `C1.source/privatevoice.src/internal/model/registry.go`: language profile order, model profiles, language profiles, and normalization. Runtime model download URLs are part of the App Store / privacy evidence chain and must remain HTTPS-only; `internal/model/model_test.go` guards this invariant. |
| | | - `C1.source/privatevoice.src/internal/modelselection/selection.go`: resolves current language and model, including manual-model fallback. |
| | | - Round 6 Cantonese path: |
| | | - Language ID: `yue-HK`, displayed as `粤语`. |
| | | - Default model: `sensevoice-yue-2025-09-09`. |
| | | - Backend: SenseVoice with `LanguageParam="yue"`. |
| | | - The Cantonese language profile intentionally has no upgrade models, so Qwen3-ASR and X-ASR are not shown as Cantonese-specific options. |
| | | |
| | | ## Platform Modules |
| | | |
| | | - Audio: `privatevoice.src/internal/audio/recorder.go` |
| | | - Uses `github.com/gen2brain/malgo`. |
| | | - Cross-platform capture target: 16 kHz, 16-bit, mono. |
| | | - Audio: `C1.source/privatevoice.src/internal/audio/recorder.go`; recorder state is prepared under `Recorder.mu`, but `malgo.Device.Start()` runs after releasing that mutex so miniaudio capture callbacks can enter `onData()` and fill PCM during device startup. On macOS, `C1.source/privatevoice.src/internal/audio/microphone_darwin.go` preflights / requests microphone authorization before native device initialization; denied / restricted microphone state returns `audio.ErrMicrophonePermission` and does not enter `initDevice` / native `Start()`. `Start()` registers a cancellable start intent before microphone authorization, so `Stop()` / `StopAndGetSamples()` during a pending TCC request is preserved and prevents late-granted authorization from continuing into native init/start. A separate recorder lifecycle lock keeps `Close()` from releasing the native context while `Start()` is using it. If `Stop()` / `StopAndGetSamples()` is called while native `Start()` is still in flight, or after `Start()` has entered but before a device is published, it snapshots buffered PCM immediately, records the stop intent, and cleans up the initialized / started device when the startup path returns. Native device startup logs `Audio device initialized; starting native device`, emits a 2s pending diagnostic if `ma_device_start()` does not return, and logs `Native audio device start returned` before `Recording started`. |
| | | - Hotkey: |
| | | - macOS: `privatevoice.src/internal/hotkey/hotkey_darwin.go` |
| | | - Windows: `privatevoice.src/internal/hotkey/hotkey_windows.go` |
| | | - macOS: `C1.source/privatevoice.src/internal/hotkey/hotkey_darwin.go` |
| | | - Windows: `C1.source/privatevoice.src/internal/hotkey/hotkey_windows.go` |
| | | - Shared display: `C1.source/privatevoice.src/internal/hotkey/hotkey.go`; R222 maps `0x5C` to macOS `R-⌘` and Windows `R-Win`. |
| | | - macOS side-modifier diagnostics now cover both Option and Command keys. `R-⌘` / `L-⌘` polling logs include HID physical state, CombinedSession physical state, event-tap tracked state, recent fallback state, aggregate modifier flag state, event flags, tap startup / disabled counters and the selected source. Side-specific hotkeys only accept reliable side-specific sources (`physicalHID`, `physicalCombined`, `eventTap`, `recent`). `aggregateCommandFlag` is diagnostic-only because macOS aggregate Command flags cannot distinguish left vs right Command. |
| | | - Hold mode state machine lives in `C1.source/privatevoice.src/app.go`; hold pre-capture marks pending state and schedules a cancellable activation timer on initial key-down, while recorder startup runs outside the app mutex so slow audio device startup cannot delay `holdActivationDelay` confirmation. Physical modifier keys that emit no repeated key-down event still enter recording; release-before-confirm and combination-key detection cancel pending pre-capture. Normal release waits for startup-period PCM before `StopAndGetSamples` -> ASR -> text output when native recorder startup is still in flight; if PCM never arrives before the bounded wait, it stops without ASR and reports an audio-not-ready failure instead of a false `no_voice`. Escape / combination cancellation while startup is blocked remains cancel-only and must not enter ASR. |
| | | - Config defaults: |
| | | - `C1.source/privatevoice.src/internal/config/config.go`: first generated config uses `hold`; default hotkey is build-scoped. Non-App-Store builds keep the R222 platform default (`0x5C` on macOS / Windows), Windows keeps `R-Win`, and `darwin && appstore` first generated config uses `HotkeyVK=0` to represent "trigger key not set". Existing config files load through a legacy-safe fallback so missing `HotkeyVK` does not inherit the R222 new default. |
| | | - `C1.source/privatevoice.src/internal/config/default_hotkey_appstore_darwin.go` and `default_hotkey_default.go`: build-tag seam for the AX / MAS unset-default policy. |
| | | - Text input: |
| | | - macOS: `privatevoice.src/internal/input/paste_darwin.go` |
| | | - Windows: `privatevoice.src/internal/input/paste_windows.go` |
| | | - macOS: `C1.source/privatevoice.src/internal/input/paste_darwin.go` |
| | | - Windows: `C1.source/privatevoice.src/internal/input/paste_windows.go` |
| | | - AX mainline integration: |
| | | - `C1.source/privatevoice.src/internal/textoutput/`: output router after ASR post-processing and user dictionary application. |
| | | - Default / Developer ID / Windows builds use the legacy `internal/input.Paster` path through `router_default.go`, preserving paste then type fallback behavior. |
| | | - `darwin && appstore` builds use `internal/axinput` for Accessibility focused-element direct insert and treat `AXValue` replacement plus write-after-readback verification as the only success condition. If the system-wide focused element is unavailable, the driver retries through the system focused application and the frontmost application accessibility element before reporting `target=unknown`. Direct-insert failure diagnostics are logged only as generalized categories and AX numeric error codes, never transcript, target, method, message, window title, or document title. |
| | | - AX failures keep the recognized text in `textoutput.Result` and expose clipboard fallback through `services.TextOutputService`; fallback does not auto-paste by default. When `CopyToClipboard=true` and the failed AX target is safe for fallback, the appstore route writes the recognized text to the pasteboard for manual paste while still reporting direct output failure; known Terminal, iTerm, Warp, shell, console, or terminal-role targets remain blocked so fallback operations cannot modify known non-writable command-line windows. `target=unknown` can still expose explicit clipboard fallback because it only writes the pasteboard and does not modify the focused app. AX-10DT adds a default-off `AutoPasteExperiment` flag for `darwin && appstore`: when explicitly enabled, the app captures a frontmost app identity at recognition pipeline start, then the router re-reads the current frontmost identity before any pasteboard write; if the identity is unavailable, changed, or terminal/console-like, the automatic experiment is cancelled before touching the pasteboard. Safe automatic experiment writes verify the pasteboard before sending exactly one synthetic Cmd+V. When `CopyToClipboard=false`, the experiment uses `PrepareClipboardFallback` / `RestoreClipboardFallback` token semantics instead of a tokenless write, so changed-clipboard restore rules stay intact. This experiment is not the default direct-insert path and is tracked by `clipboard_auto_paste_experiment`, not `directInserted=true`. The macOS AX fallback driver declares `NSPasteboardTypeString` before writing and verifies pasteboard writes by reading back the requested string before returning success or a restore token; write / clear failures return a failed result and are logged only as generalized status, booleans, token presence, and text length. |
| | | - `C1.source/privatevoice.src/frontend/src/components/settings/GeneralPage.svelte` and `C1.source/privatevoice.src/frontend/src/components/settings/PermissionsPage.svelte` expose production fallback controls through `TextOutputFallbackPanel.svelte`: refresh latest output status, explicitly prepare clipboard fallback, and restore original clipboard without overwriting a user-changed clipboard. Fallback UI shows only generalized status and action messages, never transcript text, target, method, message, window title, or document title. `app.go` emits `text-output:updated` after each production output result so an already-mounted Settings page refreshes pending fallback state without auto-pasting or exposing transcript, target, method, message, window title, or document title text in the event payload or default production output logs. Frontend fallback controls key their prepared-token state by the backend result `createdAt` and poll latest status while mounted so a missed Wails event cannot leave the button bound to an old prepared token; each new output clears the old token / prepared state so the user can prepare the latest retained text, while restore / skip-restore messages remain visible for the same output result. |
| | | - Forbidden AX core paths: IMK / TIS / InputMethodKit, AppleEvents / Automation, Input Monitoring, Screen Recording, event taps, `CGEventPost`, automatic Cmd+V, system input-method installation, release/tag/manifest actions. |
| | | - ASR engine: |
| | | - macOS: `privatevoice.src/internal/engine/engine_darwin.go` |
| | | - Windows: `privatevoice.src/internal/engine/engine_windows.go` |
| | | - macOS: `C1.source/privatevoice.src/internal/engine/engine_darwin.go` |
| | | - Windows: `C1.source/privatevoice.src/internal/engine/engine_windows.go` |
| | | - Linux: `C1.source/privatevoice.src/internal/engine/engine_linux.go` |
| | | - Overlay: |
| | | - macOS: `privatevoice.src/internal/overlay/overlay_darwin.go` |
| | | - Windows: `privatevoice.src/internal/overlay/overlay_windows.go` |
| | | - macOS: `C1.source/privatevoice.src/internal/overlay/overlay_darwin.go` |
| | | - Windows: `C1.source/privatevoice.src/internal/overlay/overlay_windows.go` |
| | | - Native ready text is produced by `hotkeyReadyText` in `C1.source/privatevoice.src/app.go`; unset trigger keys (`HotkeyVK=0` / empty display name) must show `触发键未设置` instead of concatenating an empty key into hold/tap prompts. |
| | | |
| | | ## Windows Preview Scope |
| | | |
| | | - Windows preview supports `SenseVoice` only. |
| | | - Non-SenseVoice models remain visible in settings but are disabled through `model.IsModelSupportedInCurrentBuild`. |
| | | - Windows unsupported reason: `windows_preview_unsupported`. |
| | | - Build script: `privatevoice.src/scripts/build-windows-preview.sh`. |
| | | - Evidence verifier: `privatevoice.src/scripts/verify-windows-qa-evidence.sh`. |
| | | - Runtime package includes: |
| | | - `PrivateVoice Dictation.exe` |
| | | - `onnxruntime.dll` |
| | | - `sherpa-onnx-c-api.dll` |
| | | - `sherpa-onnx-cxx-api.dll` |
| | | - `QA-WINDOWS-PREVIEW.ps1` |
| | | - `README-WINDOWS-PREVIEW.txt` |
| | | - Windows overlay remains native Win32/GDI+, with a larger translucent spectrum-style capsule indicator for recording states. |
| | | - Build script: `C1.source/privatevoice.src/scripts/build-windows-preview.sh`. |
| | | - Evidence verifier: `C1.source/privatevoice.src/scripts/verify-windows-qa-evidence.sh`. |
| | | |
| | | ## macOS Package Scope |
| | | |
| | | - Local DMG script: `C1.source/privatevoice.src/scripts/build-local-macos.sh`. |
| | | - App Store local sandbox QA script: `C1.source/privatevoice.src/scripts/build-appstore-macos.sh`; the appstore-local bundle must remove the sherpa-onnx Go module cache `LC_RPATH` from the main executable so dyld resolves `@rpath/libsherpa-onnx-c-api.dylib` and `@rpath/libonnxruntime.*.dylib` from `Contents/Frameworks` before any external module-cache path. Because hardened runtime library validation rejects self-signed local dylibs without a real Team ID, this script prefers an available `Apple Development:` identity for appstore-local QA signing, with `PRIVATEVOICE_APPSTORE_LOCAL_SIGN_IDENTITY` / `PRIVATEVOICE_LOCAL_SIGN_IDENTITY` as explicit overrides. For local QA App refresh without DMG / ZIP / package output, run it with `PRIVATEVOICE_APPSTORE_LOCAL_APP_ONLY=1`. |
| | | - App Store local sandbox QA and Mac App Store package scripts support candidate version overrides through `PRIVATEVOICE_APP_VERSION` and `PRIVATEVOICE_APP_BUILD`. The override is applied to both Go binary version metadata via `-ldflags -X main.appVersion/main.appBuild` and the assembled bundle `Info.plist` (`CFBundleShortVersionString` / `CFBundleVersion`), so MAS proof candidates can use `2.2.3` plus a fresh build without prematurely changing the default source `Info.plist`. |
| | | - App Store local sandbox QA and Mac App Store package scripts require `C1.source/privatevoice.src/build/darwin/PrivacyInfo.xcprivacy` and copy it to `Contents/Resources/PrivacyInfo.xcprivacy`. The current manifest declares `NSPrivacyAccessedAPICategoryFileTimestamp` / `C617.1` for app-owned and user-selected file metadata checks used by model, log, history, diagnostics, updater, path, and startup workflows; do not add required-reason categories without source or bundle evidence. |
| | | - Mac App Store package script: `C1.source/privatevoice.src/scripts/build-macappstore-pkg-macos.sh`; before signing it resolves the requested app signing identity to a concrete SHA1 leaf fingerprint and verifies that fingerprint is present in the embedded provisioning profile `DeveloperCertificates`. This prevents building ASC candidates whose app executable or nested dylibs are signed by a same-named but profile-mismatched certificate. |
| | | - Universal DMG script: `C1.source/privatevoice.src/scripts/build-local-macos-universal.sh`. |
| | | - Monterey compatibility script: `C1.source/privatevoice.src/scripts/build-local-macos-monterey.sh`. |
| | | - Developer ID / notarization script: `C1.source/privatevoice.src/scripts/build-release-macos.sh`. |
| | | - Mainline macOS local packages keep `LSMinimumSystemVersion=14.0`. |
| | | - Monterey-compatible packages remain a separate build line. |
| | | |
| | | ## Standard Verification Commands |
| | | |
| | | Run from `privatevoice.src`: |
| | | Run from `C1.source/privatevoice.src`: |
| | | |
| | | ```bash |
| | | npm --prefix frontend ls --omit=dev --all |
| | | npm --prefix frontend run build |
| | | go test ./... |
| | | go test -tags appstore ./internal/textoutput ./services |
| | | ./scripts/verify-ax-output-boundary.sh |
| | | GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go test -exec=/usr/bin/true ./... |
| | | ./scripts/build-windows-preview.sh |
| | | ./scripts/build-local-macos.sh arm64 |
| | | ./scripts/build-local-macos.sh x86_64 |
| | | ./scripts/build-local-macos-universal.sh |
| | | ./scripts/verify-windows-qa-evidence.sh /path/to/qa-evidence-YYYYMMDD-HHMMSS.zip |
| | | ``` |
| | | |
| | | ## Known Gaps |
| | | |
| | | - Windows runtime QA must still be performed on Windows 10/11 hardware. |
| | | - Run `QA-WINDOWS-PREVIEW.ps1` from the extracted preview package to collect startup, engine, recording, recognition, paste, and Notepad target text evidence. |
| | | - After evidence is returned, run `verify-windows-qa-evidence.sh` and require `WINDOWS_QA_EVIDENCE_PASS` before claiming the goal complete. |
| | | - The Windows preview zip is unsigned and not an installer. |
| | | - SmartScreen / Defender prompts are expected for local testing. |
| | | - Windows runtime QA must still be performed on Windows 10/11 hardware before calling Windows stable. |
| | | - macOS 2.2.2 real-user test DMGs are locally signed and not notarized. |
| | | - Monterey compatibility was not revalidated for the 2.2.2 package set. |
| | | - Independent-site sales still need Developer ID release packaging, download page, authorization/activation, update strategy, support path, release manifest, and rollback policy. |