fix: data-safety fixes from the project review

- autosave: pages a failed flush could not write no longer end up in the
  notebook that is opened next; they go to the recovery folder
- app: finish an open text edit and save before switching notebooks, and
  stay in the notebook when saving fails
- trash: purge by the deletion time in the entry name, not by the folder's
  modification time, which survives the move into .trash
- notebooks are identified by their folder name, so a copied folder is a
  notebook of its own instead of an alias of the original
- manifest: fall back to manifest.json.bak only for a damaged file, not for
  a newer format version or an I/O error
- strokes keep their width after scaling and a restart
- PDF export draws shapes with their stroke width instead of a fixed 1.5 pt
- a first page that fails to load shows the warning; a first page that was
  never saved is an empty page; make-space refuses read-only pages

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
haraldandClaude Fable 5.2 committed 2026-11-14 15:39:54 +01:00
1 parent 7c69edab12
commit c2652ea05b
6 files changed
+540 -135

No files matched your search

+27 -1
View File
@@ -691,6 +691,15 @@ impl Stroke {
if let Some(ref mut shape) = self.shape {
shape.scale(origin, factors);
}
// The line width grows with the stroke. It is stored and the mesh is rebuilt from it
// on load; without this a scaled stroke would be back at its old width after a restart.
// Images and text fields have no line width.
if !self.is_object() {
let factor = (factors.x * factors.y).abs().sqrt();
if factor.is_finite() && factor > 0.0 {
self.width *= factor;
}
}
self.recompute_aabb();
}
@@ -1574,7 +1583,12 @@ impl Notebook {
/// der Seite und gibt die Kennungen der geänderten Seiten zurück (`None`: nichts zu tun).
pub fn make_space(&mut self, page_index: usize, y: f32, dy: f32) -> Option<Vec<u64>> {
let run = self.space_run(page_index)?;
if self.pages[run.clone()].iter().any(|p| !p.is_loaded) {
// A page that could not be loaded is never saved: strokes pushed onto it would be gone
// after a restart, and its own content is unknown
if self.pages[run.clone()]
.iter()
.any(|p| !p.is_loaded || p.is_readonly())
{
return None;
}
let dy = if dy < 0.0 {
@@ -2680,6 +2694,18 @@ mod tests {
);
}
#[test]
fn test_make_space_refuses_pages_that_failed_to_load() {
let mut nb = Notebook::new();
nb.add_page(PageBackground::Blank);
nb.pages[0].strokes = vec![block(1, 1000.0, 100.0)];
nb.pages[1].persist = PersistState::ReadOnlyAfterLoadError;
assert!(nb.make_space(0, 900.0, 300.0).is_none());
assert_eq!(top_of(&nb, 0, 1), Some(1000.0));
nb.pages[1].persist = PersistState::Normal;
assert!(nb.make_space(0, 900.0, 300.0).is_some());
}
#[test]
fn test_remove_and_restore_current_page() {
let mut nb = Notebook::new();