mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-01 06:16:58 -06:00
* File › Close closes the document and keeps the window (#1712) File › Close (Mod+W, Mod+F4) quit the whole app. In Word it closes the document and the window stays. WordCraft already has that state from the Start screen (#1129): no page, the ribbon greyed out but for Help, File › New and Open to go on. `WordApp::execute` now handles `file.close` itself. After the usual Save / Don't Save / Cancel, the document is replaced by the placeholder blank document that marks "no document open" and the Backstage closes. The window's close button passes `{"quit": true}`, so it still asks and then quits. The parameter travels with the prompt, so a Cancel leaves no state behind. With no document open, Close has nothing to close. Tests: the Close tests that used `quit_requested` as "Close went through" now check that the document closed and the window stayed. Closing from the empty window asks nothing, File › New goes on from there, and the window's close button still quits after Don't Save. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Close keeps the window for programmatic closes too (#1712, #1391) #1391 landed on main meanwhile: a programmatic `file.close` on unsaved work needs `{"force": true}`. Its tests, and the save prompt's focus test, took "Close went through" to mean `quit_requested`. With #1712 a close, forced or not, closes the document and keeps the window, so they now check that the document closed and the app is still running. The `force` guard itself is unchanged. docs/control-protocol.md: a closed document leaves the window open, and `file.new` or `file.open` goes on from there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1712: a direct file.close never discards unsaved changes unless forced; Don't Save forces it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f5b3c5d069
commit
879e688fec
@@ -832,17 +832,17 @@ mod tests {
|
||||
h.get_by_label("Close").click();
|
||||
h.run_steps(4);
|
||||
assert!(matches!(h.state().dialog, Some(crate::dialogs::Dialog::SaveChanges { .. })), "unsaved changes ask first");
|
||||
assert!(!h.state().quit_requested);
|
||||
assert!(h.state().has_document());
|
||||
|
||||
h.state_mut().run("ui.saveChanges", json!({"answer": "cancel"})).unwrap();
|
||||
h.run_steps(4);
|
||||
assert!(!h.state().quit_requested, "Cancel keeps the document open");
|
||||
assert!(h.state().has_document(), "Cancel keeps the document open");
|
||||
assert!(h.state().ui.backstage && h.state().ui.backstage_page == "info", "Cancel leaves the Backstage as it was");
|
||||
|
||||
h.get_by_label("Close").click();
|
||||
h.run_steps(4);
|
||||
h.state_mut().run("ui.saveChanges", json!({"answer": "dontSave"})).unwrap();
|
||||
assert!(h.state().quit_requested, "Don't Save closes");
|
||||
assert!(!h.state().has_document() && !h.state().quit_requested, "Don't Save closes the document, not the window (#1712)");
|
||||
}
|
||||
|
||||
/// A document without unsaved changes closes without a prompt.
|
||||
@@ -862,7 +862,7 @@ mod tests {
|
||||
h.get_by_label("Close").click();
|
||||
h.run_steps(1);
|
||||
assert!(h.state().dialog.is_none(), "no prompt");
|
||||
assert!(h.state().quit_requested);
|
||||
assert!(!h.state().has_document() && !h.state().quit_requested);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -107,7 +107,7 @@ fn click_events(app: &mut WordApp, pos: egui::Pos2, button: egui::PointerButton,
|
||||
|
||||
/// Programmatic calls never open the Save prompt, so closing unsaved work must be explicit:
|
||||
/// save first, or pass `"force": true` (only a real boolean true counts) to discard it (#1268).
|
||||
fn close_refused(app: &WordApp, id: &str, params: &Value) -> Option<&'static str> {
|
||||
pub(crate) fn close_refused(app: &WordApp, id: &str, params: &Value) -> Option<&'static str> {
|
||||
let force = params.get("force").and_then(Value::as_bool) == Some(true);
|
||||
(id == "file.close" && app.session.dirty && !force)
|
||||
.then_some("the document has unsaved changes: save it first, or pass {\"force\": true} to discard them and close")
|
||||
|
||||
@@ -526,10 +526,10 @@ mod tests {
|
||||
let r = a.run("ui.saveChanges", json!({"answer": "save"})).unwrap();
|
||||
assert_eq!(r["pending"], "saveAs");
|
||||
a.poll_file_dialog();
|
||||
assert!(!a.quit_requested, "nothing closes before the save");
|
||||
assert!(a.has_document(), "nothing closes before the save");
|
||||
answer(&shown, Some(&path.to_string_lossy()));
|
||||
a.poll_file_dialog();
|
||||
assert!(a.quit_requested, "closed once saved");
|
||||
assert!(!a.has_document(), "closed once saved");
|
||||
assert!(saved_text(&path).contains(UNSAVED));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
+76
-18
@@ -914,6 +914,9 @@ impl WordApp {
|
||||
if let Some(r) = self.ui_command(id, ¶ms) {
|
||||
return r;
|
||||
}
|
||||
if id == "file.close" {
|
||||
return self.close_document(¶ms);
|
||||
}
|
||||
if id == "file.autosave" {
|
||||
return self.set_autosave(¶ms);
|
||||
}
|
||||
@@ -1075,6 +1078,16 @@ impl WordApp {
|
||||
if document != self.session.document_id() {
|
||||
return Err("the document the prompt asked about has been replaced".into());
|
||||
}
|
||||
// Don't Save is the user's say-so to discard the changes, which Close otherwise refuses.
|
||||
let mut params = params;
|
||||
if choice == SaveChoice::DontSave && then == "file.close" {
|
||||
match params.as_object_mut() {
|
||||
Some(p) => {
|
||||
p.insert("force".into(), json!(true));
|
||||
}
|
||||
None => params = json!({"force": true}),
|
||||
}
|
||||
}
|
||||
let go = match choice {
|
||||
SaveChoice::Cancel => false,
|
||||
SaveChoice::DontSave => true,
|
||||
@@ -1255,10 +1268,33 @@ impl WordApp {
|
||||
if self.quit_requested || !self.session.dirty || !self.has_document() {
|
||||
return true;
|
||||
}
|
||||
let _ = self.run("file.close", json!({}));
|
||||
// `quit`: once the prompt is answered the window closes too, not just the document.
|
||||
let _ = self.run("file.close", json!({"quit": true}));
|
||||
false
|
||||
}
|
||||
|
||||
/// File › Close (Mod+W, Mod+F4) closes the document and keeps the window, as Word does
|
||||
/// (#1712): it shows no page, as after Esc on the Start screen (#1129), and File › New or Open
|
||||
/// goes on from there. `{"quit": true}`, from the window's close button, quits instead. With no
|
||||
/// document open there is nothing to close. Unsaved changes are never discarded unasked: a
|
||||
/// dirty document closes only with `{"force": true}` (Don't Save in the prompt passes it).
|
||||
fn close_document(&mut self, params: &Value) -> Result<Value, String> {
|
||||
if let Some(e) = control::close_refused(self, "file.close", params) {
|
||||
return Err(e.into());
|
||||
}
|
||||
if params.get("quit").and_then(Value::as_bool) == Some(true) {
|
||||
self.quit_requested = true;
|
||||
return Ok(json!({"quit": true}));
|
||||
}
|
||||
if !self.has_document() {
|
||||
return Ok(json!({"closed": false}));
|
||||
}
|
||||
self.execute("file.new", json!({}))?;
|
||||
self.no_document = Some(self.session.document_id());
|
||||
self.ui.backstage = false;
|
||||
Ok(json!({"closed": true}))
|
||||
}
|
||||
|
||||
fn after_command(&mut self, id: &str) {
|
||||
options::sync(self);
|
||||
self.canvas.caret_visible_since = now_ms();
|
||||
@@ -4434,25 +4470,46 @@ mod tests {
|
||||
assert!(body_text(&a).contains(UNSAVED));
|
||||
}
|
||||
|
||||
/// Mod+W ran `file.close`, which quit at once.
|
||||
/// Mod+W ran `file.close`, which quit at once; then it asked first, but still quit the whole
|
||||
/// app. #1712: as in Word, it closes the document and the window stays, showing no page, until
|
||||
/// File › New or Open.
|
||||
#[test]
|
||||
fn close_asks_before_quitting() {
|
||||
fn close_asks_then_closes_the_document_and_keeps_the_window() {
|
||||
let mut a = typed();
|
||||
a.run("file.close", json!({})).unwrap();
|
||||
assert_eq!(prompt(&a), Some("saveChanges"));
|
||||
assert!(!a.quit_requested);
|
||||
a.run("ui.saveChanges", json!({"answer": "cancel"})).unwrap();
|
||||
assert!(!a.quit_requested);
|
||||
assert!(a.has_document() && body_text(&a).contains(UNSAVED), "Cancel keeps the document");
|
||||
|
||||
a.run("file.close", json!({})).unwrap();
|
||||
a.run("ui.saveChanges", json!({"answer": "dontSave"})).unwrap();
|
||||
assert!(a.quit_requested);
|
||||
assert!(!a.quit_requested, "the window stays");
|
||||
assert!(!a.has_document() && !a.session.dirty && !a.ui.backstage, "no document, no page");
|
||||
assert!(a.close_requested(), "closing the empty window asks nothing");
|
||||
a.run("file.close", json!({})).unwrap();
|
||||
assert!(!a.quit_requested && !a.has_document(), "nothing left to close");
|
||||
a.run("file.new", json!({})).unwrap();
|
||||
assert!(a.has_document(), "File › New goes on from there");
|
||||
|
||||
// A clean document closes straight away.
|
||||
let mut clean = app();
|
||||
clean.run("file.close", json!({})).unwrap();
|
||||
assert_eq!(prompt(&clean), None);
|
||||
assert!(clean.quit_requested);
|
||||
assert!(!clean.quit_requested && !clean.has_document());
|
||||
}
|
||||
|
||||
/// A direct programmatic `file.close` (no prompt, no control channel) never discards unsaved
|
||||
/// changes either: it is refused unless `force` says so, as over the control channel.
|
||||
#[test]
|
||||
fn a_direct_close_keeps_unsaved_changes_unless_forced() {
|
||||
let mut a = typed();
|
||||
for params in [json!({}), json!({"quit": true}), json!({"force": "yes"})] {
|
||||
let e = a.execute("file.close", params.clone()).unwrap_err();
|
||||
assert!(e.contains("unsaved") && e.contains("force"), "{params}: {e}");
|
||||
assert!(a.has_document() && !a.quit_requested && body_text(&a).contains(UNSAVED), "{params}");
|
||||
}
|
||||
a.execute("file.close", json!({"force": true})).unwrap();
|
||||
assert!(!a.has_document() && !a.quit_requested);
|
||||
}
|
||||
|
||||
/// The window's close button quit at once; now it asks (the host cancels the close on false).
|
||||
@@ -4585,7 +4642,7 @@ mod tests {
|
||||
#[test]
|
||||
fn enter_answers_with_the_focused_button() {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
for (focus, quits, saves) in [(Some("Cancel"), false, false), (Some("Don't Save"), true, false), (None, true, true)] {
|
||||
for (focus, closes, saves) in [(Some("Cancel"), false, false), (Some("Don't Save"), true, false), (None, true, true)] {
|
||||
let dir = scratch(&format!("enter-{}", focus.map_or(0, str::len)));
|
||||
let path = docx_on_disk(&dir, "Draft");
|
||||
let before = std::fs::read(&path).unwrap();
|
||||
@@ -4622,7 +4679,7 @@ mod tests {
|
||||
}
|
||||
let a = h.state();
|
||||
assert_eq!(prompt(a), None, "{focus:?}: answered");
|
||||
assert_eq!(a.quit_requested, quits, "{focus:?}: quit");
|
||||
assert_eq!(!a.has_document(), closes, "{focus:?}: closed");
|
||||
assert_eq!(std::fs::read(&path).unwrap() != before, saves, "{focus:?}: file written");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
@@ -4633,7 +4690,7 @@ mod tests {
|
||||
#[test]
|
||||
fn arrow_keys_choose_the_save_prompt_answer() {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
for (arrows, focused, quits, saves) in [
|
||||
for (arrows, focused, closes, saves) in [
|
||||
(vec![egui::Key::ArrowLeft], "Save", true, true),
|
||||
(vec![egui::Key::ArrowRight], "Don't Save", true, false),
|
||||
(vec![egui::Key::ArrowRight, egui::Key::ArrowRight, egui::Key::ArrowRight], "Cancel", false, false),
|
||||
@@ -4675,7 +4732,7 @@ mod tests {
|
||||
}
|
||||
let a = h.state();
|
||||
assert_eq!(prompt(a), None, "{arrows:?}: answered");
|
||||
assert_eq!(a.quit_requested, quits, "{arrows:?}: quit");
|
||||
assert_eq!(!a.has_document(), closes, "{arrows:?}: closed");
|
||||
assert_eq!(std::fs::read(&path).unwrap() != before, saves, "{arrows:?}: file written");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
@@ -4723,7 +4780,7 @@ mod tests {
|
||||
h.step();
|
||||
}
|
||||
assert_eq!(prompt(h.state()), None, "answered");
|
||||
assert!(h.state().quit_requested, "Don't Save closes");
|
||||
assert!(!h.state().has_document() && !h.state().quit_requested, "Don't Save closes the document, not the window (#1712)");
|
||||
assert_eq!(std::fs::read(&path).unwrap(), before, "nothing was saved");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
@@ -5776,7 +5833,7 @@ mod tests {
|
||||
a.services.pick_save = Some(Box::new(move |_| Some(picked.clone())));
|
||||
a.run("file.close", json!({})).unwrap();
|
||||
a.run("ui.saveChanges", json!({"answer": "save"})).unwrap();
|
||||
assert!(a.quit_requested);
|
||||
assert!(!a.has_document(), "closed once saved");
|
||||
let saved = wordcraft_engine::io::open_path(&path).unwrap();
|
||||
assert!(saved.plain_text(wordcraft_doc::StoryRef::Body).contains(UNSAVED));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
@@ -5811,17 +5868,18 @@ mod tests {
|
||||
assert_eq!(r["ok"], json!(false), "{r}");
|
||||
let e = r["error"].as_str().unwrap_or("");
|
||||
assert!(e.contains("unsaved") && e.contains("force"), "{e}");
|
||||
assert!(!a.quit_requested);
|
||||
assert!(a.has_document() && !a.quit_requested);
|
||||
assert!(body_text(&a).contains(UNSAVED));
|
||||
|
||||
let r = control_execute(&mut a, json!({"command": "file.close", "params": {"force": true}}));
|
||||
assert_eq!(r["ok"], json!(true), "{r}");
|
||||
assert!(a.quit_requested);
|
||||
// Closed: the document, not the window (#1712).
|
||||
assert!(!a.has_document() && !a.quit_requested);
|
||||
|
||||
let mut clean = app();
|
||||
let r = control_execute(&mut clean, json!({"command": "file.close"}));
|
||||
assert_eq!(r["ok"], json!(true), "{r}");
|
||||
assert!(clean.quit_requested);
|
||||
assert!(!clean.has_document() && !clean.quit_requested);
|
||||
}
|
||||
|
||||
/// Every way to name `file.close` over the control channel is guarded (#1268).
|
||||
@@ -5843,13 +5901,13 @@ mod tests {
|
||||
let mut a = typed();
|
||||
let r = send(&mut a, method, params.clone());
|
||||
assert_eq!(r["ok"], json!(false), "{method} {params}: {r}");
|
||||
assert!(!a.quit_requested, "{method} {params}");
|
||||
assert!(a.has_document() && !a.quit_requested, "{method} {params}");
|
||||
assert!(body_text(&a).contains(UNSAVED));
|
||||
}
|
||||
let mut a = typed();
|
||||
let r = send(&mut a, "file.close", json!({"force": true}));
|
||||
assert_eq!(r["ok"], json!(true), "{r}");
|
||||
assert!(a.quit_requested);
|
||||
assert!(!a.has_document() && !a.quit_requested);
|
||||
}
|
||||
|
||||
const MAILINGS: [&str; 3] = ["mailings.envelopes", "mailings.labels", "mailings.finish"];
|
||||
|
||||
@@ -83,7 +83,8 @@ document with unsaved changes it replies with an error (the app keeps running an
|
||||
stay); save first (`file.save`), or pass `{"force": true}` to discard the changes and close.
|
||||
The bare form `{"method": "file.close", "force": true}` (a method name that is a command id) is
|
||||
guarded the same way, and only a boolean `true` counts as `force`. A document without unsaved
|
||||
changes closes at once (#1268).
|
||||
changes closes at once (#1268). Closing closes the document, not the app: the window stays and
|
||||
shows no page, and `file.new` or `file.open` goes on from there (#1712).
|
||||
|
||||
### Native printing
|
||||
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# UI parity with Microsoft Word
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (the Spike: Ctrl+F3 and Ctrl+Shift+F3; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: the tab strip never draws tabs over Comments / Editing / Share, #2026; previously trivial: Columns on selected text, #2024; previously trivial: Different First Page headers reachable from page 1, #2826; previously trivial: a copied equation also goes in the clipboard's MathML formats, #1297; previously trivial: Read Mode keeps the keyboard, shows whole columns and keeps text clear of the page-turn bar, #1772); previously trivial: font list entries of tall-ascent fonts sit in their row, not cut off, #2570); previously trivial: Split Cells and Convert Text to Table dialogs follow Word's merged-cell limits and Initial column width, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout text fills the window width, #1876; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717)); previously trivial: comment balloons readable in the dark theme, #2045; previously trivial: Recently Used Fonts section at the top of the font list, #2068; previously trivial: web preferences kept between visits, #2132; previously trivial: dialog close control: a Windows-style box at the title bar's right end, the whole box clickable, red on hover, #1885); previously trivial (Modify Table Style formats every table style part, #714); previously trivial: a click on the ribbon chrome hands the keyboard to the page in the same frame, #1093 follow-up; previously trivial: Page Setup vertical alignment, #1320; previously trivial: the app opens on the Home tab every launch, as in Word; previously trivial: no document behind the Start screen, #1129; previously trivial: equation pictures share a few textures, #1002; previously trivial: selection bar in the left margin, #856; 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 major: first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (File › Close keeps the window, #1712; previously trivial: the Spike: Ctrl+F3 and Ctrl+Shift+F3; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: the tab strip never draws tabs over Comments / Editing / Share, #2026; previously trivial: Columns on selected text, #2024; previously trivial: Different First Page headers reachable from page 1, #2826; previously trivial: a copied equation also goes in the clipboard's MathML formats, #1297; previously trivial: Read Mode keeps the keyboard, shows whole columns and keeps text clear of the page-turn bar, #1772); previously trivial: font list entries of tall-ascent fonts sit in their row, not cut off, #2570); previously trivial: Split Cells and Convert Text to Table dialogs follow Word's merged-cell limits and Initial column width, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout text fills the window width, #1876; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717)); previously trivial: comment balloons readable in the dark theme, #2045; previously trivial: Recently Used Fonts section at the top of the font list, #2068; previously trivial: web preferences kept between visits, #2132; previously trivial: dialog close control: a Windows-style box at the title bar's right end, the whole box clickable, red on hover, #1885); previously trivial (Modify Table Style formats every table style part, #714); previously trivial: a click on the ribbon chrome hands the keyboard to the page in the same frame, #1093 follow-up; previously trivial: Page Setup vertical alignment, #1320; previously trivial: the app opens on the Home tab every launch, as in Word; previously trivial: no document behind the Start screen, #1129; previously trivial: equation pictures share a few textures, #1002; previously trivial: selection bar in the left margin, #856; 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 major: first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
|
||||
How WordCraft looks and feels next to Word: ribbon, dialogs, panes, keyboard, mouse and touch.
|
||||
Typing behaviour has its own checklist ([`typing-parity.md`](typing-parity.md)). All assets are our
|
||||
@@ -29,7 +29,7 @@ Document title opens Rename Document; the account badge opens Options › Genera
|
||||
| Modal dialogs | **58** (`Dialog` enum: Discard Recovery, Font, Enclose Characters, Paragraph, Tabs, Borders and Shading, Find, Go To, Insert Table, Table Properties, Page Setup, Link, Bookmark, Word Count, Zoom, Watermark, New Style, Modify Style, Manage Styles, Table Style, Commands, Icons, Templates and Add-ins, Organizer, Compare and Combine, AutoCorrect Options, Paste Special, About, Save Changes, Password, Encrypt with Password, Define New Multilevel List, Track Changes Options; Columns, Symbol with Special Characters, Field (#321); Field code (#339); mail merge Recipients, Insert Merge Field, Find Recipient, Rules, Match Fields, Check for Errors; Caption with New Label and Numbering, Index, Mark Index Entry, Custom Table of Contents with Options and TOC styles, Source Manager, Create/Edit Source (#402); Cross-reference (#916); Line Numbers, Hyphenation with Manual Hyphenation, Language, Envelopes, Labels (#407); Page Setup has a Paper size list and custom width and height, opened from Layout › Size › More Paper Sizes… (#1433); every dialog's close control is a box at the title bar's right end, the full title-bar height and 1.6× as wide, all of it clickable, red with a white ✕ on hover as a Windows caption button (#1885); Split Cells (columns, rows, merge before split; defaults follow the selection as in Word: twice its columns, its rows, Merge offered for several cells or one merged across columns; a merged cell splits into 1 to its height rows, its height or 1 × 1 cutting it back into rows, more refused with Word's message; parts split across columns are equal and other rows keep their grid lines) and Convert Text to Table (live column count, read-only rows; Initial column width Auto or 0 to 22 in, AutoFit to contents/window; Paragraphs/Commas/Tabs/Other "-" separators) (#2592)) plus panes and menus for the rest | ~100 (Options panes…) | ~45% | 11–19 |
|
||||
| Task panes (Navigation, Styles, Comments, Reviewing, Format Picture/Shape, Thesaurus, Accessibility, Clipboard, Selection) | Navigation (long headings truncate with an ellipsis instead of expanding the pane, #759), Styles, Comments (balloons and cards read in both themes, #2045), Thesaurus, Restrict Editing, Accessibility (grouped findings, jump-to, fixes) and Alt Text (#386), Read Aloud, Zotero; Style Inspector (#236); Clipboard pane (#264) open | all | 70% | 4–6 |
|
||||
| Context menus (right-click) | text, spelling suggestions, fields (Update Field, Edit Field…, Toggle Field Codes, #339; Update Field on a table of contents entry updates the table, #1197); right-click selects a picture, shape or text box and offers Wrap Text (styles, Move with Text / Fix Position on Page, #513); tables, the items following the selection as in Word (Insert, Delete › Cells, Columns, Rows or Table whatever is selected (#1198), Merge and Split Cells, Distribute, AutoFit, Text Direction, Insert Caption…, Table Properties…; no Border Styles yet) | rich per context | 65% | 2–4 |
|
||||
| Backstage (Home, New, Open, Info, Save As, Print, Share, Export, Options) | Home and New separate (#254), Info properties kept (#268), Options in Word's panes with working settings (#485), Print page with preview, page range and copies (#641); launched with nothing to open, the app starts on Home (the Start screen: Blank document, templates, Recent; no document is open until one is picked or opened, so Esc or Back shows an empty window with the ribbon greyed out but for Help, #1129), with File › Options › General › Start-up to turn it off (#766); Info › Inspect Document finds and removes hidden text by its effective formatting (direct, character or paragraph style) in every story: body, headers, footers, notes, comments and text boxes, and lists what it couldn't reach (#1344) | | 68% | 4–6 |
|
||||
| Backstage (Home, New, Open, Info, Save As, Print, Share, Export, Options) | Home and New separate (#254), Info properties kept (#268), Options in Word's panes with working settings (#485), Print page with preview, page range and copies (#641); launched with nothing to open, the app starts on Home (the Start screen: Blank document, templates, Recent; no document is open until one is picked or opened, so Esc or Back shows an empty window with the ribbon greyed out but for Help, #1129), with File › Options › General › Start-up to turn it off (#766); Info › Inspect Document finds and removes hidden text by its effective formatting (direct, character or paragraph style) in every story: body, headers, footers, notes, comments and text boxes, and lists what it couldn't reach (#1344); File › Close (Mod+W, Mod+F4) closes the document and keeps the window, showing no page, as Word does; the window's close button quits (#1712) | | 68% | 4–6 |
|
||||
| Rulers: indents, tabs, margins, table columns | indents draggable (one undo, #62); tab stops: corner selector (Left/Center/Right/Decimal/Bar), click to set, drag to move, drag off to clear, per-kind markers, Alt skips the snap, one undo step per gesture, double-click opens the Tabs dialog (#320); table column and row markers drag their borders (one undo) and in a cell the indents are measured from it (#700) | ✅ | 90% | 1 |
|
||||
| On-canvas objects: select, drag, resize handles, nudge | ✅ tested (#228); a picture or shape behind text can be dragged across the overlapping text (body and header/footer), whether or not it was selected first, while a click without movement still edits that text | ✅ | 75% | — |
|
||||
| On-canvas objects: select, drag, Ctrl+drag to copy, resize handles, nudge | ✅ tested (#228, #1014) | ✅ | 75% | — |
|
||||
@@ -65,6 +65,7 @@ Find selects the Results tab, including when it prefills selected text. Editing
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-15 | trivial | File › Close (Mod+W, Mod+F4) closes the document and keeps the window, showing no page, as Word does (it quit the whole app); the window's close button still asks to save and quits (#1712) |
|
||||
| 2026-10-15 | trivial | Dialogs, the Read Aloud player and the file browser: the close control is a box at the title bar's right end, the full title-bar height and 1.6× as wide, all of it clickable; red with a white ✕ while the pointer is over it, darker red pressed, in both themes; Escape as before (#1885) |
|
||||
| 2026-10-15 | trivial | Font box (ribbon and mini toolbar): a Recently Used Fonts section at the top of the list holds the fonts applied from the box, the mini toolbar or the Font dialog, most recent first, once each, ten at most, kept between runs in `ui.json`; All Fonts follows; the sections show while nothing is typed and a query lists its matches only; Up/Down walk the recent entries then the list, Enter applies the highlighted one; a recent font no longer installed stays listed (#2068) |
|
||||
| 2026-10-16 | trivial | Web Layout text fills the window width and re-wraps once a resize settles or at once on a zoom, instead of a fixed 800 pt column; Draft and Outline keep their 800 pt column (#1876) |
|
||||
|
||||
Reference in New Issue
Block a user