Word draws no red squiggle under the word being typed. It appears when the word ends or the
caret leaves it by a key or a click. The session records the word after a text command that
edits the caret's paragraph, with apostrophes and hyphens counted as the spelling check counts
them, and drops it once the caret is anywhere else. The layout leaves that one range out of the
spelling squiggles, and a caret move without an edit lays out only that paragraph again. F7 and
the Spelling pane still report the word, and grammar marks are unchanged.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #2138: save writes the chosen file when its folder refuses a temporary file (macOS privacy protection)
On macOS a Save-panel pick inside a protected folder (Desktop, Documents…) grants
access to that file only, so the sibling .wordcraft-save-*.tmp was likely refused
with EPERM. On PermissionDenied creating the temporary file, write the destination
directly; a remaining permission error on macOS says where to allow access.
Untested on a Mac: the fallback path is pinned on Unix with a read-only folder.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #2138: a failed save in a folder refusing the temporary file keeps the original
Review (FredApps): the in-place fallback truncated the destination before
writing, so a failure partway through lost the previous document.
The fallback now keeps the existing file's bytes in memory (up to 512 MB)
before writing it in place and writes them back if the save fails, or
removes a new file it created. If that write-back fails too, the error
says the file may be incomplete. A file over the limit isn't written in
place; the error suggests Save As.
Tests: the reviewer's partial-write test (read-only folder), a new file
removed on failure, and the size limit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW
* #2138: keep the original untouched when no safe replacement is possible
When the folder refuses the sibling temporary file, an existing destination
is no longer overwritten in place (no in-memory rollback): it stays
byte-identical and the error says to use Save As to another folder or allow
access to the folder (macOS: Privacy & Security > Files and Folders). A
destination that doesn't exist yet is created exclusively and removed again
if the write fails.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* gap: nested ink WIP
* gap 2: native ink nested in groups and drawing canvases survives DOCX saves
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gap 2: review round 1: ungrouping keeps nested ink; no orphan parts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gap 2: Impact cell matches the restated impact
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* gap 2: only Word ink pins a group against Ungroup
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* gap 2: alternate ink must hold an a:graphic to be written as standalone
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* gap: nested ink WIP
* gap 2: native ink nested in groups and drawing canvases survives DOCX saves
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gap 2: review round 1: ungrouping keeps nested ink; no orphan parts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gap 2: Impact cell matches the restated impact
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* gap 2: only Word ink pins a group against Ungroup
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* gap 2: alternate ink must hold an a:graphic to be written as standalone
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Ruler numbers now thin out as the page shrinks on screen: a number
every 1, 2, 4, 8... units, whichever keeps them at least 30 px apart,
and the small ticks drop out below 4 px, with the text at its usual
size. A number gives way to an indent, tab or column marker sitting on
it, and the margins get ticks only. Both rulers use the caret page's
rect and scale in every layout, including One Page and Multiple Pages,
and follow the caret to another page. Tests cover the density rule, the
marks, the marker rule for indents, tab stops (set or dragged) and
table columns and rows, and the multi-page geometry.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The Quick Access Toolbar's menu ends with Hide Quick Access Toolbar, as Word's does. Show Quick Access Toolbar brings it back from the ribbon's right-click menu and from a check box on Customize Ribbon's Quick Access Toolbar page. The setting is kept between runs, and keytips skip the toolbar's digit badges while it is hidden. The UI command ui.quickAccessToolbar drives it from scripts and MCP. Regenerates docs/parity-checklist.md, which was behind the catalog. Adds my contributor entry.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* gap: RTF formatting revisions WIP
* gap 19: RTF formatting and paragraph-mark revisions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gap 19: review round 1: list kept on Reject; old formatting normalised; section marks; hostile borders
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gap 19: review round 2: \oldcprops holds only the changed properties, as Word writes it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gap 19: old formatting in styled paragraphs uses the style's size and font
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gap 19: RTF import keeps main's heading and code normalisation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gap 19: file-format-parity status line keeps main's history
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* gap 19: a paragraph that was a list item gets its old list back on Reject
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* gap 19: a numbered item made bulleted keeps its numbered list for Reject
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* gap 19: format matrix names the tracked section mark
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* rtf test: Writer has the section fields main added; rustfmt
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Adds the 402 entries the French catalog lacked: equation gallery (matrices, accents, brackets, integrals, operators, functions), built-in style and table-style names, citation styles, paper sizes, watermarks, and the Bookmark, Go To and Print dialog strings. Drops the obsolete 'Style: APA' entry. Terminology reviewed by a native French speaker and Word user. The Polish superset test exempts fr like the other catalogs carrying newer strings. Progress docs and people.toml updated.
Co-authored-by: Vivien JANNY <vivien@avertissance.fr>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(ui): expose save confirmation context (#1015)
* #1015: require alert-dialog semantics in save regression
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Each picture, and each embedded object or macro part kept for saving, was
copied out of the unzipped package (bytes.to_vec()), so a document with
pictures held them twice while it opened, and the package's copies were
left behind as freed memory. Package now holds its parts as Arc<Vec<u8>>,
the type the document keeps media and passthrough parts in, and hands them
over with get_shared. keep_unmodelled also searches the main part in place
instead of copying it.
A 92 MB document with 20 pictures on macOS 27: the app's footprint once
open 866 -> 772 MB (freed blocks kept 206 -> 118 MB, peak 915-932 ->
821-823 MB); wordcraft-cli info peak 319 -> 231 MB.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* SCRUM-47: offer Save As for imported Word files
* #2445: reject Save As answers for a replaced document
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Layout: preserve equation baselines under exact line spacing (task 243)
* layout-parity: keep the cell-margin inheritance note on the Tables row
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Word 2024 saves Strict Open XML with measures written out with their unit:
the issue's file carries `w:spacing w:after="8pt" w:line="13.90pt"
w:lineRule="auto"` in its paragraph defaults. The reader sent `13.90`
straight into the `auto` branch, where a bare number counts 240ths of a
line (240 = single), so 13.90pt became a 13.9 multiple: 191.8 pt lines,
and the issue's document laid out as 10 pages against Word's 1.
Under `lineRule="auto"` a unit-suffixed height now reads through twips, as
Word renders it (ISO 29500 `ST_SignedTwipsMeasure`: 13.90 pt = 278 twips =
the 1.1583 multiple). A bare number still counts 240ths of a line;
`exact`/`atLeast` already read units as points.
Checked with the issue's file: 1 page with baselines on Word's (15.98 pt
line advance against Word's 16.10/15.96; paragraph advance 23.99 against
24.00; table lines 12.65 against 12.60/12.72), and a save/reopen keeps it.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Paste of plain text from another program gave every line after the first
a paragraph with default formatting. The text became a fragment of
unformatted paragraphs (Fragment::from_text), and insert_fragment keeps
the target's formatting only in the first one. Pasting several lines into
a right-to-left paragraph turned all but the first left-to-right, and the
text after the caret lost its paragraph formatting too.
Paste now inserts plain text as Keep Text Only does. Each line after the
first splits the paragraph as Enter does, so it keeps the paragraph's
direction, style, alignment, indents, spacing and list. Empty lines become
empty paragraphs with that formatting. Copies from WordCraft and HTML or
RTF from other programs keep their own formatting as before. A paste is
still one undo step.
Tests cover a right-to-left paragraph, empty lines and a final line break,
and a list. The typing and UI parity docs and the ROADMAP progress log
have rows for the change.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #1765: WIP
* #1765: ASK and SET fields show nothing
Like Word, ASK and SET only assign text to a bookmark: they draw no
result on the page or in a PDF. The stored result is kept and saved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* file-format-parity: note ASK/SET on the existing Fields row instead of a third one
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #1741: WIP
* #1741: list items no longer get the sentence-capital grammar squiggle
The English "Capitalize the first word of a sentence" rule skips the first
word of a list paragraph (own or style numbering); later sentences are still
checked. check_text_with and paragraph_issues take a list_item flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1741: review fixes
Thread the capital-first flag into the Polish, Croatian, Italian, Turkish and
Portuguese grammar rules; test the drawn squiggles through layout() for own,
style and num 0 numbering; tidy the proof test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1741: main's Vietnamese proofing test passes the new list-item argument
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1741: the third call in main's Vietnamese proofing test
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* rtf-sect-export: WIP
* Gap 19: RTF export writes sections
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Gap 19: RTF export writes sections — review fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* odt-sect-export: WIP
* Gap 19: ODT export writes sections
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Gap 19: ODT export writes sections — review fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* gaps log: RTF and ODT section export both done
* Gap 19: ODT continued lists share the earlier list's style; one RTF and ODT row in the parity table
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Old releases of heavy faces (the core-fonts Arial Black) claim weight 400 in
OS/2, so bold text in them was drawn with synthetic bold. Current releases say
900 and Word draws bold Arial Black unchanged. A static face whose file says less
than semibold now takes the weight its family's last word names: Black/Heavy 900,
ExtraBold/UltraBold 800. Files that already declare a heavy weight keep it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* WIP for #1609
* Equations: a script after _ or ^ is the whole run of letters and digits (#1609)
Word for Mac 16.113.4: V_dd and V_(dd) build up the same (dd is all
subscript), likewise x^ab, and Linear format shows V_dd bare.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Equations: bare scripts end at a space, digits continue a script run, upright function names (#1609)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Equations: a bare script also ends before a following period (#1609 review)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* ROADMAP: drop three progress rows main already has
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 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* odt-sect-export: WIP
* Gap 19: ODT export writes sections
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Gap 19: ODT export writes sections — review fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT export: a list cut by a section continues with text:continue-list; drop duplicate parity rows
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Spike: Ctrl+F3 collects cuts, Ctrl+Shift+F3 inserts them and empties it (gap #24)
* Search test: a lone INSERT's witness within the box's 14 results; Table checked past it
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Read w:eastAsianLayout (ISO/IEC 29500-1 §17.3.2.10) into a new
CharProps::east_asian_layout and write it back after w:lang, in runs and
styles. Values outside the schema are left unset. Layout is unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Field results (STYLEREF, REF, PAGE text, note marks) shown as one atomic
cluster now place their inner clusters in UAX #9 visual order, in the
direction of the text around the field, so "פרק א" in a header no longer
draws reversed. Smart quotes already matched Word's logical-order result
in Hebrew (” in acronyms, not gershayim); pinned with a typing test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1670: fix round 1: field results shaped per bidi run, so brackets mirror correctly
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>
* WIP for #1543
* DOCX: split mixed-direction runs and mark right-to-left pieces w:rtl so Word orders them as WordCraft does (#1543)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* DOCX: right-to-left pieces of split runs say their language (w:lang w:bidi) (#1543)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* DOCX: no phantom tracked formatting on split runs; typed text doesn't inherit run direction (#1543)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Engine: text typed or inserted anywhere takes its direction from its own letters, not the caret's run (#1543)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* WIP for #1480
* Web: an input method's DeleteSurrounding erases around the caret, so non-Latin typing no longer repeats (#1480)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Web: input-method rewrites keep formatting and the typing undo group; AutoCorrect resets the web text field (#1480)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #1706: WIP
* #1706: Update Fields refreshes fields in headers, footers and text boxes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
# Conflicts:
# ROADMAP.md
* #1706: review fixes
Revision-history row, update_fields doc comment, test covers body field and locked header field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1706: update the fields of the part being edited, as in Word
Word check: F9 with the caret in the body leaves header fields alone; with the caret in a header it updates that header. SEQ, citations and TOC run only from the body.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1706: printing updates every field in the document
references.updateFields takes {"all"?: bool}: the body (with SEQ, citations, TOC) plus every part story, wherever the caret is. Printing passes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1706: ROADMAP status line after rebase
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1706: tests build the field form value added on main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1706: the test's fields take main's new dirty flag
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* track-move-options: WIP
* track-move-options: Moves options in Track Changes Options and w:doNotTrackMoves
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* track-move-options: Moved from Hidden leaves the layout; per-revision hiding
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* track-move-options: Word's mark lists, (none) moves are plain, no borders on collapsed paragraphs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* track-move-options: hr and sv translations for Moved from:/Moved to:
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* ink-arrows-2: WIP
* ink-arrows-2: Ink to Shape makes two-stroke arrows and closed-triangle heads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ink-arrows-2: review fixes — recognise each stroke once, drawing-order pairing, arrow rect
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Dialogs, the Read Aloud player and the file browser used egui's small close glyph: the hit area was the glyph alone and hovering only changed its stroke. The close control is now a box at the right end of the title bar, the full title-bar height and 1.6 times as wide, as a Windows caption button on a Word dialog. The whole box closes the window. It fills red with a white cross while the pointer is over it and darker red while pressed and still under the pointer, with colours from the theme tokens in both themes. The box is pointer-only, so Tab never lands on it; dragging from it does not move the window and Escape closes dialogs as before. Its accessibility name is translated like its tooltip.
docs/parity-checklist.md is the regenerated checklist.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A bullet stored as a private-use code (U+F0xx) in a font that isn't
installed, and that isn't a mapped Symbol/Wingdings code, fell back to
any installed face with a glyph at that code - an icon font such as
Font Awesome / Nerd Fonts, where U+F081 is the Twitter logo. Such a
bullet now shows the level's default bullet (• ○ ▪), so level 2 is a
hollow circle.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The web shell never loaded or saved the UiState the desktop app keeps in
ui.json, so a reload reset the theme and every other preference. It now
restores it from localStorage at startup and writes it back whenever it
changes (a reload runs no exit code).
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The font box (ribbon and mini toolbar) opens with a Recently Used Fonts
section: the fonts applied from the box, the mini toolbar or the Font
dialog, most recent first, once each, ten at most, kept in ui.json. All
Fonts follows. The sections show while nothing is typed; a query lists
its matches only. Up/Down walk the recent entries then the list and Enter
applies the highlighted one; a click on a heading keeps the list open.
The typed list is sized again when its length changes, in a discarded
pass so it never blanks; it used to stay one row tall after a one-match
query. Names over 128 bytes are not kept, and reading a saved list stops
at ten names.
Co-authored-by: Brandon Thomas <echelon@gmail.com>