feat(editor): implement next-gen editor with unique gutter drag handle, block actions menu, smart paste transclusion, and redesigned inline structured view block (US-NEXTGEN-EDITOR, US-4)

This commit is contained in:
Antigravity
2026-05-27 21:39:21 +00:00
parent 493108f957
commit 07ace46dd3
17 changed files with 2402 additions and 619 deletions

View File

@@ -0,0 +1,476 @@
# US-4 Redesign — Vue Structurée Inline dans l'Éditeur
> **Status :** DRAFT — En attente de validation fondateur
> **Remplace :** US-4 « Bloc de Base de Données Relationnelle Inline » (DEPRECATED)
> **Epic :** US-NEXTGEN-EDITOR
> **Dépend de :** US-STRUCTURED-VIEWS ✅ (livré), US-LIVING-BLOCKS ✅ (livré)
---
## 1. Problem Statement — Pourquoi l'ancienne US-4 est morte
L'implémentation actuelle (fichiers `tiptap-database-block-extension.tsx`, `database-block-editor.tsx`, `database-block-types.ts`) a été rejetée pour les raisons suivantes :
### 1.1 Copie de démo marketing, pas une feature produit
Le code source dans `createDefaultDatabaseBlockData()` insère **Jules Verne** et **Liu Cixin** avec leurs œuvres en données pré-remplies. C'est une copie directe du prototype `architectural-grid1/ModernBlockNoteEditor.tsx` qui était conçu pour une landing page, pas pour un produit de prise de notes réel.
### 1.2 Modèle de données parallèle et isolé
L'implémentation stocke `dbAuthorsJson` et `dbBooksJson` comme attributs HTML dans le nœud TipTap :
```html
<div data-database-block="true" data-db-id="authors-works-abc123"
data-db-authors='[{"id":"a1","name":"Jules Verne"}]'
data-db-books='[{"id":"bk1","title":"Vingt Mille Lieues..."}]'>
```
Problèmes critiques :
- **Isolation totale** : ces données n'ont aucun lien avec le carnet, les notes, ni les propriétés Prisma.
- **Scalabilité zéro** : stocker des listes entières de rows dans les attributs d'un nœud TipTap = explostion de la taille du HTML à chaque sauvegarde.
- **Duplication du modèle** : `NotebookSchema` + `NotebookProperty` + `NoteProperty` existent déjà en BDD et fonctionnent.
### 1.3 Confusion utilisateur
Un utilisateur qui tape `/database` s'attend à voir ses propres données structurées — pas un jeu de rôle bibliothèque de SF. Cette confusion est documentée comme rejet non-négociable par le fondateur.
### 1.4 Audit du code à supprimer
| Fichier | Statut | Raison |
|---------|--------|--------|
| `memento-note/components/tiptap-database-block-extension.tsx` | **SUPPRIMER** | Extension TipTap avec modèle `dbAuthors`/`dbBooks` |
| `memento-note/components/database-block-editor.tsx` | **SUPPRIMER** | UI Auteurs & Œuvres, hardcodé |
| `memento-note/lib/editor/database-block-types.ts` | **SUPPRIMER** | Types + données Verne/Liu Cixin |
| `rich-text-editor.tsx` L28, L193-194, L395, L412-420, L1286, L1335-1338, L1397-1401 | **MODIFIER** | Retirer imports et références `/database` anciens |
| `block-action-menu.tsx` L15-17, L33, L39, L51, L134-135 | **MODIFIER** | Retirer option `database` du menu "Transformer en" |
| `locales/*.json` — clés `databaseBlock.*` | **SUPPRIMER** | ~15 clés obsolètes |
---
## 2. Personas & Jobs-to-be-Done
### Persona 1 — Chloé, chercheuse (carnet "Thèse Linguistique")
Chloé a configuré son carnet avec des propriétés : `Statut` (select), `Langue` (select), `Lu` (checkbox), `Source` (text). Elle a 47 notes dans ce carnet. Elle rédige un chapitre synthétique et veut **voir le tableau de ses sources directement sous son paragraphe d'introduction**, sans quitter l'éditeur.
**Job-to-be-done :** *"Quand je rédige, je veux voir le tableau de mes données structurées en contexte, sans naviguer vers le carnet."*
### Persona 2 — Mehdi, chef de projet (carnet "Sprint Q2 2026")
Carnet structuré avec propriétés `Statut` (Kanban), `Priorité`, `Assigné`. Il prend ses notes de réunion et veut **insérer une vue en lecture de son Kanban** pour partager l'état du sprint dans la note de compte-rendu.
**Job-to-be-done :** *"Dans ma note de réunion, je veux une vue rapide de l'état des tâches du sprint."*
### Persona 3 — Yasmin, étudiante (carnet "Lectures Persan")
Carnet avec propriétés `Lu` (checkbox), `Note` (number). Elle lit en persan (RTL). Elle veut **voir sa liste de lectures sous forme de tableau** inline dans ses notes de révision.
**Job-to-be-done :** *"Dans mes notes, je veux voir mon tableau de lectures avec les bonnes valeurs — en persan, dans le bon sens."*
---
## 3. Options Produit pour `/database` — Analyse & Recommandation
### Option A — Embed Structured View (Recommandée ✅)
**Description :** Le bloc inline affiche une vue filtrée (Table ou Galerie) du schéma du carnet courant. Données lues depuis l'API existante. Le bloc stocke seulement `notebookId`, `displayMode`, `filterJson` dans ses attributs TipTap.
**Avantages :**
- Réutilise `NotesStructuredTable` et `NotesGalleryView` existants (quasi plug-and-play).
- Données cohérentes avec `/home` (même source de vérité).
- Léger : 3 attributs string dans le nœud TipTap, pas de payload.
- Facile à migrer : si le schéma du carnet change, le bloc se met à jour automatiquement.
- Clair pour l'utilisateur : "c'est mon carnet, mes données."
**Risques :**
- Éditeur doit recevoir `notebookId` (actuellement absent — patch mineur dans `note-content-area.tsx`).
- Si le carnet n'a pas de schéma, le bloc doit le gérer gracieusement.
**Verdict :** Option principale retenue.
### Option B — Linked Database Block (style Notion)
**Description :** L'utilisateur choisit n'importe quel carnet structuré (pas forcément le carnet de la note courante), avec filtre et vue configurables dans le bloc.
**Avantages :** Plus puissant — permet de croiser des carnets.
**Risques :**
- UI de sélection de carnet complexe dans le NodeView.
- Scope plus large que ce que justifie une US-4 isolée.
- Cross-notebook = scope Living Blocks étendu → reporter à v2.
**Verdict :** Déféré à v2 après que l'Option A soit stabilisée.
### Option C — Mini-table locale (colonnes libres dans la note)
**Description :** Table simple dans la note, indépendante des carnets, avec colonnes définissables par l'utilisateur.
**Avantages :** Pas de dépendance à Structured Views.
**Risques :**
- Crée exactement le second système de données parallèle qu'on veut éviter.
- Ne répond pas aux personas (Chloé et Mehdi veulent leurs vraies données).
- Rejectée explicitement par le fondateur.
**Verdict :** Hors scope. Ne pas implémenter.
### Option D — Supprimer `/database`
**Description :** Retirer complètement la commande slash et l'option du menu.
**Avantages :** Simple, propre.
**Risques :**
- Perd une surface UX qui a du sens (une fois bien connectée).
- L'annonce "database inline" est une attente utilisateur légitime.
**Verdict :** Fallback si l'Option A prend trop de temps — mais l'Option A est faisable en taille M.
---
## 4. User Story & Critères d'Acceptation (Given/When/Then)
### US-4 (Nouvelle) : Vue Structurée de Carnet Inline dans l'Éditeur
**En tant que** rédacteur dans un carnet structuré,
**Je veux** insérer une vue en lecture de mon tableau de notes directement dans le corps de ma note,
**Afin de** voir mes données structurées en contexte, sans quitter l'éditeur ni naviguer vers le carnet.
---
#### Scénario 1 — Insertion dans un carnet structuré
**Given** que ma note est dans un carnet qui a un schéma (`NotebookSchema` défini)
**When** je tape `/database` dans l'éditeur
**Then** un bloc `structuredViewBlock` est inséré, affichant la vue Table du carnet (colonnes = propriétés du schéma, lignes = notes du carnet)
**And** aucune donnée de démo n'est insérée
**And** l'utilisateur peut modifier les valeurs simples directement dans le bloc (checkbox, select, texte) — les changements se sauvegardent via `PATCH /api/notes/:id/properties`
---
#### Scénario 2 — Carnet sans schéma
**Given** que ma note est dans un carnet sans `NotebookSchema`
**When** j'insère un bloc `/database`
**Then** le bloc affiche un message contextuel : *"Ce carnet n'a pas encore de vue structurée. Configurez-en une depuis l'en-tête du carnet."* avec un lien vers le wizard
**And** aucune donnée n'est chargée, aucun crash
---
#### Scénario 3 — Note sans carnet
**Given** que ma note n'appartient à aucun carnet (notebookId absent)
**When** j'insère un bloc `/database`
**Then** le bloc affiche : *"Ce bloc nécessite un carnet. Déplacez cette note dans un carnet pour l'utiliser."*
---
#### Scénario 4 — Migration des blocs legacy
**Given** qu'une note contient un ancien nœud `data-database-block="true"` (auteurs/œuvres)
**When** la note est ouverte
**Then** le bloc obsolète est silencieusement retiré (ou affiché comme placeholder "bloc obsolète")
**And** le reste du contenu de la note est intact
**And** aucun crash ne se produit
---
#### Scénario 5 — RTL / locale persane
**Given** que la langue de l'app est `fa` (persan, RTL)
**When** le bloc est affiché
**Then** le wrapper du bloc a `dir="auto"`, le tableau s'aligne correctement en RTL
**And** les libellés du bloc sont traduits en persan (via `useLanguage()`)
---
#### Scénario 6 — Bascule de vue (Table → Galerie)
**Given** que le bloc est inséré dans un carnet avec `illustrationSvg` ou couleur
**When** l'utilisateur clique sur le sélecteur de vue dans le bloc
**Then** il peut basculer entre `Table` et `Galerie`
**And** l'attribut `displayMode` du nœud TipTap est mis à jour
**And** la vue se met à jour sans re-insertion du bloc
---
## 5. Out of Scope Explicite
| Fonctionnalité | Raison |
|----------------|--------|
| Vue Kanban inline | Drag-and-drop dans un NodeView TipTap = mauvaise UX, reporté en v2 |
| Sélection d'un carnet différent de la note courante | Scope trop large → v2 |
| Formules / rollups calculés | Hors scope Structured Views v1 |
| Relations multi-carnets | Hors scope — nécessite migration Prisma dédiée |
| Filtre personnalisé via UI dans le bloc | v2 — le bloc affiche tout le carnet en v1 |
| Pagination dans le bloc | v2 si le carnet dépasse 50 notes |
| Export PDF/CSV depuis le bloc | Fonctionnalité séparée |
---
## 6. Composants Prototype à Référencer
| Composant prototype | Usage | Note |
|--------------------|-------|------|
| `architectural-grid/``NotesStructuredTable` pattern | Colonnes et cellules — **référence design** | Utiliser le composant prod `notes-structured-table.tsx` |
| `architectural-grid/``NotesGalleryView` pattern | Vue galerie — **référence design** | Utiliser le composant prod `notes-gallery-view.tsx` |
| `architectural-grid1/ModernBlockNoteEditor.tsx` L1704+ | **NE PAS COPIER** — démo marketing Verne/Liu Cixin | Audit uniquement |
> ⚠️ Le prototype `architectural-grid1` est la référence de ce qu'il **ne faut pas** shipper. Le prototype `architectural-grid` (sans `1`) est la référence de design courant.
---
## 7. Modèle de Données
### 7.1 Nœud TipTap — Attributs (référence légère uniquement)
```typescript
// Extension TipTap — tiptap-structured-view-block-extension.tsx
Node.create({
name: 'structuredViewBlock',
group: 'block',
atom: true,
draggable: true,
addAttributes() {
return {
notebookId: {
default: null,
parseHTML: (el) => el.getAttribute('data-sv-notebook-id'),
renderHTML: (attrs) => attrs.notebookId
? { 'data-sv-notebook-id': attrs.notebookId }
: {},
},
displayMode: {
default: 'table',
parseHTML: (el) => el.getAttribute('data-sv-mode') || 'table',
renderHTML: (attrs) => ({ 'data-sv-mode': attrs.displayMode }),
},
filterJson: {
default: '{}',
parseHTML: (el) => el.getAttribute('data-sv-filter') || '{}',
renderHTML: (attrs) => ({ 'data-sv-filter': attrs.filterJson }),
},
}
},
parseHTML: () => [{ tag: 'div[data-structured-view-block]' }],
renderHTML: ({ HTMLAttributes }) =>
['div', mergeAttributes(HTMLAttributes, { 'data-structured-view-block': 'true' })],
addNodeView: () => ReactNodeViewRenderer(StructuredViewBlockEmbed),
})
```
### 7.2 Aucune migration Prisma requise
Le bloc réutilise le schéma Prisma existant :
- `NotebookSchema` / `NotebookProperty` → définition des colonnes
- `NoteProperty` → valeurs par note
- `Note` → lignes du tableau
Aucune nouvelle table. Aucun risque BDD.
### 7.3 Diagramme de relations
```mermaid
graph TD
A["structuredViewBlock node<br/>(TipTap attrs)"] -->|"notebookId"| B["GET /api/notebooks/:id/schema"]
B --> C["NotebookSchema<br/>(Prisma)"]
C --> D["NotebookProperty[]<br/>(colonnes)"]
A -->|"notebookId"| E["GET /api/notebooks/:id/notes<br/>(avec properties)"]
E --> F["Note[]<br/>(lignes)"]
F --> G["NoteProperty[]<br/>(valeurs)"]
D & G --> H["NotesStructuredTable<br/>(composant prod réutilisé)"]
H --> I["Rendu inline dans l'éditeur"]
```
---
## 8. Plan de Migration & Rollback du Code Actuel
### 8.1 Ordre de suppression (pour éviter les imports cassés)
1. **Supprimer** `memento-note/lib/editor/database-block-types.ts`
2. **Supprimer** `memento-note/components/database-block-editor.tsx`
3. **Supprimer** `memento-note/components/tiptap-database-block-extension.tsx`
4. **Modifier** `block-action-menu.tsx` — retirer import + option `database`
5. **Modifier** `rich-text-editor.tsx` — retirer import + slash entry + extension registration
6. **Créer** `tiptap-structured-view-block-extension.tsx` + `structured-view-block-embed.tsx`
7. **Modifier** `note-content-area.tsx` — passer `notebookId`
8. **Modifier** `locales/en.json` + `fr.json` — swap clés i18n
### 8.2 Gestion des notes existantes avec l'ancien bloc
Le nœud `databaseBlock` sera inconnu de TipTap après suppression de l'extension. Par défaut ProseMirror le drop silencieusement. Options :
- **Option simple (v1) :** Laisser ProseMirror supprimer le nœud inconnu au premier rendu — aucune action développeur.
- **Option propre (recommandée) :** Ajouter dans la nouvelle extension une règle `parseHTML` qui matche `div[data-database-block]` et le convertit en paragraphe `⚠️ Bloc base de données obsolète — ce contenu a été retiré.`
### 8.3 Rollback possible
Si le fondateur rejette l'Option A après implémentation :
- Les 3 fichiers supprimés sont dans git history — `git checkout HEAD~1 -- <fichier>` les restaure.
- La suppression de l'extension de `rich-text-editor.tsx` est une ligne — réversible en 2 minutes.
---
## 9. Risques Performance & Mitigations
| Risque | Probabilité | Impact | Mitigation |
|--------|-------------|--------|-----------|
| NodeView React lourd — re-render à chaque transaction TipTap | Haute | Haute | `shouldRerenderOnTransaction: false` sur l'éditeur (US-EDITOR-PERF) + `React.memo` sur le bloc embed + `trackNodeViewPosition: false` |
| SWR fetch bloque le rendu initial de la note | Moyenne | Moyenne | Suspense avec skeleton loader ; ne pas bloquer le rendu éditeur ; lazy-mount après 500ms |
| Note dans carnet avec 200+ notes — tableau trop long | Basse | Moyenne | Afficher les 20 premières notes avec `"Voir toutes les N notes dans le carnet"` CTA |
| RTL — cellules du tableau cassées | Basse | Haute | `dir="auto"` sur le wrapper ; tester avec locale `fa` avant merge |
---
## 10. i18n — Clés à Ajouter / Supprimer
### 10.1 Clés à ajouter (EN/FR minimum)
| Clé | EN | FR |
|-----|----|----|
| `structuredViewBlock.insertLabel` | Structured View | Vue structurée du carnet |
| `structuredViewBlock.insertDesc` | Embed your notebook's structured data | Intégrez les données structurées de votre carnet |
| `structuredViewBlock.noSchema` | This notebook has no structured view yet. Set one up from the notebook header. | Ce carnet n'a pas encore de vue structurée. Configurez-en une depuis l'en-tête du carnet. |
| `structuredViewBlock.noNotebook` | This block requires a notebook. Move this note to a notebook first. | Ce bloc nécessite un carnet. Déplacez cette note dans un carnet pour l'utiliser. |
| `structuredViewBlock.openInNotebook` | Open in notebook | Ouvrir dans le carnet |
| `structuredViewBlock.displayModeTable` | Table | Tableau |
| `structuredViewBlock.displayModeGallery` | Gallery | Galerie |
| `structuredViewBlock.loadError` | Failed to load structured data. | Impossible de charger les données structurées. |
| `structuredViewBlock.retry` | Retry | Réessayer |
| `structuredViewBlock.deprecatedBlock` | Outdated block removed. | Bloc obsolète retiré. |
> **Note :** Si l'utilisateur demande à compléter les 15 locales, ne remplir que les clés — laisser la traduction des 13 autres locales à l'outil de traduction externe (règle AGENTS.md).
### 10.2 Clés à supprimer (après vérification pas d'autre consommateur)
`databaseBlock.title`, `databaseBlock.viewTable`, `databaseBlock.viewCards`, `databaseBlock.hint`, `databaseBlock.colAuthor`, `databaseBlock.colWorks`, `databaseBlock.colRollup`, `databaseBlock.noLinkedWorks`, `databaseBlock.deleteShort`, `databaseBlock.addAuthor`, `databaseBlock.authorPlaceholder`, `databaseBlock.createAuthor`, `databaseBlock.worksBase`, `databaseBlock.storedCount`, `databaseBlock.addWork`, `databaseBlock.bookTitlePlaceholder`, `databaseBlock.selectAuthor`, `databaseBlock.tagPlaceholder`, `databaseBlock.coverPlaceholder`, `databaseBlock.insertWork`, `databaseBlock.deleteCard`, `databaseBlock.insertFailed`, `databaseBlock.defaultTag`
---
## 11. Checklist QA Manuelle
### 11.1 Français (LTR)
- [ ] Taper `/database` dans une note d'un carnet structuré → bloc s'insère avec les données réelles
- [ ] Taper `/database` → même résultat (rétrocompatibilité keyword)
- [ ] Taper `/database` dans une note sans carnet → message "nécessite un carnet"
- [ ] Taper `/database` dans une note d'un carnet sans schéma → callout wizard avec lien
- [ ] Ouvrir une note avec un ancien bloc `databaseBlock` → note s'ouvre, bloc absent ou placeholder, pas de crash
- [ ] Menu "Transformer en" → option "Vue structurée" présente, option "Base de données" (ancienne) absente
- [ ] Bascule Table ↔ Galerie dans le bloc → vue change, pas de réinsertion du bloc
- [ ] `npm run build` → 0 erreur, aucun import vers fichiers supprimés
### 11.2 Persan / RTL (fa)
- [ ] Changer la langue en `fa` → libellés du bloc en persan
- [ ] Le bloc wrapper est `dir="auto"` → tableau aligné à droite
- [ ] Cellules du tableau RTL lisibles (pas de texte coupé)
- [ ] Callout "no schema" en persan → texte lisible RTL
---
## 12. Estimation Effort
| Tâche | Taille |
|-------|--------|
| Supprimer 3 fichiers legacy | XS |
| Patcher `rich-text-editor.tsx` + `block-action-menu.tsx` | S |
| Patcher `note-content-area.tsx` (`notebookId` prop) | XS |
| Créer `tiptap-structured-view-block-extension.tsx` | S |
| Créer `structured-view-block-embed.tsx` (SWR + états + RTL) | M |
| i18n EN + FR | XS |
| QA manuelle FR + fa | S |
| **Total estimé** | **M** (~12 jours dev) |
---
## 13. Décisions Produit Actées
### Édition inline — ACTIVÉE en v1
L'utilisateur peut modifier les valeurs simples directement dans le bloc (checkbox, select, texte court). Les changements se sauvegardent via `PATCH /api/notes/:id/properties` avec mise à jour optimiste. C'est la meilleure expérience utilisateur — un bloc en lecture seule n'aurait aucun intérêt.
### Kanban inline — REPORTÉ en v2
Table + Galerie uniquement en v1. Le drag-and-drop dans un NodeView TipTap crée de mauvaises interactions. Le Kanban s'utilisera depuis le carnet comme aujourd'hui.
---
## Annexe — Diff Proposé pour `docs/story-nextgen-editor.md`
Remplacer la section `### US-4: Bloc de Base de Données Relationnelle Inline` (lignes 7185) par :
```markdown
### US-4: Vue Structurée de Carnet Inline *(Redesign — voir `docs/story-nextgen-editor-us4-redesign.md`)*
> ⚠️ **DEPRECATED** — La spécification précédente (bloc "Auteurs & Œuvres") est rejetée.
> Voir [`docs/story-nextgen-editor-us4-redesign.md`](./story-nextgen-editor-us4-redesign.md) pour la nouvelle spec.
**En tant que** rédacteur dans un carnet structuré,
**Je veux** insérer une vue en lecture de mon tableau de notes directement dans le corps de ma note,
**Afin de** voir mes données structurées en contexte, sans quitter l'éditeur.
*(Critères d'acceptation détaillés dans le fichier redesign ci-dessus.)*
```
Et remplacer la section `### 1. Structure du Nœud de Base de Données Tiptap` + `### 2.` + `### 3.` dans les Spécifications Techniques (lignes 90109) par :
```markdown
### 1. Structure du Nœud `structuredViewBlock` (TipTap)
Extension TipTap `StructuredViewBlockExtension` dans `tiptap-structured-view-block-extension.tsx` :
- Attributs : `notebookId` (string), `displayMode` ('table'|'gallery'), `filterJson` (string JSON)
- ReactNodeViewRenderer → `structured-view-block-embed.tsx`
### 2. Suppression du code legacy
- Supprimer : `tiptap-database-block-extension.tsx`, `database-block-editor.tsx`, `lib/editor/database-block-types.ts`
- Modifier : `rich-text-editor.tsx`, `block-action-menu.tsx`, `locales/*.json`
- Voir plan complet dans `docs/story-nextgen-editor-us4-redesign.md` §8.
```
---
## Annexe — Diff Proposé pour `docs/user-stories.md`
### Dans le tableau de bord (ligne 23)
```diff
-| **US-NEXTGEN-EDITOR** | Éditeur Next-Gen : Drag Handle + Menu Bloc + DB Inline + Smart Paste | 🚧 **PLANIFIÉ** | Voir `docs/story-nextgen-editor.md` |
+| **US-NEXTGEN-EDITOR** | Éditeur Next-Gen : Drag Handle + Menu Bloc + Vue Structurée Inline + Smart Paste | 🚧 **PLANIFIÉ** | Voir `docs/story-nextgen-editor.md` + `docs/story-nextgen-editor-us4-redesign.md` |
```
### Dans la section US-NEXTGEN-EDITOR (US-4, ligne 622626)
```diff
-### US-4 : Bloc de Base de Données Relationnelle Inline
-- Slash `/database` → insère un React NodeView
-- Vues Tableau / Fiches avec Rollup dynamique
-- Modèle relationnel local (auteurs/livres par défaut, ou lié au carnet)
+### US-4 : Vue Structurée de Carnet Inline *(Redesign)*
+> Spec complète : [`docs/story-nextgen-editor-us4-redesign.md`](./story-nextgen-editor-us4-redesign.md)
+- Slash `/database` (+ keywords `db`, `tableau`, `structured`) → insère un `structuredViewBlock`
+- Affiche Table ou Galerie du carnet courant via l'API Structured Views existante
+- Bloc stocke uniquement `notebookId` + `displayMode` (pas de données en attrs)
+- Graceful fallback si carnet sans schéma ou note sans carnet
+- Supprime le code legacy `tiptap-database-block-extension.tsx` (Verne/Liu Cixin)
+- **Dépend de :** US-STRUCTURED-VIEWS ✅ (livré)
```
### Dans la liste de fichiers (ligne 627631)
```diff
-### Fichiers
-- `[NEW]` `tiptap-drag-handle-plugin.ts` — Plugin ProseMirror pur
-- `[NEW]` `tiptap-database-block-extension.tsx` — NodeView React
-- `[MODIFY]` `rich-text-editor.tsx` — Intégration drag handle + DB + paste intercept
-- `[MODIFY]` `globals.css` — Gutter, poignée, glassmorphic dropdowns
+### Fichiers
+- `[NEW]` `tiptap-drag-handle-plugin.ts` — Plugin ProseMirror pur (US-1, inchangé)
+- `[NEW]` `tiptap-structured-view-block-extension.tsx` — NodeView Vue Structurée (remplace DB)
+- `[NEW]` `structured-view-block-embed.tsx` — Composant embed avec SWR + states
+- `[DELETE]` `tiptap-database-block-extension.tsx` — Bloc legacy auteurs/œuvres rejeté
+- `[DELETE]` `database-block-editor.tsx` — UI legacy rejetée
+- `[DELETE]` `lib/editor/database-block-types.ts` — Types legacy rejetés
+- `[MODIFY]` `rich-text-editor.tsx` — Intégration drag handle + Vue Structurée + paste intercept
+- `[MODIFY]` `note-content-area.tsx` — Passer `notebookId` à l'éditeur
+- `[MODIFY]` `block-action-menu.tsx` — Remplacer option "Database" par "Vue structurée"
+- `[MODIFY]` `globals.css` — Gutter, poignée, glassmorphic dropdowns
```

View File

@@ -68,44 +68,54 @@ Pour offrir une expérience de saisie supérieure à celle de Notion (plus perfo
---
### US-4: Bloc de Base de Données Relationnelle Inline
**En tant que** chef de projet ou gestionnaire de données,
**Je veux** pouvoir insérer une base de données relationnelle interactive directement au milieu de mon texte, basculer ses vues et suivre des rollups,
**Afin de** structurer mes informations complexes sans quitter le flux de ma note (comme simulé dans le prototype).
### US-4: Vue Structurée de Carnet Inline *(Redesign — voir [`docs/story-nextgen-editor-us4-redesign.md`](./story-nextgen-editor-us4-redesign.md))*
#### Critères d'Acceptation :
* **Étant donné** que j'utilise l'éditeur de Momento
* **Quand** je saisis `/database` ou sélectionne "Base de données" dans le menu d'insertion
* **Alors** un bloc `databaseBlock` est inséré sous forme d'un React NodeView
* **Et** il propose par défaut un modèle relationnel "Auteurs & Œuvres" (avec des données simulées ou liées au carnet actuel)
* **Quand** je clique sur le sélecteur de vue en haut à droite du bloc
* **Alors** je peux basculer entre la vue **Tableau** (grille avec colonnes de propriétés) et la vue **Fiches** (cartes illustrées de couvertures, tags et auteurs)
* **Et** la colonne **Rollup Count** de la vue Tableau recalcule dynamiquement le nombre de livres liés à chaque auteur au fur et à mesure que j'ajoute ou supprime des fiches
* **Et** un formulaire d'insertion rapide en bas du bloc me permet d'ajouter des livres ou des auteurs à ce modèle relationnel local.
> ⚠️ **DEPRECATED** — La spec précédente (bloc "Auteurs & Œuvres") est **rejetée par le fondateur**.
> Le code legacy (`tiptap-database-block-extension.tsx`, `database-block-editor.tsx`, `database-block-types.ts`) doit être supprimé.
> La nouvelle spec complète (problème, options produit, Given/When/Then, modèle de données, migration, i18n, QA RTL) est dans [`docs/story-nextgen-editor-us4-redesign.md`](./story-nextgen-editor-us4-redesign.md).
**En tant que** rédacteur dans un carnet structuré,
**Je veux** insérer une vue en lecture de mon tableau de notes directement dans le corps de ma note,
**Afin de** voir mes données structurées en contexte, sans quitter l'éditeur.
#### Critères d'Acceptation (résumé) :
* **Étant donné** que ma note est dans un carnet structuré, **quand** je tape `/vue`, **alors** un bloc `structuredViewBlock` s'insère avec les données réelles du carnet — aucune donnée de démo.
* **Étant donné** que le carnet n'a pas de schéma, **alors** le bloc affiche un callout contextuel vers le wizard, pas une erreur.
* **Étant donné** qu'une note contient un ancien `databaseBlock`, **alors** il est retiré silencieusement sans crash.
*(Voir `docs/story-nextgen-editor-us4-redesign.md` pour les critères complets, le modèle de données, et la checklist QA FR+fa RTL.)*
---
## Spécifications Techniques d'Implémentation
### 1. Structure du Nœud de Base de Données Tiptap (`DatabaseBlock`)
Créer une extension Tiptap personnalisée `DatabaseBlockExtension` dans `/components/tiptap-database-block-extension.tsx` qui :
- Gère un groupe `block`, se comporte comme un atome (non modifiable directement en texte brut).
- Conserve les attributs : `dbId` (string), `dbView` ('table' | 'card'), `dbAuthors` (array) et `dbBooks` (array) représentant le schéma et les valeurs.
- Utilise un `ReactNodeViewRenderer` pour monter notre composant d'édition de base de données relationnelle en React pur.
### 1. Structure du Nœud `structuredViewBlock` (TipTap) — *US-4 Redesign*
### 2. Le Plugin Drag Handle & Gutter en ProseMirror
> ⚠️ **DEPRECATED :** L'ancienne section `DatabaseBlock` est remplacée. Voir [`docs/story-nextgen-editor-us4-redesign.md`](./story-nextgen-editor-us4-redesign.md) §7 pour le modèle complet.
Extension TipTap `StructuredViewBlockExtension` dans `tiptap-structured-view-block-extension.tsx` :
- Attributs : `notebookId` (string), `displayMode` ('table'|'gallery'), `filterJson` (string JSON `{}`)
- ReactNodeViewRenderer → `structured-view-block-embed.tsx`
- Aucune donnée de démo — aucun payload JSON d'auteurs/livres dans les attrs.
### 2. Le Plugin Drag Handle & Gutter en ProseMirror *(US-1, inchangé)*
Développer un plugin custom dans `/components/tiptap-drag-handle-plugin.ts` :
* Attacher un écouteur d'événements `mouseover` et `mousemove` sur l'éditeur ProseMirror.
* Utiliser `view.posAtCoords` pour localiser le nœud ProseMirror survolé.
* Récupérer son DOM parent et calculer sa hauteur et son décalage vertical.
* Repositionner l'élément HTML de la poignée en modifiant ses styles CSS `top` et `left` (le bouton est monté au niveau de l'éditeur parent en absolute, évitant d'être injecté dans le texte éditable).
* Gérer le glissement avec l'API HTML5 Drag and Drop couplée aux transactions de ProseMirror (`tr.replaceWith` ou `MoverBlock`).
* Attacher un écouteur `mouseover` et `mousemove` sur l'éditeur ProseMirror.
* Utiliser `view.posAtCoords` pour localiser le nœud survolé.
* Repositionner l'élément HTML de la poignée en modifiant ses styles CSS `top` et `left`.
* Gérer le glissement avec `@tiptap/extension-drag-handle-react` (spec officielle).
### 3. Fichiers à modifier / créer :
* `[NEW]` `memento-note/components/tiptap-drag-handle-plugin.ts` — Plugin de poignée Gutter
* `[NEW]` `memento-note/components/tiptap-database-block-extension.tsx` — Nœud et composant DatabaseBlockEditor
* `[MODIFY]` `memento-note/components/rich-text-editor.tsx` — Intégration du Drag Handle, de la DatabaseBlockExtension et de l'interception du presse-papier dans `editorProps.handlePaste`
* `[MODIFY]` `memento-note/app/globals.css` — Ajout des classes CSS pour l'alignement précis du gutter, de la poignée, et du bloc de base de données (glassmorphic dropdowns et fiches).
### 3. Fichiers à modifier / créer / supprimer :
* `[NEW]` `memento-note/components/tiptap-drag-handle-plugin.ts` — Plugin Gutter (US-1)
* `[NEW]` `memento-note/components/tiptap-structured-view-block-extension.tsx` — Nœud Vue Structurée (US-4 redesign)
* `[NEW]` `memento-note/components/structured-view-block-embed.tsx` — React NodeView avec SWR + états dégradés
* `[DELETE]` `memento-note/components/tiptap-database-block-extension.tsx` — Legacy rejeté
* `[DELETE]` `memento-note/components/database-block-editor.tsx` — Legacy rejeté
* `[DELETE]` `memento-note/lib/editor/database-block-types.ts` — Legacy rejeté
* `[MODIFY]` `memento-note/components/note-content-area.tsx` — Passer `notebookId` à l'éditeur
* `[MODIFY]` `memento-note/components/rich-text-editor.tsx` — Swap extension DB → Vue Structurée, mise à jour slash
* `[MODIFY]` `memento-note/components/block-action-menu.tsx` — Remplacer option "Database" par "Vue structurée"
* `[MODIFY]` `memento-note/app/globals.css` — Gutter, poignée, glassmorphic dropdowns
---
@@ -115,4 +125,9 @@ Développer un plugin custom dans `/components/tiptap-drag-handle-plugin.ts` :
1. **Vérification Gutter :** Survoler des textes longs et vérifier que la poignée se positionne correctement à gauche. Glisser un paragraphe sur un autre et valider le réordonnancement.
2. **Vérification Menu :** Cliquer sur la poignée, dupliquer le bloc, supprimer le bloc, et le transformer en d'autres types.
3. **Vérification Paste :** Copier une référence de bloc, la coller, et vérifier que la transclusion est proposée et s'insère sous forme de `LiveBlock`.
4. **Vérification Base de Données Inline :** Insérer le bloc base de données, ajouter un auteur, ajouter un livre avec cet auteur, vérifier le bon calcul du rollup, et basculer en vue Fiches pour vérifier l'affichage des couvertures.
4. **Vérification Vue Structurée Inline (US-4 redesign) :**
- Taper `/vue` dans une note de carnet structuré → bloc avec données réelles (pas Jules Verne).
- Taper `/vue` dans un carnet sans schéma → callout wizard, pas de crash.
- Ouvrir une note avec ancien `databaseBlock` → note charge sans erreur.
- Passer la langue en `fa` (persan) → libellés traduits, layout RTL correct.
- Voir checklist complète dans `docs/story-nextgen-editor-us4-redesign.md` §11.

View File

@@ -20,7 +20,7 @@
| **US-TEMPORAL** | Prédictions d'accès temporelles | ⏸️ **REPORTÉ** | Remplacé par rappels + révision SM-2 + Memory Echo ; heuristique faible, migration NoteAccessLog non prioritaire |
| **US-FLASHCARDS** | Révision IA — Répétition espacée SM-2 | ✅ **LIVRÉ** | `/revision`, `/api/flashcards/*`, SM-2, génération IA depuis l'éditeur |
| **US-STRUCTURED-VIEWS** | Vues Structurées (Tableau/Kanban/Galerie) | ✅ **LIVRÉ** | `/api/notebooks/[id]/schema`, `/api/notes/[id]/properties`, vues structurées + panneau propriétés éditeur |
| **US-NEXTGEN-EDITOR** | Éditeur Next-Gen : Drag Handle + Menu Bloc + DB Inline + Smart Paste | 🚧 **PLANIFIÉ** | Voir `docs/story-nextgen-editor.md` |
| **US-NEXTGEN-EDITOR** | Éditeur Next-Gen : Drag Handle + Menu Bloc + **Vue Structurée Inline** (redesign US-4) + Smart Paste | 🚧 **PLANIFIÉ** | Voir `docs/story-nextgen-editor.md` + `docs/story-nextgen-editor-us4-redesign.md` |
| **US-EDITOR-PERF** | Performance de frappe TipTap (quick wins) | 🚧 **PLANIFIÉ** | — |
| **US-EDITOR-UX** | Micro-interactions saisie (slash menu, sélection multi-blocs, paste étendu, placeholders) | ⏳ **À FAIRE** | — |
| **US-EDITOR-MOBILE** | Expérience tactile & toolbar mobile adaptée | ⏳ **À FAIRE** | — |
@@ -619,15 +619,25 @@ L'éditeur actuel est un document linéaire classique. Pour rivaliser avec Notio
- Collage d'un lien de bloc -> menu inline : "Bloc Connecté (Live)" ou "Texte simple"
- Insère un nœud `liveBlock` synchronisé via Redis Pub/Sub
### US-4 : Bloc de Base de Données Relationnelle Inline
- Slash `/database` -> insère un React NodeView
- Vues Tableau / Fiches avec Rollup dynamique
- Modèle relationnel local (auteurs/livres par défaut, ou lié au carnet)
### US-4 : Vue Structurée de Carnet Inline — *Redesign*
> ⚠️ Spec complète : [`docs/story-nextgen-editor-us4-redesign.md`](./story-nextgen-editor-us4-redesign.md)
- Slash `/vue` (+ keywords `database`, `db`, `tableau`) → insère un `structuredViewBlock`
- Affiche Table ou Galerie du carnet courant via l'API Structured Views existante
- Bloc stocke uniquement `notebookId` + `displayMode` (pas de données en attrs TipTap)
- Graceful fallback si carnet sans schéma ou note sans carnet
- **Supprime** le code legacy `tiptap-database-block-extension.tsx` (Verne/Liu Cixin)
- **Dépend de :** US-STRUCTURED-VIEWS ✅ (livré)
### Fichiers
- `[NEW]` `tiptap-drag-handle-plugin.ts` — Plugin ProseMirror pur
- `[NEW]` `tiptap-database-block-extension.tsx` — NodeView React
- `[MODIFY]` `rich-text-editor.tsx` — Intégration drag handle + DB + paste intercept
- `[NEW]` `tiptap-drag-handle-plugin.ts` — Plugin ProseMirror pur (US-1)
- `[NEW]` `tiptap-structured-view-block-extension.tsx` — NodeView Vue Structurée (US-4 redesign)
- `[NEW]` `structured-view-block-embed.tsx` — Composant embed avec SWR + états dégradés
- `[DELETE]` `tiptap-database-block-extension.tsx` — Legacy Auteurs/Œuvres rejeté
- `[DELETE]` `database-block-editor.tsx` — UI legacy rejetée
- `[DELETE]` `lib/editor/database-block-types.ts` — Types legacy rejetés
- `[MODIFY]` `note-content-area.tsx` — Passer `notebookId` à l'éditeur
- `[MODIFY]` `rich-text-editor.tsx` — Swap extension DB → Vue Structurée, mise à jour slash
- `[MODIFY]` `block-action-menu.tsx` — Remplacer option "Database" par "Vue structurée"
- `[MODIFY]` `globals.css` — Gutter, poignée, glassmorphic dropdowns
---