App: crash reports and a log file; Help › Report a Bug (#107)

The desktop app installs the crash reporter and a log file at start
(both in a temporary folder in --control and CI runs, #266), journals
the window's commands (background jobs included) and every control
request's method, and ends the journal on a clean exit. A start after a
crash asks: "FieldCraft closed unexpectedly", Report It… or Not Now.

Help › Report a Bug (also Tools › Help and ui.start
diagnostics.bug_report) shows exactly what the GitHub bug form will be
filled with: the version, the OS, the last error, and for a crash where
it happened. Share my session adds the replayable commands, Include the
model file the model. Open GitHub Issue opens the form in the browser
through the system's opener; files to attach are saved to
bug-reports/<time>/ and a long session goes on the clipboard. Nothing is
sent without that click and the user's submit on GitHub.

The control channel answers the new prompts (crashed: report, close;
report_bug: set, close) and ui.inspect shows the draft; it never opens
the browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 00:36:18 -04:00
1 parent cb7e3a2711
commit e06c85eb9e
9 files changed
+503 -8

No files matched your search

+20 -1
View File
@@ -164,6 +164,8 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) ->
let f = |k: &str| p.get(k).and_then(Value::as_f64).filter(|x| x.is_finite());
let b = |k: &str| p.get(k).and_then(Value::as_bool).unwrap_or(false);
let mods = || egui::Modifiers { shift: b("shift"), ctrl: b("ctrl"), command: b("ctrl"), alt: b("alt"), ..Default::default() };
// A crash nothing catches (#248) still says which request came last.
fieldcraft_engine::crash::activity(&format!("control {}", req.method));
match req.method.as_str() {
"engine.execute" => {
let Some(id) = s("command") else { return err("missing `command`") };
@@ -202,6 +204,8 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) ->
crate::files::Prompt::Design(p) => json!({"design": p}),
crate::files::Prompt::ReplaceMesh(r) => json!({"replace_mesh": r.question, "warnings": r.warnings}),
crate::files::Prompt::Delete(d) => json!({"delete": d.question(), "kind": d.kind, "name": d.name, "users": d.users, "allowed": d.allowed()}),
crate::files::Prompt::Crashed(r) => json!({"crashed": r}),
crate::files::Prompt::ReportBug => json!({"report_bug": app.report.as_ref().and_then(|f| f.drafted_json())}),
}),
"recent": app.recent.paths,
"version": crate::VERSION,
@@ -374,6 +378,21 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) ->
app.prompt = None;
ok(Value::Null)
}
(crate::files::Prompt::Crashed(r), Some("report")) => {
app.report_bug(r.first().map(|c| c.id.clone()));
ok(json!({"prompt": "report_bug"}))
}
(crate::files::Prompt::Crashed(_), Some("close" | "cancel")) => {
app.prompt = None;
ok(Value::Null)
}
// The form's fields; sending opens the browser, which only a click does.
(crate::files::Prompt::ReportBug, Some("set")) => wrap(app.set_report(p)),
(crate::files::Prompt::ReportBug, Some("close" | "cancel")) => {
app.prompt = None;
app.report = None;
ok(Value::Null)
}
(crate::files::Prompt::Recover, Some("recover")) => match s("id") {
Some(id) => {
app.recover(id);
@@ -382,7 +401,7 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) ->
None => err("missing `id` (from file.recovery.list)"),
},
(_, a) => err(format!(
"`answer` {a:?} does not answer this prompt (unsaved: save, discard, cancel; design: new, keep, cancel; replace_mesh: replace, cancel; delete: delete, cancel; recover: recover with id, close; samples: open with name, close)"
"`answer` {a:?} does not answer this prompt (unsaved: save, discard, cancel; design: new, keep, cancel; replace_mesh: replace, cancel; delete: delete, cancel; recover: recover with id, close; samples: open with name, close; crashed: report, close; report_bug: set with title/observed/share_session/include_model, close)"
)),
}
}