Décisions et leurs raisons
Les choix structurants du projet, avec ce qui les a motivés et ce qu'on a écarté. Avant de remettre l'un d'eux en cause, lisez pourquoi il a été fait ; s'il change, modifiez sa fiche plutôt que de l'effacer.
Exécution
Les noyaux Basthon officiels, tels quels
- Décision : Python, SQL et OCaml s'exécutent avec les paquets npm
@basthon/kernel-*, sans surcouche ni copie de leur code ; tous à la même version. - Pourquoi : le prototype réimplémentait des morceaux de Basthon (démarrage, polyfills, réinitialisation du contexte) et cassait ce qui marche ailleurs (
input(), imports, graphiques). Basthon est maintenu pour l'enseignement ; ce qui manque se fait chez lui (nom de fichier des tracebacks, fin d'entrée), puis CapyCode en profite. - Écarté : un noyau Pyodide « autonome » maison (doublon, deux versions de Python, hors ligne illusoire).
Toujours le worker isolé
- Décision : chaque noyau tourne dans le worker « sandboxé » de Basthon (origine opaque) ; jamais de noyau « legacy » dans la page.
- Pourquoi : dans l'origine de l'application, le code de l'élève pouvait lire l'IndexedDB du projet et du dépôt Git, le
localStorage, et faire des requêtes avec les cookies.
Pas de COOP/COEP
- Décision : ni
Cross-Origin-Opener-PolicyniCross-Origin-Embedder-Policy, comme les autres interfaces Basthon en production. - Pourquoi : COEP bloquait le proxy des workers isolés et compliquerait l'intégration dans Capytale. Sans
SharedArrayBuffer,input()passe par l'API réseau de Basthon, qui fonctionne.
Un noyau neuf par exécution
- Décision : le bouton ▶ et chaque
python …du terminal démarrent un noyau neuf ; le prompt de chaque langage garde le sien ; une nouvelle instance de noyau à chaque démarrage. - Pourquoi : la garantie « nouveau processus » est pédagogique (pas de variable fantôme d'une exécution à l'autre). L'instantané Pyodide rend ce choix rapide (~1 s). On ne réutilise pas l'instance (
kernel.restart()) parce que Basthon rejouerait alors les fichiers déjà copiés dans le noyau, ce qui ferait réapparaître des fichiers supprimés du projet. - Écarté : remettre à zéro un noyau existant (isolation imparfaite : modules,
builtins, état de turtle) ; un noyau de réserve démarré à l'avance (mémoire, gain devenu faible).
python seul ouvre la console Python
- Décision : l'interpréteur interactif est la console Python du panneau inférieur ;
pythontapé seul dans le terminal y bascule. - Pourquoi : un seul interpréteur interactif, avec historique, complétion et sorties graphiques, plutôt que deux implémentations de REPL.
Architecture
Les services notifient, les stores s'abonnent
- Décision : toute écriture passe par le VFS ou le service Git, qui notifient ; les stores s'abonnent ; personne ne rafraîchit à la main ; une écriture se termine quand l'interface est à jour.
- Pourquoi : le prototype multipliait les
refreshTree()etgitStore.refresh()dispersés, avec des oublis (statut Git après une sauvegarde) et des doublons (arborescence relue à chaque Ctrl+S). C'est le choix qui évite le plus de bugs.
Des couches vérifiées par l'outil
- Décision :
utils,i18n→services→languages→stores→composables→commands→components, imposé par ESLint. - Pourquoi : une règle seulement écrite finit contournée (le linter et le terminal importaient des stores).
Décrire une fois
- Décision : registre des langages, registre des commandes de l'IDE, registre des commandes du terminal.
- Pourquoi : ajouter un langage touchait sept fichiers ; une même action était déclarée à quatre endroits (keymap CodeMirror, écouteur global, menu, palette), avec des raccourcis gérés deux fois.
Un objet Workspace et des formats versionnés
- Décision : les services de projet reçoivent le
Workspacesur lequel agir ; l'instantané de projet porte un numéro de version (SNAPSHOT_VERSION = 2). Il utilise la compression fflate (DEFLATE + Base64URL) remplaçantlz-stringet permet de transmettre la liste des extensions activées. - Pourquoi : préparer Capytale (projet par activité, sauvegarde sur un serveur) sans singletons partout ; un lien de partage qui circule doit rester lisible ou être refusé proprement. La compression DEFLATE de
fflateréduit la taille des liens de plus de 60 % par rapport àlz-string, évitant les coupures dans les messageries et ENT. - Conséquence assumée : les liens de l'ancien format ne s'ouvrent plus.
Une fusion en conflit se résout comme avec Git
- Décision : une fusion en conflit écrit les marqueurs dans les fichiers et reste en cours (
.git/MERGE_HEAD,MERGE_MSG), jusqu'au commit de fusion ou à l'abandon ; l'interface l'accompagne (bandeau du panneau Git, boutons au-dessus des blocs, commandes au clavier) et le terminal parle comme Git (git status,git merge --continue,--abort). - Pourquoi : un conflit est une étape normale d'un travail à plusieurs, à apprendre plutôt qu'à éviter ; un élève doit pouvoir le résoudre dans CapyCode, puis retrouver le même vocabulaire avec le vrai Git.
Fonctionnalités modulaires sous forme d'extensions intégrées (Git, Linter, Formateur, Structure, SQL, OCaml)
- Décision : Les fonctionnalités avancées et les langages secondaires sont gérés sous forme d'extensions intégrées déclaratives (
src/services/extensions/,src/stores/extensions.ts). Un panneau Extensions (raccourci standard VS CodeCtrl+Maj+X, icôneBlocksdans la barre d'activité) permet de les installer et désinstaller en un clic. Chaque extension affiche son logo officiel de langage (Python pour Linter, Formateur et Structure, Git pour Git, SQL pour SQL, OCaml pour OCaml).- L'extension Détection d'erreurs (Linter) est activée par défaut pour guider immédiatement les élèves lors de la frappe en Python.
- Les extensions Git, Formateur de code, Structure du document, Support SQL et Support OCaml sont désactivées par défaut.
- Pourquoi : Un débutant complet peut être submergé par trop d'outils, d'onglets et de boutons à l'écran. L'approche modulaire à la VS Code permet à l'élève ou à l'enseignant d'activer les outils au fur et à mesure de sa progression pédagogique. De plus, désactiver l'exécution SQL et OCaml par défaut garantit qu'aucun noyau Basthon lourd (comme le noyau OCaml de 5,6 Mo) n'est téléchargé inutilement sur le réseau de la classe.
- Articulation avec l'hôte : Si la page hôte (Capytale) interdit une fonctionnalité (ex:
git: falseviapostMessage), l'extension correspondante est désactivée et son bouton d'installation est grisé avec la mention « Désactivé par la plateforme ».
Pas de fichiers binaires dans un lien de partage
- Décision : un fichier qui n'est pas de l'UTF-8 valide (image…) est écarté du lien, et signalé à qui partage ; l'archive ZIP reste le moyen de le transmettre.
- Pourquoi : le format ne porte que du texte, et un lien doit rester court ; lu comme du texte, une image arrivait corrompue, sans que personne le sache.
- Écarté : encoder les binaires en base64, qui demanderait une version 2 du format et rallongerait le lien au point de le rendre souvent inutilisable.
Interface
Anglais par défaut, dix langues
- Décision : interface en anglais, traduite (
?lang=, choix enregistré, langue du navigateur), aucun texte en dur ; dix langues européennes. - Pourquoi : le projet vise plus large que la France ; une internationalisation à moitié faite est une dette qui grossit à chaque texte ajouté. Les clés typées et le test des dictionnaires empêchent un oubli.
Jamais de dialogue natif
- Décision :
window.confirm,alertetpromptinterdits (ESLint), remplacés par le storedialogs. - Pourquoi : ni accessibles, ni traduisibles, ni testables, et bloqués dans certaines iframes, donc dans Capytale.
RGAA par des briques communes
- Décision : chaque motif WAI-ARIA est porté une fois (
BaseDialog,MenuButton,useTreeNavigation,useSeparator,useTabEscape) ; couleurs de texte par des jetons vérifiés à 4,5:1. - Pourquoi : une application publique de l'Éducation nationale doit être accessible ; réécrire la gestion du clavier dans chaque composant garantit qu'elle sera fausse quelque part.
Options de la page hôte par postMessage
- Décision : Capytale règle CapyCode (Git activé ou non) par messages, à tout moment ; jamais par l'URL.
- Pourquoi : un réglage doit pouvoir changer sans recharger la page, et un paramètre d'URL se recopie, se partage et se contourne.
Outillage
Ruff en WebAssembly, chargé à la demande
- Décision : Ruff pour l'analyse et le formatage Python, chargé au premier fichier Python, mis en cache à la première utilisation, jamais précaché.
- Pourquoi : le plus complet pour le moins cher (voir Analyse Python) ; précaché, il triplait le téléchargement à l'installation pour tout le monde.
Tests par domaine, faux noyau et Playwright
- Décision : tests rangés par domaine et documentés ; faux noyau pour la logique de CapyCode, Playwright avec les vrais noyaux pour le comportement réel.
- Pourquoi : le rangement par domaine est celui de qui écrit les scénarios (
testing.md) ; le faux noyau est rapide et provoque les cas limites, mais seul le vrai Basthon prouve que l'exécution marche.
Aucun avertissement ESLint
- Décision :
--max-warnings 0en CI, hookpre-commitqui refuse le commit. - Pourquoi : un avertissement toléré en appelle d'autres ; les
anycachaient des erreurs de type.
Commits atomiques sur main
- Décision : un sujet par commit, message en français au format Conventional Commits, documentation dans le même commit ; travail sur
main, poussé par le mainteneur. - Pourquoi : un historique lisible sert de journal de bord ; la documentation ne prend pas de retard si elle voyage avec le code.
Déploiement
La page d'abord depuis le réseau
- Décision : le service worker charge
index.htmldepuis le réseau (repli sur le cache hors ligne ou après 4 s), recharge une page restée ouverte quand c'est sans risque, et les noyaux sont publiés sous un dossier versionné. - Pourquoi : après un déploiement, les élèves voyaient l'ancienne version jusqu'à un Ctrl+Maj+R, et les noyaux en cache ne se mettaient jamais à jour.
Configuration Apache publiée avec le site
- Décision :
public/.htaccess(HTTPS, en-têtes de sécurité, cache) est copié dansdist/et déployé avec lui. - Pourquoi : le
rsync --deletedu déploiement effacerait toute configuration posée à la main. Pas deX-Frame-Options(Capytale affiche CapyCode dans un cadre) ; une CSP reste à mettre au point.
Documentation publiée sous doc/
- Décision : cette documentation est construite par la CI et publiée avec l'application, sous
<BASE_PATH>doc/, hors du précache et de la route de navigation du service worker. - Pourquoi : un contributeur la trouve à côté de l'application, toujours à jour de la version déployée, sans hébergement de plus.