mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-10 12:20:12 -06:00
208facfb925e6167566d036769d8631bbbc4eace
299
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4932017711 |
Dialogs are modal, as in Word (#4076)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1f026078be |
Format Shape: the Angle field leaves an angle above 359.9° alone (#3959)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
63ceaf5b80 |
File › Print: Return prints, as Word's default Print button does (#4651)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
604ac6ddb2 |
#641: print N-up sheets and folded booklets (#3916)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
688cce4731 | Read Mode: ink and drag previews follow the Page color, which is kept between entries (#4228) | ||
|
|
ba58e70cc8 |
Flag document fonts that aren't installed (#4770)
* Gap 4: flag document fonts that aren't installed The font box (ribbon, mini toolbar, both Font dialog boxes) shows a warning mark when the current font isn't installed; its hover note names the font drawn instead. The status bar says "N fonts not installed" and lists each with the faces drawn instead, plus Add Font…. Agents get `font.missing` and `format.state.fontShownAs`. Layout records, where it picks a face per text segment, the fonts the shown text asks for that aren't installed and the faces drawn instead (ParaLayout::substituted, DocLayout::substituted_fonts), so hidden text, unused styles and orphaned stories never count; list labels, field results and East Asian and complex-script text do, and so does a missing roman font drawn in Cambria. Metric-compatible substitutes and symbol fonts drawn as Unicode aren't flagged. Bounded for hostile files: 64 pairs a paragraph, 1024 a document, names kept to 256 characters, 20 rows in the popup. Ported from alehel/wordcraft#1 onto current main; English strings only (translations belong in storytold/craft-i18n). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 4: missing-font review fixes - Judge a padded font name by the face layout drew for it, and skip blank names. - Name the face CJK text is drawn in for a missing East Asian font, not Cambria. - Keep the missing-fonts list per window, so several windows don't rebuild it every frame. - Expose the font box's warning to screen readers as its description. - Count missing fonts with craft-i18n plurals (`i18n::tn`). - One table of metric twins (`fonts::word::METRIC_TWINS`): a missing font is drawn in an installed twin first, and the warning reads the same table. - Find the status-bar item by part of its label in the click test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 4: warn for a font picked at the caret; drop fonts of hidden list items - At a collapsed caret the font box warns for a missing font typing will use, also at the end of text in another font (it waited for the first letter). - A list item that shows nothing (#3982) reports no missing font: its number was shaped, and its font recorded, before layout dropped it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 4: say where Add Font… is, and that it needs the font file The font box's note said "Add Font… installs it": Add Font… opens a file picker at the end of the list, so it only helps with a font file at hand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 4: docs say the popup lists 20, and when the font box warns ahead CodeRabbit review: the status-bar popup lists the first 20 missing fonts, then a count; the font box also warns at a collapsed caret for a font no shown text uses yet; `fontShownAs` is the font's substitute, fallback faces aside. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 4: the Font dialog warns for a missing font over a selection CodeRabbit review: the dialog's boxes show the fonts OK applies, which no shown text may use yet (an Asian text font over Latin text, a font typed into the dialog), so they name the substitute whatever is selected. The ribbon's box over a selection still reads the list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 4: the Asian font box names the face CJK text gets CodeRabbit review: the status bar's list is by font name, so for a font Latin text also uses it named the Latin text's substitute. The Asian text box now always looks up the face layout draws Chinese, Japanese and Korean text in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 4: gaps.md status on the current line, missing-font scope and twin wording Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 4: gaps.md says which missing fonts are flagged and what font.missing lists Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 4: font.missing lists what the current view lays out Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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 |
||
|
|
a591879294 |
Comments: a long comment's card and balloon stop at ten lines (#3382) (#3899)
* #3382: a long comment's card and balloon stop at ten lines A comment or reply field, in the Comments pane and in a selected balloon, is at most ten lines tall and scrolls beyond that, keeping its caret in view. A balloon that isn't selected shows the first ten lines of the comment and of each reply and cuts off the rest; clicking it opens the scrolling field. The buttons and the next comment stay in view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #3382: record the ten-line comment cap in the UI parity doc; test the drawn rows The Comments entry of the UI parity doc notes that a long comment's card, balloon and fields stop at ten lines. A new test checks what an unselected long balloon paints: its first ten rows inside the card's clip and none of the rows after them, in Latin and Arabic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #3382: check the shown balloon rows start inside the clip The drawn-rows test now also checks that each of the first ten rows starts at or below the top of the balloon body's clip, so a clip that hid the first rows would fail it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- 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> |
||
|
|
3abe452b7c |
M7.SYNC: reconcile Proofing pane with current main
Preserve upstream changes and keep shared documentation headers aligned with main; retain feature status in its dedicated document. Credit: @totoz64 |
||
|
|
c934977157 |
Keytips: Alt, F shows the Backstage pages' letters; Alt, F, P and Alt, F, V print (#4960) (#4981)
* Keytips: Alt, F shows the Backstage pages' letters; Alt, F, P and Alt, F, V print (#4960) Keytips ended as soon as File opened the Backstage, so Alt, F, P and Word's older Alt, F, V (Print Preview) did nothing. Alt, F now badges the side menu with Word's letters (Home H, New N, Open O, Info I, Save S, Save As A, Print P, Export E, Close C, Options T), and V opens the Print page too, as it does in Word. A page picked this way acts as a click on it does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keytips: closing the Backstage ends its page letters (#4960) The Backstage closed by its back chevron or a mouse Save while Alt, F's badges showed left keytips waiting for a page letter. The next key was eaten instead of typed, and picked a page with the Backstage gone: C closed the document and S saved it. Keytips now end with the Backstage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keytips: keep the doc comment on works_without_document; ui-parity and ROADMAP rows (#4960) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
a2446aa330 |
View › Macros opens a dialog to run, delete and record macros (#4327) (#4426)
* View › Macros opens a dialog to run, delete and record macros (#4327) * Docs: date the Macros dialog row and bump the ui-parity status line (#4327) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
7c20316dd5 | M7.SYNC: refresh Proofing pane against main 0.13.2 | ||
|
|
c077c155e4 | PROOF.UI: refresh Proofing pane against main | ||
|
|
a57dd81dba |
Copy selected objects along an axis with Ctrl+Shift (#4764) (#4775)
* #4764: copy selected objects along an axis with Ctrl+Shift * #4775: preserve aligned copies through scrolling and secondary selection * docs: keep the gaps.md note in the status line and revision table * docs: drop a stray blank line --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
9a172e2241 |
Table Properties: row, column and cell settings follow the selection (#4857) (#4896)
Row, column and cell settings applied to the caret's row, column and cell only, so selecting the whole table and unticking "Allow row to break across pages" changed one row and the others still split across pages. - table.properties applies row height and rule, allow break and header row to every selected row, the column width to every selected column, and the cell width and vertical alignment to every selected cell (the caret's with nothing selected), in one undo step. - The dialog shows "Allow row to break" and "Repeat as header row" as mixed when the selected rows differ; left alone they change nothing, clicked they set every row. - The row-height rule box is named by "Specify height" for screen readers. Claude-Session: https://claude.ai/code/session_01LvG6DxS4tKmWSLCiDPvb4G Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
1be606de03 |
Insert › Shapes shows the basic shapes as an icon grid (#4349) (#4739)
Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4fcc409c8b |
AutoCorrect Options opens with no document open (#4585) (#4626)
File › Options › Proofing › AutoCorrect Options did nothing on the Start screen: tools.autocorrect wasn't in works_without_document, so the click was dropped. Its options are user preferences (session.prefs), like the other File › Options settings, so it now works without a document. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
bd85c72f39 |
Shading buttons show the last picked colour (#4412) (#4505)
* Shading buttons show the last picked colour as their bar (#4412) The paragraph and table Shading buttons always drew the icon's yellow bar, whatever colour a click applied. They now draw the remembered colour as the bar, as Font Color and Text Highlight Color do (#4087), on an icon without a bar of its own. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * docs: ui-parity row for the Shading button colour (#4412) --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4a12ab9a19 |
Layout › Arrange › Group stays on while a group is selected, so Ungroup is reachable (#4229) (#4440)
Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
41d5284fa8 |
Options: a blank User Name stays blank after a restart (#4368) (#4421)
* Options: a blank User Name stays blank after a restart (#4368) * docs: ui-parity row for the blank User Name (#4368) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
5714408dd8 |
M14: validate window fallback geometry and avoid duplicate symbol names (#4343)
* M14: validate window fallback geometry and avoid duplicate symbol names Verified regression cases and full cargo xtask ci before publication. * M14: document review helpers and regression contracts Documentation-only follow-up; full cargo xtask ci passed. --------- Co-authored-by: kalmarr <680152+kalmarr@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
3508cac3b0 |
Prevent X11 crashes from NUL in document titles (#3634) (#4301)
Ported onto current main (title_stem now lives in app/frame.rs; the window title is built in windows.rs). Co-authored-by: Lucenx9 <185146821+Lucenx9@users.noreply.github.com> |
||
|
|
5a9e414d1e | M9: generate all recipient label sheets (#4880) | ||
|
|
e490c13cdb |
M6: sync Proofing pane with main module extractions
Merge main
|
||
|
|
62f9050848 |
M7: resolve exact proofing selections and address pane review
Give the exact selected issue priority over overlapping grammar when querying the focus, including reversed selections. Keep point lookup for explicit other positions. Add command and pane regressions for overlapping repeated misspellings, correction and undo. Include command/window/document/canvas integration paths in preview workflow triggers. Preserve historical three-platform evidence while recording f126's successful gate/previews/Windows/Linux and outstanding macOS validation after pre-run cancellations. These address the three CodeRabbit findings on official #4471. Local cargo xtask ci was attempted but cargo is absent; git diff --check passes. New hosted checks must validate this SHA before any claim of success. |
||
|
|
056b18b714 |
M7: match proofing pane geometry after rendered comparison
Inspect the successful |
||
|
|
f6f9d65b0e |
M7: follow the observed Word for Windows proofing pane
Observe F7 in the installed Word with synthetic text, then replace the central correction form with a right-side task pane. Show sentence context, directly clickable suggestion cards and ignore/dictionary links. Use the existing pane host, palette and original icons; copy no Microsoft assets. Keep per-window state and clear targets on document switch. Adapt UI tests, pseudo-localization exemptions and light/dark previews. Record observed measurements and remaining feature differences. Local cargo xtask ci was attempted but cargo is absent; exact-head hosted checks and visual comparison remain required. |
||
|
|
b2787bf36e |
M7: add a persistent language-aware spelling review dialog
Keep compact context menus, but show a correction form for user spelling/editor commands. Advance without wrapping after change, ignore or dictionary actions; reject stale targets and retain programmatic command results. Add navigation/UI regressions and light/dark preview CI. cargo xtask ci was attempted on the preparation PC but cargo is absent; hosted validation is required before integration. |
||
|
|
e900513104 |
Merge branch 'w3' into w6
# Conflicts: # ROADMAP.md # crates/engine/src/lib.rs # crates/engine/src/session.rs # crates/ui-egui/Cargo.toml # crates/ui-egui/src/lib.rs # docs/gaps.md # docs/parity-checklist.md # docs/ui-parity.md |
||
|
|
5632755be4 |
Merge branch 'w2' into w3
# Conflicts: # Cargo.lock # ROADMAP.md # apps/wordcraft/src/main.rs # crates/ui-egui/Cargo.toml # crates/ui-egui/src/backstage.rs # crates/ui-egui/src/control.rs # crates/ui-egui/src/dialogs.rs # crates/ui-egui/src/keys.rs # crates/ui-egui/src/lib.rs # crates/ui-egui/src/panes.rs # crates/ui-egui/src/previews.rs # docs/ui-parity.md |
||
|
|
4029a4e6fc |
Merge remote-tracking branch 'upstream/main' into w1
# Conflicts: # ROADMAP.md # crates/engine/src/lib.rs # crates/engine/src/session.rs # crates/mcp/src/tools.rs # crates/ui-egui/src/control.rs # crates/ui-egui/src/i18n/bg.tsv # crates/ui-egui/src/i18n/cs.tsv # crates/ui-egui/src/i18n/da.tsv # crates/ui-egui/src/i18n/de.tsv # crates/ui-egui/src/i18n/es.tsv # crates/ui-egui/src/i18n/et.tsv # crates/ui-egui/src/i18n/fr.tsv # crates/ui-egui/src/i18n/hr.tsv # crates/ui-egui/src/i18n/hu.tsv # crates/ui-egui/src/i18n/it.tsv # crates/ui-egui/src/i18n/ja.tsv # crates/ui-egui/src/i18n/ko.tsv # crates/ui-egui/src/i18n/nl.tsv # crates/ui-egui/src/i18n/pl.tsv # crates/ui-egui/src/i18n/pt-br.tsv # crates/ui-egui/src/i18n/ro.tsv # crates/ui-egui/src/i18n/ru.tsv # crates/ui-egui/src/i18n/sl.tsv # crates/ui-egui/src/i18n/sr-latn.tsv # crates/ui-egui/src/i18n/sr.tsv # crates/ui-egui/src/i18n/sv.tsv # crates/ui-egui/src/i18n/tr.tsv # crates/ui-egui/src/i18n/uk.tsv # crates/ui-egui/src/i18n/vi.tsv # crates/ui-egui/src/i18n/zh-hans.tsv # crates/ui-egui/src/i18n/zh-hant.tsv # crates/ui-egui/src/lib.rs # docs/gaps.md # docs/mcp.md # docs/parity-checklist.md # docs/ui-parity.md |
||
|
|
49731f3faa |
#2982: File ▸ Info Custom Document Properties dialog (#2997)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1b2f7a8c09 |
#1845: filter-as-you-type command search in Customize Ribbon, QAT and Keyboard pickers (#1848)
# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# interactive rebase in progress; onto
|
||
|
|
40a7fe6be2 |
Make three-column header/footer presets editable tables (#2197) (#2266)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
50a1c34647 |
#1475: Scale all style font sizes at once (#2620)
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> |
||
|
|
85db9cfef5 |
#4177: regex Replace expands capture groups (#4201)
* With Regular expressions on, Replace with reads $1…$n, ${n}, ${name}, $name,
$0/${0}, $$, \1…\9, \n (paragraph mark, splits the paragraph through the ^p
path), \t and \\, alongside the ^ codes. Named groups resolve through
meta::Regex::group_info(); missing, unknown and out-of-range groups expand to
nothing. Plain and wildcard Replace are unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #4177: review — special:false keeps regex group references
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #4177: review — \$ and \^ in regex Replace keep the character as typed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
||
|
|
d1db090a7c | Mini toolbar: the alignment buttons mark the alignment seen on the page, as the ribbon does (#3886) | ||
|
|
6f6ff64af1 |
Merge PR #4205: Track Changes: Accept and Reject can move to the next change or stop tracking
# Conflicts: # docs/ui-parity.md |
||
|
|
bc4a69b17d |
Merge PR #4182: Parity catalog: the checklist counts Read Mode's Column Width, Page Color and Layout
# Conflicts: # ROADMAP.md # docs/ui-parity.md |
||
|
|
1b629d1299 | Merge PR #4155: Tables: Convert to Text asks for the separator and converts only the selected rows | ||
|
|
f409bd93c9 |
Track Changes: Accept and Reject can move to the next change or stop tracking
Word's Accept and Reject menus have Accept and Move to Next, Reject and Move to Next, Accept All Changes and Stop Tracking, and Reject All Changes and Stop Tracking. WordCraft's menus had only Accept This Change, Accept All Changes, Reject Change and Reject All Changes. review.accept and review.reject take "next": after the change at the caret is resolved, the first change from the caret on is selected, including one that starts and ends there, such as an empty paragraph's formatting. Past the last change, the first change in the document is selected, as Next Change does. With the caret on no change, they select the next change as before and resolve nothing. review.acceptAll and review.rejectAll take "stopTracking", which also turns Track Changes off in the same undo step. The ribbon's Accept and Reject menus list the four new items. The big Accept and Reject buttons are unchanged. |
||
|
|
5dfef70759 |
Docs: progress log and Reading Mode row for Read Mode's View menu in the catalog
ROADMAP.md's progress log and revision history, and docs/ui-parity.md's Reading Mode row and revision history, record the catalog rows for Read Mode's View menu: Column Width works, Page Color and Layout are missing (catalog 503/520). Both status lines are bumped. |
||
|
|
dd865648c2 | Merge PR #4079: Comments: times in the user's own time zone, in the pane and the balloons (#2322, #2337) | ||
|
|
8e8d8b4dd2 | Merge PR #4172: Read Mode: a Page color menu shows the columns on white, sepia or a dark page | ||
|
|
b400a26aaa |
Read Mode: a Page color menu shows the columns on white, sepia or a dark page
Word's Read Mode has View › Page Color with None, Sepia and Inverse. The Read Mode toolbar now has a Page color menu beside Line spacing, backed by the view.readPageColor command. None draws the columns on white and does not show the document's page color or page picture. Sepia draws them on Word's sepia paper (RGB 112, 66, 20 at 80% transparency over white) and leaves the text's colors alone. Inverse draws the dark page that View › Switch Modes draws. The setting is screen only, is not saved, and starts at None on each entry to Read Mode, as the other reading controls do. In Read Mode it sets the paper even while Switch Modes is on. Print Layout and the other views still show the document's page color, and Switch Modes still darkens them. |
||
|
|
cae5efffa5 | #4087: clear the highlight bar for No Color | ||
|
|
0259725ee1 |
Tables: Convert to Text asks for the separator and converts only the selected rows
Table Layout › Data › Convert to Text ran at once with tabs and always turned the whole table into plain paragraphs. It now opens a Convert to Text dialog that asks which separator goes between the cells: paragraph marks, tabs, commas or another character. With rows selected only those rows become text, and the rows above and below stay tables; vertical merges and row content controls that the cut crosses end above it and start again below it, and when rows stay above, the rows below a floating table go in the text flow. The text keeps its formatting and objects, a table nested in a cell stays a table, and a content control around the table keeps both its ends. `table.toText` takes `other` with `char`, as `table.fromText` does, and an unknown separator is refused. |
||
|
+39 |
254351c01b |
Insert › Table: the grid previews the hovered table on the page (#3931) (#4052)
* Insert › Table: the grid previews the hovered table on the page (#3931) * Insert › Table preview: the test checks the table lands at the caret; a new hovered size repaints the page (#3931) * Insert › Table preview: the pages are drawn from the copy the preview laid out, whose paragraph paths are the layout's (#3931) * Insert › Table preview: a caret move or a view change with the grid still hovered lays the preview out again (#3931) * Insert › Table preview: the caret and selection are drawn from the previewed copy, Reading Highlight waits for the preview to end, and the word being typed stays unmarked on it (#3931) * Format negative zero as 0 in spreadsheet import (#4040) Found while testing crates/formats/src/sheet.rs:726-727 (fn number) Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Ignore quoted text when detecting recipient CSV delimiter (#4125) Found while testing crates/engine/src/cmd/mailings.rs:526-527 (parse_csv) Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Cap label and envelope text length in mailings commands (#4008) Fixes #3633 Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * DOCX: a hinted East Asian or complex-script font no longer replaces the Latin font (fixes #4031) (#4067) * Phonetic Guide dialog puts the keyboard in the Ruby text box on open (#3914) (#4021) * Route ui.screenshot path writes through the guarded engine writer (#4048) Fixes #3635 Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Dialogs: draw real tabs (Paragraph, Find, About) (#4029) * Dialog tabs: draw real tabs in Paragraph, Find and About (#4026) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * A word with no break opportunity in it makes the layout quadratic (#3808) * A word with no break opportunity in it no longer makes the layout quadratic Laying out a line beside a floating object measures the word that starts on it, to tell whether the word fits the full line. The measurement walked to the end of the paragraph every time, so a word with no break opportunity in it — which takes a line per few characters — walked the paragraph once per line, and the layout was quadratic in the word's length. The measurement now stops once the word is clearly wider than the room there is: the answer is known by then, and the advances are widths, so the rest of the word could only make it wider — unless a font pulls a glyph back with a negative advance, and then the measurement carries on only as far as the rest of the word could take the width back. The same answer, the same layout, one line's worth of clusters per line instead of a paragraph's. A 1 MiB word went from 29.42 s to 0.35 s, and a 4 MiB one from 472.34 s to 1.38 s. #3412 * Record the layout fix in the progress docs The layout parity table gains the measurement for a word with no break opportunity in it laid out beside a floating object, and a note says what the fix was. The ranked gap list holds one row per gap now, and its #20 row carries the measured figure. The progress log and the revision history gain the entry. #3412 * Measure every word once, before the line is broken (#3412) The measurement of the word that starts a line walked the word to find out how wide it was, and a font that pulls a glyph back with a negative advance keeps the walk going: the bound that stopped it early was the sum of the advances the rest of the word could give back, and when the advances alternate in sign that sum stays so negative that the walk never stops early. Every word is measured once now, before the line is broken: the width of a word is the sum of its advances, the negative ones included, and asking whether the word fits the full line is a lookup rather than another pass over it. The answer is the same: the sum is taken over the same clusters the scan took them over. It is added up from the word's end rather than its start, which is the same number in all but the last bits of a float — the sum a float holds depends on the order it is added in, and the two differ by far less than the tenth of a point the comparison that uses the width allows. The ranked-gap rows that were not this fix's are back to how main has them. * Lay the negative-advance word out beside a float (#3412) The width of a word is checked against the width of a word, and a font that pulls a glyph back takes the width off the rest of the word as well. The layout now does that too: the word is laid out beside a floating object, in the strip the object leaves, and it fits there — where the same word without the glyph pulled back does not, and moves on whole below the object instead. The strip the object leaves is half way between the two widths, so the test does not depend on the font's metrics. * 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-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 ei… * Outline: re-match collapsed headings only after edits, via a hash index (#4014) * Outline: re-match collapsed headings only after edits, via a hash index Fixes #3621 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Write a text-box story shared by many shapes only once on docx save (#3983) Fixes #3653 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Keep pending formatting revisions when Style Inspector clears formatting (#3971) Fixes #3671 Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Cap the headless ui_requests queue and keep one Paste Special request (#3997) Fixes #3641 Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Validate PNG extension in file.exportPng (#3837) * 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> * changes to filter dialogue * 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> * 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-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- C… * DOCX: write the default font size when the document leaves it unset (#4092) A Document whose styles.default_chr.size is None resolves its text at 11 pt (resolve.rs falls back to 11.0), but the writer left w:sz out of w:docDefaults/w:rPrDefault and the reader fills an omitted size with 10 pt. So WordCraft's own save and reopen changed the effective default size from 11 pt to 10 pt with no edit in between. Name the resolver's fallback DEFAULT_FONT_SIZE, use it in both resolver sites, and have the writer emit it when the default size is unset. The reader is unchanged, so a file from another program that has no size is read exactly as before. The written size is the resolver's final fallback and not what Normal resolves to, so a style that sets a size is unaffected. Adds four tests in tests/it/default_size.rs; two fail without the writer change. Co-authored-by: narain1 <narain1@users.noreply.github.com> * #3994: watermark font names round-trip through the textpath's CSS style (#4071) * #3994: watermark font names round-trip through the textpath's CSS style The writer deleted ", ', ; and : from a watermark's font name, so a name like "Bob's Serif" was saved under a different name, a trailing backslash escaped the style's closing quote (the font lost its size), and a control character made the CSS invalid. The four characters a quoted CSS string can't carry raw are now CSS escapes (\22, \3b, \3a, \5c, each with its terminating space), control characters are dropped, and the apostrophe — legal inside a double-quoted string — is kept. Both textpath font-family readers map the escapes back, so the name round-trips. Co-Authored-By: GLM-5.3 <noreply@z.ai> * #3994: a font name's own apostrophes survive the textpath reader Both textpath font-family readers trimmed every quote and apostrophe off the ends of the value, so a font named 'Serif' (or Trailing') came back without them. The reader now removes only the one pair of quotes around the value; the round-trip test covers both cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: GLM-5.3 <noreply@z.ai> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Keep the direction of left, up and down block arrows (#3919) (#4022) * Keep the direction of left, up and down block arrows (#3919) * Cap and deduplicate the .aff TRY alphabet at parse time (#3981) Fixes #3654 Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Render: a dimmed header's borders keep one tone where they meet (#2946) (#3725) #2947 fixed the fade by drawing a dimmed layer inside a single opacity layer, but left no regression test for the tone: where a header table's borders cross, a crossed or shared rule would quietly come out darker than a rule that is not drawn on top of anything, and the undimmed view and the PDF export would not agree with the screen. The test renders the same page with the header dimmed and undimmed and checks three rules: the darkest pixel at a crossing of a cell's top and left rules, the darkest pixel on a border two neighbouring cells share, and one on an outer border well clear of both. With the fade done as one layer, the crossing and the shared border keep the tone of an isolated rule; measured against a clean full-strength render, the dimmed tone is 0.45 of the background, not 0.45 squared. * Refuse arrange.group when members exceed the group child cap (#3958) Fixes #3679 Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Ignore docx chart number-format codes longer than 255 bytes (#3963) Fixes #3729 Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Format Painter: show the copy pointer over the text while armed (#3453) (#3560) * Format Painter: show the copy pointer over the text while armed (#3453) The canvas chose the I-beam over text whether or not the Format Painter held copied formatting, so nothing showed the tool was waiting for a click. While it is armed, the pointer over the text is now the copy pointer (egui has no paintbrush), the same choice as the open DeckCraft fix for the same report (storytold/deckcraft#846). Pictures, table handles and resize edges keep their own pointers. * Format Painter: the selection bar keeps its arrow while the painter is armed Review follow-up: the bar's arrow shows that a click there selects whole lines, so it outranks the painter's copy pointer (#3453). * Format Painter pointer test: the selection bar keeps its arrow while armed Review follow-up (#3453): the test now also hovers the selection bar with the painter armed, so a precedence regression there fails it. * Canvas: document show() Review follow-up (#3453): CodeRabbit's docstring check counts the function the pointer change sits in. * #3453: Format Painter pointer test requires the I-beam once the painter is off Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #3453: Format Painter pointer test expects the hidden pointer where the I-beam is drawn On Windows the canvas hides the system I-beam and draws its own (#1746), so the pointer over the text reads None there, not Text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Ruler: dragging a margin edge leaves the view where it is (#3900) Each step of a margin drag on a ruler runs layout.margins, and like any command that brought the caret back into view. With the caret off screen (dragging the bottom margin while the text being edited is near the top of the page) the view scrolled mid-drag: the page slid under the pointer, the pointer's distance from the page's top changed with it, and the margin ran far past where it was dropped. A drag of about 1 cm on the vertical ruler set a 2 cm bottom margin to 8.1 cm. The margin handles now run the command with run_keep_view, as table border drags do (#1738). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Save, export and print keep the view where it is (#3895) * Save, export and print keep the view where it is (fixes #3864) * File › Options keeps the view where it is too Changing a setting such as spell checking moves neither the caret nor the text, so it no longer scrolls back to the caret. * Layout: an empty paragraph with a hidden mark shows nothing, not even its list number (#3982) (#3992) * Layout: an empty paragraph with a hidden mark shows nothing, not even its list number (#3982) A paragraph whose mark is hidden and whose text is empty or hidden too is now laid out as nothing, like a paragraph deleted in the final text: no room, no list number and no shading. Its number is still counted, as in Word ("1. Alpha", hidden item, "3. Beta"). Before, the body gave it no room but drew its number under the next item's, and a table cell gave it a line of its own with the number. * 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-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 call… --------- Co-authored-by: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> Co-authored-by: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Co-authored-by: Joseph Anglada <fluorine@users.noreply.github.com> Co-authored-by: Mat Strange <54028700+matstrange@users.noreply.github.com> 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: opencode <noreply@opencode.ai> Co-authored-by: skylar taylor-barrick <hi@skylar.cc> Co-authored-by: Brandon Thomas <echelon@gmail.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: Menonro <nitayd2001@gmail.com> Co-authored-by: Gabriel Luís da Silva <79342117+mercuryVM@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: Dmitriy <dmitriy@mrzv.org> Co-authored-by: Mat Strange <mat@matstrange.net> Co-authored-by: Dmitrii Khlapov <dkhlapov@pm.me> Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: VoiceLessQ <44067308+VoiceLessQ@users.noreply.github.com> Co-authored-by: SlavenDj <53652906+SlavenDj@users.noreply.github.com> Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk> Co-authored-by: naxvog <naxvog@users.noreply.github.com> Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com> Co-authored-by: Federico Olivares <119693761+FedeOlivares@users.noreply.github.com> Co-authored-by: Federico Olivares <FedeOlivares@users.noreply.github.com> Co-authored-by: rabis <340973947+rabis7575@users.noreply.github.com> Co-authored-by: Dylan Taylor <dylan@craftmaker.dev> Co-authored-by: Narain <narainp1@outlook.com> Co-authored-by: narain1 <narain1@users.noreply.github.com> Co-authored-by: Capri <96988330+SusieeTheLinuxUser@users.noreply.github.com> Co-authored-by: GLM-5.3 <noreply@z.ai> |
||
|
|
00856d7178 |
Merge remote-tracking branch 'upstream/main' into w6
# Conflicts: # ROADMAP.md # crates/engine/src/session.rs # crates/ui-egui/Cargo.toml # crates/ui-egui/src/control.rs # crates/ui-egui/src/i18n/bg.tsv # crates/ui-egui/src/i18n/cs.tsv # crates/ui-egui/src/i18n/da.tsv # crates/ui-egui/src/i18n/de.tsv # crates/ui-egui/src/i18n/es.tsv # crates/ui-egui/src/i18n/et.tsv # crates/ui-egui/src/i18n/fr.tsv # crates/ui-egui/src/i18n/hr.tsv # crates/ui-egui/src/i18n/hu.tsv # crates/ui-egui/src/i18n/it.tsv # crates/ui-egui/src/i18n/ja.tsv # crates/ui-egui/src/i18n/ko.tsv # crates/ui-egui/src/i18n/nl.tsv # crates/ui-egui/src/i18n/pl.tsv # crates/ui-egui/src/i18n/pt-br.tsv # crates/ui-egui/src/i18n/ro.tsv # crates/ui-egui/src/i18n/ru.tsv # crates/ui-egui/src/i18n/sl.tsv # crates/ui-egui/src/i18n/sr-latn.tsv # crates/ui-egui/src/i18n/sr.tsv # crates/ui-egui/src/i18n/sv.tsv # crates/ui-egui/src/i18n/tr.tsv # crates/ui-egui/src/i18n/uk.tsv # crates/ui-egui/src/i18n/vi.tsv # crates/ui-egui/src/i18n/zh-hans.tsv # crates/ui-egui/src/i18n/zh-hant.tsv # crates/ui-egui/src/lib.rs # docs/gaps.md # docs/parity-checklist.md # docs/ui-parity.md |