@atlashub/smartstack-cli 3.20.0 → 3.22.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 (41) hide show
  1. package/dist/index.js +70 -6
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +69 -3
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/project/api.ts.template +8 -29
  7. package/templates/project/appsettings.json.template +1 -0
  8. package/templates/skills/application/references/application-roles-template.md +2 -2
  9. package/templates/skills/application/steps/step-05-frontend.md +40 -35
  10. package/templates/skills/application/templates-frontend.md +64 -36
  11. package/templates/skills/business-analyse/html/ba-interactive.html +642 -156
  12. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
  13. package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
  14. package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
  15. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +95 -8
  16. package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
  17. package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
  18. package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
  19. package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
  20. package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
  21. package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
  22. package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
  23. package/templates/skills/business-analyse/html/src/template.html +8 -76
  24. package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
  25. package/templates/skills/business-analyse/references/deploy-data-build.md +13 -9
  26. package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
  27. package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
  28. package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
  29. package/templates/skills/business-analyse/references/validate-incremental-html.md +28 -5
  30. package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
  31. package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
  32. package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
  33. package/templates/skills/business-analyse/steps/step-03c-compile.md +169 -2
  34. package/templates/skills/business-analyse/steps/step-03d-validate.md +217 -28
  35. package/templates/skills/business-analyse/steps/step-05a-handoff.md +189 -3
  36. package/templates/skills/business-analyse/steps/step-05b-deploy.md +55 -0
  37. package/templates/skills/ralph-loop/references/category-rules.md +5 -2
  38. package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
  39. package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
  40. package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
  41. package/templates/skills/ralph-loop/steps/step-02-execute.md +81 -0
@@ -139,7 +139,7 @@ body {
139
139
  PRINT
140
140
  ============================================ */
141
141
  @media print {
142
- .sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove, .phase-progress { display: none !important; }
142
+ .sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove { display: none !important; }
143
143
  .main { max-width: 100%; padding: 0; }
144
144
  .section { display: block !important; page-break-inside: avoid; }
145
145
  body { background: #fff; color: #1a1a1a; }
@@ -152,67 +152,125 @@ body {
152
152
 
153
153
  /* --- 03-navigation.css --- */
154
154
  /* ============================================
155
- SIDEBAR - Navigation 5 niveaux
155
+ SIDEBAR - Hierarchical Tree Navigation
156
156
  ============================================ */
157
- .nav-group { padding: 1rem 0; }
157
+
158
+ /* Sidebar header */
159
+ .sidebar-header {
160
+ padding: 0.75rem 1rem;
161
+ border-bottom: 1px solid var(--border);
162
+ background: var(--bg-hover);
163
+ }
164
+ .sidebar-app-name {
165
+ font-size: 0.95rem;
166
+ font-weight: 600;
167
+ color: var(--primary-light);
168
+ display: block;
169
+ white-space: nowrap;
170
+ overflow: hidden;
171
+ text-overflow: ellipsis;
172
+ }
173
+
174
+ /* Nav groups (top-level: Cadrage, Modules, Consolidation, Synthese) */
175
+ .nav-group { padding: 0.4rem 0; }
158
176
  .nav-group + .nav-group { border-top: 1px solid var(--border); }
159
177
 
160
178
  .nav-group-title {
161
- font-size: 0.65rem;
179
+ font-size: 0.7rem;
162
180
  text-transform: uppercase;
163
181
  letter-spacing: 0.1em;
164
182
  color: var(--text-muted);
165
- padding: 0 1rem;
166
- margin-bottom: 0.5rem;
183
+ padding: 0.4rem 1rem;
184
+ margin-bottom: 0.15rem;
167
185
  font-weight: 600;
186
+ cursor: pointer;
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 0.3rem;
190
+ user-select: none;
191
+ transition: color var(--transition-fast);
168
192
  }
193
+ .nav-group-title:hover { color: var(--text-bright); }
169
194
 
195
+ /* Chevron icon for expand/collapse */
196
+ .nav-chevron {
197
+ font-size: 0.6rem;
198
+ display: inline-block;
199
+ transition: transform var(--transition-fast);
200
+ color: var(--text-muted);
201
+ width: 12px;
202
+ text-align: center;
203
+ flex-shrink: 0;
204
+ }
205
+ .nav-chevron.expanded { transform: rotate(90deg); }
206
+
207
+ /* Nav items (leaf nodes) */
170
208
  .nav-item {
171
209
  display: flex;
172
210
  align-items: center;
173
- gap: 0.5rem;
174
- padding: 0.45rem 1rem;
211
+ gap: 0.4rem;
212
+ padding: 0.35rem 1rem;
175
213
  color: var(--text);
176
214
  text-decoration: none;
177
- font-size: 0.85rem;
215
+ font-size: 0.82rem;
178
216
  cursor: pointer;
179
217
  transition: all var(--transition-fast);
180
218
  border-left: 3px solid transparent;
181
219
  }
182
220
  .nav-item:hover { background: var(--bg-hover); color: var(--text-bright); }
183
- .nav-item.active { background: rgba(99,102,241,0.1); border-left-color: var(--primary); color: var(--primary-light); font-weight: 500; }
221
+ .nav-item.active {
222
+ background: rgba(99,102,241,0.1);
223
+ border-left-color: var(--primary);
224
+ color: var(--primary-light);
225
+ font-weight: 500;
226
+ }
184
227
 
185
- .nav-item .nav-icon { font-size: 1rem; width: 20px; text-align: center; }
228
+ .nav-item .nav-icon {
229
+ font-size: 0.45rem;
230
+ width: 12px;
231
+ text-align: center;
232
+ color: var(--border-light);
233
+ flex-shrink: 0;
234
+ }
186
235
  .nav-item .nav-badge {
187
236
  margin-left: auto;
188
- font-size: 0.65rem;
237
+ font-size: 0.6rem;
189
238
  background: var(--bg-hover);
190
- padding: 0.1rem 0.4rem;
239
+ padding: 0.1rem 0.35rem;
191
240
  border-radius: 10px;
192
241
  color: var(--text-muted);
242
+ min-width: 16px;
243
+ text-align: center;
193
244
  }
194
245
 
195
- .nav-children { margin-left: 1.2rem; }
196
- .nav-children .nav-item { font-size: 0.8rem; padding: 0.3rem 1rem; }
246
+ /* Nested children (indented) */
247
+ .nav-children { margin-left: 0; }
248
+ .nav-children .nav-item { padding-left: 1.6rem; font-size: 0.8rem; }
249
+ .nav-children .nav-children .nav-item { padding-left: 2.4rem; font-size: 0.78rem; }
250
+ .nav-children .nav-children .nav-children .nav-item { padding-left: 3rem; font-size: 0.75rem; }
197
251
 
198
- /* ============================================
199
- PHASE PROGRESS
200
- ============================================ */
201
- .phase-progress {
202
- display: flex; align-items: center; gap: 0.3rem;
203
- padding: 0.75rem 1rem; border-bottom: 1px solid var(--border);
252
+ /* Module header in nav (collapsible) */
253
+ .nav-module-header {
254
+ font-weight: 500;
255
+ color: var(--text-bright);
256
+ font-size: 0.85rem;
204
257
  }
205
- .phase-dot {
206
- width: 24px; height: 24px; border-radius: 50%;
207
- display: flex; align-items: center; justify-content: center;
208
- font-size: 0.6rem; font-weight: 700; color: var(--text-muted);
209
- background: var(--bg-hover); border: 2px solid var(--border);
210
- transition: all var(--transition-fast);
258
+ .nav-module-header:hover { color: var(--primary-light); }
259
+
260
+ /* Section/Resource items in nav */
261
+ .nav-section-item { margin-top: 0.1rem; }
262
+ .nav-section-link { font-style: italic; }
263
+ .nav-icon-section { font-size: 0.5rem; color: var(--accent); }
264
+ .nav-icon-resource { font-size: 0.7rem; color: var(--border-light); }
265
+ .nav-resource-link {
266
+ cursor: default;
267
+ font-size: 0.72rem;
268
+ color: var(--text-muted);
269
+ padding-top: 0.2rem;
270
+ padding-bottom: 0.2rem;
211
271
  }
212
- .phase-dot.completed { background: var(--success); border-color: var(--success); color: #fff; }
213
- .phase-dot.current { background: var(--primary); border-color: var(--primary); color: #fff; }
214
- .phase-line { flex: 1; height: 2px; background: var(--border); }
215
- .phase-line.completed { background: var(--success); }
272
+ .nav-resource-link:hover { background: transparent; color: var(--text-muted); }
273
+ .nav-resources { margin-left: 0.5rem; }
216
274
 
217
275
 
218
276
  /* --- 04-cards.css --- */
@@ -806,6 +864,70 @@ body {
806
864
  margin-bottom: 0.2rem;
807
865
  }
808
866
 
867
+ /* ============================================
868
+ STRUCTURE TAB (Sections/Resources)
869
+ ============================================ */
870
+ .struct-section {
871
+ background: var(--bg-card);
872
+ border: 1px solid var(--border);
873
+ border-radius: 10px;
874
+ margin-bottom: 1rem;
875
+ overflow: hidden;
876
+ }
877
+ .struct-section-header {
878
+ display: flex;
879
+ align-items: center;
880
+ gap: 0.75rem;
881
+ padding: 0.75rem 1rem;
882
+ background: var(--bg-hover);
883
+ border-bottom: 1px solid var(--border);
884
+ }
885
+ .struct-section-code {
886
+ font-weight: 600;
887
+ color: var(--text-bright);
888
+ font-size: 0.95rem;
889
+ }
890
+ .struct-section-desc {
891
+ flex: 1;
892
+ font-size: 0.8rem;
893
+ color: var(--text-muted);
894
+ }
895
+ .struct-section-badge {
896
+ font-size: 0.65rem;
897
+ color: var(--text-muted);
898
+ background: rgba(99,102,241,0.1);
899
+ padding: 0.1rem 0.5rem;
900
+ border-radius: 4px;
901
+ white-space: nowrap;
902
+ }
903
+ .struct-resources {
904
+ padding: 0.5rem 0;
905
+ }
906
+ .struct-resource {
907
+ display: flex;
908
+ align-items: baseline;
909
+ gap: 0.5rem;
910
+ padding: 0.35rem 1rem 0.35rem 1.5rem;
911
+ font-size: 0.85rem;
912
+ transition: background var(--transition-fast);
913
+ }
914
+ .struct-resource:hover {
915
+ background: var(--bg-hover);
916
+ }
917
+ .struct-resource-icon {
918
+ color: var(--primary-light);
919
+ font-size: 0.7rem;
920
+ flex-shrink: 0;
921
+ }
922
+ .struct-resource-name {
923
+ font-weight: 500;
924
+ color: var(--text-bright);
925
+ }
926
+ .struct-resource-desc {
927
+ font-size: 0.8rem;
928
+ color: var(--text-muted);
929
+ }
930
+
809
931
 
810
932
  /* --- 06-wireframes.css --- */
811
933
  /* ============================================
@@ -871,6 +993,48 @@ body {
871
993
  .svg-wireframe svg {
872
994
  max-width: 100%;
873
995
  height: auto;
996
+ display: block;
997
+ }
998
+
999
+ /* ============================================
1000
+ WIREFRAME VIEW TOGGLE (SVG / ASCII)
1001
+ ============================================ */
1002
+ .wireframe-toggle {
1003
+ display: flex;
1004
+ gap: 2px;
1005
+ background: var(--bg-dark);
1006
+ border-radius: 6px;
1007
+ padding: 2px;
1008
+ border: 1px solid var(--border);
1009
+ margin-left: auto;
1010
+ }
1011
+
1012
+ .wireframe-toggle-btn {
1013
+ padding: 0.25rem 0.6rem;
1014
+ font-size: 0.7rem;
1015
+ font-weight: 500;
1016
+ letter-spacing: 0.02em;
1017
+ border: none;
1018
+ border-radius: 4px;
1019
+ cursor: pointer;
1020
+ background: transparent;
1021
+ color: var(--text-muted);
1022
+ transition: all 0.15s ease;
1023
+ font-family: inherit;
1024
+ }
1025
+
1026
+ .wireframe-toggle-btn:hover {
1027
+ color: var(--text-bright);
1028
+ background: var(--bg-hover);
1029
+ }
1030
+
1031
+ .wireframe-toggle-btn.active {
1032
+ background: var(--primary);
1033
+ color: #ffffff;
1034
+ }
1035
+
1036
+ .wireframe-view:not(.active) {
1037
+ display: none;
874
1038
  }
875
1039
 
876
1040
  .wireframe-description {
@@ -1484,6 +1648,7 @@ body {
1484
1648
  <span class="header-app-name" id="appName">{{APPLICATION_NAME}}</span>
1485
1649
  <div class="header-spacer"></div>
1486
1650
  <div class="header-actions">
1651
+ <button class="btn btn-sm" onclick="resetToEmbedded()" title="Reinitialiser depuis les donnees d'origine (supprime les modifications locales)">Reset</button>
1487
1652
  <button class="btn btn-sm" onclick="saveToLocalStorage()" title="Sauvegarder les modifications dans le navigateur">Sauvegarder</button>
1488
1653
  <button class="btn btn-sm btn-review" onclick="saveReviewJSON()" title="Sauvegarder les corrections pour creer une nouvelle version">Sauvegarder corrections</button>
1489
1654
  <button class="btn btn-sm btn-primary" onclick="exportJSON()" title="Exporter les donnees au format JSON pour l'extraction">Exporter JSON</button>
@@ -1496,85 +1661,16 @@ body {
1496
1661
 
1497
1662
  <div class="body" id="appBody">
1498
1663
  <!-- ============================================
1499
- SIDEBAR - Navigation 5 niveaux
1664
+ SIDEBAR - Navigation hierarchique
1500
1665
  ============================================ -->
1501
1666
  <aside class="sidebar">
1502
- <!-- Phase Progress -->
1503
- <div class="phase-progress">
1504
- <div class="phase-dot current" id="phase-1" title="Cadrage">1</div>
1505
- <div class="phase-line" id="pline-1"></div>
1506
- <div class="phase-dot" id="phase-2" title="Decomposition">2</div>
1507
- <div class="phase-line" id="pline-2"></div>
1508
- <div class="phase-dot" id="phase-3" title="Specification">3</div>
1509
- <div class="phase-line" id="pline-3"></div>
1510
- <div class="phase-dot" id="phase-4" title="Consolidation">4</div>
1511
- <div class="phase-line" id="pline-4"></div>
1512
- <div class="phase-dot" id="phase-5" title="Synthese">5</div>
1513
- </div>
1514
-
1515
- <!-- Phase 1 : Cadrage -->
1516
- <div class="nav-group">
1517
- <div class="nav-group-title">1. Cadrage</div>
1518
- <a class="nav-item active" onclick="showSection('cadrage-context')" data-section="cadrage-context">
1519
- <span class="nav-icon">&#9679;</span> Contexte
1520
- </a>
1521
- <a class="nav-item" onclick="showSection('cadrage-stakeholders')" data-section="cadrage-stakeholders">
1522
- <span class="nav-icon">&#9679;</span> Parties prenantes
1523
- <span class="nav-badge" id="stakeholderCount">0</span>
1524
- </a>
1525
- <a class="nav-item" onclick="showSection('cadrage-scope')" data-section="cadrage-scope">
1526
- <span class="nav-icon">&#9679;</span> Perimetre fonctionnel
1527
- </a>
1528
- <a class="nav-item" onclick="showSection('cadrage-risks')" data-section="cadrage-risks">
1529
- <span class="nav-icon">&#9679;</span> Risques et hypotheses
1530
- </a>
1531
- <a class="nav-item" onclick="showSection('cadrage-success')" data-section="cadrage-success">
1532
- <span class="nav-icon">&#9679;</span> Criteres de reussite
1533
- </a>
1534
- </div>
1535
-
1536
- <!-- Phase 2 : Decomposition -->
1537
- <div class="nav-group">
1538
- <div class="nav-group-title">2. Decomposition</div>
1539
- <a class="nav-item" onclick="showSection('decomp-modules')" data-section="decomp-modules">
1540
- <span class="nav-icon">&#9679;</span> Domaines fonctionnels
1541
- <span class="nav-badge" id="moduleCount">0</span>
1542
- </a>
1543
- <a class="nav-item" onclick="showSection('decomp-dependencies')" data-section="decomp-dependencies">
1544
- <span class="nav-icon">&#9679;</span> Dependances
1545
- </a>
1546
- </div>
1547
-
1548
- <!-- Phase 3 : Specification par module -->
1549
- <div class="nav-group" id="modulesNav">
1550
- <div class="nav-group-title">3. Specification</div>
1551
- <!-- Populated dynamically per module -->
1552
- </div>
1553
-
1554
- <!-- Phase 4 : Consolidation -->
1555
- <div class="nav-group">
1556
- <div class="nav-group-title">4. Consolidation</div>
1557
- <a class="nav-item" onclick="showSection('consol-datamodel')" data-section="consol-datamodel">
1558
- <span class="nav-icon">&#9679;</span> Modele de donnees
1559
- <span class="nav-badge" id="entityCount">0</span>
1560
- </a>
1561
- <a class="nav-item" onclick="showSection('consol-interactions')" data-section="consol-interactions">
1562
- <span class="nav-icon">&#9679;</span> Interactions
1563
- </a>
1564
- <a class="nav-item" onclick="showSection('consol-permissions')" data-section="consol-permissions">
1565
- <span class="nav-icon">&#9679;</span> Coherence des acces
1566
- </a>
1567
- <a class="nav-item" onclick="showSection('consol-flows')" data-section="consol-flows">
1568
- <span class="nav-icon">&#9679;</span> Parcours bout en bout
1569
- </a>
1667
+ <!-- Application Name -->
1668
+ <div class="sidebar-header">
1669
+ <span class="sidebar-app-name" id="sidebarAppName">{{APPLICATION_NAME}}</span>
1570
1670
  </div>
1571
-
1572
- <!-- Phase 5 : Synthese -->
1573
- <div class="nav-group">
1574
- <div class="nav-group-title">5. Synthese</div>
1575
- <a class="nav-item" onclick="showSection('handoff-summary')" data-section="handoff-summary">
1576
- <span class="nav-icon">&#9679;</span> Vue d'ensemble
1577
- </a>
1671
+ <!-- Dynamic Tree Navigation -->
1672
+ <div id="sidebarNav">
1673
+ <!-- Populated by buildNavTree() -->
1578
1674
  </div>
1579
1675
  </aside>
1580
1676
 
@@ -2092,6 +2188,14 @@ data.cadrage.scope = data.cadrage.scope || { vital: [], important: [], optional:
2092
2188
 
2093
2189
  // Defensive init: moduleSpecs (may be missing if LLM didn't follow mapping)
2094
2190
  data.moduleSpecs = data.moduleSpecs || {};
2191
+ // Ensure ALL modules have a moduleSpecs entry (prevents missing schemas)
2192
+ (data.modules || []).forEach(function(m) {
2193
+ if (!data.moduleSpecs[m.code]) {
2194
+ data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
2195
+ }
2196
+ // Ensure anticipatedSections array exists
2197
+ m.anticipatedSections = m.anticipatedSections || [];
2198
+ });
2095
2199
 
2096
2200
  // Vibe coding mode: hide non-relevant sections
2097
2201
  const isVibeCoding = data.metadata?.vibeCoding === true;
@@ -2132,11 +2236,7 @@ function setNestedValue(obj, path, value) {
2132
2236
  }
2133
2237
 
2134
2238
  function updateCounts() {
2135
- document.getElementById('stakeholderCount').textContent = data.cadrage.stakeholders.length;
2136
- document.getElementById('moduleCount').textContent = data.modules.length;
2137
- const totalEntities = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.entities || []).length, 0);
2138
- document.getElementById('entityCount').textContent = totalEntities;
2139
- updateModulesNav();
2239
+ buildNavTree();
2140
2240
  updateDepSelects();
2141
2241
  }
2142
2242
 
@@ -2184,7 +2284,8 @@ document.addEventListener('DOMContentLoaded', function() {
2184
2284
  renderConsolidation();
2185
2285
  renderHandoff();
2186
2286
  renderE2EFlows();
2187
- updateCounts();
2287
+ buildNavTree();
2288
+ updateDepSelects();
2188
2289
  initInlineComments();
2189
2290
  renderReviewPanel();
2190
2291
  });
@@ -2192,9 +2293,12 @@ document.addEventListener('DOMContentLoaded', function() {
2192
2293
 
2193
2294
  /* --- 02-navigation.js --- */
2194
2295
  /* ============================================
2195
- NAVIGATION
2296
+ NAVIGATION - Hierarchical Tree
2196
2297
  ============================================ */
2197
2298
  let currentSectionId = 'cadrage-context';
2299
+ let navCollapseState = {};
2300
+
2301
+ /* ---------- Core ---------- */
2198
2302
 
2199
2303
  function showSection(sectionId) {
2200
2304
  currentSectionId = sectionId;
@@ -2202,9 +2306,30 @@ function showSection(sectionId) {
2202
2306
  const section = document.getElementById(sectionId);
2203
2307
  if (section) section.style.display = 'block';
2204
2308
 
2205
- document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
2206
- const navItem = document.querySelector('[data-section="' + sectionId + '"]');
2207
- if (navItem) navItem.classList.add('active');
2309
+ // Highlight active nav item
2310
+ document.querySelectorAll('#sidebarNav .nav-item').forEach(n => n.classList.remove('active'));
2311
+ const navItem = document.querySelector('#sidebarNav [data-section="' + sectionId + '"]');
2312
+ if (navItem) {
2313
+ navItem.classList.add('active');
2314
+ // Auto-expand parent groups to reveal active item
2315
+ let parent = navItem.parentElement;
2316
+ while (parent && parent.id !== 'sidebarNav') {
2317
+ if (parent.classList.contains('nav-children') && parent.style.display === 'none') {
2318
+ parent.style.display = '';
2319
+ const groupEl = parent.parentElement;
2320
+ if (groupEl) {
2321
+ const groupId = groupEl.dataset.groupId;
2322
+ if (groupId) {
2323
+ navCollapseState[groupId] = false;
2324
+ const chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
2325
+ if (chevron) chevron.classList.add('expanded');
2326
+ }
2327
+ }
2328
+ }
2329
+ parent = parent.parentElement;
2330
+ }
2331
+ saveNavState();
2332
+ }
2208
2333
  }
2209
2334
 
2210
2335
  function restoreCurrentSection() {
@@ -2214,6 +2339,187 @@ function restoreCurrentSection() {
2214
2339
  }
2215
2340
  }
2216
2341
 
2342
+ /* ---------- Tree Builder ---------- */
2343
+
2344
+ function buildNavTree() {
2345
+ const nav = document.getElementById('sidebarNav');
2346
+ if (!nav) return;
2347
+
2348
+ nav.innerHTML =
2349
+ renderNavGroup('cadrage', 'Cadrage', buildCadrageItems()) +
2350
+ renderNavGroup('modules', 'Modules (' + data.modules.length + ')', buildModuleItems()) +
2351
+ renderNavGroup('consolidation', 'Consolidation', buildConsolidationItems()) +
2352
+ renderNavGroup('synthese', 'Synthese', buildSyntheseItems());
2353
+
2354
+ restoreNavState();
2355
+ highlightActiveNavItem();
2356
+ }
2357
+
2358
+ function renderNavGroup(id, title, itemsHtml) {
2359
+ const collapsed = navCollapseState[id] === true;
2360
+ return '<div class="nav-group" data-group-id="' + id + '">' +
2361
+ '<div class="nav-group-title" onclick="toggleNavGroup(\'' + id + '\')">' +
2362
+ '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">&#9656;</span> ' +
2363
+ title +
2364
+ '</div>' +
2365
+ '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>' +
2366
+ itemsHtml +
2367
+ '</div>' +
2368
+ '</div>';
2369
+ }
2370
+
2371
+ function renderNavItem(sectionId, label, badge) {
2372
+ return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\')" data-section="' + sectionId + '">' +
2373
+ '<span class="nav-icon">&#9679;</span> ' + label +
2374
+ (badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
2375
+ '</a>';
2376
+ }
2377
+
2378
+ /* ---------- Group Builders ---------- */
2379
+
2380
+ function buildCadrageItems() {
2381
+ return renderNavItem('cadrage-context', 'Contexte') +
2382
+ renderNavItem('cadrage-stakeholders', 'Parties prenantes', data.cadrage.stakeholders.length) +
2383
+ renderNavItem('cadrage-scope', 'Perimetre fonctionnel') +
2384
+ renderNavItem('cadrage-risks', 'Risques et hypotheses', data.cadrage.risks.length) +
2385
+ renderNavItem('cadrage-success', 'Criteres de reussite');
2386
+ }
2387
+
2388
+ function buildModuleItems() {
2389
+ let html = '';
2390
+ data.modules.forEach(function(mod) {
2391
+ html += renderModuleNavItem(mod);
2392
+ });
2393
+ // Global module views at bottom
2394
+ html += renderNavItem('decomp-modules', 'Vue d\'ensemble', data.modules.length);
2395
+ html += renderNavItem('decomp-dependencies', 'Dependances', data.dependencies.length);
2396
+ return html;
2397
+ }
2398
+
2399
+ function buildConsolidationItems() {
2400
+ var totalEntities = data.modules.reduce(function(sum, m) {
2401
+ return sum + (data.moduleSpecs[m.code]?.entities || []).length;
2402
+ }, 0);
2403
+ return renderNavItem('consol-datamodel', 'Modele de donnees', totalEntities) +
2404
+ renderNavItem('consol-interactions', 'Interactions') +
2405
+ renderNavItem('consol-permissions', 'Coherence des acces') +
2406
+ renderNavItem('consol-flows', 'Parcours bout en bout', data.consolidation.e2eFlows.length);
2407
+ }
2408
+
2409
+ function buildSyntheseItems() {
2410
+ return renderNavItem('handoff-summary', 'Vue d\'ensemble');
2411
+ }
2412
+
2413
+ /* ---------- Module Sub-Tree ---------- */
2414
+
2415
+ function renderModuleNavItem(mod) {
2416
+ var code = mod.code;
2417
+ var spec = data.moduleSpecs[code] || {};
2418
+ var ucCount = (spec.useCases || []).length;
2419
+ var brCount = (spec.businessRules || []).length;
2420
+ var entCount = (spec.entities || []).length;
2421
+ var sections = mod.anticipatedSections || [];
2422
+ var groupId = 'mod-' + code;
2423
+ var collapsed = navCollapseState[groupId] === true;
2424
+
2425
+ var html = '<div class="nav-module" data-group-id="' + groupId + '">';
2426
+
2427
+ // Module header (clickable to expand + navigate to module spec)
2428
+ html += '<a class="nav-item nav-module-header" onclick="toggleNavGroup(\'' + groupId + '\');showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">';
2429
+ html += '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">&#9656;</span> ';
2430
+ html += (mod.name || mod.code);
2431
+ html += '</a>';
2432
+
2433
+ // Children: tabs
2434
+ html += '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>';
2435
+ html += renderModuleTabNavItem(code, 'uc', 'Cas d\'utilisation', ucCount);
2436
+ html += renderModuleTabNavItem(code, 'br', 'Regles metier', brCount);
2437
+ html += renderModuleTabNavItem(code, 'ent', 'Donnees', entCount);
2438
+ html += renderModuleTabNavItem(code, 'perm', 'Droits d\'acces');
2439
+ html += renderModuleTabNavItem(code, 'mock', 'Maquettes');
2440
+ html += renderModuleTabNavItem(code, 'struct', 'Structure', sections.length);
2441
+
2442
+ // Children: sections/resources (deeper tree)
2443
+ if (sections.length > 0) {
2444
+ sections.forEach(function(section) {
2445
+ var resources = section.resources || [];
2446
+ html += '<div class="nav-section-item">';
2447
+ html += '<a class="nav-item nav-section-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'struct\')" data-section="module-struct-' + code + '-' + section.code + '">';
2448
+ html += '<span class="nav-icon nav-icon-section">&#9655;</span> ' + section.code;
2449
+ if (resources.length > 0) html += ' <span class="nav-badge">' + resources.length + '</span>';
2450
+ html += '</a>';
2451
+ if (resources.length > 0) {
2452
+ html += '<div class="nav-children nav-resources">';
2453
+ resources.forEach(function(res) {
2454
+ var resName = typeof res === 'string' ? res : (res.code || res.name || '');
2455
+ html += '<a class="nav-item nav-resource-link">';
2456
+ html += '<span class="nav-icon nav-icon-resource">&#8226;</span> ' + resName;
2457
+ html += '</a>';
2458
+ });
2459
+ html += '</div>';
2460
+ }
2461
+ html += '</div>';
2462
+ });
2463
+ }
2464
+
2465
+ html += '</div>'; // nav-children
2466
+ html += '</div>'; // nav-module
2467
+ return html;
2468
+ }
2469
+
2470
+ function renderModuleTabNavItem(code, tabId, label, badge) {
2471
+ var sectionId = 'module-spec-' + code;
2472
+ return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\');switchTab(\'' + code + '\',\'' + tabId + '\')" data-section="' + sectionId + '-' + tabId + '">' +
2473
+ '<span class="nav-icon">&#9679;</span> ' + label +
2474
+ (badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
2475
+ '</a>';
2476
+ }
2477
+
2478
+ /* ---------- Collapse/Expand ---------- */
2479
+
2480
+ function toggleNavGroup(groupId) {
2481
+ navCollapseState[groupId] = !navCollapseState[groupId];
2482
+ var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
2483
+ if (groupEl) {
2484
+ var children = groupEl.querySelector(':scope > .nav-children');
2485
+ var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
2486
+ if (children) children.style.display = navCollapseState[groupId] ? 'none' : '';
2487
+ if (chevron) chevron.classList.toggle('expanded', !navCollapseState[groupId]);
2488
+ }
2489
+ saveNavState();
2490
+ }
2491
+
2492
+ function saveNavState() {
2493
+ try { localStorage.setItem(APP_KEY + '-nav', JSON.stringify(navCollapseState)); } catch(e) {}
2494
+ }
2495
+
2496
+ function restoreNavState() {
2497
+ try {
2498
+ var saved = localStorage.getItem(APP_KEY + '-nav');
2499
+ if (saved) {
2500
+ navCollapseState = JSON.parse(saved);
2501
+ // Apply collapse state to all groups
2502
+ Object.keys(navCollapseState).forEach(function(groupId) {
2503
+ if (navCollapseState[groupId]) {
2504
+ var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
2505
+ if (groupEl) {
2506
+ var children = groupEl.querySelector(':scope > .nav-children');
2507
+ var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
2508
+ if (children) children.style.display = 'none';
2509
+ if (chevron) chevron.classList.remove('expanded');
2510
+ }
2511
+ }
2512
+ });
2513
+ }
2514
+ } catch(e) {}
2515
+ }
2516
+
2517
+ function highlightActiveNavItem() {
2518
+ if (!currentSectionId) return;
2519
+ var navItem = document.querySelector('#sidebarNav [data-section="' + currentSectionId + '"]');
2520
+ if (navItem) navItem.classList.add('active');
2521
+ }
2522
+
2217
2523
 
2218
2524
  /* --- 03-render-cadrage.js --- */
2219
2525
  /* ============================================
@@ -2433,6 +2739,7 @@ function addModule() {
2433
2739
  featureType: document.getElementById('mod-type').value,
2434
2740
  priority: document.getElementById('mod-priority').value,
2435
2741
  entities: document.getElementById('mod-entities').value.split('\n').filter(e => e.trim()),
2742
+ anticipatedSections: [],
2436
2743
  status: 'pending'
2437
2744
  });
2438
2745
 
@@ -2489,14 +2796,7 @@ function renderModules() {
2489
2796
  }
2490
2797
 
2491
2798
  function updateModulesNav() {
2492
- const nav = document.getElementById('modulesNav');
2493
- const navItems = data.modules.map(m => `
2494
- <a class="nav-item" onclick="showSection('module-spec-${m.code}')" data-section="module-spec-${m.code}">
2495
- <span class="nav-icon">&#9679;</span> ${m.name}
2496
- <span class="nav-badge">${(data.moduleSpecs[m.code]?.useCases || []).length}</span>
2497
- </a>
2498
- `).join('');
2499
- nav.innerHTML = '<div class="nav-group-title">3. Specification</div>' + (navItems || '<div style="padding:0.3rem 1rem;font-size:0.8rem;color:var(--text-muted);font-style:italic;">Aucun domaine</div>');
2799
+ buildNavTree();
2500
2800
  }
2501
2801
 
2502
2802
  /* ============================================
@@ -2671,6 +2971,7 @@ function renderModuleSpecSection(mod) {
2671
2971
  <button class="tab-btn" onclick="switchTab('${code}', 'perm')">Droits d'acces</button>
2672
2972
  <button class="tab-btn" onclick="switchTab('${code}', 'mock')">Maquettes</button>
2673
2973
  <button class="tab-btn" onclick="switchTab('${code}', 'notes')">Notes</button>
2974
+ <button class="tab-btn" onclick="switchTab('${code}', 'struct')">Structure</button>
2674
2975
  </div>
2675
2976
 
2676
2977
  <!-- TAB: Cas d'utilisation -->
@@ -2747,7 +3048,12 @@ function renderModuleSpecSection(mod) {
2747
3048
  <div class="tab-panel" id="tab-${code}-ent">
2748
3049
  <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Les types de donnees que ce domaine gere. Decrivez les informations importantes a enregistrer pour chaque type.</p>
2749
3050
  <div id="entList-${code}">
2750
- ${spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')}
3051
+ ${spec.entities.length > 0
3052
+ ? spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')
3053
+ : (mod.entities || []).length > 0
3054
+ ? '<div class="card" style="color:var(--text-muted);padding:1.5rem;"><p style="margin-bottom:0.75rem;">Les schemas detailles ne sont pas encore disponibles. Entites identifiees :</p><ul style="list-style:disc;padding-left:1.5rem;">' + mod.entities.map(e => '<li>' + e + '</li>').join('') + '</ul><p style="font-size:0.8rem;margin-top:0.75rem;font-style:italic;">Utilisez le bouton ci-dessous pour ajouter les schemas detailles.</p></div>'
3055
+ : ''
3056
+ }
2751
3057
  </div>
2752
3058
  <button class="add-btn" onclick="toggleForm('addEntForm-${code}')">+ Ajouter un type de donnees</button>
2753
3059
  <div class="inline-form" id="addEntForm-${code}">
@@ -2824,6 +3130,14 @@ function renderModuleSpecSection(mod) {
2824
3130
  <div class="editable" contenteditable="true" data-module-code="${code}" data-module-field="notes" data-placeholder="Notez ici tout ce qui concerne ce domaine : questions, precisions, contraintes particulieres...">${spec.notes || ''}</div>
2825
3131
  </div>
2826
3132
  </div>
3133
+
3134
+ <!-- TAB: Structure (sections/resources) -->
3135
+ <div class="tab-panel" id="tab-${code}-struct">
3136
+ <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Organisation des ecrans et ressources de ce domaine. Structure hierarchique : sections regroupant des ressources.</p>
3137
+ <div id="structContainer-${code}">
3138
+ ${renderModuleStructure(code)}
3139
+ </div>
3140
+ </div>
2827
3141
  </div>`;
2828
3142
  }
2829
3143
 
@@ -3007,18 +3321,30 @@ function renderModuleMockups(code) {
3007
3321
  </div>`;
3008
3322
  }
3009
3323
 
3010
- return wireframes.map((wf, i) => `
3324
+ return wireframes.map((wf, i) => {
3325
+ const hasSvg = !!wf.svgContent;
3326
+ const wireframeId = `wf-${code}-${i}`;
3327
+
3328
+ return `
3011
3329
  <div class="mockup-frame" style="${i > 0 ? 'margin-top:1.5rem;' : ''}">
3012
3330
  <div class="mockup-toolbar">
3013
3331
  <div class="mockup-dot mockup-dot-red"></div>
3014
3332
  <div class="mockup-dot mockup-dot-yellow"></div>
3015
3333
  <div class="mockup-dot mockup-dot-green"></div>
3016
3334
  <span class="mockup-title">${wf.screen || wf.section}</span>
3335
+ ${hasSvg ? `
3336
+ <div class="wireframe-toggle">
3337
+ <button class="wireframe-toggle-btn active" data-target="${wireframeId}" data-view="svg" onclick="toggleWireframeView('${wireframeId}', 'svg')">SVG</button>
3338
+ <button class="wireframe-toggle-btn" data-target="${wireframeId}" data-view="ascii" onclick="toggleWireframeView('${wireframeId}', 'ascii')">ASCII</button>
3339
+ </div>` : ''}
3017
3340
  </div>
3018
- <div class="mockup-content">
3019
- ${wf.format === 'ascii'
3020
- ? `<pre class="ascii-wireframe">${wf.content || ''}</pre>`
3021
- : `<div class="svg-wireframe">${wf.content || ''}</div>`}
3341
+ <div class="mockup-content" id="${wireframeId}">
3342
+ ${hasSvg
3343
+ ? `<div class="svg-wireframe wireframe-view active" data-view="svg">${wf.svgContent}</div>
3344
+ <pre class="ascii-wireframe wireframe-view" data-view="ascii" style="display:none;">${wf.content || ''}</pre>`
3345
+ : (wf.format === 'ascii'
3346
+ ? `<pre class="ascii-wireframe">${wf.content || ''}</pre>`
3347
+ : `<div class="svg-wireframe">${wf.content || ''}</div>`)}
3022
3348
  </div>
3023
3349
  ${wf.description ? `
3024
3350
  <div class="wireframe-description">
@@ -3057,7 +3383,27 @@ function renderModuleMockups(code) {
3057
3383
  >${getWireframeComment(code, wf.screen)}</textarea>
3058
3384
  </div>
3059
3385
  </div>
3060
- `).join('');
3386
+ `}).join('');
3387
+ }
3388
+
3389
+ function toggleWireframeView(wireframeId, view) {
3390
+ const container = document.getElementById(wireframeId);
3391
+ if (!container) return;
3392
+
3393
+ // Toggle visibility of wireframe views
3394
+ container.querySelectorAll('.wireframe-view').forEach(el => {
3395
+ const isTarget = el.dataset.view === view;
3396
+ el.style.display = isTarget ? '' : 'none';
3397
+ el.classList.toggle('active', isTarget);
3398
+ });
3399
+
3400
+ // Toggle button active states
3401
+ const toolbar = container.closest('.mockup-frame').querySelector('.wireframe-toggle');
3402
+ if (toolbar) {
3403
+ toolbar.querySelectorAll('.wireframe-toggle-btn').forEach(btn => {
3404
+ btn.classList.toggle('active', btn.dataset.view === view);
3405
+ });
3406
+ }
3061
3407
  }
3062
3408
 
3063
3409
  function getPermRoles() {
@@ -3181,6 +3527,47 @@ function refreshAllPermGrids() {
3181
3527
  });
3182
3528
  }
3183
3529
 
3530
+ function renderModuleStructure(code) {
3531
+ const mod = data.modules.find(m => m.code === code);
3532
+ const sections = mod ? (mod.anticipatedSections || []) : [];
3533
+
3534
+ if (sections.length === 0) {
3535
+ return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
3536
+ '<p>Aucune section definie pour ce module.</p>' +
3537
+ '<p style="font-size:0.85rem;margin-top:0.5rem;">Les sections et ressources seront identifiees lors de l\'analyse approfondie.</p>' +
3538
+ '</div>';
3539
+ }
3540
+
3541
+ return sections.map(function(section) {
3542
+ var resources = section.resources || [];
3543
+ var html = '<div class="struct-section">';
3544
+ html += '<div class="struct-section-header">';
3545
+ html += '<span class="struct-section-code">' + (section.code || section.name || '') + '</span>';
3546
+ if (section.description) {
3547
+ html += '<span class="struct-section-desc">' + section.description + '</span>';
3548
+ }
3549
+ html += '<span class="struct-section-badge">' + resources.length + ' ressource' + (resources.length !== 1 ? 's' : '') + '</span>';
3550
+ html += '</div>';
3551
+
3552
+ if (resources.length > 0) {
3553
+ html += '<div class="struct-resources">';
3554
+ resources.forEach(function(res) {
3555
+ var resName = typeof res === 'string' ? res : (res.code || res.name || '');
3556
+ var resDesc = typeof res === 'object' ? (res.description || '') : '';
3557
+ html += '<div class="struct-resource">';
3558
+ html += '<span class="struct-resource-icon">&#8226;</span>';
3559
+ html += '<span class="struct-resource-name">' + resName + '</span>';
3560
+ if (resDesc) html += '<span class="struct-resource-desc">' + resDesc + '</span>';
3561
+ html += '</div>';
3562
+ });
3563
+ html += '</div>';
3564
+ }
3565
+
3566
+ html += '</div>';
3567
+ return html;
3568
+ }).join('');
3569
+ }
3570
+
3184
3571
  function switchTab(code, tabId) {
3185
3572
  const section = document.getElementById('module-spec-' + code);
3186
3573
  if (!section) return;
@@ -3189,7 +3576,7 @@ function switchTab(code, tabId) {
3189
3576
  const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
3190
3577
  if (targetPanel) targetPanel.classList.add('active');
3191
3578
  const buttons = section.querySelectorAll('.tab-btn');
3192
- const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5 }[tabId];
3579
+ const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5, struct: 6 }[tabId];
3193
3580
  if (buttons[tabIndex]) buttons[tabIndex].classList.add('active');
3194
3581
  }
3195
3582
 
@@ -3471,7 +3858,9 @@ function renderCoverageMatrix() {
3471
3858
  <thead><tr><th>Besoin</th><th>Priorite</th><th>Domaine</th><th>Couvert</th></tr></thead>
3472
3859
  <tbody>
3473
3860
  ${allScope.map(item => {
3474
- const moduleName = data.modules.length > 0 ? data.modules[0].name : 'A definir';
3861
+ const moduleName = item.module
3862
+ ? (data.modules.find(m => m.code === item.module)?.name || item.module)
3863
+ : (data.modules.length > 0 ? data.modules.map(m => m.name).join(', ') : 'A definir');
3475
3864
  return `
3476
3865
  <tr>
3477
3866
  <td>${item.name}</td>
@@ -3499,37 +3888,127 @@ function saveToLocalStorage() {
3499
3888
  showNotification('Modifications sauvegardees');
3500
3889
  }
3501
3890
 
3891
+ function resetToEmbedded() {
3892
+ if (!confirm('Reinitialiser toutes les donnees depuis la version d\'origine ? Vos modifications locales (commentaires, notes) seront perdues.')) return;
3893
+ localStorage.removeItem(APP_KEY);
3894
+ // Restore data from ORIGINAL_DATA
3895
+ Object.keys(data).forEach(k => delete data[k]);
3896
+ Object.assign(data, JSON.parse(JSON.stringify(ORIGINAL_DATA)));
3897
+ // Re-render everything
3898
+ initEditableFields();
3899
+ renderStakeholders();
3900
+ renderScope();
3901
+ renderRisks();
3902
+ renderCriteria();
3903
+ renderModules();
3904
+ renderDependencies();
3905
+ renderAllModuleSpecs();
3906
+ renderConsolidation();
3907
+ renderHandoff();
3908
+ renderE2EFlows();
3909
+ updateCounts();
3910
+ renderReviewPanel();
3911
+ showNotification('Donnees reinitialisees depuis la version d\'origine');
3912
+ }
3913
+
3502
3914
  function loadFromLocalStorage() {
3503
3915
  const saved = localStorage.getItem(APP_KEY);
3504
- if (saved) {
3505
- try {
3506
- const parsed = JSON.parse(saved);
3507
- // Deep merge with defaults
3508
- data.metadata = { ...data.metadata, ...parsed.metadata };
3509
- data.cadrage = {
3510
- ...data.cadrage,
3511
- ...parsed.cadrage,
3512
- scope: { ...data.cadrage.scope, ...(parsed.cadrage?.scope || {}) }
3513
- };
3514
- data.modules = parsed.modules || [];
3515
- data.dependencies = parsed.dependencies || [];
3516
- data.moduleSpecs = parsed.moduleSpecs || {};
3916
+ if (!saved) return;
3917
+
3918
+ try {
3919
+ const parsed = JSON.parse(saved);
3920
+
3921
+ // Build fingerprint of embedded structural data to detect HTML regeneration
3922
+ const embeddedFingerprint = ORIGINAL_DATA.modules.map(m => m.code).sort().join(',')
3923
+ + '|' + (ORIGINAL_DATA.metadata?.createdAt || '')
3924
+ + '|' + ORIGINAL_DATA.modules.length;
3925
+ const cachedFingerprint = parsed._structureFingerprint || '';
3926
+
3927
+ const structureChanged = embeddedFingerprint !== cachedFingerprint;
3928
+ const embeddedModuleCount = ORIGINAL_DATA.modules?.length || 0;
3929
+ const cachedModuleCount = (parsed.modules || []).length;
3930
+ const hasNewModules = embeddedModuleCount > cachedModuleCount;
3931
+ const embeddedModuleCodes = new Set(ORIGINAL_DATA.modules.map(m => m.code));
3932
+ const cachedModuleCodes = new Set((parsed.modules || []).map(m => m.code));
3933
+ const missingModules = [...embeddedModuleCodes].filter(c => !cachedModuleCodes.has(c));
3934
+
3935
+ if (structureChanged || hasNewModules || missingModules.length > 0) {
3936
+ // HTML was regenerated or has new modules — keep embedded structural data
3937
+ // Only restore user-specific edits (comments, custom roles, notes)
3938
+ data.wireframeComments = parsed.wireframeComments || {};
3939
+ data.specComments = parsed.specComments || {};
3940
+ data.customRoles = parsed.customRoles || [];
3941
+ data.customActions = parsed.customActions || [];
3942
+ data.comments = parsed.comments || [];
3943
+
3944
+ // Merge user-added notes per module (preserve existing module notes)
3945
+ for (const code of Object.keys(parsed.moduleSpecs || {})) {
3946
+ if (data.moduleSpecs[code] && parsed.moduleSpecs[code]?.notes) {
3947
+ data.moduleSpecs[code].notes = parsed.moduleSpecs[code].notes;
3948
+ }
3949
+ }
3950
+
3951
+ // Save fresh embedded data with fingerprint
3952
+ data._structureFingerprint = embeddedFingerprint;
3953
+ autoSave();
3954
+ } else {
3955
+ // Cache matches current structure — safe to restore user edits on cadrage/notes
3956
+ // IMPORTANT: Always keep embedded modules and moduleSpecs as structural source of truth
3957
+ // Only merge cadrage user-editable fields and notes
3958
+ if (parsed.cadrage) {
3959
+ // Merge cadrage context (user may have edited text fields)
3960
+ if (parsed.cadrage.context) {
3961
+ data.cadrage.context = { ...data.cadrage.context, ...parsed.cadrage.context };
3962
+ }
3963
+ // Merge scope only if user added items interactively
3964
+ if (parsed.cadrage.scope) {
3965
+ data.cadrage.scope = { ...data.cadrage.scope, ...parsed.cadrage.scope };
3966
+ }
3967
+ // Merge stakeholders and risks if user edited them
3968
+ if (parsed.cadrage.stakeholders) data.cadrage.stakeholders = parsed.cadrage.stakeholders;
3969
+ if (parsed.cadrage.risks) data.cadrage.risks = parsed.cadrage.risks;
3970
+ }
3971
+ data.dependencies = parsed.dependencies || data.dependencies;
3517
3972
  data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
3518
- data.handoff = parsed.handoff || {};
3973
+
3974
+ // Merge moduleSpecs: keep embedded structure, overlay user-editable fields (notes, custom permissions)
3975
+ for (const code of Object.keys(data.moduleSpecs)) {
3976
+ const cached = parsed.moduleSpecs?.[code];
3977
+ if (cached) {
3978
+ // Preserve user notes
3979
+ if (cached.notes) data.moduleSpecs[code].notes = cached.notes;
3980
+ // Preserve user-added use cases/rules/entities (merged with embedded)
3981
+ // Only add items that don't exist in embedded (by name match)
3982
+ const embeddedUcNames = new Set((data.moduleSpecs[code].useCases || []).map(uc => uc.name));
3983
+ (cached.useCases || []).forEach(uc => {
3984
+ if (!embeddedUcNames.has(uc.name)) data.moduleSpecs[code].useCases.push(uc);
3985
+ });
3986
+ const embeddedBrNames = new Set((data.moduleSpecs[code].businessRules || []).map(br => br.name));
3987
+ (cached.businessRules || []).forEach(br => {
3988
+ if (!embeddedBrNames.has(br.name)) data.moduleSpecs[code].businessRules.push(br);
3989
+ });
3990
+ // Preserve user permissions edits
3991
+ if (cached.permissions) data.moduleSpecs[code].permissions = cached.permissions;
3992
+ }
3993
+ }
3994
+
3519
3995
  data.wireframeComments = parsed.wireframeComments || {};
3520
3996
  data.specComments = parsed.specComments || {};
3521
3997
  data.customRoles = parsed.customRoles || [];
3522
3998
  data.customActions = parsed.customActions || [];
3523
3999
  data.comments = parsed.comments || [];
3524
4000
 
3525
- // Restore editable fields
3526
- document.querySelectorAll('.editable[data-field]').forEach(el => {
3527
- const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
3528
- if (value) el.textContent = value;
3529
- });
3530
- renderProcessFlow();
3531
- } catch (e) { console.error('Error loading saved data:', e); }
3532
- }
4001
+ // Update fingerprint
4002
+ data._structureFingerprint = embeddedFingerprint;
4003
+ autoSave();
4004
+ }
4005
+
4006
+ // Restore editable fields
4007
+ document.querySelectorAll('.editable[data-field]').forEach(el => {
4008
+ const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
4009
+ if (value) el.textContent = value;
4010
+ });
4011
+ } catch (e) { console.error('Error loading saved data:', e); }
3533
4012
  }
3534
4013
 
3535
4014
 
@@ -3999,6 +4478,13 @@ function getSectionLabel(sectionId) {
3999
4478
  const mod = data.modules.find(m => m.code === code);
4000
4479
  return mod ? mod.name : code;
4001
4480
  }
4481
+ // Handle module structure sections (module-struct-{code}-{section})
4482
+ const structMatch = sectionId.match(/^module-struct-(.+?)-(.+)$/);
4483
+ if (structMatch) {
4484
+ const mod = data.modules.find(m => m.code === structMatch[1]);
4485
+ const modName = mod ? mod.name : structMatch[1];
4486
+ return modName + ' > Structure > ' + structMatch[2];
4487
+ }
4002
4488
  // Handle list-based sectionIds (ucList-*, brList-*, entList-*)
4003
4489
  const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
4004
4490
  if (listMatch) {