Docs
Architecture
One conversion engine, compiled from source data, driven by many surfaces. Every tool is a thin layer around the same pure string functions.
Build pipeline
The lexicon is authored as CSVs and compiled to JavaScript maps; the content script is then bundled with Rollup. No TypeScript — JSDoc supplies the types.
data/*.csv → compile-lexicon.js → dist/lexicon.js
→ dist/abbreviations.js
→ dist/contractions.js
→ dist/phrases.js
src/content/*.js + dist/*.js → Rollup (iife) → dist/content-bundle.jsThe engine is DOM-free
The conversion core is text-in / text-out. Each stage is a pure function, so it runs anywhere — a browser page, an Electron iframe, a Word taskpane, an Apps Script runtime, a test harness:
tokenize(text)— words, separators, and contractions (contraction-aware).tagWord(word)— the lexical CLAWS7 candidate set from the lexicon.convert(word, tokens, idx)— context-aware respelling with full disambiguation.
The one DOM-coupled piece
Only
walkTextNodes(root, convert)touches the DOM. Drivers that have a DOM (the extension, Eupub) use it; drivers that don't (Word, LibreOffice, Google Docs, dictation) call convertText(text) directly. This is why the engine ports so cleanly across surfaces.The convert flow
function convert(word, tokens, idx) {
const entry = lexicon.get(word.toLowerCase()) ?? contractions.get(word.toLowerCase());
if (!entry) return word;
const variants = entry.encoding % 10; // euspelling count
if (variants === 0) return word; // unchanged
if (variants === 1) return matchCase(word, entry.spellings[0]);
const i = disambiguate(entry, tokens, idx); // pos.js or semantic/
return matchCase(word, entry.spellings[i] ?? word);
}Surfaces around the core
- Chrome extension — content script + service worker; its own PDF.js viewer for PDFs.
- Eupub reader — Electron; injects the engine bundle into each chapter iframe.
- Office converters — Word (Office.js), LibreOffice (Python port), Google Docs (Apps Script port), each validated 35/35 against the reference engine.
- Dictation — an additive authoring path feeding speech through
convertText. - Derived lexicons — PLS for TTS, Morfologik / Harper for grammar checkers.
Testing
- Unit tests — converter, DOM walker,
matchCase, individual POS functions. - Corpus tests — CLAWS7-tagged sentences per ambiguous word, asserting the disambiguation output.
- Cross-engine fixtures — the Python and Apps Script ports are checked against the JS engine.