Parte IV — Chioma, suolo e cura

09 · La chioma: il sito

La chioma è ciò che si vede da lontano. In Baobáxia è il sito in SvelteKit, con lenti che guardano gli stessi saberes in modi diversi — ogni lente con un simbolo adinkra.

LenteSimboloRotta
Archivio Sankofa — torna indietro e prendilo/[lang]/busca, /[lang]/saber/[id]
Blog Nea Onnim — chi non sa può imparare/[lang]/blog
Mappa Mate Masie — ciò che sento, lo custodisco/[lang]/mapa
Agenda Akoma Ntoaso — cuori uniti/[lang]/agenda
Timeline Mmere Dane — il tempo cambia/[lang]/timeline
Mocambola Akoma — il cuore/[lang]/mocambolas
Territori Fihankra — la casa protetta/[lang]/territorios

File da leggere

  1. svelte.config.js, vite.config.ts — adapter-node, PWA.
  2. src/hooks.server.ts + src/lib/server/api.ts — il token per richiesta sul lato SvelteKit che gira sulla mucua (AsyncLocalStorage) e la cache breve degli elenchi.
  3. src/lib/api/client.ts — il client dell'API (≈ 1200 righe), fetchComEspera.
  4. src/routes/[lang]/+layout.server.ts e +layout.svelte — ciò che ogni pagina carica.
  5. src/lib/stores/ — auth, permissoes, tocador, galaxias, filtros.
  6. src/routes/[lang]/saber/[id]/+page.svelte — la pagina più completa.
  7. src/lib/components/RaizHistoria.svelte, Tocador.svelte, TeiaConstelacao.svelte, BaobaVivo.svelte — le viste con più disegno.
  8. src/lib/i18n/ — pt-BR, en-US, es-ES, it-IT e aw-AW (A'uwẽ).

Struttura

flowchart TB
    subgraph Navegador["Browser"]
        PG["+page.svelte"]
        ST["stores<br/>auth · permissoes · tocador"]
        CL["ApiClient<br/>token dal localStorage"]
    end
    subgraph Node["SvelteKit sulla mucua (adapter-node, :3000)"]
        HK["hooks.server.ts<br/>legge il cookie auth_token"]
        ALS["AsyncLocalStorage<br/>{token, versione}"]
        LD["+page.server.ts / +layout.server.ts<br/>load()"]
        SAC["ServerApiClient<br/>cache di 30 s degli elenchi"]
    end
    API[("API /api/v3<br/>127.0.0.1:8000")]
    PG --> ST --> CL -->|"/api/v3 via nginx"| API
    HK --> ALS --> LD --> SAC -->|"SSR diretto"| API

Perché AsyncLocalStorage

Il client dell'API è un singleton nel processo Node. Se il token fosse un suo campo, la richiesta della persona A potrebbe usare il token della persona B a metà di un await. Ogni richiesta gira in un contesto proprio (authContext.run), e il ServerApiClient legge il token solo da quel contesto. La cache degli elenchi mette l'hash del token nella chiave: un visitatore non riceve mai ciò che è stato preso per l'admin.

Permessi nel front

Il front non decide mai i permessi: legge i booleani da GET /auth/me/permissoes, calcolati dallo stesso motore che il backend applica. Servono a nascondere pulsanti; il 403 resta la verità.

Radici: liste, storie e libri

La lente Radici (/[lang]/raizes) mostra le collezioni, e ogni formato ha il suo modo di essere visto: la lista, la storia (una radice che scende lungo la pagina) e il libro. Un libro è una collezione con formato: livro (data.livro contiene sottotitolo, autori, introduzione, copertina). I capitoli sono i membri, in ordine; il campo parte di ogni membro raggruppa l'indice. src/lib/livros/livro.ts monta il libro a partire da GET /colecoes/{uuid}; /raizes/[id] mostra la copertina con l'indice e /raizes/[id]/[n] è la lettura di un capitolo. I diagrammi Mermaid sono disegnati dall'action lib/actions/mermaid.ts, con la libreria inclusa nel build (funziona senza internet). Titolo, corpo, parti e introduzione possono essere in più lingue ({pt: …, en: …, es: …}): il sito mostra la lingua dell'URL e torna al portoghese quando manca la traduzione — questo corso è fatto così.

I capitoli vivono nel libro: l'elenco dell'Archivio e la home non li mostrano (SaberService.search con application="acervo" e senza testo lascia fuori i membri delle raccolte formato: livro). In una ricerca per testo compaiono; ?capitulos=true li include sempre.

Galassia in zip

In Galassie, chi amministra la mucua può esportare una galassia come .zip (cronologia git + allegati; services/galaxia_zip.py) e importare uno zip per installare la galassia senza rete — la stessa strada dell'installazione da URL, solo che l'origine è il file.

Il lettore e le storie

stores/tocador.svelte.ts mantiene una coda di tracce (audio/video) che attraversa le pagine. Una storia (collezione nel formato historia) conserva, per file (uuid::arquivo in historia.itens), i brani da riprodurre (cortes: [{de, ate}]), con titolo e testo propri. RaizHistoria.svelte disegna la storia come una radice che scende lungo la pagina.

Sicurezza nella chioma

  • Il markdown dei saberes è reso da lib/util/markdown.ts: fa l'escape dell'HTML e, dalla 3.3, accetta solo link http(s), mailto, xmpp, tel, geo o relativi (hrefSeguro). Ogni href che viene dai dati deve passare di lì.
  • Il token vive nel localStorage e in un cookie letto dall'SSR — qualsiasi XSS ruba la sessione. Vedi il modulo 11.

Esercizi

  1. Esegui npm run dev e apri la pagina di un saber con la scheda di rete aperta. Quali chiamate partono dal browser e quali sono arrivate pronte dall'SSR?
  2. Aggiungi una parola nuova nei cinque file di src/lib/i18n/locales/ e usala in un componente con $_('…').
  3. In markdown.ts, scrivi un test (anche con node --experimental-strip-types) che garantisca che [x](javascript:alert(1)) non diventi un link.
  4. npm run check mostra ~55 vecchi errori di tipo. Scegli un file e azzera i suoi errori.

Da discutere

  • Cosa significa avere l'interfaccia dell'archivio in una lingua indigena, oltre a tradurre parole? (Date, numeri, l'ordine delle cose, chi decide i termini.)

vedi questo capitolo come sapere