@atlashub/smartstack-cli 3.19.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 (35) 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/agents/gitflow/cleanup.md +5 -1
  7. package/templates/agents/gitflow/finish.md +2 -0
  8. package/templates/agents/gitflow/init-clone.md +13 -0
  9. package/templates/agents/gitflow/init-validate.md +14 -0
  10. package/templates/agents/gitflow/status.md +6 -0
  11. package/templates/project/api.ts.template +8 -29
  12. package/templates/project/appsettings.json.template +1 -0
  13. package/templates/skills/business-analyse/html/ba-interactive.html +562 -150
  14. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
  15. package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
  16. package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
  17. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +57 -2
  18. package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
  19. package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
  20. package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
  21. package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
  22. package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
  23. package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
  24. package/templates/skills/business-analyse/html/src/template.html +8 -76
  25. package/templates/skills/business-analyse/references/deploy-data-build.md +9 -7
  26. package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
  27. package/templates/skills/business-analyse/references/validate-incremental-html.md +2 -1
  28. package/templates/skills/business-analyse/steps/step-02-decomposition.md +16 -3
  29. package/templates/skills/business-analyse/steps/step-03c-compile.md +55 -2
  30. package/templates/skills/business-analyse/steps/step-03d-validate.md +82 -15
  31. package/templates/skills/business-analyse/steps/step-05a-handoff.md +77 -3
  32. package/templates/skills/business-analyse/steps/step-05b-deploy.md +27 -0
  33. package/templates/skills/gitflow/_shared.md +65 -17
  34. package/templates/skills/gitflow/phases/status.md +8 -3
  35. package/templates/skills/gitflow/steps/step-start.md +3 -0
@@ -47,6 +47,14 @@ data.cadrage.scope = data.cadrage.scope || { vital: [], important: [], optional:
47
47
 
48
48
  // Defensive init: moduleSpecs (may be missing if LLM didn't follow mapping)
49
49
  data.moduleSpecs = data.moduleSpecs || {};
50
+ // Ensure ALL modules have a moduleSpecs entry (prevents missing schemas)
51
+ (data.modules || []).forEach(function(m) {
52
+ if (!data.moduleSpecs[m.code]) {
53
+ data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
54
+ }
55
+ // Ensure anticipatedSections array exists
56
+ m.anticipatedSections = m.anticipatedSections || [];
57
+ });
50
58
 
51
59
  // Vibe coding mode: hide non-relevant sections
52
60
  const isVibeCoding = data.metadata?.vibeCoding === true;
@@ -87,11 +95,7 @@ function setNestedValue(obj, path, value) {
87
95
  }
88
96
 
89
97
  function updateCounts() {
90
- document.getElementById('stakeholderCount').textContent = data.cadrage.stakeholders.length;
91
- document.getElementById('moduleCount').textContent = data.modules.length;
92
- const totalEntities = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.entities || []).length, 0);
93
- document.getElementById('entityCount').textContent = totalEntities;
94
- updateModulesNav();
98
+ buildNavTree();
95
99
  updateDepSelects();
96
100
  }
97
101
 
@@ -139,7 +143,8 @@ document.addEventListener('DOMContentLoaded', function() {
139
143
  renderConsolidation();
140
144
  renderHandoff();
141
145
  renderE2EFlows();
142
- updateCounts();
146
+ buildNavTree();
147
+ updateDepSelects();
143
148
  initInlineComments();
144
149
  renderReviewPanel();
145
150
  });
@@ -1,7 +1,10 @@
1
1
  /* ============================================
2
- NAVIGATION
2
+ NAVIGATION - Hierarchical Tree
3
3
  ============================================ */
4
4
  let currentSectionId = 'cadrage-context';
5
+ let navCollapseState = {};
6
+
7
+ /* ---------- Core ---------- */
5
8
 
6
9
  function showSection(sectionId) {
7
10
  currentSectionId = sectionId;
@@ -9,9 +12,30 @@ function showSection(sectionId) {
9
12
  const section = document.getElementById(sectionId);
10
13
  if (section) section.style.display = 'block';
11
14
 
12
- document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
13
- const navItem = document.querySelector('[data-section="' + sectionId + '"]');
14
- if (navItem) navItem.classList.add('active');
15
+ // Highlight active nav item
16
+ document.querySelectorAll('#sidebarNav .nav-item').forEach(n => n.classList.remove('active'));
17
+ const navItem = document.querySelector('#sidebarNav [data-section="' + sectionId + '"]');
18
+ if (navItem) {
19
+ navItem.classList.add('active');
20
+ // Auto-expand parent groups to reveal active item
21
+ let parent = navItem.parentElement;
22
+ while (parent && parent.id !== 'sidebarNav') {
23
+ if (parent.classList.contains('nav-children') && parent.style.display === 'none') {
24
+ parent.style.display = '';
25
+ const groupEl = parent.parentElement;
26
+ if (groupEl) {
27
+ const groupId = groupEl.dataset.groupId;
28
+ if (groupId) {
29
+ navCollapseState[groupId] = false;
30
+ const chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
31
+ if (chevron) chevron.classList.add('expanded');
32
+ }
33
+ }
34
+ }
35
+ parent = parent.parentElement;
36
+ }
37
+ saveNavState();
38
+ }
15
39
  }
16
40
 
17
41
  function restoreCurrentSection() {
@@ -20,3 +44,184 @@ function restoreCurrentSection() {
20
44
  if (section) section.style.display = 'block';
21
45
  }
22
46
  }
47
+
48
+ /* ---------- Tree Builder ---------- */
49
+
50
+ function buildNavTree() {
51
+ const nav = document.getElementById('sidebarNav');
52
+ if (!nav) return;
53
+
54
+ nav.innerHTML =
55
+ renderNavGroup('cadrage', 'Cadrage', buildCadrageItems()) +
56
+ renderNavGroup('modules', 'Modules (' + data.modules.length + ')', buildModuleItems()) +
57
+ renderNavGroup('consolidation', 'Consolidation', buildConsolidationItems()) +
58
+ renderNavGroup('synthese', 'Synthese', buildSyntheseItems());
59
+
60
+ restoreNavState();
61
+ highlightActiveNavItem();
62
+ }
63
+
64
+ function renderNavGroup(id, title, itemsHtml) {
65
+ const collapsed = navCollapseState[id] === true;
66
+ return '<div class="nav-group" data-group-id="' + id + '">' +
67
+ '<div class="nav-group-title" onclick="toggleNavGroup(\'' + id + '\')">' +
68
+ '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">&#9656;</span> ' +
69
+ title +
70
+ '</div>' +
71
+ '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>' +
72
+ itemsHtml +
73
+ '</div>' +
74
+ '</div>';
75
+ }
76
+
77
+ function renderNavItem(sectionId, label, badge) {
78
+ return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\')" data-section="' + sectionId + '">' +
79
+ '<span class="nav-icon">&#9679;</span> ' + label +
80
+ (badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
81
+ '</a>';
82
+ }
83
+
84
+ /* ---------- Group Builders ---------- */
85
+
86
+ function buildCadrageItems() {
87
+ return renderNavItem('cadrage-context', 'Contexte') +
88
+ renderNavItem('cadrage-stakeholders', 'Parties prenantes', data.cadrage.stakeholders.length) +
89
+ renderNavItem('cadrage-scope', 'Perimetre fonctionnel') +
90
+ renderNavItem('cadrage-risks', 'Risques et hypotheses', data.cadrage.risks.length) +
91
+ renderNavItem('cadrage-success', 'Criteres de reussite');
92
+ }
93
+
94
+ function buildModuleItems() {
95
+ let html = '';
96
+ data.modules.forEach(function(mod) {
97
+ html += renderModuleNavItem(mod);
98
+ });
99
+ // Global module views at bottom
100
+ html += renderNavItem('decomp-modules', 'Vue d\'ensemble', data.modules.length);
101
+ html += renderNavItem('decomp-dependencies', 'Dependances', data.dependencies.length);
102
+ return html;
103
+ }
104
+
105
+ function buildConsolidationItems() {
106
+ var totalEntities = data.modules.reduce(function(sum, m) {
107
+ return sum + (data.moduleSpecs[m.code]?.entities || []).length;
108
+ }, 0);
109
+ return renderNavItem('consol-datamodel', 'Modele de donnees', totalEntities) +
110
+ renderNavItem('consol-interactions', 'Interactions') +
111
+ renderNavItem('consol-permissions', 'Coherence des acces') +
112
+ renderNavItem('consol-flows', 'Parcours bout en bout', data.consolidation.e2eFlows.length);
113
+ }
114
+
115
+ function buildSyntheseItems() {
116
+ return renderNavItem('handoff-summary', 'Vue d\'ensemble');
117
+ }
118
+
119
+ /* ---------- Module Sub-Tree ---------- */
120
+
121
+ function renderModuleNavItem(mod) {
122
+ var code = mod.code;
123
+ var spec = data.moduleSpecs[code] || {};
124
+ var ucCount = (spec.useCases || []).length;
125
+ var brCount = (spec.businessRules || []).length;
126
+ var entCount = (spec.entities || []).length;
127
+ var sections = mod.anticipatedSections || [];
128
+ var groupId = 'mod-' + code;
129
+ var collapsed = navCollapseState[groupId] === true;
130
+
131
+ var html = '<div class="nav-module" data-group-id="' + groupId + '">';
132
+
133
+ // Module header (clickable to expand + navigate to module spec)
134
+ html += '<a class="nav-item nav-module-header" onclick="toggleNavGroup(\'' + groupId + '\');showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">';
135
+ html += '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">&#9656;</span> ';
136
+ html += (mod.name || mod.code);
137
+ html += '</a>';
138
+
139
+ // Children: tabs
140
+ html += '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>';
141
+ html += renderModuleTabNavItem(code, 'uc', 'Cas d\'utilisation', ucCount);
142
+ html += renderModuleTabNavItem(code, 'br', 'Regles metier', brCount);
143
+ html += renderModuleTabNavItem(code, 'ent', 'Donnees', entCount);
144
+ html += renderModuleTabNavItem(code, 'perm', 'Droits d\'acces');
145
+ html += renderModuleTabNavItem(code, 'mock', 'Maquettes');
146
+ html += renderModuleTabNavItem(code, 'struct', 'Structure', sections.length);
147
+
148
+ // Children: sections/resources (deeper tree)
149
+ if (sections.length > 0) {
150
+ sections.forEach(function(section) {
151
+ var resources = section.resources || [];
152
+ html += '<div class="nav-section-item">';
153
+ html += '<a class="nav-item nav-section-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'struct\')" data-section="module-struct-' + code + '-' + section.code + '">';
154
+ html += '<span class="nav-icon nav-icon-section">&#9655;</span> ' + section.code;
155
+ if (resources.length > 0) html += ' <span class="nav-badge">' + resources.length + '</span>';
156
+ html += '</a>';
157
+ if (resources.length > 0) {
158
+ html += '<div class="nav-children nav-resources">';
159
+ resources.forEach(function(res) {
160
+ var resName = typeof res === 'string' ? res : (res.code || res.name || '');
161
+ html += '<a class="nav-item nav-resource-link">';
162
+ html += '<span class="nav-icon nav-icon-resource">&#8226;</span> ' + resName;
163
+ html += '</a>';
164
+ });
165
+ html += '</div>';
166
+ }
167
+ html += '</div>';
168
+ });
169
+ }
170
+
171
+ html += '</div>'; // nav-children
172
+ html += '</div>'; // nav-module
173
+ return html;
174
+ }
175
+
176
+ function renderModuleTabNavItem(code, tabId, label, badge) {
177
+ var sectionId = 'module-spec-' + code;
178
+ return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\');switchTab(\'' + code + '\',\'' + tabId + '\')" data-section="' + sectionId + '-' + tabId + '">' +
179
+ '<span class="nav-icon">&#9679;</span> ' + label +
180
+ (badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
181
+ '</a>';
182
+ }
183
+
184
+ /* ---------- Collapse/Expand ---------- */
185
+
186
+ function toggleNavGroup(groupId) {
187
+ navCollapseState[groupId] = !navCollapseState[groupId];
188
+ var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
189
+ if (groupEl) {
190
+ var children = groupEl.querySelector(':scope > .nav-children');
191
+ var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
192
+ if (children) children.style.display = navCollapseState[groupId] ? 'none' : '';
193
+ if (chevron) chevron.classList.toggle('expanded', !navCollapseState[groupId]);
194
+ }
195
+ saveNavState();
196
+ }
197
+
198
+ function saveNavState() {
199
+ try { localStorage.setItem(APP_KEY + '-nav', JSON.stringify(navCollapseState)); } catch(e) {}
200
+ }
201
+
202
+ function restoreNavState() {
203
+ try {
204
+ var saved = localStorage.getItem(APP_KEY + '-nav');
205
+ if (saved) {
206
+ navCollapseState = JSON.parse(saved);
207
+ // Apply collapse state to all groups
208
+ Object.keys(navCollapseState).forEach(function(groupId) {
209
+ if (navCollapseState[groupId]) {
210
+ var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
211
+ if (groupEl) {
212
+ var children = groupEl.querySelector(':scope > .nav-children');
213
+ var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
214
+ if (children) children.style.display = 'none';
215
+ if (chevron) chevron.classList.remove('expanded');
216
+ }
217
+ }
218
+ });
219
+ }
220
+ } catch(e) {}
221
+ }
222
+
223
+ function highlightActiveNavItem() {
224
+ if (!currentSectionId) return;
225
+ var navItem = document.querySelector('#sidebarNav [data-section="' + currentSectionId + '"]');
226
+ if (navItem) navItem.classList.add('active');
227
+ }
@@ -13,6 +13,7 @@ function addModule() {
13
13
  featureType: document.getElementById('mod-type').value,
14
14
  priority: document.getElementById('mod-priority').value,
15
15
  entities: document.getElementById('mod-entities').value.split('\n').filter(e => e.trim()),
16
+ anticipatedSections: [],
16
17
  status: 'pending'
17
18
  });
18
19
 
@@ -69,14 +70,7 @@ function renderModules() {
69
70
  }
70
71
 
71
72
  function updateModulesNav() {
72
- const nav = document.getElementById('modulesNav');
73
- const navItems = data.modules.map(m => `
74
- <a class="nav-item" onclick="showSection('module-spec-${m.code}')" data-section="module-spec-${m.code}">
75
- <span class="nav-icon">&#9679;</span> ${m.name}
76
- <span class="nav-badge">${(data.moduleSpecs[m.code]?.useCases || []).length}</span>
77
- </a>
78
- `).join('');
79
- 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>');
73
+ buildNavTree();
80
74
  }
81
75
 
82
76
  /* ============================================
@@ -37,6 +37,7 @@ function renderModuleSpecSection(mod) {
37
37
  <button class="tab-btn" onclick="switchTab('${code}', 'perm')">Droits d'acces</button>
38
38
  <button class="tab-btn" onclick="switchTab('${code}', 'mock')">Maquettes</button>
39
39
  <button class="tab-btn" onclick="switchTab('${code}', 'notes')">Notes</button>
40
+ <button class="tab-btn" onclick="switchTab('${code}', 'struct')">Structure</button>
40
41
  </div>
41
42
 
42
43
  <!-- TAB: Cas d'utilisation -->
@@ -113,7 +114,12 @@ function renderModuleSpecSection(mod) {
113
114
  <div class="tab-panel" id="tab-${code}-ent">
114
115
  <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>
115
116
  <div id="entList-${code}">
116
- ${spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')}
117
+ ${spec.entities.length > 0
118
+ ? spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')
119
+ : (mod.entities || []).length > 0
120
+ ? '<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>'
121
+ : ''
122
+ }
117
123
  </div>
118
124
  <button class="add-btn" onclick="toggleForm('addEntForm-${code}')">+ Ajouter un type de donnees</button>
119
125
  <div class="inline-form" id="addEntForm-${code}">
@@ -190,6 +196,14 @@ function renderModuleSpecSection(mod) {
190
196
  <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>
191
197
  </div>
192
198
  </div>
199
+
200
+ <!-- TAB: Structure (sections/resources) -->
201
+ <div class="tab-panel" id="tab-${code}-struct">
202
+ <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>
203
+ <div id="structContainer-${code}">
204
+ ${renderModuleStructure(code)}
205
+ </div>
206
+ </div>
193
207
  </div>`;
194
208
  }
195
209
 
@@ -547,6 +561,47 @@ function refreshAllPermGrids() {
547
561
  });
548
562
  }
549
563
 
564
+ function renderModuleStructure(code) {
565
+ const mod = data.modules.find(m => m.code === code);
566
+ const sections = mod ? (mod.anticipatedSections || []) : [];
567
+
568
+ if (sections.length === 0) {
569
+ return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
570
+ '<p>Aucune section definie pour ce module.</p>' +
571
+ '<p style="font-size:0.85rem;margin-top:0.5rem;">Les sections et ressources seront identifiees lors de l\'analyse approfondie.</p>' +
572
+ '</div>';
573
+ }
574
+
575
+ return sections.map(function(section) {
576
+ var resources = section.resources || [];
577
+ var html = '<div class="struct-section">';
578
+ html += '<div class="struct-section-header">';
579
+ html += '<span class="struct-section-code">' + (section.code || section.name || '') + '</span>';
580
+ if (section.description) {
581
+ html += '<span class="struct-section-desc">' + section.description + '</span>';
582
+ }
583
+ html += '<span class="struct-section-badge">' + resources.length + ' ressource' + (resources.length !== 1 ? 's' : '') + '</span>';
584
+ html += '</div>';
585
+
586
+ if (resources.length > 0) {
587
+ html += '<div class="struct-resources">';
588
+ resources.forEach(function(res) {
589
+ var resName = typeof res === 'string' ? res : (res.code || res.name || '');
590
+ var resDesc = typeof res === 'object' ? (res.description || '') : '';
591
+ html += '<div class="struct-resource">';
592
+ html += '<span class="struct-resource-icon">&#8226;</span>';
593
+ html += '<span class="struct-resource-name">' + resName + '</span>';
594
+ if (resDesc) html += '<span class="struct-resource-desc">' + resDesc + '</span>';
595
+ html += '</div>';
596
+ });
597
+ html += '</div>';
598
+ }
599
+
600
+ html += '</div>';
601
+ return html;
602
+ }).join('');
603
+ }
604
+
550
605
  function switchTab(code, tabId) {
551
606
  const section = document.getElementById('module-spec-' + code);
552
607
  if (!section) return;
@@ -555,6 +610,6 @@ function switchTab(code, tabId) {
555
610
  const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
556
611
  if (targetPanel) targetPanel.classList.add('active');
557
612
  const buttons = section.querySelectorAll('.tab-btn');
558
- const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5 }[tabId];
613
+ const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5, struct: 6 }[tabId];
559
614
  if (buttons[tabIndex]) buttons[tabIndex].classList.add('active');
560
615
  }
@@ -76,7 +76,9 @@ function renderCoverageMatrix() {
76
76
  <thead><tr><th>Besoin</th><th>Priorite</th><th>Domaine</th><th>Couvert</th></tr></thead>
77
77
  <tbody>
78
78
  ${allScope.map(item => {
79
- const moduleName = data.modules.length > 0 ? data.modules[0].name : 'A definir';
79
+ const moduleName = item.module
80
+ ? (data.modules.find(m => m.code === item.module)?.name || item.module)
81
+ : (data.modules.length > 0 ? data.modules.map(m => m.name).join(', ') : 'A definir');
80
82
  return `
81
83
  <tr>
82
84
  <td>${item.name}</td>
@@ -11,35 +11,125 @@ function saveToLocalStorage() {
11
11
  showNotification('Modifications sauvegardees');
12
12
  }
13
13
 
14
+ function resetToEmbedded() {
15
+ if (!confirm('Reinitialiser toutes les donnees depuis la version d\'origine ? Vos modifications locales (commentaires, notes) seront perdues.')) return;
16
+ localStorage.removeItem(APP_KEY);
17
+ // Restore data from ORIGINAL_DATA
18
+ Object.keys(data).forEach(k => delete data[k]);
19
+ Object.assign(data, JSON.parse(JSON.stringify(ORIGINAL_DATA)));
20
+ // Re-render everything
21
+ initEditableFields();
22
+ renderStakeholders();
23
+ renderScope();
24
+ renderRisks();
25
+ renderCriteria();
26
+ renderModules();
27
+ renderDependencies();
28
+ renderAllModuleSpecs();
29
+ renderConsolidation();
30
+ renderHandoff();
31
+ renderE2EFlows();
32
+ updateCounts();
33
+ renderReviewPanel();
34
+ showNotification('Donnees reinitialisees depuis la version d\'origine');
35
+ }
36
+
14
37
  function loadFromLocalStorage() {
15
38
  const saved = localStorage.getItem(APP_KEY);
16
- if (saved) {
17
- try {
18
- const parsed = JSON.parse(saved);
19
- // Deep merge with defaults
20
- data.metadata = { ...data.metadata, ...parsed.metadata };
21
- data.cadrage = {
22
- ...data.cadrage,
23
- ...parsed.cadrage,
24
- scope: { ...data.cadrage.scope, ...(parsed.cadrage?.scope || {}) }
25
- };
26
- data.modules = parsed.modules || [];
27
- data.dependencies = parsed.dependencies || [];
28
- data.moduleSpecs = parsed.moduleSpecs || {};
39
+ if (!saved) return;
40
+
41
+ try {
42
+ const parsed = JSON.parse(saved);
43
+
44
+ // Build fingerprint of embedded structural data to detect HTML regeneration
45
+ const embeddedFingerprint = ORIGINAL_DATA.modules.map(m => m.code).sort().join(',')
46
+ + '|' + (ORIGINAL_DATA.metadata?.createdAt || '')
47
+ + '|' + ORIGINAL_DATA.modules.length;
48
+ const cachedFingerprint = parsed._structureFingerprint || '';
49
+
50
+ const structureChanged = embeddedFingerprint !== cachedFingerprint;
51
+ const embeddedModuleCount = ORIGINAL_DATA.modules?.length || 0;
52
+ const cachedModuleCount = (parsed.modules || []).length;
53
+ const hasNewModules = embeddedModuleCount > cachedModuleCount;
54
+ const embeddedModuleCodes = new Set(ORIGINAL_DATA.modules.map(m => m.code));
55
+ const cachedModuleCodes = new Set((parsed.modules || []).map(m => m.code));
56
+ const missingModules = [...embeddedModuleCodes].filter(c => !cachedModuleCodes.has(c));
57
+
58
+ if (structureChanged || hasNewModules || missingModules.length > 0) {
59
+ // HTML was regenerated or has new modules — keep embedded structural data
60
+ // Only restore user-specific edits (comments, custom roles, notes)
61
+ data.wireframeComments = parsed.wireframeComments || {};
62
+ data.specComments = parsed.specComments || {};
63
+ data.customRoles = parsed.customRoles || [];
64
+ data.customActions = parsed.customActions || [];
65
+ data.comments = parsed.comments || [];
66
+
67
+ // Merge user-added notes per module (preserve existing module notes)
68
+ for (const code of Object.keys(parsed.moduleSpecs || {})) {
69
+ if (data.moduleSpecs[code] && parsed.moduleSpecs[code]?.notes) {
70
+ data.moduleSpecs[code].notes = parsed.moduleSpecs[code].notes;
71
+ }
72
+ }
73
+
74
+ // Save fresh embedded data with fingerprint
75
+ data._structureFingerprint = embeddedFingerprint;
76
+ autoSave();
77
+ } else {
78
+ // Cache matches current structure — safe to restore user edits on cadrage/notes
79
+ // IMPORTANT: Always keep embedded modules and moduleSpecs as structural source of truth
80
+ // Only merge cadrage user-editable fields and notes
81
+ if (parsed.cadrage) {
82
+ // Merge cadrage context (user may have edited text fields)
83
+ if (parsed.cadrage.context) {
84
+ data.cadrage.context = { ...data.cadrage.context, ...parsed.cadrage.context };
85
+ }
86
+ // Merge scope only if user added items interactively
87
+ if (parsed.cadrage.scope) {
88
+ data.cadrage.scope = { ...data.cadrage.scope, ...parsed.cadrage.scope };
89
+ }
90
+ // Merge stakeholders and risks if user edited them
91
+ if (parsed.cadrage.stakeholders) data.cadrage.stakeholders = parsed.cadrage.stakeholders;
92
+ if (parsed.cadrage.risks) data.cadrage.risks = parsed.cadrage.risks;
93
+ }
94
+ data.dependencies = parsed.dependencies || data.dependencies;
29
95
  data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
30
- data.handoff = parsed.handoff || {};
96
+
97
+ // Merge moduleSpecs: keep embedded structure, overlay user-editable fields (notes, custom permissions)
98
+ for (const code of Object.keys(data.moduleSpecs)) {
99
+ const cached = parsed.moduleSpecs?.[code];
100
+ if (cached) {
101
+ // Preserve user notes
102
+ if (cached.notes) data.moduleSpecs[code].notes = cached.notes;
103
+ // Preserve user-added use cases/rules/entities (merged with embedded)
104
+ // Only add items that don't exist in embedded (by name match)
105
+ const embeddedUcNames = new Set((data.moduleSpecs[code].useCases || []).map(uc => uc.name));
106
+ (cached.useCases || []).forEach(uc => {
107
+ if (!embeddedUcNames.has(uc.name)) data.moduleSpecs[code].useCases.push(uc);
108
+ });
109
+ const embeddedBrNames = new Set((data.moduleSpecs[code].businessRules || []).map(br => br.name));
110
+ (cached.businessRules || []).forEach(br => {
111
+ if (!embeddedBrNames.has(br.name)) data.moduleSpecs[code].businessRules.push(br);
112
+ });
113
+ // Preserve user permissions edits
114
+ if (cached.permissions) data.moduleSpecs[code].permissions = cached.permissions;
115
+ }
116
+ }
117
+
31
118
  data.wireframeComments = parsed.wireframeComments || {};
32
119
  data.specComments = parsed.specComments || {};
33
120
  data.customRoles = parsed.customRoles || [];
34
121
  data.customActions = parsed.customActions || [];
35
122
  data.comments = parsed.comments || [];
36
123
 
37
- // Restore editable fields
38
- document.querySelectorAll('.editable[data-field]').forEach(el => {
39
- const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
40
- if (value) el.textContent = value;
41
- });
42
- renderProcessFlow();
43
- } catch (e) { console.error('Error loading saved data:', e); }
44
- }
124
+ // Update fingerprint
125
+ data._structureFingerprint = embeddedFingerprint;
126
+ autoSave();
127
+ }
128
+
129
+ // Restore editable fields
130
+ document.querySelectorAll('.editable[data-field]').forEach(el => {
131
+ const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
132
+ if (value) el.textContent = value;
133
+ });
134
+ } catch (e) { console.error('Error loading saved data:', e); }
45
135
  }
@@ -118,6 +118,13 @@ function getSectionLabel(sectionId) {
118
118
  const mod = data.modules.find(m => m.code === code);
119
119
  return mod ? mod.name : code;
120
120
  }
121
+ // Handle module structure sections (module-struct-{code}-{section})
122
+ const structMatch = sectionId.match(/^module-struct-(.+?)-(.+)$/);
123
+ if (structMatch) {
124
+ const mod = data.modules.find(m => m.code === structMatch[1]);
125
+ const modName = mod ? mod.name : structMatch[1];
126
+ return modName + ' > Structure > ' + structMatch[2];
127
+ }
121
128
  // Handle list-based sectionIds (ucList-*, brList-*, entList-*)
122
129
  const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
123
130
  if (listMatch) {
@@ -90,7 +90,7 @@
90
90
  PRINT
91
91
  ============================================ */
92
92
  @media print {
93
- .sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove, .phase-progress { display: none !important; }
93
+ .sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove { display: none !important; }
94
94
  .main { max-width: 100%; padding: 0; }
95
95
  .section { display: block !important; page-break-inside: avoid; }
96
96
  body { background: #fff; color: #1a1a1a; }