Parte IV — Copa, suelo y cuidado

09 · La copa: el sitio

La copa es lo que se ve de lejos. En Baobáxia es el sitio en SvelteKit, con lentes que miran los mismos saberes de maneras distintas — cada lente con un símbolo adinkra.

LenteSímboloRuta
Acervo Sankofa — volver y buscar/[lang]/busca, /[lang]/saber/[id]
Blog Nea Onnim — quien no sabe puede aprender/[lang]/blog
Mapa Mate Masie — lo que oigo, lo guardo/[lang]/mapa
Agenda Akoma Ntoaso — corazones unidos/[lang]/agenda
Línea de tiempo Mmere Dane — el tiempo cambia/[lang]/timeline
Mocambolas Akoma — el corazón/[lang]/mocambolas
Territorios Fihankra — la casa protegida/[lang]/territorios

Archivos para leer

  1. svelte.config.js, vite.config.ts — adapter-node, PWA.
  2. src/hooks.server.ts + src/lib/server/api.ts — el token por petición en el lado de SvelteKit que corre en la mucua (AsyncLocalStorage) y la caché corta de los listados.
  3. src/lib/api/client.ts — el cliente de la API (≈ 1200 líneas), fetchComEspera.
  4. src/routes/[lang]/+layout.server.ts y +layout.svelte — lo que carga toda página.
  5. src/lib/stores/ — auth, permissoes, tocador, galaxias, filtros.
  6. src/routes/[lang]/saber/[id]/+page.svelte — la página más completa.
  7. src/lib/components/RaizHistoria.svelte, Tocador.svelte, TeiaConstelacao.svelte, BaobaVivo.svelte — las vistas con más dibujo.
  8. src/lib/i18n/ — pt-BR, en-US, es-ES, it-IT y aw-AW (A'uwẽ).

Estructura

flowchart TB
    subgraph Navegador["Navegador"]
        PG["+page.svelte"]
        ST["stores<br/>auth · permissoes · tocador"]
        CL["ApiClient<br/>token del localStorage"]
    end
    subgraph Node["SvelteKit en la mucua (adapter-node, :3000)"]
        HK["hooks.server.ts<br/>lee la cookie auth_token"]
        ALS["AsyncLocalStorage<br/>{token, versión}"]
        LD["+page.server.ts / +layout.server.ts<br/>load()"]
        SAC["ServerApiClient<br/>caché de 30 s de los listados"]
    end
    API[("API /api/v3<br/>127.0.0.1:8000")]
    PG --> ST --> CL -->|"/api/v3 vía nginx"| API
    HK --> ALS --> LD --> SAC -->|"SSR directo"| API

Por qué AsyncLocalStorage

El cliente de la API es un singleton en el proceso Node. Si el token fuera un campo suyo, la petición de la persona A podría usar el token de la persona B en medio de un await. Cada petición corre en un contexto propio (authContext.run), y el ServerApiClient solo lee el token de ese contexto. La caché de los listados usa el hash del token en la clave: un visitante nunca recibe lo que se buscó para el admin.

Permisos en el front

El front nunca decide permisos: lee los booleanos de GET /auth/me/permissoes, calculados por el mismo motor que aplica el backend. Sirven para esconder botones; el 403 sigue siendo la verdad.

Raíces: listas, historias y libros

La lente Raíces (/[lang]/raizes) muestra las colecciones, y cada formato tiene su manera de verse: la lista, la historia (una raíz que baja por la página) y el libro. Un libro es una colección con formato: livro (data.livro guarda subtítulo, autoría, introducción, portada). Los capítulos son los miembros, en orden; el campo parte de cada miembro agrupa el índice. src/lib/livros/livro.ts arma el libro a partir de GET /colecoes/{uuid}; /raizes/[id] muestra la portada con el índice y /raizes/[id]/[n] es la lectura de un capítulo. Los diagramas Mermaid los dibuja la action lib/actions/mermaid.ts, con la biblioteca empaquetada en el build (funciona sin internet). Título, cuerpo, partes e introducción pueden venir en varios idiomas ({pt: …, en: …, es: …}): el sitio muestra el idioma de la URL y vuelve al portugués cuando falta la traducción — este curso está hecho así.

Los capítulos viven en el libro: el listado del Acervo y la home no los muestran (SaberService.search con application="acervo" y sin texto deja fuera a los miembros de colecciones formato: livro). En una búsqueda por texto sí aparecen; ?capitulos=true los trae siempre.

Galaxia en zip

En Galaxias, quien administra la mucua puede exportar una galaxia como .zip (historial git + anexos; services/galaxia_zip.py) e importar un zip para instalar la galaxia sin red — el mismo camino que instalar por URL, solo que el origen es el archivo.

El reproductor y las historias

stores/tocador.svelte.ts mantiene una cola de pistas (audio/video) que atraviesa las páginas. Una historia (colección en el formato historia) guarda, por archivo (uuid::arquivo en historia.itens), los fragmentos a reproducir (cortes: [{de, ate}]), título y texto propios. RaizHistoria.svelte dibuja la historia como una raíz que baja por la página.

Seguridad en la copa

  • El markdown de un saber lo renderiza lib/util/markdown.ts: escapa HTML y, desde la 3.3, solo acepta enlaces http(s), mailto, xmpp, tel, geo o relativos (hrefSeguro). Todo href que venga de datos debe pasar por ahí.
  • El token vive en localStorage y en una cookie que lee el SSR — cualquier XSS roba la sesión. Ver el módulo 11.

Prácticas

  1. Corre npm run dev y abre la página de un saber con la pestaña de red abierta. ¿Qué llamadas salen del navegador y cuáles llegaron listas del SSR?
  2. Agrega una palabra nueva en los cinco archivos de src/lib/i18n/locales/ y úsala en un componente con $_('…').
  3. En markdown.ts, escribe un test (puede ser con node --experimental-strip-types) que garantice que [x](javascript:alert(1)) no se vuelve enlace.
  4. npm run check muestra ~55 errores de tipos antiguos. Elige un archivo y deja sus errores en cero.

Para conversar

  • ¿Qué significa tener la interfaz del acervo en una lengua indígena, más allá de traducir palabras? (Fechas, números, el orden de las cosas, quién decide los términos.)

vedi questo capitolo come sapere