@atlashub/smartstack-cli 4.74.0 → 4.76.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 (121) hide show
  1. package/dist/index.js +152 -31
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +14 -3
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/agents/ba-reader.md +17 -15
  7. package/templates/agents/ba-writer.md +49 -51
  8. package/templates/skills/apex/SKILL.md +2 -2
  9. package/templates/skills/apex/_shared.md +1 -1
  10. package/templates/skills/apex/references/checks/backend-checks.sh +21 -7
  11. package/templates/skills/apex/references/checks/frontend-checks.sh +26 -0
  12. package/templates/skills/apex/references/checks/infrastructure-checks.sh +47 -10
  13. package/templates/skills/apex/references/checks/seed-checks.sh +47 -7
  14. package/templates/skills/apex/references/core-seed-data.md +20 -18
  15. package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +3 -0
  16. package/templates/skills/apex/references/post-checks.md +23 -3
  17. package/templates/skills/apex/references/smartstack-api.md +4 -4
  18. package/templates/skills/apex/references/smartstack-frontend.md +54 -8
  19. package/templates/skills/apex/references/smartstack-layers.md +6 -6
  20. package/templates/skills/apex/steps/step-00-init.md +75 -1
  21. package/templates/skills/apex/steps/step-03-execute.md +16 -4
  22. package/templates/skills/apex/steps/step-03b-layer1-seed.md +65 -6
  23. package/templates/skills/apex/steps/step-03c-layer2-backend.md +50 -5
  24. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +226 -4
  25. package/templates/skills/apex/steps/step-04-examine.md +163 -0
  26. package/templates/skills/apex-verify/SKILL.md +110 -0
  27. package/templates/skills/apex-verify/references/audit-rules.md +50 -0
  28. package/templates/skills/apex-verify/steps/step-00-init.md +119 -0
  29. package/templates/skills/apex-verify/steps/step-01-nav-audit.md +92 -0
  30. package/templates/skills/apex-verify/steps/step-02-crud-audit.md +127 -0
  31. package/templates/skills/apex-verify/steps/step-03-perm-audit.md +119 -0
  32. package/templates/skills/apex-verify/steps/step-04-route-audit.md +98 -0
  33. package/templates/skills/apex-verify/steps/step-05-report.md +110 -0
  34. package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +3 -0
  35. package/templates/skills/application/templates-frontend.md +2 -2
  36. package/templates/skills/business-analyse/SKILL.md +17 -3
  37. package/templates/skills/business-analyse/_shared.md +64 -0
  38. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +34 -26
  39. package/templates/skills/business-analyse/questionnaire/01-context.md +13 -9
  40. package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +20 -27
  41. package/templates/skills/business-analyse/questionnaire.md +86 -9
  42. package/templates/skills/business-analyse/references/03-json-schemas.md +221 -0
  43. package/templates/skills/business-analyse/references/03-post-check-validation.md +208 -0
  44. package/templates/skills/business-analyse/references/03-smartstack-entity-guards.md +32 -0
  45. package/templates/skills/business-analyse/references/04-cross-module-validation.md +95 -0
  46. package/templates/skills/business-analyse/references/04-file-allocation.md +162 -0
  47. package/templates/skills/business-analyse/references/04-naming-audit-checks.md +174 -0
  48. package/templates/skills/business-analyse/references/04-semantic-validation-matrix.md +118 -0
  49. package/templates/skills/business-analyse/references/canonical-json-formats.md +7 -3
  50. package/templates/skills/business-analyse/references/domain-research-playbook.md +234 -0
  51. package/templates/skills/business-analyse/references/entity-sourcing-presentation.md +166 -0
  52. package/templates/skills/business-analyse/references/init-resume-logic.md +70 -0
  53. package/templates/skills/business-analyse/references/module-completeness-challenge.md +174 -0
  54. package/templates/skills/business-analyse/references/multi-app-detection.md +149 -0
  55. package/templates/skills/business-analyse/references/portal-classification.md +52 -0
  56. package/templates/skills/business-analyse/references/robustness-checks.md +1 -1
  57. package/templates/skills/business-analyse/references/validation-checklist.md +35 -6
  58. package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +50 -6
  59. package/templates/skills/business-analyse/steps/step-00-init.md +22 -190
  60. package/templates/skills/business-analyse/steps/step-01-cadrage.md +365 -269
  61. package/templates/skills/business-analyse/steps/step-02-structure.md +98 -20
  62. package/templates/skills/business-analyse/steps/step-03-specify.md +810 -229
  63. package/templates/skills/business-analyse/steps/step-04-consolidate.md +509 -278
  64. package/templates/skills/business-analyse-design/SKILL.md +10 -0
  65. package/templates/skills/business-analyse-design/references/screens-post-check.md +221 -0
  66. package/templates/skills/business-analyse-design/references/screens-type-mapping.md +138 -0
  67. package/templates/skills/business-analyse-design/references/smartcomponents-templates.md +225 -0
  68. package/templates/skills/{business-analyse → business-analyse-design}/references/spec-auto-inference.md +117 -117
  69. package/templates/skills/business-analyse-design/steps/step-01-screens.md +36 -162
  70. package/templates/skills/business-analyse-design/steps/step-02-wireframes.md +8 -7
  71. package/templates/skills/business-analyse-design/steps/step-03-navigation.md +89 -42
  72. package/templates/skills/business-analyse-develop/references/compact-loop.md +9 -0
  73. package/templates/skills/business-analyse-develop/references/handoff-quality-gate.md +132 -0
  74. package/templates/skills/business-analyse-develop/references/prd-v3-transformation.md +326 -0
  75. package/templates/skills/business-analyse-develop/references/report-reconciliation.md +140 -0
  76. package/templates/skills/business-analyse-develop/references/report-template.md +142 -0
  77. package/templates/skills/business-analyse-develop/steps/step-01-task.md +5 -177
  78. package/templates/skills/business-analyse-develop/steps/step-02-execute.md +17 -4
  79. package/templates/skills/business-analyse-develop/steps/step-03-commit.md +6 -2
  80. package/templates/skills/business-analyse-develop/steps/step-04-check.md +6 -0
  81. package/templates/skills/business-analyse-develop/steps/step-05-report.md +3 -269
  82. package/templates/skills/business-analyse-handoff/SKILL.md +10 -0
  83. package/templates/skills/business-analyse-handoff/references/agent-handoff-transform-prompt.md +211 -0
  84. package/templates/skills/business-analyse-handoff/references/context-isolation-pattern.md +47 -0
  85. package/templates/skills/business-analyse-handoff/references/handoff-file-inventory.md +49 -0
  86. package/templates/skills/business-analyse-handoff/references/handoff-global-validation.md +142 -0
  87. package/templates/skills/business-analyse-handoff/references/prd-validation-checks.md +125 -0
  88. package/templates/skills/business-analyse-handoff/references/project-index-update.md +98 -0
  89. package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +9 -160
  90. package/templates/skills/business-analyse-handoff/steps/step-02-export.md +10 -99
  91. package/templates/skills/business-analyse-html/SKILL.md +10 -0
  92. package/templates/skills/business-analyse-html/html/ba-interactive.html +504 -97
  93. package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +79 -2
  94. package/templates/skills/business-analyse-html/html/src/scripts/02-navigation.js +6 -46
  95. package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +80 -11
  96. package/templates/skills/business-analyse-html/html/src/scripts/06-render-consolidation.js +2 -2
  97. package/templates/skills/business-analyse-html/html/src/scripts/06-render-mockups.js +94 -36
  98. package/templates/skills/business-analyse-html/html/src/scripts/12-render-diagrams.js +162 -0
  99. package/templates/skills/business-analyse-html/html/src/styles/10-diagrams.css +73 -0
  100. package/templates/skills/business-analyse-html/html/src/template.html +2 -0
  101. package/templates/skills/business-analyse-html/references/02-embedded-artifacts-building.md +144 -0
  102. package/templates/skills/business-analyse-html/references/02-feature-data-building.md +143 -0
  103. package/templates/skills/business-analyse-html/references/02-mapping-tables.md +442 -0
  104. package/templates/skills/business-analyse-html/references/02-normalization-helpers.md +139 -0
  105. package/templates/skills/business-analyse-html/references/02-screen-format-detection.md +283 -0
  106. package/templates/skills/business-analyse-html/references/02-self-check-validation.md +199 -0
  107. package/templates/skills/business-analyse-html/references/data-build.md +24 -1
  108. package/templates/skills/business-analyse-html/references/data-mapping.md +119 -17
  109. package/templates/skills/business-analyse-html/steps/step-02-build-data.md +18 -555
  110. package/templates/skills/business-analyse-html/steps/step-04-verify.md +92 -3
  111. package/templates/skills/business-analyse-quick/SKILL.md +807 -0
  112. package/templates/skills/{sketch → business-analyse-quick}/references/domain-heuristics.md +59 -3
  113. package/templates/skills/business-analyse-quick/references/prd-schema.md +268 -0
  114. package/templates/skills/business-analyse-review/SKILL.md +10 -0
  115. package/templates/skills/business-analyse-review/references/review-data-mapping.md +6 -0
  116. package/templates/skills/business-analyse-status/SKILL.md +8 -0
  117. package/templates/skills/dev-start/SKILL.md +143 -307
  118. package/templates/skills/efcore/SKILL.md +13 -0
  119. package/templates/skills/sketch/SKILL.md +15 -153
  120. package/templates/skills/ui-components/SKILL.md +1 -1
  121. package/templates/skills/ui-components/patterns/data-table.md +1 -1
@@ -4,6 +4,8 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>{{APPLICATION_NAME}} - Analyse métier</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
8
+ <script>mermaid.initialize({ startOnLoad: false, theme: 'dark', securityLevel: 'loose' });</script>
7
9
  <style>
8
10
  /* --- 01-variables.css --- */
9
11
  /* ============================================
@@ -2024,6 +2026,82 @@ body {
2024
2026
  .mock-form-tab-content.active {
2025
2027
  display: block;
2026
2028
  }
2029
+
2030
+
2031
+ /* --- 10-diagrams.css --- */
2032
+ /* ==========================================
2033
+ MERMAID DIAGRAMS
2034
+ ========================================== */
2035
+
2036
+ .diagram-container {
2037
+ margin: 1.5rem 0;
2038
+ padding: 1rem;
2039
+ background: var(--bg-card);
2040
+ border-radius: 8px;
2041
+ border: 1px solid var(--border);
2042
+ overflow-x: auto;
2043
+ }
2044
+
2045
+ .diagram-container h3,
2046
+ .diagram-container h4 {
2047
+ color: var(--text-bright);
2048
+ margin-bottom: 1rem;
2049
+ font-size: 1.1rem;
2050
+ }
2051
+
2052
+ .diagram-container .mermaid {
2053
+ display: flex;
2054
+ justify-content: center;
2055
+ min-height: 200px;
2056
+ }
2057
+
2058
+ .diagram-container .mermaid svg {
2059
+ max-width: 100%;
2060
+ height: auto;
2061
+ }
2062
+
2063
+ /* ERD specific */
2064
+ .diagram-erd {
2065
+ margin-bottom: 2rem;
2066
+ border-left: 3px solid var(--primary);
2067
+ }
2068
+
2069
+ /* State machine specific */
2070
+ .diagram-state-machine {
2071
+ border-left: 3px solid var(--accent);
2072
+ }
2073
+
2074
+ /* Sequence diagram specific */
2075
+ .diagram-sequence {
2076
+ border-left: 3px solid var(--success);
2077
+ }
2078
+
2079
+ /* Diagram section header */
2080
+ .diagram-section-header {
2081
+ display: flex;
2082
+ align-items: center;
2083
+ gap: 0.5rem;
2084
+ margin-bottom: 1rem;
2085
+ color: var(--text-bright);
2086
+ font-size: 1.2rem;
2087
+ font-weight: 600;
2088
+ }
2089
+
2090
+ .diagram-section-header::before {
2091
+ content: '';
2092
+ width: 4px;
2093
+ height: 1.2em;
2094
+ background: var(--primary);
2095
+ border-radius: 2px;
2096
+ }
2097
+
2098
+ /* No diagrams fallback */
2099
+ .diagram-empty {
2100
+ color: var(--text-muted);
2101
+ font-style: italic;
2102
+ padding: 1rem;
2103
+ text-align: center;
2104
+ }
2027
2105
 
2028
2106
  </style>
2029
2107
  </head>
@@ -2515,9 +2593,22 @@ if (!data.cadrage.scope || (!data.cadrage.scope.inscope?.length && data.cadrage.
2515
2593
  outofscope: (gs.outOfScope || []).map(s => typeof s === 'string' ? { name: s, description: '' } : s)
2516
2594
  };
2517
2595
  } else {
2596
+ function normScopeItem(s) {
2597
+ if (typeof s === 'string') return { name: s, description: '' };
2598
+ if (s.name) return s;
2599
+ var nm = s.feature || s.label || s.title || JSON.stringify(s);
2600
+ var parts = [];
2601
+ if (s.priority) parts.push(s.priority.toUpperCase());
2602
+ if (s.module) parts.push('Module: ' + s.module);
2603
+ return { name: nm, description: parts.join(' — ') };
2604
+ }
2518
2605
  data.cadrage.scope = {
2519
- inscope: (gs.inScope || []).map(s => typeof s === 'string' ? { name: s, description: '' } : s),
2520
- outofscope: (gs.outOfScope || []).map(s => typeof s === 'string' ? { name: s, description: '' } : s)
2606
+ inscope: (gs.inScope || []).map(normScopeItem),
2607
+ outofscope: (gs.outOfScope || []).map(function(s) {
2608
+ if (typeof s === 'string') return { name: s, description: '' };
2609
+ if (s.name) return s;
2610
+ return { name: s.feature || s.reason || JSON.stringify(s), description: s.reason || '' };
2611
+ })
2521
2612
  };
2522
2613
  }
2523
2614
  }
@@ -2555,6 +2646,70 @@ data.moduleSpecs = data.moduleSpecs || {};
2555
2646
  });
2556
2647
  });
2557
2648
 
2649
+ // Normalize permissions: convert any object format to "Role|Action" pipe-delimited strings
2650
+ // Handles 5 formats:
2651
+ // A: "Role|Action" strings (already correct)
2652
+ // B: {role, permissions: ["Read:all", ...]} with role-permission pairs
2653
+ // C: {role, permissions: ["Module.Entity.Read", ...]} with path-based permissions
2654
+ // D: {code, label, description} permission definitions without role assignment
2655
+ // E: {role: "", permissions: []} empty entries (skip)
2656
+ (data.modules || []).forEach(function(m) {
2657
+ var spec = data.moduleSpecs[m.code];
2658
+ if (!spec || !spec.permissions || spec.permissions.length === 0) return;
2659
+ // Skip if already normalized (first element is a pipe-delimited string)
2660
+ if (typeof spec.permissions[0] === 'string' && spec.permissions[0].indexOf('|') !== -1) return;
2661
+ if (typeof spec.permissions[0] !== 'object') return;
2662
+
2663
+ var actionMap = {
2664
+ 'read': 'Consulter', 'create': 'Créer', 'update': 'Modifier',
2665
+ 'delete': 'Supprimer', 'approve': 'Valider', 'validate': 'Valider',
2666
+ 'export': 'Exporter', 'admin': 'Administrer', 'import': 'Importer',
2667
+ 'viewsalary': 'Consulter', 'assignteam': 'Modifier', 'cancel': 'Supprimer',
2668
+ 'viewbalance': 'Consulter', 'invoicetime': 'Valider'
2669
+ };
2670
+ var normalized = [];
2671
+ var first = spec.permissions[0];
2672
+
2673
+ // Detect Format D: {code, label, description} — permission codes without roles
2674
+ if (first.code && !first.role && !first.permissions) {
2675
+ // Extract actions from permission codes, assign to stakeholder roles
2676
+ var roles = (data.cadrage.stakeholders || []).map(function(s) { return s.role; }).filter(Boolean);
2677
+ if (roles.length === 0) roles = ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
2678
+ spec.permissions.forEach(function(permDef) {
2679
+ var code = permDef.code || '';
2680
+ var raw = code.split('.').pop(); // "Module.Entity.Read" → "Read"
2681
+ var action = actionMap[raw.toLowerCase()] || raw;
2682
+ // Assign to first role (admin) by default — better than empty
2683
+ if (roles[0]) {
2684
+ var key = roles[0] + '|' + action;
2685
+ if (normalized.indexOf(key) === -1) normalized.push(key);
2686
+ }
2687
+ });
2688
+ } else {
2689
+ // Format B/C: {role, permissions: [...]}
2690
+ spec.permissions.forEach(function(entry) {
2691
+ var role = entry.role || entry.name || '';
2692
+ if (!role) return; // Skip Format E (empty role)
2693
+ var perms = entry.permissions || entry.actions || [];
2694
+ if (perms.length === 0 && entry.permissionPattern && entry.permissionPattern.endsWith('*')) {
2695
+ ['Consulter', 'Créer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'].forEach(function(a) {
2696
+ normalized.push(role + '|' + a);
2697
+ });
2698
+ return;
2699
+ }
2700
+ perms.forEach(function(perm) {
2701
+ if (typeof perm !== 'string') return;
2702
+ var raw = perm.split(':')[0]; // "Read:all" → "Read"
2703
+ if (raw.indexOf('.') !== -1) raw = raw.split('.').pop(); // "Module.Entity.Read" → "Read"
2704
+ var action = actionMap[raw.toLowerCase()] || raw;
2705
+ var key = role + '|' + action;
2706
+ if (normalized.indexOf(key) === -1) normalized.push(key);
2707
+ });
2708
+ });
2709
+ }
2710
+ spec.permissions = normalized;
2711
+ });
2712
+
2558
2713
  // Detect if modules use section-level specs (hierarchical mode)
2559
2714
  function hasHierarchicalSpecs(mod) {
2560
2715
  return (mod.anticipatedSections || []).some(function(s) {
@@ -2851,54 +3006,14 @@ function renderModuleNavItem(mod) {
2851
3006
  var ucCount = (spec.useCases || []).length;
2852
3007
  var brCount = (spec.businessRules || []).length;
2853
3008
  var entCount = (spec.entities || []).length;
2854
- var sections = (mod.anticipatedSections || []).map(function(s) {
2855
- return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
2856
- });
2857
- var groupId = 'mod-' + code;
2858
- var collapsed = navCollapseState[groupId] === true;
2859
-
2860
- var html = '<div class="nav-module" data-group-id="' + groupId + '">';
2861
-
2862
- // Module header (clickable to expand + navigate to module spec)
2863
3009
  var totalItems = ucCount + brCount + entCount;
2864
- html += '<a class="nav-item nav-module-header" onclick="toggleNavGroup(\'' + groupId + '\');showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">';
2865
- html += '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">&#9656;</span> ';
2866
- html += (mod.name || mod.code);
2867
- if (totalItems > 0) html += ' <span class="nav-badge">' + totalItems + '</span>';
2868
- html += '</a>';
2869
-
2870
- // Children: sections/resources (navigable sub-tree)
2871
- // Tab-level items (UC, BR, Données, etc.) are NOT shown here — they are accessed
2872
- // via the tab bar in the module content area to avoid redundancy.
2873
- html += '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>';
2874
- if (sections.length > 0) {
2875
- sections.forEach(function(section) {
2876
- var resources = section.resources || [];
2877
- var sectionUCs = (section.useCases || []).length;
2878
- var sectionBRs = (section.businessRules || []).length;
2879
- var contentCount = sectionUCs + sectionBRs + resources.length;
2880
- html += '<div class="nav-section-item">';
2881
- html += '<a class="nav-item nav-section-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'struct\')" data-section="module-struct-' + code + '-' + section.code + '">';
2882
- html += '<span class="nav-icon nav-icon-section">&#9655;</span> ' + escapeHtml(section.code || section.name || '');
2883
- if (contentCount > 0) html += ' <span class="nav-badge">' + contentCount + '</span>';
2884
- html += '</a>';
2885
- if (resources.length > 0) {
2886
- html += '<div class="nav-children nav-resources">';
2887
- resources.forEach(function(res) {
2888
- var resName = typeof res === 'string' ? res : (res.code || res.name || '');
2889
- html += '<a class="nav-item nav-resource-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'mock\');scrollToMockup(\'' + code + '\',\'' + section.code + '\')">';
2890
- html += '<span class="nav-icon nav-icon-resource">&#8226;</span> ' + escapeHtml(resName);
2891
- html += '</a>';
2892
- });
2893
- html += '</div>';
2894
- }
2895
- html += '</div>';
2896
- });
2897
- }
2898
3010
 
2899
- html += '</div>'; // nav-children
2900
- html += '</div>'; // nav-module
2901
- return html;
3011
+ // Flat module link — sections/resources are accessible via tabs (Structure, Maquettes)
3012
+ return '<a class="nav-item nav-module-header" onclick="showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">' +
3013
+ '<span class="nav-icon">&#9679;</span> ' +
3014
+ (mod.name || mod.code) +
3015
+ (totalItems > 0 ? ' <span class="nav-badge">' + totalItems + '</span>' : '') +
3016
+ '</a>';
2902
3017
  }
2903
3018
 
2904
3019
  function renderModuleTabNavItem(code, tabId, label, badge) {
@@ -3642,6 +3757,11 @@ function renderModuleSpecSection(mod) {
3642
3757
  }
3643
3758
 
3644
3759
  function renderUseCase(code, uc, index) {
3760
+ var preconditions = uc.preconditions || [];
3761
+ var postconditions = uc.postconditions || [];
3762
+ var errorScenarios = uc.errorScenarios || [];
3763
+ var description = uc.description || '';
3764
+
3645
3765
  return `
3646
3766
  <div class="uc-item">
3647
3767
  <div class="uc-header">
@@ -3652,8 +3772,12 @@ function renderUseCase(code, uc, index) {
3652
3772
  </div>
3653
3773
  </div>
3654
3774
  <div class="uc-actors"><div class="uc-actor">${escapeHtml(uc.actor)}</div></div>
3655
- ${uc.steps ? `<div class="uc-detail-label">Déroulement</div><div class="uc-detail">${escapeHtml(uc.steps).replace(/\n/g, '<br>')}</div>` : ''}
3656
- ${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>` : ''}
3775
+ ${description ? `<div class="uc-detail-label">Description</div><div class="uc-detail" style="font-style:italic;color:var(--text-muted);">${escapeHtml(description)}</div>` : ''}
3776
+ ${preconditions.length > 0 ? `<div class="uc-detail-label">Préconditions</div><div class="uc-detail"><ul style="margin:0;padding-left:1.2rem;">${preconditions.map(p => '<li>' + escapeHtml(typeof p === 'string' ? p : p.description || '') + '</li>').join('')}</ul></div>` : ''}
3777
+ ${uc.steps ? `<div class="uc-detail-label">Déroulement principal</div><div class="uc-detail">${escapeHtml(uc.steps).replace(/\n/g, '<br>')}</div>` : ''}
3778
+ ${uc.alternative ? `<div class="uc-detail-label">Scénarios alternatifs</div><div class="uc-detail" style="color:var(--warning);">${escapeHtml(uc.alternative)}</div>` : ''}
3779
+ ${errorScenarios.length > 0 ? `<div class="uc-detail-label">Scénarios d'erreur</div><div class="uc-detail" style="color:var(--danger);"><ul style="margin:0;padding-left:1.2rem;">${errorScenarios.map(e => '<li><strong>' + escapeHtml(typeof e === 'string' ? e : e.name || '') + '</strong>' + (typeof e === 'object' && e.steps ? ' : ' + escapeHtml(Array.isArray(e.steps) ? e.steps.join(', ') : e.steps) : '') + '</li>').join('')}</ul></div>` : ''}
3780
+ ${postconditions.length > 0 ? `<div class="uc-detail-label">Postconditions</div><div class="uc-detail"><ul style="margin:0;padding-left:1.2rem;">${postconditions.map(p => '<li>' + escapeHtml(typeof p === 'string' ? p : p.description || '') + '</li>').join('')}</ul></div>` : ''}
3657
3781
  <div style="padding:0.5rem 0.75rem;border-top:1px solid var(--border);background:var(--bg-input);border-radius:0 0 8px 8px;">
3658
3782
  <label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire / Feedback :</label>
3659
3783
  <textarea class="form-textarea" placeholder="Ajouter un commentaire sur ce cas d'utilisation..."
@@ -3746,15 +3870,16 @@ function renderEntity(code, ent, index) {
3746
3870
  </div>
3747
3871
  ${(ent.attributes || []).length > 0 ? `
3748
3872
  <table class="attr-table">
3749
- <thead><tr><th>Information</th><th>Description</th></tr></thead>
3873
+ <thead><tr><th>Information</th><th style="width:100px;">Type</th><th>Description</th></tr></thead>
3750
3874
  <tbody>
3751
- ${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('')}
3875
+ ${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(a.name)}</td><td style="font-family:monospace;font-size:0.8rem;color:var(--accent);">${escapeHtml(a.type || 'string')}</td><td>${escapeHtml(a.description || '')}</td></tr>`).join('')}
3752
3876
  </tbody>
3753
3877
  </table>` : ''}
3754
3878
  <div style="padding:0.3rem 0.75rem;">
3755
3879
  <button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="toggleForm('${attrFormId}')">+ Ajouter un attribut</button>
3756
3880
  <div class="inline-form" id="${attrFormId}">
3757
3881
  <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>
3882
+ <div class="form-group"><label class="form-label">Type</label><input type="text" class="form-input" id="attr-type-${code}-${index}" placeholder="string, guid, int, decimal, date, enum, boolean" value="string"></div>
3758
3883
  <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>
3759
3884
  <div class="form-actions"><button class="btn" onclick="toggleForm('${attrFormId}')">Annuler</button><button class="btn btn-primary" onclick="addEntityAttribute('${code}',${index})">Ajouter</button></div>
3760
3885
  </div>
@@ -3779,7 +3904,7 @@ function addEntity(code) {
3779
3904
 
3780
3905
  const attrs = document.getElementById('ent-attrs-' + code).value.split('\n').filter(l => l.trim()).map(l => {
3781
3906
  const parts = l.split(' - ');
3782
- return { name: parts[0]?.trim() || l.trim(), description: parts.slice(1).join(' - ').trim() };
3907
+ return { name: parts[0]?.trim() || l.trim(), type: parts[1]?.trim() || 'string', description: parts.slice(2).join(' - ').trim() || parts.slice(1).join(' - ').trim() };
3783
3908
  });
3784
3909
  const rels = document.getElementById('ent-rels-' + code).value.split('\n').filter(l => l.trim());
3785
3910
 
@@ -3803,13 +3928,14 @@ function removeEntity(code, index) {
3803
3928
 
3804
3929
  function addEntityAttribute(code, entityIndex) {
3805
3930
  var attrName = document.getElementById('attr-name-' + code + '-' + entityIndex);
3931
+ var attrType = document.getElementById('attr-type-' + code + '-' + entityIndex);
3806
3932
  var attrDesc = document.getElementById('attr-desc-' + code + '-' + entityIndex);
3807
3933
  if (!attrName || !attrName.value.trim()) return;
3808
3934
  if (!data.moduleSpecs[code]?.entities?.[entityIndex]) return;
3809
3935
  if (!data.moduleSpecs[code].entities[entityIndex].attributes) {
3810
3936
  data.moduleSpecs[code].entities[entityIndex].attributes = [];
3811
3937
  }
3812
- data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), description: (attrDesc?.value || '').trim() });
3938
+ data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), type: (attrType?.value || 'string').trim(), description: (attrDesc?.value || '').trim() });
3813
3939
  renderAllModuleSpecs();
3814
3940
  autoSave();
3815
3941
  }
@@ -3920,13 +4046,68 @@ function toggleWireframeView(wireframeId, view) {
3920
4046
  }
3921
4047
  }
3922
4048
 
4049
+ // Normalize permissions array: handles 3 formats:
4050
+ // Format 1 (string): "Role|Action" → already correct
4051
+ // Format 2 (object): {role, permissions: ["Read:all", "Create:all", ...]}
4052
+ // Format 3 (object): {role, permissions: ["Module.Entity.Read", ...]}
4053
+ function normalizePermissions(permsArray) {
4054
+ if (!permsArray || permsArray.length === 0) return [];
4055
+ // If first element is a string with '|', already normalized
4056
+ if (typeof permsArray[0] === 'string' && permsArray[0].includes('|')) return permsArray;
4057
+ // If first element is a string without '|', return as-is (unknown format)
4058
+ if (typeof permsArray[0] === 'string') return permsArray;
4059
+ // Format 2/3: objects with {role, permissions[]}
4060
+ const actionMap = {
4061
+ 'read': 'Consulter', 'create': 'Créer', 'update': 'Modifier',
4062
+ 'delete': 'Supprimer', 'approve': 'Valider', 'validate': 'Valider',
4063
+ 'export': 'Exporter', 'admin': 'Administrer', 'import': 'Importer',
4064
+ 'viewsalary': 'Consulter', 'assignteam': 'Modifier', 'cancel': 'Supprimer'
4065
+ };
4066
+ const normalized = [];
4067
+ permsArray.forEach(function(entry) {
4068
+ if (typeof entry !== 'object' || !entry) return;
4069
+ var role = entry.role || entry.name || '';
4070
+ if (!role) return;
4071
+ var perms = entry.permissions || entry.actions || [];
4072
+ if (perms.length === 0 && entry.permissionPattern && entry.permissionPattern.endsWith('*')) {
4073
+ // Wildcard: expand to all standard actions
4074
+ ['Consulter', 'Créer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'].forEach(function(a) {
4075
+ normalized.push(role + '|' + a);
4076
+ });
4077
+ return;
4078
+ }
4079
+ perms.forEach(function(perm) {
4080
+ var action = '';
4081
+ if (typeof perm === 'string') {
4082
+ // "Read:all" → extract "Read"
4083
+ var raw = perm.split(':')[0];
4084
+ // "Module.Entity.Read" → extract "Read"
4085
+ if (raw.includes('.')) raw = raw.split('.').pop();
4086
+ action = actionMap[raw.toLowerCase()] || raw;
4087
+ }
4088
+ if (action) {
4089
+ var key = role + '|' + action;
4090
+ if (normalized.indexOf(key) === -1) normalized.push(key);
4091
+ }
4092
+ });
4093
+ });
4094
+ return normalized;
4095
+ }
4096
+
3923
4097
  function getPermRoles() {
3924
- // Extract roles from actual permission data (handles English role names from JSON data)
4098
+ // Extract roles from actual permission data (handles multiple formats)
3925
4099
  const rolesFromPerms = [];
3926
4100
  const seen = new Set();
3927
4101
  data.modules.forEach(m => {
3928
- (data.moduleSpecs[m.code]?.permissions || []).forEach(p => {
3929
- const role = p.split('|')[0];
4102
+ var perms = data.moduleSpecs[m.code]?.permissions || [];
4103
+ // Handle object format directly for role extraction
4104
+ perms.forEach(p => {
4105
+ var role = '';
4106
+ if (typeof p === 'string') {
4107
+ role = p.split('|')[0];
4108
+ } else if (typeof p === 'object' && p) {
4109
+ role = p.role || p.name || '';
4110
+ }
3930
4111
  if (role && !seen.has(role)) { seen.add(role); rolesFromPerms.push(role); }
3931
4112
  });
3932
4113
  });
@@ -3949,7 +4130,9 @@ function renderPermissionGrid(code) {
3949
4130
  const baseRolesCount = roles.length - (data.customRoles || []).length;
3950
4131
  const baseActionsCount = 6;
3951
4132
 
3952
- const perms = data.moduleSpecs[code]?.permissions || [];
4133
+ // Normalize permissions to "Role|Action" format (handles object and string formats)
4134
+ const rawPerms = data.moduleSpecs[code]?.permissions || [];
4135
+ const perms = normalizePermissions(rawPerms);
3953
4136
 
3954
4137
  return `
3955
4138
  <table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
@@ -4154,7 +4337,8 @@ function renderModuleMockups(code) {
4154
4337
  }) : [];
4155
4338
 
4156
4339
  // Priority 1: HTML mockups from screens[] specs (wireframes NOT shown when screens exist)
4157
- if (screens.length > 0) {
4340
+ var hasResources = screens.some(function(s) { return (s.resources || []).length > 0; });
4341
+ if (screens.length > 0 && hasResources) {
4158
4342
  var html = '';
4159
4343
  if (typeof renderScreenMockups === 'function') {
4160
4344
  html = renderScreenMockups(code);
@@ -4302,9 +4486,9 @@ function renderDataModel() {
4302
4486
  ${ent.description ? `<div class="dm-entity-desc">${escapeHtml(ent.description)}</div>` : ''}
4303
4487
  ${attrs.length > 0 ? `
4304
4488
  <table class="dm-attr-table">
4305
- <thead><tr><th>Champ</th><th>Description</th></tr></thead>
4489
+ <thead><tr><th>Champ</th><th style="width:100px;">Type</th><th>Description</th></tr></thead>
4306
4490
  <tbody>
4307
- ${attrs.map(a => `<tr><td class="dm-attr-name">${escapeHtml(a.name)}</td><td class="dm-attr-desc">${escapeHtml(a.description || '')}</td></tr>`).join('')}
4491
+ ${attrs.map(a => `<tr><td class="dm-attr-name">${escapeHtml(a.name)}</td><td style="font-family:monospace;font-size:0.8rem;color:var(--accent);">${escapeHtml(a.type || 'string')}</td><td class="dm-attr-desc">${escapeHtml(a.description || '')}</td></tr>`).join('')}
4308
4492
  </tbody>
4309
4493
  </table>` : ''}
4310
4494
  ${rels.length > 0 ? `
@@ -4444,13 +4628,16 @@ function renderScreenMockups(code) {
4444
4628
 
4445
4629
  return screens.map(function(screen, si) {
4446
4630
  var resources = screen.resources || [];
4631
+ var content = resources.length > 0
4632
+ ? resources.map(function(res, ri) {
4633
+ return renderResourceMockup(code, screen.sectionCode, res, ri);
4634
+ }).join('')
4635
+ : '<div class="card" style="padding:1.5rem;color:var(--text-muted);text-align:center;"><p>Maquette en attente de spécification pour cette section.</p></div>';
4447
4636
  return '<div class="screen-section" id="screen-' + code + '-' + screen.sectionCode + '" style="margin-bottom:2rem;">' +
4448
4637
  '<h3 style="color:var(--text-bright);font-size:1rem;margin-bottom:1rem;">' +
4449
4638
  '<span style="color:var(--accent);">&#9656;</span> ' + escapeHtml(screen.sectionLabel || screen.sectionCode) +
4450
4639
  '</h3>' +
4451
- resources.map(function(res, ri) {
4452
- return renderResourceMockup(code, screen.sectionCode, res, ri);
4453
- }).join('') +
4640
+ content +
4454
4641
  '</div>';
4455
4642
  }).join('');
4456
4643
  }
@@ -4625,31 +4812,36 @@ function renderSmartFormMockup(res) {
4625
4812
  html += '<div class="mock-form-tab-content' + (ti === 0 || isOnly ? ' active' : '') + '"';
4626
4813
  html += ' data-mockup="' + mockupId + '" data-tab="' + ti + '">';
4627
4814
 
4628
- var fields = tab.fields || [];
4629
- var rows = [];
4630
- for (var i = 0; i < fields.length; i += 2) {
4631
- rows.push(fields.slice(i, i + 2));
4632
- }
4815
+ // Tab-level subtable: the entire tab is a subtable (type: "subtable", entity, columns)
4816
+ if (tab.type === 'subtable') {
4817
+ html += renderSubtableMockup(tab);
4818
+ } else {
4819
+ var fields = tab.fields || [];
4820
+ var rows = [];
4821
+ for (var i = 0; i < fields.length; i += 2) {
4822
+ rows.push(fields.slice(i, i + 2));
4823
+ }
4633
4824
 
4634
- rows.forEach(function(row) {
4635
- if (row.length === 1 && row[0].type === 'subtable') {
4636
- html += renderSubtableMockup(row[0]);
4637
- } else {
4638
- html += '<div class="mock-form-row">';
4639
- row.forEach(function(field) {
4640
- html += '<div class="mock-form-group">';
4641
- html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
4642
- if (field.required) html += ' <span style="color:var(--error);">*</span>';
4643
- html += '</label>';
4644
- html += renderFormFieldMockup(field);
4825
+ rows.forEach(function(row) {
4826
+ if (row.length === 1 && row[0].type === 'subtable') {
4827
+ html += renderSubtableMockup(row[0]);
4828
+ } else {
4829
+ html += '<div class="mock-form-row">';
4830
+ row.forEach(function(field) {
4831
+ html += '<div class="mock-form-group">';
4832
+ html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
4833
+ if (field.required) html += ' <span style="color:var(--error);">*</span>';
4834
+ html += '</label>';
4835
+ html += renderFormFieldMockup(field);
4836
+ html += '</div>';
4837
+ });
4645
4838
  html += '</div>';
4646
- });
4647
- html += '</div>';
4648
- }
4649
- });
4839
+ }
4840
+ });
4650
4841
 
4651
- if (fields.length === 0) {
4652
- html += '<div style="padding:2rem;text-align:center;color:var(--text-muted);font-style:italic;">Contenu de l\'onglet &laquo; ' + escapeHtml(tab.label) + ' &raquo;</div>';
4842
+ if (fields.length === 0) {
4843
+ html += '<div style="padding:2rem;text-align:center;color:var(--text-muted);font-style:italic;">Contenu de l\'onglet &laquo; ' + escapeHtml(tab.label) + ' &raquo;</div>';
4844
+ }
4653
4845
  }
4654
4846
 
4655
4847
  html += '</div>';
@@ -4694,20 +4886,39 @@ function renderSubtableMockup(field) {
4694
4886
 
4695
4887
  /* ---------- SmartCard ---------- */
4696
4888
  function renderSmartCardMockup(res) {
4697
- var columns = res.columns || res.fields || [];
4889
+ var fields = res.fields || res.columns || [];
4698
4890
  var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Cartes') + '</span></div>';
4699
4891
  html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:1rem;">';
4700
4892
 
4701
4893
  for (var i = 0; i < 4; i++) {
4702
4894
  html += '<div style="background:var(--bg-hover);border:1px solid var(--border);border-radius:8px;padding:1rem;">';
4703
- columns.forEach(function(col, ci) {
4704
- var label = col.label || col.field || col;
4705
- if (ci === 0) {
4895
+ // Title/Subtitle pattern (SmartCard with title + subtitle + fields + actions)
4896
+ if (res.title) {
4897
+ html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.25rem;">' + escapeHtml(res.title) + ' #' + (i + 1) + '</div>';
4898
+ }
4899
+ if (res.subtitle) {
4900
+ html += '<div style="font-size:0.8rem;color:var(--accent);margin-bottom:0.5rem;">' + escapeHtml(res.subtitle) + '</div>';
4901
+ }
4902
+ // Fields
4903
+ fields.forEach(function(col, ci) {
4904
+ var label = col.label || col.field || (typeof col === 'string' ? col : '');
4905
+ var iconMap = { calendar: '&#128197; ', 'dollar-sign': '&#128176; ', 'alert-circle': '&#9888; ', mail: '&#9993; ', building: '&#127970; ', user: '&#128100; ' };
4906
+ var icon = col.icon ? (iconMap[col.icon] || '&#9679; ') : '';
4907
+ if (!res.title && ci === 0) {
4706
4908
  html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.5rem;">' + escapeHtml(label) + ' #' + (i + 1) + '</div>';
4707
4909
  } else {
4708
- 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>';
4910
+ html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.25rem;">' + icon + escapeHtml(label) + ': <span style="color:var(--text);">valeur</span></div>';
4709
4911
  }
4710
4912
  });
4913
+ // Actions
4914
+ if (res.actions && res.actions.length > 0) {
4915
+ html += '<div style="margin-top:0.75rem;display:flex;gap:0.4rem;">';
4916
+ res.actions.forEach(function(a) {
4917
+ var actionLabel = typeof a === 'string' ? a.replace(/-/g, ' ') : (a.label || a.action || '');
4918
+ html += '<span class="mock-btn" style="font-size:0.75rem;">' + escapeHtml(actionLabel) + '</span>';
4919
+ });
4920
+ html += '</div>';
4921
+ }
4711
4922
  html += '</div>';
4712
4923
  }
4713
4924
  html += '</div>';
@@ -4716,7 +4927,7 @@ function renderSmartCardMockup(res) {
4716
4927
 
4717
4928
  /* ---------- SmartKanban ---------- */
4718
4929
  function renderSmartKanbanMockup(res) {
4719
- var options = res.options || res.columns || ['À faire', 'En cours', 'Terminé'];
4930
+ var options = (res.options && res.options.length > 0) ? res.options : (res.columns && res.columns.length > 0) ? res.columns : ['À faire', 'En cours', 'Terminé'];
4720
4931
  var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Kanban') + '</span></div>';
4721
4932
  html += '<div style="display:flex;gap:1rem;overflow-x:auto;padding-bottom:0.5rem;">';
4722
4933
 
@@ -4790,11 +5001,42 @@ function kpiDisplayValue(kpi) {
4790
5001
 
4791
5002
  /* ---------- SmartFilter ---------- */
4792
5003
  function renderSmartFilterMockup(res) {
4793
- var options = res.options || [];
4794
- var html = '<div style="display:flex;gap:0.4rem;flex-wrap:wrap;padding:0.5rem 0;">';
4795
- html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--primary);color:#fff;cursor:pointer;">Tous</span>';
4796
- options.forEach(function(opt) {
4797
- 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>';
5004
+ var filters = res.filters || [];
5005
+ if (filters.length === 0) {
5006
+ // Fallback: legacy options[] format (tag pills)
5007
+ var options = res.options || [];
5008
+ if (options.length === 0) return '<div style="padding:1rem;text-align:center;color:var(--text-muted);">Aucun filtre défini</div>';
5009
+ var html = '<div style="display:flex;gap:0.4rem;flex-wrap:wrap;padding:0.5rem 0;">';
5010
+ html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--primary);color:#fff;cursor:pointer;">Tous</span>';
5011
+ options.forEach(function(opt) {
5012
+ 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(typeof opt === 'string' ? opt : opt.label || '') + '</span>';
5013
+ });
5014
+ html += '</div>';
5015
+ return html;
5016
+ }
5017
+
5018
+ // Rich filter bar: render each filter based on its type
5019
+ var html = '<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:flex-end;padding:0.5rem 0;">';
5020
+ filters.forEach(function(f) {
5021
+ var label = typeof f === 'string' ? f : (f.label || f.field || '');
5022
+ var type = typeof f === 'string' ? 'text' : (f.type || 'text');
5023
+ html += '<div style="display:flex;flex-direction:column;gap:0.2rem;">';
5024
+ html += '<span style="font-size:0.7rem;color:var(--text-muted);">' + escapeHtml(label) + '</span>';
5025
+ switch (type) {
5026
+ case 'select':
5027
+ var opts = f.options || ['Option 1', 'Option 2'];
5028
+ html += '<span class="mock-input" style="width:auto;min-width:130px;font-size:0.8rem;color:var(--text-muted);">' + escapeHtml(opts[0]) + ' &#9662;</span>';
5029
+ break;
5030
+ case 'lookup':
5031
+ html += '<span class="mock-input" style="width:auto;min-width:130px;font-size:0.8rem;color:var(--accent);">' + escapeHtml(f.entity || label) + ' &#128269;</span>';
5032
+ break;
5033
+ case 'daterange':
5034
+ html += '<span class="mock-input" style="width:auto;min-width:180px;font-size:0.8rem;color:var(--text-muted);">01/01/2025 — 31/12/2025 &#128197;</span>';
5035
+ break;
5036
+ default:
5037
+ html += '<span class="mock-input" style="width:auto;min-width:160px;font-size:0.8rem;color:var(--text-muted);">Rechercher... &#128269;</span>';
5038
+ }
5039
+ html += '</div>';
4798
5040
  });
4799
5041
  html += '</div>';
4800
5042
  return html;
@@ -5721,6 +5963,171 @@ function scrollToCommentThread(sectionId, safeElId) {
5721
5963
  }
5722
5964
  }, 100);
5723
5965
  }
5966
+
5967
+
5968
+ /* --- 12-render-diagrams.js --- */
5969
+ /* ==========================================
5970
+ MERMAID DIAGRAM RENDERING
5971
+ Renders ERD, state machines, and sequence diagrams
5972
+ from consolidation.mermaidDiagrams data.
5973
+ ========================================== */
5974
+
5975
+ function renderMermaidDiagrams() {
5976
+ const data = window.FEATURE_DATA;
5977
+ if (!data) return;
5978
+
5979
+ const diagrams = data.consolidation?.mermaidDiagrams;
5980
+ if (!diagrams) return;
5981
+
5982
+ // 1. ERD in consol-datamodel (prepend before entity cards)
5983
+ if (diagrams.erd) {
5984
+ const erdContainer = document.getElementById('dataModelContainer');
5985
+ if (erdContainer) {
5986
+ const erdDiv = document.createElement('div');
5987
+ erdDiv.className = 'diagram-container diagram-erd';
5988
+ erdDiv.innerHTML =
5989
+ '<div class="diagram-section-header">Diagramme Entit\u00e9-Relation (ERD)</div>' +
5990
+ '<div class="mermaid">' + escapeHtml(diagrams.erd) + '</div>';
5991
+ erdContainer.insertBefore(erdDiv, erdContainer.firstChild);
5992
+ }
5993
+ }
5994
+
5995
+ // 1b. MCD (Modèle Conceptuel de Données) — after ERD
5996
+ if (diagrams.mcd) {
5997
+ var erdContainer = document.getElementById('dataModelContainer');
5998
+ if (erdContainer) {
5999
+ var mcdDiv = document.createElement('div');
6000
+ mcdDiv.className = 'diagram-container diagram-mcd';
6001
+ mcdDiv.innerHTML =
6002
+ '<div class="diagram-section-header" style="font-size:0.95rem;font-weight:600;color:var(--text-bright);margin-bottom:0.75rem;">Mod\u00e8le Conceptuel de Donn\u00e9es (MCD)</div>' +
6003
+ '<div class="mermaid">' + escapeHtml(diagrams.mcd) + '</div>';
6004
+ var erdDiv = erdContainer.querySelector('.diagram-erd');
6005
+ if (erdDiv && erdDiv.nextSibling) {
6006
+ erdContainer.insertBefore(mcdDiv, erdDiv.nextSibling);
6007
+ } else {
6008
+ erdContainer.appendChild(mcdDiv);
6009
+ }
6010
+ }
6011
+ }
6012
+
6013
+ // 2. State machine diagrams — inject in module spec sections or consol-datamodel
6014
+ if (diagrams.stateMachines && Object.keys(diagrams.stateMachines).length > 0) {
6015
+ const smContainer = document.getElementById('dataModelContainer');
6016
+ if (smContainer) {
6017
+ const smSection = document.createElement('div');
6018
+ smSection.innerHTML = '<div class="diagram-section-header">Cycles de vie (State Machines)</div>';
6019
+
6020
+ Object.entries(diagrams.stateMachines).forEach(function(entry) {
6021
+ var entity = entry[0];
6022
+ var def = entry[1];
6023
+ var smDiv = document.createElement('div');
6024
+ smDiv.className = 'diagram-container diagram-state-machine';
6025
+ smDiv.innerHTML =
6026
+ '<h4>' + escapeHtml(entity) + '</h4>' +
6027
+ '<div class="mermaid">' + escapeHtml(def) + '</div>';
6028
+ smSection.appendChild(smDiv);
6029
+ });
6030
+
6031
+ smContainer.appendChild(smSection);
6032
+ }
6033
+ }
6034
+
6035
+ // 2b. Use Case diagrams — inject in each module's UC tab
6036
+ if (diagrams.useCases && Object.keys(diagrams.useCases).length > 0) {
6037
+ Object.entries(diagrams.useCases).forEach(function(entry) {
6038
+ var moduleCode = entry[0];
6039
+ var def = entry[1];
6040
+ var ucTab = document.getElementById('tab-' + moduleCode + '-uc');
6041
+ if (!ucTab) {
6042
+ // Try kebab-case version
6043
+ var kebab = moduleCode.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
6044
+ ucTab = document.getElementById('tab-' + kebab + '-uc');
6045
+ }
6046
+ if (ucTab) {
6047
+ var ucDiagramDiv = document.createElement('div');
6048
+ ucDiagramDiv.className = 'diagram-container diagram-usecase';
6049
+ ucDiagramDiv.innerHTML =
6050
+ '<div class="diagram-section-header" style="font-size:0.95rem;font-weight:600;color:var(--text-bright);margin-bottom:0.75rem;">Diagramme de cas d\'utilisation</div>' +
6051
+ '<div class="mermaid">' + escapeHtml(def) + '</div>';
6052
+ // Insert after the first paragraph (description text)
6053
+ var firstP = ucTab.querySelector('p');
6054
+ if (firstP && firstP.nextSibling) {
6055
+ ucTab.insertBefore(ucDiagramDiv, firstP.nextSibling);
6056
+ } else {
6057
+ ucTab.insertBefore(ucDiagramDiv, ucTab.firstChild);
6058
+ }
6059
+ }
6060
+ });
6061
+ }
6062
+
6063
+ // 3. Sequence diagrams in consol-flows
6064
+ if (diagrams.sequences && Object.keys(diagrams.sequences).length > 0) {
6065
+ var flowsContainer = document.getElementById('consolFlowsContainer');
6066
+ // Fallback: try the consol-flows section
6067
+ if (!flowsContainer) {
6068
+ var consolFlows = document.getElementById('consol-flows');
6069
+ if (consolFlows) {
6070
+ flowsContainer = consolFlows.querySelector('.section-body') || consolFlows;
6071
+ }
6072
+ }
6073
+
6074
+ if (flowsContainer) {
6075
+ var seqSection = document.createElement('div');
6076
+ seqSection.innerHTML = '<div class="diagram-section-header">Diagrammes de s\u00e9quence</div>';
6077
+
6078
+ Object.entries(diagrams.sequences).forEach(function(entry) {
6079
+ var flowName = entry[0];
6080
+ var def = entry[1];
6081
+ var seqDiv = document.createElement('div');
6082
+ seqDiv.className = 'diagram-container diagram-sequence';
6083
+ seqDiv.innerHTML =
6084
+ '<h4>' + escapeHtml(flowName) + '</h4>' +
6085
+ '<div class="mermaid">' + escapeHtml(def) + '</div>';
6086
+ seqSection.appendChild(seqDiv);
6087
+ });
6088
+
6089
+ flowsContainer.appendChild(seqSection);
6090
+ }
6091
+ }
6092
+
6093
+ // 4. Render all mermaid elements
6094
+ try {
6095
+ if (typeof mermaid !== 'undefined' && mermaid.run) {
6096
+ mermaid.run({ querySelector: '.mermaid' });
6097
+ }
6098
+ } catch (e) {
6099
+ console.warn('Mermaid rendering failed:', e);
6100
+ // Fallback: show raw text
6101
+ document.querySelectorAll('.mermaid').forEach(function(el) {
6102
+ if (!el.querySelector('svg')) {
6103
+ el.style.whiteSpace = 'pre-wrap';
6104
+ el.style.fontFamily = 'monospace';
6105
+ el.style.fontSize = '0.85rem';
6106
+ el.style.color = 'var(--text-muted)';
6107
+ }
6108
+ });
6109
+ }
6110
+ }
6111
+
6112
+ // Escape HTML for safe injection
6113
+ function escapeHtml(text) {
6114
+ if (!text) return '';
6115
+ // For mermaid content, we need to preserve the syntax
6116
+ // Only escape actual HTML tags, not mermaid arrows
6117
+ return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
6118
+ }
6119
+
6120
+ // Auto-run after consolidation rendering
6121
+ (function() {
6122
+ // Wait for DOM and other render functions to complete
6123
+ if (document.readyState === 'loading') {
6124
+ document.addEventListener('DOMContentLoaded', function() {
6125
+ setTimeout(renderMermaidDiagrams, 500);
6126
+ });
6127
+ } else {
6128
+ setTimeout(renderMermaidDiagrams, 500);
6129
+ }
6130
+ })();
5724
6131
 
5725
6132
  </script>
5726
6133
  </body>