* Tables: select, insert and move with the mouse; double-click a border to AutoFit
Like Word, on top of the border resizing:
- Selection areas: just above a column the pointer becomes a down arrow,
left of a row the row arrow, along a cell's left edge (inside it) the
cell arrow. A click selects the column, row or cell; a drag selects every
one up to the pointer. Selected cells are shaded as cells, empty ones too.
- Insert buttons: left of a row border and above a column border a "+"
shows with the border marked; a click adds a row or column there and puts
the caret in it.
- Move handle past the table's top-left corner: a click selects the table;
a drag shows a dashed outline and, on release, the table floats where it
is dropped with the text wrapping around it (Word's behaviour). One undo
step. Nested tables and tables outside the main text are only selected.
- A double-click on a column border fits the column left of it to its
contents (the right neighbour gives or takes the room; on the right edge
the table's width changes, up to the right margin).
- Table Layout > Select > Select Column (catalog 418/449).
Whole-cell selection: a column (or a rectangle of cells over several rows)
is not one run of the text, so selecting it makes a block of its cells'
paragraphs (`CellRange` on the existing `ColumnBlock`). Bold, alignment,
Delete, typing, copy and numbering act on those cells only (a numbered
column is one list); table commands (shading, merge, delete columns) take
the rectangle; Tab moves on without clearing it; Undo brings the selection
back. Cells of one row, or whole rows, stay an ordinary stream selection.
The format painter no longer clears a block's text (edit.pasteFormat was
handled like a paste). The mini toolbar stays hidden over whole-cell
selections and during these drags: pinned above the selection it covered
the column areas, the "+" buttons and the move handle.
Engine: crates/engine/src/cmd/table_mouse.rs - table.selectAt,
table.insertAt, table.moveDrag, table.fitColumn (page coordinates, so
agents and the control channel can do the same) and table.selectColumn;
hit testing reuses table_resize's drawn geometry, and borders keep priority
for resizing. 10 engine tests (areas, a column edited cell by cell,
numbering, rows/cells/drags, insert buttons, move, fit, format painter,
hostile params).
UI: crates/ui-egui/src/table_mouse.rs, wired into the existing canvas mouse
handler (no extra ui.interact areas, so clicks in a cell's text still place
the caret); kittest tests with real pointer events: column click, row drag,
"+" button, move-handle drag in one undo step, double-click fit.
New labels translated in all eleven catalogs. Progress docs updated: gaps,
ui-parity, target-app-parity, ROADMAP, parity checklist.
`cargo xtask ci` passes (fmt, clippy, test, assets, layers, wasm).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: a selected column's word count is its cells'; merged cells shade once
With whole cells selected (a column, or a rectangle that isn't one run of
the text) the status bar said "12 of 23 words": `Session::selected_text`
took everything between the selection's ends. It now takes each piece's
own text, a line each, for a column block too, so the status bar, Word
Count and the other callers see what is selected.
Since #669 a vertically merged cell is one box as tall as its rows, with a
`covered` box for each continuation row; shading selected cells skips
those, so a merged cell isn't shaded twice. `selected_paths` follows
main's `para_paths_in` (#418): a selection ending at the start of the next
paragraph doesn't take it.
- Test: a selected column's text is its three cells' and Word Count says
three words.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: double-click AutoFit and the "+" buttons work as in Word
Checked against Word (2019, Windows):
- A double-click on a column border fits the column left of it to its
contents and the columns right of it keep their widths, moving with the
border, so the table narrows or widens (up to the right margin). The
right neighbour used to give or take the difference.
- A "+" insert button selects the new row or column (the caret used to
go into it); typing then goes into its first cell. If the table can't
take another row or column, nothing changes.
- A double-click along a cell's left edge (the cell selection area)
selects its row.
- Tests: engine (fit keeps the columns on the right; insert selects the
new row or column, typing lands in its first cell) and kittest (real
double-click on a border; "+" selects the row; double-click by a cell's
edge selects the row).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: drags and Shift+arrows over cells select whole cells
An ordinary drag, Shift+click or Shift+arrow from one table cell into
another now selects the rectangle of whole cells between them, as Word
does, instead of the run of text between the two points: bold, alignment
and Delete leave the cells outside the rectangle alone (they already did
for Select Column and the table's selection areas). Between a cell and
text outside its table the selection takes whole rows, from the cell's
row out to the table's edge, and the text beyond. Extending again goes on
from where the selection really started and ends: dragging back into the
first cell selects its text again, Shift+Up after Shift+Down shrinks the
rectangle, and from a nested table's cell into another cell of the table
around it the outer table's cells are taken.
- Engine: `table::extend_selection` (the rule, down through nested
tables) and `Session::sel_ends` (where a widened selection really
runs); every caret command run with `extend` goes through it in
`column::dispatch`, so agents get the same with `caret.*`.
- UI: a text drag extends through `caret.set` (still straight to the
session, so nothing scrolls on every frame); the view follows the real
caret.
- Tests: engine (drag across cells and back, nested tables, Shift+Down
and Shift+Up through and past a table, Shift+click, from text into a
table) and a kittest with a real mouse drag.
Progress docs updated: gaps (the known gap from #637 is gone),
ui-parity, target-app-parity, ROADMAP. `cargo xtask ci` passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: rabis <340973947+rabis7575@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Tables: select, insert and move with the mouse; double-click a border to AutoFit
Like Word, on top of the border resizing:
- Selection areas: just above a column the pointer becomes a down arrow,
left of a row the row arrow, along a cell's left edge (inside it) the
cell arrow. A click selects the column, row or cell; a drag selects every
one up to the pointer. Selected cells are shaded as cells, empty ones too.
- Insert buttons: left of a row border and above a column border a "+"
shows with the border marked; a click adds a row or column there and puts
the caret in it.
- Move handle past the table's top-left corner: a click selects the table;
a drag shows a dashed outline and, on release, the table floats where it
is dropped with the text wrapping around it (Word's behaviour). One undo
step. Nested tables and tables outside the main text are only selected.
- A double-click on a column border fits the column left of it to its
contents (the right neighbour gives or takes the room; on the right edge
the table's width changes, up to the right margin).
- Table Layout > Select > Select Column (catalog 418/449).
Whole-cell selection: a column (or a rectangle of cells over several rows)
is not one run of the text, so selecting it makes a block of its cells'
paragraphs (`CellRange` on the existing `ColumnBlock`). Bold, alignment,
Delete, typing, copy and numbering act on those cells only (a numbered
column is one list); table commands (shading, merge, delete columns) take
the rectangle; Tab moves on without clearing it; Undo brings the selection
back. Cells of one row, or whole rows, stay an ordinary stream selection.
The format painter no longer clears a block's text (edit.pasteFormat was
handled like a paste). The mini toolbar stays hidden over whole-cell
selections and during these drags: pinned above the selection it covered
the column areas, the "+" buttons and the move handle.
Engine: crates/engine/src/cmd/table_mouse.rs - table.selectAt,
table.insertAt, table.moveDrag, table.fitColumn (page coordinates, so
agents and the control channel can do the same) and table.selectColumn;
hit testing reuses table_resize's drawn geometry, and borders keep priority
for resizing. 10 engine tests (areas, a column edited cell by cell,
numbering, rows/cells/drags, insert buttons, move, fit, format painter,
hostile params).
UI: crates/ui-egui/src/table_mouse.rs, wired into the existing canvas mouse
handler (no extra ui.interact areas, so clicks in a cell's text still place
the caret); kittest tests with real pointer events: column click, row drag,
"+" button, move-handle drag in one undo step, double-click fit.
New labels translated in all eleven catalogs. Progress docs updated: gaps,
ui-parity, target-app-parity, ROADMAP, parity checklist.
`cargo xtask ci` passes (fmt, clippy, test, assets, layers, wasm).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: a selected column's word count is its cells'; merged cells shade once
With whole cells selected (a column, or a rectangle that isn't one run of
the text) the status bar said "12 of 23 words": `Session::selected_text`
took everything between the selection's ends. It now takes each piece's
own text, a line each, for a column block too, so the status bar, Word
Count and the other callers see what is selected.
Since #669 a vertically merged cell is one box as tall as its rows, with a
`covered` box for each continuation row; shading selected cells skips
those, so a merged cell isn't shaded twice. `selected_paths` follows
main's `para_paths_in` (#418): a selection ending at the start of the next
paragraph doesn't take it.
- Test: a selected column's text is its three cells' and Word Count says
three words.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: double-click AutoFit and the "+" buttons work as in Word
Checked against Word (2019, Windows):
- A double-click on a column border fits the column left of it to its
contents and the columns right of it keep their widths, moving with the
border, so the table narrows or widens (up to the right margin). The
right neighbour used to give or take the difference.
- A "+" insert button selects the new row or column (the caret used to
go into it); typing then goes into its first cell. If the table can't
take another row or column, nothing changes.
- A double-click along a cell's left edge (the cell selection area)
selects its row.
- Tests: engine (fit keeps the columns on the right; insert selects the
new row or column, typing lands in its first cell) and kittest (real
double-click on a border; "+" selects the row; double-click by a cell's
edge selects the row).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: the right-click menu offers the table's commands
Right-clicking a table cell now offers what Word's table menu does, all
through existing commands: Insert (rows above/below, columns left/right),
Delete (cells, rows, columns, table), Select (cell, column, row, table),
Merge Cells, Split Cells, Distribute Rows, Distribute Columns, AutoFit
(contents, window, fixed column width), Text Direction, Cell Alignment
(the nine alignments) and Table Properties… (opens the dialog, as the
ribbon's button does).
A right-click beside selected cells now moves the caret there, as in
Word: with a column selected, the cells between its first and last cell
in the text (the other columns of those rows) counted as "inside the
selection", so the selection stayed and the menu acted on the column
instead of the cell clicked. A right-click on a selected cell keeps the
selection, so Cell Alignment, Distribute and the rest act on it.
- UI only (crates/ui-egui/src/canvas.rs): the table part of
`context_menu()` and a small check before the right-click caret logic
(separate from it, so it doesn't overlap #521's change to that block).
Table Layout's nine cell alignments are one shared list (`CELL_ALIGN`
in ribbon.rs) for the ribbon grid and the menu.
- Two new labels, "Cell Alignment" and "Table Properties…", translated
in all eleven catalogs; the rest reuse the ribbon's labels.
- Tests: kittest with real pointer events: right-click in a cell, the
menu's table entries, Select > Select Column; with a column selected,
a right-click on it keeps it and Cell Alignment > Align Bottom Right
aligns just the column (one undo step), and a right-click beside it
drops it.
Progress docs updated: ui-parity (context menus 55% -> 60%), gaps,
target-app-parity, ROADMAP. No new commands, so the catalog is unchanged.
`cargo xtask ci` passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: the right-click menu's table items follow the selection, as in Word
Checked against Word (2019, Windows): Word's table items in the context
menu depend on what is selected, and there is no Select or Cell
Alignment submenu among them.
- Caret in a cell: Insert, Delete Cells, Split Cells, Text Direction,
Table Properties…
- Several cells: Insert, Delete Cells, Merge Cells, Text Direction,
Table Properties…
- Whole columns: Insert, Delete Columns, Merge Cells, Distribute Rows,
Distribute Columns, Text Direction, Table Properties… (whole rows the
same with Delete Rows; not checked in Word).
- The whole table: Insert, Delete Table, Distribute Rows and Columns,
AutoFit, Text Direction, Insert Caption…, Table Properties…
Insert lists the columns first, as Word does. Select and Cell Alignment
are gone, and with them the shared CELL_ALIGN constant in the ribbon;
"Insert Caption…" replaces "Cell Alignment" in all fifteen catalogs.
Word's Border Styles and Insert Cells… aren't here yet.
- Tests (kittest, real right-clicks): the menu with the caret in a cell
and with the whole table; a selected column keeps its column menu and
Text Direction turns only it; beside it the menu is a cell's again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: rabis <340973947+rabis7575@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The right-click menu could only be opened with the mouse. Shift+F10,
Windows' keyboard way to the context menu (the Menu key's equivalent),
now opens the same menu just below the caret, with the spelling
suggestions and synonyms for the word there; Escape closes it.
ui.contextMenu opens it for scripts and agents.
The Menu key itself still does nothing: egui-winit drops that key
before the app sees it, so it needs an upstream change (or a winit
hook) to reach the same path.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* R17: no automatic paragraph spacing between list items
Word drops automatic (HTML-style) space before and after between two list
items, of one list or two, at any level; the first item keeps its space
before and the last its space after, and measured spacing stays. Measured in
Word for Windows: 16 synthetic cases. NIH's bulleted IND report items now
touch as in Word.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ROADMAP: keep the progress log separator under its header
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found by rendering 12 US government Word documents (NIH, EPA, MCC) in
Word for Windows and WordCraft and comparing the pages side by side,
then pinning each rule down with synthetic documents measured in Word.
- Kerning: the shaper kerned every run, so WordCraft's lines held more
text than Word's and broke differently. Word kerns only a run with
Kerning for fonts (w:kern) at or above that size; no w:kern and
w:kern="0" are unkerned. New documents keep kerning from 1 pt, like
Word's Normal template.
- Automatic paragraph spacing (w:beforeAutospacing/w:afterAutospacing,
common in text pasted from web pages) was ignored. It is 14 pt at
any size, none before the document's or a cell's first paragraph,
none after a cell's last, and the larger of two neighbouring spaces
counts. Read, written back, and settable through para.spacing
(beforeAuto/afterAuto); a number, Ctrl+0 and Remove Space After
replace it as in Word.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Tables: select, insert and move with the mouse; double-click a border to AutoFit
Like Word, on top of the border resizing:
- Selection areas: just above a column the pointer becomes a down arrow,
left of a row the row arrow, along a cell's left edge (inside it) the
cell arrow. A click selects the column, row or cell; a drag selects every
one up to the pointer. Selected cells are shaded as cells, empty ones too.
- Insert buttons: left of a row border and above a column border a "+"
shows with the border marked; a click adds a row or column there and puts
the caret in it.
- Move handle past the table's top-left corner: a click selects the table;
a drag shows a dashed outline and, on release, the table floats where it
is dropped with the text wrapping around it (Word's behaviour). One undo
step. Nested tables and tables outside the main text are only selected.
- A double-click on a column border fits the column left of it to its
contents (the right neighbour gives or takes the room; on the right edge
the table's width changes, up to the right margin).
- Table Layout > Select > Select Column (catalog 418/449).
Whole-cell selection: a column (or a rectangle of cells over several rows)
is not one run of the text, so selecting it makes a block of its cells'
paragraphs (`CellRange` on the existing `ColumnBlock`). Bold, alignment,
Delete, typing, copy and numbering act on those cells only (a numbered
column is one list); table commands (shading, merge, delete columns) take
the rectangle; Tab moves on without clearing it; Undo brings the selection
back. Cells of one row, or whole rows, stay an ordinary stream selection.
The format painter no longer clears a block's text (edit.pasteFormat was
handled like a paste). The mini toolbar stays hidden over whole-cell
selections and during these drags: pinned above the selection it covered
the column areas, the "+" buttons and the move handle.
Engine: crates/engine/src/cmd/table_mouse.rs - table.selectAt,
table.insertAt, table.moveDrag, table.fitColumn (page coordinates, so
agents and the control channel can do the same) and table.selectColumn;
hit testing reuses table_resize's drawn geometry, and borders keep priority
for resizing. 10 engine tests (areas, a column edited cell by cell,
numbering, rows/cells/drags, insert buttons, move, fit, format painter,
hostile params).
UI: crates/ui-egui/src/table_mouse.rs, wired into the existing canvas mouse
handler (no extra ui.interact areas, so clicks in a cell's text still place
the caret); kittest tests with real pointer events: column click, row drag,
"+" button, move-handle drag in one undo step, double-click fit.
New labels translated in all eleven catalogs. Progress docs updated: gaps,
ui-parity, target-app-parity, ROADMAP, parity checklist.
`cargo xtask ci` passes (fmt, clippy, test, assets, layers, wasm).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: a selected column's word count is its cells'; merged cells shade once
With whole cells selected (a column, or a rectangle that isn't one run of
the text) the status bar said "12 of 23 words": `Session::selected_text`
took everything between the selection's ends. It now takes each piece's
own text, a line each, for a column block too, so the status bar, Word
Count and the other callers see what is selected.
Since #669 a vertically merged cell is one box as tall as its rows, with a
`covered` box for each continuation row; shading selected cells skips
those, so a merged cell isn't shaded twice. `selected_paths` follows
main's `para_paths_in` (#418): a selection ending at the start of the next
paragraph doesn't take it.
- Test: a selected column's text is its three cells' and Word Count says
three words.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: double-click AutoFit and the "+" buttons work as in Word
Checked against Word (2019, Windows):
- A double-click on a column border fits the column left of it to its
contents and the columns right of it keep their widths, moving with the
border, so the table narrows or widens (up to the right margin). The
right neighbour used to give or take the difference.
- A "+" insert button selects the new row or column (the caret used to
go into it); typing then goes into its first cell. If the table can't
take another row or column, nothing changes.
- A double-click along a cell's left edge (the cell selection area)
selects its row.
- Tests: engine (fit keeps the columns on the right; insert selects the
new row or column, typing lands in its first cell) and kittest (real
double-click on a border; "+" selects the row; double-click by a cell's
edge selects the row).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: rabis <340973947+rabis7575@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Layout: space before at the top of a page after page breaks, as in Word
Found comparing Microsoft's specification documents with Word for
Windows, then measured with 32 synthetic documents in compatibility
modes 12 (Word 2007) and 15 (Word 2013+):
- A paragraph that ends with a page break keeps its mark with the
break: WordCraft put an empty line at the top of the next page.
- The paragraph after it, first on the new page, gets no space before.
- A paragraph that starts with a page break starts on the new page
with its space before there (WordCraft put the space on the old page).
- A page break before keeps the space before in modes up to 14 and
drops it in mode 15.
- Natural breaks, section breaks and the first page were already right.
All 32 cases now land where Word puts them (within 0.3 pt), and the
caret still reaches a paragraph mark kept with its break.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Layout: after a page break before or a section break, the space after above counts
Measured in Word for Windows (10 more cases): at the top of a page after a page break before
(modes up to 14) or a next-page section break, the previous paragraph's space after comes off the
space before (24 pt before after 10 pt after leaves 14 pt; after 30 pt leaves none). This put the
numbered Heading 1s of Microsoft's specification documents 9 pt too low. All 42 page-top cases now
match Word.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found in NIH's SARP form compared with Word for Windows: the form's
second section has a narrow label column (67 pt) and a wide text column
(438 pt), and each answer starts with a column break into the wide one.
Before: the paragraph's lines were broken once, for the column it
started in, so the text after the break ran a word or two wide down the
wide column. After: when a column break lands the rest of a paragraph
in a column of another width, that rest is broken again for it, as in
Word. Equal columns are unaffected (nothing is laid out twice).
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Word positions a floating object "relative to paragraph" (and to its line)
from the paragraph's start above its space before, below the previous
paragraph's space after. WordCraft measured from the first line, so floats
in spaced paragraphs sat lower by the space before. Measured in Word for
Windows: 10 synthetic cases.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #701: Undo history list beside the Quick Access Toolbar's Undo
A drop-down arrow next to Undo lists recent steps, newest first; hovering a
row highlights it and every newer step, the footer reads "Undo N Actions"
(Cancel when none is highlighted), and a click undoes them all at once.
Engine: Session::undo_history (newest first, capped at UNDO_LIST_MAX = 100)
and Session::undo_steps; edit.undoTo {"count": n} (0, negative and non-number
counts rejected, counts past the depth clamped, stops at protection) and the
read-only edit.undoHistory for CLI and MCP. Catalog: Undo List.
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>
* #701: Bulgarian for the Undo history strings; drop a duplicate Turkish entry
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>
* R21: an empty continuous section mark takes no room
An empty paragraph that only carries a section break before a continuous
section takes no room in Word: the text after it follows right on, and
columns balanced above it don't count it. A break on a paragraph with text
keeps its line. Measured in Word for Windows: 10 synthetic cases. The SARP
form's fields drifted a line lower at each of its continuous breaks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ROADMAP: keep the progress log separator under its header
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #766: start on File › Home (Start screen) when launched with nothing to open
Desktop and web start on Backstage Home (Blank document, templates, Recent)
when no file, --sample or --control is given; Esc/back leaves the empty blank
document. File › Options › General › Start-up checkbox (ui.startScreen, kept
between runs, default on). WordApp::new is unchanged; hosts opt in via
WordApp::start_screen. ui_shot: {"empty":true,"startScreen":true}.
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>
* #766: Bulgarian for the Start screen setting
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>
Word moves a "keep with next" paragraph to the next page only when it would
fit on this one by itself; one that doesn't fit anyway breaks as usual, its
last lines going over with the next paragraph (with keep lines together it
moves whole). A chain of kept paragraphs moves together. Measured in Word
for Windows: 20 synthetic cases. NIH's five-line kept paragraph no longer
leaves a third of a page empty.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #835: AutoCorrect's English replacement list applies only to English text
Built-in word entries (teh, i, dont…) are replaced only when the word's
language (run w:lang, style, document default) is English or unset;
symbols, fractions and user entries apply in every language. Polish,
Croatian, Bosnian and Serbian "i" ("and") is no longer turned into "I".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #835 review: untagged text follows the editing language; English as proofing decides it
- Text with no language at all (a .docx without w:lang) is in the editing language, so with a
Polish interface `Ala i kot ` keeps its `i` there too.
- `wordcraft_proof::ProofLang::from_tag` decides what is English (it also takes `eng`), so
AutoCorrect and proofing agree; the local `is_english` is gone.
- Tests through `set_editing_language` (the document-default path) and with no language at all.
- docs/typing-parity.md: the Croatian/Bosnian/Serbian case needs the text tagged, as those
interfaces set no editing language yet.
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>
A numbered paragraph style's own indents (and those of styles based on it)
now beat the list level's, its base styles' don't, and the paragraph's own
list beats every style. A paragraph that switches its list off (numId 0)
drops the styles' indents, as Word does; the first Backspace in a List
Paragraph item makes the item's indent the paragraph's own so the text stays
put. Measured in Word for Windows: 27 synthetic cases, all match. NIH's
Heading 3s sit at their 0.9" and its "sample text" paragraphs at the margin.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found in NIH's monitoring-plan template compared with Word for Windows:
its footer puts the page number in a text frame (w:framePr: centred,
text wrapping around it), and Word draws "NCCIH DSMP Template" ending
just left of the number on the same line.
Before: frames other than drop caps were ignored on import, so the
number took a line of its own above the footer text (making every page
a line shorter), and the frame was lost on save.
After: a paragraph's text frame is read (size, anchors, alignment,
offsets, wrap, distances) and written back. In headers, footers, table
cells and text boxes it is placed like a floating object, sized to its
text unless it has a width, and the text after it wraps around it; the
DSMP footer now matches Word's. Frames in the body text still lay out
in line.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Read and write w:hideMark on table cells, and lay it out like Word: a cell's
empty last paragraph takes no room, spacing and all, so a row of only such
empty cells is just its borders; an ordinary empty cell keeps the row a line
tall. Measured in Word for Windows: 9 synthetic cases. NIH's Table A no
longer shows a blank row.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #836, #837: language-aware sorting and locale numbers in tables
Sorts (Table > Sort, Home > Sort, index and its letter headings, bibliography,
Table of Authorities) use the text language's alphabet via a small pure-Rust
collation (crates/doc/src/collate.rs). Table sort and formulas read numbers
with the cell language's decimal/group separators and write formula results
with its decimal separator (crates/doc/src/locale_number.rs). Sort comparators
are now total orders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #836, #837 review: ordinals, leading zeros, percentages; untagged text; decomposed letters
- parse_number: a trailing separator with no digits is the whole number (`1.`, a
numbered "Lp." column, sorts as 1); a first group of `0` is never grouping
(`0.125` in Polish is 0.125, not 125); a trailing `%` is ignored (`12 %` is 12);
an en dash is a minus.
- Text with no language follows the document default, else the editing language
(`cmd::default_lang`), as AutoCorrect does: a Polish interface reads `3,50` as
3.5 in an untagged document. Table sort and formulas, paragraph sort, the
index, the bibliography and the Table of Authorities use it.
- Collation composes a letter and a following combining mark first (table from
Unicode NFC), so a decomposed `ż` (z + U+0307) sorts as `ż`.
- Tests: every Polish letter and its index heading, decomposed letters, the
ordinal column, an untagged Polish SUM, Polish bibliography order.
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>
Word sizes a list item's first line with the label's ascent when it is
taller, but keeps the text's descent below the baseline: a Symbol bullet's
deeper descent (and a Courier New label's) doesn't count. Measured in Word
for Windows: 6 synthetic cases. NIH's bulleted items were a fifth of a point
taller each, enough to push lines over a page bottom.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found in NIH's monitoring-plan template, compared with Word for Windows.
Before: a w:numPr that gives a level (w:ilvl) but no list (w:numId) was
dropped. Outline-numbered templates do this all the time: Heading 3 is
based on Heading 2 (which names the list) and only says "level 3", so
every Heading 3 numbered as a Heading 2 (6.15 where Word shows 6.4.5).
After: such a level takes the list of the nearest style in its based-on
chain that names one (ECMA-376 17.3.1.19), in styles and in paragraphs;
with no list anywhere in the chain there is no numbering. Saved files
carry an ordinary list reference.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Word 365 cover page lost its look. The missing fills came from
theme-coloured shapes (scheme colours and wps:style fill references),
which the reporter's 0.5.0 build didn't read; #611 fixed that on main.
Two causes were still open on main:
- A freeform (a:custGeom) inside a wpg group was drawn as a rectangle.
read_wsp now reads custom geometry, so group members get it as well.
- Text boxes ignored wps:bodyPr lIns/tIns/rIns/bIns and a:spAutoFit.
Layout always used Word's default margins, so a title box with no
margins that was just tall enough for its text lost its second line.
TextBody now carries the box's own margins and auto_fit. Layout uses
those margins and doesn't cut the text of a box that resizes to fit.
Both are written back on save.
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>
Found by comparing EPA's .doc templates with Word for Windows.
Before: custom tab stops in .doc files were skipped, so centred titles,
right-aligned dates and dot leaders set with tabs lost their alignment;
and a section mark (0x0C) at a section's end became a page break inside
a paragraph, so the file's sections (continuous breaks, footers) were
lost and every section break started a new page.
After: sprmPChgTabsPapx and sprmPChgTabs are read in styles and
paragraphs (removed stops clear inherited ones; each added stop keeps
its alignment and leader), and sprmPChgTabs' one-byte size is decoded
per [MS-DOC] (it was read as two bytes, swallowing the sprms after it).
A section mark at a section's end now ends that section's last
paragraph, so the section's properties and footers are kept. Both EPA
.doc templates in the comparison set now have Word's page count.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found comparing Microsoft's own specification documents with Word for
Windows: their tables of contents were a third taller in WordCraft
(13.4 pt per entry against Word's 10.9). Each entry's 9 pt text has tab
runs in an 11 pt font, and WordCraft sized the line by them.
Measured in Word with 13 synthetic documents: 11-20 pt tabs, spaces and
ideographic spaces next to 9 pt text leave the 9 pt line as it is,
wherever they are on the line; a line of only such whitespace takes the
paragraph mark's height; a big letter or no-break space does grow the
line. WordCraft now skips spaces and tabs when it sizes a line, and all
13 cases match.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Word for Windows' Alt+Q opens the "Tell me" box; in WordCraft it did nothing.
Interface commands (ui.*) aren't in the engine's keymap, so keys.rs gets a
small table of interface shortcuts, checked before the keymap from the page
and from the rest of the window. Alt+Q opens command search
(ui.dialog {"name": "commands"}) and its key text isn't typed. Not on macOS,
where Option+Q types a character.
Also fixes the ROADMAP progress log, whose separator row sat below two rows.
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>
View › Show stacked four check boxes in a column made for three: since
Hide White Space (#645) joined Ruler, Gridlines and Navigation Pane, the
fourth row was drawn over the "Show" group label. It now sits in a second
column.
A new test draws every tab in every interface language and fails on any
group taller than the ribbon's content area (CONTENT_H, 66 pt). It found:
- Developer › Controls, 75 pt: 3 pt after each row of small buttons. The
rows now fill the height with no gaps, like every other stack.
- Layout › Paragraph (#654), which panicked in debug builds: `add_space`
between grid cells trips egui's "add_space makes no sense in a grid
layout" assertion. The 8 pt gap before the Spacing column now sits
inside those cells, so it looks the same. The group was also 68.5 pt:
1.5 pt between rows instead of 3 fits it.
- widgets.rs: under cfg(test), group_titled records any group whose
content is taller than CONTENT_H.
- ribbon.rs: ribbon_groups_fit_above_their_labels.
- ROADMAP progress log and docs/ui-parity.md revision history.
- contributors/people.toml: my own entry.
Co-authored-by: Manuel Carmona <328347120+manucarcor@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Found comparing Microsoft's own specification documents with Word for
Windows: their table of contents was blue and underlined in WordCraft
and plain in Word.
Measured with nine synthetic documents in Word: a run with the
Hyperlink character style, inside a hyperlink, inside a TOC field's
result, is drawn plain (with or without the TOC's \h switch). A
Hyperlink-styled run outside a hyperlink, another character style,
direct formatting, or a hyperlink in another field keep their look.
The import now drops the Hyperlink style from exactly those runs (the
links stay), so all nine cases render as Word does.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The painted title uses a Latin-only face, so names in other scripts were shown as question marks. A covering fallback face is now loaded for the caption.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Observed in Word for Windows (Microsoft 365): pressing Alt badges the
tabs File F, Home H, Insert N, Design G, Layout P, References S,
Mailings M, Review R, View W, Help Y, and the Search box Q.
Before, WordCraft used Design L, References R, Review S and View V,
and File and Help had no letter, so Alt, W or Alt, R went to the wrong
tab for anyone used to Word. Now the letters match; Alt, F opens the
Backstage and ends keytips.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(windows): start 32-bit builds on OpenGL so launch no longer exits
DirectX 12 graphics pipeline creation fails on 32-bit Windows, so the app quit before the window appeared.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Add @odedigo to the contributor names list
The About window credits this username; no real name or display name was chosen.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #838: new documents follow the system region (A4/cm/decimal comma outside the US)
- geom: Region (from a locale tag; CLDR Letter and US-measurement territories,
decimal separator), Unit::format_with; parse_measure takes either separator
and a Unicode minus.
- engine: Session.region (plain value, en-US headless), Session::units()
(explicit Measurement units choice wins, else the region's),
blank_document(); file.new blank uses the region's paper; file.options
takes "region" and units "auto" and reports unitsFromRegion/region.
Prefs.units is now Option and saved as measurementUnits (old saved values
were the inch default, not a choice).
- ui: region from WORDCRAFT_REGION, LC_PAPER/LC_MEASUREMENT/LC_NUMERIC, the
platform locale (browser on the web) and Windows' home location; length
fields show and read the region's decimal separator (MeasureExt); Size
menu lists sizes in the measurement unit.
- Tests: engine new_documents_follow_the_region, geom region/format/parse,
ui length_fields_follow_the_region. Docs: localization parity, roadmap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #857: keep a measurement unit chosen before the rename
Settings saved before #838 kept the unit under `editing.units`, defaulting to inches. On load,
a value there other than inches was the user's own choice, so it now carries over to
`measurementUnits` (unless that key is already present); inches, junk and non-strings follow
the region. Test: `a_unit_chosen_before_regions_survives_the_rename`.
Also merged main (ROADMAP.md, engine lib.rs re-exports: `blank_document` and main's
`set_system_locale`). The Windows region code (`GlobalizationPreferences::HomeGeographicRegion`)
was cross-checked with clippy -D warnings for x86_64-pc-windows-gnu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #838 review: saved region choice, Unix conventions apart, the browser's locale
- File › Options › Advanced › Region: Automatic (the system's) or a chosen region, saved with
the preferences (`Prefs::region`; `file.options {"region": "pl-PL" | "auto"}`). The session
keeps the platform's region as `system_region`; `Session::region()` is the one in effect.
Measurement units gains an Automatic entry (follows the region).
- Unix: numbers follow LC_NUMERIC (else LANG), paper LC_PAPER and measurement LC_MEASUREMENT,
each on its own (`Region::with_paper_and_measurement`); WORDCRAFT_REGION and LC_ALL still set
all three.
- Web: sys-locale's `js` feature, so the browser's language reaches the region (and the
interface language, as its comment already said).
- Runs without preferences (WORDCRAFT_NO_PREFS) stay en-US unless WORDCRAFT_REGION is set.
- docs/localization-parity.md: where the region comes from, the Windows home location vs. Word's
regional format, and where the decimal separator comes from.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #838 review: rustfmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #838: rustfmt after merging main
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>
---------
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>
* #687, #833: paste keeps other programs' formatting; Wayland clipboard pictures
- Paste (Ctrl+V, Home > Paste) reads the system clipboard's HTML, else RTF,
and keeps its formatting; our own copy still wins while the clipboard holds it.
Windows CF_HTML headers are understood; oversized or empty payloads fall back
to the text.
- Merge Formatting inserts the content in the destination's font and paragraph,
keeping bold, italic, underline and strikethrough (it did nothing before).
- Paste Special lists what the system clipboard holds (HTML, RTF, picture,
text); Mod+Alt+V opens it even with only a picture there.
- Services gain clipboard_html, clipboard_rtf and clipboard_formats hooks; the
host reads RTF on Windows (clipboard-win) and Wayland (wl-clipboard-rs).
- Linux: arboard's wayland-data-control feature, so pictures copied in a
browser or Spectacle reach a native Wayland window (KWin only syncs to
Xwayland while an X11 window has focus); other picture types are read
from Wayland directly. A paste that finds nothing usable names the formats
on the clipboard, or why its picture couldn't be read, instead of "empty".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #847: translate the Paste Special picture strings; Windows/macOS unused import
- `Picture` and `Pastes the clipboard's picture.` translated in all 15 interface catalogs.
- clipboard.rs: `MAX_FILE_BYTES` is used only by the Linux/Wayland module, so it is named
there; the top-level import was unused (a warning, and a clippy -D warnings error) on Windows
and macOS.
- Merged main (ROADMAP.md, docs/gaps.md, docs/file-format-parity.md, main.rs services: keeps
both the clipboard hooks and the default-app hook from #295). Cargo.lock adds only the
wl-clipboard-rs set.
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>
---------
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>
* ODT import reads page size, orientation and margins from styles.xml (#810)
* Review fixes for #829: clamp ODT page size and margins
Page width/height from styles.xml are kept to 36..=1584 pt (the DOCX
reader's floor, Word's 22-inch maximum) and each margin is capped at the
page's longer side, like w:pgMar. Adds a hostile-values test and collapses
a nested if for clippy.
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>
* i18n: add Bulgarian (bg) interface translation
A 2,386-entry Bulgarian catalog: every key of the reference catalog, the equation gallery's
labels and everything the interface audit finds. Written from the meaning of the English
labels in ordinary Bulgarian computing vocabulary; no other product's localisation and no other
catalog in this repository was used. Counts use count-neutral wording ("Думи: 21").
- bg, bg-BG, bg_BG.UTF-8 select it automatically; "Български" in Options and
ui.language with {"value":"bg"}.
- The interface audit checks Bulgarian coverage the way it checks Russian.
- Tests: locales and saved preference, catalog coverage and key translations, count wording,
and the bundled Inter/JetBrains Mono faces covering the Bulgarian letters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #825: Bulgarian built-in style names
Adds the built-in style names Russian translates (Normal Table, Default
Paragraph Font, Annotation Text/Reference/Subject, Envelope Address/Return,
Index Heading, Index {n}, Line Number, Normal Indent, Table of Figures, TOA
Heading, TOC {n}) and Good afternoon; updates the entry count in the docs.
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>
* M15: Polish interface, and Polish spelling, grammar and hyphenation
Interface
- Polish is the tenth interface language ("Polski", pl.tsv, 1,045 entries);
pl, pl-PL and pl_PL.UTF-8 system locales select it automatically.
- Right-click menu labels now go through translation in every language.
- Review > Language is a menu (English US/UK, Polski, Do not check spelling
or grammar); the status bar shows the language at the caret.
- With a Polish interface, new blank documents and opened .txt/.md files
default to pl-PL.
Proofing
- Each run is checked in its own language (w:lang, resolved through
styles): squiggles, F7, review.issues, suggestions and hyphenation.
Text in languages without proofing tools is no longer flagged as English.
- Polish spelling from the SJP.PL Hunspell dictionary (Apache-2.0 option),
read by a new Rust affix engine (crates/proof/src/affix.rs); 875 KB asset,
~4.5M word forms. Regenerate with the pl_dictionary example.
- Polish suggestions: case, missing diacritics, rz/ż, ó/u, ch/h confusions,
missing spaces (napewno -> na pewno), edit distance.
- Conservative Polish grammar rules: repeated words, spacing, doubled
punctuation, sentence capitals, missing comma before że/który/ponieważ/
aby/żeby/jeśli…, we/ze forms.
- Polish hyphenation with the hyph-utf8 hyph-pl.tex patterns (MIT option);
pattern keys widened to 20 letters, TeX exceptions parsed; the heuristic
no longer treats ł, ż, ś… as vowels.
review.language validates and normalizes tags, can set noProof alone and
the document default; format.state reports lang and noProof.
Tests for every part, docs and ATTRIBUTION.md/NOTICE updated;
cargo xtask ci passes (fmt, clippy, test, assets, layers, wasm).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Layout: rustfmt the test module order left unformatted by #391
cargo fmt --check failed on main after #391 added mod tests_grid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #346: read Polish spelling from the installed Hunspell dictionary
- Polish spelling uses the Hunspell pl_PL dictionary installed on the
system (OS packages, Homebrew, LibreOffice, ~/.local/share/hunspell, or
WORDCRAFT_DICTIONARIES) instead of a bundled 870 KB SJP.PL file;
without one, Polish text gets no spelling marks, like Word without the
language's proofing tools. .aff/.dic are size-capped and decoded from
UTF-8 or ISO 8859-2 (crates/proof/src/hunspell.rs).
- The dictionary can be replaced at runtime (install_dictionary) or
re-searched (reload_dictionary); an empty one accepts every word.
- Tests use small synthetic dictionaries written for them, so they don't
depend on what the machine has installed.
- The bundled pl.dic, its ATTRIBUTION/NOTICE entries and the generator
example are removed; docs describe the installed dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #100: affix — accept FLAG UTF-8 and DEFAULT flag modes
Per-character flag modes are what the reader already parses; only the
multi-character encodings (long, num) stay errors. VERO pt_BR declares
FLAG UTF-8.
* #100: affix — widen flag masks to u128 (128 flag capacity)
VERO pt_BR needs 106 distinct flags; the u64 bitmask capped at 64.
Serialization is unaffected: WCAFF1 stores flag characters and
re-derives the masks on load.
* #100: affix — support FORBIDDENWORD
VERO pt_BR marks spellings it must reject with a forbidden flag (ý).
Stems carrying it are listed but never correct and never inflect.
* #100: affix — continuation classes in check
SFX A 0 able/B: the form derived by A carries flag B, licensing a
further suffix of class B (workable -> workables) while the bare stem
cannot take one. form_has_flag walks the chain depth-bounded
(MAX_CHAIN = 3); with no continuations declared it degenerates to the
stem lookup, so existing dictionaries are unaffected. VERO pt_BR uses
26 continuation rules.
* #100: affix — continuation classes in forms
Enumerate chained suffix forms (workable -> workables) under the same
MAX_CHAIN bound the checker walks, so every form the checker accepts
is listed and vice versa.
* #100: affix — round-trip continuations and FORBIDDENWORD through WCAFF1
The compact format keeps its WCAFF1 magic: continuation classes ride
on the rule's appended field (as in the .aff source) and the forbidden
flag becomes a FORBIDDENWORD line. Old files read as before.
* #100: affix — strip a UTF-8 BOM from .aff/.dic input
VERO's files open with a BOM; glued to the count line it parsed as a
stem (one garbage entry per load) and to SET it hid the directive.
* #782: affix — the module doc still rejected what the code now reads
Continuation classes and FORBIDDENWORD are supported; the header said
Rejected for both.
* #100: pt — Portuguese spelling, following the Polish module
ProofLang::Pt resolves pt/pt-BR/pt-PT/por tags; the pt module reads the
installed Hunspell pt_BR dictionary (VERO) through the affix engine —
FLAG UTF-8 flags, continuation classes and FORBIDDENWORD spellings all
exercised by the synthetic test dictionary. Hyphenated compounds are
checked part by part (guarda-roupa, ex-marido); without a dictionary
installed, Portuguese text gets no marks. Spelling message in
Portuguese; suggestions and grammar land next.
* #100: pt — Portuguese suggestions
Rounds of rising cost, driven by the dictionary's own tables: missing
accents from MAP (coraçao -> coração, saude -> saúde), the REP
confusions (seje -> seja, excessão -> exceção), a missing space after a
short function word (nãoé -> não é, dagua -> da água), one typo over
TRY, and an accent-folding stem scan — the Polish rounds, retuned for
Portuguese.
* #100: pt — Portuguese grammar flags
Conservative rules with Portuguese messages: repeated words (except
the emphatic doubles Portuguese allows), space before punctuation and
double spaces, sentence capitals knowing the Portuguese abbreviations
and dialogue dashes, and doubled punctuation. No comma rule: que opens
a clause only sometimes, so a wrong flag would cost more than a missed
one.
* #100: pt — Portuguese hyphenation
hyph-utf8's hyph-pt.tex bundled unmodified (BSD 3-clause, ATTRIBUTION
row with source and hash): 426 Liang patterns and 2 exception words;
falls back to the generic heuristic outside their reach.
* #100: pt — Review › Language offers Português (Brasil), and a pt-br interface writes pt-BR
The menu's fourth entry sets the run language; the status bar names
Portuguese; editing_lang is now allowed for pt-br (wordcraft-proof
proofs it), so new documents under a Portuguese interface are
Portuguese by default.
* #100: pt — engine test: Portuguese proofing through the review commands
review.language pt-BR normalizes the tag, review.issues reports
Portuguese spelling and grammar with the lang field, F7 and Change
apply a suggestion, Add to Dictionary works for Portuguese.
* #100: pt — Portuguese in the mixed-language proofing tests and docs
check_text proofs a Portuguese span with the Portuguese message; the
never-panic loops and the module docs cover the third language.
* #100: docs — Portuguese proofing in the gap, parity and roadmap docs
Gap 8 narrowed (English, Polish and Portuguese), localization parity
gains a Portuguese row and a revision entry, target-app-parity's
Proofing row moves 40% -> 42%, and the ROADMAP progress log records
what landed.
* #100: hunspell — LibreOffice extension folders follow the language
The search paths hardcoded LibreOffice's dict-pl; a pt_BR user whose
only dictionary is LibreOffice's dict-pt-BR extension — on Windows the
only realistic on-disk source — got silently no spelling. Both the
primary-language folder and the dashed full name are now tried, so
Polish keeps dict-pl and Portuguese finds dict-pt-BR.
* #100: pt — review fixes
Suggestion ties break toward the change nearest the word's start
(saúde before saudê, proven against a fixture that has both); the
Language menu's label is the English source "Portuguese (Brazil)" with
translations in all twelve catalogs (as Polish is); the attribution
counts pt.pat's 2 exception words; the pt fixture drops a duplicate
stem; docs/roadmap.md catches up with the landing; the throwaway spike
is gone.
* Review fixes for #782: two-suffix chain limit, module doc
- MAX_CHAIN = 2 counts suffixes in a row, matching Hunspell's two-suffix limit
(the old bound of 3 allowed up to four); test pins that a third is rejected.
- Module doc: say probes allocate small scratch strings instead of claiming none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #797: NOTICE for pt.pat, no indexing in the LibreOffice folder helper
- NOTICE reproduces hyph-pt.tex's BSD 3-Clause copyright notice, conditions and
disclaimer, which the licence requires for binary redistributions.
- hunspell::lo_ext_dirs builds its list without indexing (never-crash rule).
- Catalogs: main already translates "Portuguese (Brazil)" in every language, so the
duplicate rows the branch added are dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: witkowski01 <konradwitkowski01@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Correct TWo INitial CApitals: a finished word of 3+ letters with two
leading capitals and the rest lowercase loses its second capital
("WOrd" -> "Word"), before the replacement list ("TEh" -> "The").
Built-in exceptions (IDs, CDs, PCs, TVs, ...) plus user exceptions.
- Correct accidental cAPS LOCK: "wORD" -> "Word" (not iOS, mRNA, ...).
- Capitalize first letter of table cells, under its own option.
- Session::autocorrect_caps (AutoCorrectCaps, all on by default);
tools.autocorrect takes twoInitialCaps / capsLock / capCells /
addException / removeException.
- Each fix is one undo step right after it (the existing AutoFormat
snapshot); case changes replace letter by letter, keeping formatting.
- Tests in tests_typing.rs; docs/typing-parity.md and ROADMAP updated.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Persist the custom dictionary across sessions (#40)
* Review fixes for #796: session-only Ignore All, desktop-only persistence, capped atomic I/O
- review.ignoreAll no longer adds to (and saves) the custom dictionary: ignored words go to a
separate session-only list, as Ignore All does in Word.
- Only the desktop app wires the dictionary file. Headless MCP and `wordcraft-cli run` keep
words in memory, so an agent never reads or changes the person's dictionary (and nothing
writes outside an automation write root, #769).
- Loading reads through `take(1 MB)`, so a huge file is never read whole; lines that aren't a
single word (spaces, control characters, invalid UTF-8, over 64 characters) are skipped.
- review.addToDictionary applies the same word rule (proof::custom_word), so a word added is
never dropped on the next load.
- Saving merges the words on disk first (another process's additions survive; words just
removed stay out), creates the folder, and replaces the file atomically (temp + rename).
The file I/O lives in the proof crate, so the engine doesn't touch std::fs itself.
- Tests for each.
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>
* Engine: check automation read and write roots on every path a command reads or writes (#769)
* MCP: confine the headless session to the automation roots (#769)
* CLI: add --automation-read-root and --automation-write-root to mcp (#769)
* Docs: document --automation-read-root and --automation-write-root for mcp (#769)
* Review fixes for #798: confine file.printPdf; guard all of engine/src
- print.rs::print_pdf_to_file (file.printPdf, added on main) wrote with std::fs::write
directly, so a confined agent could print a PDF anywhere. It now goes through
io::write_file, which checks the automation write root.
- The guard test now walks every file under crates/engine/src (not only cmd/), except
io.rs, file_access.rs and the tests*.rs modules, and also flags OpenOptions.
- Tests: an engine test that file.printPdf is refused outside the write root, works inside
and is unchanged without roots; the MCP integration test covers it too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #798: list file.printPdf among the confined writes in docs/mcp.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Font files added by path go through io, inside the automation read root (#769)
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: complete Turkish interface catalog with 2,253 entries and full parity
* Review fixes for #774: Turkish entry count after merging main (2,325)
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>
* Bidi: auto-detect RTL paragraph direction and keep typing caret advancing smoothly
When typing in Kurdish, Arabic or Hebrew in a new document, default
paragraph direction (bidi: None) was resolving to LTR. In an LTR
paragraph, visual reordering pinned newly typed characters and the
caret to the left margin (x = 72.0), resetting to the start of the line
with every keystroke.
- Add first_strong_is_rtl() following UAX #9 rules P2/P3.
- Auto-resolve unassigned paragraph direction to RTL in layout when the
paragraph starts with an RTL script.
- Auto-promote paragraph to RTL (bidi = Some(true)) when typing text
starting with an RTL character.
- Add regression test in wordcraft-engine.
* Review fixes for #713: RTL only when typing into an empty paragraph
- Drop the layout fallback that drew any paragraph without w:bidi as RTL when its first
strong character is Arabic/Hebrew: opened DOCX paragraphs keep their direction, and a
style that sets bidi=false is respected.
- type_text promotes a paragraph to RTL only when it had no letters before the insert and
nothing (paragraph, style chain, document defaults) sets its direction; typing into a
paragraph that already has text no longer flips it.
- docs/typing-parity.md: Text direction section; test in tests_typing.rs.
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>
* i18n: add Hungarian (hu) interface translation
- crates/ui-egui/src/i18n/hu.tsv: complete catalog, all 1,572 keys of
es.tsv in the same order, written clean-room from the plain meaning
of the English labels; placeholders, escapes and trailing ellipses
kept. Commands are nouns, messages use the informal second person,
and only the first word of a label is capitalised. A noun after a
number stays singular in Hungarian, so the context-free format needs
no plural forms.
- LANGUAGES gains `hu` (Magyar); `hu`, `hu-HU` and `hu_HU.UTF-8`
resolve to it.
- The Font dialog's preview sample is "AaBbYyZz ŐőŰű", so All Caps
shows the double acute capitals.
- Tests: locale resolution and saved preference, key parity with the
Spanish catalog, sample translations, count wording, the sample's
capitals and Hungarian letters in the bundled interface fonts.
- Docs: new localization-hu.md (conventions, terminology, scope);
README, ROADMAP, localization-parity and control-protocol list the
new language; ATTRIBUTION row for hu.tsv.
* i18n(hu): built-in style names, equation gallery and citation styles
- hu.tsv now carries every key of the Russian catalog: built-in style
names (Normal, No Spacing, Subtitle, TOC {n}…), the equation gallery
and structure labels, paper sizes, watermarks and the Backstage
greetings, plus the citation style names (1,946 entries).
- The interface audit (untranslated_interface_text) checks Hungarian
alongside Russian and reports every language's gaps in one run.
- Tests: Hungarian keys cover the Russian catalog; built-in style
names and numbered style templates show in Hungarian.
- Docs: entry counts and scope in localization-hu, localization-parity
and ROADMAP.
* Review fixes for #731: Hungarian stays a partial catalog
- The i18n audit requires completeness from Russian only again: hu.tsv lacks ~280 strings of the
dialogs that landed since (Compare Documents, Math AutoCorrect, …), so it can't claim to be
complete. The catalog test no longer asserts it holds every Russian/Spanish key.
- 43 Hungarian rows for tab and command labels that reached main since (charts, content controls,
Developer tab, templates and the styles organizer, WordArt), which every_tab_and_command_is_translated
requires.
- Docs say partial, with the entry count (1,989); localization-parity's header keeps its quote
marker and the #505 note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #731: Hungarian rows for the Outlining tab
24 rows for Outline view's tab and commands, which landed on main meanwhile; 2,013 entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #731: ROADMAP header keeps main's SmartArt note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(hu): complete the Hungarian catalog again
- hu.tsv: 374 new rows for the strings that reached main while #731 was
in review (Compare Documents, Restrict Editing, Math AutoCorrect,
charts, SmartArt, the Options sections, the print page and more).
The catalog now holds every key of ru.tsv again: 2,397 entries.
- The completeness checks are back: the interface audit requires
Hungarian alongside Russian, and the catalog test asserts hu.tsv holds
every key of ru.tsv and es.tsv. The docs describe a complete catalog.
- Seven review rows follow the catalog's existing patterns: Text Pane
(Szöveg panel, like the other panes), Design Mode (Tervezőmód, one
compound), Picture and Date Picker Content Control (clearer compounds),
Promote to Heading 1 / Demote to Body Text (style name after a colon,
no case ending) and Reset Graphic (Ábra visszaállítása, shorter).
* Hungarian: translate interface strings added on main since this PR
Adds the 10 strings main gained with Polish proofing (spelling and grammar
messages, Do Not Check Spelling or Grammar, comment/browse buttons), so
hu.tsv again covers every key of the other catalogs (2,419 entries), and
drops the stale "Wildcards (regex)" row; updates the documented counts.
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>
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
* i18n: add French (fr) interface
A complete 1813-entry catalog with the same key set and order as
ru.tsv (the most complete reference), written from the meaning of the
English source. Registered in LANGUAGES after Turkish. The
locale-resolution tests that used fr-FR as the example of a language
without a catalog now assert the French mapping and use Italian,
which has no catalog, to keep testing the unsupported path.
* i18n: complete the French catalog and apply review fixes
Rebase onto main (1805 uk.tsv keys, up from 1812 rows at the original
PR): 357 new translations for the charts, Draw, content controls,
Caption/Index/TOC dialogs and formatting features that landed after
the PR was opened. Also applied the reviewer's five fixes: Bring
Forward → Avancer d'un niveau, Print Layout → Mode Page, Not used by
any command → N'est utilisé par aucune commande, Through → Au travers,
and consistent U+2019 apostrophes throughout.
* Review fixes for #729: attribution, French tests, language lists
- ATTRIBUTION row for fr.tsv, in the form the other catalogs use.
- French locale/saved-preference test and bundled-font coverage test (accents, œ, « », ’, and the
no-break spaces in Inter).
- Tests that need a language without a catalog use a private-use tag (qaa-QM, qtz) instead of
it-IT/de-DE, so they keep passing as real languages land.
- 11 French rows for commands that landed on main since (templates, styles organizer, Overtype,
Move with Text), which every_tab_and_command_is_translated requires.
- fr in docs/control-protocol.md, README and localization-parity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #729: French rows for Outline view and SmartArt
34 rows for the Outlining and SmartArt Design tabs and their commands, which landed on main
meanwhile and every_tab_and_command_is_translated requires; 1,850 entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* French: translate interface strings added on main since this PR
Adds 174 French entries (fr.tsv now covers every key in the other
catalogs, 2,023 entries) and drops the stale "Wildcards (regex)" row;
updates the documented entry count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* French: translate interface strings added on main since this PR
Adds the 10 strings main gained with Polish proofing (spelling and grammar
messages, Do Not Check Spelling or Grammar, comment/browse buttons): 2,033
entries; documented count updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* French: strings main added since the last merge (default app, numerals, KeyTips, Reading Mode)
---------
Co-authored-by: sanches <cfic34@ya.ru>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
* #659: one drop-down marker (the dropdown icon) on every ribbon button
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #658: menu buttons grey out while their command is disabled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #653: large-button labels end above the group title row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #654: Layout › Paragraph fields fit above the title and line up
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>
* #659: one drop-down marker (the dropdown icon) on every ribbon button
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #658: menu buttons grey out while their command is disabled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #653: large-button labels end above the group title row
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>