> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wrappy.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Assets de scène & CDN

> Comment les assets de scène (modèles 3D, images, vidéos) sont servis, versionnés par namespace et utilisés lors du développement d'une scène.

## En bref

Les assets « lourds » d'une scène (modèles `.glb`, textures, images de template, vidéos, PDF) ne sont **pas** embarqués en dur dans l'app `open`. Ils sont servis depuis un **CDN Cloudflare (R2 public)**, résolus au runtime par le helper `asset()` de `@wrappy/core`.

Deux familles d'assets, à ne pas confondre :

| Famille                                   | Exemple                                                   | Où                         | Résolu par                  |
| ----------------------------------------- | --------------------------------------------------------- | -------------------------- | --------------------------- |
| **Template de scène** (partagé, statique) | le modèle 3D de l'invitation, le ticket, la vidéo d'intro | Bucket R2 **public** + CDN | `asset(namespace, path)`    |
| **UGC par invitation** (dynamique)        | les photos uploadées par l'organisateur                   | Bucket R2 **privé** (UGC)  | `payload.attachments[].url` |

Cette page couvre la **première** famille. L'UGC par invitation passe par le module `storage` de l'API et le champ `attachments` du payload.

## Le schéma de versionning

L'URL d'un asset template suit toujours cette forme :

```
<racine-CDN>/<namespace>/<version>/<chemin>
```

* **`namespace`** = la scène (ou le composant partagé) propriétaire de l'asset. C'est le slug de la scène : `fete-des-laurens`, `web-form`, `3d-book`, `gift-physics`…
* **`version`** = `v1`, `v2`… **versionnée par namespace** (voir plus bas). Chaque scène évolue à son rythme.
* **`chemin`** = le chemin de l'asset dans son dossier (`/models/x.glb`, `/images/ticket.png`).

Exemple concret :

```
https://cdn.wrappy.app/templates/scenes/fete-des-laurens/v1/models/fete-des-laurens-v2.glb
└──────────── racine CDN ────────────┘└── ns ──────┘└v┘└───── chemin ─────┘
```

<Note>
  Le versionning est **par namespace**, pas global. Changer un asset de `web-form` bumpe
  `web-form` en `v2` sans toucher `fete-des-laurens`, qui reste en `v1`.
</Note>

### Pourquoi versionner

Les dossiers publiés sont **immuables** : on ne réécrit jamais un `vN` déjà en ligne. Une invitation déjà envoyée a été buildée avec `VITE_ASSET_BASE_URL` pointant sur une version donnée — si on écrasait les assets, elle changerait d'apparence après coup. Une révision d'asset = **nouveau dossier `v2`**, l'ancien continue de vivre.

## Comment ça marche

### Le helper `asset()`

`packages/core/src/lib/assets.ts` :

```ts theme={null}
const CDN_ROOT =
  import.meta.env.VITE_ASSET_BASE_URL || "/templates/scenes";

// Version courante de chaque namespace.
const VERSIONS: Record<string, string> = {
  "fete-des-laurens": "v1",
  "web-form": "v1",
  "3d-book": "v1",
  "gift-physics": "v1",
};

export function asset(namespace: string, path: string): string {
  const version = VERSIONS[namespace] ?? "v1";
  return `${CDN_ROOT}/${namespace}/${version}${path}`;
}
```

* **`VITE_ASSET_BASE_URL`** = la **racine CDN**, sans version (ex: `https://cdn.wrappy.app/templates/scenes`). La version n'est **pas** dans l'env var — elle vit dans `VERSIONS`.
* **`VERSIONS`** = la table qui fixe la version courante de chaque namespace. C'est le seul endroit à toucher pour bumper.

### Dev vs prod : même structure

En dev, `VITE_ASSET_BASE_URL` est vide → `CDN_ROOT` retombe sur `/templates/scenes`, servi statiquement par Vite depuis `apps/open/public/`.

**Le dossier `public/` est organisé exactement comme le CDN** : dev et prod résolvent le même chemin, aucune divergence.

```
apps/open/public/templates/scenes/
  fete-des-laurens/v1/models/fete-des-laurens-v2.glb
  web-form/v1/images/TICKET.png
  web-form/v1/videos/fete-des-laurens-synchro.mp4
  3d-book/v1/images/page-1.png
  gift-physics/v1/GiftBox.gltf
```

|                                           | Dev                                               | Prod                                                                    |
| ----------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------- |
| `VITE_ASSET_BASE_URL`                     | *(vide)*                                          | `https://cdn.wrappy.app/templates/scenes`                               |
| `asset("web-form", "/images/TICKET.png")` | `/templates/scenes/web-form/v1/images/TICKET.png` | `https://cdn.wrappy.app/templates/scenes/web-form/v1/images/TICKET.png` |
| Servi par                                 | Vite (`public/`)                                  | CDN Cloudflare                                                          |

## Développer une scène : utiliser `asset()`

<Steps>
  <Step title="Ranger les fichiers dans public/">
    Place tes assets sous `apps/open/public/templates/scenes/<ns>/v1/…`, en gardant une arborescence lisible (`/models`, `/images`, `/videos`).

    ```
    apps/open/public/templates/scenes/ma-scene/v1/models/hero.glb
    ```
  </Step>

  <Step title="Déclarer le namespace dans VERSIONS">
    Ajoute une entrée dans `VERSIONS` (`packages/core/src/lib/assets.ts`) :

    ```ts theme={null}
    const VERSIONS: Record<string, string> = {
      // …
      "ma-scene": "v1",
    };
    ```

    <Note>Si le namespace est absent de `VERSIONS`, `asset()` retombe sur `v1` par défaut — mais déclare-le explicitement pour pouvoir le bumper plus tard.</Note>
  </Step>

  <Step title="Référencer via asset() dans la scène">
    Ne mets **jamais** de chemin d'asset en dur. Passe toujours par `asset(namespace, path)` :

    ```tsx theme={null}
    import { asset } from "#/lib/assets";

    // Un asset
    const { scene } = useGLTF(asset("ma-scene", "/models/hero.glb"));

    // Une liste
    const PAGES = ["/images/p1.png", "/images/p2.png"]
      .map((p) => asset("ma-scene", p));
    ```

    <Warning>
      `asset` prend **deux** arguments. `["/a.png"].map(asset)` ne marche pas (`.map`
      passe l'index en 2ᵉ argument) — utilise `.map((p) => asset("ma-scene", p))`.
    </Warning>
  </Step>

  <Step title="Tester en dev">
    `bun run dev` et vérifie que l'asset charge (onglet Network → `200` sur `/templates/scenes/ma-scene/v1/…`). Comme `public/` reflète le CDN, ce qui marche en dev marchera en prod.
  </Step>
</Steps>

### Assets partagés entre scènes

Si un **composant** est réutilisé par plusieurs scènes (ex: le livre 3D `3DBook`, embarqué par `fete-des-laurens` **et** `web-form`), ses assets vivent sous **son propre namespace** (`3d-book`), pas dupliqués sous chaque scène :

```tsx theme={null}
// dans le composant partagé
const COVER = asset("3d-book", "/images/book-cover.png");
```

## Bumper un asset (nouvelle version)

Quand tu modifies un asset déjà publié en prod :

<Steps>
  <Step title="Créer le nouveau dossier de version">
    Copie les assets du namespace sous `…/<ns>/v2/…` dans `public/templates/scenes/`. **Ne touche pas** au dossier `v1`.
  </Step>

  <Step title="Bumper VERSIONS">
    ```ts theme={null}
    const VERSIONS = {
      "ma-scene": "v2", // ← était "v1"
    };
    ```

    Tous les `asset("ma-scene", …)` pointent maintenant sur `v2`. Aucun autre changement de code.
  </Step>

  <Step title="Publier sur le CDN">
    Voir la section suivante. Le `v1` reste en ligne pour les invitations déjà buildées.
  </Step>
</Steps>

## Publier sur le CDN

Le script `scripts/upload-scene-assets.sh` est un **miroir récursif** de `public/templates/scenes/` vers le bucket R2 public :

```bash theme={null}
./scripts/upload-scene-assets.sh wrappy-assets
```

Il pousse chaque fichier sous la même clé (`templates/scenes/<ns>/<version>/…`) via `wrangler`. Comme les dossiers de version sont immuables, republier ne réécrit que ce qui a changé (les nouveaux `vN`).

Côté build de `open`, poser :

```bash theme={null}
VITE_ASSET_BASE_URL=https://cdn.wrappy.app/templates/scenes
```

<Note>
  `VITE_ASSET_BASE_URL` est une variable **build-time** de Vite (préfixe `VITE_`),
  injectée via un `ARG`/`ENV` dans `apps/open/Dockerfile`. Elle est figée au build.
</Note>

## Ce qui reste hors CDN

Les petits assets **non liés à une scène** restent servis par l'app depuis `public/` (à la racine, pas sous `templates/scenes/`) : logos, favicon, `manifest.json`, sons courts (`page-flip.mp3`), image OG de fallback. Trop petits pour justifier le détour CDN.

## Récap

* Un asset de scène = `asset(namespace, path)`, jamais un chemin en dur.
* `namespace` = slug de la scène ; `path` = chemin dans son dossier.
* La version vit dans `VERSIONS` (`packages/core/src/lib/assets.ts`), **par namespace**.
* `public/templates/scenes/` reflète le CDN à l'identique → dev == prod.
* Bump = nouveau dossier `vN` + mise à jour de `VERSIONS`, jamais d'écrasement.
* Publication = `scripts/upload-scene-assets.sh`.
