@atlashub/smartstack-cli 4.75.0 → 4.79.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.
- package/dist/index.js +87 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/project/claude-md/root.CLAUDE.md.template +1 -1
- package/templates/skills/ai-prompt/SKILL.md +64 -0
- package/templates/skills/ai-prompt/references/ai-agent-modes.md +89 -0
- package/templates/skills/ai-prompt/references/eval-framework.md +129 -0
- package/templates/skills/apex/SKILL.md +2 -2
- package/templates/skills/apex/references/checks/frontend-checks.sh +123 -11
- package/templates/skills/apex/references/checks/seed-checks.sh +81 -7
- package/templates/skills/apex/references/core-seed-data.md +27 -22
- package/templates/skills/apex/references/domain-events-pattern.md +45 -0
- package/templates/skills/apex/references/entity-hooks-pattern.md +68 -0
- package/templates/skills/apex/references/licensing-enforcement.md +52 -0
- package/templates/skills/apex/references/post-checks.md +18 -1
- package/templates/skills/apex/references/smartstack-api.md +116 -5
- package/templates/skills/apex/references/smartstack-frontend.md +1 -1
- package/templates/skills/apex/references/smartstack-layers.md +6 -6
- package/templates/skills/apex/steps/step-00-init.md +1 -1
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +26 -0
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +124 -2
- package/templates/skills/apex/steps/step-04-examine.md +163 -0
- package/templates/skills/apex-verify/SKILL.md +110 -0
- package/templates/skills/apex-verify/references/audit-rules.md +50 -0
- package/templates/skills/apex-verify/steps/step-00-init.md +119 -0
- package/templates/skills/apex-verify/steps/step-01-nav-audit.md +96 -0
- package/templates/skills/apex-verify/steps/step-02-crud-audit.md +127 -0
- package/templates/skills/apex-verify/steps/step-03-perm-audit.md +119 -0
- package/templates/skills/apex-verify/steps/step-04-route-audit.md +98 -0
- package/templates/skills/apex-verify/steps/step-05-report.md +110 -0
- package/templates/skills/application/references/contexts-cheatsheet.md +86 -0
- package/templates/skills/application/references/extensions-system.md +158 -0
- package/templates/skills/application/references/frontend-route-naming.md +7 -5
- package/templates/skills/application/references/frontend-verification.md +7 -5
- package/templates/skills/application/references/provider-template.md +4 -2
- package/templates/skills/application/references/smartstack-provider.md +118 -0
- package/templates/skills/application/references/themes-db-driven.md +484 -0
- package/templates/skills/application/templates-frontend.md +2 -2
- package/templates/skills/application/templates-seed.md +4 -2
- package/templates/skills/audit-route/references/routing-pattern.md +3 -1
- package/templates/skills/business-analyse/SKILL.md +3 -3
- package/templates/skills/business-analyse/_shared.md +37 -0
- package/templates/skills/business-analyse/react/components.md +30 -28
- package/templates/skills/business-analyse/references/03-json-schemas.md +11 -3
- package/templates/skills/business-analyse/references/03-post-check-validation.md +64 -0
- package/templates/skills/business-analyse/references/canonical-json-formats.md +7 -3
- package/templates/skills/business-analyse/references/robustness-checks.md +1 -1
- package/templates/skills/business-analyse/references/validation-checklist.md +5 -5
- package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +15 -4
- package/templates/skills/business-analyse/steps/step-03-specify.md +162 -4
- package/templates/skills/business-analyse/steps/step-04-consolidate.md +211 -1
- package/templates/skills/business-analyse/templates-react.md +15 -15
- package/templates/skills/business-analyse-handoff/references/agent-handoff-transform-prompt.md +3 -0
- package/templates/skills/business-analyse-html/html/ba-interactive.html +198 -16
- package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +64 -0
- package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +80 -11
- package/templates/skills/business-analyse-html/html/src/scripts/06-render-consolidation.js +2 -2
- package/templates/skills/business-analyse-html/html/src/scripts/06-render-mockups.js +6 -3
- package/templates/skills/business-analyse-html/html/src/scripts/12-render-diagrams.js +46 -0
- package/templates/skills/business-analyse-html/references/02-feature-data-building.md +4 -2
- package/templates/skills/business-analyse-html/references/data-build.md +2 -0
- package/templates/skills/business-analyse-html/references/data-mapping.md +88 -21
- package/templates/skills/business-analyse-html/steps/step-02-build-data.md +6 -0
- package/templates/skills/business-analyse-html/steps/step-04-verify.md +92 -3
- package/templates/skills/business-analyse-quick/SKILL.md +807 -0
- package/templates/skills/{sketch → business-analyse-quick}/references/domain-heuristics.md +59 -3
- package/templates/skills/business-analyse-quick/references/prd-schema.md +268 -0
- package/templates/skills/business-analyse-review/references/review-data-mapping.md +6 -0
- package/templates/skills/cli-app-sync/SKILL.md +105 -4
- package/templates/skills/cli-app-sync/references/comparison-map.md +13 -0
- package/templates/skills/cli-app-sync/references/diff-entities.md +162 -0
- package/templates/skills/dev-start/SKILL.md +7 -7
- package/templates/skills/documentation/templates.md +16 -16
- package/templates/skills/migrate/SKILL.md +312 -0
- package/templates/skills/migrate/references/v3.34-to-v3.46.md +289 -0
- package/templates/skills/sketch/SKILL.md +15 -153
- package/templates/skills/smoke-generation/SKILL.md +313 -0
- package/templates/skills/ui-components/SKILL.md +11 -1
- package/templates/skills/ui-components/patterns/data-table.md +1 -1
- package/templates/skills/ui-components/references/component-catalog.md +82 -0
- package/templates/skills/workflow/SKILL.md +70 -1
|
@@ -244,6 +244,120 @@ This includes:
|
|
|
244
244
|
- 5 contract verification checks (BR formulas, computed attributes, UC counts, permission counts, versioned attributes)
|
|
245
245
|
- Blocking rule: 0 ERROR → PASS, ≥1 ERROR → BLOCK (user must fix before proceeding)
|
|
246
246
|
|
|
247
|
+
**BR-EXAMPLES-COMPLETE check — qualité test-ready (NEW):**
|
|
248
|
+
```javascript
|
|
249
|
+
for (const module of completedModules) {
|
|
250
|
+
const rules = module.rules || [];
|
|
251
|
+
if (rules.length === 0) continue;
|
|
252
|
+
|
|
253
|
+
// 1. Couverture : toutes les règles doivent avoir des exemples
|
|
254
|
+
const withExamples = rules.filter(r => r.examples?.length > 0);
|
|
255
|
+
const exampleRate = withExamples.length / rules.length;
|
|
256
|
+
|
|
257
|
+
if (exampleRate < 0.4) {
|
|
258
|
+
ERROR(`Module ${module.code}: ${withExamples.length}/${rules.length} BRs ont des exemples (${Math.round(exampleRate*100)}% < 40% minimum) — BLOQUANT`);
|
|
259
|
+
} else if (exampleRate < 0.7) {
|
|
260
|
+
WARNING(`Module ${module.code}: ${withExamples.length}/${rules.length} BRs ont des exemples (${Math.round(exampleRate*100)}% — cible: 100%)`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 2. Format test-ready : exemples avec given/when/then
|
|
264
|
+
const testReady = rules.filter(r =>
|
|
265
|
+
r.examples?.some(e => e.given && e.when && e.then)
|
|
266
|
+
);
|
|
267
|
+
const proseOnly = rules.filter(r =>
|
|
268
|
+
r.examples?.length > 0 && !r.examples.some(e => e.given && e.when && e.then)
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (testReady.length < 4 && module.featureType !== 'portal') {
|
|
272
|
+
WARNING(`Module ${module.code}: ${testReady.length}/4 BRs au format test-ready {given, when, then}`);
|
|
273
|
+
}
|
|
274
|
+
if (proseOnly.length > 0) {
|
|
275
|
+
WARNING(`Module ${module.code}: ${proseOnly.length} BRs avec exemples en prose — convertir en format test-ready pour génération de tests`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 3. BR-CALC : formule + exemple chiffré obligatoires
|
|
279
|
+
const calcRules = rules.filter(r => r.category === 'calculation');
|
|
280
|
+
const calcWithoutFormula = calcRules.filter(r => !r.formula);
|
|
281
|
+
const calcWithoutNumericExample = calcRules.filter(r =>
|
|
282
|
+
!r.examples?.some(e =>
|
|
283
|
+
(e.scenario === 'calculation') ||
|
|
284
|
+
(e.then && Object.values(e.then).some(v => typeof v === 'number'))
|
|
285
|
+
)
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
if (calcWithoutFormula.length > 0) {
|
|
289
|
+
ERROR(`Module ${module.code}: ${calcWithoutFormula.length} BR-CALC sans champ formula`);
|
|
290
|
+
}
|
|
291
|
+
if (calcWithoutNumericExample.length > 0) {
|
|
292
|
+
WARNING(`Module ${module.code}: ${calcWithoutNumericExample.length} BR-CALC sans exemple chiffré — les tests de calcul nécessitent des valeurs numériques`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 4. Couverture scénarios : chaque règle devrait avoir happy_path + error
|
|
296
|
+
const withBothScenarios = rules.filter(r =>
|
|
297
|
+
r.examples?.some(e => e.scenario === 'happy_path' || e.then?.result === 'success') &&
|
|
298
|
+
r.examples?.some(e => e.scenario === 'error' || e.then?.result === 'error')
|
|
299
|
+
);
|
|
300
|
+
if (withBothScenarios.length < rules.length * 0.5 && module.featureType !== 'portal') {
|
|
301
|
+
WARNING(`Module ${module.code}: ${withBothScenarios.length}/${rules.length} BRs couvrent happy_path + error — cible: 100%`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### 4b. Language Coherence Check (MANDATORY)
|
|
307
|
+
|
|
308
|
+
Verify that ALL generated content respects `{language}` from config:
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
const expectedLang = language; // from config.json
|
|
312
|
+
|
|
313
|
+
// Heuristics for language detection
|
|
314
|
+
const frIndicators = /\b(le|la|les|un|une|des|du|de|est|sont|doit|peut|dans|pour|avec|sur|par|cette|tout|qui)\b/i;
|
|
315
|
+
const enIndicators = /\b(the|a|an|is|are|must|can|in|for|with|on|by|this|all|who|should|when|each)\b/i;
|
|
316
|
+
|
|
317
|
+
function detectLang(text) {
|
|
318
|
+
if (!text || text.length < 10) return null;
|
|
319
|
+
const frScore = (text.match(frIndicators) || []).length;
|
|
320
|
+
const enScore = (text.match(enIndicators) || []).length;
|
|
321
|
+
if (frScore > enScore * 1.5) return 'fr';
|
|
322
|
+
if (enScore > frScore * 1.5) return 'en';
|
|
323
|
+
return null; // ambiguous
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const langIssues = [];
|
|
327
|
+
for (const module of completedModules) {
|
|
328
|
+
// Sample entity descriptions
|
|
329
|
+
(module.entities || []).forEach(e => {
|
|
330
|
+
const detected = detectLang(e.description);
|
|
331
|
+
if (detected && detected !== expectedLang) {
|
|
332
|
+
langIssues.push({ module: module.code, type: 'entity', name: e.name, detected, text: e.description?.substring(0, 60) });
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
// Sample BR statements
|
|
336
|
+
(module.rules || []).forEach(r => {
|
|
337
|
+
const detected = detectLang(r.statement);
|
|
338
|
+
if (detected && detected !== expectedLang) {
|
|
339
|
+
langIssues.push({ module: module.code, type: 'rule', name: r.id, detected, text: r.statement?.substring(0, 60) });
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
// Sample UC names
|
|
343
|
+
(module.useCases || module.usecases || []).forEach(u => {
|
|
344
|
+
const detected = detectLang(u.name);
|
|
345
|
+
if (detected && detected !== expectedLang) {
|
|
346
|
+
langIssues.push({ module: module.code, type: 'useCase', name: u.id || u.name, detected, text: u.name });
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (langIssues.length > 0) {
|
|
352
|
+
WARNING(`${langIssues.length} content items detected in wrong language (expected: ${expectedLang})`);
|
|
353
|
+
Display(langIssues.slice(0, 10).map(i => ` - ${i.module}/${i.type} "${i.name}": detected ${i.detected}, text: "${i.text}"`).join('\n'));
|
|
354
|
+
|
|
355
|
+
if (langIssues.length > completedModules.length * 3) {
|
|
356
|
+
BLOCKING_ERROR(`Too many language violations (${langIssues.length}). Re-specify affected modules in ${expectedLang}.`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
247
361
|
### 5. Data Model Consolidation
|
|
248
362
|
|
|
249
363
|
Generate global entity relationship diagram:
|
|
@@ -389,7 +503,98 @@ for (const step of flow.steps) {
|
|
|
389
503
|
|
|
390
504
|
Store as `consolidation.mermaidDiagrams.sequences[{flowName}]` (string).
|
|
391
505
|
|
|
392
|
-
#### 5b-iv.
|
|
506
|
+
#### 5b-iv. Use Case Diagrams (per module)
|
|
507
|
+
|
|
508
|
+
For EACH module, generate a Mermaid use case diagram showing actors and their interactions:
|
|
509
|
+
|
|
510
|
+
```javascript
|
|
511
|
+
const useCaseDiagrams = {};
|
|
512
|
+
|
|
513
|
+
for (const module of completedModules) {
|
|
514
|
+
const usecases = module.useCases || module.usecases || [];
|
|
515
|
+
if (usecases.length === 0) continue;
|
|
516
|
+
|
|
517
|
+
// Collect unique actors
|
|
518
|
+
const actors = [...new Set(usecases.map(uc => uc.primaryActor || uc.actor).filter(Boolean))];
|
|
519
|
+
|
|
520
|
+
// Build flowchart-based UC diagram (Mermaid does not have native usecase syntax)
|
|
521
|
+
let diagram = "flowchart LR\n";
|
|
522
|
+
|
|
523
|
+
// Add actors (left side)
|
|
524
|
+
actors.forEach((actor, i) => {
|
|
525
|
+
const actorId = 'actor_' + actor.replace(/[^a-zA-Z0-9]/g, '_');
|
|
526
|
+
diagram += ` ${actorId}[/"👤 ${actor}"\\]\n`;
|
|
527
|
+
diagram += ` style ${actorId} fill:#e1f5fe,stroke:#0288d1,color:#01579b\n`;
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// Group UCs by section
|
|
531
|
+
const sections = [...new Set(usecases.map(uc => uc.sectionCode).filter(Boolean))];
|
|
532
|
+
|
|
533
|
+
sections.forEach(section => {
|
|
534
|
+
const sectionUCs = usecases.filter(uc => uc.sectionCode === section);
|
|
535
|
+
diagram += ` subgraph ${section}["${section}"]\n`;
|
|
536
|
+
sectionUCs.forEach(uc => {
|
|
537
|
+
const ucId = (uc.id || uc.name).replace(/[^a-zA-Z0-9]/g, '_');
|
|
538
|
+
diagram += ` ${ucId}(("${uc.name}"))\n`;
|
|
539
|
+
});
|
|
540
|
+
diagram += ` end\n`;
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// UCs without section
|
|
544
|
+
const noSectionUCs = usecases.filter(uc => !uc.sectionCode);
|
|
545
|
+
noSectionUCs.forEach(uc => {
|
|
546
|
+
const ucId = (uc.id || uc.name).replace(/[^a-zA-Z0-9]/g, '_');
|
|
547
|
+
diagram += ` ${ucId}(("${uc.name}"))\n`;
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Connect actors to UCs
|
|
551
|
+
usecases.forEach(uc => {
|
|
552
|
+
const actor = uc.primaryActor || uc.actor;
|
|
553
|
+
if (!actor) return;
|
|
554
|
+
const actorId = 'actor_' + actor.replace(/[^a-zA-Z0-9]/g, '_');
|
|
555
|
+
const ucId = (uc.id || uc.name).replace(/[^a-zA-Z0-9]/g, '_');
|
|
556
|
+
diagram += ` ${actorId} --> ${ucId}\n`;
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
useCaseDiagrams[module.code] = diagram;
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Store as `consolidation.mermaidDiagrams.useCases` (object: moduleCode → diagram string).
|
|
564
|
+
|
|
565
|
+
#### 5b-v. MCD (Modèle Conceptuel de Données)
|
|
566
|
+
|
|
567
|
+
Generate a simplified conceptual data model showing ONLY entity names and named relationships with cardinalities. Unlike the ERD which shows all attributes, the MCD focuses on the conceptual structure.
|
|
568
|
+
|
|
569
|
+
```javascript
|
|
570
|
+
let mcd = "erDiagram\n";
|
|
571
|
+
|
|
572
|
+
// Entities: name + PK only (no attributes)
|
|
573
|
+
for (const ent of globalEntities) {
|
|
574
|
+
mcd += ` ${ent.name} {\n`;
|
|
575
|
+
mcd += ` ${ent.pk || 'guid'} Id PK\n`;
|
|
576
|
+
mcd += ` }\n`;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Named relationships with cardinalities
|
|
580
|
+
for (const rel of globalRelationships) {
|
|
581
|
+
const card = rel.cardinality === "1:N" ? "||--o{" :
|
|
582
|
+
rel.cardinality === "N:1" ? "}o--||" :
|
|
583
|
+
rel.cardinality === "N:M" ? "}o--o{" :
|
|
584
|
+
rel.cardinality === "1:1" ? "||--||" : "||--o{";
|
|
585
|
+
|
|
586
|
+
// Use description as relationship label, or derive from cardinality
|
|
587
|
+
const label = rel.description ||
|
|
588
|
+
(rel.cardinality === "1:N" ? "contient" :
|
|
589
|
+
rel.cardinality === "N:1" ? "appartient" : "associe");
|
|
590
|
+
|
|
591
|
+
mcd += ` ${rel.sourceEntity} ${card} ${rel.targetEntity} : "${label}"\n`;
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Store as `consolidation.mermaidDiagrams.mcd` (string).
|
|
596
|
+
|
|
597
|
+
#### 5b-vi. Storage
|
|
393
598
|
|
|
394
599
|
Write to `consolidation.json`:
|
|
395
600
|
|
|
@@ -397,6 +602,11 @@ Write to `consolidation.json`:
|
|
|
397
602
|
{
|
|
398
603
|
"mermaidDiagrams": {
|
|
399
604
|
"erd": "erDiagram\n Employee {\n guid id\n string code\n ...\n }\n ...",
|
|
605
|
+
"mcd": "erDiagram\n Employee {\n guid Id PK\n }\n Employee ||--o{ Contract : \"contient\"\n ...",
|
|
606
|
+
"useCases": {
|
|
607
|
+
"Employees": "flowchart LR\n actor_RH[/\"👤 RH Admin\"\\]\n ...",
|
|
608
|
+
"Absences": "flowchart LR\n ..."
|
|
609
|
+
},
|
|
400
610
|
"stateMachines": {
|
|
401
611
|
"Absence": "stateDiagram-v2\n [*] --> Draft\n Draft --> Submitted : submit\n ...",
|
|
402
612
|
"Invoice": "stateDiagram-v2\n [*] --> Draft\n ..."
|
|
@@ -120,15 +120,15 @@ const docData: DocData = {
|
|
|
120
120
|
]
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
-
// Priority Badge Component
|
|
123
|
+
// Priority Badge Component (theme-aware via SmartStack status tokens)
|
|
124
124
|
function PriorityBadge({ priority }: { priority: string }) {
|
|
125
125
|
const colors = {
|
|
126
|
-
Must:
|
|
127
|
-
Should: 'bg-
|
|
128
|
-
Could:
|
|
126
|
+
Must: 'bg-[var(--error-bg)] text-[var(--error-text)] border border-[var(--error-border)]',
|
|
127
|
+
Should: 'bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]',
|
|
128
|
+
Could: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]'
|
|
129
129
|
};
|
|
130
130
|
return (
|
|
131
|
-
<span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[priority as keyof typeof colors] || 'bg-
|
|
131
|
+
<span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[priority as keyof typeof colors] || 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]'}`}>
|
|
132
132
|
{priority}
|
|
133
133
|
</span>
|
|
134
134
|
);
|
|
@@ -186,7 +186,7 @@ export function {ModuleName}DocPage() {
|
|
|
186
186
|
<span className="px-2 py-0.5 rounded bg-[var(--color-primary-600)]/10 text-[var(--color-primary-600)] text-xs font-medium">
|
|
187
187
|
{docData.featureId}
|
|
188
188
|
</span>
|
|
189
|
-
<span className="px-2 py-0.5 rounded bg-
|
|
189
|
+
<span className="px-2 py-0.5 rounded bg-[var(--success-bg)] text-[var(--success-text)] border border-[var(--success-border)] text-xs font-medium">
|
|
190
190
|
v{docData.version}
|
|
191
191
|
</span>
|
|
192
192
|
</div>
|
|
@@ -264,7 +264,7 @@ export function {ModuleName}DocPage() {
|
|
|
264
264
|
</div>
|
|
265
265
|
<p className="text-sm text-[var(--text-secondary)] mb-2">{uc.description}</p>
|
|
266
266
|
<div className="flex items-center gap-2 text-xs">
|
|
267
|
-
<Shield className="w-3 h-3 text-
|
|
267
|
+
<Shield className="w-3 h-3 text-[var(--warning-text)]" />
|
|
268
268
|
<code className="bg-[var(--bg-secondary)] px-2 py-0.5 rounded">{uc.permission}</code>
|
|
269
269
|
</div>
|
|
270
270
|
</div>
|
|
@@ -318,11 +318,11 @@ export function {ModuleName}DocPage() {
|
|
|
318
318
|
))}
|
|
319
319
|
</div>
|
|
320
320
|
|
|
321
|
-
{/* Permission Warning */}
|
|
322
|
-
<div className="mt-4 p-4 rounded-lg bg-
|
|
323
|
-
<AlertTriangle className="w-5 h-5 text-
|
|
321
|
+
{/* Permission Warning (theme-aware) */}
|
|
322
|
+
<div className="mt-4 p-4 rounded-lg bg-[var(--warning-bg)] border border-[var(--warning-border)] flex items-start gap-3">
|
|
323
|
+
<AlertTriangle className="w-5 h-5 text-[var(--warning-text)] flex-shrink-0 mt-0.5" />
|
|
324
324
|
<div>
|
|
325
|
-
<div className="font-medium text-
|
|
325
|
+
<div className="font-medium text-[var(--warning-text)]">{t('warnings.permissionCheck')}</div>
|
|
326
326
|
<p className="text-sm text-[var(--text-secondary)]">
|
|
327
327
|
{t('warnings.permissionCheckDescription')}
|
|
328
328
|
</p>
|
|
@@ -367,22 +367,22 @@ export function {ModuleName}DocPage() {
|
|
|
367
367
|
</thead>
|
|
368
368
|
<tbody>
|
|
369
369
|
<tr>
|
|
370
|
-
<td className="py-2 px-3"><span className="px-2 py-0.5 rounded bg-
|
|
370
|
+
<td className="py-2 px-3"><span className="px-2 py-0.5 rounded bg-[var(--success-bg)] text-[var(--success-text)] border border-[var(--success-border)]">GET</span></td>
|
|
371
371
|
<td className="py-2 px-3">/api/{'{module}'}</td>
|
|
372
372
|
<td className="py-2 px-3 text-[var(--text-secondary)]">.read</td>
|
|
373
373
|
</tr>
|
|
374
374
|
<tr className="bg-[var(--bg-secondary)]/50">
|
|
375
|
-
<td className="py-2 px-3"><span className="px-2 py-0.5 rounded bg-
|
|
375
|
+
<td className="py-2 px-3"><span className="px-2 py-0.5 rounded bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]">POST</span></td>
|
|
376
376
|
<td className="py-2 px-3">/api/{'{module}'}</td>
|
|
377
377
|
<td className="py-2 px-3 text-[var(--text-secondary)]">.create</td>
|
|
378
378
|
</tr>
|
|
379
379
|
<tr>
|
|
380
|
-
<td className="py-2 px-3"><span className="px-2 py-0.5 rounded bg-
|
|
380
|
+
<td className="py-2 px-3"><span className="px-2 py-0.5 rounded bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]">PUT</span></td>
|
|
381
381
|
<td className="py-2 px-3">/api/{'{module}'}/{'{id}'}</td>
|
|
382
382
|
<td className="py-2 px-3 text-[var(--text-secondary)]">.update</td>
|
|
383
383
|
</tr>
|
|
384
384
|
<tr className="bg-[var(--bg-secondary)]/50">
|
|
385
|
-
<td className="py-2 px-3"><span className="px-2 py-0.5 rounded bg-
|
|
385
|
+
<td className="py-2 px-3"><span className="px-2 py-0.5 rounded bg-[var(--error-bg)] text-[var(--error-text)] border border-[var(--error-border)]">DELETE</span></td>
|
|
386
386
|
<td className="py-2 px-3">/api/{'{module}'}/{'{id}'}</td>
|
|
387
387
|
<td className="py-2 px-3 text-[var(--text-secondary)]">.delete</td>
|
|
388
388
|
</tr>
|
package/templates/skills/business-analyse-handoff/references/agent-handoff-transform-prompt.md
CHANGED
|
@@ -47,6 +47,9 @@ When reading flat files, prefer canonical keys but fall back to alternatives:
|
|
|
47
47
|
- `primaryActor`: `uc.primaryActor || uc.actor`
|
|
48
48
|
- `mainScenario`: `uc.mainScenario || uc.steps` (if `steps[]` contains objects, extract `.action`)
|
|
49
49
|
- `rules`: `data.rules || data.businessRules || []`
|
|
50
|
+
- `rules[].examples`: prefer `rule.examples[]` (`{input, expected}`); fallback: wrap `rule.example` string as `[{input: rule.example, expected: ""}]`
|
|
51
|
+
- `rules[].statement`: prefer `rule.statement`; fallback: `rule.description`
|
|
52
|
+
- `rules[].id`: prefer `rule.id`; fallback: `rule.code`
|
|
50
53
|
- `screens`: `data.sections || data.screens` (if `screens[]` exists, treat each screen as a section with 1 resource)
|
|
51
54
|
|
|
52
55
|
This safety net handles pre-4.52 BA outputs. It should become unnecessary once step-03-specify enforces canonical keys.
|
|
@@ -2646,6 +2646,70 @@ data.moduleSpecs = data.moduleSpecs || {};
|
|
|
2646
2646
|
});
|
|
2647
2647
|
});
|
|
2648
2648
|
|
|
2649
|
+
// Normalize permissions: convert any object format to "Role|Action" pipe-delimited strings
|
|
2650
|
+
// Handles 5 formats:
|
|
2651
|
+
// A: "Role|Action" strings (already correct)
|
|
2652
|
+
// B: {role, permissions: ["Read:all", ...]} with role-permission pairs
|
|
2653
|
+
// C: {role, permissions: ["Module.Entity.Read", ...]} with path-based permissions
|
|
2654
|
+
// D: {code, label, description} permission definitions without role assignment
|
|
2655
|
+
// E: {role: "", permissions: []} empty entries (skip)
|
|
2656
|
+
(data.modules || []).forEach(function(m) {
|
|
2657
|
+
var spec = data.moduleSpecs[m.code];
|
|
2658
|
+
if (!spec || !spec.permissions || spec.permissions.length === 0) return;
|
|
2659
|
+
// Skip if already normalized (first element is a pipe-delimited string)
|
|
2660
|
+
if (typeof spec.permissions[0] === 'string' && spec.permissions[0].indexOf('|') !== -1) return;
|
|
2661
|
+
if (typeof spec.permissions[0] !== 'object') return;
|
|
2662
|
+
|
|
2663
|
+
var actionMap = {
|
|
2664
|
+
'read': 'Consulter', 'create': 'Créer', 'update': 'Modifier',
|
|
2665
|
+
'delete': 'Supprimer', 'approve': 'Valider', 'validate': 'Valider',
|
|
2666
|
+
'export': 'Exporter', 'admin': 'Administrer', 'import': 'Importer',
|
|
2667
|
+
'viewsalary': 'Consulter', 'assignteam': 'Modifier', 'cancel': 'Supprimer',
|
|
2668
|
+
'viewbalance': 'Consulter', 'invoicetime': 'Valider'
|
|
2669
|
+
};
|
|
2670
|
+
var normalized = [];
|
|
2671
|
+
var first = spec.permissions[0];
|
|
2672
|
+
|
|
2673
|
+
// Detect Format D: {code, label, description} — permission codes without roles
|
|
2674
|
+
if (first.code && !first.role && !first.permissions) {
|
|
2675
|
+
// Extract actions from permission codes, assign to stakeholder roles
|
|
2676
|
+
var roles = (data.cadrage.stakeholders || []).map(function(s) { return s.role; }).filter(Boolean);
|
|
2677
|
+
if (roles.length === 0) roles = ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
|
|
2678
|
+
spec.permissions.forEach(function(permDef) {
|
|
2679
|
+
var code = permDef.code || '';
|
|
2680
|
+
var raw = code.split('.').pop(); // "Module.Entity.Read" → "Read"
|
|
2681
|
+
var action = actionMap[raw.toLowerCase()] || raw;
|
|
2682
|
+
// Assign to first role (admin) by default — better than empty
|
|
2683
|
+
if (roles[0]) {
|
|
2684
|
+
var key = roles[0] + '|' + action;
|
|
2685
|
+
if (normalized.indexOf(key) === -1) normalized.push(key);
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
} else {
|
|
2689
|
+
// Format B/C: {role, permissions: [...]}
|
|
2690
|
+
spec.permissions.forEach(function(entry) {
|
|
2691
|
+
var role = entry.role || entry.name || '';
|
|
2692
|
+
if (!role) return; // Skip Format E (empty role)
|
|
2693
|
+
var perms = entry.permissions || entry.actions || [];
|
|
2694
|
+
if (perms.length === 0 && entry.permissionPattern && entry.permissionPattern.endsWith('*')) {
|
|
2695
|
+
['Consulter', 'Créer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'].forEach(function(a) {
|
|
2696
|
+
normalized.push(role + '|' + a);
|
|
2697
|
+
});
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
perms.forEach(function(perm) {
|
|
2701
|
+
if (typeof perm !== 'string') return;
|
|
2702
|
+
var raw = perm.split(':')[0]; // "Read:all" → "Read"
|
|
2703
|
+
if (raw.indexOf('.') !== -1) raw = raw.split('.').pop(); // "Module.Entity.Read" → "Read"
|
|
2704
|
+
var action = actionMap[raw.toLowerCase()] || raw;
|
|
2705
|
+
var key = role + '|' + action;
|
|
2706
|
+
if (normalized.indexOf(key) === -1) normalized.push(key);
|
|
2707
|
+
});
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
spec.permissions = normalized;
|
|
2711
|
+
});
|
|
2712
|
+
|
|
2649
2713
|
// Detect if modules use section-level specs (hierarchical mode)
|
|
2650
2714
|
function hasHierarchicalSpecs(mod) {
|
|
2651
2715
|
return (mod.anticipatedSections || []).some(function(s) {
|
|
@@ -3693,6 +3757,11 @@ function renderModuleSpecSection(mod) {
|
|
|
3693
3757
|
}
|
|
3694
3758
|
|
|
3695
3759
|
function renderUseCase(code, uc, index) {
|
|
3760
|
+
var preconditions = uc.preconditions || [];
|
|
3761
|
+
var postconditions = uc.postconditions || [];
|
|
3762
|
+
var errorScenarios = uc.errorScenarios || [];
|
|
3763
|
+
var description = uc.description || '';
|
|
3764
|
+
|
|
3696
3765
|
return `
|
|
3697
3766
|
<div class="uc-item">
|
|
3698
3767
|
<div class="uc-header">
|
|
@@ -3703,8 +3772,12 @@ function renderUseCase(code, uc, index) {
|
|
|
3703
3772
|
</div>
|
|
3704
3773
|
</div>
|
|
3705
3774
|
<div class="uc-actors"><div class="uc-actor">${escapeHtml(uc.actor)}</div></div>
|
|
3706
|
-
${
|
|
3707
|
-
${
|
|
3775
|
+
${description ? `<div class="uc-detail-label">Description</div><div class="uc-detail" style="font-style:italic;color:var(--text-muted);">${escapeHtml(description)}</div>` : ''}
|
|
3776
|
+
${preconditions.length > 0 ? `<div class="uc-detail-label">Préconditions</div><div class="uc-detail"><ul style="margin:0;padding-left:1.2rem;">${preconditions.map(p => '<li>' + escapeHtml(typeof p === 'string' ? p : p.description || '') + '</li>').join('')}</ul></div>` : ''}
|
|
3777
|
+
${uc.steps ? `<div class="uc-detail-label">Déroulement principal</div><div class="uc-detail">${escapeHtml(uc.steps).replace(/\n/g, '<br>')}</div>` : ''}
|
|
3778
|
+
${uc.alternative ? `<div class="uc-detail-label">Scénarios alternatifs</div><div class="uc-detail" style="color:var(--warning);">${escapeHtml(uc.alternative)}</div>` : ''}
|
|
3779
|
+
${errorScenarios.length > 0 ? `<div class="uc-detail-label">Scénarios d'erreur</div><div class="uc-detail" style="color:var(--danger);"><ul style="margin:0;padding-left:1.2rem;">${errorScenarios.map(e => '<li><strong>' + escapeHtml(typeof e === 'string' ? e : e.name || '') + '</strong>' + (typeof e === 'object' && e.steps ? ' : ' + escapeHtml(Array.isArray(e.steps) ? e.steps.join(', ') : e.steps) : '') + '</li>').join('')}</ul></div>` : ''}
|
|
3780
|
+
${postconditions.length > 0 ? `<div class="uc-detail-label">Postconditions</div><div class="uc-detail"><ul style="margin:0;padding-left:1.2rem;">${postconditions.map(p => '<li>' + escapeHtml(typeof p === 'string' ? p : p.description || '') + '</li>').join('')}</ul></div>` : ''}
|
|
3708
3781
|
<div style="padding:0.5rem 0.75rem;border-top:1px solid var(--border);background:var(--bg-input);border-radius:0 0 8px 8px;">
|
|
3709
3782
|
<label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire / Feedback :</label>
|
|
3710
3783
|
<textarea class="form-textarea" placeholder="Ajouter un commentaire sur ce cas d'utilisation..."
|
|
@@ -3797,15 +3870,16 @@ function renderEntity(code, ent, index) {
|
|
|
3797
3870
|
</div>
|
|
3798
3871
|
${(ent.attributes || []).length > 0 ? `
|
|
3799
3872
|
<table class="attr-table">
|
|
3800
|
-
<thead><tr><th>Information</th><th>Description</th></tr></thead>
|
|
3873
|
+
<thead><tr><th>Information</th><th style="width:100px;">Type</th><th>Description</th></tr></thead>
|
|
3801
3874
|
<tbody>
|
|
3802
|
-
${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(a.name)}</td><td>${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
3875
|
+
${ent.attributes.map(a => `<tr><td style="font-weight:500;color:var(--text-bright);">${escapeHtml(a.name)}</td><td style="font-family:monospace;font-size:0.8rem;color:var(--accent);">${escapeHtml(a.type || 'string')}</td><td>${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
3803
3876
|
</tbody>
|
|
3804
3877
|
</table>` : ''}
|
|
3805
3878
|
<div style="padding:0.3rem 0.75rem;">
|
|
3806
3879
|
<button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="toggleForm('${attrFormId}')">+ Ajouter un attribut</button>
|
|
3807
3880
|
<div class="inline-form" id="${attrFormId}">
|
|
3808
3881
|
<div class="form-group"><label class="form-label">Nom de l'attribut</label><input type="text" class="form-input" id="attr-name-${code}-${index}" placeholder="Nom de l'attribut"></div>
|
|
3882
|
+
<div class="form-group"><label class="form-label">Type</label><input type="text" class="form-input" id="attr-type-${code}-${index}" placeholder="string, guid, int, decimal, date, enum, boolean" value="string"></div>
|
|
3809
3883
|
<div class="form-group"><label class="form-label">Description (optionnel)</label><input type="text" class="form-input" id="attr-desc-${code}-${index}" placeholder="Description"></div>
|
|
3810
3884
|
<div class="form-actions"><button class="btn" onclick="toggleForm('${attrFormId}')">Annuler</button><button class="btn btn-primary" onclick="addEntityAttribute('${code}',${index})">Ajouter</button></div>
|
|
3811
3885
|
</div>
|
|
@@ -3830,7 +3904,7 @@ function addEntity(code) {
|
|
|
3830
3904
|
|
|
3831
3905
|
const attrs = document.getElementById('ent-attrs-' + code).value.split('\n').filter(l => l.trim()).map(l => {
|
|
3832
3906
|
const parts = l.split(' - ');
|
|
3833
|
-
return { name: parts[0]?.trim() || l.trim(), description: parts.slice(1).join(' - ').trim() };
|
|
3907
|
+
return { name: parts[0]?.trim() || l.trim(), type: parts[1]?.trim() || 'string', description: parts.slice(2).join(' - ').trim() || parts.slice(1).join(' - ').trim() };
|
|
3834
3908
|
});
|
|
3835
3909
|
const rels = document.getElementById('ent-rels-' + code).value.split('\n').filter(l => l.trim());
|
|
3836
3910
|
|
|
@@ -3854,13 +3928,14 @@ function removeEntity(code, index) {
|
|
|
3854
3928
|
|
|
3855
3929
|
function addEntityAttribute(code, entityIndex) {
|
|
3856
3930
|
var attrName = document.getElementById('attr-name-' + code + '-' + entityIndex);
|
|
3931
|
+
var attrType = document.getElementById('attr-type-' + code + '-' + entityIndex);
|
|
3857
3932
|
var attrDesc = document.getElementById('attr-desc-' + code + '-' + entityIndex);
|
|
3858
3933
|
if (!attrName || !attrName.value.trim()) return;
|
|
3859
3934
|
if (!data.moduleSpecs[code]?.entities?.[entityIndex]) return;
|
|
3860
3935
|
if (!data.moduleSpecs[code].entities[entityIndex].attributes) {
|
|
3861
3936
|
data.moduleSpecs[code].entities[entityIndex].attributes = [];
|
|
3862
3937
|
}
|
|
3863
|
-
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), description: (attrDesc?.value || '').trim() });
|
|
3938
|
+
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), type: (attrType?.value || 'string').trim(), description: (attrDesc?.value || '').trim() });
|
|
3864
3939
|
renderAllModuleSpecs();
|
|
3865
3940
|
autoSave();
|
|
3866
3941
|
}
|
|
@@ -3971,13 +4046,68 @@ function toggleWireframeView(wireframeId, view) {
|
|
|
3971
4046
|
}
|
|
3972
4047
|
}
|
|
3973
4048
|
|
|
4049
|
+
// Normalize permissions array: handles 3 formats:
|
|
4050
|
+
// Format 1 (string): "Role|Action" → already correct
|
|
4051
|
+
// Format 2 (object): {role, permissions: ["Read:all", "Create:all", ...]}
|
|
4052
|
+
// Format 3 (object): {role, permissions: ["Module.Entity.Read", ...]}
|
|
4053
|
+
function normalizePermissions(permsArray) {
|
|
4054
|
+
if (!permsArray || permsArray.length === 0) return [];
|
|
4055
|
+
// If first element is a string with '|', already normalized
|
|
4056
|
+
if (typeof permsArray[0] === 'string' && permsArray[0].includes('|')) return permsArray;
|
|
4057
|
+
// If first element is a string without '|', return as-is (unknown format)
|
|
4058
|
+
if (typeof permsArray[0] === 'string') return permsArray;
|
|
4059
|
+
// Format 2/3: objects with {role, permissions[]}
|
|
4060
|
+
const actionMap = {
|
|
4061
|
+
'read': 'Consulter', 'create': 'Créer', 'update': 'Modifier',
|
|
4062
|
+
'delete': 'Supprimer', 'approve': 'Valider', 'validate': 'Valider',
|
|
4063
|
+
'export': 'Exporter', 'admin': 'Administrer', 'import': 'Importer',
|
|
4064
|
+
'viewsalary': 'Consulter', 'assignteam': 'Modifier', 'cancel': 'Supprimer'
|
|
4065
|
+
};
|
|
4066
|
+
const normalized = [];
|
|
4067
|
+
permsArray.forEach(function(entry) {
|
|
4068
|
+
if (typeof entry !== 'object' || !entry) return;
|
|
4069
|
+
var role = entry.role || entry.name || '';
|
|
4070
|
+
if (!role) return;
|
|
4071
|
+
var perms = entry.permissions || entry.actions || [];
|
|
4072
|
+
if (perms.length === 0 && entry.permissionPattern && entry.permissionPattern.endsWith('*')) {
|
|
4073
|
+
// Wildcard: expand to all standard actions
|
|
4074
|
+
['Consulter', 'Créer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'].forEach(function(a) {
|
|
4075
|
+
normalized.push(role + '|' + a);
|
|
4076
|
+
});
|
|
4077
|
+
return;
|
|
4078
|
+
}
|
|
4079
|
+
perms.forEach(function(perm) {
|
|
4080
|
+
var action = '';
|
|
4081
|
+
if (typeof perm === 'string') {
|
|
4082
|
+
// "Read:all" → extract "Read"
|
|
4083
|
+
var raw = perm.split(':')[0];
|
|
4084
|
+
// "Module.Entity.Read" → extract "Read"
|
|
4085
|
+
if (raw.includes('.')) raw = raw.split('.').pop();
|
|
4086
|
+
action = actionMap[raw.toLowerCase()] || raw;
|
|
4087
|
+
}
|
|
4088
|
+
if (action) {
|
|
4089
|
+
var key = role + '|' + action;
|
|
4090
|
+
if (normalized.indexOf(key) === -1) normalized.push(key);
|
|
4091
|
+
}
|
|
4092
|
+
});
|
|
4093
|
+
});
|
|
4094
|
+
return normalized;
|
|
4095
|
+
}
|
|
4096
|
+
|
|
3974
4097
|
function getPermRoles() {
|
|
3975
|
-
// Extract roles from actual permission data (handles
|
|
4098
|
+
// Extract roles from actual permission data (handles multiple formats)
|
|
3976
4099
|
const rolesFromPerms = [];
|
|
3977
4100
|
const seen = new Set();
|
|
3978
4101
|
data.modules.forEach(m => {
|
|
3979
|
-
|
|
3980
|
-
|
|
4102
|
+
var perms = data.moduleSpecs[m.code]?.permissions || [];
|
|
4103
|
+
// Handle object format directly for role extraction
|
|
4104
|
+
perms.forEach(p => {
|
|
4105
|
+
var role = '';
|
|
4106
|
+
if (typeof p === 'string') {
|
|
4107
|
+
role = p.split('|')[0];
|
|
4108
|
+
} else if (typeof p === 'object' && p) {
|
|
4109
|
+
role = p.role || p.name || '';
|
|
4110
|
+
}
|
|
3981
4111
|
if (role && !seen.has(role)) { seen.add(role); rolesFromPerms.push(role); }
|
|
3982
4112
|
});
|
|
3983
4113
|
});
|
|
@@ -4000,7 +4130,9 @@ function renderPermissionGrid(code) {
|
|
|
4000
4130
|
const baseRolesCount = roles.length - (data.customRoles || []).length;
|
|
4001
4131
|
const baseActionsCount = 6;
|
|
4002
4132
|
|
|
4003
|
-
|
|
4133
|
+
// Normalize permissions to "Role|Action" format (handles object and string formats)
|
|
4134
|
+
const rawPerms = data.moduleSpecs[code]?.permissions || [];
|
|
4135
|
+
const perms = normalizePermissions(rawPerms);
|
|
4004
4136
|
|
|
4005
4137
|
return `
|
|
4006
4138
|
<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
|
|
@@ -4205,7 +4337,8 @@ function renderModuleMockups(code) {
|
|
|
4205
4337
|
}) : [];
|
|
4206
4338
|
|
|
4207
4339
|
// Priority 1: HTML mockups from screens[] specs (wireframes NOT shown when screens exist)
|
|
4208
|
-
|
|
4340
|
+
var hasResources = screens.some(function(s) { return (s.resources || []).length > 0; });
|
|
4341
|
+
if (screens.length > 0 && hasResources) {
|
|
4209
4342
|
var html = '';
|
|
4210
4343
|
if (typeof renderScreenMockups === 'function') {
|
|
4211
4344
|
html = renderScreenMockups(code);
|
|
@@ -4353,9 +4486,9 @@ function renderDataModel() {
|
|
|
4353
4486
|
${ent.description ? `<div class="dm-entity-desc">${escapeHtml(ent.description)}</div>` : ''}
|
|
4354
4487
|
${attrs.length > 0 ? `
|
|
4355
4488
|
<table class="dm-attr-table">
|
|
4356
|
-
<thead><tr><th>Champ</th><th>Description</th></tr></thead>
|
|
4489
|
+
<thead><tr><th>Champ</th><th style="width:100px;">Type</th><th>Description</th></tr></thead>
|
|
4357
4490
|
<tbody>
|
|
4358
|
-
${attrs.map(a => `<tr><td class="dm-attr-name">${escapeHtml(a.name)}</td><td class="dm-attr-desc">${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
4491
|
+
${attrs.map(a => `<tr><td class="dm-attr-name">${escapeHtml(a.name)}</td><td style="font-family:monospace;font-size:0.8rem;color:var(--accent);">${escapeHtml(a.type || 'string')}</td><td class="dm-attr-desc">${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
4359
4492
|
</tbody>
|
|
4360
4493
|
</table>` : ''}
|
|
4361
4494
|
${rels.length > 0 ? `
|
|
@@ -4495,13 +4628,16 @@ function renderScreenMockups(code) {
|
|
|
4495
4628
|
|
|
4496
4629
|
return screens.map(function(screen, si) {
|
|
4497
4630
|
var resources = screen.resources || [];
|
|
4631
|
+
var content = resources.length > 0
|
|
4632
|
+
? resources.map(function(res, ri) {
|
|
4633
|
+
return renderResourceMockup(code, screen.sectionCode, res, ri);
|
|
4634
|
+
}).join('')
|
|
4635
|
+
: '<div class="card" style="padding:1.5rem;color:var(--text-muted);text-align:center;"><p>Maquette en attente de spécification pour cette section.</p></div>';
|
|
4498
4636
|
return '<div class="screen-section" id="screen-' + code + '-' + screen.sectionCode + '" style="margin-bottom:2rem;">' +
|
|
4499
4637
|
'<h3 style="color:var(--text-bright);font-size:1rem;margin-bottom:1rem;">' +
|
|
4500
4638
|
'<span style="color:var(--accent);">▸</span> ' + escapeHtml(screen.sectionLabel || screen.sectionCode) +
|
|
4501
4639
|
'</h3>' +
|
|
4502
|
-
|
|
4503
|
-
return renderResourceMockup(code, screen.sectionCode, res, ri);
|
|
4504
|
-
}).join('') +
|
|
4640
|
+
content +
|
|
4505
4641
|
'</div>';
|
|
4506
4642
|
}).join('');
|
|
4507
4643
|
}
|
|
@@ -5856,6 +5992,24 @@ function renderMermaidDiagrams() {
|
|
|
5856
5992
|
}
|
|
5857
5993
|
}
|
|
5858
5994
|
|
|
5995
|
+
// 1b. MCD (Modèle Conceptuel de Données) — after ERD
|
|
5996
|
+
if (diagrams.mcd) {
|
|
5997
|
+
var erdContainer = document.getElementById('dataModelContainer');
|
|
5998
|
+
if (erdContainer) {
|
|
5999
|
+
var mcdDiv = document.createElement('div');
|
|
6000
|
+
mcdDiv.className = 'diagram-container diagram-mcd';
|
|
6001
|
+
mcdDiv.innerHTML =
|
|
6002
|
+
'<div class="diagram-section-header" style="font-size:0.95rem;font-weight:600;color:var(--text-bright);margin-bottom:0.75rem;">Mod\u00e8le Conceptuel de Donn\u00e9es (MCD)</div>' +
|
|
6003
|
+
'<div class="mermaid">' + escapeHtml(diagrams.mcd) + '</div>';
|
|
6004
|
+
var erdDiv = erdContainer.querySelector('.diagram-erd');
|
|
6005
|
+
if (erdDiv && erdDiv.nextSibling) {
|
|
6006
|
+
erdContainer.insertBefore(mcdDiv, erdDiv.nextSibling);
|
|
6007
|
+
} else {
|
|
6008
|
+
erdContainer.appendChild(mcdDiv);
|
|
6009
|
+
}
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
|
|
5859
6013
|
// 2. State machine diagrams — inject in module spec sections or consol-datamodel
|
|
5860
6014
|
if (diagrams.stateMachines && Object.keys(diagrams.stateMachines).length > 0) {
|
|
5861
6015
|
const smContainer = document.getElementById('dataModelContainer');
|
|
@@ -5878,6 +6032,34 @@ function renderMermaidDiagrams() {
|
|
|
5878
6032
|
}
|
|
5879
6033
|
}
|
|
5880
6034
|
|
|
6035
|
+
// 2b. Use Case diagrams — inject in each module's UC tab
|
|
6036
|
+
if (diagrams.useCases && Object.keys(diagrams.useCases).length > 0) {
|
|
6037
|
+
Object.entries(diagrams.useCases).forEach(function(entry) {
|
|
6038
|
+
var moduleCode = entry[0];
|
|
6039
|
+
var def = entry[1];
|
|
6040
|
+
var ucTab = document.getElementById('tab-' + moduleCode + '-uc');
|
|
6041
|
+
if (!ucTab) {
|
|
6042
|
+
// Try kebab-case version
|
|
6043
|
+
var kebab = moduleCode.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
6044
|
+
ucTab = document.getElementById('tab-' + kebab + '-uc');
|
|
6045
|
+
}
|
|
6046
|
+
if (ucTab) {
|
|
6047
|
+
var ucDiagramDiv = document.createElement('div');
|
|
6048
|
+
ucDiagramDiv.className = 'diagram-container diagram-usecase';
|
|
6049
|
+
ucDiagramDiv.innerHTML =
|
|
6050
|
+
'<div class="diagram-section-header" style="font-size:0.95rem;font-weight:600;color:var(--text-bright);margin-bottom:0.75rem;">Diagramme de cas d\'utilisation</div>' +
|
|
6051
|
+
'<div class="mermaid">' + escapeHtml(def) + '</div>';
|
|
6052
|
+
// Insert after the first paragraph (description text)
|
|
6053
|
+
var firstP = ucTab.querySelector('p');
|
|
6054
|
+
if (firstP && firstP.nextSibling) {
|
|
6055
|
+
ucTab.insertBefore(ucDiagramDiv, firstP.nextSibling);
|
|
6056
|
+
} else {
|
|
6057
|
+
ucTab.insertBefore(ucDiagramDiv, ucTab.firstChild);
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
});
|
|
6061
|
+
}
|
|
6062
|
+
|
|
5881
6063
|
// 3. Sequence diagrams in consol-flows
|
|
5882
6064
|
if (diagrams.sequences && Object.keys(diagrams.sequences).length > 0) {
|
|
5883
6065
|
var flowsContainer = document.getElementById('consolFlowsContainer');
|