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>
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>
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>
- 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>
Test suite covering all three layers:
Python backend (25 tests):
- AppController: state machine, start/stop, callbacks, settings reload
- API server: REST endpoints, config CRUD, status, devices
- Config: dot-notation get/set, persistence, nested paths
- Main headless: ready event port format validation
Svelte frontend (14 tests via Vitest):
- Backend store: exported properties/methods, port derivation, URLs
- Config store: method names (fetchConfig not loadConfig), defaults
- Transcriptions store: add/clear/plaintext
- File extension regression: ensures $state runes only in .svelte.ts
Rust sidecar (24 tests via cargo test):
- Platform/arch detection, asset name construction
- Ready event deserialization (with extra fields tolerance)
- Path construction, version read/write, old version cleanup
- Zip extraction, SidecarManager lifecycle
CI workflow (.gitea/workflows/test.yml):
- Runs on push to main and PRs
- Three parallel jobs: Python, Frontend, Rust
Also fixes three bugs found during test planning:
- Settings: /api/check-updates -> GET /api/check-update
- Settings: /api/remote/login -> /api/login
- Settings: /api/remote/register -> /api/register
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Step outputs via GITHUB_OUTPUT are unreliable with act runner on
Windows (BOM encoding issues). Replaced with job-level env var
RELEASE_TAG set directly from inputs.tag, and checkout ref also
uses inputs.tag directly. Eliminated the Determine tag step
entirely — no intermediate output needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The act runner on Windows doesn't have bash available. Switched back
to PowerShell with the inputs.tag fallback chain. Uses Out-File for
GITHUB_OUTPUT instead of echo redirection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The workflow_dispatch input was accessed as github.event.inputs.tag
which can be empty depending on the Gitea runner. Now tries both
inputs.tag (modern syntax) and github.event.inputs.tag as fallback,
with a final fallback to the latest matching git tag.
Also switched Windows Determine-tag steps from PowerShell to bash
(via Git Bash) for consistency with the other platforms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously per-OS build workflows triggered on tag push events, but
Gitea doesn't fire events for tags pushed by other workflows. Now:
- release.yml dispatches build-app-{linux,windows,macos}.yml via
the Gitea API after creating the tag and release
- sidecar-release.yml dispatches build-sidecar-{linux,windows,macos}.yml
Per-OS workflows changed from push+dispatch triggers to dispatch-only
with tag as a required input. To re-run a failed build for the same
version, just dispatch the specific OS workflow with the same tag --
upload logic replaces existing assets automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactored from 2 monolithic workflows into 8 targeted ones:
Coordinators (version bump + tag + release creation):
- release.yml: bumps app version, tags v*, creates Gitea release
- sidecar-release.yml: bumps sidecar version, tags sidecar-v*
Per-OS app builds (triggered by v* tags or workflow_dispatch):
- build-app-linux.yml: .deb, .rpm, .AppImage
- build-app-windows.yml: .msi, -setup.exe
- build-app-macos.yml: .dmg
Per-OS sidecar builds (triggered by sidecar-v* tags or workflow_dispatch):
- build-sidecar-linux.yml: CUDA + CPU variants
- build-sidecar-windows.yml: CUDA + CPU variants
- build-sidecar-macos.yml: CPU only
Each build workflow can be re-triggered independently without
re-running the version bump or rebuilding other platforms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The CPU build steps used `uv run pyinstaller` which re-resolves
dependencies from pyproject.toml's [tool.uv.sources] before running,
pulling CUDA torch back in after the CPU-only reinstall. This made
CPU and CUDA zips the same size.
Fix: run pyinstaller directly from the venv (.venv/bin/pyinstaller
on Linux/macOS, .venv\Scripts\pyinstaller.exe on Windows) to skip
uv's dependency resolution entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
macOS sidecar: `uv run` re-resolves dependencies using CUDA sources
even after `uv sync --no-sources`. Use UV_NO_SOURCES=1 env var instead
so it applies to all uv commands in the step.
Blank window: When the Tauri app starts without the Python backend
running, it showed a completely blank window. Now shows a "Connecting
to backend..." spinner, or an error state with instructions to start
the backend manually.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
macOS: pyproject.toml's [tool.uv.sources] forces torch from the CUDA
index which has no macOS ARM wheels. Use `uv sync --no-sources` to
bypass this and get torch from PyPI (which includes MPS support).
Windows: Add additional uv PATH locations ($LOCALAPPDATA\uv\bin) for
robustness with different runner environments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two workflows adapted from voice-to-notes:
- release.yml: Builds the Tauri app shell (.deb/.rpm for Linux, .msi
for Windows, .dmg for macOS) on push to main. Auto-bumps version,
creates Gitea release, uploads platform binaries.
- build-sidecar.yml: Builds the headless Python backend sidecar via
PyInstaller when client/server/backend code changes. Produces CUDA
and CPU variants for Linux/Windows, CPU-only for macOS. Uses the new
local-transcription-headless.spec (no PySide6 dependencies).
Also adds local-transcription-headless.spec — a simplified PyInstaller
config for the headless backend that excludes all Qt/PySide6 imports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>