Parte IV — Copa, chão e cuidado

09 · A copa: o site

A copa é o que se vê de longe. No Baobáxia ela é o site em SvelteKit, com lentes que olham os mesmos saberes de jeitos diferentes — cada lente com um símbolo adinkra.

LenteSímboloRota
Acervo Sankofa — voltar e buscar/[lang]/busca, /[lang]/saber/[id]
Blog Nea Onnim — quem não sabe pode aprender/[lang]/blog
Mapa Mate Masie — o que ouço, guardo/[lang]/mapa
Agenda Akoma Ntoaso — corações unidos/[lang]/agenda
Linha do tempo Mmere Dane — o tempo muda/[lang]/timeline
Mocambolas Akoma — o coração/[lang]/mocambolas
Territórios Fihankra — a casa protegida/[lang]/territorios

Arquivos para ler

  1. svelte.config.js, vite.config.ts — adapter-node, PWA.
  2. src/hooks.server.ts + src/lib/server/api.ts — o token por requisição no lado do SvelteKit que roda na mucua (AsyncLocalStorage) e o cache curto das listas.
  3. src/lib/api/client.ts — o cliente da API (≈ 1200 linhas), fetchComEspera.
  4. src/routes/[lang]/+layout.server.ts e +layout.svelte — o que toda página carrega.
  5. src/lib/stores/ — auth, permissoes, tocador, galaxias, filtros.
  6. src/routes/[lang]/saber/[id]/+page.svelte — a página mais completa.
  7. src/lib/components/RaizHistoria.svelte, Tocador.svelte, TeiaConstelacao.svelte, BaobaVivo.svelte — as visões com mais desenho.
  8. src/lib/i18n/ — pt-BR, en-US, es-ES, it-IT e aw-AW (A'uwẽ).

Estrutura

flowchart TB
    subgraph Navegador
        PG["+page.svelte"]
        ST["stores<br/>auth · permissoes · tocador"]
        CL["ApiClient<br/>token do localStorage"]
    end
    subgraph Node["SvelteKit na mucua (adapter-node, :3000)"]
        HK["hooks.server.ts<br/>lê cookie auth_token"]
        ALS["AsyncLocalStorage<br/>{token, versão}"]
        LD["+page.server.ts / +layout.server.ts<br/>load()"]
        SAC["ServerApiClient<br/>cache 30 s das listas"]
    end
    API[("API /api/v3<br/>127.0.0.1:8000")]
    PG --> ST --> CL -->|"/api/v3 via nginx"| API
    HK --> ALS --> LD --> SAC -->|"SSR direto"| API

Por que AsyncLocalStorage

O cliente da API é um singleton no processo Node. Se o token fosse um campo dele, a requisição da pessoa A poderia usar o token da pessoa B no meio de um await. Cada requisição roda num contexto próprio (authContext.run), e o ServerApiClient só lê o token desse contexto. O cache das listas usa o hash do token na chave: um visitante nunca recebe o que foi buscado para o admin.

Permissões no front

O front nunca decide permissão: lê os booleanos de GET /auth/me/permissoes, calculados pelo mesmo motor que o backend aplica. Eles servem para esconder botões; o 403 continua sendo a verdade.

Raízes: listas, histórias e livros

A lente Raízes (/[lang]/raizes) mostra as coleções, e cada formato tem o seu jeito de ser visto: a lista, a história (uma raiz que desce pela página) e o livro. Um livro é uma coleção com formato: livro (data.livro guarda subtítulo, autoria, introdução, capa). Os capítulos são os membros, na ordem; o campo parte de cada membro agrupa o sumário. src/lib/livros/livro.ts monta o livro a partir do GET /colecoes/{uuid}; /raizes/[id] mostra a capa com o sumário e /raizes/[id]/[n] é a leitura de um capítulo. Os diagramas Mermaid são desenhados pela action lib/actions/mermaid.ts, com a biblioteca empacotada no build (funciona sem internet). Título, corpo, partes e introdução podem vir em vários idiomas ({pt: …, en: …, es: …}): o site mostra o idioma da URL e cai no português quando falta a tradução — este curso é assim.

Os capítulos moram no livro: a listagem do Acervo e a home não os mostram (SaberService.search com application="acervo" e sem texto deixa de fora os membros de coleções formato: livro). Numa busca por texto eles aparecem; ?capitulos=true traz sempre.

Galáxia em zip

Em Galáxias, quem administra a mucua pode exportar uma galáxia como .zip (história git + anexos; services/galaxia_zip.py) e importar um zip para instalar a galáxia sem rede — o mesmo caminho de instalar por URL, só que a origem é o arquivo.

O tocador e as histórias

stores/tocador.svelte.ts mantém uma fila de faixas (áudio/vídeo) que atravessa as páginas. Uma história (coleção no formato historia) guarda, por arquivo (uuid::arquivo em historia.itens), os trechos a tocar (cortes: [{de, ate}]), título e texto próprios. RaizHistoria.svelte desenha a história como uma raiz que desce pela página.

Segurança na copa

  • Markdown de saber é renderizado por lib/util/markdown.ts: escapa HTML e, desde a 3.3, só aceita links http(s), mailto, xmpp, tel, geo ou relativos (hrefSeguro). Qualquer href vindo de dado deve passar por ali.
  • O token vive no localStorage e num cookie lido pelo SSR — qualquer XSS rouba sessão. Ver o módulo 11.

Práticas

  1. Rode npm run dev, abra a página de um saber com a aba de rede aberta. Quais chamadas saem do navegador e quais vieram prontas do SSR?
  2. Acrescente uma palavra nova nos cinco arquivos de src/lib/i18n/locales/ e use-a num componente com $_('…').
  3. Em markdown.ts, escreva um teste (pode ser com node --experimental-strip-types) que garanta que [x](javascript:alert(1)) não vira link.
  4. npm run check mostra ~55 erros de tipo antigos. Escolha um arquivo e zere os erros dele.

Para conversar

  • O que significa ter a interface do acervo numa língua indígena, além de traduzir palavras? (Datas, números, ordem das coisas, quem decide os termos.)

ver este capítulo como saber