edit | blame | history | raw

CODEGRAPH

Project

  • Product: Wails v3 / Go desktop dictation app.
  • 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

  • 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: 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: 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: 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: 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: 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.
  • 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 C1.source/privatevoice.src:

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 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.