@atlashub/smartstack-cli 3.20.0 → 3.21.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 (26) hide show
  1. package/dist/index.js +53 -1
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +1 -0
  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/business-analyse/html/ba-interactive.html +562 -150
  9. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
  10. package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
  11. package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
  12. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +57 -2
  13. package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
  14. package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
  15. package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
  16. package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
  17. package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
  18. package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
  19. package/templates/skills/business-analyse/html/src/template.html +8 -76
  20. package/templates/skills/business-analyse/references/deploy-data-build.md +9 -7
  21. package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
  22. package/templates/skills/business-analyse/references/validate-incremental-html.md +2 -1
  23. package/templates/skills/business-analyse/steps/step-03c-compile.md +55 -2
  24. package/templates/skills/business-analyse/steps/step-03d-validate.md +82 -15
  25. package/templates/skills/business-analyse/steps/step-05a-handoff.md +77 -3
  26. package/templates/skills/business-analyse/steps/step-05b-deploy.md +27 -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
  /* ============================================
@@ -1484,6 +1606,7 @@ body {
1484
1606
  <span class="header-app-name" id="appName">{{APPLICATION_NAME}}</span>
1485
1607
  <div class="header-spacer"></div>
1486
1608
  <div class="header-actions">
1609
+ <button class="btn btn-sm" onclick="resetToEmbedded()" title="Reinitialiser depuis les donnees d'origine (supprime les modifications locales)">Reset</button>
1487
1610
  <button class="btn btn-sm" onclick="saveToLocalStorage()" title="Sauvegarder les modifications dans le navigateur">Sauvegarder</button>
1488
1611
  <button class="btn btn-sm btn-review" onclick="saveReviewJSON()" title="Sauvegarder les corrections pour creer une nouvelle version">Sauvegarder corrections</button>
1489
1612
  <button class="btn btn-sm btn-primary" onclick="exportJSON()" title="Exporter les donnees au format JSON pour l'extraction">Exporter JSON</button>
@@ -1496,85 +1619,16 @@ body {
1496
1619
 
1497
1620
  <div class="body" id="appBody">
1498
1621
  <!-- ============================================
1499
- SIDEBAR - Navigation 5 niveaux
1622
+ SIDEBAR - Navigation hierarchique
1500
1623
  ============================================ -->
1501
1624
  <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>
1625
+ <!-- Application Name -->
1626
+ <div class="sidebar-header">
1627
+ <span class="sidebar-app-name" id="sidebarAppName">{{APPLICATION_NAME}}</span>
1513
1628
  </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>
1570
- </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>
1629
+ <!-- Dynamic Tree Navigation -->
1630
+ <div id="sidebarNav">
1631
+ <!-- Populated by buildNavTree() -->
1578
1632
  </div>
1579
1633
  </aside>
1580
1634
 
@@ -2092,6 +2146,14 @@ data.cadrage.scope = data.cadrage.scope || { vital: [], important: [], optional:
2092
2146
 
2093
2147
  // Defensive init: moduleSpecs (may be missing if LLM didn't follow mapping)
2094
2148
  data.moduleSpecs = data.moduleSpecs || {};
2149
+ // Ensure ALL modules have a moduleSpecs entry (prevents missing schemas)
2150
+ (data.modules || []).forEach(function(m) {
2151
+ if (!data.moduleSpecs[m.code]) {
2152
+ data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
2153
+ }
2154
+ // Ensure anticipatedSections array exists
2155
+ m.anticipatedSections = m.anticipatedSections || [];
2156
+ });
2095
2157
 
2096
2158
  // Vibe coding mode: hide non-relevant sections
2097
2159
  const isVibeCoding = data.metadata?.vibeCoding === true;
@@ -2132,11 +2194,7 @@ function setNestedValue(obj, path, value) {
2132
2194
  }
2133
2195
 
2134
2196
  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();
2197
+ buildNavTree();
2140
2198
  updateDepSelects();
2141
2199
  }
2142
2200
 
@@ -2184,7 +2242,8 @@ document.addEventListener('DOMContentLoaded', function() {
2184
2242
  renderConsolidation();
2185
2243
  renderHandoff();
2186
2244
  renderE2EFlows();
2187
- updateCounts();
2245
+ buildNavTree();
2246
+ updateDepSelects();
2188
2247
  initInlineComments();
2189
2248
  renderReviewPanel();
2190
2249
  });
@@ -2192,9 +2251,12 @@ document.addEventListener('DOMContentLoaded', function() {
2192
2251
 
2193
2252
  /* --- 02-navigation.js --- */
2194
2253
  /* ============================================
2195
- NAVIGATION
2254
+ NAVIGATION - Hierarchical Tree
2196
2255
  ============================================ */
2197
2256
  let currentSectionId = 'cadrage-context';
2257
+ let navCollapseState = {};
2258
+
2259
+ /* ---------- Core ---------- */
2198
2260
 
2199
2261
  function showSection(sectionId) {
2200
2262
  currentSectionId = sectionId;
@@ -2202,9 +2264,30 @@ function showSection(sectionId) {
2202
2264
  const section = document.getElementById(sectionId);
2203
2265
  if (section) section.style.display = 'block';
2204
2266
 
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');
2267
+ // Highlight active nav item
2268
+ document.querySelectorAll('#sidebarNav .nav-item').forEach(n => n.classList.remove('active'));
2269
+ const navItem = document.querySelector('#sidebarNav [data-section="' + sectionId + '"]');
2270
+ if (navItem) {
2271
+ navItem.classList.add('active');
2272
+ // Auto-expand parent groups to reveal active item
2273
+ let parent = navItem.parentElement;
2274
+ while (parent && parent.id !== 'sidebarNav') {
2275
+ if (parent.classList.contains('nav-children') && parent.style.display === 'none') {
2276
+ parent.style.display = '';
2277
+ const groupEl = parent.parentElement;
2278
+ if (groupEl) {
2279
+ const groupId = groupEl.dataset.groupId;
2280
+ if (groupId) {
2281
+ navCollapseState[groupId] = false;
2282
+ const chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
2283
+ if (chevron) chevron.classList.add('expanded');
2284
+ }
2285
+ }
2286
+ }
2287
+ parent = parent.parentElement;
2288
+ }
2289
+ saveNavState();
2290
+ }
2208
2291
  }
2209
2292
 
2210
2293
  function restoreCurrentSection() {
@@ -2214,6 +2297,187 @@ function restoreCurrentSection() {
2214
2297
  }
2215
2298
  }
2216
2299
 
2300
+ /* ---------- Tree Builder ---------- */
2301
+
2302
+ function buildNavTree() {
2303
+ const nav = document.getElementById('sidebarNav');
2304
+ if (!nav) return;
2305
+
2306
+ nav.innerHTML =
2307
+ renderNavGroup('cadrage', 'Cadrage', buildCadrageItems()) +
2308
+ renderNavGroup('modules', 'Modules (' + data.modules.length + ')', buildModuleItems()) +
2309
+ renderNavGroup('consolidation', 'Consolidation', buildConsolidationItems()) +
2310
+ renderNavGroup('synthese', 'Synthese', buildSyntheseItems());
2311
+
2312
+ restoreNavState();
2313
+ highlightActiveNavItem();
2314
+ }
2315
+
2316
+ function renderNavGroup(id, title, itemsHtml) {
2317
+ const collapsed = navCollapseState[id] === true;
2318
+ return '<div class="nav-group" data-group-id="' + id + '">' +
2319
+ '<div class="nav-group-title" onclick="toggleNavGroup(\'' + id + '\')">' +
2320
+ '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">&#9656;</span> ' +
2321
+ title +
2322
+ '</div>' +
2323
+ '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>' +
2324
+ itemsHtml +
2325
+ '</div>' +
2326
+ '</div>';
2327
+ }
2328
+
2329
+ function renderNavItem(sectionId, label, badge) {
2330
+ return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\')" data-section="' + sectionId + '">' +
2331
+ '<span class="nav-icon">&#9679;</span> ' + label +
2332
+ (badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
2333
+ '</a>';
2334
+ }
2335
+
2336
+ /* ---------- Group Builders ---------- */
2337
+
2338
+ function buildCadrageItems() {
2339
+ return renderNavItem('cadrage-context', 'Contexte') +
2340
+ renderNavItem('cadrage-stakeholders', 'Parties prenantes', data.cadrage.stakeholders.length) +
2341
+ renderNavItem('cadrage-scope', 'Perimetre fonctionnel') +
2342
+ renderNavItem('cadrage-risks', 'Risques et hypotheses', data.cadrage.risks.length) +
2343
+ renderNavItem('cadrage-success', 'Criteres de reussite');
2344
+ }
2345
+
2346
+ function buildModuleItems() {
2347
+ let html = '';
2348
+ data.modules.forEach(function(mod) {
2349
+ html += renderModuleNavItem(mod);
2350
+ });
2351
+ // Global module views at bottom
2352
+ html += renderNavItem('decomp-modules', 'Vue d\'ensemble', data.modules.length);
2353
+ html += renderNavItem('decomp-dependencies', 'Dependances', data.dependencies.length);
2354
+ return html;
2355
+ }
2356
+
2357
+ function buildConsolidationItems() {
2358
+ var totalEntities = data.modules.reduce(function(sum, m) {
2359
+ return sum + (data.moduleSpecs[m.code]?.entities || []).length;
2360
+ }, 0);
2361
+ return renderNavItem('consol-datamodel', 'Modele de donnees', totalEntities) +
2362
+ renderNavItem('consol-interactions', 'Interactions') +
2363
+ renderNavItem('consol-permissions', 'Coherence des acces') +
2364
+ renderNavItem('consol-flows', 'Parcours bout en bout', data.consolidation.e2eFlows.length);
2365
+ }
2366
+
2367
+ function buildSyntheseItems() {
2368
+ return renderNavItem('handoff-summary', 'Vue d\'ensemble');
2369
+ }
2370
+
2371
+ /* ---------- Module Sub-Tree ---------- */
2372
+
2373
+ function renderModuleNavItem(mod) {
2374
+ var code = mod.code;
2375
+ var spec = data.moduleSpecs[code] || {};
2376
+ var ucCount = (spec.useCases || []).length;
2377
+ var brCount = (spec.businessRules || []).length;
2378
+ var entCount = (spec.entities || []).length;
2379
+ var sections = mod.anticipatedSections || [];
2380
+ var groupId = 'mod-' + code;
2381
+ var collapsed = navCollapseState[groupId] === true;
2382
+
2383
+ var html = '<div class="nav-module" data-group-id="' + groupId + '">';
2384
+
2385
+ // Module header (clickable to expand + navigate to module spec)
2386
+ html += '<a class="nav-item nav-module-header" onclick="toggleNavGroup(\'' + groupId + '\');showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">';
2387
+ html += '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">&#9656;</span> ';
2388
+ html += (mod.name || mod.code);
2389
+ html += '</a>';
2390
+
2391
+ // Children: tabs
2392
+ html += '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>';
2393
+ html += renderModuleTabNavItem(code, 'uc', 'Cas d\'utilisation', ucCount);
2394
+ html += renderModuleTabNavItem(code, 'br', 'Regles metier', brCount);
2395
+ html += renderModuleTabNavItem(code, 'ent', 'Donnees', entCount);
2396
+ html += renderModuleTabNavItem(code, 'perm', 'Droits d\'acces');
2397
+ html += renderModuleTabNavItem(code, 'mock', 'Maquettes');
2398
+ html += renderModuleTabNavItem(code, 'struct', 'Structure', sections.length);
2399
+
2400
+ // Children: sections/resources (deeper tree)
2401
+ if (sections.length > 0) {
2402
+ sections.forEach(function(section) {
2403
+ var resources = section.resources || [];
2404
+ html += '<div class="nav-section-item">';
2405
+ html += '<a class="nav-item nav-section-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'struct\')" data-section="module-struct-' + code + '-' + section.code + '">';
2406
+ html += '<span class="nav-icon nav-icon-section">&#9655;</span> ' + section.code;
2407
+ if (resources.length > 0) html += ' <span class="nav-badge">' + resources.length + '</span>';
2408
+ html += '</a>';
2409
+ if (resources.length > 0) {
2410
+ html += '<div class="nav-children nav-resources">';
2411
+ resources.forEach(function(res) {
2412
+ var resName = typeof res === 'string' ? res : (res.code || res.name || '');
2413
+ html += '<a class="nav-item nav-resource-link">';
2414
+ html += '<span class="nav-icon nav-icon-resource">&#8226;</span> ' + resName;
2415
+ html += '</a>';
2416
+ });
2417
+ html += '</div>';
2418
+ }
2419
+ html += '</div>';
2420
+ });
2421
+ }
2422
+
2423
+ html += '</div>'; // nav-children
2424
+ html += '</div>'; // nav-module
2425
+ return html;
2426
+ }
2427
+
2428
+ function renderModuleTabNavItem(code, tabId, label, badge) {
2429
+ var sectionId = 'module-spec-' + code;
2430
+ return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\');switchTab(\'' + code + '\',\'' + tabId + '\')" data-section="' + sectionId + '-' + tabId + '">' +
2431
+ '<span class="nav-icon">&#9679;</span> ' + label +
2432
+ (badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
2433
+ '</a>';
2434
+ }
2435
+
2436
+ /* ---------- Collapse/Expand ---------- */
2437
+
2438
+ function toggleNavGroup(groupId) {
2439
+ navCollapseState[groupId] = !navCollapseState[groupId];
2440
+ var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
2441
+ if (groupEl) {
2442
+ var children = groupEl.querySelector(':scope > .nav-children');
2443
+ var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
2444
+ if (children) children.style.display = navCollapseState[groupId] ? 'none' : '';
2445
+ if (chevron) chevron.classList.toggle('expanded', !navCollapseState[groupId]);
2446
+ }
2447
+ saveNavState();
2448
+ }
2449
+
2450
+ function saveNavState() {
2451
+ try { localStorage.setItem(APP_KEY + '-nav', JSON.stringify(navCollapseState)); } catch(e) {}
2452
+ }
2453
+
2454
+ function restoreNavState() {
2455
+ try {
2456
+ var saved = localStorage.getItem(APP_KEY + '-nav');
2457
+ if (saved) {
2458
+ navCollapseState = JSON.parse(saved);
2459
+ // Apply collapse state to all groups
2460
+ Object.keys(navCollapseState).forEach(function(groupId) {
2461
+ if (navCollapseState[groupId]) {
2462
+ var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
2463
+ if (groupEl) {
2464
+ var children = groupEl.querySelector(':scope > .nav-children');
2465
+ var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
2466
+ if (children) children.style.display = 'none';
2467
+ if (chevron) chevron.classList.remove('expanded');
2468
+ }
2469
+ }
2470
+ });
2471
+ }
2472
+ } catch(e) {}
2473
+ }
2474
+
2475
+ function highlightActiveNavItem() {
2476
+ if (!currentSectionId) return;
2477
+ var navItem = document.querySelector('#sidebarNav [data-section="' + currentSectionId + '"]');
2478
+ if (navItem) navItem.classList.add('active');
2479
+ }
2480
+
2217
2481
 
2218
2482
  /* --- 03-render-cadrage.js --- */
2219
2483
  /* ============================================
@@ -2433,6 +2697,7 @@ function addModule() {
2433
2697
  featureType: document.getElementById('mod-type').value,
2434
2698
  priority: document.getElementById('mod-priority').value,
2435
2699
  entities: document.getElementById('mod-entities').value.split('\n').filter(e => e.trim()),
2700
+ anticipatedSections: [],
2436
2701
  status: 'pending'
2437
2702
  });
2438
2703
 
@@ -2489,14 +2754,7 @@ function renderModules() {
2489
2754
  }
2490
2755
 
2491
2756
  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>');
2757
+ buildNavTree();
2500
2758
  }
2501
2759
 
2502
2760
  /* ============================================
@@ -2671,6 +2929,7 @@ function renderModuleSpecSection(mod) {
2671
2929
  <button class="tab-btn" onclick="switchTab('${code}', 'perm')">Droits d'acces</button>
2672
2930
  <button class="tab-btn" onclick="switchTab('${code}', 'mock')">Maquettes</button>
2673
2931
  <button class="tab-btn" onclick="switchTab('${code}', 'notes')">Notes</button>
2932
+ <button class="tab-btn" onclick="switchTab('${code}', 'struct')">Structure</button>
2674
2933
  </div>
2675
2934
 
2676
2935
  <!-- TAB: Cas d'utilisation -->
@@ -2747,7 +3006,12 @@ function renderModuleSpecSection(mod) {
2747
3006
  <div class="tab-panel" id="tab-${code}-ent">
2748
3007
  <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
3008
  <div id="entList-${code}">
2750
- ${spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')}
3009
+ ${spec.entities.length > 0
3010
+ ? spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')
3011
+ : (mod.entities || []).length > 0
3012
+ ? '<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>'
3013
+ : ''
3014
+ }
2751
3015
  </div>
2752
3016
  <button class="add-btn" onclick="toggleForm('addEntForm-${code}')">+ Ajouter un type de donnees</button>
2753
3017
  <div class="inline-form" id="addEntForm-${code}">
@@ -2824,6 +3088,14 @@ function renderModuleSpecSection(mod) {
2824
3088
  <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
3089
  </div>
2826
3090
  </div>
3091
+
3092
+ <!-- TAB: Structure (sections/resources) -->
3093
+ <div class="tab-panel" id="tab-${code}-struct">
3094
+ <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>
3095
+ <div id="structContainer-${code}">
3096
+ ${renderModuleStructure(code)}
3097
+ </div>
3098
+ </div>
2827
3099
  </div>`;
2828
3100
  }
2829
3101
 
@@ -3181,6 +3453,47 @@ function refreshAllPermGrids() {
3181
3453
  });
3182
3454
  }
3183
3455
 
3456
+ function renderModuleStructure(code) {
3457
+ const mod = data.modules.find(m => m.code === code);
3458
+ const sections = mod ? (mod.anticipatedSections || []) : [];
3459
+
3460
+ if (sections.length === 0) {
3461
+ return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
3462
+ '<p>Aucune section definie pour ce module.</p>' +
3463
+ '<p style="font-size:0.85rem;margin-top:0.5rem;">Les sections et ressources seront identifiees lors de l\'analyse approfondie.</p>' +
3464
+ '</div>';
3465
+ }
3466
+
3467
+ return sections.map(function(section) {
3468
+ var resources = section.resources || [];
3469
+ var html = '<div class="struct-section">';
3470
+ html += '<div class="struct-section-header">';
3471
+ html += '<span class="struct-section-code">' + (section.code || section.name || '') + '</span>';
3472
+ if (section.description) {
3473
+ html += '<span class="struct-section-desc">' + section.description + '</span>';
3474
+ }
3475
+ html += '<span class="struct-section-badge">' + resources.length + ' ressource' + (resources.length !== 1 ? 's' : '') + '</span>';
3476
+ html += '</div>';
3477
+
3478
+ if (resources.length > 0) {
3479
+ html += '<div class="struct-resources">';
3480
+ resources.forEach(function(res) {
3481
+ var resName = typeof res === 'string' ? res : (res.code || res.name || '');
3482
+ var resDesc = typeof res === 'object' ? (res.description || '') : '';
3483
+ html += '<div class="struct-resource">';
3484
+ html += '<span class="struct-resource-icon">&#8226;</span>';
3485
+ html += '<span class="struct-resource-name">' + resName + '</span>';
3486
+ if (resDesc) html += '<span class="struct-resource-desc">' + resDesc + '</span>';
3487
+ html += '</div>';
3488
+ });
3489
+ html += '</div>';
3490
+ }
3491
+
3492
+ html += '</div>';
3493
+ return html;
3494
+ }).join('');
3495
+ }
3496
+
3184
3497
  function switchTab(code, tabId) {
3185
3498
  const section = document.getElementById('module-spec-' + code);
3186
3499
  if (!section) return;
@@ -3189,7 +3502,7 @@ function switchTab(code, tabId) {
3189
3502
  const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
3190
3503
  if (targetPanel) targetPanel.classList.add('active');
3191
3504
  const buttons = section.querySelectorAll('.tab-btn');
3192
- const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5 }[tabId];
3505
+ const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5, struct: 6 }[tabId];
3193
3506
  if (buttons[tabIndex]) buttons[tabIndex].classList.add('active');
3194
3507
  }
3195
3508
 
@@ -3471,7 +3784,9 @@ function renderCoverageMatrix() {
3471
3784
  <thead><tr><th>Besoin</th><th>Priorite</th><th>Domaine</th><th>Couvert</th></tr></thead>
3472
3785
  <tbody>
3473
3786
  ${allScope.map(item => {
3474
- const moduleName = data.modules.length > 0 ? data.modules[0].name : 'A definir';
3787
+ const moduleName = item.module
3788
+ ? (data.modules.find(m => m.code === item.module)?.name || item.module)
3789
+ : (data.modules.length > 0 ? data.modules.map(m => m.name).join(', ') : 'A definir');
3475
3790
  return `
3476
3791
  <tr>
3477
3792
  <td>${item.name}</td>
@@ -3499,37 +3814,127 @@ function saveToLocalStorage() {
3499
3814
  showNotification('Modifications sauvegardees');
3500
3815
  }
3501
3816
 
3817
+ function resetToEmbedded() {
3818
+ if (!confirm('Reinitialiser toutes les donnees depuis la version d\'origine ? Vos modifications locales (commentaires, notes) seront perdues.')) return;
3819
+ localStorage.removeItem(APP_KEY);
3820
+ // Restore data from ORIGINAL_DATA
3821
+ Object.keys(data).forEach(k => delete data[k]);
3822
+ Object.assign(data, JSON.parse(JSON.stringify(ORIGINAL_DATA)));
3823
+ // Re-render everything
3824
+ initEditableFields();
3825
+ renderStakeholders();
3826
+ renderScope();
3827
+ renderRisks();
3828
+ renderCriteria();
3829
+ renderModules();
3830
+ renderDependencies();
3831
+ renderAllModuleSpecs();
3832
+ renderConsolidation();
3833
+ renderHandoff();
3834
+ renderE2EFlows();
3835
+ updateCounts();
3836
+ renderReviewPanel();
3837
+ showNotification('Donnees reinitialisees depuis la version d\'origine');
3838
+ }
3839
+
3502
3840
  function loadFromLocalStorage() {
3503
3841
  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 || {};
3842
+ if (!saved) return;
3843
+
3844
+ try {
3845
+ const parsed = JSON.parse(saved);
3846
+
3847
+ // Build fingerprint of embedded structural data to detect HTML regeneration
3848
+ const embeddedFingerprint = ORIGINAL_DATA.modules.map(m => m.code).sort().join(',')
3849
+ + '|' + (ORIGINAL_DATA.metadata?.createdAt || '')
3850
+ + '|' + ORIGINAL_DATA.modules.length;
3851
+ const cachedFingerprint = parsed._structureFingerprint || '';
3852
+
3853
+ const structureChanged = embeddedFingerprint !== cachedFingerprint;
3854
+ const embeddedModuleCount = ORIGINAL_DATA.modules?.length || 0;
3855
+ const cachedModuleCount = (parsed.modules || []).length;
3856
+ const hasNewModules = embeddedModuleCount > cachedModuleCount;
3857
+ const embeddedModuleCodes = new Set(ORIGINAL_DATA.modules.map(m => m.code));
3858
+ const cachedModuleCodes = new Set((parsed.modules || []).map(m => m.code));
3859
+ const missingModules = [...embeddedModuleCodes].filter(c => !cachedModuleCodes.has(c));
3860
+
3861
+ if (structureChanged || hasNewModules || missingModules.length > 0) {
3862
+ // HTML was regenerated or has new modules — keep embedded structural data
3863
+ // Only restore user-specific edits (comments, custom roles, notes)
3864
+ data.wireframeComments = parsed.wireframeComments || {};
3865
+ data.specComments = parsed.specComments || {};
3866
+ data.customRoles = parsed.customRoles || [];
3867
+ data.customActions = parsed.customActions || [];
3868
+ data.comments = parsed.comments || [];
3869
+
3870
+ // Merge user-added notes per module (preserve existing module notes)
3871
+ for (const code of Object.keys(parsed.moduleSpecs || {})) {
3872
+ if (data.moduleSpecs[code] && parsed.moduleSpecs[code]?.notes) {
3873
+ data.moduleSpecs[code].notes = parsed.moduleSpecs[code].notes;
3874
+ }
3875
+ }
3876
+
3877
+ // Save fresh embedded data with fingerprint
3878
+ data._structureFingerprint = embeddedFingerprint;
3879
+ autoSave();
3880
+ } else {
3881
+ // Cache matches current structure — safe to restore user edits on cadrage/notes
3882
+ // IMPORTANT: Always keep embedded modules and moduleSpecs as structural source of truth
3883
+ // Only merge cadrage user-editable fields and notes
3884
+ if (parsed.cadrage) {
3885
+ // Merge cadrage context (user may have edited text fields)
3886
+ if (parsed.cadrage.context) {
3887
+ data.cadrage.context = { ...data.cadrage.context, ...parsed.cadrage.context };
3888
+ }
3889
+ // Merge scope only if user added items interactively
3890
+ if (parsed.cadrage.scope) {
3891
+ data.cadrage.scope = { ...data.cadrage.scope, ...parsed.cadrage.scope };
3892
+ }
3893
+ // Merge stakeholders and risks if user edited them
3894
+ if (parsed.cadrage.stakeholders) data.cadrage.stakeholders = parsed.cadrage.stakeholders;
3895
+ if (parsed.cadrage.risks) data.cadrage.risks = parsed.cadrage.risks;
3896
+ }
3897
+ data.dependencies = parsed.dependencies || data.dependencies;
3517
3898
  data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
3518
- data.handoff = parsed.handoff || {};
3899
+
3900
+ // Merge moduleSpecs: keep embedded structure, overlay user-editable fields (notes, custom permissions)
3901
+ for (const code of Object.keys(data.moduleSpecs)) {
3902
+ const cached = parsed.moduleSpecs?.[code];
3903
+ if (cached) {
3904
+ // Preserve user notes
3905
+ if (cached.notes) data.moduleSpecs[code].notes = cached.notes;
3906
+ // Preserve user-added use cases/rules/entities (merged with embedded)
3907
+ // Only add items that don't exist in embedded (by name match)
3908
+ const embeddedUcNames = new Set((data.moduleSpecs[code].useCases || []).map(uc => uc.name));
3909
+ (cached.useCases || []).forEach(uc => {
3910
+ if (!embeddedUcNames.has(uc.name)) data.moduleSpecs[code].useCases.push(uc);
3911
+ });
3912
+ const embeddedBrNames = new Set((data.moduleSpecs[code].businessRules || []).map(br => br.name));
3913
+ (cached.businessRules || []).forEach(br => {
3914
+ if (!embeddedBrNames.has(br.name)) data.moduleSpecs[code].businessRules.push(br);
3915
+ });
3916
+ // Preserve user permissions edits
3917
+ if (cached.permissions) data.moduleSpecs[code].permissions = cached.permissions;
3918
+ }
3919
+ }
3920
+
3519
3921
  data.wireframeComments = parsed.wireframeComments || {};
3520
3922
  data.specComments = parsed.specComments || {};
3521
3923
  data.customRoles = parsed.customRoles || [];
3522
3924
  data.customActions = parsed.customActions || [];
3523
3925
  data.comments = parsed.comments || [];
3524
3926
 
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
- }
3927
+ // Update fingerprint
3928
+ data._structureFingerprint = embeddedFingerprint;
3929
+ autoSave();
3930
+ }
3931
+
3932
+ // Restore editable fields
3933
+ document.querySelectorAll('.editable[data-field]').forEach(el => {
3934
+ const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
3935
+ if (value) el.textContent = value;
3936
+ });
3937
+ } catch (e) { console.error('Error loading saved data:', e); }
3533
3938
  }
3534
3939
 
3535
3940
 
@@ -3999,6 +4404,13 @@ function getSectionLabel(sectionId) {
3999
4404
  const mod = data.modules.find(m => m.code === code);
4000
4405
  return mod ? mod.name : code;
4001
4406
  }
4407
+ // Handle module structure sections (module-struct-{code}-{section})
4408
+ const structMatch = sectionId.match(/^module-struct-(.+?)-(.+)$/);
4409
+ if (structMatch) {
4410
+ const mod = data.modules.find(m => m.code === structMatch[1]);
4411
+ const modName = mod ? mod.name : structMatch[1];
4412
+ return modName + ' > Structure > ' + structMatch[2];
4413
+ }
4002
4414
  // Handle list-based sectionIds (ucList-*, brList-*, entList-*)
4003
4415
  const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
4004
4416
  if (listMatch) {