Commit Graph
58 Commits
Author SHA1 Message Date
Federico Olivares a98e00c11a Merge origin/main; fix false missing-opening-mark flags on correctly opened questions; stabilise dictionary test 2026-10-23 20:57:36 -03:00
Federico Olivares 319dec238a storytold/wordcraft#3360: Spanish address lookup is a binary search, not a scan per character
Maintainer review on storytold/wordcraft#3562: opening_marks reverse-scans the characters before a
question or exclamation mark, and ends_clause asked in_address for each one,
which scanned every address range, so a paragraph of thousands of addresses
followed by a question took quadratic time (about 99 KB: hundreds of ms).

address_at() finds the range a byte is in with a binary search over the
sorted, disjoint ranges, and also gives ends_clause the range's end without
another scan. Behaviour is unchanged (the URL and punctuation regressions
still pass).

- address_lookup_finds_the_range_a_byte_is_in: boundaries of the lookup
- many_addresses_before_a_question_stay_linear: 99 KB of addresses before a
  question or exclamation (the review's reproducer and two variants), checked
  for the right issue range and for taking under 75 ms (about 1 to 5 ms now,
  about 300 ms with a scan per character)
2026-10-23 19:27:07 -03:00
Federico Olivares 6d27ab0de9 Merge origin/main into spanish-proofing
Upstream merged storytold/wordcraft#3177 (dictionary hints for every Hunspell language) and storytold/wordcraft#3480.
Resolve the conflicts in crates/proof/src/lib.rs by taking storytold/wordcraft#3177's
dictionary_loaded/dictionary_needed structure and adding the four Spanish
varieties to it, and in docs/spelling-dictionaries.md keeping both sides.
2026-10-23 19:06:06 -03:00
36651e4df8 Options › Language names the proofing language at the caret (storytold/wordcraft#3150) (storytold/wordcraft#3177)
The note under "Proofing language" always said English (United States). It now names
the language at the caret, as the status bar does, and the 22 catalogs carry the new sentence.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
2026-10-23 17:24:46 -04:00
Federico Olivares 834044eb29 Merge origin/main into spanish-proofing
Resolve the ROADMAP.md conflict: the status line keeps both changes, and main's
revision-history row comes before the Spanish proofing one.
2026-10-23 13:20:42 -03:00
NetterSilverandClaude Haiku 5.5 1711c147cb Cap and deduplicate the .aff TRY alphabet at parse time (storytold/wordcraft#3981)
Fixes storytold/wordcraft#3654

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-23 11:44:50 -04:00
Federico Olivares f4fe03610d storytold/wordcraft#3360: user dictionary and Ignore All use the composed key spelling uses
Maintainer review on storytold/wordcraft#3562: spelling looks words up composed (NFC), but
add_word, remove_word, ignore_word, custom_word (so loading the dictionary
file) and save_user_dictionary's removed list kept the raw lower-cased
spelling. A word added or ignored decomposed (`zqprue\u{301}ba`) therefore
stayed flagged.

- dictionary_key(): lower case, then composed; every write uses it, and
  in_user_dictionary() looks up composed, so either form adds, ignores,
  removes and finds the same entry; NFD entries in an existing file load
  and save composed
- English's known_core() reuses in_user_dictionary() instead of a copy of it
- issue byte ranges still cover the text as typed
- regressions: proof (add, Ignore All, remove, save/load with an NFD entry,
  both forms rechecked) and engine (review.addToDictionary and
  review.ignoreAll with the issue's NFD text)
2026-10-23 10:31:35 -03:00
Federico Olivares 970bfcf7a8 storytold/wordcraft#3360: Spanish proofing code clean-up (no behaviour change)
- es/mod.rs: one variety() match gives the dictionary slot, log name and file
  names (variety_name() folded in); load_installed() destructures it
- es/grammar.rs: the sentence-start rule for the missing ¿/¡ is one named
  function, ends_clause(), replacing is_boundary() and three closures
  rebuilt per mark; in_address() is shared; repeated_words() builds its
  helpers once instead of per word pair
- es/suggest.rs reuses the Czech ranking's MAX_LETTERS instead of a copy

Same tests pass unchanged; a temporary old-vs-new differential proptest
(20,000+ generated inputs with URLs and punctuation) found identical output.
2026-10-23 09:19:18 -03:00
Federico Olivares ecb4638980 storytold/wordcraft#3360: decomposed accents stay in their word; URLs aren't Spanish questions
Maintainer review on storytold/wordcraft#3562:
- words() keeps combining marks with the word they follow, so a word typed
  decomposed (NFD, `tambie\u{301}n`) is one word with its own byte range;
  is_correct_in_with and suggest_in look it up composed (NFC), so it is
  checked like `también` in every language (unicode-normalization, MIT/Apache)
- the Spanish missing-¿/¡ rule skips marks in web and e-mail addresses
  (`Ver https://example.com/?`), and punctuation inside an address or a word
  (`example.com`, `https:`, `10:30`) no longer starts the sentence the
  opening mark is offered for; a mark ending an address still ends a sentence
- Spanish repeated-word check matches composed and decomposed spellings
- regressions: check_text with the Spanish fixture and NFC/NFD equivalents
  (acceptance and ranges), words() and composed(), URLs ending in /? beside
  ordinary questions

CodeRabbit review: Session::region_spanish takes the ISO 639-2 `spa` as
Spanish, so a `spa-MX` region writes es-MX documents.
2026-10-23 08:43:44 -03:00
Federico Olivares e0abb96729 storytold/wordcraft#3360: Spanish honours the proofing options; es-AR finds es_AR
CodeRabbit review on storytold/wordcraft#3562:
- with Ignore words in UPPERCASE off, short words in capitals are checked
  against the Spanish dictionary instead of accepted as acronyms; likewise
  words with digits with Ignore words that contain numbers off. Direct
  es::is_correct/is_correct_with keep the defaults.
- Latin American Spanish (which es-AR resolves to) also looks for es_AR and
  es-AR, after the neutral es_US and es_MX, so Argentine text is checked when
  only that dictionary is installed
- docs: the exact candidate order per variety in localization-parity.md, and
  es_AR in the spelling-dictionaries table
- the Spain variant's doc lists Uruguay among the regions it doesn't cover
2026-10-22 17:48:42 -03:00
Federico Olivares d80c136e55 Merge origin/main into spanish-proofing
Resolve conflicts with German hyphenation (ProofLang::De), Russian and Czech
in installed_dictionaries(), and storytold/wordcraft#2164's U+FE0F flag fix: affix.rs takes
main's fix and drops this branch's duplicate. The Danish, Dutch, Romanian and
Slovenian catalogs gain the Spanish (Latin America) and (Uruguay) names; the
language menu tests get room for the four Spanish entries.
2026-10-22 02:46:30 -03:00
Federico Olivares 3cd7f35193 storytold/wordcraft#3360, storytold/wordcraft#3553: Spanish proofing (es-ES, es-MX, es-419, es-UY)
Spanish text was never checked: ProofLang had no Spanish variant, so every
es-* run fell through to Other (no marks). And the RLA-ES dictionaries
LibreOffice ships were refused by the affix engine for their `☎️` flag
(☎ + variation selector U+FE0F), which Hunspell reads as ☎.

- ProofLang::Es (Spain, plain es, spa), EsMx (es-MX), Es419 (es-419 and
  the other countries of the Americas) and EsUy (es-UY), chosen by region
  like the English dialects
- each variety reads its own installed Hunspell dictionary with fallbacks
  (es_ES; es_MX; es_419/es_US/es_MX; es_UY/es_AR); nothing bundled
- variation selectors in affix flag fields belong to the flag before them
- Uruguayan vos forms accepted when the dictionary has the infinitive:
  imperatives (vení, andá), with pronouns (decime, decímelo), present
  (tenés); a vos form typed without its accent gets it back first
- suggestions use the dictionary-driven Czech ranking (prueva → prueba,
  corector → corrector, tambien → también)
- grammar: repeated word, spacing, missing opening ¿/¡, space after an
  opening mark, with Spanish messages
- Review › Language, status bar, Language dialog and File › Options ›
  Proofing list the four varieties; the two new names translated in every
  catalog; a Spanish interface writes new documents in the region's Spanish
- the dictionaries-folder engine test holds the Italian and Spanish
  fixtures, so parallel proofing tests no longer lose their dictionaries
- tests use hand-written synthetic dictionaries; engine end-to-end tests
2026-10-22 01:53:37 -03:00
aiongg 9a51627cc2 Merge 178 open PRs (backlog round 1) (storytold/wordcraft#3555)
Integrates the backlog round's ready set from aiongg:next.
2026-10-22 00:24:38 -04:00
aiongg 0712924629 Merge PR storytold/wordcraft#1761: Hyphenation: German text follows the German patterns
crates/proof/src/lang.rs: main's English-dialect helpers kept; code() doc lists `de`.
crates/proof/src/lib.rs: hostile-input test loops over main's languages plus De.
crates/ui-egui/src/options.rs: language_name maps EnGb/EnCa and De to no dictionary name.
ROADMAP.md: main's status lines; both progress-log and revision rows kept.
docs/gaps.md: main's status line; gap 8 row is main's with German hyphenation added; revision rows unioned.
docs/layout-parity.md: main's status line, Tabs and Drop caps rows; the PR's Hyphenation row; revision rows unioned.
docs/localization-parity.md: main's status line; revision rows unioned.
2026-10-22 01:20:31 +00:00
aiongg 2c1ac35c64 Merge PR storytold/wordcraft#3252: Russian and Czech dictionaries are reported, not just checked (storytold/wordcraft#3226) 2026-10-22 01:19:47 +00:00
aiongg 27d89300a6 Merge PR storytold/wordcraft#2164: proof: emoji flags followed by U+FE0F load (RLA-ES Spanish) 2026-10-22 01:19:47 +00:00
0034538d4d storytold/wordcraft#1281: English dialects in proofing — en-GB, en-CA, en-AU… flag each other's spellings (storytold/wordcraft#1045) (storytold/wordcraft#2632)
* storytold/wordcraft#1281: English dialects in proofing — en-GB, en-CA, en-AU… flag each other's spellings (storytold/wordcraft#1045)

English was one proofing language: en-GB, en-CA and en-AU all proofed like
US English, and US documents accepted British spellings. Now ProofLang has
three Englishes, picked by the tag's region:

- En (en-US, other regions, no language): British spellings flagged
  (colour → color).
- EnGb (en-GB, en-AU, en-NZ, en-IE, en-ZA, en-IN…): American spellings
  flagged (color → colour, center → centre, analyze → analyse); -ize and
  -ise both right (Oxford spelling).
- EnCa (en-CA): British -our/-re/-ence, American -ize, tire, program.

Data: assets/spelling/en-variants.txt, our own ~280 American/British
pairs with a policy per English (MIT OR Apache-2.0, ATTRIBUTION.md row).
Inflections by rule (crates/proof/src/en_variants.rs). A dialect's form
is right when the shared list knows it or its other spelling, so
"realisation" is right in en-GB. No system dictionary needed.

Suggestions put the dialect's own form first. Review › Language lists
English (Australia) and (Canada); the Language dialog adds India,
Ireland, New Zealand and South Africa (names in every catalog, not
native-reviewed). An English interface on an en-GB/en-CA/en-AU… region
writes new documents in that English. Options › Language shows the
document's language instead of a fixed "English (United States)".

Tests: per-dialect accept/flag/suggest, tag mapping, no-panic proptests,
engine end to end through review.language, DOCX w:lang round trip,
region-based editing language, language menu.

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

* storytold/wordcraft#1281: review — pin the storytold/wordcraft#40 words (judgement, defence, licence, programme, catalogue) in en-GB and en-US

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

* storytold/wordcraft#1281: review — harness picks every proofing language in Review › Language and checks it is ticked

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

* storytold/wordcraft#1281: review — pin more British spellings in en-GB (goitre, sceptre, enrol, fulfil, instalment, skilful, wilful, encyclopaedia…)

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 19:52:57 -04:00
dmim2502-commitsandClaude Opus 5.5 08d69d5fd0 proof: drop U+FE0F from FORBIDDENWORD and NOSUGGEST flags too
Review follow-up: the directives that name one flag read the same emoji
form, so they now skip the variation selector like PFX/SFX and stem flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 16:21:24 -05:00
dmim2502-commitsandClaude Opus 5.5 11a455cf24 proof: emoji flags followed by U+FE0F load (RLA-ES Spanish)
RLA-ES, the Spanish Hunspell dictionaries in LibreOffice and most Linux
distributions, writes one flag as U+260E followed by U+FE0F, in the rules
(PFX ☎️ Y 3) and on stems (banco/S☎️). parse_aff rejected it as "not one
character", so the whole dictionary failed to load. The variation selector
is now dropped wherever flags are read, with a regression test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:27:06 -05:00
MichaelandClaude Sonnet 5 eab36a9ec8 Update the dictionary and its in-memory origin under one lock
CodeRabbit (storytold/wordcraft#3252): reload_dictionary() wrote DICT and DICT_IN_MEMORY
under two separate locks. If install_dictionary() ran between them,
the final state could hold an in-memory dictionary with the flag left
false, so installed_path() would report a same-named disk file
instead of None.

Combined both into one DictState struct behind a single RwLock in
both ru and cs, so install_dictionary() and reload_dictionary() each
update the dictionary and its origin in one critical section.

Didn't add a dedicated concurrency test: genuinely proving a race's
absence needs real multi-threaded stress testing, and the previous
commit on this PR already found that tests directly exercising
install_dictionary()/reload_dictionary() concurrency in this crate's
shared global state cause real, reproducible flakiness in unrelated
tests. The fix is correct by construction (one lock acquisition around
both field writes) and all 222 existing proof tests, run 15 times in
a row, stay green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-20 17:41:20 +08:00
MichaelandClaude Sonnet 5 36d64a2859 installed_path() ignores a matching file while an in-memory dictionary is in use
CodeRabbit (storytold/wordcraft#3252): installed_path() searched the filesystem
unconditionally, so a dictionary given directly with install_dictionary
(a user-picked one, or a test fixture) was reported as the file of a
same-named dictionary that happened to also be installed, even though
spelling was actually using the in-memory one, not that file.

Added a DICT_IN_MEMORY flag to both modules, set in install_dictionary
and cleared in reload_dictionary; installed_path() now returns None
immediately when it's set, before even searching the filesystem.

Tried adding a dedicated test that calls install_dictionary() and
asserts installed_path() is None. It made dictionaries_folder_is_
searched_first flaky (~15-35% of runs across repeated local testing):
ru's and cs's own test modules already call install_dictionary()
directly in several tests of their own, sharing the same process-wide
static, and dictionaries_folder_is_searched_first's exact-path
assertion for Russian/Czech could observe whichever one last touched
it. Reverted the dedicated test; the production fix alone never
touches that shared state (it's a pure read-only check) and caused no
flakiness by itself across 25 repeated runs. Instead, narrowed
dictionaries_folder_is_searched_first's Russian/Czech assertion to the
language only, not the exact path, since unlike the other four
languages, installed_path() for these two is now sensitive to
concurrently-running tests' legitimate in-memory installs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-20 17:31:06 +08:00
MichaelandClaude Sonnet 5 a26cf9350f Reload Russian and Czech dictionaries when the folder changes too
CodeRabbit (storytold/wordcraft#3252): set_dictionaries_folder() reloaded every other
proofed language but not Russian or Czech, so after a folder change
installed_dictionaries() would report a path from the new folder
while spelling kept using the stale cached dictionary.

Also added Czech's test fixture to the shared dictionaries-folder
test's temp folder (the same content czech_proofing_through_the_
review_commands installs directly): since that folder-change test
now reloads Czech too, it was clearing a concurrently running Czech
test's installed dictionary with nothing to replace it, intermittently
emptying it mid-test. Same fixture content either way keeps the race
harmless, exactly as it already does for Polish/Portuguese/Indonesian.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-20 17:08:27 +08:00
MichaelandClaude Sonnet 5 ac06c87831 Russian and Czech dictionaries are reported, not just checked (storytold/wordcraft#3226)
installed_dictionaries() never included Russian or Czech at all,
because neither crates/proof/src/ru nor crates/proof/src/cs had an
installed_path() function for it to call (every other proofed
Hunspell language — Polish, Portuguese, Italian, Croatian, Turkish,
Indonesian — already did). File > Options > Proofing's dictionaries
list iterates installed_dictionaries() directly, so Russian and Czech
never showed up there at all, installed or not, matching the report.

Added installed_path() to both modules, mirroring the existing
pattern (crate::hunspell::find(NAMES).map(|(_, dic)| dic)), and added
both to installed_dictionaries().

The actual spell-checking dispatch (is_correct_in_with) already
routed Russian and Czech correctly — confirmed by installing the real
system ru_RU Hunspell dictionary and checking it directly
(ru::is_correct("молоко") true, ru::is_correct("малоко") false), so
that part of the pipeline isn't what's broken. The reporter's other
symptom (Russian text in the document not being checked at all) did
not reproduce with the real dictionary installed; I could not pin
that part down further without more specific repro details from
their setup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-20 16:50:41 +08:00
MarekSpodBiedryandBrandon Thomas 401ea058d1 Proofing: say when a dictionary is installed but unreadable, with file and reason (storytold/wordcraft#2830) (storytold/wordcraft#2900)
* Proofing: say when a dictionary is installed but unreadable, with file and reason (storytold/wordcraft#2830)

* proof test: use SHIFT_JIS as the unsupported encoding (ISO 8859-1 is decoded since storytold/wordcraft#2904)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:51:13 -04:00
MarekSpodBiedryandBrandon Thomas 0b12f06ea1 Decode ISO 8859-1 Hunspell dictionaries so the original pt_BR works (storytold/wordcraft#2824) (storytold/wordcraft#2904)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:50:59 -04:00
Marco ArnulfoandBrandon Thomas 2d5fb1279a Proofing: a hyphenated word added to the dictionary is no longer flagged (fixes storytold/wordcraft#2082) (storytold/wordcraft#2085)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:33:30 -04:00
de00c8a16a Lowercase list items no longer get a grammar underline (storytold/wordcraft#1741) (storytold/wordcraft#1783)
* storytold/wordcraft#1741: WIP

* storytold/wordcraft#1741: list items no longer get the sentence-capital grammar squiggle

The English "Capitalize the first word of a sentence" rule skips the first
word of a list paragraph (own or style numbering); later sentences are still
checked. check_text_with and paragraph_issues take a list_item flag.

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

* storytold/wordcraft#1741: review fixes

Thread the capital-first flag into the Polish, Croatian, Italian, Turkish and
Portuguese grammar rules; test the drawn squiggles through layout() for own,
style and num 0 numbering; tidy the proof test.

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

* storytold/wordcraft#1741: main's Vietnamese proofing test passes the new list-item argument

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

* storytold/wordcraft#1741: the third call in main's Vietnamese proofing test

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

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:57:15 -04:00
5fc80099e7 Proofing: -ically adverbs such as "specifically" are no longer flagged (storytold/wordcraft#1694) (storytold/wordcraft#1854)
* storytold/wordcraft#1694: -ically adverbs are no longer flagged as misspelled

English spelling accepts "-ally" adverbs built on an "-ic" adjective in the
word list (specific → specifically, scientific → scientifically), as Word does.

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

* storytold/wordcraft#1694: only -ic adjectives take -ally

Review (FredApps): the "-ally" → stem rule applied to every known word,
so "desktopally" was accepted. The rule is now "-ically" → "-ic"
(specifically → specific); other words don't take "-ally".

Test: non_ic_words_do_not_take_ally (desktop, house, final, green).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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 09:56:04 -04:00
NaxvogandClaude Opus 5.5 9f34c5ad61 Hyphenation: exact German pattern count, a parser test for skipped commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:48:38 +02:00
NaxvogandClaude Opus 5.5 7968a4452b Hyphenation: German patterns in NOTICE; pattern files skip \message
Review follow-ups: the German patterns' MIT notice goes into NOTICE with
the other pattern sets; the pattern reader skips other commands'
arguments, so hyph-de-1996.tex's \message{} line no longer adds junk
patterns and exception words; a load test for the German set; the
status bar names a bare "de" tag; the docs list the languages with
their own hyphenation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:48:38 +02:00
NaxvogandClaude Opus 5.5 897107e7f6 Hyphenation: German text follows the German patterns
Runs in German (de, de-DE, de-AT, de-CH) hyphenated with the English
patterns, giving breaks such as "unterlieg-ende" and "rechtli-cher".
They now use hyph-utf8's German patterns for the reformed spelling
(MIT, bundled unmodified as assets/hyphenation/de.pat). German gets a
ProofLang of its own that, like Bulgarian, hyphenates only: no spelling
or grammar marks yet (storytold/wordcraft#1564). Portuguese, Italian and German share one
pattern lookup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:48:37 +02:00
4da49a6fe8 storytold/wordcraft#498: Indonesian (id-ID) spelling proofing (storytold/wordcraft#2576)
Indonesian spelling reads the installed Hunspell id_ID dictionary at runtime
(Debian/Ubuntu hunspell-id, LibreOffice dict-id, the Dictionaries folder or
WORDCRAFT_DICTIONARIES), like Czech, Polish and Portuguese: nothing is bundled,
and without a dictionary Indonesian text gets no spelling marks.

- ProofLang::Id for id, id-ID, ind and the legacy in tag
- suggestions reuse the dictionary-driven Czech ranking (MAP/REP/TRY/KEY of
  the loaded dictionary), nothing Indonesian hard-coded
- hyphenated reduplication accepted whole or by parts (kupu-kupu, anak-anak);
  hyphenated clitics after capitals/abbreviations (KTP-nya, se-Indonesia)
- generic grammar only: repeated word (offers anak-anak first), space before
  punctuation, double space, with Indonesian messages
- Review > Language, status bar, Set Proofing Language, File > Options >
  Proofing list Indonesian; name translated in every catalog
- tests use a hand-written synthetic dictionary; engine end-to-end test

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:42:52 -04:00
Brandon ThomasandClaude Opus 5.5 3cd526ca7e CI: rustfmt the proofing language list
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:53:07 -04:00
c14e974285 proof: Czech (cs-CZ) spelling from the installed Hunspell dictionary (storytold/wordcraft#1048)
Same pattern as Portuguese (storytold/wordcraft#797) and Polish (storytold/wordcraft#346): the cs_CZ
dictionary is read from the system at runtime (e.g. Debian/Ubuntu
hunspell-cs), nothing is bundled. Suggestions use the dictionary's own
MAP/REP/TRY/KEY; grammar stays generic (repeated word, spacing), with
digit ratios like "3 : 1" exempt. The Czech interface now writes new
documents (and opened .txt/.md) in cs-CZ.

Engine fixes the Czech dictionary needs, also used by pl/pt:
- PFX/SFX header lines may carry a trailing comment (cs_CZ has
  "SFX y Y 80 #…"); rule lines are left literal;
- NOSUGGEST stems are accepted when typed but never offered;
- KEY is parsed for neighbour-key suggestions.

Tests use a hand-written synthetic Czech dictionary only; an
end-to-end engine test mirrors the Portuguese one.

Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 00:52:43 -04:00
e4edea9f85 Proofing: Vietnamese text under the default English language gets no marks (storytold/wordcraft#1553)
A document typed in Vietnamese under the default editing language (en-US) was
covered in red underlines, every Vietnamese word checked as English. A
paragraph that reads as Vietnamese (words with Vietnamese-only letters: the
breve and horn letters, đ, ĩ ũ, the tone-marked vowels of Latin Extended
Additional, or stacked combining marks) is now treated like any language
without proofing tools: no spelling or grammar marks, as Word's automatic
language detection does. A Vietnamese name in English text (Nguyễn) is no
longer flagged either; French and Portuguese, which share â ê ô, are unaffected.

Automatic hyphenation also leaves Vietnamese paragraphs and words whole
(single syllables never take a hyphen); the English patterns happen not to
split these words today, and the guard keeps it so.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 23:12:46 -04:00
3802149875 Italian proofing: po', qual è, È and un/un' grammar rules; more elisions in spelling (storytold/wordcraft#1585)
* Italian proofing: po', qual è, È and un/un' grammar rules; more elisions in spelling

- grammar (works without a dictionary): un pò / un po -> un po'; qual'è -> qual è;
  E' -> È; un'altro -> un altro and un altra -> un'altra (also nessun, ciascun,
  qualcun, alcun), gender from the ending with masculine -ista/-ma/-cida... left alone;
  more emphatic doubles allowed (pian piano, poco poco, così così...)
- spelling: more elisions (dov'è, cos'è, anch'io, senz'altro, nessun'altra, n'ha...),
  truncations po'/mo'/be' accepted, pò and qual'è left to grammar (no double marks)
- tests: rule tests, grammar proptests, Italian suggestion proptest, It in mixed-span proptest

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

* Contributors: credit lupant by their GitHub profile name

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 19:50:35 -04:00
488c246c75 Proofing: status bar flags a language whose dictionary isn't installed (storytold/wordcraft#1167) (storytold/wordcraft#1441)
* Proofing: status bar flags a language whose dictionary isn't installed (storytold/wordcraft#1167)

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

* vi: missing-dictionary status strings

Co-Authored-By: Claude Opus 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 19:32:30 -04:00
neronek22andneronek22 b740615573 Improve Polish spelling suggestions for final consonant devoicing (storytold/wordcraft#1298)
Co-authored-by: neronek22 <aantoszek@gmail.com>
2026-10-14 18:17:07 -04:00
85f0a76e68 Russian proofing: spelling, suggestions, grammar and word division (storytold/wordcraft#894)
* Russian proofing: spelling, suggestions, grammar and word division

Runs tagged ru get Russian tools instead of none:
- spelling against an installed ru_RU Hunspell dictionary (not bundled;
  LibreOffice dict-ru folders searched; KOI8-R and Windows-1251 decoded),
  with ё optional, hyphenated words and solid compounds understood, не with
  adjectives, participles and verbal nouns, prefixed verbs, abbreviations
  with endings and camel case left alone, plus our supplement of modern
  words with their declensions (assets/spelling/ru-extra.txt);
- suggestions tuned for Russian: е/ё, letter groups, common confusions,
  missing spaces, typos and a stem scan;
- grammar rules with Russian messages (repeated words, spaces, sentence
  capitals with Russian abbreviations, likely missing commas before
  clause openers, doubled punctuation);
- Russian word division from the spelling rules, written as code.

* Options: Russian in the dictionaries list (ProofLang::Ru arm)

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

---------

Co-authored-by: Магда М.Г. <mail@mgmagda.ru>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:21:39 -04:00
4fa0fd1847 Proofing: a Dictionaries folder setting, and the dictionary each language found (storytold/wordcraft#1063) (storytold/wordcraft#1084)
File › Options › Proofing gets a Dictionaries folder: a folder of Hunspell
.aff/.dic files searched before the system locations, saved with the settings
and applied at start-up. An app started from Finder or the Dock doesn't see
WORDCRAFT_DICTIONARIES, so this is how it finds a dictionary kept in a folder
of the user's own. Changing it reloads the Polish and Portuguese dictionaries
and checks the text again. The pane lists the dictionary file each language
found, or that none was found, so missing spelling marks are explained.

Agents set it with file.options {"dictionariesFolder": path | "auto"}; a
missing folder or a relative path is refused and changes nothing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:25:00 -04:00
74b0eab93a proof: Turkish spelling, suggestions, grammar and hyphenation (storytold/wordcraft#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
Eduardo GarciaandClaude Sonnet 5.5 49a3a02950 fix(proof): British spellings such as honour and prioritise are not flagged (storytold/wordcraft#40) (storytold/wordcraft#1536)
The bundled Moby list already knows many British forms (judgement,
defence, programme) but still flagged others: honour and its inflections,
colourful, colourless, rigour, glamour, prioritise, privatise,
synthesise, paediatric and a few more.

Adds those base forms to assets/spelling/en-extra.txt (inflections come
from the existing rules) and a regression test. A separate en-GB
dictionary chosen from w:lang is still open on the issue.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 14:29:23 -04:00
Saad AbdullahandSaad Abdullah 54f7b5b900 fix(proof): classify each whitespace token once when checking spelling (storytold/wordcraft#1335) (storytold/wordcraft#1490)
Co-authored-by: Saad Abdullah <saadkhantareen@users.noreply.github.com>
2026-10-14 13:26:10 -04:00
mbaksaandBrandon Thomas 95a9aaac66 Add Croatian spelling and grammar support (storytold/wordcraft#1114)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 02:00:58 -04:00
RegalDragoon200andBrandon Thomas 77e550ea0c Italian proofing: it_IT and it_CH spelling, suggestions, grammar and hyphenation (storytold/wordcraft#951) (storytold/wordcraft#1090)
* Italian proofing: it-IT spelling, suggestions and grammar (storytold/wordcraft#951)

* Italian proofing: Review Language menu and editing language

* Italian proofing: interface test for editing language

* Italian proofing: find the Swiss it_CH dictionary too

* Italian proofing: TeX hyphenation patterns (MIT option)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 01:52:47 -04:00
8caa51af04 Hyphenation: Bulgarian text follows the Bulgarian syllable rules (storytold/wordcraft#826)
Runs in Bulgarian (`w:lang` bg, bg-BG) are hyphenated by the Bulgarian syllable rules
(crates/proof/src/bg.rs, clean-room from the spelling rules; no pattern file or word list):
vowel-vowel splits, one consonant goes with the next syllable, a consonant cluster splits after
its first consonant except that an obstruent before р/л starts the next syllable (до-бре,
та-бли-ца), й stays with its vowel, дж/дз and ьо are never split.

- ProofLang::Bg (bg, bul): hyphenation only. It isn't "proofed", so like Other there are no
  spelling or grammar marks until there is a Bulgarian dictionary.
- Latin-script words in a Bulgarian run are hyphenated as English, as in any other language.
- The layout already takes each word's language from its run (ProofRun), so nothing changes there.

Tests: the rules (срич-ко-пре-на-ся-не, та-бли-ца, шо-фьор, хо-джа, строй-ка), tag resolution,
hyphenate_word_in with the paragraph limits (Бъл-га-рия), and a laid-out bg-BG paragraph whose
hyphenated lines all break at Bulgarian points.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 07:51:05 -04:00
3090bcaef0 Portuguese proofing: pt-BR spelling, suggestions, grammar and hyphenation (storytold/wordcraft#100) (storytold/wordcraft#797)
* M15: Polish interface, and Polish spelling, grammar and hyphenation

Interface
- Polish is the tenth interface language ("Polski", pl.tsv, 1,045 entries);
  pl, pl-PL and pl_PL.UTF-8 system locales select it automatically.
- Right-click menu labels now go through translation in every language.
- Review > Language is a menu (English US/UK, Polski, Do not check spelling
  or grammar); the status bar shows the language at the caret.
- With a Polish interface, new blank documents and opened .txt/.md files
  default to pl-PL.

Proofing
- Each run is checked in its own language (w:lang, resolved through
  styles): squiggles, F7, review.issues, suggestions and hyphenation.
  Text in languages without proofing tools is no longer flagged as English.
- Polish spelling from the SJP.PL Hunspell dictionary (Apache-2.0 option),
  read by a new Rust affix engine (crates/proof/src/affix.rs); 875 KB asset,
  ~4.5M word forms. Regenerate with the pl_dictionary example.
- Polish suggestions: case, missing diacritics, rz/ż, ó/u, ch/h confusions,
  missing spaces (napewno -> na pewno), edit distance.
- Conservative Polish grammar rules: repeated words, spacing, doubled
  punctuation, sentence capitals, missing comma before że/który/ponieważ/
  aby/żeby/jeśli…, we/ze forms.
- Polish hyphenation with the hyph-utf8 hyph-pl.tex patterns (MIT option);
  pattern keys widened to 20 letters, TeX exceptions parsed; the heuristic
  no longer treats ł, ż, ś… as vowels.

review.language validates and normalizes tags, can set noProof alone and
the document default; format.state reports lang and noProof.

Tests for every part, docs and ATTRIBUTION.md/NOTICE updated;
cargo xtask ci passes (fmt, clippy, test, assets, layers, wasm).

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

* Layout: rustfmt the test module order left unformatted by storytold/wordcraft#391

cargo fmt --check failed on main after storytold/wordcraft#391 added mod tests_grid.

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

* Review fixes for storytold/wordcraft#346: read Polish spelling from the installed Hunspell dictionary

- Polish spelling uses the Hunspell pl_PL dictionary installed on the
  system (OS packages, Homebrew, LibreOffice, ~/.local/share/hunspell, or
  WORDCRAFT_DICTIONARIES) instead of a bundled 870 KB SJP.PL file;
  without one, Polish text gets no spelling marks, like Word without the
  language's proofing tools. .aff/.dic are size-capped and decoded from
  UTF-8 or ISO 8859-2 (crates/proof/src/hunspell.rs).
- The dictionary can be replaced at runtime (install_dictionary) or
  re-searched (reload_dictionary); an empty one accepts every word.
- Tests use small synthetic dictionaries written for them, so they don't
  depend on what the machine has installed.
- The bundled pl.dic, its ATTRIBUTION/NOTICE entries and the generator
  example are removed; docs describe the installed dictionary.

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

* storytold/wordcraft#100: affix — accept FLAG UTF-8 and DEFAULT flag modes

Per-character flag modes are what the reader already parses; only the
multi-character encodings (long, num) stay errors. VERO pt_BR declares
FLAG UTF-8.

* storytold/wordcraft#100: affix — widen flag masks to u128 (128 flag capacity)

VERO pt_BR needs 106 distinct flags; the u64 bitmask capped at 64.
Serialization is unaffected: WCAFF1 stores flag characters and
re-derives the masks on load.

* storytold/wordcraft#100: affix — support FORBIDDENWORD

VERO pt_BR marks spellings it must reject with a forbidden flag (ý).
Stems carrying it are listed but never correct and never inflect.

* storytold/wordcraft#100: affix — continuation classes in check

SFX A 0 able/B: the form derived by A carries flag B, licensing a
further suffix of class B (workable -> workables) while the bare stem
cannot take one. form_has_flag walks the chain depth-bounded
(MAX_CHAIN = 3); with no continuations declared it degenerates to the
stem lookup, so existing dictionaries are unaffected. VERO pt_BR uses
26 continuation rules.

* storytold/wordcraft#100: affix — continuation classes in forms

Enumerate chained suffix forms (workable -> workables) under the same
MAX_CHAIN bound the checker walks, so every form the checker accepts
is listed and vice versa.

* storytold/wordcraft#100: affix — round-trip continuations and FORBIDDENWORD through WCAFF1

The compact format keeps its WCAFF1 magic: continuation classes ride
on the rule's appended field (as in the .aff source) and the forbidden
flag becomes a FORBIDDENWORD line. Old files read as before.

* storytold/wordcraft#100: affix — strip a UTF-8 BOM from .aff/.dic input

VERO's files open with a BOM; glued to the count line it parsed as a
stem (one garbage entry per load) and to SET it hid the directive.

* storytold/wordcraft#782: affix — the module doc still rejected what the code now reads

Continuation classes and FORBIDDENWORD are supported; the header said
Rejected for both.

* storytold/wordcraft#100: pt — Portuguese spelling, following the Polish module

ProofLang::Pt resolves pt/pt-BR/pt-PT/por tags; the pt module reads the
installed Hunspell pt_BR dictionary (VERO) through the affix engine —
FLAG UTF-8 flags, continuation classes and FORBIDDENWORD spellings all
exercised by the synthetic test dictionary. Hyphenated compounds are
checked part by part (guarda-roupa, ex-marido); without a dictionary
installed, Portuguese text gets no marks. Spelling message in
Portuguese; suggestions and grammar land next.

* storytold/wordcraft#100: pt — Portuguese suggestions

Rounds of rising cost, driven by the dictionary's own tables: missing
accents from MAP (coraçao -> coração, saude -> saúde), the REP
confusions (seje -> seja, excessão -> exceção), a missing space after a
short function word (nãoé -> não é, dagua -> da água), one typo over
TRY, and an accent-folding stem scan — the Polish rounds, retuned for
Portuguese.

* storytold/wordcraft#100: pt — Portuguese grammar flags

Conservative rules with Portuguese messages: repeated words (except
the emphatic doubles Portuguese allows), space before punctuation and
double spaces, sentence capitals knowing the Portuguese abbreviations
and dialogue dashes, and doubled punctuation. No comma rule: que opens
a clause only sometimes, so a wrong flag would cost more than a missed
one.

* storytold/wordcraft#100: pt — Portuguese hyphenation

hyph-utf8's hyph-pt.tex bundled unmodified (BSD 3-clause, ATTRIBUTION
row with source and hash): 426 Liang patterns and 2 exception words;
falls back to the generic heuristic outside their reach.

* storytold/wordcraft#100: pt — Review › Language offers Português (Brasil), and a pt-br interface writes pt-BR

The menu's fourth entry sets the run language; the status bar names
Portuguese; editing_lang is now allowed for pt-br (wordcraft-proof
proofs it), so new documents under a Portuguese interface are
Portuguese by default.

* storytold/wordcraft#100: pt — engine test: Portuguese proofing through the review commands

review.language pt-BR normalizes the tag, review.issues reports
Portuguese spelling and grammar with the lang field, F7 and Change
apply a suggestion, Add to Dictionary works for Portuguese.

* storytold/wordcraft#100: pt — Portuguese in the mixed-language proofing tests and docs

check_text proofs a Portuguese span with the Portuguese message; the
never-panic loops and the module docs cover the third language.

* storytold/wordcraft#100: docs — Portuguese proofing in the gap, parity and roadmap docs

Gap 8 narrowed (English, Polish and Portuguese), localization parity
gains a Portuguese row and a revision entry, target-app-parity's
Proofing row moves 40% -> 42%, and the ROADMAP progress log records
what landed.

* storytold/wordcraft#100: hunspell — LibreOffice extension folders follow the language

The search paths hardcoded LibreOffice's dict-pl; a pt_BR user whose
only dictionary is LibreOffice's dict-pt-BR extension — on Windows the
only realistic on-disk source — got silently no spelling. Both the
primary-language folder and the dashed full name are now tried, so
Polish keeps dict-pl and Portuguese finds dict-pt-BR.

* storytold/wordcraft#100: pt — review fixes

Suggestion ties break toward the change nearest the word's start
(saúde before saudê, proven against a fixture that has both); the
Language menu's label is the English source "Portuguese (Brazil)" with
translations in all twelve catalogs (as Polish is); the attribution
counts pt.pat's 2 exception words; the pt fixture drops a duplicate
stem; docs/roadmap.md catches up with the landing; the throwaway spike
is gone.

* Review fixes for storytold/wordcraft#782: two-suffix chain limit, module doc

- MAX_CHAIN = 2 counts suffixes in a row, matching Hunspell's two-suffix limit
  (the old bound of 3 allowed up to four); test pins that a third is rejected.
- Module doc: say probes allocate small scratch strings instead of claiming none.

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

* Review fixes for storytold/wordcraft#797: NOTICE for pt.pat, no indexing in the LibreOffice folder helper

- NOTICE reproduces hyph-pt.tex's BSD 3-Clause copyright notice, conditions and
  disclaimer, which the licence requires for binary redistributions.
- hunspell::lo_ext_dirs builds its list without indexing (never-crash rule).
- Catalogs: main already translates "Portuguese (Brazil)" in every language, so the
  duplicate rows the branch added are dropped.

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

---------

Co-authored-by: witkowski01 <konradwitkowski01@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 07:43:24 -04:00
229f9b32d2 Affix engine: FLAG UTF-8, 128 flag bits, continuation classes, FORBIDDENWORD (storytold/wordcraft#100) (storytold/wordcraft#782)
* M15: Polish interface, and Polish spelling, grammar and hyphenation

Interface
- Polish is the tenth interface language ("Polski", pl.tsv, 1,045 entries);
  pl, pl-PL and pl_PL.UTF-8 system locales select it automatically.
- Right-click menu labels now go through translation in every language.
- Review > Language is a menu (English US/UK, Polski, Do not check spelling
  or grammar); the status bar shows the language at the caret.
- With a Polish interface, new blank documents and opened .txt/.md files
  default to pl-PL.

Proofing
- Each run is checked in its own language (w:lang, resolved through
  styles): squiggles, F7, review.issues, suggestions and hyphenation.
  Text in languages without proofing tools is no longer flagged as English.
- Polish spelling from the SJP.PL Hunspell dictionary (Apache-2.0 option),
  read by a new Rust affix engine (crates/proof/src/affix.rs); 875 KB asset,
  ~4.5M word forms. Regenerate with the pl_dictionary example.
- Polish suggestions: case, missing diacritics, rz/ż, ó/u, ch/h confusions,
  missing spaces (napewno -> na pewno), edit distance.
- Conservative Polish grammar rules: repeated words, spacing, doubled
  punctuation, sentence capitals, missing comma before że/który/ponieważ/
  aby/żeby/jeśli…, we/ze forms.
- Polish hyphenation with the hyph-utf8 hyph-pl.tex patterns (MIT option);
  pattern keys widened to 20 letters, TeX exceptions parsed; the heuristic
  no longer treats ł, ż, ś… as vowels.

review.language validates and normalizes tags, can set noProof alone and
the document default; format.state reports lang and noProof.

Tests for every part, docs and ATTRIBUTION.md/NOTICE updated;
cargo xtask ci passes (fmt, clippy, test, assets, layers, wasm).

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

* Layout: rustfmt the test module order left unformatted by storytold/wordcraft#391

cargo fmt --check failed on main after storytold/wordcraft#391 added mod tests_grid.

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

* Review fixes for storytold/wordcraft#346: read Polish spelling from the installed Hunspell dictionary

- Polish spelling uses the Hunspell pl_PL dictionary installed on the
  system (OS packages, Homebrew, LibreOffice, ~/.local/share/hunspell, or
  WORDCRAFT_DICTIONARIES) instead of a bundled 870 KB SJP.PL file;
  without one, Polish text gets no spelling marks, like Word without the
  language's proofing tools. .aff/.dic are size-capped and decoded from
  UTF-8 or ISO 8859-2 (crates/proof/src/hunspell.rs).
- The dictionary can be replaced at runtime (install_dictionary) or
  re-searched (reload_dictionary); an empty one accepts every word.
- Tests use small synthetic dictionaries written for them, so they don't
  depend on what the machine has installed.
- The bundled pl.dic, its ATTRIBUTION/NOTICE entries and the generator
  example are removed; docs describe the installed dictionary.

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

* storytold/wordcraft#100: affix — accept FLAG UTF-8 and DEFAULT flag modes

Per-character flag modes are what the reader already parses; only the
multi-character encodings (long, num) stay errors. VERO pt_BR declares
FLAG UTF-8.

* storytold/wordcraft#100: affix — widen flag masks to u128 (128 flag capacity)

VERO pt_BR needs 106 distinct flags; the u64 bitmask capped at 64.
Serialization is unaffected: WCAFF1 stores flag characters and
re-derives the masks on load.

* storytold/wordcraft#100: affix — support FORBIDDENWORD

VERO pt_BR marks spellings it must reject with a forbidden flag (ý).
Stems carrying it are listed but never correct and never inflect.

* storytold/wordcraft#100: affix — continuation classes in check

SFX A 0 able/B: the form derived by A carries flag B, licensing a
further suffix of class B (workable -> workables) while the bare stem
cannot take one. form_has_flag walks the chain depth-bounded
(MAX_CHAIN = 3); with no continuations declared it degenerates to the
stem lookup, so existing dictionaries are unaffected. VERO pt_BR uses
26 continuation rules.

* storytold/wordcraft#100: affix — continuation classes in forms

Enumerate chained suffix forms (workable -> workables) under the same
MAX_CHAIN bound the checker walks, so every form the checker accepts
is listed and vice versa.

* storytold/wordcraft#100: affix — round-trip continuations and FORBIDDENWORD through WCAFF1

The compact format keeps its WCAFF1 magic: continuation classes ride
on the rule's appended field (as in the .aff source) and the forbidden
flag becomes a FORBIDDENWORD line. Old files read as before.

* storytold/wordcraft#100: affix — strip a UTF-8 BOM from .aff/.dic input

VERO's files open with a BOM; glued to the count line it parsed as a
stem (one garbage entry per load) and to SET it hid the directive.

* storytold/wordcraft#782: affix — the module doc still rejected what the code now reads

Continuation classes and FORBIDDENWORD are supported; the header said
Rejected for both.

* Review fixes for storytold/wordcraft#782: two-suffix chain limit, module doc

- MAX_CHAIN = 2 counts suffixes in a row, matching Hunspell's two-suffix limit
  (the old bound of 3 allowed up to four); test pins that a third is rejected.
- Module doc: say probes allocate small scratch strings instead of claiming none.

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

---------

Co-authored-by: witkowski01 <konradwitkowski01@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 07:38:10 -04:00
a0faa851e3 Persist the custom dictionary across sessions (storytold/wordcraft#40) (storytold/wordcraft#796)
* Persist the custom dictionary across sessions (storytold/wordcraft#40)

* Review fixes for storytold/wordcraft#796: session-only Ignore All, desktop-only persistence, capped atomic I/O

- review.ignoreAll no longer adds to (and saves) the custom dictionary: ignored words go to a
  separate session-only list, as Ignore All does in Word.
- Only the desktop app wires the dictionary file. Headless MCP and `wordcraft-cli run` keep
  words in memory, so an agent never reads or changes the person's dictionary (and nothing
  writes outside an automation write root, storytold/wordcraft#769).
- Loading reads through `take(1 MB)`, so a huge file is never read whole; lines that aren't a
  single word (spaces, control characters, invalid UTF-8, over 64 characters) are skipped.
- review.addToDictionary applies the same word rule (proof::custom_word), so a word added is
  never dropped on the next load.
- Saving merges the words on disk first (another process's additions survive; words just
  removed stay out), creates the folder, and replaces the file atomically (temp + rename).
  The file I/O lives in the proof crate, so the engine doesn't touch std::fs itself.
- Tests for each.

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-12 07:23:51 -04:00
4655ae188c Skip non-English runs in proofing instead of flagging them (storytold/wordcraft#100) (storytold/wordcraft#741)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:55:40 -04:00