Skip to content

docs: la documentation Typst finie — sections 6 à 8, 16 captures réelles, hébergement permanent (#217) - #219

Merged
DireDoch merged 13 commits into
developfrom
docs/217-documentation-typst
Aug 19, 2026
Merged

DireDoch merged 13 commits into
developfrom
docs/217-documentation-typst

Conversation

@DireDoch

Copy link
Copy Markdown
Owner

Suite de #218, dont seul le premier commit avait été poussé.

  • Sections 6 à 8 de Docs/documentation.typ, refonte visuelle (tableaux, schéma réseau, blocs de code), sortie en deux thèmes depuis une source unique.
  • 16 captures réelles sur 18, prises en jouant l'application, légendes corrigées par les faits.
  • Runbook d'hébergement permanent : tunnel cloudflared nommé sur typperacer.uk, unité systemd du backend.
  • Un seul .env et un seul .env.example, à la racine — celui du frontend n'a plus lieu d'être.

Closes #217

Anthony Boily and others added 13 commits August 17, 2026 15:49
Premier lot de `Docs/documentation.typ` : le thème, la page de garde, la table
des matières, et les sections 1 à 5 rédigées. Les sections 6 à 8 sont posées en
squelette — leurs titres fixent le plan et résolvent les renvois croisés des
sections précédentes, leur corps arrive au lot suivant.

Le thème importe sa palette de `design/composants.typ` et n'écrit aucun hex :
une retouche de `frontend/src/style.css` remonte jusqu'au PDF. Corps inversé
pour l'impression, `corail` réservé aux filets et aux étiquettes.

Contient 4 des 11 diagrammes (trois axes d'une Race, machine d'état, pipeline
d'assets, chaîne d'intégration — plus le handshake d'identité en séquence), les
18 placeholders de capture avec leur légende, et les 12 PNG de `design/out/`
embarqués.

Trois écarts avec l'énoncé de l'issue, tranchés en faveur du code : le protocole
porte 29 messages et non 25 (20 ClientEvent + 9 ServerEvent), une Race a cinq
états terminaux et non quatre (`race-state.ts`), et les décomptes de lignes de
l'issue étaient surestimés.

La compilation exige `--root .` : le document importe hors de `Docs/`.

`logo()` de `design/composants.typ` ciblait la seule famille « JetBrainsMono NF »
et retombait sur la sérif par défaut là où elle n'est pas installée — le wordmark
cessait alors d'être en chasse fixe. Passé en pile de repli, celle que
`--font-mono` nomme déjà dans le CSS.

`Docs/onboarding.*` n'est pas encore supprimé et le README pas encore modifié :
c'est le lot final.
…t 1 (#217)

Second et dernier lot. Les sections 6 à 8 sont rédigées, les six diagrammes qui
manquaient sont dessinés, `Docs/onboarding.*` est supprimé et le README renvoie
au nouveau document. 37 pages, 11 diagrammes, 18 placeholders de capture.

Le thème choisissait « Segoe UI » et « Consolas » — des polices Windows,
absentes de la machine de compilation. Typst ne le signale pas : il retombe en
silence sur sa sérif par défaut, et tout le document sortait dans le style
Typst d'origine, celui-là même que ce travail remplace. Passé à `Fira Sans` et
`JetBrainsMono NF`, choisies parmi celles que `typst fonts` liste réellement.
Le wordmark de `design/composants.typ` avait le même défaut de repli ; l'export
des PNG reste identique au bit près.

Le PDF committé n'était pas une fonction de sa source. La page de garde le
datait avec `datetime.today()`, et Typst horodate le fichier lui-même : deux
compilations d'une source inchangée donnaient deux binaires différents. Date
écrite, et compilation sous `SOURCE_DATE_EPOCH` — la commande de l'en-tête le
porte, et le résultat est vérifiable par `cmp`.

Les tableaux passent au supplément « Tableau » : sous « Figure », leur compteur
séparé produisait deux « Figure 3 » dans le même document.

Les diagrammes de séquence sont composés en corps réduit. chronos dimensionne
ses colonnes sur la largeur mesurée des commentaires, et la soumission d'un Run
débordait des deux marges — sans erreur de compilation.

Trois corrections de fond : une parenthèse orpheline, un « à la section la
section 6.3 », et « sept Réglages de salon » alors que `RoomSetting` en compte
six génériques plus quatre propres aux Modes de jeu. Le décompte des cinq
états terminaux est désormais réconcilié avec `CONTEXT.md`, qui n'en nomme que
quatre — le glossaire exclut « fini », le code ne l'exclut pas, et le document
dit maintenant laquelle des deux lectures il illustre.

Le README annonçait 28 et 13 tests ; il y en a 382 et 159.
…ux, schéma réseau, blocs de code (#217)

Le document était juste : il était uniforme. Une seule grille pour toute donnée,
des boîtes fletcher pour tout schéma, et dix-huit cadres en attente à la place
des captures. Cette passe lui donne un registre.

TROIS FAMILLES DE TABLEAUX, choisies sur ce que le lecteur a à FAIRE de la
donnée, pas sur le goût du moment. `chiffres()` — sans filet ni colonne, pour
douze nombres qui ne se comparent pas entre eux. `cartes()` — un catalogue
qu'on parcourt à la recherche d'un seul élément, d'où les 29 messages du
protocole. `zebre()` — un contrat qu'on lit ligne par ligne, d'où les endpoints,
les migrations et les ADR.

SCHÉMA RÉSEAU en cetz, la bibliothèque qui dessine déjà la voiture du jeu.
Fletcher pose des boîtes reliées par des flèches, et huit joueurs dans huit
boîtes ressemblent à huit modules. Ici les clients sont des silhouettes, l'hôte
porte une couronne — c'est une différence de DROITS, elle doit se voir — et les
trois portées de diffusion sont dessinées plutôt que décrites.

DEUX BLOCS DE CODE, fond `nuit` et filet corail, avec le vrai code du dépôt :
le dispatch du fil côté Rust, la réception et la transition pure côté
TypeScript. La coloration vient de `nuit.tmTheme`, écrit pour ce document — le
thème par défaut de Typst est fait pour du papier blanc et rendait du bleu
sombre sur du bleu sombre.

Le piège que ce bloc a révélé mérite d'être noté : Typst ne colore QUE les
jetons couverts par une règle du thème. Tout le reste — parenthèses, accolades,
identifiants — garde la couleur de texte ambiante, ici `encre`, c'est-à-dire le
fond. La moitié du code était peinte en invisible, et la compilation restait
verte. D'où le `fill: texte` explicite dans `code-reseau()`.

ARBORESCENCE en vrai arbre : les glyphes de branche sont calculés depuis la
profondeur, pas écrits à la main — `└─` plutôt que `├─` dépend de savoir si
l'entrée est la dernière de sa fratrie, et un arbre écrit à la main se trompe
là-dessus dès la première insertion au milieu.

CAPTURES : les dix-huit `[PLACEHOLDER-*.png]` deviennent de vraies intégrations
`image()`. Les fichiers pas encore capturés portent un gabarit aux bonnes
dimensions, produit par `Docs/captures/build.sh` et marqué comme tel en toutes
lettres. La mise en page est donc déjà celle de la version finale, et prendre
une capture consiste à écraser un PNG — aucune ligne de Typst ne bouge ce
jour-là.

ENCARTS SOMBRES pour ce qui appartient à l'écran et non au papier : la pile du
projet en ouverture, l'invariant de la frontière de confiance, et un bandeau de
titre `nuit` sur chaque section. Le bandeau plutôt que des pages alternées
sombres : le document est imprimable, et une page sur deux en aplat coûterait
une cartouche pour rendre les captures illisibles en photocopie.

Deux corrections attrapées au passage. Les blocs de code recevaient le panneau
clair du document par-dessus le leur : la règle passe de `it => block(…)` à
`show … : set block(…)`, seule forme qu'une règle interne puisse écraser. Et les
grilles de cartes étaient posées `height: 100%` dans une grille à rangées
automatiques — ce 100 % se résolvait sur la hauteur restante de la PAGE, et
chaque carte occupait une colonne entière.

43 pages, 12 diagrammes, 18 captures intégrées. PDF toujours reproductible au
bit près sous SOURCE_DATE_EPOCH.
Une variable décide, tout le reste en dérive — même patron qu'une poignée de
variables CSS : rien dans le corps du document ne connaît sa propre couleur.

    ./Docs/build.sh
      → Docs/documentation.pdf        thème sombre, pour l'écran
      → Docs/documentation-clair.pdf  thème clair,  pour l'impression

Une seule source, et c'est le point. Deux `.typ` tenus en parallèle divergent au
premier paragraphe corrigé d'un seul côté, et une documentation fausse à moitié
est une documentation fausse.

Six noms portent toute la bascule : `papier`, `encre`, `gris`, `filet`,
`papier-doux`, `accent-encre`. Deux points ne sont pas de simples inversions.
`corail` pur ne passe pas 3:1 sur blanc et doit être assombri là ; sur `nuit` il
passe largement, et c'est la version pure qu'on veut — inverser mécaniquement
aurait donné un accent délavé. Et les blocs « écran » (encarts, code, bandeaux
de titre) ont pour fond `nuit` en thème clair : en thème sombre `nuit` EST le
fond de page, ils seraient devenus invisibles, d'où `surface` qui remonte d'un
cran sur `panel`.

Les deux PDF restent reproductibles au bit près : `build.sh` exporte
SOURCE_DATE_EPOCH, sans quoi les fichiers committés changent à chaque
compilation même quand pas une ligne n'a bougé.

Le README renvoie aux deux et dit lequel sert à quoi.
Les gabarits sont remplacés par de vraies captures d'écran, prises en lançant
l'app en local (backend en mode dev + Vite) et en jouant : trois onglets, trois
joueurs — alice, bob, carol.

Ce qui est capturé pour de bon : le Menu · la barre de configuration solo
dépliée · un Practice en cours, avec une faute réelle en rouge · l'écran de
résultats et son graphe SVG (26 wpm, 98,5 %, recomputé par le serveur) · les
cent leçons · les trois portes d'entrée · le salon en trois colonnes à trois
présents · une course à trois voitures à des positions différentes · Floor is
lava · le podium avec ses Gap · Play of the Game · les Paramètres · le bandeau
d'erreurs.

Deux captures ont demandé plus qu'un clic.

Le Play of the Game n'existe que si deux joueurs finissent à moins de deux
secondes l'un de l'autre. Un pilotage séquentiel ne peut pas : chaque aller-retour
coûte une trentaine de secondes, et les arrivées se retrouvaient à +34 s. Il a
fallu programmer les trois arrivées sur un même horodatage absolu, chaque page
tenant sa dernière frappe en attente. Résultat : +0,7 s, photo-finish, duel
rejoué sur horloge partagée.

Le bandeau d'erreurs ne s'allume que sur une erreur NON RATTRAPÉE. Le 502 du
proxy de citations n'en est pas une — l'application le traite proprement et
affiche « Impossible de charger la citation », ce qui est le bon comportement.
La capture vient donc d'une vraie panne : backend arrêté, `CreateRoom`, et la
promesse d'ouverture du WebSocket qui part en rejet — « ⚠ WS: ouverture
échouée ».

2560 × 1440, densité 2 : les captures restent nettes à l'impression.

Cinq gardent leur gabarit. `spam` demande une remise à plat du salon. `ci-jobs`
et `release` vivent sur GitHub. `rich-presence` et `activity-salon-vocal`
exigent un vrai client Discord avec l'Activity publiée — aucune automatisation
locale ne peut les produire, et une image fabriquée serait un mensonge sur le
seul point du document qui prouve que le câblage fonctionne.
… par les faits (#217)

`floor-is-lava` et `spam` remplacent leurs gabarits, et `ci-jobs` / `release`
viennent de GitHub. Restent deux gabarits, ceux qui exigent un vrai client
Discord.

Floor is lava a demandé cinq joueurs et non trois. Avec trois, la course
s'arrête au deuxième battement — vingt secondes de fenêtre, moins que le temps
d'aller-retour du pilotage. À cinq (alice, bob, carol, dave, erin), les
battements s'étalent sur quatre-vingts secondes. La capture montre ce que le
mode fait vraiment : dave brûlé à 40 s, erin à 20 s, tous deux grisés, et trois
joueurs encore en course à des positions différentes.

Le texte cible n'est pas lisible pendant une course sous ce mode : le badge
« 🔥 20 » s'insère en tête de la zone de frappe et masque le premier mot. D'où
un `RoomState` intercepté à la source, sur le WebSocket, avant que le rendu ne
s'en mêle.

Deux légendes ne disaient pas la vérité, et c'est la capture qui l'a montré.

« Les trois travaux verts sur une poussée » : aucune exécution du dépôt n'en
montre trois. Le travail `release` (#117) est bien déclaré et dépend bien des
deux autres, mais il n'a jamais tourné — les trois versions publiées sont
antérieures à son ajout. La poussée sur la branche principale affiche
`frontend`, `backend`, et un champ « Artifacts » vide.

« Une version publiée avec son archive » : `v0.0.3` n'a aucune archive, pour la
même raison. Les notes, elles, viennent bien du fichier de changements — cette
moitié-là était juste.

Les deux légendes disent maintenant ce que l'image montre, et « Limites connues »
gagne l'entrée qui manquait : la publication automatisée est en place, sa garde
fonctionne, et il lui manque la première version qui la franchisse.
… captures qui restent (#217)

`bandeau-erreur.png` reprenait l'écran entier de l'entrée multijoueur, déjà
montré deux figures plus haut : quatre-vingt-quinze pour cent de l'image
répétait la précédente, et la bande rouge dont parle la légende occupait deux
lignes tout en bas. Recadré sur la bande. C'est d'ailleurs la proportion que le
gabarit d'origine réservait — 1280 × 300 — avant que la vraie capture ne soit
prise en plein écran.

`Docs/captures/A-PRENDRE.md` : la marche à suivre pour `rich-presence` et
`activity-salon-vocal`, les deux seules qui exigent un vrai client Discord.
Mise en place, ce qui doit être lisible dans chacune, le cadrage attendu, et le
piège des App Testers qui n'ont pas accepté leur invitation.

Aucune image supprimée : les dix-huit captures et les douze visuels de
`design/out/` sont tous référencés par le document, vérifié par recoupement des
appels `capture()` et `visuel()` avec le contenu des dossiers.
`Docs/DEPLOIEMENT.md`. Le `DEPLOY.md` généré dans l'archive de release décrit le
binaire ; celui-ci décrit l'hébergement — le passage de « ça tourne pendant que
je teste » à « les joueurs peuvent jouer n'importe quand ».

Trois bloqueurs, aucun dans le code : le quick tunnel change d'adresse à chaque
redémarrage, rien ne relance le serveur tout seul, et une Activity non publiée
reste invisible à qui n'est pas App Tester.

Et une correction : ce ne sont pas trois services mais DEUX. En production le
binaire Rust sert lui-même le build de Vite — le `npm run dev` du démarrage
rapide n'existe qu'en développement.

Le reste : tunnel nommé Cloudflare pour une URL stable, unités systemd pour le
backend et le tunnel, secrets dans un EnvironmentFile en 0600, base SQLite en
chemin absolu et sauvegardée par `.backup` et non par `cp`.

Deux avertissements qui viennent du code lui-même. `VITE_DISCORD_CLIENT_ID` est
figée à la compilation par Vite : sans elle le bundle part en mode dev, tous les
joueurs deviennent `dev-player-1`, et ça ne se voit qu'une fois déployé. Et les
Rooms vivent en mémoire : un `systemctl restart` éjecte toute course en cours,
d'où `Restart=on-failure` et pas de redémarrage périodique.
`backend/.env` est fusionné dans le `.env` de la racine, et `.env.example` le
suit. Un seul fichier d'environnement pour tout le dépôt.

Ça marche sans une ligne de code : `dotenvy::dotenv()` cherche `.env` dans le
dossier courant PUIS remonte les parents (`find.rs`, `directory.parent()`).
`cd backend && cargo run` le trouve donc à la racine. Vérifié en démarrant le
serveur avec le seul `.env` racine — aucun « ⚠️ MODE DEV » au démarrage, donc
les secrets Discord sont bien lus.

Une exception, notée dans les deux fichiers : `VITE_DISCORD_CLIENT_ID` reste
dans `frontend/.env`. Vite ne remonte pas les dossiers parents, et cette
variable est de toute façon figée dans le bundle à la compilation.

Le `.env` racine portait un `API_NINJA_KEY` vide, que rien ne lit — le backend
attend `APININJAS_API_KEY`. Il disparaît dans la fusion.

Le commentaire de `main.rs`, `CONTEXT.md` et le README disaient tous
« backend/.env » ; ils disent maintenant où le fichier vit réellement.
… lieu d'être (#217)

`backend/.env.example` était déjà parti au commit précédent, fusionné dans celui
de la racine. Restait `frontend/.env.example`, et il était encore nécessaire :
Vite ne remonte pas les dossiers parents, il fallait donc un second fichier avec
`VITE_DISCORD_CLIENT_ID` recopiée — deux copies d'une même valeur, qui finissent
toujours par diverger.

`envDir: ".."` dans `vite.config.ts` lève la contrainte. Vite lit désormais le
`.env` de la racine, la variable y est documentée, et les deux fichiers du
frontend disparaissent.

La question qui se pose évidemment : est-ce que pointer Vite sur un fichier qui
contient `DISCORD_CLIENT_SECRET` fait fuiter le secret dans le bundle ? Non —
`envPrefix` vaut `VITE_` par défaut, et rien d'autre n'est injecté. Vérifié
plutôt que supposé, en construisant le bundle et en l'inspectant :

  VITE_DISCORD_CLIENT_ID  présent  ← attendu, il est public
  DISCORD_CLIENT_SECRET   absent
  APININJAS_API_KEY       absent
  DATABASE_URL            absent

382 tests toujours au vert.

Deux références mortes corrigées au passage : `Docs/agents/git-workflow.md` et
le commentaire du travail `release` de la CI pointaient encore vers
`frontend/.env.example`.
Le premier des trois bloqueurs du déploiement permanent est levé. Domaine acheté
chez Cloudflare Registrar, tunnel nommé créé, CNAME posé sur l'apex, et
`~/.cloudflared/config.yml` écrit.

`Docs/DEPLOIEMENT.md` porte maintenant les vraies valeurs plutôt qu'un exemple :
l'identifiant du tunnel, le hostname, et la séquence exacte qui a été jouée.

L'étape que le message d'erreur de `cloudflared tunnel create` ne nomme pas est
consignée : c'est `tunnel login` qui produit le `cert.pem` sans lequel `create`
refuse de partir — et `login` exige un domaine déjà Active dans le compte.

Vérifié de bout en bout, tunnel et backend lancés à la main :

  GET https://typperacer.uk/api/health  → 200
  GET https://typperacer.uk/            → 200, le jeu est servi
  bundle servi                          → porte VITE_DISCORD_CLIENT_ID

Ce dernier point vaut d'être noté : il prouve que la consolidation du `.env` et
le `envDir` de Vite tiennent jusqu'au bundle réellement servi en public.

La section `cloudflared.service` dit maintenant que `service install` lit
`/etc/cloudflared/` et non le dossier personnel — les deux fichiers sont à
recopier et le chemin `credentials-file` à corriger.
Le document décrivait un service sous /srv/typeracer avec un compte dédié —
une cible, pas ce qui tourne. Il décrit maintenant l'unité réelle : le binaire
de release lancé depuis le dépôt, sous le compte anthonyb.

Pas d'EnvironmentFile : le binaire lit le .env de la racine lui-même, puisque
WorkingDirectory est dans backend/ et que dotenvy remonte les parents. Une
source de vérité au lieu de deux, et aucun risque que systemd et dotenvy
interprètent la même ligne différemment.

La contrepartie est écrite noir sur blanc : le service sert ce qu'il y a dans le
dépôt, donc un git checkout change ce qui tourne. Le chemin vers /srv et un
compte de service est décrit pour le jour où ça deviendra gênant.
…tion-typst

# Conflicts:
#	Docs/documentation.pdf
#	Docs/documentation.typ
#	design/composants.typ
@DireDoch
DireDoch merged commit 76b3f87 into develop Aug 19, 2026
@DireDoch
DireDoch deleted the docs/217-documentation-typst branch August 19, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant