fix/issue-1566-deterministic-desktop-test
1635
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
24efca6a83 | test(desktop): resolve Bash portably | ||
|
|
28f69d37aa | Merge remote-tracking branch 'origin/main' into fix/issue-1566-deterministic-desktop-test | ||
|
|
ca7fb9c68d |
Merge pull request #1594 from debpalash/chore/project-agent-skills
chore(agents): install project development skills |
||
|
|
3dfe9664cf | fix: place desktop test changelog under Fixed | ||
|
|
cc9c7cfa18 | test(desktop): isolate cleanup script artifacts | ||
|
|
51163cf260 | Merge remote-tracking branch 'origin/main' into chore/project-agent-skills | ||
|
|
4ce4f05c06 |
Merge pull request #1559 from Eman-Yousaf/fix/path-security-separator-parity
fix(paths): treat / as a separator on Windows so stored sub-paths resolve |
||
|
|
e77feae817 | chore(agents): install project development skills | ||
|
|
fdc02b398e | Merge remote-tracking branch 'origin/main' into fix/path-security-separator-parity | ||
|
|
6e1bb44e0d |
docs(docker): add product media to Docker Hub overview (#1593)
Add the current v0.5 engine-switching GIF plus Model Catalogue and gallery-save screenshots to the canonical Docker Hub overview using absolute raw GitHub asset URLs. Includes a changelog entry. |
||
|
|
4dc90a7f4f |
docs(docker): refresh Docker Hub overview for v0.5 authentication (#1592)
Refresh current v0.5.0/0.5 tag examples, document API-key and share-PIN behavior, and require encrypted private-overlay access for remote deployments. Keeps the Docker install guide and changelog synchronized. |
||
|
|
3b64d317ae | fix(paths): accept persisted separators on every host | ||
|
|
ee7202b1eb | Merge remote-tracking branch 'origin/main' into codex/pr1559 | ||
|
|
871d68a6ff |
fix(auth): offer API-key login on server-mode admin 403s (#1569)
Fix server-mode admin authentication recovery without trapping PIN-only deployments, and prevent stale 403 responses from clearing or superseding newly issued sessions. Includes backend/frontend regression coverage, docs, and changelog credit for @paoloantinori. |
||
|
|
b37466b2e5 |
fix(ci): allowlist Ed25519 type-name false positive (#1591)
Restore weekly full-history gitleaks scans by allowlisting only the exact cryptography type name Ed25519PrivateKey, with an exact-value regression guard and changelog entry. |
||
|
|
2d5f2e800e |
feat(omnivoice): voice prompts that survive restarts + opt-in FlashInfer (~2.2x) (#1565)
* feat(omnivoice): port upstream VoiceClonePrompt persistence + FlashInfer opt-in Upstream k2-fsa teardown ports, verified with generated voice samples: - VoiceClonePrompt.save()/.load() (upstream format v1, weights_only-safe) on the vendored model, and a disk layer under the in-memory prompt LRU (DATA_DIR/prompt_cache, keyed by ref path+mtime+ref_text+preprocess, 32 newest kept, OMNIVOICE_PROMPT_DISK_CACHE=0 opts out). First generation of a session with a known voice skips the reference re-encode and any auto-transcription pass — verified across two real processes (encodes=1 then encodes=0, same voice). - omnivoice_flashinfer.py ported (packed CFG attention, fused kernels, optional CUDA graphs), schedule adapted to our num_step+1 divergence. Opt-in via OMNIVOICE_FLASHINFER=1|graph, CUDA-only, replaces torch.compile for the session; missing package / apply failure / runtime failure all degrade with a named reason (same #278 contract as compile: classify → unapply → retry once, session latch). Measured 2.20x at batch=1 on an RTX 4090 with byte-identical text and clean ASR round-trip. - Docs: OmniVoice guide gains instruct+reference combination semantics (consistent instruct stabilizes cloning, reference wins conflicts), inline pronunciation control (pinyin / CMU), prompt persistence, and corrects the 'no voice design' claim; performance.md documents both new env knobs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: point changelog entries at the real PR number (#1565) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(pr): harden FlashInfer lifecycle + prompt-cache writes per review Bot harvest round 1 (#1565): unapply on apply-failure (half-patched model could crash the next render); pin eager-mode FlashInfer inference to one thread too — the attention plan and packed position ids are per-generation module state, so interleaved _gpu_pool workers would corrupt each other; restore the CAPTURED pre-apply attention impl (could be flash_attention_2) instead of assuming sdpa; unique tmp name per prompt-cache write; correct the _forward_logits layout docstring; resolve VoiceClonePrompt at test runtime; docs — Known limits keeps only the limitation, performance.md states the VRAM cost and scopes the fallback claim to classified kernel failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(pr): round-2 review — publish only a fully restored model, redact latch reason, tighten CPU-persistence test Greptile: the runtime fallback now unapplies BEFORE swapping generate, so a concurrent render keeps queuing behind the thread-affinity wrapper while teardown mutates modules. CodeRabbit: FlashInfer failure reasons pass through core.failure.sanitize before latching/logging (wheel paths embed the user's home); the save-portability test now creates the tokens on CUDA when available and asserts the persisted payload itself is CPU-resident. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(pr): fail-closed latch reason when the sanitizer itself breaks CodeQL empty-except + CodeRabbit round 3: if core.failure.sanitize raises, the raw reason (home paths, wheel paths) was latched anyway. Now only the exception class survives with a fixed redaction note; two regression tests (normal redaction + sanitizer failure). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4db02d0c97 |
fix(test): watermark producer scan must match code, not prose (#1564)
* fix(test): watermark producer scan must match code, not prose
|
||
|
|
ee35d2389e | fix: require remote voice render parity | ||
|
|
1fda5bdf96 | fix: preserve voice identity on remote workers | ||
|
|
2477dde688 |
docs: streamline README structure and specifications (#1560)
* docs: streamline README structure and specifications * docs: address README review findings |
||
|
|
48c9a3b1f8 |
feat(settings): compute-device override (auto / CUDA / ROCm / XPU / MPS / CPU) (#1557)
* feat(settings): compute-device override — auto | CUDA | ROCm | XPU | MPS | CPU Auto-detect stays the default; the override kills the 'auto-detect picked wrong' issue class. Applied at the single choke point (_probe()'s family selection) so routing, get_best_device(), and every badge inherit it. Resolution: OMNIVOICE_DEVICE env > Settings pick (prefs.json) > auto (#981 pattern). An override can steer, never invent hardware: a family the host lacks is noted and ignored; cpu is always honorable. Applies at next backend start (host caps are immutable per process — same restart contract as the rest of the Performance tab, RestartBadge shown). GET/PUT /api/settings/compute-device (admin-gated) reports resolved vs applied so the panel shows restart-required truthfully and disables itself under an env pin instead of pretending. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(changelog): entry for the compute-device override (#1557) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(device-override): harvest — the override reaches CT2 ASR, full i18n, honest edge states - _ctranslate2_cuda_ok() and the ASR sidecar now gate on the probe's family, so a cpu pin (or ROCm host) can never hand CTranslate2 a CUDA device — the override reaches every CT2 loader through one shared gate - override_ignored exposed by the API and shown by the panel (env pin naming a device this machine lacks: auto is in effect, restart won't change it) - all 8 panel strings + 5 device-family labels translated into all 21 locales; failed saves keep their error visible through the re-sync - test isolation: cleanup drops OMNIVOICE_DEVICE before re-probing so no overridden caps leak into later tests; panel tests wait for loaded state - xpu/intel search keywords; oxfmt formatting Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(device-override): round 2 — fail-safe probe fallbacks, complete i18n, combined pin state - a broken capability probe now means CPU everywhere (CT2 gate + ASR sidecar) — never a torch-derived guess that would bypass a cpu pin or re-open #1529 on ROCm; regression test added - env-pinned AND not-detected shows both facts in one subtitle - device_load_failed/perf_save_failed translated into all 21 locales; CJK/th/vi/ar strings no longer say literal 'Auto' - test_ctranslate2_never_gets_cuda_on_a_rocm_build pins the probe family (it was order-dependent on the lru_cache before) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(test): pin the probe family in the faster-whisper OOM-fallback test Same class as the rocm-build test: it mocked torch but not the probe the new override gate consults first, so on a cpu-family CI host the CUDA fallback chain under test was unreachable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
030d5ea01f |
docs(engines): a guide for every engine + index; fix two engine-metadata bugs (#1556)
* docs(engines): a guide for every engine + index; fix two engine-metadata bugs 21 new pages under docs/engines/ (10 TTS, 10 ASR, index README) — every registered engine now has one: what it's for, platform support, model env vars, quirks with issue refs. Linked from both READMEs' engine sections. Code fixes found while verifying facts against the registries: - KittenTTS docstring claimed default voice 'Jasper'; the code default is expr-voice-2-f - the isolated-ASR sidecar read only ASR_MODEL_FW while the download preflight read ASR_MODEL_FASTER — set one and the other quietly used a different model; both now resolve ASR_MODEL_FW-override → ASR_MODEL_FASTER - moonshine's install hint named 'useful-moonshine', a package the backend never imports; now moonshine-onnx / moonshine-voice Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(changelog): entries for the engine guides + sidecar model fix (#1556) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(engines): second-harvest fixes — TLS guidance, matrix/code alignment, CN counts - README matrix aligned to gpu_compat (the code is the source of truth): CosyVoice macOS is CPU not MPS, IndexTTS and GGUF gain their real CUDA/CPU/MPS cells - gpt-sovits guide: prefer https/tunnel for non-loopback servers, plaintext warning; first-use download guidance on both OmniVoice pages - preflight empty-env fallback matches the sidecar (ASR_MODEL_FASTER='' no longer resolves a different repo) - nano installs via uv pip; kitten log level wording; index links install guides incl. the Gatekeeper step; README_CN engine counts 16/11 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(readme-cn): the all-engines-local claim now excludes the remote client Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b79ba9bd3b |
docs(readme): lead with download + first clone; seed benchmarks page (#1555)
* docs(readme): lead with download + first clone; seed benchmarks page Quickstart (installers, install guides, a three-step first-clone walkthrough) moves above What's-new/Features in both READMEs — visitors get the action before the pitch. New docs/benchmarks.md anchors measured per-engine/device numbers on the bench_pipeline.py harness, community-contributed, no estimates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(changelog): entry for the README conversion restructure (#1555) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): emit RTF + CUDA peak VRAM; guard NaN RAM; define the benchmarks schema Bot harvest on #1555: the tts stage now prints RTF per warm measurement and CUDA peak VRAM (None elsewhere — no made-up zeros), the stage floor refuses unmeasurable RAM instead of sailing past a NaN comparison (FLOOR_GB=0 overrides), docs/benchmarks.md columns map 1:1 to what the harness prints, and the download badges say they open the release page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(readme): link palash.dev from the maker section Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): name the resolved engine, track VRAM from resolution, comment the guards Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(readme): the quick-switch gif is the hero image The hero shows motion now; the Launchpad screenshot moves into the 0.5.0 What's-new slot so nothing appears twice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): peak VRAM is reserved memory; adapter engines name their model Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): subprocess-isolated engines report VRAM n/a, not a parent-side zero Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): out-of-process detection is declarative; sherpa rows name their model 'runs_out_of_process' is now a TTSBackend attribute set by SubprocessBackend AND omnivoice-gguf (which inherits TTSBackend directly but spawns a binary per generate — the isinstance check missed it). Duck-typed for the same module-purge reason as _is_subprocess_isolated. Sherpa-onnx identity comes from _model_dir's basename when _model_id is absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): backends self-report model identity via TTSBackend.model_identity() Greptile enumerated the adapter engines one at a time (mlx _model_id, sherpa _model_dir, cosyvoice env-only) — the attribute sniffing rots per engine. The hook fixes the class: each multi-model backend reports its own identity, the profiler just asks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3d0c9605df |
test(shell): backend-lifecycle fault-injection harness (#1551)
* test(shell): backend-lifecycle fault-injection harness Runs spawn_backend_and_wait/supervise_backend against REAL dying child processes and asserts the user receives the correct NAMED diagnosis — not merely that recovery happens. Wrong/missing explanation was 61% of the historical "can't reach the backend" class; this rig is the permanent regression harness for every future lifecycle fix. Seam: OMNIVOICE_BACKEND_CMD (JSON argv or whitespace form) runs any command as "the backend" — venv bootstrap and ffmpeg resolution are skipped, everything else (err-log run offsets, drainer threads, env pinning, real OS pipes, spawn-failure diagnostics) stays real. Plus OMNIVOICE_LOG_DIR (per-test log+marker dirs, also a support tool) and harness-only timing overrides OMNIVOICE_STARTUP_BUDGET_S / OMNIVOICE_SUPERVISOR_POLL_MS whose production defaults are pinned by unit tests. Lifecycle fns genericized over tauri::Runtime for the MockRuntime app; behavior-neutral with the env unset (unit-pinned). Scenarios (tests/backend_lifecycle.rs, scenario children = this test binary re-invoking itself; serial by mutex + CI --test-threads=1): - port conflict (exit 78) → the detectHints-matchable port phrasing - generic chained traceback → root cause survives into the diagnosis and the crash marker - spawn failure → spawn diagnostic reaches the user, NO bogus marker - slow start past budget → timeout names the budget + last stderr - post-Ready crash loop → 3 restarts announced, markers before restarts, "kept crashing" diagnosis naming the last exit - SIGKILL (unix) → named as signal 9 - deliberate kill → supervisor yields silently, no marker, never Failed - deferred-startup FATAL → the named step reaches the user, forensics, and the splash narration CI: harness added to the 3-OS tauri-cross-platform matrix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ci): temporary Windows loader bisect probe for the harness binary Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(shell): embed Common-Controls v6 manifest into Windows test binaries Bisected on #1551: EVERY integration-test binary of this crate died at load on Windows with STATUS_ENTRYPOINT_NOT_FOUND (0xc0000139) — cargo gives test binaries no manifest, so the loader resolves comctl32 v5, which lacks the TaskDialogIndirect entry point tauri's dialog/tray stack imports. build.rs now embeds tests/windows-test.manifest via rustc-link-arg-tests on Windows targets. Bisect probe removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(test): scenario gate is PID-valued — the parent can't self-inject CodeRabbit on #1551: in a parallel local `cargo test`, the parent's own scenario_child test could observe the armed env and start playing the backend in-process (binding the port, idling 600s). The gate value is now the arming process's PID; a matching PID stays inert, so only the spawned child — a different process — runs the scenario. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bb813ff676 |
feat(startup): bind the socket in ~1s and narrate startup step by step (#1550)
* feat(startup): bind the socket in ~1s and narrate startup step by step The structural fix for the "can't reach the local backend" class (~1 in 5 of every issue ever filed): uvicorn served nothing until torch import (10-20s cold), the 30-router fan-out, an import-time DB migration, the cuDNN preload, and alembic all finished — every slow or fragile step rendered as an unexplained dead backend. main.py now keeps module scope fast and defers the heavy work: - _phase_a_build (executor thread): prefs/env restore + #963 migration, yt-dlp overlay, cuDNN preload, torchaudio, model_manager, router imports — order preserved, literal imports so PyInstaller still traces. - _phase_a_finalize (event loop, no awaits → atomic wrt requests): include_router, mounts, MCP, SPA, openapi bust. - _phase_b: the old lifespan startup body; handles on app.state so shutdown survives a startup that never finished. - Eager mode (pytest / OMNIVOICE_EAGER_INIT=1) runs everything at import — byte-equivalent behavior for the ~100 lifespan-less TestClient sites and for embedders (dump_api_routes, probe boot runner opt in). While starting: /health answers 503 with the current step, new /startup/progress serves the full ledger (always 200), and StartupGateMiddleware 503s everything else with the [starting] marker (same skip-the-Report-button convention as [shutting_down]). A deferred failure keeps import-crash semantics: traceback to stderr → shell crash forensics, run sentinel stays uncleared, exit 1 names the failed step. Shell: startup_progress() probe (marker-header-gated so a foreign responder can't narrate the splash) feeds per-step log lines into the launch poll and the supervisor's reconnect wait. --health-check absorbs the deferred init (60→180s); --diagnose runs Phase A up front so it still sees restored prefs. Docker HEALTHCHECK semantics unchanged (curl -f fails on 503 exactly as it did on connection-refused). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(startup): join the Phase A thread on shutdown; async fail-path sleep Bot-review harvest on #1550: cancelling the deferred-startup task cannot stop the executor thread inside Phase A's blocking imports — shutdown now waits (bounded, only when a build started and hasn't finished) on a thread-completion event so interpreter teardown can't race a mid-import (#1000 class). The failure path's last-poll beat is now awaited, not time.sleep — a blocking sleep froze the very loop that beat exists to let serve. Also: dump_api_routes forces eager (assignment, not setdefault), and the integration test's child gets DEVNULL instead of an undrained pipe that could wedge a cold boot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(startup): close the Phase A submission race; CodeQL nits Review finds on #1550: shutdown could sample _phase_a_started unset while the executor callable was queued-but-not-running, skipping the thread join. started is now set BEFORE submission, the submission is shielded so a cancel can't strand a queued callable that would never set _phase_a_finished, and the wrapper sets finished on every exit including the already-built early return. Contract pinned by test_phase_a_thread_join_contract. Plus explanatory comments on the new bare excepts and a consistent return in the gate's websocket branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bc6acec5a3 |
fix(shell): gate Ready on the deep health probe; pace crash-loop restarts (#1548)
* fix(shell): gate Ready on the deep health probe; pace crash-loop restarts Two supervisor hardenings from the backend-reliability root-cause pass: Ready now requires backend_ready() — the identity probe (/system/info string-sniff) AND the deep probe (/profiles must 200) — at both Ready transitions (startup poll, supervisor respawn wait). The shallow probe alone announced a backend whose install/DB had broken underneath as up; the UI looked alive while every real request 500'd or dead-ended on "can't reach the backend". Death detection stays process-exit-only, so a busy-but-alive backend is still never killed. Supervisor respawns now back off: first respawn immediate (a one-off crash self-heals fast), then 5s, then 15s, capped — the budget check ends a hopeless loop, not an unbounded sleep. The pause runs behind the already-visible "reconnecting" banner and yields within 500ms to app quit or a deliberate retry-flow replace. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(shell): yield backoff to a tracked replacement child, not just the flag Greptile P1 on #1548: a completed Retry/Clean&Retry sets the deliberate- kill flag and track_backend_child clears it — possibly both between two 500ms backoff samples, so the flag alone can be missed and the old supervisor would free_port() the retry's healthy replacement. The dead child we observed can never read as alive again, so a live tracked child during backoff can only be a replacement — yield to it promptly so the retry's spawn_backend_and_wait can claim the supervisor slot at Ready. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(shell): backoff yields on spawn-generation change, not liveness Second Greptile pass on #1548: a replacement child that itself exits before the old supervisor's next 500ms sample read as "still dead" under the liveness check, so ownership transfer was missed. The spawn generation (bumped by every track_backend_child, never un-bumped) is observable regardless of the replacement's fate — snapshot it at death detection, yield the moment it changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(shell): snapshot spawn generation before observing the exit Third-pass review find: sampled after try_wait, a replacement tracked in the gap bakes its own generation into the snapshot and the ownership transfer is missed. Snapshot first, and re-check once more before touching the port so the zero-backoff first respawn is covered too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
94ba362ef2 |
feat(triage): crash-class recurrence report — the reliability metric (#1549)
* feat(triage): crash-class recurrence report — the reliability metric scripts/crash_class_report.py measures the "backend died / never came up" class (the project's #1 lifetime failure, ~1 in 5 of all issues) filtered to reports from the current version — the definition of done for the reliability cycle. Buckets by the bug reporter's Build-status stamp (#1547): current / outdated / unknown (pre-deflection builds), so deflection-miss noise never pollutes the number the work is judged on. tests/scripts/test_crash_class_report.py pins the title→sub-class mapping against the real historical title shapes and locks the stamp literals to frontend/src/utils/bugReport.js so a reworded marker fails in CI instead of silently zeroing the metric. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(triage): --version is authoritative; loud fetch-cap warning Bot-review harvest on #1549: with --version, the Environment Version line now decides the bucket (extracted to pure classify_build + tests) — a report stamped "current at filing time" during another version's window no longer counts toward this version's recurrence. Hitting the 500-issue fetch cap now warns loudly instead of silently understating. The stamp lockstep test asserts the full Build-status prefix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
aabe5783f3 |
fix(report): offer the latest release before filing from an outdated build (#1547)
* fix(report): offer the latest release before filing from an outdated build 6 in 10 sampled "can't reach the backend" reports came from builds that were already obsolete when filed, and were closed with "please update" — pure triage noise. Every Report-bug affordance now funnels through openBugReport(): on an outdated build it offers the latest release first (with a "File anyway" escape hatch), and the report body carries a triage-greppable "**Build status:**" line either way, so current-version recurrence — the reliability metric — is countable separately from stale-build reports. Freshness sources per deployment (behavior identical, implementation per mode): desktop reads the Rust updater's channel-aware verdict from the store (no new network path, no CSP widening); browser/dev/Docker make one bounded latest-release GET, only once the user has initiated the report flow whose destination is github.com. An 'unknown' dev build stays silent entirely — never nudged, never claimed current. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(report): anchor version parsing; zh-TW reportBug.title in Traditional Bot-review harvest on #1547: parseVersionTriple now rejects trailing non-semver data (1.2.3.4, 1.2.3garbage) instead of silently reading the leading triple into an outdated/current verdict; the pre-existing zh-TW reportBug.title was Simplified-script — now properly Traditional. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
854b4852ed |
perf(frontend): coalesce persistence writes off input paths (#1546)
Defer and coalesce omnivoice.app and omni_ui persistence behind a 250 ms quiet window with a 1,000 ms hard maximum, preserving storage schemas, synchronous pending reads, legacy formats, Factory Reset semantics, widget read-only ownership, and lifecycle (pagehide/visibilitychange) durability. Adds scheduler, restore, reset, StrictMode, concurrent-render, role-ownership, and migration regression tests plus an opt-in production-bundle responsiveness harness. Lands #1541 by @bultodepapas (maintainer landing branch; the out-of-scope attribution-policy commit was dropped). Co-authored-by: Gius <bultodepapas@gmail.com> |
||
|
|
579f2e0a2e |
fix(paths): treat / as a separator on Windows so stored sub-paths resolve
resolve_within split candidate paths on os.sep alone. Windows accepts / as a real separator but os.sep is \ there, so a persisted sub-path such as "job_123/out.mp4" stayed a single component, failed the basename-equality check, and raised UnsafePath — while the identical value split cleanly and resolved on POSIX. A data directory written on Linux or by the Docker deployment and then opened by the Windows desktop app hit exactly that. Split on both separator families instead, which is what the comment above the split already states the code intends. This is not a loosening: every component still goes through the same basename / "." / ".." / empty rejection, and the commonpath containment check and symlink resolution below are unchanged. POSIX behaviour is unchanged too — a backslash is already rejected there as a foreign separator before the split runs. This also restores real coverage of the symlink-escape guard on Windows. test_resolve_within_rejects_symlink_escape asserts through "link/secret.wav", which previously raised at component validation before reaching the containment check it exists to cover, so it passed for the wrong reason. It now matches on the reason. |
||
|
|
420bc73e78 |
fix(release): harden the AppImage repair — pinned tooling, final-writer manifest (#1545)
* fix(release): harden the AppImage repair step All three review findings on #1544, fixed before the tag re-runs it: - appimagetool pinned to the immutable 1.9.1 release with a verified SHA-256 — a mutable 'continuous' binary must not execute with the updater signing key and a release-write token in its environment - the release tag reaches the script as env data, never interpolated into shell source (zizmor template-injection) - a failed latest.json download now fails the step unless the asset is confirmed absent, and the patch refuses to upload unless at least one linux signature was actually replaced — a repacked AppImage can never ship paired with stale updater metadata Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(release): the updater manifest gets one final writer, after the matrix CodeRabbit + Greptile on #1545: every tauri-action leg re-uploads the shared latest.json, so patching it inside the Linux leg races the other platforms — a later leg's upload could resurrect the stale pre-repack signature. The manifest patch moves to a post-matrix job that runs once after all legs: it aligns the manifest's linux entries with the .sig asset that actually shipped (self-verifying — no cross-job state), and no-ops when they already agree. The leg keeps asset repack/re-sign only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(release): a failed .sig download fails the manifest-align job Same fail-closed rule as the manifest itself: absence is decided by the asset list; any other download failure must not exit 0 with a stale signature left in latest.json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>v0.5.0 |
||
|
|
fb46fa4788 |
fix(release): repack the AppImage with a real .DirIcon file, re-sign, re-upload (#1544)
The v0.5.0 tag build — the first real release since #1518's guard — proved the files-map fix loses: linuxdeploy re-links .DirIcon to an ABSOLUTE build-machine path after tauri places the real bytes, and the guard correctly refused to publish. tauri-action's atomic build+sign+upload leaves only a post-upload seam, so the Linux job now repairs the packed artifact: extract, replace .DirIcon with the icon bytes as a regular file (nothing left to dangle), repack with appimagetool, re-sign with the updater key, clobber the draft release's asset and patch the linux signature inside latest.json. The existing smoke then validates the repaired AppImage. No-ops cleanly when .DirIcon already resolves. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f83b7371c3 |
docs: remote-workers guide speaks VoiceStudio (#1543)
CodeRabbit's last-cycle Minor on #1540, applied as the immediate follow-up the docs-sync rule prescribes: three OmniVoice mentions in docs/remote-workers.md now carry the product's name. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9184d7d625 |
chore: bump to 0.5.0 (#1540)
* chore: bump to 0.5.0 Owner-requested minor bump. package.json is the source of truth; the three mirrors (Cargo.toml, pyproject.toml, _FALLBACK_VERSION), the three lockfiles and the branding pin move in lockstep, and the accumulated Unreleased section becomes the curated 0.5.0 release notes — quiet Highlights first, one-liner subsections after, duplicates folded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: fresh README for the VoiceStudio era; 0.5.0 notes wear the release README: 551 lines from 656 — a What's-new-in-0.5.0 section with real captures, the engine tables corrected to the actual 16 TTS registrations, a stale Settings path and a broken Colab link fixed, roadmap/FAQ/credits trimmed to what earns its place. Release notes: the quick-switch GIF and catalogue/gallery screenshots, captured from the running app during the pre-bump test pass, embedded after the Highlights; #1542's gallery work and the ffmpeg CI fallback recorded in their subsections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: the feature inventory's Remote Model Downloads mention survives the README trim check-docs-drift requires every docs/features.yaml name verbatim in the README; the overhaul folded the phrase away. It now lives in the remote workers feature line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: one blockquote, and an architecture claim that survives the opt-ins CodeRabbit on #1540: MD028 blank line inside adjacent blockquotes, and 'every layer is on your machine' contradicted the opt-in remote paths documented two sections away — it now states local-by-default with the opt-ins named. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0ee62b2261 |
feat(gallery): save gallery voices as profiles, with validated audio references (#1542)
* feat(gallery): save gallery voices as profiles, with validated audio references Work-in-progress lifted from the concurrent gallery session at the owner's request (its uncommitted working tree, preserved verbatim from base 92b1ee5d; safety snapshot remains at rescue/gallery-wip): - gallery voices can be saved as local profiles: audio is copied into the profile store with content-addressed filenames, existing profiles are detected and refreshed only when the source clip changed - backend/core/audio_validation.py: symlink-rejecting, root-contained resolution for persisted profile WAV references, with tests - archetype/community routers and the Voice Gallery UI updated for the save-as-profile handoff (spec: docs/specs/longform/26-gallery-use-handoff.md) - locale updates for the new gallery strings across all 21 files Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: drop a stray local screenshot script that rode in with the tree copy * fix(community): explain the tolerated Content-Length parse failure; drop an unused import CodeQL on #1542: the empty except now says why it is safe (the streamed byte counter enforces the same cap regardless), and the test file loses an unused Path import. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(gallery): review findings — copy outside the write lock, no stale completions CodeRabbit on #1542, all findings addressed: - the profile-audio copy stages to a .part temp BEFORE BEGIN IMMEDIATE and publishes via atomic os.replace inside it — other backend writers no longer block for the duration of an audio copy; a mid-copy failure leaves no temp droppings and no profile row (both pinned by tests) - VoiceGallery async ops carry per-operation generation tokens: a preview or save-as-profile that resolves after unmount (or after a newer operation) can no longer play audio, redirect into a workspace, or touch state — three fail-before regression tests - VoiceGalleryActions imports the page at test runtime; the e2e locator uses a stable data-testid instead of a translated string; symlink tests skip cleanly where the OS can't create symlinks; the changelog line carries its PR ref Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: static ffmpeg fallback when the chocolatey feed is down Third feed outage to break a PR run (2026-07-20, 2026-07-28, today — three attempts, three 'installed 0/1'). Chocolatey is a distribution channel, not the dependency: after the retry loop exhausts, fetch the static gyan.dev build from its GitHub release mirror and put it on PATH — same binary, no feed in the path. URL verified live (HTTP 200). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
77ae194f9c |
fix(asr): ROCm torch is not CUDA — keep CTranslate2 off the HIP GPU (#1539)
An RX 7900 XTX in the :rocm image crashed ASR init with 'CUDA driver version is insufficient for CUDA runtime version' (#1529): ROCm torch answers torch.cuda.is_available() and hands out 'cuda' device strings, but whisperx/faster-whisper run on CTranslate2, whose CUDA runtime is NVIDIA-only. Same class as the Apple/#1127 lesson, on the AMD axis. - _ctranslate2_cuda_ok(): 'cuda' for CTranslate2 only when torch is a real CUDA build (torch.version.hip is the honest tell); ROCm hosts take CPU int8 instead of a native crash. - _auto_detect(): on a ROCm-GPU host prefer pytorch-whisper — a pure transformers pipeline riding torch itself, so it actually uses the HIP GPU while CTranslate2 engines would idle on the CPU. Fail-before/pass-after: 4 new tests fail on the old device pick/order. Fixes #1529 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d3822c4976 |
feat(engines): open the door between the LLM family and its providers (#1538)
* feat(engines): open the door between the LLM family and its providers The openai-compat family entry and the LLM Providers panel are one system — llm_backend resolves every call through the active provider — but the UI presented them as unrelated (council coherence finding). Now: - the catalogue's openai-compat row carries a 'Provider · model' hint naming the endpoint that actually answers (decorative: a provider registry hiccup degrades to no hint, never a failed listing) - the row offers 'Configure providers' straight into Settings → LLM Providers; the panel gains the backlink into catalogue → LLM family - three new strings in all 21 locales, matching each file's provider terminology Also: bugReport's encoded-ceiling test is hermetic now — it was the one test in its file trusting ambient fetch, and hung on any machine where a local backend holds the port without answering. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(i18n): the catalogue note names the ACTIVE provider, not the edited one CodeRabbit on #1538: the panel can be editing a provider that is not active, and 'this provider answers…' then points at the wrong one. The note now says the provider MARKED ACTIVE answers, which is true under any selection — no state-dependent copy needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
19ae20111a |
fix(security): replace persistent admin keys with scoped sessions (#1528)
* fix(security): replace persistent admin keys with sessions Exchange the remote administrator key once for bounded, revocable credentials. Canonicalize backend principals, enforce cookie CSRF and exact origins, and use path-bound one-use WebSocket tickets. Migrate the bundled UI away from durable master-key storage and credential-bearing URLs. Add unit, integration, static-hygiene, and production-browser regressions plus synchronized operator documentation. * docs: link session hardening to PR 1528 * fix(security): key session indexes with process pepper Use HMAC-SHA-256 instead of an unkeyed digest for in-memory session and WebSocket-ticket indexes. This preserves constant-size lookup identifiers, makes copied records unusable without the process pepper, and resolves CodeQL's weak sensitive-data hash finding. * fix(auth): align empty bearer migration precedence Centralize the Authorization-channel presence decision with canonical principal parsing. Bearer followed only by spaces now remains an empty channel during legacy cookie migration, while unsupported or invalid explicit credentials stay authoritative and fail closed. * fix(security): harden admin session review boundaries * fix(security): derive key generations with HKDF * fix(auth): anchor the admin-session store so module reloads cannot fork it test_master_exchange_does_not_bypass_pin_on_normal_routes failed in full-suite runs: test_mcp_bindings' client fixture purges the services.* tree from sys.modules and reloads main, so api.routers.auth re-imported a fresh services.admin_sessions (new AdminSessionStore) while core.auth kept its import-time reference to the old one — the exchange issued the cookie into one store and the middleware resolved it against another, turning the expected "PIN required" into "API key required". Root cause is the class of bug, not the one test: a process-global auth store defined as a bare module-level singleton forks under importlib.reload or purge-and-reimport. Fix at the source: admin_session_store now resolves through a synthetic sys.modules anchor (_omnivoice_admin_session_store_anchor) that reloads never re-execute and package-prefix purges never match, so every copy of the module shares the one per-process store. No consumer or behavior changes. Regression test reproduces both fork vectors (in-place reload and sys.modules purge + fresh import) and asserts previously issued sessions still resolve and the store identity is preserved; it fails before this fix and passes after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(auth): honor X-Forwarded-Proto for CSRF origin and Secure cookies behind TLS proxies Behind Tailscale Serve (docs/remote-gpu.md) or any TLS-terminating proxy, the browser talks https while the backend hop stays http, so exact-origin CSRF compared an https Origin against an http expectation and rejected every legitimate request, and the session cookie shipped without Secure. uvicorn's ProxyHeadersMiddleware only rewrites the scope for loopback peers, which misses Docker and any non-loopback proxy topology. New core.csrf.effective_scheme derives the client-facing scheme: resolved scope first (uvicorn's trusted-proxy rewrite wins), then an upgrade-only read of X-Forwarded-Proto's first value — https/wss promotes http to https, everything else is ignored, and a genuine TLS hop can never be downgraded. Used by both the destination-origin comparison and auth._secure_cookie so the WS-ticket/logout CSRF paths and the cookie Secure flag agree. Spoofing gains nothing: the host:port half of the origin tuple is untouched, browsers cannot attach the header cross-site without a preflight this API never grants, and forging it on plain http only adds Secure (the browser then drops the cookie — self-harm only). Regression tests: proxied https origin accepted (origin check, Secure flag, logout), comma-separated chains, scope-fallback path, spoofed header still rejects cross-origin, cannot downgrade real https, junk values ignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(auth): consume the stored admin key only after a successful exchange A remote-backend user upgrading with their backend unreachable lost the only stored copy of OMNIVOICE_API_KEY: every migration path deleted the durable ov_api_key BEFORE the session exchange settled, stranding them until they recovered the key from the server box. Close the whole class: - client.ts bootstrap: read the legacy key, exchange first, and remove the durable copy only after the exchange succeeds; on failure the key stays so the next launch retries the migration (auth gate still rises). - authSession.ts exchangeApiKey: move removeLegacyMaster from before the fetch to the cookie/bearer success paths — the key never coexists with a live session, but a rejected or hung exchange no longer consumes it. - remoteBackendProbe.ts configuredRemoteBackend: stop wiping the key on every app mount. - RemoteBackendPanel: a connection test or an aborted save no longer wipes the pending key; only disabling the remote backend discards it. - prefKeys.js: ov_api_key moves from PREF_KEYS to PRESERVED_KEYS — factory reset preserves the pending connection credential exactly like ov_backend_url; the successful migration is what deletes it. Tighten the credential-hygiene static guard to match: it accepted sessionStorage.setItem('ov_api_key', …) — the exact class it exists to close. The guard now flags .setItem(<master key>) on any storage receiver, quote style, or injected-store alias, with a self-test pinning what it catches and what stays legal. Fail-before/pass-after regression tests: backend unreachable retains the key and the next bootstrap retries it; a successful exchange removes it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf(auth): make session validation occupancy-independent * test(auth): catch optional master-key storage calls * feat(docs): add PR control document for bultodepapas in VoiceStudio * docs: keep the PR tracking board in the fork; credit the changelog line The pr-control document is excellent process discipline, but it is the contributor's own operational board (their inventory, their update commands) — it lives naturally in their fork, and docs/agents/ here is context every repo agent loads. Removed with appreciation; the changelog line gains its contributor credit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: debpalash <4178343+debpalash@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b982192011 |
fix(worker): reserve the capacity slot before announcing the accept (#1537)
* fix(worker): reserve the capacity slot before announcing the accept Two changes for #1536 (flaky test_worker_at_capacity_rejects_without_penalty): The client now inserts into _running BEFORE awaiting the accept send. Today _send enqueues synchronously so the old order could not actually interleave — but the reserve-then-announce order is the invariant that stays correct if _send ever gains backpressure (a bounded outbox is the natural evolution), instead of silently reopening an over-accept window. If the accept send fails, the reserved task is cancelled: work the scheduler never saw accepted must not run to double-execution. The test now pins the real invariant — no over-concurrency — rather than the scheduler's bookkeeping timing: on a loaded CI runner the first attempt can die environmentally (a stream hiccup fails _run, whose finally frees the slot), after which accepting the second task is the CORRECT behaviour the old flat assertion punished as a failure. The assertion now applies only while the first attempt is still running, and names the over-accept explicitly when it fires. Fixes #1536 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(worker): release the reserved slot on a cancelled accept-send CodeRabbit on #1537: - except BaseException, not Exception: a handler cancelled while the accept send is in flight must release the reserved slot too, or the unaccepted task keeps running and double-executes after reassignment. Fail-before/pass-after regression test included. - the capacity test now polls the second task out of its dispatch states instead of sleeping 0.5s, and asserts the penalty-free invariant (excluded_workers empty) unconditionally — capacity rejections never exclude the worker regardless of the first attempt's health. - TaskAccepted.envelope finding skipped: the field is read nowhere server-side (registration is the only envelope consumer) — pre-existing unused-field design, not introduced here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
81b146f53b |
fix(i18n): 53 default-value-only strings now speak all 21 languages (#1534)
* fix(i18n): 53 default-value-only strings now speak all 21 languages
Every string used via t(key, { defaultValue }) without a locale entry
rendered English for every non-English user — the worker/compute chrome
(quick settings, join panel, QR enrolment), clone/design labels, workspace
voice strip, and two crash explainers. All 53 keys now exist in en.json
and carry reviewed translations in the 20 other locales, matching each
file's established terminology (existing worker/token/engine vocabulary,
catalogue tab names for the in-text path references, registers preserved,
{{placeholders}} byte-identical, the ovw_ token prefix untranslated).
The two crash explainers are translated from their FULL concatenated
source text — the extraction initially captured only the first string
segment, which src/test/streamDropError.test.ts caught by failing on the
missing proxy/buffering guidance.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(i18n): reference localized UI labels inside diagnostic strings
CodeRabbit on #1534, fixed as the class: every locale's crash_broken_env
quoted the English "Clean & Retry" although the button itself is
localized — all 19 now quote each file's own clean_retry label. Plus the
flagged singles: es unload verb disambiguated from downloading, hi unload
verb aligned with crash_oom_kill, sv kontrollplan gender agreement, de
crash_broken_env moved to the file's Sie register, ru seed_reroll_hint
mistranslation, zh-TW path label matched to the real 系統日誌 section name.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
72cabb3daf |
feat(launchpad): wear the signal-field waveform in the hero (#1533)
The same cover artwork the project uses on the web, bleeding in from the hero's right — where the layout holds only air — behind a radial feather plus right-edge fade so no box edge survives, screen-blended so its dark field vanishes into the chrome. Decorative: aria-hidden, empty alt, pointer-inert; 22 KB webp bundled via vite. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4a228a00b4 |
fix(crash): bound crash evidence to the run that produced it (#1532)
* fix(crash): bound crash evidence to the run that produced it backend_err.log is one file shared by every backend run, and it was TRUNCATED on each spawn. Both properties destroyed the evidence a crash marker exists to carry: a respawn wiped the dead process's final words, and an unbounded tail read afterwards attached the replacement's healthy startup to the old run's crash marker — the undiagnosable report in #1510 (startup lines, no traceback, timestamps after the recorded crash). The file is append-only now with a run-start header; each spawn records the byte offset where its run begins; and every death path (crash markers, venv-heal detection, restart-budget message, the 300s startup timeout) reads through read_error_log_tail_for_run(), which cannot see another run's output. The log rotates to backend_err.log.1 past 1 MiB so append-only cannot grow unbounded. Bootstrap-phase reads (uv sync) keep the whole-file reader — no backend run exists yet there. Fail-before/pass-after: the new tests fail under the old File::create truncation and unbounded tail. Fixes #1510 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(crash): flush the dying run's stderr before the next offset; redact home paths CodeRabbit on #1532: - the stderr drainer is tracked now and joined (2s bound) before a new spawn records its offset, so a dead run's buffered tail cannot be appended after the new run's start and misattributed. Full per-child offset binding is unnecessary: spawns are serialized by the #1223 spawn-once flow; the buffered tail was the only remaining window. - the spawn-failure diagnostic redacts the home-directory prefix — it is retained across runs now and lands verbatim in bug reports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f81ace68d1 |
feat(engines): frame uninstalled engines as headroom, not failures (#1531)
The catalogue's group captions read "Available" / "Not installed", which renders a fresh install (3 of 16 engines ready) as a mostly-broken app. The sections now say "Ready to use" / "Add more engines", and the unavailable-row details toggle asks "What it needs" instead of "Why unavailable?" — same information, framed as headroom to unlock. Council outcome (first-run seat): unavailable engines must read as more you could install, never as brokenness. Keys added to all 21 locales. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9832fbd693 |
feat(engines): switch engines from anywhere — footer quick switch, workspace chips, shortcuts (#1530)
* feat(engines): add quick switching controls * docs(changelog): note engine quick switching (#1530) * fix(support): theme amount cards * fix(support): restore themed amount cards * fix(engines): address quick switch review findings * fix(engines): green the full frontend suite around the quick switch Three failure classes the targeted runs missed: - the popover referenced --chrome-radius, which does not exist; it now wears the footer's shared MENU_SURFACE like the compute popover - LogsFooter tests hand-wrote their api/system and api/hooks mocks, which drop every export the footer gains next; they are partial mocks now - DubHeader/AudiobookHero tests rendered without a QueryClientProvider, which useEngines needs Also: workspace-header chips open the popover downward (dropUp stays on the footer instance) so it cannot clip off the top of the viewport. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(engines): one QueryClient per test module, not per render CodeRabbit: the inline client made every wrapper render a fresh cache, so rerender() restarted the /engines query mid-test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cd54113173 |
fix(security): close server-mode admin bypasses (#1525)
* fix(security): require keys for remote admin actions * fix(frontend): guard unavailable scrollIntoView * docs: link changelog to PR 1525 * fix(security): align PIN-only discovery policy * fix(security): preserve strict sidecar boundary * fix(security): normalize remote API keys * fix(auth): normalize credential fallback order |
||
|
|
6948399e61 |
fix(wayland): rewrite a stale portal identity instead of trusting it (#1526)
* fix(wayland): rewrite a stale portal identity instead of trusting it Found live: with the desktop entry's Exec pointing at a binary that had been moved, GLib resolves the entry to NULL, the host portal answers "Could not register app ID: App info not found", CreateSession then fails with "An app id is required" — and the dictation shortcut is silently dead for the entire session. Only the focused-window fallback keeps working, which reads as "the shortcut randomly stopped". ensure_desktop_identity() trusted any existing entry. It now validates the USER-LOCAL entry's Exec target and rewrites the entry when the program is gone (parsing both the current quoted spelling and the unquoted one older builds wrote). System-dir entries stay untouched — deb installs manage their own. The stale-entry class is easy to hit in the wild: a dev entry pinned to target/debug survives cargo clean; an AppImage entry survives the file being moved or renamed. (Rebuilt from the first push, whose `git add -A` had swept in another working session's unrelated in-progress files; this commit carries only the wayland fix and its changelog line.) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(wayland): read Exec from the Desktop Entry group only CodeRabbit, #1526: find_map over every line accepted an Exec= from a [Desktop Action …] group, so an entry with no main-group Exec — which GLib resolves to NULL — could be retained as healthy, keeping exactly the stale identity the rewrite exists to replace. Parsing is scoped to [Desktop Entry] now, with an action-only regression case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
92b1ee5d1b |
test(asr): parse the selector guard instead of grepping it (#1524)
* test(asr): parse the guard, don't grep it Two Majors CodeRabbit raised on #1523 — which I merged before reading them, so this is the follow-up rather than a fix on the branch. - Approved files were matched by BASENAME, so any future `<anything>/asr_backend.py` was exempt from the guard it exists to enforce. Matching is by relative path now; a decoy `backend/engines/asr_backend.py` calling the selector is caught. - Detection was a line regex, wrong in both directions: it missed `import get_active_asr_backend as pick` and fired on the name inside docstrings and comments. It walks the AST now, alias-aware, so only real calls count. Both verified by planting the exact bypasses: an aliased call in services/tts_backend.py and the decoy module above. Neither was caught before this change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(asr): resolve the selector's bindings before flagging a call CodeRabbit, #1524: matching any call named get_active_asr_backend also reported a local helper or an unrelated object's method that happens to share the name. False positives are how a guard stops being believed — people add allowlist entries for code that was never the bug. Bindings are resolved first now: a bare call counts only if the name was imported FROM services.asr_backend, an attribute call only if it hangs off a module alias for it. Six shapes are pinned in the suite — direct, aliased and module-attribute calls flagged; a same-named local function, an unrelated method, and the name inside a docstring not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9760f0c501 |
feat(ui): one support page, and tabs where there were toggles (#1522)
* feat(ui): one support page, and tabs where there were toggles Sponsor, commercial licence and contact were three destinations for one question — how do I support this / how do I reach these people — and each one made you leave to find the others. They are now three sections of a single page: support, licence, contact, in that order, separated by a hairline rather than more chrome. Every existing entry point still works; `initialView` scrolls to the right section instead of hiding the other two, so the footer heart, the dub/export commercial-licence links and Contact all land where they meant to. ContactPage becomes `ContactSections` — the body without the shell — and its "Support the project" CTA now scrolls up to the support section rather than navigating, because that surface is on the same page. Model Catalogue: the Engines/Models switch and the matrix's TTS/ASR/LLM switch are tabs, not Segmented. These pick between workspaces, not between the two states of one setting, and Tabs carries roving tabindex and role="tab" from the primitive. The matrix tabs keep their active engine chip and now keep their hover title too — Tabs passes `title` through. Tests: the pane/family switches are driven by pointer down, not click — Radix activates on pointer down, so a bare fireEvent.click leaves the pane unchanged and reads as a switcher that ignores itself. The contact suite now covers the section (its host owns the header), and asserts the support CTA scrolls without ever reaching for Ko-fi. Full frontend suite: 2022 passed. The one unhandled `window is not defined` rejection in the parallel run predates this change — same error, same count, on the base commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(engines): available engines first, and unavailable ones recede Two things the matrix got wrong for a list you pick FROM: it rendered in payload order, so a usable engine could sit under four you cannot select, and an unavailable row was faded WHOLE — which took its status badge and GPU chips down with it, the two things that say why it is unavailable. Available rows now sort to the top, preserving registration order inside each group (that order is meaningful — it puts the defaults first). The name of an unavailable engine recedes instead, and its mark dims with it; the evidence stays at full contrast. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(support): the footer heart returns you to the support section CodeRabbit, #1522: App.jsx renders SupportPage in the same tree position for donate / enterprise / contact, so React keeps ONE instance and only swaps props. The scroll effect treated 'support' as "already at the top" and returned early — correct for a fresh mount, wrong for the only way this page is actually reached. Clicking the footer heart from the contact section left you sitting on contact. Every view scrolls now. The regression test drives the prop change the way the router does and fails without the fix. Also adds the (#NNN) refs the Unreleased entries were missing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(support): cover the enterprise route's licence section CodeRabbit, #1522: the suite drove support and contact but not the third destination — and every section renders regardless, so only the scroll target proves the mapping. Uses the exact initialView App.jsx passes for mode === 'enterprise'. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
41a8a7b644 |
test(asr): guard the raw selector across the whole backend (#1523)
The recurrence guard from #1512 scanned api/routers only. A service or engine module that transcribes on a request's behalf skips ensure_loaded() just as thoroughly, so the guard could be sidestepped by moving the call one module down the stack — verified: adding a get_active_asr_backend() call to services/tts_backend.py passes the router scan and fails this one. The broader scan is the one thing #1519 did better than the fix that landed in #1515; absorbing it here rather than leaving it in a PR that now conflicts. Thanks @ahov520. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
41722afe3b |
refactor(launchpad): quieter, borderless design refresh (#1515)
* refactor(launchpad): quieter, borderless design refresh The launchpad carried decoration from an earlier direction: icon chips, corner-hung count badges, a permanently visible filled arrow, uppercase mono card titles, and a dotted stipple divider — plus a frame that had been invisible since the app-wide border tokens were zeroed. Rework it around what the borderless direction actually implies: - Feature tiles get a whisper-faint surface instead of a dead frame, and read as three bands (bare glyph + count / title + arrow / description). `--card-hue` is spent sparingly — the glyph at rest, the surface, count and arrow only once raised. Titles move to sans sentence case; counts are plain tabular numerals. Lift softened 4px -> 2px, coloured glow -> neutral shadow, plus an explicit focus ring and a staggered entrance. - Hero drops the boxed "646" pill and the filled A/B-Compare button for quiet type, with a hairline standing in for the separation. - Section labels trade the dotted stipple for a single fading hairline; rows are transparent until hover and reveal "Open" on hover/focus (it stays in the DOM, so AT and keyboard always reach it). - Hero, tiles, recent files, callout and project lists now share one 1180px column — previously only the top half was capped, so lists ran edge-to-edge on a wide display while the deck stayed centred. Two bugs found and fixed while doing it: - Buttons that had `border border-solid border-transparent` removed fell back to the UA default border and rendered a visible 1px outline. They now carry `border-0` explicitly. - `.lp-animate` used `animation-fill-mode: both`, so after the entrance it kept owning `transform` — and animation-origin declarations outrank normal ones, which silently killed the card hover lift. Now `backwards`, which still holds the from-state through the stagger delay. Also drops CSS the page has not rendered since #904: the cursor-spotlight layer, the breath ring, and the per-card waveform strip. Verified with headless renders at 1600/1280/940 and the empty state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(dictation): decode Wayland portal signals and show the capture pill The GlobalShortcuts portal declares Activated/Deactivated as (o session, s shortcut_id, t timestamp, a{sv} options). We decoded the timestamp as u32, so zbus rejected every signal with Signature mismatch: got `(osta{sv})`, expected `(osua{sv})` and the press was dropped as an invalid signal. Registration succeeded and the desktop even reported the bound chord back, so the hotkey looked wired up while doing nothing at all — on every Wayland compositor, for the whole life of the feature (#1490). Decode the 64-bit timestamp, and keep the 32-bit spelling as a fallback so a non-conforming portal degrades to working rather than to silence. With presses arriving, the second half of the failure showed: nothing had shown the widget window since it became a hidden recorder host, so a capture ran with no pill on screen — and a mic or Accessibility failure rendered into a window nobody could see. Add show_dictation_pill, which bottom-centres the capsule on the monitor under the pointer and shows it without taking focus (Windows keeps SW_SHOWNOACTIVATE so paste still lands in the user's document), and call it from the widget for every state but idle. Wayland denies clients their own placement, so the compositor picks the spot there; the pill still appears. dispatch_dictation_capture now logs whether a press was emitted or queued — a press that reaches Rust and produces nothing was otherwise indistinguishable from one the compositor never delivered. Tests: portal signals decode at both timestamp widths (the 64-bit case fails before this change with the exact production error); pill placement centres, respects a second monitor's origin, and clamps rather than going off-screen; the widget shows for a state needing the user, stays hidden while idle, and never shows for a press that arrives while dictation is disabled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: sync in-progress workspace changes Uncommitted work already in the tree, checkpointed so the branch matches the local machine: - Remote GPU workers: join-from-the-app flow, one-time secrets, QR join codes, a Compute control in the status bar, and the device-list Workers panel (#1516) - Model Catalogue workspace, with Settings pointing at it - Settings sidebar search and keyboard navigation - Demo assets for dubbing, dictation and voice design, plus the scripts that render them - Backend: validation-error handling, ASR request-path degradation, and the accompanying tests - CHANGELOG entries for the above and for the Wayland dictation fix Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(tests): follow Engines to the Model Catalogue, and green the sweep - test_supertonic3 asserted the license gate points at "Settings" while the engine now names Model Catalogue → Engines, which is where the accept button actually lives. The assertion follows the move; what it pins is unchanged — the hint must name a place the user can reach it. - Carries the CJK allowlist entries for the rendered dub bundle (#1517) and the regenerated route snapshot for /workers/agent (#1516), both of which this branch inherits from the workspace sync. - docs/install/linux.md: the dictation capsule is bottom-anchored everywhere except Wayland, where the protocol gives applications no say in their placement. Documented rather than left as a surprise (CodeRabbit). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: stop a flaky dependency fetch from failing green runs en-core-web-sm resolves to a direct GitHub release URL, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own three retries all land within the same few seconds and fail together, so the whole job dies on a dependency that has nothing to do with the change under test — it cost #1518 and #1517 an otherwise-green run tonight. Two changes: back off between whole `uv sync` attempts, which is what actually clears it, and pass --no-sync to the pytest steps. `uv run` re-resolves the environment before running, so every test step was a fresh chance to hit the same fetch even though the install step had already synced — that is exactly how #1518 failed, in the isolated backend/tests step, with all 5467 tests already passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: one retry seam for every uv sync, not just the job that failed last en-core-web-sm resolves to a direct GitHub *release* URL rather than a package index, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own retries all land inside the same ~10 seconds and fail together, so a job dies on a dependency unrelated to the change under test. Tonight that cost four otherwise-green runs across #1515, #1517 and #1518 — and the first fix only covered the Tests job, so the next failure simply moved to Smoke (Linux), which syncs separately. The fetch is per-job, so the fix has to be per-job: scripts/uv-sync-retry.sh backs off between whole attempts (15s, 45s, 90s) and every workflow that syncs now goes through it — ci.yml (tests + the platform matrix), release.yml, security.yml, evals.yml. It still fails loudly after four attempts, so a genuinely broken lockfile is not disguised as a flake. The Tests job also lacked the UV_HTTP_TIMEOUT / UV_HTTP_RETRIES the smoke matrix has always set, which is part of why it was the one that kept dying; it has them now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ci): pin the Intel-Mac contract by intent, not by command spelling test_ci_verifies_intel_mac_as_the_documented_remote_only_host asserted the literal line `run: uv sync --extra pockettts`, so routing every sync through scripts/uv-sync-retry.sh read as a broken Intel-Mac contract. The contract it exists to protect is that the pockettts extra installs ONLY on backend_supported legs — which the regex now pins, while leaving how the sync is invoked free to change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: keep every uv run out of the resolver, and bound the retry budget CodeRabbit, #1517: - `uv run` re-resolves before running, so the smoke suite, the worker-artifact tests, the release test run and the eval run were each a fresh chance to hit the flaky direct-URL fetch outside the retry loop. All of them pass --no-sync now; the environment is already synced by the step that owns the retries. security.yml's `uv run --with pip-audit` is deliberately left alone — it layers an ephemeral package rather than running the project's own tests. - The retry count multiplied uv's own budget (UV_HTTP_RETRIES=5 with a 120 s timeout on the smoke matrix). Three attempts and 60 s of total backoff outlast the refusals actually observed while staying well inside the jobs' timeout-minutes. - The Intel-Mac contract test pinned the smoke command literally too, so --no-sync tripped it exactly like the sync line did. Same fix: assert the contract (smoke runs only on backend_supported legs), not its spelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |