Format .mmap v4 : `node.side` (cote d'une branche de niveau 1) et `MindMap.relations` (relations secondaires). Migration v3 -> v4 sans perte, les cartes v1/v2/v3 restent ouvrables telles quelles. Une relation est une connexion purement visuelle : elle ne touche jamais parentId, order, les enfants, le repli ni l'auto-layout. `withEdges()` continue de deriver les liens de parentId et se contente de retirer les relations dont une extremite a disparu. Mind map bilaterale : repartition gloutonne et deterministe des branches selon la taille des sous-arbres, heritage du cote par sous-arbre, deux passes dagre recalees sur la racine. Choisir une disposition ne deplace plus la carte : seul Reorganiser applique. Ajoute aussi une galerie de huit modeles locaux, l'import Markdown (fichier ou texte, nouvelle carte ou sous-arbre) reutilisant l'analyseur d'outline existant, et l'export Markdown de la hierarchie. Corrige un bug de rendu : React Flow ne remesure pas les poignees quand elles changent de cote, ce qui faisait disparaitre les liens concernes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
727 lines
28 KiB
Markdown
727 lines
28 KiB
Markdown
# OpenMind
|
|
|
|
**Cartes mentales libres, local-first, sans compte et sans abonnement.**
|
|
|
|
OpenMind est une alternative gratuite et open source aux services de mind mapping
|
|
qui placent l'export, les themes ou le nombre de cartes derriere un abonnement.
|
|
Toutes les fonctionnalites sont disponibles, sans exception et sans compte.
|
|
|
|
- Aucune limitation artificielle, aucun paiement, aucun "premium".
|
|
- Aucun compte obligatoire : il n'y a d'ailleurs aucun systeme de comptes.
|
|
- Aucune telemetrie, aucun tracker, aucune publicite.
|
|
- Vos cartes vous appartiennent : elles restent dans votre navigateur.
|
|
- Export PDF, PNG, SVG et impression : gratuits.
|
|
- Un partage par lien, uniquement si vous le demandez explicitement.
|
|
- Un seul conteneur Docker a demarrer.
|
|
|
|
---
|
|
|
|
## Philosophie local-first
|
|
|
|
Une carte que vous creez n'est **jamais** envoyee au serveur.
|
|
|
|
Elle est enregistree dans l'**IndexedDB de votre navigateur**, automatiquement,
|
|
apres une courte periode d'inactivite. Le backend n'existe que pour une seule
|
|
chose : generer un lien de partage lorsque vous cliquez explicitement sur
|
|
**Partager**.
|
|
|
|
Consequences concretes :
|
|
|
|
- Fermer le navigateur, redemarrer le PC ou redemarrer le conteneur ne fait
|
|
perdre aucune carte locale.
|
|
- Une fois l'application chargee une premiere fois, elle continue de fonctionner
|
|
sans reseau (PWA) : creation, edition, export, impression.
|
|
- Vider les donnees du site, ou changer de navigateur/machine, fait disparaitre
|
|
les cartes locales. **Exportez vos cartes en `.mmap`** pour en conserver une
|
|
sauvegarde portable.
|
|
|
|
---
|
|
|
|
## Demarrage rapide
|
|
|
|
Prerequis : Docker et Docker Compose.
|
|
|
|
```bash
|
|
git clone <url-du-depot>
|
|
cd Mind-Mapping
|
|
cp .env.example .env
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Puis ouvrez :
|
|
|
|
```
|
|
http://localhost:8080
|
|
```
|
|
|
|
Verifier que le service repond :
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/health
|
|
```
|
|
|
|
Reponse attendue : `{"status":"ok"}`
|
|
|
|
Etat du conteneur (le healthcheck Docker interroge `/api/health`) :
|
|
|
|
```bash
|
|
docker compose ps
|
|
```
|
|
|
|
Arreter :
|
|
|
|
```bash
|
|
docker compose down
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
Un seul service applicatif. FastAPI sert a la fois l'API et les fichiers
|
|
statiques du frontend compile.
|
|
|
|
```
|
|
Navigateur
|
|
|
|
|
+-- React / TypeScript (Vite, Tailwind, React Flow, Zustand)
|
|
|
|
|
+-- IndexedDB (Dexie)
|
|
| +-- cartes privees/locales <-- ne quittent jamais le navigateur
|
|
|
|
|
+-- API, uniquement pour les fonctions serveur (partage)
|
|
|
|
|
v
|
|
FastAPI (+ fichiers statiques du frontend)
|
|
|
|
|
v
|
|
SQLite
|
|
|
|
|
+-- cartes explicitement partagees
|
|
```
|
|
|
|
Choix structurants :
|
|
|
|
| Sujet | Choix | Pourquoi |
|
|
|---|---|---|
|
|
| Hierarchie | `parentId` fait autorite, les liens en sont derives | un seul invariant a maintenir, pas de desynchronisation |
|
|
| Ordre des freres | `node.order`, renumerote a chaque modification | une source par preoccupation : la relation d'un cote, la position de l'autre |
|
|
| Modele | types metier propres, adaptes vers React Flow a l'affichage | le format de fichier ne depend pas de la bibliotheque de rendu |
|
|
| Historique | instantanes `{nodes, edges}`, groupes par action | un glisser-deposer = une seule annulation |
|
|
| Auto-layout | dagre, declenche uniquement a la demande | les deplacements manuels ne sont jamais ecrases |
|
|
| Versionnement | champ `version` + point d'extension de migration | le format peut evoluer sans casser les fichiers existants |
|
|
|
|
---
|
|
|
|
## Variables d'environnement
|
|
|
|
Toutes definies dans `.env` (modele : `.env.example`). Aucun secret n'est requis.
|
|
|
|
| Variable | Defaut | Role |
|
|
|---|---|---|
|
|
| `APP_NAME` | `OpenMind` | Nom du produit, affiche dans l'interface et l'onglet |
|
|
| `APP_PORT` | `8080` | Port publie sur l'hote (le conteneur ecoute toujours 8080) |
|
|
| `DATABASE_URL` | `sqlite:////app/data/openmind.db` | Base des cartes partagees |
|
|
| `MAX_MAP_SIZE_MB` | `10` | Taille maximale d'une carte envoyee au serveur |
|
|
| `MAX_NODES` | `5000` | Nombre maximal de noeuds par carte |
|
|
| `CORS_ORIGINS` | vide | Origines externes autorisees, separees par des virgules |
|
|
| `LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
|
|
Le nom du produit est centralise : cote backend via `APP_NAME`, cote frontend
|
|
dans `frontend/src/config/app.ts`. Le frontend recupere le nom a l'execution via
|
|
`GET /api/config` : changer `APP_NAME` suffit, sans recompiler.
|
|
|
|
---
|
|
|
|
## Donnees et sauvegarde
|
|
|
|
Deux stockages **totalement distincts** :
|
|
|
|
| Donnee | Ou | Sauvegarde |
|
|
|---|---|---|
|
|
| Cartes privees | IndexedDB du navigateur | Export `.mmap` depuis le tableau de bord ou l'editeur |
|
|
| Cartes partagees | SQLite, dans `./data/openmind.db` | Copie du dossier `./data` |
|
|
|
|
Le dossier `./data` est monte en **bind mount** (aucun volume Docker nomme) :
|
|
les fichiers sont donc directement lisibles depuis l'hote.
|
|
|
|
```bash
|
|
docker compose stop
|
|
tar czf openmind-partages-$(date +%F).tar.gz data/
|
|
docker compose start
|
|
```
|
|
|
|
SQLite fonctionne en mode WAL : `openmind.db`, `openmind.db-wal` et
|
|
`openmind.db-shm` doivent etre sauvegardes ensemble.
|
|
|
|
Redemarrer le serveur n'a **aucun impact** sur les cartes privees locales.
|
|
|
|
---
|
|
|
|
## Fonctionnement du partage
|
|
|
|
Une carte n'est envoyee au serveur que lorsque vous cliquez sur **Partager**,
|
|
apres une explication et une confirmation.
|
|
|
|
Deux secrets distincts sont generes cote serveur avec le module Python
|
|
`secrets` :
|
|
|
|
| Jeton | Entropie | Ou il vit | A quoi il sert |
|
|
|---|---|---|---|
|
|
| `share_token` | `secrets.token_urlsafe(12)`, environ 96 bits | dans l'URL publique `/s/<token>` | **lecture seule** |
|
|
| `management_token` | `secrets.token_urlsafe(32)`, environ 256 bits | **uniquement** dans l'IndexedDB du proprietaire | modification et suppression |
|
|
|
|
Regles appliquees :
|
|
|
|
- Le `management_token` n'apparait **jamais** dans une URL, jamais dans un export
|
|
`.mmap`, jamais dans les logs.
|
|
- Il est transmis dans l'en-tete `X-Management-Token` pour `PUT` et `DELETE`.
|
|
- Seul son **hash SHA-256** est stocke en base ; la comparaison est faite en
|
|
temps constant avec `secrets.compare_digest`.
|
|
- Connaitre le `share_token` ne permet donc **ni** de modifier **ni** de
|
|
supprimer un partage.
|
|
- Le lien est **non repertorie** : il n'apparait dans aucune liste publique,
|
|
mais toute personne qui le connait peut consulter la carte.
|
|
|
|
Cycle de vie :
|
|
|
|
1. **Partager** : un instantane de la carte est copie sur le serveur, un lien est
|
|
renvoye, par exemple `http://localhost:8080/s/a8F3xP9k`.
|
|
2. **Mettre a jour le partage** : remplace l'instantane par la version actuelle.
|
|
Tant que vous ne le faites pas, le lien public montre l'ancienne version.
|
|
3. **Supprimer le partage** : efface l'instantane serveur. Le lien affiche alors
|
|
une page propre indiquant que le partage n'existe plus. La carte locale n'est
|
|
pas affectee.
|
|
|
|
Si vous supprimez une carte locale, sa reference de partage locale part avec
|
|
elle : supprimez d'abord le partage si vous voulez aussi retirer la copie
|
|
serveur.
|
|
|
|
### API
|
|
|
|
| Methode | Route | Auth | Role |
|
|
|---|---|---|---|
|
|
| `GET` | `/api/health` | - | Etat du service |
|
|
| `GET` | `/api/config` | - | Nom de l'application et limites |
|
|
| `POST` | `/api/shares` | - | Creer un partage |
|
|
| `GET` | `/api/shares/{share_token}` | - | Lire une carte partagee |
|
|
| `PUT` | `/api/shares/{share_token}` | `X-Management-Token` | Mettre a jour |
|
|
| `DELETE` | `/api/shares/{share_token}` | `X-Management-Token` | Supprimer |
|
|
|
|
Documentation interactive : `http://localhost:8080/api/docs`.
|
|
|
|
---
|
|
|
|
## Fonctionnalites
|
|
|
|
**Tableau de bord** : creation, import, recherche, date de derniere
|
|
modification, renommage, duplication, export, suppression avec confirmation.
|
|
Une carte d'exemple est creee au tout premier lancement.
|
|
|
|
**Editeur** : canvas infini, zoom, deplacement, zoom-to-fit, minimap, grille
|
|
optionnelle, creation/suppression/deplacement de noeuds, selection multiple,
|
|
connexion entre noeuds avec detection de boucle, edition du texte au
|
|
double-clic, undo/redo, copier/coller, duplication, sauvegarde automatique.
|
|
|
|
**Mind mapping** : noeud racine et noeuds standard, branches droites, courbes ou
|
|
Bezier, branches repliables avec compteur d'elements masques.
|
|
|
|
**Auto-layout**, uniquement sur demande : mind map droite, mind map gauche,
|
|
mind map bilaterale, arbre horizontal, arbre vertical. L'espacement est
|
|
configurable (distance parent/enfant et espacement entre branches), avec les
|
|
presets Compact, Normal et Aere. Choisir une disposition ou modifier
|
|
l'espacement ne deplace rien : c'est *Reorganiser* qui applique, ce qui protege
|
|
les positions placees a la main.
|
|
|
|
**Mind map bilaterale** : la racine reste au centre, les branches de niveau 1 se
|
|
repartissent a gauche et a droite, et tous les descendants heritent du cote de
|
|
leur branche. La repartition automatique equilibre les deux cotes selon la
|
|
taille des sous-arbres ; le cote se change ensuite a la main (*Placer la branche
|
|
a gauche / a droite*).
|
|
|
|
**Relations secondaires** : une connexion visuelle entre deux noeuds
|
|
quelconques, independante de la hierarchie. Elle ne modifie ni le parent, ni
|
|
l'ordre, ni le repli, et l'auto-layout l'ignore totalement. Libelle libre,
|
|
trace (Bezier, courbe, droite), couleur, epaisseur, trait plein ou pointille,
|
|
fleches aux deux extremites. Une relation dont une extremite est masquee par un
|
|
repli disparait de l'affichage et revient au deploiement.
|
|
|
|
**Modeles** : galerie locale de huit modeles (carte vide, brainstorming, gestion
|
|
de projet, architecture IT, reunion, SWOT, plan d'etude, planification
|
|
personnelle). Choisir un modele en cree une copie : l'original n'est jamais
|
|
modifie, et la carte obtenue est une carte comme les autres.
|
|
|
|
**Markdown** : import d'un fichier `.md` ou d'un texte colle, comme nouvelle
|
|
carte depuis le tableau de bord ou comme sous-arbre d'un noeud existant. Les
|
|
titres et les listes construisent la hierarchie ; les paragraphes et les blocs
|
|
de code rejoignent la note du noeud, en texte brut. Export inverse en `.md` :
|
|
contenu et hierarchie uniquement, sans positions ni couleurs, avec un
|
|
avertissement si la carte contient des relations secondaires.
|
|
|
|
**Proprietes du noeud**, regroupees en sections repliables (Contenu,
|
|
Typographie, Apparence, Mise en page, Branche) : texte, icone, URL, notes ;
|
|
police, taille, gras, italique, alignement horizontal et vertical, interligne ;
|
|
couleurs de fond/texte/bordure, forme (rectangle, arrondi, capsule, cercle),
|
|
ombre ; taille automatique ou personnalisee, largeur, hauteur, padding
|
|
horizontal et vertical, retour a la ligne, largeur maximale du texte ; style,
|
|
epaisseur et couleur de la branche. Un reglage s'applique a toute la selection,
|
|
avec un etat *Mixte* quand les valeurs different.
|
|
|
|
**Creation rapide** : *Ajouter plusieurs enfants* (une ligne = un noeud, avec
|
|
apercu), collage d'une liste multi-ligne directement sur un noeud, import d'une
|
|
arborescence indentee (espaces ou tabulations, puces retirees, indentation
|
|
incoherente signalee), ajout avant / apres un frere.
|
|
|
|
**Hierarchie** : ordre explicite des freres (monter, descendre, placer en
|
|
premier / en dernier), remontee d'un niveau, *Deplacer vers...* avec recherche
|
|
du nouveau parent, suppression d'un noeud en conservant ses enfants.
|
|
|
|
**Branches** : selectionner, dupliquer, copier / coller comme enfant, deplacer
|
|
sur le canvas avec ses descendants, replier / developper une branche, tout
|
|
replier, tout developper, replier au-dela d'un niveau donne.
|
|
|
|
**Mise en page visuelle** : redimensionnement des noeuds a la souris (une seule
|
|
annulation par redimensionnement), taille automatique qui epouse le contenu,
|
|
alignement et distribution d'une multi-selection, aimantation sur la grille,
|
|
guides d'alignement pendant le deplacement, copier / coller / reinitialiser le
|
|
style (Ctrl+Maj+C, Ctrl+Maj+V), menu contextuel par clic droit. Le style couvre
|
|
les couleurs, la forme, la typographie, la mise en page, la presentation de
|
|
l'icone et la branche ; l'icone elle-meme, comme le texte, la note et l'URL,
|
|
appartient au contenu du noeud et n'est jamais remplacee par un collage.
|
|
|
|
**Bibliotheque d'icones** : emojis et icones integrees (Lucide), avec position
|
|
(gauche, droite, au-dessus), taille et espacement configurables.
|
|
|
|
**Themes** : Default, Minimal, Dark, Midnight, Ocean, Forest, Pastel,
|
|
Monochrome, Blueprint. Plus un mode clair/sombre pour l'interface. Changer de
|
|
theme ne supprime jamais une personnalisation explicite d'un noeud.
|
|
|
|
**Recherche dans la carte** : resultats listes, navigation entre eux, centrage
|
|
et mise en evidence temporaire du noeud, depliage automatique des branches
|
|
repliees.
|
|
|
|
**Exports** : `.mmap` (format natif), Markdown (`.md`), PNG (x1, x2, fond transparent), SVG, PDF
|
|
(A4/A3, portrait/paysage, marges, adapter a une page ou taille reelle, fond,
|
|
titre, date), impression via `@media print`.
|
|
|
|
**PWA** : installable, et utilisable hors ligne pour les cartes locales une fois
|
|
l'application chargee.
|
|
|
|
---
|
|
|
|
## Raccourcis clavier
|
|
|
|
`Cmd` remplace `Ctrl` sur macOS.
|
|
|
|
| Raccourci | Action |
|
|
|---|---|
|
|
| `Tab` | Ajouter un noeud enfant |
|
|
| `Entree` | Ajouter un noeud frere |
|
|
| `Suppr` / `Retour arriere` | Supprimer la selection, hors saisie de texte |
|
|
| `F2` | Editer le texte du noeud selectionne |
|
|
| Double-clic | Editer le texte du noeud |
|
|
| `Ctrl+Z` | Annuler |
|
|
| `Ctrl+Y` ou `Ctrl+Shift+Z` | Retablir |
|
|
| `Ctrl+C` | Copier |
|
|
| `Ctrl+V` | Coller |
|
|
| `Ctrl+D` | Dupliquer |
|
|
| `Ctrl+Maj+C` | Copier le style du noeud (sans son icone) |
|
|
| `Ctrl+Maj+V` | Coller le style sur la selection |
|
|
| `Maj+Tab` | Remonter le noeud d'un niveau |
|
|
| `Maj+Entree` | Ajouter plusieurs enfants |
|
|
| `Alt+Haut` / `Alt+Bas` | Monter / descendre parmi les freres |
|
|
| `Alt` + glisser | Inverser le mode de deplacement de branche |
|
|
| `Ctrl+V` | Coller : noeuds OpenMind, sinon liste de texte en enfants |
|
|
| `Ctrl+A` | Tout selectionner |
|
|
| `Ctrl+S` | Sauvegarde explicite |
|
|
| `Ctrl+F` | Rechercher dans la carte |
|
|
| `F` | Adapter la carte a l'ecran |
|
|
| `Echap` | Quitter l'edition ou deselectionner |
|
|
|
|
---
|
|
|
|
## Format de fichier `.mmap`
|
|
|
|
Un `.mmap` est un fichier **JSON UTF-8** contenant le document complet de la
|
|
carte. Il ne contient jamais de jeton de partage.
|
|
|
|
Le format est en **version 3**.
|
|
|
|
| Version | Apport | Migration a l'import |
|
|
|---|---|---|
|
|
| v1 | format initial | `style.width` devient `layout.width` |
|
|
| v2 | mise en page des noeuds (`layout`, `iconStyle`, `branch`) | l'ordre des freres est materialise depuis leur position dans `nodes` |
|
|
| v3 | ordre explicite des freres (`node.order`) | aucune |
|
|
| v4 | cote des branches (`node.side`) et relations secondaires (`relations`) | aucune |
|
|
|
|
Les fichiers v1, v2 et v3 restent lisibles, et les cartes deja enregistrees dans
|
|
IndexedDB s'ouvrent sans modification. Le passage en v3 etait justifie : l'ordre
|
|
des freres fait partie de la semantique du document, et un lecteur v2
|
|
l'ignorerait en reordonnant silencieusement la carte. Les noeuds sont malgre
|
|
tout ecrits en parcours de profondeur, freres dans l'ordre, ce qui donne le bon
|
|
resultat meme a un lecteur qui ignore `order`.
|
|
|
|
Le passage en v4 l'est aussi : un lecteur v3 ignorerait `relations` et perdrait
|
|
donc une partie du document. La migration v3 -> v4 n'a en revanche rien a
|
|
inventer : aucun cote defini signifie "a repartir automatiquement", aucune
|
|
relation signifie "aucune". Une carte v3 s'affiche exactement comme avant.
|
|
|
|
Toute carte qui entre dans l'editeur passe par le meme point d'entree,
|
|
`normalizeMapDocument()`, quelle que soit sa provenance :
|
|
|
|
```
|
|
fichier .mmap ┐
|
|
IndexedDB ├─> validation -> migration de version -> normalisation -> modele courant
|
|
partage ┘
|
|
```
|
|
|
|
La normalisation est **non destructive** : le document fourni n'est jamais
|
|
modifie et rien n'est reecrit en base. Une carte v1 restee en IndexedDB s'ouvre
|
|
donc exactement comme avant, et n'est reenregistree au format courant que si
|
|
vous la modifiez.
|
|
|
|
```json
|
|
{
|
|
"version": 1,
|
|
"id": "uuid",
|
|
"name": "Architecture Homelab",
|
|
"createdAt": "2026-01-01T10:00:00.000Z",
|
|
"updatedAt": "2026-01-01T12:30:00.000Z",
|
|
"nodes": [
|
|
{
|
|
"id": "uuid",
|
|
"type": "root",
|
|
"text": "Infrastructure",
|
|
"position": { "x": 0, "y": 0 },
|
|
"parentId": null,
|
|
"order": 0,
|
|
"side": "right",
|
|
"collapsed": false,
|
|
"icon": "\ud83d\udda5\ufe0f",
|
|
"url": "https://exemple.fr",
|
|
"note": "Texte libre",
|
|
"style": {
|
|
"backgroundColor": "#eef2ff",
|
|
"shape": "rounded",
|
|
"textAlign": "center",
|
|
"verticalAlign": "middle",
|
|
"lineHeight": 1.35,
|
|
"fontFamily": "sans",
|
|
"shadow": "soft"
|
|
},
|
|
"layout": {
|
|
"widthMode": "fixed",
|
|
"width": 240,
|
|
"heightMode": "auto",
|
|
"paddingX": 14,
|
|
"paddingY": 8,
|
|
"wrap": true,
|
|
"maxTextWidth": 260
|
|
},
|
|
"iconStyle": { "kind": "emoji", "position": "left", "size": 16, "gap": 8 },
|
|
"branch": { "style": "bezier", "width": 2, "color": "#94a3b8" }
|
|
}
|
|
],
|
|
"edges": [{ "id": "e-a-b", "source": "a", "target": "b" }],
|
|
"relations": [
|
|
{
|
|
"id": "r-uuid",
|
|
"sourceId": "a",
|
|
"targetId": "b",
|
|
"label": "depend de",
|
|
"style": "bezier",
|
|
"color": "#a855f7",
|
|
"width": 2,
|
|
"dashed": true,
|
|
"markerEnd": "arrow"
|
|
}
|
|
],
|
|
"viewport": { "x": 0, "y": 0, "zoom": 1 },
|
|
"settings": {
|
|
"theme": "default",
|
|
"layout": "mindmap-right",
|
|
"branchStyle": "bezier",
|
|
"showGrid": true,
|
|
"layoutSpacing": { "parentGap": 100, "siblingGap": 50 },
|
|
"snapToGrid": false,
|
|
"snapGrid": 20,
|
|
"alignmentGuides": true,
|
|
"dragMovesBranch": false
|
|
}
|
|
}
|
|
```
|
|
|
|
Deux precisions sur ce document :
|
|
|
|
- `edges` est **derive** de `parentId` : il est recalcule a chaque modification
|
|
et n'est jamais une source de verite. `relations`, au contraire, est une
|
|
donnee a part entiere, que rien ne permet de deduire de la hierarchie.
|
|
- `side` n'a de sens que sur un enfant direct d'une racine. Tous ses descendants
|
|
heritent du cote de leur branche, ce qui evite d'entretenir la meme
|
|
information sur chaque noeud.
|
|
|
|
A l'import, le fichier est **valide et normalise** : structure, version, champs
|
|
essentiels, limite de noeuds, assainissement des URL (`javascript:` et `data:`
|
|
sont supprimes), rejet des styles invalides. Le contenu n'est jamais execute ni
|
|
interprete comme du HTML ; un fichier invalide produit un message clair.
|
|
|
|
Le champ `version` permet les migrations futures : le point d'extension se
|
|
trouve dans `frontend/src/services/mapSerialization.ts`, fonction `migrate`.
|
|
|
|
---
|
|
|
|
## Developpement
|
|
|
|
### Backend
|
|
|
|
```bash
|
|
cd backend
|
|
python -m venv .venv
|
|
source .venv/bin/activate
|
|
pip install -r requirements-dev.txt
|
|
DATABASE_URL=sqlite:///./openmind-dev.db STATIC_DIR=./static uvicorn app.main:app --reload --port 8080
|
|
```
|
|
|
|
Sous Windows, activez l'environnement avec `.venv\Scripts\activate`.
|
|
|
|
### Frontend
|
|
|
|
```bash
|
|
cd frontend
|
|
npm install
|
|
npm run dev
|
|
```
|
|
|
|
Le serveur Vite ecoute sur `http://localhost:5173` et proxyfie `/api` vers
|
|
`http://localhost:8080`.
|
|
|
|
Autres commandes :
|
|
|
|
```bash
|
|
npm run build
|
|
npm run lint
|
|
npm run typecheck
|
|
npm run test
|
|
```
|
|
|
|
Regenerer les icones de la PWA :
|
|
|
|
```bash
|
|
python scripts/generate_icons.py
|
|
```
|
|
|
|
---
|
|
|
|
## Tests et qualite
|
|
|
|
Backend :
|
|
|
|
```bash
|
|
cd backend
|
|
python -m pytest -q
|
|
ruff check .
|
|
ruff format --check .
|
|
```
|
|
|
|
Frontend :
|
|
|
|
```bash
|
|
cd frontend
|
|
npm run test
|
|
npm run lint
|
|
npm run typecheck
|
|
```
|
|
|
|
Sans installation locale de Node ou Python, tout peut tourner via Docker :
|
|
|
|
```bash
|
|
docker run --rm -v "$PWD/backend:/work" -w /work python:3.12-slim sh -c "pip install -q -r requirements-dev.txt && ruff check . && python -m pytest -q"
|
|
```
|
|
|
|
```bash
|
|
docker run --rm -v "$PWD/frontend:/app" -w /app node:20-alpine sh -c "npm ci && npm run lint && npm run test && npm run build"
|
|
```
|
|
|
|
Les tests backend couvrent : healthcheck, creation, lecture, mise a jour et
|
|
suppression d'un partage, refus avec un mauvais jeton de gestion, refus sans
|
|
jeton, partage inexistant, payload invalide, limites de taille et de noeuds,
|
|
non-divulgation du jeton de gestion, repli SPA, acceptation des documents v3 et
|
|
v4 (ordre des freres, cotes, relations) et refus d'une relation incoherente.
|
|
|
|
Les tests frontend couvrent : stockage IndexedDB, serialisation et validation
|
|
d'import, migrations v1 -> v4, non-mutation et idempotence de la normalisation,
|
|
store Zustand (creation, suppression de branche, undo/redo, groupement du
|
|
glisser-deposer, copier/coller, duplication, detection de boucle, recherche,
|
|
auto-layout, lecture seule), repartition et heritage des cotes, auto-layout
|
|
bilateral, relations secondaires (creation, refus, style, repli, duplication,
|
|
independance vis-a-vis de la hierarchie), analyse et export Markdown, galerie de
|
|
modeles, assainissement des URL.
|
|
|
|
---
|
|
|
|
## Structure du projet
|
|
|
|
```
|
|
.
|
|
├── Dockerfile # build multi-stage : node -> python -> image finale
|
|
├── docker-compose.yml # un seul service, bind mount ./data
|
|
├── .env.example
|
|
├── README.md
|
|
├── scripts/
|
|
│ └── generate_icons.py # generation des icones PWA
|
|
├── data/ # bind mount : SQLite des partages
|
|
├── backend/
|
|
│ ├── requirements.txt
|
|
│ ├── pyproject.toml # configuration ruff et pytest
|
|
│ ├── app/
|
|
│ │ ├── main.py # assemblage uniquement
|
|
│ │ ├── config.py
|
|
│ │ ├── logging_config.py
|
|
│ │ ├── middleware.py # en-tetes de securite, limite de taille
|
|
│ │ ├── web.py # service des fichiers statiques du frontend
|
|
│ │ ├── api/ # routes health, config, shares
|
|
│ │ ├── db/ # moteur, session, initialisation du schema
|
|
│ │ ├── models/ # SharedMap (SQLAlchemy)
|
|
│ │ ├── schemas/ # validation Pydantic
|
|
│ │ └── services/ # jetons, logique de partage, erreurs metier
|
|
│ └── tests/
|
|
└── frontend/
|
|
├── index.html
|
|
├── public/ # manifest PWA, service worker, icones
|
|
└── src/
|
|
├── config/app.ts # nom du produit et constantes
|
|
├── types/map.ts # modele metier versionne
|
|
├── db/ # schema Dexie
|
|
├── services/ # depots, serialisation, client API
|
|
├── stores/ # Zustand : editeur, interface, configuration
|
|
├── hooks/ # routeur minimal, debounce
|
|
├── components/ # briques d'interface reutilisables
|
|
├── features/
|
|
│ ├── editor/ # canvas, noeud, barre d'outils, panneaux, layout, bilateral, relations, themes
|
|
│ ├── templates/ # galerie et definitions des modeles
|
|
│ ├── maps/ # tableau de bord
|
|
│ ├── export/ # PNG, SVG, PDF, impression
|
|
│ └── sharing/ # dialogue de partage, page publique
|
|
├── utils/
|
|
└── styles/
|
|
```
|
|
|
|
---
|
|
|
|
## Securite
|
|
|
|
- Le texte d'un noeud est **toujours** traite comme du texte. Aucun
|
|
`dangerouslySetInnerHTML`, aucun HTML issu d'une carte n'est interprete.
|
|
- Les URL sont assainies : seuls `http`, `https` et `mailto` sont acceptes. Les
|
|
liens s'ouvrent avec `rel="noopener noreferrer nofollow"`.
|
|
- Validation Pydantic de tout ce qui entre dans l'API, avec limites de taille et
|
|
de nombre de noeuds ; un depassement produit un message clair, jamais un
|
|
plantage.
|
|
- En-tetes de securite : CSP stricte, `X-Content-Type-Options`,
|
|
`X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`,
|
|
`Cross-Origin-Opener-Policy`.
|
|
- CORS restrictif : aucune origine externe par defaut.
|
|
- Acces SQL exclusivement via l'ORM SQLAlchemy.
|
|
- Jetons cryptographiquement aleatoires, jeton de gestion stocke hashe et jamais
|
|
journalise.
|
|
- Le conteneur tourne avec un utilisateur non-root.
|
|
- Aucun secret dans le frontend.
|
|
|
|
---
|
|
|
|
## Limitations connues
|
|
|
|
- **Export SVG** : les noeuds sont encapsules dans un `foreignObject` (HTML
|
|
embarque). Le fichier reste vectoriel et se redimensionne sans perte, les
|
|
branches sont de vrais chemins SVG, mais certains editeurs vectoriels
|
|
n'exploitent pas le contenu HTML des noeuds. Preferez PNG ou PDF pour un usage
|
|
bureautique.
|
|
- **Exports image et PDF en arriere-plan** : la generation s'appuie sur une
|
|
frame d'animation du navigateur, qui n'est pas produite lorsque l'onglet est
|
|
masque. Gardez l'onglet au premier plan pendant un export ; sinon l'operation
|
|
est interrompue au bout de 45 secondes avec un message explicite.
|
|
- **PDF en taille reelle** : si la carte depasse la page choisie, elle est
|
|
reduite pour tenir entierement plutot que d'etre coupee ; un message le
|
|
signale.
|
|
- **Gestion d'un partage** : le jeton de gestion vit dans l'IndexedDB du
|
|
navigateur d'origine. Depuis un autre appareil, un partage ne peut etre ni mis
|
|
a jour ni supprime.
|
|
- **Dispositions reservees** : `organization` et `freeform` existent dans le
|
|
modele de donnees mais ne sont pas proposees dans l'interface.
|
|
- **Relations et export Markdown** : l'export `.md` decrit le contenu et la
|
|
hierarchie. Les relations secondaires n'y ont pas de representation fidele et
|
|
ne sont pas inventees ; un message le signale au moment de l'export.
|
|
- **Relations et minimap** : la minimap n'affiche que les noeuds. Les relations
|
|
y sont invisibles, comme les branches hierarchiques.
|
|
- **Doublon de relation** : deux relations identiques (meme source vers meme
|
|
cible) se superposeraient, la seconde serait donc invisible et impossible a
|
|
selectionner. Une seule est acceptee par couple ; le sens inverse, lui, reste
|
|
autorise.
|
|
- **Relation dupliquee** : dupliquer ou coller une branche reporte les relations
|
|
dont les deux extremites sont dans la branche. Une relation qui sort vers un
|
|
noeud exterieur n'est pas recreee : rien ne dit que la copie doit viser la
|
|
meme cible, et le deviner creerait un lien non demande.
|
|
- **Bilaterale et racines multiples** : le recalage des deux cotes se fait sur
|
|
la premiere racine. Une carte a plusieurs racines reste affichee, mais les
|
|
racines suivantes ne sont pas recentrees individuellement.
|
|
- **Types de noeuds reserves** : `note`, `image`, `group`, `link` et `task` sont
|
|
acceptes par le format, mais la V1 ne produit que `root` et `standard`.
|
|
- **Migrations de schema SQLite** : la V1 initialise le schema avec
|
|
`create_all`, une seule table. Alembic n'est pas utilise ; il faudra
|
|
l'introduire au premier changement de schema.
|
|
- **Guides d'alignement** : ils apparaissent pendant un deplacement lorsqu'un
|
|
bord ou un centre coincide (a 6 px pres) avec celui d'un autre noeud. Ils sont
|
|
purement visuels : ils n'aimantent pas le noeud. Pour aimanter, activez
|
|
*Aimanter sur la grille*.
|
|
- **Alignement vertical** : il n'a d'effet visible que si le noeud est plus haut
|
|
que son texte, c'est-a-dire avec une hauteur fixe ou un padding vertical
|
|
important.
|
|
- **Cercle** : un noeud circulaire garde toujours un rapport 1:1. Modifier sa
|
|
largeur ajuste sa hauteur et inversement ; convertir un noeud rectangulaire en
|
|
cercle le ramene a un carre. Il n'est donc pas possible d'obtenir un ovale.
|
|
- **Plusieurs racines** : le modele autorise depuis la v1 une carte a plusieurs
|
|
racines, et certaines cartes existantes en dependent ; ce n'est donc pas
|
|
interdit. Une carte mentale classique n'en a qu'une, et l'interface pousse
|
|
dans ce sens : *Detacher* est une action explicite, et un noeud detache
|
|
devient une racine a part entiere. Un noeud ne peut jamais devenir son propre
|
|
parent ni celui d'un de ses ancetres.
|
|
- **Reordonner ne deplace pas les noeuds sur le canvas** : l'ordre est logique.
|
|
Il se voit immediatement dans l'ordre des liens et dans le resultat de
|
|
*Reorganiser* ; les positions libres, elles, restent celles choisies par
|
|
l'utilisateur.
|
|
- **Deplacement sur une tres grande carte** : sur une carte de ~180 noeuds, une
|
|
frame de glisser-deposer coute environ 60 a 80 ms car React Flow recalcule
|
|
l'ensemble des noeuds controles. Le deplacement reste utilisable mais moins
|
|
fluide que sur une carte de taille courante.
|
|
- **Mobile** : consultation et edition basique fonctionnent, mais l'interface
|
|
est concue pour desktop et tablette.
|
|
- **Aucun compte utilisateur** : c'est voulu. Pas de connexion, pas de
|
|
collaboration temps reel, pas de synchronisation cloud.
|
|
|
|
---
|
|
|
|
## Licences des dependances
|
|
|
|
Toutes les dependances sont sous licence libre permissive, compatible avec une
|
|
application publique et gratuite. Aucune bibliotheque a licence commerciale
|
|
n'est utilisee.
|
|
|
|
| Dependance | Licence |
|
|
|---|---|
|
|
| React, React DOM | MIT |
|
|
| `@xyflow/react` (React Flow) | MIT |
|
|
| Zustand | MIT |
|
|
| Dexie | Apache-2.0 |
|
|
| dagre | MIT |
|
|
| html-to-image | MIT |
|
|
| jsPDF | MIT |
|
|
| lucide-react | ISC |
|
|
| Vite, Tailwind CSS, TypeScript, ESLint, Vitest | MIT |
|
|
| FastAPI, Starlette, Pydantic, SQLAlchemy, Uvicorn | MIT / BSD |
|
|
|
|
---
|
|
|
|
## Licence
|
|
|
|
OpenMind est distribue sous licence **MIT** : voir le fichier [LICENSE](LICENSE).
|