From b6580f197dfc813a058e76c0481b4f2bca784a3b Mon Sep 17 00:00:00 2001 From: Harald Date: Wed, 7 Oct 2026 07:03:08 +0200 Subject: [PATCH] =?UTF-8?q?fix(storage):=20Inhalt=20der=20zuletzt=20ge?= =?UTF-8?q?=C3=B6ffneten=20Seite=20nach=20Neustart=20nicht=20=C3=BCberschr?= =?UTF-8?q?eiben?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim Start wurde nur die erste Seite geladen. War zuletzt eine spätere Seite offen, war sie nach dem Neustart ein leerer, ungeladener Platzhalter: Das Blatt erschien leer, und Zeichnen/Radieren/Hintergrund wechseln speicherte den Platzhalter und überschrieb den bisherigen Inhalt der Seite. - load_notebook lädt zusätzlich die aktuelle Seite - Autosave schreibt nie ungeladene Platzhalter (Schutz ging beim Merge des Storage-Branches verloren) - Regressionstests für beide Fälle Co-Authored-By: Claude Opus 5.5 --- src/autosave.rs | 7 +++++- src/storage.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/autosave.rs b/src/autosave.rs index 69d45bc..279bef6 100644 --- a/src/autosave.rs +++ b/src/autosave.rs @@ -312,7 +312,12 @@ impl AutosaveController { /// Markiert eine Seite als geändert und startet/erneuert das Debounce-Fenster. pub fn mark_page_dirty(&mut self, page: &Page, manifest: &NotebookManifest) { - if self.read_only || page.persist == crate::document::PersistState::ReadOnlyAfterLoadError { + // Nicht geladene Platzhalter nie schreiben: Sie sind leer und würden den Inhalt + // der Seitendatei überschreiben + if self.read_only + || !page.is_loaded + || page.persist == crate::document::PersistState::ReadOnlyAfterLoadError + { return; } let page_data = PageData::from(page); diff --git a/src/storage.rs b/src/storage.rs index b4d5d0e..b924203 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -697,6 +697,15 @@ pub fn load_notebook(notebook_dir: &Path) -> Result<(Notebook, NotebookManifest) ); notebook.load_warnings = load_warnings; + // Die zuletzt geöffnete Seite sofort laden: Sie ist nach dem Start die aktuelle Seite, + // und ein leerer Platzhalter würde beim Weiterschreiben ihren Inhalt überschreiben. + let current = notebook.current_page_index; + if !notebook.pages[current].is_loaded { + if let Err(e) = ensure_page_loaded(notebook_dir, &mut notebook, current) { + eprintln!("[Storage] Aktuelle Seite {current} konnte nicht geladen werden: {e}"); + } + } + Ok((notebook, manifest)) } @@ -2167,4 +2176,61 @@ mod tests { assert_eq!(loaded.pages[1].background, PageBackground::Lined); assert!(crate::export::generate_pdf_document(&loaded, Default::default()).is_ok()); } + + #[test] + fn test_restart_on_later_page_keeps_its_content() { + // Regression: Beim Start wurde nur Seite 1 geladen; war zuletzt eine spätere Seite + // offen, war sie ein leerer Platzhalter, und Weiterschreiben überschrieb ihren Inhalt. + let temp_dir = tempfile::tempdir().unwrap(); + let (mut notebook, mut manifest, dir) = + create_new_notebook(temp_dir.path(), "Neustart").unwrap(); + notebook.add_page(PageBackground::Lined); + notebook.pages[1] = page_with_one_stroke(notebook.pages[1].id, PageBackground::Lined); + notebook.current_page_index = 1; + save_notebook(&dir, ¬ebook, &mut manifest).unwrap(); + + let (mut loaded, mut loaded_manifest) = load_notebook(&dir).unwrap(); + assert_eq!(loaded.current_page_index, 1); + assert!( + loaded.current_page().is_loaded, + "aktuelle Seite muss geladen sein" + ); + assert_eq!(loaded.current_page().strokes.len(), 1); + + // Weiterschreiben und speichern darf den bisherigen Inhalt nicht verlieren + let extra = page_with_one_stroke(99, PageBackground::Lined).strokes[0].clone(); + loaded.current_page_mut().strokes.push(extra); + let mut autosave = AutosaveController::new(dir.clone(), Duration::from_secs(60)); + autosave.mark_dirty(&loaded, &mut loaded_manifest); + autosave.flush().unwrap(); + autosave.shutdown().unwrap(); + + let (reloaded, _) = load_notebook(&dir).unwrap(); + assert_eq!(reloaded.current_page().strokes.len(), 2); + } + + #[test] + fn test_autosave_never_writes_unloaded_placeholder() { + let temp_dir = tempfile::tempdir().unwrap(); + let (mut notebook, mut manifest, dir) = + create_new_notebook(temp_dir.path(), "Platzhalter").unwrap(); + notebook.add_page(PageBackground::Lined); + notebook.pages[1] = page_with_one_stroke(notebook.pages[1].id, PageBackground::Lined); + save_notebook(&dir, ¬ebook, &mut manifest).unwrap(); + let page_file = dir.join(format!("page_{}.json", notebook.pages[1].id)); + let before = fs::read(&page_file).unwrap(); + + // Platzhalter (nicht geladen) als aktuelle Seite markieren und speichern + let placeholder = Page::new_unloaded(notebook.pages[1].id, PageBackground::Lined); + let mut autosave = AutosaveController::new(dir.clone(), Duration::from_secs(60)); + autosave.mark_page_dirty(&placeholder, &manifest); + autosave.flush().unwrap(); + autosave.shutdown().unwrap(); + + assert_eq!( + fs::read(&page_file).unwrap(), + before, + "Seitendatei wurde überschrieben" + ); + } }