# Development [← README](../README.md) ``` manifest.json what the shell loads preview.png marketplace preview, the same shot as the README hero BarWidget.qml the button in the bar; owns the slot, forwards to the panel Panel.qml all the state, the network, the twelve tabs lib/Portal.js network policy: allowlist, argv, bounds, endpoints lib/Article.js portal markup → the blocks the panel draws lib/Sections.js one entry → the model behind each tab lib/Index.js the bundled word index: fold, search, browse lib/Cache.js cache file naming and the size cap lib/Files.js bounded reads: every file this plugin reads back off disk lib/About.js what the "À propos" panel says lib/Store.js the kept and recent word lists ui/ the article paragraph, the word row, the chip, the sparkline data/words/ 87 268 words, one file per initial letter tools/build-index.py rebuilds data/words/ tools/test.js unit tests for lib/*.js tools/check-portal.js the same code against the live portal docs/ this manual, the data and security notes, and their images ``` The QML is a view over `lib/*.js`, and that is where the tests are. ```bash node tools/test.js # 74 tests, no dependencies omarchy plugin validate ~/.config/omarchy/plugins/jmaeder.frenchdict-cnrtl qmllint -I /usr/share/omarchy/shell BarWidget.qml Panel.qml ``` `tools/test.js` is offline by design, and that is also its blind spot: it runs against hand-written fixtures, so it cannot notice the portal moving, renaming a field or retiring a route. `tools/check-portal.js` covers that half, using the plugin's own URL builders and its own parser rather than a second copy of either: ```bash node tools/check-portal.js # entries, search, lexicon, every link node tools/check-portal.js bicyclette # one word of your choosing ``` It exits non-zero when the panel would be broken, which is what makes it worth running before a release. In September 2026 ATILF moved the portal to cnrtl.fr and left a 301 behind; the plugin does not follow redirects, so every lookup failed while all 74 tests stayed green. ## Iterating on a live shell The shell keeps compiled QML in memory, so a changed `.qml` file needs a shell restart to take effect, not just a save: ```bash rsync -a --delete --exclude .git ./ ~/.config/omarchy/plugins/jmaeder.frenchdict-cnrtl/ omarchy-shell shell rescanPlugins ``` If a widget disappears from the bar after an edit, it is a QML error, not a logic one: the shell log under `/run/user/$UID/quickshell/by-id/*/log.qslog` names the file and the line. ## Rebuilding the word index `tools/build-index.py` walks CNRTL's paginated TLFi listing. It caches every page it fetches and is resumable, so a re-run only fetches what is missing; a cold run takes about ten minutes over 1 100 pages. ```bash python3 tools/build-index.py # every letter python3 tools/build-index.py W X # just these ``` The file explains why the index comes from there rather than from the portal's own `/api/lexicon`, which is capped at 10 000 entries per letter. ## The README animation The hero at the top of the README is `docs/demo.webp`: a screen recording of the panel opening, converted to an animated webp so GitHub plays it inline without a video player and the repository stays small. ```bash ffmpeg -i screenrecording.mp4 -vf "fps=12,scale=900:-2:flags=lanczos" \ -loop 0 -q:v 55 -compression_level 6 docs/demo.webp ``` 12 fps is enough for a panel opening and typing; 900 px is the recording's own width, so the text stays at 1:1 and does not go through a resample it cannot afford. `-q:v 55` was chosen by comparing the small grey lines of a Traductions article against `-q:v 75`: they are indistinguishable, and 55 is 300 KB cheaper. `-2` rather than `-1` on the height keeps it even, which the encoder requires. `.mp4` and `.mkv` are ignored by git, so the source recording can sit in the checkout while the converted webp is what gets committed.