Commit Graph
51 Commits
Author SHA1 Message Date
Palash Debnath 353cc40fdc Merge remote-tracking branch 'origin/main' into fix/1845-setup-pill
# Conflicts:
#	CHANGELOG.md
2026-09-09 14:45:52 -07:00
Palash Debnath e5a0a553e9 Merge remote-tracking branch 'origin/main' into fix/1845-setup-pill
# Conflicts:
#	CHANGELOG.md
2026-09-09 14:27:16 -07:00
Palash Debnath b7eb5428c7 Merge remote-tracking branch 'origin/main' into fix/1856-dictation-step
# Conflicts:
#	CHANGELOG.md
2026-09-09 14:27:12 -07:00
Palash DebnathandClaude Opus 5 accef57865 fix(dictation): stop the Accessibility prompt owning the screen indefinitely
Closes #1845. Closes #1886.

The widget window is created always-on-top, and the setup state had no time
limit at all. On a clean macOS install the pill sat over the first-run setup
window — covering the disk-space line and the Start installation button — and
over every other application, until Accessibility was granted or the user
dismissed it by hand. There was no cap and no safety net: the stranded-pill
reconcile only runs while idle, and this state is not idle.

A permission the user has not granted yet does not outrank what they are
actually doing, and mid-setup they usually cannot grant it yet anyway. The
prompt now gets a bounded claim on the screen and then steps aside.

Polling deliberately continues after the window hides, so granting
Accessibility later still returns the widget to idle on its own — what expires
is the pill's claim on the screen, not the reconciliation. The hide is latched
so it fires once rather than fighting anything that legitimately shows the
window again; both properties have a test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 14:07:53 -07:00
Palash Debnath d07fa1ffff Merge remote-tracking branch 'origin/main' into fix/1957-untrusted-mount
# Conflicts:
#	CHANGELOG.md
2026-09-09 14:04:34 -07:00
Palash DebnathandClaude Opus 5 d313045b13 fix(setup): stop the last onboarding step failing three times with no model
Closes #1856.

The mandatory-only install path ships no speech-to-text model, and the
dictation step rendered its three script cards regardless. Every card came up
red with "No speech-to-text model is installed", and the step's own copy
invited the user to press the hotkey or hit Replay, neither of which can
transcribe anything. That is the final screen of first-run setup, so the last
thing a new user saw was three failures they were told to cause.

The step now checks readiness the same way the component already checks for
its bundled sample WAVs, and when no model is installed it offers the model
chooser in place of the cards — the same picker the Transcriptions page uses,
so the user installs one and continues rather than reading an error three
times. A model already on disk can be selected without a download.

`checking` deliberately keeps the cards: the probe resolves in well under a
second, and flashing the install panel first would be worse than the wait.
A test pins that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 14:03:03 -07:00
Palash DebnathandClaude Opus 5 fd7d2d94dd fix(errors): explain a Windows untrusted-mount failure instead of echoing WinError 448
Closes #1957.

A download failed with nothing but the OS sentence: "[WinError 448] The path
cannot be traversed because it contains an untrusted mount point". That is a
Windows rule about the VOLUME — Dev Drives, mounted VHD/ReFS volumes and
junctions into another user profile all trigger it — so retrying the same link
can never work, and the message names nothing the user can change.

Classified now, with a remedy that points at Settings → Storage and gives the
fsutil escape hatch for a folder that has to stay put. Matched on the numeric
code first, since Windows translates the sentence, with the English phrase as a
fallback. Allowlisted for context-free surfaces because it arrives through the
global 500 handler, which otherwise attaches no hint at all — and its trigger
is unmistakable, so it cannot land on an unrelated failure.

A test pins that the offending path never comes back in the payload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 13:47:51 -07:00
Palash Debnath 1c37c479be Merge remote-tracking branch 'origin/main' into land/queue2
# Conflicts:
#	CHANGELOG.md
2026-09-09 13:43:21 -07:00
Palash DebnathandClaude Opus 5 b55098f044 fix: repair the Colab cell edit and point the clear test at the split resolver
Two CI failures, both mine to fix.

The warning I added to the Colab ASR cell used \n escapes inside the notebook
JSON, and they landed as real newlines, so the cell's Python had an
unterminated string and tests/test_colab_asr_setup.py could not exec it. The
block prints line by line now, with no escapes to get wrong.

test_tauri_log_clear_reports_truncate_failure patched _tauri_log_candidates,
but #1925 moved Clear onto _tauri_plugin_log_candidates, so the patch no longer
reached the code under test and the real resolver was consulted instead. It
passed on a machine with a shell log on disk and failed on a clean runner.
Patches both halves, matching the fixture in test_tauri_log_clear.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 13:43:17 -07:00
Palash DebnathandClaude Opus 5 7d46563ea8 fix(i18n): finish the zh-CN locale and make its own parity suite pass
#1877 completes the zh-CN translation and drops its ratchet to zero, but the
PR never ran today's gates — it has been conflicting, so CI reported nothing —
and the file it lands does not pass tests/test_locale_parity.py.

Three things fixed here:

- Twelve keys were declared twice inside the same object (timing_concise,
  autofit_quality, the plan_* set, the role_* set). Python's parser rejects a
  duplicate key outright, so the whole suite errored rather than failing one
  assertion. Deduped keeping the first occurrence, which is the block #1877
  actually translated.
- The `player` section appeared twice: the complete new one and an older
  two-key stub. JSON keeps the LAST, so the stub silently won and six keys
  vanished at runtime. The stub is gone.
- `settings.hf_source_*_label` appeared twice with slightly different wording.

The file is rewritten as canonical JSON (indent 2, non-ASCII preserved), which
is byte-identical to how en.json already serialises, so the format matches the
other locales exactly. zh-CN now has zero keys missing and zero beyond en.

Also fixes the review finding on #1959: the capture route picks its engine from
a `mode` form field, not an `accurate` flag, so parametrising on `accurate`
sent a field the route ignores and ran the default fast path twice. Both
engines are exercised now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 13:24:06 -07:00
Palash Debnath b6143cce08 Merge remote-tracking branch 'origin/pr/1959' into land/queue3 2026-09-09 13:18:49 -07:00
Palash Debnath 512aad5c48 Merge remote-tracking branch 'origin/main' into land/1952
# Conflicts:
#	frontend/src/pages/AudiobookTab.jsx
2026-09-09 13:17:13 -07:00
Palash Debnath 8526ff56f7 Merge remote-tracking branch 'origin/main' into land/queue2
# Conflicts:
#	CHANGELOG.md
2026-09-09 13:15:23 -07:00
Palash DebnathandClaude Opus 5 df7dab43b6 fix(i18n): keep the China-mirror comments out of the CJK guard
tests/test_no_hardcoded_cjk.py fails on any non-English text outside the
translation layer, allowlist aside, and #1892 put the mirror region's Chinese
label into two Rust doc comments. The comments only quote what the UI shows, so
naming the region in English says the same thing and keeps the guard green
without widening the allowlist for a comment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 13:12:22 -07:00
Palash DebnathandClaude Opus 5 f48a33edcf fix: address the review findings on the PRs landed here
Bot review raised one blocking and several real findings across these PRs.
Each is fixed here rather than merged and followed up.

#1892 — apply_pypi_index_env() now runs for EVERY uv invocation, and with the
default region "auto" it called the UNCACHED auto_detect_region(), racing two
live network probes with a 4s timeout per uv call. On a blocked or offline
network that is a repeated multi-second stall, and it multiplies the outbound
calls a local-first app makes unasked. The probe is memoised for the life of
the process. It also now clears UV_INDEX_URL before setting it, so a stale
ambient value cannot outrank the region the user picked.

#1925 — backend.rs trimmed OMNIVOICE_LOG_DIR for its emptiness guard but built
the path from the RAW value, while the Python reader strips it. A padded value
therefore had the writer and the reader looking at different directories, which
is the divergence the PR exists to close.

#1920 — the rotation walk caught bare OSError, so a PermissionError or a real
I/O failure was swallowed and the panel silently rendered less. Only the race
the guard exists for (a file that rolled away, and on Windows the handler's own
sharing violation) is skipped now; anything else surfaces.

#1951 — the fix was right but shipped no tests and no changelog entry. Both
added, including a case pinning that the wizard preflight and the diagnostic
route the same host the same way, since they carry separate copies of the
branch.

#1923 — the cell hardcodes the CTranslate2 model, but if the cuDNN 8 step
failed the backend falls back to PyTorch Whisper and downloads a second
multi-gigabyte model. The cell now says so while the download it just spent is
still on screen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 13:09:12 -07:00
Palash DebnathandClaude Opus 5 791a46d9dd fix(errors): match WebKit's space-labelled frames so the filter works on Safari
The frame matcher required non-whitespace from line start up to the `@`,
which is right for rejecting a V8 header posing as a frame but wrong for
JSC: it labels top-level frames `global code@url`, `eval code@url` and
`module code@url`. Those are exactly the frames an injected extension
script throws from, so on WKWebView — the macOS desktop shell — and Safari
no frame matched, the origin came back unknown, and the extension's error
still offered "Report this bug". #1901 was fixed on Chromium only.

The three labels are enumerated rather than allowing spaces generally, so
the header false positive the anchoring exists for stays closed; a test
pins that. Each WebKit case uses a distinct message because shouldShow()
throttles by message text and a shared one would pass on the throttle
instead of the frame match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 13:02:24 -07:00
Palash Debnath 61eb27dbba Merge remote-tracking branch 'origin/pr/1892' into land/queue2 2026-09-09 13:02:15 -07:00
Palash Debnath 19d371f9d6 Merge remote-tracking branch 'origin/pr/1951' into land/queue2 2026-09-09 13:02:15 -07:00
Palash Debnath c760347339 Merge remote-tracking branch 'origin/pr/1925' into land/queue2
# Conflicts:
#	CHANGELOG.md
#	backend/api/routers/system.py
2026-09-09 13:02:15 -07:00
Palash Debnath ee8a8fb928 Merge remote-tracking branch 'origin/pr/1924' into land/queue2
# Conflicts:
#	CHANGELOG.md
2026-09-09 13:01:46 -07:00
Palash Debnath d7177be66c Merge remote-tracking branch 'origin/pr/1923' into land/queue2
# Conflicts:
#	CHANGELOG.md
2026-09-09 13:01:46 -07:00
Palash Debnath 88e59326fd Merge remote-tracking branch 'origin/pr/1920' into land/queue2
# Conflicts:
#	CHANGELOG.md
2026-09-09 13:01:45 -07:00
Palash Debnath f02880cc5c Merge remote-tracking branch 'origin/pr/1897' into land/queue2
# Conflicts:
#	CHANGELOG.md
2026-09-09 13:01:45 -07:00
Palash Debnath f9ff185a22 Merge remote-tracking branch 'origin/main' into land/1952
# Conflicts:
#	CHANGELOG.md
2026-09-09 12:58:49 -07:00
Palash Debnath cc96de27b8 Merge remote-tracking branch 'origin/main' into land/queue1 2026-09-09 12:58:32 -07:00
Palash DebnathandClaude Opus 5 e8af4fae12 fix(engines): give audiocpp its docs link and pin docs_url in the registry shape
#1917 adds a Learn more link to the unavailable-engine row, driven by
_ENGINE_DOCS, and a guard that every registered engine has a doc page. It was
written before audiocpp landed on main, so against today's main the guard
failed on audiocpp and the registry shape test failed on the new docs_url key
— PR-green under an older base, main-red on merge.

docs/engines/audio-cpp.md already existed; only the id-to-path mapping was
missing. The shape test now expects docs_url, with a note pointing at the
guard so the next engine added without a doc fails loudly rather than
quietly dropping its link.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 12:58:29 -07:00
Palash Debnath 9ffc9d52d7 Merge remote-tracking branch 'origin/main' into land/1952
# Conflicts:
#	CHANGELOG.md
#	bun.lock
#	frontend/package.json
#	frontend/src/components/CaptureWidget.jsx
#	frontend/src/pages/Transcriptions.jsx
2026-09-09 12:31:06 -07:00
Palash Debnath 40199e39cf Merge remote-tracking branch 'origin/main' into work/1955
# Conflicts:
#	CHANGELOG.md
2026-09-09 12:29:36 -07:00
Palash DebnathandClaude Opus 5 c21696b9ae style: format the two files #1930 left unformatted
`bun run format:check` is a CI gate and SetupWizard.jsx plus its test came in
unformatted. No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 12:28:05 -07:00
Palash Debnath 67817b89bf Merge remote-tracking branch 'origin/pr/1942' into land/queue1
# Conflicts:
#	CHANGELOG.md
2026-09-09 12:25:35 -07:00
Palash Debnath 20bc24c312 Merge remote-tracking branch 'origin/pr/1930' into land/queue1 2026-09-09 12:25:35 -07:00
Palash Debnath 810abf9187 Merge remote-tracking branch 'origin/pr/1919' into land/queue1
# Conflicts:
#	CHANGELOG.md
2026-09-09 12:25:35 -07:00
Palash Debnath cfb318c50b Merge remote-tracking branch 'origin/pr/1918' into land/queue1
# Conflicts:
#	CHANGELOG.md
#	frontend/src/components/BootstrapSplash.jsx
2026-09-09 12:25:34 -07:00
Palash Debnath 2e1e52f26e Merge remote-tracking branch 'origin/pr/1917' into land/queue1
# Conflicts:
#	CHANGELOG.md
2026-09-09 12:24:12 -07:00
Palash Debnath 2f663011e5 Merge remote-tracking branch 'origin/pr/1916' into land/queue1
# Conflicts:
#	CHANGELOG.md
2026-09-09 12:24:11 -07:00
Palash Debnath 773c4728c8 Merge remote-tracking branch 'origin/pr/1915' into land/queue1
# Conflicts:
#	CHANGELOG.md
2026-09-09 12:24:11 -07:00
Palash Debnath 6b68c4d9fe Merge remote-tracking branch 'origin/pr/1914' into land/queue1
# Conflicts:
#	CHANGELOG.md
2026-09-09 12:24:11 -07:00
Palash Debnath 5f5bb04f41 Merge remote-tracking branch 'origin/pr/1912' into land/queue1
# Conflicts:
#	CHANGELOG.md
2026-09-09 12:24:10 -07:00
Palash DebnathandClaude Opus 5 7afb82bb43 style: format the four files this branch left unformatted
`bun run format:check` is a CI gate and these four came in unformatted. No
behaviour change — running the formatter in write mode touches nothing else
in the tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 12:14:47 -07:00
Palash DebnathandClaude Opus 5 d630db29f5 fix(errors): stop a context-free failure borrowing another stage's remediation
Closes #1943.

A macOS mlx-audio text-to-speech failure returned a 500 advising the user
that "the connection to the video server dropped mid-download". No video was
involved. VIDEO_DOWNLOAD_NETWORK triggers on bare phrases — "timed out",
"connection reset", "broken pipe" — so any unrelated failure carrying one
is handed a confidently wrong next step, which is worse than no hint at all.

failure._CONTEXT_FREE_HINT_CLASSES already existed for exactly this, and its
own comment names VIDEO_DOWNLOAD_NETWORK as the class that must never appear
on a stageless surface. Only append_hint honoured it; public_exception_response
took over the 500 path without carrying the rule across, and the streaming
error frame then inherited the same gap through it.

The filter now lives in public_exception_response, so every context-free
caller gets it. MODEL_CACHE_CORRUPT joins the allowlist — its trigger is a
VoiceStudio-authored sentence, no library can produce it, and the 500 handler
is the surface a corrupt cache actually reaches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 12:13:25 -07:00
Palash DebnathandClaude Fable 5.1 f49d31d5b3 test(backend): only accept positive startup evidence in the Windows watchdog test
The poll loop treated any status other than "starting" as success, so a
backend that stayed alive but reported a failed startup would pass the
very test meant to catch a broken start (CodeRabbit + Greptile on #1955).
Succeed only when the ML import step is done or status is ready; fail
loudly on any other terminal status or error. Also give the child an
empty HF_HUB_CACHE so it never reads the developer's populated cache.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCDZcBpP6QQa4dzUa8z6rh
2026-09-09 12:06:46 -07:00
Palash DebnathandClaude Opus 5 4f4d21ff5b fix(errors): name the backend error class on an unclassified streaming failure
Closes #1800.

Every engine failure the taxonomy cannot classify renders one floor message,
"Generation failed. Check the selected engine and try again." The auto bug
reporter puts that message and a stack of minified bundle frames into the
issue, so unrelated faults arrive as byte-identical reports — roughly a dozen
of the open issues are that same report filed again, and none of them can be
told apart, let alone triaged.

The streaming error frame now carries the exception's TYPE NAME, the frontend
keeps it on StreamingPreviewError, and the report prints it as "Backend error
class: …". A MemoryError and a FileNotFoundError stop being the same issue.

Only the class name — no substring of the exception message is copied, so the
response-safety contract still holds and a test pins that a path in the
exception never reaches the payload. This is the same datum the dub routes
already put on the wire as error_class and the analytics allowlist already
treats as content-free.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 12:06:40 -07:00
Palash DebnathandClaude Opus 5 b82205fbab fix(deps): regenerate the workspace lockfile for the vitest bump
frontend/ is a bun workspace, so its package.json is locked by the
repo-root bun.lock. Dependabot bumped only the manifest, so
`bun install --frozen-lockfile` — which CI and deploy/Dockerfile both
run — rejected the tree and the Tests job never got past install.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 12:01:45 -07:00
Palash DebnathandClaude Opus 5 7601f0f1a8 fix(dictation): drop the token border the picker's nesting rail reintroduced
tests/test_no_literal_borders.py guards the app-wide border removal: a
`border-[var(--chrome-border…)]` renders a stray hairline the moment that
token stops resolving transparent. The picker's indent rail used one, which
failed the guard. The indent and padding already carry the nesting, so the
rail keeps its width as border-transparent and shows nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 12:00:42 -07:00
Palash DebnathandClaude Fable 5.1 5c7f8a8e7a test(backend): Windows integration regression for the desktop stdin watchdog hang
Spawns the real backend the way the desktop shell does (containment marker
plus a piped stdin) and asserts startup gets past the ML import. On the
pre-fix watchdog it times out after 180 s; on the fix it passes in ~4 s.
Windows-only, since the deadlock is a Windows loader-lock interaction and
CI's backend job runs on Linux.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCDZcBpP6QQa4dzUa8z6rh
2026-09-09 11:51:17 -07:00
Palash DebnathandClaude Opus 5 7dcb88452c fix(deps): regenerate the root lockfile for this branch's refreshed pins
frontend/package.json moved but the workspace-root bun.lock did not, so
`bun install --frozen-lockfile` — what CI and deploy/Dockerfile both run —
rejected the tree. Plain `bun install` tolerates the drift, so a green local
run said nothing about it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 11:49:29 -07:00
Palash Debnath db50111c54 fix(sidebar): restore project rename, lost when the Dub landing became history-only
The Dub landing used to carry a WorkspaceProjects panel, and that panel was
the only caller of the project rename endpoint. Making the landing
history-only removed the panel and left rename unreachable from the entire
UI, while the rename route, the project list and the inline-rename CSS all
stayed. App.jsx's renameProject became an unused variable, which is what
failed CI lint — the lint error was the symptom, the lost capability was the
bug.

Projects now live only in the sidebar rail, so the affordance moves there:
inline rename on each project row, commit on Enter or Save, abandon on
Escape, empty and unchanged names ignored, and the button hidden when no
handler is wired. The orphaned WorkspaceProjects component is deleted.

Also fixes a Windows-only failure in initialLoadRetry.test.js: it took
.pathname off a file:// URL, which on Windows yields "/C:/..." and made
readFileSync resolve "C:\C:\...", so the file ENOENT'd on every Windows
checkout. Uses fileURLToPath instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
(cherry picked from commit 0a164add119335c3a150725cfe4c4ce8959c50a5)
2026-09-09 11:48:22 -07:00
Palash Debnath 6b7c2bac8f feat(transcriptions): let the user pick which dictation model to install
The missing-model empty state offered exactly one action: download the
recommended Whisper Tiny. The six other catalogue models — the more accurate
English Parakeet, the 25–44 MB streaming models that show text while you
speak, the bilingual zh/en ones — were only reachable through Settings, and
a user who already had one on disk was still told to download Whisper Tiny.

The page now lists the whole sherpa-onnx catalogue grouped by the trade-off
the user is actually choosing between (best accuracy vs lowest latency),
with languages and download size on every row. Any model can be installed
in one click, an installed one can be switched to without a download, and
the progress bar names the model that was picked. If the catalogue cannot
be read the single recommended-download button remains as the fallback.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
(cherry picked from commit 9c4ba2bab2a2253fbf4f82bf082db530267ddadc)
2026-09-09 11:47:38 -07:00
Palash DebnathandClaude Opus 5 48d1b22fd9 feat(dictation): model picker in the engine quick-switch, and a recoverable Windows dev stack
Adds the sherpa-onnx dictation model picker under the Transcription engine
row so the model the hotkey loads is switchable without opening Settings,
routes the Sherpa transcription path through that same preference, and makes
the Windows desktop dev stack recover instead of demanding Task Manager.
Refreshes the Tauri and npm dependency pins that went with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
2026-09-09 11:47:31 -07:00
Palash DebnathandClaude Fable 5.1 6a6dd83efa docs(changelog): note the Windows backend startup hang fix (#1955)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCDZcBpP6QQa4dzUa8z6rh
2026-09-09 11:40:13 -07:00
Palash DebnathandClaude Fable 5.1 09a260bb26 fix(backend): stop Windows desktop launches freezing at "Loading ML runtime"
Every backend spawned by the Windows desktop shell hung forever in the
startup worker's `import torch`, inside the loader for numpy's OpenBLAS
DLL. The desktop parent-liveness watchdog (0a20aeb0) parks a synchronous
read on the stdin pipe the shell hands the backend, and that pending read
deadlocks the DLL initializer. The identical command from a terminal, with
no stdin pipe and no watchdog, starts in seconds — which is why it only
reproduced under the app.

Bisected outside the app by spawning the backend with the shell's exact
env, pipes, creation flags and job object: a watchdog thread that merely
sleeps is harmless; a pending ReadFile, via the C runtime or straight to
the kernel, hangs it every time. Native stacks (py-spy --native) show the
watchdog in NtReadFile and the importer waiting on a critical section from
inside the OpenBLAS initializer.

Fix: on Windows the watchdog polls PeekNamedPipe and reads only bytes that
are already buffered, so no I/O is ever outstanding on the pipe. It still
exits the instant the desktop closes its end (ERROR_BROKEN_PIPE), and a
non-pipe stdin keeps the shared blocking reader. Verified: the app-style
spawn goes from an indefinite hang to ready in ~3 s, and the desktop-prod
build boots and loads the model.

Not in v0.5.1; the watchdog landed 2026-08-30 on main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCDZcBpP6QQa4dzUa8z6rh
2026-09-09 11:39:50 -07:00