mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-01 22:37:10 -06:00
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:
@@ -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
+39
-13
File diff suppressed because one or more lines are too long
+48
-5
File diff suppressed because one or more lines are too long
+50
-11
File diff suppressed because one or more lines are too long
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user