The backend-only test PR needs to cover the concrete recorder paths named in the ticket, not just empty input handling. This adds focused Rust unit coverage for non-target sample-rate resampling, emitted PCM shape, scaled RMS levels, and peak tracking while keeping the production recorder unchanged.\n\nConstraint: Scope is Rust-only backend tests; do not launch or link extra Tauri runtime paths for Windows CI.\nConfidence: high\nScope-risk: narrow\nTested: cargo test --manifest-path openless-all/app/src-tauri/backend-tests/Cargo.toml recorder:: -- --nocapture\nTested: cargo test --manifest-path openless-all/app/src-tauri/backend-tests/Cargo.toml\nTested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib\nCo-authored-by: OmX <omx@oh-my-codex.dev>
The Rust-only backend harness already ran existing hotkey, recorder, and insertion unit tests, but the review could not see target edge coverage directly in this diff. Add focused edge tests for modifier trigger filtering/re-emission, recorder callback no-op inputs, and insertion fallback status so the coverage is explicit in the changed files.\n\nConstraint: Keep scope to backend Rust unit tests and avoid app/runtime integration.\nRejected: Add UI or Tauri runtime tests | conflicts with the Rust-only backend boundary.\nConfidence: high\nScope-risk: narrow\nTested: cargo test --manifest-path openless-all/app/src-tauri/backend-tests/Cargo.toml\nTested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib\nTested: git diff --check\nNot-tested: GitHub Windows runner result after push.\n\nCo-authored-by: OmX <omx@oh-my-codex.dev>
The Vocabulary screen advertises that entries are passed to "both ASR
hotword and post-processing model context", and that contract holds for
Volcengine ASR (which receives them as hotwords) and the Polish LLM
(which receives them as a system-prompt block). Whisper-compatible
providers (whisper / siliconflow / zhipu / groq) were the gap: their
client never set the OpenAI `prompt` field, so the dictionary had no
effect on the STT stage at all.
That gap shows up as inconsistent transliteration of registered proper
nouns. A user who registers "梁山泊" still gets "両山泊" / "良山泊" /
"りょうざんぱく" from Whisper, and the Polish LLM cannot always recover
once the script choice has already drifted.
This change:
- Adds an optional `prompt: Option<String>` to `WhisperBatchASR` and
attaches it as a `prompt` multipart field when non-blank. Empty/blank
prompts are skipped defensively so they cannot trigger
provider-specific 4xx responses.
- Adds `build_prompt_from_phrases`, a pure helper that joins enabled
vocabulary phrases with `, ` and a trailing period, capped at 240
characters (Whisper's published limit is 244 tokens; CJK is worst-case
~1 token/char, so 240 chars is the safe cap). Over-budget entries are
skipped individually rather than truncating mid-list, which preserves
registration order and lets short later entries still ride along when
one earlier entry is unusually long.
- Wires `enabled_phrases(inner)` through the helper at the existing
Whisper-compatible branch in `coordinator.rs`, mirroring how the
Volcengine branch already builds its hotword list.
Existing call sites unchanged in behavior when the dictionary is empty
(`None` prompt → no `prompt` field on the request). No new dependencies.
Tests in `asr::whisper::tests` cover: empty / all-blank input returns
None; single and multi phrase joining; per-entry trimming; mid-list
blanks are skipped without breaking the join; an over-long single entry
is dropped while shorter entries that follow it are kept; the resulting
string never exceeds the budget; truncation prefers earlier entries.
The backend-tests crate is intentionally an integration-test harness, but declaring the target explicitly makes Cargo discovery and review intent unambiguous. The harness comment also records that path-included modules run their own cfg(test) unit tests for hotkey, recorder, and insertion without linking the full Tauri library.\n\nConstraint: Stay within Rust-only backend tests; do not add a Tauri lib/runtime target.\nRejected: Add src/lib.rs just to satisfy target discovery | would introduce an unused crate surface instead of the actual test harness.\nConfidence: high\nScope-risk: narrow\nTested: cargo test --manifest-path openless-all/app/src-tauri/backend-tests/Cargo.toml -- --list | grep -E '^(hotkey|recorder|insertion)::'\nTested: cargo test --manifest-path openless-all/app/src-tauri/backend-tests/Cargo.toml\nTested: git diff --check\nNot-tested: GitHub Windows runner rerun result pending after push.\n\nCo-authored-by: OmX <omx@oh-my-codex.dev>
Windows intentionally normalizes cmd/command shortcut modifiers to ctrl, while macOS and Linux keep the super bit. The Rust-only backend test crate now executes these source tests on Windows, so the assertion must follow the same platform contract as production parsing instead of assuming the non-Windows result.\n\nConstraint: Keep the fix inside Rust backend tests and avoid changing shortcut behavior.\nRejected: Change normalize_modifier_tag to keep cmd as super on Windows | would alter user-facing hotkey semantics.\nConfidence: high\nScope-risk: narrow\nTested: cargo test --manifest-path openless-all/app/src-tauri/backend-tests/Cargo.toml\nTested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib\nNot-tested: GitHub Windows runner rerun result pending after push.\n\nCo-authored-by: OmX <omx@oh-my-codex.dev>
Windows cannot reliably execute the full Tauri lib test harness in CI because the process exits before Rust tests start in the desktop runtime loader path. Split the coordinator session state transitions into a Tauri-free Rust module used by coordinator.rs, and add a small Rust-only backend test crate that path-includes coordinator/hotkey/recorder/insertion logic for real Windows execution.
Constraint: User scoped this follow-up to backend-only Rust-to-Rust tests, not app/runtime smoke tests.
Constraint: Existing Windows CI still keeps full lib --no-run cfg/link coverage.
Rejected: Keep trying to execute cargo test --lib on Windows | it starts the full Tauri-linked test binary and fails before unit tests run.
Rejected: Rework release/runtime DLL handling | outside the backend unit-test scope.
Confidence: high
Scope-risk: moderate
Directive: Keep Windows actual unit execution on the Rust-only backend-tests manifest unless the full Tauri lib harness is proven runnable on clean Windows CI.
Tested: cargo test --manifest-path openless-all/app/src-tauri/backend-tests/Cargo.toml
Tested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib
Tested: cargo check --manifest-path openless-all/app/src-tauri/Cargo.toml
Tested: python yaml parse for .github/workflows/ci.yml; git diff --check
Not-tested: Windows runner execution; intended to be verified by GitHub Actions.
Related: #295
Co-authored-by: OmX <omx@oh-my-codex.dev>
Auto-update was failing on Windows because OpenLessIme.dll is a TSF input-method
DLL: once registered, Windows maps it into every text-input client process
(browsers, IDEs, chat apps...). The installer's PREINSTALL hook tried to
overwrite the file directly while it was still mapped, hitting a sharing
violation and aborting the update.
NSIS hook (openless-ime-hooks.nsh):
- PREINSTALL now calls OPENLESS_IME_UNREGISTER_X86/X64 first to release the
COM/TSF registration. On a fresh install this is a harmless no-op (existing
unregister macros log the exit code without aborting).
- DLL bytes are written to OpenLessIme.dll.new and atomically renamed into
place. If the old DLL is still locked by a TSF client process, Delete fails
and we fall back to Rename /REBOOTOK + SetRebootFlag — Windows queues the
replacement via PendingFileRenameOperations and the new bytes activate after
the next reboot. Same TSF registration path is preserved, so re-registration
in POSTINSTALL stays valid across the reboot.
WiX (openless-ime.wxs):
- UnregisterOpenLessImeX86/X64 now also fire Before InstallFiles when a
previous version is installed (Installed AND NOT REMOVE). Original condition
(REMOVE="ALL", Before RemoveFiles) only covered the dedicated uninstall
sequence; auto-update / minor-upgrade skipped it entirely and ran straight
into the same sharing violation as NSIS.
Bumps to 1.2.24-5 (Beta-5).
Co-authored-by: baiqing <lbx12309@icloud.com>
The #295 unit suite now runs in CI, but the Windows clean runner exits before the lib test harness starts with STATUS_ENTRYPOINT_NOT_FOUND when optional native runtime entrypoints are absent. Windows still needs cfg/link coverage, while macOS and Linux can execute the shared Rust unit suite.
Constraint: Preserve the new Rust test gate without making optional Windows native runtime installation a CI prerequisite.
Rejected: Install Foundry/Windows App Runtime into every CI run | broader, slower, and unrelated to backend unit-test coverage.
Confidence: high
Scope-risk: narrow
Directive: Do not switch Windows back to executing the lib test binary until the optional native runtime dependency is present on the runner.
Tested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib
Not-tested: Windows CI rerun before push.
The first #295 pass made cargo tests visible but still left the reviewer-requested state-machine, modifier-edge, and insertion fallback contracts too implicit. This adds narrow unit coverage around those contracts without refactoring runtime flow.\n\nConstraint: Address review compliance with minimal production changes.\nRejected: Split coordinator or hotkey adapters for testability | broader than the issue and riskier for the active PR.\nConfidence: high\nScope-risk: narrow\nTested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib; cargo check --manifest-path src-tauri/Cargo.toml; npm run build\nNot-tested: Live macOS/Windows native hotkey hooks and clipboard/AX behavior.
Issue #295 calls out that core Rust paths have been too dependent on manual app runs. Keep the change narrow by covering existing pure state/audio/clipboard helpers instead of refactoring coordinator or touching hardware-facing code, then wire the existing lib test suite into the lightweight CI lane.
Constraint: Global hotkey, recorder, and insertion integration still require real OS permissions and devices.
Rejected: Split coordinator before adding tests | broader refactor would raise review risk and is tracked separately.
Confidence: high
Scope-risk: narrow
Directive: Keep Rust CI on --lib unless integration tests get explicit OS fixtures.
Tested: npm run build
Tested: cargo test --manifest-path src-tauri/Cargo.toml --lib
Tested: cargo check --manifest-path src-tauri/Cargo.toml
Not-tested: Live microphone, global hotkey, clipboard, and insertion device paths.
Related: #295
The desktop WebView no longer runs with an unexplained disabled CSP. The policy keeps scripts self-hosted, permits Tauri IPC and local Vite development connections, keeps provider/update/model networking on the Rust side, and documents the only unsafe allowance for React inline styles used by the existing pixel-aligned UI.
Constraint: Issue #226 requires csp:null to be replaced or explicitly bounded
Constraint: Provider validation, updater checks, and model downloads are Rust-side paths, not frontend fetch allowlist entries
Rejected: Keep csp:null with documentation only | weaker defense-in-depth and leaves the WebView effectively unconstrained
Confidence: high
Scope-risk: narrow
Directive: Do not add external provider domains to WebView connect-src unless a frontend fetch path is introduced
Tested: npm run build
Tested: npm run tauri -- info
Tested: cargo check --manifest-path src-tauri/Cargo.toml
The dictation coordinator used a wrapping u64 counter to guard delayed recorder and startup events. Replacing that guard with UUID-backed session ids removes the theoretical wraparound collision while keeping the existing stale-event comparison model.
Constraint: Issue #297 requests the UUID-based fix rather than checked arithmetic.\nRejected: checked_add with warning | still preserves a numeric boundary condition instead of eliminating the collision class.\nConfidence: high\nScope-risk: narrow\nTested: cargo check --manifest-path openless-all/app/src-tauri/Cargo.toml\nTested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml coordinator::tests:: --lib\nNot-tested: Full cargo test --lib still has unrelated existing failure in types::tests::legacy_hotkey_trigger_still_produces_effective_key_codes.\nRelated: #297
Hotkey settings are a shared context for multiple pages, so one rejected IPC call must not leave the provider in a permanent loading state. The refresh path now records a localized recoverable error, keeps any successfully loaded half of the state, and always releases loading so consumers can render retry UI instead of staying blank.
Constraint: Issue #316 requires local load failures to stay visible and non-fatal.
Rejected: Keep Promise.all with one catch | it would still discard partial success from the other IPC call.
Confidence: high
Scope-risk: narrow
Tested: git diff --check; npm run build; cargo check --manifest-path src-tauri/Cargo.toml
Not-tested: Manual Tauri UI session with a deliberately corrupted preferences.json.
Related: https://github.com/appergb/openless/issues/316
CI only checked macOS and Windows before merge, so Linux-only Rust paths could fail cargo check for the first time during release packaging. The matrix now includes an Ubuntu lane that runs the same frontend build and backend cargo check, with only the Linux libraries needed for checking Tauri dependencies and no bundle step.
Constraint: Issue #300 asks for PR-time Linux validation while skipping Tauri bundle work.\nRejected: Add Linux release bundle to CI | too slow and duplicates release-tauri.yml.\nConfidence: high\nScope-risk: narrow\nTested: python ci.yml textual assertions; git diff --check; cd openless-all/app && npm run build; cd openless-all/app && cargo check --manifest-path src-tauri/Cargo.toml\nNot-tested: Full GitHub Actions ubuntu-latest runner execution before PR.\nRelated: https://github.com/appergb/openless/issues/300
The History mutation handlers already wait for backend success before changing visible list state, so failure recovery does not need to write back the request-time snapshot. Removing that restoration avoids overwriting a newer refresh result, and delete success now updates from the latest list state instead of the closure captured before the IPC call.
Constraint: PR #343 review identified stale snapshot writes during overlapping refresh and mutation requests.\nRejected: Add action-wide locking | more UI state than needed once failed mutations stop writing stale snapshots.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && git diff --check && npm run build\nNot-tested: Manual overlapping refresh/delete in the Tauri app.
History actions previously assumed list, clear, and delete IPC calls would resolve. A rejected load could leave the page stuck in loading, while failed mutations gave no user-visible feedback. The page now clears loading in finally, shows retryable inline load errors, and reports clear/delete failures while restoring the prior local selection and items.
Constraint: Issue #310 asks for inline card feedback without changing Rust handlers or introducing a toast system.\nRejected: New shared error component | broader UI surface than needed for this single-page failure path.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && git diff --check && npm run build\nNot-tested: Live Tauri IPC failure injection.\nRelated: https://github.com/appergb/openless/issues/310
Overview previously treated rejected history and credential IPC calls like empty data, which made backend read failures indistinguishable from a clean first run. The page now keeps separate error flags, shows provider status as unreadable when credential status cannot load, and replaces history-derived metrics/recent rows with retryable inline failure states.
Constraint: Issue #311 asks for per-card inline feedback without changing backend handler signatures or adding global toast UI.\nRejected: Global error boundary or toast | broader UI behavior than needed for the Overview failure path.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && git diff --check && npm run build\nNot-tested: Live Tauri IPC failure injection.\nRelated: https://github.com/appergb/openless/issues/311
The Selection Ask page now disables its card toggles while a save is in flight, but the nested shortcut recorder still accepted new recordings. That left a small overlap window where a second QA hotkey save could race the current setQaHotkey/savePrefs pair.\n\nThis keeps the recorder default behavior unchanged for other pages by adding an optional disabled prop and only wiring it from Selection Ask during its saving state.\n\nConstraint: Review feedback on PR #338 requested preventing overlapping QA shortcut saves without broad settings-queue refactoring.\nRejected: Global serialization inside ShortcutRecorder | unnecessary for pages that already own their own persistence state.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && npm run build\nTested: git diff --check\nNot-tested: Manual browser interaction against a slow Tauri IPC response.\nRelated: https://github.com/appergb/openless/issues/315\nRelated: https://github.com/appergb/openless/pull/338
Selection Ask reused the same QA hotkey and history persistence paths as other settings pages, but the page did not surface pending, success, or failure states. A rejected hotkey registration or settings write could therefore leave users believing the feature or history option was enabled when the backend had not accepted the change.\n\nThis keeps the existing persistence contract and mirrors the Translation page status treatment: register QA hotkeys before saving prefs, stop on registration errors, refresh after write failures, and add localized feedback for all supported UI languages.\n\nConstraint: Issue #315 requests minimal UI feedback and rollback behavior without refactoring the shared settings queue.\nRejected: Refactor HotkeySettingsContext persistence semantics | broader cross-page behavior change than needed for this bug.\nConfidence: high\nScope-risk: narrow\nTested: cd openless-all/app && npm run build\nTested: git diff --check\nNot-tested: Manual Tauri hotkey conflict flow on macOS/Windows.\nRelated: https://github.com/appergb/openless/issues/315