02 — Architecture cible
Statut : accepté · Phase : transverse · Mis à jour : 25/10/2026.
1. Vue d'ensemble
Code
2. Le modèle Story
On généralise CompiledDeck (presentation-engine/packages/core/src/types). Les champs propres à
un format sont sous une clé dédiée. Le cœur ne connaît aucun format.
TypeScript
compileDeckdevientcompileStory.compileDeckreste un alias pendant la migration.- Le deck utilise
scene.html,stepsetnotesHtml, comme aujourd'hui. - Le motion relit
scene.mdastetscene.attrspour produire un handover emaki (section 4). - Les composants MDX restent compilés côté CLI ou Vite, un module par scène
(
<file>.recit-scene-N.jsx, ex-presentation-slide-N.jsx, voir 6.2).
2 bis. Entrées : Markdown, JSON et langues
compileStory accepte un .md/.mdx ou un .json typé (StorySource), complété par des
compagnons JSON et des variantes de langue (example.fr.md, example.fr.json). Le Markdown est
d'abord converti en StorySource, puis tout suit le même chemin. La spécification complète (schémas
zod, JSON Schema publiés, fusion par id de scène, résolution des langues et repli) est dans
09-langues-et-json.md.
Code
3. Syntaxe commune
La syntaxe de presentation-engine est la référence. Elle est déjà documentée dans
presentation-engine/docs/pages/documentation/writing.
Markdown
| Élément | Syntaxe |
|---|---|
| Métadonnées du récit | frontmatter YAML |
| Séparateur de scène | --- seul, ou --- clé=valeur … (attributs de scène) |
| Blocs | directives :::card, :::callout, :::step, :::left / :::right, :::col, :::code-group, :::component{name=…}, :::notes |
| Inline | :badge[…]{type=…}, ::image{src alt fit}, :kbd[…] |
| MDX | .mdx : imports ESM et composants React |
Registre des directives (règle B4, 06-roadmap.md). Le cœur ne garde que les noms des
directives et des attributs réservés. Le plugin de chaque bloc vit dans la fondation qui le porte :
::chart dans @recit/data, ::map dans @recit/geo, :term (P14, plan 12) et le bloc
```pixel dans @recit/components. Attributs réservés : type=, entry=, end, prompt=,
cast=, beat=, set=, shot=, pose=, expression=, camera=, background (jamais bg). Le
mot kind est réservé au discriminant des documents JSON (09-langues-et-json.md §2.2) : aucun
réglage ni attribut ne s'appelle kind. Les réglages du frontmatter sont à plat
(08-formats.md) ; la forme imbriquée n'existe que dans recit.config.
Décision à valider : les directives de scène d'emaki. emaki/PLAN.md (phase 4) propose
<!-- type: stat, id: proof, hold: 1 -->, invisible sur GitHub. Récit retient les attributs du
séparateur (--- type=stat id=proof hold=1) comme forme canonique. Le commentaire HTML est
accepté comme alias en première ligne de scène, et le parseur fusionne les deux. Une seule forme est
émise lors de la réécriture : la canonique.
4. Pont deck ↔ motion
Une même source peut être rendue en deck et en vidéo (recit build et recit render). On passe
d'un layout de scène à un bloc emaki par un tableau fermé. Une forme sans correspondance produit
une erreur avec numéro de ligne, jamais une devinette, selon la règle emaki « aucune métrique
inventée ».
| Scène Récit | Bloc emaki | Props |
|---|---|---|
layout=cover (H1 et H3 ou paragraphe) | title | H1 → text, sous-titre → kicker |
layout=section (H1 ou H2) | chapter | titre → title, number via attribut ou préfixe 01 · |
layout=quote, ou paragraphe seul | statement | texte → text, gras → emphasis[] |
| titre et liste à puces | list | titre → title, puces → items[] |
titre et tableau label | before | after | compare-bars | lignes → rows[], unit via attribut |
type=stat explicite | stat | H1 → value, paragraphe → label, italique → caption |
type=ui-mock / type=ui-scene | idem | bloc ```yaml = props |
tout bloc ```yaml ou ```json avec type= | type | props du bloc (échappatoire universelle) |
grid, split, image-*, code, Mermaid, KaTeX, composant MDX | — | erreur en v1 (motion: layout "grid" has no block). Rendu en image fixe en v2 (section 5.3 de 05-video.md) |
:::notes devient la voix off ou le sous-titre de la scène (champ à ajouter au schéma emaki).
Les :::step deviennent des temps d'apparition (hold).
5. Configuration : un seul recit.config.ts
recit.config.ts est le seul fichier de configuration d'un projet Récit. Il remplace :
recit.config.ts/recit.config.tsx(configuration du site du framework de sites) ;presentation.config.ts(configuration des decks de presentation-engine).
Un projet sans site (un deck seul, lancé avec recit dev slides.md) n'y met que les sections de
format. Un site y ajoute les options du framework de sites habituelles, au même niveau.
TypeScript
Comment ça marche :
defineConfigvalide tout le fichier avec zod 4 et sépare les deux parties :- les options du framework de sites sont passées telles quelles au chargeur du framework de sites. Elles sont validées par le framework de sites lui-même, qui retire les clés inconnues : les sections Récit doivent donc être extraites avant ;
- les sections Récit configurent
recitPluginetrecitVitePlugin, qui sont injectés automatiquement dansplugins. Plus de double déclaration (plugin Vite + plugin le framework de sites) : cela remplace le patch « cléstories» prévu en 6.2.
- Chargement : le cœur ne connaît que
recit.config.*(liste en dur danspackages/site/src/config/loader.ts). La liste contientrecit.config.{ts,tsx,js,jsx,mjs}. - Compatibilité : un
recit.config.tsexistant continue de fonctionner, avec un avertissement qui invite à le renommer.presentation.config.tsest lu avec le même avertissement, jusqu'à la 0.2. recit dev | build(CLI) lit le même fichier qu'un site : un deck a le même rendu en CLI et dans la doc.- Le schéma est en zod 4 (catalogue
4.6.5,zod/mini), fait en P3 danspackages/core/src/config/validate.ts(presentation-engine validait à la main). Le JSON Schema derecit.configest publié avec les autres (recit.dev/schema/v1/config.json). publish:etstories:(tranché le 24/10) : la section du site qui publie les récits s'appellepublish: { dir, routePrefix, decks, layout, components, loadDeckConfig }(ex-stories:;decksliste les récits hors dedir, comme aujourd'hui). La cléstories:est libre pour les réglages du format stories, commedeck:oumotion:pour les leurs : la règle «settingsd'un récit = section de son format » vaut pour tous les formats (09-langues-et-json.md§2.2,15-export-pdf.md§3.2). Le renommage est fait dans le code le 24/10 (PublishConfigSchemadanspackages/core/src/config/validate.ts), sans alias : une clé de site sousstories:échoue à la validation, avec un message qui renvoie àpublish.<clé>.- Toutes les sections sont facultatives. Sans section de format, ce sont les valeurs par défaut.
Sans
i18n, le projet est monolingue (voir09-langues-et-json.md§4).
6. Intégration au le framework de sites
6.1 Niveau 1 : plugin (aucun patch du cœur)
C'est ce que fait déjà @presentation/site-extension sur le framework de sites 0.86 :
recitVitePlugin({ dir, decks }): module virtuelvirtual:recit-stories, une compilation par récit, des modules MDX par scène, et?rawdes sources.recitPlugin({ routePrefix: "/present", layout: "none", components }):getRoutes()déclare une route par récit ;getMdxComponents()expose<StoryEmbed slug slide />(ex-PresentationEmbed) et<StoryGallery />(ex-PresentationGallery).
- Recherche : sous-résultats Pagefind
#slide-N, contenu marquédata-pagefind-body. - CSS : limitée par postcss à
.recit-wrapper(ex-.site-presentation-wrapper). On ajoute en tête la déclaration d'ordre des layers@layer properties; @layer theme, base, components, utilities;. À conserver, c'est ce qui a réglé le conflit de styles avec le framework de sites. - Hydratation : rendu client seulement, via
useSyncExternalStore. C'est ce qui a corrigé l'erreur React #418.
À adapter au passage de 0.86 à 0.93 : API des plugins (SitePlugin, getRoutes,
getMdxComponents), react-router 8.4, Vite 8.1, Shiki 4.5. Le test d'intégration
examples/site-embed sert de garde-fou.
6.2 Niveau 2 : support natif (patchs du fork, plus tard)
À faire seulement après le niveau 1, et si le besoin est réel. Chaque patch est listé ici et
dans UPSTREAM.md (supprimé depuis), section « Local patches », qui fait foi.
| Patch | Pourquoi | Fichier du framework de sites touché |
|---|---|---|
stories: { dir, routePrefix } dans recit.config.tsxrecit.config.ts (§5) et le patch n° 3 | Éviter la double déclaration (plugin Vite + plugin du framework de sites) | schéma de config, chargeur Vite |
Type de navigation { type: "story", slug } | Une entrée de sidebar avec l'icône du format et le nombre de scènes | schéma navigation |
Afficher le title des blocs de code | Problème connu : Le framework de sites affiche « Code » au lieu du title | plugin MDX ou Shiki |
| Ids de modules MDX | Le plugin MDX du framework de sites intercepte nos modules de scène, d'où le contournement .jsx. Une option d'exclusion serait plus propre | plugin MDX |
7. Ce qui reste séparé
- Remotion et Chrome Headless Shell (environ 93 Mo) ne sont jamais des dépendances de
@recit/core,@recit/deckou@recit/site-stories. Ils vivent uniquement dans@recit/motion-render. - Shiki reste côté build. L'entrée
@recit/core/browsern'importe ni Shiki ni le pipeline Node (stubsoptionalDependenciesPlugin). - Mermaid est chargé côté client, à la demande.
- KaTeX : CSS embarquée en local, aucun CDN.