Merge remote-tracking branch 'origin/main' into fix/1773-classic-error-class

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Palash Debnath
2026-09-09 22:58:26 -07:00
3 changed files with 106 additions and 0 deletions
+1
View File
@@ -10,6 +10,7 @@ the frozen-backend fallback mirror it for their toolchains.
**Highlights**
- A bare 500 report now names the backend error class, so two unrelated faults stop filing the same issue (#1773)
- The first-run install log is kept on disk instead of vanishing with the setup screen (#1847)
- `bun run desktop` reclaims port 3900 from a backend the app itself left running, instead of refusing to start (#1974)
- A dictation shortcut another app already owns now says so, instead of silently doing nothing (#1858)
- Quitting on Windows is no longer reported as a crash on the next launch (#1898)
+22
View File
@@ -963,6 +963,28 @@ repair is required.
**Linked issue:** [#1590](https://github.com/debpalash/VoiceStudio/issues/1590)
## Reading the first-run install log after setup finishes
The Activity panel on the first-run screen shows the install as it happens, and
that screen closes the moment setup succeeds — so it is not where you go
afterwards to check what was installed, or to attach the log to a bug report.
The same lines are written to **`bootstrap.log`**, beside the backend logs:
| Platform | Location |
|---|---|
| macOS | `~/Library/Logs/OmniVoice/bootstrap.log` |
| Windows | `%LOCALAPPDATA%\OmniVoice\Logs\bootstrap.log` |
| Linux | `~/.local/state/OmniVoice/bootstrap.log` |
`OMNIVOICE_LOG_DIR` moves it, along with the other logs.
It covers the current run only — it is truncated when a bootstrap starts, so a
retry replaces the previous attempt rather than appending to it. If you need
the log from an attempt that has already been superseded, copy it before
retrying.
## RTX 50-series (Blackwell, `sm_120`): backend never starts
**Symptom.** The desktop app stays on "starting backend", `/health` returns 503,
+83
View File
@@ -119,6 +119,44 @@ pub struct LogPayload {
pub line: String,
}
/// Where the first-run log is kept so it outlives the splash.
///
/// 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
/// pause and nowhere to retrieve it (#1847). A user who wanted to check what
/// had just been installed, or hand it to a bug report, had nothing.
///
/// Sits beside backend.log so everything about a run is in one directory.
fn bootstrap_log_path() -> PathBuf {
crate::backend::backend_log_path().with_file_name("bootstrap.log")
}
/// Truncate once per process, then append.
///
/// A bootstrap is a single episode, and the interesting question is always
/// "what happened THIS time" — an ever-growing file would bury that and grow
/// without bound across retries. Truncating on the first write of the process
/// keeps it to the current run without needing a hook on every restart path.
static BOOTSTRAP_LOG_STARTED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
fn append_bootstrap_log(stage: &str, line: &str) {
use std::io::Write;
let path = bootstrap_log_path();
let fresh = BOOTSTRAP_LOG_STARTED.set(()).is_ok();
let opened = fs::OpenOptions::new()
.create(true)
.write(true)
.append(!fresh)
.truncate(fresh)
.open(&path);
// Best effort throughout: a log we cannot write must never take the
// bootstrap down with it.
if let Ok(mut file) = opened {
let _ = writeln!(file, "[{stage}] {line}");
}
}
pub fn emit_log<R: tauri::Runtime>(app: &tauri::AppHandle<R>, stage: &str, line: &str) {
let payload = LogPayload { stage: stage.to_string(), line: line.to_string() };
// Buffer the log so the frontend can backfill on mount.
@@ -127,6 +165,9 @@ pub fn emit_log<R: tauri::Runtime>(app: &tauri::AppHandle<R>, stage: &str, line:
logs.push(payload.clone());
}
}
// Persist before emitting: the in-memory buffer and the event both die
// with the splash, the file does not.
append_bootstrap_log(stage, line);
let _ = app.emit("bootstrap-log", payload);
}
@@ -4473,6 +4514,48 @@ mod code_fingerprint_tests {
assert_ne!(both, backend_only);
}
// #1847: the splash is the only surface with a Show/Copy affordance for
// the first-run log, and it unmounts the moment the stage flips to ready,
// so on a successful install the whole log was gone for good. It is
// written beside backend.log now.
#[test]
fn bootstrap_log_sits_beside_the_backend_log() {
// One directory for everything about a run, so a bug report does not
// have to hunt in two places.
let bootstrap = bootstrap_log_path();
let backend = crate::backend::backend_log_path();
assert_eq!(bootstrap.parent(), backend.parent());
assert_eq!(bootstrap.file_name().unwrap(), "bootstrap.log");
}
#[test]
fn append_bootstrap_log_writes_the_stage_and_line() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("bootstrap.log");
// Exercise the same write shape the helper uses, against a path we
// control: the helper itself resolves a per-OS location, and a test
// that redirected that would be testing the redirection.
use std::io::Write;
let mut file = fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&path)
.unwrap();
writeln!(file, "[{}] {}", "installing_deps", "Collecting torch").unwrap();
drop(file);
let body = fs::read_to_string(&path).unwrap();
assert!(body.contains("[installing_deps] Collecting torch"), "{body}");
}
#[test]
fn append_bootstrap_log_never_panics_on_an_unwritable_path() {
// Best effort by contract: a log we cannot write must not take the
// bootstrap down with it.
append_bootstrap_log("checking", "a line");
}
// #1898: on Windows the backend is force-terminated with no graceful
// phase, so it never clears its own run sentinel and every deliberate
// quit was reported as a crash on the next launch. The shell retires the