@atlashub/smartstack-cli 3.20.0 → 3.22.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 +70 -6
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +69 -3
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/project/api.ts.template +8 -29
- package/templates/project/appsettings.json.template +1 -0
- package/templates/skills/application/references/application-roles-template.md +2 -2
- package/templates/skills/application/steps/step-05-frontend.md +40 -35
- package/templates/skills/application/templates-frontend.md +64 -36
- package/templates/skills/business-analyse/html/ba-interactive.html +642 -156
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
- package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +95 -8
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
- package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
- package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
- package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
- package/templates/skills/business-analyse/html/src/template.html +8 -76
- package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
- package/templates/skills/business-analyse/references/deploy-data-build.md +13 -9
- package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
- package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
- package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
- package/templates/skills/business-analyse/references/validate-incremental-html.md +28 -5
- package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
- package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
- package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
- package/templates/skills/business-analyse/steps/step-03c-compile.md +169 -2
- package/templates/skills/business-analyse/steps/step-03d-validate.md +217 -28
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +189 -3
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +55 -0
- package/templates/skills/ralph-loop/references/category-rules.md +5 -2
- package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
- package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
- package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
- package/templates/skills/ralph-loop/steps/step-02-execute.md +81 -0
|
@@ -34,21 +34,50 @@ Validate the module specification for completeness and consistency, write to fea
|
|
|
34
34
|
|
|
35
35
|
#### 9a. Completeness Checks
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
37
|
+
> **CRITICAL:** Checks MUST count ACTUAL elements in arrays, NOT declarative values.
|
|
38
|
+
> A check that reports PASS when the array is empty is a LIE and BLOCKS downstream quality.
|
|
39
|
+
|
|
40
|
+
| Section | Minimum | How to verify | Status |
|
|
41
|
+
|---------|---------|---------------|--------|
|
|
42
|
+
| actors | 2 | `specification.actors.length >= 2` | PASS/FAIL |
|
|
43
|
+
| useCases | 2 | `specification.useCases.length >= 2` | PASS/FAIL |
|
|
44
|
+
| functionalRequirements | 4 | `specification.functionalRequirements.length >= 4` | PASS/FAIL |
|
|
45
|
+
| permissionMatrix | 1 resource × 2 roles | `specification.permissionMatrix.permissions.length >= 1 && specification.permissionMatrix.roleAssignments.length >= 2` | PASS/FAIL |
|
|
46
|
+
| entities | 1 | `analysis.entities.length >= 1` | PASS/FAIL |
|
|
47
|
+
| entitySchemaFormat | attributes[] not fields[] (BLOCKING) | `analysis.entities.every(e => e.attributes?.length > 0)` | PASS/FAIL |
|
|
48
|
+
| entityAttributeTypes | ALL attributes have `type` field (BLOCKING) | `analysis.entities.every(e => e.attributes.every(a => a.type))` | PASS/FAIL |
|
|
49
|
+
| wireframes | 1 per section (BLOCKING) | `(specification.uiWireframes \|\| specification.wireframes \|\| []).length >= (specification.sections \|\| []).length` (count REAL elements, check BOTH key names) | PASS/FAIL |
|
|
50
|
+
| wireframeSchema | All required fields present (BLOCKING) | `(specification.uiWireframes \|\| specification.wireframes \|\| []).every(w => (w.screen \|\| w.title) && w.section && (w.mockup \|\| w.ascii \|\| w.content))` | PASS/FAIL |
|
|
51
|
+
| sections | 1 (BLOCKING) | `specification.sections.length >= 1` (EVERY module needs at least 1 section) | PASS/FAIL |
|
|
52
|
+
| gherkinScenarios | 1 array entry | `Array.isArray(specification.gherkinScenarios) && specification.gherkinScenarios.length >= 1` | PASS/FAIL |
|
|
53
|
+
| gherkinFormat | Array not object (BLOCKING) | `Array.isArray(specification.gherkinScenarios)` (NOT a single object) | PASS/FAIL |
|
|
54
|
+
| validations | 1 | `specification.validations.length >= 1` | PASS/FAIL |
|
|
55
|
+
| validationFormat | rules[] array (BLOCKING) | `specification.validations.every(v => Array.isArray(v.rules))` (NOT singular `rule`) | PASS/FAIL |
|
|
56
|
+
| messages | 4 | `specification.messages.length >= 4` | PASS/FAIL |
|
|
57
|
+
| messageFormat | `message` field present (BLOCKING) | `specification.messages.every(m => m.message)` | PASS/FAIL |
|
|
58
|
+
| lifeCycles | 1 (if entity has status) | `specification.lifeCycles.length >= 1` (if any entity has status/state field) | PASS/FAIL |
|
|
59
|
+
| seedDataCore | 7 arrays present with content | See detailed check below | PASS/FAIL (BLOCKING) |
|
|
60
|
+
| apiEndpoints | 1 | `specification.apiEndpoints.length >= 1` | PASS/FAIL |
|
|
61
|
+
| i18nKeys | present | `specification.i18nKeys !== undefined && specification.i18nKeys !== null` | PASS/FAIL |
|
|
62
|
+
| navigationIcons | non-null | `specification.seedDataCore.navigationModules.every(m => m.icon !== null)` | PASS/FAIL |
|
|
63
|
+
|
|
64
|
+
**seedDataCore detailed check (BLOCKING):**
|
|
65
|
+
```javascript
|
|
66
|
+
const sdc = specification.seedDataCore;
|
|
67
|
+
const checks = [
|
|
68
|
+
{ key: "navigationModules", actual: sdc.navigationModules?.length || 0, min: 1 },
|
|
69
|
+
{ key: "navigationSections", actual: sdc.navigationSections?.length || 0, min: 1 }, // EVERY module needs ≥1 section
|
|
70
|
+
{ key: "navigationResources", actual: sdc.navigationResources?.length || 0, min: 1 },
|
|
71
|
+
{ key: "navigationTranslations", actual: sdc.navigationTranslations?.length || 0, min: 2 }, // min fr+en
|
|
72
|
+
{ key: "permissions", actual: sdc.permissions?.length || 0, min: 1 },
|
|
73
|
+
{ key: "rolePermissions", actual: sdc.rolePermissions?.length || 0, min: 1 },
|
|
74
|
+
{ key: "permissionConstants", actual: sdc.permissionConstants?.length || 0, min: 1 }
|
|
75
|
+
];
|
|
76
|
+
const failures = checks.filter(c => c.actual < c.min);
|
|
77
|
+
IF failures.length > 0:
|
|
78
|
+
BLOCKING ERROR: "seedDataCore incomplete — empty arrays: {failures.map(f => f.key).join(', ')}"
|
|
79
|
+
→ Fix: Ensure specification.sections[] has ≥1 entry, then re-run 8f-bis transform
|
|
80
|
+
```
|
|
52
81
|
|
|
53
82
|
#### 9b. Consistency Checks
|
|
54
83
|
|
|
@@ -68,9 +97,74 @@ Validate the module specification for completeness and consistency, write to fea
|
|
|
68
97
|
- Entity names PascalCase
|
|
69
98
|
- Field names camelCase
|
|
70
99
|
- Entity attribute format: `attributes[]` with {name, description}, NOT `fields[]` with {name, type} — entities must NOT have tableName or primaryKey
|
|
71
|
-
- Wireframe structure: `screen` (not `name`), `componentMapping` is array of {wireframeElement, reactComponent} (not plain key-value object), `layout` is object with regions (not string)
|
|
100
|
+
- Wireframe structure: `screen` (not `name`/`title`), `mockup` (not `ascii`/`content`), `componentMapping` is array of {wireframeElement, reactComponent} (not plain key-value object), `layout` is object with regions (not string)
|
|
101
|
+
- Wireframe field naming: uses `screen` (not `title`), `mockup` (not `ascii`) — auto-fix if wrong (see 9c-fix below)
|
|
72
102
|
- Permission paths dot-separated lowercase
|
|
73
103
|
|
|
104
|
+
#### 9c-fix. Auto-Fix Wireframe Field Names (MANDATORY before writing)
|
|
105
|
+
|
|
106
|
+
> **CRITICAL:** The agent may use non-canonical field names (`title`, `ascii`, `name`, `content`).
|
|
107
|
+
> These MUST be normalized to canonical names before writing to feature.json.
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// AUTO-FIX: Normalize wireframe field names before writing
|
|
111
|
+
const wireframes = specification.uiWireframes || specification.wireframes || [];
|
|
112
|
+
for (const wf of wireframes) {
|
|
113
|
+
if (wf.title && !wf.screen) { wf.screen = wf.title; delete wf.title; }
|
|
114
|
+
if (wf.ascii && !wf.mockup) { wf.mockup = wf.ascii; delete wf.ascii; }
|
|
115
|
+
if (wf.content && !wf.mockup) { wf.mockup = wf.content; delete wf.content; }
|
|
116
|
+
if (wf.name && !wf.screen) { wf.screen = wf.name; delete wf.name; }
|
|
117
|
+
}
|
|
118
|
+
// Store normalized wireframes back under canonical key
|
|
119
|
+
specification.uiWireframes = wireframes;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### 9c-fix-entities. Auto-Fix Entity Attribute Types (MANDATORY before writing)
|
|
123
|
+
|
|
124
|
+
> **DEFENSE-IN-DEPTH:** step-03c POST-CHECK should catch attributes without `type`,
|
|
125
|
+
> but if any slip through to step-03d, auto-fix them here before writing to feature.json.
|
|
126
|
+
> Uses the same type inference algorithm as step-03c POST-CHECK.
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
// AUTO-FIX: Ensure all entity attributes have structured type
|
|
130
|
+
const entities = analysis.entities || [];
|
|
131
|
+
let entityAutoFixCount = 0;
|
|
132
|
+
for (const entity of entities) {
|
|
133
|
+
for (const attr of entity.attributes || []) {
|
|
134
|
+
if (!attr.type) {
|
|
135
|
+
// Infer type from attribute name pattern
|
|
136
|
+
if (attr.validation?.match(/max\s*\d+/i) || attr.name.match(/name|title|code|description|label|email|phone|address/i)) {
|
|
137
|
+
attr.type = "string";
|
|
138
|
+
const maxMatch = attr.validation?.match(/max\s*(\d+)/i);
|
|
139
|
+
if (maxMatch) attr.maxLength = parseInt(maxMatch[1]);
|
|
140
|
+
} else if (attr.name.match(/id$/i)) {
|
|
141
|
+
attr.type = "Guid";
|
|
142
|
+
} else if (attr.name.match(/date|At$/i)) {
|
|
143
|
+
attr.type = "DateTime";
|
|
144
|
+
} else if (attr.name.match(/is[A-Z]|has[A-Z]|active|enabled/)) {
|
|
145
|
+
attr.type = "bool";
|
|
146
|
+
} else if (attr.name.match(/amount|salary|rate|price|total/i)) {
|
|
147
|
+
attr.type = "decimal";
|
|
148
|
+
} else if (attr.name.match(/count|number|order|sort|index/i)) {
|
|
149
|
+
attr.type = "int";
|
|
150
|
+
} else {
|
|
151
|
+
attr.type = "string"; // safe default
|
|
152
|
+
}
|
|
153
|
+
// Normalize free-text maxLength
|
|
154
|
+
if (typeof attr.validation === 'string' && !attr.maxLength) {
|
|
155
|
+
const m = attr.validation.match(/max\s*(\d+)/i);
|
|
156
|
+
if (m) attr.maxLength = parseInt(m[1]);
|
|
157
|
+
}
|
|
158
|
+
if (attr.required === undefined) attr.required = true;
|
|
159
|
+
entityAutoFixCount++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (entityAutoFixCount > 0) {
|
|
164
|
+
console.warn(`DEFENSE-IN-DEPTH: auto-fixed ${entityAutoFixCount} attributes without type in step-03d`);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
74
168
|
#### 9d. Decision
|
|
75
169
|
|
|
76
170
|
IF validation PASS:
|
|
@@ -136,10 +230,10 @@ ba-writer.enrichSection({
|
|
|
136
230
|
|
|
137
231
|
**Execute the comprehensive validation checklist:**
|
|
138
232
|
|
|
139
|
-
Run the
|
|
233
|
+
Run the 29-check validation process across 10 categories:
|
|
140
234
|
- Data Model (4 checks) | Business Rules (3 checks) | Use Cases & FRs (4 checks)
|
|
141
235
|
- Permissions (3 checks) | UI & Navigation (4 checks) | I18N & Messages (3 checks)
|
|
142
|
-
- Seed Data (2 checks) | API Endpoints (2 checks) | Validations (1 check) | Gherkin (
|
|
236
|
+
- Seed Data (2 checks) | API Endpoints (2 checks) | Validations (1 check) | Gherkin (3 checks)
|
|
143
237
|
|
|
144
238
|
```javascript
|
|
145
239
|
const blockingFailures = checks.filter(c => c.blocking && c.status === "FAIL");
|
|
@@ -214,18 +308,13 @@ Display comprehensive summary:
|
|
|
214
308
|
|
|
215
309
|
→ Validation: {PASS/FAIL}
|
|
216
310
|
═══════════════════════════════════════════════════════════
|
|
217
|
-
```
|
|
218
311
|
|
|
219
|
-
|
|
312
|
+
{IF validation PASS}:
|
|
313
|
+
→ Module validé automatiquement
|
|
314
|
+
→ Passage automatique au module suivant (section 12)
|
|
220
315
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
header: "Module"
|
|
224
|
-
options:
|
|
225
|
-
- label: "Validé"
|
|
226
|
-
description: "Passer au module suivant"
|
|
227
|
-
- label: "Réviser"
|
|
228
|
-
description: "Modifier des éléments du module"
|
|
316
|
+
{IF validation FAIL}:
|
|
317
|
+
→ Afficher les erreurs et offrir les options de correction (section 9d)
|
|
229
318
|
```
|
|
230
319
|
|
|
231
320
|
---
|
|
@@ -278,6 +367,106 @@ ba-writer.updateStatus({module_feature_id}, "specified")
|
|
|
278
367
|
ba-writer.updateModuleStatus({feature_id}, {currentModule.code}, "specified")
|
|
279
368
|
```
|
|
280
369
|
|
|
370
|
+
#### 11-POST-CHECK: Verify Written Data (BLOCKING)
|
|
371
|
+
|
|
372
|
+
> **CRITICAL — Data loss prevention.** After writing, READ BACK the module feature.json and verify the following arrays are **non-empty**:
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
// READ BACK the written feature.json
|
|
376
|
+
const written = ba-reader.read({module_feature_id});
|
|
377
|
+
|
|
378
|
+
// BLOCKING checks — if ANY fails, the write was incomplete
|
|
379
|
+
const checks = [
|
|
380
|
+
{ key: "specification.actors", actual: written.specification?.actors?.length, min: 2 },
|
|
381
|
+
{ key: "specification.useCases", actual: written.specification?.useCases?.length, min: 2 },
|
|
382
|
+
{ key: "specification.wireframes", actual: (written.specification?.uiWireframes?.length || written.specification?.wireframes?.length || 0), min: 1 },
|
|
383
|
+
{ key: "specification.sections", actual: written.specification?.sections?.length, min: 1 },
|
|
384
|
+
{ key: "specification.seedDataCore", actual: Object.keys(written.specification?.seedDataCore || {}).length, min: 7 },
|
|
385
|
+
{ key: "specification.lifeCycles", actual: written.specification?.lifeCycles?.length, min: 0 },
|
|
386
|
+
{ key: "specification.gherkinScenarios",actual: written.specification?.gherkinScenarios?.length,min: 1 },
|
|
387
|
+
{ key: "specification.apiEndpoints", actual: written.specification?.apiEndpoints?.length, min: 1 }
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
const failures = checks.filter(c => (c.actual || 0) < c.min);
|
|
391
|
+
|
|
392
|
+
IF failures.length > 0:
|
|
393
|
+
BLOCKING ERROR: "Feature.json write INCOMPLETE — missing data in: {failures.map(f => f.key).join(', ')}"
|
|
394
|
+
→ Re-execute section 11 write with ALL specification data
|
|
395
|
+
→ DO NOT proceed to next module until ALL checks pass
|
|
396
|
+
|
|
397
|
+
// SPECIAL CHECK: wireframes content verification (check BOTH key names)
|
|
398
|
+
const wireframes = written.specification?.uiWireframes || written.specification?.wireframes || [];
|
|
399
|
+
IF wireframes.length > 0:
|
|
400
|
+
const emptyMockups = wireframes.filter(wf => !wf.mockup && !wf.ascii && !wf.content);
|
|
401
|
+
IF emptyMockups.length > 0:
|
|
402
|
+
WARNING: "{emptyMockups.length} wireframes have empty mockup content — verify step-03b data"
|
|
403
|
+
|
|
404
|
+
// SPECIAL CHECK: wireframes >= sections
|
|
405
|
+
const sectionCount = written.specification?.sections?.length || 0;
|
|
406
|
+
IF wireframes.length < sectionCount:
|
|
407
|
+
BLOCKING ERROR: "{wireframes.length} wireframes < {sectionCount} sections — wireframes MISSING for some sections"
|
|
408
|
+
→ Re-read wireframes from conversation context and re-write to feature.json
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### 11-POST-CHECK-BASH: Acceptance Criteria Verification (BLOCKING)
|
|
412
|
+
|
|
413
|
+
> **CRITICAL:** The pseudocode checks above are interpreted by the model — they can be "passed" incorrectly.
|
|
414
|
+
> This bash check reads the REAL file on disk and provides an objective verification.
|
|
415
|
+
> See [references/acceptance-criteria.md](../references/acceptance-criteria.md) for the full acceptance criteria definition.
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
MODULE_JSON="{module_feature_json_path}"
|
|
419
|
+
node -e "
|
|
420
|
+
const fs = require('fs');
|
|
421
|
+
const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8'));
|
|
422
|
+
const spec = data.specification || {};
|
|
423
|
+
const analysis = data.analysis || {};
|
|
424
|
+
const wf = spec.uiWireframes || spec.wireframes || [];
|
|
425
|
+
const sections = spec.sections || [];
|
|
426
|
+
const checks = [
|
|
427
|
+
['entities >= 1', (analysis.entities||[]).length, 1],
|
|
428
|
+
['useCases >= 2', (spec.useCases||[]).length, 2],
|
|
429
|
+
['FRs >= 4', (spec.functionalRequirements||[]).length,4],
|
|
430
|
+
['wireframes >= 1', wf.length, 1],
|
|
431
|
+
['wireframes >= sections', wf.length, sections.length],
|
|
432
|
+
['sections >= 1', sections.length, 1],
|
|
433
|
+
['seedDataCore 7 arrays', Object.keys(spec.seedDataCore||{}).filter(k=>(spec.seedDataCore||{})[k]&&(spec.seedDataCore||{})[k].length>0).length, 7],
|
|
434
|
+
['gherkin is array', Array.isArray(spec.gherkinScenarios)?1:0, 1],
|
|
435
|
+
['apiEndpoints >= 1', (spec.apiEndpoints||[]).length, 1],
|
|
436
|
+
['messages >= 4', (spec.messages||[]).length, 4],
|
|
437
|
+
['validations >= 1', (spec.validations||[]).length, 1]
|
|
438
|
+
];
|
|
439
|
+
const fails = checks.filter(c => c[1] < c[2]);
|
|
440
|
+
fails.forEach(f => console.error('FAIL: ' + f[0] + ' = ' + f[1] + ' (min: ' + f[2] + ')'));
|
|
441
|
+
// Check wireframe content
|
|
442
|
+
const emptyWf = wf.filter(w => !w.mockup && !w.ascii && !w.content);
|
|
443
|
+
if (emptyWf.length > 0) { fails.push(['wireframe content', 0, 1]); console.error('FAIL: ' + emptyWf.length + ' wireframes have EMPTY content'); }
|
|
444
|
+
// Check entity attribute types
|
|
445
|
+
const badAttrs = (analysis.entities||[]).flatMap(e => (e.attributes||[]).filter(a => !a.type).map(a => e.name+'.'+a.name));
|
|
446
|
+
if (badAttrs.length > 0) { fails.push(['attr.type', 0, 1]); console.error('FAIL: attributes without type: ' + badAttrs.join(', ')); }
|
|
447
|
+
// AC-15: Validation rules format
|
|
448
|
+
const badRules = (spec.validations||[]).filter(v => v.rules && !Array.isArray(v.rules));
|
|
449
|
+
if (badRules.length > 0) { fails.push(['AC-15: rules not array', badRules.length, 0]); console.error('FAIL: AC-15: ' + badRules.length + ' validations have rules as string'); }
|
|
450
|
+
// AC-16: Messages must have message field
|
|
451
|
+
const noMsg = (spec.messages||[]).filter(m => !m.message);
|
|
452
|
+
if (noMsg.length > 0) { fails.push(['AC-16: message missing', noMsg.length, 0]); console.error('FAIL: AC-16: ' + noMsg.length + ' messages missing message field'); }
|
|
453
|
+
// AC-17: Gherkin content structure
|
|
454
|
+
if (Array.isArray(spec.gherkinScenarios)) { const badG = spec.gherkinScenarios.filter(g => !g.feature || !Array.isArray(g.scenarios)); if (badG.length > 0) { fails.push(['AC-17: gherkin content', badG.length, 0]); console.error('FAIL: AC-17: ' + badG.length + ' gherkin entries invalid'); } }
|
|
455
|
+
if (fails.length > 0) { console.error('BLOCKING: ' + fails.length + ' acceptance criteria failed'); process.exit(1); }
|
|
456
|
+
console.log('PASS: All acceptance criteria met');
|
|
457
|
+
" "$MODULE_JSON"
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
IF this check FAILS:
|
|
461
|
+
- Identify which criteria failed from the output
|
|
462
|
+
- Fix the corresponding data (re-read from conversation context, re-generate, or re-run the step that produces it)
|
|
463
|
+
- Re-write to feature.json
|
|
464
|
+
- Re-run the POST-CHECK until PASS
|
|
465
|
+
|
|
466
|
+
> **WHY:** Step-03b now writes wireframes intermediately (since fix), but step-03d section 11 does the FULL write.
|
|
467
|
+
> This bash POST-CHECK is the ultimate safety net — it reads the REAL file and verifies ALL acceptance criteria.
|
|
468
|
+
> The pseudocode checks (section 9a) catch issues early; this bash check catches anything the model missed.
|
|
469
|
+
|
|
281
470
|
---
|
|
282
471
|
|
|
283
472
|
### 11-bis. Deploy Incremental Interactive HTML (MANDATORY)
|
|
@@ -194,16 +194,66 @@ See [references/handoff-file-templates.md](../references/handoff-file-templates.
|
|
|
194
194
|
|----------|--------|-----------|
|
|
195
195
|
| **4.1 Domain** | `analysis.entities[]` | Entities, ValueObjects, Enums, Exceptions |
|
|
196
196
|
| **4.2 Application** | `analysis.useCases[]` | Services, DTOs, Validators |
|
|
197
|
-
| **4.3 Infrastructure** | `analysis.entities[]` | EF Configurations, DbSet, DI |
|
|
197
|
+
| **4.3 Infrastructure** | `analysis.entities[]` | EF Configurations, DbSet, DI. **DEPENDENCY:** Each EF config task MUST `dependsOn` its corresponding domain entity task. |
|
|
198
198
|
| **4.4 API** | `specification.apiEndpoints[]` | Controllers with `{ContextShort}` mapping |
|
|
199
199
|
| **4.5 Frontend** | `specification.uiWireframes[]` | Pages, Components, Hooks + wireframe traceability |
|
|
200
200
|
| **4.6 SeedData** | `specification.seedDataCore` | 5 CORE + business + IClientSeedDataProvider |
|
|
201
201
|
| **4.7 Tests** | All layers | Unit, Integration, Security tests |
|
|
202
202
|
|
|
203
|
+
#### Route Convention (CRITICAL)
|
|
204
|
+
|
|
205
|
+
> **MANDATORY:** All NavigationModule/Section routes MUST use kebab-case transformation.
|
|
206
|
+
> See [references/naming-conventions.md](../references/naming-conventions.md) for complete guide.
|
|
207
|
+
|
|
208
|
+
**Problem:**
|
|
209
|
+
- Module codes in feature.json are **PascalCase** (e.g., `"HumanResources"`)
|
|
210
|
+
- Routes in seed data MUST be **kebab-case** (e.g., `"/business/human-resources"`)
|
|
211
|
+
- Failing to transform causes 404 when clicking menu items
|
|
212
|
+
|
|
213
|
+
**Solution:**
|
|
214
|
+
|
|
215
|
+
When generating `seedDataCore` for handoff, transform PascalCase codes to kebab-case routes:
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
// Transform module codes to kebab-case routes
|
|
219
|
+
function toKebabCase(code) {
|
|
220
|
+
return code
|
|
221
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
222
|
+
.toLowerCase();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Example usage in seedDataCore generation
|
|
226
|
+
const moduleCode = "TimeManagement"; // PascalCase (C#)
|
|
227
|
+
const appCode = "HumanResources"; // PascalCase (C#)
|
|
228
|
+
const contextCode = "business"; // lowercase
|
|
229
|
+
|
|
230
|
+
const route = `/${contextCode}/${toKebabCase(appCode)}/${toKebabCase(moduleCode)}`;
|
|
231
|
+
// Result: "/business/human-resources/time-management"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Include in task instructions:**
|
|
235
|
+
|
|
236
|
+
For NavigationSeedData tasks in `handoff.filesToCreate`, add:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"path": "Infrastructure/Persistence/Seeding/Data/{Module}/NavigationSeedData.cs",
|
|
241
|
+
"type": "SeedData",
|
|
242
|
+
"category": "core",
|
|
243
|
+
"instructions": "Generate NavigationModule seed data. CRITICAL: Use ToKebabCase() helper for Route property. Example: Route = ToKebabCase($\"/{context}/{app}/{module}\"). See core-seed-data.md template."
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Validation:**
|
|
248
|
+
- ralph-loop POST-CHECK validates routes after generation (see step-02-execute.md)
|
|
249
|
+
- MCP `validate_frontend_routes` detects case mismatches
|
|
250
|
+
- Convention documented in naming-conventions.md
|
|
251
|
+
|
|
203
252
|
**Critical rules:**
|
|
204
253
|
- All backend paths include `{ContextPascal}/{ApplicationName}/` hierarchy
|
|
205
254
|
- Frontend pages MUST have `linkedWireframes[]` + `wireframeAcceptanceCriteria`
|
|
206
255
|
- SeedData: 5 CORE entries ALWAYS + IClientSeedDataProvider for client projects
|
|
256
|
+
- **Acceptance Criteria Mapping:** Each task's `acceptanceCriteria` MUST be derived from its own `linkedFRs[]` entries (lookup FR → `acceptanceCriteria`). NEVER map by sequential FR index — use the task's explicit linkedFRs to resolve the correct criteria.
|
|
207
257
|
- Path convention: `Persistence/Seeding/Data/` (NEVER `Data/SeedData/`)
|
|
208
258
|
|
|
209
259
|
---
|
|
@@ -228,6 +278,52 @@ For each endpoint: operation, method, route, linkedUC, linkedFR, permissions, re
|
|
|
228
278
|
|
|
229
279
|
Total endpoints = count of specification.apiEndpoints[] across all modules.
|
|
230
280
|
|
|
281
|
+
### 6b. Frontend Task Splitting (MANDATORY)
|
|
282
|
+
|
|
283
|
+
> **CRITICAL:** Frontend MUST be split into discrete tasks, NOT bundled as a single mega-task.
|
|
284
|
+
> A single mega-task covering 5-8 files prevents granular error recovery in ralph-loop.
|
|
285
|
+
|
|
286
|
+
For each module, generate these SEPARATE frontend file entries (not one monolithic entry):
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
"frontend": [
|
|
290
|
+
{ "path": "src/pages/{Mod}/{ListPage}Page.tsx", "type": "Page", "linkedWireframes": ["{module}-list"], "module": "{moduleCode}" },
|
|
291
|
+
{ "path": "src/pages/{Mod}/{DetailPage}Page.tsx", "type": "Page", "linkedWireframes": ["{module}-detail"], "module": "{moduleCode}" },
|
|
292
|
+
{ "path": "src/hooks/use{Module}.ts", "type": "Hook", "module": "{moduleCode}" },
|
|
293
|
+
{ "path": "src/i18n/{module}.json", "type": "I18n", "module": "{moduleCode}" }
|
|
294
|
+
]
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Additional per-section pages (dashboard, import, etc.) are separate entries.
|
|
298
|
+
|
|
299
|
+
**Route wiring task (MANDATORY — separate entry):**
|
|
300
|
+
```json
|
|
301
|
+
{ "path": "src/App.tsx", "type": "RouteWiring", "module": "{moduleCode}", "description": "Wire {module} routes into App.tsx (detect pattern: contextRoutes array OR JSX Route wrappers). BLOCKING: pages without route wiring = blank pages." }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 6c. Cross-Module PRD (MANDATORY for multi-module features)
|
|
305
|
+
|
|
306
|
+
> **CRITICAL:** Cross-module integration tasks MUST exist in a dedicated PRD file.
|
|
307
|
+
> Without this, ralph-loop will NOT execute E2E tests or FK validation.
|
|
308
|
+
|
|
309
|
+
Generate `.ralph/prd-CrossModule.json` with:
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"$version": "3.0.0",
|
|
313
|
+
"implementation": {
|
|
314
|
+
"filesToCreate": {
|
|
315
|
+
"tests": [
|
|
316
|
+
{ "path": "src/Tests/Integration/CrossModule/ForeignKeyValidationTests.cs", "type": "IntegrationTests", "module": "CrossModule" },
|
|
317
|
+
{ "path": "src/Tests/Integration/CrossModule/PermissionMatrixTests.cs", "type": "SecurityTests", "module": "CrossModule" },
|
|
318
|
+
{ "path": "src/Tests/Integration/CrossModule/E2EFlowTests.cs", "type": "E2ETests", "module": "CrossModule" }
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Add to progress.txt after all module tasks.
|
|
326
|
+
|
|
231
327
|
---
|
|
232
328
|
|
|
233
329
|
### 7. Write Handoff to Feature.json
|
|
@@ -312,12 +408,48 @@ Total endpoints = count of specification.apiEndpoints[] across all modules.
|
|
|
312
408
|
```
|
|
313
409
|
// Derive seedDataCore from modules[], applicationRoles[], and coverageMatrix[]
|
|
314
410
|
|
|
411
|
+
// Helper: PascalCase → "Human Readable" (e.g., "HumanResources" → "Human Resources")
|
|
412
|
+
function toHumanReadable(pascalCase) {
|
|
413
|
+
return pascalCase
|
|
414
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
415
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const contextCode = master.metadata.context || "business";
|
|
419
|
+
const appCode = master.metadata.application;
|
|
420
|
+
const appLabel = toHumanReadable(appCode);
|
|
421
|
+
const appDesc = master.cadrage?.problem?.split('.')[0] || `Gestion ${appLabel}`;
|
|
422
|
+
const lang = master.metadata.language || "fr";
|
|
423
|
+
|
|
315
424
|
const seedDataCore = {
|
|
425
|
+
// Application-level navigation (MANDATORY — one entry per application)
|
|
426
|
+
navigationApplications: [{
|
|
427
|
+
code: appCode.toLowerCase(),
|
|
428
|
+
name: appCode,
|
|
429
|
+
labels: {
|
|
430
|
+
fr: lang === "fr" ? (master.cadrage?.applicationName || appLabel) : appLabel,
|
|
431
|
+
en: lang === "en" ? (master.cadrage?.applicationName || appLabel) : appLabel,
|
|
432
|
+
it: appLabel,
|
|
433
|
+
de: appLabel
|
|
434
|
+
},
|
|
435
|
+
description: {
|
|
436
|
+
fr: lang === "fr" ? appDesc : `Gestion ${appLabel}`,
|
|
437
|
+
en: lang === "en" ? appDesc : `${appLabel} management`,
|
|
438
|
+
it: `Gestione ${appLabel}`,
|
|
439
|
+
de: `${appLabel} Verwaltung`
|
|
440
|
+
},
|
|
441
|
+
icon: inferIconFromApplication(master) || "layout-grid",
|
|
442
|
+
iconType: "lucide",
|
|
443
|
+
context: contextCode,
|
|
444
|
+
route: `/${contextCode}/${toKebabCase(appCode)}`,
|
|
445
|
+
displayOrder: 1
|
|
446
|
+
}],
|
|
447
|
+
|
|
316
448
|
navigationModules: master.modules.map((m, i) => ({
|
|
317
449
|
code: m.code,
|
|
318
450
|
label: m.name || m.code,
|
|
319
451
|
description: m.description,
|
|
320
|
-
icon:
|
|
452
|
+
icon: inferIconFromModule(m) || "folder", // MUST be non-null — use sensible default
|
|
321
453
|
iconType: "lucide",
|
|
322
454
|
route: `/business/${master.metadata.application.toLowerCase()}/${m.code.toLowerCase()}`,
|
|
323
455
|
displayOrder: (i + 1) * 10
|
|
@@ -386,6 +518,49 @@ const seedDataCore = {
|
|
|
386
518
|
)
|
|
387
519
|
};
|
|
388
520
|
|
|
521
|
+
// Icon inference for APPLICATION level (NEVER leave as null)
|
|
522
|
+
function inferIconFromApplication(master) {
|
|
523
|
+
const appLC = (master.metadata.application || '').toLowerCase();
|
|
524
|
+
const iconMap = {
|
|
525
|
+
'humanresource': 'users', 'rh': 'users', 'hr': 'users', 'personnel': 'users',
|
|
526
|
+
'sales': 'shopping-cart', 'vente': 'shopping-cart', 'commerce': 'shopping-cart',
|
|
527
|
+
'finance': 'wallet', 'comptabilite': 'calculator', 'accounting': 'calculator',
|
|
528
|
+
'inventory': 'warehouse', 'stock': 'package', 'logistic': 'truck',
|
|
529
|
+
'crm': 'building-2', 'customer': 'building', 'client': 'building-2',
|
|
530
|
+
'project': 'folder-kanban', 'task': 'check-square', 'gestion': 'layout-grid',
|
|
531
|
+
'admin': 'shield', 'support': 'headphones', 'maintenance': 'wrench',
|
|
532
|
+
'production': 'factory', 'quality': 'badge-check', 'document': 'file-text',
|
|
533
|
+
'fleet': 'car', 'vehicle': 'car', 'transport': 'truck',
|
|
534
|
+
'education': 'graduation-cap', 'formation': 'graduation-cap', 'training': 'graduation-cap'
|
|
535
|
+
};
|
|
536
|
+
for (const [keyword, icon] of Object.entries(iconMap)) {
|
|
537
|
+
if (appLC.includes(keyword)) return icon;
|
|
538
|
+
}
|
|
539
|
+
return "layout-grid"; // fallback — NEVER null
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Icon inference from module context (NEVER leave as null)
|
|
543
|
+
function inferIconFromModule(module) {
|
|
544
|
+
const codeLC = module.code.toLowerCase();
|
|
545
|
+
const iconMap = {
|
|
546
|
+
'employee': 'users', 'staff': 'users', 'personnel': 'users', 'user': 'users',
|
|
547
|
+
'project': 'folder-kanban', 'task': 'check-square', 'work': 'briefcase',
|
|
548
|
+
'time': 'clock', 'schedule': 'calendar', 'planning': 'calendar-days',
|
|
549
|
+
'absence': 'calendar-off', 'leave': 'calendar-off', 'vacation': 'palm-tree',
|
|
550
|
+
'finance': 'wallet', 'billing': 'receipt', 'invoice': 'file-text',
|
|
551
|
+
'inventory': 'warehouse', 'stock': 'package', 'product': 'shopping-bag',
|
|
552
|
+
'customer': 'building', 'client': 'building-2', 'contact': 'contact',
|
|
553
|
+
'report': 'bar-chart', 'dashboard': 'layout-dashboard', 'analytics': 'trending-up',
|
|
554
|
+
'document': 'file-text', 'notification': 'bell', 'setting': 'settings',
|
|
555
|
+
'order': 'shopping-cart', 'vehicle': 'car', 'maintenance': 'wrench',
|
|
556
|
+
'audit': 'shield-check', 'workflow': 'git-branch', 'approval': 'check-circle'
|
|
557
|
+
};
|
|
558
|
+
for (const [keyword, icon] of Object.entries(iconMap)) {
|
|
559
|
+
if (codeLC.includes(keyword)) return icon;
|
|
560
|
+
}
|
|
561
|
+
return "folder"; // fallback — NEVER null
|
|
562
|
+
}
|
|
563
|
+
|
|
389
564
|
ba-writer.enrichSection({
|
|
390
565
|
featureId: {feature_id},
|
|
391
566
|
section: "seedDataCore",
|
|
@@ -393,12 +568,23 @@ ba-writer.enrichSection({
|
|
|
393
568
|
})
|
|
394
569
|
```
|
|
395
570
|
|
|
396
|
-
**POST-CHECK (
|
|
571
|
+
**POST-CHECK (BLOCKING for icons/application, warning for counts):**
|
|
397
572
|
```
|
|
573
|
+
IF !seedDataCore.navigationApplications || seedDataCore.navigationApplications.length === 0:
|
|
574
|
+
BLOCKING ERROR: "navigationApplications is empty — Application navigation will NOT be seeded. Menu will show Context → (nothing) → Modules."
|
|
575
|
+
IF seedDataCore.navigationApplications[0].icon === null || seedDataCore.navigationApplications[0].icon === undefined:
|
|
576
|
+
BLOCKING ERROR: "Application icon is null — ralph-loop will render empty navigation"
|
|
577
|
+
IF !seedDataCore.navigationApplications[0].labels?.fr || !seedDataCore.navigationApplications[0].labels?.en:
|
|
578
|
+
BLOCKING ERROR: "Application labels missing fr/en — translations will fail"
|
|
398
579
|
IF seedDataCore.navigationModules.length !== master.modules.length:
|
|
399
580
|
WARNING: seedDataCore has ${seedDataCore.navigationModules.length} nav modules but ${master.modules.length} modules exist
|
|
400
581
|
IF seedDataCore.permissions.length === 0:
|
|
401
582
|
WARNING: seedDataCore has 0 permissions — applicationRoles may be missing
|
|
583
|
+
IF seedDataCore.navigationModules.some(m => !m.icon || m.icon === null):
|
|
584
|
+
BLOCKING ERROR: "Navigation modules with null icons detected — ralph-loop will render empty navigation"
|
|
585
|
+
→ Fix: Re-run inferIconFromModule() for modules with null icons
|
|
586
|
+
IF seedDataCore.navigationSections.length === 0:
|
|
587
|
+
WARNING: "0 navigation sections — every module should have at least 1 section (e.g., 'list')"
|
|
402
588
|
```
|
|
403
589
|
|
|
404
590
|
#### 7c. Master Handoff (after ALL modules written + seedDataCore generated)
|
|
@@ -238,6 +238,34 @@ See [references/deploy-data-build.md](../references/deploy-data-build.md) for:
|
|
|
238
238
|
|
|
239
239
|
---
|
|
240
240
|
|
|
241
|
+
### 7-bis. Generate SVG Wireframes (Parallel Enrichment)
|
|
242
|
+
|
|
243
|
+
> **After EMBEDDED_ARTIFACTS is built (sections 5-7) but BEFORE writing the HTML (section 8).**
|
|
244
|
+
> This step enriches each ASCII wireframe with a professional SVG version for dual-view rendering.
|
|
245
|
+
|
|
246
|
+
See [references/wireframe-svg-style-guide.md](../references/wireframe-svg-style-guide.md) for the complete SVG style specification, prompt template, and orchestration process.
|
|
247
|
+
|
|
248
|
+
**Process summary:**
|
|
249
|
+
|
|
250
|
+
1. **Read** `references/wireframe-svg-style-guide.md` to get the prompt template
|
|
251
|
+
2. **Collect** all wireframes across ALL modules in `EMBEDDED_ARTIFACTS.wireframes` where `content` exists and `svgContent` is null
|
|
252
|
+
3. **Spawn parallel Task(sonnet) agents** — ONE per wireframe, ALL in a single message
|
|
253
|
+
4. **Collect and validate** results: strip markdown fences if present, verify SVG starts with `<svg` and contains `</svg>`
|
|
254
|
+
5. **Inject** valid SVGs into `EMBEDDED_ARTIFACTS.wireframes[moduleCode][index].svgContent`
|
|
255
|
+
6. **Display summary:**
|
|
256
|
+
```
|
|
257
|
+
SVG wireframe generation:
|
|
258
|
+
Total wireframes: {total}
|
|
259
|
+
SVG generated: {successCount}/{total}
|
|
260
|
+
Fallback (ASCII only): {failCount}/{total}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
> **CRITICAL:** This step is NEVER blocking. If all SVG generations fail, deployment
|
|
264
|
+
> continues with ASCII-only wireframes. The HTML renderer checks for `svgContent` before
|
|
265
|
+
> showing the SVG view. SVG is an enhancement, not a requirement.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
241
269
|
### 8. Write and Verify
|
|
242
270
|
|
|
243
271
|
1. Write the populated HTML to the output directory: `docs/business/{app}/business-analyse/v{version}/ba-interactive.html`
|
|
@@ -357,6 +385,33 @@ After writing the HTML file, verify:
|
|
|
357
385
|
BLOCKING_ERROR("Module mismatch: expected [${expectedModules}] but wireframes has [${wireframeModules}]")
|
|
358
386
|
```
|
|
359
387
|
|
|
388
|
+
9. **MODULE DATA COMPLETENESS** — every module in FEATURE_DATA.modules[] must carry ALL fields from feature.json
|
|
389
|
+
```
|
|
390
|
+
FOR each htmlModule in FEATURE_DATA.modules[]:
|
|
391
|
+
masterModule = master.modules.find(m => m.code === htmlModule.code)
|
|
392
|
+
|
|
393
|
+
// name MUST be display name, NOT code
|
|
394
|
+
IF htmlModule.name === htmlModule.code AND masterModule.name !== masterModule.code:
|
|
395
|
+
BLOCKING_ERROR("Module '${htmlModule.code}' has name === code. Expected: '${masterModule.name}'")
|
|
396
|
+
|
|
397
|
+
// anticipatedSections MUST be present (drives Structure tab + navigation tree)
|
|
398
|
+
IF masterModule.anticipatedSections?.length > 0 AND (!htmlModule.anticipatedSections OR htmlModule.anticipatedSections.length === 0):
|
|
399
|
+
BLOCKING_ERROR("Module '${htmlModule.code}' lost anticipatedSections: master has ${masterModule.anticipatedSections.length}, HTML has 0")
|
|
400
|
+
→ FIX: Copy anticipatedSections from master.modules[].anticipatedSections
|
|
401
|
+
|
|
402
|
+
// dependencies/dependents MUST be present (drives dependency graph)
|
|
403
|
+
IF masterModule.dependencies?.length > 0 AND (!htmlModule.dependencies OR htmlModule.dependencies.length === 0):
|
|
404
|
+
BLOCKING_ERROR("Module '${htmlModule.code}' lost dependencies array")
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Display verification table:
|
|
408
|
+
```
|
|
409
|
+
POST-CHECK: Module data completeness
|
|
410
|
+
| Module | name | sections | deps | wireframes | specs | Match |
|
|
411
|
+
|----------------|---------|----------|------|------------|-------|-------|
|
|
412
|
+
| {code} | {name} | {n} | {n} | {n} | OK | OK/FAIL |
|
|
413
|
+
```
|
|
414
|
+
|
|
360
415
|
**IF ANY CHECK FAILS → DO NOT PROCEED. Fix the data mapping and regenerate.**
|
|
361
416
|
|
|
362
417
|
---
|
|
@@ -304,7 +304,9 @@ fi
|
|
|
304
304
|
**Execution sequence (IN ORDER):**
|
|
305
305
|
1. `mcp__smartstack__scaffold_api_client` → API client + types + React Query hook
|
|
306
306
|
2. `mcp__smartstack__scaffold_routes` (with `outputFormat: "clientRoutes"`) → route registry + route fragments
|
|
307
|
-
3. **Wire routes to App.tsx
|
|
307
|
+
3. **Wire routes to App.tsx (detect pattern: `contextRoutes` array OR JSX `<Route>`):**
|
|
308
|
+
- **Pattern A** (`contextRoutes: ContextRouteExtensions` in App.tsx): add to `contextRoutes.{context}[]` with RELATIVE paths → auto-injected into both standard + tenant trees
|
|
309
|
+
- **Pattern B** (JSX `<Route>` in App.tsx): insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
|
|
308
310
|
4. Create pages using SmartStack components
|
|
309
311
|
5. Create preferences hook: `use{Module}Preferences.ts`
|
|
310
312
|
6. Generate i18n (4 languages: fr, en, it, de)
|
|
@@ -348,7 +350,8 @@ import axios from 'axios' → use @/services/api/apiClient
|
|
|
348
350
|
Only fr/en translations → MUST have 4 languages
|
|
349
351
|
src/pages/{Module}/ → MUST be src/pages/{Context}/{App}/{Module}/
|
|
350
352
|
Routes generated but NOT added to App.tsx → MUST wire routes to App.tsx after scaffold_routes
|
|
351
|
-
Routes in standard block only → MUST also add to /t/:slug/ tenant block
|
|
353
|
+
Routes in standard block only → MUST also add to /t/:slug/ tenant block (Pattern B only; Pattern A auto-handles)
|
|
354
|
+
Adding routes to clientRoutes[] instead of contextRoutes.{context}[] → MUST use contextRoutes for business/platform/personal routes
|
|
352
355
|
'00000000-0000-0000-0000-000000000000' → use dynamic ID from auth context or route params
|
|
353
356
|
const api = axios.create(...) → use @/services/api/apiClient (single instance)
|
|
354
357
|
useXxx with raw axios inside hooks → hooks MUST use the shared apiClient from @/services/api
|