#1297: copied equations go in the MathML clipboard formats Word reads (#2614)

A copy or cut of one equation on its own now also puts its MathML in the
platform's MathML clipboard formats, beside the HTML (with MathML, #816) and
the linear text: "MathML" and "MathML Presentation" (UTF-16LE) on Windows,
public.mathml.presentation and public.mathml on macOS. These are the names
W3C MathML 3 section 6.2.4 registers, and Microsoft's "MathML Support in
Microsoft 365" doc says Office checks them first when it pastes math. No
public clipboard format carries OMML. A copy with text around the equation
doesn't offer them, so Word keeps the text.

Not yet verified in Word: the parity docs say MathML clipboard formats, not
an editable paste.


Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-18 03:46:15 -04:00
1 parent 81098c52f6
commit ce38b0c372
13 files changed
+262 -33

No files matched your search

Generated
+1
View File
@@ -5144,6 +5144,7 @@ dependencies = [
"log",
"objc2 0.6.5",
"objc2-app-kit 0.3.2",
"objc2-foundation 0.3.2",
"open",
"pollster 0.4.0",
"rfd",
+2 -1
View File
@@ -2,7 +2,7 @@
**Stage: alpha** · next: beta, ~15 points (60% → 75% ready for real work) and ~150–230 h away
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (progress log: Indonesian proofing, #498; previously trivial: progress log: DOCX glossary documents (building blocks); previously trivial: progress log: Read Mode keeps the keyboard and shows whole, unobscured columns, #1772); previously trivial: progress log: font list entries of tall-ascent fonts no longer cut off, #2570); previously trivial: progress log: Split Cells and Convert Text to Table dialogs follow the Word check, #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 fills the window width, #1876; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717)); previously trivial (table style editor formats every part, #714); previously trivial: VML freeforms read and kept on save; previously trivial: progress log: 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: progress log: grouped `flowChartOr` shapes draw their circle and crossing spokes; previously trivial: progress log: copies keep their formatting in other programs, #1523; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; 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 tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; 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; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (progress log: Indonesian proofing, #498; previously trivial: progress log: DOCX glossary documents (building blocks); previously trivial: progress log: Read Mode keeps the keyboard and shows whole, unobscured columns, #1772); previously trivial: progress log: font list entries of tall-ascent fonts no longer cut off, #2570); previously trivial: progress log: Split Cells and Convert Text to Table dialogs follow the Word check, #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 fills the window width, #1876; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: progress log: a copied equation also goes in the MathML clipboard formats, #1297); previously trivial: table style editor formats every part, #714; previously trivial: VML freeforms read and kept on save; previously trivial: progress log: 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: progress log: grouped `flowChartOr` shapes draw their circle and crossing spokes; previously trivial: progress log: copies keep their formatting in other programs, #1523; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; 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 tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; 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; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; previously trivial: progress log: comments readable in the dark theme, #2045; previously trivial: progress log: ODT sections, #1379; previously trivial: progress log: table deletes, Change Case and Unlink Fields respect Track Changes and locked content controls, #1329; previously trivial: progress log: rounded rectangle corners, #1476; previously trivial: progress log: Save As type list, #1399; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; 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 tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; 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; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below.
@@ -202,6 +202,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
| 2026-10-13 | Interface size means what it says (#822): a chosen size is absolute, as in PhotoCraft and PdfCraft, so "100%" is one screen pixel per point even where the system reports 200 % (a bare window manager on X11, where winit guesses the scale from the monitor's DPI). The new default, Auto, keeps the system's display scale and its menu entry shows the size it draws at; sizes run 75–300 %. A saved size from before stays the same if it was the default (now Auto) and becomes absolute otherwise |
| 2026-10-14 | Copied text keeps its formatting in other programs (#1523): Copy and Cut put HTML beside the plain text on the system clipboard for every selection, not only one holding an equation, so a browser, mail client or another word processor pastes bold, italic, fonts and sizes. RTF on the clipboard is not written yet |
| 2026-10-13 | Copying an equation also puts MathML in HTML on the clipboard beside the linear text like `y=2x_s` (#816; Word/LibreOffice paste not yet verified; OMML is #1297). HTML export writes equations as MathML too, and opening or pasting HTML with MathML gives equations back |
| 2026-10-16 | Copying one equation on its own also puts it in the clipboard's MathML formats, which Microsoft documents Word reading first when it pastes math. Whether it pastes into Word as an editable equation hasn't been confirmed yet; testers welcome (#1297) |
| 2026-10-13 | The Equation tab's galleries, structure buttons and symbols keep the theme's colours on Windows: their pictures share a few textures written in place (no wipe of pictures already on screen, room for the built-ins at high display scales), instead of hundreds of small textures freed in bulk, which on DirectX 12 showed inverted, red or teal tiles (#1002) |
| 2026-10-13 | `file.close` over the control channel or MCP bridge no longer quits and discards unsaved changes (#1268): it returns an error unless the document is saved or `{"force": true}` is passed; the user's Save / Don't Save / Cancel prompt is unchanged |
| 2026-10-14 | Text copied out of an exported PDF reads as shown (#1473): All Caps and Small Caps text comes out in capitals, as from Word's PDFs, and plain text after it keeps its own case (before, a capital first drawn for a lowercase letter made later capitals extract lowercase) |
+5 -1
View File
@@ -38,8 +38,12 @@ fmv-macos-events = "=0.1.0"
# NSWorkspace: a safe call in these bindings. Already in the build through eframe, rfd and arboard,
# at this version; this adds the `NSAccessibility` bindings and, to fit the title bar around the
# traffic lights, the window, button and view ones.
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSWorkspace", "NSAccessibility", "NSApplication", "NSWindow", "NSButton", "NSControl", "NSView", "NSResponder"] }
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSWorkspace", "NSAccessibility", "NSApplication", "NSWindow", "NSButton", "NSControl", "NSView", "NSResponder", "NSPasteboard"] }
objc2 = "0.6"
# A copied equation's MathML goes on the pasteboard as `public.mathml.presentation` beside
# arboard's HTML and text (#1297), through NSPasteboard's safe `setString:forType:`. Already in
# the build through arboard, at this version; this adds only the NSString bindings.
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "NSString"] }
# Closing the window must quit through NSApplication rather than by dropping the NSWindow
# inside the AppKit run loop (see `schedule_macos_terminate`).
dispatch2 = "0.3"
+88 -8
View File
@@ -10,9 +10,14 @@
//! Everything read here comes from other programs: sizes are capped as it is read.
//!
//! Written here: a copy puts HTML beside its text, so other programs keep its formatting (#1523),
//! with equations as MathML (#816; pasting into Word and LibreOffice not yet verified). arboard sets HTML with a
//! text alternative on every desktop platform; formats of other names (Word's "Office Open XML"
//! with OMML, #1297) it can't, so they aren't offered.
//! with equations as MathML (#816). arboard sets HTML with a text alternative on every desktop
//! platform. A copy of one equation alone also goes in the platform's MathML formats (#1297),
//! added beside arboard's: on Windows "MathML" and "MathML Presentation" (UTF-16LE), on macOS
//! `public.mathml.presentation` and `public.mathml` — the names W3C MathML 3 §6.2.4 registers
//! for transferring MathML, and the ones Microsoft documents its Office apps reading first when
//! they paste math (learn.microsoft.com/office/math/mathml, "Clipboard support"). Pasting into
//! Word and LibreOffice is not yet verified. Linux and the BSDs have no Word, and arboard can't
//! add MIME types of other names, so there the HTML carries the MathML alone.
use wordcraft_engine::cmd::paste::MAX_PASTE_BYTES;
use wordcraft_ui_egui::paste_picture::ClipboardPicture;
@@ -26,13 +31,24 @@ thread_local! {
static WRITER: std::cell::RefCell<Option<arboard::Clipboard>> = const { std::cell::RefCell::new(None) };
}
/// Put `html` on the clipboard with `text` as its plain-text alternative. `false` when it couldn't
/// (the caller copies the text alone), including a Wayland session without the data-control
/// protocol, where arboard would fall back to X11 and native Wayland programs might not see it.
pub fn set_html(html: &str, text: &str) -> bool {
/// Put `html` on the clipboard with `text` as its plain-text alternative, and `mathml` (a copy of
/// one equation alone; empty for any other copy) in the platform's MathML formats. `false` when
/// the HTML and text couldn't be set (the caller copies the text alone), including a Wayland
/// session without the data-control protocol, where arboard would fall back to X11 and native
/// Wayland programs might not see it. MathML that can't be added is logged: the HTML still
/// carries it.
pub fn set_html(html: &str, text: &str, mathml: &str) -> bool {
if !platform::can_set() {
return false;
}
let set = set_html_text(html, text);
if set && !mathml.is_empty() && mathml.len() <= MAX_PASTE_BYTES {
platform::add_mathml(mathml);
}
set
}
fn set_html_text(html: &str, text: &str) -> bool {
WRITER.with(|w| {
let Ok(mut slot) = w.try_borrow_mut() else { return false };
if slot.is_none() {
@@ -146,6 +162,9 @@ mod platform {
)
}
/// arboard serves its own MIME types only; Word, which reads the MathML formats, isn't here.
pub fn add_mathml(_mathml: &str) {}
pub fn rtf() -> Option<Vec<u8>> {
if !wayland() {
return None;
@@ -193,9 +212,68 @@ mod platform {
pub fn can_set() -> bool {
true
}
/// Add the MathML to what arboard just set, as "MathML" and "MathML Presentation": UTF-16LE
/// text (with its terminating NUL, as Windows text formats have).
pub fn add_mathml(mathml: &str) {
let Ok(_open) = Clipboard::new_attempts(10) else {
log::warn!("clipboard: couldn't open it to add MathML");
return;
};
let bytes: Vec<u8> = mathml.encode_utf16().chain(std::iter::once(0)).flat_map(u16::to_le_bytes).collect();
for name in super::MATHML_FORMATS {
let Some(format) = raw::register_format(name) else { continue };
if let Err(e) = raw::set_without_clear(format.get(), &bytes) {
log::warn!("clipboard: {name}: {e}");
}
}
}
}
#[cfg(not(any(target_os = "windows", all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))))]
/// The clipboard format names for MathML that Windows programs read (W3C MathML 3 §6.2.4; Office
/// reads and writes both).
#[cfg(target_os = "windows")]
const MATHML_FORMATS: [&str; 2] = ["MathML", "MathML Presentation"];
#[cfg(target_os = "macos")]
mod platform {
use objc2_app_kit::NSPasteboard;
use objc2_foundation::NSString;
pub fn formats() -> Vec<String> {
Vec::new()
}
pub fn rtf() -> Option<Vec<u8>> {
None
}
pub fn encoded_picture() -> Option<Vec<u8>> {
None
}
pub fn can_set() -> bool {
true
}
/// Add the MathML to the pasteboard item arboard just wrote, as the MathML types (W3C MathML 3
/// §6.2.4): presentation markup, and the generic type for readers that only know that one.
pub fn add_mathml(mathml: &str) {
let board = NSPasteboard::generalPasteboard();
let text = NSString::from_str(mathml);
for name in ["public.mathml.presentation", "public.mathml"] {
if !board.setString_forType(&text, &NSString::from_str(name)) {
log::warn!("clipboard: couldn't add {name}");
}
}
}
}
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")))
)))]
mod platform {
pub fn formats() -> Vec<String> {
Vec::new()
@@ -212,4 +290,6 @@ mod platform {
pub fn can_set() -> bool {
true
}
pub fn add_mathml(_mathml: &str) {}
}
+20
View File
@@ -40,6 +40,26 @@ impl Fragment {
}
found
}
/// The equation (its linear format and whether it is a display equation) when the fragment
/// is that one equation and nothing else but white space and empty paragraphs: a copy of it
/// also goes on the system clipboard in the MathML formats that equation editors read (#1297).
pub fn sole_equation(&self) -> Option<(&str, bool)> {
let mut found = None;
for b in &self.blocks {
let Block::Para(p) = b else { return None };
for c in p.text.chars().filter(|c| !c.is_whitespace()) {
if c != crate::para::OBJ || found.is_some() {
return None;
}
// The paragraph's only visible character, so its first object.
match p.objects.first() {
Some(InlineObject::Equation { linear, display, .. }) => found = Some((linear.as_str(), *display)),
_ => return None,
}
}
}
found
}
/// The text a copy puts on the clipboard: [`Self::plain_text`] with symbol-font characters
/// (Wingdings…) as their Unicode equivalents.
pub fn clipboard_text(&self) -> String {
+14 -2
View File
@@ -167,13 +167,25 @@ fn copy(s: &mut Session, _: &Value) -> CmdResult {
}
/// What Copy and Cut return: the clipboard's text, and the copy as HTML (equations as MathML,
/// #816) for the host to put on the system clipboard too, so other programs keep the formatting.
/// #816) for the host to put on the system clipboard too, so other programs keep the formatting;
/// and for a copy of one equation alone, its MathML (#1297).
pub(crate) fn copy_result(s: &mut Session) -> Value {
s.clipboard_html = match &s.clipboard {
Some(f) => wordcraft_formats::html::export_fragment(&f.to_document()),
None => String::new(),
};
if s.clipboard_html.is_empty() { json!({"text": s.clipboard_text}) } else { json!({"text": s.clipboard_text, "html": s.clipboard_html}) }
let mut r = json!({"text": s.clipboard_text});
if !s.clipboard_html.is_empty() {
r["html"] = json!(s.clipboard_html);
}
// A copy of one equation alone, as MathML for the clipboard formats equation editors read
// ("MathML" on Windows, `public.mathml.presentation` on macOS; #1297). Word looks for them
// before anything else when it pastes; a copy with text around the equation doesn't offer
// them, so pasting it keeps the text.
if let Some((linear, display)) = s.clipboard.as_ref().and_then(|f| f.sole_equation()) {
r["mathml"] = json!(wordcraft_formats::mathml::linear_to_mathml(linear, display));
}
r
}
fn cut(s: &mut Session, v: &Value) -> CmdResult {
+93
View File
@@ -948,6 +948,99 @@ fn copying_an_equation_puts_mathml_on_the_clipboard() {
assert!(r["html"].as_str().is_some_and(|h| h.contains("Speed") && !h.contains("<math")), "{r}");
}
/// Whether `xml` is one element whose tags all nest and close (enough to check a clipboard
/// payload without an XML parser in the engine's tests).
fn one_balanced_element(xml: &str) -> bool {
let mut stack: Vec<String> = Vec::new();
let mut closed_root = false;
let mut rest = xml;
while let Some(lt) = rest.find('<') {
if closed_root || (stack.is_empty() && !rest[..lt].trim().is_empty()) {
return false;
}
let Some(gt) = rest[lt..].find('>') else { return false };
let tag = &rest[lt + 1..lt + gt];
rest = &rest[lt + gt + 1..];
if let Some(name) = tag.strip_prefix('/') {
if stack.pop().as_deref() != Some(name.trim()) {
return false;
}
closed_root = stack.is_empty();
} else if !tag.ends_with('/') {
stack.push(tag.split_whitespace().next().unwrap_or_default().to_string());
} else if stack.is_empty() {
closed_root = true;
}
}
closed_root && stack.is_empty() && rest.trim().is_empty()
}
#[test]
fn copying_one_equation_gives_its_mathml_for_the_math_clipboard_formats() {
// #1297: Word reads a pasted equation first from the clipboard's "MathML" formats; a copy of
// one equation alone carries its MathML for the host to put there.
let cases = [
("y=(a+b)/(c-d)", "<mfrac>"),
("x_i^2+e^(-t)", "<msubsup>"),
("√(x^2+1)+∛8", "<msqrt>"),
("∑_(k=1)^n k^2", "<munderover>"),
("∫_0^1 f(x)dx", "∫"),
("■(1&0@0&1)", "<mtable>"),
];
for (linear, element) in cases {
let mut s = s();
run(&mut s, "insert.equation", json!({"linear": linear, "edit": false}));
run(&mut s, "select.all", json!({}));
let r = run(&mut s, "edit.copy", json!({}));
let mathml = r["mathml"].as_str().unwrap_or_default().to_string();
assert!(mathml.starts_with("<math xmlns=\"http://www.w3.org/1998/Math/MathML\""), "{linear}: {mathml}");
assert!(mathml.contains(element), "{linear}: {mathml}");
// Alone on its line, it is a display equation.
assert!(mathml.contains("display=\"block\""), "{linear}: {mathml}");
assert!(one_balanced_element(&mathml), "{linear}: not one well-formed element: {mathml}");
// No XML prolog: the W3C transfer rules want `<math` first, so readers that sniff the
// start of the text recognise it.
assert!(!mathml.contains("<?xml"), "{mathml}");
// Pasting it back (as a MathML reader would get it) gives the same equation.
let mut t = s_with_paste(&mathml);
assert_eq!(equations(&t), equations(&s), "{mathml}");
// Its presentation elements alone, without our linear-format annotation (which Word
// ignores), still read as an equation of the same shape.
let bare = match (mathml.find("<annotation"), mathml.find("</annotation>")) {
(Some(a), Some(b)) => format!("{}{}", &mathml[..a], &mathml[b + "</annotation>".len()..]),
_ => mathml.clone(),
};
t = s_with_paste(&bare);
assert_eq!(equations(&t).len(), 1, "{linear}: {bare}");
}
}
fn s_with_paste(html: &str) -> Session {
let mut t = s();
run(&mut t, "edit.paste", json!({"text": "", "html": html}));
t
}
#[test]
fn copying_an_equation_with_text_offers_no_math_clipboard_formats() {
// #1297: Word takes the "MathML" format before the HTML, so offering it for a copy with text
// around the equation would drop the text. Text alone has none either.
let mut s = s();
run(&mut s, "document.setText", json!({"text": "Speed "}));
run(&mut s, "caret.docEnd", json!({}));
run(&mut s, "insert.equation", json!({"linear": "y=2x_s", "edit": false}));
run(&mut s, "select.all", json!({}));
let r = run(&mut s, "edit.copy", json!({}));
assert!(r.get("mathml").is_none() && r["html"].as_str().is_some_and(|h| h.contains("<math")), "{r}");
run(&mut s, "select.range", json!({"anchor": {"block": 0, "off": 0}, "focus": {"block": 0, "off": 5}}));
let r = run(&mut s, "edit.copy", json!({}));
assert!(r.get("mathml").is_none(), "{r}");
// Cut of the equation alone offers it.
run(&mut s, "select.range", json!({"anchor": {"block": 0, "off": 6}, "focus": {"block": 0, "off": 9}}));
let r = run(&mut s, "edit.cut", json!({}));
assert!(r["mathml"].as_str().is_some_and(|m| m.contains("<msub>") && !m.contains("display=")), "an inline equation: {r}");
}
#[test]
fn copying_bold_text_puts_html_on_the_clipboard() {
// #1523: formatted text reaches other programs as HTML, not only as plain text.
+3 -2
View File
@@ -1,7 +1,8 @@
//! Equations as MathML (W3C MathML Core / Presentation MathML): written into HTML export and the
//! HTML a copy puts on the system clipboard, so programs that read MathML can get a formatted
//! equation instead of its linear text (#816; pasting into Word and LibreOffice not yet verified;
//! OMML for Word is #1297); and read back from pasted or opened HTML.
//! equation instead of its linear text (#816); for a copy of one equation alone, the text of the
//! platform's MathML clipboard formats, which Word reads first when it pastes math (#1297;
//! pasting into Word and LibreOffice not yet verified); and read back from pasted or opened HTML.
//!
//! The writer walks the equation tree ([`MNode`]) and wraps it in `<semantics>` with the linear
//! format as an `application/x-unicodemath` annotation, which renderers ignore and WordCraft reads
+6 -4
View File
@@ -144,10 +144,12 @@ pub struct Services {
/// Desktop: the HTML on the system clipboard — another program's formatted copy (a Word
/// document, a web page) — if any. Paste keeps its formatting (#687); see [`system_clipboard`].
pub clipboard_html: Option<Box<dyn Fn() -> Option<String>>>,
/// Desktop: put HTML on the system clipboard with its plain-text alternative (html, text), for
/// a copy holding an equation (as MathML, #816). `true` when both are there; otherwise the
/// copy's text goes through egui as usual.
pub set_clipboard_html: Option<Box<dyn Fn(&str, &str) -> bool>>,
/// Desktop: put HTML on the system clipboard with its plain-text alternative (html, text, and
/// for a copy of one equation alone its MathML, else empty), so other programs keep the
/// formatting (#1523) and equations (#816). The MathML goes in the platform's MathML formats
/// as well, which Word reads a pasted equation from (#1297). `true` when the HTML and text are
/// there; otherwise the copy's text goes through egui as usual.
pub set_clipboard_html: Option<Box<dyn Fn(&str, &str, &str) -> bool>>,
/// Desktop: the RTF on the system clipboard, where the platform offers it (#687).
pub clipboard_rtf: Option<Box<dyn Fn() -> Option<String>>>,
/// Desktop: the names of the formats on the system clipboard (MIME types, Windows format
+19 -8
View File
@@ -68,13 +68,14 @@ pub(crate) fn fill(app: &mut WordApp, id: &str, params: &mut Value) -> Option<Re
}
/// Put a copy on the system clipboard: its HTML beside the text (the formatting, #1523, and
/// equations as MathML, #816) when the host can set it, else the text through egui. egui sets its
/// text at the end of the frame, which would replace the HTML, so only one of them runs.
pub(crate) fn put(app: &WordApp, ctx: &egui::Context, text: &str, html: &str) {
/// equations as MathML, #816; a lone equation in the MathML formats too, #1297) when the host can
/// set it, else the text through egui. egui sets its text at the end of the frame, which would
/// replace the HTML, so only one of them runs.
pub(crate) fn put(app: &WordApp, ctx: &egui::Context, text: &str, html: &str, mathml: &str) {
if text.is_empty() {
return;
}
if !html.is_empty() && app.services.set_clipboard_html.as_ref().is_some_and(|f| f(html, text)) {
if !html.is_empty() && app.services.set_clipboard_html.as_ref().is_some_and(|f| f(html, text, mathml)) {
return;
}
ctx.copy_text(text.to_string());
@@ -83,7 +84,7 @@ pub(crate) fn put(app: &WordApp, ctx: &egui::Context, text: &str, html: &str) {
/// Put what Copy or Cut returned on the system clipboard (see [`put`]).
pub(crate) fn put_result(app: &WordApp, ctx: &egui::Context, r: &Value) {
let s = |k: &str| r.get(k).and_then(Value::as_str).unwrap_or("");
put(app, ctx, s("text"), s("html"));
put(app, ctx, s("text"), s("html"), s("mathml"));
}
/// Format names worth showing: not the clipboard protocol's own bookkeeping targets.
@@ -244,11 +245,11 @@ mod tests {
#[test]
fn copying_an_equation_puts_its_html_on_the_system_clipboard() {
// #816: the host gets HTML with the equation as MathML beside the text.
let got = std::rc::Rc::new(std::cell::RefCell::new(Vec::<(String, String)>::new()));
let got = std::rc::Rc::new(std::cell::RefCell::new(Vec::<(String, String, String)>::new()));
let sink = got.clone();
let mut a = app(Services {
set_clipboard_html: Some(Box::new(move |h, t| {
sink.borrow_mut().push((h.to_string(), t.to_string()));
set_clipboard_html: Some(Box::new(move |h, t, m| {
sink.borrow_mut().push((h.to_string(), t.to_string(), m.to_string()));
true
})),
..Default::default()
@@ -263,6 +264,8 @@ mod tests {
let got_now = got.borrow().clone();
assert_eq!(got_now.len(), 1);
assert!(got_now[0].0.contains("<math") && got_now[0].1.contains("y=2x_s"), "{got_now:?}");
// With text around it, no MathML formats: Word would paste the equation alone (#1297).
assert!(got_now[0].2.is_empty(), "{got_now:?}");
// Text without an equation gets HTML too, so other programs keep its formatting (#1523).
a.run("select.range", json!({"anchor": {"block": 0, "off": 0}, "focus": {"block": 0, "off": 5}})).unwrap();
let r = a.run("edit.copy", json!({})).unwrap();
@@ -270,5 +273,13 @@ mod tests {
let got_now = got.borrow().clone();
assert_eq!(got_now.len(), 2);
assert!(got_now[1].0.contains("Speed") && !got_now[1].0.contains("<math") && got_now[1].1 == "Speed", "{got_now:?}");
assert!(got_now[1].2.is_empty(), "{got_now:?}");
// The equation alone: its MathML for the platform's MathML formats too (#1297).
a.run("select.range", json!({"anchor": {"block": 0, "off": 6}, "focus": {"block": 0, "off": 9}})).unwrap();
let r = a.run("edit.copy", json!({})).unwrap();
put_result(&a, &ctx, &r);
let got_now = got.borrow().clone();
assert_eq!(got_now.len(), 3);
assert!(got_now[2].2.starts_with("<math") && got_now[2].2.contains("<msub>") && got_now[2].1 == "y=2x_s", "{got_now:?}");
}
}
+3 -2
View File
@@ -1,6 +1,6 @@
# File-format parity with Microsoft Word
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-17 · **Change:** trivial (DOCX glossary documents (building blocks) read, kept and written; previously trivial: cached field results keep their visible run formatting; previously trivial: cached REF results retain hyperlink destinations; previously trivial: CLI conversions can explicitly refresh fields; previously trivial: every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-17 · **Change:** trivial (DOCX glossary documents (building blocks) read, kept and written; previously trivial: cached field results keep their visible run formatting; previously trivial: cached REF results retain hyperlink destinations; previously trivial: CLI conversions can explicitly refresh fields; previously trivial: a copied equation also goes in the clipboard's MathML formats, #1297); previously trivial: every table style part round-trips, #714; previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats
Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the
share of a typical real-world file's content and formatting that survives (read: shows correctly;
@@ -44,7 +44,7 @@ Weights are the share of a Word user's file traffic (estimate). Read/write: ✅
| `.rtf` Rich Text Format | 3% | R/W | 🟡 | 🟡 | ~50% | 10–15 | `crates/formats/src/tests.rs` | ~1,500 lines: text, character/paragraph formatting, styles, lists, tables, pictures (`\shppict`, #154), fields, bookmarks. 8-bit text in the document's code page (`\ansicpg`) or the current font's (`\fcharset`, `\cpg`): Cyrillic, Central European, Greek, Turkish, Hebrew, Arabic, Baltic, Vietnamese, Thai (was Windows-1252 only), Mac OS Roman (`\mac`, `\fcharset77`) and DOS code pages (`\pc` 437, `\pca` 850, `\cpg` 437/737/850/852/857/866, `\fcharset254`) (#1653). Double-byte East Asian code pages (Japanese `\fcharset128`, Chinese, Korean) not decoded yet. Pasted from the system clipboard where the platform offers it (Windows, Wayland) when there is no HTML (#687). Tracked insertions and deletions read and written (`\revtbl`, `\revised`/`\deleted`, authors, `\revdttm` dates), so Accept/Reject All work after opening or a round trip; paragraph-mark and formatting revisions not yet. Sections on import (#1378): each `\sect` ends a section with its own page size and orientation, margins, header/footer distance, columns, break type, page-number restart, start and format, title page and headers/footers (those it leaves out link to the previous); export still writes one section. Thin: comments, notes, section export, RTL |
| `.odt` OpenDocument Text | 2% | R/W | 🟡 | 🟡 | ~50% | 8–12 | `crates/formats/src/tests.rs`, `crates/formats/src/odt.rs` unit tests, `crates/engine/tests/odt_sections.rs` | Styles, lists (#87), tables, pictures, headers/footers, page breaks (#86), page size, orientation and margins (#810), sections: one per master-page change on a paragraph or table (next page, with its own page size, orientation, margins, columns and page-numbering restart) and per columned `text:section` (continuous, with its columns and separator line) (#1379); tracked insertions and deletions with author and date, inserted and deleted paragraph marks and text inserted then deleted included (ODF change tracking; format changes read as none; pictures and tables inside a tracked deletion are dropped). No footnotes, comments, fields or RTL |
| `.txt` Plain text | 2% | R/W | ✅ | ✅ | ~95% | 1–2 | unit tests | Encoding detection. Files with other extensions (`.m3u`/`.m3u8` playlists, logs, config files, no extension) open as text when they are text, Word and RTF files by their signature; binary files are refused (#569). A Word or RTF extension that contradicts the content also opens by signature, as in Word: RTF saved as `.doc` (hh.ru résumés, accounting exports), a `.docx` renamed `.doc`, a Word 97-2003 file renamed `.docx`. Word's encoding choice dialog and line-ending options missing; such files save only through Save As |
| `.htm/.html` Web Page and Web Page, Filtered | 1% | R/W | 🟡 | 🟡 | ~60% | 6–10 | `crates/formats/src/tests.rs` | Tables (#102), relative pictures embedded (#113), missing `</head>` tolerated (#154). Header cells (`<th>`) bold unless their CSS says otherwise; `text-align` on a cell, or inherited from its row, row group or table, aligns the cell's paragraphs (#1381). HTML gives no page size, so it opens on the region's paper (A4 or Letter), as Markdown and plain text do; `@page` isn't read (#1381). No Word "Web Page" (with `_files` folder) round-trip. The same reader takes HTML from the system clipboard: Paste keeps another program's formatting (a Word document, a web page; Windows' "HTML Format" header understood), Merge Formatting keeps its emphasis (#687). Equations are written as MathML (with the linear format as a UnicodeMath annotation) and read back from MathML: our annotation, else the presentation elements, else a TeX annotation (#816) |
| `.htm/.html` Web Page and Web Page, Filtered | 1% | R/W | 🟡 | 🟡 | ~60% | 6–10 | `crates/formats/src/tests.rs` | Tables (#102), relative pictures embedded (#113), missing `</head>` tolerated (#154). Header cells (`<th>`) bold unless their CSS says otherwise; `text-align` on a cell, or inherited from its row, row group or table, aligns the cell's paragraphs (#1381). HTML gives no page size, so it opens on the region's paper (A4 or Letter), as Markdown and plain text do; `@page` isn't read (#1381). No Word "Web Page" (with `_files` folder) round-trip. The same reader takes HTML from the system clipboard: Paste keeps another program's formatting (a Word document, a web page; Windows' "HTML Format" header understood), Merge Formatting keeps its emphasis (#687). Equations are written as MathML (with the linear format as a UnicodeMath annotation) and read back from MathML: our annotation, else the presentation elements, else a TeX annotation (#816). A copy of one equation alone also goes in the clipboard's MathML formats (Windows "MathML" and "MathML Presentation", macOS `public.mathml.presentation` and `public.mathml`; W3C MathML 3 §6.2.4), which Microsoft documents Word reading first when it pastes math (#1297; the paste into Word is not yet verified) |
| `.mht/.mhtml` Single File Web Page | <1% | R/W | ❌ | ❌ | 0% | 3–5 | — | |
| `.xml` Word XML Document (Flat OPC) | <1% | R/W | ✅ | ✅ | ~65% | — | `crates/docx/tests/word_xml.rs`, `roundtrip.rs` (`flat_opc_round_trip_equals_docx`) | The DOCX package in one XML file, read and written by the DOCX code (#548); File › Export › Word XML Document. `.xml` files are told apart by their root element |
| `.xml` Word 2003 XML (WordprocessingML 2003) | <1% | R/W | 🟡 | 🟡 | ~55% | 1–2 | `crates/docx/tests/word_xml.rs` | Opened by rewriting it as a package for the DOCX reader (#548): styles, default fonts, lists, tables, sections, headers/footers, footnotes/endnotes, comments, bookmarks, tracked insertions/deletions, hyperlinks, `w:binData` pictures, document properties. Not read: formatting changes, `w:docPr` settings, smart tags. Written (#1081) by rewriting the DOCX writer's package into the 2003 vocabulary (`crates/docx/src/wordml2003_write.rs`, `crates/docx/tests/wordml2003_write.rs`): core properties, styles (document defaults folded into Normal), lists, text and paragraph/character formatting, tables, hyperlinks, footnotes/endnotes inline, headers/footers in the section properties, bookmarks, comments and tracked insertions/deletions as `aml:annotation`, section page setup, and PNG/JPEG/GIF/BMP/EMF/WMF pictures as `w:pict` with `w:binData`. **Dropped on write:** shapes, text boxes, groups, charts, SmartArt, ink, equations, OLE objects, picture crop, rotation and floating position (pictures become inline), content-control wrappers (their content stays), formatting-change tracking, theme colours/fonts, custom XML, macros, even/odd header setting. File › Export › Word 2003 XML Document, `file.save`/`file.saveAs` with `format: "wordml2003"`, `wordcraft-cli convert … --format wordml2003`; a plain save over a 2003 file keeps that format. Never opened in Word yet (written from the published schemas; needs a tester) |
@@ -241,6 +241,7 @@ Imported text fields use the first nonempty cached result run's formatting on th
| 2026-10-13 | trivial | Remove personal information / Remove date and time settings kept and honoured on save: anonymous annotation authors, no creator or last-modified-by, no annotation dates (#1062) |
| 2026-10-12 | trivial | Direct run-level `w:contentPart` InkML keeps its relationship, content type and bytes through DOCX edits and repeated saves; nested group/canvas ink and native rendering remain missing |
| 2026-10-13 | trivial | HTML export writes equations as MathML instead of linear text, and HTML import (open and paste) reads `<math>` back into equations; a copy holding an equation puts that HTML on the system clipboard: MathML in HTML on the clipboard (Word/LibreOffice paste not yet verified; OMML is #1297) (#816) |
| 2026-10-16 | trivial | A copy of one equation alone also goes in the clipboard's MathML formats ("MathML"/"MathML Presentation" on Windows, `public.mathml.presentation`/`public.mathml` on macOS), which Word reads first when it pastes math, instead of OMML, for which no clipboard format is published (#1297; the paste into Word is not yet verified) |
| 2026-10-14 | minor | DOCX save merges `settings.xml` into the file's original: unmodelled settings (compatibility options, rsids, document variables, proofing state…) survive (#1034) |
| 2026-10-14 | minor | DOCX save carries unmodelled package parts verbatim; comment durable ids and UTC dates, localized `w:default`, latent styles, list `nsid`/`tmpl`/`multiLevelType` and `app.xml` template/company round-trip; comment reference marks use the document's own style id (#1034) |
| 2026-10-14 | trivial | PDF export: All Caps and Small Caps text extracts in capitals, as displayed, and no longer turns later capitals in uncapitalised text lowercase (#1473) |
+6 -4
View File
@@ -1,6 +1,6 @@
# Where WordCraft falls short of Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** 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 (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-18 · **Change:** 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: 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
> **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below.
@@ -52,7 +52,7 @@ Document renaming: desktop renaming requires a filesystem that supports hard lin
| 27 | **View windows**: Side by Side, Synchronous Scrolling, Arrange All, Switch Windows | | [parity-checklist.md](parity-checklist.md) (View 25/29); PR #331 open | Comparing documents | 3–5 | [ui-parity.md](ui-parity.md) |
| 26 | **Minor formats**: MHT, Works/WordPerfect | | Word XML Document (Flat OPC) opens and saves, Word 2003 XML opens (#548) and saves (#1081, not yet checked in Word) | Rare | 14–26 | [file-format-parity.md](file-format-parity.md) |
| 28 | **Dictate** | | `tools.dictate` missing | Dictation users | 8–15 + owner (speech model) | [hardware-parity.md](hardware-parity.md) |
| 29 | **Equations**: ink equations; copied equations as OMML for Word (#816, #1297) | | #191 notes; long display equations wrap at operators (#326); a copy holding an equation puts MathML in HTML on the clipboard (#816; Word/LibreOffice paste not yet verified) | Maths-heavy documents | 2–3 | [layout-parity.md](layout-parity.md) |
| 29 | **Equations**: ink equations; a copied equation pasting into Word as an editable equation, still to be confirmed (#816, #1297) | | #191 notes; long display equations wrap at operators (#326); a copy holding an equation puts MathML in HTML on the clipboard (#816), and a copy of one equation alone also its MathML in the "MathML" clipboard formats Word reads first (Windows, macOS; #1297; Word/LibreOffice paste not yet verified) | Maths-heavy documents | 2–3 | [layout-parity.md](layout-parity.md) |
**Beta needs #1–#7** (~150–230 h with the stability and layout work they imply): they are the
blocking gaps, and closing them lifts file formats, layout, stability and UI enough to take the
@@ -73,8 +73,9 @@ The same gaps grouped the way the parity documents are, for agents working in on
- **UI/UX gaps:** #7, #18, #24; context menus for objects (the table's landed; it still lacks Border Styles); Linux title bar theming (#78); File ›
still missing: the Paste Options button after a paste, and RTF on macOS and X11. Copy puts
HTML beside the text on the clipboard, so other programs keep the formatting (#1523), with
equations as MathML (#816; pasting into Word/LibreOffice not yet verified; OMML on the
clipboard is #1297); RTF on the clipboard is not written yet.
equations as MathML (#816), and a lone equation also in the MathML clipboard formats on
Windows and macOS (#1297; pasting into Word/LibreOffice not yet verified); RTF on the
clipboard is not written yet.
Options › Animation effects › Use system setting reads macOS (Reduce motion) and the Linux/BSD
desktops (`apps/wordcraft/src/animations.rs`) but hasn't been tried on a Mac or in a real
GNOME or KDE session yet.
@@ -194,6 +195,7 @@ The Language dialog reads effective character formatting, including document def
| 2026-10-10 | trivial | #10: Drawing Canvas landed (#344); in-canvas dragging of members remains |
| 2026-10-14 | trivial | Feature gaps: every copy puts HTML beside its text on the clipboard, not only copies with an equation (#1523); writing RTF stays open |
| 2026-10-13 | trivial | #29: copied equations reach the clipboard as MathML in HTML (#816); OMML for Word stays open (#1297) |
| 2026-10-16 | trivial | #29: a copy of one equation alone also goes in the MathML clipboard formats Word reads first (#1297); the gap keeps confirming the paste in Word |
| 2026-10-10 | trivial | #29: long display equations wrap across lines (#326) |
| 2026-10-10 | trivial | #3: Windows lag on GPUs without a DirectX 12 driver fixed with an OpenGL fallback (#316) |
| 2026-10-10 | minor | #8: Polish proofing (spelling, grammar, hyphenation) and a proofing language per run landed; the gap is now the other languages (20–40 h) |
+2 -1
View File
@@ -1,6 +1,6 @@
# UI parity with Microsoft Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** 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 (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 (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 (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
@@ -94,6 +94,7 @@ The Language dialog reads effective character formatting, including document def
| 2026-10-13 | trivial | Clicks on ribbon and status-bar chrome that run no command button (Styles and style-set gallery tiles, tab names, empty ribbon space, View check boxes, the status bar's view buttons and language menu) no longer take keyboard focus from the page: the caret stays and typing continues, as in Word; text fields such as the font size box still take focus. Tabs, gallery tiles and the status bar's view buttons report their name and state to screen readers (#1093) |
| 2026-10-13 | trivial | Interface size: a chosen size is absolute ("100%" is one screen pixel per point, even where the system reports 200 % under a bare window manager) and the new default, Auto, keeps the display scale and shows the size it draws at; 75–300 %; an old saved size becomes the size it drew at; on the web a chosen size cancels browser zoom, Auto follows it (#822) |
| 2026-10-13 | trivial | Copy and Cut of a selection holding an equation put HTML with the equation as MathML on the system clipboard beside the text (desktop, through arboard), as MathML in HTML on the clipboard (Word/LibreOffice paste not yet verified; OMML is #1297) (#816). Only copies containing an equation get HTML. Web: text only |
| 2026-10-16 | trivial | Copy and Cut of one equation alone also put its MathML in the clipboard formats Word reads first when it pastes math: "MathML" and "MathML Presentation" on Windows, `public.mathml.presentation` and `public.mathml` on macOS (#1297). A copy with text around the equation doesn't offer them, so pasting keeps the text. The paste into Word is not yet verified. Linux, BSD and web: unchanged |
| 2026-10-12 | trivial | New/Modify Table Style: Apply formatting to any of the 13 table style parts (whole table, header/total row, first/last column, odd/even row and column bands, four corner cells), each with font, size, bold, italic, underline, colour, fill, borders (line style, width, colour; all/none/outside/inside/each edge) and the 9-way cell alignment, over a live preview (#714) |
| 2026-10-14 | trivial | Save Changes prompt on the web: Save before New/Open holds the command back until the browser's save picker has written and closed the file; a cancelled or failed picker save keeps the document and brings the prompt back with the reason (#1315) |
| 2026-10-14 | trivial | Inspect Document (#1344): hidden text found by effective formatting (direct, character style, paragraph style) in the body, headers, footers, notes, comments and text boxes; Remove All deletes it all in one undo step (whole hidden paragraphs with their marks, and the notes of hidden references) and reports a `residual` list, including tables whose style hides text |