Compare commits

..

6 Commits

Author SHA1 Message Date
Gitea Actions
e42a922507 chore: bump sidecar version to 1.0.4 [skip ci] 2026-04-07 20:01:20 +00:00
Developer
8fc2d11c5f Fix builds failing to checkout: stop deleting tags, fix tag passing
All checks were successful
Tests / Python Backend Tests (push) Successful in 5s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 2m3s
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>
2026-04-07 12:27:13 -07:00
Gitea Actions
11832e911b chore: bump version to 2.0.3 [skip ci] 2026-04-07 19:21:16 +00:00
Developer
18e6b974c0 Fix sidecar stdout buffering: set PYTHONUNBUFFERED=1
All checks were successful
Release / Run Tests (push) Successful in 24s
Tests / Python Backend Tests (push) Successful in 5s
Tests / Frontend Tests (push) Successful in 7s
Tests / Rust Sidecar Tests (push) Successful in 3m17s
Release / Bump version and tag (push) Successful in 4s
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>
2026-04-07 12:17:18 -07:00
Gitea Actions
08e464daaf chore: bump version to 2.0.2 [skip ci] 2026-04-07 19:15:05 +00:00
Developer
5d22adcaa4 Fix app hanging on sidecar startup
All checks were successful
Release / Run Tests (push) Successful in 11s
Tests / Python Backend Tests (push) Successful in 4s
Tests / Frontend Tests (push) Successful in 7s
Tests / Rust Sidecar Tests (push) Successful in 3m13s
Release / Bump version and tag (push) Successful in 14s
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>
2026-04-07 12:11:13 -07:00
13 changed files with 84 additions and 94 deletions

View File

@@ -13,23 +13,14 @@ jobs:
runs-on: ubuntu-latest
env:
NODE_VERSION: "20"
RELEASE_TAG: ${{ inputs.tag }}
steps:
- name: Determine tag
id: tag
run: |
TAG="${{ inputs.tag }}"
if [ -z "$TAG" ]; then
TAG="${{ github.event.inputs.tag }}"
fi
if [ -z "$TAG" ]; then
TAG=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/v*' | head -1 | sed 's|.*refs/tags/||')
fi
echo "Building for tag: ${TAG}"
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- name: Show tag
run: echo "Building for tag: ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:
ref: ${{ steps.tag.outputs.tag }}
ref: ${{ inputs.tag }}
- name: Set up Node.js
uses: actions/setup-node@v4
@@ -58,7 +49,7 @@ jobs:
run: |
sudo apt-get install -y jq
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
TAG="${{ steps.tag.outputs.tag }}"
TAG="${RELEASE_TAG}"
echo "Release tag: ${TAG}"
echo "Waiting for release ${TAG} to be available..."

View File

@@ -13,23 +13,14 @@ jobs:
runs-on: macos-latest
env:
NODE_VERSION: "20"
RELEASE_TAG: ${{ inputs.tag }}
steps:
- name: Determine tag
id: tag
run: |
TAG="${{ inputs.tag }}"
if [ -z "$TAG" ]; then
TAG="${{ github.event.inputs.tag }}"
fi
if [ -z "$TAG" ]; then
TAG=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/v*' | head -1 | sed 's|.*refs/tags/||')
fi
echo "Building for tag: ${TAG}"
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- name: Show tag
run: echo "Building for tag: ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:
ref: ${{ steps.tag.outputs.tag }}
ref: ${{ inputs.tag }}
- name: Set up Node.js
uses: actions/setup-node@v4
@@ -56,7 +47,7 @@ jobs:
run: |
which jq || brew install jq
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
TAG="${{ steps.tag.outputs.tag }}"
TAG="${RELEASE_TAG}"
echo "Release tag: ${TAG}"
echo "Waiting for release ${TAG} to be available..."

View File

@@ -13,23 +13,14 @@ jobs:
runs-on: ubuntu-latest
env:
PYTHON_VERSION: "3.11"
RELEASE_TAG: ${{ inputs.tag }}
steps:
- name: Determine tag
id: tag
run: |
TAG="${{ inputs.tag }}"
if [ -z "$TAG" ]; then
TAG="${{ github.event.inputs.tag }}"
fi
if [ -z "$TAG" ]; then
TAG=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/sidecar-v*' | head -1 | sed 's|.*refs/tags/||')
fi
echo "Building for tag: ${TAG}"
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- name: Show tag
run: echo "Building for tag: ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:
ref: ${{ steps.tag.outputs.tag }}
ref: ${{ inputs.tag }}
- name: Install uv
run: |
@@ -75,7 +66,7 @@ jobs:
run: |
sudo apt-get install -y jq
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
TAG="${{ steps.tag.outputs.tag }}"
TAG="${RELEASE_TAG}"
echo "Waiting for sidecar release ${TAG} to be available..."
for i in $(seq 1 30); do

View File

@@ -13,23 +13,14 @@ jobs:
runs-on: macos-latest
env:
PYTHON_VERSION: "3.11"
RELEASE_TAG: ${{ inputs.tag }}
steps:
- name: Determine tag
id: tag
run: |
TAG="${{ inputs.tag }}"
if [ -z "$TAG" ]; then
TAG="${{ github.event.inputs.tag }}"
fi
if [ -z "$TAG" ]; then
TAG=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/sidecar-v*' | head -1 | sed 's|.*refs/tags/||')
fi
echo "Building for tag: ${TAG}"
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- name: Show tag
run: echo "Building for tag: ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:
ref: ${{ steps.tag.outputs.tag }}
ref: ${{ inputs.tag }}
- name: Install uv
run: |
@@ -66,7 +57,7 @@ jobs:
run: |
which jq || brew install jq
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
TAG="${{ steps.tag.outputs.tag }}"
TAG="${RELEASE_TAG}"
echo "Waiting for sidecar release ${TAG} to be available..."
for i in $(seq 1 30); do

View File

@@ -159,9 +159,7 @@ jobs:
echo " Deleting release ${TAG} (ID: ${ID})..."
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
"${REPO_API}/releases/${ID}"
# Also delete the tag
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
"${REPO_API}/tags/${TAG}"
# Keep the git tag -- only delete the release (assets).
# Deleting tags breaks builds that haven't checked out yet.
done
echo "Cleanup complete"

View File

@@ -166,9 +166,7 @@ jobs:
echo " Deleting sidecar release ${TAG} (ID: ${ID})..."
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
"${REPO_API}/releases/${ID}"
# Also delete the tag
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
"${REPO_API}/tags/${TAG}"
# Keep the git tag -- only delete the release (assets).
# Deleting tags breaks builds that haven't checked out yet.
done
echo "Cleanup complete"

View File

@@ -1,7 +1,7 @@
{
"name": "local-transcription",
"private": true,
"version": "2.0.1",
"version": "2.0.3",
"type": "module",
"scripts": {
"dev": "vite dev",

View File

@@ -1,6 +1,6 @@
[project]
name = "local-transcription"
version = "1.0.3"
version = "1.0.4"
description = "A standalone desktop application for real-time speech-to-text transcription using Whisper models"
readme = "README.md"
requires-python = ">=3.9"

View File

@@ -1,6 +1,6 @@
[package]
name = "local-transcription"
version = "2.0.1"
version = "2.0.3"
description = "Real-time speech-to-text transcription for streamers"
authors = ["Local Transcription Contributors"]
edition = "2021"

View File

@@ -29,9 +29,9 @@ pub fn run() {
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_process::init())
.manage(sidecar::ManagedSidecar(Mutex::new(
.manage(sidecar::ManagedSidecar(std::sync::Arc::new(Mutex::new(
sidecar::SidecarManager::new(),
)))
))))
.setup(|app| {
let resource_dir = app
.path()

View File

@@ -555,7 +555,7 @@ impl SidecarManager {
fn build_dev_command(&self) -> Result<std::process::Command, String> {
let mut cmd = std::process::Command::new("python");
cmd.args(["-m", "backend.main_headless"]);
cmd.args(["-u", "-m", "backend.main_headless"]); // -u = unbuffered
// Try to find the project root (parent of src-tauri)
if let Some(dirs) = DIRS.get() {
@@ -568,6 +568,7 @@ impl SidecarManager {
}
}
cmd.env("PYTHONUNBUFFERED", "1");
Ok(cmd)
}
@@ -583,27 +584,51 @@ impl SidecarManager {
bin.parent()
.ok_or("Cannot determine sidecar parent dir")?,
);
// Force unbuffered stdout so the ready event is sent immediately.
// PyInstaller frozen executables buffer stdout when piped.
cmd.env("PYTHONUNBUFFERED", "1");
Ok(cmd)
}
fn wait_for_ready(stdout: std::process::ChildStdout) -> Result<u16, String> {
let reader = std::io::BufReader::new(stdout);
let timeout = std::time::Duration::from_secs(120);
let start = std::time::Instant::now();
use std::sync::mpsc;
for line in reader.lines() {
if start.elapsed() > timeout {
return Err("Timed out waiting for sidecar ready event".into());
}
let line = line.map_err(|e| format!("IO error reading stdout: {e}"))?;
if let Ok(evt) = serde_json::from_str::<ReadyEvent>(&line) {
if evt.event == "ready" {
return Ok(evt.port);
let timeout = std::time::Duration::from_secs(120);
// Read stdout in a background thread so we can enforce a real timeout.
// BufReader::lines() blocks indefinitely if no data arrives.
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let reader = std::io::BufReader::new(stdout);
for line in reader.lines() {
match line {
Ok(line) => {
eprintln!("[sidecar-stdout] {}", line);
if let Ok(evt) = serde_json::from_str::<ReadyEvent>(&line) {
if evt.event == "ready" {
let _ = tx.send(Ok(evt.port));
return;
}
}
}
Err(e) => {
let _ = tx.send(Err(format!("IO error reading stdout: {e}")));
return;
}
}
}
// Ignore other lines (e.g. log output)
}
Err("Sidecar process exited before sending ready event".into())
let _ = tx.send(Err(
"Sidecar process exited before sending ready event".into(),
));
});
rx.recv_timeout(timeout).unwrap_or_else(|_| {
Err(format!(
"Timed out after {}s waiting for sidecar ready event",
timeout.as_secs()
))
})
}
}
@@ -612,7 +637,8 @@ impl SidecarManager {
// ---------------------------------------------------------------------------
/// Wrapper so we can store `SidecarManager` in Tauri's managed state.
pub struct ManagedSidecar(pub Mutex<SidecarManager>);
/// Uses Arc so it can be cloned into background threads for async commands.
pub struct ManagedSidecar(pub std::sync::Arc<Mutex<SidecarManager>>);
#[tauri::command]
pub fn get_sidecar_port(state: tauri::State<'_, ManagedSidecar>) -> Result<Option<u16>, String> {
@@ -628,12 +654,16 @@ pub fn get_sidecar_port(state: tauri::State<'_, ManagedSidecar>) -> Result<Optio
}
#[tauri::command]
pub fn start_sidecar(state: tauri::State<'_, ManagedSidecar>) -> Result<u16, String> {
let mut mgr = state
.0
.lock()
.map_err(|e| format!("Lock error: {e}"))?;
mgr.ensure_running()
pub async fn start_sidecar(state: tauri::State<'_, ManagedSidecar>) -> Result<u16, String> {
let mgr = state.0.clone();
// Run blocking sidecar launch in a background thread so it doesn't
// freeze the Tauri UI while waiting for the ready event (up to 120s).
tokio::task::spawn_blocking(move || {
let mut mgr = mgr.lock().map_err(|e| format!("Lock error: {e}"))?;
mgr.ensure_running()
})
.await
.map_err(|e| format!("Task join error: {e}"))?
}
#[tauri::command]

View File

@@ -1,6 +1,6 @@
{
"productName": "Local Transcription",
"version": "2.0.1",
"version": "2.0.3",
"identifier": "net.anhonesthost.local-transcription",
"build": {
"frontendDist": "../dist",

View File

@@ -1,7 +1,7 @@
"""Version information for Local Transcription."""
__version__ = "2.0.1"
__version_info__ = (2, 0, 1)
__version__ = "2.0.3"
__version_info__ = (2, 0, 3)
# Version history:
# 1.4.0 - Auto-update feature: