Phase 2 : mind map bilaterale, relations, templates et Markdown

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>
This commit is contained in:
2026-09-10 08:12:00 +02:00
parent 2afbf637ce
commit e3c530348e
33 changed files with 4151 additions and 104 deletions

112
README.md
View File

@@ -227,10 +227,36 @@ double-clic, undo/redo, copier/coller, duplication, sauvegarde automatique.
Bezier, branches repliables avec compteur d'elements masques.
**Auto-layout**, uniquement sur demande : mind map droite, mind map gauche,
arbre horizontal, arbre vertical. L'espacement est configurable (distance
parent/enfant et espacement entre branches), avec les presets Compact, Normal
et Aere. Modifier l'espacement ne deplace rien tant que l'organisation n'est pas
relancee.
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 ;
@@ -274,7 +300,7 @@ theme ne supprime jamais une personnalisation explicite d'un noeud.
et mise en evidence temporaire du noeud, depliage automatique des branches
repliees.
**Exports** : `.mmap` (format natif), PNG (x1, x2, fond transparent), SVG, PDF
**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`.
@@ -326,14 +352,20 @@ Le format est en **version 3**.
| 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 et v2 restent lisibles, et les cartes deja enregistrees dans
IndexedDB s'ouvrent sans modification. Le passage en v3 est justifie : l'ordre
des freres fait desormais partie de la semantique du document, et un lecteur v2
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 :
@@ -362,6 +394,8 @@ vous la modifiez.
"text": "Infrastructure",
"position": { "x": 0, "y": 0 },
"parentId": null,
"order": 0,
"side": "right",
"collapsed": false,
"icon": "\ud83d\udda5\ufe0f",
"url": "https://exemple.fr",
@@ -389,6 +423,19 @@ vous la modifiez.
}
],
"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",
@@ -398,11 +445,21 @@ vous la modifiez.
"layoutSpacing": { "parentGap": 100, "siblingGap": 50 },
"snapToGrid": false,
"snapGrid": 20,
"alignmentGuides": true
"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
@@ -488,12 +545,17 @@ docker run --rm -v "$PWD/frontend:/app" -w /app node:20-alpine sh -c "npm ci &&
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.
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, store Zustand (creation, suppression de branche,
undo/redo, groupement du glisser-deposer, copier/coller, duplication, detection
de boucle, recherche, auto-layout, lecture seule), assainissement des URL.
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.
---
@@ -535,7 +597,8 @@ de boucle, recherche, auto-layout, lecture seule), assainissement des URL.
├── hooks/ # routeur minimal, debounce
├── components/ # briques d'interface reutilisables
├── features/
│ ├── editor/ # canvas, noeud, barre d'outils, panneaux, layout, themes
│ ├── 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
@@ -583,9 +646,24 @@ de boucle, recherche, auto-layout, lecture seule), assainissement des URL.
- **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** : `mindmap-both`, `organization` et `freeform`
existent dans le modele de donnees mais ne sont pas proposees dans
l'interface.
- **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