Files
LoreMindMJ/docs/plan.md

37 KiB
Raw Blame History

Plan de développement LoreMind

Contexte du projet

LoreMind est une application d'aide aux Maîtres de Jeu (JDR) permettant de gérer le Lore, les campagnes, et intègre un moteur IA pour générer du contenu structuré à partir de templates. À terme, les données seront exportables vers FoundryVTT.

Stack technique

  • Frontend : Angular
  • Backend Core (Données & Métier) : Java (Spring Boot, DDD, Architecture Hexagonale)
  • Backend AI (Génération & LLM) : Python (FastAPI, Ollama en local)
  • Base de données : PostgreSQL

Architecture Backend Java

  • Domain-Driven Design (DDD) strict
  • Architecture Hexagonale (Ports et Adaptateurs)
  • Bounded Contexts : LoreContext, CampaignContext, GenerationContext
  • Cœur du domaine sans dépendances techniques (ni framework, ni base de données)

État actuel du projet

🆕 Projet initialisé (14 avril 2026)

  • Création des dossiers structurels (core/, web/, brain/, docs/)
  • Documentation de contexte et règles (.windsurfrules, loremind-contexte.md)

🚀 Backend Java - Phase 1 (16 avril 2026)

  • Initialisation du projet Spring Boot (pom.xml)
  • Configuration de la base de données PostgreSQL
  • Création des entités de domaine :
    • LoreContext : Lore, LoreNode, Page, Template
    • CampaignContext : Campaign, Arc, Chapter, Scene
  • Création des Ports (Repositories interfaces) pour TOUS les contexts
  • Création des Adaptateurs d'infrastructure :
    • JPA Entities (LoreJpaEntity, LoreNodeJpaEntity, etc.)
    • JPA Repositories (LoreJpaRepository, etc.)
    • Postgres Repositories (PostgresLoreRepository, etc.)
  • Création des Services d'application PARTIELS :
    • LoreService
    • CampaignService
  • Création des Services d'application restants :
    • LoreNodeService
    • PageService
    • TemplateService
    • ArcService
    • ChapterService
    • SceneService
  • Création des DTOs et Mappers :
    • LoreContext DTOs
    • CampaignContext DTOs
    • Mappers pour toutes les entités
  • Création des REST Controllers :
    • LoreController, CampaignController
    • LoreNodeController, PageController, TemplateController
    • ArcController, ChapterController, SceneController
  • Configuration CORS
  • Création du GenerationContext (entités, ports, adaptateurs, services)

À faire

Phase 2 : Frontend Angular (Priorité haute)

  • Initialisation du projet Angular
  • Création du layout de base (Sidebar + Main Content)
  • Création du composant Sidebar (redesign complet : logo, palette violette, OUTILS, version)
  • Configuration routing Angular
  • Styles globaux dark theme
  • Installation lucide-angular (icônes SVG)
  • Page Lore (Vos Univers) — grille de cartes, carte "Nouveau Lore"
  • Page Campagnes — grille de cartes, carte "Nouvelle Campagne"
  • Modal création de Lore (LoreCreateComponent)
  • Branchement LoreService.createLore()
  • Composant SecondarySidebar (réutilisable Lore + Campagne)
  • LayoutService (contrôle affichage secondary sidebar)
  • Page détail Lore (/lore/:id) avec arborescence noeuds
  • Page détail Campagne (/campaigns/:id) avec arborescence arcs/chapitres
  • Formulaire création Campagne (modal)
  • Sidebar globale contextuelle (liste lores/campagnes + retour)
  • Secondary sidebar avec toggle pli/dépli
  • Écran création de noeud (Lore)
  • Refonte secondary sidebar : actions contextuelles (Lore: + Noeud / + Page, Campagne: + Nouvel arc). Bouton "Sauvegarder" retiré (persistance immédiate via REST).
  • Écran création/modification de page (Lore) — Phase 5A livrée : domaine Page enrichi (loreId, values: Map<String,String>, notes, tags, relatedPageIds), écrans page-create (titre + grille de templates + noeud pré-rempli depuis template.defaultNodeId) et page-edit basique (champs dynamiques du template rendus en textarea + notes privées). Pages affichées dans l'arbre sous leur noeud + action "+ Nouvelle page" par noeud.
  • Écran création/modification de template (Lore) (domaine enrichi loreId + defaultNodeId + List<String> fields, panneau sidebar "Templates" fidèle à la maquette)
  • Écran création d'arc (Campagne) (champ illustration reporté — à ajouter quand l'écran détail d'un arc sera implémenté)
  • Écran création de chapitre (Campagne) (chargement chapitres existants + action "+ Nouveau chapitre" inline dans l'arbre)
  • Écran création de scène (Campagne)
  • Refactor : helper campaign-tree.helper.ts (chargement arbre + construction TreeItem[]) + rendu récursif 3 niveaux dans SecondarySidebar
  • Écrans détail/modification (Campagne) : arc-edit, chapter-edit, scene-edit avec Sauvegarder + Supprimer
  • SecondarySidebar : séparation chevron/label pour permettre expand + navigation sur un même item
  • Enrichissement domaine narratif Arc/Chapter/Scene (sous-tâches 1-3 , sous-tâche 4 cross-context Lore↔Campaign restante)

Enrichissement du domaine Template (Lore) (18 avril 2026)

Maquettes : docs/maquettes/lore/Création de template.png et Modification de template.png.

Changements backend Java (Option i : champs typés explicites, DDD-friendly) :

  • Template.java : ajout loreId, defaultNodeId, List<String> fields ; suppression Map<String,Object> structure ; méthodes métier fieldCount, addField, removeField.
  • TemplateRepository.java (port) : ajout findByLoreId(String loreId).
  • TemplateJpaEntity.java : colonnes typées lore_id (NOT NULL), default_node_id, fields (TEXT via converter JSON).
  • Nouveau converter StringListJsonConverter : List<String> ↔ JSON pour la persistance PostgreSQL.
  • PostgresTemplateRepository.java : mapping bidirectionnel enrichi + implémentation findByLoreId.
  • TemplateJpaRepository.java : méthode dérivée findByLoreId(Long).
  • TemplateDTO.java + TemplateMapper.java : alignement sur le nouveau domaine (expose fieldCount calculé côté serveur).
  • TemplateService.java : signatures refactorées ; updateTemplate en Parameter Object pattern ; loreId volontairement immuable (pas de migration cross-Lore via simple update).
  • TemplateController.java : POST /api/templates, GET /api/templates?loreId=X, GET /{id}, PUT /{id}, DELETE /{id}. Retrait du PATCH /{id}/structure obsolète.

Changements frontend Angular :

  • template.model.ts, template.service.ts : modèle TS + service HTTP complet.
  • layout.service.ts : nouveaux types BottomPanel, BottomPanelItem ; SecondarySidebarConfig.bottomPanel? ajouté. footerLabel déprécié (gardé optionnel pour compat des callers campagne).
  • SecondarySidebarComponent : rendu du bottomPanel avec toggle ouvert/fermé, items cliquables + meta (ex: "8 champs").
  • Nouveau helper @app/lore/lore-sidebar.helper.ts : charge lore + all lores + nodes + templates en parallèle et construit la config sidebar complète (panneau Templates inclus). Utilisé par lore-detail et lore-node-create (→ résout la dette "duplications de pattern côté Lore").
  • TemplateCreateComponent et TemplateEditComponent : écrans fidèles aux maquettes (colonne gauche = identité, colonne droite = liste dynamique ajout/suppression de champs).
  • Routes ajoutées : /lore/:loreId/templates/create et /lore/:loreId/templates/:templateId.

Enrichissement du domaine Page (Lore) — Phase 5A (18 avril 2026)

Maquettes : docs/maquettes/lore/création de page.png et Modification d'une page.png.

Changements backend Java (structure alignée sur les maquettes) :

  • Page.java : suppression content: String (trop vague) ; ajout loreId, values: Map<String,String> (valeurs des champs dynamiques du template, clé = fieldName), notes (privé MJ), tags: List<String>, relatedPageIds: List<String>. Méthodes métier setFieldValue, getFieldValue, addTag, removeTag.
  • PageRepository.java : ajout findByLoreId(String loreId).
  • PageJpaEntity.java : colonne lore_id NOT NULL, values_json (colonne renommée car values est un mot-clé SQL), notes, tags, related_page_ids — tous stockés en JSON (TEXT) via converters.
  • Nouveau converter StringMapJsonConverter : Map<String,String> ↔ JSON (distinct du MapJsonConverter générique Map<String,Object> dont Page n'a pas besoin).
  • PostgresPageRepository.java + PageJpaRepository.java : mapping et méthode dérivée findByLoreId.
  • PageDTO.java + PageMapper.java : alignement complet.
  • PageService.java : createPage(loreId, nodeId, templateId, title) minimaliste ; updatePage(id, changes) Parameter Object (applique title/nodeId/values/notes/tags/relatedPageIds). loreId et templateId immuables après création.
  • PageController.java : POST /api/pages, GET /api/pages?loreId=X ou ?nodeId=Y, GET /{id}, PUT /{id}, DELETE /{id}. Suppression de PATCH /{id}/template et de GET /node/{nodeId} (unifié en query param).

Changements frontend Angular :

  • page.model.ts, page.service.ts : modèle TS + service HTTP.
  • lore-sidebar.helper.ts enrichi : charge aussi les pages ; les pages apparaissent dans l'arbre sous leur noeud (TreeItem children) ; chaque noeud a un item action "+ Nouvelle page" pointant vers /lore/:loreId/nodes/:nodeId/pages/create ; bouton "+ Page" du header de sidebar pointe vers /lore/:loreId/pages/create (choix libre du template).
  • PageCreateComponent : fidèle à la maquette — titre + grille de cartes Template sélectionnables + select Noeud de destination (pré-rempli depuis template.defaultNodeId si l'URL n'impose pas déjà un nodeId). Redirige vers page-edit après création.
  • PageEditComponent basique — titre, noeud (déplaçable), un textarea par field du template (valeurs stockées dans values), notes privées. Bouton "Assistant IA" stub (sera branché en Phase 3 Python).
  • Routes ajoutées : /lore/:loreId/pages/create, /lore/:loreId/nodes/:nodeId/pages/create, /lore/:loreId/pages/:pageId.

Renommage "dossier" + icônes + hiérarchie (18 avril 2026)

Triple corrections suite au retour utilisateur :

Renommage UI "noeud" → "dossier" (nom interne LoreNode conservé côté Java pour limiter l'impact BDD et code) :

  • Textes visibles mis à jour dans : lore-node-create.*, lore-detail.*, lore.component.*, page-create.*, page-edit.*, template-create.*, template-edit.*, bouton sidebar + Dossier.

Bug corrigé — icônes de dossier invisibles :

  • Côté backend : l'icône envoyée par l'UI était silencieusement ignorée (icon absent de LoreNode.java, LoreNodeDTO.java, du mapping JPA). Ajouté partout : domaine + JPA entity (colonne icon) + DTO + mapper + repository + service (Parameter Object pour createLoreNode/updateLoreNode).
  • Côté frontend : nouveau registre partagé @app/lore/lore-icons.ts (LORE_ICON_OPTIONS + resolveIcon(key)). Refactor de lore-node-create pour utiliser ce registre. Sidebar rend l'icône via TreeItem.iconKey (nouveau champ) + méthode iconFor().

Dossiers imbriqués :

  • Le backend supportait déjà parentId — seul le frontend ne l'exposait pas. Ajout du champ parentId à LoreNode/LoreNodeCreate TS + formulaire lore-node-create (select "Dossier parent"). Nouvelle route /lore/:loreId/folders/:parentId/create pour pré-remplir depuis la sidebar.
  • Helper lore-sidebar.helper.ts refactoré : construction récursive de l'arbre (fonction buildFolderItem). Chaque dossier affiche ses sous-dossiers + ses pages + actions + Nouveau dossier et + Nouvelle page inline.

Édition et suppression de dossier (18 avril 2026)

Complète la CRUD manquante pour les LoreNode (dossiers).

Frontend Angular :

  • LoreService enrichi : getLoreNodeById, updateLoreNode, deleteLoreNode. URL des lore-nodes factorisée dans nodesUrl.
  • Nouveau LoreNodeEditComponent (lore-node-edit/) — formulaire avec nom, icône (grille), dossier parent (select). Header avec boutons Annuler / Supprimer / Sauvegarder.
  • Helper lore-sidebar.helper.ts : ajout de la fonction utilitaire collectDescendantIds(rootId, allNodes) qui calcule de manière itérative l'ensemble des descendants d'un dossier. Utilisée pour empêcher les cycles : le select "Dossier parent" de l'édition exclut le dossier courant et tous ses descendants.
  • Chaque dossier dans la sidebar a maintenant une route pointant vers son écran d'édition (clic sur le label → édition, clic sur le chevron → expand/collapse).
  • Route ajoutée : /lore/:loreId/folders/:folderId/edit.

Règle de suppression (safe) :

  • La suppression est refusée si le dossier contient des sous-dossiers ou des pages (message explicite "Videz-le d'abord : X sous-dossier(s) et Y page(s)").
  • Raison : protéger les notes MJ contre un clic accidentel. Pas de cascade silencieuse.
  • La vérification se fait côté frontend à partir des données déjà chargées dans la sidebar (pas d'appel HTTP supplémentaire). Le backend accepte le DELETE sans check, mais le frontend ne l'émet jamais si le dossier n'est pas vide.

Pages — Phases à venir

Phase 5B : édition complète (18 avril 2026)

  • Tags (chips) — nouveau composant réutilisable app-chips-input (@app/shared/chips-input/). UX : Entrée ou virgule pour ajouter, Backspace sur input vide retire le dernier chip, doublons silencieusement ignorés, trim automatique. Binding deux-sens via [value] / (valueChange).
  • Liens vers d'autres pages — nouveau composant app-lore-link-picker (@app/shared/lore-link-picker/). Input de recherche avec dropdown de suggestions filtrées (max 8), chips cliquables pour chaque page liée (clic → navigation, X → retrait). Exclut la page courante via excludePageId.
  • Intégration dans page-edit : sections "Tags" et "Pages liées" ajoutées entre les champs dynamiques et les notes privées. allPages: Page[] récupéré depuis la sidebar pour alimenter le picker.
  • Le composant app-lore-link-picker est conçu pour être réutilisé en Phase cross-context Campagne↔Lore (Arc/Chapter/Scene pourront lier des Pages du Lore via ce même picker).

Phase 5C : affichage et navigation fine

  • Compteur de pages sur chaque dossier dans le sidebar (ex: PNJ · 3) (19 avril 2026).
  • Correction compteurs home "Vos univers" (nodeCount/pageCount calculés à la volée via countByLoreId au lieu d'être stockés en BDD et jamais MAJ) (19 avril 2026).
  • Breadcrumb Lore > Dossier > Page dans page-edit (19 avril 2026) — composant réutilisable app-breadcrumb prêt pour Arc/Chapter/Scene.
  • Raccourci clavier Ctrl+S pour sauvegarder.

Phase 5D : intégration IA (dépend de la Phase 3 Python)

  • Branchement du bouton "Assistant IA" de page-edit sur l'endpoint Python Ollama.
  • Affichage streaming des suggestions par champ.

Enrichissement du domaine narratif (Campagne)

Les maquettes (docs/maquettes/campagne/détail/*.png) révèlent que chaque entité narrative doit porter bien plus que name + description. Voici le détail des champs manquants qui doivent être ajoutés côté Java (domain + JPA + DTO + mapper) et côté Angular (model + forms).

Approche retenue :

  • Colonnes TEXT dédiées (pas de JSONB) car chaque champ a un sens métier précis et sera utilisé par l'Assistant IA (Phase 3) comme prompt structuré.
  • Les liens vers le Lore (PNJ, lieux, objets) = IDs stockés en JSONB ou table de liaison, pas de relation JPA cross-context (principe DDD : Bounded Contexts isolés).

Sous-tâche 1 : Arc (5 champs texte)

  • themes — Thèmes principaux
  • stakes — Enjeux globaux
  • gmNotes — Notes et planification du MJ (privé, non exporté FoundryVTT)
  • rewards — Récompenses et progression
  • resolution — Dénouement prévu
  • Impact : Arc.java (domain), ArcJpaEntity.java (JPA + colonnes TEXT auto via ddl-auto=update), ArcDTO.java, ArcMapper.java, PostgresArcRepository.java, ArcService.updateArc() refactoré en Parameter Object pattern, ArcController.updateArc(), campaign.model.ts, arc-edit.component.* (formulaire enrichi, arc-create reste volontairement minimal comme la maquette).

Sous-tâche 2 : Chapter (3 champs texte)

  • gmNotes — Notes du Maître de Jeu (privé)
  • playerObjectives — Objectifs des joueurs
  • narrativeStakes — Enjeux narratifs
  • Impact : Chapter.java, ChapterJpaEntity.java, ChapterDTO.java, ChapterMapper.java, PostgresChapterRepository.java, ChapterService.updateChapter() (Parameter Object), ChapterController.updateChapter(), campaign.model.ts, chapter-edit.component.*.

Sous-tâche 3 : Scene (8 champs texte répartis en sections)

Sections de la maquette (chaque section est un bloc d'UI expandable) :

  • Contexte et ambiance : location (court), timing (court), atmosphere (long)
  • Narration pour les joueurs : playerNarration (long)
  • Notes et secrets du MJ (privé, variant "private" rouge) : gmSecretNotes (long)
  • Choix et conséquences : choicesConsequences (long)
  • Combat ou rencontre : combatDifficulty (court), enemies (long)
  • Impact : Scene.java, SceneJpaEntity.java, SceneDTO.java, SceneMapper.java, PostgresSceneRepository.java, SceneService.updateScene() (Parameter Object), SceneController.updateScene(), campaign.model.ts, scene-edit.component.* (11 champs totaux, 5 sections expandables).
  • Nouveau composant partagé : @app/shared/expandable-section/ — réutilisable (propriétés : title, icon emoji, initiallyOpen, variant: 'default' | 'private').

Sous-tâche 4 : Liens cross-context Lore ↔ Campaign

Tous des List<String> d'IDs de LoreNode :

  • Arc : antagonistNpcIds, allyNpcIds
  • Chapter : involvedNpcIds, visitedLocationIds
  • Scene : presentNpcIds, importantObjectIds
  • Composant Angular réutilisable : app-lore-link-picker (autocomplete + liste de chips)
  • Endpoint GET /api/lore-nodes?ids=a,b,c (résolution multi-IDs) côté Java

Phase 3 : Backend Python (Priorité basse - PLUS TARD)

  • Initialisation de la structure
  • Configuration Ollama en local
  • Adaptateur OllamaProvider
  • Routes API pour la génération

Structure des dossiers

LoreMind/
├── core/                    # Backend Java
│   ├── src/main/java/com/loremind/
│   │   ├── domain/          # Entités de domaine (DDD)
│   │   │   ├── lorecontext/
│   │   │   ├── campaigncontext/
│   │   │   └── generationcontext/
│   │   ├── application/     # Use Cases
│   │   └── infrastructure/   # Adaptateurs (JPA, REST)
│   └── pom.xml
├── web/                     # Frontend Angular
│   ├── src/app/
│   │   ├── shared/          # Composants partagés (Sidebar)
│   │   ├── campaigns/       # Module Campaigns
│   │   ├── lore/            # Module Lore
│   │   └── services/        # Services HTTP
│   └── package.json
├── brain/                   # Backend Python (IA)
│   ├── app/
│   │   ├── core/
│   │   ├── domain/
│   │   ├── infrastructure/
│   │   └── api/
│   └── requirements.txt
└── docs/                    # Documentation
    ├── academy/             # Documents pédagogiques
    ├── loremind-contexte.md # Contexte du projet
    └── plan.md              # Ce fichier

Configuration prévue

Base de données PostgreSQL

  • URL : jdbc:postgresql://localhost:5432/loremind
  • Username : ietm64
  • Password : M&Ipourlavie64

Serveurs

Notes importantes

  • Le Backend Java suit strictement l'Architecture Hexagonale
  • Le Frontend Angular utilise des services HTTP pour communiquer avec le Backend
  • L'IA sera configurée avec Ollama en local (pas d'API OpenAI pour le moment)
  • Les templates PostgreSQL utiliseront le type JSONB pour la flexibilité

Points à surveiller / Dette technique connue

Ces points sont à garder en tête pour de futures refactorisations. Pas bloquant aujourd'hui — à traiter quand le besoin se manifestera.

  • Champ illustration manquant sur Arc/Chapter/Scene (front + back) — les maquettes prévoient une URL d'illustration. Reporté jusqu'à l'implémentation des écrans de détail (affichage) correspondants. Impact : domaine Java + JPA + DTO + migration Postgres + champ Angular.
  • Chargement de l'arbre campagne via N+1 requêtes HTTPcampaign-tree.helper.ts fait 1 arcs + N chapters + M scenes appels HTTP en forkJoin. Correct pour des campagnes de taille modeste. À remplacer par un endpoint agrégé GET /api/campaigns/:id/tree quand les volumes l'exigeront (>20 chapitres par ex).
  • Calcul de order naïf — actuellement order = existingCount + 1. Ne gère pas les réordonnancements ni les suppressions (risque de collisions). À reprendre avec un pattern de fractional indexing ou recalcul côté backend.
  • Duplications de pattern côté Lore Résolu le 18 avril 2026 via lore-sidebar.helper.ts — utilisé par lore-detail, lore-node-create, template-create, template-edit. L'écran de création de page à venir suivra le même pattern.
  • Harmonisation du style bouton primaire Résolu le 18 avril 2026 sur les 6 composants pattern A (arc/chapter/scene × create/edit). Partials créés : web/src/styles/_buttons.scss et _forms.scss. 572 → 120 lignes (-79%). Reste la unification pattern A / pattern B (voir entrée dédiée ci-dessous).
  • Deux design systems cohabitent — Pattern A (#1f2937 / radius 8px / padding 0.75rem, utilisé par arc/chapter/scene/campaign/lore-create) vs Pattern B (#1a1a2e / radius 6px / padding 0.7rem, utilisé par template/page/lore-node). C'est un vrai choix de design à faire, pas juste de la dette. Option : garder A pour les écrans "campagne simples" et B pour les écrans "Lore premium". Option alternative : unifier sur un seul pattern.
  • Fusion create + edit — les composants *-create et *-edit partagent ~80% du code (form, layout, soumission). À fusionner en un seul composant par entité avec un flag mode: 'create' | 'edit' OU router-data. Pas urgent : YAGNI tant que les 2 écrans restent simples.
  • Breadcrumb Résolu le 19 avril 2026. Composant réutilisable app-breadcrumb créé dans web/src/app/shared/breadcrumb/. Intégré dans page-edit. Reste à intégrer sur arc-edit, chapter-edit, scene-edit si nécessaire (le composant est prêt).
  • Modale de confirmation personnalisée — la suppression utilise confirm() natif. À remplacer par une modale Angular cohérente avec la charte graphique.
  • Gestion des migrations DB — actuellement spring.jpa.hibernate.ddl-auto=update (auto-alter). Acceptable en dev, inutilisable en prod (perte de données possible). À remplacer par Flyway ou Liquibase avant la mise en prod (chaque changement de schéma devra être versionné en fichier SQL).

Dernière mise à jour

19 avril 2026 - Phase 5C en cours : compteurs + breadcrumb livrés.

Breadcrumb (fil d'Ariane) dans page-edit :

  • Nouveau composant réutilisable app-breadcrumb dans web/src/app/shared/breadcrumb/. Interface BreadcrumbItem { label, route? } — item sans route = position courante (non-cliquable, style différencié).
  • Séparateur visuel géré en CSS via ::before (pas de DOM supplémentaire).
  • Intégré dans page-edit au-dessus du header. Chemin construit dynamiquement par un getter breadcrumbItems qui remonte la hiérarchie de dossiers via parentId depuis les nodes déjà chargés pour la sidebar — zéro appel HTTP supplémentaire.
  • Le getter recalcule à chaque render (Angular change detection) : peu coûteux (dizaines de nœuds max), reste réactif si le nodeId change via le select "Dossier".
  • Composant prêt pour réutilisation sur arc-edit, chapter-edit, scene-edit (dette technique correspondante marquée résolue).
  • 5 fichiers touchés (3 nouveaux + page-edit TS/HTML).

19 avril 2026 - Phase 5C démarrée : compteurs de pages.

Compteurs de pages par dossier (sidebar) :

  • Nouveau champ meta?: string sur TreeItem (layout.service.ts) pour afficher un badge aligné à droite.
  • buildFolderItem dans lore-sidebar.helper.ts renseigne meta = nodePages.length (count direct, undefined si 0 — ne pas polluer visuellement).
  • Rendu dans secondary-sidebar.component.html via <span class="tree-item-meta"> + style SCSS margin-left: auto cohérent avec .panel-item-meta existant (DRY).
  • 4 fichiers touchés, ~9 lignes ajoutées.

Bug fix — compteurs home "Vos univers" affichaient 0 :

  • Cause : les champs Lore.nodeCount / pageCount étaient stockés en BDD mais les méthodes métier incrementNodeCount() / incrementPageCount() n'étaient jamais appelées par LoreNodeService ou PageService. Les compteurs restaient à 0 depuis la création.
  • Correction : bascule vers un calcul à la volée (Option B — source of truth = tables nodes/pages).
  • Ports enrichis : countByLoreId(String loreId) ajouté sur LoreNodeRepository et PageRepository.
  • JPA : long countByLoreId(Long loreId) en Spring Data query derivation (pas de @Query à écrire).
  • Adaptateurs Postgres : impl propagée avec conversion String → Long.
  • LoreService injecte les 2 ports supplémentaires et enrichit via une méthode privée withCounts(Lore) appliquée dans getAllLores() et getLoreById(). Anciennes colonnes node_count/page_count laissées en BDD (ignorées, à nettoyer plus tard).
  • N+1 assumé (1 find + 2 COUNT par Lore). Négligeable à petite échelle.
  • 7 fichiers touchés (2 ports, 2 JPA, 2 adaptateurs, 1 service).

18 avril 2026 (fin de soirée, après B2) - Cross-context Campagne ↔ Lore complet sur Arc + Chapter + Scene.

B2 — relatedPageIds sur Arc / Chapter / Scene :

  • Backend (18 fichiers touchés, 3 entités × 6 couches) :
    • Domaine : List<String> relatedPageIds ajouté avec @Builder.Default (jamais null) + méthodes métier linkPage(id) / unlinkPage(id) idempotentes et réutilisables.
    • Persistance JPA : nouvelle colonne related_page_ids en TEXT avec le converter existant StringListJsonConverter (pattern déjà utilisé pour Page + Template).
    • Repositories Postgres : propagation dans les deux sens (toDomainEntity / toJpaEntity) avec defensive copy (new ArrayList<>(src)) pour garantir qu'un changement côté domaine ne modifie pas accidentellement l'entité JPA.
    • DTO + Mapper : idem, propagation + defensive copy. Initialisation new ArrayList<>() pour que Jackson sérialise toujours un tableau JSON (jamais null).
    • Services : updateArc/Chapter/Scene déjà en Parameter Object (full entity) → une seule ligne ajoutée par service (entity.setRelatedPageIds(updated.getRelatedPageIds())).
  • Frontend (7 fichiers touchés) :
    • Modèles Arc/Chapter/Scene + *Create : champ relatedPageIds?: string[] ajouté.
    • arc-edit, chapter-edit, scene-edit : injection PageService, chargement conditionnel des pages du Lore via switchMap (si campaign.loreId est défini), binding bidirectionnel avec app-lore-link-picker. Le picker est caché avec un message explicatif si la campagne n'a pas de Lore associé.
    • scene-edit : le picker est dans une app-expandable-section avec l'icône 🔗 pour rester cohérent avec les 5 autres sections de la scène.
  • Principe DDD réaffirmé : aucune classe du Lore Context importée dans le Campaign Context. Seuls des IDs (String) voyagent. Le LoreLinkPickerComponent est le SEUL point où les deux contextes se rejoignent, et c'est côté UI — pas côté domaine.
  • Le app-lore-link-picker que j'avais introduit pour page-edit (Phase 5B) est désormais réutilisé à l'identique dans 3 nouveaux écrans → pari design payant, la composantisation a tenu ses promesses.

18 avril 2026 (soir, après dette technique) - Extraction SCSS + mise en valeur bouton retour sidebar.

Extraction SCSS partagés :

  • Nouveaux partials web/src/styles/_buttons.scss (.btn-primary, .btn-secondary, .btn-danger + modificateurs .btn-sm et .btn-icon) et _forms.scss (.field, .field-hint, .field-row, .page-header, .form-actions). Importés via @use dans styles.scss.
  • 6 composants pattern A nettoyés : arc-create/edit, chapter-create/edit, scene-create/edit. Réduction de 572 → 120 lignes (-79%, 452 lignes économisées). Seuls les overrides contextuels subsistent (ex: .btn-danger { margin-left: auto } pour pousser Supprimer à droite).
  • Composants pattern B (template, page, lore-node) volontairement non modifiés — ils utilisent un design différent (#1a1a2e / radius 6px). Ajouté comme nouvelle entrée dans la dette "Deux design systems cohabitent".
  • ⚠️ Piège Angular ViewEncapsulation rencontré : les sélecteurs CSS locaux ont une spécificité plus élevée que les globaux (ajout d'un attribut [_ngcontent-xxx]). Il fallait donc réellement supprimer les blocs dupliqués pour que les globaux s'appliquent — sinon ils étaient silencieusement ignorés.

Bouton retour sidebar (Tous les lores / Toutes les campagnes) :

  • Avant : simple libellé gris sans bordure, confondu avec les items de contexte.
  • Après : vrai bouton bordé avec icône ArrowLeft Lucide, centré, séparateur visuel au-dessus via ::before, hover marqué (bordure violet, texte blanc, flèche qui glisse à gauche en micro-interaction).
  • Principe UX appliqué : "affordance visuelle" — un bouton doit avoir l'air cliquable avant même qu'on le survole.

18 avril 2026 (soir, après B1) - Weak reference Campaign ↔ Lore + 4 corrections de bugs.

B1 — Lien optionnel Campaign ↔ Lore (cross-context weak reference) :

  • Backend : Campaign.loreId ajouté (nullable) avec méthodes métier linkToLore / unlinkFromLore / isLinkedToLore. Pas de @ManyToOne, pas de FK — c'est volontaire, les Bounded Contexts (Lore, Campaign) doivent rester indépendants. Normalisation ""null côté service. CampaignService refacto en Parameter Object (CampaignData).
  • Frontend : select "Univers associé" (optionnel, "— Aucun univers —") dans la modal de création de campagne. Badge cliquable 🌐 <nom du Lore> dans campaign-detail qui navigue vers le Lore. Cas dégradé "Univers introuvable" (rouge italique) si le Lore a été supprimé entre-temps.

4 bugs corrigés suite retour utilisateur :

  1. Pollution cross-lore (🔴 critique) — GET /api/lore-nodes?loreId=X ignorait le query param (pas d'annotation @RequestParam), renvoyait TOUS les dossiers de TOUS les lores. Les dossiers d'un Lore apparaissaient dans les autres. Pattern aligné sur TemplateController / PageController.
  2. Grille "Dossiers" plate dans lore-detail — les sous-dossiers apparaissaient au même niveau que leurs parents dans la grille principale. Fix : rootNodes = nodes.filter(n => !n.parentId). Les sous-dossiers restent accessibles via l'arbre de la sidebar.
  3. Switch entre Lores/Campaigns ne rechargeait pas — même bug que le page-edit précédent : route.snapshot.paramMap.get('id') lu une seule fois dans ngOnInit, alors qu'Angular réutilise le composant. Fix : subscription à route.paramMap avec comparaison à l'id courant, appliqué à lore-detail ET campaign-detail (préventif).
  4. Pas de bouton Modifier/Supprimer sur Lore et Campaign — nouveau mode édition inline dans le header des deux écrans détail. Rename + description (+ select Lore associé pour Campaign) + sauvegarde/annulation. Suppression protégée : refus si Lore contient encore des dossiers, ou si Campagne contient encore des arcs (message explicite avec le nombre). Pattern cohérent avec lore-node-edit.

Encore en attente (B2, ~24 fichiers) : relatedPageIds: List<String> sur Arc + Chapter + Scene avec app-lore-link-picker filtré sur campaign.loreId. User a demandé pause pour tester B1 avant d'attaquer B2.

18 avril 2026 (fin de journée, après Option A) - Phase 5B Pages livrée : Tags + Liens entre pages. Deux nouveaux composants réutilisables dans @app/shared/ : app-chips-input (tags génériques avec Entrée/virgule/Backspace) et app-lore-link-picker (autocomplete + chips cliquables pour navigation, conçu pour être réutilisé en Phase cross-context Campagne↔Lore). Intégration dans page-edit entre champs dynamiques et notes privées. allPages récupéré depuis la sidebar pour alimenter le picker.

18 avril 2026 (fin de journée) - Édition/suppression de dossier livrée. Nouveau LoreNodeEditComponent : renommage, changement d'icône, déplacement dans un autre parent (avec collectDescendantIds pour bloquer les cycles), suppression protégée (refus si non-vide, message explicite). LoreService enrichi (getLoreNodeById, updateLoreNode, deleteLoreNode). Chaque dossier de la sidebar est maintenant cliquable (label → édition, chevron → expand/collapse — la séparation des zones existait déjà). Route /lore/:loreId/folders/:folderId/edit.

18 avril 2026 (soir, après Pages) - UX Lore : renommage + icônes + dossiers imbriqués. Trois corrections groupées suite au retour utilisateur :

  • Renommage UI "noeud" → "dossier" (texte visible uniquement ; LoreNode reste le nom interne côté Java).
  • Bug corrigé : l'icône choisie à la création d'un dossier n'était jamais persistée ni affichée. Ajout du champ icon dans LoreNode (domaine + JPA + DTO + mapper + Postgres repository) + refacto LoreNodeService en Parameter Object. Frontend : nouveau registre partagé @app/lore/lore-icons.ts consommé à la fois par lore-node-create (grille de sélection) et par la sidebar (rendu dans l'arbre via TreeItem.iconKey).
  • Dossiers imbriqués activés : le backend supportait déjà parentId, seul le frontend ne l'exposait pas. Ajout d'un select "Dossier parent" dans lore-node-create, nouvelle route /lore/:loreId/folders/:parentId/create, helper lore-sidebar.helper.ts refactoré en construction récursive (fonction buildFolderItem) avec sous-dossiers + pages + actions "+ Nouveau dossier" / "+ Nouvelle page" par dossier.

18 avril 2026 (soir) - Phase 5A Pages livrée : domaine Page enrichi (suppression content: String, ajout loreId, values: Map<String,String>, notes, tags, relatedPageIds). Nouveau converter StringMapJsonConverter. Écrans page-create (fidèle maquette : titre + grille de templates + noeud auto-rempli) et page-edit basique (champs dynamiques du template rendus en textarea + notes privées). Helper lore-sidebar.helper.ts enrichi pour afficher les pages sous leur noeud dans l'arbre + actions "+ Nouvelle page" par noeud. Phases 5B (tags/liens), 5C (breadcrumb/compteurs) et 5D (Assistant IA) planifiées.

18 avril 2026 (matin) - Enrichissement du domaine Template (Lore) : loreId, defaultNodeId, List<String> fields avec converter JSON. Panneau "Templates" fidèle à la maquette ajouté en bas de la secondary sidebar (nouveaux types BottomPanel / BottomPanelItem). Écrans template-create et template-edit avec gestion dynamique des champs. Nouveau helper lore-sidebar.helper.ts.

Prochaines étapes prioritaires (Immédiat)

1. Compléter les Services d'application

Pourquoi ? Chaque entité de domaine a besoin de son service pour orchestrer les opérations métier selon l'Architecture Hexagonale.

Tâches :

  • LoreNodeService
  • PageService
  • TemplateService
  • ArcService
  • ChapterService
  • SceneService

2. Créer les DTOs et Mappers

Pourquoi ? Les DTOs isolent l'API REST des entités de domaine, protégeant le cœur métier des changements d'interface.

Tâches :

  • LoreContext DTOs (LoreDTO, LoreNodeDTO, PageDTO, TemplateDTO)
  • CampaignContext DTOs (CampaignDTO, ArcDTO, ChapterDTO, SceneDTO)
  • Mappers pour toutes les entités

3. Créer les REST Controllers

Pourquoi ? Expose l'API pour le Frontend Angular.

Tâches :

  • LoreController, CampaignController
  • LoreNodeController, PageController, TemplateController
  • ArcController, ChapterController, SceneController
  • Configuration CORS

4. Démarrer le Frontend Angular

Pourquoi ? Une fois le Backend fonctionnel, le Frontend peut consommer l'API.

Tâches :

  • Initialisation projet Angular
  • Layout de base avec Sidebar