23 — Report : le format report et report.recit.dev
Statut : proposé · Phase : P6 · Mis à jour : 02/11/2026.
Ce plan détaille la phase P6 de 06 · Feuille de route : le format report (rapports
de données en HTML et en PDF paginé), la fondation @recit/data qui calcule les chiffres, et le
site report.recit.dev. Il précise et met à jour la spécification de 08 · Formats §1
(tâches R1 à R6) sans revenir sur les décisions acceptées : d3 sans DOM pour les graphiques
(question 10 de 06), table des matières sans numéros en P6 (question 2 de
15 · Export PDF), réglages à plat (question 4 de 15), blocs en directives dans
le body (règle B2 de 06). Là où le code impose de préciser une de ces décisions, la précision
est posée en question ouverte (§12).
L'export PDF du report n'est pas redécrit ici : il appartient à PDF0 et PDF1 de 15 · Export PDF, logés dans P6. Ce plan ne dit que ce que le report y apporte (sa vue imprimable, son extension de schéma) et dans quel ordre les lots s'enchaînent.
1. Pourquoi
Un rapport trimestriel, une étude ou un tableau de bord figé s'écrit en Markdown, et chaque chiffre affiché vient des données, jamais du texte.
- Des chiffres justes par construction. Dans un rapport écrit à la main, le texte et le
graphique divergent à la première mise à jour des données. Ici,
:valueet::chartlisent la même source au build : on change le CSV, on reconstruit, le texte suit. - Une source, deux sorties. La même story donne une page web continue (sommaire, ancres, thème
clair ou sombre) et un PDF A4 ou Letter balisé, avec page de titre et tableaux paginés, par la
chaîne unique de
15. - Le premier client de
@recit/data.::chart,::tableet:valueservent ensuite au deck, aux docs, aux cartes (13 · Cartes, qui partagent d3) et aux stories web (pont report → stories en P11) : P6 pose une fondation, pas un format isolé. - Un format lisible par les agents. Les directives sont typées par zod, les erreurs donnent le fichier, la ligne et la colonne la plus proche (« did you mean »), et le MCP décrit les colonnes d'une source : un agent écrit un rapport juste sans deviner.
Non-objectifs de P6 : tableaux de bord interactifs (filtres, info-bulles en JS), requêtes SQL,
Parquet et DuckDB-wasm, camembert (pie, v2 selon 08), table des matières numérotée (P8, moteur
pagedjs), mise à jour des données sans rebuild.
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 report en fait |
|---|---|---|
StoryFormatSchema (inclut report) | packages/core/src/types/index.ts | rien à ajouter : le format est déjà déclaré, et frontmatter.ts l'accepte |
ReportSourceSchema = genericSource(...) | packages/core/src/schema/source.ts | remplacé par un schéma strict (§3.4) : réglages et scènes typés, comme DeckSourceSchema et MotionSourceSchema |
DataSourceSchema = z.record(..., unknown) | packages/core/src/schema/source.ts (champ data de chaque story) | typé (§3.2) ; le champ data existe déjà dans storyFields et dans SidecarSchema, donc le jumeau et le compagnon le portent |
JSON Schema report.json | packages/core/schema/v1/report.json | régénéré par pnpm -F @recit/core schema:json après R0 |
markdownToSource, sourceToMarkdown | packages/core/src/schema/convert.ts | aller-retour du report sans code propre au format : le frontmatter data: va dans data, le reste dans settings |
| Test d'aller-retour de tous les exemples | packages/core/src/__tests__/source-conversion.spec.ts | couvre examples/report-* dès qu'ils existent, avec ou sans .json sur disque |
remarkRecitDirectives, sanitisation | packages/core/src/parser/unified-pipeline.ts, compile/compile-story.ts | point d'accroche des directives de données (§3.3) ; aucun crochet d'extension n'existe aujourd'hui, R0 l'ajoute |
StoryDocument (format docs) | packages/integrations/site-stories/src/StoryDocument.tsx | modèle du rendu continu : scènes l'une après l'autre, titres rétrogradés ; le report y ajoute sommaire et ancres |
Callout, Card, Columns, Mermaid… | packages/components/src | directives existantes, utilisables telles quelles dans une section |
| Runtime autonome du deck | packages/formats/deck/src/runtime (DeckApp, mount), packages/cli/src/render | modèle de @recit/report/runtime pour recit dev et recit build hors site |
| Aiguillage par format dans la CLI | packages/cli/src/motion/source.ts (readMotionKind), commands/dev.ts | généralisé en readStoryFormat() (§6.1) |
| Export PDF du deck | packages/cli/src/commands/export-pdf.ts | migre dans @recit/paged en PDF1 ; le report s'y branche par sa vue imprimable |
@recit/data, @recit/report, @recit/paged | packages/data, packages/formats/report, packages/formats/paged | stubs privés (export {}, 0.0.0) avec leur tag scope:* déjà posé |
| Fiche du format sur les sites | packages/tools/site-kit/src/formats.ts (status: "planned", phase: "P6") | passe à available avec site: "report" en R8 |
d3-dsv, d3-scale, d3-shape 3 | lockfile (transitifs de Mermaid) | deviennent des dépendances directes de @recit/data, sans nouvelle bibliothèque |
yaml 2 | lockfile | lecture des sources YAML |
Ce qui manque et que ce plan crée : les schémas des directives de données, le crochet de directives
dans compileStory, @recit/data (chargement, requêtes, formatage, SVG), @recit/report (vue web,
vue imprimable, pont vers le deck), l'aiguillage de la CLI, les exemples, le site.
3. Le modèle
3.1 Une story report
Une scène est une section : le séparateur --- en ouvre une, son premier titre est l'entrée du
sommaire, son id est l'ancre (#regions). Le titre du rapport est meta.title, jamais un titre
de scène. Les réglages s'écrivent à plat dans le frontmatter (pas de clé report:), et sont la
section report de recit.config.ts (09-langues-et-json.md §2.2).
Markdown
3.2 Sources de données
Les sources sont nommées dans data:. Une entrée est un chemin (relatif au fichier de la story), un
objet qui précise le format et les colonnes, ou des lignes inline. La même forme vaut dans le
frontmatter, dans le jumeau (data) et dans un compagnon (kind: "sidecar", qui peut remplacer le
chemin d'une source, par exemple en CI).
TypeScript
DataSourcesSchema remplace DataSourceSchema dans storyFields et SidecarSchema : roadmap et
calendar (P9, P10) en héritent. Règles :
- une source introuvable, illisible ou vide est une erreur avec la ligne du frontmatter ;
- une colonne déclarée absente, ou une cellule qui ne se lit pas dans le type déclaré, est une
erreur avec le fichier de données et sa ligne (
data/sales.csv:14: "n/a" is not a number) ; - les dates s'écrivent en ISO 8601 (
2026-10,2026-10-01) ; aucun format local n'est deviné ; - les nombres d'un CSV s'écrivent avec un point décimal, sans séparateur de milliers. Le formatage
local se fait à l'affichage (
Intl), jamais à la lecture.
3.3 Les directives de données
Les quatre directives sont des directives dans le body de la scène (règle B2) : SceneSource
ne gagne aucun champ charts[], tables[] ni kpis[] (09 §2.3). Leurs attributs sont validés
par des schémas zod de @recit/core, et le cœur ne garde que leurs noms (règle B4) ; leur rendu vit
dans @recit/data.
TypeScript
Les noms exacts des API zod/mini (z._default, z.length) sont à vérifier à l'implémentation,
comme dans 15 §3.2. Les types sont dérivés (z.infer) et exportés par @recit/core/schema ;
aucun package ne les redéfinit.
| Directive | Rendu web et PDF | Erreurs (avec la ligne) |
|---|---|---|
:value{…} | un <data value="…"> avec le texte formaté par Intl.NumberFormat ou Intl.DateTimeFormat de la locale | source ou champ inconnus (« did you mean »), agg sur une colonne non numérique, growth sans by ni period |
::chart{…} | <figure> avec un SVG rendu au build (d3 sans DOM), role="img", <title>, <desc> généré (min, max, tendance), légende en HTML | colonne x ou y inconnue, y non numérique, plus de 8 séries (illisible : erreur, pas d'avertissement) |
::table{…} | <table> avec <caption>, <thead> répété à chaque page en PDF, nombres alignés et formatés | colonne inconnue dans columns ou sort |
:::kpi | grille de chiffres clés (<dl>), une entrée par élément de liste | élément sans :value |
| directives existantes | :::callout, :::card, :::col, Mermaid, KaTeX, code, inchangées | celles d'aujourd'hui |
Le crochet de directives (R0). compileStory n'a pas de point d'extension :
createSlidePlugins fixe la liste des plugins remark et rehype. R0 ajoute une option typée :
TypeScript
DirectiveNameest l'énumération du registre du cœur (DirectiveNameSchema, déjà annoncée par15§3.2 pour le manifeste d'exports) ; R0 y inscritvalue,chart,tableetkpi.- Le nœud produit est inséré après
rehypeSanitize: il est généré par Récit, jamais copié du source, et chaque texte venu des données passe par un nœud texte hast (échappé). L'allowlist de sanitisation n'a donc pas à accepter le SVG de l'auteur. - Une directive enregistrée sans gestionnaire (un deck compilé sans
@recit/data) donne une erreur claire (::chart needs @recit/data; recit build loads it for you), jamais undivvide.
3.4 Le jumeau JSON et les réglages typés
Le report n'a aucun champ de scène propre (09 §2.3) : la scène JSON a id, attrs, body,
notes. Les réglages sont typés à leur phase (L6) :
TypeScript
pdf n'entre dans le schéma qu'avec PDF1.6 (« aucun champ avant sa phase », 15 §3.2) ; avant, R0
livre le schéma sans lui. Le schéma est strict : une clé inconnue du frontmatter d'un report est
une erreur avec sa ligne, au lieu d'être ignorée (le deck garde looseObject pour ses clés
historiques). ReportConfigSchema (la section report de recit.config.ts) est le même objet,
avec z.prefault pour que ses défauts s'appliquent quand la section est absente (règle du
AGENTS.md).
Jumeau de l'exemple du §3.1 (quarterly.json) :
JSON
- Le jumeau référence les mêmes fichiers de données : ils ne sont jamais copiés dans le JSON.
Des lignes inline (
rows) sont permises dans les deux formes (YAML du frontmatter, JSON). - Le multilingue suit
09:quarterly.fr.md, ou desbodyenLocalized. Les libellés (title,caption,altdes directives) sont dans lebody, donc traduits avec lui. Les données restent les mêmes ; le formatage suit la locale (1 234,5 €enfr). schema/parity.tsvérifie quedataetsettingssont identiques d'une langue à l'autre, saufpdf.output.
3.5 Le modèle compilé
Le report compile vers la Story commune, sans second modèle (règle 5 de context.md) : chaque
scène a son html où les directives sont déjà rendues (texte formaté, SVG, tableaux). Les données
ne voyagent pas vers le client : ClientStory ne contient que le HTML, pas les lignes. Les
seules informations propres au format sont, sous une clé dédiée (02-architecture.md §2),
l'empreinte des sources (report.data: { name, file, rows, sha256 }[]), pour exports.json, la
surveillance de recit dev et le cache du build.
4. Le rendu
4.1 Web
@recit/report(formats, publié) expose :.:Report(racine),ReportToc(sommaire latéral, section courante suivie au scroll avecIntersectionObserver, repli en tiroir sur mobile),ReportSection,ReportHeader(titre, auteur, date localisée) ;./runtime:ReportAppetmount, pour la page autonome derecit devetrecit build, comme@recit/deck/runtime;./print: la vue imprimable (§4.2) ;./bridge: le pont vers le deck (§5) ;./styles.css: la feuille compilée de ses modules (Lightning CSS, table typéeX.module.css.ts), comme@recit/deck.
@recit/data(fondation, publié) exposeValue,DataTable,Chart(composants React qui reçoivent des données déjà évaluées, pour une page MDX ou une story de Storybook),./node(chargement des fichiers, Node seulement) et./directives(les gestionnaires du §3.3). Le rendu du SVG est une fonction pure,renderChart(spec, rows, options) → hast, sans DOM, appelée au build.- Style : plain CSS modules (pas de Mantine dans un package publié, pas de PostCSS), loi du plan
19 (19 · Style). Les couleurs des séries viennent des jetons du thème par des classes
du module (
classes.series1àseries8,light-dark()), jamais par un attributfillen dur : le même SVG suit le mode sombre. Un thème nomméreportrejoint@recit/theme(typographie de lecture, interlignage, tableaux), comme le prévoit R3 de08. - Intégration au framework de sites :
@recit/site-storiesroute une storyformat: reportversReport(comme elle routemotionversMotionViewer), etStoryEmbedaccepteformat="report"(rendu dans la page, sans plein écran).Reportn'est pas un module lourd au sens duAGENTS.md(aucun Shiki ni Mermaid à l'entrée : le HTML est déjà rendu) ; il reste chargé dans le code découpé par route du plugin. - Accessibilité :
<main>, un<nav aria-label>pour le sommaire, un<section aria-labelledby>par scène,<figure>et<figcaption>pour les graphiques, et, sous chaque graphique, un<details>« Data » qui montre leDataTabledes points tracés (le tableau accessible, comme pour::mapdans13). - Rien de calculé dans le navigateur :
Intltourne au build, en Node. Le HTML prérendu et l'hydratation affichent donc le même texte, et les captures ne dépendent pas de l'ICU du navigateur.
4.2 PDF, par le plan 15
Le report est, avec le deck, le premier client de la chaîne de 15 (PDF1). Il n'en fournit que deux
choses :
- sa vue imprimable (
@recit/report/print, rendue sur?print=1) : page de titre (titre, description, auteur, date), table des matières en liens, sans numéros, sections dans l'ordre,break-inside: avoidsur les figures et les KPI,<thead>répété (display: table-header-group), sommaire latéral et contrôles masqués, puiswhenPrintable()de@recit/core/printquand les polices sont chargées ; - son extension de schéma (
ReportPdfOptionsSchema, déjà esquissée dans15§3.2 :enginelimité àchromiumen P6,tables.repeatHeader).
En-tête courant (titre) et pied (numéro de page) passent par les boîtes de marge de Chromium,
générées par pageCss(options) de @recit/paged. Les signets viennent des titres de section
(outline: true, qui force tagged). engine: pagedjs et la table numérotée arrivent en P8
(PDF3.2), en option : aucun travail du report n'est prévu pour cela en P6.
4.3 Règles de contenu
- Aucun chiffre en dur dans un
::chart: il n'a pas d'attribut de valeur, seulement des noms de colonnes. - Lint « nombre écrit à la main » : un nombre de trois chiffres ou plus, ou suivi de
%,€,$, dans une section qui contient une directive de données, donne un avertissement (08§9), désactivable parrules.hardcodedNumbers: false(ReportRulesSchema, qui étendRulesConfigSchemaparz.extend). Les années (2026) et les numéros d'idsont exclus. - Densité : les règles de densité du deck ne s'appliquent pas à une page continue. Le report garde seulement l'avertissement de section trop longue sans titre intermédiaire (au-delà de 600 mots). Les règles du deck s'appliquent au résultat du pont (§5).
5. Le pont report → deck
recit convert quarterly.md --to deck produit une story format: deck en Markdown (ou son jumeau
JSON avec -o deck.json). La correspondance est fermée (08 §0) :
| Report | Deck |
|---|---|
meta, data | identiques |
| une section (scène) | une slide, même id |
::chart, ::table, :::kpi, :value | gardés tels quels (le deck les rend par les mêmes gestionnaires de @recit/data) |
::table de plus de 8 lignes | limit=8 ajouté, avec un avertissement |
| prose au-delà des règles de densité | erreur avec la ligne, jamais de résumé inventé : l'auteur coupe la section en deux |
toc, numbering, pdf | abandonnés (avertissement) ; pdf du deck reprend ses défauts |
La fonction est pure, reportToDeck(source: ReportSource): { source: DeckSource; diagnostics },
dans @recit/report/bridge. Elle travaille sur la source, pas sur la Story compilée, pour que la
sortie soit une source éditable. Le deck gagne au passage les directives de données (un deck peut
contenir un ::chart dès P6) : @recit/deck ne dépend pas de @recit/data, c'est la CLI et
@recit/site-stories qui passent les gestionnaires à compileStory.
6. CLI et MCP
6.1 La CLI
Aujourd'hui dev, build et export supposent un deck, sauf dev, qui aiguille vers motion par
readMotionKind(). R4 généralise l'aiguillage :
readStoryFormat(file)(Markdown :formatdu frontmatter ; JSON :formatdu jumeau) remplacereadMotionKind()comme premier test ; chaque format a son module,packages/cli/src/report/, commepackages/cli/src/motion/;- la CLI charge les sources par
@recit/data/node, puis appellecompileStoryavecdirectives: dataDirectives(tables, locale).
| Commande | Effet pour un report | Lot |
|---|---|---|
recit init --format report | report.md, data/sample.csv (Lorem), recit.config.ts avec --config | R4 |
recit dev report.md | page autonome, rechargement à chaque changement du .md, du .json ou d'un fichier de données | R4 |
recit build report.md | site statique (index.html, assets) ; échoue sur une erreur de données | R4 |
recit export --to pdf report.md | PDF par @recit/paged, écrit dans exports.json ; --all-locales donne un PDF par locale | PDF1.8 |
recit export --html report.md | HTML autonome (comme le deck), graphiques inclus | R4 |
recit validate report.md | schéma, puis chargement des données et évaluation de chaque directive, sans build | R4 |
recit convert report.md --to json | deck | jumeau JSON ; pont vers le deck (§5) | R4, R5 |
Le flag --to de convert mélange aujourd'hui deux sens (json, md : la représentation ; film
: le modèle de rendu de motion) et son aide parle encore de deck pour le film (§13). Le pont
ajoute un troisième sens, un format cible : question 5.
6.2 Le MCP
@recit/mcp ne décrit aujourd'hui que motion et deck (FORMATS de
packages/mcp/src/server.ts).
describe_formatacceptereport: syntaxe Markdown, directives et leurs schémas, JSON Schema du jumeau. Guide en ressourcerecit://guide/report, rédigé une fois et servi aussi parrecit skills export(règle B9).- Nouvel outil
describe_data: pour une story et un nom de source, rend les colonnes, leurs types inférés, le nombre de lignes et trois lignes d'exemple. C'est ce qui permet à un agent d'écrire un:valuejuste du premier coup. - La validation passe par l'outil de validation existant étendu à tous les formats, s'il existe à ce
moment-là ; sinon par
recit validate, que le guide cite.
7. Le site report.recit.dev
Un site de format comme motion.recit.dev (sites/motion) : du Markdown et un recit.config.ts,
aucun composant propre (règle « sites are Markdown and config »).
sites/report, projet nxreport, tagscope:site, port de dev 3004 (réservé par 07 · Domaines §4), preview e2e 4304 (e2e/ports.ts), projet Vercelrecit-reportavec sonvercel.json(nx-ignore report), teinte sarcelle.SITES.reportdanspackages/tools/site-kit/src/sites.ts, fichereportdeformats.tspassée àavailableavecsite: "report".- Pages (
sites/report/pages, en anglais) :index.mdx:FormatHeroavecexamples/report-quarterlyrendu dans la page, sa source dépliable, et, à côté, l'aperçu de son PDF (ExportPanel, qui litexports.json) ;gallery.mdx:report-quarterly,report-study;from-data.mdx: la démo « les chiffres viennent des données » (:value,::chart,::tableavec leur source, et unSourceToggleMarkdown ⇄ JSON) ;pdf.mdx: le même rapport en PDF, ce que la chaîne garantit (balisage, signets) ;report-to-deck.mdx: le pont, avec la slide obtenue enStoryEmbed format="deck".
publish.entrieslit les exemples dansexamples/(pas de copie). Le site déclare ces exemples endevDependencies(règle ST-20 duAGENTS.md), sinon un build affecté peut sauter le site.- La doc du format reste sur docs.recit.dev (
Formats › Report,Components › Chart,Table,Value,Kpi) : le site de format n'a pas de documentation (07). - Les PDF sont produits en CI, jamais sur Vercel ; leur arrivée sur le site dépend de la question 8
de
15(déploiement prébuilt). Sans fichier,ExportPaneln'affiche pas de lien.
8. Les exemples
Contenu en Lorem Ipsum, aucun nom réel, données générées. Dossiers examples/report-* (règle B7),
tag scope:example, scripts dev, build, export comme examples/motion-hello.
| Exemple | Contenu | Ce qu'il exerce |
|---|---|---|
examples/report-quarterly | quarterly.md, quarterly.fr.md, quarterly.json (jumeau), data/sales.csv (24 mois), data/regions.json, data/targets.yaml, recit.config.ts | les trois formats de fichier, :value avec growth, line, bar, ::table, :::kpi, deux langues, PDF |
examples/report-study | study.md, study.json, data/survey.csv (300 lignes, colonnes catégorielles), une section MDX | area, dot, color=, stack, where, un long tableau paginé en PDF |
Les données sont produites une fois par un script déterministe (graine fixe) et committées : le
build ne génère rien au hasard, les captures sont stables. Le script lui-même est un scripts/ de
l'exemple, hors de la chaîne de build.
9. Les tests
Suivant la pyramide de 21 · Tests.
- Unitaires (vitest)
@recit/core: parse de chaque schéma (§3.2 à §3.4), refus d'une clé inconnue, défauts parz.prefault; crochet de directives (gestionnaire appelé avec la position, directive sans gestionnaire = erreur) ; le nœud inséré après la sanitisation ne laisse passer aucun texte non échappé (test avec un CSV contenant<script>) ; JSON Schema régénéré.@recit/data: lecture CSV, JSON, YAML, inférence des types, erreurs de cellule avec ligne ; chaque agrégation,growthsur des mois et des trimestres,where; formatageenetfr(Intl, avec une ICU complète en CI) ;renderChartvérifié par la géométrie (nombre de points, domaine des échelles), pas par un snapshot de HTML (interdit par21).@recit/report:reportToDeck(correspondance, erreur de densité avec ligne).@recit/cli:readStoryFormat, aiguillage dedev,build,validatesur un report ; message d'erreur de:valueinconnu identique à08§1.
- Jumeau :
source-conversion.spec.tscouvreexamples/report-*sans code nouveau (compile(md) == compile(toJson(md))et inversement). Un cas ajouté vérifie l'égalité du HTML rendu avec les données chargées, pas seulement des sources. - Stories avec
play(une par composant, axe en erreur) :Data/Value,Data/DataTable(tri au clavier si activé, en-têtesscope),Data/Chart(nom accessible,<details>qui s'ouvre et montre le tableau),Report/Report(le sommaire suit la section visible, le lien déplace le focus),Report/ReportToc(tiroir mobile : ouvre,Escapeferme). Titres ajoutés à l'arborescence de21§3.1,node scripts/check-stories.jsles exige. - e2e (Playwright)
e2e/site-report.spec.ts: chaque page du site, captures pleine page en clair et en sombre, aucune erreur console, ancres du sommaire ;e2e/report-export.spec.ts:recit export --to pdfsurexamples/report-quarterly, lu parpdfjs-dist(PDF0.4a) : nombre et taille des pages, signets égaux aux titres, PDF balisé,Lang, polices embarquées, en-tête répété d'un tableau coupé, pages rendues en PNG et comparées ;e2e/storybook-report.spec.ts: la surfaceReportetDatadu Storybook ;scripts/e2e-projects.jsreçoit les trois specs (sitesreportetrecit-storybook).- Les captures
-linuxde référence ne s'écrivent que parpnpm test:e2e:update:linux e2e/site-report.spec.ts e2e/report-export.spec.ts.
- Couverture : un glob par package nouveau (
packages/data,packages/formats/report) dans levitest.config.tsracine, avecautoUpdate.
10. Lots
Les identifiants prolongent ceux de 08 §1 (R1 à R6) : R0 est ajouté avant, R7 à R9 après, et les
sous-tâches s'écrivent R1.2 (règle B10). Chaque lot laisse pnpm check et les tests de ce qu'il
touche au vert, et ajoute son plan de version (pnpm exec nx release plan, preminor pour la
première publication de @recit/data et @recit/report, puis prerelease). NOTICE.md reçoit les
nouveaux chemins dans le même changement.
Ordre et parallélisme (vagues de 20 · Multi-agents) :
Code
R0 — Les types du report dans @recit/core (S)
- R0.1 —
schema/data.ts:DataSourcesSchema, schémas des quatre directives, types dérivés ;DataSourceSchemaremplacé dansstoryFieldsetSidecarSchema. - R0.2 —
ReportSettingsSourceSchema(sanspdf),ReportSceneSourceSchema,ReportSourceSchemastrict ;ReportConfigSchemadansconfig/validate.ts(sectionreport,z.prefault). - R0.3 —
DirectiveNameSchemaavecvalue,chart,table,kpi; optiondirectivesdecompileStory, insertion après la sanitisation, erreur pour une directive sans gestionnaire. - R0.4 — JSON Schemas régénérés (
report.json,sidecar.json,story.json).
Sortie : un JSON de report invalide échoue avec le chemin du champ ; un report Markdown fait
l'aller-retour ; compileStory accepte un gestionnaire de test.
R1 — @recit/data (M)
- R1.1 — Package publié (
privateretiré,tsdown, exports.,./node,./directives,./styles.css), dépendancesd3-dsv,d3-scale,d3-shape,yaml(déjà dans le lockfile). - R1.2 — Chargement et validation des sources, inférence des types, diagnostics avec fichier et ligne.
- R1.3 — Requêtes : agrégations,
where,growth; formatage parIntl. - R1.4 —
renderChart(hast, sans DOM) pourline,bar,area,dot; axes, légende,<desc>généré. - R1.5 — Gestionnaires des directives (
:value,::chart,::table,:::kpi) avec « did you mean » ; composantsValue,DataTable,Chartet leurs stories.
Sortie : un test compile examples/report-quarterly (fixture provisoire) en HTML sans DOM ; un
champ mal écrit donne report.md:12 :value: field "revnue" not in sales (did you mean "revenue"?).
R2 — La chaîne PDF (plan 15)
- PDF0 puis PDF1 de 15 · Export PDF, sans changement. R3 fournit la vue
imprimable dont PDF1.7 a besoin ; PDF1.6 ajoute
pdfàReportSettingsSourceSchema.
R3 — @recit/report (M)
- R3.1 — Package publié ;
Report,ReportToc,ReportSection,ReportHeaderet leurs stories ; modules CSS compilés, mode sombre parlight-dark(). - R3.2 —
./runtime(ReportApp,mount) pour la page autonome. - R3.3 —
./print: la vue imprimable du §4.2, branchée surwhenPrintable(). - R3.4 — Thème nommé
reportdans@recit/theme.
Sortie : les stories Report/* passent avec axe ; la vue imprimable se rend sur ?print=1.
R4 — CLI (M)
- R4.1 —
readStoryFormat(), modulepackages/cli/src/report/, aiguillage dedev,build,export --html,validate. - R4.2 — Surveillance des fichiers de données dans
recit dev. - R4.3 —
recit init --format report. - R4.4 — Avec PDF1.8 :
recit export --to pdfsur un report.
Sortie : recit build et recit export --to pdf fonctionnent sur examples/report-quarterly
(critère de P6).
R5 — Pont report → deck (S)
- R5.1 —
reportToDeckdans@recit/report/bridge, tests de correspondance et de densité. - R5.2 —
recit convert --to deck(forme du flag selon la question 5) ; la CLI et@recit/site-storiespassent les gestionnaires de données au deck.
R6 — Exemples (S)
- R6.1 —
examples/report-quarterly(en, fr, jumeau), données générées et committées. - R6.2 —
examples/report-study.
R7 — Intégration aux sites et au MCP (S)
- R7.1 —
@recit/site-stories: routeformat: report,StoryEmbed format="report", chargement des données et surveillance dans le plugin Vite (addWatchFile), index Pagefind des sections. - R7.2 — MCP :
describe_format report,describe_data, guiderecit://guide/report.
R8 — report.recit.dev et la doc (M)
- R8.1 —
sites/report(§7),SITES.report, ficheavailable,vercel.json, port e2e. - R8.2 —
sites/docs:Formats › Report, pages des composants de données.
R9 — Tests transverses et clôture (S)
- R9.1 — Les trois specs e2e, captures
-linuxde référence,scripts/e2e-projects.js. - R9.2 — Seuils de couverture,
pnpm verifyetpnpm test:e2e:linuxau vert.
Sortie de P6 (celle de 06, inchangée, précisée) : recit build et recit export --to pdf
fonctionnent sur examples/report-quarterly ; chaque chiffre est calculé et un champ inconnu donne
une erreur avec ligne ; le PDF du deck passe par @recit/paged sans régression ; les PDF du deck et
du report sont balisés, ont des signets, sont vérifiés en e2e et listés dans exports.json ;
report.recit.dev est en ligne ; 0.2.0-alpha est publiée sur validation explicite.
11. Risques
| Risque | Parade |
|---|---|
Le formatage Intl diffère selon l'ICU (Node sans ICU complète, versions de navigateur) | formatage au build seulement ; Node avec ICU complète en CI (vérifiée par un test fr) ; captures prises dans le conteneur Linux de référence |
| Un graphique d3 sans DOM moins soigné qu'Observable Plot (ticks, étiquettes qui se chevauchent) | quatre types seulement en P6 ; règles de ticks testées ; plus de 8 séries refusé |
| Injection par les données (un CSV qui contient du HTML) | nœuds hast texte, insertion après la sanitisation, test dédié |
| Données lourdes dans le bundle client | les lignes ne quittent jamais le build ; ClientStory ne porte que le HTML ; size-limit sur @recit/report |
growth ambigu (quel point de comparaison ?) | by et period obligatoires ; définition écrite dans la doc et le guide MCP ; question 3 |
| Le crochet de directives devient une API de plugins générale | il n'accepte que les noms du registre (DirectiveName), pas des plugins remark arbitraires |
| Le deck gagne des directives de données sans les tests d'un format | le pont (R5) porte les tests du deck avec données ; captures du deck inchangées sans données |
P6 surchargé (PDF0, PDF1, ::map, report, site) | ordre du §10 ; ::map ne démarre qu'après R1 ; le site (R8) peut glisser sans bloquer le format |
| PDF du site absent sur Vercel | ExportPanel piloté par exports.json ; question 8 de 15 |
12. Questions ouvertes
aggobligatoire. Le §3.3 rendaggobligatoire sur:value, sans défaut, pour qu'aucun chiffre ne soit choisi implicitement. D'accord, oulastpar défaut ?- Filtre
where. Égalités seulement en v1 (region=North,year=2026). Faut-il des comparaisons (>,<) et des listes dès P6, au prix d'un petit langage à spécifier et à tester ? - Définition de
growth. Proposé : dernière période complète debycomparée à la précédente, enperiod(month,quarter,year). Faut-il aussi la comparaison sur un an (yoy) ? - Nombre écrit à la main. Avertissement (proposé,
08§9) ou erreur en mode--strict? recit convert --to deck.--todésigne déjà une représentation (json,md) ou le modèle de rendu de motion (film). Proposé :--to <format>pour un pont (deck), la représentation se déduisant de l'extension de-o(Markdown par défaut). Autre option : un flag distinct,--format deck.- Schéma strict. Un report refuse une clé inconnue du frontmatter (proposé), là où le deck l'ignore. D'accord pour tous les nouveaux formats, ou alignement sur le deck ?
- Thème
report. Un thème nommé dans@recit/theme(prévu par08R3), ou seulement des réglages typographiques du format sur les thèmes existants ? - Graphiques interactifs. Info-bulles et survol demandent du JS client. Hors de P6 (proposé) ; à prévoir en option après la 1.0 ?
- Directives de données dans le deck dès P6. Le pont (§5) les y fait entrer. Faut-il les documenter et les montrer sur presentation.recit.dev dès P6, ou les garder discrètes jusqu'à P7 ?
13. Incohérences relevées dans d'autres documents (à corriger par l'intégrateur)
packages/cli/src/index.ts: l'aide deconvert --to(« deck (motion deck.json) ») et le message d'erreur (« --to must be json, md or deck ») parlent dedeck, alors que la cible acceptée estfilm(ConvertTargetdepackages/cli/src/commands/convert.ts) et que le vocabulaire réserve « deck » aux slides.15-export-pdf.md§2.1 décritSTORY_FORMATScomme « un tableau typé à la main, sans schéma zod » : le code dérive déjàSTORY_FORMATSdeStoryFormatSchema(types/index.ts). Une partie de PDF0.1 est faite.context.md(table des plans, ligne de15) et03-packaging.md§6 (ligne@recit/cli, «export pdf --site») écrivent la commande en sous-commande, contre la règle DO-8 qu'ils citent (recit export --to pdf).08-formats.md§0 place@recit/pageddanspackages/tools/pagedcomme un fait ; le stub est encore danspackages/formats/pagedjusqu'à PDF1.0.