@atlashub/smartstack-cli 4.48.0 → 4.50.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/.documentation/testing-ba-e2e.md +76 -24
  2. package/package.json +1 -1
  3. package/templates/agents/gitflow/init.md +26 -0
  4. package/templates/skills/apex/references/parallel-execution.md +22 -4
  5. package/templates/skills/apex/steps/step-00-init.md +38 -0
  6. package/templates/skills/apex/steps/step-03a-layer0-domain.md +21 -0
  7. package/templates/skills/apex/steps/step-03b-layer1-seed.md +60 -0
  8. package/templates/skills/apex/steps/step-03c-layer2-backend.md +124 -13
  9. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +32 -0
  10. package/templates/skills/application/references/backend-controller-hierarchy.md +14 -4
  11. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +2 -0
  12. package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +2 -0
  13. package/templates/skills/business-analyse/schemas/application-schema.json +36 -1
  14. package/templates/skills/business-analyse/schemas/sections/specification-schema.json +19 -0
  15. package/templates/skills/business-analyse/steps/step-00-init.md +64 -14
  16. package/templates/skills/business-analyse/steps/step-01-cadrage.md +49 -2
  17. package/templates/skills/business-analyse/steps/step-02-structure.md +41 -17
  18. package/templates/skills/business-analyse/steps/step-04-consolidate.md +171 -0
  19. package/templates/skills/business-analyse-develop/references/quality-gates.md +91 -1
  20. package/templates/skills/business-analyse-develop/steps/step-01-task.md +147 -1
  21. package/templates/skills/business-analyse-handoff/references/acceptance-criteria.md +53 -1
  22. package/templates/skills/business-analyse-handoff/references/handoff-file-templates.md +42 -0
  23. package/templates/skills/business-analyse-handoff/references/handoff-mappings.md +15 -1
  24. package/templates/skills/business-analyse-handoff/references/prd-generation.md +59 -0
  25. package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +25 -1
  26. package/templates/skills/business-analyse-handoff/steps/step-02-export.md +32 -4
  27. package/templates/skills/business-analyse-html/html/ba-interactive.html +80 -11
  28. package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +4 -2
  29. package/templates/skills/business-analyse-html/html/src/scripts/02-navigation.js +16 -2
  30. package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +19 -4
  31. package/templates/skills/business-analyse-html/html/src/scripts/06-render-mockups.js +1 -1
  32. package/templates/skills/business-analyse-html/html/src/styles/03-navigation.css +2 -2
  33. package/templates/skills/business-analyse-html/html/src/styles/05-modules.css +38 -0
  34. package/templates/skills/business-analyse-html/references/data-build.md +4 -1
  35. package/templates/skills/business-analyse-html/references/data-mapping.md +4 -1
  36. package/templates/skills/business-analyse-html/steps/step-02-build-data.md +113 -1
  37. package/templates/skills/business-analyse-html/steps/step-04-verify.md +17 -1
  38. package/templates/skills/controller/references/mcp-scaffold-workflow.md +8 -4
  39. package/templates/skills/controller/steps/step-05-validate.md +2 -2
  40. package/templates/skills/controller/templates.md +4 -3
  41. package/templates/skills/feature-full/steps/step-01-implementation.md +18 -5
@@ -208,6 +208,64 @@ Each file entry in a category array must have:
208
208
 
209
209
  ---
210
210
 
211
+ ### 6. Specification Files (MANDATORY)
212
+
213
+ Each PRD MUST include a `specificationFiles` object and 5 companion files in `.ralph/`:
214
+
215
+ ```json
216
+ {
217
+ "$version": "3.0.0",
218
+ "implementation": { "filesToCreate": { /* 8 categories */ } },
219
+ "brToCodeMapping": [ /* enriched with statement, example, formula */ ],
220
+ "apiEndpointSummary": [ /* ... */ ],
221
+ "specificationFiles": {
222
+ "entities": "prd-{moduleCode}.entities.json",
223
+ "rules": "prd-{moduleCode}.rules.json",
224
+ "usecases": "prd-{moduleCode}.usecases.json",
225
+ "screens": "prd-{moduleCode}.screens.json",
226
+ "permissions": "prd-{moduleCode}.permissions.json"
227
+ }
228
+ }
229
+ ```
230
+
231
+ **Companion files** are VERBATIM copies of BA source files, colocated in `.ralph/`:
232
+
233
+ ```
234
+ .ralph/
235
+ prd-Employees.json # PRD manifest (compact)
236
+ prd-Employees.entities.json # VERBATIM copy of entities.json
237
+ prd-Employees.rules.json # VERBATIM copy of rules.json
238
+ prd-Employees.usecases.json # VERBATIM copy of usecases.json
239
+ prd-Employees.screens.json # VERBATIM copy of screens.json
240
+ prd-Employees.permissions.json # VERBATIM copy of permissions.json
241
+ ```
242
+
243
+ **Validation:**
244
+ ```javascript
245
+ const specFiles = prd.specificationFiles;
246
+ if (!specFiles) {
247
+ BLOCKING_ERROR('specificationFiles missing from PRD');
248
+ }
249
+ const specKeys = ['entities', 'rules', 'usecases', 'screens', 'permissions'];
250
+ for (const key of specKeys) {
251
+ if (!specFiles[key]) {
252
+ BLOCKING_ERROR(`specificationFiles.${key} missing`);
253
+ }
254
+ const filePath = path.join('.ralph', specFiles[key]);
255
+ if (!fs.existsSync(filePath)) {
256
+ BLOCKING_ERROR(`Companion file not found: ${filePath}`);
257
+ }
258
+ }
259
+ ```
260
+
261
+ **Why companion files matter:**
262
+ - `/apex` loads only the specs needed per layer (entities for L0, rules+usecases for L2, screens for L3)
263
+ - Context per layer is ~200-500 lines instead of ~2000 lines monolithic
264
+ - Zero risk of specs being lost at the bottom of a large context window
265
+ - Full BA detail available for oneshot generation (attributes, formulas, screen columns)
266
+
267
+ ---
268
+
211
269
  ## Validation Checklist
212
270
 
213
271
  Use this checklist when validating PRD files:
@@ -219,6 +277,7 @@ Use this checklist when validating PRD files:
219
277
  | **Categories** | All 8 categories present | PASS/FAIL |
220
278
  | **File Counts** | PRD counts match index.json handoff.filesToCreate | PASS/FAIL |
221
279
  | **File Schemas** | All files have path, type, module | PASS/FAIL |
280
+ | **Spec Files** | `specificationFiles` present with 5 paths, all companion files exist | PASS/FAIL |
222
281
 
223
282
  **All checks must PASS before proceeding to /business-analyse-develop**
224
283
 
@@ -151,6 +151,25 @@ Write via ba-writer.enrichModuleHandoff with the complete handoff payload:
151
151
  - totalFiles, totalTasks, handedOffAt (ISO timestamp)
152
152
  - featureDescription: {featureDescription}
153
153
 
154
+ ### Specification Files (MANDATORY)
155
+ After writing the handoff data, ALSO write 5 companion files:
156
+ 1. `.ralph/prd-{moduleCode}.entities.json` = VERBATIM copy of `{moduleDir}/entities.json`
157
+ 2. `.ralph/prd-{moduleCode}.rules.json` = VERBATIM copy of `{moduleDir}/rules.json`
158
+ 3. `.ralph/prd-{moduleCode}.usecases.json` = VERBATIM copy of `{moduleDir}/usecases.json`
159
+ 4. `.ralph/prd-{moduleCode}.screens.json` = VERBATIM copy of `{moduleDir}/screens.json`
160
+ 5. `.ralph/prd-{moduleCode}.permissions.json` = VERBATIM copy of `{moduleDir}/permissions.json`
161
+
162
+ Add `specificationFiles` to the PRD referencing these files (relative names, all in `.ralph/`):
163
+ ```json
164
+ "specificationFiles": {
165
+ "entities": "prd-{moduleCode}.entities.json",
166
+ "rules": "prd-{moduleCode}.rules.json",
167
+ "usecases": "prd-{moduleCode}.usecases.json",
168
+ "screens": "prd-{moduleCode}.screens.json",
169
+ "permissions": "prd-{moduleCode}.permissions.json"
170
+ }
171
+ ```
172
+
154
173
  ### POST-CHECK (BLOCKING)
155
174
  After writing, verify:
156
175
  1. Handoff not empty
@@ -159,7 +178,12 @@ After writing, verify:
159
178
  4. Section resources have entity field
160
179
  5. SeedData contains CORE entries (NavigationModuleSeedData, NavigationSectionSeedData if sections exist, PermissionsSeedData, RolesSeedData)
161
180
  6. For FIRST module only: SeedData contains APP-LEVEL CORE entries (NavigationApplicationSeedData, ApplicationRolesSeedData)
162
- Display: "POST-CHECK PASS: {moduleCode} -- 8 categories, {brCount} BRs mapped, {coreCount} core seeds"
181
+ 7. All 5 companion files exist in `.ralph/` and are non-empty
182
+ 8. `specificationFiles` present in the PRD with all 5 paths
183
+ 9. Entity count in companion matches source: `prd-{moduleCode}.entities.json` entities[] count = `{moduleDir}/entities.json` entities[] count
184
+ 10. BR count in companion matches source: `prd-{moduleCode}.rules.json` rules[] count = `{moduleDir}/rules.json` rules[] count
185
+ 11. Each `brToCodeMapping[].statement` is non-empty (not just a title paraphrase)
186
+ Display: "POST-CHECK PASS: {moduleCode} -- 8 categories, {brCount} BRs mapped, {coreCount} core seeds, 5 companion files"
163
187
  ```
164
188
 
165
189
  ### 3. Display Progress
@@ -91,6 +91,26 @@ for (const cat of categories) {
91
91
  BLOCKING_ERROR(`${cat}: prd=${prdCount} but feature=${featureCount}`);
92
92
  }
93
93
  }
94
+
95
+ // Check 5: Specification files (companion files)
96
+ const specFiles = prd.specificationFiles;
97
+ if (!specFiles) {
98
+ BLOCKING_ERROR("specificationFiles missing from PRD");
99
+ }
100
+ const specKeys = ['entities', 'rules', 'usecases', 'screens', 'permissions'];
101
+ for (const key of specKeys) {
102
+ if (!specFiles[key]) {
103
+ BLOCKING_ERROR(`specificationFiles.${key} path missing`);
104
+ }
105
+ const companionPath = `.ralph/${specFiles[key]}`;
106
+ if (!fileExists(companionPath)) {
107
+ BLOCKING_ERROR(`Companion file not found: ${companionPath}`);
108
+ }
109
+ const companionSize = fileSize(companionPath);
110
+ if (companionSize < 10) {
111
+ BLOCKING_ERROR(`Companion file empty or too small: ${companionPath} (${companionSize} bytes)`);
112
+ }
113
+ }
94
114
  ```
95
115
 
96
116
  Display verification table showing all 8 categories match between module JSON files and prd.json.
@@ -187,10 +207,15 @@ Readiness Scores:
187
207
  └──────────────┴────────────┴──────────────┘
188
208
 
189
209
  Artifacts generated:
190
- .ralph/prd-{module}.json — Task breakdown per module
191
- .ralph/progress.txt Progression tracker
192
- .ralph/modules-queue.json Module execution order (if multi-module)
193
- docs/index.json BA manifest (status: handed-off)
210
+ .ralph/prd-{module}.json — Task breakdown per module
211
+ .ralph/prd-{module}.entities.json Entity specifications (companion)
212
+ .ralph/prd-{module}.rules.json Business rules (companion)
213
+ .ralph/prd-{module}.usecases.json Use cases (companion)
214
+ .ralph/prd-{module}.screens.json — Screen specifications (companion)
215
+ .ralph/prd-{module}.permissions.json — Permissions (companion)
216
+ .ralph/progress.txt — Progression tracker
217
+ .ralph/modules-queue.json — Module execution order (if multi-module)
218
+ docs/index.json — BA manifest (status: handed-off)
194
219
 
195
220
  [NEXT] Development:
196
221
  1. Run /business-analyse-develop to begin implementation
@@ -210,5 +235,8 @@ Artifacts generated:
210
235
  4. `docs/index.json` updated with correct entry count and status "handed-off"
211
236
  5. All PRD files have $version=3.0.0 or 4.0.0, file manifest present, 8 categories
212
237
  6. File counts match between PRD and handoff for all categories
238
+ 7. For EACH module: 5 companion files exist in `.ralph/` (entities, rules, usecases, screens, permissions)
239
+ 8. Each PRD contains `specificationFiles` with all 5 paths
240
+ 9. Each companion file is non-empty (>10 bytes)
213
241
 
214
242
  **IF any check fails -> fix before completing.**
@@ -378,13 +378,13 @@ body {
378
378
  .nav-icon-section { font-size: 0.5rem; color: var(--accent); }
379
379
  .nav-icon-resource { font-size: 0.7rem; color: var(--border-light); }
380
380
  .nav-resource-link {
381
- cursor: default;
381
+ cursor: pointer;
382
382
  font-size: 0.72rem;
383
383
  color: var(--text-muted);
384
384
  padding-top: 0.2rem;
385
385
  padding-bottom: 0.2rem;
386
386
  }
387
- .nav-resource-link:hover { background: transparent; color: var(--text-muted); }
387
+ .nav-resource-link:hover { background: var(--bg-hover); color: var(--accent); }
388
388
  .nav-resources { margin-left: 0.5rem; }
389
389
 
390
390
 
@@ -1031,6 +1031,44 @@ body {
1031
1031
  color: var(--text-muted);
1032
1032
  }
1033
1033
 
1034
+ /* ============================================
1035
+ STRUCTURE LEGEND
1036
+ ============================================ */
1037
+ .struct-legend {
1038
+ background: var(--bg-card);
1039
+ border: 1px solid var(--border);
1040
+ border-radius: 10px;
1041
+ padding: 0.75rem 1rem;
1042
+ margin-bottom: 1.25rem;
1043
+ }
1044
+ .struct-legend-title {
1045
+ font-size: 0.8rem;
1046
+ font-weight: 600;
1047
+ color: var(--text-muted);
1048
+ margin-bottom: 0.5rem;
1049
+ }
1050
+ .struct-legend-grid {
1051
+ display: grid;
1052
+ grid-template-columns: 1fr 1fr;
1053
+ gap: 0.4rem 1.5rem;
1054
+ }
1055
+ .struct-legend-item {
1056
+ font-size: 0.78rem;
1057
+ color: var(--text-muted);
1058
+ line-height: 1.4;
1059
+ }
1060
+ .struct-legend-code {
1061
+ display: inline-block;
1062
+ font-weight: 600;
1063
+ color: var(--accent);
1064
+ background: rgba(6,182,212,0.1);
1065
+ padding: 0.05rem 0.4rem;
1066
+ border-radius: 3px;
1067
+ font-size: 0.72rem;
1068
+ margin-right: 0.3rem;
1069
+ font-family: monospace;
1070
+ }
1071
+
1034
1072
  /* ============================================
1035
1073
  SECTION GROUPS (Hierarchical Mode)
1036
1074
  ============================================ */
@@ -2416,8 +2454,10 @@ data.moduleSpecs = data.moduleSpecs || {};
2416
2454
  if (!data.moduleSpecs[m.code]) {
2417
2455
  data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
2418
2456
  }
2419
- // Ensure anticipatedSections array exists
2420
- m.anticipatedSections = m.anticipatedSections || [];
2457
+ // Ensure anticipatedSections array exists and normalize strings to objects
2458
+ m.anticipatedSections = (m.anticipatedSections || []).map(function(s) {
2459
+ return typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s;
2460
+ });
2421
2461
  m.applicationCode = m.applicationCode || '';
2422
2462
  // Initialize screens array for interface specs
2423
2463
  if (!data.moduleSpecs[m.code].screens) {
@@ -2729,7 +2769,9 @@ function renderModuleNavItem(mod) {
2729
2769
  var ucCount = (spec.useCases || []).length;
2730
2770
  var brCount = (spec.businessRules || []).length;
2731
2771
  var entCount = (spec.entities || []).length;
2732
- var sections = mod.anticipatedSections || [];
2772
+ var sections = (mod.anticipatedSections || []).map(function(s) {
2773
+ return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
2774
+ });
2733
2775
  var groupId = 'mod-' + code;
2734
2776
  var collapsed = navCollapseState[groupId] === true;
2735
2777
 
@@ -2762,7 +2804,7 @@ function renderModuleNavItem(mod) {
2762
2804
  html += '<div class="nav-children nav-resources">';
2763
2805
  resources.forEach(function(res) {
2764
2806
  var resName = typeof res === 'string' ? res : (res.code || res.name || '');
2765
- html += '<a class="nav-item nav-resource-link">';
2807
+ html += '<a class="nav-item nav-resource-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'mock\');scrollToMockup(\'' + code + '\',\'' + section.code + '\')">';
2766
2808
  html += '<span class="nav-icon nav-icon-resource">&#8226;</span> ' + escapeHtml(resName);
2767
2809
  html += '</a>';
2768
2810
  });
@@ -2785,6 +2827,18 @@ function renderModuleTabNavItem(code, tabId, label, badge) {
2785
2827
  '</a>';
2786
2828
  }
2787
2829
 
2830
+ function scrollToMockup(moduleCode, sectionCode) {
2831
+ setTimeout(function() {
2832
+ var el = document.getElementById('screen-' + moduleCode + '-' + sectionCode);
2833
+ if (el) {
2834
+ el.scrollIntoView({ behavior: 'smooth', block: 'start' });
2835
+ el.style.outline = '2px solid var(--accent)';
2836
+ el.style.borderRadius = '10px';
2837
+ setTimeout(function() { el.style.outline = ''; el.style.borderRadius = ''; }, 2000);
2838
+ }
2839
+ }, 150);
2840
+ }
2841
+
2788
2842
  /* ---------- Collapse/Expand ---------- */
2789
2843
 
2790
2844
  function toggleNavGroup(groupId) {
@@ -3907,7 +3961,9 @@ function refreshAllPermGrids() {
3907
3961
 
3908
3962
  function renderModuleStructure(code) {
3909
3963
  const mod = data.modules.find(m => m.code === code);
3910
- const sections = mod ? (mod.anticipatedSections || []) : [];
3964
+ const sections = mod ? (mod.anticipatedSections || []).map(function(s) {
3965
+ return typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s;
3966
+ }) : [];
3911
3967
 
3912
3968
  if (sections.length === 0) {
3913
3969
  return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
@@ -3916,7 +3972,16 @@ function renderModuleStructure(code) {
3916
3972
  '</div>';
3917
3973
  }
3918
3974
 
3919
- return sections.map(function(section) {
3975
+ var legend = '<div class="struct-legend">' +
3976
+ '<div class="struct-legend-title">&#9432; Types de sections</div>' +
3977
+ '<div class="struct-legend-grid">' +
3978
+ '<div class="struct-legend-item"><span class="struct-legend-code">list</span> Page principale — grille de données avec filtres, tri et actions (créer, exporter)</div>' +
3979
+ '<div class="struct-legend-item"><span class="struct-legend-code">detail</span> Fiche détaillée — affichée au clic sur une ligne, avec onglets (infos, relations, historique)</div>' +
3980
+ '<div class="struct-legend-item"><span class="struct-legend-code">dashboard</span> Tableau de bord — KPIs, graphiques et métriques du module</div>' +
3981
+ '<div class="struct-legend-item"><span class="struct-legend-code">approve</span> Workflow — file de validation avec actions (approuver, rejeter, mettre en attente)</div>' +
3982
+ '</div></div>';
3983
+
3984
+ return legend + sections.map(function(section) {
3920
3985
  var resources = section.resources || [];
3921
3986
  var sectionUCs = section.useCases || [];
3922
3987
  var sectionBRs = section.businessRules || [];
@@ -3977,7 +4042,9 @@ function renderModuleStructure(code) {
3977
4042
  /* ---------- Section-Grouped Rendering (Hierarchical Mode) ---------- */
3978
4043
 
3979
4044
  function renderSectionGroupedItems(mod, itemsKey, code, renderFn) {
3980
- var sections = mod.anticipatedSections || [];
4045
+ var sections = (mod.anticipatedSections || []).map(function(s) {
4046
+ return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
4047
+ });
3981
4048
  var html = '';
3982
4049
  sections.forEach(function(section) {
3983
4050
  var items = section[itemsKey] || [];
@@ -4000,7 +4067,9 @@ function renderModuleMockups(code) {
4000
4067
  var screens = spec.screens || [];
4001
4068
  var wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
4002
4069
  var mod = data.modules.find(function(m) { return m.code === code; });
4003
- var sections = mod ? (mod.anticipatedSections || []) : [];
4070
+ var sections = mod ? (mod.anticipatedSections || []).map(function(s) {
4071
+ return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
4072
+ }) : [];
4004
4073
 
4005
4074
  // Priority 1: HTML mockups from screens[] specs
4006
4075
  if (screens.length > 0) {
@@ -4295,7 +4364,7 @@ function renderScreenMockups(code) {
4295
4364
 
4296
4365
  return screens.map(function(screen, si) {
4297
4366
  var resources = screen.resources || [];
4298
- return '<div class="screen-section" style="margin-bottom:2rem;">' +
4367
+ return '<div class="screen-section" id="screen-' + code + '-' + screen.sectionCode + '" style="margin-bottom:2rem;">' +
4299
4368
  '<h3 style="color:var(--text-bright);font-size:1rem;margin-bottom:1rem;">' +
4300
4369
  '<span style="color:var(--accent);">&#9656;</span> ' + escapeHtml(screen.sectionLabel || screen.sectionCode) +
4301
4370
  '</h3>' +
@@ -66,8 +66,10 @@ data.moduleSpecs = data.moduleSpecs || {};
66
66
  if (!data.moduleSpecs[m.code]) {
67
67
  data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
68
68
  }
69
- // Ensure anticipatedSections array exists
70
- m.anticipatedSections = m.anticipatedSections || [];
69
+ // Ensure anticipatedSections array exists and normalize strings to objects
70
+ m.anticipatedSections = (m.anticipatedSections || []).map(function(s) {
71
+ return typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s;
72
+ });
71
73
  m.applicationCode = m.applicationCode || '';
72
74
  // Initialize screens array for interface specs
73
75
  if (!data.moduleSpecs[m.code].screens) {
@@ -149,7 +149,9 @@ function renderModuleNavItem(mod) {
149
149
  var ucCount = (spec.useCases || []).length;
150
150
  var brCount = (spec.businessRules || []).length;
151
151
  var entCount = (spec.entities || []).length;
152
- var sections = mod.anticipatedSections || [];
152
+ var sections = (mod.anticipatedSections || []).map(function(s) {
153
+ return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
154
+ });
153
155
  var groupId = 'mod-' + code;
154
156
  var collapsed = navCollapseState[groupId] === true;
155
157
 
@@ -182,7 +184,7 @@ function renderModuleNavItem(mod) {
182
184
  html += '<div class="nav-children nav-resources">';
183
185
  resources.forEach(function(res) {
184
186
  var resName = typeof res === 'string' ? res : (res.code || res.name || '');
185
- html += '<a class="nav-item nav-resource-link">';
187
+ html += '<a class="nav-item nav-resource-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'mock\');scrollToMockup(\'' + code + '\',\'' + section.code + '\')">';
186
188
  html += '<span class="nav-icon nav-icon-resource">&#8226;</span> ' + escapeHtml(resName);
187
189
  html += '</a>';
188
190
  });
@@ -205,6 +207,18 @@ function renderModuleTabNavItem(code, tabId, label, badge) {
205
207
  '</a>';
206
208
  }
207
209
 
210
+ function scrollToMockup(moduleCode, sectionCode) {
211
+ setTimeout(function() {
212
+ var el = document.getElementById('screen-' + moduleCode + '-' + sectionCode);
213
+ if (el) {
214
+ el.scrollIntoView({ behavior: 'smooth', block: 'start' });
215
+ el.style.outline = '2px solid var(--accent)';
216
+ el.style.borderRadius = '10px';
217
+ setTimeout(function() { el.style.outline = ''; el.style.borderRadius = ''; }, 2000);
218
+ }
219
+ }, 150);
220
+ }
221
+
208
222
  /* ---------- Collapse/Expand ---------- */
209
223
 
210
224
  function toggleNavGroup(groupId) {
@@ -611,7 +611,9 @@ function refreshAllPermGrids() {
611
611
 
612
612
  function renderModuleStructure(code) {
613
613
  const mod = data.modules.find(m => m.code === code);
614
- const sections = mod ? (mod.anticipatedSections || []) : [];
614
+ const sections = mod ? (mod.anticipatedSections || []).map(function(s) {
615
+ return typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s;
616
+ }) : [];
615
617
 
616
618
  if (sections.length === 0) {
617
619
  return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
@@ -620,7 +622,16 @@ function renderModuleStructure(code) {
620
622
  '</div>';
621
623
  }
622
624
 
623
- return sections.map(function(section) {
625
+ var legend = '<div class="struct-legend">' +
626
+ '<div class="struct-legend-title">&#9432; Types de sections</div>' +
627
+ '<div class="struct-legend-grid">' +
628
+ '<div class="struct-legend-item"><span class="struct-legend-code">list</span> Page principale — grille de données avec filtres, tri et actions (créer, exporter)</div>' +
629
+ '<div class="struct-legend-item"><span class="struct-legend-code">detail</span> Fiche détaillée — affichée au clic sur une ligne, avec onglets (infos, relations, historique)</div>' +
630
+ '<div class="struct-legend-item"><span class="struct-legend-code">dashboard</span> Tableau de bord — KPIs, graphiques et métriques du module</div>' +
631
+ '<div class="struct-legend-item"><span class="struct-legend-code">approve</span> Workflow — file de validation avec actions (approuver, rejeter, mettre en attente)</div>' +
632
+ '</div></div>';
633
+
634
+ return legend + sections.map(function(section) {
624
635
  var resources = section.resources || [];
625
636
  var sectionUCs = section.useCases || [];
626
637
  var sectionBRs = section.businessRules || [];
@@ -681,7 +692,9 @@ function renderModuleStructure(code) {
681
692
  /* ---------- Section-Grouped Rendering (Hierarchical Mode) ---------- */
682
693
 
683
694
  function renderSectionGroupedItems(mod, itemsKey, code, renderFn) {
684
- var sections = mod.anticipatedSections || [];
695
+ var sections = (mod.anticipatedSections || []).map(function(s) {
696
+ return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
697
+ });
685
698
  var html = '';
686
699
  sections.forEach(function(section) {
687
700
  var items = section[itemsKey] || [];
@@ -704,7 +717,9 @@ function renderModuleMockups(code) {
704
717
  var screens = spec.screens || [];
705
718
  var wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
706
719
  var mod = data.modules.find(function(m) { return m.code === code; });
707
- var sections = mod ? (mod.anticipatedSections || []) : [];
720
+ var sections = mod ? (mod.anticipatedSections || []).map(function(s) {
721
+ return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
722
+ }) : [];
708
723
 
709
724
  // Priority 1: HTML mockups from screens[] specs
710
725
  if (screens.length > 0) {
@@ -11,7 +11,7 @@ function renderScreenMockups(code) {
11
11
 
12
12
  return screens.map(function(screen, si) {
13
13
  var resources = screen.resources || [];
14
- return '<div class="screen-section" style="margin-bottom:2rem;">' +
14
+ return '<div class="screen-section" id="screen-' + code + '-' + screen.sectionCode + '" style="margin-bottom:2rem;">' +
15
15
  '<h3 style="color:var(--text-bright);font-size:1rem;margin-bottom:1rem;">' +
16
16
  '<span style="color:var(--accent);">&#9656;</span> ' + escapeHtml(screen.sectionLabel || screen.sectionCode) +
17
17
  '</h3>' +
@@ -110,11 +110,11 @@
110
110
  .nav-icon-section { font-size: 0.5rem; color: var(--accent); }
111
111
  .nav-icon-resource { font-size: 0.7rem; color: var(--border-light); }
112
112
  .nav-resource-link {
113
- cursor: default;
113
+ cursor: pointer;
114
114
  font-size: 0.72rem;
115
115
  color: var(--text-muted);
116
116
  padding-top: 0.2rem;
117
117
  padding-bottom: 0.2rem;
118
118
  }
119
- .nav-resource-link:hover { background: transparent; color: var(--text-muted); }
119
+ .nav-resource-link:hover { background: var(--bg-hover); color: var(--accent); }
120
120
  .nav-resources { margin-left: 0.5rem; }
@@ -443,6 +443,44 @@
443
443
  color: var(--text-muted);
444
444
  }
445
445
 
446
+ /* ============================================
447
+ STRUCTURE LEGEND
448
+ ============================================ */
449
+ .struct-legend {
450
+ background: var(--bg-card);
451
+ border: 1px solid var(--border);
452
+ border-radius: 10px;
453
+ padding: 0.75rem 1rem;
454
+ margin-bottom: 1.25rem;
455
+ }
456
+ .struct-legend-title {
457
+ font-size: 0.8rem;
458
+ font-weight: 600;
459
+ color: var(--text-muted);
460
+ margin-bottom: 0.5rem;
461
+ }
462
+ .struct-legend-grid {
463
+ display: grid;
464
+ grid-template-columns: 1fr 1fr;
465
+ gap: 0.4rem 1.5rem;
466
+ }
467
+ .struct-legend-item {
468
+ font-size: 0.78rem;
469
+ color: var(--text-muted);
470
+ line-height: 1.4;
471
+ }
472
+ .struct-legend-code {
473
+ display: inline-block;
474
+ font-weight: 600;
475
+ color: var(--accent);
476
+ background: rgba(6,182,212,0.1);
477
+ padding: 0.05rem 0.4rem;
478
+ border-radius: 3px;
479
+ font-size: 0.72rem;
480
+ margin-right: 0.3rem;
481
+ font-family: monospace;
482
+ }
483
+
446
484
  /* ============================================
447
485
  SECTION GROUPS (Hierarchical Mode)
448
486
  ============================================ */
@@ -51,7 +51,10 @@ const FEATURE_DATA = {
51
51
  featureType: module.featureType || "data-centric",
52
52
  estimatedComplexity: module.estimatedComplexity || "medium",
53
53
  entities: module.entities || [],
54
- anticipatedSections: module.anticipatedSections || [],
54
+ // NORMALIZE: source may have strings (coverageMatrix) or objects (module schema)
55
+ anticipatedSections: (module.anticipatedSections || []).map(s =>
56
+ typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s
57
+ ), // ALWAYS [{code, name, description, resources[]}]
55
58
  dependencies: module.dependencies || [],
56
59
  dependents: module.dependents || []
57
60
  }
@@ -57,7 +57,10 @@ Build a JSON object following this **exact mapping** from index.json to the HTML
57
57
  description: m.description || "",
58
58
  featureType: m.featureType || "data-centric",
59
59
  entities: m.entities || [],
60
- anticipatedSections: m.anticipatedSections || [], // [{code, description, resources[]}]
60
+ // NORMALIZE: source may have strings (coverageMatrix) or objects (module schema)
61
+ anticipatedSections: (m.anticipatedSections || []).map(s =>
62
+ typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s
63
+ ), // ALWAYS [{code, name, description, resources[]}]
61
64
  dependencies: m.dependencies || [],
62
65
  dependents: m.dependents || [],
63
66
  estimatedComplexity: m.estimatedComplexity || "medium",