* #3867: review — header repeats, picture outlines and linked-box widths bounded the same way every layout
- Header repeats: a Clip (floating Drawing Canvas) counts its members, depth-bounded, so a 1000-member canvas no longer counts as one item (CodeRabbit thread 4373103686); the budget is per layout (Ctx), not per table.
- Tight-wrap outlines: each cache entry stores its picture's pixel cost; a cached hit is charged to the layout's decode budget like a decode, so cold and warm layouts (and the CLI) wrap alike.
- Linked boxes: widths are budgeted by laid-out work (MAX_FLOW_WORK), not 16 widths, so ordinary stories keep exact widths for every box.
- header_rows.contains -> index compare; layout-parity doc revision.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
* #3867: review — a picture that fails to decode still costs its pixels when cached; header repeats restored on retry
- contour: Alphas records the pixels each picture charged (costs map), also when the body then fails to decode, so the cache entry's cost is right and warm layouts charge it like cold ones.
- Snap saves and restores ctx.header_repeats so balance_pass retries don't keep their charge.
- Tests: truncated large PNG cold vs warm; canvas header row end to end through place_table; linked-chain bound <= 8; vertical box's layout differs from every other box's.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
* #3867: CodeRabbit — header and linked-box budgets count every clip descendant
placed_cost counts iteratively (explicit stack, stops past the limit) and is shared by flow_work.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
* #3867: docs — progress log, gap 20 note for layout resource caps and linear-time passes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
* #3867: review — roadmap gives each cap its unit: header items, wrap-cache pixels, linked text box lines and items
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #3867: CodeRabbit — the vertical-box check runs past the work cap too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #3867: review — a linked chain's work counts the story it lays out, not only the lines it shows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #3867: review — header-row cost import follows main's explicit layout imports (#4501)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #3867: review — a test pins the header repeats that column balancing gives back; docs keep main's caps row and both gap status lines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
OpenSSF Scorecard (v5.6.0, 4.0/10) flagged:
- Token-Permissions: release.yml granted contents: write at the top level.
Now read at the top, write on the release job only (same effective access).
- Security-Policy: no SECURITY.md. Added one pointing to GitHub private
vulnerability reporting (already enabled on the repo).
- Vulnerabilities: ctutils 0.4.3 -> 0.4.7 (RUSTSEC-2026-0385); clipboard-win
5.4.1 -> 5.4.3 (yanked).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* #3871: review — metafile input charged to the decode budget; inline BLIPs deduped; budget scales with input
- A compressed metafile is charged max(inflated size, zlib input), so a record claiming to inflate to nothing still costs its input.
- Inline pictures share one decode per BLIP record, cached by (stream, foDelay, record's own length) instead of the FBSE size field.
- The decode budget is max(512 MB, 64 x input bytes), capped at 2 GiB, so documents of many large legitimate DIBs decode; high-ratio bombs stay capped.
- Decode-budget refusals get their own log line.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
* #3871: review — a DIB's copy into a BMP is charged to the decode budget before it is made
The charge was w*h*4 after the copy, so a 1x1 header followed by megabytes of tail cost 4 bytes per picture. It is now max(pixels, DIB length), charged first; a bitmap whose copy doesn't fit is dropped rather than copied. The doc comment on the budget scale now says RLE8/RLE4 bitmaps (about 510x) rely on the 512 MB floor.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* #3680: Read Aloud tidies in one linear pass and cuts space-free sentences
Finding 56. tidy() repeated whole-string replacements until nothing
changed; each pass removed one level of nested empty brackets, so 40,000
pairs took ~50 s in a debug build. It is now one pass that drops a closing
bracket's empty pair as it goes, checked against the old algorithm on
20,000 generated strings. Sentences with no space to split at are cut at
MAX_UTTERANCE (1,200) characters, and each sentence is sliced from a char
vector instead of skipping from the paragraph's start.
Fixes#3680
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #3692: Paste Special reads only a regular picture file, bounded
Finding 24. A copied path naming a device such as /dev/zero reports a
length of 0, so the size check passed and std::fs::read read forever.
The path and the opened handle must both be regular files, and at most
the picture limit + 1 bytes are read. A named pipe swapped in between the
check and the open can still block the open (no portable non-blocking
open without platform flags); noted in the code.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #3692: macOS signing import decodes the certificate into a private temp folder
Finding 25. The .p12 bundle was written to a predictable name in the
shared temp folder, where a planted link could redirect it. It now goes
into a mktemp -d folder under the same temp root, removed on exit. The
keychain is unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #3666: engine file reads open once and check the handle; a FIFO is refused without blocking
read_file_capped checked the path's metadata and then read the path
again, so a file swapped or grown after the check was read whole;
read_file_head opened the path with no type check, so saving to a FIFO
.xml path (the Word 2003 sniff) blocked until a writer appeared. Both
now go through open_regular_file (path checked before opening, handle
checked after, opened once) and a bounded read; read_regular_file uses
the same pieces. Tests: both helpers refuse a FIFO without blocking
and a folder; read_file_capped refuses content over the cap.
Fixes#3666
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #3692: Paste Special reads picture files through the engine's bounded read
Ultrareview finding 9: paste_picture kept a third copy of the regular-file
bounded read; it now calls wordcraft_engine::io::read_regular_file (public,
with a wasm stub).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #3666: open a checked file non-blocking so a swap to a named pipe can't hang
CodeRabbit: a path replaced by a FIFO between the regular-file check and
File::open blocked in open, before the handle check could refuse it. On Unix the
open now uses O_NONBLOCK (no effect on regular files; libc is already in the
lockfile) and the handle check refuses the pipe. The FIFO tests' cleanup writers
open non-blocking too, so a regression fails the test instead of hanging it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #3666: review — Paste Special refuses a picture file whose handle reports more than the cap before reading it
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
* #3692: review — macOS signing keychain lives in the private mktemp folder too; removed if the import fails
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
* #3666: review — libc comes from the workspace dependencies
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
* #3680: review — Read Aloud logs a warning when its sentence cap leaves text unread
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
* #3692: review — engine speech timing test gets a margin a busy CI machine can't break
tidy_is_linear_on_nested_brackets: 2 s -> 10 s. The one-pass tidy takes ~13 ms on a loaded
machine; tidy_by_replacing (the old tidy, kept in the tests) took 293 s on the same input.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* With Regular expressions on, Replace with reads $1…$n, ${n}, ${name}, $name,
$0/${0}, $$, \1…\9, \n (paragraph mark, splits the paragraph through the ^p
path), \t and \\, alongside the ^ codes. Named groups resolve through
meta::Regex::group_info(); missing, unknown and out-of-range groups expand to
nothing. Plain and wildcard Replace are unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #4177: review — special:false keeps regex group references
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #4177: review — \$ and \^ in regex Replace keep the character as typed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #2852: personal templates in File › New, Save as Word Template
File › New lists the .dotx/.dotm files in the user templates folder under
Personal (a subfolder per category, search covers them); a click makes an
untitled document attached to the template. Save As › Save as Word Template
starts in that folder (<settings>/Templates, made on first use; File ›
Options › Advanced › File locations › User templates overrides it).
Engine: file.listTemplates (bounded, network folders refused, read root
applies), file.newFromTemplate sets the attached template, file.options
userTemplatesFolder, file.saveTemplate keeps a .dotm name. UI:
ui.saveAsTemplate; Save dialog requests carry a start folder.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #2852: review — a personal template asks about unsaved changes; network templates folder refused before it's looked at; New lists the folder once per visit with Refresh; .dotx/.dotm labels alike
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
* #2617: centre equation-array rows without &
A row with no alignment mark sat in the first column and was right-aligned
to it, widening that column and pushing the aligned rows right (Word's
"First Property of Radicals" case equation). Word centres such a row in the
array; it no longer widens the aligned columns.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #2617: review — test a lone eq-array row wider than the aligned columns
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
* #1281: English dialects in proofing — en-GB, en-CA, en-AU… flag each other's spellings (#1045)
English was one proofing language: en-GB, en-CA and en-AU all proofed like
US English, and US documents accepted British spellings. Now ProofLang has
three Englishes, picked by the tag's region:
- En (en-US, other regions, no language): British spellings flagged
(colour → color).
- EnGb (en-GB, en-AU, en-NZ, en-IE, en-ZA, en-IN…): American spellings
flagged (color → colour, center → centre, analyze → analyse); -ize and
-ise both right (Oxford spelling).
- EnCa (en-CA): British -our/-re/-ence, American -ize, tire, program.
Data: assets/spelling/en-variants.txt, our own ~280 American/British
pairs with a policy per English (MIT OR Apache-2.0, ATTRIBUTION.md row).
Inflections by rule (crates/proof/src/en_variants.rs). A dialect's form
is right when the shared list knows it or its other spelling, so
"realisation" is right in en-GB. No system dictionary needed.
Suggestions put the dialect's own form first. Review › Language lists
English (Australia) and (Canada); the Language dialog adds India,
Ireland, New Zealand and South Africa (names in every catalog, not
native-reviewed). An English interface on an en-GB/en-CA/en-AU… region
writes new documents in that English. Options › Language shows the
document's language instead of a fixed "English (United States)".
Tests: per-dialect accept/flag/suggest, tag mapping, no-panic proptests,
engine end to end through review.language, DOCX w:lang round trip,
region-based editing language, language menu.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW
* #1281: review — pin the #40 words (judgement, defence, licence, programme, catalogue) in en-GB and en-US
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1281: review — harness picks every proofing language in Review › Language and checks it is ticked
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1281: review — pin more British spellings in en-GB (goitre, sceptre, enrol, fulfil, instalment, skilful, wilful, encyclopaedia…)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
* #1678: Focus fills the screen; Full Screen View is a bindable command
View › Focus goes full screen with the document on black and a slim bar
whose "⋯" drops the ribbon over the page. Full Screen View
(view.fullScreenView, no default key; bind one in Customize Keyboard)
shows the pages alone with the search box above them; the two turn each
other off. Full Screen (view.fullScreen, F11, #980) keeps its whole-window
behaviour. Escape leaves Focus or Full Screen View, then Full Screen, once
nothing nearer takes it; the system leaving full screen ends all three.
Ctrl+F1 collapses the ribbon (not on macOS). Immersive Reader no longer
turns Focus on. The page widget keeps a fixed id so it keeps focus when
the rulers hide.
Fixes#1678
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1678: one Focus path after the rebase; pin the dropped ribbon staying down on a ribbon click
main's Focus (#1017) consumed Escape in WordApp::ui before
leave_full_screen, so Escape would leave Focus even when it should first
collapse a selection or close a popup. That handler is removed: Focus,
Full Screen View and Full Screen are left through leave_full_screen
only. main's focus_hides_the_ribbon_and_escape_brings_it_back still
pins the panes coming back. Full Screen View and Show the ribbon added to
the hr, ko and sv catalogs, which main gained since.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1678: Show the ribbon is a named button; a bound Ctrl+F1 wins; Esc in Read Mode leaves a dialog's Escape alone
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1678: parity checklist regenerated after the rebase (Full Screen View counted)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1678: harness tests for the one Escape path and Ctrl+F1 through the app
In Focus with text selected on the page, the first Escape collapses the
selection and keeps Focus; the second leaves Focus (fails if the old
focus_mode Escape handler in WordApp::ui comes back). Ctrl+F1 sent as a
key event collapses the ribbon when unbound, and runs the user's command
when bound in the keymap, leaving the ribbon alone (not on macOS).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1678: review — a click on the dropped Focus ribbon gives the page the keyboard; only Ctrl+F1 yields to a binding
The ribbon dropped over the page in Focus lies over the canvas viewport and
in a Middle-order area, so refocus_after_chrome_click skipped clicks on it
and typing went nowhere until the page was clicked (which also put the
ribbon away). Clicks on the focus_ribbon layer now count as chrome. The
Focus test asserts the page has the keyboard after a ribbon click and that
typing reaches it with the ribbon still down.
dispatch checks the keymap ahead of an interface shortcut only for Ctrl+F1;
Alt+Q and Cmd+Option+R keep their place ahead of user bindings, as before
#1678 (test: a_bound_alt_q_still_opens_command_search).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1678: review — Ctrl+F1 (⌘⌥R on macOS) collapses or expands the ribbon; a shortcut of your own on Ctrl+F1 wins
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1678: review — ui-parity's Word column gives Word for Mac's ribbon shortcut (⌘⌥R)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1678: review — Focus's dropped ribbon is a command, ui.focusRibbon; "⋯" and a click on the page run it
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1678: review — the Focus UI test turns Focus on from the status bar's Focus button
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1697: legacy menu keys (Alt, V, U and 17 more Word 2003 sequences)
After Alt, a letter no tab keytip or Search uses starts a legacy menu
sequence, as Word for Windows still takes them (observed there): Alt, V, U
turns on Full Screen View; also views, ruler, zoom, Select All, Find /
Replace / Go To, Symbol, Picture, Font, Paragraph, Word Count and Insert
Table. Hidden as in Word; a hint under the tab row names the keys so far.
A letter that leads nowhere is ignored, Esc cancels, Alt goes back to the
tab badges. Tests keep the first letters off every ribbon letter and check
each target exists.
Fixes#1697
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1697: legacy menu keys stay off on macOS, F10's badges included
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1697: review — the legacy hint shows without a ribbon; Alt, V, U through the app; not on macOS in the docs
With no ribbon showing (Focus, Full Screen View) no tab rects are recorded, so
Alt, V's hint didn't show; it now hangs under the slim bar at the top of the
window. A harness test drives Alt, V, U through the whole app and checks no
letter is typed. The parity row and progress log say the sequences are not on
macOS.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
* #1500: shape fills keep gradients and transparency (model, DOCX read/write)
A shape's, text box's and canvas's fill is now a `Fill` (solid with
transparency, or a gradient kept as the file states it) instead of one
opaque colour, so gradients and transparency survive opening and saving.
- doc: `fill::Fill`/`Gradient` with sanitizing for hostile input; `.json`
keeps writing opaque solid fills as `[r,g,b]` and reads both forms;
`ShapeExtra::outline_fill` keeps an outline gradient.
- docx read: `a:solidFill` with `a:alpha`, `a:gradFill` (stops, `a:lin`,
`a:path`/`fillToRect`, `tileRect`, `rotWithShape`, `flip`), theme fill
styles through `a:fillRef` 1-999/1001+ with `phClr`, `a:grpFill`,
`satMod`, outline gradients, VML `v:fill` gradients and opacity, canvas
backgrounds.
- docx write: `a:solidFill` + `a:alpha`, `a:gradFill` as read.
- engine/docbin/layout: call sites; drawing still uses the main colour
(next commit draws gradients).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1500: draw shape gradients and transparency (layout, render, PDF)
- layout: WordArt's paint becomes a shared `display::Paint` (solid,
linear, radial); `paint_for` lays a fill over the shape's frame
(linear at its angle, `scaled` honoured; path gradients circular from
their focus to the farthest corner). Shapes, freeforms and canvas
backgrounds use it.
- render: one `set_paint` for shapes, paths and WordArt; radial
gradients; soft edges fade the real fill.
- pdf: axial and radial shadings with per-stop opacity.
- Review fixes: nested groups without a fill pass theirs on to
`a:grpFill`, text-box contents don't take a group's fill, `phClr`
transparency only on `phClr` colours, VML focus ends consistent and
opacity clamped, `rotWithShape`/`scaled` written only when set,
Shape Outline clears an outline gradient, shared stop sanitizing.
- docs: file-format, layout parity, gaps #1, roadmap progress log.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1500: shape.fill sets transparency and gradients on every selected shape
- shape.fill (now cmd/shape_fill.rs) takes {"color", "transparency"?} or {"gradient": {stops,
type, angle, focus, rotateWithShape}}; a colour alone keeps the transparency; values are
sanitized; applies to every selected shape, text box and canvas as one undo step.
- Formatting (shape.fill, shape.effects) keeps a Shift+click multi-selection, as in Word.
- view.formatShapePane toggles the Format Shape pane.
- Session::undo_mark/merge_undo_since: a pane drag that applies as it goes undoes in one step.
- Helpers for the pane: params(fill), stop add/remove/move, directions, variations and
presets (variation and preset colours provisional until compared with Word).
- Catalog: Shape Fill Gradient, Format Shape Pane.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1500: Shape Fill › Gradient and the Format Shape pane
- Shape Fill › Gradient: No Gradient, Light and Dark Variations of the shape's colour, More
Gradients… (opens the pane).
- Format Shape pane (right): No fill / Solid fill (colour, transparency) / Gradient fill
(presets, type, direction, angle, stops bar with add/remove/drag and arrow keys, per-stop
colour, position, transparency, rotate with shape). Drags preview live, one undo step.
- Right-click › Format Shape… on shapes, text boxes and canvases.
- Gradient swatches have accessible names; new labels translated (own wording, clean room).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1500: preset gradients are our own design (clean room)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1500: Gradient variations, starting gradient and directions as Word 365 does them
Observed in Word 365 (behaviour and values only):
- Shape Fill › Gradient: a No Gradient swatch, then 13 Light and 13 Dark Variations in a
grid five wide, in Word's order; light stops tinted 66/44.5/23.5% with saturation ×1.6,
dark shaded 30/67.5/100% with ×1.15; linear ones scaled; corners with their tile rectangle.
- Format Shape › Gradient fill starts from the theme's first accent (four stops, 0/74/83/100%).
- Corner directions in the pane get the same tile rectangle; Add/Remove sit by the stops bar.
- shape.fill carries "scaled" and "tile"; docx exports color_modified (lumMod, lumOff, tint,
shade, satMod) so variations use the reader's own colour maths.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1500: review fixes for the shape fill ribbon controls
- Gradient variations are built from the colour the shown variation came from, so picking
variations in turn, or No Gradient, doesn't drift the colour.
- A drag's undo mark is forgotten while the Format Shape pane is closed (a stale mark could
merge later undo steps).
- Dragging on the stops bar away from a handle no longer moves the selected stop; the arrow
keys are taken by the bar (no focus moves or shape nudges).
- Direction and Type changes keep a linear gradient's "scaled" and an imported tile
rectangle; only a corner direction sets its tile.
- Colour modifiers (lumMod, lumOff, tint, shade, satMod) live on Rgb in the doc crate, shared
by the DOCX reader and the gradient builders.
- Previews build their stop list once per swatch, not per vertex.
- Translations for the pane's hint, direction and preset names.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1500: adapt to main: caret contrast samples gradient fills; upstream tests use Fill/Paint
- Paint::at gives a paint's colour and opacity at a point; the caret's background sampling
(#299) uses it for shape fills, now gradients or transparent, and the Format Shape
previews use it too.
- Upstream tests (#886 shapes survive saving, render) build fills as Fill/Paint.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1500: adapt to main after the rebase: VML presets, rounded corners, new catalogs
- vml_fill takes the shape's shapetype: a VML preset shape (#1377) takes its fill, opacity and
gradient from its shapetype when it states none of its own, as it already took its colour.
- Rounded-rectangle corners (#1476) and the gradient paints meet in render, PDF and the caret's
background sampling; ShapeExtra keeps both `corner` and `outline_fill`.
- Tests that landed on main since build fills as Fill/Paint (VML presets, cover pages, VML
freeforms, path arrowheads, PDF and render shapes).
- The 40 Shape Fill / Format Shape strings in the hr, ko, sv and vi catalogs, which main
gained since.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1500: docs: shape gradients and transparency, Shape Fill › Gradient and the Format Shape pane
Re-applied on main's docs after the rebase: progress log, file-format, layout and UI parity
rows, gaps #1, status lines and revision history; parity checklist regenerated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1500: a theme picture fill falls back on the reference colour; VML freeform opacity note
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1500: adapt to main after the second rebase: VML shapetype tests compare Fill; parity checklist regenerated
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1500: one VML fraction parser; a preset takes its gradient from its shapetype
Review notes: story.rs's watermark/freeform opacity reuses shape_style's vml_fraction (clamped at the callers) instead of a second copy; a test pins a VML preset whose v:fill gradient comes from its v:shapetype, and the 65536ths form of a fraction.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1500: adapt to main after the third rebase: the shape duplicate preview test fills with Fill
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1500: review — harness test: right-click a shape, Format Shape… opens the pane and keeps the shape selected
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1500: review — theme keeps every fill style a fillRef can reach (999 per list), still bounded
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
* #1678: Focus fills the screen; Full Screen View is a bindable command
View › Focus goes full screen with the document on black and a slim bar
whose "⋯" drops the ribbon over the page. Full Screen View
(view.fullScreenView, no default key; bind one in Customize Keyboard)
shows the pages alone with the search box above them; the two turn each
other off. Full Screen (view.fullScreen, F11, #980) keeps its whole-window
behaviour. Escape leaves Focus or Full Screen View, then Full Screen, once
nothing nearer takes it; the system leaving full screen ends all three.
Ctrl+F1 collapses the ribbon (not on macOS). Immersive Reader no longer
turns Focus on. The page widget keeps a fixed id so it keeps focus when
the rulers hide.
Fixes#1678
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1678: one Focus path after the rebase; pin the dropped ribbon staying down on a ribbon click
main's Focus (#1017) consumed Escape in WordApp::ui before
leave_full_screen, so Escape would leave Focus even when it should first
collapse a selection or close a popup. That handler is removed: Focus,
Full Screen View and Full Screen are left through leave_full_screen
only. main's focus_hides_the_ribbon_and_escape_brings_it_back still
pins the panes coming back. Full Screen View and Show the ribbon added to
the hr, ko and sv catalogs, which main gained since.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1678: Show the ribbon is a named button; a bound Ctrl+F1 wins; Esc in Read Mode leaves a dialog's Escape alone
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1678: parity checklist regenerated after the rebase (Full Screen View counted)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1678: harness tests for the one Escape path and Ctrl+F1 through the app
In Focus with text selected on the page, the first Escape collapses the
selection and keeps Focus; the second leaves Focus (fails if the old
focus_mode Escape handler in WordApp::ui comes back). Ctrl+F1 sent as a
key event collapses the ribbon when unbound, and runs the user's command
when bound in the keymap, leaving the ribbon alone (not on macOS).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1678: review — a click on the dropped Focus ribbon gives the page the keyboard; only Ctrl+F1 yields to a binding
The ribbon dropped over the page in Focus lies over the canvas viewport and
in a Middle-order area, so refocus_after_chrome_click skipped clicks on it
and typing went nowhere until the page was clicked (which also put the
ribbon away). Clicks on the focus_ribbon layer now count as chrome. The
Focus test asserts the page has the keyboard after a ribbon click and that
typing reaches it with the ribbon still down.
dispatch checks the keymap ahead of an interface shortcut only for Ctrl+F1;
Alt+Q and Cmd+Option+R keep their place ahead of user bindings, as before
#1678 (test: a_bound_alt_q_still_opens_command_search).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1678: review — Ctrl+F1 (⌘⌥R on macOS) collapses or expands the ribbon; a shortcut of your own on Ctrl+F1 wins
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1678: review — ui-parity's Word column gives Word for Mac's ribbon shortcut (⌘⌥R)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1678: review — Focus's dropped ribbon is a command, ui.focusRibbon; "⋯" and a click on the page run it
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1678: review — the Focus UI test turns Focus on from the status bar's Focus button
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
* #2048: text above a float's anchor wraps around it
A floating object joined the page's wrap areas only when layout reached
its anchor paragraph, so paragraphs above the anchor ran behind a picture
placed higher up the page (fixed on the page, or moved up from its
paragraph). Layout now notes such floats and lays the pass out again
(at most 3 times) with their wrap areas known from the page's start.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #2048: early-float wrap settles when the anchor moves to the next page
Review follow-up. Rewrap passes add to the wrap areas pages start with
instead of replacing them, and a pass is done when every float found
above its anchor sits inside the room its page started with. A float
whose wrap pushes its own anchor onto the next page has no fixed point;
the first page keeps the room and the float goes with its anchor, so
the passes no longer swap between the two pages. The last allowed pass
moves any float still not covered into its seeded room, or down to its
paragraph's top, so the returned layout never has text under it.
Tests: the reviewer's page-boundary case, and a last pass with nothing
seeded keeping floats clear of earlier text.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW
* #2048: review — the early-float page-boundary case is pinned for 4 to 18 paragraphs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #2048: review — a column break into another width keeps the floats where the first piece drew them
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
* #1740: triple borders draw as three lines on screen and in PDF
Triple borders (tables, paragraphs, the ### AutoFormat line) were drawn as
double lines. Layout now emits a Triple stroke; the renderer and the PDF
exporter draw three thin lines across the line's direction, so vertical
table borders get three side-by-side lines too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1740: rebase onto main — triple border strokes use the line's f64 ends
Main now converts the line ends to f64 once and draws through a shared
offset-line closure; the triple stroke uses both instead of dereferencing
the shadowed values.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #1740: review — PDF triple-border test checks the three strokes are distinct and 3 widths apart
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
* #1746: the text pointer shows on white paper on Windows
Windows' inverting I-beam pointer stays white over the GPU-drawn page, so
it vanished on white paper (#1746, #1417, #1126). On Windows the canvas now
hides the system pointer wherever it would be the I-beam and draws one in
its place: black on light paper, white on dark (as the caret, #299), edged
in the other colour. Other systems keep their own I-beam.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1746: the drawn I-beam snaps to physical pixels, so it stays white on the dark page at 125%/150%
Review note on #2230: the 1 pt fill inside a 3 pt edge was snapped to
half a point, so at fractional scaling the white stem was anti-aliased
into the black edge and read grey. Fill and edge are now whole pixels
wide and lie on pixel edges.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1746: the drawn I-beam takes the canvas colour between pages and on cut margins
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1746: tests pin whole-pixel strokes at 125%/150% and the canvas colour off the page
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #1746: review — hovering the page leaves the selection alone (test)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
* #2103: double-clicking a later page's footer opens it without scrolling up
The double-click's first press in the footer band moved the caret to the
nearest body text and the caret-follow scrolled the view up to it, away
from the footer being double-clicked. A press in the header/footer band
from the body now leaves the caret and the view alone, as in Word; the
release still opens the footer on the clicked page.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #2103: the footer test checks the caret and the view at the press too
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
* #2103: review — footer test checks the clicked page's exact footer story, with a header present
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
The Proofing checkbox wrote session view state only; ui.json had no key
for it. Saved as UiState.checkSpelling (default on), like darkPage.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
the_bar_scrolls_under_the_ribbon_not_over_it (#1069) expected the bar to
keep an anchor with its selection scrolled off the document area; since
#1501 the bar hides then. The test now checks the bar is gone and the
ruler's left-indent marker still drags.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
The Recover page forced a black fill and white text whatever the theme. It now uses the same fills and text colours as the other Backstage pages.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #2972: regression tests for File page / sidebar AccessKit focus paths
The macOS back chevron beside the traffic lights (the report's "sidebar
collapse toggle"; there is no other toggle there), File > New/Home > Blank
document, Escape, and File > Options > back (both layouts, with a document
and from the Start screen) all run under kittest's AccessKit. The chevron
and Options tests panic with "Focused ID ... is not in the node list"
before #2856 and pass on main.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #2972: review — fresh document per Blank document path; Options back asserts closing and focus in every case
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #2999: a file dialog that "cancels" at once asks again without a parent, then shows the built-in browser
rfd folds the xdg-desktop-portal's failure response into "cancelled", so on desktops whose
FileChooser portal fails to show a dialog, Save As, Export and Open did nothing and logged
nothing. A cancel quicker than anyone can make one (300 ms) is now taken as that failure: the
dialog is asked for once more without the parent window (GridCraft, which passes none, works on
the reporter's machine), and if that too comes back at once the app shows its built-in browser.
Each step logs a warning.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
* #2999: review — the quick-cancel retry runs only on Linux and the BSDs; Windows and macOS answer a quick cancel as a cancel
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #2108: File › Print scales pages to another paper size
Scale to paper size on File › Print (Word: Pages per sheet › Scale to
paper size): No Scaling or any Layout › Size paper (Letter, Legal, A4, …).
Every page is scaled to the paper and centred, the paper turned to the
page's orientation, in the preview, the print job and Save these pages
as PDF. Native jobs also ask the spooler for that media. The document's
page size is unchanged. New `paper` param on ui.print/ui.printSubmit and
file.printPdf; unknown papers are errors.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* pdf: bookmark destinations from #2364 use the scaled Fit too
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #1230: Insert Caption on pictures and shapes; Page Setup header and footer options
- Right-clicking a picture, shape, text box or chart offers Insert Caption…, as in Word.
- references.caption with a picture or shape selected puts the caption next to the
paragraph holding it, in the same cell or text box (it used to go after the whole table).
- The Caption dialog proposes Figure for a picture selected in a table, not Table.
- Page Setup: Headers and footers check boxes (Different Odd & Even Pages, Different First
Page), applied on OK only if changed, in one undo step; a title page can go unnumbered.
- Tests: engine caption_for_an_object_in_a_cell_stays_in_the_cell; kittest
right_click_on_a_picture_offers_insert_caption and
page_setup_different_first_page_leaves_the_title_page_unnumbered.
- Docs: ui-parity, target-app-parity, ROADMAP progress log.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW
* ui-parity: keep Insert Caption on the context-menu row
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Math straight in the body or a cell (m:oMathPara/m:oMath without w:p,
allowed by EG_BlockLevelElts) gets its own paragraph instead of vanishing.
- mc:AlternateContent Requires="m" picks the equation, not its fallback.
- Inside an equation, runs in w:hyperlink, w:fldSimple, w:dir, w:bdo and
mc:AlternateContent are kept (EG_PContentMath); depth stays bounded.
- w:sym, w:noBreakHyphen and Symbol-font text in math runs become Unicode.
- Layout: n-ary operators, delimiters, accents and group characters the math
font lacks use a fallback font instead of a missing-glyph box.
- Coverage test reads every ECMA-376 §22.1 element and checks no argument's
text is dropped; tests for body-level math, Requires="m", symbols, hostile
nesting and the font fallback. Parity docs and progress log updated.
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
No Color removed the highlight from the selection, but the Highlight
split button kept the colour picked before, so the next click on it
highlighted again. As in Word, No Color now becomes the button's choice
and the next click removes highlighting. The ribbon and the mini toolbar
share one highlight menu (the ribbon's copy is gone); No Color is
translated in the mini toolbar and no longer shows Ctrl+Alt+H beside it
in the ribbon.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #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>
* #1694: -ically adverbs are no longer flagged as misspelled
English spelling accepts "-ally" adverbs built on an "-ic" adjective in the
word list (specific → specifically, scientific → scientifically), as Word does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #1694: only -ic adjectives take -ally
Review (FredApps): the "-ally" → stem rule applied to every known word,
so "desktopally" was accepted. The rule is now "-ically" → "-ic"
(specifically → specific); other words don't take "-ally".
Test: non_ic_words_do_not_take_ally (desktop, house, final, green).
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>
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>
With Different First Page on, page 1 shows the first-page header, but
Insert > Header/Footer/Page Number always wrote the default one (which
page 1 no longer shows), Edit Header without a page went to the default
too, and a double-click in page 1's header area was ignored while
another header was being edited (the usual case: edit the header, tick
Different First Page, double-click page 1). Now hf_part and edit_hf use
the slot of the caret's page, and a double-click in a header/footer
zone switches to the one that page shows even from another header.
Fixes#2826Fixes#2811
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
LibreOffice puts a paragraph indented on both sides on the clipboard as
<blockquote>, its margins in the page's stylesheet. Paste mapped it to the
centred, italic Quote style. On paste only, it now becomes a Normal
paragraph indented by the blockquote's stylesheet margins (else 0.5 in each
side). Opening .html files still maps blockquote to Quote.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A copy or cut of one equation on its own now also puts its MathML in the
platform's MathML clipboard formats, beside the HTML (with MathML, #816) and
the linear text: "MathML" and "MathML Presentation" (UTF-16LE) on Windows,
public.mathml.presentation and public.mathml on macOS. These are the names
W3C MathML 3 section 6.2.4 registers, and Microsoft's "MathML Support in
Microsoft 365" doc says Office checks them first when it pastes math. No
public clipboard format carries OMML. A copy with text around the equation
doesn't offer them, so Word keeps the text.
Not yet verified in Word: the parity docs say MathML clipboard formats, not
an editable paste.
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Tab already follows reading order (next logical cell) and right-to-left
tables (w:bidiVisual) already lay the first cell out at the right. The
bug was that insert.table ignored the caret paragraph's direction, so a
table made while writing RTL text was a left-to-right table and Tab went
rightwards. Like Word, insert.table now makes a right-to-left table when
the caret's paragraph reads right to left.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Indonesian spelling reads the installed Hunspell id_ID dictionary at runtime
(Debian/Ubuntu hunspell-id, LibreOffice dict-id, the Dictionaries folder or
WORDCRAFT_DICTIONARIES), like Czech, Polish and Portuguese: nothing is bundled,
and without a dictionary Indonesian text gets no spelling marks.
- ProofLang::Id for id, id-ID, ind and the legacy in tag
- suggestions reuse the dictionary-driven Czech ranking (MAP/REP/TRY/KEY of
the loaded dictionary), nothing Indonesian hard-coded
- hyphenated reduplication accepted whole or by parts (kupu-kupu, anak-anak);
hyphenated clitics after capitals/abbreviations (KTP-nya, se-Indonesia)
- generic grammar only: repeated word (offers anak-anak first), space before
punctuation, double space, with Indonesian messages
- Review > Language, status bar, Set Proofing Language, File > Options >
Proofing list Indonesian; name translated in every catalog
- tests use a hand-written synthetic dictionary; engine end-to-end test
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
With no system fonts (a headless CLI), Arabic and Persian text drew as
empty boxes: the document font database only loaded the craft-fonts
Japanese and Chinese faces. Load every non-Japanese craft-fonts face.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A shape anchored in a text:p (draw:custom-shape, draw:rect, ...) had its
own text:p read as a body paragraph. That closed the paragraph the shape
sits in, and the text after the shape was dropped. LibreOffice's
MadeByLO7.odt lost three of its seven body paragraphs.
Drawing shapes are now skipped with their text, as text boxes already
are. Groups are still walked, so the pictures inside them are read.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* View Gridlines outlines header and footer tables (#1628)
Table Layout › View Gridlines drew only the body's table cells; a table
in a header or footer showed no gridlines. Walk the page's header and
footer items too.
Fixes#1628
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Test: Placed::Cell names its clip flag (#1617)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- A position wrapped in mc:AlternateContent inside wp:anchor (wp14:pctPosVOffset)
takes the Fallback's wp:posOffset; the shapes were placed at the top of the column.
- A custGeom path that ends where it started without a:close is closed, so it's
filled as in Word; the cover's panel (and its white title) was invisible.
- Credits: add benandez to contributors/people.toml.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>