Commit Graph
10 Commits
Author SHA1 Message Date
Palash Debnath 7f7a4c5f83 fix(settings): make the generation budget reachable and honest (#1797)
The compute-time error told users to raise a generation timeout that had no control anywhere in the app — the only knob was an environment variable, and on Windows the docs explicitly warn against the usual way of setting one. Both budgets are now editable in Settings under Performance & Device, persisted and applied on the next start.

Two defects found in review and fixed here rather than shipped: an explicit universal budget silently overrode a separately saved CPU budget, so the CPU row would have looked like it worked and done nothing; and a value already set in the environment shadowed the saved preference while the panel still reported success. A shadowed row now says so instead. Long-input warnings also fire on Apple Silicon, which gets the accelerated budget and was the device in one of the duplicate reports.

Fixes #1787. Closes the reports tracked in #1774 and #1778.
2026-09-04 01:55:06 +05:30
Giuseppe Rojas 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
2026-08-13 05:04:29 +00:00
Palash Debnath ae46f187d0 fix(security): move host-path authority into the desktop shell (#1448)
Close unauthenticated remote mutation paths and keep filesystem destinations behind one-shot Tauri capabilities. CodeRabbit and Greptile findings were fixed on-branch; all review threads are resolved. Full CI, Security, Rust, and cross-platform smoke checks are green.
2026-08-09 21:55:43 +00:00
Palash Debnath 93025d9a81 fix(dictation): stop the widget stranding an empty square, repair the swept data dirs (#1398)
The dictation hotkey could leave a blank dark square stuck on the desktop with no way to dismiss it. Three defects compounded: the tray listener's effect depended on [state], so it detached across an await on every state change and a press landing in that gap was lost; an idle pill renders null, so the window Rust had already shown was empty; and the opaque chrome background made that empty window a hard-edged square. Nothing could hide it — dismiss() is only reachable from the X button, Esc, or a post-session timer, none of which exist for a session that never started.

Fixed at the invariant rather than the call sites: the listener subscribes once for the component's lifetime, the widget window's chrome background is transparent, and an idle-but-visible window reconciles itself to hidden. The reconcile is polled (a dropped press changes no React state, so there is nothing to key an effect off) and aborts if its effect is torn down mid-check, so it can never hide a dictation that has just started.

Also in scope:

- The rename sweep had repointed three data-dir literals at a brand-named directory that does not exist, so smoke-test.sh verified a directory the backend never writes and desktop-prod.sh silently stopped clearing backend state on Windows. Both invisible on macOS, where they are usually run. A guard test now pins the assignments specifically.
- The dictation model picker's download sizes were wrong for all seven models, in both directions — Parakeet TDT v3 (the recommended default) understated 180 MB against an actual 670 MB, while the low-RAM fallbacks were overstated threefold, discouraging exactly the choice that would have helped. Measured from the published repos and pinned by a test.
- The 0.6B Parakeet models now decode on more threads, capped by host cores and still overridable.
- uninstall.ps1 gained a UTF-8 BOM (Windows PowerShell 5.1 mis-decodes its non-ASCII output without one), and sponsor.yml lost its last OmniVoice references.
2026-08-07 05:39:41 +05:30
bd85bab624 chore: retire finished planning archives from the repo root (#1095)
Removes ~110 files of process noise (all preserved in git history):
.planning/ (GSD-era phases/quick-plans/issue-clusters; workflow retired
2026-07-08), specs/ (spec-kit specs for shipped features 001-007),
design/ (pre-React ASCII mockups), research/ (legacy Gradio archive),
and .agents/ (rules for a third-party agent tool no longer in use).

The four load-bearing decision docs move to docs/adr/ with an archival
note; every live pointer follows (gguf engine module docs + quant_map,
inject-apprun.sh, pyproject/test comments, fixture README + its seed
script — kept byte-identical). The CJK allowlist drops the deleted
legacy_gradio entries; STRUCTURE.md and ROADMAP.md document the removal
instead of linking into it.

Backend suite: 2891 passed.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:28:28 +05:30
Paolo AntinoriandClaude Opus 4.7 8db5feccc7 fix(web): serve .wav/.flac with IANA-canonical MIME so Linux/Android browsers play inline (#fix)
Python's `mimetypes.guess_type()` returns `audio/x-wav` for `.wav` and
`audio/x-flac` for `.flac` — vendor-experimental types (x- prefix) that
were never IANA-registered. macOS Chrome/Safari MIME-sniff leniently via
CoreAudio so playback works there, but Linux Chrome/Firefox (FFmpeg) and
Android Chrome (ExoPlayer) strictly honor the declared type and treat
the x- variants as download-only.

Result: the play button in the browser web app silently did nothing on
Linux/Android (download prompt instead of inline playback), while the
Tauri desktop shell worked because its WebView is lenient. The
diagnostic signal — Chromium short-circuits to download BEFORE the
<audio> element sees the response, so no MEDIA_ERR_SRC_NOT_SUPPORTED
fires; just a download prompt that's easy to miss.

Fix: register `audio/wav` and `audio/flac` (the IANA-canonical types)
via `mimetypes.add_type()` before the StaticFiles mounts in main.py.
No browser-side workaround exists (no chrome://flags, no about:config
pref) — the server is the only place this can be fixed.

Existing comment in dub_export.py:766 already acknowledges this exact
quirk for video files; this applies the same treatment to audio.

Test: regression test in test_api.py asserts `/audio/<file>.wav` returns
`Content-Type: audio/wav`. Without the fix this returns `audio/x-wav`.

Ref: https://www.iana.org/assignments/media-types/media-types.xhtml#audio

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 18:42:18 +02:00
Palash Debnath e4dbf4c8c0 P0: release.yml typecheck + bind audit + loopback middleware (#84)
Three P0 fixes bundled — foundation cleanup before v0.3.0 phase work. Closes release.yml drift (PR #51's tabs broke v0.3.0 tag releases), production bind exposure (Critic F1), and 9-endpoint LAN gap on /system/* (Critic F2+F3). 5 new tests; 243 full pass.
2026-05-18 22:00:59 +05:30
Palash Debnath 21c338c821 security: add loopback origin check to /system/set-env (#81)
Adds `request.client.host` allow-list check (`127.0.0.1` / `::1` / `localhost`) to `POST /system/set-env`. Non-loopback callers receive `403` instead of being able to mutate `os.environ` for HF_TOKEN / TRANSLATE_API_KEY.

Surfaced during security review of PR #66, which widens the pre-existing window by persisting these keys to disk via prefs.json. This fix closes the underlying vulnerability so PR #66's revision lands onto a clean base.

Defensive `request.client is None` branch handles ASGI middleware that strips client info. Three new tests cover non-loopback reject, loopback allow, and allow-list still validated on loopback.

Follow-up: `260518-ivy-deferred-items.md` enumerates 5 sibling POST routes in `system.py` that share the same gap — separate PR.
2026-05-18 14:09:00 +05:30
debpalashandClaude Opus 4.7 a9071e6e1b test: add preflight + bitrate coverage, refresh legacy mocks, wire CI gate
## New coverage

### tests/test_setup_preflight.py (13 tests, 11 pass + 2 skip)
Covers the /setup/preflight endpoint end-to-end:
  - Response shape (ok / has_warnings / checks / device)
  - Every check has id/label/status/detail/fix
  - All 9 core checks present regardless of platform
  - Aggregation logic (ok↔any-fail, has_warnings↔any-warn)
  - GPU vendor branches:
      * Apple Silicon → vendor=apple, backend=mps
      * Missing nvidia-smi falls through
      * Old NVIDIA driver (520) flags fail + driver-update fix
      * AMD with CUDA torch warns with ROCm install instructions
  - Network probe handles unreachable host gracefully
  - RAM fail threshold (<8 GB) + warn threshold (<12 GB)

Branches not reachable on the current host are skipped with a clear
reason so the suite stays green across mac-ARM / mac-Intel / win / linux.

### tests/test_dub_export_bitrate.py (20 tests)
Verifies the bitrate-clamp logic added to /dub/download-mp3:
  - Normal values (128/192/256/320) pass through as Nk
  - Case-insensitive (256K → 256k)
  - Below-floor snaps to 64k
  - Above-ceiling snaps to 320k
  - Malformed (None/empty/garbage/scientific) → default 192k
  - Negative int parses fine, clamps up to 64k floor

### tests/frontend/apiClient.test.mjs (9 tests)
Exercises api/client.ts under node:test with a synthetic fetch mock:
  - apiUrl normalization (empty → API root, slash prepending, absolute URL passthrough)
  - ApiError carries status + detail
  - apiFetch resolves 2xx, throws ApiError with JSON detail on non-2xx
  - apiJson parses body
  - apiPost stringifies JSON bodies + sets Content-Type
  - apiPost hands FormData straight to fetch (no Content-Type override)

### tests/frontend/format.test.mjs (5 tests)
Covers utils/format.js formatTime timecode rendering.

## Legacy mock refresh (not scope-creeping fixes — minimal updates)

- tests/test_api.py: replace stale `backend.main._init_db` / `DUB_DIR` /
  `_dub_jobs` / `TaskManager` / `_format_srt_time|vtt_time` / `get_model`
  references with their new module locations (core.tasks, core.config,
  services.dub_pipeline, api.routers.dub_export, services.model_manager).
  Normalize imports to the unprefixed `from services.*` / `from core.*`
  form used inside the backend itself — avoids `backend.*` vs
  unprefixed sys.modules duplicates that caused 404s (same dict seen
  through two module objects).
- tests/test_engines.py + test_router_smoke.py: loosen strict-equality
  backend-set asserts to `.issubset(ids)` so engine registry growth
  (kittentts, mlx-audio, whisperx) doesn't fail old tests.
- tests/test_engines.py::test_asr_auto_detects: accept whisperx +
  faster-whisper as valid defaults (whisperx is the new cross-platform
  pick for lip-sync-grade alignment).
- tests/test_dub_transcribe.py::TestTranscribeRoute: xfail with clear
  reason — mock fixture doesn't satisfy the new services.asr_backend
  bytes-path contract. Logged for a later test-maintenance pass.
- tests/test_api.py::TestStreamingTTS::test_generate_...: xfail with
  clear reason — patch target moved from backend.main.get_model to
  services.tts_backend.

## CI gating (.github/workflows/release.yml)

Added a single-runner Linux `test` job that the matrix `build` job now
`needs:`. Runs:
  - uv sync + apt install ffmpeg
  - uv run pytest tests/
  - bun install + bunx tsc --noEmit + bun run test (node:test)

Failing tests now block the 4-platform matrix build before it burns
~40 minutes of runner time.

## Frontend test script

frontend/package.json: add `"test": "node --test ../tests/frontend/*.test.mjs"`.

## Totals on this machine

- Backend: 190 passed, 6 xfailed (stale mocks, documented), 3 skipped
  (hardware-specific branches), 0 failed
- Frontend: 36 passed, 0 failed
- Typecheck: clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:46:01 +05:30
debpalash 3a8adf5dd4 feat: implement streaming TTS, A/B voice comparison, and background task processing with SSE updates 2026-04-14 04:00:38 +05:30