@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
@@ -13,7 +13,7 @@ function renderScreenMockups(code) {
13
13
  var resources = screen.resources || [];
14
14
  return '<div class="screen-section" style="margin-bottom:2rem;">' +
15
15
  '<h3 style="color:var(--text-bright);font-size:1rem;margin-bottom:1rem;">' +
16
- '<span style="color:var(--accent);">&#9656;</span> ' + (screen.sectionLabel || screen.sectionCode) +
16
+ '<span style="color:var(--accent);">&#9656;</span> ' + escapeHtml(screen.sectionLabel || screen.sectionCode) +
17
17
  '</h3>' +
18
18
  resources.map(function(res, ri) {
19
19
  return renderResourceMockup(code, screen.sectionCode, res, ri);
@@ -31,9 +31,9 @@ function renderResourceMockup(code, sectionCode, res, index) {
31
31
  html += '<div class="mockup-dot mockup-dot-red"></div>';
32
32
  html += '<div class="mockup-dot mockup-dot-yellow"></div>';
33
33
  html += '<div class="mockup-dot mockup-dot-green"></div>';
34
- html += '<span class="mockup-title">' + (res.label || res.code) + ' (' + res.type + ')</span>';
34
+ html += '<span class="mockup-title">' + escapeHtml(res.label || res.code) + ' (' + res.type + ')</span>';
35
35
  if (res.permission) {
36
- html += '<span style="margin-left:auto;font-size:0.65rem;color:var(--text-muted);background:var(--bg-dark);padding:0.15rem 0.5rem;border-radius:4px;">' + res.permission + '</span>';
36
+ html += '<span style="margin-left:auto;font-size:0.65rem;color:var(--text-muted);background:var(--bg-dark);padding:0.15rem 0.5rem;border-radius:4px;">' + escapeHtml(res.permission) + '</span>';
37
37
  }
38
38
  html += '</div>';
39
39
 
@@ -66,7 +66,7 @@ function renderResourceMockup(code, sectionCode, res, index) {
66
66
  // Notes
67
67
  if (res.notes) {
68
68
  html += '<div style="padding:0.5rem 1rem;font-size:0.8rem;color:var(--text-muted);border-top:1px solid var(--border);background:var(--bg-input);">';
69
- html += '<strong>Notes:</strong> ' + res.notes;
69
+ html += '<strong>Notes:</strong> ' + escapeHtml(res.notes);
70
70
  html += '</div>';
71
71
  }
72
72
 
@@ -93,7 +93,7 @@ function renderSmartTableMockup(res) {
93
93
 
94
94
  // Header with actions
95
95
  html += '<div class="mock-header">';
96
- html += '<span class="mock-title">' + (res.label || 'Liste') + '</span>';
96
+ html += '<span class="mock-title">' + escapeHtml(res.label || 'Liste') + '</span>';
97
97
  html += '<div style="display:flex;gap:0.4rem;">';
98
98
  (res.actions || []).forEach(function(action) {
99
99
  var isPrimary = action === 'create' || action === 'export';
@@ -115,7 +115,7 @@ function renderSmartTableMockup(res) {
115
115
  html += '<table class="mock-table">';
116
116
  html += '<thead><tr>';
117
117
  columns.forEach(function(col) {
118
- html += '<th>' + (col.label || col.field);
118
+ html += '<th>' + escapeHtml(col.label || col.field);
119
119
  if (col.sortable) html += ' <span style="font-size:0.6rem;color:var(--text-muted);">&#9650;&#9660;</span>';
120
120
  html += '</th>';
121
121
  });
@@ -166,7 +166,7 @@ function renderSmartFormMockup(res) {
166
166
 
167
167
  // Header
168
168
  html += '<div class="mock-header">';
169
- html += '<span class="mock-title">' + (res.label || 'Formulaire') + '</span>';
169
+ html += '<span class="mock-title">' + escapeHtml(res.label || 'Formulaire') + '</span>';
170
170
  html += '<div style="display:flex;gap:0.4rem;">';
171
171
  (res.actions || ['save', 'cancel']).forEach(function(action) {
172
172
  var isPrimary = action === 'save';
@@ -178,7 +178,7 @@ function renderSmartFormMockup(res) {
178
178
  if (tabs.length > 1) {
179
179
  html += '<div style="display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:1.5rem;">';
180
180
  tabs.forEach(function(tab, i) {
181
- html += '<span style="padding:0.5rem 1rem;font-size:0.85rem;cursor:pointer;border-bottom:2px solid ' + (i === 0 ? 'var(--primary)' : 'transparent') + ';color:' + (i === 0 ? 'var(--primary-light)' : 'var(--text-muted)') + ';">' + tab.label + '</span>';
181
+ html += '<span style="padding:0.5rem 1rem;font-size:0.85rem;cursor:pointer;border-bottom:2px solid ' + (i === 0 ? 'var(--primary)' : 'transparent') + ';color:' + (i === 0 ? 'var(--primary-light)' : 'var(--text-muted)') + ';">' + escapeHtml(tab.label) + '</span>';
182
182
  });
183
183
  html += '</div>';
184
184
  }
@@ -197,7 +197,7 @@ function renderSmartFormMockup(res) {
197
197
  html += '<div class="mock-form-row">';
198
198
  row.forEach(function(field) {
199
199
  html += '<div class="mock-form-group">';
200
- html += '<label class="mock-label">' + (field.label || field.field);
200
+ html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
201
201
  if (field.required) html += ' <span style="color:var(--error);">*</span>';
202
202
  html += '</label>';
203
203
  html += renderFormFieldMockup(field);
@@ -233,11 +233,11 @@ function renderSubtableMockup(field) {
233
233
  var entity = field.entity || 'Element';
234
234
  var html = '<div style="margin:1rem 0;border:1px solid var(--border);border-radius:8px;overflow:hidden;">';
235
235
  html += '<div style="display:flex;justify-content:space-between;align-items:center;padding:0.5rem 0.75rem;background:var(--bg-hover);">';
236
- html += '<span style="font-weight:500;color:var(--text-bright);font-size:0.85rem;">' + entity + '</span>';
236
+ html += '<span style="font-weight:500;color:var(--text-bright);font-size:0.85rem;">' + escapeHtml(entity) + '</span>';
237
237
  html += '<span class="mock-btn" style="font-size:0.75rem;padding:0.2rem 0.5rem;">+ Ajouter</span>';
238
238
  html += '</div>';
239
239
  html += '<table class="mock-table"><thead><tr>';
240
- cols.forEach(function(c) { html += '<th>' + c + '</th>'; });
240
+ cols.forEach(function(c) { html += '<th>' + escapeHtml(c) + '</th>'; });
241
241
  html += '</tr></thead><tbody>';
242
242
  html += '<tr>' + cols.map(function() { return '<td style="color:var(--text-muted);font-style:italic;">...</td>'; }).join('') + '</tr>';
243
243
  html += '</tbody></table></div>';
@@ -247,7 +247,7 @@ function renderSubtableMockup(field) {
247
247
  /* ---------- SmartCard ---------- */
248
248
  function renderSmartCardMockup(res) {
249
249
  var columns = res.columns || res.fields || [];
250
- var html = '<div class="mock-header"><span class="mock-title">' + (res.label || 'Cartes') + '</span></div>';
250
+ var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Cartes') + '</span></div>';
251
251
  html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:1rem;">';
252
252
 
253
253
  for (var i = 0; i < 4; i++) {
@@ -255,9 +255,9 @@ function renderSmartCardMockup(res) {
255
255
  columns.forEach(function(col, ci) {
256
256
  var label = col.label || col.field || col;
257
257
  if (ci === 0) {
258
- html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.5rem;">' + label + ' #' + (i + 1) + '</div>';
258
+ html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.5rem;">' + escapeHtml(label) + ' #' + (i + 1) + '</div>';
259
259
  } else {
260
- html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.25rem;">' + label + ': <span style="color:var(--text);">valeur</span></div>';
260
+ html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.25rem;">' + escapeHtml(label) + ': <span style="color:var(--text);">valeur</span></div>';
261
261
  }
262
262
  });
263
263
  html += '</div>';
@@ -269,14 +269,14 @@ function renderSmartCardMockup(res) {
269
269
  /* ---------- SmartKanban ---------- */
270
270
  function renderSmartKanbanMockup(res) {
271
271
  var options = res.options || res.columns || ['À faire', 'En cours', 'Terminé'];
272
- var html = '<div class="mock-header"><span class="mock-title">' + (res.label || 'Kanban') + '</span></div>';
272
+ var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Kanban') + '</span></div>';
273
273
  html += '<div style="display:flex;gap:1rem;overflow-x:auto;padding-bottom:0.5rem;">';
274
274
 
275
275
  options.forEach(function(col, ci) {
276
276
  var colLabel = typeof col === 'string' ? col : (col.label || col.field || 'Colonne');
277
277
  html += '<div style="min-width:200px;flex:1;background:var(--bg-hover);border-radius:8px;padding:0.75rem;">';
278
278
  html += '<div style="font-weight:600;font-size:0.85rem;color:var(--text-bright);margin-bottom:0.75rem;display:flex;justify-content:space-between;">';
279
- html += colLabel + ' <span style="font-size:0.7rem;background:var(--bg-card);padding:0.1rem 0.4rem;border-radius:4px;color:var(--text-muted);">' + (3 - ci) + '</span>';
279
+ html += escapeHtml(colLabel) + ' <span style="font-size:0.7rem;background:var(--bg-card);padding:0.1rem 0.4rem;border-radius:4px;color:var(--text-muted);">' + (3 - ci) + '</span>';
280
280
  html += '</div>';
281
281
  for (var j = 0; j < Math.max(1, 3 - ci); j++) {
282
282
  html += '<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:6px;padding:0.5rem;margin-bottom:0.5rem;font-size:0.8rem;">';
@@ -292,7 +292,7 @@ function renderSmartKanbanMockup(res) {
292
292
 
293
293
  /* ---------- SmartDashboard ---------- */
294
294
  function renderSmartDashboardMockup(res) {
295
- var html = '<div class="mock-header"><span class="mock-title">' + (res.label || 'Tableau de bord') + '</span></div>';
295
+ var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Tableau de bord') + '</span></div>';
296
296
 
297
297
  // KPI cards
298
298
  html += '<div class="mock-kpi-grid">';
@@ -303,7 +303,7 @@ function renderSmartDashboardMockup(res) {
303
303
  { label: 'Taux', value: '80%' }
304
304
  ];
305
305
  kpis.forEach(function(kpi) {
306
- html += '<div class="mock-kpi"><div class="mock-kpi-value">' + (kpi.value || '0') + '</div><div class="mock-kpi-label">' + (kpi.label || '') + '</div></div>';
306
+ html += '<div class="mock-kpi"><div class="mock-kpi-value">' + escapeHtml(kpi.value || '0') + '</div><div class="mock-kpi-label">' + escapeHtml(kpi.label || '') + '</div></div>';
307
307
  });
308
308
  html += '</div>';
309
309
 
@@ -319,7 +319,7 @@ function renderSmartFilterMockup(res) {
319
319
  var html = '<div style="display:flex;gap:0.4rem;flex-wrap:wrap;padding:0.5rem 0;">';
320
320
  html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--primary);color:#fff;cursor:pointer;">Tous</span>';
321
321
  options.forEach(function(opt) {
322
- html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--bg-hover);color:var(--text);border:1px solid var(--border);cursor:pointer;">' + opt + '</span>';
322
+ html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--bg-hover);color:var(--text);border:1px solid var(--border);cursor:pointer;">' + escapeHtml(opt) + '</span>';
323
323
  });
324
324
  html += '</div>';
325
325
  return html;
@@ -5,6 +5,7 @@ function renderHandoff() {
5
5
  renderHandoffStats();
6
6
  renderHandoffModules();
7
7
  renderCoverageMatrix();
8
+ renderProgressIndicator();
8
9
  }
9
10
 
10
11
  function renderHandoffStats() {
@@ -43,8 +44,8 @@ function renderHandoffModules() {
43
44
  <div style="display:flex;align-items:center;gap:0.75rem;">
44
45
  <div style="width:28px;height:28px;border-radius:50%;background:var(--primary);display:flex;align-items:center;justify-content:center;color:#fff;font-size:0.75rem;font-weight:700;flex-shrink:0;">${i + 1}</div>
45
46
  <div style="flex:1;">
46
- <div style="font-weight:600;color:var(--text-bright);">${m.name}</div>
47
- <div style="font-size:0.8rem;color:var(--text-muted);">${m.description || ''}</div>
47
+ <div style="font-weight:600;color:var(--text-bright);">${escapeHtml(m.name)}</div>
48
+ <div style="font-size:0.8rem;color:var(--text-muted);">${escapeHtml(m.description || '')}</div>
48
49
  </div>
49
50
  <div style="display:flex;gap:1rem;font-size:0.75rem;color:var(--text-muted);">
50
51
  <span>${(spec.useCases || []).length} cas d'utilisation</span>
@@ -77,12 +78,31 @@ function renderCoverageMatrix() {
77
78
  : (data.modules.length > 0 ? data.modules.map(m => m.name).join(', ') : 'À définir');
78
79
  return `
79
80
  <tr>
80
- <td>${item.name}</td>
81
+ <td>${escapeHtml(item.name)}</td>
81
82
  <td><span class="priority priority-${item.priority}">${formatPriority(item.priority)}</span></td>
82
- <td style="color:var(--text-muted);">${moduleName}</td>
83
+ <td style="color:var(--text-muted);">${escapeHtml(moduleName)}</td>
83
84
  <td style="text-align:center;color:var(--success);">&#10003;</td>
84
85
  </tr>`;
85
86
  }).join('')}
86
87
  </tbody>
87
88
  </table>`;
88
89
  }
90
+
91
+ function renderProgressIndicator() {
92
+ var container = document.getElementById('progressIndicator');
93
+ if (!container) return;
94
+ var p = computeProgress();
95
+ var html = '<div class="progress-bar-container">';
96
+ html += '<div class="progress-bar-fill" style="width:' + p.pct + '%;"></div>';
97
+ html += '</div>';
98
+ html += '<div class="progress-label">' + p.pct + '% complet — ' + p.doneCount + '/' + p.total + ' critères</div>';
99
+ html += '<div class="progress-checks">';
100
+ p.checks.forEach(function(c) {
101
+ html += '<div class="progress-check ' + (c.done ? 'done' : '') + '">';
102
+ html += '<span class="progress-check-icon">' + (c.done ? '&#10003;' : '&#9675;') + '</span> ';
103
+ html += escapeHtml(c.label);
104
+ html += '</div>';
105
+ });
106
+ html += '</div>';
107
+ container.innerHTML = html;
108
+ }
@@ -1,9 +1,13 @@
1
1
  /* ============================================
2
2
  PERSISTENCE
3
3
  ============================================ */
4
+ let _saveTimer;
4
5
  function autoSave() {
5
- data.metadata.lastModified = new Date().toISOString();
6
- localStorage.setItem(APP_KEY, JSON.stringify(data));
6
+ clearTimeout(_saveTimer);
7
+ _saveTimer = setTimeout(function() {
8
+ data.metadata.lastModified = new Date().toISOString();
9
+ localStorage.setItem(APP_KEY, JSON.stringify(data));
10
+ }, 500);
7
11
  }
8
12
 
9
13
  function saveToLocalStorage() {
@@ -1,13 +1,10 @@
1
1
  /* ============================================
2
2
  EXPORT JSON
3
3
  ============================================ */
4
- function exportJSON() {
5
- // Collect all editable fields (cadrage)
4
+ function collectEditableFields() {
6
5
  document.querySelectorAll('.editable[data-field]').forEach(el => {
7
6
  setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
8
7
  });
9
-
10
- // Collect module editable fields
11
8
  document.querySelectorAll('.editable[data-module-field]').forEach(el => {
12
9
  const code = el.dataset.moduleCode;
13
10
  const field = el.dataset.moduleField;
@@ -15,17 +12,39 @@ function exportJSON() {
15
12
  data.moduleSpecs[code][field] = el.textContent.trim();
16
13
  }
17
14
  });
15
+ }
16
+
17
+ function buildModuleSpecsExport() {
18
+ var result = {};
19
+ data.modules.forEach(function(m) {
20
+ var spec = data.moduleSpecs[m.code] || {};
21
+ result[m.code] = {
22
+ module: m,
23
+ useCases: (spec.useCases || []).map(function(uc, i) {
24
+ return Object.assign({ id: 'UC-' + String(i + 1).padStart(3, '0') }, uc);
25
+ }),
26
+ businessRules: (spec.businessRules || []).map(function(br, i) {
27
+ return Object.assign({ id: 'BR-' + (br.category || 'GEN').toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0') }, br);
28
+ }),
29
+ entities: spec.entities || [],
30
+ permissions: spec.permissions || [],
31
+ notes: spec.notes || ''
32
+ };
33
+ });
34
+ return result;
35
+ }
18
36
 
37
+ function exportJSON() {
38
+ collectEditableFields();
19
39
  data.metadata.lastModified = new Date().toISOString();
20
40
  data.metadata.exportedAt = new Date().toISOString();
21
41
 
22
- // Build complete export with structured data
23
42
  const exportData = {
24
43
  metadata: data.metadata,
25
44
  cadrage: data.cadrage,
26
45
  modules: data.modules,
27
46
  dependencies: data.dependencies,
28
- moduleSpecifications: {},
47
+ moduleSpecifications: buildModuleSpecsExport(),
29
48
  consolidation: data.consolidation,
30
49
  artifacts: EMBEDDED_ARTIFACTS,
31
50
  wireframeComments: data.wireframeComments,
@@ -35,25 +54,6 @@ function exportJSON() {
35
54
  comments: data.comments
36
55
  };
37
56
 
38
- // Structure module specs for export
39
- data.modules.forEach(m => {
40
- const spec = data.moduleSpecs[m.code] || {};
41
- exportData.moduleSpecifications[m.code] = {
42
- module: m,
43
- useCases: (spec.useCases || []).map((uc, i) => ({
44
- id: 'UC-' + String(i + 1).padStart(3, '0'),
45
- ...uc
46
- })),
47
- businessRules: (spec.businessRules || []).map((br, i) => ({
48
- id: 'BR-' + br.category.toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0'),
49
- ...br
50
- })),
51
- entities: spec.entities || [],
52
- permissions: spec.permissions || [],
53
- notes: spec.notes || ''
54
- };
55
- });
56
-
57
57
  const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
58
58
  const url = URL.createObjectURL(blob);
59
59
  const a = document.createElement('a');
@@ -93,24 +93,13 @@ function detectChanges(original, current) {
93
93
  }
94
94
 
95
95
  function saveReviewJSON() {
96
- // Collect all editable fields (same as exportJSON)
97
- document.querySelectorAll('.editable[data-field]').forEach(el => {
98
- setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
99
- });
100
- document.querySelectorAll('.editable[data-module-field]').forEach(el => {
101
- const code = el.dataset.moduleCode;
102
- const field = el.dataset.moduleField;
103
- if (data.moduleSpecs[code]) {
104
- data.moduleSpecs[code][field] = el.textContent.trim();
105
- }
106
- });
96
+ collectEditableFields();
107
97
 
108
98
  const changes = detectChanges(ORIGINAL_DATA, data);
109
99
  const hasChanges = changes.cadrage || changes.modulesAdded.length > 0
110
100
  || changes.modulesRemoved.length > 0 || changes.modulesModified.length > 0
111
101
  || changes.commentsCount > 0;
112
102
 
113
- // Build review export with metadata envelope
114
103
  const reviewData = {
115
104
  _reviewMeta: {
116
105
  sourceVersion: data.metadata.version || '1.0',
@@ -129,7 +118,7 @@ function saveReviewJSON() {
129
118
  cadrage: data.cadrage,
130
119
  modules: data.modules,
131
120
  dependencies: data.dependencies,
132
- moduleSpecifications: {},
121
+ moduleSpecifications: buildModuleSpecsExport(),
133
122
  consolidation: data.consolidation,
134
123
  wireframeComments: data.wireframeComments,
135
124
  specComments: data.specComments,
@@ -138,25 +127,6 @@ function saveReviewJSON() {
138
127
  comments: data.comments
139
128
  };
140
129
 
141
- // Structure module specs (same logic as exportJSON)
142
- data.modules.forEach(m => {
143
- const spec = data.moduleSpecs[m.code] || {};
144
- reviewData.moduleSpecifications[m.code] = {
145
- module: m,
146
- useCases: (spec.useCases || []).map((uc, i) => ({
147
- id: 'UC-' + String(i + 1).padStart(3, '0'),
148
- ...uc
149
- })),
150
- businessRules: (spec.businessRules || []).map((br, i) => ({
151
- id: 'BR-' + br.category.toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0'),
152
- ...br
153
- })),
154
- entities: spec.entities || [],
155
- permissions: spec.permissions || [],
156
- notes: spec.notes || ''
157
- };
158
- });
159
-
160
130
  const blob = new Blob([JSON.stringify(reviewData, null, 2)], { type: 'application/json' });
161
131
  const url = URL.createObjectURL(blob);
162
132
  const a = document.createElement('a');
@@ -4,14 +4,24 @@
4
4
 
5
5
  /**
6
6
  * Comments are stored in data.comments[] with structure:
7
- * { id, sectionId, cardIndex, author, timestamp, content, status, category }
7
+ * { id, sectionId, elementId, author, timestamp, content, status, category }
8
8
  *
9
- * - sectionId: matches the section div id (e.g. "cadrage-problem", "module-spec-Clients")
10
- * - cardIndex: index of the card within that section (0-based)
9
+ * - sectionId: matches the section div id (e.g. "cadrage-context", "module-spec-Clients")
10
+ * - elementId: stable identifier for the element (name, role, or generated id)
11
+ * - cardIndex: (deprecated, kept for backward compat) positional index
11
12
  * - status: "to-review" | "validated"
12
13
  * - category: "clarification" | "correction" | "suggestion"
13
14
  */
14
15
 
16
+ function getElementId(item, sectionId, index) {
17
+ // Extract a stable ID from the element's data
18
+ var title = item.querySelector('.uc-title, .entity-name, .stakeholder-role, .card-title');
19
+ if (title) return title.textContent.trim();
20
+ var nameEl = item.querySelector('[data-field]');
21
+ if (nameEl) return nameEl.dataset.field;
22
+ return sectionId + '-' + index;
23
+ }
24
+
15
25
  function initInlineComments() {
16
26
  // Cadrage sections: direct card children
17
27
  document.querySelectorAll('.section > .card, .section > .stakeholder-card, .section > .uc-item').forEach(function(card) {
@@ -21,7 +31,8 @@ function initInlineComments() {
21
31
  var sectionId = section ? section.id : 'unknown';
22
32
  var siblings = Array.from(section.querySelectorAll(':scope > .card, :scope > .stakeholder-card, :scope > .uc-item'));
23
33
  var index = siblings.indexOf(card);
24
- card.appendChild(createCommentUI(sectionId, index));
34
+ var elementId = getElementId(card, sectionId, index);
35
+ card.appendChild(createCommentUI(sectionId, elementId));
25
36
  });
26
37
 
27
38
  // Module spec lists: nested items in ucList, brList, entList containers
@@ -32,7 +43,8 @@ function initInlineComments() {
32
43
  var listId = list.id;
33
44
  var siblings = Array.from(list.children);
34
45
  var index = siblings.indexOf(item);
35
- item.appendChild(createCommentUI(listId, index));
46
+ var elementId = getElementId(item, listId, index);
47
+ item.appendChild(createCommentUI(listId, elementId));
36
48
  });
37
49
 
38
50
  // Stakeholder cards in grid
@@ -44,7 +56,8 @@ function initInlineComments() {
44
56
  var sectionId = section ? section.id : 'stakeholders';
45
57
  var siblings = Array.from(grid.children);
46
58
  var index = siblings.indexOf(card);
47
- card.appendChild(createCommentUI(sectionId, index));
59
+ var elementId = getElementId(card, sectionId, index);
60
+ card.appendChild(createCommentUI(sectionId, elementId));
48
61
  });
49
62
 
50
63
  // Scope items
@@ -54,36 +67,39 @@ function initInlineComments() {
54
67
  container.querySelectorAll('.uc-item').forEach(function(item, index) {
55
68
  if (item.dataset.commentInitialized) return;
56
69
  item.dataset.commentInitialized = 'true';
57
- item.appendChild(createCommentUI(containerId, index));
70
+ var elementId = getElementId(item, containerId, index);
71
+ item.appendChild(createCommentUI(containerId, elementId));
58
72
  });
59
73
  });
60
74
  }
61
75
 
62
- function createCommentUI(sectionId, cardIndex) {
63
- const comments = getCommentsForCard(sectionId, cardIndex);
76
+ function createCommentUI(sectionId, elementId) {
77
+ const comments = getCommentsForCard(sectionId, elementId);
64
78
  const count = comments.length;
79
+ const safeElId = String(elementId).replace(/[^a-zA-Z0-9_-]/g, '_');
80
+ const threadId = 'comment-thread-' + sectionId + '-' + safeElId;
65
81
 
66
82
  const container = document.createElement('div');
67
83
  container.className = 'comment-btn-container';
68
84
  container.dataset.sectionId = sectionId;
69
- container.dataset.cardIndex = cardIndex;
85
+ container.dataset.elementId = elementId;
70
86
 
71
87
  container.innerHTML = `
72
- <button class="comment-toggle-btn" onclick="toggleCommentThread('${sectionId}', ${cardIndex})">
88
+ <button class="comment-toggle-btn" onclick="toggleCommentThread('${sectionId}', '${safeElId}')">
73
89
  Commentaires <span class="comment-count ${count === 0 ? 'empty' : ''}">${count}</span>
74
90
  </button>
75
- <div class="comment-thread" id="comment-thread-${sectionId}-${cardIndex}">
76
- <div class="comment-items" id="comment-items-${sectionId}-${cardIndex}">
77
- ${renderCommentItems(sectionId, cardIndex)}
91
+ <div class="comment-thread" id="${threadId}">
92
+ <div class="comment-items" id="comment-items-${sectionId}-${safeElId}">
93
+ ${renderCommentItems(sectionId, elementId)}
78
94
  </div>
79
95
  <div class="comment-add-form">
80
- <textarea id="comment-text-${sectionId}-${cardIndex}" placeholder="Ajouter un commentaire..."></textarea>
81
- <select id="comment-cat-${sectionId}-${cardIndex}">
96
+ <textarea id="comment-text-${sectionId}-${safeElId}" placeholder="Ajouter un commentaire..."></textarea>
97
+ <select id="comment-cat-${sectionId}-${safeElId}">
82
98
  <option value="clarification">Clarification</option>
83
99
  <option value="correction">Correction</option>
84
100
  <option value="suggestion">Suggestion</option>
85
101
  </select>
86
- <button onclick="addInlineComment('${sectionId}', ${cardIndex})">Ajouter</button>
102
+ <button onclick="addInlineComment('${sectionId}', '${elementId.replace(/'/g, "\\'")}')">Ajouter</button>
87
103
  </div>
88
104
  </div>
89
105
  `;
@@ -91,25 +107,27 @@ function createCommentUI(sectionId, cardIndex) {
91
107
  return container;
92
108
  }
93
109
 
94
- function getCommentsForCard(sectionId, cardIndex) {
95
- return (data.comments || []).filter(c =>
96
- c.sectionId === sectionId && c.cardIndex === cardIndex
97
- );
110
+ function getCommentsForCard(sectionId, elementId) {
111
+ return (data.comments || []).filter(function(c) {
112
+ if (c.elementId) return c.sectionId === sectionId && c.elementId === elementId;
113
+ // Backward compat: match by cardIndex if elementId not set
114
+ return c.sectionId === sectionId && c.cardIndex === elementId;
115
+ });
98
116
  }
99
117
 
100
- function toggleCommentThread(sectionId, cardIndex) {
101
- const thread = document.getElementById('comment-thread-' + sectionId + '-' + cardIndex);
118
+ function toggleCommentThread(sectionId, safeElId) {
119
+ const thread = document.getElementById('comment-thread-' + sectionId + '-' + safeElId);
102
120
  if (thread) thread.classList.toggle('visible');
103
121
  }
104
122
 
105
- function renderCommentItems(sectionId, cardIndex) {
106
- const comments = getCommentsForCard(sectionId, cardIndex);
123
+ function renderCommentItems(sectionId, elementId) {
124
+ const comments = getCommentsForCard(sectionId, elementId);
107
125
  if (comments.length === 0) {
108
126
  return '<div style="font-size:0.8rem;color:var(--text-muted);padding:0.5rem 0;font-style:italic;">Aucun commentaire</div>';
109
127
  }
110
128
 
111
- return comments.map((c, i) => {
112
- const initials = (c.author || 'U').substring(0, 2).toUpperCase();
129
+ return comments.map(function(c, i) {
130
+ const initials = escapeHtml((c.author || 'U').substring(0, 2).toUpperCase());
113
131
  const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
114
132
  const globalIndex = data.comments.indexOf(c);
115
133
 
@@ -118,12 +136,12 @@ function renderCommentItems(sectionId, cardIndex) {
118
136
  <div class="comment-avatar">${initials}</div>
119
137
  <div class="comment-body">
120
138
  <div class="comment-meta">
121
- <span class="comment-author">${c.author || 'Utilisateur'}</span>
122
- <span class="comment-date">${date}</span>
123
- <span class="comment-category comment-category-${c.category}">${c.category}</span>
139
+ <span class="comment-author">${escapeHtml(c.author || 'Utilisateur')}</span>
140
+ <span class="comment-date">${escapeHtml(date)}</span>
141
+ <span class="comment-category comment-category-${escapeHtml(c.category)}">${escapeHtml(c.category)}</span>
124
142
  <span class="comment-status comment-status-${c.status}">${c.status === 'validated' ? 'Validé' : 'À revoir'}</span>
125
143
  </div>
126
- <div class="comment-text">${c.content}</div>
144
+ <div class="comment-text">${escapeHtml(c.content)}</div>
127
145
  <div class="comment-actions">
128
146
  <button class="comment-action-btn" onclick="toggleCommentStatus(${globalIndex})">${c.status === 'validated' ? 'Remettre à revoir' : 'Valider'}</button>
129
147
  <button class="comment-action-btn" onclick="deleteComment(${globalIndex})" style="color:var(--error);">Supprimer</button>
@@ -134,16 +152,17 @@ function renderCommentItems(sectionId, cardIndex) {
134
152
  }).join('');
135
153
  }
136
154
 
137
- function addInlineComment(sectionId, cardIndex) {
138
- const textEl = document.getElementById('comment-text-' + sectionId + '-' + cardIndex);
139
- const catEl = document.getElementById('comment-cat-' + sectionId + '-' + cardIndex);
155
+ function addInlineComment(sectionId, elementId) {
156
+ var safeElId = String(elementId).replace(/[^a-zA-Z0-9_-]/g, '_');
157
+ const textEl = document.getElementById('comment-text-' + sectionId + '-' + safeElId);
158
+ const catEl = document.getElementById('comment-cat-' + sectionId + '-' + safeElId);
140
159
  const content = textEl.value.trim();
141
160
  if (!content) return;
142
161
 
143
162
  const comment = {
144
163
  id: 'comment-' + Date.now(),
145
164
  sectionId: sectionId,
146
- cardIndex: cardIndex,
165
+ elementId: elementId,
147
166
  author: 'Utilisateur',
148
167
  timestamp: new Date().toISOString(),
149
168
  content: content,
@@ -154,7 +173,7 @@ function addInlineComment(sectionId, cardIndex) {
154
173
  data.comments.push(comment);
155
174
  textEl.value = '';
156
175
 
157
- refreshCommentUI(sectionId, cardIndex);
176
+ refreshCommentUI(sectionId, elementId);
158
177
  renderReviewPanel();
159
178
  autoSave();
160
179
  }
@@ -163,32 +182,35 @@ function toggleCommentStatus(globalIndex) {
163
182
  const comment = data.comments[globalIndex];
164
183
  if (!comment) return;
165
184
  comment.status = comment.status === 'validated' ? 'to-review' : 'validated';
166
- refreshCommentUI(comment.sectionId, comment.cardIndex);
185
+ var elId = comment.elementId || comment.cardIndex;
186
+ refreshCommentUI(comment.sectionId, elId);
167
187
  renderReviewPanel();
168
188
  autoSave();
169
189
  }
170
190
 
171
191
  function deleteComment(globalIndex) {
192
+ if (!confirm('Supprimer ce commentaire ?')) return;
172
193
  const comment = data.comments[globalIndex];
173
194
  if (!comment) return;
174
- const sectionId = comment.sectionId;
175
- const cardIndex = comment.cardIndex;
195
+ var sectionId = comment.sectionId;
196
+ var elId = comment.elementId || comment.cardIndex;
176
197
  data.comments.splice(globalIndex, 1);
177
- refreshCommentUI(sectionId, cardIndex);
198
+ refreshCommentUI(sectionId, elId);
178
199
  renderReviewPanel();
179
200
  autoSave();
180
201
  }
181
202
 
182
- function refreshCommentUI(sectionId, cardIndex) {
203
+ function refreshCommentUI(sectionId, elementId) {
204
+ var safeElId = String(elementId).replace(/[^a-zA-Z0-9_-]/g, '_');
183
205
  // Update comment items
184
- const itemsContainer = document.getElementById('comment-items-' + sectionId + '-' + cardIndex);
206
+ const itemsContainer = document.getElementById('comment-items-' + sectionId + '-' + safeElId);
185
207
  if (itemsContainer) {
186
- itemsContainer.innerHTML = renderCommentItems(sectionId, cardIndex);
208
+ itemsContainer.innerHTML = renderCommentItems(sectionId, elementId);
187
209
  }
188
210
 
189
211
  // Update count badge
190
- const count = getCommentsForCard(sectionId, cardIndex).length;
191
- const container = document.querySelector(`.comment-btn-container[data-section-id="${sectionId}"][data-card-index="${cardIndex}"]`);
212
+ const count = getCommentsForCard(sectionId, elementId).length;
213
+ const container = document.querySelector('.comment-btn-container[data-section-id="' + sectionId + '"][data-element-id="' + elementId + '"]');
192
214
  if (container) {
193
215
  const badge = container.querySelector('.comment-count');
194
216
  if (badge) {