Commit Graph
1565 Commits
Author SHA1 Message Date
aiongg 5e31171462 Merge branch 'lang-1-text' into lang-2-ui 2026-10-26 01:55:34 +00:00
aiongg fdecd50f47 font-store: removing a family's last style keeps files the store didn't install 2026-10-26 01:55:20 +00:00
aiongg f4c6718d68 craft-text: a locale's script must match the hyphenation patterns' script 2026-10-26 01:54:26 +00:00
Dylan M. Taylor e8c1c4757a Sonar: use PoisonError::into_inner, explicit test imports, [[ in import-cert
- proof: pass PoisonError::into_inner instead of an identity closure (S1612).
- layout tests: replace glob super::* imports with explicit names (S2208).
- packaging/macos/import-cert.sh: use [[ for the status test (S7688).
2026-10-25 21:28:59 -04:00
skylar taylor-barrick cb53221767 fix(print): stop relying on inflate error output; bump miniz_oxide to 0.9 (#348)
* fix(print): do not treat early inflate errors as partial output

* chore(deps): bump miniz_oxide to 0.9
2026-10-25 20:37:49 -04:00
Martin BallandClaude Opus 5.5 06af26ed67 storytold/wordcraft#3692: review — proof timing tests get a margin a busy CI machine can't break
Wall-clock bounds of 1-2 s failed on loaded runners. Each bound is now 10 s, at least 10x the
fixed code's time and well under the old code's on the same input (measured by running the
pre-fix code):

- self-continuing rules: 40,000 rules (was 16,000); ~1 ms now, old checker 14 s at 16,000.
- many strips per suffix: 200,000 rules (was 30,000); 0.4 s now, old index 3 s at 30,000 and
  11 min at 300,000.
- forms of self-continuing rules: the MAX_FORMS count catches the old code (9 million forms);
  the time bound is a backstop.
- overlong Turkish words: ~20 ms now; without the caps it ran past 5 minutes.
- capped Hunspell reads: no clock; the "not a regular file" error is the proof (the old reader
  never returned from /dev/zero).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-26 00:17:25 +00:00
Martin BallandClaude Opus 5.5 935c10d3d0 storytold/wordcraft#3661: review — ISO 8859-9 Turkish dictionaries load through the capped reader
Asked to confirm a non-UTF-8 Turkish dictionary still loads: before this PR
tr_TR was read with read_to_string, so a non-UTF-8 file never loaded; now it
goes through read_capped and decode, which didn't know ISO 8859-9 (Latin-5,
the encoding of older tr_TR dictionaries) and refused it as unsupported.
decode now reads `SET ISO8859-9`, and a test writes an ISO 8859-9 .aff/.dic
to disk and finds words with ğ ı ş İ ç ö ü, as stems and with suffixes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-26 00:17:25 +00:00
Martin BallandClaude Opus 5.5 a86631131f storytold/wordcraft#3625: review — "bir kaç" still gets "birkaç": the replacement table is checked before multi-word input is refused
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-26 00:17:25 +00:00
Martin BallandClaude Opus 5.5 a3983c4789 storytold/wordcraft#3625: Turkish suggestions and lookups have a word-length cap
Audit finding 20.

- tr::suggest_with refuses words over 64 letters (and words with spaces), like the other
  languages' suggesters (40 there; Turkish agglutinated words run past 40). Each candidate edit
  copies the whole word, so a long word cost O(n^2) per suggestion request on the UI thread.
- TrDict::contains returns false past MAX_WORD_BYTES (120, the affix engine's bound) instead of
  collecting the char indices of the whole word.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 00:17:25 +00:00
Martin BallandClaude Opus 5.5 eb37158db4 storytold/wordcraft#3657: Russian hyphenation and hyphenated output linear in word length
Audit findings 10 (storytold/wordcraft#3657) and 15 (storytold/wordcraft#3692).

- ru::hyphen::points: the cuts come in increasing order, so a repeat can only be the last one;
  compare with `out.last()` instead of `out.contains` per syllable (O(V^2) on a long Cyrillic
  word, during layout).
- hyphenate_word_in: walk the sorted break points alongside the chars instead of searching the
  point list for every char. Same output.

Test: russian_hyphenation_and_hyphenated_output_are_linear (8x the letters costs ~8x; the old
code measured 31x and 30x).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 00:17:25 +00:00
Martin BallandClaude Opus 5.5 f0561b7fb0 storytold/wordcraft#3661: bounded dictionary reads — hyphenation inflate limit, capped Hunspell and Turkish reads
Audit finding 4, and the rest of storytold/wordcraft#3661.

- dict.rs: `Dictionary::from_bytes` inflates with `decompress_to_vec_with_limit` (64 MiB; the
  bundled en-US one is ~1 MB) and caps the words it keeps (bytes and count): front coding lets a
  2-byte line spell out a 255-byte word, so the inflated size alone doesn't bound storage.
- hunspell.rs: one `read_capped` reader — regular files only, size checked on the open handle and
  the read itself bounded by `take(MAX_FILE + 1)`, so no TOCTOU between metadata and read and no
  endless read from a FIFO or device.
- tr: the Turkish loader reads through `read_capped` and decodes with `hunspell::decode` (it
  read both files whole, with no cap at all).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 00:17:25 +00:00
Martin BallandClaude Opus 5.5 8135718dac storytold/wordcraft#3613: affix rules — checker follows merged flag masks, forms bounded, suffix index hashed
Part of storytold/wordcraft#3613 (audit findings 2 and 6).

- Checker: `suffixed` and `form_has_flag` OR the flags of a group's matching rules into one
  mask and recurse once, not once per rule. Same answer (the walk is a disjunction over flags);
  16k self-continuing empty-append rules: 6 s -> <1 ms per check (debug).
- Finding 2: `forms` expands each (form, continuation) pair once, walks continuation rules through
  a per-flag rule list, and stops at MAX_FORMS (4096) forms / MAX_FORMS_WORK rule visits, so a
  dictionary of self-continuing rules can't allocate R^2 strings per stem.
- Finding 6: the suffix index finds a rule's (append, strip) group by hash, not by scanning the
  append's groups: building with many strips per append is linear.

Not done here (rest of storytold/wordcraft#3613): a work budget for distinct-strip chains in the checker, rule caps
in parse_aff, proofing off the UI thread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 00:17:17 +00:00
Dylan M. Taylor e3bcd5657f update(install): retry a busy disk-image attach for about a minute (#349) 2026-10-25 19:56:47 -04:00
Dylan M. Taylor 12369a86b6 Merge pull request storytold/wordcraft#4356 from storytold/chore/deps-safe-refresh
chore(deps): safe dependency refresh
2026-10-25 19:27:38 -04:00
Dylan M. Taylor c5a345e472 Merge pull request #347 from storytold/chore/deps-safe-refresh
chore(deps): safe dependency refresh (serde_json, fixed_decimal, roxmltree, miniz_oxide)
2026-10-25 19:03:41 -04:00
skylar 5f7ccde9fb fix(proof): keep inflate error output out of messages 2026-10-25 16:57:21 -06:00
skylar 8dd9441fa9 chore(deps): bump miniz_oxide to 0.9 2026-10-25 16:57:11 -06:00
skylar 9d24443754 fix(deps): keep miniz_oxide at 0.8 2026-10-25 16:53:12 -06:00
skylar 8e42b9ff4e fix(print): keep miniz_oxide at 0.8 2026-10-25 16:52:57 -06:00
skylar cb0f20361d chore(deps): refresh safe dependency bumps
serde_json 1.0.154, proptest 1.12, base64 0.23, arboard 3.7, open 5.4.5,
winresource 0.1.34, pollster 1.0, miniz_oxide 0.9, sha2 0.11.1, libc 0.2.191.
2026-10-25 16:36:45 -06:00
skylar ce020c5811 chore(deps): bump roxmltree, miniz_oxide, serde_json, fixed_decimal
craft-print: roxmltree 0.20 -> 0.21 (locked 0.21.2, fixes a stack
overflow on deeply nested XML) and miniz_oxide 0.8 -> 0.9.
Lockfile only: serde_json 1.0.154, fixed_decimal 0.7.3.
2026-10-25 16:35:43 -06:00
Dylan M. Taylor 495e5971bc Merge pull request #344 from dylanmtaylor/fix/update-prompt-glyphs
fix(update): draw the prompt's close mark and version pill without glyphs a font may lack
2026-10-25 16:36:37 -04:00
Dylan M. Taylor 47edfe3255 fix(update): use egui::Role for the close button's accessibility info 2026-10-25 16:31:38 -04:00
Dylan M. Taylor 4f6fe25d77 fix(update): draw the prompt's close mark and version pill without glyphs a font may lack
The close button was a ✕ (U+2715) text glyph and the version pill used →, neither of which the bundled faces are guaranteed to have; apps that panic on a missing glyph took the whole prompt down. The close mark is two painted strokes, the pill reads 'v0.1.0 to 0.2.0', and a test asserts the prompt paints only ASCII text.

Fixes #343
2026-10-25 16:31:12 -04:00
aiongg 0ca797fbcd Merge branch 'lang-3-fonts' into lang-4-window 2026-10-25 19:44:12 +00:00
aiongg e51a392278 Merge branch 'lang-2-ui' into lang-3-fonts 2026-10-25 19:44:10 +00:00
aiongg 91eb5f19cd ui workflow: path filters list craft-ui and craft-i18n only 2026-10-25 19:44:08 +00:00
aiongg 47b9e5bf05 Merge branch 'lang-3-fonts' into lang-4-window 2026-10-25 19:42:40 +00:00
aiongg 402828151d Merge branch 'lang-2-ui' into lang-3-fonts 2026-10-25 19:42:34 +00:00
aiongg 5120c0ce2d Font store and Font Manager: review fixes
- FontStore::remove_files only touches names the family's manifest entry
  lists and that are safe file names.
- A user source's folder carries a hash of its full ID, so sources whose
  IDs slug alike don't share a folder.
- The download dialog and the sidebar note name the family's own source,
  and the server for a source the user added.
- The face cache takes out only faces the last frame didn't draw.
- The catalog test lists missing translations instead of failing.
- ATTRIBUTION lists the three bundled craft-font-index files.
2026-10-25 19:41:46 +00:00
aiongg 4617a971ae craft-ui, craft-fonts-build: harness direction, caret clamp, served-face conversion, byte-equal font copies 2026-10-25 19:40:45 +00:00
aiongg 4577959305 Merge branch 'lang-1-text' into lang-2-ui 2026-10-25 19:37:14 +00:00
aiongg a04bb9da2a craft-text: locale codes with a script or year subtag; Slovak data through feature unification 2026-10-25 19:37:05 +00:00
aiongg e3b9e239c5 docking: a window-edge drop beside an iconic column highlights only the edge (#336)
A group dragged to the window edge where an iconic column sits showed the
edge line and an outline around the column. The edge line is drawn just
inside the window, so it lay in the column's body, and paint_drop_line
outlines the iconic column a line lies in. That outline is for drops into
the column; a window-edge drop docks beside it, so it now shows the line
alone.
2026-10-25 15:20:32 -04:00
aiongg f8a83e8204 craft-window: window list, cascade placement, Window menu and viewports for apps with one window per document 2026-10-25 19:09:54 +00:00
aiongg 06a4ec194a craft-font-catalog, craft-font-store and craft-font-manager: installed fonts, the font store and the Font Manager panel 2026-10-25 19:09:33 +00:00
aiongg 3c574ddc61 craft-ui: interface fonts, text direction, IME composer, localized widgets and screen testing
craft-fonts-build gains Embed::serve (served faces for web builds) and the coverage module that craft-ui's fonts use.
2026-10-25 19:08:59 +00:00
aiongg acf6f79739 craft-text: hyphenation, line-break segmentation, bidi and line breaking 2026-10-25 19:08:20 +00:00
Dylan M. Taylor db19c373c8 Merge pull request #333 from dylanmtaylor/fix/updater-pending-close
fix(update): keep the close request pending while the app holds unsaved work
2026-10-25 15:03:02 -04:00
aiongg 271594c081 window: the title bar's empty area takes no keyboard focus (#334) 2026-10-25 14:36:07 -04:00
Dylan M. Taylor 05c9f70e6e fix(update): keep the close request pending while the app holds unsaved work
A user can dismiss the prompt while the update installs and then edit. The close request is now kept until the app can close, so a close the user cancels is asked again once the work is saved. The installed message is set when the install finishes.
2026-10-25 14:05:40 -04:00
Dylan M. Taylor 5b0f3c572a Merge pull request #332 from dylanmtaylor/feat/updater-can-start
feat(update): Updater::show_if holds the install back while the app has unsaved work
2026-10-25 14:03:27 -04:00
aiongg c13668493c docking: test send_keys from a native input hook (#331) 2026-10-25 14:02:27 -04:00
Dylan M. Taylor 12f199b2b5 feat(update): Updater::show_if holds the install back while the app has unsaved work
The restart helper is scheduled before the app closes, so a close the user then cancels would leave it waiting for an exit that never comes. Apps pass whether it is safe to start.
2026-10-25 13:55:49 -04:00
Dylan M. Taylor b9ba47f589 Merge pull request #330 from storytold/fix/zip9-windows
fix(update): zip 9's entry name is fallible (Windows build)
2026-10-25 13:18:52 -04:00
Dylan M. Taylor f3540a1327 fix(update): check the executable's name before the app quits for an update
windows_precheck now refuses an executable that isn't the app's own, as the helper does, so the error is shown while the app can still show it. check_helper keeps the same check as the final guard.
2026-10-25 13:18:35 -04:00
Dylan M. Taylor 51da82901c test(update): a standalone executable updates from the portable zip
Detection now treats any executable as a standalone install, so the helper's test no longer expects a refusal once portable.txt is gone.
2026-10-25 13:12:18 -04:00
Dylan M. Taylor 8b7e8a13de fix(update): the update helper only restarts the app's own executable
check_helper accepted any existing file as a standalone install, so a differently named executable passed (the Windows test caught it). The program's name must be one of the app's own.
2026-10-25 13:05:31 -04:00
Dylan M. Taylor dbc86fdbf3 fix(update): zip 9's entry name is fallible
Fixes the Windows build after the move to zip 9: an entry whose name can't be read counts as unsafe.
2026-10-25 12:59:00 -04:00
Dylan M. Taylor a0916808f7 Merge pull request #268 from storytold/migration/egui-037-20261024
Migrate shared UI to egui 0.37 and preserve current docking and printing fixes
2026-10-25 12:57:14 -04:00