Rend une instance exposee sur Internet exploitable sans la transformer en infrastructure : le produit reste local-first, sans compte et sans telemetrie. Protection du service : limitation de debit en memoire par client et par famille d'endpoint (health et ready jamais limites), interrupteur SHARING_ENABLED, capacite maximale de partages, taille propre aux partages. X-Forwarded-For n'est cru que si TRUST_PROXY_HEADERS l'autorise, et on lit alors la derniere valeur, celle ajoutee par le proxy. Expiration facultative des partages (24 h a 90 jours, ou jamais) : un lien expire repond 410 et la page publique le dit, sans autre detail. Le proprietaire peut prolonger ; aucune purge automatique. Protection des liens non repertories : robots.txt, X-Robots-Tag et meta robots sur /s/ et /api/, Referrer-Policy no-referrer sur tout le site, redaction des jetons dans les journaux d'acces, share_token porte a 128 bits (les anciens restent valides). Schema gere par Alembic, applique au demarrage. Une base creee avant Alembic est estampillee puis migree : verifie sur la base reelle de developpement, 4 partages inchanges et expires_at NULL. Administration par CLI locale au conteneur (stats, liste, suppression, purge, sauvegarde a chaud via l'API SQLite, restauration prudente) : aucune interface /admin exposee. Le jeton de gestion peut enfin etre exporte dans un fichier .openmind-share puis reimporte depuis un autre navigateur, apres verification aupres du serveur. Il n'est toujours jamais ajoute a un export .mmap. Ajoute une page /privacy dont le texte est centralise, et corrige un bug trouve en observant les logs : Alembic, lance dans le processus applicatif, reappliquait la configuration de logs d'alembic.ini et desactivait les loggers existants, ce qui faisait disparaitre les journaux d'acces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
984 lines
40 KiB
Markdown
984 lines
40 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` |
|
|
|
|
Exposition publique :
|
|
|
|
| Variable | Defaut | Role |
|
|
|---|---|---|
|
|
| `SHARING_ENABLED` | `true` | `false` coupe entierement le service de partage ; les cartes locales continuent de fonctionner |
|
|
| `SHARE_CREATE_RATE_LIMIT` | `10/hour` | Limite de creation de partages, par client |
|
|
| `SHARE_READ_RATE_LIMIT` | `120/minute` | Limite de lecture d'un partage, par client |
|
|
| `SHARE_WRITE_RATE_LIMIT` | `60/hour` | Limite des mises a jour, changements d'expiration et suppressions |
|
|
| `MAX_SHARED_MAPS` | `10000` | Nombre total de partages heberges. `0` = illimite |
|
|
| `MAX_SHARE_SIZE_MB` | `0` | Taille maximale d'un partage. `0` reprend `MAX_MAP_SIZE_MB` |
|
|
| `DEFAULT_SHARE_EXPIRY_DAYS` | `30` | Duree de vie par defaut d'un nouveau partage. `0` = permanent |
|
|
| `ALLOW_PERMANENT_SHARES` | `true` | Autorise l'option "Jamais" dans le choix d'expiration |
|
|
| `TRUST_PROXY_HEADERS` | `false` | Croire `X-Forwarded-For`. **A n'activer que derriere un reverse proxy de confiance** |
|
|
|
|
Format des limites de debit : `<nombre>/<fenetre>`, ou la fenetre vaut `second`,
|
|
`minute`, `hour` ou `day`. Une valeur vide, `0` ou `off` desactive la limite
|
|
concernee.
|
|
|
|
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.
|
|
|
|
### Sauvegarde a chaud
|
|
|
|
SQLite fonctionne en mode WAL : les ecritures recentes vivent dans
|
|
`openmind.db-wal`, et un simple `cp openmind.db` peut donc produire une copie
|
|
incoherente. La commande de sauvegarde utilise l'API de sauvegarde de SQLite,
|
|
qui produit une base complete **pendant que l'application tourne** :
|
|
|
|
```bash
|
|
docker compose exec openmind python -m app.cli backup /app/data/backups/openmind-$(date +%F).db
|
|
```
|
|
|
|
Le fichier obtenu se trouve sur l'hote dans `./data/backups/`. C'est une base
|
|
SQLite ordinaire : elle s'ouvre avec n'importe quel outil, sans OpenMind.
|
|
|
|
A froid, une archive du dossier reste valable :
|
|
|
|
```bash
|
|
docker compose stop
|
|
tar czf openmind-partages-$(date +%F).tar.gz data/
|
|
docker compose start
|
|
```
|
|
|
|
### Restauration
|
|
|
|
```bash
|
|
docker compose exec openmind python -m app.cli restore /app/data/backups/openmind-2026-09-10.db --yes
|
|
docker compose restart
|
|
```
|
|
|
|
La commande verifie d'abord que le fichier est bien une base OpenMind, place une
|
|
copie de securite de la base actuelle dans `./data/backups/pre-restore-*.db`,
|
|
puis ecrit le contenu restaure. Le redemarrage n'est pas indispensable mais reste
|
|
recommande. Pour une restauration sans aucun risque, arretez le conteneur, copiez
|
|
le fichier a la place de `openmind.db` (en supprimant les `-wal` / `-shm`
|
|
associes), puis redemarrez.
|
|
|
|
> **Ne modifiez pas `openmind.db` depuis l'hote pendant que le conteneur tourne.**
|
|
> Les fichiers `-wal` / `-shm` de SQLite sont partages entre les processus qui
|
|
> ont la base ouverte : une ecriture concurrente depuis l'hote, a travers un bind
|
|
> mount, peut etre perdue ou desynchroniser l'application du fichier. Passez par
|
|
> la CLI dans le conteneur, ou arretez le conteneur.
|
|
|
|
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(16)`, 128 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.
|
|
- Les jetons emis avant le passage a 128 bits restent valides : la recherche se
|
|
fait par valeur exacte, sans contrainte de longueur.
|
|
|
|
Protection du lien :
|
|
|
|
- `robots.txt` interdit `/s/` et `/api/`, et les reponses de ces chemins portent
|
|
`X-Robots-Tag: noindex, nofollow, noarchive`. La page publique ajoute la balise
|
|
`<meta name="robots">` correspondante. Ce n'est pas une mesure de securite :
|
|
c'est le jeton qui autorise la lecture.
|
|
- `Referrer-Policy: no-referrer` est applique a tout le site. Un lien externe
|
|
clique depuis une carte partagee ne transmet donc pas l'URL `/s/<token>` au
|
|
site de destination. Les liens des noeuds s'ouvrent avec
|
|
`rel="noopener noreferrer nofollow"`.
|
|
- Les journaux applicatifs remplacent les jetons par `[REDACTED]` dans les
|
|
chemins `/s/...` et `/api/shares/...`, et designent un partage par une
|
|
empreinte courte non reversible. Les autres routes restent lisibles pour le
|
|
diagnostic. **Un reverse proxy place devant l'instance a ses propres journaux**,
|
|
qui ne dependent pas d'OpenMind : configurez-les separement si vous ne voulez
|
|
pas y voir apparaitre les jetons.
|
|
|
|
### Expiration
|
|
|
|
Un partage peut expirer automatiquement. La duree est choisie a la creation
|
|
(24 heures, 7 jours, 30 jours, 90 jours, ou jamais) et modifiable ensuite depuis
|
|
le dialogue de partage. `DEFAULT_SHARE_EXPIRY_DAYS` fixe la valeur par defaut de
|
|
l'instance, `ALLOW_PERMANENT_SHARES=false` retire l'option "Jamais".
|
|
|
|
- Un partage expire repond `410 Gone` et la page publique affiche *Ce partage a
|
|
expire*, sans autre detail.
|
|
- Il n'est pas supprime pour autant : son proprietaire peut le prolonger, et
|
|
l'administrateur le purger.
|
|
- Les partages crees avant l'introduction de l'expiration ont `expires_at = NULL`
|
|
et restent donc valides indefiniment.
|
|
|
|
### Recuperer la main sur un partage
|
|
|
|
Le jeton de gestion vit uniquement dans l'IndexedDB du navigateur d'origine.
|
|
Depuis le dialogue de partage, **Exporter la cle de gestion** produit un petit
|
|
fichier `.openmind-share` :
|
|
|
|
```json
|
|
{ "version": 1, "shareToken": "...", "managementToken": "...", "title": "..." }
|
|
```
|
|
|
|
Depuis un autre navigateur, ouvrez la carte correspondante puis **Importer une
|
|
cle de gestion** : la cle est verifiee aupres du serveur concerne avant d'etre
|
|
enregistree localement. Ce fichier contient un secret : il n'est jamais ajoute
|
|
automatiquement a un export `.mmap`, et son export est toujours une action
|
|
explicite.
|
|
|
|
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 |
|
|
| `GET` | `/api/ready` | - | Le service peut-il repondre (acces SQLite verifie) |
|
|
| `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 le contenu |
|
|
| `PATCH` | `/api/shares/{share_token}` | `X-Management-Token` | Modifier l'expiration |
|
|
| `POST` | `/api/shares/{share_token}/verify` | `X-Management-Token` | Verifier une cle de gestion |
|
|
| `DELETE` | `/api/shares/{share_token}` | `X-Management-Token` | Supprimer |
|
|
|
|
`/api/health` et `/api/ready` ne sont soumis a aucune limitation de debit : le
|
|
healthcheck du conteneur reste fiable meme sous charge. Toutes les routes de
|
|
partage repondent `503` quand `SHARING_ENABLED=false`, et `429` avec un en-tete
|
|
`Retry-After` quand une limite est atteinte.
|
|
|
|
Documentation interactive : `http://localhost:8080/api/docs`.
|
|
|
|
---
|
|
|
|
## Administration d'une instance
|
|
|
|
Il n'existe volontairement **aucune interface `/admin` exposee sur Internet**.
|
|
L'administration passe par une CLI locale au conteneur, donc reservee a qui a
|
|
deja un acces machine :
|
|
|
|
```bash
|
|
docker compose exec openmind python -m app.cli stats
|
|
docker compose exec openmind python -m app.cli shares list --limit 20
|
|
docker compose exec openmind python -m app.cli shares list --expired
|
|
docker compose exec openmind python -m app.cli shares delete <share-token> --yes
|
|
docker compose exec openmind python -m app.cli shares purge-expired --dry-run
|
|
docker compose exec openmind python -m app.cli shares purge-expired --yes
|
|
docker compose exec openmind python -m app.cli backup /app/data/backups/openmind.db
|
|
docker compose exec openmind python -m app.cli restore /app/data/backups/openmind.db --yes
|
|
```
|
|
|
|
| Commande | Role |
|
|
|---|---|
|
|
| `stats` | Nombre de partages (total, actifs, expires), capacite, taille de la base, plus ancien et plus recent |
|
|
| `shares list` | Listage. Options `--limit`, `--active`, `--expired` |
|
|
| `shares delete <token>` | Suppression administrative, sans jeton de gestion. Demande confirmation sauf `--yes` |
|
|
| `shares purge-expired` | Supprime physiquement les partages expires. `--dry-run` affiche sans rien supprimer |
|
|
| `backup <chemin>` | Sauvegarde a chaud via l'API SQLite. `--yes` ecrase un fichier existant |
|
|
| `restore <chemin>` | Restauration prudente : verification, copie de securite, puis remplacement |
|
|
|
|
Aucune commande n'affiche le jeton de gestion, son hash, ni le contenu des
|
|
cartes. Les jetons de partage y sont masques (`sbcY...BCyw`) : suffisant pour
|
|
identifier une ligne, insuffisant pour ouvrir le lien.
|
|
|
|
---
|
|
|
|
## Deploiement derriere un reverse proxy
|
|
|
|
Le conteneur ecoute simplement sur `8080` en HTTP. Le TLS est termine par le
|
|
reverse proxy ; OpenMind n'embarque ni certificat ni Traefik, et aucune solution
|
|
n'est imposee.
|
|
|
|
Points a respecter :
|
|
|
|
1. Transmettre l'hote et le protocole d'origine.
|
|
2. Activer `TRUST_PROXY_HEADERS=true` **uniquement** dans ce cas : la limitation
|
|
de debit lit alors la derniere valeur de `X-Forwarded-For`, celle ajoutee par
|
|
votre proxy. Laisse a `false`, un client direct pourrait forger cet en-tete et
|
|
changer d'identite a chaque requete.
|
|
3. Ne pas mettre en cache `/api/`.
|
|
|
|
Aucune variable `PUBLIC_BASE_URL` n'est necessaire : le serveur ne fabrique
|
|
jamais d'URL absolue (il renvoie un chemin relatif `/s/<token>`), et le lien
|
|
affiche est construit dans le navigateur a partir de `window.location.origin`.
|
|
Un utilisateur sur `https://mind.example.com` obtient donc un lien sur ce
|
|
domaine, sans configuration.
|
|
|
|
Exemple Nginx :
|
|
|
|
```nginx
|
|
server {
|
|
listen 443 ssl;
|
|
server_name mind.example.com;
|
|
|
|
# certificats geres par votre outillage habituel
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
client_max_body_size 12m; # >= MAX_MAP_SIZE_MB
|
|
}
|
|
}
|
|
```
|
|
|
|
Exemple Traefik (labels sur le service `openmind` du `docker-compose.yml`) :
|
|
|
|
```yaml
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.openmind.rule=Host(`mind.example.com`)
|
|
- traefik.http.routers.openmind.entrypoints=websecure
|
|
- traefik.http.routers.openmind.tls.certresolver=letsencrypt
|
|
- traefik.http.services.openmind.loadbalancer.server.port=8080
|
|
```
|
|
|
|
Les journaux du reverse proxy contiennent les URL demandees, jetons de partage
|
|
compris. Si cela vous derange, configurez leur format ou leur retention : c'est
|
|
independant d'OpenMind.
|
|
|
|
---
|
|
|
|
## Migrations de schema
|
|
|
|
Le schema SQLite est gere par **Alembic**. Les migrations sont appliquees
|
|
automatiquement au demarrage du conteneur, avant qu'uvicorn ne serve la moindre
|
|
requete :
|
|
|
|
```
|
|
docker compose up -d -> docker-entrypoint.sh -> alembic upgrade head -> uvicorn
|
|
```
|
|
|
|
Trois cas, tous pris en charge sans intervention :
|
|
|
|
| Etat de depart | Ce qui se passe |
|
|
|---|---|
|
|
| Base absente | Toutes les revisions s'appliquent |
|
|
| Base creee avant Alembic (`create_all`) | La revision initiale est **estampillee** (aucune donnee touchee), puis les suivantes s'appliquent |
|
|
| Base deja a jour | Rien a faire |
|
|
|
|
Manuellement :
|
|
|
|
```bash
|
|
docker compose exec openmind python -m app.db.migrations # applique head
|
|
docker compose exec openmind alembic current # revision courante
|
|
docker compose exec openmind alembic history # historique
|
|
```
|
|
|
|
| Revision | Apport |
|
|
|---|---|
|
|
| `0001_initial_schema` | Table `shared_maps` telle qu'elle existait avant Alembic |
|
|
| `0002_share_expiry` | Colonne `expires_at` nullable et son index |
|
|
|
|
---
|
|
|
|
## 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.
|
|
S'y ajoutent, pour l'exposition publique : limitation de debit (creation,
|
|
lecture, exemption du healthcheck, desactivation), coupure du partage, capacite
|
|
maximale, taille propre aux partages, expiration et prolongation, verification
|
|
d'une cle de gestion, resolution de l'adresse client derriere un proxy,
|
|
en-tetes d'indexation et de referrer, redaction des jetons dans les journaux,
|
|
migrations Alembic sur base neuve et sur base d'avant Alembic, et l'ensemble des
|
|
commandes de la CLI d'administration.
|
|
|
|
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
|
|
│ ├── alembic.ini # configuration Alembic (URL prise dans DATABASE_URL)
|
|
│ ├── migrations/ # revisions de schema
|
|
│ ├── app/
|
|
│ │ ├── main.py # assemblage uniquement
|
|
│ │ ├── cli.py # administration locale : stats, partages, sauvegarde
|
|
│ │ ├── config.py
|
|
│ │ ├── logging_config.py # format des logs et redaction des jetons
|
|
│ │ ├── middleware.py # en-tetes de securite, cache, limite de taille
|
|
│ │ ├── web.py # service des fichiers statiques du frontend
|
|
│ │ ├── api/ # routes health, ready, config, shares
|
|
│ │ ├── db/ # moteur, session, migrations
|
|
│ │ ├── models/ # SharedMap (SQLAlchemy)
|
|
│ │ ├── schemas/ # validation Pydantic
|
|
│ │ └── services/ # jetons, partage, limitation de debit, adresse client
|
|
│ └── tests/
|
|
└── frontend/
|
|
├── index.html
|
|
├── public/ # manifest PWA, service worker, icones
|
|
└── src/
|
|
├── config/ # nom du produit, constantes, texte de confidentialite
|
|
├── 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
|
|
│ ├── privacy/ # page /privacy
|
|
│ ├── 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`.
|
|
- **Limitation de debit en memoire** : les compteurs vivent dans le processus.
|
|
Un redemarrage du conteneur les remet a zero, et plusieurs workers uvicorn
|
|
auraient chacun les leurs. C'est un garde-fou contre la saturation triviale
|
|
d'une instance publique, pas un quota strict. Un reverse proxy peut ajouter sa
|
|
propre limitation si le besoin est plus exigeant.
|
|
- **Partage expire et information** : un lien expire repond `410` et non `404`,
|
|
ce qui revele qu'un partage a existe a cette adresse. C'est assume : sans cela,
|
|
la page publique ne pourrait pas expliquer la difference entre un lien expire
|
|
et un lien erronne. Rien d'autre n'est divulgue.
|
|
- **Purge des partages expires** : elle n'est pas automatique. Un partage expire
|
|
cesse d'etre lisible mais reste en base jusqu'a un
|
|
`shares purge-expired`, ce qui laisse a son proprietaire la possibilite de le
|
|
prolonger. Planifiez la commande si vous voulez liberer l'espace regulierement.
|
|
- **Ecriture concurrente sur la base SQLite** : ne modifiez pas `openmind.db`
|
|
depuis l'hote pendant que le conteneur tourne. Les fichiers `-wal` / `-shm`
|
|
sont partages entre processus ; a travers un bind mount, une ecriture externe
|
|
peut etre perdue ou desynchroniser l'application du fichier. Utilisez la CLI
|
|
dans le conteneur, ou arretez le conteneur.
|
|
- **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).
|