Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

crab-exoskeleton-webapp

O webapp é a parte da stack que o membro realmente vê. Esta página o descreve como componente: o que ele é, a única regra de transporte que rege toda mudança nele, e onde vive o código dele.

O que é

crab-exoskeleton-webapp é uma aplicação Next.js 15 que usa o App Router. Ele é ao mesmo tempo a interface de usuário e um backend-for-frontend: as páginas em app/ desenham o chat e o console de administração, e os route handlers em app/api/ são uma camada no servidor que chama o upstream em nome do navegador.

O serviço dele no compose é chat-webapp, não o nome do repositório. O campo name do package.json dele também, e a imagem publicada em docker-compose.prod.yaml também. Se você está procurando este componente em um arquivo compose, em docker compose logs, ou em uma listagem de containers, procure por chat-webapp.

O arranjo de backend-for-frontend é o ponto, não um detalhe. O navegador guarda um cookie de sessão e mais nada — nenhum token, nenhum id de conta, nenhuma URL de upstream. Toda requisição vai do navegador para o route handler, dele para o gateway Mycelium, e dele para o crab-shell-proxy, de modo que a mesma identidade verificada que protege o backend protege a interface sem que a interface tenha que reimplementar nada disso.

Pelo que ele é responsável

Sustentar a sessão. O login é um magic link. O route handler o completa e guarda a sessão do gateway em um cookie HTTP-only; middleware.ts protege /chat e /onboarding verificando que o cookie é interpretável e que o token dele ainda não passou da própria validade, redirecionando para /signin e limpando o cookie quando não passa. Essa verificação explicitamente não é validação — um token pode ser revogado no upstream enquanto a validade dele ainda está no futuro, então a resposta de verdade continua vindo da primeira chamada ao upstream, que limpa a sessão em um 401.

A experiência de chat. Respostas em streaming, histórico de conversas, busca, renomear e etiquetar, deep links da forma /chat/{agent}/{sessionId}, upload de arquivo para o workspace do membro, e visões de linha do tempo e de árvore da atividade passada. Veja O cliente de chat.

O console do operador. Tenants, subscriptions e membros, o registro de modelos por agente e as atribuições de modelo por usuário, skills compartilhadas e conteúdo compartilhado, segredos, e identidade visual. O gateway continua sendo quem impõe quem pode fazer o quê; estas telas são uma superfície sobre a API de administração do proxy, não um segundo sistema de autorização. Veja Guia de administração.

Um banco de dados próprio. Uma conexão Postgres, configurada por DATABASE_URL, carrega o índice de conversas e os metadados do lado da aplicação. No arquivo compose de desenvolvimento, isso é o serviço separado chat-webapp-postgres.

A regra de transporte: sempre JSON-RPC, nunca uma nova chamada REST

Esta é a única convenção que você precisa conhecer antes de escrever uma linha de código aqui.

O gateway do Mycelium expõe tanto uma API REST quanto um endpoint JSON-RPC 2.0 em POST /_adm/rpc. Eles não são intercambiáveis e REST é o padrão errado. Os endpoints REST beginners do gateway são exclusivos de provedor de identidade externo: para um usuário de magic link — que é todo usuário deste deploy — eles respondem 400 "Invalid provider". O dispatcher de RPC resolve o emissor interno, então ele é o único transporte que funciona para os membros desta stack. Isso foi estabelecido testando, e a evidência está guardada em .specs/features/onboarding/context.md neste repositório. A superfície de RPC também é mais ampla: operações inteiras, como convidar e desconvidar um convidado, não têm equivalente REST que esta stack consiga alcançar.

Na prática, isso significa chamar myceliumRpc() de lib/mycelium.ts, nunca adicionar um novo caminho fetchMycelium() a uma rota REST /_adm. Os parâmetros são camelCase, e o registro autoritativo dos nomes de método é ports/api/src/rpc/method_names.rs no código-fonte do mycelium — nunca adivinhe um, porque um nome inventado falha só em tempo de execução e a falha parece um problema de permissão.

A regra é imposta, não confiada. .github/workflows/mycelium-transport.yml passa um grep em app/ e lib/ a cada pull request que os toca e quebra o build quando um caminho /_adm é passado para fetchMycelium a partir de um arquivo fora da sua lista de permissões. A lista de permissões é onde vivem as exceções, cada uma com um motivo: o par de requisição e verificação do magic link antes da sessão, que não tem token para autenticar uma chamada RPC; o próprio lib/mycelium.ts, porque POST /_adm/rpc é o transporte RPC; e app/api/tenants/[id], que é anterior à checagem e contradiz a regra como escrita, colocado na lista para ficar visível em vez de silenciosamente tolerado. O comentário do próprio workflow é honesto sobre o seu limite: ele casa um caminho /_adm em uma linha, então uma chamada montada em várias linhas ou através de uma variável não é pega. É uma catraca contra o caminho fácil, não uma prova.

Mais uma fronteira que vale declarar: as requisições ao crab-shell-proxy — os caminhos /{agent}/v1/... e /alpha/v1/admin/... — são a API HTTP do próprio proxy e continuam REST. “Chame o mycelium por JSON-RPC” não é “converta o proxy para JSON-RPC”.

Pelo que ele não é responsável

Ele nunca fala com um container de agente. Não existe caminho desta aplicação até um harness que não passe pelo gateway e pelo proxy.

Ele não decide quem pode fazer o quê. A autorização é do gateway, e as telas a refletem em vez de implementá-la.

Ele não embute nada específico de deploy em tempo de build. MYCELIUM_INTERNAL_URL e DATABASE_URL são lidas no servidor a cada requisição, que é o que permite a uma única imagem publicada servir todo deploy. A UI de administração do Mycelium que vem ao lado dele, mycelium-webapp, é uma SPA puramente do lado do cliente cuja URL de API é compilada junto; as duas são diferentes nesse aspecto e o arquivo compose diz isso.

Como é construído e testado

O desenvolvimento é o loop comum do Next.js:

yarn install
yarn dev        # http://localhost:3000

Dois workflows rodam no CI, e nenhum dos dois roda a suíte de testes. mycelium-transport.yml é o grep de transporte descrito acima, nos pull requests que tocam app/ ou lib/. release-image.yml constrói e publica a imagem em um push para main ou em uma tag de versão; o Dockerfile instala com yarn install --frozen-lockfile e depois roda yarn build, então um erro de build quebra a publicação e um teste falhando não.

yarn test roda a suíte do Vitest, que é substancial e cobre lib/, components/ e partes de app/. Ela está documentada no README do repositório e é a verificação para rodar antes de você abrir um pull request — só saiba que nada no CI vai rodá-la por você.

yarn lint não funciona. O script é next lint, mas o ESLint não é uma dependência deste repositório: ele não aparece nem em package.json nem em yarn.lock, e não está instalado. O script é um resquício. Não o coloque em um checklist de contribuição e não espere que um linter pegue nada aqui.

tsconfig.json define noEmit, então o TypeScript é um verificador de tipos em vez de uma etapa de build, e não há script que o invoque sozinho; os erros de tipo aparecem através de yarn build e do seu editor.

A imagem de produção é pequena porque next.config.ts define output: "standalone", que rastreia só as dependências realmente usadas em tempo de execução — o estágio final copia o servidor rastreado e os arquivos estáticos e não precisa nem de node_modules nem do yarn. O Dockerfile carrega um aviso longo contra reativar o corepack, que substituiu um yarn empacotado que funcionava por um shim que resolve a sua versão pela rede e transformou um build offline em um que precisava do registro npm.

Como o código está organizado

app/chat/        the chat experience
app/admin/       the operator console
app/api/         the backend-for-frontend route handlers
app/signin/      magic-link sign-in
components/      shared UI, plus the pre-auth landing page
lib/             everything that is not a component: mycelium.ts, session.ts,
                 the model, media, memory and admin helpers, i18n
middleware.ts    the session guard on /chat and /onboarding
.specs/          specifications; start with .specs/project/PROJECT.md

Os testes ficam ao lado do código que cobrem, como *.test.ts e *.test.tsx. vitest.config.ts exclui node_modules, .next e .claude por glob em vez de por nome puro, porque um git worktree criado sob .claude/worktrees/ uma vez trouxe o seu próprio node_modules e os testes de outro branch para dentro da execução.

A estilização é Tailwind CSS v4 com class-variance-authority para variantes, em vez de strings className condicionais ou interpoladas.

Para onde ir agora

O cliente de chat cobre o uso da aplicação como membro, e Guia de administração cobre as telas do operador. Trabalhando na stack tem os comandos de build e teste de cada repositório, e Contribuindo tem as convenções que uma mudança precisa seguir.