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.

LensSymbolRoute
Collection Sankofa — go back and fetch it/[lang]/busca, /[lang]/saber/[id]
Blog Nea Onnim — whoever does not know can learn/[lang]/blog
Map Mate Masie — what I hear, I keep/[lang]/mapa
Calendar Akoma Ntoaso — joined hearts/[lang]/agenda
Timeline Mmere Dane — time changes/[lang]/timeline
Mocambolas Akoma — the heart/[lang]/mocambolas
Territories Fihankra — the protected house/[lang]/territorios

Files to read

  1. svelte.config.js, vite.config.ts — adapter-node, PWA.
  2. 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.
  3. src/lib/api/client.ts — the API client (≈ 1200 lines), fetchComEspera.
  4. src/routes/[lang]/+layout.server.ts and +layout.svelte — what every page loads.
  5. src/lib/stores/ — auth, permissoes, tocador, galaxias, filtros.
  6. src/routes/[lang]/saber/[id]/+page.svelte — the most complete page.
  7. src/lib/components/RaizHistoria.svelte, Tocador.svelte, TeiaConstelacao.svelte, BaobaVivo.svelte — the views with the most drawing in them.
  8. 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 accepts http(s), mailto, xmpp, tel, geo or relative links (hrefSeguro). Any href coming from data must go through it.
  • The token lives in localStorage and in a cookie read by SSR — any XSS steals the session. See module 11.

Practice

  1. Run npm run dev and open a saber's page with the network tab open. Which calls leave the browser, and which came ready-made from SSR?
  2. Add a new word to the five files in src/lib/i18n/locales/ and use it in a component with $_('…').
  3. In markdown.ts, write a test (it can be with node --experimental-strip-types) ensuring that [x](javascript:alert(1)) does not become a link.
  4. npm run check shows ~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.)

vedi questo capitolo come sapere