Skip to content

feat: Hunspell spell-check engine plugin - #25

Open
hamidfzm wants to merge 1 commit into
fix/dictionary-de-api-versionfrom
feat/hunspell-engine
Open

hamidfzm wants to merge 1 commit into
fix/dictionary-de-api-versionfrom
feat/hunspell-engine

Conversation

@hamidfzm

@hamidfzm hamidfzm commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds the official Hunspell engine plugin (com.glyph.hunspell). It registers a spell-check engine through ctx.spellcheck.registerEngine (plugin API 0.28.0), so every installed dictionary is checked by Hunspell, compiled to WebAssembly, instead of the app's built-in checker. The built-in checker ignores compound flags, so German needs this plugin for Fußball, Kindergarten and every other compound.

Refs hamidfzm/glyph#1052 (closed by hamidfzm/glyph#1071, the app side).

Status: merge after hamidfzm/glyph#1071 is in a release (0.28.0). Merging publishes the plugin, which declares apiVersion 0.28.0 and calls registerEngine, which only that release has; before then the app's plugin contract job, which checks every published entry against the current host, would fail on every app PR, as German did. If the API ships later than 0.28.0, the manifest moves to that release first. This branch is built on #24 (German's API version and the engine docs), so merge #24 first.

Changes

  • plugins/com.glyph.hunspell/: main.js (hand-written) registers the engine, which loads on the first dictionary checked: assets/hunspell.js is imported from a blob URL and handed assets/hunspell.wasm as bytes from ctx.assets.readBinary. A failed load is dropped so the next dictionary retries. The affix file is always read as UTF-8 (the app hands over decoded text), and the .dic word count is replaced by the lines it holds, counted without splitting the file, so a few bytes claiming 500 million words cannot hold the shared heap at 2 GB. Unsandboxed, since the editor checks each word synchronously; no permissions.
  • assets/hunspell.js is built from src/hunspell.js with esbuild (build.mjs; hunspell-wasm 0.3.0 and esbuild pinned in the plugin's own lockfile), and assets/hunspell.wasm is copied from the package. LICENSE carries MPL 1.1 for Hunspell and hunspell-wasm (chosen from LGPL 2.0, GPL 2.0 or MPL 1.1) and MIT for the Emscripten runtime, and ships in the package.
  • main.test.mjs: compound begin/middle/end parts, compound-only parts, homonyms, suffixes permitted in compounds, suggestions, added words, encodings, the word-count bound (the heap stays under 64 MiB, where trusting the count reaches 460 MiB for 100 million), malformed dictionaries, and activation: one engine load for dictionaries loading together, and a retry after a failed read.
  • scripts/dictionaries.test.mjs now checks the German, Spanish and Persian word lists with the plugin's committed build, so the repo-level test package (package.json, package-lock.json) and the smoke job's install step go away.
  • Links to the plugin from the German README, the API reference and the dictionary recipe; docs/ecosystem.md says why the engine lives here rather than in the app; docs/contributing.md describes the smoke job.
  • plugin.json carries the placeholder sha256; the release job rewrites packageUrl and the digest. index.json, index/, and docs/plugin-catalog.md regenerated with scripts/build-index.mjs.

Testing

  • node --test at the root: 46 passing, including the plugin's tests and the three word lists, with nothing installed.
  • node scripts/check-plugins.mjs passes, and a fresh scripts/build-index.mjs leaves the committed index unchanged.
  • npm ci --ignore-scripts && npm run build in the plugin folder reproduces the committed assets.
  • In headless WebKit under the app's shipped CSP, main.js imported from a blob URL loads its assets, compiles the WebAssembly and checks compounds, accented capitals and suggestions; the same probe fails when the CSP allows no WebAssembly compile.
  • Not installed into a running app: it needs the API in feat(spellcheck): let plugins supply the spell-check engine and ship English as a core plugin hamidfzm/glyph#1071.

com.glyph.hunspell registers a spell-check engine (ctx.spellcheck.registerEngine, API 0.28.0) so every dictionary is checked by Hunspell, compiled to WebAssembly, instead of the app's built-in checker, which ignores compound flags. It runs in the app context because the editor checks each word synchronously, and loads its engine on the first dictionary checked: main.js imports assets/hunspell.js from a blob URL and hands it assets/hunspell.wasm as bytes. The affix file is always read as UTF-8, and the .dic word count is replaced by the lines it holds, so a dictionary claiming millions of words cannot inflate the shared heap. assets/hunspell.js is built from src/ with esbuild; LICENSE carries MPL 1.1 for Hunspell and hunspell-wasm and MIT for the Emscripten runtime.

The dictionary word lists now run on the plugin's committed build, so the repo-level test package and the smoke job's install step go away. The German README says to install the plugin for compound words, and the API reference, recipe, ecosystem page and contributing guide describe engines.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant