23 KiB
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 :
<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+NotePropertyexistent 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
NotesStructuredTableetNotesGalleryViewexistants (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 dansnote-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-grid1est la référence de ce qu'il ne faut pas shipper. Le prototypearchitectural-grid(sans1) 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)
// 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 colonnesNoteProperty→ valeurs par noteNote→ lignes du tableau
Aucune nouvelle table. Aucun risque BDD.
7.3 Diagramme de relations
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)
- Supprimer
memento-note/lib/editor/database-block-types.ts - Supprimer
memento-note/components/database-block-editor.tsx - Supprimer
memento-note/components/tiptap-database-block-extension.tsx - Modifier
block-action-menu.tsx— retirer import + optiondatabase - Modifier
rich-text-editor.tsx— retirer import + slash entry + extension registration - Créer
tiptap-structured-view-block-extension.tsx+structured-view-block-embed.tsx - Modifier
note-content-area.tsx— passernotebookId - 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
parseHTMLqui matchediv[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.tsxest 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
/databasedans 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
/databasedans une note sans carnet → message "nécessite un carnet" - Taper
/databasedans 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 (~1–2 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 71–85) par :
### 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 90–109) par :
### 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)
-| **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 622–626)
-### 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 627–631)
-### 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