Part 4 of 8
LeXi: the ARASAAC seed, a preloaded catalog
Downloading ARASAAC pictograms, generating a manifest and an idempotent SQL, and detecting changes in CI.
LeXi needs a base pictogram catalog so the app works from the very first second, even offline. ARASAAC symbols are the reference for augmentative communication in Spanish, distributed under CC BY-NC-SA 4.0 (attribution required).
The problem: the API does not serve images
I researched ARASAAC thoroughly before writing a single line:
- The search API works:
GET /v1/pictograms/es/search/{term}returns JSON metadata. - Downloading images via the API does not work (
?downloadType=png→ 400). - What is reliable is the static CDN:
https://static.arasaac.org/pictograms/{id}/{id}_500.png(PNG only, in 300/500; JPG and SVG return 404).
Lesson: before building a scraping pipeline, check the real data source. The API looked nice on paper but was useless for what we needed; the static CDN was the stable path.
The strategy: seed at build time, not runtime
Key decision: the catalog is downloaded and committed to the repo. This way:
- The app works 100% offline from minute one.
- Zero ARASAAC calls at runtime.
- The vocabulary is editable (pictograms do not change; the text does).
Structure:
data/core-vocab.es.json— 8 categories, 285 terms, with anoverridesblock.scripts/seed-arasaac.mjs— searches the API, downloads from the CDN, generates manifest + SQL.public/assets/arasaac/— 286 500px PNGs (~4.9 MB) committed.migrations/0002_seed_arasaac.sql— idempotent seed (INSERT OR IGNORE,user_id = NULL→ global catalog).
Vocabulary failures and overrides
Generating the seed produced wrong matches: té → “tres en raya” (tictac-toe), feliz → “Merry Christmas!”, ¿qué? → “bon appétit!”. I fixed them with an overrides block mapping the term to an alternative search:
{
"overrides": {
"¿por qué?": "porque",
"sentarse": "sentar en circulo"
}
}
Other issues: reflexive verbs (“levantarse”, “lavarse”) return 404 → fallback to the base form; and terms like algo have no pictogram → removed from the vocabulary.
What we learned the hard way: the card id must derive from the term, not the pictogram.
lavarandlavarseshare a pictogram; if the key was the image id, cards collided (287 in the manifest vs 281 in D1).
The --check mode for CI
To avoid constantly re-running the seed, the script has two modes: --download regenerates, and --check compares the regenerated manifest against the committed one. A GitHub Actions workflow runs it and only regenerates when there are changes.
Lesson: GitHub Actions with
bash -ekills the step whennode --checkexits with code 1 (which is exactly when there are changes). The first workflow version never ran the conditional step. And a gitignored.arasaac-hashmade CI always see “changed”. The fix was comparing the committed manifest (which does exist in CI) and capturing the exit code withset +e.
Wrap-up
- Commit
feat: ARASAAC core seed (285 cards, script + CI). - Applied remotely with
npm run db:remote. - The app footer includes the required ARASAAC attribution and license.
In the next part, the card grid, the on-screen keyboard and TTS.