Merge PR #2886: Recovery: keep explicit New/Open visible after startup discovery

crates/ui-egui/src/backstage.rs: #3125's t.nav fill kept, without the recovery black. Docs: status entries prepended to main's lines; the recovery paragraph placed after the status line.
This commit is contained in:
aiongg
2026-10-22 01:54:24 +00:00
6 changed files with 118 additions and 5 deletions
+3 -1
View File
File diff suppressed because one or more lines are too long
+99 -1
View File
@@ -761,7 +761,7 @@ mod tests {
}
#[cfg(not(target_arch = "wasm32"))]
fn recovery_app() -> (WordApp, std::path::PathBuf) {
fn recovery_fixture() -> (WordApp, std::path::PathBuf) {
let root = std::env::temp_dir().join(format!(
"wordcraft-recovery-ui-{}-{}",
std::process::id(),
@@ -780,10 +780,42 @@ mod tests {
)
.unwrap();
let mut app = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), Services::default());
app.ui.language = "en".into();
(app, root)
}
#[cfg(not(target_arch = "wasm32"))]
fn recovery_app() -> (WordApp, std::path::PathBuf) {
let (mut app, root) = recovery_fixture();
app.session.enable_recovery(root.clone(), std::sync::Arc::new(|| {})).unwrap();
(app, root)
}
/// Hold discovery polling until the accessible control has dispatched its command.
/// Warming up first keeps font installation independent of worker timing.
#[cfg(not(target_arch = "wasm32"))]
fn pending_recovery_harness(
app: WordApp,
root: &std::path::Path,
) -> (egui_kittest::Harness<'static, WordApp>, std::rc::Rc<std::cell::Cell<bool>>) {
let poll = std::rc::Rc::new(std::cell::Cell::new(true));
let gate = poll.clone();
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).build_ui_state(
move |ui, app: &mut WordApp| {
if gate.get() {
app.logic(&ui.ctx().clone());
}
app.ui(ui);
},
app,
);
h.run_steps(4);
poll.set(false);
h.state_mut().session.enable_recovery(root.to_path_buf(), std::sync::Arc::new(|| {})).unwrap();
assert!(!h.state().session.recovery_ready());
(h, poll)
}
#[cfg(not(target_arch = "wasm32"))]
fn recovery_harness(app: WordApp) -> egui_kittest::Harness<'static, WordApp> {
egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).build_ui_state(
@@ -862,6 +894,72 @@ mod tests {
}
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn recovery_does_not_replace_an_explicit_new_document() {
for template in ["Blank document", "Report"] {
let (mut app, root) = recovery_fixture();
app.ui.backstage = true;
app.ui.backstage_page = "home".into();
let (mut h, poll) = pending_recovery_harness(app, &root);
let previous = h.state().session.document_id();
h.get_by_label("New").click();
h.run_steps(4);
h.get_by_label(template).click();
h.run_steps(4);
assert!(!h.state().session.recovery_ready());
let document = h.state().session.document_id();
let text = h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body);
assert_ne!(document, previous);
poll.set(true);
settle_recovery(&mut h);
assert!(!h.state().ui.backstage, "the requested template must stay visible");
assert_eq!(h.state().session.document_id(), document);
assert_eq!(h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body), text);
assert_eq!(h.state().session.recovery_documents().len(), 1, "older copies remain available");
h.state_mut().ui.backstage = true;
h.run_steps(4);
h.get_by_label("Recover").click();
h.run_steps(4);
assert!(h.state().ui.backstage && h.state().ui.backstage_page == "recover");
drop(h);
let _ = std::fs::remove_dir_all(root);
}
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn recovery_does_not_replace_an_explicitly_opened_file() {
let (mut app, root) = recovery_fixture();
let path = root.join("Requested.txt");
std::fs::write(&path, "Requested synthetic document").unwrap();
app.services.pick_open = Some(Box::new(move |_| Some(path.to_string_lossy().into_owned())));
app.ui.backstage = true;
app.ui.backstage_page = "home".into();
let (mut h, poll) = pending_recovery_harness(app, &root);
let previous = h.state().session.document_id();
h.get_by_label("Open").click();
h.run_steps(4);
h.get_by_label("Browse…").click();
h.run_steps(4);
assert!(!h.state().session.recovery_ready());
let document = h.state().session.document_id();
assert_ne!(document, previous);
poll.set(true);
settle_recovery(&mut h);
assert!(!h.state().ui.backstage);
assert_eq!(h.state().session.document_id(), document);
assert_eq!(h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body), "Requested synthetic document");
assert_eq!(h.state().session.recovery_documents().len(), 1);
h.state_mut().ui.backstage = true;
h.run_steps(4);
h.get_by_label("Recover").click();
h.run_steps(4);
assert!(h.state().ui.backstage && h.state().ui.backstage_page == "recover");
drop(h);
let _ = std::fs::remove_dir_all(root);
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn recovery_page_keeps_dismissed_copies_and_confirms_discard() {
+3
View File
@@ -1068,6 +1068,9 @@ impl WordApp {
// When the document has been replaced (not when Open only showed its picker), a pending
// "Save changes?" and AutoSave's go-ahead were both about the old one.
if self.session.document_id() != document {
// A requested New/Open wins over the asynchronous startup recovery scan.
// Older copies remain available through File > Recover.
self.recovery_seen = true;
self.autosave_path = None;
if matches!(self.dialog, Some(dialogs::Dialog::SaveChanges { .. })) {
self.dialog = None;
+4 -1
View File
@@ -1,6 +1,8 @@
# Where WordCraft falls short of Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** trivial (text mini toolbar available with the right-click menu, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: tracked table/row/cell/section property changes record in WordCraft, narrowing gap 5; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: Format Page Numbers dialog carries Word's number formats and Continue from previous section / Start at, #1113; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: #8: English dialects — en-GB, en-CA, en-AU… flag each other's spellings, #1281; previously trivial: #1: shape gradients and transparency, #1500; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: line breaking: a slash opening a word travels whole to the next line, #2963; previously trivial: recipient list search and merged-only view, #1576; previously trivial: #2 impact restated: only ink rendering and OLE editing remain; previously trivial: #2 narrowed: native ink nested in groups and drawing canvases kept on save; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: automatic Asian/Latin and Asian/number spacing laid out, #2358; previously trivial: hint-reliant CJK outlines; previously trivial: numbered headings, #1793; previously trivial: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: #8 narrowed: Indonesian proofing from the installed Hunspell dictionary, #498; previously trivial: glossary/building blocks closed; previously trivial: Split Cells and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: consistent preferred cell widths; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** trivial (explicit documents keep the editor when recovery discovery finishes; readable recovery controls; previously trivial: text mini toolbar available with the right-click menu, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: tracked table/row/cell/section property changes record in WordCraft, narrowing gap 5; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: Format Page Numbers dialog carries Word's number formats and Continue from previous section / Start at, #1113; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: #8: English dialects — en-GB, en-CA, en-AU… flag each other's spellings, #1281; previously trivial: #1: shape gradients and transparency, #1500; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: line breaking: a slash opening a word travels whole to the next line, #2963; previously trivial: recipient list search and merged-only view, #1576; previously trivial: #2 impact restated: only ink rendering and OLE editing remain; previously trivial: #2 narrowed: native ink nested in groups and drawing canvases kept on save; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: automatic Asian/Latin and Asian/number spacing laid out, #2358; previously trivial: hint-reliant CJK outlines; previously trivial: numbered headings, #1793; previously trivial: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: #8 narrowed: Indonesian proofing from the installed Hunspell dictionary, #498; previously trivial: glossary/building blocks closed; previously trivial: Split Cells and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: consistent preferred cell widths; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
Recovery discovery no longer interrupts an explicitly created or opened document. Older copies remain in File › Recover, whose buttons use the interface palette. See [local recovery](recovery.md).
> **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below.
@@ -123,6 +125,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps
| Date | Change | Summary |
|---|---|---|
| 2026-10-18 | trivial | Explicit New/Open is not interrupted by startup recovery discovery; older copies stay available and Recover follows the interface theme. |
| 2026-10-21 | trivial | Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459) |
| 2026-10-21 | trivial | #3172: the ribbon's font name and size boxes, and the dropdown arrows beside them, say what they are to a screen reader |
| 2026-10-21 | trivial | Gap 19: ODT table column widths are read on import, so they survive a save |
+5 -1
View File
@@ -1,7 +1,11 @@
# Local document recovery
Desktop WordCraft periodically keeps a local copy of unsaved work, including new drafts.
File › Recover lists available copies. The page opens at startup when copies are found.
File › Recover lists available copies. The page opens at startup when copies are found,
unless a document was explicitly created or opened before the startup scan completed.
In particular, a window launched for New/Open keeps the requested document visible;
older copies remain available through File › Recover. Recovery uses the regular interface
palette so its labels and buttons stay readable in light and dark themes.
Restore opens an unsaved document with no file path. Save As chooses where to keep it.
Recovery never writes to the original document. AutoSave's permission to write to an earlier
file is cleared on restore.
+4 -1
View File
File diff suppressed because one or more lines are too long