MedGryd
의대생이 만든 하나의 계정으로 플래너·문제은행·환자 시뮬레이터·보드 플랜·OMM 프로토콜·성적 관리까지 제공
Classinus는 AI 기반 학습 애플리케이션이다
Classinus est une application d'étude avec IA : pose des questions, recherche des documents et des vidéos, révise avec des fiches, des QCM et des animations, exécute du Python dans le chat ou utilise
교육TypeScript · JavaScriptGitHub @petitboura온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Classinus est une application d'étude avec IA : pose des questions, recherche des documents et des vidéos, révise avec des fiches, des QCM et des animations, exécute du Python dans le chat ou utilise le plugin Thonny-Classinus pour obtenir de l'aide dans l'éditeur.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Frontend Next.js (App Router, TypeScript) de Clovis, IA autonome pour
établissements scolaires. Anciennement classgpt-frontend, renommé
pendant l'été 2026. Dépôt séparé de djiguigne-frontend (dont il a
hérité une partie de la structure de code initiale) et de son backend
clovis-backend — isolation totale, le lien avec l'écosystème Djiguignè
ne doit jamais transparaître pour l'utilisateur final.
Ce même dépôt produit aussi l'app mobile Android/iOS via Capacitor
(dossiers android/ et ios/) : pas de code natif séparé pour l'UI, la
même app Next.js tourne dans une WebView, avec une barre d'onglets et un
menu natifs par-dessus.
Ce README décrit l'état réel du code. En cas de doute, le code fait foi — pas d'anciennes conversations ou de documentation externe.
app/
(app)/ écrans connectés (voir "Écrans" plus bas)
connexion/, inscription/ authentification
mot-de-passe-oublie/, reinitialiser-mot-de-passe/ réinitialisation du mot de passe par email
oauth/, oauth/consent/ retour OAuth (Notion, connecteurs génériques)
mon-espace/ section publique "Mon espace" (bibliothèque publique, portfolio)
telecharger/ page de téléchargement : dernier APK Android (GitHub Releases), installeur
Windows (releases de classinus-updates-pc), PWA iOS
cgu/, copyright/ contenu légal
decouvrir/ pages publiques de présentation (SEO/AEO), sans compte : accueil, bibliothèque,
personnaliser, concentration, et usages/ (11/10/2026) : une page par question
d'étudiant (trouver des PDF, apprendre à coder, trouver des vidéos, étudier),
avec questions fréquentes. Les textes des usages vivent dans lib/usagesDecouvrir.ts,
source unique partagée avec llms.txt et les données structurées ; adresses dans
lib/routesDecouvrir.ts ; une fonction qui change dans l'appli se corrige là
Chaque page se termine par un bouton vers le chat et, quand elle parle d'une section,
par un bouton vers cette section de l'appli (components/CtaDecouvrir.tsx, posé une
fois dans le layout ; la table page vers section est dans lib/ctaDecouvrir.ts) et les
deux mêmes boutons en icônes seules en haut à droite (components/CtaDecouvrirIcones.tsx).
La page Thonny a pour second bouton Télécharger Thonny (site externe). Dans l'appli, un
bouton boussole (components/BoutonDecouvrir.tsx) mène à /decouvrir, à gauche de l'horloge
des minuteurs, masqué dans le chat
layout.tsx layout racine, résout NEXT_PUBLIC_APP_URL pour les balises OG, porte la
description de l'appli (RESUME_CLASSINUS) et la fiche WebApplication en
données structurées (lib/donneesApplication.ts, components/DonneesStructurees.tsx)
llms.txt/route.ts fiche /llms.txt pour les IA, fabriquée à partir des mêmes listes que les pages
/decouvrir (rien d'écrit à la main)
robots.ts, sitemap.ts autorisations des robots (robots des IA nommés un par un, avec la liste des pages
privées répétée pour chacun, voir le commentaire du fichier) et plan du site
components/
InteractionGlobale.tsx couche montée une seule fois au niveau du layout racine : fournisseurs du chat,
du retour, du curseur, du canal en direct et de la voix, plus les éléments
flottants du canal (curseur, bulle, journal, bouton, superposition vocale) rendus
dans un portail. Ils restent donc en place en passant de l'app aux pages
Découvrir. À chaque changement de compte, la voix et le canal sont coupés et
le contexte du chat recharge son historique et ses outils.
Logo.tsx la plume dorée, deux versions de la même image : par défaut sur sa tuile sombre
(public/logo-classinus.webp), et avec la prop sansFond la plume seule, fond transparent
(public/logo-classinus-sans-fond.webp), utilisée partout (appli et pages publiques) ;
la tuile ne sert plus qu'aux icônes et à l'image de partage.
SplashOuverture.tsx (écran d'ouverture) utilise la plume seule sur le fond du thème
AppShell.tsx coquille de toute l'app connectée : sidebar desktop classique (AppSidebar),
hamburger + tiroir coulissant sur web mobile ; en natif, la nav passe par
components/mobile/ à la place. Sur le chat (estPageChat), la page ne réserve
aucune place en haut : le chat va jusqu'au bord de l'écran (voir le voile du chat
plus bas)
MenuProfil.tsx bouton profil et son panneau (profil, Paramètres, thème, déconnexion), utilisé par
AppSidebar (rail et tiroir du chat) et par l'en-tête du menu Plus du téléphone ;
photo et nom viennent de lib/useMonProfilAffiche.ts
AppSidebar.tsx sidebar desktop (Bureau/Bibliothèque/Notes en direct, groupes
"Personnaliser Clovis" et "Scolarité") ; repliée par défaut, elle se
déplie au survol du bouton "Classinus" par-dessus la page, sans la décaler.
Sur téléphone, le tiroir du chat montre les quatre groupes (Bibliothèque,
Concentration, Bureau, Personnaliser) avec leurs sous-sections ; le rail
ordinateur et le tiroir ne montent que leurs propres boutons de groupe
(lib/useEstDesktop.ts), pour qu'un menu masqué ne ferme jamais celui qui
est visible
voix/ voix en direct (Gemini Live) : OndeVoix (onde dessinée, trois aspects selon l'état)
et VoixDirecteSuperposition (onde plein écran dans le chat, bulle réduite,
bulle qui suit le curseur de Classinus quand le canal en direct est actif).
Aucun texte d'état à l'écran. Session unique partagée : lib/contexteVoixDirecte.tsx,
montée dans InteractionGlobale ; réglages (modèle, consignes, relances) côté serveur
dans core/gemini_live_config.py.
mobile/
BarreOngletsNative.tsx vraie barre d'onglets système (plugin Capgo, pas une barre CSS/React) :
Bibliothèque, Contrôle de session, Chat (au milieu), Notes, Personnaliser
Clovis ; masquée automatiquement pendant le chat plein écran
BarreOngletsWeb.tsx équivalent affiché quand l'app tourne dans un navigateur mobile classique ;
elle descend hors de l'écran quand le clavier s'ouvre au lieu de remonter au
dessus de lui (lib/useClavierOuvert.ts, partagé avec le canal en direct)
MenuHamburgerNatif.tsx menu "Plus" en natif : bouton haut gauche (icône 3 barres décroissantes) +
panneau flottant, reprend le contenu de l'ancien onglet Plus
MenuHamburgerWeb.tsx même menu, variante web
ProfilEnteteMenuPlus.tsx en tête du menu Plus (natif et web), à la place du titre : le bouton profil
de la barre latérale du chat (photo, nom, puis profil, Paramètres, thème,
déconnexion) ou "Se connecter" sans compte. Paramètres n'est donc plus dans la liste
GestionRetourNatif.tsx gestion du bouton retour matériel Android
chat/ composants du chat (ChatIA.tsx, ChatFlottant.tsx, BarreDeSaisie.tsx...) ;
ChatSection.tsx (07/09/2026) : version "page normale" du chat plein écran
pour la route /chat (chantier en cours "chat plein écran = vraie section" --
ChatFlottant.tsx gère encore l'ancien overlay fixed en parallèle tant que le
chantier n'est pas terminé) ;
chat/barre/ (01/10/2026) : morceaux de la barre de saisie, le bouton "+" et son
menu (MenuPlus.tsx), le bouton "Réglages" avec ses lignes
(choix dans un volet latéral sur PC, dépliés sous la ligne sur mobile) et sa bulle des réglages actuels au survol (BoutonReglages.tsx, LigneReglage.tsx), les données du mode
pédagogique, des modes ressources et du code enseignant (useReglagesPedagogiques.ts,
useModeActif.ts), le bandeau d'accès bloqué d'un mineur sans code, et les listes
de réglages partagées (reglagesReponse.ts) ;
Chat plein écran (18/10/2026) : après le premier message, le texte des messages passe
derrière la rangée du haut (modèle, hamburger, cloche, minuteur) sur PC et téléphone,
adouci par un fondu sans aucun flou (classe voile-chat-haut dans app/globals.css, réglable
par --dj-voile-opacite). Sur téléphone seulement, la barre de saisie
est posée par-dessus la liste, comme une carte qui flotte (prop flotteSurListe de
BarreDeSaisie.tsx), pleine et non transparente, et le texte s'estompe doucement en
descendant vers elle (classe voile-chat-bas, réglable par --dj-voile-bas-opacite, aucun
flou). ChatIA.tsx pose la
rangée du modèle et la barre par-dessus la liste (chat plein écran seulement, la popup
est inchangée), réserve en haut --dj-chat-haut et en bas la hauteur mesurée de la
barre (--hauteur-saisie-chat) ;
SelecteurModeleHaut.tsx (14/10/2026) : bouton du modèle en haut au milieu du chat,
nom et icône du fournisseur du modèle actif, liste dépliante groupée par fournisseur. Les modèles pas encore
ouverts (modeles_bientot, envoyés par le backend) y sont grisés avec la mention Bientôt.
Il remplace la ligne Modèle de Réglages dans la barre de saisie (BoutonReglages garde
cette ligne seulement pour le canal en direct, prop afficherChoixModele).
Plus de choix "Auto" (15/10/2026) : le modèle actif est le dernier choix de l'élève
(gardé sur son compte, profiles.modele_prefere, donc le même sur tous ses appareils),
sinon DeepSeek Flash, sinon le premier de la liste (lib/modeleActif.ts). Sur
téléphone (chat plein écran) le bouton est posé tout en haut, sur la ligne du hamburger ;
IconeFournisseur.tsx : icône d'un fournisseur de modèles, depuis public/icones-fournisseurs/ ;
LigneApercuPieces.tsx (02/10/2026) : ligne unique défilable des pièces jointes,
avant envoi (BarreDeSaisie.tsx) et après envoi (BulleMessage.tsx) ; carrés pour
images et vidéos, pilules empilées par deux pour le reste ; FenetreTexteColle.tsx
et FenetreMedia.tsx : fenêtres de lecture d'un texte collé et d'un audio ou d'une
vidéo ; lib/texteColle.ts : détection du langage d'un texte collé ;
PleinEcranApercu.tsx (20/09/2026) : vrai plein écran de bord à bord du bouton
Agrandir des aperçus (BlocExpansible.tsx : widget, animation, PDF, Office, texte), monté
dans <body> par un portail pour ne jamais être recadré par le message ;
BlocLarge.tsx (20/09/2026) : règle des blocs larges (tableaux, blocs de code,
aperçus md et code) qui s'élargissent des deux côtés de la colonne de texte sans
bouger le texte ; toute nouvelle zone de chat doit porter data-zone-chat (voir
ChatIA.tsx) et tout futur bloc large s'envelopper dans BlocLarge ;
BlocExpansible.tsx, option direct (04/10/2026) : le widget interactif et
l'animation s'affichent tout de suite dans le fil, à la largeur du texte comme un
tableau, sans puce repliée, sans carte, sans ligne de titre ni bouton Fermer ; les
boutons (Filmer, Copier, Agrandir) sont dans une fine rangée au dessus, révélée au
survol sur ordinateur et toujours visible sur écran tactile, hors du widget pour ne
jamais apparaître dans la vidéo de Filmer, puis dans un rail d'icônes collé en haut
du bloc (au survol) quand cette rangée sort de l'écran ; les autres contenus (code, PDF, Office,
texte) gardent la puce repliée ; le widget suit le thème de l'utilisateur sans rechargement
(état conservé), l'animation se recrée avec les nouvelles couleurs et reprend à la même
seconde, en lecture ou en pause ; le widget et l'animation ne font qu'un avec le chat : ni
contour ni coins arrondis sur l'iframe, document au fond transparent et sans marge, et
la hauteur du widget suit son contenu (plafond de sécurité à 6000 px) sans défilement
dans le widget lui même
(la barre de défilement du document du widget et de l'animation est masquée, avec
!important : un dépassement de 1 ou 2 px affichait une barre blanche à droite ; la
molette, le doigt et le clavier défilent toujours)
minuteurs/ minuteurs (20/09/2026) : DockMinuteurs.tsx (cartes et pastilles en position fixe
sous la cloche, montée UNE fois dans AppShell.tsx pour tous les écrans, 03/10/2026),
CarteMinuteur.tsx, PastilleMinuteur.tsx (version réduite),
LanceurMinuteur.tsx (bouton horloge à gauche de la cloche : durée libre sans plafond
en secondes/minutes/heures, suite facultative, retrouver les masqués),
AnneauMinuteur.tsx
AnimationLecteur.tsx bloc ```animation du markdown (29/09/2026) : animation qui se regarde comme une
vidéo (lecture, pause, barre de progression, barre d'espace), en 2D (SVG) ou en 3D
(Three.js r128 chargé depuis cdnjs, jsdelivr en secours), pour n'importe quel sujet,
dans une iframe isolée comme le widget (WidgetSandbox.tsx), affichée directement
dans le fil (04/10/2026) et lancée seulement quand elle approche de l'écran ;
branché dans composantsMarkdownRiches.tsx et RenduMarkdownAutonome.tsx
animation/ le lecteur lui même : construireDocumentAnimation.ts (page de l'iframe, thème,
barre de lecture) et runtimeAnimation.ts (code exécuté dans l'iframe). Le modèle
n'écrit que le contenu : une ou plusieurs parties (animer), chacune étant une fonction de sa
progression p (0 à 1), rejouée dans l'ordre à chaque image (animer peut être écrit après installer ou à l'intérieur) ; titres cliquables et
légende facultatifs. Pause et retour en arrière sont donc exacts. Textes dans lib/textesAnimation.ts. Consignes données au modèle
dans clovis-backend, core/profils_agents.py (INSTRUCTIONS_FORMATS_AFFICHAGE)
BoutonVideoAnimation.tsx bouton Télécharger de la barre d'actions du bloc animation (07/10/2026) : il ouvre un petit menu
(MenuActionsCarte) pour choisir le format, 16:9 ou 9:16, puis télécharge l'animation en vidéo MP4, fabriquée par le serveur (classinus-backend, api/rendu_animation.py),
donc aussi sur téléphone. Sans état : tout vit dans lib/useVideoAnimation.ts (envoi, suivi de
l'avancement, annulation, téléchargement). Appels dans lib/apiRenduVideoAnimation.ts, textes dans
lib/textesVideoAnimation.ts. Le document envoyé est celui du lecteur, mis en page au format de la
vidéo par animation/styleRenduVideo.ts (16:9 : la scène seule ; 9:16 : titre de la partie en haut,
scène au milieu, légende en bas, les textes dessinés dans l'animation restant prioritaires) et
piloté par animation/runtimeRenduVideo.ts (dessiner l'animation à un instant donné). three.js
est aussi copié côté serveur : changer sa version ici impose de changer cette copie
icones/, icons/ icônes du produit
VisionneurLivre.tsx liseuse des livres numériques EPUB et MOBI (11/10/2026), dépliée dans le fil comme le PDF
(components/chat/FichierChip.tsx). Charge le moteur public/foliate-js à l'ouverture
seulement, lit le fichier en suivant l'avancement (refus au-delà de
NEXT_PUBLIC_LIVRE_TAILLE_MAX_MO, 100 par défaut), sommaire, flèches du clavier, thème
clair ou sombre. Chaque page du livre passe par lib/neutraliserPageLivre.ts avant
d'être affichée
chat/LecteurMedia.tsx lecteur audio et vidéo du chat. Un fichier d'un site externe passe par le relais
(lib/useLienRelaisExterne.ts) ; si la lecture échoue, il retente avec l'adresse
d'origine puis affiche une carte de téléchargement
chat/LinkPreview.tsx carte d'un lien du chat (aperçu du site, vignette YouTube). Depuis le 12/10/2026, le texte
du lien markdown écrit par le modèle devient le titre de la carte et l'attribut title
([Titre](url "Description")) sa description (2 lignes sur la carte, en entier dans l'aperçu
ouvert au clic) ; le titre du site ne sert que de repli (les vignettes YouTube gardent le titre de la vidéo et
la ligne de la chaîne). La carte porte data-titre-lien et
data-description-lien, que GardienLiensSortants.tsx relit pour les passer à l'aperçu
chat/BoutonCopierLien.tsx bouton qui copie l'adresse d'un lien (11/10/2026). Icône nue (sans cadre ni
texte). Variante survol : posée à côté d'une carte de LinkPreview, visible seulement au
survol de CETTE carte (groupe nommé group/lien) et à la souris, cachée sur téléphone ;
variante texte : même icône dans l'en-tête de l'aperçu (VisionneurPositionGlobal), pour
tous les types sauf la carte de site, qui a la sienne sous la carte
lib/arretServeur.ts appel POST /api/chat/arreter : demande au serveur d'arrêter les outils et la boucle d'agent du tour (12/10/2026)
lib/repriseFluxChat.ts lecture du flux du chat, comptage des événements et reconnexion au tour qui continue sur le serveur (28/10/2026) ;
lib/api.ts (suivreTourEnCours, lireEtatTourEnCours) et ChatIA.tsx s'en servent au rechargement ou au retour sur une conversation
lib/tacheCanal.ts état de la tâche du canal et signal d'arrêt commun : le bouton Arrêter (chat ou canal) coupe aussi la
voix, le commentaire affiché et la frappe simulée, et prévient le serveur (abonnerArretTache,
arreterToutLocalEtServeur)
lib/copierMarkdown.ts copie "formatée" d'un markdown : HTML rendu + markdown brut dans le presse-papiers
(boutons "Copier formaté" / "Copier brut" de ContenuMarkdown, VisionneuseBibliotheque.tsx).
FenetreTexteColle.tsx (aperçu d'un long texte collé) affiche le texte formaté par défaut,
avec un bouton Brut ; code et LaTeX gardent leur rendu
public/
icones-fournisseurs/ icônes SVG des fournisseurs de modèles (claude, gpt, gemini, deepseek, grok, meta), nommées comme la clé
"distributeur" du backend, tirées de @lobehub/icons-static-svg (licence MIT). Pour ajouter
un fournisseur : déposer son SVG ici et l'ajouter dans components/chat/IconeFournisseur.tsx
foliate-js/ moteur de lecture EPUB et MOBI (foliate-js, licence MIT), copié sans modification
(voir ORIGINE.txt dans le dossier : commit repris, fichiers gardés). Servi tel quel,
jamais intégré au bundle (chargé par import dynamique depuis VisionneurLivre.tsx) ;
pour le mettre à jour, remplacer les fichiers par ceux du dépôt d'origine puis
rouvrir un livre dont une page contient un script, pour vérifier que rien ne s'exécute
pdf-worker/ worker pdf.js du lecteur PDF
pyodide-worker.mjs worker Python (voir lib/executionPython.ts)
lib/
api.ts client HTTP vers clovis-backend (NEXT_PUBLIC_API_URL)
supabase.ts client Supabase (auth + DB), enregistrement des plugins Capacitor natifs
contexteChat.tsx état global du chat (état "plein_ecran" lu par la barre d'onglets,
préremplissage d'une nouvelle conversation via useOuvrirChatAvecTexte) ;
porte aussi l'état de la conversation elle-même (agent, messages,
historique...) depuis le 07/09/2026, remonté depuis ChatFlottant.tsx pour
qu'il soit lisible à la fois par ChatFlottant.tsx et par la future route
/chat (ChatSection.tsx) sans dupliquer le chargement initial
contexteCatalogue.tsx, contexteFenetres.tsx, contexteRetour.tsx autres contextes React globaux
useDeplacable.ts éléments flottants déplaçables au doigt ou à la souris (20/09/2026) : décalage par
rapport à l'emplacement d'origine, bornes à l'écran, clic ignoré après un glissement ;
utilisé par CanalEnDirectFlottant.tsx (canal + dictée + écriture) et BoutonJournalAgent.tsx
contexteMinuteurs.tsx minuteurs (20/09/2026) : liste, actions de l'étudiant, prise en charge de la fin
(message automatique invisible : par le chat à l'écran, sinon par le canal en
direct et sa bulle s'il est actif, 03/10/2026) ;
minuteurs.ts (appels /api/minuteurs, formats), textesMinuteurs.ts (textes par
langue), useMaintenantMs.ts (horloge locale des affichages)
crayonEcran.ts crayon du canal en direct sur PC (11/10/2026) : traits dessinés sur l'écran, jamais effacés
avant l'envoi (estompage 10 s après l'envoi seulement), la zone d'écriture s'ouvre seule à la fin
d'un trait, trois raccourcis (components/CoucheCrayonAgent.tsx, BoutonsCrayonCanal.tsx) ;
zonesCrayon.ts (rectangle de chaque trait), lectureEntoureCrayon.ts (tous les éléments lus dans la
fenêtre au premier plan, ceux qui tombent dans ces rectangles marqués comme entourés), phraseCrayon.ts (phrase envoyée
au grand modèle et message de la bulle quand rien n'est lisible), envoiCrayon.ts (envoi) ;
pas de bouton Envoyer propre au dessin : c'est celui du champ d'écriture du canal
(ControlesInteractionCanal.tsx) qui joint le dessin, avec ou sans texte. Les traits effacés
restent rappelables. Seules les applications extérieures sont lues, pas encore Classinus
canalTempsReel.ts client du canal temps réel avec le backend (exploration de dossier mobile...)
executionPython.ts bouton Exécuter des blocs de code : Python (Pyodide) tourne dans un Worker
(public/pyodide-worker.mjs) sur l'appareil de l'étudiant. prechargerPython() démarre
le chargement de Pyodide dès l'ouverture de l'appli (AppShell.tsx, au repos du
navigateur) et après toute coupure du worker, pour que l'exécution soit instantanée ;
useExecutionPython.ts gère l'état d'un bloc (sortie, input(), sys.argv)
telecharger.ts téléchargement réel d'un fichier par adresse (10/10/2026) : lecture au fil de l'eau
avec pourcentage (Content-Length exposé par le backend), échec signalé avec un message
clair, jamais d'exception. L'état de chaque téléchargement vit dans etatTelechargements.ts ;
les boutons l'affichent via components/EtatTelechargement.tsx (icône, pourcentage, rouge
+ "Réessayer" en cas d'échec) et components/IndicateurTelechargements.tsx (carte globale,
layout racine) couvre les téléchargements sans bouton (lien du chat, sélection). Textes
dans textesTelechargement.ts
liensSortants.ts règle unique des liens qui veulent sortir de l'appli (20/09/2026) : un fichier se
télécharge vraiment (lib/telecharger.ts), tout autre lien passe par l'aperçu interne
(VisionneurPositionGlobal) qui demande ensuite d'ouvrir le site ; appliquée à tous les
clics par components/GardienLiensSortants.tsx (layout racine). data-lien-libre sur un
lien = garde son comportement natif. Seul ouvrirSiteExterieur quitte réellement l'appli
neutraliserPageLivre.ts filtre de sécurité des livres numériques (11/10/2026) : retire scripts, gestionnaires
on*, liens javascript:, iframes, objets et redirections de chaque page d'un livre, et
pose une règle Content-Security-Policy qui interdit le reste. Un livre venu d'un site
externe est un contenu non fiable, ses pages tournent avec les mêmes droits que
l'appli (limite de WebKit sur le bac à sable des iframes)
useHauteurWidget.ts hauteur du cadre d'un widget interactif (03/10/2026) : suit la hauteur du contenu entre
120 et 1200 px, repart de 384 px (l'ancienne hauteur fixe) ; garde contre un widget
qui dépend de la hauteur de la fenêtre (retour à 384 px après 5 agrandissements de
suite), ignorée en plein écran, figée pendant un enregistrement du bouton Filmer ;
scriptHauteurWidget.ts : le script posé dans le widget qui mesure son contenu (bas
de l'élément le plus bas, éléments en position fixe ignorés)
paletteIframe.ts palette de l'interface pour les contenus en iframe isolée (widget et animation, une
seule source) : variables CSS --dj-*, objet THEME du widget, script qui applique un
changement de thème sans recharger le widget (04/10/2026) ; le modèle n'écrit plus
aucune couleur, fond ni police, la consigne est dans le backend (profils_agents.py)
useProcheEcran.ts devient vrai quand un élément approche de l'écran (04/10/2026), puis le reste ;
sert au widget interactif et à l'animation, qui ne démarrent qu'à ce moment
pour qu'une longue conversation n'en lance pas dix d'un coup
usePluginNatif.ts hook d'accès générique aux plugins Capacitor
useSurAppliPc.ts vrai seulement dans l'appli PC installée (Electron) ; sert à retirer des Paramètres
ce qui n'existe que sur téléphone (champ masqueSurPc de lib/groupeSections.tsx)
useNotificationsPush.ts abonnement aux notifications Web Push (protégé : jamais appelé en natif,
la WebView Capacitor n'a pas l'objet Notification du navigateur)
erreurs.ts messages d'erreur centralisés, miroir de core/erreurs.py côté backend
textesPagesErreur.ts textes (français et anglais) des écrans d'erreur et du lien de réinitialisation impossible à vérifier
logosSites.ts vrais logos des sites et sources visés par un outil du chat (YouTube, Internet Archive, arXiv, HAL) ;
tout autre site montre l'icône qu'il publie lui-même, demandée au serveur (route /api/logos-sites,
core/logos_sites.py côté backend) qui répond 404 quand il n'y en a pas, et dans ce cas la première
lettre du nom s'affiche, jamais un globe générique (components/LogoDeSite.tsx, partagé par
components/chat/LogoSite.tsx et components/connexions/LogoMcp.tsx).
Les domaines viennent du champ "domaines" des événements d'outil (core/domaines_affichage.py côté backend)
outils.ts, matieres.ts, coloration.ts, dateRelative.ts, formatageHeure.ts, salutations.ts utilitaires
android/, ios/ projets Capacitor (capacitor.config.ts minimal, export statique build:capacitor
vers webDir "out", pas de rechargement du site distant dans la WebView)
app/(app))| Route | Contenu |
|---|---|
/ | Accueil (raccourcis "Mon espace", activité récente) |
/bureau | Bureau : page d'accueil en liste, même principe que Personnaliser Clovis |
/bureau/audit | Audit hebdomadaire des signalements des élèves |
/bureau/codes | Mes codes |
/bureau/programme | Programme de notions par code |
/bureau/entrer-code | Entrer un code reçu |
/bureau/etablissements | Établissements (la fiche /etablissements/[id] y ramène) |
/bureau/signalements | Signalements reçus des élèves |
/bibliotheque | Bibliothèque : page d'accueil en liste (Perso, Publique, Dossiers du téléphone), même principe que Personnaliser Clovis |
/bibliotheque/perso | Bibliothèque personnelle |
/bibliotheque/publique | Bibliothèque publique (catalogue partagé par tout le monde) |
/bibliotheque/telephone | Dossiers du téléphone (appli mobile uniquement) |
/memoire | Ma mémoire |
/comportements | Mes skills ("comportements" en interne) |
/skills-publics | Skills publics (catalogue partagé par les autres étudiants) |
/configuration | Configuration (procédures, règles, comportements, styles), dans Personnaliser Classinus ; l'ancienne adresse /bureau/configuration redirige ici |
/personnaliser | Personnaliser Clovis : page d'accueil en liste (Mes skills, Skills publics, Ma mémoire, Configuration, Applications connectées) — onglet central du menu natif |
/controle-session | Concentration : page d'accueil en liste, même principe que Personnaliser Clovis |
/controle-session/session | Contrôle de session |
/controle-session/temps-ecran | Temps d'écran |
Toutes les pages du groupe app/(app) s'affichent sans compte (mode invité).
Seules les actions qui ont vraiment besoin d'un compte le demandent, via
components/CompteRequisModal.tsx (déjà utilisé par la Bibliothèque publique,
les établissements, les notes/commentaires...). Le chat garde sa propre
limite invité (5 messages, ChatSection.tsx/ChatFlottant.tsx) avant
d'afficher cette même modale.
Historique : une garde globale bloquant toute page sans compte (sauf les
pages d'élément partagé) a existé du 19/09/2026 au 22/09/2026
(components/AppShell.tsx, liste dans l'ex-lib/routesPubliques.ts) --
retirée à la demande de Bourama, qui bloquait aussi le chat invité.
Tout passe par Supabase Auth depuis le frontend, le backend ne gère aucun mot de passe.
/connexion, l'onglet Email affiche "Mot de passe oublié ?" (absent de l'onglet Téléphone : un compte
créé avec un numéro n'a pas d'email où envoyer le lien)./mot-de-passe-oublie demande l'email et appelle resetPasswordForEmail. Le message de confirmation est le
même que le compte existe ou non. Le renvoi du lien est bloqué 60 secondes./reinitialiser-mot-de-passe, où l'utilisateur choisit son nouveau mot de passe
(updateUser), puis revient au chat, connecté. Un lien expiré ou déjà utilisé affiche un écran qui propose
d'en recevoir un nouveau.Le code partagé (adresses, délais, messages d'erreur en français) est dans lib/reinitialisationMotDePasse.ts.
Dans l'appli installée, l'adresse du lien vient de NEXT_PUBLIC_APP_URL. Sur Android, ce lien ouvre ensuite l'appli
(voir "Liens qui ouvrent l'appli" plus bas) ; sur PC, il ouvre le navigateur.
À configurer côté Supabase (Authentication, URL Configuration, Redirect URLs) : ajouter
<NEXT_PUBLIC_APP_URL>/reinitialiser-mot-de-passe pour chaque projet (production, et test si besoin). Sans cela,
Supabase refuse d'envoyer le lien. Le texte de l'email se règle dans Authentication, Email Templates.
Un lien de Classinus doit ouvrir l'appli installée, pas le navigateur. Une seule lecture des liens pour tous
les cas : lib/liensEntrants.ts (pur, testé), branchée par components/EcouteurLiensEntrants.tsx (monté dans
app/layout.tsx, via l'événement appUrlOpen de @capacitor/app).
| Plateforme | Comment | Fichiers |
|---|---|---|
| Android | App Links : un lien https://classinus.com/... s'ouvre dans l'appli une fois vérifié par Android | android/app/src/main/AndroidManifest.xml (filtre autoVerify), android/app/build.gradle (hoteLiensApp), public/.well-known/assetlinks.json |
| PC (Windows) | Protocole classinus://app/... enregistré par l'appli (instance unique : le lien arrive dans la fenêtre ouverte) | electron/capacitor.electron.config.ts (deepLinks) |
| Navigateur PC | Un lien https ne peut pas ouvrir une appli de bureau : un bandeau "Ouvrir dans l'appli Classinus" apparaît sur les liens de partage (?fichier=, ?dossier=, ?skill=) | components/BandeauOuvrirDansAppli.tsx |
| iOS | Rien pour l'instant : l'appli iOS n'est distribuée qu'en PWA, sans identifiant d'équipe Apple |
Règles à connaître :
/oauth, /agent-superposition et /dev-demo-question ne s'ouvrent jamais dans l'appli
(PREFIXES_NON_OUVERTS_DANS_APPLI). Le consentement du connecteur Claude doit rester dans le navigateur,
sinon le retour vers Claude casse.app/ : mettre cette liste à jour. npm run test:liens-entrants échoue sinon.assetlinks.json contient l'empreinte SHA-256 du certificat de signature de l'APK (lue dans l'APK
publié v1.5.0), pour les paquets com.classinus.app et com.classinus.app.externe. Si la clé de signature
change, mettre ce fichier à jour, sinon les liens ouvrent de nouveau le navigateur.classinus.com) est écrit dans build.gradle (hoteLiensApp) : il doit rester l'hôte de
NEXT_PUBLIC_APP_URL, ce que vérifie le test. Le site ne doit jamais rediriger /.well-known/assetlinks.json
(ni vers www, ni ailleurs), sinon Android refuse la vérification./reinitialiser-mot-de-passe.Une section en groupe a une page d'accueil en liste (components/ListeSections.tsx)
et des pages filles qui sont de vraies routes, pas des onglets en mémoire. Les lignes de cette liste sont
à nu, sans carte ni flèche : un trait les sépare et commence après l'icône. Ce trait, ainsi que le trait
renforcé des icônes de section, viennent de lib/styleLignes.ts, lu aussi par les listes de la Bibliothèque,
de la Bibliothèque publique et des Dossiers du téléphone.
La page Paramètres (components/ParametresAccueil.tsx) suit le même visuel et s'affiche tout de suite, sans écran de
chargement : seule sa ligne de profil montre un squelette le temps de recevoir sa donnée. Skills publics, Entrer un code,
Ma mémoire et l'écran d'accueil sont aussi à nu, sans carte, de même que la liste de Mes skills (une ligne par skill, plus de pastilles). Dans Paramètres et dans le panneau Plus du téléphone, les groupes de plusieurs lignes sont enfermés dans un cadre arrondi, avec les mêmes lignes de séparation à l'intérieur. Le panneau Plus s'ouvre collé en haut de l'écran (option enHaut de components/PanneauFlottant.tsx), son titre en haut. Dans Ma mémoire, chaque catégorie est repliée par défaut. Chaque
page fille affiche un fil d'Ariane avec retour, et le passage d'une page voisine à
l'autre remplace l'entrée d'historique au lieu d'en ajouter une. Sur PC, un menu à
gauche liste les pages voisines ; sur téléphone, la même liste s'affiche en haut
sous forme de pastilles (OngletsLiens dans components/OngletsSegment.tsx).
Les deux se branchent via la prop groupe de components/SectionPage.tsx.
La ligne du titre (avec le retour et le « i ») et, sur téléphone, les pastilles restent en place quand la page défile.
Bibliothèque : la liste est dans lib/sectionsBibliotheque.tsx, les adresses dans
lib/routesBibliotheque.ts. Bureau : lib/sectionsBureau.tsx et lib/routesBureau.ts. Concentration :
lib/sectionsConcentration.tsx et lib/routesConcentration.ts. Personnaliser Clovis :
lib/sectionsPersonnaliser.tsx et lib/routesPersonnaliser.ts (/comportements et /memoire gardent
leurs adresses historiques hors de /personnaliser, seul /skills-publics est nouveau).
Dans le chat, chaque groupe devient un bouton qui déplie la liste de ses pages
(components/MenuGroupeRail.tsx) : au survol sur PC (le premier clic ne change rien, le second
entre dans la page du groupe), au toucher dans le tiroir mobile (un toucher ouvre la liste, un autre
la referme, on entre dans une page en touchant sa ligne).
Sur PC, cette liste a l'apparence compacte de la bulle du bouton Réglages de la barre de saisie
(largeur du contenu, petit texte, lignes serrées) ; le tiroir mobile garde des lignes tactiles.
La barre latérale a le même fond que la barre de saisie () et des icônes
petites au trait renforcé, avec des noms en semi-gras ; leur taille et leur trait viennent de .
Le prop se fabrique avec (). Le titre
et le bouton "i" d'une page fille sont portés par la page (),
les écrans ne les répètent pas dans leur carte.
Décision confirmée : barre d'onglets système en bas (Bibliothèque,
Contrôle de session, Chat, Notes, Personnaliser Clovis) + menu "Plus" en
icône hamburger haut gauche plutôt qu'en onglet. Implémentée avec de
vrais composants système (@capgo/capacitor-native-navigation +
@capgo/capacitor-transitions), pas une barre stylée en CSS. La version
web mobile (navigateur, hors app installée) garde un hamburger + tiroir
coulissant équivalent mais non natif.
Neuf plugins Capacitor natifs existent côté Android (MainActivity.java) :
Dossiers, ControleSession, Connecteurs, Accessibilité, MiseAJour, PontNatif,
TempsEcran, Notifications et Telechargement (vrai téléchargement système
Android -- DownloadManager pour les fichiers déjà en ligne, MediaStore.Downloads
lib/telecharger.ts),
tous ont désormais un point d'entrée dans la nav web/TypeScript.clovis-backend.npm run build:capacitor), distribuée hors store (APK via /telecharger) ;
socle natif historique (Kotlin/Compose + Swift/SwiftUI) conservé pour
référence dans le dépôt séparé clovis-mobile, plus de nouveau
développement natif direct là bas.| Variable | Usage |
|---|---|
NEXT_PUBLIC_API_URL | URL de clovis-backend |
NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY | client Supabase (auth + DB) |
NEXT_PUBLIC_LIVRE_TAILLE_MAX_MO | taille maximale, en Mo, d'un livre numérique ouvert dans la liseuse (100 si absente ou invalide) ; au-delà, carte de téléchargement. Optionnelle |
NEXT_PUBLIC_OTA_LATEST_URL | adresse de latest.json pour les mises à jour OTA de l'appli installée (voir "Mises à jour OTA") ; adresse du dépôt classinus-updates par défaut. Optionnelle |
NEXT_PUBLIC_APP_URL | URL publique du déploiement, utilisée par app/layout.tsx (balises OG) et le bouton partager (lien de téléchargement de l'app) ; sur Vercel, doit être créée en type "Configuration", jamais "Secrète" (incompatible avec un préfixe NEXT_PUBLIC_) |
Voir .env.local.example.
npm install
npm run dev
npm run build:capacitor
Build statique Next.js vers out/, puis npx cap sync. Nécessite
Android Studio/Xcode installés localement pour ouvrir et compiler les
projets android//ios/ ensuite (hors de portée d'un sandbox sans ces
outils).
Par défaut, ce build vise la production (variables de .env.local, ou celles
du workflow OTA). Pour tester l'appli PC contre le backend de test Railway et
la base Supabase de test, utiliser npm run build:capacitor:test : il ajoute
CLASSINUS_CIBLE_TEST=true, et seul ce script déclenche la cible de test
(voir next.config.mjs).
L'appli PC (Electron) se met à jour toute seule avec electron-updater et se
publie par une étiquette pc-vX.Y.Z (workflow .github/workflows/publier-pc.yml,
releases du dépôt public petitboura/classinus-updates-pc). C'est indépendant de l'OTA
mobile ci dessous. Depuis la 1.0.5, l'installeur embarque aussi le relais
téléphone (Relais-Class-Tech.exe, construit pendant le workflow depuis le
dépôt class-tech), qui démarre tout seul avec l'appli (la carte « Relais
téléphone » des Paramètres n'est qu'un voyant d'état). Mode d'emploi, réglages
à faire une fois et limites :
docs/appli-pc-mise-a-jour-et-publication.md.
L'APK que sert la page /telecharger (variante play) se construit et se publie
tout seul, comme l'appli PC : on pousse une étiquette vX.Y.Z (par exemple
v1.6.0) sur un commit de main, et .github/workflows/publier-android.yml
construit l'APK, le signe, le vérifie, puis le publie en release de ce dépôt
(Classinus.apk, marquée "latest"). C'est un geste volontaire, indépendant de
l'OTA : l'OTA met à jour le code web de l'appli installée à chaque push sur
main, la release n'est à refaire que pour un changement natif (plugin,
permission, manifest, icône native) ou pour que le fichier téléchargé soit à jour.
Avant d'étiqueter : changer versionName (identique à l'étiquette sans le "v") et
versionCode (strictement supérieur au précédent) dans
android/version-app.properties, et le mettre dans main. Le workflow refuse de
publier si l'étiquette et versionName diffèrent, si versionCode ne monte pas,
si la clé de signature n'est pas la même que celle de la release précédente, ou si
le commit n'est pas dans main. Rien n'est publié tant que ces contrôles ne
passent pas : /telecharger garde alors la version précédente.
Réglages GitHub à faire une fois (Settings, Secrets and variables, Actions) :
secrets ANDROID_KEYSTORE_BASE64 (le fichier .jks encodé en base64),
ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD, plus les
variables déjà utilisées par ota.yml (NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_APP_URL).
La clé doit être celle des publications précédentes (voir
android/README-SIGNATURE.md).
Le code web de l'appli installée se met à jour tout seul : à l'ouverture, l'appli télécharge en arrière-plan le dernier bundle, et il s'active à l'ouverture suivante. Seuls les changements natifs (nouveau plugin, permission, manifest Android, mise à jour de Capacitor) exigent un nouvel APK.
main lance
.github/workflows/ota.yml (jamais depuis staging). Il construit l'export
statique, publie bundle.zip en release du dépôt classinus-updates, puis met
à jour latest.json dans ce même dépôt. Les 5 dernières versions sont gardées
(variable OTA_VERSIONS_GARDEES pour changer).lib/miseAJourOta.ts, lancé par components/MiseAJourOta.tsx
(monté dans app/layout.tsx). Plugin @capgo/capacitor-updater en mode
manuel, aucun service Capgo n'est utilisé. Si un bundle ne démarre pas, le
plugin revient à la version précédente et cette version n'est plus retentée.ota.config.json contient versionNativeMin, le
versionCode (android/app/build.gradle) minimal d'un APK pour recevoir les
bundles. À relever dès qu'un changement natif rend les anciens APK
incompatibles avec le nouveau code web.UPDATES_TOKEN (jeton avec droit d'écriture sur
classinus-updates), variables OTA_DEPOT (petitboura/classinus-updates),
NEXT_PUBLIC_API_URL, NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_APP_URL. Le workflow s'arrête
avec un message clair si l'un d'eux manque./telecharger), ensuite tout le code web passe en OTA./rappels |
| Notes / rappels |
/connecter-claude | Connexion du serveur MCP public comme connecteur externe |
/connecter-thonny | Guide pas à pas pour installer le plugin Classinus dans Thonny (captures dans public/guide-classinus-thonny/) |
/parametres | Paramètres (profil, préférences, confidentialité, capacités du téléphone, accessibilité, aide, à propos, zone de danger) |
/plus | Menu "Plus" en version web (bureau, scolarité, connecter Claude, admin, paramètres) |
/admin/signalements | Modération des signalements de contenu public |
bg-dj-surfacelib/styleBarreLaterale.tsgroupeconstruireGroupelib/groupeSections.tsxcomponents/DefinirInfoSection.tsx