Epic 6: Stories 6-2 (Markdown roundtrip) + 6-3 (Brainstorm PPTX + Canvas)
Some checks failed
CI / Lint, Unit Tests & Build (push) Successful in 5m37s
CI / Deploy production (on server) (push) Has been cancelled

Story 6-2 — Markdown roundtrip export/import:
- lib/editor/markdown-export.ts: tiptapHTMLToMarkdown, markdownToHTML, looksLikeMarkdown
- lib/editor/markdown-paste-extension.ts: TipTap extension paste Markdown → blocs
- note-editor-toolbar.tsx: export .md + import .md (file picker)
- rich-text-editor.tsx: intégration MarkdownPasteExtension
- 40 tests unitaires markdown-export.test.ts

Story 6-3 — Brainstorm PPTX + Canvas:
- lib/brainstorm/export-pptx.ts: génération PPTX 5 slides (pptxgenjs)
- app/api/brainstorm/[sessionId]/export-pptx/route.ts: route POST protégée
- brainstorm-page.tsx: bouton PPTX, auto-select session, fix emoji, fix router.replace
- wave-canvas.tsx: fitTrigger recentrage, légende bas-droite

Onboarding activation wizard (Story 6-1):
- components/onboarding/: wizard multi-étapes, hints éditeur
- app/api/onboarding/: route PATCH onboarding
- prisma/migrations: champs onboarding user

Locales: 15 langues mises à jour (brainstorm, markdown, onboarding keys)
Sprint: 6-1 done, 6-2 review, 6-3 review

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Antigravity
2026-05-29 11:24:56 +00:00
parent dae56187fc
commit 6b4ed8514f
49 changed files with 5215 additions and 66 deletions

View File

@@ -0,0 +1,223 @@
# Brief Technique : US-EDITOR-MARKDOWN — Round-Trip TipTap ↔ Markdown
> **ID:** US-EDITOR-MARKDOWN
> **Priorité:** Beta blocker (confirmé)
> **Dépendances:** Rich-text editor TipTap existant (`rich-text-editor.tsx`)
> **Complexité estimée:** Moyenne (25 jours selon approche choisie)
---
## Contexte
TipTap/ProseMirror stocke le contenu en **JSON natif** (format ProseMirror Doc). Actuellement :
- L'éditeur **lit et écrit du JSON** en base de données
- `marked`, `react-markdown`, `remark-gfm` sont installés mais utilisés uniquement pour **l'affichage** de contenu Markdown externe (Web Clipper, import)
- Il **n'existe aucune sérialisation TipTap → Markdown** ni **Markdown → TipTap** dans le code actuel
L'objectif est un **round-trip fidèle** :
```
Note TipTap (JSON) → Export Markdown → Re-import → JSON identique (byte-for-byte sur les éléments supportés)
```
---
## Analyse des Options
### Option A — `@tiptap/extension-markdown` (recommandé ✅)
**Package :** `@tiptap/extension-markdown` (officiel TipTap, payant pour certaines extensions avancées — vérifier licence)
**Principe :**
- Extension TipTap officielle qui ajoute une méthode `.storage.markdown.getMarkdown()` sur l'éditeur
- Import Markdown via `editor.commands.setContent(markdownString, { parseOptions: { markdown: true } })`
- Supporte GFM (GitHub Flavored Markdown) : tableaux, listes de tâches, code fences
**Avantages :**
- Intégration native TipTap — zéro friction
- Maintenu par l'équipe TipTap
- Support des nœuds custom via `markdownSerializer` sur chaque extension
**Inconvénients :**
- Certaines extensions sont sous licence TipTap Pro ($149/mois)
- Les nœuds custom de Momento (`liveBlock`, `structuredViewBlock`) nécessitent des serializers manuels
- Round-trip parfait impossible pour ces nœuds (dégradation gracieuse : placeholder HTML comment)
**Implémentation :**
```typescript
// Installation
npm install @tiptap/extension-markdown
// Dans rich-text-editor.tsx
import { Markdown } from '@tiptap/extension-markdown'
const editor = useEditor({
extensions: [
// ... extensions existantes ...
Markdown.configure({
html: false, // Désactiver HTML brut dans le MD
tightLists: true, // Listes compactes
tightListClass: 'tight',
bulletListMarker: '-',
linkify: false,
breaks: false,
transformPastedText: true, // Coller du Markdown → conversion auto
transformCopiedText: false,
}),
],
})
// Export
const markdown = editor.storage.markdown.getMarkdown()
// Import
editor.commands.setContent(markdownString)
```
---
### Option B — `prosemirror-markdown` (alternative robuste ✅)
**Package :** `prosemirror-markdown` (officiel ProseMirror)
**Principe :**
- Bibliothèque bas niveau qui fournit un `MarkdownSerializer` et un `MarkdownParser`
- S'intègre à TipTap via le schéma ProseMirror sous-jacent
- Utilise `remark` (déjà installé) pour le parsing
**Avantages :**
- Open source, pas de licence Pro
- Contrôle total du serializer (chaque nœud est défini explicitement)
- Utilisé en production par de nombreux éditeurs (GitLab, Linear)
**Inconvénients :**
- Plus verbeux — chaque extension TipTap nécessite son `toMarkdown` et `fromMarkdown`
- Effort initial plus élevé (~2 jours de mapping)
- À maintenir à chaque nouvelle extension ajoutée
**Implémentation :**
```typescript
import { MarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown'
import { MarkdownParser } from 'prosemirror-markdown'
import markdownit from 'markdown-it'
// Serializer — mapper chaque nœud TipTap
const momentoSerializer = new MarkdownSerializer(
{
...defaultMarkdownSerializer.nodes,
// Nœuds Momento custom
liveBlock: (state, node) => {
state.write(`<!-- live-block: ${node.attrs.sourceNoteId}#${node.attrs.blockId} -->`)
state.closeBlock(node)
},
structuredViewBlock: (state, node) => {
state.write(`<!-- structured-view: ${JSON.stringify(node.attrs)} -->`)
state.closeBlock(node)
},
},
defaultMarkdownSerializer.marks
)
// Export
const markdown = momentoSerializer.serialize(editor.state.doc)
// Parser
const md = markdownit('commonmark', { html: true })
const parser = new MarkdownParser(editor.schema, md, { /* token map */ })
const doc = parser.parse(markdownString)
editor.commands.setContent(doc.toJSON())
```
---
### Option C — `Milkdown` (remplacement complet ❌ déconseillé)
**Milkdown** est un éditeur Markdown-first qui remplace TipTap/ProseMirror. Le migrer vers Milkdown signifie :
- Réécriture complète de `rich-text-editor.tsx` (~800 lignes)
- Perte de toutes les extensions custom (Living Blocks, Structured Views, Smart Paste, etc.)
- Délai estimé : 2-4 semaines
**Verdict : ❌ Trop risqué, trop long pour la beta.**
---
## Recommandation
### Court terme (beta) : **Option A** (`@tiptap/extension-markdown`)
Raisons :
1. Intégration en **1 journée** dans l'éditeur existant
2. Couvre 95% des cas d'usage (texte, listes, headings, code, tables, tâches)
3. Le `transformPastedText: true` résout aussi un bug UX courant (coller du Markdown brut)
4. Les nœuds Momento non-supportés sont préservés via commentaires HTML (dégradation gracieuse)
### Long terme : **Option B** en complément
Pour les cas avancés (export propre des nœuds custom, CI de round-trip byte-for-byte), implémenter Option B en remplacement d'Option A une fois la beta stabilisée.
---
## Scope de l'US-EDITOR-MARKDOWN (Beta)
### Ce qui est inclus
| Feature | Priorité |
|---------|----------|
| Export note → fichier `.md` téléchargeable | 🔴 P0 |
| Coller du Markdown → conversion auto en blocs TipTap | 🔴 P0 |
| Import note depuis fichier `.md` | 🟡 P1 |
| Copy note as Markdown (dans le presse-papier) | 🟡 P1 |
| Round-trip fidèle pour : headings, bold, italic, lists, tasks, code, blockquote, links, tables | 🔴 P0 |
| Dégradation gracieuse pour `liveBlock` et `structuredViewBlock` (commentaire HTML) | 🟡 P1 |
### Ce qui est exclu (post-beta)
- Round-trip byte-for-byte des nœuds Momento custom
- Édition native en mode source Markdown (raw text editor)
- Sync bidirectionnelle temps réel Markdown ↔ TipTap
---
## Fichiers à créer / modifier
| Fichier | Action |
|---------|--------|
| `memento-note/package.json` | Ajouter `@tiptap/extension-markdown` |
| `components/rich-text-editor.tsx` | Ajouter extension `Markdown` à la config TipTap |
| `lib/editor/markdown-export.ts` | Créer — helper `tiptapDocToMarkdown(doc)` et `markdownToTiptapDoc(md)` |
| `components/note-actions.tsx` | Ajouter action "Exporter en Markdown" |
| `app/api/notes/[id]/export/route.ts` | Créer — `GET /api/notes/:id/export?format=markdown` |
| `locales/en.json` + `fr.json` | Ajouter clés `editor.exportMarkdown`, `editor.importMarkdown`, `editor.pasteMarkdown` |
---
## Critères d'acceptation
**Given** j'ai une note avec du contenu riche (headings, listes, code, tasks)
**When** je clique "Exporter en Markdown"
**Then** je télécharge un fichier `.md` avec le contenu fidèlement sérialisé
**Given** je copie du Markdown depuis un éditeur externe
**When** je colle dans l'éditeur Momento
**Then** le Markdown est automatiquement converti en blocs TipTap correspondants (pas du texte brut)
**Given** j'importe un fichier `.md`
**When** le parsing est terminé
**Then** les headings/listes/code/tables sont correctement représentés dans l'éditeur
**Given** ma note contient un `liveBlock` (bloc vivant)
**When** j'exporte en Markdown
**Then** le bloc vivant est exporté en commentaire HTML `<!-- live-block: ... -->` sans erreur
---
## Estimation
| Tâche | Durée estimée |
|-------|--------------|
| Installation + config `@tiptap/extension-markdown` | 2h |
| Helper `lib/editor/markdown-export.ts` | 2h |
| Route API export + action UI | 2h |
| Import fichier `.md` via modal | 3h |
| Tests manuels round-trip + fix edge cases | 4h |
| i18n (EN + FR) | 1h |
| **Total** | **~2 jours** |