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.
| Lente | Símbolo | Ruta |
|---|---|---|
| Acervo | /[lang]/busca, /[lang]/saber/[id] | |
| Blog | /[lang]/blog | |
| Mapa | /[lang]/mapa | |
| Agenda | /[lang]/agenda | |
| Línea de tiempo | /[lang]/timeline | |
| Mocambolas | /[lang]/mocambolas | |
| Territorios | /[lang]/territorios |
Archivos para leer
svelte.config.js,vite.config.ts— adapter-node, PWA.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.src/lib/api/client.ts— el cliente de la API (≈ 1200 líneas),fetchComEspera.src/routes/[lang]/+layout.server.tsy+layout.svelte— lo que carga toda página.src/lib/stores/—auth,permissoes,tocador,galaxias,filtros.src/routes/[lang]/saber/[id]/+page.svelte— la página más completa.src/lib/components/RaizHistoria.svelte,Tocador.svelte,TeiaConstelacao.svelte,BaobaVivo.svelte— las vistas con más dibujo.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 enlaceshttp(s),mailto,xmpp,tel,geoo relativos (hrefSeguro). Todohrefque venga de datos debe pasar por ahí. - El token vive en
localStoragey en una cookie que lee el SSR — cualquier XSS roba la sesión. Ver el módulo 11.
Prácticas
- Corre
npm run devy 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? - Agrega una palabra nueva en los cinco archivos de
src/lib/i18n/locales/y úsala en un componente con$_('…'). - En
markdown.ts, escribe un test (puede ser connode --experimental-strip-types) que garantice que[x](javascript:alert(1))no se vuelve enlace. npm run checkmuestra ~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.)
