* 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
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>
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>
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.
* #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>
* 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.
`--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>
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>
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>
* #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>
`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>
* 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
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.
Fixesstorytold/WordCraft#2652
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
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>
* 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>
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>
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>
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>
* 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>
* 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>
* 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>
* 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>
* 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>
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>
* 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>
* 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>
* #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>
* --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>
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>
* 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>
* #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>
* #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>
* #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>
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>