Commit Graph
2369 Commits
Author SHA1 Message Date
Palash Debnath 5b842280c8 Merge pull request #1997 from debpalash/fix/1931-blackwell-docs
docs+test: finish the RTX 50-series story (#1931)
2026-09-10 00:56:32 -07:00
Palash Debnath 770fe35da9 Merge pull request #1995 from debpalash/fix/1927-crash-hint
fix(crash): say what the exit code means in the crash details (#1927)
2026-09-10 00:36:16 -07:00
Palash Debnath 46b1fb2f57 Merge remote-tracking branch 'origin/main' into fix/1927-crash-hint
# Conflicts:
#	CHANGELOG.md
2026-09-10 00:05:39 -07:00
Palash Debnath 924172e012 Merge pull request #1986 from debpalash/fix/1960-name-the-language
fix(dub): name the source language code that was rejected
2026-09-10 00:04:53 -07:00
Palash DebnathandClaude Opus 5 14d6b90836 docs+test: finish the RTX 50-series story (#1931)
The code half of #1931 landed already: `torchaudio.set_audio_backend()` is
guarded, so the torch 2.9.x upgrade a Blackwell card needs no longer trades one
`ml_imports` crash for another. Two things were still missing.

The changelog said the upgrade was documented. It was not — nothing in docs/
mentions sm_120, Blackwell, or the 50-series at all, so a user hitting a native
access violation inside `import torch` had the issue thread and nothing else.
troubleshooting.md now carries it: why the pinned torch 2.8.0 cannot work
(no sm_120 kernels in the wheel — not a setting, not a workaround), the trio
that has to move together, the verification command that proves the kernels
arrived, and the fact that the change is to the repo's own pins so a later pull
will undo it.

The part most likely to be missed is that there are TWO pin lists.
`constraint-dependencies` governs `uv sync`/`uv lock`/`uv run`;
deploy/torch-constraints.txt governs the `uv pip install` paths, which ignore
project-level uv settings. Editing one leaves the other behind, which is what
`RuntimeError: operator torchvision::nms does not exist` looks like from the
outside. Both are named.

The second gap: nothing protected the guard. CI runs the pinned torch 2.8.0,
where `set_audio_backend` still exists, so deleting the `hasattr` as a
"simplify this no-op" cleanup would pass every test in the suite and restore a
hard startup crash for every RTX 50-series user. tests/ now walks the backend
AST and fails on any reach for a torchaudio API that 2.9 removed unless
something proves it is there — a `hasattr`/`getattr` check or a `try`. Fails
with the guard removed, passes with it.

Not addressed here, because it is already fixed: the reporter's third
observation, that launching through the desktop shell hung inside `import
torch`'s native init, is the OpenBLAS/stdin-pipe deadlock closed under #1952.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-10 00:03:56 -07:00
Palash Debnath 2bf922d9e5 Merge pull request #1993 from debpalash/land/1987-followup
docs(docker): finish the ARM64 Compose guidance (#1987)
2026-09-10 00:00:11 -07:00
Palash Debnath d52a4c89a7 Merge pull request #1991 from debpalash/fix/windows-symlink-tests
test: let a stock Windows checkout run the symlink tests (#1990)
2026-09-09 23:59:58 -07:00
Palash DebnathandClaude Opus 5 f67e5da289 fix(crash): say what the exit code means in the crash details (#1927)
The report in #1927 is a Windows access violation 13 seconds after "Loading
VoiceStudio model on device: cuda" — a native fault inside the compute stack,
which produces no Python traceback because the process is executing bad machine
code. What the user was shown was "Backend died (exit code -1073741819)", a
timestamp, an uptime, and a log ending mid-startup. The issue they filed has an
empty description, which is the honest response to being handed a number and no
next step.

The classification already existed and is good: `crashCauseHint` distinguishes
a native fault (a GPU driver disagreeing with the bundled CUDA runtime, or a
partially downloaded weight file), an exit 78 port conflict, an OOM kill and a
half-built Python environment, and names concrete actions including the
crash-isolated engines. It just never reached this surface — the only place it
rendered was the message on a stream dropped by a crash, and a crash with no
request in flight has no stream to drop.

So the details dialog renders it. A sentinel marker is deliberately excluded:
it cannot know a crash happened at all (sleep, force-quit and a stopped VM
leave the same trace), so it has no cause to explain, and asserting one would
be the #1375 fabrication in a new place.

Three tests: the access violation gets the compute-stack guidance, a port
conflict gets its own rather than the GPU one, and a sentinel gets none. The
first two fail against the previous component.

2853 vitest tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 23:51:50 -07:00
Palash Debnath b945ddd163 Merge remote-tracking branch 'origin/main' into tmp/1986
# Conflicts:
#	CHANGELOG.md
2026-09-09 23:48:36 -07:00
Palash Debnath 525a2809eb Merge pull request #1989 from debpalash/land/1981-structure
docs: refresh STRUCTURE.md and pin its counts to the tree (#1981)
2026-09-09 23:48:11 -07:00
Palash DebnathandClaude Opus 5 78a6c489c9 docs(docker): finish the ARM64 Compose guidance (#1987)
@yangfan-yf-yf pushed two more commits to #1987 after the first pass landed.
Two things in them were worth taking:

  - an explicit `compose pull` step, so the platform override is proven before
    `up -d` rather than discovered when the pull inside it fails; and
  - a PowerShell form. An ARM64 Windows host cannot use `export`, and the
    surrounding page only ever shows Bash — so the guidance did not actually
    reach the users most likely to need it.

Not taken: the same commits also moved `--platform linux/amd64` into the
default `docker pull` / `docker run` quick start. That is a no-op for the
amd64 majority and contradicts the Architecture section directly above, which
introduces the flag as the conditional ARM64 step. The canonical command stays
the one almost everyone should run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 23:41:44 -07:00
Palash Debnath 5e4f863c4d Merge pull request #1988 from debpalash/land/1987-docker-arch
docs(docker): document the amd64-only images and the ARM64 workaround (#1987)
2026-09-09 23:39:41 -07:00
Palash Debnath 12794f3dae Merge pull request #1985 from debpalash/fix/1773-classic-error-class
fix(errors): carry the backend error class onto a classic 500
2026-09-09 23:39:30 -07:00
Palash Debnath 744a0550d3 Merge remote-tracking branch 'origin/main' into tmp/1986
# Conflicts:
#	CHANGELOG.md
2026-09-09 23:25:50 -07:00
Palash DebnathandClaude Opus 5 56e9e42ac2 test: let a stock Windows checkout run the symlink tests (#1990)
Creating a symlink on Windows needs SeCreateSymbolicLinkPrivilege, which a
normal account does not hold unless Developer Mode is on. GitHub's hosted
Windows runners hold it, so seven unguarded call sites passed in CI and failed
only on a contributor's own machine, with WinError 1314 and no connection to
whatever they were working on:

  tests/backend/services/test_audiocpp_backend.py  (5)
  tests/test_exports_api.py                        (1)
  tests/test_storage_report.py                     (1)

The repo already knew about this — tests/test_hf_cache_repair.py carries a
private _symlink_or_skip helper whose docstring describes exactly this failure.
The pattern simply never reached the other files, which is the whole class of
the bug: a convention that lives in one module's private helper gets rewritten
from scratch, or forgotten, at every new call site.

So the helper is now a `symlink_or_skip` fixture in tests/conftest.py, and
tests/test_symlink_guards.py walks the AST of every test module and fails on a
raw symlink_to / os.symlink that has no way to skip. Guarded means the fixture,
a try, a skipif marker (module-level pytestmark included), or a test that has
already run a skipping helper — the three legitimate existing patterns, which
it recognises rather than forcing a rewrite.

Coverage is unchanged: the full pytest job runs on Linux, where nothing skips.
Fails before (7 errors, then the guard reports the offending files), passes
after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 23:25:16 -07:00
Palash Debnath c2f27c39d2 Merge remote-tracking branch 'origin/main' into tmp/1985
# Conflicts:
#	CHANGELOG.md
2026-09-09 23:24:18 -07:00
Palash Debnath 60b696f37f Merge pull request #1984 from debpalash/fix/1949-phoneme-visible
fix(pronunciation): say when an IPA/CMU entry is stored but not applied
2026-09-09 23:23:18 -07:00
Palash DebnathandClaude Opus 5 5237f7849a docs: keep STRUCTURE.md's README annotation in English
tests/test_no_hardcoded_cjk.py rejects CJK outside frontend/src/i18n/, and the
refreshed tree annotated README_CN.md with the characters themselves. The file
name already says which language it is; the annotation does not need to be in
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 23:22:58 -07:00
Palash DebnathandClaude Opus 5 a5f4d61eda docs: keep STRUCTURE.md's counts honest with a test
Lands #1981 by @Dawcraft, which refreshes docs/STRUCTURE.md to match the tree
as it actually is — the old file still described a root-level layout that the
2026-07-12 cleanup removed, and pointed at a tests/services/ mirror that has
not existed since the tests/backend/ reorganisation.

Verified every path, directory and CI claim in the refreshed file against the
repo: the router auto-include list, the isolated backend/tests/ pytest step,
the smoke-matrix job and its HF_HUB_OFFLINE guard, and every file the tree
names. One number was off — backend/services/ holds 78 modules, not 79.

Off-by-one in a doc is the symptom; the class is a count nothing checks, which
is wrong the week after it is written. tests/test_structure_doc.py now pins
the router count, the service count and the engine-adapter list to the tree,
so the next module to land fails the suite with the line to update instead of
quietly aging the doc. Fails before the fix (79 != 78), passes after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 23:06:59 -07:00
Palash Debnath c7d0e314d9 Merge branch 'pr/1981' into land/1981-structure 2026-09-09 23:03:14 -07:00
Palash DebnathandClaude Opus 5 c4f214858b docs(docker): cover Compose in the ARM64 guidance
Lands #1987 by @yangfan-yf-yf, which closes #1921.

The published images are linux/amd64 only, and the quick start reached image
resolution before saying so — an ARM64 user met "no matching manifest for
linux/arm64/v8" with no explanation. Verified against docker.yml, which says
so in its own comment: "only building linux/amd64".

One gap in the original: the platform override was documented for docker pull
and docker run, but Compose has no per-command --platform flag, so the
recommended Compose command still resolved the missing ARM64 manifest and
failed exactly as before. DOCKER_DEFAULT_PLATFORM covers it, with the same
caveat the rest of the section makes — emulation, not native support, and only
the CPU profile makes sense under it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 23:00:44 -07:00
Palash Debnath 7aa2333823 Merge remote-tracking branch 'origin/pr/1987' into land/1987-docker-arch 2026-09-09 23:00:14 -07:00
Palash Debnath fe012ad3a5 Merge remote-tracking branch 'origin/main' into fix/1960-name-the-language
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:58:31 -07:00
Palash Debnath 1b7e858886 Merge remote-tracking branch 'origin/main' into fix/1773-classic-error-class
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:58:26 -07:00
Palash Debnath fd74865f7c Merge remote-tracking branch 'origin/main' into fix/1949-phoneme-visible
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:58:22 -07:00
Palash Debnath 0f237a9b25 Merge pull request #1983 from debpalash/fix/1847-bootstrap-log-file
fix(bootstrap): keep the first-run install log after setup finishes
2026-09-09 22:57:53 -07:00
Yang Fan ae25a6a594 docs: clarify Docker image architecture requirements 2026-09-10 13:51:58 +08:00
Palash DebnathandClaude Opus 5 aa984075a7 test(dub): pair each rejected code with its own response
The two requests in this case send different bad codes; asserting one string
against both bodies passed on whichever happened to match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 22:46:12 -07:00
Palash DebnathandClaude Opus 5 80e0b91ce8 test(dub): assert the substance of the rejection, not its exact wording
The existing case pinned the literal string "Invalid source language code",
which the #1960 fix replaces with a message that names the offending code. It
now asserts what the test is actually about — a 400 that identifies the code —
so improving the guidance again does not fail it for the wrong reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 22:44:48 -07:00
Palash DebnathandClaude Opus 5 d1e4c847cb fix(dub): name the source language code that was rejected
Closes #1960.

The report was "400 Bad Request: Invalid source language code" and nothing
else. That cannot be acted on or triaged: it does not say which of the ninety
or so codes was wrong, so neither the user nor a maintainer reading the
auto-filed issue can tell whether the picker offered something the backend does
not accept, or a stale preference from an older build is still being sent.

I could not determine the cause from the report, which is exactly the problem.
Naming the code makes the next one answerable instead of guessing at this one.

The value is a language code chosen from a menu, not private data, and the
engine validator a few lines away already echoes its input the same way.

Also adds the check I actually wanted while investigating: a test that reads
the picker's own LANG_CODES and asserts the backend accepts every one of them,
so a code added to the menu cannot silently become a 400. It passes today —
the menu and the allowlist do agree — which is how I ruled that out as the
cause rather than assuming it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 22:42:27 -07:00
Palash Debnath 91515310f0 Merge remote-tracking branch 'origin/main' into fix/1773-classic-error-class
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:40:35 -07:00
Palash Debnath 6087085d43 Merge remote-tracking branch 'origin/main' into fix/1949-phoneme-visible
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:40:30 -07:00
Palash Debnath 2f66e95958 Merge remote-tracking branch 'origin/main' into fix/1847-bootstrap-log-file
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:40:26 -07:00
Palash Debnath f905230fd8 Merge pull request #1982 from debpalash/fix/1974-dev-port-ownership
fix(dev): reclaim the port from a backend the app itself left running
2026-09-09 22:39:58 -07:00
Palash DebnathandClaude Opus 5 b6ed598770 fix(errors): carry the backend error class onto a classic 500
Closes #1773.

The 500 handler has always put error_class in the response body, but nothing
lifted it onto the Error object — and the auto bug reporter reads the Error. So
every unclassified 500 filed "VoiceStudio hit an internal error; check the
backend log for details." and nothing else: identical reports, none of them
triageable, with the distinguishing datum sitting unused in the payload that
produced them.

#1956 did exactly this for the streaming path. The classic path had been
carrying the field on the wire the whole time; it just never survived the hop
onto the exception.

Only a string is kept. A 404 or a validation error has no class, and an empty
one would put a blank line in every report; a non-string is ignored rather than
stringified. Both pinned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 22:25:56 -07:00
Palash Debnath ac09da3c24 Merge remote-tracking branch 'origin/main' into fix/1949-phoneme-visible
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:22:37 -07:00
Palash Debnath ef8636ee78 Merge remote-tracking branch 'origin/main' into fix/1847-bootstrap-log-file
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:22:31 -07:00
Palash Debnath 72249a8d8e Merge remote-tracking branch 'origin/main' into fix/1974-dev-port-ownership
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:22:26 -07:00
Palash Debnath 8e2928ca56 Merge pull request #1980 from debpalash/fix/1858-shortcut-registration
fix(dictation): say when another app already owns the shortcut
2026-09-09 22:22:01 -07:00
Palash DebnathandClaude Opus 5 de962d164a fix(pronunciation): say when an IPA/CMU entry is stored but not applied
Closes #1949.

Settings offers three notations. Only Respelling substitutes text today; IPA
and CMU rows save cleanly, are validated, get a badge and can be toggled on,
then get dropped before term matching and are never read again.

That much is Phase 1 behaving as designed. The defect is that it was INVISIBLE:
"Test a sentence" answered "No entries match — spoken as written" for a term
that does match. Not a degraded answer, a wrong one — and it sent the user off
to re-type an entry that was already correct, or to convert it to Respelling,
where a phoneme string is then read as graphemes.

docs/specs/01-expressive-tts.md asked for exactly the opposite: such entries
"passed through and flagged 'phoneme not honored on this engine' (parity-rule:
visible degradation)". That flag was never implemented. This is it.

The dry run reports inert entries separately, and the panel names them. The
substitution path is deliberately untouched — this does NOT start feeding raw
phoneme strings into the grapheme stream, which is the thing Phase 1 refuses on
purpose, and a test pins that it still refuses.

Not Phase 2. Lowering IPA/CMU to engine markup is a real feature per engine and
stays open; what changes here is that the gap is now honest rather than silent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 22:11:05 -07:00
Palash Debnath 3d76f1ca26 Merge remote-tracking branch 'origin/main' into fix/1847-bootstrap-log-file
# Conflicts:
#	CHANGELOG.md
#	frontend/src-tauri/src/bootstrap.rs
2026-09-09 22:06:20 -07:00
Palash Debnath 6203fe41e9 Merge remote-tracking branch 'origin/main' into fix/1974-dev-port-ownership
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:04:53 -07:00
Palash Debnath 8e87755845 Merge remote-tracking branch 'origin/main' into fix/1858-shortcut-registration
# Conflicts:
#	CHANGELOG.md
2026-09-09 22:04:48 -07:00
Palash Debnath 3013cdc241 Merge pull request #1979 from debpalash/fix/1898-windows-quit
fix(windows): stop reporting every deliberate quit as a crash
2026-09-09 22:04:19 -07:00
Palash DebnathandClaude Opus 5 939ed4b12d fix(bootstrap): keep the first-run install log after setup finishes
Closes #1847.

The splash is the only surface with a Show/Copy affordance for these lines, and
it unmounts the moment the stage flips to ready — so on a successful first run
the whole install log was gone for good, with no completion pause and nowhere
to retrieve it. A user who wanted to check what had just been installed, or
attach it to a bug report, had nothing.

The lines are written to bootstrap.log beside backend.log now, so everything
about a run is in one directory and a bug report does not have to hunt in two.

Truncated once per process rather than appended forever: a bootstrap is a
single episode and the useful question is always "what happened this time".
That also bounds the file across repeated retries without needing a hook on
every restart path. The docs say so, and say to copy it first if you need a
superseded attempt.

Best effort throughout — a log that cannot be written must never take the
bootstrap down with it, and a test pins that it does not.

The counter half of this issue (Activity frozen at 200) was already fixed on
main by #1918; I verified that before starting rather than assuming the whole
issue was open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 21:48:53 -07:00
Palash Debnath 9e768c9552 Merge remote-tracking branch 'origin/main' into fix/1974-dev-port-ownership
# Conflicts:
#	CHANGELOG.md
2026-09-09 21:45:27 -07:00
Palash Debnath 18d2d1e076 Merge remote-tracking branch 'origin/main' into fix/1858-shortcut-registration
# Conflicts:
#	CHANGELOG.md
2026-09-09 21:45:22 -07:00
Palash Debnath 8d0b826c92 Merge remote-tracking branch 'origin/main' into fix/1898-windows-quit
# Conflicts:
#	CHANGELOG.md
2026-09-09 21:45:17 -07:00
Palash Debnath 939bdc7248 Merge pull request #1978 from debpalash/fix/1857-reduced-motion
feat(a11y): add an in-app Reduce motion switch
2026-09-09 21:44:50 -07:00
DawcraftandClaude Opus 5 71d4114354 docs: correct the CI and mirror-path claims in STRUCTURE.md
Both points from the review are right:

- The three test homes do not each get their own CI job. `ci.yml` runs all
  three as steps of the single `test` job (`Run pytest`, `Run pytest
  (backend/tests, isolated)`, `Run Vitest`); what makes `backend/tests/`
  separate is the pytest session, not the job.
- `tests/backend/services/test_dub_pipeline*.py` does not exist — that
  regression test is flat, at `tests/backend/test_dub_pipeline_wav.py`.
  The mirroring example now uses a path that exists
  (`backend/services/ffmpeg_utils.py` ->
  `tests/backend/services/test_ffmpeg_utils.py`) and says that
  backend-wide and cross-cutting suites stay flat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LDyC6prbjFydox9XQGhyny
2026-09-10 13:32:07 +09:00