# 05 — Le format `motion` : la vidéo avec emaki

**Statut : fait · Phase : P4 · Mis à jour : 25/10/2026.**

Source : the emaki repository. Ce document reprend `emaki/PLAN.md` et répond à ses questions
ouvertes, puisque **le projet hôte est maintenant Récit**.

## 0. Ce qu'apporte emaki

- 10 packages `@emaki/*` en `0.1.0-beta.7` (`studio` est privé) :

  | Package   | Rôle                                                                                                      |
  | --------- | --------------------------------------------------------------------------------------------------------- |
  | `schema`  | `Deck` zod 4, `parseDeck` (seule source de vérité), ops                                                   |
  | `core`    | moteur et timing                                                                                          |
  | `ui`      | primitives                                                                                                |
  | `blocks`  | `title`, `chapter`, `statement`, `stat`, `list`, `compare-bars`, `ui-mock`, `ui-scene` (framer-motion 12) |
  | `themes`  | `warm-editorial`, `saas-product`                                                                          |
  | `render`  | Remotion 4 vers MP4                                                                                       |
  | `extract` | le _handover_, format souple sans `id`                                                                    |
  | `mcp`     | serveur MCP                                                                                               |
  | `cli`     | commande `emaki`                                                                                          |
  | `studio`  | éditeur Monaco                                                                                            |

- Formats d'image : `16:9`, `1:1`, `9:16`.
- Garde-fous : goldens PNG (`fixtures/goldens/saas-*.png` dans emaki, aujourd'hui
  `packages/formats/motion-render/fixtures/goldens/`, `pnpm -F @recit/motion-render goldens:check`),
  tests vitest et règle « aucune métrique inventée ».
- « Deux chemins de rendu qui correspondent » : le même moteur de blocs joue dans le navigateur
  (prévisualisation, studio) et dans Remotion (MP4). C'est ce qui permettra d'intégrer une vidéo
  **jouable** dans une page du framework de sites sans Remotion.

## 1. Réponses aux questions ouvertes de `emaki/PLAN.md`

| Question                          | Réponse                                                                                                                                                                                                             |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Projet hôte ?                     | **Récit** (ce fork) : pnpm 11, Node ≥ 22.22, nx, React 19.2, zod 4.6, TS 6, Vite 8                                                                                                                                  |
| Mode d'intégration (phase 1)      | **Copie**, comme presentation-engine (`04-migration.md`) : le `.git` local d'emaki a été supprimé. Référence : dernier commit `1b86ccd` (`0.1.0-beta.7`), cité dans chaque commit d'import                          |
| Emplacement et scope              | copie directe dans les dossiers finaux (`packages/formats/motion*`, `packages/mcp`), sans dossier `emaki/` intermédiaire. Le scope devient `@recit/*` (voir le tableau de `03-packaging.md`)                        |
| Emaki reste-t-il publiable seul ? | Non : les packages deviennent `@recit/motion*`. Les `@emaki/*@beta` déjà publiés reçoivent un `npm deprecate` qui pointe vers `@recit/motion`                                                                       |
| Markdown par défaut ?             | **Oui**, dans Récit. Le Markdown est la porte d'entrée commune, et le JSON reste supporté (`deck.json`) comme format machine et MCP                                                                                 |
| Format hybride, doc vers film ?   | **Oui, c'est la promesse de Récit** : un même `story.md` se rend en deck (`recit build`) et en vidéo (`recit render`). Une page MDX de la doc peut intégrer l'un ou l'autre (`<StoryEmbed slug format="motion" />`) |

## 2. Phases

Les phases V2 à V4 peuvent démarrer **dans le dépôt emaki**, avant l'import, comme le prévoyait
`emaki/PLAN.md`. Il faut juste écrire la spec Markdown dans la syntaxe Récit (V3).

### V1 — Import dans le fork (S/M), reprend les phases 1 et 2 d'emaki

**Fait le 24/10/2026** (`journal.md` §2) : préparé dans une copie du dépôt, puis rapatrié. Dépôt
réuni vérifié : `pnpm check`, build et typecheck de 23 projets, 1 938 tests, 16 goldens, 139 e2e.

- [x] Dans emaki (sans git) : vérifier `pnpm build && pnpm test && pnpm goldens:check` avant la
      copie.
- [x] Copier chaque package dans son dossier final
      (`packages/formats/{motion,motion-blocks,motion-render}`, `packages/mcp`). L'origine
      (`emaki@1b86ccd`, l'auteur) est dans `NOTICE.md`, le `README.md` et l'en-tête de
      `src/index.ts` de chaque package ; les commits sont faits par l'utilisateur. `extract` est
      fusionné dans `@recit/motion` ; le studio n'est pas importé (squelette
      `packages/tools/studio`).
- [x] Ne pas copier l'outillage d'emaki (`turbo.json`, `tsconfig.base.json`, `pnpm-workspace.yaml`,
      `.changeset/`, lockfile) : nx, la base TS et le catalogue du fork le remplacent.
- [x] `allowBuilds` : `esbuild: true` suffit pour Remotion, rien d'autre n'a été ajouté.
- [x] Conserver la condition d'export `development`. Elle devient la convention de tous les
      `@recit/*` (voir `03-packaging.md`).
- [x] Dépendances :
  - React 19.2.8 et zod 4.6.5 viennent du catalogue ;
  - `react >=18` passe à `>=19.2.7` ;
  - `framer-motion` est remplacé par `motion` (`motion/react`) dès l'import : une seule stack ;
  - Remotion 4 et le SDK MCP restent locaux à `motion-render` et `mcp` ;
  - vérifier `minimumReleaseAge`.
  - **Reste ouvert** : Remotion impose une seconde version de zod (4.4.3) dans l'arbre de
    `motion-render` (`@remotion/studio`). Une autre copie existait par le pair optionnel
    `site >=0.93.0` de `@recit/site-stories`, résolu sur la copie npm du framework de sites au lieu
    du fork (antérieur à P4). Relier le fork casse le typecheck de `@recit/site-stories` (les
    sources du fork ne passent pas la config TypeScript de Récit) : à traiter avec TE-3 de
    `audit.md`. Le 25/10, après `pnpm install`, zod 4.4.3 n'est plus tiré que par Remotion.
- [ ] Chrome Headless Shell (environ 93 Mo) : mettre en cache en CI (`~/.cache/remotion`), ou sauter
      le rendu MP4 en CI et ne garder que les goldens PNG. Reporté à V2 (pas encore de job motion en
      CI).
- [x] `.gitignore` : `out/` (rendus MP4 de `recit render`).
- [x] Goldens : scripts `goldens` et `goldens:check` de `@recit/motion-render`
      (`pnpm -F @recit/motion-render goldens:check`), plutôt que des scripts racine. Les 16 PNG ont
      été **régénérés** après le passage des fixtures `showcase-*` en Lorem Ipsum. Ils dépendent des
      polices installées sur la machine (tolérance de 0,2 % pour le lissage) : à figer dans un
      conteneur en CI (§4).
- [x] `AGENTS.md` d'emaki : généré dans `packages/formats/motion/AGENTS.md`, **pas** à la racine
      (qui reste le guide du framework de sites). Reste à le référencer depuis `context.md`.
- [x] MCP : `claude mcp add recit -- npx recit mcp serve` (fusion du CLI, V6). Le binaire
      `recit-mcp` de `@recit/mcp` reste disponible.

**Critère de sortie :** depuis la racine du fork,
`nx run-many -t build test -p '@recit/motion*' @recit/mcp` et `goldens:check` passent (atteint le
24/10). `recit render fixtures/hello.deck.json` produit le même MP4 que `emaki render` avant
l'import : reporté à la fusion du CLI d'emaki (V6), mis de côté dans `scratch-emaki-cli/`.

### V2 — Un seul chargeur de deck (S), reprend la phase 3

**Fait** : `@recit/motion/source` (`formatOf`, `parseDeckSource`) et `@recit/motion/node`
(`readDeckFile`, `writeDeckFile`), utilisés par `motion-render` (`loadDeck`, script des goldens) et
`mcp` (`apply_ops`, `build_deck`, `extract`). Le CLI et le studio suivront en V6.

Sans changement par rapport à `emaki/PLAN.md` :

- `formatOf`, `parseDeckSource`, `readDeckFile` et `writeDeckFile` remplacent les 5
  `JSON.parse(readFileSync(…))` (cli, mcp `readDeck`, render `loadDeck`, studio `store.ts`) et les 4
  `writeFileSync(JSON.stringify(…))`.
- Les tests existants passent sans modification.

### V3 — Spec Markdown du format motion (S), reprend la phase 4 avec la syntaxe Récit

C'est **le changement principal** par rapport au plan emaki : pas de seconde syntaxe. Un récit
`format: motion` s'écrit comme un deck :

```md
---
title: Hello, Récit
format: motion
aspectRatio: 9:16 # alias accepté : aspect
motion:
  theme: warm-editorial
  wordsPerSecond: 2.6
---

--- id=open

Lorem

# Ipsum dolor sit amet.

--- id=why

Lorem ipsum. **Dolor sit amet.**

:::notes
Voice-over: lorem ipsum dolor sit amet.
:::

--- type=stat id=proof hold=1

# 30s

vertical film

_rendered locally_
```

- Les attributs de scène sur le séparateur (`type`, `id`, `hold`, `dur`, `unit`, `number`,
  `kicker`…) remplacent `<!-- type: …, id: … -->`. Ce commentaire reste accepté comme alias (voir
  `02-architecture.md`, section 3).
- Le tableau d'inférence est **celui d'emaki, inchangé** (`title`, `chapter`, `statement`, `list`,
  `compare-bars`, `stat` jamais inféré, `ui-*` en yaml, échappatoire ` ```yaml ` / ` ```json `). Les
  équivalences avec les layouts de deck sont dans `02-architecture.md`, section 4.
- Pas d'inférence ambiguë : une erreur avec numéro de ligne
  (`story.md:14 (stat): label is required`).
- [x] Rédiger la spec dans `sites/docs/pages/formats/motion.mdx` (en anglais).
- [x] La valider sur `hello`, `saas`, `showcase-saas` et `showcase-warm` avant de coder
      (`packages/formats/motion/fixtures/*.story.md`).

### V4 — Parseur Markdown → Deck (M), reprend la phase 5

Au lieu d'un `@emaki/markdown` qui reparse avec son propre remark, on **réutilise `@recit/core`** :

```
story.md ─► compileStory (scènes, attrs, mdast, source.line) ─► storyToHandover ─► extractHandover ─► parseDeck ─► Deck
```

- [x] `storyToHandover(story)` dans `@recit/motion`. Il lit `scene.mdast` et `scene.attrs`, applique
      le tableau d'inférence et produit un `sourceMap` (scène → ligne) à partir de `scene.source`.
- [x] `parseMarkdownDeck(text)` remappe `HandoverIssue.scene` vers des lignes Markdown.
- [x] Le brancher dans `parseDeckSource(text, "md")` (V2).
- [x] Le lint texte (glyphes non couverts par les polices du thème) s'applique aussi au `.md`.
- [x] `:::notes` → champ `voiceover` (ou `caption`) du schéma `Deck`. C'est un ajout sans rupture,
      en `version: 1`.

Tests :

- [x] **Équivalence** : `hello.story.md`, `saas.story.md` et `showcase-*.story.md` donnent un `Deck`
      égal (`toEqual`) à leur `.deck.json`.
- [x] **Goldens** : les PNG rendus depuis le `.md` sont identiques aux goldens actuels.
- [x] **Erreurs** : un cas par règle d'inférence, avec le bon numéro de ligne.

### V5 — Sérialiseur Deck → Markdown et réécriture (M/L), reprend la phase 6

- [x] `deckToMarkdown(deck)` : sortie canonique **dans la syntaxe Récit** (attributs de séparateur,
      `id=` toujours émis, bloc yaml pour les props sans forme naturelle).
- [x] Propriété testée : `parse(deckToMarkdown(deck))` est égal à `deck` pour toutes les fixtures.
- [ ] Réécriture : **v1** canonique, documentée (faite : `writeDeckFile` sur un `.md`). **v2**
      chirurgicale via `scene.source` : seules les scènes touchées par `replace`, `patch`,
      `insertAfter` et `remove` sont réécrites, le reste est conservé à l'octet près.
- [x] CLI : `recit convert deck.json --to md` et l'inverse, `recit init --format motion`.

### V6 — Intégration à Récit : CLI, site, MCP, studio (M), reprend la phase 7

CLI (`@recit/cli`) :

- [x] `recit render story.md [--aspect 9:16] [--output out/]` charge `@recit/motion-render` à la
      demande. Si le package manque, le message d'erreur explique quoi installer.
- [x] `recit validate`, `recit dur` (durée totale) et `recit extract` reprennent les commandes
      `emaki`.
- [x] `recit dev story.md` : si `format: motion`, prévisualisation dans le navigateur avec le moteur
      de blocs et le timing, live reload. Le lecteur est celui de `@recit/motion-blocks`
      (`MotionFrame`, `filmTimeline`) : `recit dev`, `recit dur`, la page et le MP4 coupent sur les
      mêmes images. `recit render` lit `recit.config` comme `validate` et `dur`.

Site et le framework de sites (`@recit/site-stories`) :

- [x] `<StoryEmbed slug="hello" format="motion" />` joue la vidéo dans la page avec le moteur
      navigateur de `@recit/motion-blocks`, sans Remotion ni MP4. Un MP4 prérendu peut être fourni
      en `poster`/`src` pour le SSR. Reste : pas de job CI qui rend les MP4 de `/video`, et pas de
      redirection `video.recit.dev` configurée.
- [x] Route `/present/<slug>` : lecteur plein écran pour un récit `motion`.
- [x] Onglet **Formats › Motion** dans `sites/docs` et site `sites/motion` (motion.recit.dev,
      `video.recit.dev` en alias, voir `07-domaines.md`) : spec (V3), galerie d'exemples, aspects
      `16:9`, `1:1` et `9:16`.
- [x] CSS des blocs : même stratégie que pour le deck, limitée au wrapper avec l'ordre des layers.

MCP (`@recit/mcp`) :

- [x] Tous les outils `deckPath` acceptent `.md` (fait en V2 : `apply_ops` et `render` lisent par
      `readDeckFile`, `out` en `.md` écrit du Markdown).
- [x] `build_deck` accepte `markdown`. Ajouter l'outil `describe_format(format)` (JSON Schema et
      spec Markdown, `09-langues-et-json.md` §7). La version du serveur MCP est lue dans son
      `package.json`.
- [x] Le guide `emaki://guide` devient `recit://guide/motion`. Ajouter `recit://guide/deck` pour les
      decks.

Studio (`@recit/studio`, privé ; squelette, import du studio d'emaki différé tant qu'il est simulé,
`journal.md` §5) :

- [ ] Ouverture et hot-reload d'un `.md`, Monaco en mode `markdown`, erreurs avec numéro de ligne.

### V7 — Renommage et unification (M)

- [x] Thèmes : exprimer `warm-editorial` et `saas-product` comme thèmes `@recit/theme` (tokens HSL
      partagés). Un récit garde le même thème en deck et en vidéo. Fait : `RECIT_THEMES` de
      `@recit/theme` (tokens HSL, polices, palette de données, échelle typographique motion),
      `toMotionTheme` dans `@recit/theme/motion`, `@recit/motion-blocks` n'a plus de copie. Le
      sélecteur est `theme: <nom>` (front matter) ou `theme.name` (`recit.config`) ; le film le lit
      après `motion.theme`. Goldens inchangés (0,000 %). Doc : `docs.recit.dev/theme/named-themes`.
- [ ] `npm deprecate "@emaki/*" "moved to @recit/motion"` : **à faire par l'auteur d'emaki**
      (mainteneur npm `yathavan`), pas par Récit. Nous n'avons pas les droits sur le scope `@emaki`
      et Récit ne publie rien sur npm avant P5.

## 3. Ordre

```
V2 ─► V3 ─► V4     (possibles dans le dépôt emaki, avant l'import)
V1 (import) ─► V4 rebasé sur @recit/core ─► V5 ─► V6 ─► V7
```

V4 dépend de `@recit/core` avec `scene.mdast`, `scene.attrs` et `scene.source`, donc de la phase 1
de la roadmap (import de presentation-engine). Si on commence dans emaki, un `split` et un remark
minimal font office de bouchon, remplacé ensuite par `compileStory`.

## 4. Risques propres à la vidéo

| Risque                                                                         | Parade                                                                                                |
| ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| Remotion et Chrome alourdissent tout le monorepo                               | `@recit/motion-render` isolé, dépendance optionnelle de la CLI, rendu MP4 hors CI par défaut          |
| Licence Remotion (licence d'entreprise au-delà d'une certaine taille d'équipe) | la vérifier avant publication et la documenter dans `NOTICE.md`. Le chemin navigateur n'en dépend pas |
| Deux syntaxes Markdown qui divergent                                           | une seule : celle de Récit. Le commentaire HTML n'est qu'un alias                                     |
| Un layout de deck sans équivalent vidéo                                        | erreur explicite en v1, puis rendu en image fixe (capture de la slide) en v2                          |
| `framer-motion` contre `motion` en double dans le bundle                       | `motion` seul dès l'import (V1), `size-limit` sur `@recit/site-stories`                               |
| Goldens instables entre macOS et la CI Linux                                   | goldens générés en CI (conteneur fixe), tolérance de diff documentée                                  |
