# Éjecter votre tableau de bord

Publier sur Vibedasher est la fin normale de la boucle : c'est ce qui garde un tableau de bord hébergé, gouverné et relié à des données que quelqu'un maintient. **L'éjection est l'autre porte, et elle est ouverte exprès.** Le tableau de bord est du React et du SQL ordinaires, et à tout moment vous pouvez prendre l'arbre entier et le faire tourner dans votre propre application, sur votre propre hébergement, sous votre propre build.

Ce n'est pas un export d'une image ou d'un blob JSON qu'il faudrait réimplémenter. C'est la source qui tourne : composants, pages, graphiques, stores, hooks, un `package.json` reconstruit à partir des vrais imports de l'arbre, une configuration Tailwind, une entrée CSS, un `tsconfig.json`, une configuration Vite, un `index.html`, et un `main.tsx` qui le monte. `npm install && npm run dev` le sert comme sa propre application React.

remarque

L'éjection est le contraire de l'[intégration](/fr/api/embedding-the-share-iframe.md). L'intégration met *notre* tableau de bord rendu dans une iframe de votre page. L'éjection vous remet la source et nous retire entièrement du chemin de rendu — nous ne gardons que le moteur de données en dessous. Si la question est « est-ce que ce code est à moi », l'éjection est la réponse. Si vous êtes encore en train de choisir entre les deux, **[Intégrer ou éjecter](/fr/guides/embed-or-eject.md)** les met côte à côte.

## Les trois appels[​](#les-trois-appels "Lien direct vers Les trois appels")

* **`eject_viz(viz_id, out_dir=...)`** — l'arbre entier plus un **manifeste de câblage** : le contrat de requête, les tokens de thème, le point d'entrée et l'ordre de ses props, la liste des fichiers, la région, et la voie de données.
* **`eject_viz_files(viz_id, paths=[...])`** — le contenu des fichiers à la demande. C'est la relance quand une réponse a été paginée, et c'est aussi la façon de lire un fichier à bas coût.
* **`eject_instructions()`** — la référence de câblage, renvoyée sous forme de données structurées plutôt que de prose à analyser. Tout ce qui est sur cette page en est une lecture ; appelez-la quand vous voulez l'original lisible par une machine.

### Passez `out_dir`, et sachez sur quel transport vous êtes[​](#passez-out_dir-et-sachez-sur-quel-transport-vous-êtes "Lien direct vers passez-out_dir-et-sachez-sur-quel-transport-vous-êtes")

Un vrai tableau de bord fait de l'ordre de quarante et quelques fichiers et un demi-million de caractères. Avec `out_dir` défini sur un chemin **absolu**, l'arbre est écrit directement sur disque et la réponse reste petite quelle que soit la taille de l'arbre.

Sur le transport **stdio** — le lancement standard, où votre client MCP démarre le serveur comme un sous-processus local sous votre propre utilisateur — ce chemin est votre propre disque, et c'est ce qui rend légitime le fait d'y écrire. Sur le transport **HTTP** ce serait le disque du *serveur*, donc `out_dir` y est **refusé** plutôt que d'écrire des fichiers quelque part où vous ne pourriez pas les lire tout en rapportant un succès. La pagination est la seule route en HTTP.

Sans `out_dir`, les fichiers sont empaquetés dans la réponse jusqu'à un budget de caractères et le reste revient sous forme de **`delivery.pendingPaths`**. Cette liste est le contrat : **une réponse avec un `pendingPaths` non vide est une page, pas un arbre.** Continuez d'appeler `eject_viz_files` avec elle jusqu'à ce qu'elle revienne vide.

## Lisez `manifest.dataLane` avant d'écrire le moindre câblage de données[​](#lisez-manifestdatalane-avant-décrire-le-moindre-câblage-de-données "Lien direct vers lisez-manifestdatalane-avant-décrire-le-moindre-câblage-de-données")

C'est la première décision et celle que vous ne pourrez pas changer ensuite, parce qu'elle décide qui peut lire le tableau de bord.

### Voie A — `dataLane.id == "wasm"` : il affiche déjà de vraies données[​](#voie-a--datalaneid--wasm--il-affiche-déjà-de-vraies-données "Lien direct vers voie-a--datalaneid--wasm--il-affiche-déjà-de-vraies-données")

Pour une visualisation **publique et publiée**, l'éjection émet un petit runner (`vibedasherWasm.ts`) et l'arbre affiche de **vraies lignes avec `npm run dev` sans aucun identifiant** : pas de clé d'API, pas de jeton d'intégration, pas de backend à vous, et cela fonctionne depuis n'importe quelle origine y compris `localhost` et un simple serveur de fichiers statiques.

Le mécanisme : un `GET` anonyme du plan wasm de la visualisation renvoie un extrait Parquet présigné par jeu de données ; le runner enregistre chacun sous son alias SQL dans DuckDB-WASM et exécute le SQL de chaque graphique dans le navigateur du visiteur. Sa seule dépendance, `@duckdb/duckdb-wasm`, est sur npm. `main.tsx` attend la mise en chauffe du runner avant de monter, si bien qu'une configuration cassée donne **un seul panneau peint** plutôt qu'une grille de tuiles vides.

Il n'y a rien à câbler ici. **N'ajoutez pas d'identifiant par-dessus** — cela vous achète un démarrage cassé en échange de rien.

Ce que cette voie n'est pas : elle sert l'extrait **publié**, pas une requête vivante, donc rafraîchir ce qu'elle montre veut dire republier. Et elle n'existe que tant que la visualisation reste publique et publiée.

**Ce que reçoit chaque visiteur, dit franchement.** L'extrait est indexé par jeu de données et instantané, jamais par une requête — le même `SELECT` avec et sans `WHERE` renvoie le fichier identique. Donc il contient toutes les lignes de chaque jeu de données que lit le tableau de bord, et c'est un seul fichier, le même, pour tout le monde. Un filtre que vous ajoutez dans la page change ce qui est dessiné, jamais ce qui a été livré, et les octets sont dans l'onglet réseau dans les deux cas. C'est inhérent au fait de mettre les données dans le navigateur. Si les visionneurs doivent voir des lignes différentes, prenez la voie B.

### Voie B — `dataLane.id == "token"` : toute autre visualisation[​](#voie-b--datalaneid--token--toute-autre-visualisation "Lien direct vers voie-b--datalaneid--token--toute-autre-visualisation")

Il n'y a pas de voie sans identifiants et l'éjection n'en a pas émis une. L'endpoint du plan wasm est protégé par la même vérification public-et-publié que le reste de la surface anonyme, donc le pointer à la main n'aide pas. `npm run dev` monte quand même, et peint un panneau qui nomme l'endpoint dont il a besoin — il n'échoue pas silencieusement et il ne substitue pas de fausses données.

Deux câblages fonctionnent, et **les deux mettent l'identifiant sur un serveur que vous contrôlez** :

1. **Votre application sert une route de jeton d'intégration.** Même origine, répondant en `application/json` `{"token": "..."}`, frappé avec la clé d'API qu'elle détient côté serveur. Pointez l'URL de jeton d'intégration de l'arbre dessus et construisez le client avec le jeton.
2. **Vous interrogez dans du code serveur de confiance** avec `new Vibedasher({ apiKey })` et passez le résultat vers le bas, ou relayez `{ sql, params }` à travers votre propre route.

**Ne mettez jamais une clé d'API dans du code de navigateur.** Elle porte l'autorité complète du compte et n'a aucun scope. Un jeton d'intégration à portée restreinte est l'identifiant côté client ; une clé d'API ne l'est pas.

Ces deux-là sont aussi les **seules** formes capables de montrer moins de lignes à un visionneur qu'à un autre. Un jeton d'intégration peut porter des `filters`, que la plateforme ajoute à chaque requête qu'elle autorise — côté serveur, avant toute jointure, tout agrégat ou tout `LIMIT`, là où la page ne peut pas les élargir. Dans du code serveur de confiance, vous restreignez le SQL vous-même.

Si l'éjection rapporte la voie `token` avec la raison `no-wasm-extract` sur une visualisation qui *est* publique et publiée, c'est simplement que le jeu de données n'a pas encore d'extrait. En demander un est la seule chose qui le construit — voir [La voie de requête wasm](/fr/guides/the-wasm-query-lane.md#you-have-to-ask-for-the-extract-before-it-exists). Republier ne cuit pas d'extrait, donc « publier et rééjecter » boucle indéfiniment.

## Installez le client[​](#installez-le-client "Lien direct vers Installez le client")

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

```
pip install vibedasher
```

Le paquet npm est **`@vibedasher/client`**. Il n'y a pas de `@vibedasher/sdk` et il n'y en aura jamais. Installez les vrais paquets plutôt que de les recopier chez vous.

## Styles, typage, et l'App Router[​](#styles-typage-et-lapp-router "Lien direct vers Styles, typage, et l'App Router")

**Le style est trois pièces et il vous faut les trois.** `tailwind.config.js` (mode sombre sur une stratégie de sélecteur liée à un attribut que l'arbre pose sur sa propre racine, si bien que le mode sombre du tableau de bord est délimité indépendamment de celui de votre application, plus une safelist pour les classes assemblées à l'exécution et donc invisibles au scanner de Tailwind) ; `styles/dashboard.css` (l'entrée Tailwind et un bloc `:root` de tokens de design comme valeurs par défaut au build) ; et `themeStyles.ts` (les mêmes tokens sous forme de chaîne que l'application injecte à l'exécution, par thème). Le CSS vous donne un premier rendu thémé ; `themeStyles.ts` vous donne le basculement clair/sombre à chaud. Si votre application utilise déjà Tailwind avec un mode sombre basé sur une classe, ne fusionnez pas les configurations — une configuration Tailwind ne supporte qu'un seul réglage de mode sombre. Sur Tailwind v4, référencez la configuration livrée avec `@config`.

**Typage.** `npm run typecheck` (`tsc --noEmit`) est censé rapporter zéro erreur sur un arbre fraîchement éjecté, et deux gates le garantissent à chaque exécution de notre suite de tests — l'un type-vérifie une éjection générée complète, l'autre installe, type-vérifie *et* construit une éjection écrite à la main. Deux conditions bornent cette affirmation, et ni l'une ni l'autre n'est une échappatoire. Elle tient **sous le `tsconfig.json` livré**, qui met `strict: false` exprès, parce que les effets de graphique générés sont écrits contre des lignes de requête faiblement typées ; activer strict produira des erreurs qui seront alors à vous de maintenir. Et elle tient pour un arbre dont les fichiers de branche ont été écrits récemment — un tableau de bord publié il y a assez longtemps porte des fichiers gelés d'une forme plus ancienne, dont deux endroits sont connus pour échouer à `tsc` tout en se construisant et en s'exécutant correctement, puisque Vite transpile sans type-vérifier. Le correctif est de **republier**, ce qui réécrit les fichiers de branche à partir de la forme actuelle.

**Next.js App Router.** Chaque module est un composant serveur par défaut. `App.tsx` et les modules `stores/` portent déjà `'use client'` dans l'éjection — les stores touchent `localStorage` à la portée du module et planteraient sur le serveur. Marquer `App.tsx` met tout son graphe d'imports dans le bundle client, si bien que rien d'autre n'a besoin de la directive à moins que vous n'importiez un graphique, un composant ou un store directement depuis un composant serveur.

## Ce que l'éjection ne vous remet pas[​](#ce-que-léjection-ne-vous-remet-pas "Lien direct vers Ce que l'éjection ne vous remet pas")

* **Tout ce qui est dans `manifest.internalDescriptors`** — des métadonnées de plateforme, déjà exclues de `files`. Ne les copiez pas.
* **Tout ce qui est dans `manifest.hostOnlyImports`** — servi depuis le bundle propre à notre moteur de rendu, sur aucun registre, délibérément absent. Remplacez-les avant de construire.
* **La plateforme de données.** L'éjection nous retire du chemin de rendu, pas du chemin de données. Les connexions, jeux de données, actualisations, projets dbt, la gouvernance et le moteur de requêtes en dessous restent là où ils sont maintenus — c'est la moitié du partage que vous ne cherchiez pas à posséder. La voie sans identifiants est la seule exception, et seulement parce qu'elle lit un extrait publié au lieu d'interroger en direct.
