Commit Graph
129 Commits
Author SHA1 Message Date
skylar taylor-barrick f7c49cb0f8 egui-037: reconcile main and protect IME undo and wrapped layout 2026-10-25 16:15:05 +01:00
skylar taylor-barrick 5b46f9ba4d M-egui037: fix fontless tests and document migration behavior 2026-10-25 15:24:23 +01:00
Marco Arnulfo f490f9da9a Linux desktop entry: stop declaring text/plain (#4206)
* Linux desktop entry: stop declaring text/plain (fixes #4137)

* Docs: plain text is no longer in the Linux desktop entry (#4137)
2026-10-25 09:54:32 -04:00
skylartaylor a605fa5ca7 M-EGUI037: reconcile WordCraft main and preserve IME editing 2026-10-25 02:19:38 +02:00
aiongg 4c8dc00fe6 Merge upstream/main (#4184 craft-libs bump) into the batch 2026-10-24 21:58:03 +00:00
aiongg ffa51443f2 Bump craft-print, craft-pdf, krilla and craft-i18n to craft-libs 1774443 (#4184)
* Bump craft-print, craft-pdf, krilla and craft-i18n to craft-libs 1774443

* PDF export writes link annotations only for safe targets (craft_pdf::write::uri_target)

* Chart default category and series names use fmt_plain, so RTL interfaces put no isolate marks into the document
2026-10-24 17:52:43 -04:00
aiongg 6f679e362f Web host bridge: a Save under a name the host page never sent (untitled, an imported .doc) is Save As (#3822) 2026-10-24 21:43:48 +00:00
skylartaylor c4974d9e8d egui037: reconcile current main, qualify dependency pins and preview accessibility 2026-10-24 23:22:06 +02:00
aiongg 226b3aa25f Web host bridge: Save As in a format that doesn't keep everything is a copy, saveAs false; no bridge on an opaque origin (#3822) 2026-10-24 21:05:19 +00:00
aiongg e62af05110 Merge PR #4171: Picture memory limits for 6 audit issues: one decoder, TIFF strips, styles, Remove Background, Windows paste, render cache 2026-10-24 20:38:55 +00:00
aiongg 6498bb20f6 Merge PR #4121: MCP: keep serving when the app isn't open yet (#4078) 2026-10-24 20:38:51 +00:00
aiongg cb7426af14 Merge PR #3822: Web: a ?host=parent bridge for pages that keep WordCraft's files 2026-10-24 20:38:40 +00:00
VoiceLessQ 5929374704 Windows paste: clipboard pictures are checked from their header before anything decodes them (#3623)
On Windows the paste path asked arboard (3.6.1) for decoded pixels.
arboard decodes the registered "PNG" format and CF_DIBV5 with no image
limits, so a clipboard item of a few dozen bytes whose header declares
100,000 x 100,000 pixels made the decoder allocate tens of GB. The
allocation failure aborted the app outside Session::run's guard, before
paste_picture's size checks ever saw the picture.

On Windows the app now reads the clipboard itself (clipboard-win, the
raw read capped at 200 MB):
- "PNG" goes to Insert Picture as encoded bytes, once its header shows
  a size a paste takes (16,000 a side, 50 M pixels).
- Otherwise CF_DIBV5 or CF_DIB is probed through
  wordcraft_geom::picture::probe_dib, checked the same way, then decoded
  within the decode budget into pixels.

probe_dib reads a DIB in the layout the image crate expects: a 32-bit
BI_RGB DIB with an alpha mask becomes bit fields, so its alpha is kept
as arboard did. A V4 or V5 bit-field DIB without the copy of its colour
masks after the header (arboard writes one) gets that copy, because the
decoder skips 12 bytes there and would otherwise read into the pixels.

Linux keeps using arboard, which decodes the clipboard's PNG within the
image crate's default 512 MB limit. macOS keeps it too: arboard decodes
the pasteboard's TIFF there, which can still reach the JPEG strip
decoder of #3645; that path is untouched here because it can't be built
or run on this machine.

cargo check and clippy for x86_64-pc-windows-gnu pass on the
Windows-only code; it hasn't been run on Windows.
2026-10-24 18:01:45 -01:00
galagersharp-code b86373974e MCP: a client that starts before WordCraft keeps its server, and a malformed --connect fails at startup
Connecting eagerly made the server fail when the app wasn't open yet, and a
client marks a failed MCP server failed for its whole session: an unavailable
app now leaves a lazy remote that connects on the first call, warns about a
missing control key only once connected, and says which state it is in.
--connect addresses are checked before the server starts, so 127.0.0.1:notaport
reports a numeric port instead of retrying on every tool call.
2026-10-25 02:36:19 +08:00
RobandClaude Opus 5.5 b9f6b33af5 Web host bridge: ready after the runner starts; the host's author name isn't stored
- wordcraft:ready is posted once WebRunner::start has succeeded, so a parent
  never hears ready and then failed.
- ?author= signs the visit but the browser keeps the user's own name in its
  stored preferences; a name chosen in File › Options is stored as usual.
- The saveAs flag is documented case by case: Save false, Save As and
  File › Export to a document format true, PDF/PNG exports false (copies).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:25:37 +01:00
Dylan M. TaylorandClaude Sonnet 5.5 ba7e7293e4 Sonar S1612: pass PoisonError::into_inner instead of a closure (#4102)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:33:12 -04:00
Dylan M. TaylorandClaude Sonnet 5.5 01d0c63451 Sonar S1612: use fn paths for simple method closures (#4103)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:33:08 -04:00
skylartaylor fd071202e7 E37: migrate WordCraft to egui 0.37 with guarded IME and capture lifecycle 2026-10-24 08:30:31 +02:00
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
RobandClaude Opus 5.5 caf5ea509c Web: a ?host=parent bridge for pages that keep WordCraft's files
A same-origin page that frames wordcraft-web (a Nextcloud app, a personal
file server) can now hand it a document and store what it saves, with the
same postMessage protocol as GridCraft's and PdfCraft's bridges:
wordcraft:ready / open / save {name, saveAs, bytes} / dirty / failed.

In host mode Save, Save As and Export go to the parent instead of the save
picker or a download (Services::host_save, with whether it was Save As).
Save writes the opened file under its own name and format, as the desktop
app does (an import-only .doc still becomes a .docx), and a Save or Save
As that keeps everything makes that name the document's file, so the next
Save goes to it. The parent guards leaving the page; ?author= names the
user for comments and tracked changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:38:12 +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