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.
Ejetar é o oposto de incorporar. 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 coloca as duas lado a lado.
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á
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
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
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
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:
- 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. - 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.
Republicar não assa um extrato, então "publicar e ejetar de novo" roda em círculos para sempre.
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
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ê
- Qualquer coisa em
manifest.internalDescriptors— metadados de plataforma, já excluídos defiles. 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.