Part IV — Canopy, ground and care
09 · The canopy: the website
The canopy is what you see from far away. In Baobáxia it is the SvelteKit website, with lenses that look at the same saberes in different ways — each lens with an adinkra symbol.
| Lens | Symbol | Route |
|---|---|---|
| Collection | /[lang]/busca, /[lang]/saber/[id] | |
| Blog | /[lang]/blog | |
| Map | /[lang]/mapa | |
| Calendar | /[lang]/agenda | |
| Timeline | /[lang]/timeline | |
| Mocambolas | /[lang]/mocambolas | |
| Territories | /[lang]/territorios |
Files to read
svelte.config.js,vite.config.ts— adapter-node, PWA.src/hooks.server.ts+src/lib/server/api.ts— the per-request token on the SvelteKit side running on the mucua (AsyncLocalStorage) and the short cache for lists.src/lib/api/client.ts— the API client (≈ 1200 lines),fetchComEspera.src/routes/[lang]/+layout.server.tsand+layout.svelte— what every page loads.src/lib/stores/—auth,permissoes,tocador,galaxias,filtros.src/routes/[lang]/saber/[id]/+page.svelte— the most complete page.src/lib/components/RaizHistoria.svelte,Tocador.svelte,TeiaConstelacao.svelte,BaobaVivo.svelte— the views with the most drawing in them.src/lib/i18n/— pt-BR, en-US, es-ES, it-IT and aw-AW (A'uwẽ).
Structure
flowchart TB
subgraph Navegador["Browser"]
PG["+page.svelte"]
ST["stores<br/>auth · permissoes · tocador"]
CL["ApiClient<br/>token from localStorage"]
end
subgraph Node["SvelteKit on the mucua (adapter-node, :3000)"]
HK["hooks.server.ts<br/>reads the auth_token cookie"]
ALS["AsyncLocalStorage<br/>{token, version}"]
LD["+page.server.ts / +layout.server.ts<br/>load()"]
SAC["ServerApiClient<br/>30 s cache for lists"]
end
API[("API /api/v3<br/>127.0.0.1:8000")]
PG --> ST --> CL -->|"/api/v3 via nginx"| API
HK --> ALS --> LD --> SAC -->|"direct SSR"| API
Why AsyncLocalStorage
The API client is a singleton in the Node process. If the token were one of its fields, person A's request could use person B's token in the middle of an await. Each request runs in its own context (authContext.run), and the ServerApiClient only reads the token from that context. The list cache puts a hash of the token in its key: a visitor never receives what was fetched for the admin.
Permissions on the front
The front never decides permissions: it reads the booleans from GET /auth/me/permissoes, computed by the same engine the backend enforces. They are used to hide buttons; the 403 is still the truth.
Roots: lists, stories and books
The Roots lens (/[lang]/raizes) shows collections, and each format has its own way of being seen: the list, the story (a root growing down the page) and the book. A book is a collection with formato: livro (data.livro holds subtitle, authorship, introduction, cover). The chapters are the members, in order; each member's parte field groups the table of contents. src/lib/livros/livro.ts assembles the book from GET /colecoes/{uuid}; /raizes/[id] shows the cover with the contents and /raizes/[id]/[n] reads one chapter. Mermaid diagrams are drawn by the lib/actions/mermaid.ts action, with the library bundled in the build (it works offline). Title, body, parts and introduction can come in several languages ({pt: …, en: …, es: …}): the site shows the URL's language and falls back to Portuguese when a translation is missing — this course is built that way.
The chapters live in the book: the Collection listing and the home page don't show them (SaberService.search with application="acervo" and no text leaves out members of formato: livro collections). A text search does find them; ?capitulos=true always includes them.
Galaxy as a zip
In Galaxies, whoever administers the mucua can export a galaxy as a .zip (git history + attachments; services/galaxia_zip.py) and import a zip to install the galaxy without a network — the same path as installing by URL, except that the source is the file.
The player and stories
stores/tocador.svelte.ts keeps a queue of tracks (audio/video) that crosses pages. A story (a collection in the historia format) keeps, per file (uuid::arquivo in historia.itens), the excerpts to play (cortes: [{de, ate}]), and its own title and text. RaizHistoria.svelte draws the story as a root going down the page.
Security in the canopy
- Saber markdown is rendered by
lib/util/markdown.ts: it escapes HTML and, since 3.3, only acceptshttp(s),mailto,xmpp,tel,geoor relative links (hrefSeguro). Anyhrefcoming from data must go through it. - The token lives in
localStorageand in a cookie read by SSR — any XSS steals the session. See module 11.
Practice
- Run
npm run devand open a saber's page with the network tab open. Which calls leave the browser, and which came ready-made from SSR? - Add a new word to the five files in
src/lib/i18n/locales/and use it in a component with$_('…'). - In
markdown.ts, write a test (it can be withnode --experimental-strip-types) ensuring that[x](javascript:alert(1))does not become a link. npm run checkshows ~55 old type errors. Pick a file and bring its errors to zero.
To talk about
- What does it mean to have the collection's interface in an Indigenous language, beyond translating words? (Dates, numbers, the order of things, who decides the terms.)
