Skip to content

Open and write FreeMind/Freeplane .mm maps - #1

Merged
thierrysays merged 5 commits into
mainfrom
mp-freemind-mm
Oct 18, 2026
Merged

thierrysays merged 5 commits into
mainfrom
mp-freemind-mm

Conversation

@Martin-PB

@Martin-PB Martin-PB commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Intention

Le format .mm de FreeMind/Freeplane est le format d'échange ouvert de facto pour les cartes heuristiques (c'est aussi le format d'export de Coggle). Cette PR est la première étape du programme de compatibilité lecture/écriture avec tous les formats de mind map : un format par PR.

Réalisation

  • src/freemind.ts (nouveau) : lecteur/écrivain .mm sur fichiers en clair et gzippés (décompression détectée par magic bytes).
  • src/files.ts : branche mm dans docBytes/parseDoc, filtre Ouvrir (.mm) et Enregistrer sous (« FreeMind / Freeplane (.mm) »).
  • src/App.tsx : entrée « FreeMind (.mm) » du menu Export, après OPML.
  • src/freemind.test.ts : 6 tests vitest (échantillon FreeMind, layout left, gzip, erreurs, aller-retour couleurs/texte/progression, hook map/Note de FreeMind 0.9).
  • src/files.test.ts : bloc d'aller-retour .mm via docBytes/parseDoc.
  • src/xmindxml.ts : le décodeur d'entités XML est exporté pour le lecteur.
  • README.md : documentation de l'ouverture/enregistrement .mm et des pertes à l'export.

Lecture : parseur XML strict (pas de DOCTYPE, entités prédéfinies seulement), textes (richcontent en conservant l'ordre des lignes via les tranches brutes), notes (richcontent et l'ancien hook map/Note), liens, liens entre topics (arrowlink), pliage, couleurs de fond et de texte, gras/italique, icônes de priorité (full-N) et de progression (0-100 %). Un sujet de premier niveau positionné à gauche ouvre une carte logique gauche ou bilatérale (réordonnancement droite d'abord).

Écriture : « Enregistrer sous .mm » + entrée Export (un <node> par feuille, <p> par ligne). Un nœud racine par sheet : les feuilles supplémentaires peuvent ne pas s'afficher dans FreeMind lui-même. Les structures au-delà des layouts latéraux, images, relations, résumés, styles par mot et positions flottantes ne sont pas transportées.

Ce que l'agent a fait et ce que l'humain a changé

  • Implémentation entièrement réalisée par l'agent (Vibe, Mistral, glm-5-latest-short), relue par Martin-PB.
  • Validé dans un jumeau exécutable en sandbox TypeScript : 45/45 contrôles passent (échantillon FreeMind avec richcontent/br/arrowlink/icônes/couleurs, remappage des ids, layout map/left, erreurs DOCTYPE/non-XML/racine absente, robustesse : échappement XML, hex sans #, multi-racines, attributs minuscules, repli sans tranche brute, arrowlink cassé).
  • Le test gzip est présent mais n'a pas pu être exécuté dans le sandbox (pas d'API de décompression) ; il tournera dans l'environnement Node 22 du dépôt.
  • npm test, tsc et npm run build n'ont PAS été tournés par l'agent (pas de Node dans le sandbox) : à lancer avant fusion (voir ci-dessous). Pas de CI automatique : ci.yml ne se déclenche que sur main et feat/*, pas sur les branches mp-*.
  • Jamais vérifié contre un vrai FreeMind/Freeplane : la conformité aux fichiers réels reste à contrôler humainement.

Vérifications humaines

  • npm test passe (tests existants + nouveaux).
  • npm run build (tsc + vite) passe.
  • Relecture du diff : pas de secret, pas de modification hors périmètre.
  • Ouvrir un .mm réel créé dans FreeMind/Freeplane, vérifier le rendu ; réexporter depuis Cartocortex et rouvrir dans Freeplane.

Mise à jour (correctif d'encodage) : le premier commit (d07eeb7) écrivait App.tsx et README.md avec leurs parties inchangées doublement encodées — tous les caractères non ASCII (raccourcis ⌘, flèches, points de suspension) étaient du mojibake. Le commit 75c75ab reconstruit ces deux fichiers depuis main avec les mêmes ajouts .mm : la branche ne change plus que ce que le support FreeMind demande. La PR #2 (MindManager .mmap) est basée sur la branche corrigée.

FreeMind's .mm is the de facto open interchange format for mind maps, so
Cartocortex reads and writes it. Reading is the strict XML parser from the
old .xmind files (no DOCTYPE, predefined entities only), on plain and
gzipped files: texts (richcontent keeps its line order), notes (richcontent
and the old map/Note hook), links, links between topics (arrowlink),
folding, fill and text colour, bold / italic, priority (full-N) and
progress (0%-100%) icons. Left-side top-level topics open as a logic-left
or two-sided map. Writing goes the other way (Save as .mm and a new
"Export FreeMind" button): one root node per sheet, so extra sheets may
not show in FreeMind itself, and structures beyond the sideways layouts,
pictures, relationships, summaries, per-word style and floating positions
are not carried. The xmindxml entity decoder is exported for the reader.

Agent: Vibe (Mistral, glm-5-latest-short)
Relu-par-humain: Martin-PB
The first commit of this branch wrote App.tsx and README.md with the
parts it did not mean to change double-encoded: every non-ASCII
character (the command-key shortcut signs, arrows, ellipses) had become
mojibake. Both files are rebuilt from main with the same .mm edits, so
the branch now only changes what the FreeMind support needs.

Agent: Vibe (Mistral, glm-5-latest-short)
Relu-par-humain: Martin-PB

@thierrysays thierrysays left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relecture de la PR : le code est propre, un seul test échoue, et c'est un défaut du test (pas du lecteur).

À corriger avant fusion : a gzipped .mm opens like a plain one (src/freemind.test.ts, ligne 54)

Le test compare deux lectures du même fichier avec toEqual, mais chaque lecture génère de nouveaux identifiants (crypto.randomUUID()), y compris pour topicLink (remappé vers ces ids). Les deux résultats ne sont donc jamais égaux. C'est l'unique échec : en local, tsc passe et 82 tests sur 83 passent.

Proposition : comparer les cartes sans les identifiants.

const noIds = (r: Node[]) => JSON.stringify(r, (k, v) => (k === "id" || k === "topicLink" ? undefined : v));
// ...
expect(noIds(await fromFreemindBytes(gz))).toBe(noIds(await fromFreemindBytes(plain)));

(à importer : Node depuis ./model, qui exporte déjà mk utilisé dans ce fichier.) Je n'ai pas exécuté ce correctif ; relance npm test après modification.

Point de CI. ci.yml ne se déclenche que sur main et feat/*, donc pas sur mp-*. Pour que la CI tourne sur la PR, deux options : nommer les branches feat/…, ou ajouter un déclencheur pull_request à ci.yml.

Reste à faire à la main (ta liste de vérifications) : ouvrir un vrai .mm créé dans FreeMind ou Freeplane, vérifier le rendu, puis réexporter depuis Cartocortex et rouvrir dans Freeplane. Ni l'agent ni la relecture n'ont pu le faire.

Le reste me va : lecteur XML strict (DOCTYPE refusé), couleurs validées en #rrggbb, échappement XML à l'écriture, et import qui ne remplace jamais le fichier source.

@thierrysays

Copy link
Copy Markdown
Collaborator

Correction à ma relecture : le point sur la CI était faux. ci.yml a bien un déclencheur pull_request, donc la CI tourne sur cette PR (c'est elle qui a fait apparaître l'échec du test gzip). Rien à changer côté workflow ; le reste de la relecture est inchangé.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Thierry Sayegh-Sauvage and others added 2 commits October 18, 2026 18:37
…ranch

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@thierrysays
thierrysays merged commit 8e91dcd into main Oct 18, 2026
2 checks passed
@thierrysays
thierrysays deleted the mp-freemind-mm branch October 18, 2026 20:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants