The act runner has a Go format bug that evaluates step outputs at
cleanup time and crashes with %!t(string=...), marking the job as
failed even though all steps succeeded.
Replaced steps.bump.outputs.* with GITHUB_ENV variables which
persist across steps without triggering the runner's output
evaluation bug.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Audio capture started immediately after spawning the WebSocket thread,
but the WebSocket hadn't connected yet. Audio chunks sent to the
unconnected WebSocket caused a broken pipe error.
Fix: added a threading.Event that start_recording() waits on (up to
15s) before opening the audio stream. The event is set in _ws_lifecycle
after the WebSocket connects and handshake completes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Start Transcription button now shows the error message when it fails
instead of silently reverting. Common causes:
- Missing PortAudio library on Linux
- Audio device not accessible
- Deepgram connection failure
Also added error details to backend console output and captured
the last error from the Deepgram engine for better diagnostics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sidecar process was orphaned when the Tauri app closed, leaving
ports 8080/8081 in use. On next launch the new sidecar couldn't bind
those ports and failed to start.
Added RunEvent::Exit handler that stops the sidecar before the app
process terminates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Linux CPU sidecar: PyPI's default torch on Linux includes CUDA
(~800MB). UV_NO_SOURCES only bypasses our custom CUDA index but
still gets CUDA-enabled torch from PyPI. Now explicitly installs
CPU-only torch from pytorch.org/whl/cpu after sync. Same fix
applied to Windows.
New cleanup-releases.yml workflow (manual trigger):
- Configurable: keep N app releases, keep N sidecar releases
- Dry run mode (default) shows what would be deleted without deleting
- Protects v1.4.0 (last pre-Tauri release)
- Shows release sizes in MB
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CUDA sidecars are ~2GB and too slow to upload from the Windows runner.
Cloud (Deepgram) provides faster transcription anyway. Removed:
- CUDA build steps from Windows and Linux sidecar workflows
- CUDA option from the SidecarSetup download screen
Remaining sidecar variants:
- Cloud (Deepgram): ~50 MB - recommended for most users
- Local CPU: ~500 MB - for offline/privacy use
CUDA can be revisited once the managed Deepgram service is ready.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
zip -9 on Linux, 7z -mx=9 on Windows. Compression takes longer but
produces smaller files which upload faster over the network.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On first launch, the cloud sidecar now:
1. Detects it's the cloud variant (DeviceManager import fails)
2. Auto-switches config from "local" to "byok" mode
3. Shows "Setup needed: Open Settings > Remote Transcription >
enter your Deepgram API key" as a friendly status message
4. Stays in READY state so the UI is fully accessible
The user can then open Settings, enter their Deepgram API key,
save, and start transcribing without needing to know about modes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cloud sidecar excludes the local Whisper engine module, but on
first launch the config defaults to remote.mode="local" which tries
to import it. Now catches the ImportError gracefully and shows an
error message telling the user to switch to Cloud (Deepgram) mode
in Settings. The API server still starts so Settings is accessible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Rust backend emits {downloaded, total, phase, message} but the
Svelte component was reading event.payload.progress which doesn't
exist, resulting in NaN. Now calculates percentage from downloaded/total.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After calling POST /api/stop, the button stayed on "Stop Transcription"
because the state update depended on the WebSocket broadcast which can
be delayed or missed (event loop threading issue).
Fix: poll GET /api/status immediately after start/stop API calls to
update the UI state directly, rather than waiting for the WebSocket.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Gitea's YAML parser treats `echo "text: value"` as a mapping when
on a single `run:` line. Using block scalar (`run: |`) avoids this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Quote RELEASE_TAG env vars in all workflow files. Unquoted
${{ inputs.tag }} caused YAML parse errors on some Gitea runners,
making dispatch return HTTP 500 for Linux/macOS.
2. Disable automatic release cleanup in both coordinators. The cleanup
races with async builds -- it deletes the release before builds
finish uploading their assets. Clean up old releases manually
from the Gitea UI instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show the Gitea API response body when dispatch returns non-204,
to help diagnose why Linux/macOS dispatches return HTTP 500.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New features:
- Settings > Transcription Engine > "Change Transcription Engine"
button stops the sidecar, deletes downloaded files, and reloads
the app to show the engine selection screen
- Improved SidecarSetup descriptions with detailed explanations
of each variant and "Recommended" tag on Cloud (Deepgram)
- Cloud option listed first as the recommended choice
- New reset_sidecar Tauri command that cleans up sidecar files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lightweight Deepgram-only sidecar that excludes PyTorch, faster-whisper,
RealtimeSTT, and CUDA. Only includes audio capture + WebSocket streaming
to Deepgram. Requires a Deepgram API key (BYOK or managed mode).
Changes:
- client/models.py: Extracted TranscriptionResult into standalone module
so deepgram_transcription.py doesn't transitively import torch
- backend/app_controller.py: Made RealtimeTranscriptionEngine and
DeviceManager imports lazy (only loaded when remote.mode == "local")
- local-transcription-cloud.spec: PyInstaller spec excluding all ML deps
- SidecarSetup.svelte: Added "Cloud Only (Deepgram)" variant option
- build-sidecar-cloud.yml: CI workflow building cloud sidecar for all 3 OS
- sidecar-release.yml: Dispatches cloud build alongside CPU/CUDA builds
Sidecar download options are now:
- Standard (CPU): ~500 MB - local Whisper on any computer
- GPU Accelerated (CUDA): ~2 GB - local Whisper with NVIDIA GPU
- Cloud Only (Deepgram): ~50 MB - requires API key, no local models
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restored the font configuration that was missing from the Tauri
rewrite. Settings now include:
- Font Source: System Font, Web-Safe, Google Font
- System Font: text input for any installed font family
- Web-Safe: dropdown with 13 universal fonts (Arial, Courier New, etc.)
- Google Font: dropdown with 35 fonts organized by category
(Sans Serif, Serif, Monospace, Display, Handwriting)
- Font Size: range slider (8-32px)
All font settings are saved to config and applied to the OBS web
display and server sync.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three changes to reduce transcription delay:
1. Send loop: queue.get() was blocking the asyncio event loop, stalling
the receive loop and delaying transcription results. Now uses
run_in_executor() to avoid blocking the event loop.
2. Block size: reduced from 4096 (~256ms) to 1024 (~64ms) for more
frequent, smaller audio chunks. Deepgram handles streaming better
with smaller packets.
3. Added punctuate=true and smart_format=true to Deepgram BYOK
params for cleaner transcription output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Dev mode: use `uv run python` instead of bare `python` to ensure
the project venv is used. Also use CARGO_MANIFEST_DIR to find the
project root reliably.
2. Engine reload: changing remote.mode (local/managed/byok) now
triggers a full engine reload. Previously only model and device
changes triggered reload, so switching to Deepgram had no effect
until the app was restarted.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed push triggers from both coordinator workflows. They now
only run via workflow_dispatch (manual "Run workflow" button).
Re-enable push triggers once the build pipeline is stable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues causing all builds to fail:
1. Cleanup steps deleted git tags along with releases. Since builds
are dispatched asynchronously, they tried to checkout tags that
had already been deleted. Now cleanup only deletes releases (which
frees storage by removing assets) but preserves git tags.
2. Linux/macOS build workflows used $GITHUB_OUTPUT step outputs for
the tag, which is unreliable on Gitea runners. Switched to the
same job-level env var pattern (RELEASE_TAG) that works on Windows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PyInstaller frozen executables buffer stdout when piped to a
subprocess (no TTY). Even with flush=True in Python, the OS-level
pipe buffer can delay output. This prevented the ready event from
reaching the Tauri app, causing the "Starting sidecar..." hang.
Fix: set PYTHONUNBUFFERED=1 env var on both prod and dev sidecar
commands, plus -u flag for dev mode Python.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues caused the app to freeze on "Starting sidecar...":
1. wait_for_ready() used a blocking BufReader::lines() iterator
with a timeout check between lines. If the sidecar produced no
stdout output (crashed, missing binary, or slow model loading),
the read blocked forever. Now uses a background thread with
mpsc::recv_timeout() for a real 120s deadline.
2. start_sidecar was a synchronous Tauri command that blocked the
main thread during the entire sidecar startup (up to 120s).
Now async via tokio::spawn_blocking, keeping the UI responsive.
Also logs all sidecar stdout lines to stderr with [sidecar-stdout]
prefix for debugging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major version bump reflecting the architecture change from PySide6/Qt
to Tauri v2 + Svelte 5 with cross-platform support for Windows,
macOS, and Linux.
Key changes since v1.4.0:
- Tauri v2 native desktop shell replacing PySide6/Qt
- Svelte 5 reactive frontend
- Headless Python backend as a downloadable sidecar
- Deepgram cloud transcription (managed + BYOK)
- Gitea CI/CD with per-OS builds and automated releases
- Sidecar auto-update checking on startup
- 63-test suite (Python + Svelte + Rust)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On launch, after confirming the sidecar is installed, the app now
checks for a newer sidecar version via the Gitea API. If an update
is available, shows a prompt with "Update Now" or "Skip":
- Update Now: shows the SidecarSetup download screen
- Skip: launches the existing sidecar version
The update check is non-blocking -- if it fails (no internet, API
error), the app silently proceeds with the current version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs preventing sidecar from starting:
1. Directory was "sidecar-sidecar-v1.0.3" (double prefix) because
sidecar_dir_for_version() prepended "sidecar-" to a version that
already contained it. Now uses the tag directly as the dir name.
2. After a crash, the Python InstanceLock PID file at
~/.local-transcription/app.lock remained, blocking the next launch
with "Another instance is already running". Now clears the stale
lock file before spawning the sidecar.
Also fixed cleanup_old_versions() and tests to match the corrected
directory naming.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the sidecar process exits before sending the ready event, the
error message now includes the last 10 lines of stderr. Stderr is
captured in a background thread and written to sidecar.log in the
app data directory.
This helps diagnose why the PyInstaller sidecar fails to start
(missing DLLs, import errors, permission issues, etc.).
Log location: %APPDATA%\net.anhonesthost.local-transcription\sidecar.log
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- test.yml: use uv venv instead of pip --break-system-packages
- release.yml: inline test job that must pass before version bump;
only triggers on source file changes (src/, src-tauri/, package.json)
- sidecar-release.yml: inline Python test job that must pass before
sidecar version bump
- Both coordinators use `needs: test` so builds never start if tests fail
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both release.yml and sidecar-release.yml were updating version.py,
causing merge conflicts when both ran on the same push. Now:
- release.yml (app) owns: package.json, tauri.conf.json, Cargo.toml, version.py
- sidecar-release.yml owns: pyproject.toml only
Also deleted the stale sidecar-v1.0.4 tag that failed to push.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>