Formats

25 — Book : le format book et book.recit.dev

Statut : proposé · Phase : P8 · Mis à jour : 02/11/2026.

Ce plan détaille la phase P8 de 06 · Feuille de route : le format book (un livre en plusieurs fichiers Markdown, imprimé en PDF, publié en EPUB 3 et lu sur le web) et le site book.recit.dev. Il précise la spécification de 08 · Formats §3 (lots B1 à B5) sans revenir sur les décisions acceptées : réglages à plat sans clé book:, trim devenu pdf.page.size en valeurs littérales, HTML intermédiaire produit par @recit/book et partagé par le PDF et l'EPUB, moteur pagedjs après le spike go/no-go PDF3.0, impression professionnelle (CMJN, PDF/X) hors périmètre (15 · Export PDF §3.4 et §3.6). Deux passages des plans existants se contredisent sur le modèle du livre ; ils sont posés en questions ouvertes (§12, 1 et 3), avec une recommandation.

Le PDF du livre appartient à PDF3 de 15 (PDF3.0 à PDF3.7). Ce plan ne redécrit pas la chaîne : il fixe ce que le livre lui fournit (le HTML intermédiaire, la vue imprimable, l'extension de schéma), et tout le reste du format.

Les identifiants B0 à B7 sont ceux des lots du livre (08 §3), pas les règles communes B1 à B11 de 06.

1. Pourquoi

Un livre, un manuel ou une thèse s'écrit en plusieurs fichiers Markdown, et la même source donne un PDF prêt à imprimer, un EPUB valide et une édition web qu'on feuillette et qu'on cherche.

  • Le livre est le format long. Un deck tient en un fichier ; un livre se répartit en chapitres, se relit chapitre par chapitre, se traduit chapitre par chapitre. Le format doit l'assumer : plusieurs fichiers, un ordre, des numéros, des renvois qui traversent les chapitres.
  • Trois sorties, un seul HTML. PDF, EPUB et web lisent le même HTML intermédiaire : un renvoi, une note ou une figure se résout une fois, et les trois sorties disent la même chose.
  • Le client exigeant de la chaîne PDF. Le livre est le seul format qui a besoin de titres courants, de pages paires et impaires, d'une table des matières et d'un index numérotés : il justifie le moteur pagedjs de 15, qui profite ensuite au report.
  • Des renvois qui ne cassent pas. Un renvoi vers un id inconnu, un chapitre listé mais absent ou une image manquante est une erreur avec le fichier et la ligne, avant toute impression.

Non-objectifs de P8 : impression professionnelle (CMJN, PDF/X, OutputIntent), écriture verticale CJK et sens de reliure RTL du lecteur PDF (15 §3.8), parties et sous-parties (« Partie I ») au-dessus des chapitres, bibliographie gérée (CSL, BibTeX), EPUB 2 et Kindle (KF8, MOBI), édition collaborative.

2. Ce qui existe déjà et qu'on réutilise

Relevé dans le code au 02/11/2026.

ÉlémentOùCe que le livre en fait
StoryFormatSchema (inclut book)packages/core/src/types/index.tsrien à ajouter : le format est déclaré
BookSourceSchema = genericSource("book")packages/core/src/schema/source.tsremplacé par un schéma strict (§3.3)
resolveStoryFiles, mergeSidecarpackages/core/src/schema/assemble.tsrésout chaque fichier du livre (entrée et chapitres) : jumeau, compagnon, variantes de langue, sans règle propre au livre
markdownToSource, sourceToMarkdownpackages/core/src/schema/convert.tsaller-retour de chaque fichier, sans code propre au format
Test d'aller-retour de tous les exemplespackages/core/src/__tests__/source-conversion.spec.tscouvre chaque fichier de examples/book-* dès lors qu'il déclare format: book (§3.2)
Notes GFM ([^1])remark-gfm dans createSlidePlugins (compile/compile-story.ts)déjà parsées ; le livre les renumérote par chapitre et les place selon la sortie
hast-util-to-html 9lockfilesérialise le HTML intermédiaire (HTML pour le web et le PDF, XHTML pour l'EPUB)
Crochet de directives de compileStoryajouté en P6 par R0 de 23 · Reportporte ::figure, :ref, :index, :::epigraph ; le cœur ne garde que les noms (règle B4)
Chaîne PDF, contrat d'impression@recit/paged, @recit/core/print (PDF1, P6)la vue imprimable du livre s'y branche ; pagedjs arrive avec PDF3.2
exports.json (ExportManifestSchema)@recit/core (PDF0, P6)gagne la variante kind: "epub" en B3
API de plugins : getRoutes, getNavigationpackages/site/src/lib/core/plugins.tsl'édition web : une route par chapitre et la table des matières en sidebar, sans patch du framework de sites (§4.3)
StoryDocumentpackages/integrations/site-stories/src/StoryDocument.tsxmodèle du rendu d'un chapitre en page continue
Fiche du formatpackages/tools/site-kit/src/formats.ts (status: "planned", phase: "P8")passe à available avec site: "book" en B5
@recit/bookpackages/formats/book (stub privé, scope:format)devient le package du format

Ce qui manque : fflate (zip de l'EPUB, nouvelle dépendance justifiée par 08 §0) et pagedjs (selon PDF3.0, 15 §4). Ni l'un ni l'autre n'est dans le lockfile.

3. Le modèle

3.1 Les fichiers d'un livre

Code
examples/book-novella/ book.md # entrée : métadonnées, réglages, ordre des chapitres (pas de scène) book.fr.md # variante de langue de l'entrée (titre, sous-titre traduits) 00-preface.md # chapitre non numéroté 01-lorem.md 01-lorem.fr.md 02-ipsum.md 03-dolor.md images/map.svg recit.config.ts

book.md :

MarkdownMarkdown
--- title: Lorem Ipsum author: Dolor Sit format: book lang: en cover: ./images/cover.svg chapters: - { file: 00-preface.md, numbered: false } - 01-lorem.md - 02-ipsum.md - 03-dolor.md front: [half-title, title, copyright, toc] back: [notes, index] publication: publisher: Lorem Press rights: © 2026 Dolor Sit, CC BY 4.0 pdf: page: size: { width: 6in, height: 9in } mirror: true toc: { depth: 2, pageNumbers: true } chapterStart: right footnotes: page ---

01-lorem.md :

MarkdownMarkdown
--- format: book --- # Lorem ipsum :::epigraph{source="Consectetur, Adipiscing"} Lorem ipsum dolor sit amet. ::: Lorem ipsum dolor sit amet[^1], as the map shows (:ref[fig-map]). ::figure{src=./images/map.svg id=fig-map alt="Lorem valley seen from above"} The Lorem valley. :: --- id=lorem-night Sed do :index[eiusmod]{see=tempor} tempor incididunt. --- id=lorem-dawn break=page Ut enim ad minim veniam. [^1]: Duis aute irure dolor.

3.2 Chapitres, sections, scènes

  • Un chapitre est un fichier. Son id est l'id de son frontmatter, sinon le nom du fichier sans préfixe numérique (01-lorem.md → lorem). Son titre est son premier H1 (08 §3).
  • Une scène est une section d'un chapitre (09-langues-et-json.md §2.3) : --- en ouvre une, la première commence au H1. Les scènes de tous les chapitres, dans l'ordre de chapters, forment les scenes de la Story compilée. Cette lecture contredit 08 §0 et formats.ts, qui disent « une scène = un chapitre » : question 1.
  • Chaque fichier de chapitre déclare format: book. Sans cela il se lirait comme un deck (défaut de frontmatter.ts), et le test d'aller-retour, qui parcourt tout .md de examples/, le traiterait comme tel. C'est une ligne par fichier ; question 2 pour l'inférer depuis l'entrée.
  • Le fichier d'entrée n'a pas de scène. Une préface ou une dédicace est un chapitre (numbered: false). Une scène dans book.md est une erreur avec sa ligne.
  • Les id de scène sont uniques dans tout le livre, puisque :ref traverse les chapitres. Un doublon est une erreur qui cite les deux fichiers et leurs lignes. Les id absents sont générés à partir de l'id du chapitre (lorem-s2), stables d'une langue à l'autre.
  • Langues : book.fr.md traduit l'entrée, 01-lorem.fr.md traduit un chapitre ; la résolution et le repli sont ceux de 09 §3, fichier par fichier. chapters nomme les fichiers de base, sans locale, et reste identique dans toutes les langues (vérifié par schema/parity.ts).

3.3 Les schémas

Dans @recit/core (zod/mini, comme source.ts), types dérivés par z.infer. pdf n'entre dans le schéma qu'avec PDF3.1 (BookPdfOptionsSchema de 15 §3.2), selon la règle « aucun champ avant sa phase ».

TypeScriptTypeScript
// packages/core/src/schema/book.ts import * as z from "zod/mini"; export const ChapterEntrySchema = z.union([ z.string(), // "01-lorem.md", relative to book.md z.strictObject({ file: z.string(), /** false: preface, foreword, afterword — no chapter number */ numbered: z._default(z.boolean(), true), }), ]); export const FrontMatterPageSchema = z.enum(["half-title", "title", "copyright", "toc"]); export const BackMatterPageSchema = z.enum(["notes", "index"]); export const PublicationSchema = z.strictObject({ /** EPUB dc:identifier; a stable urn:uuid is derived from the slug when absent */ identifier: z.optional(z.string()), isbn: z.optional(z.string()), // checked: ISBN-13 with its check digit publisher: z.optional(z.string()), rights: z.optional(LocalizedSchema), edition: z.optional(z.string()), }); /** Settings of book.md (the entry); a chapter file has none */ export const BookSettingsSourceSchema = z.strictObject({ chapters: z.optional(z.array(ChapterEntrySchema).check(z.minLength(1))), front: z._default(z.array(FrontMatterPageSchema), ["title", "toc"]), back: z._default(z.array(BackMatterPageSchema), ["notes"]), cover: z.optional(z.string()), publication: z.optional(PublicationSchema), theme: z.optional(z.union([ThemeModeSchema, z.string(), ThemeConfigSchema])), /** Footnote numbering: restarts with each chapter (default) or runs through the book */ noteNumbering: z._default(z.enum(["chapter", "book"]), "chapter"), // pdf: BookPdfOptionsSchema — added by PDF3.1 }); export const BookSceneSourceSchema = z.strictObject(sceneFields); export const BookSourceSchema = z.strictObject({ ...storyFields, format: z.literal("book"), settings: z.optional(BookSettingsSourceSchema), scenes: z.array(BookSceneSourceSchema), });

Les directives du livre suivent la règle B2 (des directives dans le body, validées par un schéma) :

TypeScriptTypeScript
export const FigureBlockSchema = z.strictObject({ src: z.string(), id: z.optional(z.string()), // required to be referenced alt: z.optional(z.string()), // missing: a warning (tagged PDF, 15 question 6) width: z.optional(LengthSchema), }); export const RefDirectiveSchema = z.strictObject({ /** "page": adds "page N" in the PDF; the web and the EPUB keep the link */ show: z._default(z.enum(["label", "page", "both"]), "label"), }); export const IndexDirectiveSchema = z.strictObject({ see: z.optional(z.string()), // "see also" entry sub: z.optional(z.string()), // sub-entry of the term }); export const EpigraphBlockSchema = z.strictObject({ source: z.optional(z.string()) });
ÉlémentRôleErreur (avec fichier et ligne)
chaptersordre des chapitres, numérotés sauf numbered: falsefichier listé mais absent ; fichier présent sans format: book
H1 d'un chapitretitre du chapitre, entrée de la table des matières, titre courantchapitre sans H1, ou deux H1
--- id=… break=page|rightsection ; saut de page (right : page impaire) en PDF, sans effet sur le webvaleur de break inconnue
[^1]note, numérotée par chapitre ; en bas de page (PDF, footnotes: page), en fin de chapitre (web), aside (EPUB)note appelée sans définition, ou définie sans appel
::figure{src id alt}figure numérotée par chapitre (« Figure 2.3 ») ; le contenu du bloc est la légende, localisée avec le bodyimage manquante ; id en double
:ref[id]{show}renvoi vers une figure, une section, un chapitre ou une note ; texte « Figure 2.3 », « Chapter 2 »id inconnu
:index[term]{see sub}entrée d'index ; la page d'index liste les termes A–Z, triés par Intl.Collator de la localesee vers un terme absent de l'index
:::epigraph{source}épigraphe en tête de chapitreépigraphe ailleurs qu'en première section (avertissement)
directives existantes:::callout, Mermaid, KaTeX, code, tableaux GFMcelles d'aujourd'hui

break rejoint le registre des attributs de scène (règle B4) ; figure, ref, index et epigraph celui des directives (DirectiveNameSchema, créé en P6). :index n'est pas le :term de 12 · Glossaire et annuaire : :term lie une définition, :index marque une occurrence pour l'index du livre. Un même mot peut porter les deux.

3.4 Le jumeau JSON

Chaque fichier a son jumeau kind: "story", comme pour tout autre format. book.json reprend l'exemple de 15 §3.3 (réglages à plat dans settings, "scenes": []). Un chapitre :

JSONJSON
{ "$schema": "https://recit.dev/schema/v1/book.json", "kind": "story", "version": 1, "format": "book", "meta": {}, "scenes": [ { "id": "lorem", "body": "# Lorem ipsum\n\n:::epigraph{source=\"Consectetur, Adipiscing\"}…" }, { "id": "lorem-night", "body": "Sed do :index[eiusmod]{see=tempor} tempor incididunt." }, { "id": "lorem-dawn", "attrs": { "break": "page" }, "body": "Ut enim ad minim veniam." } ] }
  • chapters peut nommer un .json aussi bien qu'un .md : un livre écrit entièrement en JSON (par un CMS ou un agent) est un livre complet.
  • L'épigraphe et les figures restent dans le body, sans champ epigraph ni figures[] : cela suit B2 et 08 §3, mais contredit la ligne book de 09 §2.3 (question 3).
  • recit convert book.md --to json -o book.json convertit l'entrée ; avec --chapters, il convertit aussi chaque chapitre à côté de son .md.

3.5 Le modèle compilé

Le livre compile vers la Story commune. Ce qui lui est propre vit sous une clé dédiée (02-architecture.md §2), story.book, de type BookOutline défini une fois dans @recit/core (lu par @recit/site-stories, la CLI et le MCP) :

TypeScriptTypeScript
export type BookOutline = { chapters: Array<{ id: string; title: string; number?: number; // absent when numbered: false file: string; scenes: { first: number; last: number }; // indexes into story.scenes }>; figures: Array<{ id: string; chapter: string; number: string; caption: string }>; index: Array<{ term: string; sub?: string; see?: string; targets: string[] }>; // anchor ids notes: Array<{ id: string; chapter: string; number: number }>; };

Il est calculé par assembleBook() de @recit/book/node, qui lit l'entrée, résout chaque chapitre par resolveStoryFiles du cœur, compile, numérote et résout les renvois. Le cœur ne connaît toujours aucun format : il ne porte que les schémas et le type.

4. Le rendu

4.1 Le HTML intermédiaire

renderBookHtml(story, { target }) de @recit/book produit une fois un arbre hast par chapitre, plus les pages du pré et du post-texte (front, back), où renvois, numéros de figure et notes sont déjà résolus. Trois sérialisations seulement le distinguent :

CibleFormeLue par
printun document, pages nommées (front, chapter, back), notes en float: footnote si pagedjsla vue imprimable (§4.2)
epubun XHTML par chapitre (hast-util-to-html avec closeSelfClosing, sans balise omise), nav.xhtml@recit/book/epub (§4.4)
webune page par chapitre, notes en fin de chapitre avec retour à l'appelBookReader et l'édition web (§4.3)

4.2 PDF, par le plan 15

PDF3 de 15 · Export PDF fait le travail : spike go/no-go Paged.js (PDF3.0) sur examples/book-novella, BookPdfOptionsSchema (PDF3.1), moteur pagedjs derrière PrintEngine (PDF3.2), chapitres sur page impaire, numérotation romaine puis arabe et marges miroir (PDF3.3), titres courants, table des matières numérotée, notes et target-counter pour l'index et les renvois (PDF3.4), consommation du HTML intermédiaire (PDF3.5), un PDF par langue et artefacts marqués (PDF3.6), e2e (PDF3.7). Le livre fournit :

  • la vue imprimable (@recit/book/print, sur ?print=1) : le HTML print du §4.1, la feuille des pages nommées propres au livre, et whenPrintable() une fois les images décodées et, avec pagedjs, la pagination finie ;
  • les cibles des numéros : chaque :ref et chaque entrée d'index porte un href vers une ancre, que target-counter(attr(href), page) transforme en numéro de page.

Si PDF3.0 dit no-go, le livre sort en P8 avec Chromium seul : page de titre, table des matières en liens sans numéros, notes en fin de chapitre, index sans numéros de page (liens), pas de titres courants par chapitre. Le critère « numéros de table des matières vérifiés » de 06 passe alors à la phase où un moteur les fournit ; c'est une décision à écrire à la fin du spike, pas un repli silencieux.

4.3 L'édition web

  • BookReader (React, @recit/book) : un chapitre par page, précédent et suivant (liens et touches ← → hors champ de saisie), table des matières en tiroir sur mobile, notes en fin de chapitre avec lien retour, figures numérotées, page d'index A–Z, couverture et page de titre. Plain CSS modules compilés (loi de 19 · Style), mode sombre par light-dark(), mesure de ligne autour de 66 caractères, hyphens: auto avec le lang du chapitre.
  • Dans un site (pont book → docs, 08 §0) : @recit/site-stories route une story format: book vers une page de couverture (/<prefix>/<slug>) et une route par chapitre (/<prefix>/<slug>/<chapter-id>), par getRoutes. La table des matières devient la sidebar par getNavigation, que l'API de plugins expose déjà. Pagefind indexe chaque chapitre. Lot B4.0 : vérifier que getNavigation peut fournir une sidebar limitée au préfixe du livre ; sinon, la sidebar est le sommaire de BookReader et la question 7 se pose.
  • Hors site : recit build book.md produit la même édition en statique (une page HTML par chapitre, pour le référencement et la lecture sans JS), avec @recit/book/runtime pour la navigation, comme @recit/deck/runtime.
  • StoryEmbed format="book" montre un chapitre (prop chapter) dans une page MDX.

4.4 L'EPUB 3

@recit/book/epub (Node seulement, comme @recit/motion/node) : writeEpub(story, options) → Uint8Array.

  • mimetype en premier, non compressé ; META-INF/container.xml ; content.opf (métadonnées dc:title, dc:creator, dc:language, dc:identifier, dc:rights, dcterms:modified, manifeste et spine) ; nav.xhtml (table des matières et landmarks) ; un XHTML par chapitre et par page de pré et de post-texte ; la couverture ; la feuille du thème ; les polices du thème, embarquées seulement si leur licence le permet (les polices de @recit/theme sont sous OFL).
  • Notes en <aside epub:type="footnote"> avec appel epub:type="noteref" ; renvois en liens internes ; index en liste de liens.
  • Zip par fflate (zipSync, sortie déterministe : dates fixées), pour que deux builds du même livre donnent le même fichier et la même empreinte dans exports.json.
  • Un EPUB par langue (--all-locales), dir et page-progression-direction posés depuis la locale.
  • Validé par epubcheck en CI, dans un job à part (Java), jamais dans un package (question 6).

5. CLI et MCP

5.1 La CLI

L'aiguillage par format (readStoryFormat()) arrive en P6 avec le report (R4 de 23 · Report) ; le livre y ajoute son module, packages/cli/src/book/.

CommandeEffet pour un livreLot
recit init --format bookbook.md, deux chapitres Lorem, recit.config.ts avec --configB6
recit dev book.mdédition web locale, rechargée quand l'entrée, un chapitre, un jumeau ou une image changeB6
recit build book.mdédition web statique (§4.3)B6
recit export --to pdf book.mdPDF par @recit/paged (PDF3), entrée dans exports.jsonPDF3
recit export --to epub book.mdEPUB 3 (§4.4), entrée kind: "epub" dans exports.jsonB3
recit validate book.mdchapitres, id uniques, renvois, index, notes, images, alt, sans buildB6
recit convert book.md --to json [--chapters]jumeau de l'entrée, et de chaque chapitre avec --chaptersB6

--locale et --all-locales valent pour build, export --to pdf et export --to epub (09 §5). La forme export --to <format> suit la règle DO-8 (03-packaging.md §6), sans sous-commande.

5.2 Le MCP

  • describe_format accepte book : structure des fichiers, directives et leurs schémas, JSON Schema. Guide en ressource recit://guide/book, servi aussi par recit skills export.
  • Nouvel outil book_outline : pour un book.md, rend le BookOutline (chapitres, id de sections et de figures, termes de l'index) et la liste des renvois cassés. Un agent qui écrit un chapitre y trouve les id qu'il peut citer.

6. Le site book.recit.dev

Un site de format (Markdown et recit.config.ts, aucun composant propre), sur le modèle de sites/motion.

  • sites/book, projet nx book, tag scope:site, port de dev 3006 (réservé par 07 · Domaines §4), preview e2e 4306 (e2e/ports.ts), projet Vercel recit-book (nx-ignore book), teinte ambre. SITES.book dans site-kit/src/sites.ts, fiche book passée à available.
  • Pages (en anglais) :
    • index.mdx : FormatHero avec examples/book-novella à feuilleter dans la page, sa source dépliable, et ExportPanel (PDF et EPUB du même livre, lus dans exports.json) ;
    • read/ : l'édition web complète des exemples, par publish.entries (une route par chapitre) ;
    • gallery.mdx : les exemples ; templates.mdx : TemplateCard des gabarits (roman, manuel) ;
    • one-source.mdx : le même chapitre en web, en page PDF et en page EPUB, côte à côte, avec le SourceToggle Markdown ⇄ JSON.
  • Le site déclare les exemples en devDependencies (règle ST-20) ; la doc du format est sur docs.recit.dev (Formats › Book), pas sur le site.
  • PDF et EPUB sont produits en CI ; leur arrivée sur Vercel dépend de la question 8 de 15.

7. Les exemples

Lorem Ipsum, aucun nom réel, images SVG abstraites générées. Dossiers examples/book-* (règle B7), tag scope:example.

ExempleContenuCe qu'il exerce
examples/book-novellaune préface non numérotée et 3 chapitres (08 B5), en en et fr, jumeaux JSON de l'entrée et d'un chapitreépigraphes, notes, break=page, titres courants, pages impaires, deux langues, PDF et EPUB
examples/book-handbookun manuel de 4 chapitres et une annexe, en seulementfigures numérotées, :ref entre chapitres, index avec see, code, tableaux, KaTeX

Le roman seul n'exerce ni figures, ni renvois, ni index : le manuel est proposé en plus (question 11). Le gabarit « thèse » de 07 reste une variante du manuel, sans exemple propre en P8.

8. Les tests

Suivant 21 · Tests.

  • Unitaires (vitest)
    • @recit/core : schémas du §3.3, ISBN-13 refusé si la clé est fausse, défauts de front et back, refus d'une clé inconnue, JSON Schema régénéré.
    • @recit/book : assembleBook (chapitre absent avec la ligne de book.md, chapitre sans format: book, id en double avec les deux emplacements, scène dans l'entrée) ; numérotation des chapitres, figures et notes, par chapitre et par livre ; :ref vers chaque type de cible et vers un id inconnu ; index trié en en et fr ; renderBookHtml vérifié par la structure (titres, ancres, liens résolus), sans snapshot de HTML.
    • @recit/book/epub : l'archive relue par fflate (mimetype en premier et non compressé, chaque fichier du manifeste présent, nav.xhtml égal aux chapitres) ; deux écritures identiques octet pour octet.
  • Jumeau : source-conversion.spec.ts couvre chaque fichier de examples/book-* sans code nouveau. Un test de livre ajoute l'équivalence de l'assemblage : assembleBook sur l'entrée Markdown et sur l'entrée JSON donne le même BookOutline et le même HTML.
  • Stories avec play (axe en erreur) : Book/BookReader (chapitre suivant au clavier et par le lien, le focus va au titre), Book/BookToc (tiroir : ouvre, Escape ferme, le chapitre courant a aria-current), Book/Footnote (l'appel mène à la note, le retour revient à l'appel), Book/Figure, Book/BookIndex.
  • e2e (Playwright)
    • e2e/site-book.spec.ts : pages du site, lecture d'un chapitre à l'autre, recherche Pagefind qui trouve un chapitre, captures en clair et en sombre ;
    • e2e/book-export.spec.ts : PDF lu par pdfjs-dist (numéros de la table égaux aux pages réelles, titres courants absents de l'arbre de structure, signets, un fichier par langue, pages en PNG comparées) ; EPUB relu et, dans le job CI dédié, passé à epubcheck ;
    • e2e/storybook-book.spec.ts : la surface Book du Storybook ;
    • les captures -linux ne s'écrivent que par pnpm test:e2e:update:linux avec ces specs.
  • Couverture : glob packages/formats/book dans le vitest.config.ts racine.

9. Lots

Les identifiants prolongent 08 §3 (B1 à B5) : B0 avant, B6 et B7 après, sous-tâches B1.2. Chaque lot ajoute son plan de version (preminor pour la première publication de @recit/book, puis prerelease) et ses chemins dans NOTICE.md.

Code
B0 ─► B1 ─► B5.1 (novella) ─► PDF3.0 (spike, plan 15) ─┬─► B2 (PDF3.1–3.7) ├─► B3 (EPUB) └─► B4 (édition web) ─► B6 ─► B5.2–B5.4 ─► B7

B2, B3 et B4 ne touchent pas les mêmes fichiers (print, epub, BookReader et site-stories) : trois agents en parallèle, selon 20 · Multi-agents.

B0 — Les types du livre dans @recit/core (S)

  • B0.1 — schema/book.ts : ChapterEntrySchema, pré et post-texte, PublicationSchema, BookSettingsSourceSchema (sans pdf), BookSceneSourceSchema, BookSourceSchema strict.
  • B0.2 — Schémas de ::figure, :ref, :index, :::epigraph ; noms inscrits au registre ; attribut de scène break.
  • B0.3 — Type BookOutline ; section book de recit.config.ts (z.prefault) ; JSON Schemas régénérés.

Sortie : un book.json invalide échoue avec le chemin du champ ; chaque fichier d'un livre fait l'aller-retour.

B1 — Le modèle du livre (M)

  • B1.1 — @recit/book publié ; ./node avec assembleBook (chapitres, id, langues).
  • B1.2 — Numérotation (chapitres, figures, notes), renvois, index, gestionnaires des directives.
  • B1.3 — renderBookHtml et ses trois cibles.

Sortie : examples/book-novella s'assemble, un renvoi cassé donne 02-ipsum.md:14 :ref: unknown id "fig-mapp" (did you mean "fig-map"?).

B2 — Le PDF imprimable (L, plan 15)

  • PDF3.0 à PDF3.7 de 15 · Export PDF. Le livre y apporte @recit/book/print (vue imprimable, pages nommées) et les ancres du §4.2.

B3 — L'EPUB 3 (M)

  • B3.1 — @recit/book/epub, fflate ajouté au catalogue pnpm.
  • B3.2 — Variante kind: "epub" de ExportFileSchema (15 §3.2) ; recit export --to epub.
  • B3.3 — Job CI epubcheck sur les exemples.

Sortie : l'EPUB de examples/book-novella passe epubcheck sans erreur ni avertissement.

B4 — L'édition web (M)

  • B4.0 — Spike : getNavigation de @recit/site-stories limité au préfixe d'un livre.
  • B4.1 — BookReader, BookToc, Footnote, Figure, BookIndex et leurs stories.
  • B4.2 — @recit/site-stories : routes du livre, sidebar, Pagefind, StoryEmbed format="book".
  • B4.3 — @recit/book/runtime pour recit build hors site.

B5 — Exemples, site et doc (M)

  • B5.1 — examples/book-novella (avant PDF3.0, qui s'en sert).
  • B5.2 — examples/book-handbook (selon la question 11).
  • B5.3 — sites/book (§6), SITES.book, port e2e, vercel.json.
  • B5.4 — sites/docs : Formats › Book, pages des directives.

B6 — CLI et MCP (S)

  • B6.1 — Module packages/cli/src/book/ : init, dev, build, validate, convert.
  • B6.2 — MCP : describe_format book, book_outline, guide recit://guide/book.

B7 — Tests transverses et clôture (S)

  • B7.1 — Les trois specs e2e, captures -linux, scripts/e2e-projects.js.
  • B7.2 — Seuils de couverture, pnpm verify et pnpm test:e2e:linux au vert.

Sortie de P8 (celle de 06, précisée) : examples/book-novella produit un PDF imprimable, un EPUB valide (epubcheck) et une édition web ; le PDF est balisé, a des signets, des numéros de table des matières vérifiés (sous réserve du spike PDF3.0, §4.2), un fichier par langue, et il est listé dans exports.json avec l'EPUB ; book.recit.dev est en ligne ; 0.4.0-alpha est publiée sur validation explicite.

10. Risques

RisqueParade
Paged.js figé depuis 2023, lent ou fragile sur un long livrespike PDF3.0 sur l'exemple réel avant tout code ; PrintEngine ; repli Chromium écrit (§4.2) ; temps de pagination mesuré en CI
Assemblage multi-fichiers et test d'aller-retour qui divergentformat: book dans chaque chapitre ; test d'équivalence au niveau du livre en plus de celui des fichiers
XHTML invalide (balise non fermée, entité HTML) dans l'EPUBsérialisation dédiée (closeSelfClosing, entités numériques) ; epubcheck en CI ; tests manuels sur Apple Books, Kobo et Calibre (08 §9)
id en collision entre chapitresunicité vérifiée dans tout le livre, erreur qui cite les deux emplacements
Numéros de page faux dans l'index ou la tablee2e qui compare chaque numéro imprimé à la page réelle (pdfjs-dist)
Polices non embarquables dans l'EPUB ou le PDFpolices OFL du thème ; contrôle fsType reporté (15 §3.8) ; lint de glyphes bloquant pour le PDF
Édition web qui demande un patch du framework de sitesgetNavigation existe ; spike B4.0 ; repli sur le sommaire de BookReader
Livre long : build lent en devcompilation par chapitre avec cache par empreinte ; recit dev ne recompile que le chapitre changé
Typographie par langue (espaces insécables françaises, guillemets) codée dans le seul livretransformation isolée dans un module du livre, déplaçable vers le cœur (question 9)

11. Ce que P8 ne touche pas

Les ponts proposés par d'autres plans vers le livre (la bible du scénariste de 14 · IA et scénario, par exemple) restent dans leur plan et leur phase. Le PDF des pages de doc (PDF4) reste optionnel après P8.

12. Questions ouvertes

  1. Une scène = une section ou un chapitre ? 09 §2.3 dit « section assemblée d'un chapitre » ; 08 §0 et packages/tools/site-kit/src/formats.ts (scene: "a chapter") disent « un chapitre ». Recommandé : la section (§3.2), le chapitre étant un fichier. À trancher, puis à reporter dans 08 et dans formats.ts.
  2. format: book dans chaque chapitre. Exigé en v1 (recommandé, sans cas particulier dans le cœur ni dans le test d'aller-retour), ou inféré depuis l'entrée, au prix d'une découverte des livres dans schema/discovery.ts ?
  3. Épigraphes et figures : directives ou champs ? 09 §2.3 donne à la scène JSON des champs epigraph et figures[] ; 08 §3 et la règle B2 en font des directives du body. Recommandé : les directives, et 09 à corriger.
  4. subtitle. StoryMetaSourceSchema est strict et n'a pas de sous-titre ; 08 §3 en écrit un. Ajouter meta.subtitle (optionnel, commun à tous les formats, recommandé : la page de titre, l'EPUB et l'édition web le lisent), ou settings.subtitle propre au livre ?
  5. Identifiant de l'EPUB. Un urn:uuid stable dérivé du slug quand publication.identifier manque (recommandé), ou un identifiant obligatoire ?
  6. epubcheck en CI. Il demande Java, comme veraPDF (question 6 de 15). Un job dédié avec une version épinglée (recommandé), ou une vérification manuelle avant chaque release ?
  7. Sidebar de l'édition web. Si B4.0 échoue, faut-il un patch du framework de sites pour une sidebar par livre, ou le sommaire de BookReader suffit-il ?
  8. Syntaxe des renvois. :ref[id] avec show=label|page|both (proposé), à aligner avec ce que 12 · Glossaire et annuaire fixera pour ses liens : un seul mot pour les deux, ou deux directives distinctes (:ref et :term, proposé) ?
  9. Typographie par langue. Espaces insécables françaises et guillemets : dans le livre seulement en P8, ou dans le cœur pour tous les formats (report, docs) dès cette phase ?
  10. Numérotation des notes. Par chapitre par défaut (proposé), ou dans tout le livre ?
  11. Deuxième exemple. examples/book-handbook en plus du roman (proposé, sinon figures, renvois et index ne sont exercés par aucun exemple), ou le roman seul, comme l'écrit 08 B5 ?
  12. Parties. Faut-il, dès P8, un niveau « partie » au-dessus des chapitres (chapters imbriqués) pour les manuels et les thèses, ou plus tard ?

13. Incohérences relevées dans d'autres documents (à corriger par l'intégrateur)

  • 08-formats.md §0 (« une scène = un chapitre »), ligne du pont book → docs (« un chapitre → une page ») et packages/tools/site-kit/src/formats.ts (scene: "a chapter") contre 09-langues-et-json.md §2.3 (« section assemblée d'un chapitre ») et 08 §3 (« --- sépare les sections ») : question 1.
  • 09-langues-et-json.md §2.3, ligne book : champs epigraph et figures[] contre la règle B2 et 08 §3 (:::epigraph, ::figure) : question 3.
  • 08-formats.md §3 : le frontmatter d'exemple porte subtitle (absent de StoryMetaSourceSchema) et author: Jane Doe, un nom de personne là où le AGENTS.md demande du Lorem Ipsum.