Commit Graph
236 Commits
Author SHA1 Message Date
e0a1346590 Layout: inside/outside floats swap sides on even pages; Page Setup shows mirror margins (#1181) (#1239)
* Layout: inside/outside floats swap sides on even pages; Page Setup shows mirror margins (#1181)

Floats aligned inside or outside, or placed relative to the inside or
outside margin, were always put as on an odd page, so shapes Word puts
in a book's outside margin all came out on the right. They now follow
the page they're on (ECMA-376 ST_AlignH, ST_RelFromH): on an even page
the inside is the right side. Floating tables (w:tblpXSpec) too.

With mirrored margins, even pages are laid out as odd ones and then
shifted; float positions now allow for that shift, so floats placed
from the page edge stay put (the "known limit" in #452) and the left
and right margin areas are each page's own.

Mirrored margins read from a file were invisible in the UI: the Margins
menu ticks Mirrored when they're on, and Page Setup gets a Mirror
margins check box (labelling the margins Inside and Outside), applied
through a new `mirror` param of layout.margins.

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

* i18n: Mirror margins in every catalog

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

---------

Co-authored-by: lloyd <lloyd@stackworx.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 18:57:44 -04:00
Brandon ThomasandClaude Opus 5.5 cf140ae600 CI: Select All + Delete test presses Select All until the document is selected
#1202's test selected all once with the caret in a table; since #1478,
Select All in a table grows a step at a time (cell, row, table,
document), so one press selected only the cell and the table stayed.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:25:20 -04:00
40cf85c25b Insert › Bookmark: Delete and Go To, laid out as Word's (#1236) (#1277)
* Insert › Bookmark: Delete and Go To, laid out as Word's (#1236)

The Bookmark dialog had only Add: a bookmark could not be deleted, and a
click in its list jumped to the bookmark at once. It is now laid out as
Word's (observed in Word 2019 on Windows): the name box over the list, Add,
Delete and Go To beside them, Sort by Name or Location and Hidden bookmarks
below. A click in the list picks a name; Add (Enter) adds and closes; Delete
and Go To leave the dialog open, and after a delete Cancel reads Close. The
dialog opens on the bookmark around the caret, and Add is off for a name
the command would refuse.

insert.deleteBookmark {name} removes a bookmark's start and end wherever
they are, also across paragraphs, as one undo step, keeping the selection
on its text; from the command search without a name it opens the dialog.
Adding a bookmark under a name in use goes through the same removal: it
looked for the old end only in the start's paragraph, so a bookmark across
paragraphs left a stray end behind. edit.goto with a bookmark selects its
text, as Word's Go To does, instead of placing the caret at its start.

Closes #1236

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

* Insert › Bookmark: Ctrl+click on a link to a bookmark keeps a caret (#1236)

Word's Go To selects a bookmark's text, but Ctrl+click on a link to a
bookmark only puts the caret at the bookmark's start (checked in Word 2019
on Windows). Following a link runs edit.goto, which now selects, so it
collapses the selection to its start.

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

* Resolve the doc conflict markers left by the previous merge

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-14 18:16:55 -04:00
NetterSilverandBrandon Thomas 44787764a9 Table Layout: the Cell Size boxes apply to the whole selection (#1242) (#1276)
* Table Layout: the Cell Size boxes apply to the whole selection (#1242)

table.rowHeight and table.columnWidth read the caret's cell only. With the whole
table selected the caret is in the last cell, so only the last row's height and
the last column's width changed.

Both now use the cells the selection covers (selected_cells): every selected
row gets the height and every grid column the selected cells touch gets the
width, as one edit. Without a selection that spans cells they still change the
caret's row and column.

* Docs: update the progress docs for this change

* Docs: put the new sentence in the WordCraft / evidence column

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 18:16:35 -04:00
ahutsunshinandBrandon Thomas 5ffd7a99f5 fix: remove unreachable empty-string branch in open_bytes_with match (#1258)
The match arm included | "" but ext.is_empty() is already handled above
by the guard that routes empty extensions to open_by_content. This branch
could never be reached, making it dead code that misleads readers.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 18:15:27 -04:00
Marco ArnulfoandBrandon Thomas 0c5804767c Layout menus mark the current margins, orientation, size and columns (#1214)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 17:59:59 -04:00
9710db73da Right-click on a table of contents entry offers Update Field (#1203)
* Right-click on a table of contents entry offers Update Field (part of #1197)

* Test: the TOC context menu check allows more than one Paste item

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:59:36 -04:00
Marco ArnulfoandBrandon Thomas 70a4917973 Tables: Select All then Delete removes a table the document starts with; right-click Delete offers Delete Table again (#1202)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 17:59:17 -04:00
9f7330664b Word forms keep their legacy form fields and rows' skipped grid columns when saved (#1184)
* Word forms keep their legacy form fields and rows' skipped grid columns when saved (part of #1174)

* Tests: field initializers name the new form field

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:58:07 -04:00
Brandon ThomasandClaude Opus 5.5 373df5adf1 CI: recovery pruning test retries a scan that a lock race skipped
failed_pruning_cannot_push_a_record_beyond_the_discovery_limit failed
once in a full workspace run: a record whose lock is briefly held (by a
child another test is spawning, which shares the descriptor until it
execs) is skipped rather than reported, so scan() returned Ok. The test
now retries for up to a second before asserting.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:39:06 -04:00
3d8f8144d9 File: recover unsaved work after a crash (#518) (#586)
* M10: retain local crash recovery on current main

* i18n: Backstage Recover in every catalog

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:21:57 -04:00
575c5f98df .doc: open password-protected Word 97–2003 files (RC4, RC4 CryptoAPI, XOR) (#1079) (#1087)
docbin decrypts XOR-obfuscated, RC4 and RC4 CryptoAPI files after verifying the
password against the stored verifier; the engine reuses the .docx password
prompt errors. Written from [MS-DOC] and [MS-OFFCRYPTO]; synthetic tests only.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 17:21:08 -04:00
eb54d88ec0 Mail merge: .xls recipients, legacy encodings, Filter and Sort (#778) (#885)
* #778: .xls and legacy-encoding recipient lists; filter and sort recipients

- Recipient lists: legacy Excel 97-2003 .xls (BIFF8 reader on the cfb crate already in the
  tree); Excel 5.0/95 and password-protected .xls refused with a clear message; a CSV named
  .xls reads as text, an HTML one gets an error. .xls added to the picker filters (desktop,
  web, built-in browser) and drop handling.
- Text lists (and plain-text import) detect legacy encodings when not UTF-8/UTF-16: GBK/GB18030,
  Big5, Shift_JIS, EUC-KR, Windows code pages (encoding_rs + chardetng, MIT/Apache); an
  `encoding` param overrides.
- mailings.filterRecipients / mailings.sortRecipients: field, comparison (equal, notEqual,
  less, greater, lessOrEqual, greaterOrEqual, blank, notBlank, contains, notContains), value,
  And/Or joins; sort keys. Preview, Finish & Merge and labels use only the matching records
  in sort order. Edit Recipient List edits every record and keeps the filter.
- Recipient List dialog: Filter and Sort section and a merge-order column; catalog rows;
  strings in every catalog; docs.

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

* i18n: Hungarian and French strings for this change

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

* Review fixes for #885: total order for recipient sort, Bulgarian strings

- compare_cells now ranks numbers (compared numerically) before text (compared
  case-insensitively), so it is a total order; a column mixing 9, 10 and 1a
  could make slice::sort_by panic. Regression test sorts mixed columns.
- Bulgarian catalog: the 24 Filter and Sort strings.

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

* vi: mail merge Filter and Sort strings

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:57:06 -04:00
cf9a4ff7fa STYLEREF fields show each page's result in headers and footers (#775) (#882)
* #775: STYLEREF fields in headers and footers show each page's result

Running headers/footers with STYLEREF (e.g. the current chapter title) are
evaluated per page at layout time (ECMA-376 §17.16.5.59): the page's first
paragraph of the style (\l: last), else the nearest before, else after.
Style names/ids, heading levels (STYLEREF 1), character styles, and the
\n \r \w \s \t \p switches. Body STYLEREF fields update with the other
fields. STYLEREF added to Insert › Quick Parts › Field with its options.

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

* #775: STYLEREF error text in our own words

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

* Review fixes for #882: translate the STYLEREF Field dialog strings

Adds the style picker label, the six STYLEREF switch labels and the
STYLEREF description to all 18 interface catalogs.

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

* STYLEREF: reuse the style index while the body and styles are unchanged

Headers with STYLEREF rebuilt and hashed the body's style index on every
layout pass (echelon's follow-up note on #882). The layout cache now keeps
the index with the body blocks it came from (shared, so an edited block is
a different pointer) and the styles/numbering hash, and rebuilds it only
when either changed.

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

* vi: STYLEREF field strings

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:56:47 -04:00
3e20488c15 Comments: an option to show only the selected text's comment (#1055) (#1066)
* test(engine): drop a needless drop() that clippy 1.97 rejects (drop_non_drop)

* Comments: a click inside a selected balloon moves the caret in its text (#1013)

- The canvas under the markup area also "contains" the pointer, so a press on the balloon counted as a click on the page and left it
- Presses over a balloon card now belong to the card, in both the deselect check and the document click in mouse()
- Test: press, drag and release inside the selected balloon; a click on the page still leaves it

* Comments: an option to show only the selected text's comment (#1055)

- Track Changes Options › Comments: "In the margin" (default, as in Word) or "Only for the selected text"
- The second mode takes comments out of the margin column; the comment under the caret, or the clicked one, floats beside the page, over its edge in a narrow window
- The floating card has its own foreground layer, so presses on it never reach the canvas
- review.trackingOptions takes commentDisplay: margin|selected
- comment_range_at_caret: the comment whose range holds the caret
- Strings in all catalogs; tests for the option, the helper and the card's clicks

* vi: comment display options

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:55:27 -04:00
77da439d0b DOCX: carry unmodelled parts; keep comment ids, style defaults, latent styles, list ids and app props (#1034) (#1060)
* DOCX: merge settings.xml into the original instead of regenerating it (#1034)

A save regenerated word/settings.xml from the model, so every setting the
model doesn't hold was dropped: compatibility options (lost in 18 of 18
real-world Word files), rsids, document variables, proofing state and the
colour scheme mapping.

The reader now keeps the original part when it holds such settings, and
the writer merges into it: the original root and every unowned child stay
byte for byte; owned children follow the model in CT_Settings order; in
w:compat only compatibilityMode is replaced. The privacy options are the
model's since #1062, so they are owned too. Kept children that point at a
relationship (a mail-merge data source) are dropped. Anything unexpected
falls back to the generated part. Files WordCraft wrote itself keep
nothing extra.

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

* DOCX: carry unmodelled parts and keep comment ids, style defaults, latent styles, list ids and app props (#1034)

Builds on the settings.xml merge.

- Parts the package or main document relate to that WordCraft neither
  models nor writes (webSettings, stylesWithEffects, people, add-in task
  panes, the thumbnail, future parts) are carried verbatim with their
  relationships and content types; relationship types the writer
  produces, and any the body refers to by id, are not (HANDLED_RELS).
- commentsIds / commentsExtensible: durable ids and UTC dates are read
  into the model and regenerated against the written paragraph ids.
- w:default on localized default styles, w:latentStyles, lists'
  nsid/tmpl/multiLevelType, and app.xml Template/Company/Manager/
  HyperlinkBase round-trip; counts are not carried, and Company/Manager
  are dropped when the file asks to remove personal information.
- Comment reference marks use the document's "annotation reference"
  style id instead of a hard-coded CommentReference.

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

---------

Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:55:15 -04:00
e1f34a90d6 Proofing: a Dictionaries folder setting, and the dictionary each language found (#1063) (#1084)
File › Options › Proofing gets a Dictionaries folder: a folder of Hunspell
.aff/.dic files searched before the system locations, saved with the settings
and applied at start-up. An app started from Finder or the Dock doesn't see
WORDCRAFT_DICTIONARIES, so this is how it finds a dictionary kept in a folder
of the user's own. Changing it reloads the Polish and Portuguese dictionaries
and checks the text again. The pane lists the dictionary file each language
found, or that none was found, so missing spelling marks are explained.

Agents set it with file.options {"dictionariesFolder": path | "auto"}; a
missing folder or a relative path is refused and changes nothing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:25:00 -04:00
a04356933c UI: mini toolbar steps aside for the ribbon (#1069)
* Clippy 1.96: simplify boolean guards, drop a needless drop()

Rust 1.96's clippy flags nonminimal_bool in find_pattern, table_mouse and
table_resize, and drop_non_drop in a ribbon test; `cargo xtask ci` failed at
clippy on main. Same logic, rewritten as clippy suggests.

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

* UI: mini toolbar steps aside for the ribbon

The mini toolbar covered the ribbon's drop-downs (the font size list opened
under it). A press outside the document and the bar (the ribbon, a pane, the
rulers, the status bar) now puts the bar away until the selection changes or
the next press in the document; presses on popups (the bar's own lists, an
open ribbon list over the page) neither hide nor restore it. The bar also
moves from the Tooltip layer to Middle, so every popup and menu draws over
it, as its comment always said.

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

* UI: mini toolbar scrolls under the ribbon, not over it

With text selected, scrolling the selection up toward the ribbon carried the
mini toolbar along on top of the rulers and ribbon. The bar is now clipped to
the document area: it slides under the rulers and ribbon (or the status bar)
with the text and goes once none of it shows. Its area is constrained to the
document for hit testing too, so the hidden part no longer swallows clicks
and drags meant for the ruler or the ribbon underneath.

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-14 16:23:50 -04:00
f9f8b67c54 feat(ui): implement Ctrl+drag to copy objects on canvas (#1046)
* feat(ui): implement Ctrl+drag to copy objects on canvas

* feat(ui): implement Ctrl+drag to copy objects on canvas and update translations for "Copy to"

* vi: Copy to

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

* rustfmt

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:04:34 -04:00
1f01a13dfc Track Changes: deletions inside a field result are tracked, accepted and saved (#1037) (#1043)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:04:13 -04:00
e29fbdb0bf Page background: picture and texture fills are drawn and kept on save (#999) (#1036)
* Page background: picture and texture fills are drawn and kept on save (#999)

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

* rustfmt

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:03:32 -04:00
85dd93a2ba Table eraser: borders beside vertically merged cells merge as in Word (#1039) (#1044)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:48:19 -04:00
Marco ArnulfoandBrandon Thomas 68501de224 One Page and the zoom are saved with the document and restored when it is reopened (#1023)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:47:46 -04:00
8196624e83 Caret: Down and Up from a line start beside a table go into it, not past it (#1021)
Down from the start of the line above a table (or Up from the start of the
paragraph after it) skipped the whole table: `DocLayout::vertical` took the
nearest line covering the caret's x, and a cell's lines start a cell margin in
from the table's edge, so the paragraph beyond the table won. Lines between
the caret's paragraph and that line in document order now come first: the
caret goes into the nearest such line's table row, to the cell under it or the
horizontally nearest. Another cell of the caret's own row never counts, so Down
in a table still goes to the cell below. Pinned in `tests_typing.rs` and
`docs/typing-parity.md`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:31:22 -04:00
e9d5da31d4 Track Changes: rejecting a whole inserted paragraph keeps the next paragraph's style and numbering (#1020)
Reject All (and Accept All of a whole deleted paragraph) joined the next
paragraph into the emptied one, so the next paragraph took the removed
paragraph's properties: a § heading after a rejected list item became a
list item and later § numbers shifted. An emptied paragraph is now removed
whole, as Word does. Found while round-tripping a Word-saved contract.

Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:31:04 -04:00
aedbbc9325 feat(engine): rename documents from the title bar and open account settings from the badge (#1016)
* M10: enable document renaming and account settings

* no-mistakes(review): Allow case-only document renames without overwriting files

* no-mistakes(review): Keep unsaved rename as session file name, not Title

* no-mistakes(review): Keep AutoSave and Recent across renames; Compare clears name

* no-mistakes(document): Document file.rename write-root rule; dedupe rename gap note

* vi: Rename Document strings

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:30:52 -04:00
5597351213 #916: Cross-reference opens a dialog (#1003)
* #916: Cross-reference opens a dialog

The ribbon's Cross-reference button ran insert.crossReference with no
target, which only returns the target list, so nothing happened. It now
opens a Cross-reference dialog: reference type (heading, bookmark,
equation, figure, table), what to insert (text, number, page,
above/below) and the item, ending in insert.crossReference. Scripts and
agents still get the target list.

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

* i18n: Cross-reference dialog strings in the audited catalogs (ru, hu, tr, bg)

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

* i18n: Cross-reference dialog strings in pl, it and vi

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

---------

Co-authored-by: justinjohn0306 <babyjjbabyjj549@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:30:32 -04:00
Marco Arnulfo ee33d71173 Focus hides the ribbon, status bar and panes; Escape brings them back (#1017) 2026-10-14 15:09:32 -04:00
987c41364d Tables: AutoFit in a nested table, whole cells shaded for a selection through a table, Shift+Right at a cell's end (#1004)
* Tables: AutoFit in a nested table fits within its cell (#971)

A double-click on a nested table's column border let the table widen up to
the page's right margin, far past the cell around it. It now widens only up
to the right edge of that cell's text (a right-to-left table's start margin
on the right); the columns right of the border still keep their widths.

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

* Tables: a selection through a table shades its rows' whole cells (#971)

A selection from text into or across a table takes whole rows (#722), but
only the letters in the cells were highlighted. Every row it takes from start
to end now shades all its cells (an empty one too), as selected cells do, and
the letters in them aren't shaded twice (`table_mouse::selection_rects`, used
by the canvas). A selection that starts and ends in one table keeps its text
highlight, so selecting all the text of a cell doesn't look like selecting the
cell; a nested table in a shaded row shades with it.

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

* Typing: Shift+Right at a cell's end selects the cell first (#971)

Shift+Right out of the cell the selection is in selected that cell and the
next one at once. As in Word, the first step out of the cell now takes the
cell alone (its end-of-cell mark) and the next press goes on into the next
cell; in an empty cell the first press selects it. Shift+Left in
right-to-left text, the forward step there, does the same. Pinned in
`tests_typing.rs` and `docs/typing-parity.md`; progress docs for all three
table fixes of #971.

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-14 15:08:57 -04:00
Brandon ThomasandClaude Opus 5.5 6b107250a5 CI: Turkish review-command test skips without a Turkish dictionary
#1527's engine test needs a system Turkish Hunspell dictionary; on
machines without one it failed the workspace tests.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:03:01 -04:00
3fc4ef2cf4 proof: Turkish spelling, suggestions, grammar and hyphenation (#1527)
* proof: Turkish spelling, suggestions, grammar and hyphenation

* Turkish: dictionary tests skip when no Turkish Hunspell dictionary is installed

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:45:30 -04:00
e7e4522d0d ODT: read and write tracked changes (text:tracked-changes) (#1517)
* ODT: read and write tracked changes (gap 19)

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

# Conflicts:
#	ROADMAP.md
#	crates/engine/src/tests.rs
#	crates/formats/src/model.rs
#	crates/formats/src/odt.rs
#	docs/file-format-parity.md
#	docs/gaps.md

* ODT tracked changes: deleted paragraph marks don't take the body's insertion; one region per deletion

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 Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:45:07 -04:00
mjohnson7cd 4d79e18ae8 fix(math): preserve equation text color on save/load (#1467) (#1525)
Lift equation color from run-level OMML to CharProps during read,
push it back to runs during write. Keep source OMML until the color
changes to preserve control properties the model can't hold.
2026-10-14 14:29:11 -04:00
666b9afc5a Draw: Ink to Shape recognises arrows (#1520)
* feat-ink-to-shape-arrows: WIP

* Draw: Ink to Shape recognises arrows (gap 12)

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

* Ink to Shape arrows: hand-drawn shafts, more tests, docs

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

* Ink to Shape arrows: the tip is the first near-farthest point

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

* Ink to Shape arrows: step to the real tip on dense strokes

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 14:28:59 -04:00
60c4a372d5 RTF: read and write tracked changes (insertions, deletions, authors, times) (#1516)
* feat-rtf-revisions: WIP

* RTF: read and write tracked changes (gap 19)

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

* RTF tracked changes: Unicode authors, \pard keeps revision marks, pasted deletions stay deleted

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 14:28:20 -04:00
b10c97b765 Document Inspector: find hidden text outside the body and from styles (#1344) (#1515)
* #1344: WIP

* #1344: Inspect Document finds and removes hidden text in every story and from styles

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

* #1344: hidden paragraphs, note references and table-style text handled; faster removal

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

* #1344: a hidden paragraph between tables stays; note echo capped

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 14:28:00 -04:00
37d41150e6 Track Changes and content controls: table delete, Change Case and Unlink Fields respect them (#1329) (#1514)
* #1329: WIP

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

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

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

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 14:07:47 -04:00
b1e8ae07e7 ODT import: keep each section’s page properties (#1379) (#1506)
* #1379: WIP

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

* #1379: master pages on table styles start sections too; spec tidy-ups

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 14:06:49 -04:00
0c7b4a72f2 Layout: long tables keep one layout per cell and relayout without re-measuring (#1492)
- Table autofit measured every cell paragraph at an unbounded width through
  the paragraph cache, so a long table held two layouts per cell. The cache
  now keeps only the measured narrowest/widest widths (`LayoutCache::measures`,
  same key and eviction as the layouts).
- The cache key formatted each cell's table-style text formatting with Debug
  for every cell paragraph on every pass; the hash is now memoised per pass.
- Update Fields added the hidden ListEntry style even without a generated
  list, which changed the layout environment and dropped the whole cache.
  The style is now added only when a list is rebuilt.

Co-authored-by: dfgodvan <180286157+dfgodvan@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:06:17 -04:00
42c1f71b52 Custom page sizes: Size › More Paper Sizes and Page Setup width/height (#1433) (#1496)
* #1433: WIP

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

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

* #1433: one OK is one undo step; errors stay in the dialog

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:58:33 -04:00
bf2bd69470 HTML import: bold header cells, cell alignment and the region paper (#1381) (#1494)
* #1381: WIP

* #1381: HTML import: bold <th>, cell text-align, region paper for HTML/MD/TXT

Header cells are bold unless their own CSS says otherwise. text-align on a
td/th, or inherited from its tr/row group/table, aligns the cell's
paragraphs; a paragraph's own alignment wins, and a table's align attribute
(which places the table) no longer counts. Files that give no page size
(HTML, Markdown, plain text) open on the region's paper, as a new blank
document does: file.open uses the session's region, wordcraft-cli reads the
system region the way the app does (Region::from_locale_vars, shared with
ui-egui). Tables size to that page.

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

* #1381: the CLI uses the system region everywhere; help mentions WORDCRAFT_REGION

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:58:14 -04:00
1f2b2287b6 RTF import: keep section breaks and their page settings (#1378) (#1495)
* #1378: WIP

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

* #1378: pasted sections link their headers; section orientation wins over the document's

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

* #1378: Text from File clears inserted sections' headers too

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:44:02 -04:00
MexandBrandon Thomas 4cf13cd178 Fix Replace All bypassing content-control locks (#1488)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:43:43 -04:00
ff4e560e7d Ctrl+A stays on this page, and in a table grows cell, then row, then table, then the document (#1424) (#1478)
* Ctrl+A stays on this page, and in a table grows cell, then row, then table, then the document (#1424)

Fixes #1424

* Table: Option::map for the outer-cell selection (clippy::manual_map on Rust 1.99)

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:43:06 -04:00
47ae2d495d References: TOC entries carry their headings' list numbers (#1470)
Update Table took an entry's text from the heading's plain text, which has
no list label, so headings numbered through a multilevel list (1, 1.1)
lost their numbers in the TOC. The entries now walk the body's list
counters and put the label and the level's separator (tab, space or
nothing) before the text, as Word writes it.

Co-authored-by: dfgodvan <180286157+dfgodvan@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:21:03 -04:00
0128e117b3 Layout: table rows share their cell margins and split across pages like Word (#1177)
- Every cell in a row gets the row's largest top and bottom cell margin, so
  a cell without its own margins lines up with its neighbours.
- An exact row height covers the top border, top margin and text; the
  bottom margin comes on top. Dragging a row border, or drawing a line
  through an exact row, accounts for it.
- A row split across pages must keep its cells' bottom margin, border and
  last paragraph's space after on the first page, and widow/orphan control
  applies to the paragraphs in its cells. A row whose text fits but whose
  margin doesn't no longer moves to the next page whole.
- Each cell of a split row goes on at the top of the continuation (one
  cell's widow line no longer pushes its neighbours down), and the
  continuation is as tall as the cell needing the most room, and at least
  the rest of the row (a minimum row height holds over the pages).
- A nested table's cell the page bottom cuts through goes on from the cut
  with what is left of it, and the text after it stays below it.
- A row of an exact height never splits: it moves to the next page whole.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:06:24 -04:00
Marco Arnulfo 6e6cfba765 Fonts embedded in a .docx lay out and export its text (#1176)
* Fonts embedded in a .docx lay out and export its text (fixes #1171)

* Fonts: forget lookups made while an embedded family was missing (#1171)
2026-10-14 13:06:05 -04:00
DevCoderMax 5b9010f6ad Picture transparency/corrections/color are absolute from the original + transparency slider (#1175) 2026-10-14 13:05:50 -04:00
25ca4b0752 Full screen on F11 and View › Window › Full Screen (#980) (#1153)
* #980: full screen on F11 and View › Window › Full Screen

view.fullScreen toggles OS-level full screen (ViewportCommand::Fullscreen),
on F11 and ⌃⌘F (macOS); Escape leaves it when nothing else takes Escape.
The window's own full screen changes (WM, macOS button) keep the flag in
step. Disabled on the web (the browser's own F11). Next Field moves from
F11 to Word's other keys, Alt+F1 / Alt+Shift+F1 (Shift+F11 kept).
Ribbon button with a drawn icon, keytip, translations in all catalogs.

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

* #980: Escape collapses a selection before leaving full screen; F11 only off macOS

In full screen, Escape left it even when it had a selection to collapse
or a drag to cancel; it now leaves only when nothing else takes Escape.
The shortcut read Ctrl+Ctrl+F on Windows and Linux: macOS gets ⌃⌘F and
F11, others F11. Docs: Alt+F1 may be taken by GNOME/KDE; keyboard
counts re-measured from the registry (152 commands, 172 combinations).

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 Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:05:18 -04:00
Marco ArnulfoandBrandon Thomas 7d3767ba99 Zotero: text typed just after a citation goes outside its field (#1162)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 12:51:00 -04:00