@atlashub/smartstack-cli 4.74.0 → 4.76.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 +152 -31
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +14 -3
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/ba-reader.md +17 -15
- package/templates/agents/ba-writer.md +49 -51
- package/templates/skills/apex/SKILL.md +2 -2
- package/templates/skills/apex/_shared.md +1 -1
- package/templates/skills/apex/references/checks/backend-checks.sh +21 -7
- package/templates/skills/apex/references/checks/frontend-checks.sh +26 -0
- package/templates/skills/apex/references/checks/infrastructure-checks.sh +47 -10
- package/templates/skills/apex/references/checks/seed-checks.sh +47 -7
- package/templates/skills/apex/references/core-seed-data.md +20 -18
- package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +3 -0
- package/templates/skills/apex/references/post-checks.md +23 -3
- package/templates/skills/apex/references/smartstack-api.md +4 -4
- package/templates/skills/apex/references/smartstack-frontend.md +54 -8
- package/templates/skills/apex/references/smartstack-layers.md +6 -6
- package/templates/skills/apex/steps/step-00-init.md +75 -1
- package/templates/skills/apex/steps/step-03-execute.md +16 -4
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +65 -6
- package/templates/skills/apex/steps/step-03c-layer2-backend.md +50 -5
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +226 -4
- 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 +92 -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/frontend-route-wiring-app-tsx.md +3 -0
- package/templates/skills/application/templates-frontend.md +2 -2
- package/templates/skills/business-analyse/SKILL.md +17 -3
- package/templates/skills/business-analyse/_shared.md +64 -0
- package/templates/skills/business-analyse/patterns/suggestion-catalog.md +34 -26
- package/templates/skills/business-analyse/questionnaire/01-context.md +13 -9
- package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +20 -27
- package/templates/skills/business-analyse/questionnaire.md +86 -9
- package/templates/skills/business-analyse/references/03-json-schemas.md +221 -0
- package/templates/skills/business-analyse/references/03-post-check-validation.md +208 -0
- package/templates/skills/business-analyse/references/03-smartstack-entity-guards.md +32 -0
- package/templates/skills/business-analyse/references/04-cross-module-validation.md +95 -0
- package/templates/skills/business-analyse/references/04-file-allocation.md +162 -0
- package/templates/skills/business-analyse/references/04-naming-audit-checks.md +174 -0
- package/templates/skills/business-analyse/references/04-semantic-validation-matrix.md +118 -0
- package/templates/skills/business-analyse/references/canonical-json-formats.md +7 -3
- package/templates/skills/business-analyse/references/domain-research-playbook.md +234 -0
- package/templates/skills/business-analyse/references/entity-sourcing-presentation.md +166 -0
- package/templates/skills/business-analyse/references/init-resume-logic.md +70 -0
- package/templates/skills/business-analyse/references/module-completeness-challenge.md +174 -0
- package/templates/skills/business-analyse/references/multi-app-detection.md +149 -0
- package/templates/skills/business-analyse/references/portal-classification.md +52 -0
- package/templates/skills/business-analyse/references/robustness-checks.md +1 -1
- package/templates/skills/business-analyse/references/validation-checklist.md +35 -6
- package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +50 -6
- package/templates/skills/business-analyse/steps/step-00-init.md +22 -190
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +365 -269
- package/templates/skills/business-analyse/steps/step-02-structure.md +98 -20
- package/templates/skills/business-analyse/steps/step-03-specify.md +810 -229
- package/templates/skills/business-analyse/steps/step-04-consolidate.md +509 -278
- package/templates/skills/business-analyse-design/SKILL.md +10 -0
- package/templates/skills/business-analyse-design/references/screens-post-check.md +221 -0
- package/templates/skills/business-analyse-design/references/screens-type-mapping.md +138 -0
- package/templates/skills/business-analyse-design/references/smartcomponents-templates.md +225 -0
- package/templates/skills/{business-analyse → business-analyse-design}/references/spec-auto-inference.md +117 -117
- package/templates/skills/business-analyse-design/steps/step-01-screens.md +36 -162
- package/templates/skills/business-analyse-design/steps/step-02-wireframes.md +8 -7
- package/templates/skills/business-analyse-design/steps/step-03-navigation.md +89 -42
- package/templates/skills/business-analyse-develop/references/compact-loop.md +9 -0
- package/templates/skills/business-analyse-develop/references/handoff-quality-gate.md +132 -0
- package/templates/skills/business-analyse-develop/references/prd-v3-transformation.md +326 -0
- package/templates/skills/business-analyse-develop/references/report-reconciliation.md +140 -0
- package/templates/skills/business-analyse-develop/references/report-template.md +142 -0
- package/templates/skills/business-analyse-develop/steps/step-01-task.md +5 -177
- package/templates/skills/business-analyse-develop/steps/step-02-execute.md +17 -4
- package/templates/skills/business-analyse-develop/steps/step-03-commit.md +6 -2
- package/templates/skills/business-analyse-develop/steps/step-04-check.md +6 -0
- package/templates/skills/business-analyse-develop/steps/step-05-report.md +3 -269
- package/templates/skills/business-analyse-handoff/SKILL.md +10 -0
- package/templates/skills/business-analyse-handoff/references/agent-handoff-transform-prompt.md +211 -0
- package/templates/skills/business-analyse-handoff/references/context-isolation-pattern.md +47 -0
- package/templates/skills/business-analyse-handoff/references/handoff-file-inventory.md +49 -0
- package/templates/skills/business-analyse-handoff/references/handoff-global-validation.md +142 -0
- package/templates/skills/business-analyse-handoff/references/prd-validation-checks.md +125 -0
- package/templates/skills/business-analyse-handoff/references/project-index-update.md +98 -0
- package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +9 -160
- package/templates/skills/business-analyse-handoff/steps/step-02-export.md +10 -99
- package/templates/skills/business-analyse-html/SKILL.md +10 -0
- package/templates/skills/business-analyse-html/html/ba-interactive.html +504 -97
- package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +79 -2
- package/templates/skills/business-analyse-html/html/src/scripts/02-navigation.js +6 -46
- 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 +94 -36
- package/templates/skills/business-analyse-html/html/src/scripts/12-render-diagrams.js +162 -0
- package/templates/skills/business-analyse-html/html/src/styles/10-diagrams.css +73 -0
- package/templates/skills/business-analyse-html/html/src/template.html +2 -0
- package/templates/skills/business-analyse-html/references/02-embedded-artifacts-building.md +144 -0
- package/templates/skills/business-analyse-html/references/02-feature-data-building.md +143 -0
- package/templates/skills/business-analyse-html/references/02-mapping-tables.md +442 -0
- package/templates/skills/business-analyse-html/references/02-normalization-helpers.md +139 -0
- package/templates/skills/business-analyse-html/references/02-screen-format-detection.md +283 -0
- package/templates/skills/business-analyse-html/references/02-self-check-validation.md +199 -0
- package/templates/skills/business-analyse-html/references/data-build.md +24 -1
- package/templates/skills/business-analyse-html/references/data-mapping.md +119 -17
- package/templates/skills/business-analyse-html/steps/step-02-build-data.md +18 -555
- 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/SKILL.md +10 -0
- package/templates/skills/business-analyse-review/references/review-data-mapping.md +6 -0
- package/templates/skills/business-analyse-status/SKILL.md +8 -0
- package/templates/skills/dev-start/SKILL.md +143 -307
- package/templates/skills/efcore/SKILL.md +13 -0
- package/templates/skills/sketch/SKILL.md +15 -153
- package/templates/skills/ui-components/SKILL.md +1 -1
- package/templates/skills/ui-components/patterns/data-table.md +1 -1
|
@@ -52,9 +52,22 @@ if (!data.cadrage.scope || (!data.cadrage.scope.inscope?.length && data.cadrage.
|
|
|
52
52
|
outofscope: (gs.outOfScope || []).map(s => typeof s === 'string' ? { name: s, description: '' } : s)
|
|
53
53
|
};
|
|
54
54
|
} else {
|
|
55
|
+
function normScopeItem(s) {
|
|
56
|
+
if (typeof s === 'string') return { name: s, description: '' };
|
|
57
|
+
if (s.name) return s;
|
|
58
|
+
var nm = s.feature || s.label || s.title || JSON.stringify(s);
|
|
59
|
+
var parts = [];
|
|
60
|
+
if (s.priority) parts.push(s.priority.toUpperCase());
|
|
61
|
+
if (s.module) parts.push('Module: ' + s.module);
|
|
62
|
+
return { name: nm, description: parts.join(' — ') };
|
|
63
|
+
}
|
|
55
64
|
data.cadrage.scope = {
|
|
56
|
-
inscope: (gs.inScope || []).map(
|
|
57
|
-
outofscope: (gs.outOfScope || []).map(s
|
|
65
|
+
inscope: (gs.inScope || []).map(normScopeItem),
|
|
66
|
+
outofscope: (gs.outOfScope || []).map(function(s) {
|
|
67
|
+
if (typeof s === 'string') return { name: s, description: '' };
|
|
68
|
+
if (s.name) return s;
|
|
69
|
+
return { name: s.feature || s.reason || JSON.stringify(s), description: s.reason || '' };
|
|
70
|
+
})
|
|
58
71
|
};
|
|
59
72
|
}
|
|
60
73
|
}
|
|
@@ -92,6 +105,70 @@ data.moduleSpecs = data.moduleSpecs || {};
|
|
|
92
105
|
});
|
|
93
106
|
});
|
|
94
107
|
|
|
108
|
+
// Normalize permissions: convert any object format to "Role|Action" pipe-delimited strings
|
|
109
|
+
// Handles 5 formats:
|
|
110
|
+
// A: "Role|Action" strings (already correct)
|
|
111
|
+
// B: {role, permissions: ["Read:all", ...]} with role-permission pairs
|
|
112
|
+
// C: {role, permissions: ["Module.Entity.Read", ...]} with path-based permissions
|
|
113
|
+
// D: {code, label, description} permission definitions without role assignment
|
|
114
|
+
// E: {role: "", permissions: []} empty entries (skip)
|
|
115
|
+
(data.modules || []).forEach(function(m) {
|
|
116
|
+
var spec = data.moduleSpecs[m.code];
|
|
117
|
+
if (!spec || !spec.permissions || spec.permissions.length === 0) return;
|
|
118
|
+
// Skip if already normalized (first element is a pipe-delimited string)
|
|
119
|
+
if (typeof spec.permissions[0] === 'string' && spec.permissions[0].indexOf('|') !== -1) return;
|
|
120
|
+
if (typeof spec.permissions[0] !== 'object') return;
|
|
121
|
+
|
|
122
|
+
var actionMap = {
|
|
123
|
+
'read': 'Consulter', 'create': 'Créer', 'update': 'Modifier',
|
|
124
|
+
'delete': 'Supprimer', 'approve': 'Valider', 'validate': 'Valider',
|
|
125
|
+
'export': 'Exporter', 'admin': 'Administrer', 'import': 'Importer',
|
|
126
|
+
'viewsalary': 'Consulter', 'assignteam': 'Modifier', 'cancel': 'Supprimer',
|
|
127
|
+
'viewbalance': 'Consulter', 'invoicetime': 'Valider'
|
|
128
|
+
};
|
|
129
|
+
var normalized = [];
|
|
130
|
+
var first = spec.permissions[0];
|
|
131
|
+
|
|
132
|
+
// Detect Format D: {code, label, description} — permission codes without roles
|
|
133
|
+
if (first.code && !first.role && !first.permissions) {
|
|
134
|
+
// Extract actions from permission codes, assign to stakeholder roles
|
|
135
|
+
var roles = (data.cadrage.stakeholders || []).map(function(s) { return s.role; }).filter(Boolean);
|
|
136
|
+
if (roles.length === 0) roles = ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
|
|
137
|
+
spec.permissions.forEach(function(permDef) {
|
|
138
|
+
var code = permDef.code || '';
|
|
139
|
+
var raw = code.split('.').pop(); // "Module.Entity.Read" → "Read"
|
|
140
|
+
var action = actionMap[raw.toLowerCase()] || raw;
|
|
141
|
+
// Assign to first role (admin) by default — better than empty
|
|
142
|
+
if (roles[0]) {
|
|
143
|
+
var key = roles[0] + '|' + action;
|
|
144
|
+
if (normalized.indexOf(key) === -1) normalized.push(key);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
} else {
|
|
148
|
+
// Format B/C: {role, permissions: [...]}
|
|
149
|
+
spec.permissions.forEach(function(entry) {
|
|
150
|
+
var role = entry.role || entry.name || '';
|
|
151
|
+
if (!role) return; // Skip Format E (empty role)
|
|
152
|
+
var perms = entry.permissions || entry.actions || [];
|
|
153
|
+
if (perms.length === 0 && entry.permissionPattern && entry.permissionPattern.endsWith('*')) {
|
|
154
|
+
['Consulter', 'Créer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'].forEach(function(a) {
|
|
155
|
+
normalized.push(role + '|' + a);
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
perms.forEach(function(perm) {
|
|
160
|
+
if (typeof perm !== 'string') return;
|
|
161
|
+
var raw = perm.split(':')[0]; // "Read:all" → "Read"
|
|
162
|
+
if (raw.indexOf('.') !== -1) raw = raw.split('.').pop(); // "Module.Entity.Read" → "Read"
|
|
163
|
+
var action = actionMap[raw.toLowerCase()] || raw;
|
|
164
|
+
var key = role + '|' + action;
|
|
165
|
+
if (normalized.indexOf(key) === -1) normalized.push(key);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
spec.permissions = normalized;
|
|
170
|
+
});
|
|
171
|
+
|
|
95
172
|
// Detect if modules use section-level specs (hierarchical mode)
|
|
96
173
|
function hasHierarchicalSpecs(mod) {
|
|
97
174
|
return (mod.anticipatedSections || []).some(function(s) {
|
|
@@ -149,54 +149,14 @@ 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 || []).map(function(s) {
|
|
153
|
-
return typeof s === 'string' ? { code: s, name: s, resources: [] } : s;
|
|
154
|
-
});
|
|
155
|
-
var groupId = 'mod-' + code;
|
|
156
|
-
var collapsed = navCollapseState[groupId] === true;
|
|
157
|
-
|
|
158
|
-
var html = '<div class="nav-module" data-group-id="' + groupId + '">';
|
|
159
|
-
|
|
160
|
-
// Module header (clickable to expand + navigate to module spec)
|
|
161
152
|
var totalItems = ucCount + brCount + entCount;
|
|
162
|
-
html += '<a class="nav-item nav-module-header" onclick="toggleNavGroup(\'' + groupId + '\');showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">';
|
|
163
|
-
html += '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">▸</span> ';
|
|
164
|
-
html += (mod.name || mod.code);
|
|
165
|
-
if (totalItems > 0) html += ' <span class="nav-badge">' + totalItems + '</span>';
|
|
166
|
-
html += '</a>';
|
|
167
|
-
|
|
168
|
-
// Children: sections/resources (navigable sub-tree)
|
|
169
|
-
// Tab-level items (UC, BR, Données, etc.) are NOT shown here — they are accessed
|
|
170
|
-
// via the tab bar in the module content area to avoid redundancy.
|
|
171
|
-
html += '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>';
|
|
172
|
-
if (sections.length > 0) {
|
|
173
|
-
sections.forEach(function(section) {
|
|
174
|
-
var resources = section.resources || [];
|
|
175
|
-
var sectionUCs = (section.useCases || []).length;
|
|
176
|
-
var sectionBRs = (section.businessRules || []).length;
|
|
177
|
-
var contentCount = sectionUCs + sectionBRs + resources.length;
|
|
178
|
-
html += '<div class="nav-section-item">';
|
|
179
|
-
html += '<a class="nav-item nav-section-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'struct\')" data-section="module-struct-' + code + '-' + section.code + '">';
|
|
180
|
-
html += '<span class="nav-icon nav-icon-section">▷</span> ' + escapeHtml(section.code || section.name || '');
|
|
181
|
-
if (contentCount > 0) html += ' <span class="nav-badge">' + contentCount + '</span>';
|
|
182
|
-
html += '</a>';
|
|
183
|
-
if (resources.length > 0) {
|
|
184
|
-
html += '<div class="nav-children nav-resources">';
|
|
185
|
-
resources.forEach(function(res) {
|
|
186
|
-
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
187
|
-
html += '<a class="nav-item nav-resource-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'mock\');scrollToMockup(\'' + code + '\',\'' + section.code + '\')">';
|
|
188
|
-
html += '<span class="nav-icon nav-icon-resource">•</span> ' + escapeHtml(resName);
|
|
189
|
-
html += '</a>';
|
|
190
|
-
});
|
|
191
|
-
html += '</div>';
|
|
192
|
-
}
|
|
193
|
-
html += '</div>';
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
153
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
154
|
+
// Flat module link — sections/resources are accessible via tabs (Structure, Maquettes)
|
|
155
|
+
return '<a class="nav-item nav-module-header" onclick="showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">' +
|
|
156
|
+
'<span class="nav-icon">●</span> ' +
|
|
157
|
+
(mod.name || mod.code) +
|
|
158
|
+
(totalItems > 0 ? ' <span class="nav-badge">' + totalItems + '</span>' : '') +
|
|
159
|
+
'</a>';
|
|
200
160
|
}
|
|
201
161
|
|
|
202
162
|
function renderModuleTabNavItem(code, tabId, label, badge) {
|
|
@@ -210,6 +210,11 @@ function renderModuleSpecSection(mod) {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
function renderUseCase(code, uc, index) {
|
|
213
|
+
var preconditions = uc.preconditions || [];
|
|
214
|
+
var postconditions = uc.postconditions || [];
|
|
215
|
+
var errorScenarios = uc.errorScenarios || [];
|
|
216
|
+
var description = uc.description || '';
|
|
217
|
+
|
|
213
218
|
return `
|
|
214
219
|
<div class="uc-item">
|
|
215
220
|
<div class="uc-header">
|
|
@@ -220,8 +225,12 @@ function renderUseCase(code, uc, index) {
|
|
|
220
225
|
</div>
|
|
221
226
|
</div>
|
|
222
227
|
<div class="uc-actors"><div class="uc-actor">${escapeHtml(uc.actor)}</div></div>
|
|
223
|
-
${
|
|
224
|
-
${
|
|
228
|
+
${description ? `<div class="uc-detail-label">Description</div><div class="uc-detail" style="font-style:italic;color:var(--text-muted);">${escapeHtml(description)}</div>` : ''}
|
|
229
|
+
${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>` : ''}
|
|
230
|
+
${uc.steps ? `<div class="uc-detail-label">Déroulement principal</div><div class="uc-detail">${escapeHtml(uc.steps).replace(/\n/g, '<br>')}</div>` : ''}
|
|
231
|
+
${uc.alternative ? `<div class="uc-detail-label">Scénarios alternatifs</div><div class="uc-detail" style="color:var(--warning);">${escapeHtml(uc.alternative)}</div>` : ''}
|
|
232
|
+
${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>` : ''}
|
|
233
|
+
${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>` : ''}
|
|
225
234
|
<div style="padding:0.5rem 0.75rem;border-top:1px solid var(--border);background:var(--bg-input);border-radius:0 0 8px 8px;">
|
|
226
235
|
<label style="font-size:0.75rem;color:var(--text-muted);display:block;margin-bottom:0.25rem;">Commentaire / Feedback :</label>
|
|
227
236
|
<textarea class="form-textarea" placeholder="Ajouter un commentaire sur ce cas d'utilisation..."
|
|
@@ -314,15 +323,16 @@ function renderEntity(code, ent, index) {
|
|
|
314
323
|
</div>
|
|
315
324
|
${(ent.attributes || []).length > 0 ? `
|
|
316
325
|
<table class="attr-table">
|
|
317
|
-
<thead><tr><th>Information</th><th>Description</th></tr></thead>
|
|
326
|
+
<thead><tr><th>Information</th><th style="width:100px;">Type</th><th>Description</th></tr></thead>
|
|
318
327
|
<tbody>
|
|
319
|
-
${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('')}
|
|
328
|
+
${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('')}
|
|
320
329
|
</tbody>
|
|
321
330
|
</table>` : ''}
|
|
322
331
|
<div style="padding:0.3rem 0.75rem;">
|
|
323
332
|
<button class="add-btn" style="font-size:0.75rem;padding:0.4rem;" onclick="toggleForm('${attrFormId}')">+ Ajouter un attribut</button>
|
|
324
333
|
<div class="inline-form" id="${attrFormId}">
|
|
325
334
|
<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>
|
|
335
|
+
<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>
|
|
326
336
|
<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>
|
|
327
337
|
<div class="form-actions"><button class="btn" onclick="toggleForm('${attrFormId}')">Annuler</button><button class="btn btn-primary" onclick="addEntityAttribute('${code}',${index})">Ajouter</button></div>
|
|
328
338
|
</div>
|
|
@@ -347,7 +357,7 @@ function addEntity(code) {
|
|
|
347
357
|
|
|
348
358
|
const attrs = document.getElementById('ent-attrs-' + code).value.split('\n').filter(l => l.trim()).map(l => {
|
|
349
359
|
const parts = l.split(' - ');
|
|
350
|
-
return { name: parts[0]?.trim() || l.trim(), description: parts.slice(1).join(' - ').trim() };
|
|
360
|
+
return { name: parts[0]?.trim() || l.trim(), type: parts[1]?.trim() || 'string', description: parts.slice(2).join(' - ').trim() || parts.slice(1).join(' - ').trim() };
|
|
351
361
|
});
|
|
352
362
|
const rels = document.getElementById('ent-rels-' + code).value.split('\n').filter(l => l.trim());
|
|
353
363
|
|
|
@@ -371,13 +381,14 @@ function removeEntity(code, index) {
|
|
|
371
381
|
|
|
372
382
|
function addEntityAttribute(code, entityIndex) {
|
|
373
383
|
var attrName = document.getElementById('attr-name-' + code + '-' + entityIndex);
|
|
384
|
+
var attrType = document.getElementById('attr-type-' + code + '-' + entityIndex);
|
|
374
385
|
var attrDesc = document.getElementById('attr-desc-' + code + '-' + entityIndex);
|
|
375
386
|
if (!attrName || !attrName.value.trim()) return;
|
|
376
387
|
if (!data.moduleSpecs[code]?.entities?.[entityIndex]) return;
|
|
377
388
|
if (!data.moduleSpecs[code].entities[entityIndex].attributes) {
|
|
378
389
|
data.moduleSpecs[code].entities[entityIndex].attributes = [];
|
|
379
390
|
}
|
|
380
|
-
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), description: (attrDesc?.value || '').trim() });
|
|
391
|
+
data.moduleSpecs[code].entities[entityIndex].attributes.push({ name: attrName.value.trim(), type: (attrType?.value || 'string').trim(), description: (attrDesc?.value || '').trim() });
|
|
381
392
|
renderAllModuleSpecs();
|
|
382
393
|
autoSave();
|
|
383
394
|
}
|
|
@@ -488,13 +499,68 @@ function toggleWireframeView(wireframeId, view) {
|
|
|
488
499
|
}
|
|
489
500
|
}
|
|
490
501
|
|
|
502
|
+
// Normalize permissions array: handles 3 formats:
|
|
503
|
+
// Format 1 (string): "Role|Action" → already correct
|
|
504
|
+
// Format 2 (object): {role, permissions: ["Read:all", "Create:all", ...]}
|
|
505
|
+
// Format 3 (object): {role, permissions: ["Module.Entity.Read", ...]}
|
|
506
|
+
function normalizePermissions(permsArray) {
|
|
507
|
+
if (!permsArray || permsArray.length === 0) return [];
|
|
508
|
+
// If first element is a string with '|', already normalized
|
|
509
|
+
if (typeof permsArray[0] === 'string' && permsArray[0].includes('|')) return permsArray;
|
|
510
|
+
// If first element is a string without '|', return as-is (unknown format)
|
|
511
|
+
if (typeof permsArray[0] === 'string') return permsArray;
|
|
512
|
+
// Format 2/3: objects with {role, permissions[]}
|
|
513
|
+
const actionMap = {
|
|
514
|
+
'read': 'Consulter', 'create': 'Créer', 'update': 'Modifier',
|
|
515
|
+
'delete': 'Supprimer', 'approve': 'Valider', 'validate': 'Valider',
|
|
516
|
+
'export': 'Exporter', 'admin': 'Administrer', 'import': 'Importer',
|
|
517
|
+
'viewsalary': 'Consulter', 'assignteam': 'Modifier', 'cancel': 'Supprimer'
|
|
518
|
+
};
|
|
519
|
+
const normalized = [];
|
|
520
|
+
permsArray.forEach(function(entry) {
|
|
521
|
+
if (typeof entry !== 'object' || !entry) return;
|
|
522
|
+
var role = entry.role || entry.name || '';
|
|
523
|
+
if (!role) return;
|
|
524
|
+
var perms = entry.permissions || entry.actions || [];
|
|
525
|
+
if (perms.length === 0 && entry.permissionPattern && entry.permissionPattern.endsWith('*')) {
|
|
526
|
+
// Wildcard: expand to all standard actions
|
|
527
|
+
['Consulter', 'Créer', 'Modifier', 'Supprimer', 'Valider', 'Exporter'].forEach(function(a) {
|
|
528
|
+
normalized.push(role + '|' + a);
|
|
529
|
+
});
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
perms.forEach(function(perm) {
|
|
533
|
+
var action = '';
|
|
534
|
+
if (typeof perm === 'string') {
|
|
535
|
+
// "Read:all" → extract "Read"
|
|
536
|
+
var raw = perm.split(':')[0];
|
|
537
|
+
// "Module.Entity.Read" → extract "Read"
|
|
538
|
+
if (raw.includes('.')) raw = raw.split('.').pop();
|
|
539
|
+
action = actionMap[raw.toLowerCase()] || raw;
|
|
540
|
+
}
|
|
541
|
+
if (action) {
|
|
542
|
+
var key = role + '|' + action;
|
|
543
|
+
if (normalized.indexOf(key) === -1) normalized.push(key);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
return normalized;
|
|
548
|
+
}
|
|
549
|
+
|
|
491
550
|
function getPermRoles() {
|
|
492
|
-
// Extract roles from actual permission data (handles
|
|
551
|
+
// Extract roles from actual permission data (handles multiple formats)
|
|
493
552
|
const rolesFromPerms = [];
|
|
494
553
|
const seen = new Set();
|
|
495
554
|
data.modules.forEach(m => {
|
|
496
|
-
|
|
497
|
-
|
|
555
|
+
var perms = data.moduleSpecs[m.code]?.permissions || [];
|
|
556
|
+
// Handle object format directly for role extraction
|
|
557
|
+
perms.forEach(p => {
|
|
558
|
+
var role = '';
|
|
559
|
+
if (typeof p === 'string') {
|
|
560
|
+
role = p.split('|')[0];
|
|
561
|
+
} else if (typeof p === 'object' && p) {
|
|
562
|
+
role = p.role || p.name || '';
|
|
563
|
+
}
|
|
498
564
|
if (role && !seen.has(role)) { seen.add(role); rolesFromPerms.push(role); }
|
|
499
565
|
});
|
|
500
566
|
});
|
|
@@ -517,7 +583,9 @@ function renderPermissionGrid(code) {
|
|
|
517
583
|
const baseRolesCount = roles.length - (data.customRoles || []).length;
|
|
518
584
|
const baseActionsCount = 6;
|
|
519
585
|
|
|
520
|
-
|
|
586
|
+
// Normalize permissions to "Role|Action" format (handles object and string formats)
|
|
587
|
+
const rawPerms = data.moduleSpecs[code]?.permissions || [];
|
|
588
|
+
const perms = normalizePermissions(rawPerms);
|
|
521
589
|
|
|
522
590
|
return `
|
|
523
591
|
<table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
|
|
@@ -722,7 +790,8 @@ function renderModuleMockups(code) {
|
|
|
722
790
|
}) : [];
|
|
723
791
|
|
|
724
792
|
// Priority 1: HTML mockups from screens[] specs (wireframes NOT shown when screens exist)
|
|
725
|
-
|
|
793
|
+
var hasResources = screens.some(function(s) { return (s.resources || []).length > 0; });
|
|
794
|
+
if (screens.length > 0 && hasResources) {
|
|
726
795
|
var html = '';
|
|
727
796
|
if (typeof renderScreenMockups === 'function') {
|
|
728
797
|
html = renderScreenMockups(code);
|
|
@@ -70,9 +70,9 @@ function renderDataModel() {
|
|
|
70
70
|
${ent.description ? `<div class="dm-entity-desc">${escapeHtml(ent.description)}</div>` : ''}
|
|
71
71
|
${attrs.length > 0 ? `
|
|
72
72
|
<table class="dm-attr-table">
|
|
73
|
-
<thead><tr><th>Champ</th><th>Description</th></tr></thead>
|
|
73
|
+
<thead><tr><th>Champ</th><th style="width:100px;">Type</th><th>Description</th></tr></thead>
|
|
74
74
|
<tbody>
|
|
75
|
-
${attrs.map(a => `<tr><td class="dm-attr-name">${escapeHtml(a.name)}</td><td class="dm-attr-desc">${escapeHtml(a.description || '')}</td></tr>`).join('')}
|
|
75
|
+
${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('')}
|
|
76
76
|
</tbody>
|
|
77
77
|
</table>` : ''}
|
|
78
78
|
${rels.length > 0 ? `
|
|
@@ -13,13 +13,16 @@ function renderScreenMockups(code) {
|
|
|
13
13
|
|
|
14
14
|
return screens.map(function(screen, si) {
|
|
15
15
|
var resources = screen.resources || [];
|
|
16
|
+
var content = resources.length > 0
|
|
17
|
+
? resources.map(function(res, ri) {
|
|
18
|
+
return renderResourceMockup(code, screen.sectionCode, res, ri);
|
|
19
|
+
}).join('')
|
|
20
|
+
: '<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>';
|
|
16
21
|
return '<div class="screen-section" id="screen-' + code + '-' + screen.sectionCode + '" style="margin-bottom:2rem;">' +
|
|
17
22
|
'<h3 style="color:var(--text-bright);font-size:1rem;margin-bottom:1rem;">' +
|
|
18
23
|
'<span style="color:var(--accent);">▸</span> ' + escapeHtml(screen.sectionLabel || screen.sectionCode) +
|
|
19
24
|
'</h3>' +
|
|
20
|
-
|
|
21
|
-
return renderResourceMockup(code, screen.sectionCode, res, ri);
|
|
22
|
-
}).join('') +
|
|
25
|
+
content +
|
|
23
26
|
'</div>';
|
|
24
27
|
}).join('');
|
|
25
28
|
}
|
|
@@ -194,31 +197,36 @@ function renderSmartFormMockup(res) {
|
|
|
194
197
|
html += '<div class="mock-form-tab-content' + (ti === 0 || isOnly ? ' active' : '') + '"';
|
|
195
198
|
html += ' data-mockup="' + mockupId + '" data-tab="' + ti + '">';
|
|
196
199
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
// Tab-level subtable: the entire tab is a subtable (type: "subtable", entity, columns)
|
|
201
|
+
if (tab.type === 'subtable') {
|
|
202
|
+
html += renderSubtableMockup(tab);
|
|
203
|
+
} else {
|
|
204
|
+
var fields = tab.fields || [];
|
|
205
|
+
var rows = [];
|
|
206
|
+
for (var i = 0; i < fields.length; i += 2) {
|
|
207
|
+
rows.push(fields.slice(i, i + 2));
|
|
208
|
+
}
|
|
202
209
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
rows.forEach(function(row) {
|
|
211
|
+
if (row.length === 1 && row[0].type === 'subtable') {
|
|
212
|
+
html += renderSubtableMockup(row[0]);
|
|
213
|
+
} else {
|
|
214
|
+
html += '<div class="mock-form-row">';
|
|
215
|
+
row.forEach(function(field) {
|
|
216
|
+
html += '<div class="mock-form-group">';
|
|
217
|
+
html += '<label class="mock-label">' + escapeHtml(field.label || field.field);
|
|
218
|
+
if (field.required) html += ' <span style="color:var(--error);">*</span>';
|
|
219
|
+
html += '</label>';
|
|
220
|
+
html += renderFormFieldMockup(field);
|
|
221
|
+
html += '</div>';
|
|
222
|
+
});
|
|
214
223
|
html += '</div>';
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
219
226
|
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
if (fields.length === 0) {
|
|
228
|
+
html += '<div style="padding:2rem;text-align:center;color:var(--text-muted);font-style:italic;">Contenu de l\'onglet « ' + escapeHtml(tab.label) + ' »</div>';
|
|
229
|
+
}
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
html += '</div>';
|
|
@@ -263,20 +271,39 @@ function renderSubtableMockup(field) {
|
|
|
263
271
|
|
|
264
272
|
/* ---------- SmartCard ---------- */
|
|
265
273
|
function renderSmartCardMockup(res) {
|
|
266
|
-
var
|
|
274
|
+
var fields = res.fields || res.columns || [];
|
|
267
275
|
var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Cartes') + '</span></div>';
|
|
268
276
|
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:1rem;">';
|
|
269
277
|
|
|
270
278
|
for (var i = 0; i < 4; i++) {
|
|
271
279
|
html += '<div style="background:var(--bg-hover);border:1px solid var(--border);border-radius:8px;padding:1rem;">';
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
280
|
+
// Title/Subtitle pattern (SmartCard with title + subtitle + fields + actions)
|
|
281
|
+
if (res.title) {
|
|
282
|
+
html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.25rem;">' + escapeHtml(res.title) + ' #' + (i + 1) + '</div>';
|
|
283
|
+
}
|
|
284
|
+
if (res.subtitle) {
|
|
285
|
+
html += '<div style="font-size:0.8rem;color:var(--accent);margin-bottom:0.5rem;">' + escapeHtml(res.subtitle) + '</div>';
|
|
286
|
+
}
|
|
287
|
+
// Fields
|
|
288
|
+
fields.forEach(function(col, ci) {
|
|
289
|
+
var label = col.label || col.field || (typeof col === 'string' ? col : '');
|
|
290
|
+
var iconMap = { calendar: '📅 ', 'dollar-sign': '💰 ', 'alert-circle': '⚠ ', mail: '✉ ', building: '🏢 ', user: '👤 ' };
|
|
291
|
+
var icon = col.icon ? (iconMap[col.icon] || '● ') : '';
|
|
292
|
+
if (!res.title && ci === 0) {
|
|
275
293
|
html += '<div style="font-weight:600;color:var(--text-bright);margin-bottom:0.5rem;">' + escapeHtml(label) + ' #' + (i + 1) + '</div>';
|
|
276
294
|
} else {
|
|
277
|
-
html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.25rem;">' + escapeHtml(label) + ': <span style="color:var(--text);">valeur</span></div>';
|
|
295
|
+
html += '<div style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.25rem;">' + icon + escapeHtml(label) + ': <span style="color:var(--text);">valeur</span></div>';
|
|
278
296
|
}
|
|
279
297
|
});
|
|
298
|
+
// Actions
|
|
299
|
+
if (res.actions && res.actions.length > 0) {
|
|
300
|
+
html += '<div style="margin-top:0.75rem;display:flex;gap:0.4rem;">';
|
|
301
|
+
res.actions.forEach(function(a) {
|
|
302
|
+
var actionLabel = typeof a === 'string' ? a.replace(/-/g, ' ') : (a.label || a.action || '');
|
|
303
|
+
html += '<span class="mock-btn" style="font-size:0.75rem;">' + escapeHtml(actionLabel) + '</span>';
|
|
304
|
+
});
|
|
305
|
+
html += '</div>';
|
|
306
|
+
}
|
|
280
307
|
html += '</div>';
|
|
281
308
|
}
|
|
282
309
|
html += '</div>';
|
|
@@ -285,7 +312,7 @@ function renderSmartCardMockup(res) {
|
|
|
285
312
|
|
|
286
313
|
/* ---------- SmartKanban ---------- */
|
|
287
314
|
function renderSmartKanbanMockup(res) {
|
|
288
|
-
var options = res.options
|
|
315
|
+
var options = (res.options && res.options.length > 0) ? res.options : (res.columns && res.columns.length > 0) ? res.columns : ['À faire', 'En cours', 'Terminé'];
|
|
289
316
|
var html = '<div class="mock-header"><span class="mock-title">' + escapeHtml(res.label || 'Kanban') + '</span></div>';
|
|
290
317
|
html += '<div style="display:flex;gap:1rem;overflow-x:auto;padding-bottom:0.5rem;">';
|
|
291
318
|
|
|
@@ -359,11 +386,42 @@ function kpiDisplayValue(kpi) {
|
|
|
359
386
|
|
|
360
387
|
/* ---------- SmartFilter ---------- */
|
|
361
388
|
function renderSmartFilterMockup(res) {
|
|
362
|
-
var
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
389
|
+
var filters = res.filters || [];
|
|
390
|
+
if (filters.length === 0) {
|
|
391
|
+
// Fallback: legacy options[] format (tag pills)
|
|
392
|
+
var options = res.options || [];
|
|
393
|
+
if (options.length === 0) return '<div style="padding:1rem;text-align:center;color:var(--text-muted);">Aucun filtre défini</div>';
|
|
394
|
+
var html = '<div style="display:flex;gap:0.4rem;flex-wrap:wrap;padding:0.5rem 0;">';
|
|
395
|
+
html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--primary);color:#fff;cursor:pointer;">Tous</span>';
|
|
396
|
+
options.forEach(function(opt) {
|
|
397
|
+
html += '<span style="padding:0.3rem 0.7rem;border-radius:16px;font-size:0.8rem;background:var(--bg-hover);color:var(--text);border:1px solid var(--border);cursor:pointer;">' + escapeHtml(typeof opt === 'string' ? opt : opt.label || '') + '</span>';
|
|
398
|
+
});
|
|
399
|
+
html += '</div>';
|
|
400
|
+
return html;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Rich filter bar: render each filter based on its type
|
|
404
|
+
var html = '<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:flex-end;padding:0.5rem 0;">';
|
|
405
|
+
filters.forEach(function(f) {
|
|
406
|
+
var label = typeof f === 'string' ? f : (f.label || f.field || '');
|
|
407
|
+
var type = typeof f === 'string' ? 'text' : (f.type || 'text');
|
|
408
|
+
html += '<div style="display:flex;flex-direction:column;gap:0.2rem;">';
|
|
409
|
+
html += '<span style="font-size:0.7rem;color:var(--text-muted);">' + escapeHtml(label) + '</span>';
|
|
410
|
+
switch (type) {
|
|
411
|
+
case 'select':
|
|
412
|
+
var opts = f.options || ['Option 1', 'Option 2'];
|
|
413
|
+
html += '<span class="mock-input" style="width:auto;min-width:130px;font-size:0.8rem;color:var(--text-muted);">' + escapeHtml(opts[0]) + ' ▾</span>';
|
|
414
|
+
break;
|
|
415
|
+
case 'lookup':
|
|
416
|
+
html += '<span class="mock-input" style="width:auto;min-width:130px;font-size:0.8rem;color:var(--accent);">' + escapeHtml(f.entity || label) + ' 🔍</span>';
|
|
417
|
+
break;
|
|
418
|
+
case 'daterange':
|
|
419
|
+
html += '<span class="mock-input" style="width:auto;min-width:180px;font-size:0.8rem;color:var(--text-muted);">01/01/2025 — 31/12/2025 📅</span>';
|
|
420
|
+
break;
|
|
421
|
+
default:
|
|
422
|
+
html += '<span class="mock-input" style="width:auto;min-width:160px;font-size:0.8rem;color:var(--text-muted);">Rechercher... 🔍</span>';
|
|
423
|
+
}
|
|
424
|
+
html += '</div>';
|
|
367
425
|
});
|
|
368
426
|
html += '</div>';
|
|
369
427
|
return html;
|