When polishing returns an empty finalText (errorCode stays null), the History
"Copy" button copied an empty string, so the raw transcript that is still
visible in the UI could not be recovered. Re-transcribe does not apply here
(ASR succeeded; polish is what failed).
- onCopy: fall back to rawTranscript when finalText is blank
- raw panel: add a dedicated copy button (shown only when rawTranscript exists)
- reuse existing common.copy / common.copied, no new i18n; frontend-only
Complements #653 (polish identical to raw); independent of the in-progress
history re-polish work (#666/#694). No backend/IPC changes.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge PR #740 after review and full CI. This supersedes PR #734 by including its SendInput insertion mode work plus the keyboard-list visibility preference for #738.
Immediately unsubscribe Tauri listeners if their async listen() call resolves after component unmount, preventing leaked handlers in AudioCue and Linux window chrome.
Recreate closed AudioContext instances and resume interrupted/suspended contexts before scheduling generated record-start cues. Includes state classification tests.
Fix macOS Accessibility onboarding getting stuck after TCC prompt/identity drift. Request permission only from onboarding, add targeted Accessibility TCC reset + restart recovery, and mirror the flow in settings.
Traditional-Chinese users got Simplified output. finalize_polished_text only ran
the deterministic OpenCC conversion for Raw mode / polish failures, leaving
successful polish modes to the LLM — which defaults to Simplified for Chinese.
The streaming-insert path bypassed the conversion entirely.
- finalize_polished_text: for non-translation dictation, always apply
apply_chinese_script_preference. It is a no-op for ChineseScriptPreference::Auto
(the default), so zero impact on users who didn't pick a script; for Simplified/
Traditional it now guarantees the output script instead of trusting the LLM.
- streaming_insert_eligible: gains the script preference and disables streaming
insert when it is non-Auto, so the one-shot path runs (streaming types live and
has no finished text to post-process).
Tests: polish_output_honors_chinese_script_preference (S2t actually converts;
Auto is a no-op) + streaming_disabled_for_non_auto_script_so_opencc_runs. 470 lib
tests pass.
Closes#643
- preserve audio on ASR failure + auto silent retry: always-archive recordings
(delete on success unless debug), route all engine failure arms through a single
fail_dictation -> recoverable transcribeFailed history; auto-retry re-feeds the
archived WAV via retranscribe_pcm; fix empty-transcript id mismatch (#613)
- hotkey: queue-chain -- a press buffered during processing auto-starts the next
recording (grace window distinguishes it from the #545 post-session cooldown)
- history: wire the manual re-transcribe button for transcribeFailed/emptyTranscript
entries with audio
Rebased onto beta 1.3.10; my capsule multi-monitor fix dropped (superseded by #714).
ensure_asr_credentials() 的 if-else 链未处理 MiMo provider,fall through 到末尾被火山引擎凭证检查误拦。本 PR 把 is_mimo_provider(&active_asr) 加入 API Key 验证分支(+4 行),与 build_qa_asr_start / active_asr_provider_kind 的处理保持一致。
Claude 审核:修复正确且最小化,无副作用,可合并。四平台 CI 全绿。
Auto-update was broken on every platform: builds since aaf4ccc embed pubkey
6F4B5994, but shipped 1.3.8 stable users trust 1DEAA803 and CI now signs with
1DEAA803 again. A key the installed app doesn't trust => download succeeds,
install() signature verification fails => "downloads but never updates".
Revert the desktop updater pubkey to 1DEAA803 so it matches both the CI
signing key and android/updater.rs (which already hardcodes 1DEAA803). This
also repairs Android auto-update, which was signing-vs-verifying mismatched.
One line, no logic change.
Co-authored-by: sim <sim@local>
Re-cut of the 1.3.10-2 beta from the synced cloud beta branch (supersedes the
-2 pre-release, which was deleted). Identical code to 1.3.10-2 — version-number
bump only. Bundles #701 persistence split, #702 top-bar unification, #703
onboarding accessibility gate, on top of 1.3.10-1 audit fixes.
Bundles the two macOS UX fixes already merged into beta:
- #702 unify macOS shell top inset with sidebar/background surface
- #703 require macOS Accessibility grant before first-run onboarding completes
Version synced across package.json, package-lock.json, tauri.conf.json,
Cargo.toml, Cargo.lock. Local release build verified (1.3.10-2 installs and
runs; top bar seamless; first-run requires Accessibility).
On macOS the hotkey needs Accessibility, but first-run onboarding could finish
after Microphone only and drop users into the main UI with a dead hotkey
("nothing happens when I press the key").
Cause: desktop onboarding gated completion on the hotkey *capability* flag
(requiresAccessibilityPermission). That flag is null while the capability IPC
loads, so requiresAccessibility was briefly false — if Mic got granted in that
window, refresh() called onComplete() before Accessibility was ever requested.
App.tsx's startup gate, by contrast, always requires Accessibility, so the two
disagreed.
Fix: gate completion on the real permission status, mirroring App.tsx — macOS
returns granted/denied, every other platform returns notApplicable, so this is
timing-independent and cross-platform safe. Also surface the Accessibility step
whenever it is denied (macOS-only state) and drop the capability dependency from
its grant button, so the step always appears and is actionable even before the
capability finishes loading.
No change on Windows/Linux/Android (Accessibility = notApplicable there).
Co-authored-by: sim <sim@local>
The 28px top inset that clears the macOS overlay traffic lights sat on the
content wrapper with no background, so it exposed the lighter WindowChrome
glass (--ol-window-bg) as a full-width band above the sidebar and panel —
reading as an extra top row that the Windows build (no inset) doesn't have.
Paint that wrapper with the same --ol-app-shell-bg the sidebar/background
already use, so the inset blends into one continuous surface with the panel
card floating on top. One property; token carries its own light + dark
values; no layout, token, or other UI changes.
Co-authored-by: sim <sim@local>
Behavior-unchanged decomposition of the 2757-line `persistence.rs` god-file
into a `persistence/` module, one file per concern. Everything is re-exported
via glob (`pub use <submodule>::*`) so the `crate::persistence::*` public
surface is byte-for-byte identical and no call site changes.
Layout:
- mod.rs shared fs/io helpers (data_dir, ensure_dir, atomic_write,
read_or_default) + the two cross-cutting consts + re-exports
- paths.rs models / recordings / Foundry roots + migration helpers
- credentials.rs OS credential vault (CredsRoot model, keyring/android/legacy,
CredentialAccount, CredentialsSnapshot, CredentialsVault)
- history.rs HistoryStore
- preferences.rs read_preferences + PreferencesStore
- style_pack.rs StylePackStore + ZIP import/export + prefs reconciliation
- dictionary.rs DictionaryStore + vocab presets
- correction.rs CorrectionRuleStore
Pure move + re-export: no logic, signatures, public API, or UI changed. Unit
tests moved next to the code they now cover. Shared helpers stay private in
`mod.rs` (parent items are visible to child modules), and glob re-exports keep
cfg-gated items (Android/Windows) resolution-safe across every target —
avoiding the explicit-`pub use` breakage seen in the earlier types.rs split.
Verified on macOS: cargo fmt/check/clippy clean (no new persistence warnings),
446 lib tests green. Android/Windows cfg paths verified by CI.
Part of #697.
Co-authored-by: sim <sim@local>