Engine: crash reports, a log file and bug report drafts (#107)

crash: a panic hook and a locked per-run command journal (adapted from
SolidCraft's crash reports). A panic in a command becomes a report with
the model from before it and the command; one outside commands, or an
unclean exit found at the next start (abort, segfault, a crash in a
graphics driver, a kill: what #248 needs), a report with the model the
journal starts from (autosave, file or a copy) and the commands since,
the last control request and the dead run's log lines. Reports are
bounded, deduplicated by signature and replayable (crash::replay, with
writes confined to a scratch folder). The crash folder follows the
automation temp area in --control and CI runs.

logfile: a small size-rotated file logger behind the log facade.

bug_report: GitHub bug-form drafts. Version, OS and what happened by
default, home folder shown as ~; the command journal only with Share my
session, the model only with Include the model as well; a session too
long for the link becomes a file to attach and the clipboard text.

Commands: diagnostics.crash_reports, diagnostics.crash_report,
diagnostics.bug_report (Tools › Help, with its icon) and
diagnostics.crash_test (FIELDCRAFT_CRASH_TEST=1 only).

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 3d51bde9dd
commit 080ff048c6
15 files changed
+2200 -2

No files matched your search

+26 -1
View File
@@ -1433,6 +1433,23 @@ pub const FILE_SAVE: &[Field] = &[path("path", "File", true).help("Where to save
pub const FILE_SAVE_AS: &[Field] = &[path("path", "File", true).help("The file to save to.").req()];
pub const RECOVERY_LIST: &[Field] =
&[path("dir", "Folder", false).help("The recovery folder; the default one when empty.").hint("the recovery folder")];
pub const CRASH_REPORTS: &[Field] = &[
int("limit", "Limit", 1, 1000).help("At most this many reports, newest first.").hint("20"),
path("dir", "Folder", false).help("The crash folder; the default one when empty.").hint("the crash folder"),
];
pub const CRASH_REPORT: &[Field] = &[
text("id", "Report").help("The crash report to show.").req(),
path("dir", "Folder", false).help("The crash folder; the default one when empty.").hint("the crash folder"),
];
pub const BUG_REPORT: &[Field] = &[
text("title", "Title").help("The issue's title; one is made up when empty."),
text("observed", "What happened").help("What went wrong; the last error when empty."),
text("crash", "Crash report").help("A crash report to report (its id)."),
flag("share_session", "Share my session").help("Attach the commands that built the model (they can name your files).").def("false"),
flag("include_model", "Include the model").help("Attach the model file too (with Share my session).").def("false"),
];
pub const CRASH_TEST: &[Field] =
&[choice("mode", "Mode", &[("panic", "Panic"), ("recovered", "Recovered panic"), ("abort", "Abort")]).help("How to crash.").req()];
pub const RECOVER: &[Field] = &[
text("id", "Autosave").help("The autosave to recover.").req(),
path("dir", "Folder", false).help("The recovery folder; the default one when empty.").hint("the recovery folder"),
@@ -1697,7 +1714,15 @@ mod tests {
}
}
const STATE: &[&str] = &["open a design first", "`boundary_conditions` or `loads`", "give one of", "is not an explicit", "has a result"];
const STATE: &[&str] = &[
"open a design first",
"`boundary_conditions` or `loads`",
"give one of",
"is not an explicit",
"has a result",
"no crash report",
"FIELDCRAFT_CRASH_TEST",
];
/// Fill top-level required names (a fresh one) and references (the first existing item).
fn fill_required(s: &Session, form: &[Field], p: &mut Value) {