24 — Webtoon : le format toon et webtoon.recit.dev
Statut : proposé · Phase : P7 · Mis à jour : 02/11/2026.
Ce plan détaille la phase P7 de 06 · Feuille de route : le format toon (webtoon,
ruban vertical de cases), son jumeau JSON, son lecteur, ses exports (tranches, PDF, film) et le site
webtoon.recit.dev. Il part de la spécification de 08 · Formats §2, qu'il précise sans
revenir sur les décisions acceptées ; chaque désaccord avec un plan accepté est posé en question
ouverte (§12), jamais tranché ici. Il s'appuie sur le code tel qu'il est au 02/11/2026, pas sur
l'ancien texte.
Il dépend de 15 · Export PDF (§3.5 et PDF2, accepté), de 09 · Langues et JSON, de 14 · IA et scénario (lot IA0 logé en P7, bible et attributs de scène), de 11 · Images et 16 · Atelier de personnages (personnages, Broc), de 03 · Packaging §6 et §8, de 07 · Domaines et de 21 · Tests. Le plan 23 (report, P6) livre la chaîne d'impression dont P7 a besoin ; le plan 25 (book, P8) partage la question des récits en plusieurs fichiers (séries ici, chapitres là-bas).
1. Pourquoi
- Le webtoon est la forme native de la lecture au pouce. Un ruban vertical de cases se lit sur un téléphone sans zoom ni pagination. C'est le premier format de Récit où l'image porte le récit et où le texte se pose dessus.
- Le lettrage est du texte. Dans une BD classique, les bulles sont peintes dans l'image : la traduire, c'est la redessiner. Ici les cases sont des images sans texte et les bulles sont des données typées (position, forme, texte). Le texte est donc traduisible, sélectionnable, indexé par la recherche, lu par un lecteur d'écran, et une seule série d'images sert toutes les langues.
- Une source, quatre sorties. Le même épisode se lit au scroll sur le web, s'exporte en tranches
d'images pour les plateformes, en PDF (une décision acceptée : le webtoon doit l'avoir) et
devient un film vertical
9:16par le pont toon → motion. - Le premier client du scénario. Le plan 14 a choisi le webtoon pour son épisode de
démonstration (
sites/plans/pages/annexes/broc/scenario-3.md, « Tempo a filé », 12 cases). Le format doit pouvoir l'exprimer sans exception, même si sa publication attend P16 et la licence de l'image de référence.
2. Ce qui existe déjà et ce qu'on réutilise
Relevé dans le dépôt le 02/11/2026.
| Élément | État | Ce que P7 en fait |
|---|---|---|
packages/formats/toon | stub : package.json privé (@recit/toon 0.0.0, scope:format), src/index.ts vide | devient un paquet publiable, sur le modèle de packages/formats/deck (tsdown, README.md, LICENSE.md, ./styles.css) |
@recit/core/schema (source.ts) | ToonSourceSchema = genericSource("toon") : scènes GenericSceneSourceSchema (objet lâche), settings lâche. toon est déjà dans STORY_FORMATS, et packages/core/schema/v1/toon.json est publié | le schéma devient strict, avec des champs typés, comme DeckSourceSchema et MotionSourceSchema ; ajout compatible en v1 (règle B1) |
convert.ts, parity.ts, assemble.ts | conversion Markdown ⇄ JSON par format (deckScene, docsScene, motionScene, genericScene) ; parité des variantes de langue ; jumeau (kind: "story") et compagnon (kind: "sidecar") par resolveStoryFiles | une fonction toonScene et son écriture inverse ; la parité compare aussi le lettrage |
Séparateur de scène (split-slides.ts) | attributs --- clé=valeur, valeurs entre guillemets acceptées (alt="…"), typage par SlideMetadataSchema. La forme sur une ligne :::notes texte ::: est un cas particulier de :::notes (l. 235) | réutilisé tel quel pour src, alt, height, gutter, background |
remark-directive dans le cœur | les directives :::nom{…} sont lues par le pipeline unifié de @recit/core | :::bubble, :::caption, :::sfx : noms au registre du cœur (règle B4), sens dans @recit/toon |
| Round-trip des exemples | packages/core/src/__tests__/source-conversion.spec.ts vérifie toute story de examples/ et sites/*/stories, que son JSON soit sur disque ou non | examples/toon-* y entrent sans code de test en plus |
@recit/motion et @recit/motion-blocks | le pont storyToFilm (source/story.ts) ; huit blocs (title, statement, stat, compare-bars, chapter, list, ui-mock, ui-scene) ; aucun bloc ne montre une image | le pont toon → motion demande un bloc nouveau, panel (§5.4) |
| La CLI | recit dev a un aperçu propre au motion (src/motion/client.tsx, bundlé par esbuild) ; recit export sort le PDF ou le HTML du deck, sans --to ; recit render rend un récit motion ; recit convert --to json|md|film | un aperçu toon sur le même modèle ; --to images et --to pdf arrivent avec la chaîne de P6 ; render accepte un toon |
@recit/paged/runner | n'existe pas encore : le stub est packages/formats/paged, déplacé dans packages/tools/paged en PDF1 (P6, plan 23) | prérequis de W3 (tranches) et de PDF2 |
@recit/site-stories | plugin.tsx choisit le lecteur par format : MotionViewer pour motion, StoryViewer (deck) sinon ; vite.ts compile les stories et tente storyToFilm | une branche toon vers ToonReader, chargée paresseusement |
@recit/site-kit | formats.ts décrit déjà toon (« Webtoon ») ; SITES (sites.ts) ne connaît pas encore webtoon | ajout de webtoon (port 3005, 07 §4) |
@recit/components | useCssVariable (valeurs dynamiques sous la loi du plan 19), composants partagés | positions des bulles en variables CSS |
sharp | absent du lockfile ; allowBuilds.sharp: false dans pnpm-workspace.yaml | voir §5.2 et question 4 : la découpe peut se passer de sharp |
| Scénario de Broc | sites/plans/pages/annexes/broc/scenario-3.md : source Markdown complète et brouillon du jumeau JSON d'un épisode de 12 cases ; image de référence examples/scenario-lebrocoli/cast/broc/reference.png (© vguerand, tous droits réservés) | banc d'essai du modèle (W0) ; publication en P16 seulement, dans examples/scenario-lebrocoli |
Ce qu'on ne crée pas : pas de second parseur Markdown, pas de moteur de mise en page, pas de
bibliothèque de bulles, pas de lanceur de Chromium dans @recit/toon (la CLI capture par
@recit/paged/runner, 03 §8), pas de schéma de type hors de @recit/core.
3. Vocabulaire
- Une scène de
toonest une case (panel) : « case » est le mot du lecteur, de l'interface et de la doc, comme « slide » pour le deck. Le modèle gardeScene,sceneIndex. - Un épisode est une story
format: toon. Une série est un dossier d'épisodes (§4.6). - Le lettrage désigne les bulles, encarts et onomatopées d'une case : ce qui se traduit.
- Une tranche (slice) est une image découpée du ruban pour une plateforme ; une page est une
page du PDF. Les deux découpages sont distincts (
15§3.5). - Le rendu
motiond'un épisode est un film, jamais un « deck ».
Ces mots sont à ajouter au glossaire de AGENTS.md par l'intégrateur (W7), ce plan ne le modifie
pas.
4. Le modèle
4.1 Markdown
Même grammaire que tous les formats : frontmatter, scènes séparées par --- avec leurs attributs,
directives :::. Réglages à plat dans le frontmatter (08, préambule).
Markdown
| Élément | Rôle |
|---|---|
src | image de la case (chemin relatif, partagée entre langues ; variante 01.fr.webp possible, 09 §1) |
alt | description de l'image, localisable ; manquante = avertissement (15 Q6, tranché) |
height | hauteur en px à la largeur de référence ; par défaut, celle de l'image mise à l'échelle |
gutter | espace après la case, en px ; par défaut le gutter de l'épisode |
background | couleur d'une case sans image (case de transition, noir de fin) ; un seul nom, jamais bg (règle B4) |
:::bubble{x y w tail shape cast} | bulle. x, y : centre de la bulle en % de la case ; w : largeur maximale en % (40 par défaut) ; tail ; shape : speech, thought, shout, whisper ; cast (§4.5) |
:::caption{x y w} | encart narratif |
:::sfx{x y rotate size} | onomatopée en texte vectoriel |
:::notes | notes d'auteur, jamais publiées dans le lecteur ; voix off dans le film (§5.4) |
beat, cast, set, shot, pose, prompt | attributs réservés du scénario (B4, 14 D4) : gardés dans attrs sans erreur en P7, vérifiés par @recit/scenario en P16 |
Règles :
- L'ordre du Markdown est l'ordre de lecture des éléments de lettrage d'une case, et l'ordre du DOM. Rien n'est trié par position (règle d'emaki : jamais de devinette).
- Un élément de lettrage sans
xniyest empilé en haut de la case, dans l'ordre : c'est la forme demandée par le plan 14 D6 pour les répliques venues d'un:::dialogueou d'un pont. x,y,ws'écrivent en pourcentage (62%), de 0 à 100 ; une valeur hors bornes est une erreur avec numéro de ligne. Le débordement réel (bulle trop grande pour son texte) ne se mesure qu'au rendu : avertissement du lecteur en dev et du runner à l'export.- Le corps d'une case ne contient que du lettrage et
:::notes. Toute autre chose (paragraphe, titre, autre directive) est une erreur avec numéro de ligne, pas un texte affiché au hasard. Les blocs transverses (```pixel, plus tard) s'ajouteront par la règle B2. - Une case sans
srcnibackgroundest dessinée comme une case de remplacement : un cadre neutre qui montre l'altet, s'il existe, l'iddeprompt=. Avertissement, erreur en--strict. C'est le point d'accroche des images de remplacement du plan 14 D7. - Image introuvable : erreur avec numéro de ligne (
08§2). Poids d'une image et de l'épisode au-delà du budget (budget.panelBytes,budget.episodeBytes) : avertissement. - La forme canonique des directives est sur plusieurs lignes (ouverture, texte,
:::). La forme sur une ligne de08§2 (:::bubble{…} Lorem ipsum? :::) n'est pas lue par le pipeline actuel, qui ne la connaît que pour:::notes: question 2.
4.2 Le jumeau JSON
Le schéma vit dans packages/core/src/schema/source.ts, en zod/mini comme les autres ; les types
sont dérivés (z.infer) et exportés depuis @recit/core/schema. Esquisse :
TypeScript
- Un seul tableau ordonné,
lettering.09§2.3 (accepté) écrit trois tableaux,bubbles[],captions[]etsfx[]. Trois tableaux perdent l'ordre entre un encart et une bulle, donc l'ordre de lecture, et l'aller-retour Markdown → JSON → Markdown ne rend plus le même texte (dans le scénario 3, la casec01ouvre sur l'encart, la casec12le met à la fin). Ce plan propose un tableau unique discriminé surtype; c'est la question 1, à trancher avant W0.typene désigne ici que la sorte d'élément de lettrage, à l'intérieur de l'objet ; l'attribut de scènetype=reste réservé aux blocs motion (B4). xetysont des nombres en JSON et des pourcentages en Markdown (62%⇄62). Le brouillon du scénario 3 gardait des chaînes"58%"en laissant le choix à W1 : le nombre se valide par le schéma (bornes), la chaîne demanderait unrefine.srcetaltrestent à plat (09§2.3), et non un objetimage: { src, alt }comme leDeckImageSchemadu deck : l'altd'une case vaut aussi sans image (case de remplacement, balisage du PDF).ToonPdfOptionsSchemaetToonImagesOptionsSchemavivent danspackages/core/src/config/validate.tsavec les autres options d'export (15§3.2), et la sectiontoon:derecit.config.tsestToonConfigSchema(même champs, forme imbriquée,08préambule). Aucun réglage ne s'appellekind.@recit/toonne redéfinit aucun de ces types : il les importe.
4.3 Conversion et aller-retour
markdownToSourcegagnetoonScene(dansconvert.ts, à côté demotionScene) : les attributs typés du séparateur passent dans leurs champs, les autres restent dansattrs; les directives de lettrage du corps, lues par le même AST que le reste du cœur, deviennentlettering, dans l'ordre ;:::notesdevientnotes. Un reste dans le corps est une erreur avec sa ligne (§4.1).sourceToMarkdownécrit la forme canonique : attributs dans l'ordreid src alt height gutter background, puisattrs, directives sur plusieurs lignes, attributs de directive dans l'ordre du schéma, pourcentages avec%.- Propriété testée :
compile(md) == compile(toJson(md))ettoMd(toJson(md)) == mdpour toute source canonique (09§2.4), sur des fixtures Lorem et sur les exemples. - Le compagnon (
kind: "sidecar") complète une case par sonid: utile pour desaltou des textes venus d'un CMS. Il ne redéfinit jamais la liste des cases (règle d'AGENTS.md).
4.4 Langues
episode-1.fr.mdest la même page en français (09§1) : mêmesid, mêmes images, mêmes positions, textes traduits.checkStoryParityvérifie en plus que le lettrage a le même nombre d'éléments, les mêmestype,x,y,w,shape; le texte identique à la langue par défaut donne l'avertissementuntranslated.08§2 et le scénario 3 (§4.3) décrivent un fichier de langue qui ne contient que les textes. Ce fichier n'est ni une variante complète (la parité refuse des attributs de structure manquants), ni un compagnon. Ce plan ne le crée pas : la traduction partielle passe parepisode-1.fr.json(kind: "sidecar"), etrecit i18nsait déjà produire la variante complète. Question 3.- Dans le JSON, un texte porte toutes ses langues (
{ "en": "…", "fr": "…" }) : positions écrites une fois, textes par langue. C'est la forme la plus sûre pour un outil de traduction. - Les bulles grandissent avec leur texte (largeur maximale
w, hauteur libre) ; un débordement de la case est un avertissement, par langue. direction: rtlposedir="rtl"sur le lecteur et retourne l'interface. Il ne réordonne pas le lettrage : l'auteur écrit dans l'ordre de lecture (question 7, en lien avec15§3.5).
4.5 Scénario, personnages et images
- Les attributs
beat,cast,set,shot,pose,promptsont réservés (B4) et lus par le plugin de@recit/scenarioen P16. En P7,@recit/toonles garde dansattrset ne les interprète pas, saufprompt=dont il affiche l'idsur une case de remplacement. - Qui parle. Sans
:::dialogue(plan 14, plus tard), le locuteur n'est donné que par la queue de la bulle, invisible pour un lecteur d'écran. Ce plan proposecast=sur:::bubble: le nom affiché au lecteur d'écran est celui de la bible quand elle existe (P16), sinon l'idlui-même. Question 5. - Les images ne sont jamais générées par Récit (
14D7,11). Les panneaux viennent de l'auteur ; leur provenance et la mention « généré par IA » arrivent avec le registre d'assets du plan 14. - Broc. Son épisode (scénario 3) vit dans
examples/scenario-lebrocoli/toon/(règle B7, un seul dossier lebrocoli), en P16, après la licence de l'image de référence. P7 s'en sert comme banc d'essai du schéma (W0) avec une copie Lorem de sa structure, sans copier ni son texte ni ses images dans un exemple publié. - Les blocs
```pixeletsprite(plan 11, plan 16) pourront fournir l'image d'une case plus tard, « sur besoin réel » (11D7) : rien en P7.
4.6 Séries
Un dossier = une série. 08 §2 propose un series.yaml ou le frontmatter du premier épisode. Tout
document de Récit a un jumeau JSON typé : ce plan propose un series.json (kind: "series", schéma
SeriesSchema dans @recit/core/schema, titre localisable, couverture, ordre des épisodes), ajout
compatible en v1 (B1), et pas de YAML. Le plan 25 pose la même question pour les chapitres d'un
livre (settings.chapters) : une seule réponse pour les deux. Question 8. Les séries sont le lot
W6, après les critères de sortie de P7.
5. Le rendu
5.1 Le lecteur web
Composants de @recit/toon, tous anonymes, chacun avec son X.module.css, sa story et son play :
| Composant | Rôle |
|---|---|
ToonReader | racine : ruban vertical, largeur min(100%, width), dir, langue ; sommaire de l'épisode |
Panel | une case : <figure>, image avec width/height intrinsèques (pas de décalage de mise en page), loading="lazy", decoding="async", gouttière après |
Bubble | une bulle : texte HTML posé sur une forme SVG (speech, thought, shout, whisper) avec sa queue ; position par variables CSS (useCssVariable) |
Caption | un encart |
Sfx | une onomatopée en SVG <text>, aria-hidden sauf si l'auteur la marque comme porteuse du récit |
PlaceholderPanel | case de remplacement (§4.1) |
EpisodeNav | épisode précédent et suivant (W6) |
- Mobile d'abord. La case suit la largeur de l'écran ; la taille du lettrage suit la largeur de
la case par les unités de conteneur (
cqw), en CSS simple (pas de PostCSS hors du framework de sites, plan 19). Rien ne demande de zoom à 360 px. - Bulles en HTML et SVG. Le texte reste sélectionnable, indexé par Pagefind
(
site-stories/search-index.ts), lu dans l'ordre du DOM. Chaque case est unefiguredont l'altdécrit l'image et dont le lettrage suit en texte. - Apparition au scroll avec
motion:metLazyMotionavecdomAnimationchargé à part (règle « Bundle Size » d'AGENTS.md), coupée sousprefers-reduced-motion. - Mode de couleur. Les cases sont des images ; la gouttière prend
backgroundde l'épisode ; le reste de l'interface suitdata-mantine-color-schemeparlight-dark(). Pas de.dark. - Poids. Images servies telles que l'auteur les fournit, au format choisi par lui (WebP ou AVIF
recommandés) ; la CLI vérifie le budget. Les variantes responsives (
srcset) supposent un encodeur : question 4. - Plus tard : un mode « case par case » au tap (lecture guidée), sur demande réelle.
Lettrage : une police de lettrage OFL servie localement par @recit/theme, avec son OFL.txt,
comme Lexend, et un jeton de rôle (--recit-font-lettering). font: lettering la choisit ; une
autre famille du thème reste possible (le scénario 3 utilise lexend). Le choix de la police est la
question 6.
5.2 Les tranches pour les plateformes (W3)
recit export episode.md --to images --preset webtoon-canvasproduit des PNG, JPEG ou WebP de la largeur du préréglage, coupés à hauteur fixe, lettrage compris (une série de tranches par langue,--all-locales). Les préréglages sont des données versionnées, avec leur date de vérification (08§9) ; les valeurs des plateformes sont à vérifier au moment de W3, aucune n'est promise ici.- La CLI sert le build du lecteur, l'ouvre par
@recit/paged/runneret capture chaque tranche par une capture découpée (clip) de la page : pas d'image géante en mémoire, pas de limite de texture. L'encodage WebP ou JPEG se fait dans Chromium (canvas.toBlob).sharpn'est alors plus nécessaire ;08§2 et03§6 le prévoient : question 4. 08§2 dit « en évitant de couper une bulle »,15§3.5 dit « coupées à hauteur fixe sans égard aux cases ». Les plateformes recollent les tranches sans joint : une coupe ne se voit pas. Ce plan propose la hauteur fixe par défaut et une optionavoidLettering(la coupe remonte au-dessus d'un élément de lettrage, dans une limite) : question 9.- Les fichiers sont listés dans
exports.json(ExportManifestSchema, variantekind: "images",15§3.2) ; le panneau d'exports du site les lit.
5.3 Le PDF (PDF2, plan 15)
Tout est décidé dans 15 §3.5 et PDF2 ; ce plan ne fait que le loger :
pagination: "panels"par défaut : pages de la largeur du préréglage, hauteurpageHeightindépendante des tranches, coupe toujours entre deux cases (break-inside: avoid), case trop haute sur sa propre page avec un avertissement, fond de gouttière en bas de page.@recit/toonfournit la vue imprimable (ToonPrintView, même composants, sans animation, variantes JPEG des cases) ; la CLI mesure la géométrie et imprime par@recit/paged/runner.- Les variantes JPEG sont produites dans Chromium au moment de l'impression (comme les tranches),
sauf si la question 4 retient
sharp. maxBytes: budget par PDF, avertissement, erreur en--strict. Un PDF par langue, balisé, avec signets (un par case qui porte uncaptionou parid, à fixer en PDF2.2) etlang/dir.pagination: "continuous"seulement après le spike PDF2.3.
5.4 Le film (pont toon → motion, W4)
Correspondance fermée (08 §0) : une case → un plan 9:16.
| Case | Plan du film |
|---|---|
src, height | bloc panel : l'image remplit la largeur, panoramique vertical du haut vers le bas de la case |
lettering (dans l'ordre) | bulles, encarts, sfx en apparition successive, mêmes formes que le lecteur |
notes | voiceover du plan (comme :::notes en motion) |
| durée | temps de lecture du lettrage (wordsPerSecond) + hold, ou dur donné par l'auteur |
background sans image | plan de couleur unie |
| case de remplacement | erreur : pas de film sans images |
- Le bloc
paneln'existe pas : son schéma va dans le registre de@recit/motion(schema/blocks.ts), son rendu dans@recit/motion-blocks(blocks/panel.tsx, CSS module, story etplay), avec la couche par image du plan 19 (allowlist de l'animation par frame). Le chargement d'images au rendu Remotion est à vérifier en W4.1. toonToFilm(story)vit dans@recit/toon/motion(sous-chemin : le lecteur n'importe jamais@recit/motion). Format → format : autorisé par03§8.recit render episode.mdreconnaîtformat: toonet passe partoonToFilm: pas d'option--as motion,renderne produit que des films (question 10).<StoryEmbed format="motion">sur un épisode joue le même film, parstoryMotiondesite-stories/vite.ts.
6. CLI et MCP
| Commande | Effet | Lot |
|---|---|---|
recit init --format toon | épisode Lorem de trois cases, images de remplacement | W1 |
recit dev episode.md | aperçu du lecteur, rechargement à chaud (client src/toon/client.tsx, esbuild) | W1 |
recit build episode.md | lecteur statique (HTML, images copiées, budget vérifié) | W1 |
recit validate [--strict] episode.md | schéma, lettrage, images, budget, alt, parité des langues | W0, W2 |
recit convert episode.md --to json|md | jumeau, forme canonique | W0 |
recit export episode.md --to images --preset … | tranches | W3 |
recit export episode.md --to pdf | PDF, un par langue avec --all-locales | PDF2 |
recit render episode.md | film 9:16 | W4 |
MCP (@recit/mcp), sans aucun appel de modèle :
describe_formatacceptetoon(aujourd'huimotionetdeck) : syntaxe et JSON Schema du jumeau, même texte que docs.recit.dev ;validate_story(nouveau, générique, utile à tous les formats) : diagnostics avec lignes ;- ressource
recit://guide/toon: écrire un épisode, poser le lettrage, ne jamais inventer d'image ; reprise parrecit skills export(plan 14, B9) ; - plus tard : des opérations ciblées (déplacer une bulle, changer un texte) sur le modèle
d'
apply_ops.
7. Le site webtoon.recit.dev
sites/webtoon: Markdown et config seulement (recit.config.tsaveccreateSiteConfig({ site: "webtoon" }),styles.css,vercel.json), commesites/motion. Composants dans@recit/site-kit.- Pages : accueil (hero = un épisode lisible au scroll, sélecteur de langue sur la même planche),
galerie, « une planche, deux langues », exports (panneau lu dans
exports.json: tranches, PDF, film), « épisode → film ». Aucune doc propre : la doc est sur docs.recit.dev › Formats › Webtoon. publish.entriespointe versexamples/toon-*, que le site déclare endevDependencies(audit ST-20). Lecteur plein écran sous/present/<slug>.- Port local 3005, e2e 4305 (
e2e/ports.ts), projet Vercelrecit-webtoon, alias 301toon.recit.dev(07). La création du projet et du DNS reste à l'utilisateur. - Les fichiers générés (tranches, PDF, MP4) sont produits en CI, jamais sur Vercel (
15, question 8 de ce plan-là, encore ouverte).
8. Les exemples
| Dossier | Contenu | Langues | Lot |
|---|---|---|---|
examples/toon-episode | un épisode Lorem de 8 à 10 cases : toutes les formes de bulle, encarts, sfx, une case sans image, une case haute (PDF), notes | en, fr | W5 |
examples/toon-series | deux épisodes courts et series.json, préréglage de tranches | en | W6 |
examples/scenario-lorem | micro-récit toon du lot IA0.3 (plan 14), cases de remplacement avec prompt= | en, fr | W6 |
- Contenu Lorem Ipsum, aucun nom réel, aucune image sous droits.
- Les cases sont des SVG abstraits (formes et dégradés), écrits par un script de l'exemple
(
scripts/panels.ts) et commités ; deux d'entre elles ont aussi une version WebP, produite une fois par Chromium, pour que le budget de poids et les variantes JPEG du PDF soient testés sur des images matricielles. Ligne auNOTICE.mddans le même changement. package.json:scope:example, scriptsdev,build,export:images,export:pdf,render.- Les sources Markdown ne passent jamais par oxfmt.
9. Tests
| Niveau | Ce qui est vérifié | Où |
|---|---|---|
| Schéma et jumeau | toonScene et son inverse ; aller-retour sur des fixtures Lorem (ordre du lettrage, %, guillemets, case vide) ; erreurs avec ligne (bornes, reste dans le corps, image manquante) ; parité des langues | packages/core/src/__tests__/ ; source-conversion.spec.ts pour les exemples |
| Modèle de rendu | compileToon (story → cases), budget, case de remplacement, toonToFilm (durées, ordre, voix off) | packages/formats/toon/src/__tests__/ |
| Composants | une story par composant avec play : rôles et noms (img nommée par l'alt, texte des bulles, ordre de lecture), axe en erreur ; Bubble dans ses quatre formes, ses queues, un texte long, RTL | X.stories.tsx à côté de X.tsx, pnpm test:stories |
| Bloc motion | panel : story et play, golden de frames comme les autres blocs | @recit/motion-blocks |
| CLI | dev, build, convert, validate, export --to images (nombre et taille des tranches), render (durée du film) | packages/cli/src/__tests__/ |
| e2e, site | webtoon.recit.dev : pages en clair et sombre, mobile (390 px) et bureau, chargement paresseux (les images hors écran ne sont pas demandées), changement de langue, recherche d'un texte de bulle | e2e/site-webtoon.spec.ts, captures -linux |
| e2e, exports | PDF : aucune case coupée, PNG des pages, budget de poids, deux langues (PDF2.4) ; tranches : largeur, hauteur, nombre | e2e/toon-export.spec.ts |
| Storybook | surface toon dans la galerie de captures | e2e/storybook-toon.spec.ts |
Le lecteur de PDF des tests (pdfjs-dist ou poppler) est la question 7 de 15, encore ouverte :
PDF2.4 la suppose tranchée. Seuils de couverture : une ligne packages/formats/toon/** dans le
vitest.config.ts racine, en autoUpdate.
10. Lots
Identifiants W0 à W7 : W1 à W5 reprennent ceux de 08 §2, W0, W6 et W7 sont nouveaux. Chaque lot
est assez petit pour un agent ; les règles de travail concurrent sont celles de
20 · Exécution multi-agent.
Code
W0 — Le jumeau (M)
Dépend de : rien dans P7 (questions 1 à 3 tranchées). Fichiers :
packages/core/src/schema/source.ts, convert.ts, parity.ts,
packages/core/src/config/validate.ts (ToonConfigSchema sans options d'export),
packages/core/schema/v1/toon.json (régénéré), registre des directives, tests.
- W0.1 —
ToonSceneSourceSchema,ToonLetteringSchema,ToonSettingsSourceSchema,ToonSourceSchemastrict ; types exportés. - W0.2 —
toonSceneet l'écriture canonique ; erreurs avec ligne. - W0.3 — Parité du lettrage entre langues.
- W0.4 — Fixtures Lorem, dont une copie Lorem de la structure du scénario 3 (12 cases, lettrage
mêlé, attributs de scénario) : elle valide sans perte, hors des champs que P16 doit fixer
(
cast:du frontmatter). - W0.5 —
pnpm -F @recit/core schema:json; version planprereleasepour@recit/core.
Sortie : recit convert fait l'aller-retour sur toutes les fixtures, octet pour octet ; un toon
JSON invalide échoue avec un chemin JSON, un Markdown invalide avec sa ligne.
Bash
W1 — Le paquet et le lecteur (L)
Dépend de : W0. Fichiers : packages/formats/toon/** (paquet publiable : tsdown.config.ts,
tsconfig.json, README.md, LICENSE.md, ./styles.css par scripts/build-css-modules.js),
packages/theme (police de lettrage, OFL.txt), packages/cli/src/toon/,
packages/cli/src/index.ts (init, dev, build), NOTICE.md (police).
- W1.1 —
compileToon(story): cases typées, dimensions lues dans l'en-tête des images (PNG, JPEG, WebP, AVIF ; sans dépendance, question 4), budget. - W1.2 — Composants du §5.1, un module CSS chacun, stories avec
play. - W1.3 — Police de lettrage et jeton de rôle dans
@recit/theme. - W1.4 —
recit init --format toon,recit dev,recit build. - W1.5 — Version plan
preminorpour@recit/toon(premier jalon de la phase),prereleasepour@recit/themeet@recit/cli.
Sortie : recit dev d'une fixture se lit au scroll à 360 px et à 1280 px, en clair et en sombre ;
node scripts/check-style-rule.js packages/formats/toon et node scripts/check-stories.js à 0 ;
scripts/check-package-boundaries.js sans nouvelle tolérance.
Bash
W2 — Langues et site-stories (M)
Dépend de : W1. Fichiers :
packages/integrations/site-stories/src/{plugin.tsx,vite.ts,search-index.ts}, package.json de
site-stories (dépendance @recit/toon), packages/core/src/schema/parity.ts.
- W2.1 — Branche
toondeplugin.tsxversToonReader, chargée paresseusement ;StoryEmbedavecformat="toon". - W2.2 — Routes par langue, sélecteur,
hreflang(comme les autres formats,09§4). - W2.3 — Lettrage indexé par Pagefind.
Sortie : une fixture en/fr se lit dans examples/site-embed en deux langues ; recit validate
signale une bulle non traduite.
W3 — Tranches (M)
Dépend de : W1 et @recit/paged/runner (P6, PDF1). Fichiers : ToonImagesOptionsSchema dans
packages/core/src/config/validate.ts, préréglages, packages/cli/src/commands/export-*.ts,
ExportManifestSchema (variante images).
- W3.1 — Options typées (
preset,width,sliceHeight,format,quality,maxBytes,avoidLetteringselon la question 9). - W3.2 — Capture découpée par le runner, encodage dans Chromium.
- W3.3 —
exports.json,--all-locales.
Sortie : recit export examples/toon-episode/episode-1.md --to images --preset webtoon-canvas
produit des tranches de la bonne largeur, sous le budget, listées dans exports.json.
PDF2 — Le PDF du webtoon (M)
Tâches PDF2.1 à PDF2.4 de 15 §6, sans changement. Dépend de : W1 et PDF1. Sortie : celle de 15,
reprise dans les critères de P7.
W4 — Le pont vers motion (M)
Dépend de : W1. Fichiers : packages/formats/motion/src/schema/blocks.ts (bloc panel),
packages/formats/motion-blocks/src/blocks/panel.tsx et son module, story et golden,
packages/formats/toon/src/motion/, packages/cli/src/motion/source.ts (lecture d'un toon),
site-stories/vite.ts (storyMotion), scripts/style-rule-allowlist.json si la couche d'animation
le demande.
- W4.1 — Spike : une image dans un plan rendu par Remotion (chemin, chargement, attente).
- W4.2 — Bloc
panel(schéma, rendu, story, golden). - W4.3 —
toonToFilm, durées, voix off ;recit render episode.md.
Sortie : recit render examples/toon-episode/episode-1.md produit un MP4 9:16 ; le film se joue
dans <StoryEmbed format="motion"> avec les mêmes plans. Version plan prerelease pour
@recit/motion et @recit/motion-blocks.
W5 — Exemple, site, doc (M)
Dépend de : W2, W4, et W3 + PDF2 pour la page d'exports. Fichiers : examples/toon-episode/**,
sites/webtoon/**, packages/tools/site-kit/src/sites.ts, e2e/ports.ts,
scripts/e2e-projects.js, e2e/site-webtoon.spec.ts, e2e/toon-export.spec.ts,
e2e/storybook-toon.spec.ts, sites/docs/pages/…/formats/webtoon.mdx, NOTICE.md.
- W5.1 —
examples/toon-episode(en, fr), panneaux du §8. - W5.2 —
sites/webtoon, entréewebtoondeSITES. - W5.3 — Page docs.recit.dev › Formats › Webtoon, syntaxe et JSON Schema.
- W5.4 — e2e et captures :
pnpm test:e2e:update:linux e2e/site-webtoon.spec.ts.
Sortie : les critères de P7 ci-dessous, sauf la mise en ligne.
W6 — Séries, MCP, scénario (M)
Dépend de : W1 (et la question 8 pour les séries). Fichiers : SeriesSchema dans @recit/core,
EpisodeNav, examples/toon-series, packages/mcp/src/{server.ts,guide.ts},
examples/scenario-lorem (avec le plan 14, IA0.3).
- W6.1 —
describe_formatpourtoon,validate_story,recit://guide/toon. - W6.2 — Micro-récit
toondeexamples/scenario-lorem, cases de remplacement. - W6.3 — Séries :
series.json, navigation entre épisodes,examples/toon-series.
Sortie : un agent qui lit recit://guide/toon écrit un épisode Lorem qui passe
recit validate --strict du premier coup ou avec les seules corrections signalées.
W7 — Clôture (S)
pnpm verify,
pnpm test:e2e:linux e2e/site-webtoon.spec.ts e2e/toon-export.spec.ts e2e/storybook-toon.spec.ts,
seuils de couverture, version plans relus, glossaire d'AGENTS.md proposé à l'intégrateur (case,
épisode, lettrage, tranche), note datée dans journal.md.
Critères de sortie de P7
Ceux de 06 P7, inchangés :
examples/toon-episodese lit au scroll, en deux langues ;- l'export en tranches respecte les préréglages des plateformes ;
recit renderproduit un film9:16(la forme exacte de la commande : question 10) ;examples/toon-episodeproduit un PDF par langue, sans case coupée, sous son budget de poids, listé dansexports.json;- webtoon.recit.dev est en ligne (
toon.recit.devredirige), et la version0.3.0-alphaest publiée (après le go de l'utilisateur).
S'y ajoutent, proposés par ce plan : le jumeau de chaque exemple fait l'aller-retour sans perte ;
tous les composants ont leur story avec play et passent axe.
11. Risques
| Risque | Parade |
|---|---|
P6 en retard : pas de runner, pas de --to | W0, W1, W2, W4 n'en dépendent pas ; W3 et PDF2 attendent, P7 ne sort pas sans eux |
| Poids des images sur mobile | budget avertissant, formats modernes recommandés, chargement paresseux, dimensions intrinsèques ; srcset selon la question 4 |
| Débordement des bulles après traduction (l'allemand allonge de 30 %) | bulles extensibles, avertissement par langue au rendu, captures en deux langues |
Le bloc panel ne se rend pas proprement dans Remotion | spike W4.1 avant le reste ; le pont peut sortir après P7 sans bloquer le lecteur |
Le tableau unique lettering contredit 09 §2.3 | question 1, tranchée avant W0 |
| Préréglages de plateformes qui changent | données versionnées, date de vérification, e2e sur la largeur et la hauteur seulement |
| Captures instables (chargement paresseux, animations) | ?settled=1 comme le motion, animations coupées en e2e, attente des images |
| Lecteur d'écran : bulles sans locuteur | cast= sur la bulle (question 5) |
Tentation d'ajouter sharp « pour plus tard » | règle « une seule stack » ; question 4 |
12. Questions ouvertes
- Lettrage en un tableau.
09§2.3 (accepté) écritbubbles[],captions[],sfx[]. Ce plan propose un seul tableau ordonnélettering[]discriminé surtype, parce que trois tableaux perdent l'ordre de lecture et cassent l'aller-retour. D'accord pour corriger09? - Directives sur une ligne. Garder la forme
:::bubble{…} texte :::de08§2 (il faut alors l'apprendre au parseur, comme:::notes), ou ne garder que la forme sur plusieurs lignes (recommandé : une seule forme canonique) ? - Fichier de langue « textes seulement ».
08§2 et le scénario 3 le décrivent ; il n'entre ni dans la variante complète de09ni dans le compagnon. Recommandation : variante complète (episode.fr.md) ou compagnon (episode.fr.json), pas de troisième forme. Sinon, il faut changer la parité de tous les formats. sharp.08§2 et03§6 le prévoient pour la découpe. Ce plan propose de s'en passer : capture découpée et encodage dans Chromium, dimensions lues dans l'en-tête des images par un petit module maison. Contrepartie : pas desrcsetgénéré au build en P7. D'accord ?- Locuteur d'une bulle.
cast=sur:::bubble(nom tiré de la bible en P16), ou attendre:::dialogue? - Police de lettrage. Quelle famille OFL par défaut (par exemple Comic Neue, licence à vérifier au moment de W1.3) ?
direction: rtl.15§3.5 dit qu'il « change l'ordre de lecture des bulles ». Ce plan propose que l'ordre du Markdown fasse toujours foi et quertlne change quediret l'interface. D'accord ?- Séries et récits en plusieurs fichiers.
series.json(kind: "series") plutôt queseries.yaml, et la même forme que les chapitres du livre (plan 25) ? - Coupe des tranches. Hauteur fixe sans égard au contenu (
15§3.5) ou en évitant les bulles (08§2) ? Proposition : fixe par défaut,avoidLetteringen option. - Commande du film.
recit render episode.md(format reconnu) au lieu derecit render --as motionécrit dans06P7 et08§2 ? - Second exemple.
06P12 demande deux exemples par format :examples/toon-seriessuffit-il (W6), ou faut-il un exemple en sensrtl?