mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-07 03:01:32 +00:00
main
116
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1397669dd5 |
Table formulas: read the Panamanian balboa B/. in numbers (#3546)
* Table formulas: read the Panamanian balboa B/. in numbers Word's table formula and sort number reader ignores a currency sign or short currency word around a number; the balboa's B/. was not in the list. Add it as a whitespace-delimited sign, as Word reads it: B/. 1,250.50 and 1,250.50 B/. are numbers, while B/.1,250.50 and B/ 1,250.50 are not. Tested in the locale reader and through =SUM(ABOVE); the localization parity doc records the behaviour. * Table formulas: read the balboa cell in the document's language The formula test summed a B/. cell through ev, which supplies no document or language, so a regression in locale selection could pass. Evaluate =SUM(ABOVE) with a document whose default text is es-PA and a pl-PL editing language: 1,250.50 is a number under es-PA and not under pl-PL, so the assertion fails if the formula stops consulting the cell's language. The converse case reads B/. 1 250,50 from a pl-PL document with an en-US fallback, which English alone cannot parse. * docs: close the parenthesis in the localization-parity change note (#3546) --------- Co-authored-by: Francisco Herrera <14956584+alosslessdev@users.noreply.github.com> Co-authored-by: FredApps <FredApps@users.noreply.github.com> |
||
|
|
6ec85141d5 |
Arabic interface: mirror the remaining label/field dialogs (#5449)
* Arabic interface: mirrored right-to-left window and Arabic UI fonts Adds the Arabic (`ar`) interface language's code: its catalogs are craft-i18n's `locales/ar`, so this changes no catalog. - `ar` is registered in `i18n::LANGUAGES` and `Lang::rtl()` says it is written right to left; its translations are used as written (egui's text layout orders each line's bidi text itself). A ribbon location reads from the right with a leftwards mark. - `rtl.rs`: the window is mirrored. Containers open in `rtl::root`; columns, grids, checkboxes, radio buttons, menus, rows, panels, the dialog tab strip and the ribbon's scrolling row follow the language's direction. Title bar, status bar, ribbon tabs and groups, File pages (including Print, with the settings on the right and a solid scrollbar gutter), dialogs, menus and the task panes (their docking edge swaps) are laid out from the right. egui's `Grid` panics in a right-aligned layout, so grids go through `rtl::grid`. - Arabic UI font fallback: the craft-fonts Arabic face, else an installed Arabic font, loaded when the interface is Arabic. - The "Auto (200%)" size label keeps its percent sign inside the isolated number. - Tests: Arabic locale, saved preference and lookup order on fixture catalogs; an interface audit (skipped without the craft-i18n Arabic catalog) that lays the app out right to left at several widths, checks tab, group, pane, Print and Go To positions and that nothing panics. The Arabic work builds on Abdelhaq El Amraoui's mirrored-interface PR (#737). Co-Authored-By: Abdelhaq El Amraoui <abdelhaqelamraoui@gmail.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: pin craft-i18n at a commit with the Arabic catalog; review fixes - craft-i18n.rev and the Flatpak manifest pin 19358491 (craft-i18n main), the first commit line that has locales/ar/wordcraft.tsv. The old pin had no Arabic catalog, so `ar` was registered but not embedded and two i18n tests (the_language_setting_is_a_ui_command_and_persists, the_languages_offered_are_the_embedded_ones_of_the_list) failed in CI. - The Arabic font fallback is requested for any right-to-left interface (`Lang::rtl()`), not only after the language picker sets the flag, so an automatic or saved Arabic gets the installed Arabic face. - The file browser, the update window and Reading Mode's toolbar run their bodies in `rtl::root`, so their far-side buttons and rows are mirrored. - A group in a right-to-left ribbon row is measured from the right edge: the cursor's left edge is negative infinity there, so its width was NaN and the ribbon planner dropped every group, never compacting a narrow Arabic window. A narrow window now folds groups into drop-downs. - The mirrored checkbox and radio marks are labelled by their text, so screen readers name them. - Tests: group width in a right-to-left row; the Arabic interface audit checks Reading Mode's toolbar. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: ask for the installed Arabic font separately from the CJK one A previous commit made every right-to-left interface request the installed CJK fallback font (`system_cjk`) so that the installed Arabic font would load. That also made every Arabic launch read a large system CJK font file when no craft-fonts input is embedded (the Flatpak build has none). `install_ui_fonts` now passes a separate `system_arabic` flag (`Lang::rtl()`): the installed Arabic font is read for it, and `system_cjk` is again set only by a CJK interface or by CJK text on screen. `WordApp::fonts_system_arabic` remembers what was installed, so a language change reinstalls the fonts. Tests: the_installed_arabic_font_is_asked_for_by_arabic_alone (the Arabic flag adds the installed font only when none is embedded, and the CJK request alone adds no Arabic font). Checked a headless screenshot of the Arabic interface without craft-fonts: the installed Arabic font draws the labels. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Fix the AutoSave switch knob: on is at the right, mirrored in Arabic The title bar's AutoSave knob was placed with `lead_x(rtl == on, ..)`, which reverses main's mapping in both directions: left to right it drew the knob at the left when the switch was on, and right to left at the right. main draws it at the right when on and at the left when off, so the mirrored interface must draw it at the left when on: `rtl != on`. The position is now `autosave_knob_x(rtl, on, track)`, pinned by a test for all four cases. A headless screenshot of the off switch shows the knob at the left in English and at the right in Arabic. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: file names and paths in right-to-left lines; kerning row order Tester feedback on the Arabic interface: - File > Open's Recent list, the built-in file browser and the title bar drew file names and paths raw, from the left edge. In an Arabic line the marks of a name ("(نهائي) 2.docx") and the folders of a path with Arabic parts were scrambled. Rows now start at the side reading starts from (`rtl::lead_x`), names go through `i18n::isolate` (a first-strong isolate, with a left-to-right mark before the extension so `2.docx` stays `2.docx`) and paths through `i18n::isolate_path` (a left-to-right isolate with a mark after every separator, so neighbouring Arabic folders do not swap). Other languages get the text untouched; saved and spoken text never carries the marks. - Font dialog, Kerning row: the dialog's grid is drawn left to right inside a right-to-left window, so "Points and above" came after its number; in an Arabic interface it now comes first, so the line reads "14 نقطة فأكثر" from the right. (The grid's columns themselves still keep their left-to-right order.) Tests: names_and_paths_are_isolated_in_a_right_to_left_interface. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: mirror through craft_ui::direction and Style::right_to_left The suite's craft-ui now has the direction helpers (`craft_ui::direction`) and its egui fork a `Style::right_to_left` that mirrors egui's own chrome. Use them instead of our parallel wrappers: - `rtl::sync(ctx)` applies the interface direction every frame the language's changes (`direction::apply`): it sets the shared direction state and `Style::right_to_left` on every theme, which draws check boxes and radio buttons with the mark at the reading start, mirrors submenus, scroll bars, title bars and the combo boxes' arrows (the Print page's choice lists, the font list) and opens combo menus from the right. - `rtl::checkbox`, `radio_value`, `radio` and `indeterminate_checkbox` are gone: about 200 call sites use plain `ui.checkbox`, `ui.radio_value`, ... and egui does the mirroring (and names the control by its text, as a plain widget). - `lead_x`, `lead_center`, `lead_top`, `start_panel` and `end_panel` delegate to `craft_ui::direction`. `root`, `flows`, `island`, `grid`, `menu`, `vertical`, `far_side` and the other layout helpers stay: the shared crate has no equivalent that fits them. Visual changes: the combo boxes' drop-down arrows and lists are mirrored in Arabic; nothing changes in left-to-right languages (checked headless screenshots of the English main window, Font dialog and Print page). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Recent files and the file browser: full path on hover, long names keep their start Review feedback: - Each row of File > Open's Recent list shows the document's full path as its tooltip (the path line under the name can be longer than the row). - A long name in the built-in file browser is laid out in one row, with an ellipsis at its end, from the side reading starts from, instead of being clipped at the row's edge: in a right-to-left interface the old text anchored at the right edge cut the end of an Arabic name and the start of a Latin one. The whole name is the row's tooltip when it was shortened. Checked headless screenshots of the file browser with a long English and a long Arabic name. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Font combos in a right-to-left row: the arrow joins its field on the left The ribbon's font name and size boxes are drawn from a field and a separate arrow box that share one outline: the field is square and the arrow's box is rounded on the side where the arrow goes, painted a border width under the field. That assumed the arrow is right of the field. In a right-to-left row (the Arabic ribbon) the arrow comes after the field on its left, so the field was square on the wrong side and the arrow box rounded on the side facing the field: two boxes with a gap-like seam, the arrow looking outside the frame. `arrow_box_shape(rtl, ..)` mirrors the radii and the underlap, and the field's square corners are chosen by the row's direction (the nw/sw pair in right to left). Left-to-right rows are drawn exactly as before (English before/after crops are identical). Test: the_arrow_box_is_rounded_away_from_its_field. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Pin craft-i18n 4612850 (merged Arabic WordCraft rows, craft-i18n#76) The pin and the Flatpak manifest commit move from 1935849 to 4612850, which is on craft-i18n main and adds the 70 Arabic rows this branch relies on. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Pin craft-i18n cac2cf0 (Arabic number agreement and status-bar rows) Moves the pin and the Flatpak manifest commit from 4612850 to the current craft-i18n main, which includes craft-i18n#95 (48bed03) and #81 (ef58ff3). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic: isolate paths shown beside labels; mirror the Print page zoom File > Info shows Location as its own left-to-right path label (cut short with the full path on hover, not wrapped), and the open-failure, Exported and dictionary-file messages isolate their paths. The Print preview zoom puts minus at the slider's left end and plus at its right in Arabic, as the status bar does. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic: mirror the label/field rows of the Font, Info, Paragraph, Page Setup, Insert Table and Options forms rtl::Table lays a form out as an egui Grid in a left-to-right interface (same id, columns and spacing, so the output is unchanged) and, where the interface is mirrored, with each row's cells running from the right, every column as wide as its widest cell. File > Info puts Properties at the right and Statistics after it, and the Font dialog's kerning row reads 14 points and above. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Docs: record the mirrored Arabic interface in the roadmap, gaps and localization parity Progress log, the Arabic language row, gap 11 and the status lines and revision histories of the three docs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: Insert Hyperlink, Word Count, Phonetic Guide, New Style, Modify Style, Encrypt, merge rule and Match Fields dialogs read right to left Their label-and-field rows use rtl::Table, so in a mirrored interface the label is at the right with its field to its left. rtl::Table gains striped() for Match Fields. English renders are pixel-identical (only a caret-blink strip differs in an untouched dialog). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: Line Numbers, Hyphenation, Drop Cap, Caption, Cross-reference, Index, Mark Index Entry and Source dialogs read right to left Their label-and-field rows use rtl::Table (label at the right, field to its left). English renders are pixel-identical. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: Picture Size, Split Cells, Convert Text to Table, Footnote and Endnote, Table Properties, Cell Options and Custom Properties dialogs read right to left Their label-and-field rows use rtl::Table. Picture Size's Height and Width pairs read Height first at the right. English renders are pixel-identical. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: Define New Multilevel List, Track Changes Options, content control properties, custom colours and table style dialogs read right to left Label, field and swatch rows use rtl::Table. English renders are pixel-identical. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: Columns widths, Special Characters, Compare, AutoCorrect and About Models tables read right to left Table columns now start at the right edge (rtl::Table gains top(), min_row_height() and an unset column count). About > Models stays a left-to-right scroll box with its columns listed from the right. English renders are pixel-identical (only the caret blink differs). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: table-of-contents styles, recipient filter and sort, and the ribbon Paragraph group read right to left rtl::Table now stacks its rows top to bottom whatever way the area around it runs (a ribbon group lays out in a row). English renders are pixel-identical (caret blink and a status toast differ in untouched dialogs). The TOC options and recipient filter/sort rows are converted by pattern and not seen in an image. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: Columns preset tiles and Preview label, Macros, Add/Remove arrows, Zoom slider and the Recipient List grid A preset tile is as wide as its caption and centres it (the long Arabic captions ran into the next tile); the Preview column keeps its width so its label no longer wraps letter by letter; Macros puts the name box and list at the reading start with the buttons beside them; the Add >> and << Remove arrows swap in a mirrored interface so they point the way the items move; the Zoom slider is drawn left to right as in English; the Recipient List data grid scrolls left to right instead of running past the left edge. English renders are pixel-identical. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: the chart type and SmartArt layout galleries list their tiles from the right The chart type dialog, the Insert SmartArt dialog and the ribbon's SmartArt Layouts gallery use rtl::Table rows, so the first tile sits at the reading start. English renders are pixel-identical. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Take main's panes/host.rs after the merge The merge kept a use of mirrored() that main's pane-dock fix removed; this branch does not touch the panes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: Columns preset captions are translated in the mirrored tile Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic: one Arabic and one French row in the localization parity table; About > Models columns read from the right The merge left the Arabic and French rows twice in docs/localization-parity.md; each is now a single row (main's current French text, the Arabic row with the dialogs note). About > Models keeps main's fitted table but lists its columns, their alignments and grow weights reversed when the interface is mirrored, so Company starts at the reading edge. The credits.rs change comes from tareq's helper merge. Co-authored-by: tareq <tareq.halaby@gmail.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * About > Models: the mirrored table keeps its own widths and scroll position The columns are listed in reverse when the interface is mirrored, and egui keeps the resizable column widths and the scroll offset by id. A language switched at run time would apply one direction's widths to the other's columns, so the mirrored table uses its own id. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: About > Models alignment mirrors as its comment says The mirrored Models table reversed the per-column right-align flags but did not negate them, so in Arabic the text columns stayed left-aligned and the counts right-aligned, the opposite of the comment ("counts line up on the right (left in RTL)"). Negate after reversing so the text sits at the reading edge and the counts on the left. Keep this line when touching models_ui. * About > Models: a mirrored table that overflows opens at its reading edge The reversed Arabic table puts Company in the rightmost column, but the horizontal scroll area started at the left, so when the table was wider than the dialog (narrow window, widened columns) Company and Model were offscreen. Stick the scroll area to the right in a mirrored interface. --------- Co-authored-by: Abdelhaq El Amraoui <abdelhaqelamraoui@gmail.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: email <email@email.com> Co-authored-by: tareq <tareq.halaby@gmail.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> |
||
|
|
96e63ee96b |
Merge remote-tracking branch 'origin/fix/missing-pack-bar' into fix/options-language
; Conflicts: ; docs/localization-parity.md |
||
|
|
d9e2b92e41 |
Merge main into fix/missing-pack-bar
Keep the missing-pack-bar entry, the one-language-list entry and main's craft-i18n 9d6d54d region-name pin in the parity changelog. |
||
|
|
03fba6cb10 |
Merge main into fix/one-language-list
Resolve the localization-parity changelog conflict by keeping both the one-language-list entry and main's craft-i18n 9d6d54d region-name pin. |
||
|
|
ea25e5dd3a |
CP4.3: craft-i18n.rev at 9d6d54d (craft-i18n#145): region names and the Language (Region) template in 35 locales (#5522)
Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
68512842aa |
CP4.3: merge fix/one-language-list
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco |
||
|
|
3e9e36796c |
CP4.3: localization-parity proofing bullet reads once
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco |
||
|
|
3122c16e38 |
CP4.3: merge fix/missing-pack-bar
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco |
||
|
|
3b37679f7c |
CP4.3: merge fix/missing-pack-bar
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco ; Conflicts: ; ROADMAP.md ; docs/gaps.md ; docs/localization-parity.md |
||
|
|
26c662961d |
CP4.3: merge fix/one-language-list
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco ; Conflicts: ; ROADMAP.md ; docs/gaps.md ; docs/localization-parity.md |
||
|
|
26a8182e91 |
CP4.3: merge origin/main (craft-i18n at cde4900)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco ; Conflicts: ; ROADMAP.md ; docs/gaps.md ; docs/localization-parity.md |
||
|
|
d64ef7c3f9 |
CP4.3: craft-i18n.rev at cde4900 (craft-i18n#136): Options › Language, missing-pack bar and Language pack language-name rows in 35 locales (#5518)
Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8fb57e1f6b | Merge branch 'fix/missing-pack-bar' into fix/options-language | ||
|
|
9819939d98 | Merge branch 'fix/one-language-list' into fix/missing-pack-bar | ||
|
|
94b89d95d6 |
CP4.3: review — Set Proofing Language lists every built-in and Language pack language, not 38 (CodeRabbit)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco |
||
|
|
c45945a2ea |
CP4.3: Options > Language: Your languages with proofing status, Add a language picker, Set as default
The Dictionaries folder moves here from Proofing with the Dictionary files list (#1063) kept. Set as default sets only the saved default proofing language (the new pure review.language.preferred command), and proof.packs returns the languages rows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco |
||
|
|
efb5071526 |
CP4.3: the missing-pack bar works on a fresh install and counts the caret's language
With no list of packs yet the bar offers a language with no dictionary and Install fetches the list, then the pack. The caret's typing language counts, the spelling context menu heading is in the interface language, and closing the bar is remembered per document, pack locale and tag. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco |
||
|
|
ae151ff341 |
CP4.3: one language list: Language pack languages in Set Proofing Language, Review > Language and the status bar
Every published pack is named in English, Valencian is ca-ES-valencia, and rows are matched by tag (language, script, region, variant), never by the name shown. One region helper (packs::is_region, packs::without_region) next to pack_for_tag serves the engine and the UI. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco |
||
|
|
682ce4318a |
CP4.3: craft-proof pin at craft-libs 01315d9: French elision suggestions first; Valencian reads its pack (#5509)
* CP4.3: craft-proof pin at craft-libs main 01315d9 (#589, #590): French elision suggestions first; ca-ES-valencia reads the ca-valencia pack Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco * CP4.3: review — ROADMAP has one status record; the older copy left by a merge is dropped (CodeRabbit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco * CP4.3: review — French is proofed from its Language pack in the support tables, not "no proofing" (CodeRabbit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1ff68d64e3 |
i18n: Arabic interface with mirrored RTL shell (+ parity docs) (#5370)
* Arabic interface: mirrored right-to-left window and Arabic UI fonts Adds the Arabic (`ar`) interface language's code: its catalogs are craft-i18n's `locales/ar`, so this changes no catalog. - `ar` is registered in `i18n::LANGUAGES` and `Lang::rtl()` says it is written right to left; its translations are used as written (egui's text layout orders each line's bidi text itself). A ribbon location reads from the right with a leftwards mark. - `rtl.rs`: the window is mirrored. Containers open in `rtl::root`; columns, grids, checkboxes, radio buttons, menus, rows, panels, the dialog tab strip and the ribbon's scrolling row follow the language's direction. Title bar, status bar, ribbon tabs and groups, File pages (including Print, with the settings on the right and a solid scrollbar gutter), dialogs, menus and the task panes (their docking edge swaps) are laid out from the right. egui's `Grid` panics in a right-aligned layout, so grids go through `rtl::grid`. - Arabic UI font fallback: the craft-fonts Arabic face, else an installed Arabic font, loaded when the interface is Arabic. - The "Auto (200%)" size label keeps its percent sign inside the isolated number. - Tests: Arabic locale, saved preference and lookup order on fixture catalogs; an interface audit (skipped without the craft-i18n Arabic catalog) that lays the app out right to left at several widths, checks tab, group, pane, Print and Go To positions and that nothing panics. The Arabic work builds on Abdelhaq El Amraoui's mirrored-interface PR (#737). Co-Authored-By: Abdelhaq El Amraoui <abdelhaqelamraoui@gmail.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: pin craft-i18n at a commit with the Arabic catalog; review fixes - craft-i18n.rev and the Flatpak manifest pin 19358491 (craft-i18n main), the first commit line that has locales/ar/wordcraft.tsv. The old pin had no Arabic catalog, so `ar` was registered but not embedded and two i18n tests (the_language_setting_is_a_ui_command_and_persists, the_languages_offered_are_the_embedded_ones_of_the_list) failed in CI. - The Arabic font fallback is requested for any right-to-left interface (`Lang::rtl()`), not only after the language picker sets the flag, so an automatic or saved Arabic gets the installed Arabic face. - The file browser, the update window and Reading Mode's toolbar run their bodies in `rtl::root`, so their far-side buttons and rows are mirrored. - A group in a right-to-left ribbon row is measured from the right edge: the cursor's left edge is negative infinity there, so its width was NaN and the ribbon planner dropped every group, never compacting a narrow Arabic window. A narrow window now folds groups into drop-downs. - The mirrored checkbox and radio marks are labelled by their text, so screen readers name them. - Tests: group width in a right-to-left row; the Arabic interface audit checks Reading Mode's toolbar. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: ask for the installed Arabic font separately from the CJK one A previous commit made every right-to-left interface request the installed CJK fallback font (`system_cjk`) so that the installed Arabic font would load. That also made every Arabic launch read a large system CJK font file when no craft-fonts input is embedded (the Flatpak build has none). `install_ui_fonts` now passes a separate `system_arabic` flag (`Lang::rtl()`): the installed Arabic font is read for it, and `system_cjk` is again set only by a CJK interface or by CJK text on screen. `WordApp::fonts_system_arabic` remembers what was installed, so a language change reinstalls the fonts. Tests: the_installed_arabic_font_is_asked_for_by_arabic_alone (the Arabic flag adds the installed font only when none is embedded, and the CJK request alone adds no Arabic font). Checked a headless screenshot of the Arabic interface without craft-fonts: the installed Arabic font draws the labels. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Fix the AutoSave switch knob: on is at the right, mirrored in Arabic The title bar's AutoSave knob was placed with `lead_x(rtl == on, ..)`, which reverses main's mapping in both directions: left to right it drew the knob at the left when the switch was on, and right to left at the right. main draws it at the right when on and at the left when off, so the mirrored interface must draw it at the left when on: `rtl != on`. The position is now `autosave_knob_x(rtl, on, track)`, pinned by a test for all four cases. A headless screenshot of the off switch shows the knob at the left in English and at the right in Arabic. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: file names and paths in right-to-left lines; kerning row order Tester feedback on the Arabic interface: - File > Open's Recent list, the built-in file browser and the title bar drew file names and paths raw, from the left edge. In an Arabic line the marks of a name ("(نهائي) 2.docx") and the folders of a path with Arabic parts were scrambled. Rows now start at the side reading starts from (`rtl::lead_x`), names go through `i18n::isolate` (a first-strong isolate, with a left-to-right mark before the extension so `2.docx` stays `2.docx`) and paths through `i18n::isolate_path` (a left-to-right isolate with a mark after every separator, so neighbouring Arabic folders do not swap). Other languages get the text untouched; saved and spoken text never carries the marks. - Font dialog, Kerning row: the dialog's grid is drawn left to right inside a right-to-left window, so "Points and above" came after its number; in an Arabic interface it now comes first, so the line reads "14 نقطة فأكثر" from the right. (The grid's columns themselves still keep their left-to-right order.) Tests: names_and_paths_are_isolated_in_a_right_to_left_interface. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic interface: mirror through craft_ui::direction and Style::right_to_left The suite's craft-ui now has the direction helpers (`craft_ui::direction`) and its egui fork a `Style::right_to_left` that mirrors egui's own chrome. Use them instead of our parallel wrappers: - `rtl::sync(ctx)` applies the interface direction every frame the language's changes (`direction::apply`): it sets the shared direction state and `Style::right_to_left` on every theme, which draws check boxes and radio buttons with the mark at the reading start, mirrors submenus, scroll bars, title bars and the combo boxes' arrows (the Print page's choice lists, the font list) and opens combo menus from the right. - `rtl::checkbox`, `radio_value`, `radio` and `indeterminate_checkbox` are gone: about 200 call sites use plain `ui.checkbox`, `ui.radio_value`, ... and egui does the mirroring (and names the control by its text, as a plain widget). - `lead_x`, `lead_center`, `lead_top`, `start_panel` and `end_panel` delegate to `craft_ui::direction`. `root`, `flows`, `island`, `grid`, `menu`, `vertical`, `far_side` and the other layout helpers stay: the shared crate has no equivalent that fits them. Visual changes: the combo boxes' drop-down arrows and lists are mirrored in Arabic; nothing changes in left-to-right languages (checked headless screenshots of the English main window, Font dialog and Print page). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Recent files and the file browser: full path on hover, long names keep their start Review feedback: - Each row of File > Open's Recent list shows the document's full path as its tooltip (the path line under the name can be longer than the row). - A long name in the built-in file browser is laid out in one row, with an ellipsis at its end, from the side reading starts from, instead of being clipped at the row's edge: in a right-to-left interface the old text anchored at the right edge cut the end of an Arabic name and the start of a Latin one. The whole name is the row's tooltip when it was shortened. Checked headless screenshots of the file browser with a long English and a long Arabic name. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Font combos in a right-to-left row: the arrow joins its field on the left The ribbon's font name and size boxes are drawn from a field and a separate arrow box that share one outline: the field is square and the arrow's box is rounded on the side where the arrow goes, painted a border width under the field. That assumed the arrow is right of the field. In a right-to-left row (the Arabic ribbon) the arrow comes after the field on its left, so the field was square on the wrong side and the arrow box rounded on the side facing the field: two boxes with a gap-like seam, the arrow looking outside the frame. `arrow_box_shape(rtl, ..)` mirrors the radii and the underlap, and the field's square corners are chosen by the row's direction (the nw/sw pair in right to left). Left-to-right rows are drawn exactly as before (English before/after crops are identical). Test: the_arrow_box_is_rounded_away_from_its_field. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Pin craft-i18n 4612850 (merged Arabic WordCraft rows, craft-i18n#76) The pin and the Flatpak manifest commit move from 1935849 to 4612850, which is on craft-i18n main and adds the 70 Arabic rows this branch relies on. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Pin craft-i18n cac2cf0 (Arabic number agreement and status-bar rows) Moves the pin and the Flatpak manifest commit from 4612850 to the current craft-i18n main, which includes craft-i18n#95 (48bed03) and #81 (ef58ff3). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic: isolate paths shown beside labels; mirror the Print page zoom File > Info shows Location as its own left-to-right path label (cut short with the full path on hover, not wrapped), and the open-failure, Exported and dictionary-file messages isolate their paths. The Print preview zoom puts minus at the slider's left end and plus at its right in Arabic, as the status bar does. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Arabic: mirror the label/field rows of the Font, Info, Paragraph, Page Setup, Insert Table and Options forms rtl::Table lays a form out as an egui Grid in a left-to-right interface (same id, columns and spacing, so the output is unchanged) and, where the interface is mirrored, with each row's cells running from the right, every column as wide as its widest cell. File > Info puts Properties at the right and Statistics after it, and the Font dialog's kerning row reads 14 points and above. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * docs: record Arabic mirrored interface landing (#1195) Update the language counts, Arabic parity row, gap #11, and UI/ROADMAP status so they match the craft-i18n catalog and mirrored shell already on this branch. * Fix Organizer Copy arrow in RTL and refresh roadmap status. Point the Copy glyph at the Document column when the shell is mirrored, and bump docs/roadmap.md status/revision history for Arabic UI (#1195). * fix(ui): keep pane dock left/right physical in RTL ui.paneDock and view.stylesPaneDock now store and report physical window sides; only unset defaults still open on the reading-start edge. Also wrap the language-packs grid for RTL, document the contract, and load a system Arabic face for Arabic names in LTR UIs. * docs: clarify mirrored default pane edges in RTL Start-default panes open on the physical right in Arabic; end-default panes open on the physical left — not every never-docked pane on the right. * refactor(ui): one PaneState for a saved pane place The four saved-dock arms built the same PaneState; only the place and whether it was set explicitly differ. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style(ui): rustfmt the panes re-exports Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: The Legend Of MTS Dev <tlo.mts.tsh@gmail.com> Co-authored-by: Abdelhaq El Amraoui <abdelhaqelamraoui@gmail.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: email <email@email.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> |
||
|
|
8865455cfb |
CP4.4: review — gap 8 and the localization parity rows say the compounding languages are packs (CodeRabbit)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCYmv6UJmY2QcuhfXco |
||
|
|
0842d4faec |
CP4.4: language-packs-2 pinned (75 packs, adds the compounding languages); craft-proof at craft-libs main's merge of #497 (with #496, en-AU)
- PACK_INDEX: language-packs-2 index.json, sha256 b35c7aef…, built from craft-i18n 20467b63 - craft-proof pinned at storytold/craft-libs 47c9f625 (flag-based compounding, #497; review follow-ups and en-AU, #496) - Options › Language: ProofLang::EnAu has no Hunspell dictionary name of its own (English uses SCOWL) - docs: Danish, German, Dutch, Swedish, Norwegian, Hungarian, Estonian, Croatian, Persian and Esperanto are packs; Mongolian and Thai stay hidden Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
72327bdd36 |
CP4.4: pin language-packs-1 index
craft-i18n's first Language pack release is published (https://github.com/storytold/craft-i18n/releases/tag/language-packs-1, 61 packs, built from craft-i18n main 4504e288). PACK_INDEX now pins its index.json sha256 instead of the all-zero placeholder. Docs say which languages it covers and which wait on craft-proof (compounding) or are excluded on licence grounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
1146691b90 |
CP4.3: review — craft-libs at #485 f88bd48 / #486 83c2e7b; test-source warning in proof.packs, the CLI and the bar; About › Language data a fixed-height list; Cancel reaches a stalled download
- proof.packs reports index_override; the CLI prints the test source; the bar's Install tooltip says it turns on downloading of fonts and Language packs, and names a test source. - Docs: Install works once language-packs-1 is published (until then only a test index); with a test index the sha256 only catches a broken download. - About › Language data keeps its size: a fixed-height scrolling list, licence texts read again only when the dictionaries change. - The engine's Cancel is the font store's own flag (no snapshot), so a cancel stops a fetch or a stalled download too. - Index text shown without control, bidi or format characters; long names cut; the bar works out what to offer again when the list changes (index generation, not its length). - ui_shot's store is a temporary folder (or storeDir); tests reset the backend even on panic; a fixture pack proves per-run proofing and hyphenation from a pack. - The default-build check follows craft-proof's carry-over (de, pl, pt, it, ru embedded until packs ship; CRAFT_PROOF_EMBED=en is English only); the Polish hyphenation check runs again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
2ef046bad9 |
CP4.3: craft-libs pinned to Lane 4's pushed heads; test-source warning, upstream source links, code-drawn bar close button
- craft-proof (benandez/craft-libs 6ae4f03, storytold/craft-libs#485) and craft-font-store / craft-font-catalog (630e42f, #486) by git rev instead of local paths. - ProofLang::Pack: the bar offers every language with a pack (zxx/und never). - Options › Language warns when CRAFT_LANGUAGE_PACK_INDEX supplies a test index. - Pack `source` (upstream link) when the index gives it, else the archive. - The bar's close button is the code-drawn close icon: "✕" had no glyph in the interface fonts and egui panicked drawing it. - `cargo xtask language` allows our own ru-extra, which stays embedded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
e5d46bb242 |
CP4.3: Language packs: proof.packs, proof.installPack, proof.removePack; Options › Language list, missing-pack bar, About › Language data
- engine `packs`: the packs Backend (the font store on the desktop and CLI, a fixed list in tests and ui_shot), the pinned index, installs and removals through craft-font-store's `language-pack` kind, craft-proof reading the store's `language-packs/` folder at once. Commands open nothing and never turn downloading on (only an Install click in the app does); confined automation can't write the packs folder outside its write root. - ui-egui `language_packs`: File › Options › Language › Language packs (name, size, licence, source, Install/Remove, progress, errors), the missing-pack bar (dismissible per document, never for a language without a pack), About › Language data (full licence texts). - CP4.4: `cargo xtask language` (in `ci`): the default build embeds only en-* Language data. - CP4.5: docs (help, MCP, control protocol, gaps #8, localization parity, roadmap). - Path deps to Lane 4's worktrees while developing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
211377f550 |
CP2.3: review — pin craft-proof to craft-libs#483's review fixes (each English checks its own SCOWL dictionary) and craft-i18n to #114's merge
craft-proof moves to 0675d58: an English no longer takes another English's SCOWL words (en-US flagged colourblind, en-GB colorblind), British English keeps -ize, and English suggestions drop junk splits and names. craft-i18n.rev and the Flatpak source move to storytold/craft-i18n#114's merge commit (059cbf9: judgment/judgement right in every English). No wordcraft test depended on the dialects crossing (engine 1120 + 43, layout 578 pass). localization-parity's English row says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
09e9522a35 |
CP2.3: English spelling reads SCOWL 2026; regression tests for #40, #520, #1281, #1045
craft-proof moves to storytold/craft-libs#483's head (e6e948f, on #482's 9e5862a): each English checks against its own SCOWL rel-2026.02.25 dictionary through the affix engine (ordinals via COMPOUNDRULE), with en-variants.txt as the dialect policy. Lookup order: dictionaries folder, [English pack], SCOWL, system Hunspell, the built-in Moby list; Moby stays for hyphenation. craft-i18n.rev and the Flatpak source move to storytold/craft-i18n#109's merge commit (efb1127), which adds the SCOWL files. Typed-text tests pin British spellings in en-GB (#40), modern words (#520), Canadian/Australian/NZ/Indian English (#1281, #1045) and ordinals; they skip without the Language data, as the other data tests do. Docs: typing-parity, localization-parity, ROADMAP progress log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
c967f934ca |
CP1.3: review — gaps and localization-parity point at craft-proof (craft-libs) and craft-i18n language-data instead of the removed crates/proof and assets/spelling
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
54517ace14 |
Word count: each Chinese and Japanese character is a word (#4547) (#4629)
count_words split only on whitespace, so unspaced Chinese or Japanese text counted as one word per run. Word counts each Han ideograph and kana as a word; Hangul (spaced) still counts by tokens. Fixes the status bar, the Word Count dialog and NUMWORDS, which all use count_words. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
acccf913ab |
Traditional Chinese interface: draw hanzi to the Taiwan standard with an installed TC font (#3380)
* i18n: Traditional Chinese interface draws hanzi with an installed TC font The zh-hant interface shared the Simplified Chinese font order, so its first CJK face was the embedded Noto Sans CJK SC and traditional characters were drawn with mainland glyph shapes (說, 資, 這, 為, 戶). On the desktop, a Traditional Chinese interface now puts an installed Traditional Chinese font before every CJK face: PingFang TC on macOS, Microsoft JhengHei on Windows, Noto Sans CJK TC on Linux. Noto Sans CJK SC stays after it as the fallback. Without an installed TC font, and on wasm, the order is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Credits: add TobyWu666 to contributors/people.toml Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n: find Traditional Chinese fonts at the root of a font folder Linux user font folders (~/.fonts, ~/.local/share/fonts) often hold font files directly; the Traditional Chinese candidates only named files in distribution subfolders, so those fonts were missed (CodeRabbit review). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n: skip the Traditional Chinese font test without embedded catalogs Since the catalogs come from craft-i18n (#4072), a build without CRAFT_I18N_DIR is English only, so the test now skips like the other catalog tests. Also rustfmt hant_font_chars. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
95ba3d07b4 | docs: update paths of moved files | ||
|
|
a605fa5ca7 | M-EGUI037: reconcile WordCraft main and preserve IME editing | ||
|
|
c4974d9e8d | egui037: reconcile current main, qualify dependency pins and preview accessibility | ||
|
|
fd071202e7 | E37: migrate WordCraft to egui 0.37 with guarded IME and capture lifecycle | ||
|
|
a7ec12d513 | Merge origin/main; fix false missing-opening-mark flags on correctly opened questions; stabilise dictionary test | ||
|
|
8fceea2676 |
chore(i18n): remove the in-repo catalogs and parser
crates/ui-egui/src/i18n/*.tsv and catalog.rs are gone: translations live
in storytold/craft-i18n (locales/<l>/wordcraft.tsv, synced at
wordcraft@806a945) and are credited in its ATTRIBUTION.md. To carry an
open PR's catalog rows over, from a craft-i18n checkout:
tools/sync_app.py ../wordcraft
|
||
|
|
c8c3dfbfb2 |
Merge origin/main into spanish-proofing
Resolve the docs/localization-parity.md status-line conflict, keeping both changes. |
||
|
|
806a945d85 |
i18n tests: missing translations never fail; one report lists them (#4043)
* CI: line-table debug info for dev builds, none for dependencies * CI: one integration-test binary per crate * CI: nextest with doctests, test build without examples, one wasm check * CI: the interface audit reports missing catalog entries instead of failing * i18n tests: the same checks for every language, none fail on a missing translation * i18n tests: mechanism tests use fixtures, not catalog text * Docs: the interface audit, not CI as a whole, passes with missing catalog entries * i18n tests: disabled reasons a catalog lacks are reported, not a failure * i18n tests: one missing-translation report, printed by cargo xtask ci * i18n docs: the missing-translation report * xtask: the i18n report reuses the test run's build |
||
|
|
2fb713768e |
Merge origin/main into spanish-proofing
Resolve the docs/localization-parity.md status-line and revision-history conflicts, keeping both changes. |
||
|
|
c795e3cd41 |
CI: one test binary per crate, nextest, line tables only (#4025)
* CI: line-table debug info for dev builds, none for dependencies * CI: one integration-test binary per crate * CI: nextest with doctests, test build without examples, one wasm check * CI: the interface audit reports missing catalog entries instead of failing * Docs: the interface audit, not CI as a whole, passes with missing catalog entries |
||
|
|
e05cea19d4 |
Merge origin/main into spanish-proofing
Resolve the ROADMAP.md and docs/localization-parity.md status-line conflicts, keeping both changes. |
||
|
+27 |
c539994997 |
Translate ribbon disabled-reason tooltips (#2678) (#2680)
* #1086: ribbon tooltips show why a control is disabled Hover hints give the command's own reason, cover split-button arrows, menu buttons and the font/size/outline boxes, and show on keyboard focus. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * #2678: translate disabled-control reasons in every interface language Ribbon hints show a command's reason for being off; the 28 reasons now have entries in all 22 catalogs (machine-written), and an audit test reads every enabled check from the engine's command sources and fails when a catalog lacks one. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * #2678: translate the two disabled reasons added on main Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * #2678: drop the unused combo import left by the merge Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * #2678: translate the new disabled reasons and the da/nl/ro/sl catalogs; audit skips match patterns Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * #2678: translate the no-document hint in every catalog and audit it; ROADMAP states the audit's real coverage Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Mail merge: go to any record, and merge or print a range of records (#3028) (#3894) * Restrict Editing: a file command that changes the document is checked too `always_allowed` waved through every `file.*` id, so on a protected document the file commands that do change it went unchecked: the privacy options and the properties wrote document settings, and the inspector's removals deleted content. The privacy options' own comment already said they were "refused on a protected document"; that wasn't true. The allow-list now names the file commands that don't change the open document, and the ones that do are checked like any other command. Reading the document stays allowed: the inspector without removals, the privacy settings, the info, and saving a version snapshot. Restoring a stored version replaces the whole document, and a snapshot's own protection is stale — restoring one lifted the protection and dropped its password. The live document's protection, password and formatting limit now survive a restore. Co-Authored-By: opencode <noreply@opencode.ai> * Restrict Editing: the user name can't change under protection `file.setAuthor` sets the name a protected document's named exception ranges are checked against, and the author new revisions and comments are recorded under. `always_allowed` waved through every `file.*` id, so on a protected document the name could be changed: an agent could take the identity of a named editor and unlock their exception range, and forge the authorship of the revisions it leaves behind. A protected document refuses the change now, with the rule Word uses for a content control's properties: stop protection first. The check is in the command itself — `file.setAuthor` is a pure command, so `check_before` never sees it — and in `check_before` for the day it isn't. Word lets the user name change under protection: it is a preference there. This project doesn't, because that identity gates what may be edited. Co-Authored-By: opencode <noreply@opencode.ai> * Restrict Editing: a protected document's protection needs an explicit stop The guard in `restrict` refused a protected document only when its protection had a password, so a password-less one could have its protection changed — and `file.protect {"mode": "none"}` stopped it outright. An agent could strip a user's protection silently and then edit the document freely. Two rules now, as Word has them (Stop Protection first): - The protection of a protected document doesn't change, whatever the mode. The formatting-only protection doesn't count: it stores the mode "none", and it limits the styles, not the editing. - Stopping the protection is explicit. The "none" mode is the shorthand for it, but on a protected document only `{"stop": true}` stops it — the way the pane's Stop Protection button does it — so a command that merely names a mode can't strip it. The password still guards the stop, and the formatting-only path (the mode "none" with the formatting restriction) still works. `cmd::tools::tests::protection_blocks_edits` stops the protection with `{"mode": "none"}`; it has to stop it explicitly now. Co-Authored-By: opencode <noreply@opencode.ai> * Restrict Editing: the post-check compares the settings and the metadata `outside_unchanged` is the check that rolls a command back when it changed anything outside the editable regions. It compared the text, the formatting, the page setup, the document defaults and the styles — but not the document's settings, its metadata, its custom properties, the resolved flag of its comments, its list definitions or its bibliography sources. A command that changed the metadata of a protected document was accepted and never rolled back: `file.properties {"title": "Forged title"}` on a protected document with an exception changed the title and stayed. The settings, the core properties, the custom properties, the comments' resolved flags, the list definitions and the sources are compared now, so a command that changes them is rolled back like any other change outside the editable regions. The revisions and the media store stay out of it on purpose: an edit inside an exception legitimately leaves a revision, and a picture inserted there adds to the media store. The list definitions and the sources are compared like the styles: the ones the document had must be unchanged, but a new one may be added — a list or a citation inserted inside an editable region adds its definition, like applying a style adds one. Saving is unaffected: it writes the metadata, but `file.save` is a pure command and isn't post-checked, and the protection itself is changed by `review.restrict`, which is always allowed. Co-Authored-By: opencode <noreply@opencode.ai> * The protection is stopped explicitly, and the docs say so The test that stopped the protection used the "none" shorthand, which a protected document now refuses; it stops it the explicit way instead, and pins that the shorthand doesn't stop it. `file.protect` says the same: it starts enforcement, and "none" doesn't stop a protected document. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the Restrict Editing gaps that are closed Co-Authored-By: opencode <noreply@opencode.ai> * A password guards the protection whatever its mode The guard that stops the protection of a protected document from changing asked whether the editing was restricted, so a formatting-only protection — the mode "none", which limits the styles rather than the editing — was exempted. A formatting-only protection can carry a password, and that password guarded it: it was exempt from the guard, so `{"mode": "readOnly", "password": "other"}` replaced the protection and the password with ones of the caller's choosing, and the protection could then be stopped with them. A password guards the protection whatever its mode, as it did before. Co-Authored-By: opencode <noreply@opencode.ai> * The CLI's --author names the session, it doesn't change the document `--author` dispatched `file.setAuthor`, which a protected document refuses now that the name is checked as the identity its exceptions are matched against — so a script that named itself on a protected file failed before it ran a single command. The flag names the session, not the open document, so it sets the name directly, with the same normalisation and blank-name check the command uses. `file.setAuthor` keeps its protection check for the callers that mean to change the document's identity. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the progress docs' status lines carry today's date Co-Authored-By: opencode <noreply@opencode.ai> * The document's properties and a version restore need the protection stopped Two gaps the review found, both of them a change to a protected document that went through without the protection seeing it. The properties (`file.properties`) and the privacy options (`file.privacyOptions`) are document-wide state, and they aren't tracked: under tracked changes they changed the document with no revision to show for it, and under the other modes they were only rolled back by the post-check, after the command had run. They are refused now, before the command runs, as the content control's properties are. A version restore (`file.versions`) replaces the whole document, including the content the protection kept read-only, and it did that without the password: `file.versions` is a pure command, so the protection never saw it. Keeping the live protection and its password stopped them being dropped, but the content underneath was still replaced. A restore needs the protection stopped first, and the password with it when the document has one. The live protection still wins over the snapshot's stale one, so a restore can't lock the user out of their own document either. Co-Authored-By: opencode <noreply@opencode.ai> * The inspector's removals are refused in every protected mode `file.inspect` with removals takes content out of the document — the comments, the revisions, the properties, the hidden text, the headers. Under readOnly and comments it was refused, or rolled back by the post-check where the document had an exception; under trackedChanges nothing stopped it, so a document that tracks changes could have its comments and revisions taken out with no revision to show for it. The removals are refused now, in every mode a protected document can be in, next to the properties and the privacy options. An inspection without removals only reads the document, and still works. The protection test covers the modes now, not only readOnly. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the session author, not the user name "the user name can't change while the document is protected" was the wrong term twice over: it is the session author — the name new tracked changes and comments are recorded under, and the one a named exception is matched against — and it is `file.setAuthor` that refuses to change it. The CLI's `--author` names the session directly, so it works on a protected document. The parity row, the two status lines and the two progress rows now say that. Co-Authored-By: opencode <noreply@opencode.ai> * Mail merge: go to any record, and merge or print a range of records (#3028) Preview Results gets First Record and Last Record buttons and a record number box; a number typed there and Enter preview that record. Finish & Merge is now a menu. Edit Individual Documents... and Print Documents... ask for all records, the current record, or a From and To range by record number, and Print Documents opens Print for the merged document. The dialog checks the range before Save Changes asks about unsaved work. New commands mailings.first, mailings.last, mailings.goToRecord and mailings.printDocuments; mailings.finish takes "records": all|current. A range with no records in it is refused instead of replacing the document with an empty one. * #3279: Keep protected session author fixed in CLI and Options * The Document Inspector refuses its removals while the document tracks changes The inspector's removals delete straight from the document: the hidden text, the comments, the revisions and the headers go without ever reaching the engine's tracked-delete path. On a document that tracks changes — and `trackedChanges` protection turns tracking on — the content was gone with no revision to show for it, and the revision trail was broken behind the user's back. The removals are not tracked, so they cannot be: rather than untrack them silently, the inspector refuses the whole removal form while the document tracks changes, and says why ("the document tracks changes: stop tracking before removing content"). The check comes before anything is removed, so the document is left untouched, and reading the document — the inspector without removals — keeps working. A document that does not track changes is inspected as before. Co-Authored-By: opencode <noreply@opencode.ai> * The inspector's tracking refusal doesn't cite an unrelated issue #1344 is about the inspector missing hidden text, not about its removals going untracked, so the reference only sent a reader to the wrong place. Co-Authored-By: opencode <noreply@opencode.ai> * A version snapshot belongs to the document it was taken from `Session::set_document` resets everything about the open document — the document id, the document, the selection, the undo history, the password, the file name — but not the version snapshots. A snapshot taken of one document therefore survived `file.new`, `file.open`, `file.newFromTemplate` and `file.recover`, and `file.versions {"restore": index}` then put the previous document's content into the open one. The snapshots go with the document they were taken from: `set_document` clears them, so a restore can only ever bring back a version of the document that is open. Every caller replaces the document with another one (a new or opened file, a recovered copy, a merge result, a sheet of labels), so none of them loses anything it needs. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the inspector's tracking refusal and the version snapshot's scope Co-Authored-By: opencode <noreply@opencode.ai> * A comparison clears the version snapshots of the document it replaces `review.compare` and `review.combine` open the original with `s.doc = doc`, which bypasses `set_document` — so the version snapshots of the document the comparison replaces survived it, and a restore could put the previous document back over the comparison result. The snapshots go with the document they were taken from, as everywhere else. The document itself is still assigned directly: `set_document` resets the undo history, and the comparison's own undo step would go with it. Co-Authored-By: opencode <noreply@opencode.ai> * #3277: Test Inspector refusal without protection * #1327: Keep snapshots with their document through Compare * #1327: Preserve file identity after rejected document replacement * test: mark the portal test's blocking sleep as intentional (SonarCloud) (#3989) The future is polled by hand with no async runtime, so a blocking sleep is how the test models a slow dialog. 🤖 Generated with Codebuff Co-authored-by: Codebuff <noreply@codebuff.com> * Engine: no dead-code warnings on wasm (#3542) * fix(engine): do not treat decimal points as sentence boundaries in Sentence case In crates/engine/src/cmd/format.rs, convert_chars treated any period as a sentence boundary, causing numbers with decimal points (e.g. 3.14, 2.5) to capitalize the subsequent word in Sentence case. Check if a period is surrounded by ASCII digits, and do not trigger uppercase capitalization when encountering a decimal point. Closes #3485 * proof: drop U+FE0F from FORBIDDENWORD and NOSUGGEST flags too Review follow-up: the directives that name one flag read the same emoji form, so they now skip the variation selector like PFX/SFX and stem flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fmt: rustfmt equation array centring * #3108 review: behind-text and in-front-of-text floats don't grow the row FredApps' review: the row-growth loop counted every non-inline object, so a page-anchored logo or watermark behind a cell's text would stretch its row to the object's bottom edge - contradicting table.rs (behind-text frames count for vertical alignment but don't increase the row's flow height) and Word. Only floats that take part in the text flow (Square, Tight, Through, TopAndBottom) grow the row now, the same classification layout already uses elsewhere for "takes part in wrapping". New test: a 300pt behind-text and a 300pt in-front-of-text picture each leave the row at its text height; it fails with the old condition. The existing Square-wrap tests and cell_frame_centres_empty still pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Engine: no dead-code warnings on wasm `cargo check -p wordcraft-engine --target wasm32-unknown-unknown` warned about five items whose only callers are platform-gated: - citations `by_author`, user_templates `MAX_ENTRIES` and `label_of`: used only by the desktop (non-wasm) source import and template listing. - speech `voice_language`: used by espeak (Unix but macOS) and the macOS voice picker, so `any(unix, test)`, not `any(not(windows), test)` (which also took in wasm). - speech `primary_tag`: used by those plus the Windows synthesizer, so `any(unix, windows, test)`. Each item now carries exactly its callers' gate rather than a blanket allow(dead_code). Checked with cargo check on Linux, Windows (gnu), macOS and wasm32 (zero warnings each), clippy -D warnings, and the engine tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Integration: #1847 reject comment IDs above u32::MAX * Integration: #3267 leave footnote definitions out of link definitions * Integration: #3471 drop duplicate egui options block; install_ui_fonts sets them * Integration: #2153 drop trailing blank line and use assert! for bool checks in tab_separated_list_markers * Integration: #2268 ODT lengths accept only the standard pc unit for picas * Integration: #2269 LaTeX lengths keep macro names case-sensitive * Integration: #2673 Khmer substitutes return the (list, flag) pair after #3245 * Integration: #2273 Latin weight/style and line styles take precedence over Asian, complex and line-type attributes * Integration: #1851 DOCPROPERTY accepts the reference app's LastSavedBy name * Integration: #1985 only ^0nnn codes read as Windows-1252 * Integration: #2416 collapse the nested vertical-align if (clippy::collapsible_if) * Integration: #2185 hyperlink_target slices with get() and needs the whole field name * Integration: #2034 objects without a stacking order draw above the ones before them * Integration: #3434 drop review-bot notes from test doc comments * Integration: #3419 doc comments back on their own items (EquationFrame and its test above them) * Integration: #2334 test a formula string result continued in a CONTINUE record * Integration: #3429 the based-on-chain test's doc comment back above its test * Integration: #2453 drawing_payload test builds the shape fill as a Fill * Integration: #3257 separate its Find-box test from #2407's recent-files test (auto-merge interleaved them) * Integration: cargo fmt * Integration: #1728 close the layout test the merge resolution left open * Integration: cargo fmt * Integration: cargo fmt * Integration: cargo fmt * Integration: #2722 Dutch registry comment on its own line * Integration: #3203 English text for the keys the Romanian catalog lacks Its complete-catalog test needs every key of every other catalog; the merged PRs added 72 since it was written. Rows carry the English text until translated. * Integration: #1076 English text for the keys the Slovenian catalog lacks Its complete-catalog test needs every key of every other catalog. Rows carry the English text until translated. * Integration: status lines list each change once Combining the tracks' status lines repeated a few entries (#2852, #1500, #2617). * Integration: cargo xtask parity * Integration: cargo fmt * Integration: #1832 drop a useless .into() in the end-of-document citation test clippy::useless_conversion failed cargo xtask ci. * Integration: #2244 VML on/off values read case-insensitively by the shape fill reader Main (#1500) moved VML fill reading into shape_style.rs, where filled="False" still counted as on; #2244's test vml_shape_wrap_side_and_anchor_case_insensitivity expects no fill. * Integration: #2041 English text in the Polish catalog for the keys Italian gained polish_covers_every_key_of_every_other_catalog expects Italian to have only "Style: APA" beyond Polish; #2041 translated 14 newer strings into Italian. Rows carry the English text until translated. * Integration: #2722 English text for four labels the Dutch and Danish catalogs lack every_tab_and_command_is_translated needs Format Shape Pane, Full Screen View, Personal Templates and Underline Color, added by other merged PRs. Rows carry the English text until translated. * Integration: #3335 header Esc test allows the half-pixel return scroll that #3201 brings #3201 makes entering an empty header scroll to it; Esc then scrolls back to the caret, landing 0.5 px from the start (17291.0 vs 17290.5). The test checks the view is back within a pixel. * IME: the caret follows the composition text (#3556) * Header and footer: a click in the other band edits it there (#3557) * README: the app icon above the title * Release 0.10.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The inspector's removals are refused in every protected mode `file.inspect` with removals takes content out of the document — the comments, the revisions, the properties, the hidden text, the headers. Under readOnly and comments it was refused, or rolled back by the post-check where the document had an exception; under trackedChanges nothing stopped it, so a document that tracks changes could have its comments and revisions taken out with no revision to show for it. The removals are refused now, in every mode a protected document can be in, next to the properties and the privacy options. An inspection without removals only reads the document, and still works. The protection test covers the modes now, not only readOnly. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the session author, not the user name "the user name can't change while the document is protected" was the wrong term twice over: it is the session author — the name new tracked changes and comments are recorded under, and the one a named exception is matched against — and it is `file.setAuthor` that refuses to change it. The CLI's `--author` names the session directly, so it works on a protected document. The parity row, the two status lines and the two progress rows now say that. Co-Authored-By: opencode <noreply@opencode.ai> * #3279: Keep protected session author fixed in CLI and Options * The Document Inspector refuses its removals while the document tracks changes The inspector's removals delete straight from the document: the hidden text, the comments, the revisions and the headers go without ever reaching the engine's tracked-delete path. On a document that tracks changes — and `trackedChanges` protection turns tracking on — the content was gone with no revision to show for it, and the revision trail was broken behind the user's back. The removals are not tracked, so they cannot be: rather than untrack them silently, the inspector refuses the whole removal form while the document tracks changes, and says why ("the document tracks changes: stop tracking before removing content"). The check comes before anything is removed, so the document is left untouched, and reading the document — the inspector without removals — keeps working. A document that does not track changes is inspected as before. Co-Authored-By: opencode <noreply@opencode.ai> * The inspector's tracking refusal doesn't cite an unrelated issue #1344 is about the inspector missing hidden text, not about its removals going untracked, so the reference only sent a reader to the wrong place. Co-Authored-By: opencode <noreply@opencode.ai> * A version snapshot belongs to the document it was taken from `Session::set_document` resets everything about the open document — the document id, the document, the selection, the undo history, the password, the file name — but not the version snapshots. A snapshot taken of one document therefore survived `file.new`, `file.open`, `file.newFromTemplate` and `file.recover`, and `file.versions {"restore": index}` then put the previous document's content into the open one. The snapshots go with the document they were taken from: `set_document` clears them, so a restore can only ever bring back a version of the document that is open. Every caller replaces the document with another one (a new or opened file, a recovered copy, a merge result, a sheet of labels), so none of them loses anything it needs. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the inspector's tracking refusal and the version snapshot's scope Co-Authored-By: opencode <noreply@opencode.ai> * A comparison clears the version snapshots of the document it replaces `review.compare` and `review.combine` open the original with `s.doc = doc`, which bypasses `set_document` — so the version snapshots of the document the comparison replaces survived it, and a restore could put the previous document back over the comparison result. The snapshots go with the document they were taken from, as everywhere else. The document itself is still assigned directly: `set_document` resets the undo history, and the comparison's own undo step would go with it. Co-Authored-By: opencode <noreply@opencode.ai> * #3277: Test Inspector refusal without protection * #1327: Keep snapshots with their document through Compare * #1327: Preserve file identity after rejected document replacement * test: mark the portal test's blocking sleep as intentional (SonarCloud) (#3989) The future is polled by hand with no async runtime, so a blocking sleep is how the test models a slow dialog. 🤖 Generated with Codebuff Co-authored-by: Codebuff <noreply@codebuff.com> * #3542: gate the label_of test off wasm32 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Divyanshu Gupta <divyanshu2074@gmail.com> Co-authored-by: dmim2502-commits <234869083+dmim2502-commits@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: vangel76 <vangel1976@googlemail.com> Co-authored-by: aiongg <aiongtaigi@gmail.com> Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com> Co-authored-by: bflatastic <bombayapirat@gmail.com> Co-authored-by: Brandon Thomas <brandon@storyteller.ai> Co-authored-by: Mat Strange <mat@matstrange.net> Co-authored-by: opencode <noreply@opencode.ai> Co-authored-by: skylar taylor-barrick <hi@skylar.cc> Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com> Co-authored-by: Codebuff <noreply@codebuff.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Drop U+FFFE and U+FFFF from ODT export text (#3817) Found while testing crates/formats/src/odt.rs:49-63 (fn x, the ODT text escaper; filter at line 58) Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Cap the number of VML path commands parsed from a path attribute (#3951) Fixes #3683 Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Return None from parse_number when the value overflows f64 (#3577) Found while testing crates/doc/src/locale_number.rs:104 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Fix spurious 零 after a group's trailing zeros in Chinese numerals (#3745) Found while testing crates/doc/src/cjk_numbers.rs:47-55 (Counting::format) Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * picture.corrections: a tiny sharpen value no longer panics in the blur (#3646) (#3830) Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> * Reject signs and non-digits in parse_date fields (#3575) Found while testing crates/engine/src/cmd/controls.rs:113-123 (parse_date) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Layout: keep with next takes the 0.01 pt of slack the other fit checks take (#3816) * Restrict Editing: a file command that changes the document is checked too `always_allowed` waved through every `file.*` id, so on a protected document the file commands that do change it went unchecked: the privacy options and the properties wrote document settings, and the inspector's removals deleted content. The privacy options' own comment already said they were "refused on a protected document"; that wasn't true. The allow-list now names the file commands that don't change the open document, and the ones that do are checked like any other command. Reading the document stays allowed: the inspector without removals, the privacy settings, the info, and saving a version snapshot. Restoring a stored version replaces the whole document, and a snapshot's own protection is stale — restoring one lifted the protection and dropped its password. The live document's protection, password and formatting limit now survive a restore. Co-Authored-By: opencode <noreply@opencode.ai> * Restrict Editing: the user name can't change under protection `file.setAuthor` sets the name a protected document's named exception ranges are checked against, and the author new revisions and comments are recorded under. `always_allowed` waved through every `file.*` id, so on a protected document the name could be changed: an agent could take the identity of a named editor and unlock their exception range, and forge the authorship of the revisions it leaves behind. A protected document refuses the change now, with the rule Word uses for a content control's properties: stop protection first. The check is in the command itself — `file.setAuthor` is a pure command, so `check_before` never sees it — and in `check_before` for the day it isn't. Word lets the user name change under protection: it is a preference there. This project doesn't, because that identity gates what may be edited. Co-Authored-By: opencode <noreply@opencode.ai> * Restrict Editing: a protected document's protection needs an explicit stop The guard in `restrict` refused a protected document only when its protection had a password, so a password-less one could have its protection changed — and `file.protect {"mode": "none"}` stopped it outright. An agent could strip a user's protection silently and then edit the document freely. Two rules now, as Word has them (Stop Protection first): - The protection of a protected document doesn't change, whatever the mode. The formatting-only protection doesn't count: it stores the mode "none", and it limits the styles, not the editing. - Stopping the protection is explicit. The "none" mode is the shorthand for it, but on a protected document only `{"stop": true}` stops it — the way the pane's Stop Protection button does it — so a command that merely names a mode can't strip it. The password still guards the stop, and the formatting-only path (the mode "none" with the formatting restriction) still works. `cmd::tools::tests::protection_blocks_edits` stops the protection with `{"mode": "none"}`; it has to stop it explicitly now. Co-Authored-By: opencode <noreply@opencode.ai> * Restrict Editing: the post-check compares the settings and the metadata `outside_unchanged` is the check that rolls a command back when it changed anything outside the editable regions. It compared the text, the formatting, the page setup, the document defaults and the styles — but not the document's settings, its metadata, its custom properties, the resolved flag of its comments, its list definitions or its bibliography sources. A command that changed the metadata of a protected document was accepted and never rolled back: `file.properties {"title": "Forged title"}` on a protected document with an exception changed the title and stayed. The settings, the core properties, the custom properties, the comments' resolved flags, the list definitions and the sources are compared now, so a command that changes them is rolled back like any other change outside the editable regions. The revisions and the media store stay out of it on purpose: an edit inside an exception legitimately leaves a revision, and a picture inserted there adds to the media store. The list definitions and the sources are compared like the styles: the ones the document had must be unchanged, but a new one may be added — a list or a citation inserted inside an editable region adds its definition, like applying a style adds one. Saving is unaffected: it writes the metadata, but `file.save` is a pure command and isn't post-checked, and the protection itself is changed by `review.restrict`, which is always allowed. Co-Authored-By: opencode <noreply@opencode.ai> * The protection is stopped explicitly, and the docs say so The test that stopped the protection used the "none" shorthand, which a protected document now refuses; it stops it the explicit way instead, and pins that the shorthand doesn't stop it. `file.protect` says the same: it starts enforcement, and "none" doesn't stop a protected document. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the Restrict Editing gaps that are closed Co-Authored-By: opencode <noreply@opencode.ai> * A password guards the protection whatever its mode The guard that stops the protection of a protected document from changing asked whether the editing was restricted, so a formatting-only protection — the mode "none", which limits the styles rather than the editing — was exempted. A formatting-only protection can carry a password, and that password guarded it: it was exempt from the guard, so `{"mode": "readOnly", "password": "other"}` replaced the protection and the password with ones of the caller's choosing, and the protection could then be stopped with them. A password guards the protection whatever its mode, as it did before. Co-Authored-By: opencode <noreply@opencode.ai> * The CLI's --author names the session, it doesn't change the document `--author` dispatched `file.setAuthor`, which a protected document refuses now that the name is checked as the identity its exceptions are matched against — so a script that named itself on a protected file failed before it ran a single command. The flag names the session, not the open document, so it sets the name directly, with the same normalisation and blank-name check the command uses. `file.setAuthor` keeps its protection check for the callers that mean to change the document's identity. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the progress docs' status lines carry today's date Co-Authored-By: opencode <noreply@opencode.ai> * The document's properties and a version restore need the protection stopped Two gaps the review found, both of them a change to a protected document that went through without the protection seeing it. The properties (`file.properties`) and the privacy options (`file.privacyOptions`) are document-wide state, and they aren't tracked: under tracked changes they changed the document with no revision to show for it, and under the other modes they were only rolled back by the post-check, after the command had run. They are refused now, before the command runs, as the content control's properties are. A version restore (`file.versions`) replaces the whole document, including the content the protection kept read-only, and it did that without the password: `file.versions` is a pure command, so the protection never saw it. Keeping the live protection and its password stopped them being dropped, but the content underneath was still replaced. A restore needs the protection stopped first, and the password with it when the document has one. The live protection still wins over the snapshot's stale one, so a restore can't lock the user out of their own document either. Co-Authored-By: opencode <noreply@opencode.ai> * Layout: keep with next takes the 0.01 pt of slack the other fit checks take place_para_in_flow moved a paragraph kept with the next one to a new page when the two needed more than the room left by any amount, while lines, rows and floats fit with 0.01 pt to spare. A kept paragraph whose need came to a rounding error over the page bottom moved on alone. * Release 0.10.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The inspector's removals are refused in every protected mode `file.inspect` with removals takes content out of the document — the comments, the revisions, the properties, the hidden text, the headers. Under readOnly and comments it was refused, or rolled back by the post-check where the document had an exception; under trackedChanges nothing stopped it, so a document that tracks changes could have its comments and revisions taken out with no revision to show for it. The removals are refused now, in every mode a protected document can be in, next to the properties and the privacy options. An inspection without removals only reads the document, and still works. The protection test covers the modes now, not only readOnly. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the session author, not the user name "the user name can't change while the document is protected" was the wrong term twice over: it is the session author — the name new tracked changes and comments are recorded under, and the one a named exception is matched against — and it is `file.setAuthor` that refuses to change it. The CLI's `--author` names the session directly, so it works on a protected document. The parity row, the two status lines and the two progress rows now say that. Co-Authored-By: opencode <noreply@opencode.ai> * #3279: Keep protected session author fixed in CLI and Options * The Document Inspector refuses its removals while the document tracks changes The inspector's removals delete straight from the document: the hidden text, the comments, the revisions and the headers go without ever reaching the engine's tracked-delete path. On a document that tracks changes — and `trackedChanges` protection turns tracking on — the content was gone with no revision to show for it, and the revision trail was broken behind the user's back. The removals are not tracked, so they cannot be: rather than untrack them silently, the inspector refuses the whole removal form while the document tracks changes, and says why ("the document tracks changes: stop tracking before removing content"). The check comes before anything is removed, so the document is left untouched, and reading the document — the inspector without removals — keeps working. A document that does not track changes is inspected as before. Co-Authored-By: opencode <noreply@opencode.ai> * The inspector's tracking refusal doesn't cite an unrelated issue #1344 is about the inspector missing hidden text, not about its removals going untracked, so the reference only sent a reader to the wrong place. Co-Authored-By: opencode <noreply@opencode.ai> * A version snapshot belongs to the document it was taken from `Session::set_document` resets everything about the open document — the document id, the document, the selection, the undo history, the password, the file name — but not the version snapshots. A snapshot taken of one document therefore survived `file.new`, `file.open`, `file.newFromTemplate` and `file.recover`, and `file.versions {"restore": index}` then put the previous document's content into the open one. The snapshots go with the document they were taken from: `set_document` clears them, so a restore can only ever bring back a version of the document that is open. Every caller replaces the document with another one (a new or opened file, a recovered copy, a merge result, a sheet of labels), so none of them loses anything it needs. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the inspector's tracking refusal and the version snapshot's scope Co-Authored-By: opencode <noreply@opencode.ai> * A comparison clears the version snapshots of the document it replaces `review.compare` and `review.combine` open the original with `s.doc = doc`, which bypasses `set_document` — so the version snapshots of the document the comparison replaces survived it, and a restore could put the previous document back over the comparison result. The snapshots go with the document they were taken from, as everywhere else. The document itself is still assigned directly: `set_document` resets the undo history, and the comparison's own undo step would go with it. Co-Authored-By: opencode <noreply@opencode.ai> * #3277: Test Inspector refusal without protection * #1327: Keep snapshots with their document through Compare * #1327: Preserve file identity after rejected document replacement * test: mark the portal test's blocking sleep as intentional (SonarCloud) (#3989) The future is polled by hand with no async runtime, so a blocking sleep is how the test models a slow dialog. 🤖 Generated with Codebuff Co-authored-by: Codebuff <noreply@codebuff.com> * Engine: no dead-code warnings on wasm (#3542) * fix(engine): do not treat decimal points as sentence boundaries in Sentence case In crates/engine/src/cmd/format.rs, convert_chars treated any period as a sentence boundary, causing numbers with decimal points (e.g. 3.14, 2.5) to capitalize the subsequent word in Sentence case. Check if a period is surrounded by ASCII digits, and do not trigger uppercase capitalization when encountering a decimal point. Closes #3485 * proof: drop U+FE0F from FORBIDDENWORD and NOSUGGEST flags too Review follow-up: the directives that name one flag read the same emoji form, so they now skip the variation selector like PFX/SFX and stem flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fmt: rustfmt equation array centring * #3108 review: behind-text and in-front-of-text floats don't grow the row FredApps' review: the row-growth loop counted every non-inline object, so a page-anchored logo or watermark behind a cell's text would stretch its row to the object's bottom edge - contradicting table.rs (behind-text frames count for vertical alignment but don't increase the row's flow height) and Word. Only floats that take part in the text flow (Square, Tight, Through, TopAndBottom) grow the row now, the same classification layout already uses elsewhere for "takes part in wrapping". New test: a 300pt behind-text and a 300pt in-front-of-text picture each leave the row at its text height; it fails with the old condition. The existing Square-wrap tests and cell_frame_centres_empty still pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Engine: no dead-code warnings on wasm `cargo check -p wordcraft-engine --target wasm32-unknown-unknown` warned about five items whose only callers are platform-gated: - citations `by_author`, user_templates `MAX_ENTRIES` and `label_of`: used only by the desktop (non-wasm) source import and template listing. - speech `voice_language`: used by espeak (Unix but macOS) and the macOS voice picker, so `any(unix, test)`, not `any(not(windows), test)` (which also took in wasm). - speech `primary_tag`: used by those plus the Windows synthesizer, so `any(unix, windows, test)`. Each item now carries exactly its callers' gate rather than a blanket allow(dead_code). Checked with cargo check on Linux, Windows (gnu), macOS and wasm32 (zero warnings each), clippy -D warnings, and the engine tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Integration: #1847 reject comment IDs above u32::MAX * Integration: #3267 leave footnote definitions out of link definitions * Integration: #3471 drop duplicate egui options block; install_ui_fonts sets them * Integration: #2153 drop trailing blank line and use assert! for bool checks in tab_separated_list_markers * Integration: #2268 ODT lengths accept only the standard pc unit for picas * Integration: #2269 LaTeX lengths keep macro names case-sensitive * Integration: #2673 Khmer substitutes return the (list, flag) pair after #3245 * Integration: #2273 Latin weight/style and line styles take precedence over Asian, complex and line-type attributes * Integration: #1851 DOCPROPERTY accepts the reference app's LastSavedBy name * Integration: #1985 only ^0nnn codes read as Windows-1252 * Integration: #2416 collapse the nested vertical-align if (clippy::collapsible_if) * Integration: #2185 hyperlink_target slices with get() and needs the whole field name * Integration: #2034 objects without a stacking order draw above the ones before them * Integration: #3434 drop review-bot notes from test doc comments * Integration: #3419 doc comments back on their own items (EquationFrame and its test above them) * Integration: #2334 test a formula string result continued in a CONTINUE record * Integration: #3429 the based-on-chain test's doc comment back above its test * Integration: #2453 drawing_payload test builds the shape fill as a Fill * Integration: #3257 separate its Find-box test from #2407's recent-files test (auto-merge interleaved them) * Integration: cargo fmt * Integration: #1728 close the layout test the merge resolution left open * Integration: cargo fmt * Integration: cargo fmt * Integration: cargo fmt * Integration: #2722 Dutch registry comment on its own line * Integration: #3203 English text for the keys the Romanian catalog lacks Its complete-catalog test needs every key of every other catalog; the merged PRs added 72 since it was written. Rows carry the English text until translated. * Integration: #1076 English text for the keys the Slovenian catalog lacks Its complete-catalog test needs every key of every other catalog. Rows carry the English text until translated. * Integration: status lines list each change once Combining the tracks' status lines repeated a few entries (#2852, #1500, #2617). * Integration: cargo xtask parity * Integration: cargo fmt * Integration: #1832 drop a useless .into() in the end-of-document citation test clippy::useless_conversion failed cargo xtask ci. * Integration: #2244 VML on/off values read case-insensitively by the shape fill reader Main (#1500) moved VML fill reading into shape_style.rs, where filled="False" still counted as on; #2244's test vml_shape_wrap_side_and_anchor_case_insensitivity expects no fill. * Integration: #2041 English text in the Polish catalog for the keys Italian gained polish_covers_every_key_of_every_other_catalog expects Italian to have only "Style: APA" beyond Polish; #2041 translated 14 newer strings into Italian. Rows carry the English text until translated. * Integration: #2722 English text for four labels the Dutch and Danish catalogs lack every_tab_and_command_is_translated needs Format Shape Pane, Full Screen View, Personal Templates and Underline Color, added by other merged PRs. Rows carry the English text until translated. * Integration: #3335 header Esc test allows the half-pixel return scroll that #3201 brings #3201 makes entering an empty header scroll to it; Esc then scrolls back to the caret, landing 0.5 px from the start (17291.0 vs 17290.5). The test checks the view is back within a pixel. * IME: the caret follows the composition text (#3556) * Header and footer: a click in the other band edits it there (#3557) * README: the app icon above the title * Release 0.10.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The inspector's removals are refused in every protected mode `file.inspect` with removals takes content out of the document — the comments, the revisions, the properties, the hidden text, the headers. Under readOnly and comments it was refused, or rolled back by the post-check where the document had an exception; under trackedChanges nothing stopped it, so a document that tracks changes could have its comments and revisions taken out with no revision to show for it. The removals are refused now, in every mode a protected document can be in, next to the properties and the privacy options. An inspection without removals only reads the document, and still works. The protection test covers the modes now, not only readOnly. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the session author, not the user name "the user name can't change while the document is protected" was the wrong term twice over: it is the session author — the name new tracked changes and comments are recorded under, and the one a named exception is matched against — and it is `file.setAuthor` that refuses to change it. The CLI's `--author` names the session directly, so it works on a protected document. The parity row, the two status lines and the two progress rows now say that. Co-Authored-By: opencode <noreply@opencode.ai> * #3279: Keep protected session author fixed in CLI and Options * The Document Inspector refuses its removals while the document tracks changes The inspector's removals delete straight from the document: the hidden text, the comments, the revisions and the headers go without ever reaching the engine's tracked-delete path. On a document that tracks changes — and `trackedChanges` protection turns tracking on — the content was gone with no revision to show for it, and the revision trail was broken behind the user's back. The removals are not tracked, so they cannot be: rather than untrack them silently, the inspector refuses the whole removal form while the document tracks changes, and says why ("the document tracks changes: stop tracking before removing content"). The check comes before anything is removed, so the document is left untouched, and reading the document — the inspector without removals — keeps working. A document that does not track changes is inspected as before. Co-Authored-By: opencode <noreply@opencode.ai> * The inspector's tracking refusal doesn't cite an unrelated issue #1344 is about the inspector missing hidden text, not about its removals going untracked, so the reference only sent a reader to the wrong place. Co-Authored-By: opencode <noreply@opencode.ai> * A version snapshot belongs to the document it was taken from `Session::set_document` resets everything about the open document — the document id, the document, the selection, the undo history, the password, the file name — but not the version snapshots. A snapshot taken of one document therefore survived `file.new`, `file.open`, `file.newFromTemplate` and `file.recover`, and `file.versions {"restore": index}` then put the previous document's content into the open one. The snapshots go with the document they were taken from: `set_document` clears them, so a restore can only ever bring back a version of the document that is open. Every caller replaces the document with another one (a new or opened file, a recovered copy, a merge result, a sheet of labels), so none of them loses anything it needs. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the inspector's tracking refusal and the version snapshot's scope Co-Authored-By: opencode <noreply@opencode.ai> * A comparison clears the version snapshots of the document it replaces `review.compare` and `review.combine` open the original with `s.doc = doc`, which bypasses `set_document` — so the version snapshots of the document the comparison replaces survived it, and a restore could put the previous document back over the comparison result. The snapshots go with the document they were taken from, as everywhere else. The document itself is still assigned directly: `set_document` resets the undo history, and the comparison's own undo step would go with it. Co-Authored-By: opencode <noreply@opencode.ai> * #3277: Test Inspector refusal without protection * #1327: Keep snapshots with their document through Compare * #1327: Preserve file identity after rejected document replacement * test: mark the portal test's blocking sleep as intentional (SonarCloud) (#3989) The future is polled by hand with no async runtime, so a blocking sleep is how the test models a slow dialog. 🤖 Generated with Codebuff Co-authored-by: Codebuff <noreply@codebuff.com> * #3542: gate the label_of test off wasm32 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Divyanshu Gupta <divyanshu2074@gmail.com> Co-authored-by: dmim2502-commits <234869083+dmim2502-commits@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: vangel76 <vangel1976@googlemail.com> Co-authored-by: aiongg <aiongtaigi@gmail.com> Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com> Co-authored-by: bflatastic <bombayapirat@gmail.com> Co-authored-by: Brandon Thomas <brandon@storyteller.ai> Co-authored-by: Mat Strange <mat@matstrange.net> Co-authored-by: opencode <noreply@opencode.ai> Co-authored-by: skylar taylor-barrick <hi@skylar.cc> Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com> Co-authored-by: Codebuff <noreply@codebuff.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Drop U+FFFE and U+FFFF from ODT export text (#3817) Found while testing crates/formats/src/odt.rs:49-63 (fn x, the ODT text escaper; filter at line 58) Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Cap the number of VML path commands parsed from a path attribute (#3951) Fixes #3683 Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Return None from parse_number when the value overflows f64 (#3577) Found while testing crates/doc/src/locale_number.rs:104 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Fix spurious 零 after a group's trailing zeros in Chinese numerals (#3745) Found while testing crates/doc/src/cjk_numbers.rs:47-55 (Counting::format) Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * picture.corrections: a tiny sharpen value no longer panics in the blur (#3646) (#3830) Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> * Reject signs and non-digits in parse_date fields (#3575) Found while testing crates/engine/src/cmd/controls.rs:113-123 (parse_date) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * #3816: also assert the next paragraph's first line stays on page 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Mat Strange <mat@matstrange.net> Co-authored-by: opencode <noreply@opencode.ai> Co-authored-by: dfgodvan <180286157+dfgodvan@users.noreply.github.com> Co-authored-by: Brandon Thomas <brandon@storyteller.ai> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: skylar taylor-barrick <hi@skylar.cc> Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com> Co-authored-by: Codebuff <noreply@codebuff.com> Co-authored-by: Michael Mu <69072633+MichaelMuClaude@users.noreply.github.com> Co-authored-by: Divyanshu Gupta <divyanshu2074@gmail.com> Co-authored-by: dmim2502-commits <234869083+dmim2502-commits@users.noreply.github.com> Co-authored-by: vangel76 <vangel1976@googlemail.com> Co-authored-by: aiongg <aiongtaigi@gmail.com> Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com> Co-authored-by: bflatastic <bombayapirat@gmail.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Co-authored-by: Joseph Anglada <fluorine@users.noreply.github.com> * Parse the Flat OPC part root tag once when inheriting namespaces (#3944) * Parse the Flat OPC part root tag once when inheriting namespaces Fixes #3727 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * test: mark the portal test's blocking sleep as intentional (SonarCloud) (#3989) The future is polled by hand with no async runtime, so a blocking sleep is how the test models a slow dialog. 🤖 Generated with Codebuff Co-authored-by: Codebuff <noreply@codebuff.com> * Engine: no dead-code warnings on wasm (#3542) * fix(engine): do not treat decimal points as sentence boundaries in Sentence case In crates/engine/src/cmd/format.rs, convert_chars treated any period as a sentence boundary, causing numbers with decimal points (e.g. 3.14, 2.5) to capitalize the subsequent word in Sentence case. Check if a period is surrounded by ASCII digits, and do not trigger uppercase capitalization when encountering a decimal point. Closes #3485 * proof: drop U+FE0F from FORBIDDENWORD and NOSUGGEST flags too Review follow-up: the directives that name one flag read the same emoji form, so they now skip the variation selector like PFX/SFX and stem flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fmt: rustfmt equation array centring * #3108 review: behind-text and in-front-of-text floats don't grow the row FredApps' review: the row-growth loop counted every non-inline object, so a page-anchored logo or watermark behind a cell's text would stretch its row to the object's bottom edge - contradicting table.rs (behind-text frames count for vertical alignment but don't increase the row's flow height) and Word. Only floats that take part in the text flow (Square, Tight, Through, TopAndBottom) grow the row now, the same classification layout already uses elsewhere for "takes part in wrapping". New test: a 300pt behind-text and a 300pt in-front-of-text picture each leave the row at its text height; it fails with the old condition. The existing Square-wrap tests and cell_frame_centres_empty still pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Engine: no dead-code warnings on wasm `cargo check -p wordcraft-engine --target wasm32-unknown-unknown` warned about five items whose only callers are platform-gated: - citations `by_author`, user_templates `MAX_ENTRIES` and `label_of`: used only by the desktop (non-wasm) source import and template listing. - speech `voice_language`: used by espeak (Unix but macOS) and the macOS voice picker, so `any(unix, test)`, not `any(not(windows), test)` (which also took in wasm). - speech `primary_tag`: used by those plus the Windows synthesizer, so `any(unix, windows, test)`. Each item now carries exactly its callers' gate rather than a blanket allow(dead_code). Checked with cargo check on Linux, Windows (gnu), macOS and wasm32 (zero warnings each), clippy -D warnings, and the engine tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Integration: #1847 reject comment IDs above u32::MAX * Integration: #3267 leave footnote definitions out of link definitions * Integration: #3471 drop duplicate egui options block; install_ui_fonts sets them * Integration: #2153 drop trailing blank line and use assert! for bool checks in tab_separated_list_markers * Integration: #2268 ODT lengths accept only the standard pc unit for picas * Integration: #2269 LaTeX lengths keep macro names case-sensitive * Integration: #2673 Khmer substitutes return the (list, flag) pair after #3245 * Integration: #2273 Latin weight/style and line styles take precedence over Asian, complex and line-type attributes * Integration: #1851 DOCPROPERTY accepts the reference app's LastSavedBy name * Integration: #1985 only ^0nnn codes read as Windows-1252 * Integration: #2416 collapse the nested vertical-align if (clippy::collapsible_if) * Integration: #2185 hyperlink_target slices with get() and needs the whole field name * Integration: #2034 objects without a stacking order draw above the ones before them * Integration: #3434 drop review-bot notes from test doc comments * Integration: #3419 doc comments back on their own items (EquationFrame and its test above them) * Integration: #2334 test a formula string result continued in a CONTINUE record * Integration: #3429 the based-on-chain test's doc comment back above its test * Integration: #2453 drawing_payload test builds the shape fill as a Fill * Integration: #3257 separate its Find-box test from #2407's recent-files test (auto-merge interleaved them) * Integration: cargo fmt * Integration: #1728 close the layout test the merge resolution left open * Integration: cargo fmt * Integration: cargo fmt * Integration: cargo fmt * Integration: #2722 Dutch registry comment on its own line * Integration: #3203 English text for the keys the Romanian catalog lacks Its complete-catalog test needs every key of every other catalog; the merged PRs added 72 since it was written. Rows carry the English text until translated. * Integration: #1076 English text for the keys the Slovenian catalog lacks Its complete-catalog test needs every key of every other catalog. Rows carry the English text until translated. * Integration: status lines list each change once Combining the tracks' status lines repeated a few entries (#2852, #1500, #2617). * Integration: cargo xtask parity * Integration: cargo fmt * Integration: #1832 drop a useless .into() in the end-of-document citation test clippy::useless_conversion failed cargo xtask ci. * Integration: #2244 VML on/off values read case-insensitively by the shape fill reader Main (#1500) moved VML fill reading into shape_style.rs, where filled="False" still counted as on; #2244's test vml_shape_wrap_side_and_anchor_case_insensitivity expects no fill. * Integration: #2041 English text in the Polish catalog for the keys Italian gained polish_covers_every_key_of_every_other_catalog expects Italian to have only "Style: APA" beyond Polish; #2041 translated 14 newer strings into Italian. Rows carry the English text until translated. * Integration: #2722 English text for four labels the Dutch and Danish catalogs lack every_tab_and_command_is_translated needs Format Shape Pane, Full Screen View, Personal Templates and Underline Color, added by other merged PRs. Rows carry the English text until translated. * Integration: #3335 header Esc test allows the half-pixel return scroll that #3201 brings #3201 makes entering an empty header scroll to it; Esc then scrolls back to the caret, landing 0.5 px from the start (17291.0 vs 17290.5). The test checks the view is back within a pixel. * IME: the caret follows the composition text (#3556) * Header and footer: a click in the other band edits it there (#3557) * README: the app icon above the title * Release 0.10.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The inspector's removals are refused in every protected mode `file.inspect` with removals takes content out of the document — the comments, the revisions, the properties, the hidden text, the headers. Under readOnly and comments it was refused, or rolled back by the post-check where the document had an exception; under trackedChanges nothing stopped it, so a document that tracks changes could have its comments and revisions taken out with no revision to show for it. The removals are refused now, in every mode a protected document can be in, next to the properties and the privacy options. An inspection without removals only reads the document, and still works. The protection test covers the modes now, not only readOnly. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the session author, not the user name "the user name can't change while the document is protected" was the wrong term twice over: it is the session author — the name new tracked changes and comments are recorded under, and the one a named exception is matched against — and it is `file.setAuthor` that refuses to change it. The CLI's `--author` names the session directly, so it works on a protected document. The parity row, the two status lines and the two progress rows now say that. Co-Authored-By: opencode <noreply@opencode.ai> * #3279: Keep protected session author fixed in CLI and Options * The Document Inspector refuses its removals while the document tracks changes The inspector's removals delete straight from the document: the hidden text, the comments, the revisions and the headers go without ever reaching the engine's tracked-delete path. On a document that tracks changes — and `trackedChanges` protection turns tracking on — the content was gone with no revision to show for it, and the revision trail was broken behind the user's back. The removals are not tracked, so they cannot be: rather than untrack them silently, the inspector refuses the whole removal form while the document tracks changes, and says why ("the document tracks changes: stop tracking before removing content"). The check comes before anything is removed, so the document is left untouched, and reading the document — the inspector without removals — keeps working. A document that does not track changes is inspected as before. Co-Authored-By: opencode <noreply@opencode.ai> * The inspector's tracking refusal doesn't cite an unrelated issue #1344 is about the inspector missing hidden text, not about its removals going untracked, so the reference only sent a reader to the wrong place. Co-Authored-By: opencode <noreply@opencode.ai> * A version snapshot belongs to the document it was taken from `Session::set_document` resets everything about the open document — the document id, the document, the selection, the undo history, the password, the file name — but not the version snapshots. A snapshot taken of one document therefore survived `file.new`, `file.open`, `file.newFromTemplate` and `file.recover`, and `file.versions {"restore": index}` then put the previous document's content into the open one. The snapshots go with the document they were taken from: `set_document` clears them, so a restore can only ever bring back a version of the document that is open. Every caller replaces the document with another one (a new or opened file, a recovered copy, a merge result, a sheet of labels), so none of them loses anything it needs. Co-Authored-By: opencode <noreply@opencode.ai> * Docs: the inspector's tracking refusal and the version snapshot's scope Co-Authored-By: opencode <noreply@opencode.ai> * A comparison clears the version snapshots of the document it replaces `review.compare` and `review.combine` open the original with `s.doc = doc`, which bypasses `set_document` — so the version snapshots of the document the comparison replaces survived it, and a restore could put the previous document back over the comparison result. The snapshots go with the document they were taken from, as everywhere else. The document itself is still assigned directly: `set_document` resets the undo history, and the comparison's own undo step would go with it. Co-Authored-By: opencode <noreply@opencode.ai> * #3277: Test Inspector refusal without protection * #1327: Keep snapshots with their document through Compare * #1327: Preserve file identity after rejected document replacement * test: mark the portal test's blocking sleep as intentional (SonarCloud) (#3989) The future is polled by hand with no async runtime, so a blocking sleep is how the test models a slow dialog. 🤖 Generated with Codebuff Co-authored-by: Codebuff <noreply@codebuff.com> * #3542: gate the label_of test off wasm32 Co-A… --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: VoiceLessQ <44067308+VoiceLessQ@users.noreply.github.com> Co-authored-by: Mat Strange <mat@matstrange.net> Co-authored-by: opencode <noreply@opencode.ai> Co-authored-by: skylar taylor-barrick <hi@skylar.cc> Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com> Co-authored-by: Codebuff <noreply@codebuff.com> Co-authored-by: Michael Mu <69072633+MichaelMuClaude@users.noreply.github.com> Co-authored-by: Divyanshu Gupta <divyanshu2074@gmail.com> Co-authored-by: dmim2502-commits <234869083+dmim2502-commits@users.noreply.github.com> Co-authored-by: vangel76 <vangel1976@googlemail.com> Co-authored-by: aiongg <aiongtaigi@gmail.com> Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com> Co-authored-by: bflatastic <bombayapirat@gmail.com> Co-authored-by: Brandon Thomas <brandon@storyteller.ai> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Co-authored-by: Joseph Anglada <fluorine@users.noreply.github.com> Co-authored-by: dfgodvan <dfgodvan@gmail.com> Co-authored-by: dfgodvan <180286157+dfgodvan@users.noreply.github.com> Co-authored-by: Michael Higgins <55243596+Higgs32584@users.noreply.github.com> Co-authored-by: In Mean <6434741+inmean@users.noreply.github.com> Co-authored-by: Jayesh Prajapati <156836276+Jayesh-Prajapati9@users.noreply.github.com> Co-authored-by: Gabriel Matozo <69991354+GabrielMatozo@users.noreply.github.com> Co-authored-by: Mathieu Rancourt <40959576+MatRanc@users.noreply.github.com> Co-authored-by: pakiat <49967840+pakiat@users.noreply.github.com> Co-authored-by: Marco Arnulfo <103679419+marcoarnulfo@users.noreply.github.com> Co-authored-by: Menonro <nitayd2001@gmail.com> Co-authored-by: Gabriel Luís da Silva <79342117+mercuryVM@users.noreply.github.com> Co-authored-by: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Co-authored-by: dhruvLearner8 <83899022+dhruvLearner8@users.noreply.github.com> Co-authored-by: vioreltus <142961713+vioreltus@users.noreply.github.com> Co-authored-by: Dominik Strebel <dominik.strebel@gmail.com> Co-authored-by: Mat Strange <54028700+matstrange@users.noreply.github.com> Co-authored-by: Dmitriy <dmitriy@mrzv.org> |
||
|
|
cbc27f6c5e |
#3360: Spanish honours the proofing options; es-AR finds es_AR
CodeRabbit review on #3562: - with Ignore words in UPPERCASE off, short words in capitals are checked against the Spanish dictionary instead of accepted as acronyms; likewise words with digits with Ignore words that contain numbers off. Direct es::is_correct/is_correct_with keep the defaults. - Latin American Spanish (which es-AR resolves to) also looks for es_AR and es-AR, after the neutral es_US and es_MX, so Argentine text is checked when only that dictionary is installed - docs: the exact candidate order per variety in localization-parity.md, and es_AR in the spelling-dictionaries table - the Spain variant's doc lists Uruguay among the regions it doesn't cover |
||
|
|
9a88f38332 |
docs: list Spanish in the new-document proofing defaults
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RgXNHh9jfA8c7MmVGWagx9 |
||
|
|
eac1c2e81e |
Merge origin/main into spanish-proofing
Resolve conflicts with German hyphenation (ProofLang::De), Russian and Czech in installed_dictionaries(), and #2164's U+FE0F flag fix: affix.rs takes main's fix and drops this branch's duplicate. The Danish, Dutch, Romanian and Slovenian catalogs gain the Spanish (Latin America) and (Uruguay) names; the language menu tests get room for the four Spanish entries. |
||
|
|
40494a5ca7 |
#3360, #3553: Spanish proofing (es-ES, es-MX, es-419, es-UY)
Spanish text was never checked: ProofLang had no Spanish variant, so every
es-* run fell through to Other (no marks). And the RLA-ES dictionaries
LibreOffice ships were refused by the affix engine for their `☎️` flag
(☎ + variation selector U+FE0F), which Hunspell reads as ☎.
- ProofLang::Es (Spain, plain es, spa), EsMx (es-MX), Es419 (es-419 and
the other countries of the Americas) and EsUy (es-UY), chosen by region
like the English dialects
- each variety reads its own installed Hunspell dictionary with fallbacks
(es_ES; es_MX; es_419/es_US/es_MX; es_UY/es_AR); nothing bundled
- variation selectors in affix flag fields belong to the flag before them
- Uruguayan vos forms accepted when the dictionary has the infinitive:
imperatives (vení, andá), with pronouns (decime, decímelo), present
(tenés); a vos form typed without its accent gets it back first
- suggestions use the dictionary-driven Czech ranking (prueva → prueba,
corector → corrector, tambien → también)
- grammar: repeated word, spacing, missing opening ¿/¡, space after an
opening mark, with Spanish messages
- Review › Language, status bar, Language dialog and File › Options ›
Proofing list the four varieties; the two new names translated in every
catalog; a Spanish interface writes new documents in the region's Spanish
- the dictionaries-folder engine test holds the Italian and Spanish
fixtures, so parallel proofing tests no longer lose their dictionaries
- tests use hand-written synthetic dictionaries; engine end-to-end tests
|
||
|
|
8efd6667ec |
Merge PR #1076: i18n: add Slovenian (sl) interface translation
i18n: registry holds 27 languages (sl after nl, da, ro); audit.rs and the Polish test list sl beside pl and ro; tests: both sides kept. Docs: language counts and lists name ro, sl, nl and da; status entries prepended to main's lines. |
||
|
|
5d59afae74 |
Merge PR #3203: G14: Romanian (ro) interface catalog
crates/ui-egui/src/i18n/mod.rs: registry holds nl, da (#2722) and ro (26 languages). tests.rs: both sides' tests kept. |