Commit Graph
400 Commits
Author SHA1 Message Date
e6c5a701dd The caret and clicks land between the letters of a ligature (#1974) (#2006)
* Fix #1974: the caret and clicks land between the letters of a ligature

The glyph's advance is shared evenly among its letters (mirrored in
right-to-left text); ligature letters keep their character spacing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix #1974: a long word never wraps inside a ligature

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix #1974: a hyphenation point never splits a ligature

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #1974: ligature tests use a bundled font that draws fi and ffi as ligatures

They assumed the installed default-font stand-in draws ligatures; Liberation Sans (Linux) draws
none, so they failed there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:09:22 -04:00
b8debb3050 Content controls: typed text takes the control's formatting, in any language (#3116)
* Content controls: typed text takes the control's formatting, in any language

Text typed in place of a content control's placeholder takes the
control's own run properties (w:sdtPr/w:rPr, ECMA-376 §17.5.2.27), as
in Word: in a protected form filled in Word for Mac, all 17 entered
values carry the style their control's rPr names. WordCraft kept that
rPr only as XML to write back and gave typed text the placeholder's
formatting minus the placeholder style.

It also knew the placeholder style only by its English id,
PlaceholderText. German Word writes Platzhaltertext (the name stays
"Placeholder Text"), so in forms from German Word every entry stayed
grey, on screen and in the saved file, and restoring a placeholder
added a second Placeholder Text style. The style is now found by its
built-in name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Content controls: typing drops a showing placeholder's own style

A control without run properties that shows its placeholder: text typed
over it drops the placeholder run's character style whatever its id
and name. Word localizes only the placeholder style's id, but another
program may localize its name too, and the name lookup then misses it.
Restoring a placeholder and new controls keep the name lookup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 12:53:44 -04:00
f1b4be8a04 Restrict Editing: forms protection keeps a form's content controls (#3112)
* Restrict Editing: forms protection keeps a form's content controls

With Filling in forms enforced (w:edit="forms"), Word greys out the
Developer tab's control buttons and Properties, and a field's shortcut
menu offers no Remove Content Control: only the fields' content can be
filled in. WordCraft refused these only outside the fillable fields, so
with the caret in one it removed the control (its content left as
plain, no longer fillable text), inserted new controls inside it, and
renamed it; only lock changes were refused.

Under forms protection, inserting any content control, Properties
(every setting) and Remove Content Control are now refused before they
run. Typing, Toggle Check Box, Choose List Item and Set Date still fill
the fields. Other protection modes are unchanged.

Observed in Word for Mac 16.113 on a protected real-world form
(72 content controls: rich text, check boxes, date pickers); the same
form filled in WordCraft opens in Word still protected, with its values,
ticks and dates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Restrict Editing: parity row states what forms protection refuses

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Restrict Editing: correct the comment on content-control property edits in forms mode

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 12:49:05 -04:00
Gabriel MatozoandBrandon Thomas 117aea31a8 Navigation pane: a long heading wraps within the pane instead of ending in an ellipsis (#3015) (#3029)
The pane drew each heading with `Button::selectable(..).truncate()`, so a heading wider than the
pane ended in an ellipsis and its text had to be read from the hover tooltip. Word wraps the row
instead.

`nav_heading` now wraps: the button takes `Button::wrap()` and a minimum size of the available width
and one row, so the label continues on further lines inside the pane, the row grows with the text,
and the pane still keeps its width. The trailing grow atom still keeps the text flush left under its
parent (#1199). The hover tooltip goes with the ellipsis: nothing is hidden now, and a word wider
than the pane is broken by the wrap instead of overflowing.

The navigation test asserts the wrap instead of the ellipsis. For a heading well past the width and
for a single word wider than the pane, the label has more than one line, is not elided, stays inside
the pane, does not widen it, and takes a taller row than a one-line heading in the same pane.

`docs/ui-parity.md` and `ROADMAP.md` record the new behaviour.

Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
2026-10-19 03:08:47 -04:00
Martin BallandClaude Opus 5.5 6950c22d73 #2999: a file dialog that "cancels" at once asks again without a parent, then shows the built-in browser (#3030)
* #2999: a file dialog that "cancels" at once asks again without a parent, then shows the built-in browser

rfd folds the xdg-desktop-portal's failure response into "cancelled", so on desktops whose
FileChooser portal fails to show a dialog, Save As, Export and Open did nothing and logged
nothing. A cancel quicker than anyone can make one (300 ms) is now taken as that failure: the
dialog is asked for once more without the parent window (GridCraft, which passes none, works on
the reporter's machine), and if that too comes back at once the app shows its built-in browser.
Each step logs a warning.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr

* #2999: review — the quick-cancel retry runs only on Linux and the BSDs; Windows and macOS answer a quick cancel as a cancel

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 03:04:59 -04:00
ReesandBrandon Thomas 61e565cd1c fix(ui): keep recent fonts visible after an unmatched query (#2068) (#2929)
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
2026-10-18 17:10:01 -04:00
100dfb37c8 Layout: a word after a slash wraps whole, like Word (#2963) (#2985)
* Layout: a word after a slash wraps whole, like Word (#2963)

UAX #14 (LB13) forbids a break between a space and a slash, and the break
after a slash is dropped on purpose (Word keeps "and/or" whole). Together
"caption /word" had no break opportunity at all, so a line too short for
both filled letter by letter and split the word. Add the break before a
slash that follows whitespace and starts a word.

Co-Authored-By: Claude <noreply@anthropic.com>

* Layout: no break before a slash after a no-break space (#2963)

The break before a slash word took any whitespace, U+00A0, U+2007 and
U+202F included, so "Nr. /12" could wrap after the no-break space.
Only a breakable space counts now. The parity checklist lists the rule
on its own row as untested against Word.

Co-Authored-By: Claude <noreply@anthropic.com>

* Layout: test the slash word in a fixed-width table cell (#2963)

The report came from a table cell, where the cell's own width is the
line width.

Co-Authored-By: Claude <noreply@anthropic.com>

* Docs: gaps and typing parity for the slash-word break (#2963)

Doc wording taken from #2994 (maxm11), with the test names of this PR.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude (ThinkSystem) <claude@thinksystem.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 16:41:44 -04:00
528da1f6a2 Comments: a draft survives a failed commit (#1348) (#1512)
* #1348: WIP

* #1348: keep comment drafts per document, keep them on a refused edit, write them before save/close/new/open

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1348: a refused comment draft can be dropped; fields read-only where comments can't change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1348: ui-parity: one Task panes and Context menus row after the rebase

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs after rebasing on main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1348: keep the window open while a web save is unconfirmed; split the roadmap row by operation

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #1348: pending comment drafts count as unsaved changes for the browser; a save error isn't hidden by a draft refusal

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #1348: an agent can't replace the document over a pending comment draft without force

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:57:07 -04:00
8b0f1762a1 Split Cells on merged cells: divisor message and 1x1 top-row cut (#2592) (#2913)
* #2592: Split Cells on a merged cell: divisor message, 1 x 1 cuts the top row off (Word check)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #2592: Split Cells dialog shows the divisor message; docs

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #2592: clippy

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #2592: Split Cells 1 x 1 on a merged cell changes nothing (corrected Word check)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Split Cells on a merged cell follows Word: divisor message, 1 x 1 changes nothing (#2592)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:55:32 -04:00
PanyangYandBrandon Thomas de8a81c1e4 References: Word sources file import in Manage Sources (#2640) (#2897)
* References: Word sources file import in Manage Sources (#2640)

Manage Sources gains Import… and references.sources an "import": path
parameter: it reads a b:Sources document (Word's own Sources.xml) into the
document's sources with the same reader as a document's own bibliography
part. A tag already here is left alone, an untagged entry is matched by
title and author or gets a tag like a new source, the whole import is one
undo step, and a file that isn't b:Sources is refused. Desktop only; the
web build hides the button.

The docx reader now shares the b:Sources parser (crates/docx/src/
bibliography.rs) with the import, with the same dedupe and 10,000-source
cap; the hostile-input cap test still passes.

Tests: docx unit tests (names, kinds, accessed/DOI fallbacks, repeated
tags, empty entries, wrong root), engine import test (add, undo, second
import, untagged matching, refused file), ui tests (the dialog button asks
for a "sources" file; finish_pick imports), wordcraft-cli end to end
(import, cite, bibliography, save, reopen; the package's customXml
carries the sources). The button's label is in every interface catalogue.

ROADMAP progress log and docs/roadmap.md, gaps.md, file-format-parity.md
and target-app-parity.md get their rows.

* References: review fixes for the sources import (#2640)

- read_sources validates the root namespace instead of the local name alone:
  an un-namespaced or unrelated <Sources> is refused, and both bibliography
  namespaces read — OOXML's and Word's own 2004/10 (the VBA Source.XML
  format), the latter mapped onto the canonical b prefix with parse_ns.
  Tests: the 2004/10 namespace is accepted; a no-namespace or unrelated
  root is refused.
- The import loop builds the tag and (title, author-or-editor) sets once and
  keeps a per-stem counter, with the stem rule shared with auto_tag
  (auto_tag_base). Importing thousands of untagged sources no longer costs
  O(n^3). Test: untagged sources sharing a stem get Riv21, Riv211, Src, and
  a second import adds none.
- docs/target-app-parity.md and docs/file-format-parity.md no longer claim
  bibliography sources aren't saved in DOCX: #169 and #532 are closed and
  the round trip exists (#790).

* References: doc comments for the sources import helpers (#2640)

Hoist the untagged-source matching helper (by_author) to module level and
document sources, auto_tag_base and sources_ui, so the functions this PR
touches meet CodeRabbit's docstring-coverage pre-merge check.

* References: re-apply the sources import's progress notes (#2640)

The rebase onto current main took upstream's copies of ROADMAP.md,
docs/file-format-parity.md and docs/gaps.md, because the rows this PR
adds conflicted with upstream's own appends. Re-apply them on the current
text: the progress log and revision rows, the bibliography row's
source-format note, and #15's evidence, with each status line's change
note. The other two documents (docs/roadmap.md, docs/target-app-parity.md)
merged cleanly and keep their rows.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:54:47 -04:00
2646700ca0 #2573: Picture Format › Size takes sizes in any unit, pixels too, and opens a Size dialog (#2889)
* #2573: Picture Format › Size takes sizes in any unit, pixels too, and opens a Size dialog

The picture size options were too limited (#2573, and #1901's request to enter pixels without
deforming the picture): the ribbon's W/H fields were points only, rejected "300 px", resized on
every keystroke (one undo step per digit), and there was no way to scale from the original
size, lock or unlock the aspect ratio, or reset the size.

- Picture Format › Size: W/H show the interface unit (Options › Advanced) and take a size typed
  in any unit, `px` included; it applies on Enter as one undo step, and the other side follows
  so the picture keeps its proportions. A drag still applies live.
- The Size group's launcher (and `picture.size` run without a size) opens a Size dialog: height
  and width, scale of the original size or of the size it opened with, Lock aspect ratio,
  Relative to original picture size, the original size with its pixels, and Reset.
- `picture.size {"original": true, "scale"?}` sizes from the picture's original size (its pixels
  at 96 ppi, read from the image adjustments started from, less what is cropped). A size past
  4000 pt that keeps the proportions now shrinks as a whole instead of flattening.
- New strings translated in every interface catalog.

The crop tool already exists (Picture Format › Size › Crop) and is being extended in #2027 and
#2440; options cut off in a narrow window are what #708 and #1293 (ribbon groups that shrink to
fit) address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZArmeAT48gF84aKj7Zx4C

* contributors: credit @matteo-andreuzza by their GitHub profile name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZArmeAT48gF84aKj7Zx4C

* #2573: Size keeps the proportions at the 1–4000 pt limits and follows the selection

Review follow-ups (CodeRabbit on #2889):

- Size dialog Reset on a photo larger than 4000 pt (a 6000 px wide one is 4500 pt) set it to
  the original size, which `picture.size` then clamped side by side: the picture was flattened.
  Reset now shrinks both sides by one factor.
- Locked, a side typed past the limit clamped the other on its own (15×30 pt with width 4000
  became 4000×4000). Both sides now scale by one factor.
- `fit_size` (engine) only shrank: a proportional size under 1 pt (scale 1% of 50×200 pt) became
  1×2 pt. It now picks the factor nearest 1 that brings both sides within 1–4000 pt, falling
  back to side-by-side clamping only for shapes too long and thin for any; the dialog uses it.
- Dialogs aren't modal: picking another picture while the Size dialog was open made OK size it
  with the first one's numbers. The form now follows the selection (starts over on the new
  object, closes when nothing is left to size).
- Doc comments on the Size form's methods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZArmeAT48gF84aKj7Zx4C

* #2573: Size dialog reads the picture again after a crop, change or resize

Review follow-ups (CodeRabbit on #2889):

- The open Size form only compared the selected object's position. A crop from the ribbon,
  Change Picture or a handle drag while the dialog is open left Reset and OK working from the
  old crop and size. The form now remembers the picture, crop and size it was read from (bit for
  bit, without comparing whole objects that may hold large embedded data) and reads them again
  when any changes.
- An egui harness test drives the dialog end to end: launcher, a height typed in pixels, nothing
  applied before OK, OK sizes in proportion as one undo step and closes the dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZArmeAT48gF84aKj7Zx4C

* #2573: size fields never resize a picture just by showing it

Review follow-up (CodeRabbit on #2889): egui's DragValue clamps a value already outside its
range when it is drawn and reports that as a change. The Size fields act on changes, so:

- Picture Format's W/H resized a picture smaller than 1 pt (a 1×1 px spacer is 0.75 pt) or
  larger than 4000 pt as soon as the tab was shown, an edit and an undo step nobody asked for.
  The code before this PR had the same problem with its 4–2000 pt range.
- The Size dialog's scale fields silently changed a picture enlarged past 10000% (a small icon
  made large), and its lengths a size outside 1–4000 pt, which OK then applied.

The range now bounds only what is typed or dragged (`clamp_existing_to_range(false)`). Both
regression tests fail without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZArmeAT48gF84aKj7Zx4C

* #2573: Shape Format and Chart Design size fields leave existing sizes alone too

Review follow-up (CodeRabbit on #2889, outside the diff): the same egui clamp as in d56a105.
Shape Format's W/H (4–2000 pt) and Chart Design's (36–2000 pt) clamped a size outside their
range as soon as the tab was shown and resized the object unasked. The new Size dialog makes this
easier to reach, since it takes shapes up to 4000 pt, but a thin line under 4 pt was already cut.
Both now show such a size as it is and act only on a new value. Shape Format's range follows
`picture.size` (1–4000 pt), so dragging a 3000 pt shape no longer snaps it back to 2000.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZArmeAT48gF84aKj7Zx4C

* #2573: Esc cancels a size typed in Picture Format; a typed size applies when the field is left

Review follow-up (CodeRabbit on #2889, outside the diff) asked to apply typed sizes on Enter only.
That is not done: a field keeps what was typed when it loses focus, as text fields do, and
otherwise typing a height in the Size dialog and clicking OK would throw it away. A harness test
now pins that path with a real click (press, then release frames later).

Checking it turned up a real bug: Esc did not cancel in the ribbon's W/H fields. egui 0.36 keeps
a DragValue's typed text after Esc and applies it the next frame, when the field still counts as
having just lost focus but Esc is no longer pressed. The fields now remember when Esc ended the
typing and drop that late value. (In the dialog Esc cancels the whole dialog, so nothing applies
there either; both are tested.)

Docs and comments now say "applied once typed (Enter, Tab or a click elsewhere; Esc cancels)"
instead of "applied on Enter".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZArmeAT48gF84aKj7Zx4C

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:54:25 -04:00
Fin WarmanandBrandon Thomas 596176c794 371: clear header tables for floating body tables (#2660)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:49:00 -04:00
c91c1793a3 #2108: File › Print scales pages to another paper size (#2699)
* #2108: File › Print scales pages to another paper size

Scale to paper size on File › Print (Word: Pages per sheet › Scale to
paper size): No Scaling or any Layout › Size paper (Letter, Legal, A4, …).
Every page is scaled to the paper and centred, the paper turned to the
page's orientation, in the preview, the print job and Save these pages
as PDF. Native jobs also ask the spooler for that media. The document's
page size is unchanged. New `paper` param on ui.print/ui.printSubmit and
file.printPdf; unknown papers are errors.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* pdf: bookmark destinations from #2364 use the scaled Fit too

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:48:17 -04:00
Fin WarmanandBrandon Thomas 5909ab642a T372: wrap body around page-relative footer tables (#2659)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:47:30 -04:00
Fin WarmanandBrandon Thomas 60617d9a71 371: count shared section pages in fields (#2654)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:47:03 -04:00
866b48ac55 Recipient list: find box and merged-only view (#1576) (#2621)
* #1576: Recipient list search and filtered view

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #1576: drive the recipient grid through egui_kittest

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #1576: recipient search treats final sigma as sigma

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #1576: removing a row lets go of the focused cell

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:45:59 -04:00
6e30349bbb #1327: protection checks cover tables, Paste Formatting and recipient refresh (#2618)
* #1327: protection checks cover tables, Paste Formatting, template update and recipient refresh

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #1327: record any content below the protection walk's depth limit

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #1327: formatting-only protection also refuses table properties and row height

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:45:40 -04:00
8c812bf1d6 #1230: Insert Caption on pictures and shapes; Different First Page in Page Setup (#2615)
* #1230: Insert Caption on pictures and shapes; Page Setup header and footer options

- Right-clicking a picture, shape, text box or chart offers Insert Caption…, as in Word.
- references.caption with a picture or shape selected puts the caption next to the
  paragraph holding it, in the same cell or text box (it used to go after the whole table).
- The Caption dialog proposes Figure for a picture selected in a table, not Table.
- Page Setup: Headers and footers check boxes (Different Odd & Even Pages, Different First
  Page), applied on OK only if changed, in one undo step; a title page can go unnumbered.
- Tests: engine caption_for_an_object_in_a_cell_stays_in_the_cell; kittest
  right_click_on_a_picture_offers_insert_caption and
  page_setup_different_first_page_leaves_the_title_page_unnumbered.
- Docs: ui-parity, target-app-parity, ROADMAP progress log.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW

* ui-parity: keep Insert Caption on the context-menu row

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:45:24 -04:00
dec24458cf #1273: equations from Word files keep all their content (#2613)
- Math straight in the body or a cell (m:oMathPara/m:oMath without w:p,
  allowed by EG_BlockLevelElts) gets its own paragraph instead of vanishing.
- mc:AlternateContent Requires="m" picks the equation, not its fallback.
- Inside an equation, runs in w:hyperlink, w:fldSimple, w:dir, w:bdo and
  mc:AlternateContent are kept (EG_PContentMath); depth stays bounded.
- w:sym, w:noBreakHyphen and Symbol-font text in math runs become Unicode.
- Layout: n-ary operators, delimiters, accents and group characters the math
  font lacks use a fallback font instead of a missing-glyph box.
- Coverage test reads every ECMA-376 §22.1 element and checks no argument's
  text is dropped; tests for body-level math, Requires="m", symbols, hostile
  nesting and the font fallback. Parity docs and progress log updated.


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>
2026-10-18 11:45:01 -04:00
Fin WarmanandBrandon Thomas 473df388f3 Keep the line in an empty section starting a page (#2609)
* 371: retain the line in an empty page section

* 371: resolve hidden section marks

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:44:45 -04:00
beedd1b30d Drop Cap Options dialog (#2601) (#2602)
* #2601: Drop Cap Options dialog

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gaps: keep the Drop Cap Options note on the dialog-depth row

* i18n: translate the two Drop Cap disabled reasons in the audited catalogs

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:44:30 -04:00
87cab84e4a #1086: ribbon tooltips show why a control is disabled (#2623)
Hover hints give the command's own reason, cover split-button arrows, menu buttons and the font/size/outline boxes, and show on keyboard focus.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:44:12 -04:00
5fdd0a0e89 Watermarks in header table cells drawn behind the text (#1775) (#2580)
A floating picture or VML shape in a header table cell is placed on the page behind the text.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:43:55 -04:00
6dd7c3bc3a #1963: wordcraft-cli runs commands from a script file (#2566)
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:42:13 -04:00
ReesandBrandon Thomas 1005c59bfc fix(ui): keep note editing text readable (#2545) (#2562)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:41:58 -04:00
63ee34c557 #2105: Highlight No Color sticks to the button (#2208)
No Color removed the highlight from the selection, but the Highlight
split button kept the colour picked before, so the next click on it
highlighted again. As in Word, No Color now becomes the button's choice
and the next click removes highlighting. The ribbon and the mini toolbar
share one highlight menu (the ribbon's copy is gone); No Color is
translated in the mini toolbar and no longer shows Ctrl+Alt+H beside it
in the ribbon.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:40:55 -04:00
Antoine BoxhoandBrandon Thomas 61a626820e #1552: no spelling squiggle under the word being typed (#2183)
Word draws no red squiggle under the word being typed. It appears when the word ends or the
caret leaves it by a key or a click. The session records the word after a text command that
edits the caret's paragraph, with apostrophes and hyphens counted as the spelling check counts
them, and drops it once the caret is anywhere else. The layout leaves that one range out of the
spelling squiggles, and a caret move without an edit lays out only that paragraph again. F7 and
the Spelling pane still report the word, and grammar marks are unchanged.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:39:21 -04:00
95b5c71f80 Save writes the chosen file when its folder refuses a temporary file (macOS privacy protection) (#2138) (#2167)
* #2138: save writes the chosen file when its folder refuses a temporary file (macOS privacy protection)

On macOS a Save-panel pick inside a protected folder (Desktop, Documents…) grants
access to that file only, so the sibling .wordcraft-save-*.tmp was likely refused
with EPERM. On PermissionDenied creating the temporary file, write the destination
directly; a remaining permission error on macOS says where to allow access.
Untested on a Mac: the fallback path is pinned on Unix with a read-only folder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #2138: a failed save in a folder refusing the temporary file keeps the original

Review (FredApps): the in-place fallback truncated the destination before
writing, so a failure partway through lost the previous document.

The fallback now keeps the existing file's bytes in memory (up to 512 MB)
before writing it in place and writes them back if the save fails, or
removes a new file it created. If that write-back fails too, the error
says the file may be incomplete. A file over the limit isn't written in
place; the error suggests Save As.

Tests: the reviewer's partial-write test (read-only folder), a new file
removed on failure, and the size limit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW

* #2138: keep the original untouched when no safe replacement is possible

When the folder refuses the sibling temporary file, an existing destination
is no longer overwritten in place (no in-memory rollback): it stays
byte-identical and the error says to use Save As to another folder or allow
access to the folder (macOS: Privacy & Security > Files and Folders). A
destination that doesn't exist yet is created exclusively and removed again
if the write fails.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
2026-10-18 11:38:04 -04:00
872b2bc99d DOCX: ink inside groups and drawing canvases survives saving (gap 2) (#2097)
* gap: nested ink WIP

* gap 2: native ink nested in groups and drawing canvases survives DOCX saves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 2: review round 1: ungrouping keeps nested ink; no orphan parts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 2: Impact cell matches the restated impact

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 2: only Word ink pins a group against Ungroup

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 2: alternate ink must hold an a:graphic to be written as standalone

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap: nested ink WIP

* gap 2: native ink nested in groups and drawing canvases survives DOCX saves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 2: review round 1: ungrouping keeps nested ink; no orphan parts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 2: Impact cell matches the restated impact

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 2: only Word ink pins a group against Ungroup

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 2: alternate ink must hold an a:graphic to be written as standalone

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:34:42 -04:00
Antoine BoxhoandBrandon Thomas aed0b71786 #1878: Rulers thin out with the zoom and span the caret's page (#2093)
Ruler numbers now thin out as the page shrinks on screen: a number
every 1, 2, 4, 8... units, whichever keeps them at least 30 px apart,
and the small ticks drop out below 4 px, with the text at its usual
size. A number gives way to an indent, tab or column marker sitting on
it, and the margins get ticks only. Both rulers use the caret page's
rect and scale in every layout, including One Page and Multiple Pages,
and follow the caret to another page. Tests cover the density rule, the
marks, the marker rule for indents, tab stops (set or dragged) and
table columns and rows, and the multi-page geometry.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:34:26 -04:00
Antoine BoxhoandBrandon Thomas 0f5e945a69 #1945: Hide the Quick Access Toolbar and show it again (#2089)
The Quick Access Toolbar's menu ends with Hide Quick Access Toolbar, as Word's does. Show Quick Access Toolbar brings it back from the ribbon's right-click menu and from a check box on Customize Ribbon's Quick Access Toolbar page. The setting is kept between runs, and keytips skip the toolbar's digit badges while it is hidden. The UI command ui.quickAccessToolbar drives it from scripts and MCP. Regenerates docs/parity-checklist.md, which was behind the catalog. Adds my contributor entry.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:34:10 -04:00
e50850d4dd RTF: formatting and paragraph-mark revisions are read and written (gap 19) (#2088)
* gap: RTF formatting revisions WIP

* gap 19: RTF formatting and paragraph-mark revisions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: review round 1: list kept on Reject; old formatting normalised; section marks; hostile borders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: review round 2: \oldcprops holds only the changed properties, as Word writes it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: old formatting in styled paragraphs uses the style's size and font

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: RTF import keeps main's heading and code normalisation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: file-format-parity status line keeps main's history

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 19: a paragraph that was a list item gets its old list back on Reject

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 19: a numbered item made bulleted keeps its numbered list for Reject

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 19: format matrix names the tracked section mark

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* rtf test: Writer has the section fields main added; rustfmt

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:33:46 -04:00
70fc00c80f Symbols keep their symbol font when the text font changes (#2036) (#2037)
* Symbols: a w:sym character keeps its symbol font when the text font changes (#2036)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #2036: review round 3: docx test reads sym_font, paste drops the symbol font, astral symbols stay text

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #2036: insert.symbol decides symbol or text per character

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 11:33:00 -04:00
c9a5191b70 i18n: French interface catalog completed (2,615 entries) with native-speaker review (#2492)
Adds the 402 entries the French catalog lacked: equation gallery (matrices, accents, brackets, integrals, operators, functions), built-in style and table-style names, citation styles, paper sizes, watermarks, and the Bookmark, Go To and Print dialog strings. Drops the obsolete 'Style: APA' entry. Terminology reviewed by a native French speaker and Word user. The Polish superset test exempts fr like the other catalogs carrying newer strings. Progress docs and people.toml updated.

Co-authored-by: Vivien JANNY <vivien@avertissance.fr>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:29:17 -04:00
ReesandBrandon Thomas ff39a5c357 fix(ui): deliver control input in inactive windows (#2420) (#2530)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:28:37 -04:00
ReesandBrandon Thomas 1595343fa0 fix(ui): expose save confirmation context to screen readers (#2489)
* fix(ui): expose save confirmation context (#1015)

* #1015: require alert-dialog semantics in save regression

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:28:20 -04:00
09b822fdbe Update Field after save and reopen of a table of contents; no extra blank line per cycle (#2182) (#2553)
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:27:11 -04:00
Bryan MarkandClaude Opus 5.5 8107581df2 Opening a DOCX no longer copies its pictures (#2441)
Each picture, and each embedded object or macro part kept for saving, was
copied out of the unzipped package (bytes.to_vec()), so a document with
pictures held them twice while it opened, and the package's copies were
left behind as freed memory. Package now holds its parts as Arc<Vec<u8>>,
the type the document keeps media and passthrough parts in, and hands them
over with get_shared. keep_unmodelled also searches the main part in place
instead of copying it.

A 92 MB document with 20 pictures on macOS 27: the app's footprint once
open 866 -> 772 MB (freed blocks kept 206 -> 118 MB, peak 915-932 ->
821-823 MB); wordcraft-cli info peak 319 -> 231 MB.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:26:56 -04:00
Fin WarmanandBrandon Thomas ca45f97ace Layout: exclude floating anchors from line font metrics (task 256) (#2462)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:26:32 -04:00
ReesandBrandon Thomas 9726d6f1cb fix(ui): offer Save As for imported Word files (#2448)
* SCRUM-47: offer Save As for imported Word files

* #2445: reject Save As answers for a replaced document

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:26:16 -04:00
Fin WarmanandBrandon Thomas 57dfacfc07 Layout: preserve equation baselines under exact line spacing (#2435)
* Layout: preserve equation baselines under exact line spacing (task 243)

* layout-parity: keep the cell-margin inheritance note on the Tables row

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:26:01 -04:00
PanyangYandBrandon Thomas a14948a23d DOCX: strict line heights with a unit are points under auto (#2329) (#2439)
Word 2024 saves Strict Open XML with measures written out with their unit:
the issue's file carries `w:spacing w:after="8pt" w:line="13.90pt"
w:lineRule="auto"` in its paragraph defaults. The reader sent `13.90`
straight into the `auto` branch, where a bare number counts 240ths of a
line (240 = single), so 13.90pt became a 13.9 multiple: 191.8 pt lines,
and the issue's document laid out as 10 pages against Word's 1.

Under `lineRule="auto"` a unit-suffixed height now reads through twips, as
Word renders it (ISO 29500 `ST_SignedTwipsMeasure`: 13.90 pt = 278 twips =
the 1.1583 multiple). A bare number still counts 240ths of a line;
`exact`/`atLeast` already read units as points.

Checked with the issue's file: 1 page with baselines on Word's (15.98 pt
line advance against Word's 16.10/15.96; paragraph advance 23.99 against
24.00; table lines 12.65 against 12.60/12.72), and a save/reopen keeps it.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:25:14 -04:00
Fin WarmanandBrandon Thomas d7bd5bcd3c Layout: preserve additive spacing after section breaks (task 243) (#2433)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:24:41 -04:00
28d6260a81 #1789: Add Font imports every selected font file (#2404)
* Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1789: cap font bytes waiting in the web inbox

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:24:26 -04:00
5c4408f341 Paste: plain text keeps the formatting of the paragraph it lands in (#2423) (#2424)
Paste of plain text from another program gave every line after the first
a paragraph with default formatting. The text became a fragment of
unformatted paragraphs (Fragment::from_text), and insert_fragment keeps
the target's formatting only in the first one. Pasting several lines into
a right-to-left paragraph turned all but the first left-to-right, and the
text after the caret lost its paragraph formatting too.

Paste now inserts plain text as Keep Text Only does. Each line after the
first splits the paragraph as Enter does, so it keeps the paragraph's
direction, style, alignment, indents, spacing and list. Empty lines become
empty paragraphs with that formatting. Copies from WordCraft and HTML or
RTF from other programs keep their own formatting as before. A paste is
still one undo step.

Tests cover a right-to-left paragraph, empty lines and a final line break,
and a list. The typing and UI parity docs and the ROADMAP progress log
have rows for the change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:23:43 -04:00
d6f20f93fd Tables: at-least rows taller than a page start on a fresh page; header rows follow (#1742) (#2004)
* WIP for #1742

* Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1742: parity doc separates the Word-observed case from synthetic-test behaviours

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 09:57:47 -04:00
3348e93140 ASK and SET fields show nothing (#1765) (#1785)
* #1765: WIP

* #1765: ASK and SET fields show nothing

Like Word, ASK and SET only assign text to a bookmark: they draw no
result on the page or in a PDF. The stored result is kept and saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* file-format-parity: note ASK/SET on the existing Fields row instead of a third one

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 09:57:31 -04:00
11f1d0aee6 Lowercase list items no longer get a grammar underline (#1741) (#1783)
* #1741: WIP

* #1741: list items no longer get the sentence-capital grammar squiggle

The English "Capitalize the first word of a sentence" rule skips the first
word of a list paragraph (own or style numbering); later sentences are still
checked. check_text_with and paragraph_issues take a list_item flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1741: review fixes

Thread the capital-first flag into the Polish, Croatian, Italian, Turkish and
Portuguese grammar rules; test the drawn squiggles through layout() for own,
style and num 0 numbering; tidy the proof test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1741: main's Vietnamese proofing test passes the new list-item argument

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1741: the third call in main's Vietnamese proofing test

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:57:15 -04:00
b8279c4da5 #1960: add editable table cell margins dialog (#1988)
Co-authored-by: justinjohn0306 <babyjjbabyjj549@gmail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 09:56:48 -04:00
7f4b649f6f RTF export: sections are saved (gap 19) (#1776)
* rtf-sect-export: WIP

* Gap 19: RTF export writes sections

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gap 19: RTF export writes sections — review fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* odt-sect-export: WIP

* Gap 19: ODT export writes sections

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gap 19: ODT export writes sections — review fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gaps log: RTF and ODT section export both done

* Gap 19: ODT continued lists share the earlier list's style; one RTF and ODT row in the parity table

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:55:42 -04:00