# 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](/06-roadmap) : 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](/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](/02-architecture), de [03 · Packaging](/03-packaging) §6 et §8, de
[07 · Domaines](/07-domaines), de [09 · Langues et JSON](/09-langues-et-json), de
[12 · Glossaire et annuaire](/12-glossaire-annuaire) (l'analyseur `{k=v}`, lot DI0.2, créé avant ce
plan), de [15 · Export PDF](/15-export-pdf) (chaîne d'impression, manifeste `exports.json`) et de
[21 · Tests](/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](/06-roadmap) et la page
  `sites/vision/pages/roadmap.mdx` sont 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](/12-glossaire-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](/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](/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](/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](/12-glossaire-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

```md
---
title: Lorem Product Roadmap
format: roadmap
lang: en
view: now-next-later # now-next-later | timeline | kanban
period: quarter # month | quarter | half : graduation de la frise
statuses: [planned, in-progress, shipped, paused]
---

--- id=editor

## Editor

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

- [x] Lorem ipsum export {id=export quarter=2026-Q3 end=2026-09-18 link=https://example.com}
- [ ] Dolor sit amet sync {id=sync status=in-progress start=2026-10-01 end=2026-12-15 owner=team-lorem}
- [ ] Consectetur offline mode {id=offline quarter=2027-Q1 tags=mobile,beta horizon=later}

:::item{id=offline}
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

![Lorem sketch](./img/offline.png)
:::

--- id=api

## API

- [ ] Adipiscing webhooks {id=webhooks quarter=2027-Q1 depends=sync}

--- type=milestone id=v1 date=2027-03-31

## 1.0 release

Ut enim ad minim veniam.
```

| É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.

```ts
import * as z from "zod/mini";

// packages/core/src/schema/time.ts — shared by roadmap (P9) and calendar (P10)
/** A calendar date without time or zone: "2026-11-02" */
export const PlainDateSchema = z
  .string()
  .check(z.regex(/^\d{4}-\d{2}-\d{2}$/), z.refine(isValidPlainDate, { message: "invalid date" }));
/** "2026-Q3" */
export const QuarterSchema = z.string().check(z.regex(/^\d{4}-Q[1-4]$/));

// packages/core/src/schema/source.ts
export const ROADMAP_VIEWS = ["now-next-later", "timeline", "kanban"] as const;
export const ROADMAP_HORIZONS = ["now", "next", "later"] as const;

export const RoadmapItemSourceSchema = z.strictObject({
  /** Required: anchor, RSS guid, sidecar key; stable across languages */
  id: z.string().check(z.regex(/^[a-z0-9][\w-]*$/)),
  title: LocalizedSchema,
  /** One of settings.statuses; checked once the settings are known */
  status: z.optional(z.string()),
  /** Column of the now-next-later view; derived from the status when absent */
  horizon: z.optional(z.enum(ROADMAP_HORIZONS)),
  start: z.optional(PlainDateSchema),
  end: z.optional(PlainDateSchema),
  quarter: z.optional(QuarterSchema),
  owner: z.optional(z.string()),
  tags: z.optional(z.array(z.string())),
  link: z.optional(z.url()),
  /** Ids of other items of the same story */
  depends: z.optional(z.array(z.string())),
  /** The :::item{id} block (Markdown) */
  description: z.optional(LocalizedSchema),
});

export const RoadmapSceneSourceSchema = z.strictObject({
  id: sceneFields.id,
  /** A lane unless it is a milestone */
  type: z.optional(z.enum(["milestone"])),
  title: z.optional(LocalizedSchema),
  /** Milestone date */
  date: z.optional(PlainDateSchema),
  /** Prose of the lane or milestone (before the list) */
  body: sceneFields.body,
  notes: sceneFields.notes,
  attrs: sceneFields.attrs,
  items: z.optional(z.array(RoadmapItemSourceSchema)),
});

export const RoadmapSettingsSourceSchema = z.strictObject({
  view: z.optional(z.enum(ROADMAP_VIEWS)),
  period: z.optional(z.enum(["month", "quarter", "half"])),
  statuses: z.optional(z.array(z.string()).check(z.minLength(1))),
  /** The status that means "done": changelog, RSS, [x] (default shipped) */
  done: z.optional(z.string()),
  /** Status → column when an item has no horizon */
  horizons: z.optional(z.partialRecord(z.enum(ROADMAP_HORIZONS), z.array(z.string()))),
  /** Labels of custom statuses; the default ones come from the UI strings */
  statusLabels: z.optional(z.record(z.string(), LocalizedSchema)),
  ics: z.optional(z.strictObject({ items: z.optional(z.boolean()) })),
  // pdf: RoadmapPdfOptionsSchema, added by RM3 (15 §3.2: no field before its phase)
});

export const RoadmapSourceSchema = z.strictObject({
  ...storyFields,
  format: z.literal("roadmap"),
  settings: z.optional(RoadmapSettingsSourceSchema),
  scenes: z.array(RoadmapSceneSourceSchema),
});
```

Les noms exacts de l'API zod/mini (`z.partialRecord`, `z.url`) sont à vérifier à l'implémentation,
comme dans [15 · Export PDF](/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 devient `title`, la prose avant la liste `body`,
  chaque item `{…}` un `items[]`, chaque `:::item{id}` la `description` de l'élément du même `id`,
  `type` et `date` du séparateur les champs typés, le reste `attrs` ;
- `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 table
  `SCENE_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
{
  "id": "editor",
  "title": "Editor",
  "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
  "items": [
    { "id": "export", "title": "Lorem ipsum export", "status": "shipped", "quarter": "2026-Q3", "end": "2026-09-18", "link": "https://example.com" },
    { "id": "sync", "title": "Dolor sit amet sync", "status": "in-progress", "start": "2026-10-01", "end": "2026-12-15", "owner": "team-lorem" },
    { "id": "offline", "title": "Consectetur offline mode", "quarter": "2027-Q1", "tags": ["mobile", "beta"], "horizon": "later", "description": "Sed do eiusmod…" }
  ]
}
```

`[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
{
  "kind": "sidecar",
  "version": 1,
  "scenes": {
    "editor": {
      "items": {
        "sync": { "status": "shipped", "end": "2026-11-28" },
        "offline": { "owner": "team-ipsum" }
      }
    }
  }
}
```

`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 des `PlainDate` (`2026-11-02`), `quarter` un 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` (que
  `coerceValue` transforme en nombre) produit l'erreur « date attendue `AAAA-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, sans `Temporal` : la moitié « roadmap » de la fondation
  n'a besoin d'aucun polyfill. `Temporal` n'arrive qu'avec le calendrier (plan 27).
- **Une seule horloge.** La frise trace « aujourd'hui », et l'avertissement « élément `planned` dont
  la date est passée » a besoin de la date du jour. `@recit/time` expose `today()` et `now()`, qui
  lisent `SOURCE_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 lit `Date.now()`. Le rendu
  now-next-later, lui, ne dépend jamais de la date (§4.2).
- Affichage par `Intl.DateTimeFormat` dans la langue de la page (`2026-Q3` devient « T3 2026 » en
  `fr`, « Q3 2026 » en `en`) ; 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` :

```ts
// packages/formats/roadmap/src/model.ts
export const RoadmapSchema = z.strictObject({
  title: z.string(),
  lang: LocaleTagSchema,
  view: z.enum(ROADMAP_VIEWS),
  period: z.enum(["month", "quarter", "half"]),
  statuses: z.array(z.strictObject({ id: z.string(), label: z.string(), done: z.boolean() })),
  lanes: z.array(RoadmapLaneSchema), // id, title, html, items (resolved: horizon, range, html)
  milestones: z.array(RoadmapMilestoneSchema), // id, title, date, html
  /** Today, from the build clock; only the timeline reads it */
  today: PlainDateSchema,
});
export type Roadmap = z.infer<typeof RoadmapSchema>;

export function storyToRoadmap(story: Story, options?: { file?: string }): RoadmapResult;
```

- Il relit `scene.mdast` (titres, liste, blocs `:::item`) et `scene.attrs`, jamais le texte brut ;
  c'est le chemin qui marche pour un `.md` comme 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` (comme `toClientStory`).
- `Roadmap` est un type propre au format, défini une fois dans `@recit/roadmap`, dérivé de son
  schéma (comme `FilmSchema` de `@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ée `X.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 par `light-dark()`
  et l'attribut `data-mantine-color-scheme`, jamais une classe `.dark`.
- Couleurs : la teinte du site roadmap (vert, [07 · Domaines](/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 porte `role="img"`, un titre, et la vue
  `now-next-later` reste 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)                                 |
| PDF           | `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/cli` de [03 · Packaging](/03-packaging) §6) :
  `export <file> --to ics|rss`, jamais `export 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, émet `UID` = `<id>@<slug>.<hôte>` (stable d'un build à l'autre) et
  `DTSTAMP` depuis 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, section `roadmap.pdf` de `recit.config` et `pdf:` du frontmatter
  (15 §3.2).
- Chaque fichier est listé dans `exports.json`. Ce plan ajoute à `ExportFileSchema` les variantes
  `ics` (avec `events: positiveInt`) et `rss` (avec `items: positiveInt`), réservées par
  [15 · Export PDF](/15-export-pdf) §3.2 ; le plan 27 réutilise `ics` sans 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.tsx` choisit aujourd'hui entre deux composants par un
  ternaire sur `story.format`. Au troisième format, il devient une table
  `FORMAT_VIEWERS: Partial<Record<StoryFormat, LazyViewer>>`, chaque entrée chargée en `React.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.ts` calcule déjà le film d'une story motion (`storyMotion`).
  Le même crochet calcule `Roadmap` pour une story roadmap ; `CompiledDeckEntry.motion` devient un
  champ discriminé par format, pour ne pas ajouter un champ par format.
- **Les fichiers générés.** Le build écrit `<routePrefix>/<slug>.ics` et `.rss` à côté de la page,
  et leurs lignes dans `exports.json`, que `ExportPanel` lit.
- **`StoryEmbed`** : `<StoryEmbed slug="product" view="timeline" />` dans une page MDX. La prop
  `view` est propre aux formats qui en ont ; `format` garde son sens actuel (montrer un deck en
  film) et n'est pas étendu.
- `@recit/site-stories` dé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 e2e `4307` (`e2e/ports.ts`), projet Vercel `recit-roadmap`, entrée
  `roadmap` dans la table `SITES` de `@recit/site-kit` ([07 · Domaines](/07-domaines) §4 et §5).
- `vercel.json` sert `.ics` en `text/calendar; charset=utf-8` et `.rss` en
  `application/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`) passe `roadmap` en `available`, 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](/06-roadmap) (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 :

1. **Une story, deux langues.** `sites/roadmap/stories/recit.md` (anglais) et `recit.fr.md`
   (français), au format `roadmap` : 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.
2. **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.md` converti au format roadmap », 08 §4 RM4) mélangerait deux documents ; et une page
   de `sites/plans/pages` n'est pas une story (`NOT_A_STORY` du test d'aller-retour). À la place :
   un contrôle dans `pnpm check`, `scripts/check-roadmap-sync.js`, qui compare les titres
   `## Pn — …` de 06 aux éléments `id=pn` de 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.
3. **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 la `description` de son élément dans la story ;
   la page de vision n'aurait alors plus de tableau du tout (question 2).
4. **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`                                                      |
| PDF              | 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](/12-glossaire-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_CODECS` dans `convert.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_VIEWERS` dans `@recit/site-stories`, entrée de `CompiledDeckEntry` discriminé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`, `QuarterSchema` dans `packages/core/src/schema/time.ts`.
- [ ] RM1.2 — `@recit/time` publié : `quarterOf`, `quarterRange`, `halfOf`, `isoWeek`, `today()` et
      `now()` sur `SOURCE_DATE_EPOCH`, formatage `Intl`.
- [ ] 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 — `RoadmapSourceSchema` typé, `RoadmapConfigSchema` dans `recit.config`, JSON Schema
      `roadmap.json`.
- [ ] RM2.2 — Codec Markdown ⇄ JSON, listes à clé dans `mergeSidecar`, règles du §3.6.
- [ ] RM2.3 — `storyToRoadmap` et `RoadmapSchema`.
- [ ] RM2.4 — Composants et vues (§4.2, §4.3), un module CSS et une story avec `play` chacun.
- [ ] RM2.5 — Runtime (`@recit/roadmap/runtime`, montage côté client des filtres et du dialogue) ;
      `recit init`, `dev`, `build`, `validate`, `convert` aiguillés sur `roadmap`.

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 ics` et `--to rss` ; variantes `ics` et `rss` d'`ExportFileSchema`.
- [ ] RM3.2 — `RoadmapPdfOptionsSchema`, vue imprimable `?print=1` avec `whenPrintable()`,
      `export --to pdf` par `@recit/paged`.
- [ ] RM3.3 — MCP : `describe_format` (`roadmap`), `validate_story`, guide `recit://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 gabarit `create-recit`.
- [ ] RM4.2 — `sites/roadmap/stories/recit.md` et `recit.fr.md` (§7), compagnon des statuts.
- [ ] RM4.3 — `scripts/check-roadmap-sync.js`, dans `pnpm 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 `.ics` et `.rss`.
- [ ] RM5.2 — `StoryEmbed view=…`, fichiers générés au build et `ExportPanel`.
- [ ] RM5.3 — `sites/docs` › Formats › Roadmap ; `formats.ts` de `@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

1. **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 ?
2. **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 `description` des éléments et ne
   garder que la prose ?
3. **`type=milestone`** : la règle B4 de 06 réserve `type=` aux blocs motion. Recommandé : amender
   B4 pour dire que `type=` est le type de scène **propre au format** (bloc en motion, `milestone`
   en roadmap), ce que fait déjà 08 §4. Autre voie : `--- milestone=2027-03-31 id=v1`.
4. **Colonne now-next-later** : `horizon=` explicite, sinon dérivée du statut par la table
   `horizons` (recommandé, sans date) ; ou dérivée des dates (trimestre courant = « now »), qui
   dépend de l'horloge ?
5. **`id` obligatoire** sur chaque élément (recommandé) ; ou déduit du titre, au prix de la parité
   entre langues ?
6. **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 `date` obligatoire ?
7. **Flux** : RSS 2.0 seul (recommandé, 08) ; ou aussi Atom ou JSON Feed ?
8. **`lane=`** : supprimé, puisque toute scène qui n'est pas un jalon est une piste (recommandé) ;
   ou gardé comme dans l'exemple de 08 ?
9. **Analyseur ICS des tests** : une dépendance de dev (`ical.js`, MPL-2.0), qui servirait aussi à
   la lecture d'un `.ics` en 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                                                                                                         |
