@atlashub/smartstack-cli 3.3.1 → 3.4.1

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.
@@ -160,8 +160,8 @@
160
160
  ============================================ */
161
161
  .main {
162
162
  flex: 1;
163
- padding: 2rem 2.5rem;
164
- max-width: 960px;
163
+ padding: 2rem 3rem;
164
+ max-width: 1400px;
165
165
  overflow-y: auto;
166
166
  height: calc(100vh - var(--header-height));
167
167
  }
@@ -374,6 +374,13 @@
374
374
  min-height: 200px;
375
375
  }
376
376
 
377
+ .wireframe-comment {
378
+ padding: 0.75rem 1.5rem 1rem;
379
+ border-top: 1px solid var(--border);
380
+ background: var(--bg-input);
381
+ border-radius: 0 0 10px 10px;
382
+ }
383
+
377
384
  /* Wireframe rendering */
378
385
  .ascii-wireframe {
379
386
  font-family: 'Courier New', 'Consolas', monospace;
@@ -1202,25 +1209,45 @@
1202
1209
  <span style="color: #f87171;">&#9632;</span> Fonctionnalites indispensables
1203
1210
  </h3>
1204
1211
  <div id="scopeVital" class="uc-list"></div>
1205
- <button class="add-btn" onclick="addScopeItem('vital')">+ Ajouter une fonctionnalite indispensable</button>
1212
+ <button class="add-btn" onclick="toggleForm('addScopeForm-vital')">+ Ajouter une fonctionnalite indispensable</button>
1213
+ <div class="inline-form" id="addScopeForm-vital">
1214
+ <div class="form-group"><label class="form-label">Nom de la fonctionnalite</label><input type="text" class="form-input" id="scope-name-vital" placeholder="Ex: Gestion des commandes"></div>
1215
+ <div class="form-group"><label class="form-label">Description (optionnel)</label><input type="text" class="form-input" id="scope-desc-vital" placeholder="Courte description"></div>
1216
+ <div class="form-actions"><button class="btn" onclick="toggleForm('addScopeForm-vital')">Annuler</button><button class="btn btn-primary" onclick="addScopeItem('vital')">Ajouter</button></div>
1217
+ </div>
1206
1218
 
1207
1219
  <h3 style="color: var(--text-bright); font-size: 1rem; margin: 1.5rem 0 0.75rem;">
1208
1220
  <span style="color: #facc15;">&#9632;</span> Fonctionnalites importantes
1209
1221
  </h3>
1210
1222
  <div id="scopeImportant" class="uc-list"></div>
1211
- <button class="add-btn" onclick="addScopeItem('important')">+ Ajouter une fonctionnalite importante</button>
1223
+ <button class="add-btn" onclick="toggleForm('addScopeForm-important')">+ Ajouter une fonctionnalite importante</button>
1224
+ <div class="inline-form" id="addScopeForm-important">
1225
+ <div class="form-group"><label class="form-label">Nom de la fonctionnalite</label><input type="text" class="form-input" id="scope-name-important" placeholder="Ex: Export PDF"></div>
1226
+ <div class="form-group"><label class="form-label">Description (optionnel)</label><input type="text" class="form-input" id="scope-desc-important" placeholder="Courte description"></div>
1227
+ <div class="form-actions"><button class="btn" onclick="toggleForm('addScopeForm-important')">Annuler</button><button class="btn btn-primary" onclick="addScopeItem('important')">Ajouter</button></div>
1228
+ </div>
1212
1229
 
1213
1230
  <h3 style="color: var(--text-bright); font-size: 1rem; margin: 1.5rem 0 0.75rem;">
1214
1231
  <span style="color: #4ade80;">&#9632;</span> Fonctionnalites optionnelles
1215
1232
  </h3>
1216
1233
  <div id="scopeOptional" class="uc-list"></div>
1217
- <button class="add-btn" onclick="addScopeItem('optional')">+ Ajouter une fonctionnalite optionnelle</button>
1234
+ <button class="add-btn" onclick="toggleForm('addScopeForm-optional')">+ Ajouter une fonctionnalite optionnelle</button>
1235
+ <div class="inline-form" id="addScopeForm-optional">
1236
+ <div class="form-group"><label class="form-label">Nom de la fonctionnalite</label><input type="text" class="form-input" id="scope-name-optional" placeholder="Ex: Calendrier partage"></div>
1237
+ <div class="form-group"><label class="form-label">Description (optionnel)</label><input type="text" class="form-input" id="scope-desc-optional" placeholder="Courte description"></div>
1238
+ <div class="form-actions"><button class="btn" onclick="toggleForm('addScopeForm-optional')">Annuler</button><button class="btn btn-primary" onclick="addScopeItem('optional')">Ajouter</button></div>
1239
+ </div>
1218
1240
 
1219
1241
  <h3 style="color: var(--text-bright); font-size: 1rem; margin: 1.5rem 0 0.75rem;">
1220
1242
  <span style="color: #94a3b8;">&#9632;</span> Hors perimetre
1221
1243
  </h3>
1222
1244
  <div id="scopeExcluded" class="uc-list"></div>
1223
- <button class="add-btn" onclick="addScopeItem('excluded')">+ Ajouter une exclusion</button>
1245
+ <button class="add-btn" onclick="toggleForm('addScopeForm-excluded')">+ Ajouter une exclusion</button>
1246
+ <div class="inline-form" id="addScopeForm-excluded">
1247
+ <div class="form-group"><label class="form-label">Element hors perimetre</label><input type="text" class="form-input" id="scope-name-excluded" placeholder="Ex: Gestion de la paie"></div>
1248
+ <div class="form-group"><label class="form-label">Raison (optionnel)</label><input type="text" class="form-input" id="scope-desc-excluded" placeholder="Pourquoi hors perimetre"></div>
1249
+ <div class="form-actions"><button class="btn" onclick="toggleForm('addScopeForm-excluded')">Annuler</button><button class="btn btn-primary" onclick="addScopeItem('excluded')">Ajouter</button></div>
1250
+ </div>
1224
1251
  </div>
1225
1252
 
1226
1253
  <!-- SECTION: Risques et hypotheses -->
@@ -1296,6 +1323,15 @@
1296
1323
  <div class="card-label">Conditions minimales de mise en service</div>
1297
1324
  <div class="editable" contenteditable="true" data-field="success.minimumConditions" data-placeholder="Quelles conditions minimales pour mettre le systeme en service ? (fonctionnalites presentes, donnees migrees, formation faite...)"></div>
1298
1325
  </div>
1326
+
1327
+ <h3 style="color: var(--text-bright); font-size: 1rem; margin: 2rem 0 0.75rem;">Criteres d'acceptation</h3>
1328
+ <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Criteres concrets et mesurables pour valider le succes du projet.</p>
1329
+ <div id="criteriaList" class="uc-list"></div>
1330
+ <button class="add-btn" onclick="toggleForm('addCriterionForm')">+ Ajouter un critere d'acceptation</button>
1331
+ <div class="inline-form" id="addCriterionForm">
1332
+ <div class="form-group"><label class="form-label">Critere (mesurable)</label><input type="text" class="form-input" id="criterion-text" placeholder="Ex: 80% des employes saisissent leurs heures chaque semaine"></div>
1333
+ <div class="form-actions"><button class="btn" onclick="toggleForm('addCriterionForm')">Annuler</button><button class="btn btn-primary" onclick="addCriterion()">Ajouter</button></div>
1334
+ </div>
1299
1335
  </div>
1300
1336
 
1301
1337
  <!-- ================================================================
@@ -1498,6 +1534,13 @@
1498
1534
  let data = {{FEATURE_DATA}};
1499
1535
  const EMBEDDED_ARTIFACTS = {{EMBEDDED_ARTIFACTS}};
1500
1536
 
1537
+ // Initialize optional data structures
1538
+ data.wireframeComments = data.wireframeComments || {};
1539
+ data.specComments = data.specComments || {};
1540
+ data.customRoles = data.customRoles || [];
1541
+ data.customActions = data.customActions || [];
1542
+ data.cadrage.criteria = data.cadrage.criteria || [];
1543
+
1501
1544
  /* ============================================
1502
1545
  INITIALIZATION
1503
1546
  ============================================ */
@@ -1507,6 +1550,7 @@
1507
1550
  renderStakeholders();
1508
1551
  renderScope();
1509
1552
  renderRisks();
1553
+ renderCriteria();
1510
1554
  renderModules();
1511
1555
  renderDependencies();
1512
1556
  renderAllModuleSpecs();
@@ -1610,12 +1654,15 @@
1610
1654
  SCOPE ITEMS
1611
1655
  ============================================ */
1612
1656
  function addScopeItem(priority) {
1613
- const name = prompt('Nom de la fonctionnalite :');
1657
+ const name = document.getElementById('scope-name-' + priority).value.trim();
1614
1658
  if (!name) return;
1615
- const description = prompt('Description courte (optionnel) :') || '';
1659
+ const description = document.getElementById('scope-desc-' + priority).value.trim();
1616
1660
 
1617
1661
  data.cadrage.scope[priority].push({ name, description });
1618
1662
  renderScope();
1663
+ toggleForm('addScopeForm-' + priority);
1664
+ document.getElementById('scope-name-' + priority).value = '';
1665
+ document.getElementById('scope-desc-' + priority).value = '';
1619
1666
  autoSave();
1620
1667
  }
1621
1668
 
@@ -1643,6 +1690,45 @@
1643
1690
  autoSave();
1644
1691
  }
1645
1692
 
1693
+ /* ============================================
1694
+ ACCEPTANCE CRITERIA
1695
+ ============================================ */
1696
+ function addCriterion() {
1697
+ const text = document.getElementById('criterion-text').value.trim();
1698
+ if (!text) return;
1699
+ if (!data.cadrage.criteria) data.cadrage.criteria = [];
1700
+ data.cadrage.criteria.push({ text, validated: false });
1701
+ renderCriteria();
1702
+ toggleForm('addCriterionForm');
1703
+ document.getElementById('criterion-text').value = '';
1704
+ autoSave();
1705
+ }
1706
+
1707
+ function removeCriterion(index) {
1708
+ data.cadrage.criteria.splice(index, 1);
1709
+ renderCriteria();
1710
+ autoSave();
1711
+ }
1712
+
1713
+ function toggleCriterion(index) {
1714
+ data.cadrage.criteria[index].validated = !data.cadrage.criteria[index].validated;
1715
+ renderCriteria();
1716
+ autoSave();
1717
+ }
1718
+
1719
+ function renderCriteria() {
1720
+ const container = document.getElementById('criteriaList');
1721
+ if (!container) return;
1722
+ const criteria = data.cadrage.criteria || [];
1723
+ container.innerHTML = criteria.map((c, i) => `
1724
+ <div class="uc-item" style="display:flex;align-items:center;gap:0.75rem;">
1725
+ <input type="checkbox" ${c.validated ? 'checked' : ''} onchange="toggleCriterion(${i})" style="cursor:pointer;width:18px;height:18px;flex-shrink:0;">
1726
+ <span style="flex:1;${c.validated ? 'text-decoration:line-through;color:var(--text-muted);' : ''}">${c.text}</span>
1727
+ <button class="btn btn-sm" onclick="removeCriterion(${i})" style="opacity:0.5;flex-shrink:0;">&#10005;</button>
1728
+ </div>
1729
+ `).join('');
1730
+ }
1731
+
1646
1732
  /* ============================================
1647
1733
  RISKS
1648
1734
  ============================================ */
@@ -1736,6 +1822,10 @@
1736
1822
  data.moduleSpecs = parsed.moduleSpecs || {};
1737
1823
  data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
1738
1824
  data.handoff = parsed.handoff || {};
1825
+ data.wireframeComments = parsed.wireframeComments || {};
1826
+ data.specComments = parsed.specComments || {};
1827
+ data.customRoles = parsed.customRoles || [];
1828
+ data.customActions = parsed.customActions || [];
1739
1829
 
1740
1830
  // Restore editable fields
1741
1831
  document.querySelectorAll('.editable[data-field]').forEach(el => {
@@ -1773,7 +1863,11 @@
1773
1863
  dependencies: data.dependencies,
1774
1864
  moduleSpecifications: {},
1775
1865
  consolidation: data.consolidation,
1776
- artifacts: EMBEDDED_ARTIFACTS // NEW: Include visual artifacts
1866
+ artifacts: EMBEDDED_ARTIFACTS,
1867
+ wireframeComments: data.wireframeComments,
1868
+ specComments: data.specComments,
1869
+ customRoles: data.customRoles,
1870
+ customActions: data.customActions
1777
1871
  };
1778
1872
 
1779
1873
  // Structure module specs for export
@@ -2017,7 +2111,7 @@
2017
2111
  <div class="dep-layer-modules">
2018
2112
  ${layer.map(code => {
2019
2113
  const m = data.modules.find(mod => mod.code === code);
2020
- return `<div class="dep-module">${m ? m.name : code}</div>`;
2114
+ return `<div class="dep-module">${m ? (m.name || m.code) : code}</div>`;
2021
2115
  }).join('')}
2022
2116
  </div>
2023
2117
  </div>
@@ -2065,7 +2159,7 @@
2065
2159
  return `
2066
2160
  <div class="process-step">
2067
2161
  <div class="process-step-number">Etape ${i + 1}</div>
2068
- <div class="process-step-label">${m ? m.name : code}</div>
2162
+ <div class="process-step-label">${m ? (m.name || m.code) : code}</div>
2069
2163
  </div>
2070
2164
  ${i < order.length - 1 ? '<div class="process-arrow">&#8594;</div>' : ''}
2071
2165
  `;
@@ -2219,6 +2313,32 @@
2219
2313
  <div id="permGrid-${code}">
2220
2314
  ${renderPermissionGrid(code)}
2221
2315
  </div>
2316
+ <div style="display:flex;gap:0.75rem;margin-top:1rem;flex-wrap:wrap;">
2317
+ <button class="add-btn" onclick="toggleForm('addRoleForm-${code}')" style="flex:1;min-width:200px;">+ Ajouter un role</button>
2318
+ <button class="add-btn" onclick="toggleForm('addActionForm-${code}')" style="flex:1;min-width:200px;">+ Ajouter une action</button>
2319
+ </div>
2320
+ <div class="inline-form" id="addRoleForm-${code}">
2321
+ <div class="inline-form-title">Nouveau role</div>
2322
+ <div class="form-group">
2323
+ <label class="form-label">Nom du role</label>
2324
+ <input type="text" class="form-input" id="role-name-${code}" placeholder="Exemple : Superviseur, Auditeur...">
2325
+ </div>
2326
+ <div class="form-actions">
2327
+ <button class="btn" onclick="toggleForm('addRoleForm-${code}')">Annuler</button>
2328
+ <button class="btn btn-primary" onclick="addCustomRole('${code}')">Ajouter</button>
2329
+ </div>
2330
+ </div>
2331
+ <div class="inline-form" id="addActionForm-${code}">
2332
+ <div class="inline-form-title">Nouvelle action</div>
2333
+ <div class="form-group">
2334
+ <label class="form-label">Nom de l'action</label>
2335
+ <input type="text" class="form-input" id="action-name-${code}" placeholder="Exemple : Archiver, Imprimer, Approuver...">
2336
+ </div>
2337
+ <div class="form-actions">
2338
+ <button class="btn" onclick="toggleForm('addActionForm-${code}')">Annuler</button>
2339
+ <button class="btn btn-primary" onclick="addCustomAction('${code}')">Ajouter</button>
2340
+ </div>
2341
+ </div>
2222
2342
  </div>
2223
2343
 
2224
2344
  <!-- TAB: Maquettes -->
@@ -2252,6 +2372,12 @@
2252
2372
  <div class="uc-actors"><div class="uc-actor">${uc.actor}</div></div>
2253
2373
  ${uc.steps ? `<div class="uc-detail-label">Deroulement</div><div class="uc-detail">${uc.steps.replace(/\n/g, '<br>')}</div>` : ''}
2254
2374
  ${uc.alternative ? `<div class="uc-detail-label">En cas de probleme</div><div class="uc-detail" style="color:var(--warning);">${uc.alternative}</div>` : ''}
2375
+ <div style="padding:0.5rem 0.75rem;border-top:1px solid var(--border);background:var(--bg-input);border-radius:0 0 8px 8px;">
2376
+ <label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire / Feedback :</label>
2377
+ <textarea class="form-textarea" placeholder="Ajouter un commentaire sur ce cas d'utilisation..."
2378
+ onblur="updateSpecComment('${code}','uc',${index},this.value)"
2379
+ style="min-height:45px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'uc', index)}</textarea>
2380
+ </div>
2255
2381
  </div>`;
2256
2382
  }
2257
2383
 
@@ -2283,14 +2409,21 @@
2283
2409
  const catColors = { validation: 'br-cat-validation', calculation: 'br-cat-calculation', workflow: 'br-cat-workflow', security: 'br-cat-security', data: 'br-cat-data' };
2284
2410
  const catLabels = { validation: 'Verification', calculation: 'Calcul', workflow: 'Processus', security: 'Securite', data: 'Donnees' };
2285
2411
  return `
2286
- <div class="br-item">
2287
- <span class="br-category ${catColors[br.category] || 'br-cat-validation'}">${catLabels[br.category] || br.category}</span>
2288
- <div class="br-text">
2289
- <div style="font-weight:600;color:var(--text-bright);margin-bottom:0.2rem;">${br.name}</div>
2290
- <div>${br.statement}</div>
2291
- ${br.example ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-top:0.25rem;font-style:italic;">Exemple : ${br.example}</div>` : ''}
2412
+ <div style="margin-bottom:0.5rem;">
2413
+ <div class="br-item" style="margin-bottom:0;border-radius:8px 8px 0 0;">
2414
+ <span class="br-category ${catColors[br.category] || 'br-cat-validation'}">${catLabels[br.category] || br.category}</span>
2415
+ <div class="br-text" style="flex:1;">
2416
+ <div style="font-weight:600;color:var(--text-bright);margin-bottom:0.2rem;">${br.name}</div>
2417
+ <div>${br.statement}</div>
2418
+ ${br.example ? `<div style="font-size:0.8rem;color:var(--text-muted);margin-top:0.25rem;font-style:italic;">Exemple : ${br.example}</div>` : ''}
2419
+ </div>
2420
+ <button class="btn btn-sm" onclick="removeBusinessRule('${code}',${index})" style="opacity:0.5;flex-shrink:0;">&#10005;</button>
2421
+ </div>
2422
+ <div style="padding:0.4rem 0.75rem 0.6rem;background:var(--bg-input);border:1px solid var(--border);border-top:0;border-radius:0 0 8px 8px;">
2423
+ <textarea class="form-textarea" placeholder="Commentaire sur cette regle..."
2424
+ onblur="updateSpecComment('${code}','br',${index},this.value)"
2425
+ style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'br', index)}</textarea>
2292
2426
  </div>
2293
- <button class="btn btn-sm" onclick="removeBusinessRule('${code}',${index})" style="opacity:0.5;flex-shrink:0;">&#10005;</button>
2294
2427
  </div>`;
2295
2428
  }
2296
2429
 
@@ -2337,6 +2470,12 @@
2337
2470
  <div style="padding:0.5rem 0.75rem;font-size:0.8rem;color:var(--text-muted);border-top:1px solid var(--border);">
2338
2471
  Relations : ${ent.relationships.map(r => `<span style="color:var(--accent);">${r}</span>`).join(', ')}
2339
2472
  </div>` : ''}
2473
+ <div style="padding:0.4rem 0.75rem 0.6rem;background:var(--bg-input);border-top:1px solid var(--border);border-radius:0 0 8px 8px;">
2474
+ <label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire :</label>
2475
+ <textarea class="form-textarea" placeholder="Commentaire sur cette entite..."
2476
+ onblur="updateSpecComment('${code}','ent',${index},this.value)"
2477
+ style="min-height:35px;font-size:0.8rem;resize:vertical;">${getSpecComment(code, 'ent', index)}</textarea>
2478
+ </div>
2340
2479
  </div>`;
2341
2480
  }
2342
2481
 
@@ -2368,6 +2507,27 @@
2368
2507
  autoSave();
2369
2508
  }
2370
2509
 
2510
+ function getSpecComment(code, type, index) {
2511
+ const key = code + '.' + type + '.' + index;
2512
+ return data.specComments[key] || '';
2513
+ }
2514
+
2515
+ function updateSpecComment(code, type, index, value) {
2516
+ const key = code + '.' + type + '.' + index;
2517
+ data.specComments[key] = value.trim();
2518
+ autoSave();
2519
+ }
2520
+
2521
+ function getWireframeComment(code, screen) {
2522
+ return (data.wireframeComments[code] || {})[screen] || '';
2523
+ }
2524
+
2525
+ function updateWireframeComment(code, screen, value) {
2526
+ if (!data.wireframeComments[code]) data.wireframeComments[code] = {};
2527
+ data.wireframeComments[code][screen] = value.trim();
2528
+ autoSave();
2529
+ }
2530
+
2371
2531
  function renderModuleMockups(code) {
2372
2532
  // Get embedded wireframes for this module
2373
2533
  const wireframes = EMBEDDED_ARTIFACTS?.wireframes?.[code] || [];
@@ -2413,15 +2573,39 @@
2413
2573
  </tbody>
2414
2574
  </table>
2415
2575
  </details>` : ''}
2576
+ <div class="wireframe-comment">
2577
+ <label style="font-size:0.8rem;color:var(--text-muted);display:block;margin-bottom:0.3rem;">Commentaire / Feedback :</label>
2578
+ <textarea class="form-textarea"
2579
+ data-module="${code}"
2580
+ data-screen="${wf.screen}"
2581
+ placeholder="Ajouter un commentaire sur cette maquette (ex: deplacer ce bouton, ajouter une colonne...)"
2582
+ onblur="updateWireframeComment('${code}', '${wf.screen}', this.value)"
2583
+ style="min-height:60px;font-size:0.85rem;resize:vertical;"
2584
+ >${getWireframeComment(code, wf.screen)}</textarea>
2585
+ </div>
2416
2586
  </div>
2417
2587
  `).join('');
2418
2588
  }
2419
2589
 
2420
- function renderPermissionGrid(code) {
2421
- const roles = data.cadrage.stakeholders.length > 0
2590
+ function getPermRoles() {
2591
+ const baseRoles = data.cadrage.stakeholders.length > 0
2422
2592
  ? data.cadrage.stakeholders.map(s => s.role)
2423
2593
  : ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
2424
- const actions = ['Consulter', 'Creer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'];
2594
+ return [...baseRoles, ...(data.customRoles || [])];
2595
+ }
2596
+
2597
+ function getPermActions() {
2598
+ const baseActions = ['Consulter', 'Creer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'];
2599
+ return [...baseActions, ...(data.customActions || [])];
2600
+ }
2601
+
2602
+ function renderPermissionGrid(code) {
2603
+ const roles = getPermRoles();
2604
+ const actions = getPermActions();
2605
+ const baseRolesCount = data.cadrage.stakeholders.length > 0
2606
+ ? data.cadrage.stakeholders.length
2607
+ : 4;
2608
+ const baseActionsCount = 6;
2425
2609
 
2426
2610
  const perms = data.moduleSpecs[code]?.permissions || [];
2427
2611
 
@@ -2429,12 +2613,12 @@
2429
2613
  <table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
2430
2614
  <thead><tr>
2431
2615
  <th>Profil</th>
2432
- ${actions.map(a => `<th style="text-align:center;">${a}</th>`).join('')}
2616
+ ${actions.map((a, i) => `<th style="text-align:center;">${a}${i >= baseActionsCount ? ' <span onclick="removeCustomAction('+`'${code}','${a}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer cette action">&#10005;</span>' : ''}</th>`).join('')}
2433
2617
  </tr></thead>
2434
2618
  <tbody>
2435
- ${roles.map(role => `
2619
+ ${roles.map((role, ri) => `
2436
2620
  <tr>
2437
- <td style="font-weight:500;color:var(--text-bright);">${role}</td>
2621
+ <td style="font-weight:500;color:var(--text-bright);">${role}${ri >= baseRolesCount ? ' <span onclick="removeCustomRole('+`'${code}','${role}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer ce role">&#10005;</span>' : ''}</td>
2438
2622
  ${actions.map(action => {
2439
2623
  const key = role + '|' + action;
2440
2624
  const checked = perms.includes(key);
@@ -2457,6 +2641,65 @@
2457
2641
  autoSave();
2458
2642
  }
2459
2643
 
2644
+ function addCustomRole(code) {
2645
+ const input = document.getElementById('role-name-' + code);
2646
+ const name = input.value.trim();
2647
+ if (!name) return;
2648
+ if (!data.customRoles) data.customRoles = [];
2649
+ if (!data.customRoles.includes(name) && !getPermRoles().includes(name)) {
2650
+ data.customRoles.push(name);
2651
+ }
2652
+ input.value = '';
2653
+ toggleForm('addRoleForm-' + code);
2654
+ refreshAllPermGrids();
2655
+ autoSave();
2656
+ }
2657
+
2658
+ function addCustomAction(code) {
2659
+ const input = document.getElementById('action-name-' + code);
2660
+ const name = input.value.trim();
2661
+ if (!name) return;
2662
+ if (!data.customActions) data.customActions = [];
2663
+ if (!data.customActions.includes(name) && !getPermActions().includes(name)) {
2664
+ data.customActions.push(name);
2665
+ }
2666
+ input.value = '';
2667
+ toggleForm('addActionForm-' + code);
2668
+ refreshAllPermGrids();
2669
+ autoSave();
2670
+ }
2671
+
2672
+ function removeCustomRole(code, role) {
2673
+ data.customRoles = (data.customRoles || []).filter(r => r !== role);
2674
+ // Remove associated permissions
2675
+ data.modules.forEach(m => {
2676
+ if (data.moduleSpecs[m.code]?.permissions) {
2677
+ data.moduleSpecs[m.code].permissions = data.moduleSpecs[m.code].permissions.filter(p => !p.startsWith(role + '|'));
2678
+ }
2679
+ });
2680
+ refreshAllPermGrids();
2681
+ autoSave();
2682
+ }
2683
+
2684
+ function removeCustomAction(code, action) {
2685
+ data.customActions = (data.customActions || []).filter(a => a !== action);
2686
+ // Remove associated permissions
2687
+ data.modules.forEach(m => {
2688
+ if (data.moduleSpecs[m.code]?.permissions) {
2689
+ data.moduleSpecs[m.code].permissions = data.moduleSpecs[m.code].permissions.filter(p => !p.endsWith('|' + action));
2690
+ }
2691
+ });
2692
+ refreshAllPermGrids();
2693
+ autoSave();
2694
+ }
2695
+
2696
+ function refreshAllPermGrids() {
2697
+ data.modules.forEach(m => {
2698
+ const container = document.getElementById('permGrid-' + m.code);
2699
+ if (container) container.innerHTML = renderPermissionGrid(m.code);
2700
+ });
2701
+ }
2702
+
2460
2703
  function switchTab(code, tabId) {
2461
2704
  const section = document.getElementById('module-spec-' + code);
2462
2705
  if (!section) return;
@@ -2500,13 +2743,11 @@
2500
2743
  const container = document.getElementById('consolPermissions');
2501
2744
  if (!container || data.modules.length === 0) return;
2502
2745
 
2503
- const roles = data.cadrage.stakeholders.length > 0
2504
- ? data.cadrage.stakeholders.map(s => s.role)
2505
- : ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
2746
+ const roles = getPermRoles();
2506
2747
 
2507
2748
  let html = '<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">';
2508
2749
  html += '<thead><tr><th>Profil</th>';
2509
- data.modules.forEach(m => { html += `<th style="text-align:center;">${m.name}</th>`; });
2750
+ data.modules.forEach(m => { html += `<th style="text-align:center;">${m.name || m.code}</th>`; });
2510
2751
  html += '</tr></thead><tbody>';
2511
2752
 
2512
2753
  roles.forEach(role => {
@@ -37,13 +37,12 @@
37
37
  "context": { "type": "string", "const": "business" },
38
38
  "language": { "type": "string", "enum": ["fr", "en", "it", "de"] },
39
39
  "featureDescription": { "type": "string" },
40
- "useCase": {
40
+ "workflowType": {
41
41
  "type": "string",
42
- "enum": ["application", "new", "refactoring"],
43
- "description": "Which use case triggered this analysis"
42
+ "enum": ["new", "update"],
43
+ "description": "Whether this is a new application or an update"
44
44
  },
45
45
  "mcpAvailable": { "type": "boolean" },
46
- "economyMode": { "type": "boolean" },
47
46
  "workflow": {
48
47
  "type": "object",
49
48
  "description": "Iterative module loop state",
@@ -20,12 +20,11 @@
20
20
  "type": "string",
21
21
  "enum": ["data-centric", "integration", "ui-centric", "workflow", "reporting", "full-module", "micro"]
22
22
  },
23
- "useCase": { "type": "string", "enum": ["new", "refactoring", "micro"] },
23
+ "workflowType": { "type": "string", "enum": ["new", "update"] },
24
24
  "permissionBase": { "type": "string", "description": "e.g., business.sales.orders" },
25
25
  "previousVersion": { "type": ["string", "null"] },
26
26
  "changeReason": { "type": ["string", "null"] },
27
27
  "mcpAvailable": { "type": "boolean" },
28
- "economyMode": { "type": "boolean" },
29
28
  "scope": { "type": "string", "enum": ["application", "module"], "default": "module" },
30
29
  "applicationRef": { "type": ["string", "null"], "description": "Parent application feature ID (FEAT-XXX)" },
31
30
  "applicationPath": { "type": ["string", "null"], "description": "Path to parent application-level feature.json" },