# 12 — Glossaires et annuaires : les formats `glossary` et `directory`

**Statut : accepté · Phase : P14 · Mis à jour : 24/10/2026.**

Idée d'origine (`sites/plans/pages/archive/BACKLOG_IDEA.md`) : « glossaire.recit.dev |
annuaire.recit.dev — Markdown faisant l'annuaire de site internet, ou d'adresse, ou autre glossaire
». Ce document la pousse jusqu'à une spécification du même niveau que `08-formats.md`. Il propose
des décisions, chacune avec une recommandation, et ne modifie aucun autre plan : les changements à
reporter ailleurs sont listés au §9. Les questions 1, 2, 3, 5, 6, 7 et 8 du §8 ont été tranchées le
24/10/2026, sans l'utilisateur et à sa demande (décisions réversibles).

Placement, ports, domaines et règles communes aux plans 10 à 14 : `06-roadmap.md`, section « Backlog
: plans 10 à 14 ». Ce plan s'y conforme. En résumé : un lot avancé en P9 (schémas, liste « awesome »
lue comme annuaire, vue A–Z minimale), puis P14 Directory après la 1.0. Ce plan est aussi
**propriétaire** de deux éléments partagés : le type `Place` et la commande `recit geocode` (qui
n'existe pas en v1 : les coordonnées sont écrites par l'auteur).

## 1. L'idée en une phrase, et ce qu'elle apporte à Récit

**Une liste de termes ou d'adresses s'écrit une fois en Markdown, puis elle se lit partout : en page
A–Z, en infobulles dans toutes les autres stories, en annexe de livre, en slide, en carte de story,
en carte géographique, en JSON-LD pour les moteurs.**

Les deux idées du backlog (glossaire et annuaire) sont la même chose vue de deux côtés : une
**collection d'entrées nommées**. Un glossaire associe un terme à une définition. Un annuaire
associe une entité (site, lieu, organisation) à des coordonnées. Le modèle, le tri, les filtres, la
recherche, les ancres et les données structurées sont communs, la mise en page change.

Ce que cela apporte à Récit, au-delà d'un dixième et d'un onzième format :

- **Une fonction transverse, pas seulement un format.** Les termes d'un glossaire se lient tout
  seuls dans un deck, une page de doc, un rapport ou un livre (`:term`). C'est la première fois
  qu'une source Récit **enrichit les autres sources** au lieu d'être seulement rendue. C'est la
  forme la plus littérale de « écrire une fois, raconter partout » : la définition est écrite à un
  seul endroit et apparaît partout où le mot apparaît.
- **Le glossaire de Récit lui-même.** `08-formats.md` §9 promet déjà que « la doc a un glossaire »
  pour éviter la confusion `Story` / `stories`. Ce glossaire devient le premier exemple réel du
  format, publié sur docs.recit.dev et lié automatiquement dans toute la doc, comme la roadmap de
  Récit est le premier exemple du format `roadmap`.
- **Un pont vers les données et les lieux.** Un annuaire est une source de données (`@recit/data` :
  compter les entrées par catégorie sans écrire de chiffre), une source de lieux (`calendar` :
  `location=@lieu`, cartes : vue carte) et une source de cartes de story (un lieu = une carte).
- **Une compatibilité avec l'existant.** Une liste « awesome » (`- [Nom](url) - Description.`) est
  déjà un annuaire valide, sans rien changer. Un auteur passe à Récit sans réécrire.

## 2. Ce que la recherche a appris

### 2.1 État de l'art

| Outil                                       | Syntaxe                                                                    | Lien automatique                                 | Ce qu'on retient                                                                                                       |
| ------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Material for MkDocs, abbr + `auto_append`   | `*[HTML]: Hyper Text Markup Language` dans un fichier ajouté à chaque page | oui, toutes les occurrences                      | un **fichier de termes unique** pour tout le projet                                                                    |
| mkdocs-ezglossary                           | liste de définitions `section:term`, référence `<section:term>`            | non, explicite                                   | plusieurs glossaires, page récapitulative avec **rétroréférences** (« où ce terme est utilisé »), sortie PDF           |
| docusaurus-plugin-glossary                  | JSON de termes + `<GlossaryTerm term>`                                     | oui, plugin remark sans casse, frontières de mot | le lien automatique **ignore code, liens et composants MDX**                                                           |
| GRNET / eSSIF-Lab terminology               | `%%term\|term%%`, une page par terme                                       | non                                              | contre-exemple : il **réécrit les sources** au build, ce qui est critiqué. Récit ne réécrit jamais une source au rendu |
| remark-abbr                                 | `*[TERM]: desc`                                                            | oui                                              | **cassé avec micromark** d'après la liste officielle des plugins remark : à ne pas adopter                             |
| remark-deflist / mdast-util-definition-list | `Terme` puis `: définition`                                                | —                                                | deux paquets aux nœuds incompatibles, maintenance incertaine : une dépendance de plus, écartée                         |
| awesome-lists + awesome-lint                | H2 = catégorie, `- [Nom](url) - Description.`                              | —                                                | c'est **déjà un annuaire de sites en Markdown**. Règle `no-dead-urls`                                                  |

Sources : <https://squidfunk.github.io/mkdocs-material/reference/tooltips/>,
<https://pypi.org/project/mkdocs-ezglossary-plugin/1.6.5>,
<https://tomevault.io/tome/mcclowes/docusaurus-plugin-glossary>,
<https://essif-lab.pages.grnet.gr/framework/docs/terminology-plugin-instructions>,
<https://gitlab.grnet.gr/terminology/docusaurus-terminology/-/issues/7>,
<https://github.com/remarkjs/remark/blob/HEAD/doc/plugins.md>,
<https://unifiedjs.com/explore/package/remark-deflist/>, <https://npmjs.com/package/awesome-lint>.

### 2.2 Ce que le dépôt fournit déjà (vérifié)

| Besoin                               | Déjà présent                                              | Où                           |
| ------------------------------------ | --------------------------------------------------------- | ---------------------------- |
| Directives `:term[…]{…}`, `:::entry` | `remark-directive` 3.0.1                                  | `packages/core/package.json` |
| Parcours de l'arbre Markdown         | `unist-util-visit` 5.1.0                                  | `packages/core/package.json` |
| Attributs `{k=v}` en fin d'item      | convention spécifiée pour `roadmap`                       | `08-formats.md` §4           |
| Carte au survol accessible           | `@radix-ui/react-hover-card` 1.1.23 (et tooltip, popover) | `packages/site/package.json` |
| Recherche plein texte                | `pagefind` 1.5.2, plugin `search-pagefind`                | `packages/site`              |
| Import CSV / JSON / YAML             | prévu dans `@recit/data` (P6)                             | `08-formats.md` §0           |
| Index de fin de livre                | `:index[term]` et `back: [notes, index]` dans `book`      | `08-formats.md` §3           |
| JSON-LD `Event` avec un lieu         | prévu dans `calendar`                                     | `08-formats.md` §5           |
| `llms.txt`                           | généré par le fork                                        | `packages/site`              |

`remark-directive-rehype` 1.0.0 n'est déclaré que par `packages/site` (privé, qu'aucun package
publié n'importe) : il n'est pas disponible pour `@recit/core`. On ne s'en sert pas ; s'il devenait
nécessaire, il entrerait au catalogue pnpm à la même version.

Conséquence : **aucune nouvelle dépendance n'est nécessaire**. Tri, lettres et noms de pays passent
par `Intl` (`Intl.Collator`, `Intl.DisplayNames`), la vérification des liens par le `fetch` natif de
Node.

### 2.3 Données structurées

- Glossaire : `DefinedTermSet` qui contient des `DefinedTerm` (`name`, `description`, `termCode`,
  `inDefinedTermSet`). Ces types sont dans la zone « new » de schema.org, et **aucune page de Google
  Search Central ne documente de résultat enrichi pour eux**. On émet le balisage pour
  l'interopérabilité, sans promettre de gain de référencement (règle « aucune métrique inventée »).
  <https://schema.org/DefinedTermSet>, <https://schema.org/DefinedTerm>.
- Annuaire : `ItemList` (positions à partir de 1) d'éléments `LocalBusiness` ou sous-type,
  `Organization` ou `WebSite`, avec `PostalAddress`, `GeoCoordinates`, `url`, `sameAs`. Le carrousel
  Google est en bêta, n'accepte que LocalBusiness, Product et Event, demande au moins trois éléments
  pointant vers des URL distinctes du même domaine, avec le balisage visible sur la page. Une source
  secondaire le dit limité à l'EEE, à vérifier.
  <https://developers.google.com/search/docs/appearance/structured-data/carousels-beta>.

### 2.4 Accessibilité

Les lecteurs d'écran en mode navigation ignorent souvent les infobulles posées sur du texte, et un
attribut `title` seul n'est pas un support fiable. Le terme lié doit donc être **un vrai lien**
(`<a href="/glossary#api">`), qui marche sans JavaScript et à l'impression, avec une carte au survol
**et au focus**, fermée par Échap. Pas de contenu interactif dans un `role=tooltip`.
<https://lightningdesignsystem.com/accessibility/patterns/tooltip>,
<https://getwcag.com/accessibility-guide/aria-tooltip-name>.

### 2.5 Vie privée

Des coordonnées professionnelles qui identifient une personne physique (indépendant, profession
libérale) sont des données personnelles. La CNIL a sanctionné AnnuaireFrancais.fr en 2021, notamment
pour une adresse de domicile publiée comme adresse professionnelle. Les droits de rectification,
d'effacement et d'opposition (RGPD art. 16, 17 et 21) s'appliquent.
<https://www.cnil.fr/fr/reutilisation-de-donnees-par-des-annuaires-en-ligne-quels-droits-pour-les-professionnels-concernes>,
<https://next.ink/brief-article/la-cnil-inflige-3-000-euros-damende-a-lannuairefrancais-fr/>.

### 2.6 Liens et géocodage

- Vérifier des liens externes en CI est instable par nature (sites lents, limites de débit). lychee
  est un binaire Rust, donc une seconde stack : on copie ses bonnes pratiques (cache avec âge
  maximal, `Retry-After`) sans l'adopter. <https://lychee.cli.rs/continuous-integration/github/>.
- Le Nominatim public interdit le géocodage en masse régulier, impose au plus une requête par
  seconde, un User-Agent identifiable et l'attribution OSM (ODbL).
  <https://operations.osmfoundation.org/policies/nominatim/>.

## 3. Décisions proposées

### D1 — Un package, deux formats, une fonction transverse

| Option                                                                  | Pour                                     | Contre                                                                                                 |
| ----------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| A. `@recit/glossary` et `@recit/directory`                              | noms évidents                            | modèle, A–Z, filtres, recherche et JSON-LD en double : contraire à la stack unique                     |
| B. un seul format `directory` avec `entries: glossary`                  | un seul littéral                         | le lien automatique des termes (utile à tous les formats) se retrouve enfoui dans un format d'annuaire |
| **C. un package `@recit/directory`, deux formats, termes dans le cœur** | un modèle, deux lectures, rien en double | deux littéraux de plus dans `StoryFormat`                                                              |

**Recommandation : C.**

- `@recit/directory` (`packages/formats/directory`) porte le modèle commun `Entry` et deux formats
  déclarés : `format: glossary` (A–Z et définitions) et `format: directory` (fiches, liste, tableau,
  carte en option).
- Le **lien automatique des termes** ne vit pas dans le format. Il va dans `@recit/components/terms`
  (plugin remark qui produit un nœud `term` neutre), avec son rendu (`Term`, carte au survol). Le
  cœur ne garde que le nom `term` dans son registre et le schéma `TermsConfig` (règle B4 de
  `06-roadmap.md`). Un deck ou une page de doc profitent donc des termes **sans installer**
  `@recit/directory`, ce qui respecte « un package = une raison d'être installé seul ».
- Le nom du package est `directory` (le plus général), au singulier, comme `roadmap` et `calendar`.

### D2 — Une scène = une section (catégorie), l'entrée est l'unité du modèle

| Option                                                 | Effet                                                                                                                                              |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| une scène = une entrée, chaque H2 coupe une scène      | ajoute au cœur un **second mécanisme de découpage** (le H2 en plus de `---`), qui contredit la syntaxe commune de `02-architecture.md` §3          |
| une scène = une entrée, `---` entre chaque entrée      | lourd pour un glossaire de plusieurs centaines de termes, et incompatible avec une liste « awesome » existante                                     |
| **une scène = une section, les entrées à l'intérieur** | même logique que `roadmap` (une scène = une piste, les items dedans). `---` reste le seul séparateur, et un fichier sans `---` est une seule scène |

**Recommandation : une scène = une section** (une catégorie d'annuaire, ou un groupe thématique de
glossaire). Les entrées sont rangées sous la clé propre au format (`scene.directory.entries`), comme
le veut `02-architecture.md` §2 : le cœur ne connaît aucun format. Les vues (A–Z, filtres)
travaillent sur les entrées, pas sur les scènes. Les ponts vers les formats « une chose à la fois »
(deck, stories, motion) choisissent explicitement leur unité : `--per entry` ou `--per section`.
C'est une correspondance fermée, jamais une devinette.

### D3 — Syntaxe du glossaire

Une entrée par **H2** dans une section. Les attributs de l'entrée sont en accolades à la fin du
titre, avec **le même analyseur** que les items de `roadmap` (un seul utilitaire dans `@recit/core`,
partagé). Le premier paragraphe est la **définition courte** (carte au survol, `description` du
JSON-LD, slide, carte de story). La suite est le corps complet.

```md
---
title: Lorem Glossary
format: glossary
lang: en
glossary:
  view: az # az | sections
  usedIn: true # "Used in" backreferences, computed at build time
---

--- id=core

# Core concepts

## Lorem {id=lorem aliases="lorems, lorem engine" see=ipsum tags=core}

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. The body accepts any Markdown:
lists, code, `::image{src=./img/lorem.svg alt="Lorem diagram"}`, `:::callout`.

## Ipsum {id=ipsum aliases=ipsums abbr=IPS}

Ut enim ad minim veniam, quis nostrud exercitation.

--- id=data

# Data

## Dolor set {id=dolor-set source="https://example.com/dolor" license=CC-BY-4.0}

Duis aute irure dolor in reprehenderit in voluptate.
```

| Élément                | Rôle                                                                                                                                                                                      |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--- id=…` + H1        | une section (scène). Facultative : sans `---`, tout le fichier est une section                                                                                                            |
| H2 + `{…}`             | une entrée. `id` (obligatoire en mode strict, sinon dérivé du titre), `aliases` (formes reconnues par le lien automatique, pluriels compris), `abbr`, `see` (renvois), `tags`, `termCode` |
| premier paragraphe     | définition courte, limitée par la règle de densité (avertissement au-delà du plafond de mots du thème)                                                                                    |
| reste du bloc          | définition longue (page d'entrée, annexe de livre)                                                                                                                                        |
| `source=` + `license=` | provenance d'une définition reprise d'ailleurs (affichée en note, voir §5)                                                                                                                |

Règles :

- deux entrées de même `id`, un alias partagé par deux entrées, un `see` vers un `id` inconnu :
  erreur avec numéro de ligne (`glossary.md:24 alias "lorems" already used by "lorem" (line 12)`) ;
- un terme jamais utilisé dans le projet : avertissement (si `usedIn: true`) ;
- **aucune racinisation** : les pluriels et variantes passent par `aliases`, explicitement, parce
  qu'une règle de pluriel devinée ne marche que pour une langue.

Pas d'import MkDocs (`*[HTML]: Hyper Text Markup Language`) : tranché le 24/10 (question 8).

### D4 — Les termes dans toutes les autres stories (`:term`)

Le glossaire se déclare **au niveau du projet**, dans `recit.config.ts`, sous une clé commune
`terms` (et non `glossary`, qui est la section du format) :

```ts
export default defineConfig({
  terms: {
    sources: ["./glossary/glossary.md"],
    autolink: "none", // none | first | all
    page: "/glossary", // cible des liens
  },
});
```

Dans n'importe quelle story (deck, docs, report, book, stories) :

```md
---
title: Lorem Talk
format: deck
terms:
  autolink: none # refus pour cette story
---

The :term[lorem engine]{id=lorem} reads a dolor set. Lorem is linked only once per scene.

Explicit opt-out for one word: :term[Lorem]{off}.
```

| Élément               | Rôle                                                                              |
| --------------------- | --------------------------------------------------------------------------------- |
| `terms` (config)      | sources de termes, mode de lien, page cible. Sans section, aucun lien automatique |
| `terms` (frontmatter) | surcharge par story (`autolink`, ou `terms: false`)                               |
| `:term[texte]{id}`    | lien explicite, toujours rendu même si `autolink: none`                           |
| `:term[texte]{off}`   | refus local                                                                       |

Règles du lien automatique, toutes testées :

- correspondance sans casse, sur des **frontières de mot Unicode** (`\p{L}`, `\p{N}`), **l'alias le
  plus long d'abord** (« dolor set » avant « dolor ») ;
- ignorés : code (en ligne et blocs), liens existants, titres, `:term` existants, attributs,
  composants MDX, notes présentateur ;
- par langue : une story en `fr` est liée avec les noms et alias de `glossary.fr.md` (§3, D7) ;
- `:term{id=…}` vers un `id` inconnu : erreur avec numéro de ligne ;
- l'automate de correspondance est construit **une fois par build et par langue**, puis partagé.

**Défaut v1 : `none`** (tranché le 24/10, question 2) : seul un `:term` explicite crée un lien. Le
lien automatique vient ensuite, en option, livré en P14 au plus tôt, en mode `first` (première
occurrence **par scène**), parce qu'une slide ou une carte se lit seule. Une page de doc est une
scène, donc « première occurrence par page ». Dans tous les cas, rien n'est actif sans
`terms.sources` : déclarer la source vaut opt-in.

### D5 — Syntaxe de l'annuaire

Compatible avec les listes « awesome » : H2 = catégorie, item de liste = entrée, `{…}` = champs
structurés, `:::entry{id}` = fiche longue.

```md
---
title: Lorem Places
format: directory
lang: en
entries: places # links | places | organizations (no people, see D10)
view: cards # cards | list | table | map
pages: false # default; true : une page par entrée (opt-in)
facets: [category, tags, city]
---

--- id=studios

## Studios

- [Lorem Studio](https://example.com) — Dolor sit amet. {id=lorem-studio tags=design,print street="1 Lorem Street" city=Paris postcode=75000 country=FR geo=48.8566,2.3522}
- [Ipsum Lab](https://example.org) — Consectetur adipiscing. {id=ipsum-lab tags=video city=Lyon country=FR}

--- id=venues

## Venues

- [Dolor Hall](https://example.net) — Sed do eiusmod. {id=dolor-hall tags=music city=Paris country=FR sponsored}

:::entry{id=dolor-hall}
Opening hours, access, accessibility notes. Any Markdown, including `::image{src=./img/hall.svg alt="Dolor Hall facade"}`.
:::
```

Une liste « awesome » brute, sans accolades, est lue en `entries: links` : `id` dérivé du nom,
catégorie = H2, description = texte après le tiret.

| Élément                      | Rôle                                                                                                                                                                                        |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| H2 ou `--- id=…`             | une catégorie (scène)                                                                                                                                                                       |
| `- [Nom](url) — Résumé. {…}` | une entrée. `id`, `tags`, `category` (sinon le H2), `url`, `email`, `phone`, `street`, `city`, `postcode`, `region`, `country` (ISO 3166-1 alpha-2), `geo` (lat,lon), `sameAs`, `sponsored` |
| `:::entry{id}`               | fiche longue d'une entrée, affichée sur sa page ou dépliée                                                                                                                                  |
| compagnon `places.json`      | champs gérés par un outil (`linkStatus`, `checkedAt`, `finalUrl`, `geo` ajouté par une commande), le texte restant dans le `.md` (`09-langues-et-json.md`)                                  |

Règles :

- **l'adresse est structurée, jamais analysée** : `street`, `city`, `postcode`, `country` sont des
  champs distincts. Une chaîne libre `address="…"` est acceptée pour l'affichage seulement, et le
  JSON-LD `PostalAddress` n'est émis qu'à partir des champs structurés. Découper une adresse libre
  serait une devinette ;
- `geo` hors de [-90, 90] / [-180, 180], `country` hors ISO, URL qui n'est pas `http(s)` : erreur
  avec numéro de ligne ;
- `id` en double, `:::entry` vers un `id` inconnu : erreur ;
- entrée `sponsored` : affichée avec une mention visible et `rel="sponsored"` (honnêteté envers le
  lecteur, et règle des moteurs de recherche).

**`entries` en V1 :** `links`, `places`, `organizations`. Réglages à plat dans le frontmatter
(`settings` = section `directory` de `recit.config`) ; la clé s'appelle `entries` et non `kind`,
réservé au discriminant des documents JSON (`09-langues-et-json.md` §2.2). Pas de `people` (tranché
le 24/10, vie privée, D10).

### D6 — Schéma JSON typé

Dans `@recit/core/schema` (zod 4, `zod/mini` côté navigateur), JSON Schema généré par
`z.toJSONSchema` et publié sur `recit.dev/schema/v1/glossary.json` et `/directory.json`.

Ce plan est **propriétaire** de `Place`, le seul type de lieu de Récit. Il est défini une fois dans
`@recit/core/schema` et livré avec `calendar` (P10), son premier consommateur. Le plan 13 (cartes)
l'importe sans le redéfinir. Adresse **structurée**, `geo` **facultatif** ; une adresse libre
`address` n'existe que pour l'affichage, n'est jamais analysée et n'alimente jamais le JSON-LD.

Un champ `location` (événement de `calendar`, entrée d'annuaire, marqueur de carte) accepte trois
formes, résolues une seule fois par le même module : une chaîne libre (affichage seul, comme
`location="Lorem Hall, Paris"` dans `08-formats.md` §5), une référence `@id` vers une entrée
d'annuaire, ou un `Place` inline (JSON). Ligne à reporter dans `08-formats.md` §5 à P10.

```ts
// The single Place of Récit, owned by this plan. Calendar (P10) ships it; maps (plan 13) imports it.
const Place = z.object({
  name: z.optional(Localized(z.string())),
  address: z.optional(z.string()), // display only: never parsed, never used for JSON-LD
  street: z.optional(z.string()),
  city: z.optional(z.string()),
  postcode: z.optional(z.string()),
  region: z.optional(z.string()),
  country: z.optional(z.string().check(isoAlpha2)),
  geo: z.optional(z.tuple([z.number().check(lat), z.number().check(lon)])),
});

const Entry = z.object({
  id: z.string(),
  name: Localized(z.string()),
  aliases: z.optional(Localized(z.array(z.string()))),
  summary: z.optional(Localized(z.string())), // short definition / one-line description
  body: z.optional(Localized(z.string())), // Markdown
  tags: z.optional(z.array(z.string())),
  see: z.optional(z.array(z.string())),
  source: z.optional(z.object({ url: z.url(), license: z.optional(z.string()) })),
});

// BibleEntry (plan 14) extends Entry too: one model of "named entries" in Récit.
const GlossaryEntry = Entry.extend({ abbr: z.optional(z.string()), termCode: z.optional(z.string()) });

const DirectoryEntry = Entry.extend({
  url: z.optional(z.url({ protocol: /^https?$/ })),
  email: z.optional(z.email()),
  phone: z.optional(z.string()),
  place: z.optional(Place),
  sameAs: z.optional(z.array(z.url())),
  sponsored: z.optional(z.boolean()),
});
```

Les noms exacts des vérifications (`isoAlpha2`, `lat`, `lon`) sont à écrire. Les scènes JSON des
deux formats, à ajouter au tableau de `09-langues-et-json.md` §2.3 :

| Format      | Une scène JSON contient                                                                         | Exemple de champ localisable                               |
| ----------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `glossary`  | section : `title`, `entries[] {id, name, aliases, summary, body, see, tags, abbr, source}`      | `entries[].name`, `entries[].aliases`, `entries[].summary` |
| `directory` | catégorie : `title`, `entries[] {id, name, url, summary, tags, place, email, phone, sponsored}` | `title`, `entries[].summary`, `place.name`                 |

Règle de `09` §2.2 appliquée : `url`, `geo`, `phone`, `email`, `country`, `postcode` ne sont
**jamais** localisables. Le nom d'une organisation n'est pas traduit par défaut ; il peut l'être
(`Localized`) quand l'entité a réellement un nom dans chaque langue.

Équivalence (`09` §2.4) : `compile(md) == compile(toJson(md))` pour chaque exemple et chaque
fixture. `recit convert places.md --to json`, et dans l'autre sens `--to md`.

### D7 — Langues

| Domaine   | Règle (ligne à ajouter à `09-langues-et-json.md` §5)                                                                                                                                                                                                    |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| glossary  | `glossary.fr.md` garde les mêmes `id`. Tri et lettres par `Intl.Collator(locale, { sensitivity: "base" })` (« É » sous « E »), groupe « # » pour chiffres et symboles, initiale de l'écriture hors alphabet latin. Alias et lien automatique par langue |
| directory | catégories et résumés localisés. Noms de pays par `Intl.DisplayNames(locale, { type: "region" })`. Adresses affichées telles qu'écrites (il n'existe pas d'API `Intl` pour formater une adresse)                                                        |

Un terme sans traduction dans la langue de la page n'est **pas** lié automatiquement (on ne lie pas
un mot français vers une définition anglaise). C'est une **exception explicite** à la chaîne de
repli de `09-langues-et-json.md` §3 (`Localized` → langue de repli → `defaultLocale`) : pour le lien
automatique seulement, il n'y a pas de repli. Elle est à citer dans la ligne `glossary` de 09 §5. Un
`:term` explicite, lui, suit le repli normal. `recit i18n check` signale les entrées non traduites
(avertissement), et les `id` absents d'une langue (erreur, comme pour les scènes).

### D8 — Rendu dans les autres formats

On ne promet que des correspondances fermées.

| Vers              | Glossaire                                                                                                                                                                                  | Annuaire                                                                                                                                         | Phase                |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- |
| `docs`            | termes liés dans toutes les pages, `<StoryEmbed format="glossary" view="az" />`, page `/glossary` avec ancres                                                                              | `<StoryEmbed format="directory" view="cards" />`, filtres dans l'URL                                                                             | DI2, DI3             |
| `deck`            | `::glossary{terms=lorem,ipsum}` : une slide de définitions. `recit convert --to deck --per entry` : un terme = une slide                                                                   | `::directory{src=./places.md category=studios view=cards limit=6}` : une slide de fiches                                                         | DI5                  |
| `report`          | annexe « Glossary » générée avec les seuls termes utilisés dans le rapport                                                                                                                 | l'annuaire est une source de `@recit/data` : `:value{data=places agg=count where="category=studios"}`. Les chiffres sont calculés, jamais écrits | DI5                  |
| `book`            | `back: [glossary, index]` : annexe glossaire, chaque terme suivi des pages où il apparaît (`@recit/paged`). Le glossaire (définitions) et `:index` (pages) restent deux notions distinctes | —                                                                                                                                                | DI5                  |
| `stories`         | « un mot par carte » : un terme = une carte (nom + définition courte)                                                                                                                      | un lieu ou un site = une carte (nom, résumé, ville, lien en CTA)                                                                                 | DI5                  |
| `motion`          | un terme = un plan : nom → bloc `title`, définition courte → bloc `statement`. Une définition trop dense est une erreur, pas un résumé deviné                                              | une entrée = un plan `title` + `list` (tags)                                                                                                     | DI5                  |
| `calendar`        | —                                                                                                                                                                                          | `location=@dolor-hall` dans un événement renvoie vers l'entrée (même type `Place`, même JSON-LD)                                                 | DI5                  |
| cartes            | —                                                                                                                                                                                          | `view: map` si le package de cartes (plan dédié) est installé, en dépendance **optionnelle** : liste et fiches n'en dépendent jamais             | après le plan cartes |
| `toon`, `roadmap` | rien de promis                                                                                                                                                                             | rien de promis                                                                                                                                   | —                    |

Sur papier (book, report PDF), la carte au survol n'existe pas : le terme reste un lien, et l'annexe
porte la définition.

### D9 — Navigation, recherche, vues

- **Glossaire** : rail A–Z (lettres sans entrée visibles mais désactivées), ancre stable par entrée
  (`#lorem`), page par entrée en option (pas par défaut), renvois `see`, rétroréférences « Used in »
  calculées au build à partir des nœuds `term` de tout le projet.
- **Annuaire** : vues `cards`, `list`, `table`, `map` (optionnelle). Facettes dans l'URL
  (`?category=studios&tag=design&city=Paris`), comme les filtres de `roadmap`.
- **Recherche** : Pagefind pour le plein texte du site (par langue, déjà prévu en `09` §6), et un
  filtre instantané côté client sur un JSON des entrées (nom, alias, tags) produit au build. Pas de
  fuse.js ni de minisearch.
- **Poids** : une page de doc ne charge que les définitions courtes **des termes qu'elle contient**,
  pas tout le glossaire. Le composant `Term` reste léger et n'importe aucun module lourd (règle «
  Bundle Size » de `CLAUDE.md`).
- **Icônes de sites** : pas de favicon ni de capture d'écran tirés en direct des sites listés (fuite
  de l'IP du lecteur vers des tiers, droits sur les images). Par défaut, un monogramme généré à
  partir du nom, dans la teinte unique. Une image locale (`image=./logos/lorem.svg`) reste possible
  sous la responsabilité de l'auteur.

### D10 — Annuaires de personnes (`entries: people`)

**Tranché (24/10, question 5) : pas de `entries: people`**, pour la vie privée. Les règles
ci-dessous ne servent que si la question est rouverte un jour :

- `privacy: masked` par défaut : ville sans rue, `geo` arrondi, e-mail et téléphone masqués (révélés
  au clic). Le masquage ne protège que contre la collecte automatique, la doc le dit ;
- aucun JSON-LD `Person` par défaut, `noindex` en option ;
- une page « retrait et rectification » fournie par le gabarit, avec le contact de l'éditeur ;
- un avertissement de lint pour toute entrée `person=true` avec une adresse de rue sans
  `consent=true`.

### D11 — Données structurées

Module JSON-LD **partagé** avec `calendar` (fonctions pures dans `@recit/core`, sous-chemin à nommer
avec P10, par exemple `./jsonld`), pour qu'un `Place` soit émis de la même façon dans un événement
et dans une fiche d'annuaire. Glossaire → `DefinedTermSet` ; annuaire → `ItemList`. `pages: true`
rend l'annuaire éligible au carrousel en bêta, sans le promettre. Le glossaire part aussi
naturellement dans `llms.txt`.

### D12 — Outillage et MCP

| Commande ou outil                                | Rôle                                                                                                                                                                                                                                                                                     |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `recit convert places.csv --to directory`        | import CSV / JSON via `@recit/data`, avec une table `columns:` explicite (`name: Nom`, `url: Site`). Une colonne non mappée est ignorée avec un avertissement, jamais devinée                                                                                                            |
| `recit convert places.md --to csv`               | export tabulaire, correspondance fermée sur les champs du schéma                                                                                                                                                                                                                         |
| `recit validate --links`                         | vérification des liens (opt-in, réseau). Voir D13. Plus tard                                                                                                                                                                                                                             |
| `recit geocode places.md`                        | **commande unique de géocodage de Récit** (aussi pour les cartes du plan 13) : ponctuelle, explicite, écrit `geo` dans le compagnon JSON sans réécrire la source (règle de 09), avec cache. Jamais au build. Fournisseur configurable, voir §5. Pas en v1 (tranché le 24/10) : plus tard |
| MCP `lookup_term(id \| text, locale)`            | lecture seule : définition courte et longue                                                                                                                                                                                                                                              |
| MCP `search_directory(query, filters)`           | lecture seule : entrées filtrées                                                                                                                                                                                                                                                         |
| MCP `describe_format("glossary" \| "directory")` | déjà prévu en `09` §7                                                                                                                                                                                                                                                                    |

Les deux outils MCP servent aussi le plan « scénariste » (ai.recit.dev) : le lexique d'un univers et
la liste de ses lieux deviennent des sources que l'agent consulte au lieu d'inventer. S'il faut un
skill produit pour le glossaire, il est généré par la commande unique `recit skills export` du plan
14 (IA3.3) depuis `recit://guide/glossary`, sans second générateur.

Import CSV, `validate --links`, `geocode` et JSON-LD (D11) sont **plus tard** : aucun n'entre dans
le lot P9 ni dans la première version de P14. Il n'y a pas d'import MkDocs (question 8).

### D13 — Vérification des liens

`recit validate --links` (on ajoute une option à une commande existante plutôt qu'un nouveau verbe)
:

- `fetch` natif de Node, HEAD puis repli sur GET, concurrence bornée, pause par hôte, retries avec
  backoff, respect de `Retry-After`, User-Agent identifiable ;
- cache local `.recit/cache/links.json` avec âge maximal configurable ;
- résultat écrit dans le compagnon JSON (`linkStatus`, `checkedAt`, `finalUrl`). Une redirection
  vers **un autre domaine** est signalée (domaine expiré racheté, contenu changé) ;
- par défaut : avertissement, le build ne casse pas. `--strict` pour la CI de l'auteur ;
- un lien en échec s'affiche avec un badge « à vérifier », sans être retiré ;
- les tests et e2e de Récit n'utilisent que `example.com`, `example.org`, `example.net` et un
  serveur local : aucun réseau en CI.

## 4. Packages, site, exemples et tests

### 4.1 Packages

| Package               | Groupe       | Statut    | Contenu                                                                                                                                                                                                                                        | Dépendances notables                                                                                     |
| --------------------- | ------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `@recit/core`         | fondations   | ✏️ P9     | `./schema` : `Entry`, `GlossaryEntry`, `DirectoryEntry`, `Place`, `GlossarySource`, `DirectorySource`, `TermsConfig` ; nom `term` dans le registre des directives. Analyseur `{k=v}` partagé avec `roadmap`. JSON-LD partagé (`./jsonld`, P10) | `remark-directive`, `unist-util-visit` (déjà là)                                                         |
| `@recit/components`   | fondations   | ✏️ P14    | `./terms` : plugin remark du lien automatique et de `:term` (DI1) ; `Term` (lien + carte au survol et au focus, P14)                                                                                                                           | `@radix-ui/react-hover-card` à la version déjà utilisée (1.1.23), passée au catalogue pnpm               |
| `@recit/directory`    | formats      | 📅 P9     | parseurs `glossary` et `directory`, vues, composants (§4.2), émission JSON-LD, pages par entrée, rétroréférences                                                                                                                               | `@recit/core`, `@recit/components`, `@recit/data` (import). Pas `@recit/paged` : le PDF passe par la CLI |
| `@recit/cli`          | distribution | plus tard | `convert` (csv), `validate --links`, `geocode` (après la v1)                                                                                                                                                                                   | aucune nouvelle                                                                                          |
| `@recit/mcp`          | distribution | ✏️ P14    | `lookup_term`, `search_directory`                                                                                                                                                                                                              | aucune nouvelle                                                                                          |
| `@recit/site-stories` | intégrations | ✏️ P14    | lien automatique des termes dans les pages du framework de sites, `StoryEmbed` pour les deux formats                                                                                                                                           | aucune nouvelle                                                                                          |
| `@recit/site-kit`     | outils       | ✏️ P14    | entrée `directory` dans `SITES`                                                                                                                                                                                                                | aucune nouvelle                                                                                          |

Tags nx : `@recit/directory` porte `scope:format` et ne dépend que de fondations. Aucun package
publié n'importe `@recit/site`. **Aucun patch du cœur du framework de sites.**

Exception à `03-packaging.md` §1 (« un package par format ») : `@recit/directory` déclare **deux**
formats, `glossary` et `directory`, parce qu'ils partagent tout le modèle (D1). Elle est à reporter
dans 03 §1 comme règle générale : « un package peut déclarer plusieurs formats s'ils partagent leur
modèle ».

Écartés : remark-abbr (cassé avec micromark), remark-deflist et mdast-util-definition-list (doublons
fragiles), fuse.js et minisearch (Pagefind + filtre client suffisent), lychee (seconde stack), toute
bibliothèque de géocodage, tout service de favicons.

### 4.2 Composants

`Glossary` (racine, choix de la vue), `AlphabetRail`, `GlossaryEntry` (nom, abréviation, définition,
renvois, source), `UsedIn` (rétroréférences), `Directory` (racine), `EntryCard`, `EntryList`,
`EntryTable`, `DirectoryFilters` (catégorie, tag, ville, lettre), `EntryPage`, `LinkStatusBadge`,
`Monogram`, et `Term` (dans `@recit/components`). Tous utilisables en MDX
(`<StoryEmbed slug="places" format="directory" view="table" />`).

### 4.3 Site

| Domaine                 | Rôle                              | Dossier           | Contenu                                                                                                                                                                                                                                   |
| ----------------------- | --------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **directory.recit.dev** | formats `directory` et `glossary` | `sites/directory` | hero = **le glossaire de Récit** (Story, Scene, sidecar, bridge, directive…) avec ses termes liés dans un paragraphe de démo, puis un annuaire Lorem en fiches, tableau et carte. Galerie, gabarits, démo bilingue (tri « É » sous « E ») |

| Alias                 | Cible                             | Pourquoi                                              |
| --------------------- | --------------------------------- | ----------------------------------------------------- |
| `glossary.recit.dev`  | `directory.recit.dev/glossary`    | second format du site, comme `video` → `motion/video` |
| `glossaire.recit.dev` | `directory.recit.dev/fr/glossary` | mot du backlog                                        |
| `annuaire.recit.dev`  | `directory.recit.dev/fr`          | mot du backlog                                        |

**Recommandation :** un seul site, `directory.recit.dev`, avec deux sections. Règle commune de
`07-domaines.md` §2 (proposée en 06, « Backlog ») : le domaine principal porte le nom anglais, et
les mots du backlog (`glossaire.`, `annuaire.`) ainsi que le nom technique deviennent des alias 301.
Tranché le 24/10 (question 1) : un seul site avec alias.

- Vercel : projet `recit-directory`, racine `sites/directory`, build `nx run directory:build`,
  statique seulement. Aucun appel réseau au build (la vérification des liens n'y tourne pas).
- Port local : **3012**, d'après la table d'attribution unique de `07-domaines.md` §4 (components
  3010, form 3011, directory 3012, maps 3013, ai 3014).
- Composants site-kit (`07` §4) : `FormatHero` (glossaire jouable : survol, focus, Échap, avec la
  source dépliable), `SourceToggle` (rendu / Markdown / JSON), `LocaleDemo` (en / fr),
  `FormatGallery`, `TemplateCard` (`npm create recit -- --template glossary-lorem`,
  `directory-links`, `directory-places`), `ComponentShowcase` (`Term`, `AlphabetRail`, `EntryCard`,
  `DirectoryFilters`), `ExportPanel` (composant unique de `@recit/site-kit`, qui lit `exports.json`
  : PDF, CSV, JSON, JSON-LD, chacun une variante `kind` de `ExportManifestSchema` de `@recit/core`,
  ajoutée dans la phase qui le produit), `BridgeCard` (glossaire → annexe de livre, annuaire →
  calendrier et stories), `DocsLinks` (vers `docs.recit.dev/glossary/…` et `/directory/…`),
  `FormatFooter`.
- La doc reste sur docs.recit.dev (règle anti-doublon) : Formats › Glossary et Formats › Directory,
  plus `docs.recit.dev/components/term` pour `:term`, dans la section unique des blocs transverses.
- Marque : icônes `brand/formats/fmt-glossary-*` et `fmt-directory-*` dans la teinte unique Q07.

### 4.4 Exemples et e2e

| Exemple                     | Contenu                                                                                                     |
| --------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `examples/glossary-lorem`   | glossaire Lorem en/fr, deux sections, alias, renvois, plus un deck et une page MDX qui utilisent les termes |
| `examples/directory-links`  | une liste « awesome » Lorem brute, sans accolades, lue telle quelle en `entries: links`                     |
| `examples/directory-places` | lieux Lorem en/fr (adresses fictives, `example.*`), compagnon JSON, import CSV, vue tableau et fiches       |

Paquets `recit-example-glossary-lorem`, `recit-example-directory-links`,
`recit-example-directory-places`. Lorem Ipsum partout, aucune personne réelle.

e2e (`e2e/glossary.e2e.ts`, `e2e/directory.e2e.ts`), avec captures en clair, sombre et mobile :

- rail A–Z et tri `fr` (« É » sous « E ») ;
- terme lié dans un deck et une page de doc, **non lié** dans un bloc de code, un titre et un lien ;
- carte au survol ouverte au clavier (focus), fermée par Échap, et lien qui marche sans JavaScript ;
- filtres dans l'URL, retour arrière du navigateur ;
- page d'entrée et ancres ;
- JSON-LD présent et valide contre le schéma attendu (test unitaire sur la sortie) ;
- `axe` sans violation ;
- `validate --links` contre un serveur local qui répond 200, 301 vers un autre domaine, 404, 429.

## 5. Sécurité, vie privée, licences

### 5.1 Sécurité

- URL d'entrée limitées à `http:` et `https:` (`javascript:`, `data:` refusés à la validation).
- Liens externes avec `rel="noopener noreferrer"`, plus `sponsored` ou `ugc` selon l'entrée.
- Le Markdown des fiches passe par le même pipeline (et la même sanitisation) que le reste de Récit
  : une entrée importée d'un CSV n'injecte pas de HTML brut.
- Aucun secret n'est requis. Le géocodage par un fournisseur à clé (si l'auteur en choisit un) lit
  la clé dans l'environnement de la commande, jamais dans `recit.config.ts` ni dans le bundle.

### 5.2 Vie privée

- Aucune requête vers un tiers au chargement de la page : pas de favicons distants, pas de tuiles
  par défaut (la vue carte suit les règles du plan cartes), pas de géocodage au rendu.
- La vérification des liens et le géocodage ne tournent que sur la machine de l'auteur, sur
  commande.
- Pas de `entries: people` (D10, tranché le 24/10).
- Le gabarit d'annuaire fournit un champ `contact` de l'éditeur et une page « retrait et
  rectification », même pour `places` et `organizations` : une fiche d'entreprise individuelle peut
  identifier une personne.

### 5.3 Licences des contenus

- Une définition reprise d'ailleurs porte `source=` et `license=`, affichés en note. Une source sous
  CC BY-SA (cas de Wikipédia) impose l'attribution et le partage dans les mêmes conditions : la doc
  le dit, et le lint avertit si `license` contient `SA` sans `source`.
- Un annuaire importé de données ouvertes déclare la source et la licence du jeu de données dans le
  frontmatter (`sources: { places: { file, license, url } }`). Lint : erreur en mode strict si la
  licence manque. Cas à documenter : des données OSM (ODbL) importées font de l'annuaire une base
  dérivée, soumise au partage à l'identique.
- Le géocodage via Nominatim (si l'auteur le choisit) impose l'attribution OSM (ODbL) sur le site
  publié, ajoutée automatiquement par le gabarit quand le compagnon contient des `geo` issus de
  Nominatim.

### 5.4 Lignes NOTICE.md à prévoir

| Où dans NOTICE.md               | Ligne                                                                                                                                                                                     |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| « What comes from where »       | étendre `formats/{deck,report,toon,book,roadmap,calendar,stories,paged}` à `…,directory}`                                                                                                 |
| « What comes from where »       | `sites/directory` et `examples/{glossary,directory}-*` dans la ligne des sites et exemples Récit (à créer si elle n'existe pas)                                                           |
| « Third-party assets »          | aucune : aucune nouvelle dépendance, aucun asset tiers. `@radix-ui/react-hover-card` est MIT et déjà présent                                                                              |
| « Third-party assets » (si D12) | « OpenStreetMap data via Nominatim (`recit geocode`, opt-in) — ODbL, attribution required » si le géocodage Nominatim est livré. **Ligne unique** pour Récit : le plan 13 n'en ajoute pas |
| mention d'inspiration           | « Glossary auto-linking rules inspired by docusaurus-plugin-glossary and mkdocs-ezglossary (ideas only, no code) », si on veut créditer les idées                                         |

## 6. Phases

Lots DI0 à DI7, sous-tâches `DIn.m` (convention de 06, « Backlog »). Placement accepté le 24/10 dans
`06-roadmap.md` (question 3) :

| Lot                                                                                        | Phase                |
| ------------------------------------------------------------------------------------------ | -------------------- |
| DI0, plus la part `entries: links` de DI4 et une vue A–Z minimale de DI3                   | lot avancé de P9     |
| `Place` (DI0.1)                                                                            | avec `calendar`, P10 |
| DI1 (`:term`, puis lien automatique `first` en option), DI2, reste de DI3 et DI4, DI5, DI7 | P14 Directory        |
| DI6, JSON-LD (DI3.4, DI4.3)                                                                | plus tard            |

Le lot P9 vient avant la roadmap : l'analyseur `{k=v}` est **créé ici** (DI0.2), puis réutilisé par
le format `roadmap` (RM1 à RM5).

### DI0 — Schémas (S)

- [ ] DI0.1 — `Entry`, `GlossaryEntry`, `DirectoryEntry`, `TermsConfig` dans `@recit/core/schema`.
      `glossary` et `directory` ajoutés à `StoryFormat` et à `StorySourceSchema`, comme ajout
      compatible en v1 (règle de versionnement de 06, « Backlog »). `Place` arrive avec `calendar`
      (P10).
- [ ] DI0.2 — Analyseur `{k=v}` en fin de titre ou d'item, **créé ici** et réutilisé par `roadmap`
      en P9 (un seul module, testé une fois).
- [ ] DI0.4 — Lot P9 : une liste « awesome » brute lue en `entries: links` (vues `cards` et `list`)
      et une vue A–Z minimale du glossaire, dans `@recit/directory`.
- [ ] DI0.3 — JSON Schema générés (`glossary.json`, `directory.json`), ajoutés à la publication
      `recit.dev/schema/v1`.

**Sortie :** une liste « awesome » Lorem et un glossaire Lorem passent `recit validate`, avec
erreurs localisées (ligne et chemin JSON) sur des fixtures fautives ; `examples/directory-links`
s'affiche en fiches et en liste ; le glossaire Lorem s'affiche en A–Z.

### DI1 — Termes transverses (M), P14

`:term` explicite fait partie de DI1 dans tous les cas. Le lien automatique (`first`, `all`) est une
option, livrée ensuite ; le défaut v1 reste `none` (question 2, tranchée).

- [ ] DI1.1 — `@recit/components/terms` : automate de correspondance (alias le plus long, frontières
      Unicode, sans casse), plugin remark, `:term[…]{id}` et `{off}`, modes `first` / `all` /
      `none`.
- [ ] DI1.2 — Section `terms` de `recit.config.ts` et surcharge par frontmatter.
- [ ] DI1.3 — `Term` dans `@recit/components` (lien réel + carte au survol et au focus).
- [ ] DI1.4 — Branchement dans `@recit/site-stories` (pages de doc) et dans `@recit/deck`.
- [ ] Tests : zones ignorées (code, titres, liens, MDX, notes), collisions d'alias, langues, poids
      (seules les définitions utilisées sont embarquées).

**Sortie :** un terme est lié dans un deck et une page de doc, ignoré dans le code et les titres, la
carte s'ouvre au clavier, et le lien marche sans JavaScript.

### DI2 — Le glossaire de Récit (S), P14

- [ ] DI2.1 — Rédiger le glossaire de Récit (en, fr) avec les mots du modèle et de la syntaxe
      (`Story`, `Scene`, `stories`, sidecar, bridge, directive, format…).
- [ ] DI2.2 — Activer `terms` sur `sites/docs`.

**Sortie :** docs.recit.dev lie ses termes, et la confusion `Story` / `stories` notée en `08` §9 a
une définition à un seul endroit.

### DI3 — Format `glossary` (M), P14 (vue A–Z minimale en lot P9)

- [ ] DI3.1 — Parseur (sections, H2, définition courte et longue, renvois, source).
- [ ] DI3.2 — Vues `az` et `sections`, `AlphabetRail` avec `Intl.Collator`, ancres, pages par
      entrée.
- [ ] DI3.3 — Rétroréférences « Used in » calculées au build sur tout le projet.
- [ ] DI3.4 — (plus tard) JSON-LD `DefinedTermSet` (module partagé avec `calendar`).
- [ ] Tests : goldens des deux vues en en/fr, tri, renvois inconnus en erreur.

**Sortie :** `examples/glossary-lorem` s'affiche en A–Z en en et fr, avec rétroréférences.

### DI4 — Format `directory` (M), P14 (`entries: links` en lot P9)

- [ ] DI4.1 — Parseur compatible « awesome » (H2, items, `{…}`, `:::entry`), `entries` `links`,
      `places`, `organizations`.
- [ ] DI4.2 — Vues `cards`, `list`, `table`, facettes dans l'URL, filtre client, `Monogram`, mention
      `sponsored`.
- [ ] DI4.3 — Pages par entrée (`pages: true`, opt-in ; défaut `false`). JSON-LD `ItemList` : plus
      tard.
- [ ] DI4.4 — Compagnon JSON (statut des liens, `geo`).

**Sortie :** `examples/directory-links` (liste brute) et `examples/directory-places` (en/fr)
s'affichent dans les trois vues, avec filtres partageables par URL.

### DI5 — Ponts et exports (M), P14

- [ ] DI5.1 — `::glossary` et `::directory` dans le deck, `--per entry` / `--per section` pour
      `recit convert --to deck|stories|motion`.
- [ ] DI5.2 — Annexe glossaire dans `book` (`back: [glossary]`) et `report`, avec renvois de pages
      calculés à l'impression (par la CLI et `@recit/paged`, moteur `pagedjs` ; `@recit/directory`
      n'importe pas `@recit/paged`).
- [ ] DI5.3 — L'annuaire comme source `@recit/data` (`:value … agg=count`).
- [ ] DI5.4 — `location=@id` dans `calendar`.
- [ ] DI5.5 — `recit export --to pdf` pour les deux formats.

**Sortie :** la même source de glossaire produit une page A–Z, une annexe de livre PDF, un deck
d'une slide par terme et une story d'une carte par terme. Chaque pont a un test d'équivalence.

### DI6 — Outillage (S), plus tard

- [ ] DI6.1 — `recit convert` CSV ↔ directory (table `columns:` explicite). Pas d'import MkDocs.
- [ ] DI6.2 — `recit validate --links` (cache, `Retry-After`, redirections inter-domaines).
- [ ] DI6.3 — après la v1 : `recit geocode` (opt-in, fournisseur configurable, cache, attribution),
      commande unique partagée avec les cartes (plan 13).
- [ ] DI6.4 — MCP `lookup_term`, `search_directory`, et `describe_format` pour les deux formats.

**Sortie :** un CSV Lorem devient un annuaire puis revient en CSV sans perte, et `validate --links`
passe contre le serveur local de test sur les quatre cas (200, 301 inter-domaine, 404, 429).

### DI7 — Site et doc (S), P14

- [ ] DI7.1 — `sites/directory` (directory.recit.dev), projet Vercel `recit-directory`, alias
      `glossary`, `glossaire` et `annuaire`, entrée `SITES`, port 3012.
- [ ] DI7.2 — `sites/docs` › Formats › Glossary, Formats › Directory, Components › Term.
- [ ] DI7.3 — Icônes `fmt-glossary` et `fmt-directory`, gabarits `create-recit`.
- [ ] Tests : e2e de §4.4 avec captures, `axe` sans violation, `i18n check` vert sur les exemples.

**Sortie :** le site est en ligne en en et fr, les trois exemples sont publiés, la version alpha
correspondante est publiée après validation explicite.

### Plus tard

- [ ] ~~`entries: people` avec les règles de D10.~~ Écarté le 24/10 (question 5, vie privée).
- [ ] Vue `map` quand le package de cartes existe.
- [ ] Proposition d'entrée par formulaire (plan formulaires) : le formulaire produit une entrée
      valide contre `DirectoryEntry`, relue par l'auteur avant publication.

## 7. Risques

| Risque                                                                     | Parade                                                                                                                                                    |
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Faux positifs du lien automatique sur des mots courants (« set », « map ») | mode `first` par scène, alias explicites, `:term{off}`, opt-in par déclaration de `terms.sources`, avertissement sur les termes d'un seul mot très courts |
| Lien automatique lent sur un gros projet                                   | automate construit une fois par build et par langue, tests de charge sur une fixture générée (mesure avant tout chiffre publié)                           |
| Poids de page si tout le glossaire est embarqué                            | seules les définitions courtes des termes présents dans la page                                                                                           |
| Deux mécanismes de découpage des scènes                                    | décision D2 : `---` reste le seul séparateur, l'entrée est une sous-unité                                                                                 |
| Doublon d'analyseur `{k=v}` avec `roadmap`                                 | un seul module dans `@recit/core` (DI0.2)                                                                                                                 |
| Confusion glossaire / index de livre                                       | deux notions distinctes, `back: [glossary, index]`, documentées côte à côte                                                                               |
| Liens externes morts ou rachetés                                           | `validate --links`, détection des redirections inter-domaines, badge « à vérifier »                                                                       |
| CI instable à cause du réseau                                              | aucun réseau en CI, serveur local de test, `--strict` réservé à l'auteur                                                                                  |
| Annuaire qui publie des données personnelles                               | pas de `entries: people` ; masquage, page de retrait et lint si la question est rouverte                                                                  |
| Données ou définitions sous licence virale (ODbL, CC BY-SA)                | `source`/`license` par entrée et par jeu de données, lint, doc                                                                                            |
| Adresse libre mal interprétée                                              | adresse structurée obligatoire pour le JSON-LD, aucune analyse de chaîne                                                                                  |
| Promesse SEO non tenue (carrousel en bêta, DefinedTerm sans rendu Google)  | aucune promesse dans la doc ni sur le site, seulement « balisage émis »                                                                                   |
| Encore deux formats en parallèle d'une roadmap déjà longue                 | seul un petit lot en P9 avant la 1.0 ; le reste en P14 Directory                                                                                          |

## 8. Questions ouvertes pour l'utilisateur

Les questions communes au backlog (domaines en français, « Pet », « le skill », « le projet basic »,
le brocoli, l'exception lebrocoli.fr) sont regroupées une seule fois dans `06-roadmap.md`, «
Questions ouvertes ». Un annuaire de lieux pour le gabarit lebrocoli.fr serait une section
optionnelle du dossier unique `examples/scenario-lebrocoli` (plan 14), avec des lieux réels ou
fictifs selon la réponse de l'utilisateur (06, « À l'utilisateur »). Les questions ci-dessous ont
été tranchées le 24/10/2026, sans l'utilisateur et à sa demande (décisions réversibles), sauf la 4 :

1. ~~**Domaine** : un seul site `directory.recit.dev`, avec `glossary`, `glossaire` et `annuaire` en
   301, ou deux sites ?~~ Tranché (24/10) : un seul site, avec alias.
2. ~~**Lien automatique** : défaut v1 `none`, puis `first` par scène en option ?~~ Tranché (24/10) :
   liens automatiques désactivés par défaut en v1, mode `first` en option ensuite.
3. ~~**Place dans la roadmap** : lot avancé en P9, puis P14 Directory après la 1.0 : d'accord ?~~
   Tranché (24/10) : oui (ordre du backlog accepté, 06 question 14).
4. **Glossaire et annuaire** sont-ils bien la même idée pour toi (une collection d'entrées, deux
   lectures), ou pensais-tu à deux outils séparés ? (Le choix d'un seul site avec alias va dans le
   sens d'une seule idée.)
5. ~~**Annuaire de personnes** (`entries: people`) : hors V1, ou un besoin réel dès le départ ?~~
   Tranché (24/10) : pas de `entries: people` (vie privée).
6. ~~**Pages par entrée** (`pages: true`) activées par défaut, ou une seule page par défaut ?~~
   Tranché (24/10) : pas de page par entrée par défaut (`pages: false`).
7. ~~**Géocodage** : fournir plus tard `recit geocode`, ou s'en tenir aux coordonnées écrites par
   l'auteur ?~~ Tranché (24/10) : coordonnées écrites par l'auteur, pas de commande de géocodage en
   v1. Vaut aussi pour les cartes (plan 13).
8. ~~**Import MkDocs** (`*[HTML]: …`) : utile, ou à ignorer ?~~ Tranché (24/10) : pas d'import
   MkDocs.

## 9. Impact sur le reste du plan (à reporter, non fait ici)

| Fichier                 | Ce qui change                                                                                                                                                                                               |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `02-architecture.md`    | `StoryFormat` gagne `glossary` et `directory`. Section `terms` dans la config (§5). `:term` dans la syntaxe commune (§3)                                                                                    |
| `03-packaging.md`       | `@recit/directory` 📅 dans le tableau des packages, nom npm à réserver. Extensions de `@recit/core` (schémas, nom `term`, `./jsonld`) et de `@recit/components` (`./terms`, `Term`)                         |
| `06-roadmap.md`         | fait : lot P9 et P14 Directory dans « Backlog : plans 10 à 14 ». Décision datée du 24/10 ajoutée (question 3)                                                                                               |
| `07-domaines.md`        | fait : `directory.recit.dev`, alias `glossary`, `glossaire`, `annuaire`, projet Vercel `recit-directory`, port 3012 (et `components` = 3010)                                                                |
| `08-formats.md`         | lignes dans la vue d'ensemble et les ponts (§0), `:term` dans les composants transverses (§7), `back: [glossary]` dans `book` (§3), risque `Story`/`stories` (§9) traité par DI2. Corriger « neuf formats » |
| `09-langues-et-json.md` | lignes `glossary` et `directory` en §2.3 et §5 (texte en D6 et D7), avec l'exception au repli de D7 citée en §3 et §5                                                                                       |
| `08-formats.md` §5      | forme de `location` : chaîne libre, `@id` d'annuaire ou `Place` inline, une seule résolution (D6)                                                                                                           |
| `NOTICE.md`             | lignes du §5.4                                                                                                                                                                                              |
| `brand/`                | icônes `fmt-glossary` et `fmt-directory`                                                                                                                                                                    |
