ASR validation URL normalization now edits the parsed URL path instead of string-appending to the raw endpoint, so endpoints with query parameters (e.g. api-version) remain valid after adding /audio/transcriptions.
Constraint: Keep endpoint normalization minimal and avoid changing query/fragment semantics
Rejected: Drop query parameters during validation | would break Azure-style OpenAI-compatible deployments
Confidence: high
Scope-risk: narrow
Tested: cargo test -q
Addresses review feedback: ASR validation now recognizes OpenAI-style /chat/completions endpoints when building the /audio/transcriptions URL, and response-size protection is enforced while streaming the body instead of after buffering it all.
Constraint: Keep ASR validation behavior provider-agnostic and minimal in scope
Rejected: Heuristic URL rewrites beyond known suffixes | risk of breaking custom reverse proxies
Confidence: high
Scope-risk: narrow
Directive: If changing reqwest features, keep Cargo.lock in sync and re-run tests
Tested: cargo test -q
Some OpenAI-compatible deployments expose ASR under a base URL that already ends with /audio. ASR validation now treats that as a valid prefix and appends only /transcriptions to avoid generating /audio/audio/transcriptions.
Constraint: Keep ASR validation URL normalization conservative and backwards-compatible
Rejected: Full URL path rewrite heuristics | risk of breaking custom reverse-proxy layouts
Confidence: high
Scope-risk: narrow
Tested: cargo test -q
Follow-up fixes from dual review keep the new end-to-end ASR check while removing compatibility and robustness regressions: normalize transcription endpoint URL, enforce HTTPS except localhost, cap response size, and switch to stable backend error codes mapped in i18n.
Constraint: Preserve issue #242 full-chain validation behavior with minimal surface change
Rejected: Revert to model-list-only validation | no longer verifies real transcription route
Confidence: high
Scope-risk: narrow
Directive: Keep providerHttpStatus:* and stable error-code contract synchronized with Settings error mapping
Tested: npm run build; cargo check -q
Not-tested: Manual validation against all third-party ASR endpoints
ASR validation now runs one end-to-end /audio/transcriptions request with the currently selected model and a tiny bundled silence WAV payload. This verifies endpoint, auth, model routing, and response schema in one pass instead of model-list reachability only.
Constraint: Keep UI/API contract unchanged and avoid touching recording pipeline
Rejected: Reuse live microphone capture in settings validation | invasive and non-deterministic for quick checks
Confidence: high
Scope-risk: narrow
Directive: Preserve providerHttpStatus:* error marker so frontend can keep status-specific guidance
Tested: npm run build; cargo check -q
Not-tested: Live validation against every third-party ASR provider implementation
Issue #242 asks ASR to reach parity with LLM availability checks. Validation now requires a configured ASR model and verifies it exists in the provider model list, instead of only checking endpoint/auth reachability.
Constraint: Keep existing settings flow and IPC shape with minimal UI change
Rejected: Add a new ASR test-audio transcription request | adds latency and provider-specific request complexity
Confidence: high
Scope-risk: narrow
Directive: Keep validate and fetch-models semantics aligned; validate should fail when configured model cannot actually be selected
Tested: npm run build; cargo check -q
Not-tested: Manual Settings UI interaction against live third-party ASR endpoints
README updates now place a unified acknowledgement section near the end (before License) in both English and Chinese, explicitly thanking sponsors, developers/contributors, and the LinuxDo community with a neutral non-endorsement disclaimer.
Constraint: Keep existing sponsor/developer showcase blocks intact while adding final acknowledgement wording
Rejected: Put acknowledgement near top profile blocks | user requested end-of-file placement
Confidence: high
Scope-risk: narrow
Directive: Keep acknowledgement wording neutral and avoid implying official affiliation
Tested: Manual README markdown diff review
Not-tested: Render preview in GitHub UI
Review feedback found that pre-escaping the entire markdown payload caused double-encoding in inline/fenced code, making HTML/JSON/XML examples unreadable. Switch to raw markdown parsing with renderer-level HTML token escaping so code formatting stays readable while raw HTML remains inert.
Constraint: Maintain issue #221 security boundary without broad sanitizer rewrite
Rejected: Keep full-string pre-escape | breaks code snippet readability
Confidence: high
Scope-risk: narrow
Directive: Escape only raw HTML tokens, not the whole markdown source
Tested: npm run build (openless-all/app)
Not-tested: Manual QA panel rendering across all markdown edge cases
Code review flagged a UX regression after sanitizer refactor: assistant single newlines stopped rendering as hard breaks. Re-enable marked breaks mode in the shared QA renderer to preserve previous panel readability for streamed and lightly formatted answers.
Constraint: Keep sanitizer fix while matching prior QA markdown display behavior
Rejected: Add newline post-processing in QaPanel | duplicates renderer concern and increases drift risk
Confidence: high
Scope-risk: narrow
Directive: QA markdown display flags must be centralized in qaMarkdown.ts to keep streaming/final parity
Tested: npm run build (openless-all/app)
Not-tested: Manual visual QA panel check on desktop runtime
Two parallel reviews flagged a high-risk fallback path and a link-encoding regression in the new QA sanitizer. This patch keeps the minimal #221 approach while ensuring failure fallback stays inert and safe links preserve query strings correctly.
Constraint: Preserve minimal change scope for issue #221
Rejected: Expand into CSP/capability hardening in same patch | out of issue scope and higher blast radius
Confidence: high
Scope-risk: narrow
Directive: Never return raw model output into dangerouslySetInnerHTML fallback paths
Tested: npm run build (openless-all/app)
Not-tested: Manual desktop exploit reproduction on Windows/macOS
Issue #221 requires closing the QA markdown XSS surface without broad CSP or architecture changes. This switches assistant and streaming markdown to one shared safe renderer that escapes raw HTML and rejects non-http(s)/mailto/tel links.
Constraint: Keep markdown readability while preventing executable HTML in QA WebView
Rejected: Add DOMPurify dependency | avoid new dependency for minimal fix
Confidence: medium
Scope-risk: narrow
Directive: Keep streaming and final assistant rendering on the same sanitizer path
Tested: npm run build (openless-all/app)
Not-tested: Manual malicious payload interaction in packaged desktop app
Issue #239 required the close button to be clickable, but a plain window.show() regression can steal focus and break selection capture assumptions in QA flow. Keep an explicit no-activate show on Windows while simplifying the native call path to only SW_SHOWNOACTIVATE.
Constraint: QA panel must not steal foreground focus when opening
Rejected: Keep plain window.show() | violates non-activation contract and risks wrong selection source
Confidence: medium
Scope-risk: narrow
Directive: If Windows QA show logic changes again, verify both close-button clickability and front-app selection capture
Tested: cargo check -q (openless-all/app/src-tauri)
Not-tested: Manual Windows interaction on physical device
Address review feedback by removing fragile runtime import of the autostart
plugin package and invoking plugin commands via Tauri core bridge, so production
bundles no longer depend on unresolved bare imports.
Also update preset batch apply to re-enable existing disabled terms and continue
processing remaining terms/presets when one add operation fails.
Constraint: Minimal changes limited to existing Settings/Vocab flows
Rejected: New backend batch endpoint for preset apply | broader cross-layer change
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preset apply should be idempotent and restore disabled matching terms
Tested: npm run build
Not-tested: Manual autostart toggle check on packaged desktop build
Preset edits were applied to local UI state before persistence finished, so
storage failures could show a misleading saved state and then lose changes on
reload. Also, batch apply aborted on first add_vocab error and skipped later
selected presets.
Make preset save await persistence before mutating UI/closing editor, and make
batch apply continue across per-term failures while reporting aggregated errors.
Constraint: Minimal behavior fix within existing Vocab page flow
Rejected: Add transactional backend endpoint for batch apply | larger cross-layer change
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep UI success states aligned with durable persistence completion
Tested: npm run build
Not-tested: Manual readonly-disk simulation for persist failure
Saving presets previously wrote the merged list (including built-ins) into user
storage, which froze old builtin copies and shadowed future app updates with the
same preset ids. Switch preset persistence to a delta store: custom presets,
overrides for edited built-ins, and disabled builtin ids.
Constraint: Keep current preset UI and command names unchanged
Rejected: Force builtin presets to always overwrite user file | would discard intentional user edits
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Never persist untouched builtin presets into user store
Tested: cargo test vocab_presets_roundtrip_json_file -q; npm run build
Not-tested: Manual end-to-end UX for builtin delete/restore (no delete UI yet)
Loading presets from app-data JSON previously replaced bundled defaults entirely,
so adding any custom preset made shipped scenarios disappear on the next load.
Merge user presets onto bundled defaults by id so built-ins always remain
available while still allowing user overrides.
Constraint: Minimal change in preset loading path without altering storage schema
Rejected: Persist defaults into user file on first run | unnecessary write churn and migration complexity
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve default+user merge behavior when adding future preset sources
Tested: npm run build
Not-tested: Manual Tauri UI check for merged list ordering
New preset previously persisted an empty entry immediately on click, so cancel
or navigation left permanent junk rows without any delete path. Keep new preset
as in-memory draft and persist only after explicit save.
Also add a regression test for vocab preset JSON persistence roundtrip to cover
the new preset storage workflow with automated verification.
Constraint: Minimal UI change without introducing preset delete UX in this patch
Rejected: Auto-delete blank presets on load | implicit cleanup can remove user-intended drafts
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep create flow non-persistent until user confirms save
Tested: cargo test vocab_presets_roundtrip_json_file -- --nocapture; npm run build
Not-tested: Manual UX walkthrough for preset draft cancel across app restart
The frontend build failed because the autostart plugin import could not be
resolved in the browser-targeted Vite bundle, and strict TS also flagged
implicit any in the same flow. Switch the autostart integration to runtime
lazy-loading behind the Tauri guard and type the callback parameters explicitly.
Constraint: Keep autostart UX behavior unchanged on Tauri runtime
Rejected: Add Vite external alias for plugin package | hides integration mismatch in app code
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Tauri-only plugin modules should stay runtime-gated to keep web build healthy
Tested: npm run build (openless-all/app) passed
Not-tested: End-to-end autostart toggle interaction on all desktop platforms
Issue #224 requires scenario presets with multi-select apply and editable custom
presets, while preparing for import/export. This revision moves built-in presets
into a repository JSON file and stores user presets in the app data directory as
JSON via Tauri commands, so preset persistence uses one JSON shape both in repo
and user space.
Constraint: Keep vocabulary entry backend and UI interaction model unchanged
Rejected: Keep presets in localStorage/TS constants | does not satisfy unified JSON storage requirement
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve vocab-presets JSON schema compatibility for future import/export
Tested: cargo check (src-tauri) passed
Not-tested: npm build currently fails on pre-existing Settings.tsx typing issues unrelated to this change