Files
Momento/docs/story-nextgen-editor-us4-redesign.md

23 KiB
Raw Permalink Blame History

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 + 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)

// 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

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 :

### 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 :

### 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 622626)

-### 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)

-### 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