diff --git a/crates/engine/src/cmd/insert.rs b/crates/engine/src/cmd/insert.rs index 2b76921f6..ee657166c 100644 --- a/crates/engine/src/cmd/insert.rs +++ b/crates/engine/src/cmd/insert.rs @@ -348,8 +348,11 @@ fn shape(s: &mut Session, v: &Value) -> CmdResult { if super::canvas::add_member(s, obj.clone())?.is_some() { return sel_result(s); } + // Word draws a new shape beside what is selected and never deletes it: the shape just + // inserted (which is selected) stays, and so does selected text (#2019). It goes after the + // selection and becomes the selection. (A picture does replace the selection, as in Word.) let props = s.typing_props(); - let at = delete_selection(s)?; + let (_, at) = s.sel.ordered(); let end = s.doc.insert_object(&at, obj, &props)?; s.sel = Selection { anchor: at, focus: end }; sel_result(s) diff --git a/crates/engine/src/cmd/objects.rs b/crates/engine/src/cmd/objects.rs index dedcee062..e40f2836a 100644 --- a/crates/engine/src/cmd/objects.rs +++ b/crates/engine/src/cmd/objects.rs @@ -1594,6 +1594,41 @@ mod tests { assert_eq!(s.run("arrange.selectionPane", &json!({})).unwrap().as_array().map(Vec::len), Some(2)); } + /// #2019: Insert › Shapes adds each new shape beside the selected one, as Word draws it, so + /// shapes inserted in a row are all kept (the second used to replace the first, which stays + /// selected after its insert). Selected text stays too, and the new shape is selected. + #[test] + fn shapes_inserted_in_a_row_are_all_kept_and_keep_selected_text() { + let objects = |s: &mut Session| s.run("arrange.selectionPane", &json!({})).unwrap().as_array().map(Vec::len); + let mut s = Session::new(wordcraft_doc::Document::new()); + for kind in ["rectangle", "ellipse", "star"] { + s.run("insert.shape", &json!({"kind": kind})).unwrap(); + } + assert_eq!(objects(&mut s), Some(3)); + + let mut s = Session::new(wordcraft_doc::Document::from_text("Kiln room")); + s.run("select.text", &json!({"text": "Kiln"})).unwrap(); + s.run("insert.shape", &json!({"kind": "rectangle"})).unwrap(); + let text = s.doc.plain_text(StoryRef::Body); + assert!(text.starts_with("Kiln") && text.ends_with(" room"), "{text:?}"); + assert_eq!(objects(&mut s), Some(1)); + let (a, b) = s.sel.ordered(); + let obj = wordcraft_doc::para::OBJ.len_utf8(); + assert_eq!((a.off, b.off), (4, 4 + obj), "the new shape, after the text, is selected"); + } + + /// Insert › Pictures does replace a selected picture, as Word does (checked in Word for + /// Windows while fixing #2019): only shapes are added beside the selection. + #[test] + fn an_inserted_picture_replaces_a_selected_picture() { + let data = super::super::insert::base64_encode(&png(20, 10)); + let mut s = Session::new(wordcraft_doc::Document::new()); + s.run("insert.picture", &json!({"data": data})).unwrap(); + s.sel = crate::Selection { anchor: Pos::body(0, 0), focus: Pos::body(0, wordcraft_doc::para::OBJ.len_utf8()) }; + s.run("insert.picture", &json!({"data": data})).unwrap(); + assert_eq!(s.run("arrange.selectionPane", &json!({})).unwrap().as_array().map(Vec::len), Some(1)); + } + #[test] fn picture_pipeline() { let mut s = Session::new(wordcraft_doc::Document::new()); diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 9b0785632..a7a66f3d4 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # WordCraft parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** trivial (East Asian typography: `w:eastAsianLayout` kept on save, not laid out; previously trivial: Ink to Shape recognises arrows; previously trivial: Windows Magnifier follows the caret onto a new line, #1119; previously trivial: table border drags with Ctrl and Alt, from the left edge and of selected cells, #711; 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: all 12 bibliography styles, #383; previously trivial: formatting revisions landed, #41; previously trivial: Draw Table and Eraser landed, #303; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; replaces the parity tables that lived in ROADMAP.md) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** trivial (shapes inserted in a row are all kept, #2019; previously trivial: East Asian typography: `w:eastAsianLayout` kept on save, not laid out; previously trivial: Ink to Shape recognises arrows; previously trivial: Windows Magnifier follows the caret onto a new line, #1119; previously trivial: table border drags with Ctrl and Alt, from the left edge and of selected cells, #711; 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: all 12 bibliography styles, #383; previously trivial: formatting revisions landed, #41; previously trivial: Draw Table and Eraser landed, #303; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; replaces the parity tables that lived in ROADMAP.md) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 The authoritative assessment of how close WordCraft is to Microsoft Word. [`ROADMAP.md`](../ROADMAP.md) summarizes it; [`gaps.md`](gaps.md) itemizes every shortfall; the deep checklists are @@ -251,9 +251,11 @@ can be used without committing Word output. | Date | Change | Summary | |---|---|---| +| 2026-10-15 | trivial | Pictures, shapes, text boxes: Insert › Shapes adds the new shape beside the selection, as Word draws it, so a second shape no longer replaces the first (still selected after its insert) and selected text stays; a picture still replaces a selected picture, as in Word (#2019) | | 2026-10-16 | trivial | East Asian typography row: `w:eastAsianLayout` kept on save, not laid out; no percentage change | | 2026-10-15 | trivial | Equations: a script or limit after `_` or `^` is the whole run of letters and digits (`V_dd`, `x^ab`), as in Word for Mac 16.113.4, and Linear format writes it bare (#1609) | | 2026-10-15 | trivial | Tables: Cell Margins (#1960) opens initialized from table defaults and applies edits only after confirmation; per-cell margins remain intact | +| 2026-10-15 | trivial | Pictures, shapes, text boxes: Insert › Shapes adds the new shape beside the selection, as Word draws it, so a second shape no longer replaces the first (still selected after its insert) and selected text stays; a picture still replaces a selected picture, as in Word (#2019) | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | | 2026-10-13 | trivial | Windows Magnifier follows the caret onto a new line (#1119): Accessibility row updated; no percentage change |