72 Commits
Author SHA1 Message Date
Martin BallandOPUZZLES 729cd0a5fc Typing: avoid repeated scans at protected typing boundaries (#3614) (#4754)
Lands #4754.

Co-Authored-By: OPUZZLES <109076338+OPUZZLES@users.noreply.github.com>
2026-11-06 16:07:28 +01:00
Martin BallandRatkoR 8c46f4fc1b Pictures: a bold insertion cursor while dragging an inline picture (#4195) (#4208)
Lands #4208.

Co-Authored-By: RatkoR <6648783+RatkoR@users.noreply.github.com>
2026-11-06 15:53:31 +01:00
Martin Ball 247c7feaab i18n: craft-i18n pin follows its main (watermark strings, lv, fr, ar rows) (#5787) 2026-11-06 13:19:38 +01:00
Martin BallandRatkoR 8833c604e0 Layout: a shape dropped just after a page break stays on the new page (#4262) (#4297)
Lands #4297.

Co-Authored-By: RatkoR <6648783+RatkoR@users.noreply.github.com>
2026-11-06 13:12:16 +01:00
Martin BallandD13GOOOO cad8ede1e8 Shapes: pictures and text boxes inside a drawing canvas are selected and moved on their own (#2637) (#2670)
Lands #2670.

Co-Authored-By: D13GOOOO <106814760+D13GOOOO@users.noreply.github.com>
2026-11-06 12:44:48 +01:00
Martin BallandRatkoR beca3f3485 Layout: a float anchored on a later page of a long paragraph is drawn there (#4250) (#4267)
Lands #4267.

Co-Authored-By: RatkoR <6648783+RatkoR@users.noreply.github.com>
2026-11-06 12:13:11 +01:00
Martin Ballandmatstrange ef35d79cb8 Control channel: a command that discards unsaved work is refused, not carried out (#3284)
Lands #3284.

Co-Authored-By: matstrange <54028700+matstrange@users.noreply.github.com>
2026-11-06 11:44:29 +01:00
Martin Ballandfinwarman 3d79fff0ad Layout: reserve flow space below bottom paragraph borders (#2437)
Lands #2437.

Co-Authored-By: finwarman <1339254+finwarman@users.noreply.github.com>
2026-11-06 11:29:32 +01:00
Martin Ballandrabis7575 a951642cd0 Shapes: Shape Format › Size shows the interface unit (#4196) (#4212)
Lands #4212.

Co-Authored-By: rabis7575 <340973947+rabis7575@users.noreply.github.com>
2026-11-06 11:16:36 +01:00
Martin BallandMorzilla dc388cfaab Layout: a line of only pictures takes no descent; multiple spacing adds its share (#3984)
Lands #3984.

Co-Authored-By: Morzilla <3956663+Morzilla@users.noreply.github.com>
2026-11-06 10:52:32 +01:00
Martin BallandD13GOOOO ce2961041b Equations: \int + Space waits for its limits, so _x^y decorates the operator (#4917) (#4938)
Lands #4938.

Co-Authored-By: D13GOOOO <106814760+D13GOOOO@users.noreply.github.com>
2026-11-06 10:37:36 +01:00
Martin BallandOPUZZLES aeb2e550ac Mailings: detect CSV delimiters across multiline quoted headers (#4249)
Lands #4249.

Co-Authored-By: OPUZZLES <109076338+OPUZZLES@users.noreply.github.com>
2026-11-06 10:23:39 +01:00
Martin Ballandpakiat 502256d125 Layout: justified lines keep the width of the spaces before their first character (#4307)
Lands #4307.

Co-Authored-By: pakiat <49967840+pakiat@users.noreply.github.com>
2026-11-06 10:07:41 +01:00
Martin Ballandrabis7575 615597bda1 Shapes: the resize arrow over a turned object's handle turns with it (#3932) (#3939)
Lands #3939.

Co-Authored-By: rabis7575 <340973947+rabis7575@users.noreply.github.com>
2026-11-06 09:53:44 +01:00
Martin Ballandfluorine 60535464b2 Font dialog: the preview also shows the font, small caps, super/subscript and hidden (#4058) (#4059)
Lands #4059.

Co-Authored-By: fluorine <1832604+fluorine@users.noreply.github.com>
2026-11-06 09:38:47 +01:00
Martin Ballandfluorine ca21cf0ece Ribbon: a contextual tab that appears glides in so the eye finds it (#3131)
Lands #3131.

Co-Authored-By: fluorine <1832604+fluorine@users.noreply.github.com>
2026-11-06 09:23:50 +01:00
Martin BallandOPUZZLES ae0692ee66 Layout: avoid repeated text-box lookups during page drawing (#4837)
Lands #4837.

Co-Authored-By: OPUZZLES <109076338+OPUZZLES@users.noreply.github.com>
2026-11-06 02:33:02 +01:00
Martin BallandOPUZZLES 4a48a58496 References: index source lookups during citation refresh (#4890)
Lands #4890.

Co-Authored-By: OPUZZLES <109076338+OPUZZLES@users.noreply.github.com>
2026-11-06 02:18:01 +01:00
Martin Ballandpick2510 9a9246915c Layout: Times and Courier take Times New Roman's and Courier New's line spacing (#3241)
Lands #3241.

Co-Authored-By: pick2510 <4068086+pick2510@users.noreply.github.com>
2026-11-06 02:03:34 +01:00
8c57b0775c Status bar: the live count shows words, characters, or characters without spaces (#3187)
Lands #3187.

Co-Authored-By: vangel76 <271991477+vangel76@users.noreply.github.com>
Co-Authored-By: SlavenDj <53652906+SlavenDj@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-06 00:27:28 +01:00
28925fac8c Linux: a setting chooses the display server (Wayland / X11) (#3189)
Lands #3189.

Co-Authored-By: Yasas-Sri <164534752+Yasas-Sri@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: vangel76 <271991477+vangel76@users.noreply.github.com>
2026-11-06 00:04:18 +01:00
Martin Ballandfinwarman 35e960c850 Align ordinary tabs with paragraph alignment (#3915)
Lands #3915.

Co-Authored-By: finwarman <1339254+finwarman@users.noreply.github.com>
2026-11-05 23:55:58 +01:00
Martin BallandSkaywalker 9d5924c8b7 speech: Read Aloud on Linux falls back to Speech Dispatcher (#4576)
Lands #4576.

Co-Authored-By: Skaywalker <115165402+Skaywalker@users.noreply.github.com>
2026-11-05 23:36:40 +01:00
2fb91eece2 Clipboard: copy and paste RTF (#2630)
Lands #2630.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: dkhlapov <19991457+dkhlapov@users.noreply.github.com>
2026-11-05 23:36:19 +01:00
c21d5af2a6 #4181: a click just outside a floating table skips its turned cell text (#4189)
Lands #4189.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: rabis7575 <340973947+rabis7575@users.noreply.github.com>
2026-11-05 23:35:58 +01:00
Martin Ballandmrzv ed92cb5838 doc: decode numeric XML references in legacy form labels (#3883)
Lands #3883.

Co-Authored-By: mrzv <720816+mrzv@users.noreply.github.com>
2026-11-05 23:35:39 +01:00
Martin BallandMMexicann ec3e14f98b MHT: preserve quoted MIME parameter boundaries (#5604)
Lands #5604.

Co-Authored-By: MMexicann <44814932+MMexicann@users.noreply.github.com>
2026-11-05 23:35:12 +01:00
Martin BallandMMexicann 83bef65a1d CLI: reject malformed conversion format options (#5610)
Lands #5610.

Co-Authored-By: MMexicann <44814932+MMexicann@users.noreply.github.com>
2026-11-05 23:09:30 +01:00
db8d944c5c Fonts: reuse shared MATH construction records (#4660)
Co-Authored-By: OPUZZLES <109076338+OPUZZLES@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 18:50:25 +01:00
Martin BallandClaude Opus 5.5 ea25e5dd3a CP4.3: craft-i18n.rev at 9d6d54d (craft-i18n#145): region names and the Language (Region) template in 35 locales (#5522)
Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 03:12:45 +01:00
Martin BallandClaude Opus 5.5 d64ef7c3f9 CP4.3: craft-i18n.rev at cde4900 (craft-i18n#136): Options › Language, missing-pack bar and Language pack language-name rows in 35 locales (#5518)
Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 00:57:30 +00:00
Martin BallandClaude Sonnet 5.5 bff0d1d925 #3867 follow-up: header repeats count canvas members, one budget per layout; contours and chain widths don't depend on cache history (#4385)
* #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>
2026-11-04 00:45:19 +00:00
Martin BallandClaude Opus 5.5 682ce4318a CP4.3: craft-proof pin at craft-libs 01315d9: French elision suggestions first; Valencian reads its pack (#5509)
* CP4.3: craft-proof pin at craft-libs main 01315d9 (#589, #590): French elision suggestions first; ca-ES-valencia reads the ca-valencia pack

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

* CP4.3: review — ROADMAP has one status record; the older copy left by a merge is dropped (CodeRabbit)

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

* CP4.3: review — French is proofed from its Language pack in the support tables, not "no proofing" (CodeRabbit)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 23:06:35 +00:00
Martin BallandClaude Opus 5.5 ddf88c39df CP4.3: craft-i18n.rev at 20467b6 (#116 merged): builds embed the Language data language-packs-2 was built from (#5385)
Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 22:10:18 +00:00
Martin BallandClaude Opus 5.5 5b5810fe1f CI: Scorecard hardening — release token write only on the release job, SECURITY.md, patched ctutils (#4975)
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>
2026-10-30 13:00:27 -04:00
Martin BallandClaude Sonnet 5.5 ed1296a1d4 #3871 follow-up: .doc picture decode charges zlib input and DIB copies; budget scales with the file (#4382)
* #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>
2026-10-25 22:37:10 -04:00
Martin BallandClaude Opus 5.5 b654049545 #3874: review — text box paste charges only what it copies; stories sharing Arc blocks (PDF imports) keep their text (#4381)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 22:30:32 -04:00
Martin BallandClaude Opus 5.5 4095cc3413 Security: bounded file reads, Read Aloud and macOS signing temp file (#3680, #3666) (#3877)
* #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>
2026-10-25 12:32:19 -04:00
Martin BallandClaude Opus 5.5 85db9cfef5 #4177: regex Replace expands capture groups (#4201)
* 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>
2026-10-25 10:53:13 -04:00
5a6d2b6070 File › New: personal templates from a user templates folder (#2852) (#3133)
* #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>
2026-10-21 20:18:50 -04:00
dbebb2eca7 #2617: centre equation-array rows without & (#2690)
* #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>
2026-10-21 20:04:57 -04:00
4913b20259 #1281: English dialects in proofing — en-GB, en-CA, en-AU… flag each other's spellings (#1045) (#2632)
* #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>
2026-10-21 19:52:57 -04:00
67ec657af6 Legacy menu keys: Alt, V, U and 17 more Word 2003 sequences (#1697) (#1769)
* #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>
2026-10-21 19:30:13 -04:00
6b01aa967f Shape fills: gradients and transparency (#1500) (#1605)
* #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>
2026-10-21 19:12:26 -04:00
726652f088 Focus fills the screen; Full Screen View is a bindable command (#1678) (#1733)
* #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>
2026-10-21 19:09:36 -04:00
ff3c7d8d25 Layout: text above a float's anchor wraps around it (#2048) (#2237)
* #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>
2026-10-21 18:48:29 -04:00
c02ceef673 Borders: triple borders draw as three lines on screen and in PDF (#1740) (#2210)
* #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>
2026-10-21 18:32:33 -04:00
2451b5a421 The I-beam pointer shows on white paper on Windows (#1746) (#2230)
* #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>
2026-10-21 18:17:49 -04:00
71aeebc2da Double-clicking a later page's footer opens it without scrolling up (#2103) (#2214)
* #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>
2026-10-21 18:03:45 -04:00
b4a1d34996 #1792: Check spelling as you type is remembered across restarts (#1837)
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>
2026-10-21 18:03:39 -04:00
72a08a16a9 #1841: mini toolbar test follows #1501's visible anchor (#1842)
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>
2026-10-21 18:01:15 -04:00
Martin BallandClaude Opus 5.5 7d453e7217 #3243: File › Recover follows the app theme (#3435)
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>
2026-10-21 17:47:38 -04:00
Martin BallandClaude Opus 5.5 435be0a4cb #2972: regression tests for File page AccessKit focus paths (mac chevron, Options back, Blank document) (#3019)
* #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>
2026-10-19 03:05:06 -04:00
Martin BallandClaude Opus 5.5 6950c22d73 #2999: a file dialog that "cancels" at once asks again without a parent, then shows the built-in browser (#3030)
* #2999: a file dialog that "cancels" at once asks again without a parent, then shows the built-in browser

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

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

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

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 03:04:59 -04:00
c91c1793a3 #2108: File › Print scales pages to another paper size (#2699)
* #2108: File › Print scales pages to another paper size

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

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

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:48:17 -04:00
8c812bf1d6 #1230: Insert Caption on pictures and shapes; Different First Page in Page Setup (#2615)
* #1230: Insert Caption on pictures and shapes; Page Setup header and footer options

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

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

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

---------

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


Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:45:01 -04:00
11f8881cd2 #537: test that System pins the title bar to the desktop's light/dark answer on Linux (#2559)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:41:42 -04:00
63ee34c557 #2105: Highlight No Color sticks to the button (#2208)
No Color removed the highlight from the selection, but the Highlight
split button kept the colour picked before, so the next click on it
highlighted again. As in Word, No Color now becomes the button's choice
and the next click removes highlighting. The ribbon and the mini toolbar
share one highlight menu (the ribbon's copy is gone); No Color is
translated in the mini toolbar and no longer shows Ctrl+Alt+H beside it
in the ribbon.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:40:55 -04:00
95b5c71f80 Save writes the chosen file when its folder refuses a temporary file (macOS privacy protection) (#2138) (#2167)
* #2138: save writes the chosen file when its folder refuses a temporary file (macOS privacy protection)

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:38:04 -04:00
d4a3292834 Proofing: -ically adverbs such as "specifically" are no longer flagged (#1694) (#1854)
* #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>
2026-10-18 09:56:04 -04:00
ab57696ba7 Draw undrawable private-use list bullets as the level's default bullet, not an icon-font glyph (#1683) (#2206)
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>
2026-10-18 04:03:35 -04:00
a30a1866e2 Web: keep preferences such as dark mode between visits (#2132) (#2205)
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>
2026-10-18 04:03:16 -04:00
8fb4d44d2c Different First Page: page 1's header can be edited and inserted (#2826) (#2833)
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 #2826
Fixes #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>
2026-10-18 03:47:13 -04:00
abb36aceb2 #1270: pasted HTML blockquote is an indented paragraph, not Quote (#2737)
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>
2026-10-18 03:46:54 -04:00
ce38b0c372 #1297: copied equations go in the MathML clipboard formats Word reads (#2614)
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>
2026-10-18 03:46:15 -04:00
4d8204c2f2 #2634: a table inserted in right-to-left text is right to left, so Tab moves left (#2688)
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>
2026-10-18 03:43:47 -04:00
4950690479 #498: Indonesian (id-ID) spelling proofing (#2576)
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>
2026-10-18 03:42:52 -04:00
Martin BallandClaude Opus 5.5 e5a055a310 CLI PDF export: Arabic and Persian fall back to the craft-fonts Arabic face (#1652) (#1668)
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>
2026-10-14 21:30:09 -04:00
47907a820b ODT import: text after a drawing shape stays in its paragraph (#1654) (#1657)
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>
2026-10-14 21:22:00 -04:00
116e7697a1 View Gridlines outlines header and footer tables (#1628) (#1633)
* 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>
2026-10-14 21:03:53 -04:00
93175f979f Word 365 cover pages: page-relative positions and freeforms drawn back to their start (#743) (#1487)
- 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>
2026-10-14 13:43:24 -04:00