Merge branch 'w3' into w6

# Conflicts:
#	ROADMAP.md
#	crates/engine/src/lib.rs
#	crates/engine/src/session.rs
#	crates/ui-egui/Cargo.toml
#	crates/ui-egui/src/lib.rs
#	docs/gaps.md
#	docs/parity-checklist.md
#	docs/ui-parity.md
This commit is contained in:
aiongg
2026-10-26 03:22:33 +00:00
314 changed files with 32755 additions and 3509 deletions
+3 -1
View File
@@ -43,7 +43,9 @@ cat ~/.config/wordcraft/control-key.local-7981 # Linux, outside Flatpak, port
`wordcraft-cli mcp --connect` takes the key from `WORDCRAFT_CONTROL_KEY`, or else from the key
file for the port it connects to, and reads it again on every new connection (the app may have
restarted). It sends the key only to loopback addresses (`127.0.0.0/8`, `::1`, `localhost`). For
restarted). It sends the key only to the app's own address, `127.0.0.1` (`localhost` connects
there too, never to `::1`): another loopback address such as `::1` or `127.0.0.2` is not where the
app listens, so whoever listens there gets no key (#3699). For
any other host it sends no key, says so, and the app refuses its requests: to drive an app on
another machine, forward its port to `127.0.0.1` (for example `ssh -L 7981:127.0.0.1:7981 host`)
and set `WORDCRAFT_CONTROL_KEY` to that app's key.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+48 -5
View File
File diff suppressed because one or more lines are too long
+50 -11
View File
File diff suppressed because one or more lines are too long
+9 -3
View File
@@ -1,6 +1,6 @@
# Localization parity with Microsoft Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-23 · **Change:** minor (the interface catalogs moved to storytold/craft-i18n and are looked up through the `craft-i18n` crate, embedded at build time from the commit in `craft-i18n.rev`; previously trivial: interface-language tests: a missing translation never fails a test, `cargo xtask i18n-report` in `cargo xtask ci` prints the strings each language lacks, and lookup and template tests use inline fixture catalogs or the pseudo-language; previously trivial: interface audit: missing catalog entries are reported per language and don't fail the audit; previously minor: disabled-control reasons in ribbon hints translated in every catalog, #2678; previously trivial: Slovenian interface catalog: every key of every other catalog, 2,614 entries; previously minor: Romanian interface catalog; previously trivial: the File › Home greeting is translated in every catalog; previously trivial: Japanese row: ruby (Phonetic Guide) is supported and HTML export/import keeps it, #1681; previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously minor: English dialects: en-GB, en-CA, en-AU and the other British-spelling Englishes proof their own spellings and flag the other dialect's, #1281, #1045; previously minor: how to install the Hunspell dictionaries on Windows, macOS and Linux: `docs/spelling-dictionaries.md`; previously minor: Read Aloud speaks each sentence in its own language, #1843; previously minor: Indonesian proofing: id-ID spelling from the installed Hunspell dictionary, dictionary-driven suggestions, generic checks with Indonesian messages, #498; previously trivial: print-area strings translated in every catalog; previously trivial: Italian: the interface audit checks the Italian catalog, `Privacy Settings` wording; previously trivial: Italian grammar: `po'`, `qual è`, `È`, `un`/`un'` rules; more elisions and truncations in spelling; previously trivial: Czech interface catalog completed, 2,447 entries; previously trivial: interface audit runs in CI again: size labels translated, 20 strings in every catalog, Read Aloud error race fixed, #1128; previously trivial: Review › Language reaches every language again — Set Proofing Language… and Set As Default, #860; previously minor: Italian proofing: it-IT spelling from the installed Hunspell dictionary, accent-first suggestions, conservative grammar with Italian messages; previously minor: Portuguese proofing; previously trivial: Turkish interface catalog completed, 2,325 entries, #774; previously minor: Hungarian interface added; previously trivial: sorting and table numbers follow the text's language, #836, #837; previously minor: Polish interface; Polish proofing — spelling, grammar, hyphenation — and a proofing language per run; previously minor: Turkish interface added; previously major: first version; catalogs measured from `crates/ui-egui/src/i18n`, Word's languages from its bundle listing) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (the egui 0.37 migration preserves the equation “Delete Around Caret in Equation” translation in all 27 catalogs through the shared catalog pin; previously minor: Spanish proofing: es-ES, es-MX, es-419 and es-UY from the installed Hunspell dictionaries, Uruguayan `vos` forms, opening `¿`/`¡` checks, #3360, #3553; previously minor: the interface catalogs moved to storytold/craft-i18n and are looked up through the `craft-i18n` crate, embedded at build time from the commit in `craft-i18n.rev`; previously trivial: interface-language tests: a missing translation never fails a test, `cargo xtask i18n-report` in `cargo xtask ci` prints the strings each language lacks, and lookup and template tests use inline fixture catalogs or the pseudo-language; previously trivial: interface audit: missing catalog entries are reported per language and don't fail the audit; previously minor: disabled-control reasons in ribbon hints translated in every catalog, #2678; previously trivial: Slovenian interface catalog: every key of every other catalog, 2,614 entries; previously minor: Romanian interface catalog; previously trivial: the File › Home greeting is translated in every catalog; previously trivial: Japanese row: ruby (Phonetic Guide) is supported and HTML export/import keeps it, #1681; previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously minor: English dialects: en-GB, en-CA, en-AU and the other British-spelling Englishes proof their own spellings and flag the other dialect's, #1281, #1045; previously minor: how to install the Hunspell dictionaries on Windows, macOS and Linux: `docs/spelling-dictionaries.md`; previously minor: Read Aloud speaks each sentence in its own language, #1843; previously minor: Indonesian proofing: id-ID spelling from the installed Hunspell dictionary, dictionary-driven suggestions, generic checks with Indonesian messages, #498; previously trivial: print-area strings translated in every catalog; previously trivial: Italian: the interface audit checks the Italian catalog, `Privacy Settings` wording; previously trivial: Italian grammar: `po'`, `qual è`, `È`, `un`/`un'` rules; more elisions and truncations in spelling; previously trivial: Czech interface catalog completed, 2,447 entries; previously trivial: interface audit runs in CI again: size labels translated, 20 strings in every catalog, Read Aloud error race fixed, #1128; previously trivial: Review › Language reaches every language again — Set Proofing Language… and Set As Default, #860; previously minor: Italian proofing: it-IT spelling from the installed Hunspell dictionary, accent-first suggestions, conservative grammar with Italian messages; previously minor: Portuguese proofing; previously trivial: Turkish interface catalog completed, 2,325 entries, #774; previously minor: Hungarian interface added; previously trivial: sorting and table numbers follow the text's language, #836, #837; previously minor: Polish interface; Polish proofing — spelling, grammar, hyphenation — and a proofing language per run; previously minor: Turkish interface added; previously major: first version; catalogs measured from `crates/ui-egui/src/i18n`, Word's languages from its bundle listing) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
Interface languages, script support and proofing per language. How translation works (adding a
language, the embedded catalogs): `crates/ui-egui/src/i18n/mod.rs`. The catalogs, their format and
@@ -40,9 +40,9 @@ native-speaker review (human).
Read Aloud) aren't wrapped in `tl!` yet, so they show in English in every
language: the true share of visible strings is **~93–95%** (estimated).
- Pending PRs: Norwegian Bokmål (#208) and Nynorsk (#210), Hebrew with RTL (#20).
- **Proofing: English, Polish, Portuguese, Italian, Russian, Czech and Indonesian**, chosen per run from its language (`w:lang`, resolved through
- **Proofing: English, Polish, Portuguese, Italian, Russian, Czech, Indonesian and Spanish**, chosen per run from its language (`w:lang`, resolved through
the styles; Review › Language and the status bar set it: a short list, plus Set Proofing
Language… for any of 30 languages and Set As Default for the document and future blank documents). Languages without proofing tools get
Language… for any of 38 languages and Set As Default for the document and future blank documents). Languages without proofing tools get
the styles; Review › Language and the status bar set it). Languages without proofing tools get
no spelling or grammar marks (as in Word without the language's tools) instead of being flagged
as English. Detail in [Proofing per language](#proofing-per-language).
@@ -103,6 +103,9 @@ gallery, watermarks, paper sizes and preview tiles. Counts use count-neutral wor
When no saved proofing-language default exists, Polish, Portuguese and Czech interfaces write
new documents and opened TXT/Markdown text in their proofing languages (`pl-PL`, `pt-BR`, `cs-CZ`).
A Spanish interface writes them in the region's Spanish: `es-` plus the system region when that
region's language is Spanish (`es-UY` on a Uruguayan system, `es-MX` on a Mexican one), else
Spain's `es-ES` (#3360).
**Interface audit (runs in CI).** `cargo xtask i18n-report` runs `untranslated_interface_text`,
which lays the app out in a pseudo-language (every tab, File page, dialog, pane, the right-click
@@ -130,6 +133,7 @@ Set Proofing Language › Set As Default keeps the chosen language in user prefe
| Turkish (`tr`, any region) | The Hunspell `tr_TR` dictionary installed on the system (same places as Polish; not bundled), read at runtime; proper nouns capitalized, apostrophe suffixes (`Ankara'da`, `WordCraft'ta`) and hyphenated compounds | missing diacritics / ASCII restoration first (`turkce` → `Türkçe`, `isik` → `ışık`), common confusion table (`yalnış` → `yanlış`, `herşey` → `her şey`), detached suffixes (`bende` → `ben de`, `geldinmi` → `geldi mi`), one typo | Turkish messages: repeated word (idiomatic reduplications allowed: `yavaş yavaş`), space before punctuation, double space, sentence capital (knows `vb.`, `vs.`, `prof.`, `dr.`), no comma before/after conjunctions (`ve`, `veya`, `de`), doubled punctuation | Turkish syllable rules (TDK rules); no external pattern file or word list needed |
| German (`de`, any region) | — (no marks yet, #1564) | — | — | TeX German patterns for the reformed spelling (Trennmustermannschaft; MIT), no exception words; Swiss text uses them too. The patterns leave ambiguous compound joints unbroken on purpose (`Staubecken`, `Urinstinkt`), so German text can hyphenate a little less than in Word |
| Indonesian (`id`, `id-ID`, `ind`, legacy `in`) | The system Hunspell `id_ID` dictionary, read at runtime (Debian/Ubuntu: `hunspell-id`; LibreOffice's `dict-id`; the same system, LibreOffice, Dictionaries folder and `WORDCRAFT_DICTIONARIES` search paths as Polish). No dictionary data is bundled; without an installed dictionary spelling stays off. Hyphenated reduplication is accepted whole (`kupu-kupu`) or by parts (`anak-anak`); hyphenated clitics after capitals and abbreviations (`KTP-nya`, `se-Indonesia`) are accepted | the same dictionary-driven ranking as Czech: MAP/REP/TRY/KEY tables of the loaded dictionary, missing spaces and a stem scan; nothing Indonesian hard-coded | Indonesian messages for repeated words (offering the hyphenated reduplication `anak-anak` first, then the single word), space before punctuation and double spaces only; no Indonesian-specific grammar rules | existing English fallback, as for `Other`; no Indonesian patterns bundled |
| Spanish (`es`, `spa`; by region: Spain and no region, `es-MX`, `es-UY`, and `es-419` with the other countries of the Americas) | The system Hunspell dictionary of each variety, read at runtime, the first file name found in this order: Spain `es_ES`, `es_ANY`, `es`; Mexico `es_MX`, `es_419`, `es_ANY`, `es`; Latin America `es_419`, `es_ANY`, `es`, then the ones LibreOffice ships (it has no `es_419`): `es_US`, `es_MX`, `es_AR`; Uruguay `es_UY`, the Rioplatense `es_AR`, `es_419`, `es_ANY`, `es` (each regional name also with a hyphen, `es-MX`); LibreOffice's `dict-es` and Debian/Ubuntu's `hunspell-es` have them (RLA-ES). No dictionary data is bundled; without one the status bar says the variety has no dictionary. The RLA-ES files load since their `☎️` flag is read (#2164). Hyphenated compounds are checked part by part (`teórico-práctico`). Uruguayan Spanish also accepts the `vos` forms of every verb in the dictionary: imperatives (`vení`, `tené`, `andá`), with pronouns (`decime`, `fijate`, `decímelo`) and the present (`tenés`, `querés`) | the same dictionary-driven ranking as Czech, from the variety's dictionary (its MAP restores accents and `ñ` first: `tambien` → `también`; the regional REP tables carry `vosotros` → `ustedes`); in Uruguay a `vos` form typed without its accent gets it back first (`veni` → `vení`) | Spanish messages for repeated words, space before punctuation and double spaces; a closing `?` or `!` without its opening `¿` or `¡` (the sentence with the mark is offered; `¡…?` pairs, `(?)` and `(!)` are allowed) and a space after an opening mark | existing English fallback, as for `Other`; no Spanish patterns bundled |
| Any other | — (no marks) | — | — | the English patterns, as before |
The dictionary file names each language looks for, the folders searched on each system and how to
@@ -219,10 +223,12 @@ WordCraft keeps 2,54 cm).
| Date | Change | Summary |
|---|---|---|
| 2026-10-24 | minor | Preserved “Delete Around Caret in Equation” in all 27 shared WordCraft catalogs; the native/release pin and Flatpak source advance together. |
| 2026-10-23 | minor | Interface catalogs moved to storytold/craft-i18n (`locales/<locale>/wordcraft.tsv`), looked up through craft-libs' `craft-i18n` crate and embedded from the pinned commit (`craft-i18n.rev`); strings other Crafting Apps already translate in the shared catalog now show translated; the "Open" paragraph-spacing preset no longer shows the file verb |
| 2026-10-23 | trivial | Lookup, context, template, style-name and command-search tests use inline fixture catalogs or the pseudo-language; tests that asserted one language's catalog wording removed |
| 2026-10-23 | trivial | Interface-language tests: a missing translation falls back to English and never fails a test; `cargo xtask i18n-report` (part of `cargo xtask ci`) prints the strings each language lacks; single-language coverage tests removed |
| 2026-10-23 | trivial | Interface audit: strings a catalog lacks are printed per language, for every language, instead of failing for Russian, Hungarian, Turkish and Bulgarian |
| 2026-10-22 | minor | Spanish proofing (#3360, #3553): es-ES, es-MX, es-419 and es-UY each read their installed Hunspell dictionary (RLA-ES, not bundled), Czech's dictionary-driven suggestions, Uruguayan `vos` forms (imperatives included), repeated-word, spacing and opening-mark checks with Spanish messages; Review › Language, the status bar, Set Proofing Language and File › Options › Proofing list the four varieties, and a Spanish interface writes new documents in the region's Spanish; estimates unchanged |
| 2026-10-21 | trivial | Japanese row: ruby (Phonetic Guide) is supported over its base text and HTML export/import keeps it (#1681); the row had listed ruby as missing |
| 2026-10-20 | trivial | Russian sorting keeps ё with е and recognises decomposed ё/й; synthetic ordering, case and index-heading regressions; broader parity estimates unchanged |
| 2026-10-20 | trivial | File › Home's greeting (Good morning / afternoon / evening) added to the nine catalogs that lacked it (de, et, ja, pt-br, sr, sr-latn, uk, zh-hans, zh-hant); a test keeps it in every language |
+16 -5
View File
@@ -13,7 +13,10 @@ Claude Code: `claude mcp add wordcraft -- wordcraft-cli mcp`.
With `--connect`, the bridge sends the app's control key, which it reads from
`WORDCRAFT_CONTROL_KEY` or from the key file the app writes at start. It sends the key only to
loopback addresses; see [Keys](control-protocol.md#keys).
loopback addresses; see [Keys](control-protocol.md#keys). The bridge serves even when the app
isn't open yet: tool calls return "not reachable … will retry on each call" and succeed once the
app's port opens (#4078), so a client that starts before WordCraft doesn't lose the server for
its whole session.
## Scripts from the command line
@@ -66,11 +69,16 @@ config works across the suite) keep the agent to the folders you give it:
`file.newFromTemplate`, `insert.textFromFile` and `insert.object`, `insert.picture` and
`picture.change` with a `path`, `mailings.recipients` with a `path` (csv, tsv, txt, xlsx, xlsm,
ods), `review.compare` (`path` and `original`), the templates and documents `tools.updateStyles`,
`styles.organizer` and `styles.copyFrom` read. The pictures an opened HTML file names are read
from beside that file, so they are inside the read root too (a link that leads out is not followed).
`styles.organizer` and `styles.copyFrom` read, and the folders `file.options` takes
(`dictionariesFolder`, `userTemplatesFolder`). The pictures an opened HTML file names are read
from beside that file and must be inside the read root too, even when the file was opened through
a link (#3736).
- Every file a command writes must lie inside the **write root**: `save_document`, `file.save`,
`file.saveAs`, `file.rename` (the file and its new name), `file.exportPdf`, `file.exportPng`, `file.printPdf`,
`file.saveTemplate`, and `mailings.finish` with a `path`.
`file.saveTemplate`, and `mailings.finish` with a `path`. `font.add` and `font.remove` change the
user font folder, so they are refused when it is outside the write root (#3692). A save replaces
the name it is given, so that name must be inside the write root as well as what it leads to:
a link outside the root pointing in is refused (#3735).
- Read and write access are separate. Give the same folder twice for both; a root left out grants
none of its access (with only `--automation-read-root`, nothing can be written to disk). Without
either flag nothing changes.
@@ -112,7 +120,10 @@ files it was given rather than guarding against other software on the computer.
| `new_document` | blank, sample, letter, resume, report; opens beside the open documents |
| `open_document` / `save_document` | docx, doc (open only), odt, rtf, html, md, tex, txt, json; save also pdf, png |
| `list_documents` | the open documents: `id`, `title`, `path`, `dirty`, `active` |
| `type_text` | type at the caret (`paragraphs: true` splits lines into paragraphs) |
| `type_text` | type at the caret (`paragraphs: true` splits lines into paragraphs: one undo
step; each break is the Enter it stands for, so AutoFormat and AutoCorrect run and a
one-line control refuses; at most 10,000 lines — the cap is `text.insert`'s own, so a
control-channel or CLI call is held to it too) |
| `select_text` | select the n-th occurrence of some text |
| `get_text` / `inspect_document` | read the document back (verify without screenshots) |
| `render_page` | a page as PNG |
+7 -3
View File
@@ -2,7 +2,7 @@
Generated by `cargo xtask parity` (`wordcraft-cli parity --markdown`): the word-processor feature catalog (`crates/engine/src/catalog.rs`) compared with the live command registry.
**502 of 517 catalog features have commands (97.1%).**
**505 of 520 catalog features have commands (97.1%).**
| Tab | Live | Total |
|---|---|---|
@@ -17,7 +17,8 @@ Generated by `cargo xtask parity` (`wordcraft-cli parity --markdown`): the word-
| References | 25 | 25 |
| Mailings | 23 | 23 |
| Review | 29 | 31 |
| View | 25 | 29 |
| View | 26 | 29 |
| Read Mode | 2 | 3 |
| Developer | 11 | 11 |
| Table Design | 8 | 8 |
| Table Layout | 33 | 33 |
@@ -60,7 +61,10 @@ Generated by `cargo xtask parity` (`wordcraft-cli parity --markdown`): the word-
- Window › Arrange All (`view.arrangeAll`)
- Window › View Side by Side (`view.sideBySide`)
- Window › Synchronous Scrolling (`view.syncScroll`)
- Window › Switch Windows (`view.switchWindows`)
### Read Mode
- View › Layout (`view.readLayout`)
### Equation
+1 -1
View File
@@ -11,7 +11,7 @@ this file has the WordCraft specifics.
| Platform | Artifacts | Signing |
|---|---|---|
| macOS | universal `WordCraft.app` in a `.dmg`, universal CLI zip | Developer ID + notarization (`APPLE_*` secrets) |
| Windows | x64, x86 and arm64 `.msi` + portable `.zip` | Azure Trusted Signing (`AZURE_*` secrets) |
| Windows | x64, x86 and arm64 `.msi` (per-machine, and a per-user `-windows-user-<arch>.msi` that needs no admin rights) + portable `.zip` | Azure Trusted Signing (`AZURE_*` secrets) |
| Linux | x86_64 and aarch64 `.AppImage` (+ `.zsync` for AppImageUpdate), `.flatpak` bundle, `.deb`, `.rpm`, `.tar.gz`; Flathub manifest | checksums |
| Linux riscv64 | `.tar.gz` (cross-compiled on `ubuntu-24.04`, glibc >= 2.39; CLI smoke-tested under QEMU) | checksums |
| FreeBSD | x86_64 `.tar.gz` | checksums |
+3 -2
View File
@@ -1,6 +1,6 @@
# WordCraft roadmap: milestones and what's next
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (M6: Manage Sources imports Word's `Sources.xml`, #2640; previously minor: Czech proofing review fixes; previously minor: Portuguese proofing; previously minor: Polish interface and Polish proofing; previously trivial: M7: formatting revisions landed, #41; previously trivial: M5: Draw Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: alpha gate checked: six core workflows pass; previously major: milestones moved here from ROADMAP.md; Current focus set from the re-measured gaps) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** trivial (egui 0.37 migration: 27 interface languages remain pinned by `craft-i18n.rev` (9205108…), and the input-method handling has synthetic regressions while managed CI passed and native acceptance (#155–#164) remains open; previously trivial: M6: Manage Sources imports Word's `Sources.xml`, #2640; previously minor: Czech proofing review fixes; previously minor: Portuguese proofing; previously minor: Polish interface and Polish proofing; previously trivial: M7: formatting revisions landed, #41; previously trivial: M5: Draw Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: alpha gate checked: six core workflows pass; previously major: milestones moved here from ROADMAP.md; Current focus set from the re-measured gaps) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
Forward-looking plan. The summary is [`ROADMAP.md`](../ROADMAP.md); the ranked work list is
[`gaps.md`](gaps.md); the numbers are in [`target-app-parity.md`](target-app-parity.md). Hours are
@@ -62,7 +62,7 @@ partial rows are fidelity gaps (beta work), not broken workflows. WordCraft stay
| M12 | Formats breadth (PDF, ODT, RTF, HTML, MD, TXT, LaTeX, `.doc` import) | done (first versions); `.doc` write, encryption, minor formats missing | 60–100 h |
| M13 | Performance budgets | on track (1.4 ms relayout); large real-world documents unmeasured | 10–20 h |
| M14 | 1.0 polish, packaging, signing | releases v0.1.0–v0.4.0 published (signed macOS, Windows MSI x64/x86/arm64, Linux, FreeBSD, web) | — |
| M15 | Localization (12 key languages, RTL interface, proofing languages) | 15 UI languages; English, Polish and Portuguese proofing plus Czech spelling and generic checks (system Hunspell; English hyphenation fallback), a proofing language per run | 70–115 h |
| M15 | Localization (12 key languages, RTL interface, proofing languages) | 27 UI languages from the shared catalogs pinned by `craft-i18n.rev` (`9205108c…`); proofing: English, Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech, Indonesian and Spanish, plus Bulgarian and German hyphenation only; native-speaker review and native IME acceptance (#155–#164) remain open | 70–115 h |
| M16 | Right-to-left and East Asian typography | RTL paragraphs done (#207) | 30–45 h |
## Next after beta
@@ -74,6 +74,7 @@ languages, screen-reader access, ecosystem decisions (VBA, add-ins, cloud), an i
| Date | Change | Summary |
|---|---|---|
| 2026-10-25 | trivial | egui 0.37 migration: 27 interface languages remain pinned by `craft-i18n.rev` (9205108…); input-method surrounding-delete and commit handling has synthetic regressions only, with managed CI passing; native acceptance (#155–#164) and native-speaker review remain open |
| 2026-10-18 | trivial | M6: Manage Sources imports Word's `Sources.xml` (#2640) |
| 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks |
| 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged |
+1 -1
View File
@@ -2,7 +2,7 @@
WordCraft uses `craft-pdf` with default features disabled and `write` enabled. Both that dependency
and the required root Krilla patch use the immutable `storytold/craft-libs` revision
`9f75d3dc19b4b514c4f3d4847ed1ec1b0e86b4ef`. Rust 1.95 or newer is required. The revision must be
`e8b4dde5137fe41823326b55018c31302218d3a4`. Rust 1.95 or newer is required. The revision must be
available from the remote before a fresh checkout can resolve it. The shared writer checks a
backend marker at compile time so an unpatched Krilla cannot replace the required backend.
+19 -5
View File
@@ -1,9 +1,9 @@
# Installing spelling dictionaries
> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-10-22 · **Change:** trivial (every Hunspell language shows (no dictionary), not only Polish and Portuguese, and Indonesian is listed, #3150; previously trivial: ISO 8859-1 dictionaries are read, #2824)
> **Last reviewed:** 2026-10-16 · **Last updated:** 2026-10-23 · **Change:** minor (ISO 8859-9 Turkish dictionaries are read, #3692; previously minor: Spanish: `es_ES`, `es_MX`, `es_419` and `es_UY` with their fallbacks, #3360, #3553; previously trivial: every Hunspell language shows (no dictionary), not only Polish and Portuguese, and Indonesian is listed, #3150; previously trivial: ISO 8859-1 dictionaries are read, #2824)
English spelling works out of the box. Polish, Russian, Portuguese, Czech, Italian, Croatian,
Turkish and Indonesian spelling use a standard **Hunspell** dictionary (an `.aff` and a `.dic` file) that
Turkish, Indonesian and Spanish spelling use a standard **Hunspell** dictionary (an `.aff` and a `.dic` file) that
WordCraft reads at runtime. These dictionaries are not bundled: they are large, and several are
GPL or LGPL licensed. Without one, the language isn't checked. The status bar then shows the
language with **(no dictionary)**, for example *Português (Brasil) (no dictionary)*.
@@ -30,12 +30,23 @@ WordCraft looks for the first of these file names (each name means a `<name>.aff
| Croatian | `hr_HR`, `hr-HR`, `hr` | LibreOffice `dict-hr` |
| Turkish | `tr_TR`, `tr-TR`, `tr` | LibreOffice `dict-tr` |
| Indonesian | `id_ID`, `id-ID`, `id`, `in_ID` | LibreOffice `dict-id` |
| Spanish (Spain) | `es_ES`, `es-ES`, `es_ANY`, `es` | LibreOffice `dict-es` (RLA-ES) |
| Spanish (Mexico) | `es_MX`, `es-MX`, `es_419`, `es-419`, `es_ANY`, `es` | LibreOffice `dict-es` (RLA-ES) |
| Spanish (Latin America, and the other Spanish regions of the Americas) | `es_419`, `es-419`, `es_ANY`, `es`, `es_US`, `es_MX`, `es_AR`, `es-AR` | LibreOffice `dict-es` (RLA-ES) |
| Spanish (Uruguay) | `es_UY`, `es-UY`, `es_AR`, `es-AR`, `es_419`, `es-419`, `es_ANY`, `es` | LibreOffice `dict-es` (RLA-ES) |
Every Portuguese region uses the same `pt_BR` dictionary, so text marked *Português (Portugal)*
is checked with it too.
The files may be UTF-8 or the encoding named on the `.aff`'s `SET` line (ISO 8859-1, ISO 8859-2, KOI8-R
and Windows-1251 are read as well; the original VERO `pt_BR` is ISO 8859-1).
Spanish has a dictionary per variety: text marked *Español (España)* uses `es_ES`, *Español
(México)* `es_MX`, *Español (Uruguay)* `es_UY`, and *Español (Latinoamérica)* and the other
Spanish regions of the Americas (Argentina, Colombia, the United States…) the first Latin
American one found. LibreOffice's `dict-es` has them all. Uruguayan Spanish also accepts the
`vos` forms of the dictionary's verbs (`tenés`, `vení`, `decime`, `decímelo`).
The files may be UTF-8 or the encoding named on the `.aff`'s `SET` line (ISO 8859-1, ISO 8859-2,
ISO 8859-9, KOI8-R and Windows-1251 are read as well; the original VERO `pt_BR` is ISO 8859-1, older
Turkish `tr_TR` dictionaries ISO 8859-9).
## Where WordCraft looks
@@ -105,7 +116,8 @@ curl -fLO https://raw.githubusercontent.com/LibreOffice/dictionaries/master/pt_B
```
Other languages work the same way: replace `pt_BR` with the language's folder and file names
from the LibreOffice repository (for example `it_IT/it_IT.aff`).
from the LibreOffice repository (for example `it_IT/it_IT.aff`; the Spanish ones are all in
`es/`, for example `es/es_MX.aff`).
## Checking that it works
@@ -128,7 +140,9 @@ Never commit dictionary files to this repository: they stay on the user's system
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | Spanish (#3360, #3553): the file names for Spain, Mexico, Latin America and Uruguay and their fallbacks |
| 2026-10-22 | trivial | Italian, Croatian, Turkish, Russian, Czech and Indonesian without a dictionary now show **(no dictionary)** (or **(dictionary unreadable)**) too, as this page already said; before, only Polish and Portuguese did. Indonesian is listed with its file names (#3150) |
| 2026-10-23 | trivial | ISO 8859-9 (`SET ISO8859-9`) dictionaries are read, so an older Turkish `tr_TR` loads without converting it to UTF-8; the Turkish dictionary is read through the same size-capped reader and `SET` decoding as the others (#3692) |
| 2026-10-18 | trivial | ISO 8859-1 (`SET ISO8859-1`) dictionaries are read, so the original VERO `pt_BR` works without converting it to UTF-8 (#2824) |
| 2026-10-18 | minor | An installed dictionary that can't be read shows **(dictionary unreadable)** in the status bar, with the file and reason in its tooltip and in File › Options › Proofing (#2830) |
| 2026-10-16 | major | First version: the file names per language, the search order and folders on Windows, macOS and Linux, installing `pt_BR` on each, and how to check it works |
+163 -4
View File
@@ -1,6 +1,6 @@
# Typing parity with Word
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-22 · **Change:** trivial (while an input method composes, the caret and its candidate window sit at the input method's cursor inside the composition; previously trivial: Ctrl+Tab types a tab in a table cell, #3137; previously trivial: List AutoFormat triggers in front of an already-written paragraph, not only a fresh empty one, checked in Word for Windows, #3352; previously trivial: AltGr types its characters without opening KeyTips on Linux, #2388; previously trivial: Delete on whole selected cells leaves one empty paragraph in each, with the cell's last paragraph's formatting; nested tables go too, #2802; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: a text box that keeps the keyboard no longer resets the input method every frame, #2986; previously trivial: a slash opening a word travels whole to the next line, #2963; previously trivial: typing over a selection takes the first selected character's formatting, also when the selection was made right to left, #2879; previously trivial: Turkish and Azerbaijani case mapping in AutoCorrect, #1923; previously trivial: smart quotes in Hebrew pinned: logical order, ” in acronyms as in Word, #1670; previously trivial: Backspace on a selected table, rows or columns deletes them, Delete clears them, #1844; previously trivial: typed text takes its direction from its own letters, #1543; previously trivial: an input method's request to erase text around the caret is honoured, so web typing in Cyrillic and other non-Latin text no longer repeats, #1480; previously trivial: `-->` AutoCorrect replacement, #2223; previously trivial: a heading-list preset keeps Heading styles and makes other paragraphs Heading 1, as in Word, #1793; previously trivial: numbered headings: a heading-linked Multilevel List numbers new headings by their style; numbering keeps a heading a heading, #1793; previously trivial: reversing an automatic capital adds an exception, #1857; previously trivial: smart quotes open after guillemets and low quotes and nest; previously trivial: Enter on an empty list item numbered by its paragraph style moves it up a level, #2143; previously trivial: Backspace after a section break and Delete before it remove the break, also on the last paragraph, #2127; previously trivial: a table inserted in right-to-left text is right to left, so Tab moves left, #2634; previously trivial: Shift+Delete cuts the selection on every platform, #1669; previously trivial: Select All then Delete removes a table the document starts with, #1198; previously trivial: Alt+numpad character codes, #961; previously trivial: Down and Up from a line start beside a table go into it, #1007; previously trivial: sample-text formulas on Enter, #1067; previously trivial: Track Changes: Accept All after a tracked delete from a paragraph end keeps the next paragraph; Reject All after typing into an empty paragraph keeps it (#864); previously minor: Correct TWo INitial CApitals, cAPS LOCK and table-cell capitals, #544; previously trivial: complex-script caret and Backspace, #604; Move Paragraph Up/Down, Promote/Demote, Go Back and Alt+X keys, #343) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-25 · **Change:** trivial (`*Bold* and _italic_ as real formatting` in AutoFormat As You Type; previously trivial: highlight target includes selected paragraph marks, #4085; previously trivial: full-target highlight check beyond 10,000 runs, #4085; previously trivial: queued IME replacements are rejected before mutation after their marked composition loses its body/equation target; previously trivial: the transactional equation delete proves its commit with the engine revision; previously trivial: a refused byte-counted equation deletion drops only the immediately adjacent stale replacement, so a later direct commit still applies; previously trivial: a tracked own-insertion shifts the rest of a two-sided IME deletion; previously trivial: AutoCorrect/AutoFormat classification compares the actual paragraph; previously trivial: a replaced body starts a new input-method context and interrupts the composition; a quote the engine leaves as typed is the input method's own change; equation `DeleteSurrounding` erases counted characters; previously trivial: AutoCorrect finds the word after a no-break space or an ideographic space; previously trivial: a command that changes nothing is not an edit: no undo step, no unsaved changes, no relayout, and the redo stack survives it; previously trivial: while an input method composes, the caret and its candidate window sit at the input method's cursor inside the composition; previously trivial: Ctrl+Tab types a tab in a table cell, #3137; previously trivial: List AutoFormat triggers in front of an already-written paragraph, not only a fresh empty one, checked in Word for Windows, #3352; previously trivial: AltGr types its characters without opening KeyTips on Linux, #2388; previously trivial: Delete on whole selected cells leaves one empty paragraph in each, with the cell's last paragraph's formatting; nested tables go too, #2802; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: a text box that keeps the keyboard no longer resets the input method every frame, #2986; previously trivial: a slash opening a word travels whole to the next line, #2963; previously trivial: typing over a selection takes the first selected character's formatting, also when the selection was made right to left, #2879; previously trivial: Turkish and Azerbaijani case mapping in AutoCorrect, #1923; previously trivial: smart quotes in Hebrew pinned: logical order, ” in acronyms as in Word, #1670; previously trivial: Backspace on a selected table, rows or columns deletes them, Delete clears them, #1844; previously trivial: typed text takes its direction from its own letters, #1543; previously trivial: an input method's request to erase text around the caret is honoured, so web typing in Cyrillic and other non-Latin text no longer repeats, #1480; previously trivial: `-->` AutoCorrect replacement, #2223; previously trivial: a heading-list preset keeps Heading styles and makes other paragraphs Heading 1, as in Word, #1793; previously trivial: numbered headings: a heading-linked Multilevel List numbers new headings by their style; numbering keeps a heading a heading, #1793; previously trivial: reversing an automatic capital adds an exception, #1857; previously trivial: smart quotes open after guillemets and low quotes and nest; previously trivial: Enter on an empty list item numbered by its paragraph style moves it up a level, #2143; previously trivial: Backspace after a section break and Delete before it remove the break, also on the last paragraph, #2127; previously trivial: a table inserted in right-to-left text is right to left, so Tab moves left, #2634; previously trivial: Shift+Delete cuts the selection on every platform, #1669; previously trivial: Select All then Delete removes a table the document starts with, #1198; previously trivial: Alt+numpad character codes, #961; previously trivial: Down and Up from a line start beside a table go into it, #1007; previously trivial: sample-text formulas on Enter, #1067; previously trivial: Track Changes: Accept All after a tracked delete from a paragraph end keeps the next paragraph; Reject All after typing into an empty paragraph keeps it (#864); previously minor: Correct TWo INitial CApitals, cAPS LOCK and table-cell capitals, #544; previously trivial: complex-script caret and Backspace, #604; Move Paragraph Up/Down, Promote/Demote, Go Back and Alt+X keys, #343; the body IME deletion maps signed placeholder growth/shrink, keeps control markers outside the range and scopes its undo join to the matching replacement) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
What Word does when you type, observed black-box (same keystrokes typed into Word and
WordCraft, results read back paragraph by paragraph) and pinned by tests in
@@ -8,6 +8,27 @@ WordCraft, results read back paragraph by paragraph) and pinned by tests in
tests together. Line breaking and pagination are in [`layout-parity.md`](layout-parity.md); the
overall picture is in [`target-app-parity.md`](target-app-parity.md).
## Highlight shortcut (#4085)
On Windows/Linux, Ctrl+Alt+H toggles the last chosen highlighter colour (yellow in a
new session). If every character in the formatting target already has that colour,
it applies explicit No Color; otherwise it applies the chosen colour to the target.
Choosing another colour applies it, and choosing No Color makes that the current choice.
Explicit `format.highlight {"color": ...}` remains an idempotent setter; omitting `color`
uses the toggle. Ribbon and mini-toolbar colour buttons remain setters, not toggles.
The highlight decision checks the complete target, including mixed colours after 10,000 runs;
formatting-state queries retain their existing sampling limit. Fully selected paragraph marks
(including whole cells and empty paragraphs) participate in that decision; partial text
columns ignore marks outside the selected range. Palette state is outside document undo; the formatting itself is undoable.
The existing selection/word/pending-caret targeting is unchanged (the separate request #4086
is not addressed).
Regressions: `highlight_shortcut_toggles_the_last_colour` and
`highlight_toggle_handles_mixed_inherited_and_pending_formatting`. Synthetic command/key
qualification, with additional large-selection, non-mutating preview, whole-cell mark,
empty-paragraph mark and partial-column controls. Native Word comparison has not been
performed for this change.
## Sample text (#1067)
Enter at the end of a paragraph containing exactly `=rand()` or `=lorem()` replaces
@@ -35,6 +56,7 @@ Bullets and Numbering decide whether to toggle off from the selected paragraphs'
| Enter on an empty **nested** item | moves it up one level; only a top-level empty item leaves the list. The same when the list comes from the paragraph style (List Number 2 in a Word file, #2143) | `enter_on_an_empty_nested_item_moves_it_up_a_level_first`, `enter_on_an_empty_item_numbered_by_its_style_moves_it_up_a_level` |
| `* ` Enter (nothing typed) | list cancelled, Normal paragraph | `enter_right_after_starting_a_list_cancels_it` |
| Tab / Shift+Tab at the start of an item | demote / promote | `shift_tab_and_tab_change_list_levels` |
| Tab with a whole paragraph (or several paragraphs) selected | indents the paragraph(s) by one default tab stop; the text stays. A partial selection inside one paragraph is replaced by a tab character | `tab_with_a_whole_paragraph_selected_indents_it`, `tab_with_a_partial_selection_still_inserts_a_tab` |
| Backspace at the start of an item | 1st: number removed, indent kept; 2nd: Normal paragraph — **not** joined to the one above | `backspace_at_the_start_of_an_item_removes_the_number_then_the_indent` |
| Enter mid-item / at item start | splits the item / inserts an empty item above | `enter_in_the_middle_or_at_the_start_of_an_item` |
| Shift+Enter in an item | line break inside the same item | `shift_enter_stays_inside_the_list_item` |
@@ -49,6 +71,7 @@ Bullets and Numbering decide whether to toggle off from the selected paragraphs'
| Undo after a heading list | one step: paragraph styles and the heading styles' numbers come back | `undo_takes_a_heading_preset_back_in_one_step` |
| A paragraph whose numbering was switched off, then Heading 2 from a heading-linked list | numbered (1.1) by the style | `applying_a_numbered_heading_style_numbers_the_paragraph` |
| `1. ` typed in a Heading 1 (also `a) ` in a Heading 2, `1. ` in a Subtitle) | numbered, the style kept; only body text becomes List Paragraph (checked in Word for Mac, 2026-10-15) | `typing_1_dot_in_a_heading_keeps_the_heading_style` |
| `---` or `+--+--+` Enter in a heading numbered by a heading-linked list, at the top of the document or below another paragraph | stays text, with no border or table, and the heading keeps its number, as in a paragraph numbered directly (WordCraft's rule; not compared with Word) | `border_and_table_lines_stay_text_in_a_style_numbered_heading` |
| ⌘Z right after an AutoFormat | undoes only the AutoFormat (`* ` comes back as text) | `undo_right_after_autoformat_undoes_only_the_autoformat` |
## Undo
@@ -57,6 +80,11 @@ Bullets and Numbering decide whether to toggle off from the selected paragraphs'
|---|---|---|
| type, select some of it, type over it | two undo steps: the first ⌘Z brings the selected text back (still selected), the second removes the typing. Uninterrupted typing stays one step | `typing_then_selecting_then_typing_are_two_undo_steps` |
| ⌘B with nothing selected, ⌘Z, type | the text isn't bold (Undo takes the waiting caret formatting back too); ⌘Y brings it back | `undoing_bold_at_the_caret_turns_bold_typing_off_again` |
| ⌫ at the start of the document, or typing nothing | nothing happens: no undo step is added, the document is not marked edited, and the redo stack survives ³ | `backspace_at_the_start_of_the_document_is_not_an_edit`, `a_command_that_changes_nothing_is_not_an_edit`, `a_no_op_command_keeps_the_redo_stack` |
³ Not re-observed side by side in Word for this change: Word's undo list does not grow when a
key changes nothing, and the document is not marked edited. The gap was found by the read-only
audit, which measured WordCraft's own history.
## Paste
@@ -86,6 +114,8 @@ formatting of the text before the cursor. Not yet compared side by side in Word.
| Change Case (Shift+F3, Home › Aa) (tracking on) | Word shows no insertion or deletion markup (checked with Formatting markup set to none, so Word may record it as a hidden formatting revision). **Differs:** WordCraft records the changed letters as a tracked deletion of the old and insertion of the new, since case isn't a run property a formatting revision could hold; Reject All restores the old case, Accept All keeps the new. Your own tracked insertion is rewritten in place; already-deleted text is left alone and doesn't count (with tracking off too), so changing case again (or Shift+F3) works from the visible text. Refused in content locked against editing | `tests_revision_checks::tracked_change_case_rejects_back_to_the_original_case`, `tracked_change_case_of_own_insertion_stays_an_insertion`, `tracked_change_case_twice_reads_only_the_live_text`, `tracked_change_case_handles_sharp_s_and_mixed_formatting`, `change_case_refuses_content_locked_against_editing` |
| Unlink Fields (Ctrl+Shift+F9) (tracking on) | not tracked, like Word: the field's result stays as ordinary text. Refused in content locked against editing | `tests_revision_checks::tracked_unlink_keeps_the_result_as_plain_text`, `unlink_refuses_a_field_in_content_locked_against_editing` |
> **Three-column preset update:** 2026-10-15 · `blankThree` headers/footers start the caret in the first of three table cells (#2197). `text.tab` reaches the next cell and `text.insert` edits it through the existing command path. `crates/engine/tests/it/three_column_headers.rs` checks cell navigation, independent formatting, preset/text undo/redo and repeated DOCX save/reopen.
## Editing keys
Word's documented keyboard behaviour (Microsoft's shortcut list, feature names only); the
@@ -113,7 +143,9 @@ The `-->` replacement produces `→` when completed with a space (#2223), includ
at paragraph start without creating a list. It respects AutoCorrect preferences, user
entries and undo. The existing `-> ` paragraph-start arrow bullet is unchanged.
Applied when a word is finished by a space, punctuation **or Enter** (`enter_finishes_a_word_for_autocorrect`):
Applied when a word is finished by a space, punctuation **or Enter** (`enter_finishes_a_word_for_autocorrect`).
The word starts after the last white-space character before it, also when that is a no-break space or an
ideographic space (`autocorrect_finds_the_word_after_a_wide_space`):
- First letter of a sentence (paragraph or list item start, after `. ! ?`) is capitalised; not
after abbreviations (`e.g.`, `Dr.`, …) or initials (`J.`), and not for mixed-case words
@@ -126,6 +158,13 @@ Applied when a word is finished by a space, punctuation **or Enter** (`enter_fin
is “ (U+201C) even though it shows on the right of the word, after a letter ” (U+201D); in an
acronym (`צה"ל`) it becomes ” as in Word for Mac 16.113.4, not gershayim (״); `'` after a letter
(`ג'ירפה`) is ’ as after any letter, with no geresh conversion (not yet checked in Word; `smart_quotes_in_hebrew_use_logical_order`, #1670).
- Add a period with double-space (File › Options › Proofing › AutoCorrect Options › AutoFormat As
You Type, `tools.autocorrect {"doubleSpacePeriod"}`; **off by default**, as macOS's system switch
is a per-user choice): a second Space after a letter, a digit or a closing bracket or quote turns
the first into a period (`Hello world`→`Hello. world`). Other punctuation before the spaces,
a lone space, the start of a paragraph, a tab, a selection typed over and a third space are left
as typed; Undo puts the two spaces back (`double_space_adds_a_period_when_asked`, #2848). Not
observed in Word; Word for Mac follows the macOS setting.
- Correct TWo INitial CApitals: a finished word of three or more letters with two capitals and
the rest lowercase loses its second capital (`WOrd`→`Word`, `TEh`→`The`); not the exceptions
(`IDs`, `CDs`, `PCs`, `TVs`, … plus the user's, `tools.autocorrect {"addInitialCapsException"}`)
@@ -161,6 +200,20 @@ Applied when a word is finished by a space, punctuation **or Enter** (`enter_fin
`capsLock`, `capCells` on `tools.autocorrect`; on by default)
(`capitalisation_fixes_undo_and_turn_off`).
- `1/2 1/4 3/4` → `½ ¼ ¾`; `1st 22nd 13th` get superscript suffixes (`ordinals_become_superscript`).
- With `*Bold* and _italic_ as real formatting` on (`tools.autocorrect {"boldItalic": true}`;
off by default), `*word*` becomes bold `word` and `_word_` italic `word`, without the marks,
when the word is finished ¹. The rest of this item is WordCraft's own rule: the marks may
enclose several words; WordCraft converts a span only when the opening mark starts a word and
the text inside holds a letter or digit and does not start or end with a space, so `2*3*`,
`file_name_` and `*?!*` stay as typed, and so do marks around a picture, field or content
control. AutoCorrect then goes on with the word without its marks, so `*teh* ` becomes bold
`The `, and it corrects the first word of a span of several words as it is typed, so
`*teh end* ` becomes bold `The end `. Undo right after brings the marks
back (after Enter, the Enter stays). With Track Changes on, WordCraft converts only marks the
current author typed as tracked insertions, and it converts nothing while formatting is
limited to the document's styles (`bold_and_italic_marks_become_formatting`,
`bold_and_italic_marks_stay_as_typed_unless_they_enclose_words`,
`autocorrect_goes_on_after_bold_and_italic_marks`).
- `word -- word` → en dash, `word - word` → en dash, `word--word` → em dash (`dashes`).
- Web addresses become links; following text isn't linked (`web_addresses_become_links`).
- A hyperlink covers only its own text: typing at either end of it, or after Enter at its end,
@@ -170,14 +223,43 @@ Applied when a word is finished by a space, punctuation **or Enter** (`enter_fin
- A field covers only its own result: text typed or pasted with the caret just after a field
(a Zotero citation at the end of a paragraph, the caret put there with a click) goes after
the field, not into its result (#898; `typing_after_a_citation_goes_outside_its_field`).
- With Track Changes on, the border-line and table AutoFormats below mark the characters
deleted, so Reject All brings them back; characters the same author inserted are removed.
The border is a tracked formatting change, at the top of a story too
(`a_tracked_border_line_is_a_deletion_and_a_formatting_change`); the table is inserted
untracked, as Insert › Table inserts one
(`a_tracked_table_line_keeps_its_characters_as_a_deletion`). A line holding a tracked
deletion stays as it is, so Enter after the deleted characters is a plain Enter. This is
WordCraft's own rule.
- Enter after `---` `===` `***` `___` `~~~` `###` → a bottom border on the paragraph above
(single, double, dotted, thick, wave, triple) (`border_line_autoformat`). ⌘Z right after brings the
characters back and removes the border; the Enter stays (one undo step) ¹
(`undo_right_after_a_border_line_brings_the_characters_back`).
- Built-in Heading styles (`tools.autocorrect {"headingStyles": true}`, off by default): Enter
twice after a line of five or fewer words that does not end with punctuation makes the line
Heading 1; one or two tabs before it make it Heading 2 or 3, and the tabs are removed ¹
(`two_enters_after_a_short_line_make_it_a_heading`). The rest is WordCraft's own rule: the
line is a Normal paragraph of the main text (not in a list, a table, a header or a text box)
and ends in a letter or digit; the caret stays in the empty paragraph after the heading; ⌘Z
right after brings back the tabs and the Normal style, and the second Enter stays
(`undo_right_after_a_heading_autoformat_keeps_the_enter`); with Track Changes on, the tabs are
marked deleted (or removed, when the same author inserted them) and the style change is a
tracked formatting change, and a line holding a tracked deletion stays as it is
(`a_tracked_heading_autoformat_can_be_rejected`); a heading style locked by formatting
restrictions is not applied (`a_locked_heading_style_is_not_applied_as_you_type`).
- `+---+------+` Enter (plus signs around runs of hyphens, the whole paragraph) → a one-row
table across the text width, each column as wide as its share of the hyphens; the caret goes
into the first cell. ⌘Z right after brings the characters back. More than 63 columns stay
text ¹ (`plus_and_hyphen_line_becomes_a_table`).
- Set left- and first-indent with tabs and backspaces (`tools.autocorrect {"tabIndent": true}`,
off by default): Tab before the first line of a paragraph with text indents its first line by
one default tab stop, and Tab before any other line, after a line break or where the text
wraps, indents the whole paragraph. A second Tab before an indented or hanging first line
indents the whole paragraph. Backspace at the start takes the first-line indent back, then the
paragraph's own left indent one tab stop at a time. An empty paragraph and Tab inside a line
still take a tab character. While formatting is limited to the document's styles, Tab and
Backspace work as with the option off ¹ (`tab_and_backspace_at_a_paragraph_start_set_its_indents`,
`tab_before_a_later_line_of_a_paragraph_indents_the_whole_paragraph`).
- Smileys `:)` `:-)` `:(` `:-(` `:|` → ☺ ☹ 😐, also straight after a word (`ok:)` → `ok☺`) ¹
(`smileys_are_replaced_inside_words_too`).
@@ -288,8 +370,8 @@ again.
- An input method can ask to erase characters before and after the caret (`DeleteSurrounding`)
and send replacement text. The web build's hidden text field does this whenever its contents
change other than by adding to the end: Backspace and corrections from phone keyboards, and
the Chrome caret jump below. The desktop builds never receive it (egui-winit 0.36 doesn't
send it). WordCraft erases those characters as Backspace erases a selection, stopping at the
the Chrome caret jump below. The 0.37 desktop bridge can also deliver character- or
byte-counted surrounding deletions from the input method. WordCraft erases those characters as Backspace erases a selection, stopping at the
paragraph's ends, then types the new text. The new text keeps the erased text's formatting
and never overtypes what follows. Erasing and retyping stay in the open typing undo step, and
typing after them joins it too (#1480,
@@ -312,6 +394,74 @@ again.
and the caret and the candidate window sit at the input method's cursor inside it: the end of
the range it reports, or the end of the marked text when it reports none. The range is clamped
to the text (`canvas::tests::the_caret_follows_the_input_method_cursor_in_the_composition`).
- A composition belongs to the text it was started on. When a command replaces the body under
the same document id — Replace Document Text, a version restore, Compare, Undo/Redo — or opens
a new document, the body's replacement epoch advances, the platform clears the composition, and
any marked text is dropped. Replacing the text with an identical one still starts a new
context (`canvas::tests::ime_generation_changes_on_body_replacement_with_the_same_text`,
`ime_generation_changes_on_undo_and_version_restore`,
`keys::tests::observe_ime_drops_a_preedit_without_focus`). Undo/Redo also gives the equation
being edited a fresh session token, so its context follows the restored tree. The marked text
is never painted without focus.
- If replacing the body or changing the edited equation invalidates nonempty marked text,
the next canvas input batch rejects that composition's queued surrounding deletions and one
replacement commit before any engine mutation. An associated `Text` fallback is rejected too.
A fresh nonempty preedit or ordinary typing ends that batch's suppression; without an old
marked composition, direct commits remain eligible. A later commit after the rejected one,
or in a later canvas batch, is also fresh input. New regression checks cover identical-body
replacement, both deletion units, equation exit before or within the batch, unchanged text
and Undo history, and fresh input (`keys::tests::ime_interrupted_body_preedit_drops_queued_replacement_before_mutation`,
`ime_interrupted_equation_preedit_drops_queued_body_replacement`,
`ime_interrupted_preedit_does_not_suppress_fresh_input`). Their managed validation is pending;
this does not claim native input-method acceptance or identify untagged events across frames.
- An accepted IME commit earns an `InputMethod` receipt only when the engine inserted exactly the
committed text. A single quote the engine leaves as typed (smart quotes off) is the input
method's own change; a smart quote or AutoCorrect replacement is `Other`
(`ime_apostrophe_with_smart_quotes_off_is_an_input_method_change`,
`ime_apostrophe_with_smart_quotes_on_stays_other`,
`ime_literal_commit_after_autoformat_is_input_method`). A `DeleteSurrounding` whose two sides
cannot both be applied is rolled back whole and publishes no receipt
(`a_refused_second_delete_surrounding_rolls_the_request_back`); every such refusal also
interrupts the composition and clears the painted pre-edit and both replacement joins because
the platform's hidden text already changed
(`a_refused_body_delete_clears_preedit_and_joins`). Under Track Changes, deleting text this
author inserted shortens the stored paragraph, so the following range and caret are shifted by
the bytes actually removed (`tracked_delete_surrounding_shifts_past_its_own_insertion`,
`tracked_own_insertion_then_a_refused_side_rolls_the_whole_request_back`). Emptying a content
control restores its placeholder, which may make the paragraph longer; a body deletion maps the
following range and caret by the signed stored-length change, and refuses the whole transaction
when growth cannot be proven to come from a placeholder restored at or before the erased range
(`delete_surrounding_maps_after_text_and_caret_past_a_growing_placeholder`,
`delete_surrounding_maps_a_shrinking_placeholder_and_after_text`,
`delete_surrounding_maps_a_placeholder_restored_by_the_after_side`). Control markers are
zero-length visible objects, so a range start maps after the start marker and after the end
marker: an ordinary IME deletion never breaks a control
(`ime_text::tests::control_markers_are_not_included_in_delete_ranges`). The replacement's undo
join is scoped to the next `text.insert` at the selection the deletion left; an intervening
command owns its own step (`body_delete_join_does_not_leak_to_an_intervening_command`).
- Inside an equation, `DeleteSurrounding` erases the counted characters with the equation's own
transactional command (at most 64 units a side), refusing the whole request if any step cannot
delete — including a step that would only move the caret into another argument
(`equation_delete_surrounding_refuses_a_mixed_caret_only_step`); the delete and the following
commit are one undo group. Byte counts have no reversible mapping into the
structured tree, so the whole request is refused, the composition is interrupted and the
replacement the platform sends for that rejected deletion — the immediately adjacent `Commit`,
or an adjacent `Text` + `Commit` pair — is dropped instead of being inserted beside the
untouched equation (the pre-edit never lives in the model). Any later `Text`/`Commit` in the
same raw-input batch is fresh input and applies, as is a later direct commit without a pre-edit;
no persistent tombstone can swallow it. An over-cap request clears the painted pre-edit and any
earlier deletion's replacement join before refusing. The equation's surroundings stay explicitly
unavailable (`ime_equation_delete_is_one_input_method_change`,
`ime_equation_delete_and_commit_are_one_undo_step`,
`ime_equation_byte_delete_drops_only_its_immediate_replacement`,
`over_cap_equation_delete_clears_the_painted_preedit`,
`over_cap_equation_delete_clears_the_previous_replacement_join`). The engine's checked revision
on the unavailable stamp proves a successful content change; a refusal, an automatic rewrite or
an exhausted revision publishes no receipt (`ime_transformed_equation_commits_are_other`,
`exhausted_ime_revisions_never_certify_equation_or_wide_body_edits`).
- Pre-edit and commit sizes are bounded (the visible window at 4000 bytes, a pre-edit at the same
cap, a commit at 64 KiB): larger hostile events are ignored rather than painted or inserted
(`oversized_ime_events_are_ignored`).
## Known gaps
@@ -342,6 +492,13 @@ again.
| Date | Change | Summary |
|---|---|---|
| 2026-10-25 | trivial | Highlight decisions include fully selected paragraph marks in whole cells and ordinary ranges, including empty paragraphs; partial text columns exclude unselected marks (#4085). |
| 2026-10-25 | trivial | Highlight toggle checks the full target beyond 10,000 runs without collecting resolved formatting; mixed and inherited tails and Undo/Redo covered (#4085). |
| 2026-10-24 | trivial | Input methods: reject queued old-composition replacements before mutation after body/equation target replacement (new regression validation pending); equation `DeleteSurrounding` is one transactional command whose checked revision proves a receipt; a caret-only step in a mixed request refuses the whole deletion, and an over-cap request clears the painted pre-edit and the previous replacement join; a refused byte-counted equation deletion drops only the immediately adjacent stale replacement, never a later direct commit; a tracked own-insertion shifts the following range; a body deletion maps signed placeholder growth/shrink, keeps control markers outside the range and scopes its replacement undo join to the matching `text.insert`; refusals clear the painted pre-edit and joins; AutoCorrect/AutoFormat classification compares the paragraph as typed; equation replacement interrupts the composition |
| 2026-10-24 | trivial | AutoCorrect: a word typed after a no-break space or an ideographic space is corrected as after a plain space |
| 2026-10-24 | trivial | Highlight shortcut toggles the last chosen colour off when the whole selection has it; explicit colour commands remain setters (#4085). |
| 2026-10-24 | trivial | AutoFormat As You Type: `*Bold* and _italic_ as real formatting` turns `*word*` bold and `_word_` italic without the marks; off by default |
| 2026-10-23 | trivial | Input methods: a replaced body (Replace Document Text, version restore, Compare, Undo/Redo) starts a new context and interrupts the composition; smart quotes off keeps an IME quote an `InputMethod` change; equation `DeleteSurrounding` erases counted characters; bounded pre-edit/commit sizes; a partly refused two-sided deletion is rolled back |
| 2026-10-22 | trivial | Input methods: while composing, the caret and the candidate window follow the input method's cursor inside the marked text |
| 2026-10-21 | trivial | Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459) |
| 2026-10-21 | trivial | List AutoFormat (`- `, `1. `, …) triggers at the start of an already-written paragraph, not only a fresh empty one; checked in Word for Windows (#3352) |
@@ -349,6 +506,8 @@ again.
| 2026-10-19 | trivial | Delete on whole selected cells (rows, columns, the table, a cell) leaves one empty paragraph in each, with the cell's last paragraph's formatting; nested tables go too (#2802) |
| 2026-10-19 | trivial | Ctrl+Tab (⌥Tab on macOS) types a tab character in a table cell (#3137) |
| 2026-10-16 | trivial | AltGr on Linux (Wayland) types ś, ł, ż… instead of opening KeyTips: the modifiers event without Alt that follows the Right Alt key no longer reads as Alt let go, and a bare AltGr tap doesn't open KeyTips (#2388) |
| 2026-10-20 | trivial | AutoFormat As You Type: optional "Add a period with double-space" (off by default), undoable (#2848) |
| 2026-10-15 | trivial | Three-column header/footer preset: first-cell caret, Tab navigation, independent formatting and undo/redo regression coverage (#2197) |
| 2026-10-18 | trivial | Typing over a selection takes the first selected character's formatting, also when the selection was made right to left (#2879) |
| 2026-10-17 | trivial | A table inserted in a right-to-left paragraph is a right-to-left table, so Tab moves to the cell on the left (#2634) |
| 2026-10-15 | trivial | Backspace after a section break and Delete before it remove the break, also one on the last paragraph's mark; the text takes the following section's setup and keeps the headers it showed (#2127) |
+25 -6
View File
File diff suppressed because one or more lines are too long
+26
View File
@@ -61,6 +61,32 @@ Word's "Field" in the file.)
- Zotero's document transfer (Export/Import Document) isn't needed and isn't supported.
- The web version can't reach Zotero (browsers can't open the socket).
## Who can drive the document
Zotero's protocol has no handshake that proves the program on port 23116 is Zotero, and that
program can read the citations and edit the open document. WordCraft limits who that can be.
In short: **every system** gets the loopback rule; **only Linux** also checks that the listener
isn't another user's. On Windows and macOS (and the BSDs) the loopback rule is the only check.
- Every system: it only connects to this computer (a loopback address such as `127.0.0.1`);
any other address is refused before connecting.
- Linux only: it checks, in the kernel's socket table (`/proc/net/tcp`, `/proc/net/tcp6`), that the
program listening on the port runs as the same user as WordCraft, before sending anything. If
another account on a shared machine took the port while Zotero wasn't running, WordCraft
refuses with an error and the document isn't touched. When the table doesn't show the
listener (WSL2 mirrored networking with Zotero running on Windows, a local proxy, a sandbox)
or can't be read, its owner is unknown and only the loopback rule applies, as below.
- Windows, macOS and the BSDs: **no same-user check.** They have no such table to read
without OS calls WordCraft doesn't make (it uses no `unsafe` code), so only the loopback rule
applies there (and on Linux when the listener isn't in the table). Remaining risk: on a
computer shared with other accounts, a program another user leaves on port 23116 while
Zotero is closed would be taken for Zotero. An owner check for these systems is tracked in
#3831. A program running as you is trusted on every system; it could change your documents
anyway.
- `wordcraft-cli zotero` prints Zotero's messages (and `--trace` lines) with control
characters, line breaks and backslashes shown as escapes (`\n`, `\u{1b}`, `\\`), so a peer
can't forge other output or drive the terminal.
## How it works
`crates/zotero` implements Zotero's published word-processor wire protocol (port 23116):