26 — Roadmap : le format roadmap et roadmap.recit.dev
Statut : proposé · Phase : P9 · Mis à jour : 02/11/2026.
Ce plan détaille la phase P9 de 06 · Feuille de route : le format roadmap, la
fondation @recit/time dans sa première moitié (dates, trimestres, ICS en écriture), et le site
roadmap.recit.dev. Il part de la spécification de 08 · Formats §4, qu'il précise sans
la contredire ; là où le code ou une règle acceptée oblige à s'en écarter, l'écart est posé en
question ouverte (§12). Il s'appuie sur le code tel qu'il est le 02/11/2026, pas sur les textes plus
anciens : packages/core/src/schema, packages/formats/motion, packages/cli,
packages/integrations/site-stories, examples/motion-hello et e2e/site-motion.spec.ts.
Il dépend de 02 · Architecture, de 03 · Packaging §6 et §8, de
07 · Domaines, de 09 · Langues et JSON, de
12 · Glossaire et annuaire (l'analyseur {k=v}, lot DI0.2, créé avant ce
plan), de 15 · Export PDF (chaîne d'impression, manifeste exports.json) et de
21 · Tests. Le calendrier, qui consomme les jalons de la roadmap, est le plan 27.
Chaque choix porte une recommandation. Rien n'est implémenté avant l'accord de l'utilisateur.
1. Pourquoi
- Une roadmap est une story comme une autre. Un titre, des pistes, des éléments datés ou non, des jalons : c'est un document que l'on écrit, que l'on traduit et que l'on publie. Aujourd'hui, on l'écrit dans un outil fermé (GitHub Projects, Linear, Notion), ou dans un Markdown sans structure que personne ne sait afficher en frise. Récit donne les deux : le texte en Markdown, les statuts en JSON typé, et trois vues.
- Le format prouve la promesse « une source, plusieurs rendus » sur des données. Le deck et le motion rendent de la prose ; la roadmap rend une liste d'éléments typés (statut, dates, dépendances) en trois vues, un PDF, un fichier ICS et un flux RSS, depuis la même source.
- Récit en a besoin pour lui-même. Le plan 06 · Feuille de route et la page
sites/vision/pages/roadmap.mdxsont deux roadmaps écrites à la main, en deux langues, qui dérivent l'une de l'autre. Le format est la manière de les tenir d'une seule source (§7). - Il prépare le calendrier. Les jalons datés passent dans le calendrier (pont roadmap → calendar, P10, plan 27) par le même type de date et le même écrivain ICS.
2. Ce qui existe déjà et qu'on réutilise
| Brique | État réel au 02/11/2026 | Ce que P9 en fait |
|---|---|---|
RoadmapSourceSchema | packages/core/src/schema/source.ts : genericSource("roadmap"), un looseObject sans champ typé (règle L6 : « les champs typés arrivent avec le format ») | remplacé par un strictObject typé (§3.3), comme MotionSourceSchema |
| Convertisseur Markdown ⇄ JSON | packages/core/src/schema/convert.ts : un convertisseur de scène par format (deckScene, docsScene, motionScene), choisi par une cascade de ternaires ; genericScene pour les autres | ajout de roadmapScene et de son inverse, et passage de la cascade à une table par format (§3.4) |
| Compilation d'un JSON | compileStorySource (compile/compile-story.ts) passe le JSON en Markdown par sourceToMarkdown, puis compile ce Markdown : le Markdown est le pivot de compilation | le format relit scene.mdast et scene.attrs, comme @recit/motion (source/story.ts) ; le pivot doit être exact |
| Attributs de séparateur | parser/split-slides.ts : key=value, valeurs entre guillemets, forme JSON --- {…} ; coercition des nombres (id=3 reste une chaîne, mais date=2027 deviendrait un nombre) | les dates sont relues en chaînes par le schéma du format (§3.5) |
Compagnon (kind: "sidecar") | mergeSidecar (schema/assemble.ts) fusionne par id de scène ; un id inconnu est une erreur (sidecar-unknown-scene) | étendu aux listes à clé (les items d'une piste), même règle d'erreur (§3.4) |
| Test d'aller-retour | core/src/__tests__/source-conversion.spec.ts parcourt examples/ et sites/*/stories | couvre examples/roadmap-product et la roadmap de Récit sans rien ajouter |
| JSON Schema | schema/json-schema.ts, recit schema [name], schemaUrl("roadmap") | roadmap.json publié avec les autres |
| Packages | packages/formats/roadmap et packages/time : squelettes privés (export {}), tags scope:format et scope:foundation déjà posés | deviennent publiés (exports à trois conditions, tsdown, LICENSE.md, release.projects) |
Analyseur {k=v} | à créer en DI0.2 (12 · Glossaire et annuaire), un seul module dans @recit/core | réutilisé tel quel pour les items |
| Intégration aux sites | site-stories/src/plugin.tsx choisit la vue par story.format === "motion" ? <MotionViewer> : <StoryViewer> ; vite.ts calcule le film au build (storyMotion) | une table de visionneuses par format (§6.1), à aligner avec le plan 23 |
Chaîne PDF, contrat d'impression, exports.json | spécifiés par 15 · Export PDF (PDF0 à PDF1 en P6) : ?print=1, whenPrintable(), @recit/paged/runner, ExportManifestSchema | extension RoadmapPdfOptionsSchema, variantes ics et rss du manifeste |
| Graphiques | d3 sans DOM (d3-scale, d3-shape), tranché le 24/10 (question 10 de 06) ; dans le lockfile, aujourd'hui seulement en dépendance transitive | la frise est un SVG calculé au build par d3-scale (scaleUtc) : aucune bibliothèque de plus |
| Composants de site | @recit/site-kit : FormatHero, Gallery, ExportPanel, BridgeCard… (07 · Domaines §4) ; StatusBadge existe déjà (statut d'un format) | réutilisés ; le badge d'un élément de roadmap prend un autre nom (RoadmapStatus), un mot par chose |
| Horloge | aucune : rien dans Récit ne dépend encore de la date du jour | une seule horloge de build, SOURCE_DATE_EPOCH (§3.5) |
3. Le modèle
3.1 Une scène = une piste ou un jalon
Accepté dans 08 · Formats §0 et repris tel quel : une scène est une piste (un
groupe d'éléments : « Editor », « API ») ou un jalon (une date qui compte : « 1.0 release »).
Les éléments vivent à l'intérieur d'une piste, comme les entrées d'une section d'annuaire
(12 · Glossaire et annuaire D2). Le seul séparateur reste ---.
Les vues travaillent sur les éléments, pas sur les scènes : now-next-later et kanban répartissent les éléments en colonnes, la frise place les éléments sur leurs pistes et les jalons sur l'axe.
3.2 La syntaxe Markdown
Markdown
| Élément | Rôle |
|---|---|
--- id=… | une piste. Le premier titre est son libellé, la prose qui suit (avant la liste) sa description |
item de liste terminé par {…} | un élément. Attributs : id (obligatoire, §3.6), status, horizon, start, end, quarter, owner, tags, link, depends. [x] vaut status=shipped par défaut |
[ ] sans status | premier statut de statuses (planned par défaut) |
:::item{id=…} | description longue d'un élément, Markdown complet (accepté dans 08) |
--- type=milestone id=… date=… | un jalon, sur la frise et dans l'ICS. Le corps est sa description (voir la question 3 sur type=) |
compagnon product.json (kind: "sidecar") | statuts, dates et propriétaires tenus par un outil (CI, script), le texte restant dans le .md (09 §1) |
08 · Formats écrivait --- lane=editor id=editor : l'attribut lane= répète l'id et ne sert à
rien d'autre, puisque toute scène qui n'est pas un jalon est une piste. Recommandation : --- id=…
seul ; lane= n'existe pas (question 8).
Les attributs {k=v} suivent la grammaire du séparateur (mêmes guillemets, mêmes listes séparées
par des virgules) ; c'est l'analyseur unique de DI0.2. Un item sans {…} est une ligne de prose de
la piste, pas un élément : une liste ordinaire reste possible dans la description.
3.3 Le schéma (dans @recit/core/schema)
Les schémas de source restent dans packages/core/src/schema/source.ts, à côté de ceux du deck et
du motion ; les types en sont dérivés (z.infer), et @recit/roadmap les importe sans les
redéfinir (règle d'AGENTS.md). Les primitives de date sont partagées avec le calendrier (plan 27)
et vivent dans un module à part, packages/core/src/schema/time.ts, importé par les deux.
TypeScript
Les noms exacts de l'API zod/mini (z.partialRecord, z.url) sont à vérifier à l'implémentation,
comme dans 15 · Export PDF §3.2 ; le principe est fixe. Les défauts
(view: now-next-later, period: quarter, statuses, done: shipped, et la table
horizons: { now: [in-progress], next: [planned], later: [paused] }) sont posés par la section
roadmap: de recit.config (RoadmapConfigSchema, avec .prefault({}), règle d'AGENTS.md), pas
par le schéma de source : la source dit ce que l'auteur a écrit, la config résolue dit ce qui
s'applique. Les réglages s'écrivent à plat dans le frontmatter (08, « Réglages à plat »).
Contrôles portés par le schéma (z.refine au niveau de la story, avec chemin JSON) : end avant
start, quarter et start/end qui se contredisent, date absent d'un jalon daté, items sur
un jalon. Les contrôles qui demandent toute la story (dépendances, statuts connus) sont des
diagnostics du format (§3.6), parce qu'ils doivent porter le numéro de ligne du Markdown.
3.4 Le jumeau et le compagnon
Le jumeau (kind: "story") est la même roadmap en JSON. Le convertisseur vit dans le cœur,
comme celui du motion (motionScene de convert.ts) :
markdownToSource: le premier titre de la scène devienttitle, la prose avant la listebody, chaque item{…}unitems[], chaque:::item{id}ladescriptionde l'élément du mêmeid,typeetdatedu séparateur les champs typés, le resteattrs;sourceToMarkdownémet l'ordre canonique : titre, prose, liste, blocs:::item. Une prose écrite après la liste est déplacée avant elle, avec un avertissement (roadmap-text-after-items) ; la liste se réécrit sans perte ;- la cascade
format === "deck" ? … : format === "docs" ? …devient une tableSCENE_CODECS: Record<StoryFormat, SceneCodec>(aller et retour), dans laquelle chaque format ajoute sa ligne. Le plan 23 (report) touche au même endroit : un seul mécanisme pour les deux.
Exemple du jumeau de la piste editor ci-dessus :
JSON
[x] ne s'écrit pas dans le JSON : il est status: "shipped". Au retour, un élément au statut
done s'écrit [x] sans status=, les autres [ ] avec leur status= sauf s'il vaut le premier
statut. L'aller-retour du test (compile(md) == compile(toJson(md))) passe donc par une forme
canonique unique.
Le compagnon (kind: "sidecar") complète une piste par son id, comme aujourd'hui, et ses
éléments par leur id. Dans le compagnon, les éléments s'écrivent en objet à clé, jamais en
tableau, pour que la fusion soit sans ambiguïté :
JSON
mergeSidecar gagne la notion de liste à clé : un champ déclaré comme tel par le format
(items pour la roadmap ; entries pour l'annuaire du plan 12, qui en a le même besoin) se
fusionne élément par élément. Un id d'élément inconnu est une erreur (sidecar-unknown-item),
comme un id de scène inconnu ; le compagnon ne crée jamais d'élément et n'en retire jamais (règle
d'AGENTS.md : un compagnon ne redéfinit pas la liste). La déclaration vit dans le cœur, à côté de
SCENE_CODECS.
C'est le chemin d'une synchronisation : un script lit GitHub Projects ou Linear et réécrit
product.json, jamais le .md. Les scripts eux-mêmes restent hors du cœur et hors de P9 (08 §4).
3.5 Les dates
- Une roadmap ne manie que des dates civiles, sans heure ni fuseau :
start,end,date(jalon) sont desPlainDate(2026-11-02),quarterun trimestre (2026-Q3). C'est volontaire : une roadmap ne promet pas d'heure, et une date sans fuseau ne change pas de jour d'un pays à l'autre. Le fuseau n'apparaît qu'à l'export ICS (journées entières,VALUE=DATE) et RSS (minuit UTC du jour, écrit en RFC 822). - Les valeurs du séparateur sont relues en chaînes par le convertisseur du format :
date=2027(quecoerceValuetransforme en nombre) produit l'erreur « date attendueAAAA-MM-JJ», jamais une date silencieusement fausse. - Trimestres, semestres et mois de la frise viennent de
@recit/time(quarterOf,quarterRange,halfOf), calculés sur des dates civiles, sansTemporal: la moitié « roadmap » de la fondation n'a besoin d'aucun polyfill.Temporaln'arrive qu'avec le calendrier (plan 27). - Une seule horloge. La frise trace « aujourd'hui », et l'avertissement « élément
planneddont la date est passée » a besoin de la date du jour.@recit/timeexposetoday()etnow(), qui lisentSOURCE_DATE_EPOCH(la convention des builds reproductibles) quand elle est posée, sinon l'horloge réelle. Les e2e et les goldens la fixent ; rien d'autre ne litDate.now(). Le rendu now-next-later, lui, ne dépend jamais de la date (§4.2). - Affichage par
Intl.DateTimeFormatdans la langue de la page (2026-Q3devient « T3 2026 » enfr, « Q3 2026 » enen) ; les dates ne sont jamais localisables dans la source (09 §2.3).
3.6 Les règles de validation
Toutes portent le fichier et la ligne (règle d'AGENTS.md), ou le chemin JSON pour un jumeau, par
le même Diagnostic que le reste du cœur.
| Code | Gravité | Cas |
|---|---|---|
roadmap-item-id | erreur | item {…} sans id, ou id en double dans la story |
roadmap-unknown-status | erreur | status hors de statuses |
roadmap-unknown-depends | erreur | depends vers un id inconnu |
roadmap-depends-cycle | erreur | cycle de dépendances (le chemin du cycle est dans le message) |
roadmap-date-order | erreur | end avant start, ou start/end hors du quarter donné |
roadmap-invalid-date | erreur | date ou trimestre mal écrit (2026-13-01, 2026-Q5, 2027) |
roadmap-milestone-items | erreur | une liste d'éléments dans un jalon |
roadmap-done-undated | avertissement | élément au statut done sans end : il n'entre ni dans le changelog daté ni dans le RSS |
roadmap-overdue | avertissement | élément non terminé dont end est passé (horloge de build) ; jamais une erreur |
roadmap-text-after-items | avertissement | prose après la liste d'une piste (déplacée avant, §3.4) |
sidecar-unknown-item | erreur | id d'élément du compagnon absent de la piste |
id est obligatoire (recommandé, question 5) : un id déduit du titre changerait avec la langue,
casserait la parité de recit i18n check, les ancres, les GUID du RSS et les clés du compagnon.
3.7 Les langues
Textes localisables : title de la story, des pistes, des jalons et des éléments, body,
description, statusLabels. Communs à toutes les langues : id, statuts, dates, owner, tags,
link, depends (09 §2.3, §5). recit i18n check ajoute à la parité des scènes celle des éléments
: un id d'élément présent dans product.md et absent de product.fr.md est une erreur, un statut
ou une date différents aussi (attributs structurels). Les libellés des statuts par défaut entrent
dans les packs UI_STRINGS (packages/core/src/i18n/ui.ts), en en et en fr.
4. Le rendu
4.1 Le modèle compilé
@recit/roadmap compile une Story de format roadmap en un modèle de rendu, comme
@recit/motion compile une story en Film :
TypeScript
- Il relit
scene.mdast(titres, liste, blocs:::item) etscene.attrs, jamais le texte brut ; c'est le chemin qui marche pour un.mdcomme pour un jumeau, puisque le JSON passe par le pivot Markdown (§2). - Les textes y sont déjà résolus dans une langue et rendus en HTML sanitizé par la chaîne du cœur.
Le client ne reçoit que ce modèle, sans
mdast(commetoClientStory). Roadmapest un type propre au format, défini une fois dans@recit/roadmap, dérivé de son schéma (commeFilmSchemade@recit/motion). Les types partagés (dates, source, config, diagnostics) restent ceux du cœur.
4.2 Les trois vues
| Vue | Ce qu'elle montre | Données nécessaires |
|---|---|---|
now-next-later (défaut) | trois colonnes. Un élément va dans la colonne de son horizon, sinon dans celle de son statut selon horizons. Les éléments done sont repliés dans « Livré » sous la vue | statuts ; aucune date |
timeline | une frise : une ligne par piste, une barre par élément daté (start–end, ou le trimestre), les jalons en losanges sur l'axe, la ligne « aujourd'hui », les dépendances | dates ; les éléments sans date vont dans une ligne « Non planifié » |
kanban | une colonne par statut, dans l'ordre de statuses ; lecture seule (pas de glisser-déposer : la source est le fichier) | statuts |
La vue par défaut ne dépend d'aucune date (risque « une roadmap publique qui promet des dates », 08
§9). La vue se choisit dans le frontmatter et, à la lecture, par ?view= dans l'URL (comme
?slide=N du deck). Les filtres aussi : ?status=in-progress&tag=mobile&owner=team-lorem&lane=api.
Chaque élément a une ancre (#sync), et la carte d'un élément ouvre sa description dans un
<dialog> natif.
Composants (08 §4, un renommage) : Roadmap (racine, choix de la vue), NowNextLater, Timeline,
Kanban, RoadmapItem, RoadmapStatus (au lieu de StatusBadge, déjà pris par @recit/site-kit
pour le statut d'un format), Milestone, ProgressBar (part des éléments done par piste),
RoadmapFilters, DependencyLine, Changelog (éléments done par date). Ils vivent dans
packages/formats/roadmap/src/components/, chacun avec son module CSS et sa story.
La frise est un SVG calculé au build : d3-scale (scaleUtc) place les dates, aucune mise en
page n'attend le navigateur. Elle s'affiche sans JavaScript, s'imprime telle quelle, et ne bouge pas
d'une capture à l'autre. Le JavaScript du client ne sert qu'aux filtres, au choix de vue et au
dialogue.
4.3 Style et accessibilité
- Loi du plan 19 : React et un module CSS par composant, compilé par Lightning CSS
(
scripts/build-css-modules.js, table typéeX.module.css.ts), une seule feuille publiée@recit/roadmap/styles.css. Pas de Mantine dans un package publié (Mantine n'est que dans le framework de sites et@recit/site-kit), donc des éléments natifs :<dialog>,<fieldset>de cases à cocher pour les filtres,<nav>d'onglets pour les vues. Mode sombre parlight-dark()et l'attributdata-mantine-color-scheme, jamais une classe.dark. - Couleurs : la teinte du site roadmap (vert, 07 · Domaines §4) et ses clartés. Un statut ne se lit jamais par la couleur seule : libellé et icône à chaque fois.
- Structure : chaque colonne est une liste (
<ul>titrée), chaque élément un<article>avec son titre, les dates dans des<time datetime>. La frise SVG porterole="img", un titre, et la vuenow-next-laterreste l'alternative textuelle complète, à un clic. axe tourne sur chaque story, en erreur (21).
4.4 Les exports
| Sortie | Commande | Contenu | Où |
|---|---|---|---|
| Web | recit build product.md | page statique, trois vues, filtres | dist/ |
| HTML autonome | recit export product.md --to html | un fichier, le modèle embarqué | comme le deck (--html reste l'alias) |
recit export product.md --to pdf | la frise par défaut (A4 paysage), ou pdf.view: now-next-later | @recit/paged par la CLI, vue ?print=1, signal whenPrintable() | |
| ICS | recit export product.md --to ics | un VEVENT journée entière par jalon daté ; ics.items: true ajoute les éléments datés (start–end) | product.ics ; sur un site, <routePrefix>/<slug>.ics |
| RSS | recit export product.md --to rss | RSS 2.0 : un <item> par élément done daté, du plus récent au plus ancien ; guid = URL de l'ancre | product.rss ; sur un site, <routePrefix>/<slug>.rss, un par langue |
- Une commande, un drapeau (règle B9 et ligne
@recit/clide 03 · Packaging §6) :export <file> --to ics|rss, jamaisexport ics(08 §4 l'écrivait encore ainsi). - L'écrivain ICS est dans
@recit/time(./ics), pur, sans dépendance : il plie les lignes à 75 octets, échappe les textes, émetUID=<id>@<slug>.<hôte>(stable d'un build à l'autre) etDTSTAMPdepuis l'horloge de build. Le plan 27 y ajoute les heures, les fuseaux (VTIMEZONE) et la lecture. L'écrivain RSS est petit et propre à la roadmap :@recit/roadmap/feed. - Les options PDF entrent en RM3 :
RoadmapPdfOptionsSchema = z.extend(PdfOptionsSchema, { view }), orientation paysage par défaut, sectionroadmap.pdfderecit.configetpdf:du frontmatter (15 §3.2). - Chaque fichier est listé dans
exports.json. Ce plan ajoute àExportFileSchemales variantesics(avecevents: positiveInt) etrss(avecitems: positiveInt), réservées par 15 · Export PDF §3.2 ; le plan 27 réutiliseicssans le redéfinir.
5. CLI et MCP
| Commande | Effet |
|---|---|
recit init --format roadmap [file] | crée roadmap.md (Lorem), ou roadmap.json avec un .json ; INIT_FORMATS gagne roadmap |
recit dev product.md | serveur de dev avec rechargement, comme le deck |
recit build product.md | page statique |
recit validate product.md | schéma et règles du §3.6, avec ligne ; aussi sur le jumeau et le compagnon |
recit convert product.md --to json|md | jumeau dans les deux sens |
recit export product.md --to pdf|ics|rss|html | §4.4 ; --locale et --all-locales comme partout |
recit schema roadmap | JSON Schema du jumeau |
recit i18n check | parité des pistes et des éléments (§3.7) |
La CLI aiguille sur format: (Markdown ou JSON), et @recit/cli gagne une dépendance vers
@recit/roadmap (distribution → format, permis par 03 §8). --to sur export est créé par PDF1
(P6) ; ce plan y ajoute ics et rss, rien d'autre.
MCP (packages/mcp/src/server.ts) : la liste FORMATS de describe_format gagne roadmap
(syntaxe Markdown et JSON Schema du jumeau), une ressource recit://guide/roadmap s'ajoute aux
guides (et donc à recit skills export, B9). Un seul outil de validation pour tous les formats non
motion, validate_story (story Markdown ou JSON, ou chemin), plutôt qu'un validate_roadmap : il
rend les diagnostics du §3.6. Le plan 27 l'utilise tel quel.
6. Le site
6.1 L'intégration (@recit/site-stories)
- Une table de visionneuses.
plugin.tsxchoisit aujourd'hui entre deux composants par un ternaire surstory.format. Au troisième format, il devient une tableFORMAT_VIEWERS: Partial<Record<StoryFormat, LazyViewer>>, chaque entrée chargée enReact.lazy(règle « Bundle Size » d'AGENTS.md), avec le deck en repli. Le plan 23 (report) arrive au même endroit avant P9 : la table est posée par le premier des deux, l'autre ajoute sa ligne. - Le modèle calculé au build.
vite.tscalcule déjà le film d'une story motion (storyMotion). Le même crochet calculeRoadmappour une story roadmap ;CompiledDeckEntry.motiondevient un champ discriminé par format, pour ne pas ajouter un champ par format. - Les fichiers générés. Le build écrit
<routePrefix>/<slug>.icset.rssà côté de la page, et leurs lignes dansexports.json, queExportPanellit. StoryEmbed:<StoryEmbed slug="product" view="timeline" />dans une page MDX. La propviewest propre aux formats qui en ont ;formatgarde son sens actuel (montrer un deck en film) et n'est pas étendu.@recit/site-storiesdépend de@recit/roadmap(intégration → format, permis).
6.2 roadmap.recit.dev
sites/roadmap, sur createSiteConfig({ site: "roadmap" }) de @recit/site-kit ; un site n'est
que Markdown et config (règle d'AGENTS.md).
| Page | Contenu |
|---|---|
/ | FormatHero : la roadmap de Récit en now-next-later, sa source Markdown dépliable à côté (§7) |
/gallery | roadmap-product dans ses trois vues, en en et fr |
/templates | gabarits à copier (produit, open source, équipe), TemplateCard |
/changelog | le Changelog de la roadmap de Récit, et le lien RSS |
/roadmap/<slug> | les stories en plein écran (publish.routePrefix: "/roadmap", layout: "none"), avec .ics et .rss |
- Port local
3007, port e2e4307(e2e/ports.ts), projet Vercelrecit-roadmap, entréeroadmapdans la tableSITESde@recit/site-kit(07 · Domaines §4 et §5). vercel.jsonsert.icsentext/calendar; charset=utf-8et.rssenapplication/rss+xml; charset=utf-8.- Doc :
sites/docs› Formats › Roadmap (syntaxe, jumeau, compagnon, exports). Aucune doc sur roadmap.recit.dev (règle de 07). @recit/site-kit(formats.ts) passeroadmapenavailable, et sa ligne « une scène = » de « a milestone » à « a lane or a milestone », d'après 08 §0.
7. Dogfooding : les roadmaps de Récit écrites dans le format
Deux documents de Récit sont déjà des roadmaps : 06 · Feuille de route (le plan, en
français, avec ses cases, ses décisions et ses questions) et sites/vision/pages/roadmap.mdx (la
page « Where it goes » de vision.recit.dev, en anglais, avec un tableau des phases). Proposition,
pas encore une décision :
- Une story, deux langues.
sites/roadmap/stories/recit.md(anglais) etrecit.fr.md(français), au formatroadmap: une piste par groupe (« Fondations et deck », « Formats », « Après la 1.0 »), un élément par phase (id=p9, titre « Roadmap », statut,horizon), un jalon par version publiée (0.1.0-alphaà1.0.0, daté seulement une fois publié). Les statuts des éléments viennent de l'état réel (journal.md§2 fait foi). Pas de dates promises : la vue par défaut est now-next-later. - Le plan 06 reste le plan. Ses décisions, ses risques, ses questions et ses critères de sortie
sont de la prose qu'aucune vue ne remplace. Le convertir lui-même au format (« générée depuis
06-roadmap.mdconverti au format roadmap », 08 §4 RM4) mélangerait deux documents ; et une page desites/plans/pagesn'est pas une story (NOT_A_STORYdu test d'aller-retour). À la place : un contrôle danspnpm check,scripts/check-roadmap-sync.js, qui compare les titres## Pn — …de 06 aux élémentsid=pnde la story et échoue sur un écart (phase ajoutée, renommée ou retirée d'un seul côté). Le plan 06 gagne une ligne qui renvoie à roadmap.recit.dev. - La page de vision renvoie à la story. Son tableau « The order, and the reason for it » est la
même liste que la story, en plus court, avec la raison de chaque phase. Elle garde sa prose et la
colonne « Why it comes here » (c'est son propos), et remplace le statut écrit à la main (« Two
formats are shipped… ») par un lien vers roadmap.recit.dev. Elle ne peut pas intégrer la
story : un site ne dépend jamais d'un autre site (03 §8), et
examples/n'accepte que du Lorem Ipsum. La raison de chaque phase pourrait devenir ladescriptionde son élément dans la story ; la page de vision n'aurait alors plus de tableau du tout (question 2). - Le site de plans garde 06 tel quel, et peut afficher la version française de la story par un lien, pour la même raison.
Ce que le dogfooding prouve : la vue now-next-later lisible sans dates, la parité en/fr, le
compagnon tenu par un script, et le flux RSS des phases livrées.
8. Les exemples
examples/roadmap-product (règle B7), contenu en Lorem Ipsum seulement, aucun nom réel (owner =
team-lorem, team-ipsum) :
| Fichier | Rôle |
|---|---|
product.md | trois pistes, un jalon, douze éléments couvrant les quatre statuts, deux dépendances, un :::item imagé |
product.fr.md | la même roadmap en français, mêmes id |
product.json | compagnon (kind: "sidecar") : statuts et end de trois éléments, comme les écrirait une CI |
recit.config.ts | section roadmap: (statuts par défaut, pdf.view) |
package.json | scope:example, scripts dev, build, export:pdf, export:ics, export:rss |
Le jumeau product.story.json n'est pas obligatoire sur disque : le test d'aller-retour le garantit
(AGENTS.md). Un gabarit create-recit (--template roadmap-product) en est la copie.
sites/roadmap liste l'exemple dans ses devDependencies (constat ST-20).
9. Les tests
| Niveau | Quoi | Où |
|---|---|---|
| Unitaire, cœur | schéma : chaque erreur du §3.6 avec sa ligne ou son chemin ; codec : items, [x], :::item, ordre canonique ; fusion du compagnon par élément, sidecar-unknown-item | packages/core/src/__tests__/roadmap-*.spec.ts |
| Aller-retour | compile(md) == compile(toJson(md)) et compile(json) == compile(toMd(json)) sur examples/roadmap-product et sites/roadmap/stories, sans rien ajouter au test existant | source-conversion.spec.ts |
| Unitaire, time | trimestres, semestres, semaines ISO, today() sous SOURCE_DATE_EPOCH, écrivain ICS (pliage, échappement, UID stable), relu par un analyseur ICS en test | packages/time/src/__tests__/ |
| Unitaire, format | storyToRoadmap : horizons dérivés, dépendances, cycle, éléments non planifiés ; RSS valide (XML bien formé, dates RFC 822, guid) | packages/formats/roadmap/src/__tests__/ |
| Stories | une story par composant, avec un play : filtrer par statut (cases), changer de vue (onglets), ouvrir et fermer la description (Escape), lire une date (<time>) ; axe en erreur | packages/formats/roadmap/src/components/*.stories.tsx, titre Roadmap/<Composant> |
| e2e site | chaque page en en et fr, chaque vue de /roadmap/product, filtres dans l'URL, .ics et .rss servis avec le bon type ; captures sous SOURCE_DATE_EPOCH fixe | e2e/site-roadmap.spec.ts, projet roadmap |
| e2e Storybook | captures des stories Roadmap/* | e2e/storybook-roadmap.spec.ts |
| nombre de pages, texte extrait (titres des pistes), signets | test de la CLI, comme le PDF du deck | |
| Contrôle | scripts/check-roadmap-sync.js (06 ⇄ story de Récit) dans pnpm check | scripts/__tests__/ |
Les captures -linux sont la référence, écrites seulement par
pnpm test:e2e:update:linux e2e/site-roadmap.spec.ts. scripts/check-stories.js refuse un
composant sans story ou sans play. Les seuils de couverture de vitest.config.ts gagnent une
ligne par nouveau package.
10. Les lots
Identifiants de 08 §4 conservés (RM1 à RM5), sous-tâches RMn.m (règle B10). Prérequis : le lot
avancé DI0 de P9 (12 · Glossaire et annuaire), au moins DI0.2 ({k=v}).
Chaque lot laisse pnpm check, le build et le typecheck des projets touchés et pnpm test:related
au vert, et porte son version plan (preminor pour le premier de la phase, prerelease ensuite).
RM0 — Socle d'intégration (S)
- RM0.1 —
SCENE_CODECSdansconvert.ts(la cascade de ternaires devient une table), sans changement de comportement ; si le plan 23 l'a déjà fait, rien. - RM0.2 —
FORMAT_VIEWERSdans@recit/site-stories, entrée deCompiledDeckEntrydiscriminée par format ; même remarque.
Sortie : source-conversion.spec.ts et les e2e du deck et du motion passent sans changement.
RM1 — @recit/time, moitié roadmap (S/M)
- RM1.1 —
PlainDateSchema,QuarterSchemadanspackages/core/src/schema/time.ts. - RM1.2 —
@recit/timepublié :quarterOf,quarterRange,halfOf,isoWeek,today()etnow()surSOURCE_DATE_EPOCH, formatageIntl. - RM1.3 —
@recit/time/ics: écrivain de journées entières (VALUE=DATE), pliage, échappement,UID,DTSTAMP.
Sortie : tests unitaires verts, y compris l'ICS relu par l'analyseur de test ; aucune dépendance ajoutée.
RM2 — Le modèle et les vues (M/L)
- RM2.1 —
RoadmapSourceSchematypé,RoadmapConfigSchemadansrecit.config, JSON Schemaroadmap.json. - RM2.2 — Codec Markdown ⇄ JSON, listes à clé dans
mergeSidecar, règles du §3.6. - RM2.3 —
storyToRoadmapetRoadmapSchema. - RM2.4 — Composants et vues (§4.2, §4.3), un module CSS et une story avec
playchacun. - RM2.5 — Runtime (
@recit/roadmap/runtime, montage côté client des filtres et du dialogue) ;recit init,dev,build,validate,convertaiguillés surroadmap.
Sortie : recit build examples/roadmap-product/product.md montre les trois vues en en et fr ;
pnpm test:stories packages/formats/roadmap passe, axe compris.
RM3 — Les exports (M)
- RM3.1 —
export --to icset--to rss; variantesicsetrssd'ExportFileSchema. - RM3.2 —
RoadmapPdfOptionsSchema, vue imprimable?print=1avecwhenPrintable(),export --to pdfpar@recit/paged. - RM3.3 — MCP :
describe_format(roadmap),validate_story, guiderecit://guide/roadmap.
Sortie : ICS valide (relu par l'analyseur, importé à la main dans un client), RSS valide, PDF d'une
page A4 paysage pour l'exemple ; exports.json liste les trois fichiers.
RM4 — L'exemple et la roadmap de Récit (M)
- RM4.1 —
examples/roadmap-product(§8) et son gabaritcreate-recit. - RM4.2 —
sites/roadmap/stories/recit.mdetrecit.fr.md(§7), compagnon des statuts. - RM4.3 —
scripts/check-roadmap-sync.js, danspnpm check.
Sortie : l'aller-retour passe sur les deux ; le contrôle échoue si l'on renomme une phase dans 06 seulement.
RM5 — roadmap.recit.dev et la doc (M)
- RM5.1 —
sites/roadmap(§6.2),SITES, ports, Vercel, en-têtes des.icset.rss. - RM5.2 —
StoryEmbed view=…, fichiers générés au build etExportPanel. - RM5.3 —
sites/docs› Formats › Roadmap ;formats.tsde@recit/site-kit. - RM5.4 —
e2e/site-roadmap.spec.ts,e2e/storybook-roadmap.spec.ts, captures-linux.
Sortie (celle de P9 dans 06) : examples/roadmap-product dans les trois vues en en et fr ;
exports ICS, RSS et PDF valides ; la roadmap de Récit en ligne sur roadmap.recit.dev ; pnpm verify
et pnpm test:e2e:linux e2e/site-roadmap.spec.ts verts deux fois de suite ; version 0.5.0-alpha
publiée.
Ordre et agents. RM0 et RM1 sont indépendants et peuvent partir ensemble ; RM2.1–RM2.2 (cœur)
précèdent RM2.3–RM2.5 (format) ; RM3 et RM4 se partagent après RM2 ; RM5 ferme. Un agent par lot,
chacun sur ses fichiers ; seul le coordinateur lance le conteneur e2e (AGENTS.md).
11. Les risques
| Risque | Parade |
|---|---|
| Une roadmap publique qui promet des dates | now-next-later par défaut, sans date ; dates optionnelles ; roadmap-overdue n'est qu'un avertissement |
| Le pivot Markdown perd un champ typé | codec dans le cœur, ordre canonique, aller-retour sur chaque exemple ; un champ du jumeau sans forme Markdown est refusé au schéma |
Coercition des attributs du séparateur (date=2027 lu comme nombre) | le codec relit les dates en chaînes et refuse un nombre, avec la ligne |
| Captures instables à cause de la date du jour | une horloge, SOURCE_DATE_EPOCH, fixée en e2e ; now-next-later n'en dépend pas |
| Le compagnon devient une seconde source qui ajoute des éléments | listes à clé : fusion seulement, sidecar-unknown-item en erreur |
| Les filtres côté client font clignoter la page prérendue | la page prérendue montre la vue non filtrée ; les filtres s'appliquent après l'hydratation, sans décalage de mise en page (colonnes à hauteur fixe) |
| La synchronisation GitHub ou Linear grossit le périmètre | hors du cœur et hors de P9 (08 §4) ; le contrat est le compagnon |
| La roadmap de Récit et le plan 06 dérivent | scripts/check-roadmap-sync.js dans pnpm check |
Un StatusBadge de plus | RoadmapStatus, un mot par chose |
12. Questions ouvertes
- Source de la roadmap de Récit : une story écrite dans le format (
sites/roadmap/stories/), tenue en phase avec 06 par un contrôle (recommandé, §7) ; ou une story générée depuis 06 par un script, comme l'écrit 08 §4 RM4 ? - Page de vision : garder son tableau et sa prose avec un lien vers roadmap.recit.dev
(recommandé) ; ou déplacer la raison de chaque phase dans la
descriptiondes éléments et ne garder que la prose ? type=milestone: la règle B4 de 06 réservetype=aux blocs motion. Recommandé : amender B4 pour dire quetype=est le type de scène propre au format (bloc en motion,milestoneen roadmap), ce que fait déjà 08 §4. Autre voie :--- milestone=2027-03-31 id=v1.- Colonne now-next-later :
horizon=explicite, sinon dérivée du statut par la tablehorizons(recommandé, sans date) ; ou dérivée des dates (trimestre courant = « now »), qui dépend de l'horloge ? idobligatoire sur chaque élément (recommandé) ; ou déduit du titre, au prix de la parité entre langues ?- Jalon sans date (une version pas encore datée) : permis, affiché en now-next-later et absent
de l'ICS et de la frise (recommandé) ; ou
dateobligatoire ? - Flux : RSS 2.0 seul (recommandé, 08) ; ou aussi Atom ou JSON Feed ?
lane=: supprimé, puisque toute scène qui n'est pas un jalon est une piste (recommandé) ; ou gardé comme dans l'exemple de 08 ?- Analyseur ICS des tests : une dépendance de dev (
ical.js, MPL-2.0), qui servirait aussi à la lecture d'un.icsen P10 (plan 27, question 4) ; ou un analyseur maison minimal ?
13. À reporter dans les autres plans (par l'intégrateur)
| Fichier | Ce qui change |
|---|---|
06-roadmap.md | P9 renvoie à ce plan ; RM0 ajouté ; règle B4 selon la question 3 ; sortie : « version 0.5.0-alpha » inchangée |
08-formats.md §4 | export --to ics et --to rss ; lane= selon la question 8 ; id obligatoire ; RoadmapStatus ; renvoi à ce plan pour le détail |
09-langues-et-json.md §2.3 | ligne roadmap : champs de scène type, title, date, items[] (avec horizon, depends, description) |
12-glossaire-annuaire.md | les listes à clé du compagnon (entries) suivent la même fusion que items |
15-export-pdf.md §3.2 | variantes ics et rss de ExportFileSchema, RoadmapPdfOptionsSchema |
21-tests.md §3.1 | titre Storybook Roadmap/<Composant>, surface e2e/storybook-roadmap.spec.ts |
sites/plans/recit.config.ts | entrée de navigation pour 26 |