# 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](/06-roadmap) : 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](/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](/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ément                                       | Où                                                                            | Ce que le livre en fait                                                                                                   |
| --------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `StoryFormatSchema` (inclut `book`)           | `packages/core/src/types/index.ts`                                            | rien à ajouter : le format est déclaré                                                                                    |
| `BookSourceSchema = genericSource("book")`    | `packages/core/src/schema/source.ts`                                          | remplacé par un schéma strict (§3.3)                                                                                      |
| `resolveStoryFiles`, `mergeSidecar`           | `packages/core/src/schema/assemble.ts`                                        | résout chaque fichier du livre (entrée et chapitres) : jumeau, compagnon, variantes de langue, sans règle propre au livre |
| `markdownToSource`, `sourceToMarkdown`        | `packages/core/src/schema/convert.ts`                                         | aller-retour de chaque fichier, sans code propre au format                                                                |
| Test d'aller-retour de tous les exemples      | `packages/core/src/__tests__/source-conversion.spec.ts`                       | couvre 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` 9                         | lockfile                                                                      | sérialise le HTML intermédiaire (HTML pour le web et le PDF, XHTML pour l'EPUB)                                           |
| Crochet de directives de `compileStory`       | ajouté en P6 par R0 de [23 · Report](/23-report)                              | porte `::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`, `getNavigation` | `packages/site/src/lib/core/plugins.ts`                                       | l'édition web : une route par chapitre et la table des matières en sidebar, sans patch du framework de sites (§4.3)       |
| `StoryDocument`                               | `packages/integrations/site-stories/src/StoryDocument.tsx`                    | modèle du rendu d'un chapitre en page continue                                                                            |
| Fiche du format                               | `packages/tools/site-kit/src/formats.ts` (`status: "planned"`, `phase: "P8"`) | passe à `available` avec `site: "book"` en B5                                                                             |
| `@recit/book`                                 | `packages/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

```
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` :

```md
---
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` :

```md
---
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 ».

```ts
// 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)
:

```ts
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ément                      | Rôle                                                                                                             | Erreur (avec fichier et ligne)                                  |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `chapters`                   | ordre des chapitres, numérotés sauf `numbered: false`                                                            | fichier listé mais absent ; fichier présent sans `format: book` |
| H1 d'un chapitre             | titre du chapitre, entrée de la table des matières, titre courant                                                | chapitre sans H1, ou deux H1                                    |
| `--- id=… break=page\|right` | section ; saut de page (`right` : page impaire) en PDF, sans effet sur le web                                    | valeur 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 `body`     | image 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 locale                    | `see` 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 GFM                                                                 | celles 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](/12-glossaire-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 :

```json
{
  "$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) :

```ts
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 :

| Cible   | Forme                                                                                               | Lue par                              |
| ------- | --------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `print` | un document, pages nommées (`front`, `chapter`, `back`), notes en `float: footnote` si `pagedjs`    | la vue imprimable (§4.2)             |
| `epub`  | un XHTML par chapitre (`hast-util-to-html` avec `closeSelfClosing`, sans balise omise), `nav.xhtml` | `@recit/book/epub` (§4.4)            |
| `web`   | une page par chapitre, notes en fin de chapitre avec retour à l'appel                               | `BookReader` et l'édition web (§4.3) |

### 4.2 PDF, par le plan 15

PDF3 de [15 · Export PDF](/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](/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](/23-report)) ; le livre y ajoute son module, `packages/cli/src/book/`.

| Commande                                       | Effet pour un livre                                                                      | Lot  |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------- | ---- |
| `recit init --format book`                     | `book.md`, deux chapitres Lorem, `recit.config.ts` avec `--config`                       | B6   |
| `recit dev book.md`                            | édition web locale, rechargée quand l'entrée, un chapitre, un jumeau ou une image change | B6   |
| `recit build book.md`                          | édition web statique (§4.3)                                                              | B6   |
| `recit export --to pdf book.md`                | PDF par `@recit/paged` (PDF3), entrée dans `exports.json`                                | PDF3 |
| `recit export --to epub book.md`               | EPUB 3 (§4.4), entrée `kind: "epub"` dans `exports.json`                                 | B3   |
| `recit validate book.md`                       | chapitres, `id` uniques, renvois, index, notes, images, `alt`, sans build                | B6   |
| `recit convert book.md --to json [--chapters]` | jumeau de l'entrée, et de chaque chapitre avec `--chapters`                              | B6   |

`--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](/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`.

| Exemple                  | Contenu                                                                                                        | Ce qu'il exerce                                                                             |
| ------------------------ | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `examples/book-novella`  | une 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-handbook` | un manuel de 4 chapitres et une annexe, `en` seulement                                                         | figures 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](/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`.

```
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](/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](/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

| Risque                                                                                      | Parade                                                                                                                                       |
| ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Paged.js figé depuis 2023, lent ou fragile sur un long livre                                | spike 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 divergent                              | `format: 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'EPUB                                 | sé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 chapitres                                                           | unicité vérifiée dans tout le livre, erreur qui cite les deux emplacements                                                                   |
| Numéros de page faux dans l'index ou la table                                               | e2e qui compare chaque numéro imprimé à la page réelle (`pdfjs-dist`)                                                                        |
| Polices non embarquables dans l'EPUB ou le PDF                                              | polices 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 sites                                      | `getNavigation` existe ; spike B4.0 ; repli sur le sommaire de `BookReader`                                                                  |
| Livre long : build lent en dev                                                              | compilation 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 livre | transformation 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](/14-ia-scenario), 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](/12-glossaire-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.
