docs/ had 26 developer documents flat beside the manual, and the two audiences are very differently sized: most readers want the manual and the gesture reference, a few want the register, the designs and the measurements. The manual and gestures.md stay at the top; everything for someone changing the code moves to docs/dev/, and the two documents that name their own successors — the v0.1 milestone and the UI-refinement plan — go to docs/dev/archive/ rather than being deleted, since both are still cited. docs/README.md is the index, users first. Every reference follows: code comments, Cargo manifests, the workflows, the pre-commit hook, the bench and traceability tools (which locate the repo root by docs/dev/requirements.md now), packaging, the Docker READMEs, CLAUDE.md, CONTRIBUTING.md and the README. The matrix links one level deeper and is regenerated. Links out of the moved documents into the tree gain a level; a link checker over every Markdown file finds none broken.
146 lines
5.7 KiB
Plaintext
146 lines
5.7 KiB
Plaintext
// The question asked when the catalog turns out to be damaged.
|
|
//
|
|
// # Why this is a modal, when almost nothing else here is
|
|
//
|
|
// The house rule in this interface is to put the consequence in the button's
|
|
// label rather than to raise a dialogue — "Export 40", "Empty trash · 128" —
|
|
// and a genuine modal is kept for the two cases where the answer commits
|
|
// gigabytes. This is the third case, and it earns it for a different reason:
|
|
// there is nothing behind it to interact with. The grid cannot be drawn, the
|
|
// scan must not run (it would write into the damage), and every control in the
|
|
// window is about a library that cannot be read. A banner over an empty grid
|
|
// would be a question the user could scroll away from and then wonder why
|
|
// nothing worked.
|
|
//
|
|
// # Why the backdrop does not dismiss it
|
|
//
|
|
// Every other overlay here closes on a tap outside, and this one deliberately
|
|
// does not. A stray tap that loses the two offers leaves the application in a
|
|
// state with no way forward and no obvious way back to the question. There is
|
|
// a "Leave it for now" button instead, which says what it does.
|
|
//
|
|
// # Why the destructive answer is not the primary one
|
|
//
|
|
// A restore keeps the user's collections; a rebuild cannot, because a manual
|
|
// collection is a set of images the user assembled by hand and nothing in the
|
|
// filesystem records it (docs/dev/catalog.md §8.1). So the two answers are not
|
|
// interchangeable, the difference is stated in the button rather than in a
|
|
// second dialogue after it, and the rebuild is the plain button even when it
|
|
// is the only one available.
|
|
|
|
import { Theme } from "theme.slint";
|
|
import { Button } from "widgets.slint";
|
|
|
|
export component RecoveryPrompt inherits Rectangle {
|
|
/// What went wrong, in the user's terms. Empty closes the prompt — one
|
|
/// source for "is this open", rather than a bool that can disagree with
|
|
/// the words beside it.
|
|
in property <string> title;
|
|
/// What is safe and what is not, which is the part that determines whether
|
|
/// the next minute is frightening.
|
|
in property <string> detail;
|
|
/// What SQLite actually said, kept because a bug report needs it and
|
|
/// because a diagnosis the user can read is worth more than a reassurance
|
|
/// they cannot check.
|
|
in property <string> diagnosis;
|
|
/// The restore offer, naming the backup's date. Empty when there is no
|
|
/// backup to restore from, which is the case a fresh install is in.
|
|
in property <string> restore-label;
|
|
in property <bool> can-restore: false;
|
|
/// The rebuild offer, naming what it costs — a full rescan, and the
|
|
/// collections it cannot bring back.
|
|
in property <string> rebuild-label;
|
|
/// Set while a restore or rebuild is running, so neither can be started
|
|
/// twice against the same file.
|
|
in property <bool> busy: false;
|
|
|
|
callback restore();
|
|
callback rebuild();
|
|
callback dismiss();
|
|
|
|
visible: root.title != "";
|
|
background: #000000E0;
|
|
|
|
// Swallows everything that misses the card, and answers nothing. See the
|
|
// header: losing this by a stray tap leaves nowhere to go.
|
|
TouchArea { }
|
|
|
|
Rectangle {
|
|
width: min(460px, parent.width - 2 * Theme.gap-lg);
|
|
height: min(card.preferred-height, parent.height - 2 * Theme.gap-lg);
|
|
x: (parent.width - self.width) / 2;
|
|
y: (parent.height - self.height) / 2;
|
|
background: Theme.surface;
|
|
border-radius: Theme.radius;
|
|
border-width: 1px;
|
|
border-color: Theme.rule;
|
|
|
|
TouchArea { }
|
|
|
|
card := VerticalLayout {
|
|
padding: Theme.gap-lg;
|
|
spacing: Theme.gap;
|
|
|
|
Text {
|
|
text: "Recover library";
|
|
color: Theme.ink-faint;
|
|
font-size: Theme.text-sm;
|
|
font-weight: 700;
|
|
letter-spacing: 1.2px;
|
|
}
|
|
|
|
Text {
|
|
text: root.title;
|
|
color: Theme.ink;
|
|
font-size: Theme.text-lg;
|
|
font-weight: 600;
|
|
wrap: word-wrap;
|
|
}
|
|
|
|
Text {
|
|
text: root.detail;
|
|
color: Theme.ink-dim;
|
|
font-size: Theme.text;
|
|
wrap: word-wrap;
|
|
}
|
|
|
|
// Wrapped rather than elided: this is the one line a bug report
|
|
// needs verbatim, and a truncated SQLite message is no message.
|
|
Text {
|
|
text: root.diagnosis;
|
|
color: Theme.ink-faint;
|
|
font-size: Theme.text-sm;
|
|
wrap: word-wrap;
|
|
}
|
|
|
|
Rectangle { height: 1px; background: Theme.rule; }
|
|
|
|
// Stacked, not a row: each label carries what its answer costs —
|
|
// a date, a count of photographs — and three of those side by side
|
|
// elide away exactly the part that lets the user choose.
|
|
if root.can-restore: Button {
|
|
text: root.busy ? "Working…" : root.restore-label;
|
|
primary: true;
|
|
enabled: !root.busy;
|
|
clicked => { root.restore(); }
|
|
}
|
|
|
|
Button {
|
|
text: root.busy ? "Working…" : root.rebuild-label;
|
|
// Primary only when it is the only answer there is. A rebuild
|
|
// discards collections, so it does not get the emphasis while
|
|
// a restore that keeps them is on the table.
|
|
primary: !root.can-restore;
|
|
enabled: !root.busy;
|
|
clicked => { root.rebuild(); }
|
|
}
|
|
|
|
Button {
|
|
text: "Leave it for now";
|
|
enabled: !root.busy;
|
|
clicked => { root.dismiss(); }
|
|
}
|
|
}
|
|
}
|
|
}
|