Vue d'ensemble
CapyCode reproduit l'ergonomie de VS Code (explorateur, onglets, éditeur, terminal, Git) pour des élèves débutants. Tout tourne dans le navigateur : il n'y a pas de serveur applicatif, ni pour stocker les projets, ni pour exécuter le code.
Les couches
- Services (
src/services/) : la logique technique, sans dépendance à Vue ni aux stores. Ils sont testables directement sous Node. - Stores (
src/stores/) : l'état affiché par l'interface. Ils appellent les services et s'abonnent à leurs notifications. - Composants (
src/components/) : l'affichage. Ils lisent les stores et déclenchent leurs actions eux-mêmes, plutôt que de recevoir des copies de l'état en props et de remonter des événements que le parent rejouerait. Un grand composant se découpe en sous-composants rangés dans un dossier à son nom (editor/git-graph/pourGitGraphView.vue). - Composables (
src/composables/) : la logique d'interface partagée entre composants ou sortie d'un composant trop chargé.useProjectgère le cycle de vie du projet (démarrage, activité partagée, ZIP),useEscapeKeyla fermeture de la fenêtre au premier plan,usePointerDragetuseSeparatorles poignées de redimensionnement (à la souris et au clavier),useTreeNavigationetuseTabEscapela navigation au clavier (voir Accessibilité),useGitActionsles actions Git de l'interface (avec confirmations et messages) communes à la barre latérale, au graphe et aux comparaisons,useCopyFeedbackla copie avec retour visuel.App.vuene fait plus qu'assembler l'interface. - Commandes (
src/commands/) : les actions de l'IDE, décrites une fois ; menus, palette et raccourcis en dérivent. Voir Commandes. - Langages (
src/languages/) : tout ce que l'IDE sait d'un langage, entre les services et les stores. Voir Langages.
ESLint fait respecter l'ordre des couches : chacune n'importe que les précédentes.
Dialogues et notifications
Les dialogues natifs du navigateur (window.confirm, alert, prompt) sont interdits (ESLint) : ils ne sont ni accessibles, ni traduisibles, ni testables, et sont bloqués dans certaines iframes. À la place, le store dialogs :
await dialogs.confirm({ title, message, confirmLabel, danger }): vrai si l'utilisateur confirme. Une action destructrice (danger) a son bouton en rouge et le focus initial sur Annuler.await dialogs.prompt({ title, label, initialValue, validate }): le texte saisi, ou null.dialogs.notify({ kind, message })etdialogs.notifyError(message, err): messages non bloquants. Les informations disparaissent après quelques secondes (sauf si on les survole ou les focalise), les erreurs restent jusqu'à leur fermeture.
DialogHost.vue (monté une fois dans App.vue) les affiche : une fenêtre modale à la fois (BaseDialog.vue : aria-modal, focus piégé puis rendu, Échap), et deux zones de notification toujours présentes (role="status", role="alert") pour que les lecteurs d'écran annoncent chaque message. Les nouvelles fenêtres modales s'appuient sur BaseDialog.
Les erreurs
Une erreur que l'on veut reconnaître est une classe de src/utils/errors.ts, avec un code stable et un message déjà traduit, destiné à l'utilisateur :
| Classe | Code | Exemple |
|---|---|---|
MergeConflictError | merge-conflict | Commit de fusion refusé : des conflits restent à résoudre ; files donne les fichiers concernés |
InvalidInputError | invalid-input | Message de commit vide, déplacement d'un dossier dans lui-même |
NotFoundError | not-found | Fichier introuvable ; path donne le chemin |
On les reconnaît par instanceof (ou error.code), jamais en comparant un message. L'erreur d'origine (isomorphic-git, LightningFS) est gardée dans cause. Une erreur attrapée est de type unknown : errorMessage(err) en donne le texte, errorCode(err) le code d'une erreur de bibliothèque (git.Errors.MergeConflictError.code).
Les briques techniques et pourquoi elles
| Besoin | Choix | Pourquoi |
|---|---|---|
| Interface | Vue 3, Pinia, Tailwind | Composants légers, réactivité fine, thème VS Code par classes utilitaires |
| Éditeur | CodeMirror 6 | Modulaire et léger (contrairement à Monaco), bon support mobile |
| Fichiers | LightningFS (@isomorphic-git/lightning-fs) | Système de fichiers persistant dans IndexedDB, celui qu'attend isomorphic-git |
| Git | isomorphic-git | Git complet en JavaScript, sur le même système de fichiers |
| Analyse Python | Ruff (WASM) | Lint et formatage en un seul module, assez rapide pour tourner à chaque frappe ; chargé au premier fichier Python |
| Exécution | Noyaux Basthon (Python, SQL, OCaml) | Noyaux pédagogiques maintenus, isolés dans des workers ; voir Intégration de Basthon |
| Projets | JSZip, fflate | Export/import ZIP avec l'historique .git, partage d'une activité dans l'URL |
Le workspace
Le projet de l'élève vit sous /workspace (WORKSPACE_ROOT, src/utils/path.ts) dans LightningFS, avec son dépôt Git dans /workspace/.git. Trois règles :
- Binaire avant tout. Les objets Git sont compressés : ils se lisent et s'écrivent en
Uint8Array, jamais en texte. - Une seule porte d'entrée. Toute modification passe par le service VFS (
src/services/vfs.ts) ou par le service Git, qui notifient les stores. C'est ce qui garde l'interface cohérente : voir Stores et synchronisation. - Jamais
/workspaceen dur. Les chemins se convertissent avecresolveWorkspacePath/getRelativeWorkspacePath(src/utils/path.ts) ; le chemin vu par Git est le chemin relatif.
Workspace (src/services/workspace.ts) réunit le système de fichiers et le dépôt d'un projet, avec l'identité des commits (setAuthor) et les lots (batch). Le projet de l'application est workspace ; les services de projet reçoivent le Workspace sur lequel agir, ce qui les rend utilisables sur un autre projet (tests, projet stocké ailleurs).
Le projet comme un tout
src/services/project/ traite le projet entier : projet de départ (defaults.ts), instantané versionné et lien de partage #capycode=… (snapshot.ts, shareLink.ts), archive ZIP fidèle, .git compris (archive.ts). Le format d'instantané porte un numéro de version : toute évolution passe par une nouvelle version. Voir Le projet et ses formats.
Le service Git
src/services/git/ compose trois classes : GitRepository (lecture), GitInspection (contenus, diffs) et GitService (opérations et notifications), réunies dans le singleton gitService. Le terminal comme l'interface passent par lui. Voir Git.
L'éditeur et l'analyse du code
CodeMirror 6, deux volets d'onglets, comparaisons, recherche, palette ; analyse et formatage Python par Ruff en WebAssembly, chargé à la demande. Voir Éditeur et Analyse Python.
L'exécution du code
Le code de l'élève s'exécute dans les noyaux Basthon, chacun dans un worker isolé (origine opaque) : il ne peut lire ni le workspace, ni l'état de l'IDE. Le noyau est choisi selon l'extension du fichier (.py, .sql, .ml). Voir Intégration de Basthon.
L'intégration dans une page hôte
Capytale intègre CapyCode dans un cadre et peut régler certaines fonctions (désactiver Git) par postMessage, à tout moment : src/services/embedding.ts porte le protocole, le store embedding les options. Voir Intégration dans une page hôte.
Le déploiement
npm run build produit un site statique (dist/), déployé par la CI GitLab avec sa configuration Apache (public/.htaccess). L'application est aussi une PWA : le service worker (src/sw.ts) précache l'interface, mais charge toujours la page depuis le réseau quand il le peut, pour qu'une mise à jour soit visible dès le rechargement suivant ; les noyaux Basthon et Ruff sont mis en cache à leur première utilisation. Voir Déploiement.