# Ejete seu painel

Publicar na Vibedasher é o fim normal do ciclo: é isso que mantém um painel hospedado, governado e ligado a dados que alguém mantém. **Ejetar é a outra porta, e ela está aberta de propósito.** O painel é React e SQL puros, e a qualquer momento você pode levar a árvore inteira e rodá-la no seu próprio app, na sua própria hospedagem, sob o seu próprio build.

Isso não é a exportação de uma imagem ou de um blob JSON que você teria de reimplementar. É o código-fonte que roda: componentes, páginas, gráficos, stores, hooks, um `package.json` reconstruído a partir dos imports reais da árvore, uma configuração do Tailwind, uma entrada CSS, um `tsconfig.json`, uma configuração do Vite, um `index.html`, e um `main.tsx` que o monta. `npm install && npm run dev` serve tudo como o seu próprio app React.

observação

Ejetar é o oposto de [incorporar](/pt-BR/api/embedding-the-share-iframe.md). Incorporar coloca *o nosso* painel renderizado dentro de um iframe na sua página. Ejetar entrega o código-fonte e nos tira inteiramente do caminho de renderização — nós ficamos apenas com o motor de dados por baixo. Se a pergunta é "esse código é meu?", ejetar é a resposta. Se você ainda está escolhendo entre as duas, **[Incorporar ou ejetar](/pt-BR/guides/embed-or-eject.md)** coloca as duas lado a lado.

## As três chamadas[​](#as-três-chamadas "Link direto para As três chamadas")

* **`eject_viz(viz_id, out_dir=...)`** — a árvore inteira mais um **manifesto de conexões**: o contrato de consulta, os tokens de tema, o ponto de entrada e a ordem de suas props, a lista de arquivos, a região, e a via de dados.
* **`eject_viz_files(viz_id, paths=[...])`** — o conteúdo dos arquivos sob demanda. É o acompanhamento quando uma resposta veio paginada, e é também como ler um arquivo de forma barata.
* **`eject_instructions()`** — a referência de conexões, devolvida como dados estruturados em vez de prosa que você tem de interpretar. Tudo nesta página é uma leitura dela; chame-a quando quiser o original legível por máquina.

### Passe `out_dir`, e saiba em qual transporte você está[​](#passe-out_dir-e-saiba-em-qual-transporte-você-está "Link direto para passe-out_dir-e-saiba-em-qual-transporte-você-está")

Um painel real fica na ordem de quarenta e tantos arquivos e meio milhão de caracteres. Com `out_dir` apontando para um caminho **absoluto**, a árvore é escrita direto em disco e a resposta continua pequena por maior que a árvore seja.

Sobre o transporte **stdio** — a inicialização padrão, onde o seu cliente MCP sobe o servidor como um subprocesso local sob o seu próprio usuário — esse caminho é o seu próprio disco, e é isso que torna legítimo escrever lá. Sobre o transporte **HTTP** seria o disco do *servidor*, então `out_dir` é **recusado** ali em vez de escrever arquivos em algum lugar que você não pode ler enquanto relata sucesso. A paginação é a única rota no HTTP.

Sem `out_dir`, os arquivos são empacotados na resposta até um orçamento de caracteres e o resto volta como **`delivery.pendingPaths`**. Essa lista é o contrato: **uma resposta com um `pendingPaths` não vazio é uma página, não uma árvore.** Continue chamando `eject_viz_files` com ela até que volte vazia.

## Leia `manifest.dataLane` antes de escrever qualquer conexão de dados[​](#leia-manifestdatalane-antes-de-escrever-qualquer-conexão-de-dados "Link direto para leia-manifestdatalane-antes-de-escrever-qualquer-conexão-de-dados")

Esta é a primeira decisão e a que você não pode mudar depois, porque ela decide quem pode ler o painel.

### Via A — `dataLane.id == "wasm"`: ela já renderiza dados reais[​](#via-a--datalaneid--wasm-ela-já-renderiza-dados-reais "Link direto para via-a--datalaneid--wasm-ela-já-renderiza-dados-reais")

Para uma visualização **pública e publicada**, a ejeção emite um pequeno runner (`vibedasherWasm.ts`) e a árvore renderiza **linhas reais com `npm run dev` sem credencial nenhuma**: sem chave de API, sem token de incorporação, sem backend seu, e funciona de qualquer origem, incluindo `localhost` e um simples servidor de arquivos estáticos.

O mecanismo: um `GET` anônimo do plano wasm da visualização devolve um extrato Parquet pré-assinado por conjunto de dados; o runner registra cada um sob seu alias SQL no DuckDB-WASM e executa o SQL de cada gráfico no navegador do visitante. Sua única dependência, `@duckdb/duckdb-wasm`, está no npm. O `main.tsx` aguarda o aquecimento do runner antes de montar, então uma configuração quebrada é **um painel pintado** em vez de uma grade de tiles em branco.

Não há nada a conectar aqui. **Não adicione uma credencial por cima** — isso compra uma inicialização quebrada em troca de nada.

O que essa via não é: ela serve o extrato **publicado**, não uma consulta viva, então atualizar o que ela mostra significa republicar. E ela existe apenas enquanto a visualização permanecer pública e publicada.

**O que cada visitante recebe, dito sem rodeios.** O extrato é indexado por conjunto de dados e snapshot, nunca por uma consulta — o mesmo `SELECT` com e sem `WHERE` devolve o arquivo idêntico. Então ele contém todas as linhas de cada conjunto de dados que o painel lê, e é um único arquivo, o mesmo, para todo mundo. Um filtro que você adiciona na página muda o que é desenhado, nunca o que foi entregue, e os bytes estão na aba de rede de qualquer forma. Isso é inerente a colocar os dados no navegador. Se os visualizadores precisam ver linhas diferentes, tome a via B.

### Via B — `dataLane.id == "token"`: qualquer outra visualização[​](#via-b--datalaneid--token-qualquer-outra-visualização "Link direto para via-b--datalaneid--token-qualquer-outra-visualização")

Não existe via sem credenciais e a ejeção não emitiu nenhuma. O endpoint do plano wasm é protegido pela mesma checagem de público-e-publicado que o resto da superfície anônima, então apontar para ele à mão não ajuda. O `npm run dev` ainda monta, e pinta um painel nomeando o endpoint de que precisa — ele não falha silenciosamente e não substitui por dados falsos.

Duas ligações funcionam, e **as duas colocam a credencial em um servidor que você controla**:

1. **Seu app serve uma rota de token de incorporação.** Mesma origem, respondendo `application/json` `{"token": "..."}`, emitido com a chave de API que ele guarda no servidor. Aponte a URL de token de incorporação da árvore para ela e construa o cliente com o token.
2. **Você consulta em código de servidor confiável** com `new Vibedasher({ apiKey })` e passa o resultado adiante, ou faz proxy de `{ sql, params }` pela sua própria rota.

**Nunca coloque uma chave de API em código de navegador.** Ela carrega autoridade total da conta e não tem escopos. Um token de incorporação com escopo é a credencial do lado do cliente; uma chave de API não é.

Essas duas são também as **únicas** formas capazes de mostrar a um visualizador menos linhas do que a outro. Um token de incorporação pode carregar `filters`, que a plataforma acrescenta a toda consulta que autoriza — no servidor, antes de qualquer join, agregação ou `LIMIT`, onde a página não pode alargá-los. Em código de servidor confiável, você mesmo restringe o SQL.

Se a ejeção relatar a via `token` com a razão `no-wasm-extract` em uma visualização que *é* pública e publicada, é só que o conjunto de dados ainda não tem extrato. Pedir um é a única coisa que o constrói — veja [A via de consulta wasm](/pt-BR/guides/the-wasm-query-lane.md#you-have-to-ask-for-the-extract-before-it-exists). Republicar não assa um extrato, então "publicar e ejetar de novo" roda em círculos para sempre.

## Instale o cliente[​](#instale-o-cliente "Link direto para Instale o cliente")

```
npm i @vibedasher/client
```

```
pip install vibedasher
```

O pacote npm é **`@vibedasher/client`**. Não existe `@vibedasher/sdk` e nunca vai existir. Instale os pacotes reais em vez de copiá-los para dentro do seu repositório.

## Estilos, typecheck e o App Router[​](#estilos-typecheck-e-o-app-router "Link direto para Estilos, typecheck e o App Router")

**O estilo são três peças e você precisa das três.** `tailwind.config.js` (modo escuro por uma estratégia de seletor atrelada a um atributo que a árvore coloca na sua própria raiz, de modo que o modo escuro do painel fica delimitado independentemente do do seu app, mais uma safelist para as classes montadas em tempo de execução e portanto invisíveis ao scanner do Tailwind); `styles/dashboard.css` (a entrada do Tailwind e um bloco `:root` de tokens de design como padrões de tempo de build); e `themeStyles.ts` (os mesmos tokens como uma string que o app injeta em tempo de execução, por tema). O CSS dá a você uma primeira pintura tematizada; o `themeStyles.ts` dá a troca claro/escuro ao vivo. Se o seu app já usa Tailwind com modo escuro baseado em classe, não mescle as configurações — uma configuração do Tailwind suporta um único ajuste de modo escuro. No Tailwind v4, referencie a configuração que vem junto com `@config`.

**Typecheck.** `npm run typecheck` (`tsc --noEmit`) deve relatar zero erros em uma árvore recém-ejetada, e dois gates sustentam isso em cada execução da nossa suíte de testes — um faz typecheck de uma ejeção gerada completa, o outro instala, faz typecheck *e* constrói uma escrita à mão. Duas condições limitam a afirmação, e nenhuma delas é uma ressalva evasiva. Ela vale **sob o `tsconfig.json` que vem junto**, que define `strict: false` de propósito, porque os efeitos de gráfico gerados são escritos contra linhas de consulta fracamente tipadas; ligar o strict vai produzir erros que passam a ser seus para manter. E ela vale para uma árvore cujos arquivos de branch foram escritos recentemente — um painel publicado há tempo suficiente carrega arquivos congelados de uma forma mais antiga, dois pontos dos quais são conhecidos por falhar no `tsc` mesmo construindo e rodando corretamente, já que o Vite transpila sem fazer typecheck. A correção é **republicar**, o que reescreve os arquivos de branch a partir da forma atual.

**Next.js App Router.** Todo módulo é um server component por padrão. O `App.tsx` e os módulos `stores/` já carregam `'use client'` na ejeção — os stores tocam `localStorage` no escopo do módulo e quebrariam no servidor. Marcar o `App.tsx` coloca todo o seu grafo de imports no bundle do cliente, então nada mais precisa da diretiva, a menos que você importe um gráfico, componente ou store diretamente de um server component.

## O que a ejeção não entrega a você[​](#o-que-a-ejeção-não-entrega-a-você "Link direto para O que a ejeção não entrega a você")

* **Qualquer coisa em `manifest.internalDescriptors`** — metadados de plataforma, já excluídos de `files`. Não os copie.
* **Qualquer coisa em `manifest.hostOnlyImports`** — servido a partir do bundle próprio do nosso renderizador, em registro nenhum, deliberadamente ausente. Substitua-os antes de construir.
* **A plataforma de dados.** A ejeção nos tira do caminho de renderização, não do caminho de dados. As conexões, conjuntos de dados, atualizações, projetos dbt, a governança e o motor de consultas por baixo ficam onde são mantidos — essa é a metade da divisão que você não estava tentando possuir. A via sem credenciais é a única exceção, e só porque ela está lendo um extrato publicado em vez de consultar ao vivo.
