La prose, déclarée dans la config.
docsPages tisse guides, tutoriels et changelogs dans la navigation, au-dessus de la référence API — rendus dans le navigateur comme tout le reste. La page de fonctionnalité plante le décor ; ce guide est la syntaxe complète.
Trois sortes d’entrées
docsPages est un tableau ordonné, et l’ordre du tableau est l’ordre de la nav — il n’y a pas de champ position. Les entrées se distinguent par leurs clés :
{
"docsPages": [
{ "slug": "getting-started", "title": "Bien démarrer",
"url": "/docs-pages/getting-started.md", "home": true },
{ "group": "Guides", "collapsed": false, "pages": [
{ "slug": "pagination", "title": "Pagination", "url": "/docs-pages/pagination.md" },
{ "title": "Page de statut", "href": "https://status.example.com" }
]},
{ "title": "GitHub", "href": "https://github.com/acme/api" }
]
}- Une page —
slugplus exactement une source de contenu (section suivante). Éventuellementkind: "changelog", qui l’inscrit dans le traitement en frise. - Un groupe — un seul niveau, repliable,
collapsed: truepour démarrer fermé. Un groupe contient des pages et des liens externes, jamais d’autres groupes.idest optionnel : il ne sert qu’à l’identité de fusion en multi-spec et prend par défaut le titre slugifié. - Un lien externe —
hrefau lieu d’un contenu, rendu avec une icône « externe », ouvert dans un nouvel onglet.
Une entrée invalide — page sans slug ni contenu, groupe sans pages, lien sans href — est écartée avec un avertissement console, jamais rendue de travers en silence.
Deux zones de nav : la doc au-dessus, l’annexe en dessous
Par défaut, chaque entrée vit au-dessus de la référence API, sous le titre « Documentation ». Une entrée de premier niveau — page, groupe ou lien externe — peut déclarer nav: "bottom" et rejoindre une zone finale au pied de la barre latérale, sous toute la référence et les webhooks : la moitié support-et-légal de la doc cesse de repousser les endpoints vers le bas.
{
"docsPages": [
{ "slug": "getting-started", "title": "Bien démarrer", "url": "/docs/start.md" },
{ "slug": "support", "title": "Support", "url": "/docs/support.md", "nav": "bottom" },
{ "group": "Mentions légales", "nav": "bottom", "pages": [ … ] },
{ "title": "Statut", "href": "https://status.example.com", "nav": "bottom" }
]
}La zone finale ne porte délibérément aucun titre — seulement un séparateur : elle se lit comme une annexe, pas comme une seconde section de doc. nav est un choix de premier niveau ; dans un groupe, il est ignoré avec un avertissement console, parce qu’un groupe voyage entier. Et la zone est un ordre, pas seulement une place : le pager précédent/suivant, la recherche et les exports parcourent la zone du haut d’abord, puis la référence, puis la zone du bas — une page déclarée en premier mais envoyée en bas est la dernière que le pager atteint.
D’où vient le contenu d’une page
Une clé parmi trois — déclarez-en exactement une ; s’il s’en glisse plusieurs, ce que la page transporte gagne sur ce qu’elle devrait aller chercher :
| Clé | Contenu | Usage typique |
|---|---|---|
url | Un fichier à récupérer — .md par défaut, .html et .txt reconnus à l’extension | Le défaut : des fichiers à côté d’index.html |
content | Le texte lui-même, dans la config | Un backend qui génère la config génère la prose avec |
contentId | L’id d’un élément de la page hôte contenant le texte | De la prose écrite à la main derrière un login, ou un hôte qui ne sert qu’une route |
Le réceptacle de contentId est un élément script au type non exécutable — un <script> nu exécuterait son contenu comme du JavaScript bien avant que l’app le lise :
<script type="text/markdown" id="doc-pagination">
# Pagination
Les endpoints de collection renvoient une page à la fois. Commencez par
[créer un animal](apidoc:createPet).
</script>Deux mécaniques à connaître : un </script> littéral dans un exemple de code ferme l’élément trop tôt (cette page-là relève d’un fichier ou de content), et l’indentation commune à toutes les lignes est retirée — le bloc peut donc rester indenté dans votre markup sans que Markdown lise quatre espaces comme un bloc de code. Un contentId qui ne pointe sur rien échoue comme un fichier injoignable : une erreur visible qui nomme l’id, jamais une page blanche.
Formats. Un contenu transporté n’a pas d’extension : format ("markdown", "html" ou "text") le dit quand il le faut, et gagne sur l’extension comme sur le type de l’élément. Les pages HTML passent par le même assainisseur que tout le reste et ignorent les enrichissements réservés au Markdown ; un .txt se rend préformaté, sans sommaire mais avec sa pagination. Un bloc de frontmatter YAML en tête d’un fichier Markdown est retiré et ignoré — un README écrit pour un autre outil se rend proprement.
Ou pointer un manifeste
docsPages peut être une URL au lieu d’un tableau :
{ "docsPages": "/docs-pages/manifest.json" }Le manifeste est { "pages": [ … ] } avec les mêmes entrées, et les URL relatives qu’il contient se résolvent contre le manifeste lui-même — le dossier de doc reste autonome, versionné et déplacé indépendamment de la page hôte. Un manifeste qui ne charge pas ou ne parse pas affiche une erreur visible au niveau de la nav et laisse la référence intacte.
Langues et prise de contrôle de l’accueil
title (toutes les sortes) et chaque champ de contenu d’une page acceptent une table par langue — { "en": "/guide.en.md", "fr": "/guide.fr.md" } — la résolution suit la langue courante de l’UI, puis en, puis la première clé déclarée. Changer de langue re-rend la page ouverte et les libellés de nav. Une chaîne simple signifie « pareil dans toutes les langues ».
home: true sur une page au plus en fait la vue d’atterrissage : #/ rend cette page, et la vue d’accueil technique (exports, scénarios épinglés, statistiques) déménage vers #/overview avec sa propre entrée de nav auto-générée. Déclaré deux fois, le premier gagne, avec un avertissement console.
Écrire des pages qui répondent
Les pages Markdown reçoivent trois enrichissements par-dessus le GFM, chacun dégradant proprement dans tout autre moteur de rendu (un quatrième, l’interpolation {{var}}, a sa propre section) :
Les callouts —
> [!NOTE],[!TIP],[!IMPORTANT],[!WARNING],[!CAUTION]en première ligne d’une citation deviennent de vraies alertes stylées : exactement la syntaxe que GitHub rend.Les onglets de code — des blocs adjacents sans ligne vide entre eux deviennent un seul bloc à onglets ; le libellé vient de la méta du fence (
```js Node.js) ou du nom du langage. Le choix du lecteur est mémorisé et appliqué à chaque groupe d’onglets de chaque page. Des blocs séparés par une ligne vide restent indépendants — la fonctionnalité s’active par adjacence, jamais par surprise.Les références d’opérations — un lien au schéma
apidoc:se rend en ligne avec le badge de méthode de l’opération, adressée paroperationIdou par"METHOD /path":Voir [créer un animal](apidoc:createPet) ou [lister](apidoc:GET /pets).Les cartes d’opérations viennent d’un bloc dont la chaîne d’info est
apidoc:operation, une référence par ligne — chacune rend méthode, chemin, résumé et badge de dépréciation le cas échéant, la carte entière étant un lien vers l’opération :```apidoc:operation GET /pets/{petId} createPet ```Une référence qui ne résout rien se rend visiblement cassée — barrée, avec une infobulle — jamais en lien mort. Les cartes sont des liens vers la référence, pas des consoles embarquées : envoyer des requêtes reste le rôle du panneau d’essai, à dessein. Les références visent la spec active uniquement ; pas de référence inter-spec.
Des pages qui se lisent avec les valeurs du lecteur
Un guide qui dit curl {{baseUrl}}/pets est un guide que le lecteur doit traduire avant de s’en servir. Les pages Markdown résolvent {{var}} depuis la même composition que la console d’essai — l’environnement sélectionné, avec les identifiants fournis par l’hôte en dessous — si bien que la page se rend avec son URL de base, son identifiant de tenant, et que le bouton de copie d’un bloc de code copie le texte interpolé. C’est toute la fonctionnalité : ce qu’on colle doit tourner.
Trois issues, et la page est explicite sur chacune :
- Une valeur résolue et non sensible se rend en texte brut. Ni pastille, ni surlignage, ni décoration — un extrait de code doit se lire comme un extrait de code.
- Une valeur sensible ne se rend jamais. Vous obtenez une pastille masquée (le nom et
••••), et la valeur n’entre ni dans le DOM, ni dans le presse-papiers, ni dans l’index de recherche, ni dans aucun export. Non pas caviardée après coup : jamais émise. Tout identifiant fourni par l’hôte est sensible par définition, donc couvert aussi. - Une valeur manquante se rend en pastille d’avertissement portant le nom — le même principe que le blocage d’envoi de la console d’essai, qui n’a ici aucun envoi à bloquer. Cette pastille est un bouton, et elle ouvre le gestionnaire d’environnements, parce que définir la variable est la seule suite raisonnable. Sous
environmentsLocked, la pastille reste et l’offre disparaît.
Copier une pastille donne {{name}}, le gabarit qu’elle représente — coller un nom de variable nu comme s’il avait été résolu serait la seule issue vraiment dangereuse. Une valeur vide compte comme manquante, exactement comme dans la console d’essai.
Pour écrire à propos d’une variable plutôt que d’en résoudre une, échappez-la par une barre oblique inverse. Dans un bloc ou un span de code, les barres obliques inverses sont littérales : tapez \{{name}} ; en prose, le markdown en mange une : tapez \\{{name}}. Les exemples de cette page sont le cas d’usage.
Trois surfaces restent non interpolées, chacune pour sa raison : les pages .html et .txt (l’interpolation est un enrichissement du Markdown, comme les callouts et les onglets de code, et un auteur HTML est maître de son balisage), l’index de recherche (construit une fois depuis la source — indexer les valeurs rendrait les résultats dépendants de l’environnement choisi, et indexerait des secrets) et llms.txt / llms-full.txt (des exports machine de la documentation publiée, pas de la session d’un lecteur).
Deux détails pour les auteurs : les titres s’interpolent aussi, mais leurs ancres sont calculées depuis la source avant que les valeurs n’arrivent, donc un lien profond ne dépend jamais de qui lit ; et les valeurs sont insérées comme du texte, jamais comme du balisage, si bien qu’une URL de base pleine de tirets bas ne peut pas transformer la moitié de votre paragraphe en emphase.
Les pages de changelog
Une page qui déclare kind: "changelog" se rend en frise de versions — un point dans la gouttière de gauche par h2, une ligne continue qui les relie :
{ "slug": "changelog", "title": "Journal des versions",
"url": "/docs-pages/changelog.md", "kind": "changelog" }La convention est d’un h2 par version, la date dans le texte du titre — ## 1.2.0 — 2026-05-01. Une convention et du CSS, délibérément : rien ne parse cette date, donc rien n’a besoin de s’entendre sur une locale, et le même fichier reste un changelog markdown ordinaire dans GitHub ou n’importe quelle autre visionneuse. Le traitement s’accroche au contenu rendu : il vaut pour le .md comme pour le .html ; un .txt n’a pas de h2 à décorer et ne reçoit rien. Un kind inconnu est écarté avec un avertissement console, comme un format inconnu.
« Cette page vous a-t-elle été utile ? »
Posez feedback.url et chaque page de prose gagne une ligne de verdict, entre le contenu et la pagination :
{ "feedback": { "url": "https://example.com/docs-feedback" } }Un clic envoie ceci en POST, en JSON, à votre endpoint — il n’y a pas d’autre destinataire, et aucun endpoint à nous nulle part dans le produit :
{ "page": "pagination", "verdict": "up" }verdict vaut "up" ou "down" ; page est le slug de l’entrée, pas son URL — la route est l’identité d’une page, là où les fichiers et les contenus transportés bougent en dessous. Sans feedback.url déclaré, il n’y a pas de ligne du tout : le défaut est null, et l’app n’envoie jamais rien nulle part d’elle-même. C’est le seul endroit d’ApiGlow où le geste d’un lecteur atteint un serveur, et il n’existe que parce que vous l’avez demandé, à l’adresse que vous avez donnée.
La ligne répond. En cas de succès, les boutons cèdent la place à un remerciement — un verdict par rendu, pas de compteur de votes, rien de stocké. En cas d’échec, elle le dit et reste cliquable : un réseau capricieux ne coûte rien au lecteur. Les deux issues passent par la zone d’annonce vocale, pour qu’un utilisateur de lecteur d’écran entende le résultat au lieu de le deviner.
Deux choses à prévoir. Le POST porte un en-tête content-type: application/json, ce qui en fait une requête cross-origin préliminaire (OPTIONS) dès que l’endpoint n’est pas sur la même origine : votre serveur doit y répondre aussi, la même négociation que réclame la console d’essai. Et en multi-spec, feedback se surcharge par spec — une API peut récolter des verdicts pendant que sa voisine n’en récolte aucun, ou chacune poster vers son propre collecteur.
Confort, recherche, exports
Chaque page reçoit un sommaire construit sur ses h2/h3 — la section en cours de lecture s’y surligne au défilement —, une pagination précédent/suivant dans l’ordre aplati et zoné de la nav, et sa place dans la palette Cmd+K : le contenu des pages est indexé, paresseusement, à la première ouverture de la palette — un résultat pointe directement le bon titre de section. La même prose alimente llms.txt (pages de la zone du haut sous le titre de leur groupe en ## Guides, celles du bas en ## Resources) et llms-full.txt (contenus inclus) — voir llms.txt et MCP.
En multi-spec, les pages fusionnent par identité — slug pour les pages, id pour les groupes, href pour les liens : une entrée de spec remplace sur place l’entrée racine correspondante, les autres s’ajoutent après. Des pages racine partagées par toutes les specs sont ici une fonctionnalité, contrairement aux scénarios ; home se résout par spec — une API peut prendre le contrôle de son accueil pendant que sa voisine garde la vue classique.
La seule chose que le navigateur du lecteur stocke pour tout cela est le langage d’onglet de code sélectionné. Rien d’autre ne persiste.