SelixyBrain
Second cerveau : notes reliées, graphe 3D et notes volantes
Je prends mes notes dans SelixyBrain : idées, lectures, expérimentations, matière du mémoire. Je l’ai commencé fin mai 2026 comme une interface Vue pour un vault SilverBullet, puis le dépôt a absorbé mes deux projets précédents (l’interface seule et un hub). Il est en ligne sur brain.selixy.dev, et l’essentiel du travail a eu lieu entre fin septembre et début octobre, au moment où je l’ai repris pour le rendre sûr à partager.
Graphe et éditeur
Les notes sont des fichiers Markdown reliés par des liens [[...]], et l’interface les montre en graphe 3D (3d-force-graph et three). L’éditeur est CodeMirror ; sous chaque note, il liste les rétroliens, c’est-à-dire les notes qui la citent. Les tags (#tag) alimentent l’autocomplétion et un filtre du graphe, et Ctrl+K sert à sauter d’une note à l’autre.
Les liens se résolvent en un seul endroit du code, pour le graphe, le clic, l’autocomplétion et la suppression : chemin exact, puis même chemin sans la casse, puis nom court, avec la note du même dossier en cas d’ambiguïté. Sur téléphone, un seul panneau est visible à la fois, et le graphe choisit lui-même le nœud le plus proche du doigt, parce que la bibliothèque désigne un nœud périmé au toucher.
Ne jamais perdre de texte
C’est la règle de conception que j’ai posée en premier. L’éditeur a un état séparé par note, donc Ctrl+Z ne traverse pas deux notes. La frappe en attente est envoyée dès qu’on change de note, qu’on cache l’onglet ou qu’on quitte la page.
Si l’enregistrement échoue, il est retenté (après 2 s, 5 s, 10 s, puis toutes les 30 s) et un bouton permet de réessayer. Le texte non enregistré est aussi gardé dans le navigateur, et la note propose de le restaurer à sa réouverture. Chaque note garde enfin ses versions, que l’administrateur peut restaurer, y compris pour une note supprimée.
Notes volantes
Les notes volantes servent à capturer vite : texte, voix, photo, fichier, liens. Une volante est un dossier .note dans volantes/, avec ses médias rangés par type dans des sous-dossiers, et elle se range ensuite dans des dossiers libres. Elles sont réservées à l’administrateur, sortent du graphe, et se trient dans un explorateur (récentes ou A à Z, avec une section « À ranger »). Elles se promeuvent ensuite en vraies notes. La section de transcription vocale est prévue dans le format, mais rien ne la remplit encore.
Couche d'accès et droits
Les notes vivent dans un vault tenu par SilverBullet, que je n’expose pas. Entre lui et l’interface, j’ai écrit une couche d’accès en Node 22 et TypeScript strict, sans dépendance d’exécution. Elle sert aussi l’interface, gère comptes, rôles et sessions (mots de passe hachés en scrypt, cookie signé), et relaie les appels vers SilverBullet avec son propre jeton, que l’interface ne voit jamais.
- trois rôles : administrateur, spectateur en lecture seule, et invité qui peut créer et modifier sans supprimer ;
- les droits sont appliqués côté serveur dans une table unique, l’interface ne fait que masquer les actions ;
- une note est visible par défaut. L’administrateur peut la réserver, et elle disparaît alors des listes et du graphe, avec une erreur 404 en accès direct, comme si elle n’existait pas ; il peut aussi seulement la masquer aux spectateurs ;
- ces marqueurs sont tenus dans un registre à part : rien n’est écrit dans les notes, et changer un marqueur ne modifie pas le vault d’un octet (un test le prouve) ;
- si le registre est perdu ou corrompu, les comptes non administrateurs ne voient plus rien tant que je n’ai pas restauré la sauvegarde ;
- une note peut aussi être partagée par un lien à jeton, qui ouvre cette seule note sans session et se révoque.
Une limite reste ouverte : le texte d’une note visible peut citer le nom d’une note réservée. Le serveur ne réécrit jamais une note, donc ce nom reste lisible. Le graphe, lui, ne montre que des nœuds visibles. C’est à moi d’y veiller.
Agents et MCP
Le dépôt contient un panneau agent, réservé à l’administrateur : une conversation avec un agent branché sur le coffre, qui montre ses appels d’outils sur une piste d’exécution et demande mon accord avant d’agir. Un serveur sans agent configuré répond « Agent non configuré », et le README du dépôt dit qu’il n’y a ni IA ni chat pour l’instant. Je le traite donc comme une piste, pas comme une fonction livrée.
À côté, un serveur MCP (extensions/vault-mcp) permet à un agent comme Claude Code de chercher, lire et écrire dans le vault. Il passe par la couche d’accès avec un compte ordinaire, donc avec les mêmes rôles, le même registre et les mêmes versions restaurables qu’un humain.
Déploiement et tests
L’interface (Vue 3, Vite, CodeMirror) et la couche d’accès sont livrées dans un conteneur unique, sans droits particuliers, avec un système de fichiers en lecture seule et 256 Mo. J’ai choisi un seul conteneur parce que l’interface n’est que du statique, que Node sert en une centaine de lignes, et qu’un nginx de plus aurait demandé un réseau interne et une seconde image sans rien ajouter à la sécurité. Un git push vers le VPS lance la livraison.
Le dépôt compte des tests unitaires et des parcours Playwright. Un test construit l’interface avec de faux identifiants et échoue si l’un d’eux apparaît dans le code livré. Un audit modulaire vérifie les règles que je me suis données : 200 lignes par fichier, cinq fonctions au plus, aucun commentaire dans le code, le pourquoi vivant dans le README de chaque module.
Où j'en suis
L’outil me sert, mais il reste en chantier. Le panneau agent n’est pas branché, la transcription des vocaux n’existe pas, et la migration des anciennes notes vers le format en dossier .note n’est pas terminée en production.