Skip to content

Windows desktop reliability (logs) + onboarding bits#1797

Merged
richiemcilroy merged 4 commits into
mainfrom
windows-crash-stuff
May 12, 2026
Merged

Windows desktop reliability (logs) + onboarding bits#1797
richiemcilroy merged 4 commits into
mainfrom
windows-crash-stuff

Conversation

@richiemcilroy
Copy link
Copy Markdown
Member

@richiemcilroy richiemcilroy commented May 11, 2026

Greptile Summary

This PR improves Windows desktop reliability by adding a dedicated WARN-level rolling log file, a panic hook that writes structured records to panics.log, and panic-isolation wrappers (AssertUnwindSafe + catch_unwind) around export/preview paths with an automatic FFmpeg decoder retry. It also fixes several Windows onboarding issues and a widespread data-tauri-drag-region="none""false" correctness bug.

  • Logging & diagnostics (Rust): Separates info and error logs into distinct rolling files, adds a panic hook for structured crash records, and wraps export functions to catch panics and report them to Sentry before propagating.
  • Windows onboarding: Introduces minStep so the macOS-only permissions step (step 0) is transparently skipped on Windows; all navigation guards and progress indicators are adjusted accordingly.
  • UI fixes: Corrects data-tauri-drag-region="none" to "false" across the onboarding, mode-select, and window-chrome layouts, and extracts ModeSelect into a shared component.

Confidence Score: 5/5

Safe to merge; all Rust additions are additive logging/panic-isolation plumbing and the frontend fixes correct existing bugs.

The panic hook, split log files, and catch_unwind wrappers are straightforward and well-scoped. The Windows onboarding minStep logic correctly threads through all navigation guards and progress displays. No data paths, auth flows, or shared state were altered in ways that could regress existing behaviour.

No files require special attention beyond the one narrating comment in main.rs.

Important Files Changed

Filename Overview
apps/desktop/src-tauri/src/main.rs Adds a separate WARN-level rolling log file and a panic hook that writes structured records to panics.log. Info-level logging keeps non_blocking; only the errors appender is used synchronously (already flagged in a previous thread). One new narrating comment.
apps/desktop/src-tauri/src/export.rs Wraps export and preview functions with AssertUnwindSafe + catch_unwind for panic isolation; adds FFmpeg decoder retry on frame decode errors; introduces ExportActiveGuard/ExportPreviewActiveGuard for preview/export mutual exclusion; adds unit tests.
apps/desktop/src/routes/(window-chrome)/onboarding.tsx Windows onboarding fix: computes minStep (0 on macOS, 1 on Windows) so the permissions step is skipped on Windows; clamps step navigation accordingly. Also corrects data-tauri-drag-region="none" to "false" in multiple places.
apps/desktop/src/utils/tauri.ts Auto-regenerated by tauri-specta to reflect new Rust commands (setNativeCameraPreviewEnabled, startImageImport) and updated types (SystemDiagnostics, Organization, WindowsVersionInfo, GpuInfoDiag, etc.).
apps/desktop/src/components/ModeSelect.tsx New shared ModeSelect component extracted for reuse across mode-select window and onboarding.
apps/desktop/src/routes/(window-chrome).tsx One-line fix: data-tauri-drag-region="none" to "false" on the Inner wrapper div.
apps/desktop/src/routes/mode-select.tsx One-line data-tauri-drag-region fix; now uses extracted ModeSelect component.
apps/web/public/.well-known/workflow/v1/manifest.json Re-generated manifest: reformatted, removed a duplicate builtin-steps entry, reordered workflow entries.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/desktop/src-tauri/src/main.rs:69-70
This comment narrates what `create_dir_all` does rather than capturing any non-obvious context. Per the repo's comments policy, comments that describe what the code does are banned — the function name is self-explanatory.

```suggestion
    std::fs::create_dir_all(&logs_dir).unwrap_or_else(|e| {
```

Reviews (2): Last reviewed commit: "Relax comment policy; improve error logg..." | Re-trigger Greptile

Context used:

  • Context used - AGENTS.md (source)

@brin-security-scanner brin-security-scanner Bot added contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis. labels May 11, 2026
@paragon-review
Copy link
Copy Markdown

Paragon Review Skipped

Hi @richiemcilroy! Your Polarity credit balance is insufficient to complete this review.

Please visit https://app.paragon.run to finish your review.

Comment thread apps/desktop/src-tauri/src/export.rs Outdated
Comment on lines +52 to +55
let message = format!("Export task panicked: {panic_msg}");
sentry::capture_message(&message, sentry::Level::Error);
Err(message)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently returns the panic payload as a user-facing Err(String), which can leak internal details (paths, codec strings, etc.) into the UI.

Consider returning a generic error and only sending the panic payload to logs/Sentry.

Suggested change
let message = format!("Export task panicked: {panic_msg}");
sentry::capture_message(&message, sentry::Level::Error);
Err(message)
}
let sentry_message = format!("Export task panicked: {panic_msg}");
sentry::capture_message(&sentry_message, sentry::Level::Error);
Err("Export task panicked".to_string())

const isMacOS = createMemo(() => ostype() === "macos");
const minStep = createMemo(() => (isMacOS() ? 0 : 1));

const [step, setStep] = createSignal(ostype() === "macos" ? 0 : 1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: since minStep is already computed, you can avoid duplicating the ostype() check in the initial signal value.

Suggested change
const [step, setStep] = createSignal(ostype() === "macos" ? 0 : 1);
const [step, setStep] = createSignal(minStep());

async setCameraInput(id: DeviceOrModelID | null, skipCameraWindow: boolean | null) : Promise<null> {
return await TAURI_INVOKE("set_camera_input", { id, skipCameraWindow });
},
async setNativeCameraPreviewEnabled(enabled: boolean) : Promise<null> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apps/desktop/src/utils/tauri.ts is marked as generated (tauri-specta) and warns against manual edits. These additions will likely be overwritten the next time bindings are regenerated.

If these are new commands/types, it’d be safer to update the Rust/specta source and re-generate, or put hand-written wrappers in a separate file and re-export from there.

}
},
"classes": {}
} No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is missing a trailing newline (\ No newline at end of file in the diff), which tends to cause unnecessary churn.

Suggested change
}
}

&thread_name,
&location,
&message,
&backtrace,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Synchronous file writer in async context

Replacing tracing_appender::non_blocking with a direct RollingFileAppender makes every tracing call block the calling thread until the write is flushed to disk. Inside Tokio worker threads (recording pipeline, export, preview rendering) this can stall the runtime and introduce latency spikes. If the goal is to ensure no logs are dropped on Windows, consider keeping non_blocking but explicitly flushing the guard on shutdown, or using tracing_appender::non_blocking with lossy = false.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/main.rs
Line: 182

Comment:
**Synchronous file writer in async context**

Replacing `tracing_appender::non_blocking` with a direct `RollingFileAppender` makes every `tracing` call block the calling thread until the write is flushed to disk. Inside Tokio worker threads (recording pipeline, export, preview rendering) this can stall the runtime and introduce latency spikes. If the goal is to ensure no logs are dropped on Windows, consider keeping `non_blocking` but explicitly flushing the guard on shutdown, or using `tracing_appender::non_blocking` with `lossy = false`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +41 to +57
.catch_unwind()
.await
{
Ok(result) => result,
Err(panic) => {
let panic_msg = panic_message(panic);
error!(
target: "cap_desktop_export",
panic = %panic_msg,
"export task panicked"
);
let message = format!("Export task panicked: {panic_msg}");
sentry::capture_message(&message, sentry::Level::Error);
Err(message)
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 AssertUnwindSafe on retry path

AssertUnwindSafe is correct here — caught panics return Err and the export is abandoned rather than retried into potentially corrupted state. One thing worth double-checking: the retry in export_video (with force_ffmpeg: true) runs immediately after a panic in the GPU path. If the panic leaked an exclusive lock on a WGPU device or left a GPU command encoder in an open state, the retry (even on the software/ffmpeg path) may encounter those residual locks. If you see intermittent hangs on the retry leg during Windows testing, that would be the likely cause.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/export.rs
Line: 41-57

Comment:
**`AssertUnwindSafe` on retry path**

`AssertUnwindSafe` is correct here — caught panics return `Err` and the export is abandoned rather than retried into potentially corrupted state. One thing worth double-checking: the retry in `export_video` (with `force_ffmpeg: true`) runs immediately after a panic in the GPU path. If the panic leaked an exclusive lock on a WGPU device or left a GPU command encoder in an open state, the retry (even on the software/ffmpeg path) may encounter those residual locks. If you see intermittent hangs on the retry leg during Windows testing, that would be the likely cause.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +159 to +164
let location = info
.location()
.map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()))
.unwrap_or_else(|| "<unknown>".to_string());
let message = info
.payload()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 RUST_BACKTRACE has no effect on Backtrace::force_capture()

std::backtrace::Backtrace::force_capture() (used in the panic hook) always captures a backtrace regardless of the RUST_BACKTRACE environment variable — that is the point of force_capture vs capture. The env-var block therefore only affects the default Rust panic handler (prev(info) call at the end of the hook). This works as intended for the default handler, but the block is misleading and could be removed without changing what ends up in panics.log.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/main.rs
Line: 159-164

Comment:
**`RUST_BACKTRACE` has no effect on `Backtrace::force_capture()`**

`std::backtrace::Backtrace::force_capture()` (used in the panic hook) always captures a backtrace regardless of the `RUST_BACKTRACE` environment variable — that is the point of `force_capture` vs `capture`. The env-var block therefore only affects the *default* Rust panic handler (`prev(info)` call at the end of the hook). This works as intended for the default handler, but the block is misleading and could be removed without changing what ends up in `panics.log`.

How can I resolve this? If you propose a fix, please make it concise.

@richiemcilroy
Copy link
Copy Markdown
Member Author

please rereview the pr @greptileai

.with_ansi(false)
.with_target(true)
.with_writer(non_blocking),
.with_writer(info_file_writer),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now WARN/ERROR events will still go to cap-desktop.log as well as cap-desktop-errors.log (since the info-file fmt layer has no level filter).

If the goal is truly “split logs” (and to avoid double I/O on hot paths), you can filter the info file layer down to <= INFO.

Suggested change
.with_writer(info_file_writer),
.with_writer(info_file_writer)
.with_filter(tracing_subscriber::filter::filter_fn(|m| *m.level() <= tracing::Level::INFO)),

.with_ansi(false)
.with_target(true)
.with_writer(errors_file_appender)
.with_filter(tracing_subscriber::filter::LevelFilter::WARN),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Layer is now imported at module scope (needed for .with_filter(...) here), the use tracing_subscriber::Layer; inside the debug OTEL block becomes redundant and may trip unused_imports under -D warnings. Worth dropping the inner use.

@richiemcilroy richiemcilroy merged commit 9d51a58 into main May 12, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant