Commit Graph
110 Commits
Author SHA1 Message Date
aiongg c795e3cd41 CI: one test binary per crate, nextest, line tables only (#4025)
* CI: line-table debug info for dev builds, none for dependencies

* CI: one integration-test binary per crate

* CI: nextest with doctests, test build without examples, one wasm check

* CI: the interface audit reports missing catalog entries instead of failing

* Docs: the interface audit, not CI as a whole, passes with missing catalog entries
2026-10-23 14:24:37 -04:00
Dylan M. TaylorandCodebuff 0aded47e93 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>
2026-10-23 10:18:03 -04:00
skylar taylor-barrick af2803a546 #3279: Keep protected session author fixed in CLI and Options 2026-10-22 20:52:44 -06:00
Mat Strangeandopencode 5151809e11 Merge origin/main into fix/protection-file-commands
Resolves the conflicts with the changes that landed since the branch was cut:

- crates/engine/src/cmd/protect.rs: the forms-mode refusals stay behind
  upstream's `greyed_out()` (so the ribbon still greys the Developer tab's
  control buttons out), and the refusal of the document's properties and its
  privacy options is added on top of it.
- ROADMAP.md, docs/file-format-parity.md, docs/gaps.md: both sides' entries
  are kept; the status lines carry the merge's date and this change at the
  front.

Co-Authored-By: opencode <noreply@opencode.ai>
2026-10-22 23:08:09 +01:00
aiongg 04b3881e3e Integration: track t06
docs/gaps.md row 4: one row (t06 had kept a second, older copy of it); t05's #1840 and t06's #2964 clauses both in, main's #2963 clause kept. docs/recovery.md: t06's retry sentence with t01's base64 size text. Test files: both tracks' tests kept whole.
2026-10-22 01:47:34 +00:00
aiongg 861236167f Merge PR #2098: Opening a named pipe or a device no longer hangs or eats memory, whatever its name (#1444 follow-up)
contributors/people.toml: union of both sides' new entries.
2026-10-22 01:19:58 +00:00
aiongg 934b3495ad Merge PR #3220: Wait for recovery cleanup before the app exits (#3140) 2026-10-22 01:19:45 +00:00
aiongg 3bc152f91c Merge PR #3163: CLI: run without --file uses the region's paper, like convert (#3152) 2026-10-22 01:19:43 +00:00
5a6d2b6070 File › New: personal templates from a user templates folder (#2852) (#3133)
* #2852: personal templates in File › New, Save as Word Template

File › New lists the .dotx/.dotm files in the user templates folder under
Personal (a subfolder per category, search covers them); a click makes an
untitled document attached to the template. Save As › Save as Word Template
starts in that folder (<settings>/Templates, made on first use; File ›
Options › Advanced › File locations › User templates overrides it).
Engine: file.listTemplates (bounded, network folders refused, read root
applies), file.newFromTemplate sets the attached template, file.options
userTemplatesFolder, file.saveTemplate keeps a .dotm name. UI:
ui.saveAsTemplate; Save dialog requests carry a start folder.

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

* #2852: review — a personal template asks about unsaved changes; network templates folder refused before it's looked at; New lists the folder once per visit with Refresh; .dotx/.dotm labels alike

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-10-21 20:18:50 -04:00
Dylan M. Taylor 7f38f41d8c Use chrono instead of hand-rolled UTC conversion (#3427)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled civil_from_days / days_from_civil copies and the
Excel serial-date, FILETIME and logging/PDF date conversions with chrono as
a workspace dependency (default-features = false, std only, so wasm is
unaffected). Output formats are unchanged and pinned by the existing (and one
new) test. Excel's 1900 leap-year bug and the 1900/1904 systems keep their
explicit special cases; a small proleptic-Gregorian fallback covers years
outside chrono's NaiveDate range so no input changes behaviour or panics.
AGENTS.md now says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic or reshape date output for clocks outside chrono's range

chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked
inside the logger. logging::timestamp now uses the fallible
DateTime::from_timestamp and reads such a clock as the epoch, as the old
saturating code did.

iso_from_unix_secs fell back to the Unix epoch for timestamps chrono
rejects; it now keeps the proleptic Gregorian date civil_from_days gives,
restoring the pre-chrono result for supported out-of-range u64 inputs.

Years past 9999 are written with {:04} rather than %Y (which prepends a
'+') in logging::timestamp, iso_from_unix_secs, docbin::filetime_iso and
odt::now_iso, so byte-for-byte output is preserved.

filetime_iso gained a docstring; AGENTS.md's chrono bullet now notes the
narrow out-of-range fallback. Tests cover each boundary.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* Route out-of-range dates through chrono's 400-year cycle, not hand-rolled copies

The PR kept hand-rolled Hinnant `civil_fallback`/`days_from_civil_fallback`
copies for dates outside chrono's ±262143-year range, and AGENTS.md carved a
"narrow fallback" exception for them. That left calendar arithmetic outside
chrono after all.

Replace every copy with the Gregorian 400-year cycle (146_097 days): chrono
converts the day/year within one cycle and whole cycles are added back via i128
(so i64::MIN/MAX can't overflow). This keeps chrono as the only calendar logic
and reproduces the pre-chrono proleptic Gregorian result for every value:

- engine cmd/mod.rs `civil_from_days` (reached by the system clock and by
  `iso_from_unix_secs` for a u64 seconds count past chrono's range).
- engine cmd/controls.rs `days_from_civil` and `days_in_month` (`days_in_month`
  now uses `rem_euclid(400)` since month lengths repeat every 400 years).
- formats sheet.rs `civil` and docx chart.rs `civil` (Excel serials).

Outputs are unchanged: verified the cycle result equals the old formula over a
wide range and at the extremes with a scratch chrono program. New tests pin
accepted inputs past chrono's range on both sides of the epoch, through the
public/lib entry points and the private converters, with values from the old
formula. Removed the AGENTS.md "narrow fallback" exception.

pdf/lib.rs `civil` is left as-is: its only caller is the live system clock and
its year is clamped to 0..9999 by the PDF DateTime, so the epoch fallback past
year 262143 is the accepted live-clock behavior, not a hand-rolled copy.

Tested: cargo test -p wordcraft-engine -p wordcraft-formats -p wordcraft-docx;
fmt --check; clippy --all-targets -D warnings.

* logging/docbin/odt/pdf: keep extended-year dates, no epoch fallback

FredApps (CHANGES_REQUESTED) showed `DateTime::from_timestamp(..).unwrap_or_default()`
changes output: a timestamp the old code converted past chrono's year 262143 range now
collapsed to 1970-01-01 instead of its real proleptic-Gregorian date. The same regression
exists wherever the PR swapped a hand-rolled converter for the epoch fallback. Fix every
such site to keep chrono as the only calendar logic via the 400-year Gregorian cycle
(146_097 days): chrono converts the day within one cycle, whole cycles carry in the year
(i128, saturated into i64). Verified the cycle result equals the old formula across a wide
range and at the extremes.

- apps/wordcraft/src/logging.rs `timestamp`: added `civil_from_days(u64)` (cycle), kept the
  pre-1970 epoch clamp (`duration_since`) and the milliseconds/time-of-day format exactly.
  Pins: UNIX_EPOCH + 10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z (FredApps' example,
  was 1970-...); the checked_add u64::MAX/2 s fixture now asserts 292277026596-12-04T15:30:07.000Z
  instead of the epoch.
- crates/docbin/src/metadata.rs `filetime_iso`: restored a cycle-based `civil(i64)` instead
  of `from_timestamp(..)?` (which dropped the field past chrono's range). A u64 FILETIME caps
  at ~1.83e12 unix s (year 60056), inside chrono's range, so no reachable value changes; pinned
  the largest FILETIME (u64::MAX) -> 60056-05-28T05:36:10Z to lock the extended-year path.
- crates/formats/src/odt.rs `now_iso`: reuse the crate's existing cycle converter
  `sheet::civil` (made pub(crate)) rather than the epoch fallback.
- crates/pdf/src/lib.rs `civil`: cycle converter; year returned unclamped and the caller's
  existing PDF DateTime 0..=9999 year clamp is kept exactly, so month/day/time of a far-future
  clock survive. Pins: civil(10_000_000_000_000)=(318857,5,20,17,46,40),
  civil(-10_000_000_000_000)=(-314918,8,13,6,13,20), civil(253_402_300_800)=(10000,1,1,0,0,0).

engine `cmd/mod.rs::iso_from_unix_secs` already uses the cycle fallback (last round); unchanged.

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-21 19:36:41 -04:00
Mat Strangeandopencode d809c838c4 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>
2026-10-20 17:20:53 +01:00
MichaelandClaude Sonnet 5 f550ab7bea Wait for recovery cleanup before the app exits (#3140)
A save makes the document clean, which queues discarding this
session's now-redundant recovery copy — but that's only queued, on a
background worker thread, the same as a print job. Closing the app
right after Save (the very next frame, with no regular recovery_tick
in between to notice and queue the discard, or with the discard
queued but not yet finished by the worker) could race the process
exiting before the copy was actually removed, so the next launch
wrongly offered to recover a document that was in fact saved cleanly.

Added Recovery::finish and Session::finish_recovery, mirroring the
print spooler's own finish_pending_jobs, and call it from the same
places: on_exit and the macOS hard-quit path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-20 11:22:51 +08:00
NaxvogandClaude Opus 5.5 fec27d66d1 CLI: run without --file uses the region's paper, like convert (#3152)
A bare `run` built its document with Document::new() (Letter) and never went
through file.new, so it skipped the system region's paper. It now runs
file.new, as `run --template` already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:54:41 +02:00
Diego GueriniandClaude Sonnet 5.5 49a934fba7 #3067: script parse test compares paths with Path::join (#3106)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 12:29:17 -04:00
Martin BallandClaude Opus 5.5 6950c22d73 #2999: a file dialog that "cancels" at once asks again without a parent, then shows the built-in browser (#3030)
* #2999: a file dialog that "cancels" at once asks again without a parent, then shows the built-in browser

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

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

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

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 03:04:59 -04:00
Diego GueriniandClaude Opus 5.5 18c7c1d114 #2845 follow-up: clippy passes on Windows and Linux again (#3006)
`graphics.rs` allowed dead code only on macOS (where the Windows items are unused). Since #2845
the two indirect-validation functions are called only on macOS, so on Windows and Linux
`cargo clippy --workspace --all-targets -- -D warnings` stopped with "function is never used".
They now carry `allow(dead_code)` off macOS.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:43:28 -04:00
Max Murphy 72328ab521 cli: keep the non-UTF-8 file name test off macOS (#2993)
* cli: keep the non-UTF-8 file name test off macOS

APFS refuses file names that aren't valid UTF-8, so the fixture can't be
made there (as in tests/non_utf8_args.rs); the unit test runs on the
other unixes.

* chore: add @maxm11 to About-window contributors with their own name
2026-10-18 16:37:59 -04:00
9ce4553899 fix(macos): skip wgpu indirect validation on Intel Macs so launch no longer exits (#2652) (#2845)
wgpu compiles its indirect-call validation compute shaders inside request_device.
On Intel HD Graphics 5000 (Big Sur) Metal's Intel compiler aborts on them, the
device is lost and eframe exits before the window opens. WordCraft issues no
indirect draws or dispatches, so x86_64 macOS builds leave
VALIDATION_INDIRECT_CALL out of the instance flags (WGPU_VALIDATION_INDIRECT_CALL=1
still re-enables it). Same fix as storytold/PhotoCraft#3171.

Fixes storytold/WordCraft#2652

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:50:25 -04:00
6dd7c3bc3a #1963: wordcraft-cli runs commands from a script file (#2566)
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:42:13 -04:00
2054f8fd88 fix(web): fall back after asynchronous save picker rejection (#2145)
Preserve AbortError cancellation and report download fallback failures without marking saves successful.

Fixes #1246

Assisted-by: OpenAI Codex

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:36:42 -04:00
28d6260a81 #1789: Add Font imports every selected font file (#2404)
* Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1789: cap font bytes waiting in the web inbox

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:24:26 -04:00
0c8399896f cli: open a password-protected Word file with --password (#1770)
info, text, convert, render and inspect took no password, though file.open
understood one, so an agent driving the CLI could not open a protected
document at all. --password, --password-file and WORDCRAFT_PASSWORD, in
that order.

The file form exists because a password on a command line is readable by
every other process on the machine; a trailing newline is dropped so a
file written by echo works. Never logged, never named in an error.

Rebased onto current main, which moved `open` from &str to &Path so that a
file name that isn't valid Unicode no longer crashes the CLI (#1440). Both
changes touch the same function, so the resolution keeps their shape: the
closure that substitutes the real path still stands, and it takes the
password as a second argument.

Co-authored-by: Bra1nZ <bra1nz@astraoffice.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 09:55:26 -04:00
8701f4e28d Theme: default to system appearance (#718)
* Theme: default to system appearance

Sets Appearance::System as the default variant for ThemePreference,
so fresh installs and unconfigured preference files follow the host
OS appearance dynamically.

* contributors: add the missing blank line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* contributors: drop the duplicate entry (already listed on main)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Web prefs test: junk storage keeps the new default (System) appearance

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:23:13 -04:00
a30a1866e2 Web: keep preferences such as dark mode between visits (#2132) (#2205)
The web shell never loaded or saved the UiState the desktop app keeps in
ui.json, so a reload reset the theme and every other preference. It now
restores it from localStorage at startup and writes it back whenever it
changes (a reload runs no exit code).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:03:16 -04:00
ce38b0c372 #1297: copied equations go in the MathML clipboard formats Word reads (#2614)
A copy or cut of one equation on its own now also puts its MathML in the
platform's MathML clipboard formats, beside the HTML (with MathML, #816) and
the linear text: "MathML" and "MathML Presentation" (UTF-16LE) on Windows,
public.mathml.presentation and public.mathml on macOS. These are the names
W3C MathML 3 section 6.2.4 registers, and Microsoft's "MathML Support in
Microsoft 365" doc says Office checks them first when it pastes math. No
public clipboard format carries OMML. A copy with text around the equation
doesn't offer them, so Word keeps the text.

Not yet verified in Word: the parity docs say MathML clipboard formats, not
an editable paste.


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

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:46:15 -04:00
Fin WarmanandBrandon Thomas 81098c52f6 CLI: allow explicit field updates during conversion (#2014)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:45:57 -04:00
asystemoffieldsandClaude Opus 5.5 e500df7fb6 Opening a named pipe or a device no longer hangs or eats memory, whatever its name (#1444 follow-up)
The app read a file whose name isn't valid Unicode (#1440) with a plain
std::fs::read, and file.open on a path (also wordcraft-cli) checked only
the length before std::fs::read. Neither checked for a regular file: a
named pipe blocked the open (app start-up hung), and /dev/zero read until
memory ran out.

The new io::read_document reads a document path inside the automation
read root, only as a regular file of at most 2 GB, through the existing
read_regular_file. open_path_in and the app's open_non_unicode both use
it, so the two read a path the same way.

Regression tests: file.open on a FIFO and read_document on /dev/zero are
refused without blocking (engine), and the app's open of a FIFO or a
/dev/zero symlink under a non-Unicode name is a quick warning that leaves
the open document alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:24:08 -04:00
5f83a4727a get_text and CLI text omit tracked deletions (#866) (#944)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:21:06 -04:00
60b9500347 Web: add option to load system installed fonts in dropdown (#1390)
* feat(web): add option to load system installed fonts in dropdown

Add explicit 'Load System Installed Fonts…' button in font dropdowns for web (Wasm).

- Dispatches font.loadSystem via transient user activation on click
- Calls window.queryLocalFonts() to enumerate and load installed fonts into FontDb
- Invalidates font family cache in FontPreviews upon new font installation
- Complete localization across all 18 languages
- Credit GhostMitra in contributors/people.toml

* fix(ui, engine): canvas focus after chrome click and checked file access in sniff

* Revert "fix(ui, engine): canvas focus after chrome click and checked file access in sniff"

This reverts commit b0c925bf79.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:24:19 -04:00
3ef4d1fb9a feat(print): add orientation support and pass landscape options to spooler (#1389)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:06:58 -04:00
MarekSpodBiedryandBrandon Thomas 1c814e0269 Copy puts the formatted text on the system clipboard as HTML, not only copies with equations (#1523) (#1667)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:29:56 -04:00
3cc0c16302 A file name that isn't valid Unicode no longer crashes wordcraft-cli or the app (#1440) (#1444)
* A file name that isn't valid Unicode no longer crashes wordcraft-cli or the app (#1440)

Both binaries read std::env::args(), which panics on a non-Unicode
argument such as a Latin-1 file name. The CLI now reads args_os(): the
file a command opens keeps its real name and opens (convert, info, text,
inspect, render, zotero, run --file), and any other such argument is an
error naming it, exit 1. The app leaves such a file out with a warning,
as it does a file that fails to open, since file.open takes its path as
text; a --control port that isn't valid Unicode exits 2.

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

* The app opens a file whose name isn't valid Unicode too (#1440)

file.open takes its path as text but also accepts the file's bytes
under a name, as dropped files use. The app reads such a file, opens
its bytes under the lossy name and keeps the real path on the session,
so Save writes back to it. A unit test covers the open and the save.

The CLI test now checks that every command in OPENS_FILE reaches its
open with the file, and no longer pins `pos` counting a `--page` value.
ROADMAP says which saves go back to the file.

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

* #1440: skip non-UTF-8 file name tests on macOS

APFS refuses file names that aren't valid UTF-8, so the fixtures fail
with "Illegal byte sequence" there, and the app-start test keeps running
because macOS opens a window without a display variable. The unit test
for the CLI's argument checks and the control-port test still run.

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 19:32:48 -04:00
81b5602bf0 insert.picture and insert.icon leave the caret after the picture (#965) (#1437)
* contributors: add Nael-Nathanael

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

* insert.picture and insert.icon leave the caret after the picture (#965)

A script that typed right after inserting a picture replaced it, so the
picture was missing from the saved file. Commands now leave the caret
after the new picture; Picture Format commands still act on it. The app
still selects a picture the user inserts, as in Word.

Fixes #965

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

* Select a pasted picture again, keep pictures inserted in a row (#965)

Desktop paste inserts through execute, so after the caret change it
left a pasted picture unselected. WordApp::select_inserted_picture
selects the picture just before the caret; run and the paste path call
it, paste once after the last picture so a multi-file paste keeps them
all. Tests for paste selection and for two inserts in a row.

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 19:31:55 -04:00
18ecff6fbb wordcraft --control checks its port (#1426) (#1436)
* wordcraft --control checks its port (#1426)

--control took whatever followed it as the port and dropped it when it
didn't parse, so `--control --sample` lost --sample, `--control abc`
started without the control channel, and a bad value cleared
WORDCRAFT_CONTROL_PORT. A flag after --control is now left for the loop,
and a missing, non-numeric, zero or out-of-range port prints an error
naming it and exits with status 2, after the loop so --version still wins.

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

* wordcraft --control names any negative number it rejects (#1426)

A value after --control counts as the port when it starts with a digit
or a minus and a digit, so a negative too long for i64 is named in the
error instead of left in the file list. NonZeroU16 replaces the
separate zero check.

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 19:07:45 -04:00
7188d8ae04 Save preferences through a temporary file and log write failures (#1361) (#1404)
* Save preferences through a temporary file and log write failures (#1361)

* Move the preferences test module to the end of main.rs (clippy::items_after_test_module)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:00:49 -04:00
7658819bf7 Desktop: a window too big for its screen shrinks to fit when it opens (#1215) (#1253)
eframe shrinks the start size to the monitor's, but not by the frame, the
title bar or the taskbar, and Windows places a new window away from the
screen's corner. On a laptop screen the first window's bottom, with the end
of the scroll bar, sat under the taskbar or past the screen's edge, so a
drag of the scroll bar stopped where the pointer met the edge. The window
was saved that size and opened so again on every launch.

On the frame that first knows the monitor, a window that doesn't fit with
48 pt to spare on each side (WindowGeometry::fit_on_monitor) gets a smaller
size, never below the minimum, and is centred. A window the system placed
counts its position too; a restored one, maybe on another monitor, only its
size. Maximized, full-screen and minimized windows are left alone. Checked
on Windows with no saved settings: the window now opens wholly on screen.

Closes #1215

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 18:16:05 -04:00
8ae23cee29 Desktop: a maximized window no longer flashes on and off at start-up on Windows (#23) (#1194)
* Desktop: a maximized window no longer flashes on and off at start-up on Windows (#23)

winit shows a window the moment it creates it maximized, even though eframe starts it hidden
until the first frame. A window saved as maximized therefore appeared and disappeared about four
times in a third of a second before settling. On Windows it now opens at its normal size, still
hidden, and the first frame maximizes it (WindowGeometry::maximize_after_open). Other platforms
are unchanged. Measured by polling the window's visibility every 15 ms: before, show/hide four
times; after, hidden until the first frame, shown once.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Contributors: add my display name

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 17:59:04 -04:00
3d8f8144d9 File: recover unsaved work after a crash (#518) (#586)
* M10: retain local crash recovery on current main

* i18n: Backstage Recover in every catalog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:21:57 -04:00
eb54d88ec0 Mail merge: .xls recipients, legacy encodings, Filter and Sort (#778) (#885)
* #778: .xls and legacy-encoding recipient lists; filter and sort recipients

- Recipient lists: legacy Excel 97-2003 .xls (BIFF8 reader on the cfb crate already in the
  tree); Excel 5.0/95 and password-protected .xls refused with a clear message; a CSV named
  .xls reads as text, an HTML one gets an error. .xls added to the picker filters (desktop,
  web, built-in browser) and drop handling.
- Text lists (and plain-text import) detect legacy encodings when not UTF-8/UTF-16: GBK/GB18030,
  Big5, Shift_JIS, EUC-KR, Windows code pages (encoding_rs + chardetng, MIT/Apache); an
  `encoding` param overrides.
- mailings.filterRecipients / mailings.sortRecipients: field, comparison (equal, notEqual,
  less, greater, lessOrEqual, greaterOrEqual, blank, notBlank, contains, notContains), value,
  And/Or joins; sort keys. Preview, Finish & Merge and labels use only the matching records
  in sort order. Edit Recipient List edits every record and keeps the filter.
- Recipient List dialog: Filter and Sort section and a merge-order column; catalog rows;
  strings in every catalog; docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: Hungarian and French strings for this change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review fixes for #885: total order for recipient sort, Bulgarian strings

- compare_cells now ranks numbers (compared numerically) before text (compared
  case-insensitively), so it is a total order; a column mixing 9, 10 and 1a
  could make slice::sort_by panic. Regression test sorts mixed columns.
- Bulgarian catalog: the 24 Filter and Sort strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* vi: mail merge Filter and Sort strings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:57:06 -04:00
Navid Zaman Khan 5311069ae1 fix(cli): reject invalid page and scale arguments (#982) 2026-10-14 15:08:27 -04:00
1c2f0fe65e --help and -h print the usage for the app and every CLI command (#968) (#977)
* --help and -h print the usage for the app and every CLI command (#968)

wordcraft --help / -h printed nothing useful: the app tried to open a
file named --help and started. wordcraft-cli <command> --help failed with
"unknown option --help" for every command, and -h was taken as a file
name or ignored.

The app now prints its options and returns before the window opens.
The CLI checks for --help / -h after any known command before options
are validated or files and folders opened, and prints the shared usage.
Tests run both binaries for each command and flag; the app's test runs
without a display, so a regression fails instead of opening a window.

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

* #968: test bare --help and -h on wordcraft-cli

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:08:15 -04:00
Kameron KilchristandClaude Opus 5.5 1a34583e50 macOS: the title bar fits around the traffic lights as macOS draws them (#1559)
The traffic lights grew in macOS 26, so the zoom button overlapped
AutoSave. Measure the zoom button through AppKit at start-up and size the
bar and its left inset from it, keeping the old layout as the minimum and
the fallback.



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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:45:57 -04:00
3fc4ef2cf4 proof: Turkish spelling, suggestions, grammar and hyphenation (#1527)
* proof: Turkish spelling, suggestions, grammar and hyphenation

* Turkish: dictionary tests skip when no Turkish Hunspell dictionary is installed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:45:30 -04:00
03ba9e483f Web: New/Open waits for Save before discarding the document (#1315) (#1510)
* #1445: Save in the close prompt that can't write the file says so instead of looking like Cancel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1445: a failed save from the close prompt brings the prompt back with the reason, Save As included

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1315: web Save before New/Open waits for the save picker; cancel or failure keeps the document

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1315: overlapping saves hold the replacement; cancel and waiting messages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 14:27:42 -04:00
53b7debea5 Web: image paste goes to the active edit target (#1349) (#1513)
* #1349: WIP

* Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1349: pastes onto the page work again; the guard uses page focus

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1349: pastes onto the page work again; the guard uses page focus (clippy)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1349: a focused text field outside dialogs ignores picture pastes (test)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 14:07:28 -04:00
bf2bd69470 HTML import: bold header cells, cell alignment and the region paper (#1381) (#1494)
* #1381: WIP

* #1381: HTML import: bold <th>, cell text-align, region paper for HTML/MD/TXT

Header cells are bold unless their own CSS says otherwise. text-align on a
td/th, or inherited from its tr/row group/table, aligns the cell's
paragraphs; a paragraph's own alignment wins, and a table's align attribute
(which places the table) no longer counts. Files that give no page size
(HTML, Markdown, plain text) open on the region's paper, as a new blank
document does: file.open uses the session's region, wordcraft-cli reads the
system region the way the app does (Region::from_locale_vars, shared with
ui-egui). Tables size to that page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1381: the CLI uses the system region everywhere; help mentions WORDCRAFT_REGION

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:58:14 -04:00
686f71cabb Save As: fill the file type list (#1399) (#1497)
* #1399: WIP

* Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:44:21 -04:00
aa113f0801 Copied equations reach other programs as MathML (#816) (#1147)
* #816: copied equations reach other programs as MathML

A copy or cut whose selection holds an equation now also puts HTML on the
system clipboard, with each equation as Presentation MathML (wrapped in
<semantics> with the linear format as an application/x-unicodemath
annotation), so Word, LibreOffice, Google Docs and browsers paste a
formatted equation instead of `y=2x_s`.

- formats: new mathml module, a writer from the equation tree (runs,
  fractions, scripts and pre-scripts, radicals, n-ary operators, delimiters,
  functions, limits, accents, bars, border boxes, group characters, equation
  arrays, matrices, phantoms) and a bounded reader (our annotation, else the
  presentation elements, else a TeX annotation). HTML export writes
  equations as MathML; HTML import (open and paste) reads <math> back.
- engine: edit.copy/edit.cut (and column copy) return `html` and keep
  Session::clipboard_html when the fragment holds an equation.
- ui-egui: Services::set_clipboard_html; copy sites put HTML with the text
  through it, falling back to egui's text copy.
- wordcraft: arboard set().html() with the text alternative; on Wayland
  without data-control the text-only path is kept.

OMML on the clipboard (Word's "Office Open XML") needs custom clipboard
formats arboard can't set; left as a follow-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #816: honest clipboard docs; MathML reader fixes (token cap, function names, column vectors)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 02:04:46 -04:00
09b8b436cc Save as Word 2003 XML Document (#1081) (#1088)
Write WordprocessingML 2003 by rewriting the DOCX writer's package into the 2003 vocabulary; file.save/saveAs take format wordml2003, File > Export lists the format, CLI convert --format wordml2003, a plain save over a 2003 file keeps it.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 01:52:42 -04:00
MarekSpodBiedry f322c60bdf wordcraft-cli text keeps bullet and number labels on list items (#1080) (#1094) 2026-10-14 01:50:42 -04:00