@atlashub/smartstack-cli 4.41.0 → 4.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.documentation/apex.html +2 -2
  2. package/.documentation/business-analyse.html +26 -27
  3. package/.documentation/commands.html +6 -6
  4. package/dist/index.js +24 -13
  5. package/dist/index.js.map +1 -1
  6. package/package.json +2 -2
  7. package/templates/agents/ba-reader.md +2 -2
  8. package/templates/agents/ba-writer.md +44 -9
  9. package/templates/hooks/stop-hook.sh +6 -6
  10. package/templates/ralph/README.md +1 -1
  11. package/templates/scripts/setup-ralph-loop.sh +2 -2
  12. package/templates/skills/_resources/context-digest-template.md +1 -1
  13. package/templates/skills/_shared.md +13 -13
  14. package/templates/skills/apex/SKILL.md +14 -7
  15. package/templates/skills/apex/_shared.md +1 -1
  16. package/templates/skills/apex/references/challenge-questions.md +46 -13
  17. package/templates/skills/apex/references/core-seed-data.md +4 -4
  18. package/templates/skills/apex/references/error-classification.md +3 -3
  19. package/templates/skills/apex/references/smartstack-api.md +1 -1
  20. package/templates/skills/apex/references/smartstack-layers.md +1 -1
  21. package/templates/skills/apex/steps/step-00-init.md +46 -8
  22. package/templates/skills/apex/steps/step-01-analyze.md +1 -1
  23. package/templates/skills/apex/steps/step-02-plan.md +1 -1
  24. package/templates/skills/apex/steps/step-03-execute.md +1 -1
  25. package/templates/skills/business-analyse/SKILL.md +83 -22
  26. package/templates/skills/business-analyse/_shared.md +12 -9
  27. package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +13 -0
  28. package/templates/skills/business-analyse/questionnaire/03-data-ui.md +33 -0
  29. package/templates/skills/business-analyse/questionnaire/04-risks-metrics.md +1 -1
  30. package/templates/skills/business-analyse/react/components.md +1 -1
  31. package/templates/skills/business-analyse/react/schema.md +1 -1
  32. package/templates/skills/business-analyse/references/acceptance-criteria.md +3 -3
  33. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +1 -1
  34. package/templates/skills/business-analyse/references/detection-strategies.md +2 -2
  35. package/templates/skills/business-analyse/references/entity-architecture-decision.md +1 -1
  36. package/templates/skills/business-analyse/references/naming-conventions.md +6 -6
  37. package/templates/skills/business-analyse/references/robustness-checks.md +4 -4
  38. package/templates/skills/business-analyse/references/spec-auto-inference.md +2 -2
  39. package/templates/skills/business-analyse/references/validation-checklist.md +3 -3
  40. package/templates/skills/business-analyse/schemas/feature-schema.json +1 -1
  41. package/templates/skills/business-analyse/schemas/sections/handoff-schema.json +2 -2
  42. package/templates/skills/business-analyse/schemas/sections/specification-schema.json +3 -2
  43. package/templates/skills/business-analyse/steps/step-00-init.md +15 -5
  44. package/templates/skills/business-analyse/steps/step-01-cadrage.md +14 -5
  45. package/templates/skills/business-analyse/steps/step-02-structure.md +17 -1
  46. package/templates/skills/business-analyse/steps/step-03-specify.md +136 -26
  47. package/templates/skills/business-analyse/steps/step-04-consolidate.md +44 -8
  48. package/templates/skills/business-analyse/templates/tpl-handoff.md +5 -5
  49. package/templates/skills/business-analyse/templates/tpl-launch-displays.md +4 -4
  50. package/templates/skills/business-analyse/templates-frd.md +4 -4
  51. package/templates/skills/{ba-design-ui → business-analyse-design}/SKILL.md +9 -9
  52. package/templates/skills/{ba-design-ui → business-analyse-design}/steps/step-01-screens.md +9 -0
  53. package/templates/skills/{ba-design-ui → business-analyse-design}/steps/step-03-navigation.md +9 -2
  54. package/templates/skills/business-analyse-develop/SKILL.md +248 -0
  55. package/templates/skills/{ralph-loop → business-analyse-develop}/references/category-completeness.md +1 -1
  56. package/templates/skills/{ralph-loop → business-analyse-develop}/references/init-resume-recovery.md +8 -8
  57. package/templates/skills/{ralph-loop → business-analyse-develop}/references/multi-module-queue.md +1 -1
  58. package/templates/skills/business-analyse-develop/references/quality-gates.md +70 -0
  59. package/templates/skills/{ralph-loop → business-analyse-develop}/references/task-transform-legacy.md +1 -1
  60. package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-00-init.md +20 -4
  61. package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-01-task.md +3 -2
  62. package/templates/skills/business-analyse-develop/steps/step-01-v4-execute.md +131 -0
  63. package/templates/skills/business-analyse-develop/steps/step-02-v4-verify.md +156 -0
  64. package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-04-check.md +1 -1
  65. package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-05-report.md +1 -1
  66. package/templates/skills/{derive-prd → business-analyse-handoff}/SKILL.md +7 -7
  67. package/templates/skills/{derive-prd → business-analyse-handoff}/references/acceptance-criteria.md +5 -5
  68. package/templates/skills/{derive-prd → business-analyse-handoff}/references/handoff-file-templates.md +1 -1
  69. package/templates/skills/{derive-prd → business-analyse-handoff}/references/handoff-mappings.md +1 -1
  70. package/templates/skills/{derive-prd → business-analyse-handoff}/references/handoff-seeddata-generation.md +2 -2
  71. package/templates/skills/{derive-prd → business-analyse-handoff}/references/prd-generation.md +14 -14
  72. package/templates/skills/{derive-prd → business-analyse-handoff}/schemas/handoff-schema.json +2 -2
  73. package/templates/skills/{derive-prd → business-analyse-handoff}/steps/step-00-validate.md +6 -6
  74. package/templates/skills/{derive-prd → business-analyse-handoff}/steps/step-01-transform.md +46 -7
  75. package/templates/skills/{derive-prd → business-analyse-handoff}/steps/step-02-export.md +34 -14
  76. package/templates/skills/{ba-generate-html → business-analyse-html}/SKILL.md +4 -4
  77. package/templates/skills/{ba-generate-html → business-analyse-html}/html/ba-interactive.html +709 -277
  78. package/templates/skills/{ba-generate-html → business-analyse-html}/html/build-html.js +25 -3
  79. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/01-data-init.js +54 -0
  80. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/02-navigation.js +97 -3
  81. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/03-render-cadrage.js +8 -7
  82. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/04-render-modules.js +7 -7
  83. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/05-render-specs.js +188 -85
  84. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/06-render-consolidation.js +15 -14
  85. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/06-render-mockups.js +19 -19
  86. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/07-render-handoff.js +24 -4
  87. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/08-editing.js +6 -2
  88. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/09-export.js +27 -57
  89. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/10-comments.js +67 -45
  90. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/scripts/11-review-panel.js +15 -13
  91. package/templates/skills/business-analyse-html/html/src/styles/02-layout.css +216 -0
  92. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/05-modules.css +36 -0
  93. package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/template.html +22 -12
  94. package/templates/skills/{ba-generate-html → business-analyse-html}/references/data-build.md +1 -1
  95. package/templates/skills/{ba-generate-html → business-analyse-html}/references/data-mapping.md +5 -1
  96. package/templates/skills/{ba-generate-html → business-analyse-html}/references/output-modes.md +7 -7
  97. package/templates/skills/{ba-generate-html → business-analyse-html}/steps/step-01-collect.md +25 -1
  98. package/templates/skills/{ba-generate-html → business-analyse-html}/steps/step-02-build-data.md +33 -5
  99. package/templates/skills/{ba-generate-html → business-analyse-html}/steps/step-03-render.md +2 -2
  100. package/templates/skills/{ba-generate-html → business-analyse-html}/steps/step-04-verify.md +2 -2
  101. package/templates/skills/{ba-review → business-analyse-review}/SKILL.md +11 -10
  102. package/templates/skills/{ba-review → business-analyse-review}/references/review-data-mapping.md +2 -2
  103. package/templates/skills/business-analyse-review/steps/step-00-init.md +107 -0
  104. package/templates/skills/{ba-review → business-analyse-review}/steps/step-01-apply.md +19 -11
  105. package/templates/skills/business-analyse-status/SKILL.md +118 -0
  106. package/templates/skills/documentation/SKILL.md +2 -2
  107. package/templates/skills/sketch/SKILL.md +172 -0
  108. package/templates/skills/sketch/references/domain-heuristics.md +116 -0
  109. package/templates/skills/ba-generate-html/html/src/styles/02-layout.css +0 -101
  110. package/templates/skills/ralph-loop/SKILL.md +0 -240
  111. /package/templates/skills/{ba-design-ui → business-analyse-design}/steps/step-02-wireframes.md +0 -0
  112. /package/templates/skills/{ralph-loop → business-analyse-develop}/references/category-rules.md +0 -0
  113. /package/templates/skills/{ralph-loop → business-analyse-develop}/references/compact-loop.md +0 -0
  114. /package/templates/skills/{ralph-loop → business-analyse-develop}/references/module-transition.md +0 -0
  115. /package/templates/skills/{ralph-loop → business-analyse-develop}/references/parallel-execution.md +0 -0
  116. /package/templates/skills/{ralph-loop → business-analyse-develop}/references/section-splitting.md +0 -0
  117. /package/templates/skills/{ralph-loop → business-analyse-develop}/references/team-orchestration.md +0 -0
  118. /package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-02-execute.md +0 -0
  119. /package/templates/skills/{ralph-loop → business-analyse-develop}/steps/step-03-commit.md +0 -0
  120. /package/templates/skills/{derive-prd → business-analyse-handoff}/references/entity-domain-mapping.md +0 -0
  121. /package/templates/skills/{derive-prd → business-analyse-handoff}/references/readiness-scoring.md +0 -0
  122. /package/templates/skills/{derive-prd → business-analyse-handoff}/templates/tpl-progress.md +0 -0
  123. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/cadrage-context.html +0 -0
  124. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/cadrage-scope.html +0 -0
  125. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/cadrage-stakeholders.html +0 -0
  126. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/cadrage-success.html +0 -0
  127. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/consol-datamodel.html +0 -0
  128. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/consol-flows.html +0 -0
  129. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/consol-interactions.html +0 -0
  130. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/consol-permissions.html +0 -0
  131. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/decomp-dependencies.html +0 -0
  132. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/decomp-modules.html +0 -0
  133. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/handoff-summary.html +0 -0
  134. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/partials/module-spec-container.html +0 -0
  135. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/01-variables.css +0 -0
  136. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/03-navigation.css +0 -0
  137. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/04-cards.css +0 -0
  138. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/06-wireframes.css +0 -0
  139. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/07-comments.css +0 -0
  140. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/08-review-panel.css +0 -0
  141. /package/templates/skills/{ba-generate-html → business-analyse-html}/html/src/styles/09-mockups-html.css +0 -0
  142. /package/templates/skills/{ba-generate-html → business-analyse-html}/references/wireframe-svg-style-guide.md +0 -0
@@ -27,23 +27,24 @@ function renderModuleSpecSection(mod) {
27
27
 
28
28
  return `
29
29
  <div class="section" id="module-spec-${code}" style="display:none;">
30
- <h2 class="section-title">${mod.name}</h2>
31
- <p class="section-subtitle">${mod.description || 'Spécification détaillée de ce domaine fonctionnel.'}</p>
32
-
33
- <div class="tab-bar">
34
- <button class="tab-btn active" onclick="switchTab('${code}', 'uc')">Cas d'utilisation</button>
35
- <button class="tab-btn" onclick="switchTab('${code}', 'br')">Règles métier</button>
36
- <button class="tab-btn" onclick="switchTab('${code}', 'ent')">Données</button>
37
- <button class="tab-btn" onclick="switchTab('${code}', 'perm')">Droits d'accès</button>
38
- <button class="tab-btn" onclick="switchTab('${code}', 'mock')">Maquettes</button>
39
- <button class="tab-btn" onclick="switchTab('${code}', 'notes')">Notes</button>
40
- <button class="tab-btn" onclick="switchTab('${code}', 'struct')">Structure</button>
30
+ <h2 class="section-title">${escapeHtml(mod.name)}</h2>
31
+ <p class="section-subtitle">${escapeHtml(mod.description || 'Spécification détaillée de ce domaine fonctionnel.')}</p>
32
+
33
+ <div class="tab-bar" role="tablist" aria-label="Spécifications du module ${escapeHtml(mod.name)}">
34
+ <button class="tab-btn active" role="tab" aria-selected="true" aria-controls="tab-${code}-uc" onclick="switchTab('${code}', 'uc')">Cas d'utilisation</button>
35
+ <button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-br" onclick="switchTab('${code}', 'br')">Règles métier</button>
36
+ <button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-ent" onclick="switchTab('${code}', 'ent')">Données</button>
37
+ <button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-perm" onclick="switchTab('${code}', 'perm')">Droits d'accès</button>
38
+ <button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-mock" onclick="switchTab('${code}', 'mock')">Maquettes</button>
39
+ <button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-notes" onclick="switchTab('${code}', 'notes')">Notes</button>
40
+ <button class="tab-btn" role="tab" aria-selected="false" aria-controls="tab-${code}-struct" onclick="switchTab('${code}', 'struct')">Structure</button>
41
41
  </div>
42
42
 
43
43
  <!-- TAB: Cas d'utilisation -->
44
- <div class="tab-panel active" id="tab-${code}-uc">
44
+ <div class="tab-panel active" id="tab-${code}-uc" role="tabpanel" aria-hidden="false">
45
45
  <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Décrivez ce que chaque type d'utilisateur peut faire dans ce domaine. Un cas d'utilisation = une action concrète.</p>
46
46
  <div id="ucList-${code}" class="uc-list">
47
+ ${hasHierarchicalSpecs(mod) ? renderSectionGroupedItems(mod, 'useCases', code, renderUseCase) : ''}
47
48
  ${spec.useCases.map((uc, i) => renderUseCase(code, uc, i)).join('')}
48
49
  </div>
49
50
  <button class="add-btn" onclick="toggleForm('addUcForm-${code}')">+ Ajouter un cas d'utilisation</button>
@@ -73,9 +74,10 @@ function renderModuleSpecSection(mod) {
73
74
  </div>
74
75
 
75
76
  <!-- TAB: Règles métier -->
76
- <div class="tab-panel" id="tab-${code}-br">
77
+ <div class="tab-panel" id="tab-${code}-br" role="tabpanel" aria-hidden="true">
77
78
  <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Les règles que le système doit respecter. Formulez-les sous forme de conditions : "Si... alors... sinon..."</p>
78
79
  <div id="brList-${code}">
80
+ ${hasHierarchicalSpecs(mod) ? renderSectionGroupedItems(mod, 'businessRules', code, renderBusinessRule) : ''}
79
81
  ${spec.businessRules.map((br, i) => renderBusinessRule(code, br, i)).join('')}
80
82
  </div>
81
83
  <button class="add-btn" onclick="toggleForm('addBrForm-${code}')">+ Ajouter une règle métier</button>
@@ -111,7 +113,7 @@ function renderModuleSpecSection(mod) {
111
113
  </div>
112
114
 
113
115
  <!-- TAB: Données (Entités) -->
114
- <div class="tab-panel" id="tab-${code}-ent">
116
+ <div class="tab-panel" id="tab-${code}-ent" role="tabpanel" aria-hidden="true">
115
117
  <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Les types de données que ce domaine gère. Décrivez les informations importantes à enregistrer pour chaque type.</p>
116
118
  <div id="entList-${code}">
117
119
  ${spec.entities.length > 0
@@ -148,7 +150,7 @@ function renderModuleSpecSection(mod) {
148
150
  </div>
149
151
 
150
152
  <!-- TAB: Droits d'accès -->
151
- <div class="tab-panel" id="tab-${code}-perm">
153
+ <div class="tab-panel" id="tab-${code}-perm" role="tabpanel" aria-hidden="true">
152
154
  <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Définissez qui peut faire quoi dans ce domaine. Cochez les actions autorisées pour chaque profil.</p>
153
155
  <div id="permGrid-${code}">
154
156
  ${renderPermissionGrid(code)}
@@ -182,7 +184,7 @@ function renderModuleSpecSection(mod) {
182
184
  </div>
183
185
 
184
186
  <!-- TAB: Maquettes -->
185
- <div class="tab-panel" id="tab-${code}-mock">
187
+ <div class="tab-panel" id="tab-${code}-mock" role="tabpanel" aria-hidden="true">
186
188
  <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Maquettes validées lors de l'analyse. Ces wireframes montrent la structure exacte des écrans de ce domaine.</p>
187
189
  <div id="mockupContainer-${code}">
188
190
  ${renderModuleMockups(code)}
@@ -190,7 +192,7 @@ function renderModuleSpecSection(mod) {
190
192
  </div>
191
193
 
192
194
  <!-- TAB: Notes -->
193
- <div class="tab-panel" id="tab-${code}-notes">
195
+ <div class="tab-panel" id="tab-${code}-notes" role="tabpanel" aria-hidden="true">
194
196
  <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Notes libres, questions en suspens, éléments à clarifier pour ce domaine.</p>
195
197
  <div class="card">
196
198
  <div class="editable" contenteditable="true" data-module-code="${code}" data-module-field="notes" data-placeholder="Notez ici tout ce qui concerne ce domaine : questions, précisions, contraintes particulières...">${spec.notes || ''}</div>
@@ -198,7 +200,7 @@ function renderModuleSpecSection(mod) {
198
200
  </div>
199
201
 
200
202
  <!-- TAB: Structure (sections/resources) -->
201
- <div class="tab-panel" id="tab-${code}-struct">
203
+ <div class="tab-panel" id="tab-${code}-struct" role="tabpanel" aria-hidden="true">
202
204
  <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Organisation des écrans et ressources de ce domaine. Structure hiérarchique : sections regroupant des ressources.</p>
203
205
  <div id="structContainer-${code}">
204
206
  ${renderModuleStructure(code)}
@@ -212,19 +214,19 @@ function renderUseCase(code, uc, index) {
212
214
  <div class="uc-item">
213
215
  <div class="uc-header">
214
216
  <span class="uc-id">UC-${String(index + 1).padStart(3, '0')}</span>
215
- <span class="uc-title">${uc.name}</span>
217
+ <span class="uc-title">${escapeHtml(uc.name)}</span>
216
218
  <div class="uc-actions">
217
219
  <button class="btn btn-sm" onclick="removeUseCase('${code}',${index})">Supprimer</button>
218
220
  </div>
219
221
  </div>
220
- <div class="uc-actors"><div class="uc-actor">${uc.actor}</div></div>
221
- ${uc.steps ? `<div class="uc-detail-label">Déroulement</div><div class="uc-detail">${uc.steps.replace(/\n/g, '<br>')}</div>` : ''}
222
- ${uc.alternative ? `<div class="uc-detail-label">En cas de problème</div><div class="uc-detail" style="color:var(--warning);">${uc.alternative}</div>` : ''}
222
+ <div class="uc-actors"><div class="uc-actor">${escapeHtml(uc.actor)}</div></div>
223
+ ${uc.steps ? `<div class="uc-detail-label">Déroulement</div><div class="uc-detail">${escapeHtml(uc.steps).replace(/\n/g, '<br>')}</div>` : ''}
224
+ ${uc.alternative ? `<div class="uc-detail-label">En cas de problème</div><div class="uc-detail" style="color:var(--warning);">${escapeHtml(uc.alternative)}</div>` : ''}
223
225
  <div style="padding:0.5rem 0.75rem;border-top:1px solid var(--border);background:var(--bg-input);border-radius:0 0 8px 8px;">
224
226
  <label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire / Feedback :</label>
225
227
  <textarea class="form-textarea" placeholder="Ajouter un commentaire sur ce cas d'utilisation..."
226
228
  onblur="updateSpecComment('${code}','uc',${index},this.value)"
227
- style="min-height:45px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'uc', index)}</textarea>
229
+ style="min-height:45px;font-size:0.8rem;resize:vertical;">${escapeHtml(getSpecComment(code, 'uc', index))}</textarea>
228
230
  </div>
229
231
  </div>`;
230
232
  }
@@ -247,6 +249,7 @@ function addUseCase(code) {
247
249
  }
248
250
 
249
251
  function removeUseCase(code, index) {
252
+ if (!confirm('Supprimer ce cas d\'utilisation ?')) return;
250
253
  data.moduleSpecs[code].useCases.splice(index, 1);
251
254
  renderAllModuleSpecs();
252
255
  updateCounts();
@@ -259,18 +262,18 @@ function renderBusinessRule(code, br, index) {
259
262
  return `
260
263
  <div style="margin-bottom:0.5rem;">
261
264
  <div class="br-item" style="margin-bottom:0;border-radius:8px 8px 0 0;">
262
- <span class="br-category ${catColors[br.category] || 'br-cat-validation'}">${catLabels[br.category] || br.category}</span>
265
+ <span class="br-category ${catColors[br.category] || 'br-cat-validation'}">${escapeHtml(catLabels[br.category] || br.category)}</span>
263
266
  <div class="br-text" style="flex:1;">
264
- <div style="font-weight:600;color:var(--text-bright);margin-bottom:0.2rem;">${br.name}</div>
265
- <div>${br.statement}</div>
266
- ${br.example ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-top:0.25rem;font-style:italic;">Exemple : ${br.example}</div>` : ''}
267
+ <div style="font-weight:600;color:var(--text-bright);margin-bottom:0.2rem;">${escapeHtml(br.name)}</div>
268
+ <div>${escapeHtml(br.statement)}</div>
269
+ ${br.example ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-top:0.25rem;font-style:italic;">Exemple : ${escapeHtml(br.example)}</div>` : ''}
267
270
  </div>
268
271
  <button class="btn btn-sm" onclick="removeBusinessRule('${code}',${index})" style="opacity:0.5;flex-shrink:0;">&#10005;</button>
269
272
  </div>
270
273
  <div style="padding:0.4rem 0.75rem 0.6rem;background:var(--bg-input);border:1px solid var(--border);border-top:0;border-radius:0 0 8px 8px;">
271
274
  <textarea class="form-textarea" placeholder="Commentaire sur cette règle..."
272
275
  onblur="updateSpecComment('${code}','br',${index},this.value)"
273
- style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'br', index)}</textarea>
276
+ style="min-height:35px;font-size:0.8rem;resize:vertical;">${escapeHtml(getSpecComment(code, 'br', index))}</textarea>
274
277
  </div>
275
278
  </div>`;
276
279
  }
@@ -292,18 +295,20 @@ function addBusinessRule(code) {
292
295
  }
293
296
 
294
297
  function removeBusinessRule(code, index) {
298
+ if (!confirm('Supprimer cette règle métier ?')) return;
295
299
  data.moduleSpecs[code].businessRules.splice(index, 1);
296
300
  renderAllModuleSpecs();
297
301
  autoSave();
298
302
  }
299
303
 
300
304
  function renderEntity(code, ent, index) {
305
+ var attrFormId = 'addAttrForm-' + code + '-' + index;
301
306
  return `
302
307
  <div class="entity-block">
303
308
  <div class="entity-header">
304
309
  <div>
305
- <div class="entity-name">${ent.name}</div>
306
- <div class="entity-desc">${ent.description || ''}</div>
310
+ <div class="entity-name">${escapeHtml(ent.name)}</div>
311
+ <div class="entity-desc">${escapeHtml(ent.description || '')}</div>
307
312
  </div>
308
313
  <button class="btn btn-sm" onclick="removeEntity('${code}',${index})" style="opacity:0.5;">Supprimer</button>
309
314
  </div>
@@ -311,21 +316,26 @@ function renderEntity(code, ent, index) {
311
316
  <table class="attr-table">
312
317
  <thead><tr><th>Information</th><th>Description</th></tr></thead>
313
318
  <tbody>
314
- ${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${a.name}</td><td>${a.description || ''}</td></tr>`).join('')}
319
+ ${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(a.name)}</td><td>${escapeHtml(a.description || '')}</td></tr>`).join('')}
315
320
  </tbody>
316
- </table>
321
+ </table>` : ''}
317
322
  <div style="padding:0.3rem 0.75rem;">
318
- <button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="addEntityAttribute('${code}',${index})">+ Ajouter un attribut</button>
319
- </div>` : ''}
323
+ <button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="toggleForm('${attrFormId}')">+ Ajouter un attribut</button>
324
+ <div class="inline-form" id="${attrFormId}">
325
+ <div class="form-group"><label class="form-label">Nom de l'attribut</label><input type="text" class="form-input" id="attr-name-${code}-${index}" placeholder="Nom de l'attribut"></div>
326
+ <div class="form-group"><label class="form-label">Description (optionnel)</label><input type="text" class="form-input" id="attr-desc-${code}-${index}" placeholder="Description"></div>
327
+ <div class="form-actions"><button class="btn" onclick="toggleForm('${attrFormId}')">Annuler</button><button class="btn btn-primary" onclick="addEntityAttribute('${code}',${index})">Ajouter</button></div>
328
+ </div>
329
+ </div>
320
330
  ${(ent.relationships || []).length > 0 ? `
321
331
  <div style="padding:0.5rem 0.75rem;font-size:0.8rem;color:var(--text-muted);border-top:1px solid var(--border);">
322
- Relations : ${ent.relationships.map(r => `<span style="color:var(--accent);">${r}</span>`).join(', ')}
332
+ Relations : ${ent.relationships.map(r => `<span style="color:var(--accent);">${escapeHtml(typeof r === 'string' ? r : (r.target || ''))}</span>`).join(', ')}
323
333
  </div>` : ''}
324
334
  <div style="padding:0.4rem 0.75rem 0.6rem;background:var(--bg-input);border-top:1px solid var(--border);border-radius:0 0 8px 8px;">
325
335
  <label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire :</label>
326
336
  <textarea class="form-textarea" placeholder="Commentaire sur cette entité..."
327
337
  onblur="updateSpecComment('${code}','ent',${index},this.value)"
328
- style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'ent', index)}</textarea>
338
+ style="min-height:35px;font-size:0.8rem;resize:vertical;">${escapeHtml(getSpecComment(code, 'ent', index))}</textarea>
329
339
  </div>
330
340
  </div>`;
331
341
  }
@@ -353,20 +363,21 @@ function addEntity(code) {
353
363
  }
354
364
 
355
365
  function removeEntity(code, index) {
366
+ if (!confirm('Supprimer ce type de données ?')) return;
356
367
  data.moduleSpecs[code].entities.splice(index, 1);
357
368
  renderAllModuleSpecs();
358
369
  autoSave();
359
370
  }
360
371
 
361
372
  function addEntityAttribute(code, entityIndex) {
362
- var attrName = prompt('Nom de l\'attribut :');
363
- if (!attrName) return;
364
- var attrDesc = prompt('Description (optionnel) :') || '';
373
+ var attrName = document.getElementById('attr-name-' + code + '-' + entityIndex);
374
+ var attrDesc = document.getElementById('attr-desc-' + code + '-' + entityIndex);
375
+ if (!attrName || !attrName.value.trim()) return;
365
376
  if (!data.moduleSpecs[code]?.entities?.[entityIndex]) return;
366
377
  if (!data.moduleSpecs[code].entities[entityIndex].attributes) {
367
378
  data.moduleSpecs[code].entities[entityIndex].attributes = [];
368
379
  }
369
- data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName, description: attrDesc });
380
+ data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), description: (attrDesc?.value || '').trim() });
370
381
  renderAllModuleSpecs();
371
382
  autoSave();
372
383
  }
@@ -392,37 +403,6 @@ function updateWireframeComment(code, screen, value) {
392
403
  autoSave();
393
404
  }
394
405
 
395
- function renderModuleMockups(code) {
396
- var spec = data.moduleSpecs[code] || {};
397
- var screens = spec.screens || [];
398
- var wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
399
-
400
- // Priority 1: HTML mockups from screens[] specs
401
- if (screens.length > 0) {
402
- var html = '';
403
- if (typeof renderScreenMockups === 'function') {
404
- html = renderScreenMockups(code);
405
- }
406
- // Also show wireframes below if available
407
- if (wireframes.length > 0) {
408
- html += '<h3 style="color:var(--text-bright);font-size:1rem;margin:2rem 0 1rem;">Wireframes</h3>';
409
- html += wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
410
- }
411
- return html;
412
- }
413
-
414
- // Priority 2: Wireframes from EMBEDDED_ARTIFACTS
415
- if (wireframes.length === 0) {
416
- return `
417
- <div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">
418
- <p>Aucune maquette disponible pour ce module.</p>
419
- <p style="font-size:0.85rem;margin-top:0.5rem;">Les maquettes seront générées lors de la spécification détaillée.</p>
420
- </div>`;
421
- }
422
-
423
- return wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
424
- }
425
-
426
406
  function renderWireframeMockup(code, wf, i) {
427
407
  const hasSvg = !!wf.svgContent;
428
408
  const wireframeId = `wf-${code}-${i}`;
@@ -433,7 +413,7 @@ function renderWireframeMockup(code, wf, i) {
433
413
  <div class="mockup-dot mockup-dot-red"></div>
434
414
  <div class="mockup-dot mockup-dot-yellow"></div>
435
415
  <div class="mockup-dot mockup-dot-green"></div>
436
- <span class="mockup-title">${wf.screen || wf.section}</span>
416
+ <span class="mockup-title">${escapeHtml(wf.screen || wf.section)}</span>
437
417
  ${hasSvg ? `
438
418
  <div class="wireframe-toggle">
439
419
  <button class="wireframe-toggle-btn active" data-target="${wireframeId}" data-view="svg" onclick="toggleWireframeView('${wireframeId}', 'svg')">SVG</button>
@@ -450,11 +430,11 @@ function renderWireframeMockup(code, wf, i) {
450
430
  </div>
451
431
  ${wf.description ? `
452
432
  <div class="wireframe-description">
453
- <strong>Description:</strong> ${wf.description}
433
+ <strong>Description:</strong> ${escapeHtml(wf.description)}
454
434
  </div>` : ''}
455
435
  ${(wf.elements || []).length > 0 ? `
456
436
  <div class="wireframe-metadata">
457
- <div><strong>Elements:</strong> ${wf.elements.map(e => typeof e === 'string' ? e : (e.type || e.label || e.id || '')).filter(Boolean).join(', ')}</div>
437
+ <div><strong>Elements:</strong> ${wf.elements.map(e => escapeHtml(typeof e === 'string' ? e : (e.type || e.label || e.id || ''))).filter(Boolean).join(', ')}</div>
458
438
  </div>` : ''}
459
439
  ${(() => {
460
440
  const cm = wf.componentMapping;
@@ -468,7 +448,7 @@ function renderWireframeMockup(code, wf, i) {
468
448
  <thead><tr><th>Élément maquette</th><th>Composant React</th></tr></thead>
469
449
  <tbody>
470
450
  ${mappings.map(m =>
471
- '<tr><td>' + (m.wireframeElement || '') + '</td><td><code>' + (m.reactComponent || '') + '</code></td></tr>'
451
+ '<tr><td>' + escapeHtml(m.wireframeElement || '') + '</td><td><code>' + escapeHtml(m.reactComponent || '') + '</code></td></tr>'
472
452
  ).join('')}
473
453
  </tbody>
474
454
  </table>
@@ -543,12 +523,12 @@ function renderPermissionGrid(code) {
543
523
  <table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
544
524
  <thead><tr>
545
525
  <th>Profil</th>
546
- ${actions.map((a, i) => `<th style="text-align:center;">${a}${i >= baseActionsCount ? ' <span onclick="removeCustomAction('+`'${code}','${a}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer cette action">&#10005;</span>' : ''}</th>`).join('')}
526
+ ${actions.map((a, i) => `<th style="text-align:center;">${escapeHtml(a)}${i >= baseActionsCount ? ' <span onclick="removeCustomAction('+`'${code}','${a}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer cette action">&#10005;</span>' : ''}</th>`).join('')}
547
527
  </tr></thead>
548
528
  <tbody>
549
529
  ${roles.map((role, ri) => `
550
530
  <tr>
551
- <td style="font-weight:500;color:var(--text-bright);">${role}${ri >= baseRolesCount ? ' <span onclick="removeCustomRole('+`'${code}','${role}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer ce rôle">&#10005;</span>' : ''}</td>
531
+ <td style="font-weight:500;color:var(--text-bright);">${escapeHtml(role)}${ri >= baseRolesCount ? ' <span onclick="removeCustomRole('+`'${code}','${role}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer ce rôle">&#10005;</span>' : ''}</td>
552
532
  ${actions.map(action => {
553
533
  const key = role + '|' + action;
554
534
  const wildcardKey = role + '|*';
@@ -642,24 +622,52 @@ function renderModuleStructure(code) {
642
622
 
643
623
  return sections.map(function(section) {
644
624
  var resources = section.resources || [];
625
+ var sectionUCs = section.useCases || [];
626
+ var sectionBRs = section.businessRules || [];
645
627
  var html = '<div class="struct-section">';
646
628
  html += '<div class="struct-section-header">';
647
- html += '<span class="struct-section-code">' + (section.code || section.name || '') + '</span>';
629
+ html += '<span class="struct-section-code">' + escapeHtml(section.code || section.name || '') + '</span>';
648
630
  if (section.description) {
649
- html += '<span class="struct-section-desc">' + section.description + '</span>';
631
+ html += '<span class="struct-section-desc">' + escapeHtml(section.description) + '</span>';
650
632
  }
651
633
  html += '<span class="struct-section-badge">' + resources.length + ' ressource' + (resources.length !== 1 ? 's' : '') + '</span>';
652
634
  html += '</div>';
653
635
 
636
+ // Section metadata (route, permission)
637
+ if (section.route || section.permission) {
638
+ html += '<div style="padding:0.4rem 1rem;font-size:0.75rem;color:var(--text-muted);display:flex;gap:1rem;border-bottom:1px solid var(--border);">';
639
+ if (section.route) html += '<span>Route: <code style="color:var(--accent);background:var(--bg-input);padding:0.1rem 0.3rem;border-radius:3px;">' + escapeHtml(section.route) + '</code></span>';
640
+ if (section.permission) html += '<span>Permission: <code style="color:var(--accent);background:var(--bg-input);padding:0.1rem 0.3rem;border-radius:3px;">' + escapeHtml(section.permission) + '</code></span>';
641
+ html += '</div>';
642
+ }
643
+
644
+ // Section-level UC summary
645
+ if (sectionUCs.length > 0) {
646
+ html += '<div style="padding:0.4rem 1rem;font-size:0.8rem;border-bottom:1px solid var(--border);">';
647
+ html += '<span style="color:var(--text-muted);font-weight:500;">' + sectionUCs.length + ' cas d\'utilisation :</span> ';
648
+ html += sectionUCs.map(function(uc) { return '<span style="color:var(--text-bright);">' + escapeHtml(uc.name || '') + '</span>'; }).join(', ');
649
+ html += '</div>';
650
+ }
651
+
652
+ // Section-level BR summary
653
+ if (sectionBRs.length > 0) {
654
+ html += '<div style="padding:0.4rem 1rem;font-size:0.8rem;border-bottom:1px solid var(--border);">';
655
+ html += '<span style="color:var(--text-muted);font-weight:500;">' + sectionBRs.length + ' règle' + (sectionBRs.length > 1 ? 's' : '') + ' métier :</span> ';
656
+ html += sectionBRs.map(function(br) { return '<span style="color:var(--text-bright);">' + escapeHtml(br.name || '') + '</span>'; }).join(', ');
657
+ html += '</div>';
658
+ }
659
+
654
660
  if (resources.length > 0) {
655
661
  html += '<div class="struct-resources">';
656
662
  resources.forEach(function(res) {
657
663
  var resName = typeof res === 'string' ? res : (res.code || res.name || '');
664
+ var resType = typeof res === 'object' ? (res.type || '') : '';
658
665
  var resDesc = typeof res === 'object' ? (res.description || '') : '';
659
666
  html += '<div class="struct-resource">';
660
667
  html += '<span class="struct-resource-icon">&#8226;</span>';
661
- html += '<span class="struct-resource-name">' + resName + '</span>';
662
- if (resDesc) html += '<span class="struct-resource-desc">' + resDesc + '</span>';
668
+ html += '<span class="struct-resource-name">' + escapeHtml(resName) + '</span>';
669
+ if (resType) html += '<span style="font-size:0.7rem;color:var(--accent);background:rgba(6,182,212,0.1);padding:0.1rem 0.3rem;border-radius:3px;">' + escapeHtml(resType) + '</span>';
670
+ if (resDesc) html += '<span class="struct-resource-desc">' + escapeHtml(resDesc) + '</span>';
663
671
  html += '</div>';
664
672
  });
665
673
  html += '</div>';
@@ -670,14 +678,109 @@ function renderModuleStructure(code) {
670
678
  }).join('');
671
679
  }
672
680
 
681
+ /* ---------- Section-Grouped Rendering (Hierarchical Mode) ---------- */
682
+
683
+ function renderSectionGroupedItems(mod, itemsKey, code, renderFn) {
684
+ var sections = mod.anticipatedSections || [];
685
+ var html = '';
686
+ sections.forEach(function(section) {
687
+ var items = section[itemsKey] || [];
688
+ if (items.length === 0) return;
689
+ html += '<div class="section-group">';
690
+ html += '<div class="section-group-header">';
691
+ html += '<span class="section-group-icon">&#9655;</span> ';
692
+ html += '<span class="section-group-label">' + escapeHtml(section.code || section.name || '') + '</span>';
693
+ if (section.route) html += '<span class="section-group-route">' + escapeHtml(section.route) + '</span>';
694
+ html += '<span class="nav-badge">' + items.length + '</span>';
695
+ html += '</div>';
696
+ html += items.map(function(item, i) { return renderFn(code, item, i); }).join('');
697
+ html += '</div>';
698
+ });
699
+ return html;
700
+ }
701
+
702
+ function renderModuleMockups(code) {
703
+ var spec = data.moduleSpecs[code] || {};
704
+ var screens = spec.screens || [];
705
+ var wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
706
+ var mod = data.modules.find(function(m) { return m.code === code; });
707
+ var sections = mod ? (mod.anticipatedSections || []) : [];
708
+
709
+ // Priority 1: HTML mockups from screens[] specs
710
+ if (screens.length > 0) {
711
+ var html = '';
712
+ if (typeof renderScreenMockups === 'function') {
713
+ html = renderScreenMockups(code);
714
+ }
715
+ if (wireframes.length > 0) {
716
+ html += '<h3 style="color:var(--text-bright);font-size:1rem;margin:2rem 0 1rem;">Wireframes</h3>';
717
+ html += wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
718
+ }
719
+ return html;
720
+ }
721
+
722
+ // Priority 2: Wireframes grouped by section (if sections exist)
723
+ if (wireframes.length > 0 && sections.length > 1) {
724
+ var grouped = {};
725
+ var ungrouped = [];
726
+ wireframes.forEach(function(wf, i) {
727
+ var sectionCode = wf.section || wf.sectionCode || '';
728
+ var matchingSection = sections.find(function(s) { return s.code === sectionCode; });
729
+ if (matchingSection) {
730
+ if (!grouped[sectionCode]) grouped[sectionCode] = { section: matchingSection, items: [] };
731
+ grouped[sectionCode].items.push({ wf: wf, index: i });
732
+ } else {
733
+ ungrouped.push({ wf: wf, index: i });
734
+ }
735
+ });
736
+
737
+ var html = '';
738
+ Object.keys(grouped).forEach(function(sectionCode) {
739
+ var group = grouped[sectionCode];
740
+ html += '<div class="section-group">';
741
+ html += '<div class="section-group-header">';
742
+ html += '<span class="section-group-icon">&#9655;</span> ';
743
+ html += '<span class="section-group-label">' + escapeHtml(group.section.code || '') + '</span>';
744
+ html += '<span class="nav-badge">' + group.items.length + '</span>';
745
+ html += '</div>';
746
+ group.items.forEach(function(item) {
747
+ html += renderWireframeMockup(code, item.wf, item.index);
748
+ });
749
+ html += '</div>';
750
+ });
751
+ ungrouped.forEach(function(item) {
752
+ html += renderWireframeMockup(code, item.wf, item.index);
753
+ });
754
+ return html;
755
+ }
756
+
757
+ // Priority 3: Flat wireframe list
758
+ if (wireframes.length === 0) {
759
+ return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);"><p>Aucune maquette disponible pour ce module.</p><p style="font-size:0.85rem;margin-top:0.5rem;">Les maquettes seront générées lors de la spécification détaillée.</p></div>';
760
+ }
761
+ return wireframes.map(function(wf, i) { return renderWireframeMockup(code, wf, i); }).join('');
762
+ }
763
+
673
764
  function switchTab(code, tabId) {
674
765
  const section = document.getElementById('module-spec-' + code);
675
766
  if (!section) return;
676
- section.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
677
- section.querySelectorAll('.tab-panel').forEach(panel => panel.classList.remove('active'));
767
+ section.querySelectorAll('.tab-btn').forEach(function(btn) {
768
+ btn.classList.remove('active');
769
+ btn.setAttribute('aria-selected', 'false');
770
+ });
771
+ section.querySelectorAll('.tab-panel').forEach(function(panel) {
772
+ panel.classList.remove('active');
773
+ panel.setAttribute('aria-hidden', 'true');
774
+ });
678
775
  const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
679
- if (targetPanel) targetPanel.classList.add('active');
776
+ if (targetPanel) {
777
+ targetPanel.classList.add('active');
778
+ targetPanel.setAttribute('aria-hidden', 'false');
779
+ }
680
780
  const buttons = section.querySelectorAll('.tab-btn');
681
781
  const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5, struct: 6 }[tabId];
682
- if (buttons[tabIndex]) buttons[tabIndex].classList.add('active');
782
+ if (buttons[tabIndex]) {
783
+ buttons[tabIndex].classList.add('active');
784
+ buttons[tabIndex].setAttribute('aria-selected', 'true');
785
+ }
683
786
  }
@@ -53,7 +53,7 @@ function renderDataModel() {
53
53
 
54
54
  html += `<div class="dm-module-group">`;
55
55
  html += `<div class="dm-module-header">
56
- <span class="dm-module-name">${m.name || m.code}</span>
56
+ <span class="dm-module-name">${escapeHtml(m.name || m.code)}</span>
57
57
  <span class="dm-module-count">${entities.length} entité${entities.length > 1 ? 's' : ''}</span>
58
58
  </div>`;
59
59
 
@@ -64,15 +64,15 @@ function renderDataModel() {
64
64
  html += `
65
65
  <div class="dm-entity-card">
66
66
  <div class="dm-entity-header">
67
- <span class="dm-entity-name">${ent.name}</span>
67
+ <span class="dm-entity-name">${escapeHtml(ent.name)}</span>
68
68
  <span class="dm-entity-attr-count">${attrs.length} champ${attrs.length > 1 ? 's' : ''}</span>
69
69
  </div>
70
- ${ent.description ? `<div class="dm-entity-desc">${ent.description}</div>` : ''}
70
+ ${ent.description ? `<div class="dm-entity-desc">${escapeHtml(ent.description)}</div>` : ''}
71
71
  ${attrs.length > 0 ? `
72
72
  <table class="dm-attr-table">
73
73
  <thead><tr><th>Champ</th><th>Description</th></tr></thead>
74
74
  <tbody>
75
- ${attrs.map(a => `<tr><td class="dm-attr-name">${a.name}</td><td class="dm-attr-desc">${a.description || ''}</td></tr>`).join('')}
75
+ ${attrs.map(a => `<tr><td class="dm-attr-name">${escapeHtml(a.name)}</td><td class="dm-attr-desc">${escapeHtml(a.description || '')}</td></tr>`).join('')}
76
76
  </tbody>
77
77
  </table>` : ''}
78
78
  ${rels.length > 0 ? `
@@ -80,7 +80,7 @@ function renderDataModel() {
80
80
  <div class="dm-relations-title">Relations</div>
81
81
  ${rels.map(r => {
82
82
  const relText = typeof r === 'string' ? r : (r.target + ' (' + r.type + ') - ' + (r.description || ''));
83
- return `<div class="dm-relation-item">${relText}</div>`;
83
+ return `<div class="dm-relation-item">${escapeHtml(relText)}</div>`;
84
84
  }).join('')}
85
85
  </div>` : ''}
86
86
  </div>`;
@@ -100,11 +100,11 @@ function renderConsolInteractions() {
100
100
  const toName = data.modules.find(m => m.code === d.to)?.name || d.to;
101
101
  return `
102
102
  <div class="interaction-item">
103
- <span style="font-weight:600;color:var(--text-bright);">${fromName}</span>
103
+ <span style="font-weight:600;color:var(--text-bright);">${escapeHtml(fromName)}</span>
104
104
  <span class="interaction-arrow">&#8594;</span>
105
- <span style="font-weight:600;color:var(--text-bright);">${toName}</span>
105
+ <span style="font-weight:600;color:var(--text-bright);">${escapeHtml(toName)}</span>
106
106
  <span class="interaction-type">Dépendance</span>
107
- <span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${d.description || ''}</span>
107
+ <span style="flex:1;font-size:0.8rem;color:var(--text-muted);">${escapeHtml(d.description || '')}</span>
108
108
  </div>`;
109
109
  }).join('');
110
110
  }
@@ -117,11 +117,11 @@ function renderConsolPermissions() {
117
117
 
118
118
  let html = '<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">';
119
119
  html += '<thead><tr><th>Profil</th>';
120
- data.modules.forEach(m => { html += `<th style="text-align:center;">${m.name || m.code}</th>`; });
120
+ data.modules.forEach(m => { html += `<th style="text-align:center;">${escapeHtml(m.name || m.code)}</th>`; });
121
121
  html += '</tr></thead><tbody>';
122
122
 
123
123
  roles.forEach(role => {
124
- html += `<tr><td style="font-weight:500;color:var(--text-bright);">${role}</td>`;
124
+ html += `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(role)}</td>`;
125
125
  data.modules.forEach(m => {
126
126
  const allPerms = data.moduleSpecs[m.code]?.permissions || [];
127
127
  const hasWildcard = allPerms.includes(role + '|*');
@@ -165,6 +165,7 @@ function addE2EFlow() {
165
165
  }
166
166
 
167
167
  function removeE2EFlow(index) {
168
+ if (!confirm('Supprimer ce parcours bout en bout ?')) return;
168
169
  data.consolidation.e2eFlows.splice(index, 1);
169
170
  renderE2EFlows();
170
171
  autoSave();
@@ -177,15 +178,15 @@ function renderE2EFlows() {
177
178
  container.innerHTML = data.consolidation.e2eFlows.map((flow, fi) => `
178
179
  <div class="card" style="margin-bottom:1rem;">
179
180
  <div class="card-header">
180
- <span class="card-title">${flow.name}</span>
181
+ <span class="card-title">${escapeHtml(flow.name)}</span>
181
182
  <button class="btn btn-sm" onclick="removeE2EFlow(${fi})" style="opacity:0.5;">Supprimer</button>
182
183
  </div>
183
- ${flow.actors ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.5rem;">Intervenants : ${flow.actors}</div>` : ''}
184
+ ${flow.actors ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.5rem;">Intervenants : ${escapeHtml(flow.actors)}</div>` : ''}
184
185
  <div class="e2e-flow">
185
186
  ${flow.steps.map((s, i) => `
186
187
  <div class="e2e-step">
187
- <div class="e2e-step-module">${s.module}</div>
188
- <div class="e2e-step-action">${s.action}</div>
188
+ <div class="e2e-step-module">${escapeHtml(s.module)}</div>
189
+ <div class="e2e-step-action">${escapeHtml(s.action)}</div>
189
190
  </div>
190
191
  ${i < flow.steps.length - 1 ? '<div class="process-arrow">&#8594;</div>' : ''}
191
192
  `).join('')}