Merge: recover a damaged catalog, and capture a crash locally

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-30 13:45:18 +02:00
co-authored by Claude Opus 5
15 changed files with 1954 additions and 14 deletions
+43
View File
@@ -11,6 +11,7 @@ import { GestureRow } from "gestures.slint";
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
import { CollectionsPanel, CollectionRow, OfflinePrompt } from "collections.slint";
import { HistogramPanel, HistogramView } from "histogram.slint";
import { RecoveryPrompt } from "recovery.slint";
import { PresetSheet, ScopeChips, ScopeKind } from "presets.slint";
import { FocusMarks, FocusPanel } from "peaking.slint";
import { SettingsPage } from "settings.slint";
@@ -365,6 +366,21 @@ export component AppWindow inherits Window {
callback offline-prompt-release();
callback offline-prompt-dismiss();
// The question a damaged catalog asks. Same shape as the prompt above and
// for the same reason: an empty title is what closes it, and every word in
// it is composed in Rust, which is the only side that knows what SQLite
// said and which backups exist.
in property <string> recovery-title: "";
in property <string> recovery-detail: "";
in property <string> recovery-diagnosis: "";
in property <string> recovery-restore-label: "";
in property <bool> recovery-can-restore: false;
in property <string> recovery-rebuild-label: "";
in property <bool> recovery-busy: false;
callback recovery-restore();
callback recovery-rebuild();
callback recovery-dismiss();
in property <string> library-root-label: "";
in-out property <[TimelineBar]> library-timeline;
in property <string> library-timeline-label: "";
@@ -1152,6 +1168,14 @@ in property <bool> panel-visible: true;
// would leave the library from behind an open question — the
// view changing underneath a modal, which reads as the app
// having lost its place.
//
// The recovery question is asked first because it is drawn
// over everything, the offline prompt included: Back must
// reach the thing the user can actually see.
if (root.recovery-title != "") {
root.recovery-dismiss();
return accept;
}
if (root.offline-prompt-title != "") {
root.offline-prompt-dismiss();
return accept;
@@ -2672,5 +2696,24 @@ in property <bool> panel-visible: true;
release() => { root.offline-prompt-release(); }
dismiss() => { root.offline-prompt-dismiss(); }
}
// Last, and therefore over everything including the settings page and
// the offline prompt. Not a preference about layering: this is asked
// before the grid exists, and nothing else in the window is about a
// library that can be read.
RecoveryPrompt {
width: 100%;
height: 100%;
title: root.recovery-title;
detail: root.recovery-detail;
diagnosis: root.recovery-diagnosis;
restore-label: root.recovery-restore-label;
can-restore: root.recovery-can-restore;
rebuild-label: root.recovery-rebuild-label;
busy: root.recovery-busy;
restore() => { root.recovery-restore(); }
rebuild() => { root.recovery-rebuild(); }
dismiss() => { root.recovery-dismiss(); }
}
}
}