@atlashub/smartstack-cli 3.23.0 → 3.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/mcp-entry.mjs +56 -15
  2. package/dist/mcp-entry.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/mcp-scaffolding/component.tsx.hbs +21 -1
  5. package/templates/skills/apex/references/smartstack-api.md +31 -5
  6. package/templates/skills/apex/references/smartstack-frontend.md +1081 -0
  7. package/templates/skills/apex/references/smartstack-layers.md +81 -5
  8. package/templates/skills/apex/steps/step-01-analyze.md +27 -3
  9. package/templates/skills/apex/steps/step-02-plan.md +5 -1
  10. package/templates/skills/apex/steps/step-03-execute.md +43 -3
  11. package/templates/skills/apex/steps/step-04-validate.md +159 -0
  12. package/templates/skills/apex/steps/step-05-examine.md +7 -0
  13. package/templates/skills/apex/steps/step-07-tests.md +19 -0
  14. package/templates/skills/business-analyse/_shared.md +6 -6
  15. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +1 -1
  16. package/templates/skills/business-analyse/questionnaire/07-ui.md +3 -3
  17. package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +1 -1
  18. package/templates/skills/business-analyse/references/entity-architecture-decision.md +3 -3
  19. package/templates/skills/business-analyse/references/handoff-file-templates.md +13 -5
  20. package/templates/skills/business-analyse/references/spec-auto-inference.md +14 -14
  21. package/templates/skills/business-analyse/steps/step-01-cadrage.md +2 -2
  22. package/templates/skills/business-analyse/steps/step-02-decomposition.md +1 -1
  23. package/templates/skills/business-analyse/steps/step-03a1-setup.md +2 -2
  24. package/templates/skills/business-analyse/steps/step-03b-ui.md +2 -1
  25. package/templates/skills/business-analyse/steps/step-05a-handoff.md +15 -4
  26. package/templates/skills/business-analyse/templates/tpl-frd.md +2 -2
  27. package/templates/skills/business-analyse/templates-frd.md +2 -2
  28. package/templates/skills/ralph-loop/references/category-rules.md +45 -7
  29. package/templates/skills/ralph-loop/references/compact-loop.md +2 -2
  30. package/templates/skills/ralph-loop/references/core-seed-data.md +10 -0
  31. package/templates/skills/ralph-loop/steps/step-02-execute.md +110 -1
  32. package/templates/skills/validate-feature/steps/step-05-db-validation.md +86 -1
@@ -9,7 +9,7 @@
9
9
  | string + unique | text | yes | yes | clickAction: navigate:detail |
10
10
  | string | text | yes | yes | — |
11
11
  | enum / status | badge | yes | yes (multi-select) | colorMap from lifeCycles if exists |
12
- | FK:Entity | text (join .name) | yes | yes (entity-select) | Display related entity name |
12
+ | FK:Entity | text (join .name) | yes | yes (EntityLookup) | Display related entity name, filter via searchable EntityLookup |
13
13
  | decimal | currency | yes | no | — |
14
14
  | int | number | yes | no | — |
15
15
  | datetime | date-relative | yes | yes (date-range) | — |
@@ -23,7 +23,7 @@
23
23
  | string | Input | entity.required | — |
24
24
  | string (multiline) | TextArea | entity.required | rows: 4 |
25
25
  | enum | Select | entity.required | source: enum name |
26
- | FK:Entity | EntitySelect | entity.required | source: target entity, searchable |
26
+ | FK:Entity | EntityLookup | entity.required | source: target entity, searchable. Component: `@/components/ui/EntityLookup`. NEVER plain text input for FK Guid fields. Backend API MUST support `?search=` param. See `smartstack-frontend.md` section 6. |
27
27
  | decimal | NumberInput | entity.required | — |
28
28
  | int | NumberInput | entity.required | — |
29
29
  | datetime | DatePicker | entity.required | — |
@@ -31,22 +31,22 @@
31
31
 
32
32
  ## Auto-Generated UI Components by featureType
33
33
 
34
- > **RULE:** Sections = functional zones only. `create`/`edit` are actions within `list`. `detail` is a tabbed page reached from `list`.
34
+ > **RULE:** Sections = functional zones only. `create`/`edit` are separate pages with own URL routes (`/create` and `/:id/edit`). `detail` is a tabbed page reached from `list`.
35
35
 
36
- | featureType | Sections (functional zones) | List page includes | Detail page tabs |
37
- |---|---|---|---|
38
- | data-centric | list | grid, filters, create form (modal) | Infos, {relations} |
39
- | workflow | list, approve | grid, filters, create form (modal) | Infos, {relations}, Historique |
40
- | integration | list | grid, filters, config form | Infos, Config, Logs |
41
- | reporting | dashboard | — | — |
42
- | full-module | list, dashboard | grid, filters, create form (modal) | Infos, {relations}, Historique |
36
+ | featureType | Sections (functional zones) | List page includes | Form pages | Detail page tabs |
37
+ |---|---|---|---|---|
38
+ | data-centric | list | grid, filters, create button | `/create` page, `/:id/edit` page | Infos, {relations} |
39
+ | workflow | list, approve | grid, filters, create button | `/create` page, `/:id/edit` page | Infos, {relations}, Historique |
40
+ | integration | list | grid, filters, config button | `/create` page, `/:id/edit` page | Infos, Config, Logs |
41
+ | reporting | dashboard | — | — | — |
42
+ | full-module | list, dashboard | grid, filters, create button | `/create` page, `/:id/edit` page | Infos, {relations}, Historique |
43
43
 
44
44
  ## Component Generation Rules
45
45
 
46
- 1. **list section:** SmartTable with all non-long-text attributes as columns, actions = [view, edit, delete], defaultSort = { createdAt, desc }. Create action opens SmartForm in modal/drawer.
47
- 2. **create form (in list):** SmartForm with all writable attributes as fields, component inferred from type. Opens as modal/drawer from the list page.
48
- 3. **detail page (from list click):** TabPanel with: Info tab (all attributes), relation tabs (child SmartTable for each 1:N relationship), History tab (if auditable). See "Detail Page Tab Auto-Inference" below for exact rules.
49
- 4. **edit (in detail):** Same form as create but pre-filled (mode: edit). Inline in the detail page Info tab.
46
+ 1. **list section:** SmartTable with all non-long-text attributes as columns, actions = [view, edit, delete], defaultSort = { createdAt, desc }. Create button navigates to `/create` page. Edit action navigates to `/:id/edit` page.
47
+ 2. **create page (`/create` route):** SmartForm (full page) with all writable attributes as fields, component inferred from type. Back button + form + submit. NEVER a modal.
48
+ 3. **edit page (`/:id/edit` route):** Same form as create but pre-filled with entity data. Full page with back button. NEVER a modal or inline toggle.
49
+ 4. **detail page (from list click):** TabPanel with: Info tab (all attributes read-only + edit button navigating to `/:id/edit`), relation tabs (child SmartTable for each 1:N relationship), History tab (if auditable). See "Detail Page Tab Auto-Inference" below for exact rules.
50
50
  5. **dashboard section:** Trigger 3d (dashboard specification)
51
51
 
52
52
  ## Detail Page Tab Auto-Inference
@@ -136,7 +136,7 @@ pre_analysis:
136
136
 
137
137
  **Modules identifiés :**
138
138
  - **{Module 1}** : {functional description}
139
- - Section **list** : {entity}-grid, {entity}-filters, {entity}-form (création en modale)
139
+ - Section **list** : {entity}-grid, {entity}-filters + pages `/create` et `/:id/edit`
140
140
  - Page détail (onglets) : {Infos, {relation_tabs}...}
141
141
  - {IF dashboard needed:} Section **dashboard** : {kpi-cards, charts...}
142
142
  - **{Module 2}** : {functional description}
@@ -601,7 +601,7 @@ BEFORE transitioning to step-02:
601
601
  f. **List detail page tabs** — for entities accessible via click from `list`
602
602
 
603
603
  > **SECTION RULE:** Sections are functional zones (list, dashboard, approve, import, rapport...).
604
- > `create` and `edit` are ACTIONS within the `list` page (modal/drawer).
604
+ > `create` and `edit` are SEPARATE PAGES with their own URL routes (`/create` and `/:id/edit`).
605
605
  > `detail` is a page with tabs accessible by clicking a row in `list`, NOT a standalone section.
606
606
 
607
607
  4. **OUTPUT the matrix as text** (do NOT put it inside AskUserQuestion — it won't render):
@@ -73,7 +73,7 @@ For each mustHave/shouldHave scope item:
73
73
  > "entities": ["Anticipated entity names"],
74
74
  > "estimatedComplexity": "simple|medium|complex",
75
75
  > "anticipatedSections": [
76
- > { "code": "list", "description": "Main page: grid, create action (modal), click to detail", "resources": ["entity-grid", "entity-filters", "entity-form"] },
76
+ > { "code": "list", "description": "Main page: grid, create button (navigates to /create page), click to detail", "resources": ["entity-grid", "entity-filters", "entity-create-page", "entity-edit-page"] },
77
77
  > { "code": "dashboard", "description": "Module KPIs and overview (if needed)", "resources": ["entity-kpi-cards", "entity-charts"] }
78
78
  > ],
79
79
  > "detailTabs": ["Informations", "RelatedEntity1", "Historique"]
@@ -200,7 +200,7 @@ Total: {N} requirements mapped to this module
200
200
  For each module, propose standard sections based on module type:
201
201
 
202
202
  > **RULE:** Sections = functional zones only. `list` is ALWAYS present (default).
203
- > `create`/`edit` = actions within the `list` page (modal/drawer). `detail` = tabbed page from list click.
203
+ > `create`/`edit` = separate pages with own URL routes (`/create` and `/:id/edit`). `detail` = tabbed page from list click.
204
204
 
205
205
  | Module Type | Sections (functional zones) | Detail page tabs (from list click) |
206
206
  |-------------|---------------------------|-----------------------------------|
@@ -291,7 +291,7 @@ See [references/spec-auto-inference.md](../references/spec-auto-inference.md) fo
291
291
  - Entity attribute → SmartTable column mapping (9 type rules)
292
292
  - Entity attribute → SmartForm field mapping (8 type rules)
293
293
  - Auto-generated sections per featureType (5 types)
294
- - Component generation rules (list section, create form in modal, detail page with tabs, dashboard section)
294
+ - Component generation rules (list section, create/edit form pages with own routes, detail page with tabs, dashboard section)
295
295
  - Status/lifecycle enhancement rules
296
296
 
297
297
  Write auto-generated sections to `specification.sections[]` via ba-writer.enrichSection()
@@ -319,7 +319,8 @@ For each section, identify what resources/components are needed:
319
319
 
320
320
  - List section → DataGrid, FilterBar, SearchInput, ExportButton, CreateButton
321
321
  - Detail page (from list click) → BackButton, DetailHeader, StatusBadge, TabPanel, DetailCard, SmartForm (edit mode), SmartTable (relation tabs), Timeline (history tab)
322
- - Create (in list) → SmartForm (modal/drawer), ValidationMessages, SubmitButton
322
+ - Create page (`/create` route) → SmartForm (full page), ValidationMessages, SubmitButton, BackButton
323
+ - Edit page (`/:id/edit` route) → SmartForm (full page, pre-filled), ValidationMessages, SubmitButton, BackButton
323
324
  - Approve section → StatusTransitionPanel, CommentBox, ApproveRejectButtons
324
325
  - Dashboard section → StatCard, RechartsChart (Bar/Line/Pie/Area), DashboardGrid, FilterBar
325
326
 
@@ -287,13 +287,24 @@ For each module, generate these SEPARATE frontend file entries (not one monolith
287
287
 
288
288
  ```json
289
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}" },
290
+ { "path": "src/pages/{Context}/{App}/{Mod}/{ListPage}Page.tsx", "type": "Page", "linkedWireframes": ["{module}-list"], "module": "{moduleCode}", "skill": "/ui-components" },
291
+ { "path": "src/pages/{Context}/{App}/{Mod}/{Entity}CreatePage.tsx", "type": "Page", "linkedWireframes": ["{module}-create"], "module": "{moduleCode}", "skill": "/ui-components", "fkFields": ["EmployeeId→Employee", "DepartmentId→Department"] },
292
+ { "path": "src/pages/{Context}/{App}/{Mod}/{Entity}EditPage.tsx", "type": "Page", "linkedWireframes": ["{module}-edit"], "module": "{moduleCode}", "skill": "/ui-components", "fkFields": ["EmployeeId→Employee", "DepartmentId→Department"] },
293
+ { "path": "src/pages/{Context}/{App}/{Mod}/{DetailPage}Page.tsx", "type": "Page", "linkedWireframes": ["{module}-detail"], "module": "{moduleCode}", "skill": "/ui-components" },
292
294
  { "path": "src/hooks/use{Module}.ts", "type": "Hook", "module": "{moduleCode}" },
293
- { "path": "src/i18n/{module}.json", "type": "I18n", "module": "{moduleCode}" }
295
+ { "path": "src/i18n/locales/fr/{moduleLower}.json", "type": "I18n", "language": "fr", "module": "{moduleCode}" },
296
+ { "path": "src/i18n/locales/en/{moduleLower}.json", "type": "I18n", "language": "en", "module": "{moduleCode}" },
297
+ { "path": "src/i18n/locales/it/{moduleLower}.json", "type": "I18n", "language": "it", "module": "{moduleCode}" },
298
+ { "path": "src/i18n/locales/de/{moduleLower}.json", "type": "I18n", "language": "de", "module": "{moduleCode}" }
294
299
  ]
295
300
  ```
296
301
 
302
+ **FK fields in forms:** Create/Edit page entries MUST include `"fkFields"` array listing FK properties and their target entities (format: `"PropertyId→TargetEntity"`). This tells ralph-loop to use `EntityLookup` component instead of plain text inputs. Derive FK fields from `analysis.entities[].relationships[]` where `type = "N:1"` or from properties ending in `Id` with a navigation property. See `smartstack-frontend.md` section 6 for the EntityLookup pattern.
303
+
304
+ **Page generation:** ALL pages MUST use `/ui-components` skill (indicated by `"skill"` field). NEVER write raw TSX without the skill patterns.
305
+
306
+ **I18n generation:** 4 JSON files per module with identical key structures. `{moduleLower}` = lowercase module code. Keys: actions, labels, columns, form, errors, validation, messages, empty. See `smartstack-frontend.md` template.
307
+
297
308
  Additional per-section pages (dashboard, import, etc.) are separate entries.
298
309
 
299
310
  **Route wiring task (MANDATORY — separate entry):**
@@ -344,7 +355,7 @@ Add to progress.txt after all module tasks.
344
355
  > "application": [{"path": "src/Application/Services/{Ctx}/{App}/{Mod}/{Svc}Service.cs", "type": "Service|Dto|Validator", "linkedFRs": [], "linkedUCs": [], "module": "..."}],
345
356
  > "infrastructure": [{"path": "src/Infrastructure/Persistence/Configurations/{Ctx}/{App}/{Mod}/{Entity}Configuration.cs", "type": "EFConfiguration", "linkedFRs": [], "module": "..."}],
346
357
  > "api": [{"path": "src/API/Controllers/{CtxShort}/{App}/{Entity}Controller.cs", "type": "ApiController", "linkedUCs": [], "linkedFRs": [], "module": "..."}],
347
- > "frontend": [{"path": "src/pages/{Mod}/{Page}Page.tsx", "type": "Page|Component|Hook|DashboardPage", "linkedUCs": [], "linkedWireframes": [], "module": "..."}],
358
+ > "frontend": [{"path": "src/pages/{Ctx}/{App}/{Mod}/{Page}Page.tsx", "type": "Page|Component|Hook|DashboardPage|I18n", "linkedUCs": [], "linkedWireframes": [], "module": "...", "skill": "/ui-components"}],
348
359
  > "seedData": [{"path": "src/Infrastructure/Persistence/Seeding/Data/{Mod}/...SeedData.cs", "type": "SeedData", "category": "core|business", "source": "...", "module": "..."}],
349
360
  > "tests": [{"path": "src/Tests/Unit/Domain/{Ctx}/{App}/{Mod}/{Entity}Tests.cs", "type": "UnitTests|IntegrationTests|SecurityTests", "linkedFRs": [], "linkedUCs": [], "module": "..."}]
350
361
  > },
@@ -100,8 +100,8 @@
100
100
 
101
101
  | Element | Type | Behavior | Permission |
102
102
  |---------|------|----------|------------|
103
- | [+ New] | Button | Opens create modal | `{module}.create` |
104
- | [E] | Icon | Opens edit modal | `{module}.update` |
103
+ | [+ New] | Button | Navigates to `/create` page | `{module}.create` |
104
+ | [E] | Icon | Navigates to `/:id/edit` page | `{module}.update` |
105
105
  | [D] | Icon | Confirmation + delete | `{module}.delete` |
106
106
 
107
107
  ## 5. Validation and Messages
@@ -272,8 +272,8 @@
272
272
 
273
273
  | Element | Type | Behavior | Permission |
274
274
  |---------|------|----------|------------|
275
- | [+ New] | Button | Opens create modal | `{module}.create` |
276
- | [E] | Icon | Opens edit modal | `{module}.update` |
275
+ | [+ New] | Button | Navigates to `/create` page | `{module}.create` |
276
+ | [E] | Icon | Navigates to `/:id/edit` page | `{module}.update` |
277
277
  | [D] | Icon | Confirmation + delete | `{module}.delete` |
278
278
 
279
279
  ## 5. Validation and Messages
@@ -281,6 +281,8 @@ fi
281
281
  - Swagger XML documentation
282
282
  - Consistent route patterns: `api/{context}/{app}/{module}`
283
283
  - Return DTOs, never domain entities
284
+ - **ALL GetAll endpoints MUST accept `?search=` query parameter** (enables EntityLookup on frontend)
285
+ - **GetAll returns paginated results:** `PaginatedResult<T>` with items, totalCount, page, pageSize
284
286
 
285
287
  **Controller coverage (BLOCKING):**
286
288
  - EVERY entity in the module MUST have a controller with CRUD endpoints (GET list, GET by id, POST, PUT, DELETE)
@@ -307,18 +309,28 @@ fi
307
309
  3. **Wire routes to App.tsx (detect pattern: `contextRoutes` array OR JSX `<Route>`):**
308
310
  - **Pattern A** (`contextRoutes: ContextRouteExtensions` in App.tsx): add to `contextRoutes.{context}[]` with RELATIVE paths → auto-injected into both standard + tenant trees
309
311
  - **Pattern B** (JSX `<Route>` in App.tsx): insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
310
- 4. Create pages using SmartStack components
312
+ 4. Create pages using **`/ui-components` skill** (MANDATORY for entity lists, grids, tables, dashboards, charts). The skill ensures CSS variables, EntityCard, SmartTable, loading/error/empty states, and responsive grid patterns.
311
313
  5. Create preferences hook: `use{Module}Preferences.ts`
312
- 6. Generate i18n (4 languages: fr, en, it, de)
314
+ 6. Generate i18n (4 languages: fr, en, it, de) — see I18n section below for detailed rules
313
315
  7. `npm run typecheck` MUST pass (BLOCKING)
314
316
 
315
317
  **Components:**
316
318
  - Lists: `SmartTable` + `SmartFilter` (NOT HTML `<table>`)
317
319
  - Grids: `EntityCard` (NOT custom `<div>` cards)
318
320
  - Detail: `EntityDetailCard`, `StatusBadge`, tab layout
319
- - Forms: `SmartForm` with FluentValidation-backed fields
321
+ - Forms: `SmartForm` with FluentValidation-backed fields — **MUST be full pages, NEVER modals**
322
+ - FK fields: `EntityLookup` for searchable entity selection (NEVER plain text for Guid FK)
320
323
  - Dashboard: `StatCard`, Recharts components
321
324
 
325
+ **Form pages (CRITICAL — ZERO modals/popups/drawers):**
326
+ - Create form: `EntityCreatePage.tsx` with route `/{module}/create`
327
+ - Edit form: `EntityEditPage.tsx` with route `/{module}/:id/edit`
328
+ - ALL forms are full pages with their own URL — NEVER Modal/Dialog/Drawer/Popup
329
+ - Back button with `navigate(-1)` on every form page
330
+ - Use React.lazy() + `<Suspense fallback={<PageLoader />}>` for form page imports
331
+ - **Form tests (MANDATORY):** Co-located `EntityCreatePage.test.tsx` and `EntityEditPage.test.tsx`
332
+ → Cover: rendering, validation, submit, pre-fill (edit), navigation, error handling
333
+
322
334
  **Layout wrapper mapping:**
323
335
 
324
336
  | Context | Layout | Route path |
@@ -355,17 +367,43 @@ Adding routes to clientRoutes[] instead of contextRoutes.{context}[] → MUST us
355
367
  '00000000-0000-0000-0000-000000000000' → use dynamic ID from auth context or route params
356
368
  const api = axios.create(...) → use @/services/api/apiClient (single instance)
357
369
  useXxx with raw axios inside hooks → hooks MUST use the shared apiClient from @/services/api
370
+ <input type="text" value={...employeeId} → FK Guid fields MUST use EntityLookup (searchable select)
371
+ placeholder="Enter ID" / "Enter GUID" → EntityLookup provides search-based entity selection
372
+ <Modal>/<Dialog>/<Drawer>/<Popup> for forms → forms are FULL PAGES with own URL routes (/create, /:id/edit)
373
+ useState(showCreateModal/editDialog) → navigate to form page, NEVER toggle modal visibility
358
374
  ```
359
375
 
360
376
  ---
361
377
 
362
- ## I18n
378
+ ## I18n (MANDATORY for frontend)
379
+
380
+ > **CRITICAL:** Every frontend module MUST have 4 translation files. Missing i18n = broken UI (untranslated labels, empty buttons).
381
+
382
+ **File path:** `src/i18n/locales/{lang}/{moduleLower}.json` (4 files: fr, en, it, de)
383
+
384
+ **JSON key structure (identical across all 4 languages):**
385
+ ```json
386
+ {
387
+ "actions": { "create": "...", "edit": "...", "delete": "...", "save": "...", "cancel": "...", "search": "...", "filter": "...", "export": "...", "refresh": "..." },
388
+ "labels": { "title": "...", "description": "...", "status": "...", "createdAt": "...", "updatedAt": "..." },
389
+ "columns": { "name": "...", "status": "...", "date": "...", "actions": "..." },
390
+ "form": { "name": "...", "description": "...", "submit": "...", "required": "..." },
391
+ "errors": { "loadFailed": "...", "saveFailed": "...", "deleteFailed": "...", "notFound": "..." },
392
+ "validation": { "required": "...", "minLength": "...", "maxLength": "...", "invalid": "..." },
393
+ "messages": { "created": "...", "updated": "...", "deleted": "...", "confirmDelete": "..." },
394
+ "empty": { "title": "...", "description": "...", "action": "..." }
395
+ }
396
+ ```
397
+
398
+ **Usage pattern in TSX:** `t('{moduleLower}:actions.create', 'Create')` — ALWAYS namespace prefix + fallback value.
363
399
 
364
400
  **Rules:**
365
- - 4 JSON files: fr, en, it, de
366
- - Identical key structures across all languages
401
+ - 4 JSON files: fr, en, it, de — ALL mandatory (not just fr/en)
402
+ - Identical key structures across all languages — same keys, translated values
367
403
  - All UI labels, validation messages, button text, empty states
368
- - Depends on frontend task completion
404
+ - Depends on frontend page completion (pages define which keys are needed)
405
+ - Add entity-specific keys under `labels`, `columns`, `form` for each entity field
406
+ - Reference `smartstack-frontend.md` for the complete template
369
407
 
370
408
  ---
371
409
 
@@ -99,8 +99,8 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
99
99
  | `infrastructure` with migration | `Migrations/` folder has .cs files | No migration files |
100
100
  | `infrastructure` with seed data | Seed data .cs files exist | 0 files created |
101
101
  | `application` | Service + DTO + Validator files exist | 0 files created |
102
- | `api` | Controller .cs files exist | 0 files created |
103
- | `frontend` | .tsx page files exist | 0 files created |
102
+ | `api` | Controller .cs files exist + GetAll supports `?search=` | 0 files created OR GetAll without search param |
103
+ | `frontend` | .tsx page files exist + FK fields use EntityLookup (not plain text) | 0 files created OR FK Guid field as `<input type="text">` |
104
104
  | `test` | Test project dir exists AND contains test .cs files | No test project or 0 test files |
105
105
  | `validation` | `dotnet test` exit 0 AND `dotnet build` exit 0 | Either command fails |
106
106
 
@@ -1253,3 +1253,13 @@ public class {Module}DevDataSeeder : IDevDataSeeder
1253
1253
  - Using `Guid.NewGuid()` for TenantId
1254
1254
  - Omitting idempotency check (`AnyAsync`)
1255
1255
  - Hardcoding TenantId inline (use `SeedConstants.DefaultTenantId`)
1256
+
1257
+ ### CROSS-SCHEMA FK WARNING
1258
+
1259
+ > `SeedConstants.DefaultTenantId` MUST reference a tenant that EXISTS in `core.tenant_Tenants`.
1260
+ > The SmartStack platform seeds a default tenant during `InitializeSmartStackAsync()`.
1261
+ > If you use a custom GUID, ensure it is created BEFORE `DevDataSeeder` runs (Order >= 200).
1262
+ >
1263
+ > **Pipeline validation:**
1264
+ > - ralph-loop POST-CHECK warns if GUID not found in project config
1265
+ > - validate-feature step-05 verifies FK exists in real database via SQL query
@@ -63,7 +63,7 @@ Key category triggers:
63
63
  |----------|---------------|
64
64
  | `infrastructure` with `_migrationMeta` | Migration sequence: MCP suggest → add → update → build |
65
65
  | `infrastructure` with seed data keywords | **MANDATORY**: Read `references/core-seed-data.md` |
66
- | `frontend` | MCP-first: scaffold_api_client → scaffold_routes → pages |
66
+ | `frontend` | MCP-first: scaffold_api_client → scaffold_routes → `/ui-components` skill (pages) → form pages (create/edit = full pages, ZERO modals) → form tests → i18n (4 langues) |
67
67
  | `test` | Generate via MCP → run → fix loop → 100% pass required |
68
68
  | `validation` | Clean build → full test suite → MCP validate |
69
69
 
@@ -164,6 +164,43 @@ fi
164
164
  - If `auth_Permissions` is empty → all authorization checks reject → 403 on every endpoint
165
165
  - These are **foundational** — without them, ALL subsequent features (frontend, API, tests) are useless
166
166
 
167
+ **POST-CHECK: DefaultTenantId cross-schema FK validation (WARNING)**
168
+
169
+ After generating SeedConstants.cs and DevDataSeeder, verify that `DefaultTenantId` is not a phantom GUID that doesn't exist in `core.tenant_Tenants`:
170
+
171
+ ```bash
172
+ # Find SeedConstants.cs
173
+ SEED_CONSTANTS=$(find . -path "*/SeedConstants.cs" 2>/dev/null | head -1)
174
+ if [ -n "$SEED_CONSTANTS" ]; then
175
+ # Extract the DefaultTenantId GUID value
176
+ TENANT_GUID=$(grep -oP 'DefaultTenantId\s*=\s*Guid\.Parse\("([^"]+)"\)' "$SEED_CONSTANTS" | grep -oP '"[^"]+"' | tr -d '"')
177
+
178
+ if [ -z "$TENANT_GUID" ]; then
179
+ echo "⚠️ WARNING: SeedConstants.cs found but DefaultTenantId not defined"
180
+ echo "DevDataSeeder requires SeedConstants.DefaultTenantId for business seed data"
181
+ echo "Fix: Add 'public static readonly Guid DefaultTenantId = Guid.Parse(\"...\");' to SeedConstants.cs"
182
+ else
183
+ # Verify the GUID is referenced somewhere in platform config (not an invented value)
184
+ GUID_FOUND=$(grep -r "$TENANT_GUID" appsettings*.json src/ 2>/dev/null | grep -v SeedConstants)
185
+
186
+ if [ -z "$GUID_FOUND" ]; then
187
+ echo "⚠️ WARNING: SeedConstants.DefaultTenantId ($TENANT_GUID) not found in platform config"
188
+ echo "This GUID must exist in core.tenant_Tenants at runtime"
189
+ echo "Verify: The SmartStack platform seeds this tenant during InitializeSmartStackAsync()"
190
+ echo "If using a custom TenantId, ensure it's created before DevDataSeeder runs (Order >= 200)"
191
+ echo "validate-feature step-05 will verify this FK exists in real database"
192
+ else
193
+ echo "✓ SeedConstants.DefaultTenantId ($TENANT_GUID) found in platform config"
194
+ fi
195
+ fi
196
+ fi
197
+ ```
198
+
199
+ **Why this matters:**
200
+ - If `DefaultTenantId` references a GUID not in `core.tenant_Tenants`, DevDataSeeder inserts orphan rows
201
+ - Business entities with invalid `TenantId` FK cause runtime 500 errors
202
+ - The tenant must be seeded by `InitializeSmartStackAsync()` BEFORE DevDataSeeder (Order >= 200) runs
203
+
167
204
  ### 6. Validate with MCP
168
205
 
169
206
  ```
@@ -206,6 +243,78 @@ npm run lint # ESLint — MUST exit 0
206
243
  ```
207
244
  If FAIL → read errors → fix TSX/TS files → re-run → loop until pass.
208
245
 
246
+ **Frontend POST-CHECKs (BLOCKING for frontend category):**
247
+ ```bash
248
+ # POST-CHECK: Forms must be full pages — ZERO modals/popups/drawers
249
+ PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null)
250
+ if [ -n "$PAGE_FILES" ]; then
251
+ MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet)" $PAGE_FILES 2>/dev/null)
252
+ if [ -n "$MODAL_IMPORTS" ]; then
253
+ echo "BLOCKING: Form pages must NOT use Modal/Dialog/Drawer/Popup components"
254
+ echo "Create/Edit forms MUST be full pages with own URL routes (/create, /:id/edit)"
255
+ echo "$MODAL_IMPORTS"
256
+ exit 1
257
+ fi
258
+ fi
259
+
260
+ # POST-CHECK: Create/Edit pages must exist for each module with a list page
261
+ LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
262
+ if [ -n "$LIST_PAGES" ]; then
263
+ for LP in $LIST_PAGES; do
264
+ DIR=$(dirname "$LP"); MOD=$(basename "$DIR")
265
+ if [ -z "$(find "$DIR" -name "*CreatePage.tsx" 2>/dev/null)" ]; then
266
+ echo "WARNING: Module $MOD has list page but no CreatePage"
267
+ fi
268
+ if [ -z "$(find "$DIR" -name "*EditPage.tsx" 2>/dev/null)" ]; then
269
+ echo "WARNING: Module $MOD has list page but no EditPage"
270
+ fi
271
+ done
272
+ fi
273
+
274
+ # POST-CHECK: Form pages must have companion test files
275
+ FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test)
276
+ if [ -n "$FORM_PAGES" ]; then
277
+ for FP in $FORM_PAGES; do
278
+ TEST="${FP%.tsx}.test.tsx"
279
+ if [ ! -f "$TEST" ]; then
280
+ echo "BLOCKING: Form page missing test file: $FP → expected $TEST"
281
+ exit 1
282
+ fi
283
+ done
284
+ fi
285
+
286
+ # POST-CHECK: FK fields must NOT be plain text inputs — use EntityLookup
287
+ FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test | grep -v node_modules)
288
+ if [ -n "$FORM_PAGES" ]; then
289
+ FK_TEXT_INPUTS=$(grep -Pn 'type=["\x27]text["\x27].*[a-z]+Id|value=\{[^}]*\.[a-z]+Id\}.*type=["\x27]text["\x27]' $FORM_PAGES 2>/dev/null)
290
+ if [ -n "$FK_TEXT_INPUTS" ]; then
291
+ echo "BLOCKING: FK Guid fields rendered as plain text inputs — MUST use EntityLookup"
292
+ echo "See smartstack-frontend.md section 6 for the EntityLookup pattern"
293
+ echo "$FK_TEXT_INPUTS"
294
+ exit 1
295
+ fi
296
+ FK_PLACEHOLDER=$(grep -Pn 'placeholder=["\x27].*[Ee]nter.*[Ii][Dd]|placeholder=["\x27].*[Gg][Uu][Ii][Dd]' $FORM_PAGES 2>/dev/null)
297
+ if [ -n "$FK_PLACEHOLDER" ]; then
298
+ echo "BLOCKING: Form placeholder asks user to enter ID/GUID — use EntityLookup instead"
299
+ echo "$FK_PLACEHOLDER"
300
+ exit 1
301
+ fi
302
+ fi
303
+
304
+ # POST-CHECK: Backend GetAll endpoints must support ?search= for EntityLookup
305
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
306
+ if [ -n "$CTRL_FILES" ]; then
307
+ for f in $CTRL_FILES; do
308
+ if grep -q "\[HttpGet\]" "$f" && grep -q "GetAll" "$f"; then
309
+ if ! grep -q "search" "$f"; then
310
+ echo "WARNING: Controller missing search parameter on GetAll: $f"
311
+ echo "GetAll MUST accept ?search= to enable EntityLookup on frontend"
312
+ fi
313
+ fi
314
+ done
315
+ fi
316
+ ```
317
+
209
318
  **Error resolution cycle (ALL categories):**
210
319
  1. Read the FULL error output (not just first line)
211
320
  2. Identify root cause: file path + line number
@@ -132,6 +132,65 @@ fi
132
132
 
133
133
  ---
134
134
 
135
+ ## 5b. Verify Dev Seeding Configuration
136
+
137
+ Before checking seed data, verify that dev seeding is actually enabled:
138
+
139
+ ```bash
140
+ # Check 1: EnableDevSeeding in appsettings
141
+ APPSETTINGS_DEV=$(ls appsettings.Development.json 2>/dev/null || ls src/*Api*/appsettings.Development.json 2>/dev/null)
142
+ APPSETTINGS_BASE=$(ls appsettings.json 2>/dev/null || ls src/*Api*/appsettings.json 2>/dev/null)
143
+
144
+ SEEDING_EXPLICIT_TRUE=false
145
+ SEEDING_EXPLICIT_FALSE=false
146
+
147
+ # Check Development appsettings first (overrides base)
148
+ if [ -n "$APPSETTINGS_DEV" ]; then
149
+ if grep -q '"EnableDevSeeding".*true' "$APPSETTINGS_DEV" 2>/dev/null; then
150
+ SEEDING_EXPLICIT_TRUE=true
151
+ fi
152
+ fi
153
+
154
+ # Check base appsettings
155
+ if [ -n "$APPSETTINGS_BASE" ] && [ "$SEEDING_EXPLICIT_TRUE" = "false" ]; then
156
+ if grep -q '"EnableDevSeeding".*false' "$APPSETTINGS_BASE" 2>/dev/null; then
157
+ SEEDING_EXPLICIT_FALSE=true
158
+ fi
159
+ fi
160
+
161
+ # Check 2: Program.cs override pattern
162
+ PROGRAM_CS=$(find . -name "Program.cs" -path "*/Api/*" 2>/dev/null | head -1)
163
+ PROGRAM_OVERRIDE=false
164
+ if [ -n "$PROGRAM_CS" ]; then
165
+ if grep -q 'EnableDevSeeding.*IsDevelopment\|IsDevelopment.*EnableDevSeeding' "$PROGRAM_CS" 2>/dev/null; then
166
+ PROGRAM_OVERRIDE=true
167
+ fi
168
+ fi
169
+
170
+ # Evaluate
171
+ if [ "$SEEDING_EXPLICIT_FALSE" = "true" ] && [ "$PROGRAM_OVERRIDE" = "false" ]; then
172
+ echo "⚠️ WARNING: EnableDevSeeding is FALSE in appsettings.json and NO Program.cs override detected"
173
+ echo "DevDataSeeder will NOT execute — test data will be empty"
174
+ echo "Fix option 1: Add to appsettings.Development.json: \"EnableDevSeeding\": true"
175
+ echo "Fix option 2: Add to Program.cs: options.EnableDevSeeding = builder.Environment.IsDevelopment();"
176
+ SEEDING_ENABLED=false
177
+ elif [ "$PROGRAM_OVERRIDE" = "true" ]; then
178
+ echo "✓ EnableDevSeeding: Program.cs overrides to IsDevelopment() — active in Development"
179
+ SEEDING_ENABLED=true
180
+ else
181
+ echo "✓ EnableDevSeeding: explicitly enabled"
182
+ SEEDING_ENABLED=true
183
+ fi
184
+ ```
185
+
186
+ | Result | Meaning | Action |
187
+ |--------|---------|--------|
188
+ | Program.cs override detected | PASS | Dev seeding active in Development environment |
189
+ | Explicitly enabled | PASS | Dev seeding active |
190
+ | FALSE + no override | **WARNING** | DevDataSeeder won't run — seed data checks in section 6 will likely fail |
191
+
192
+ ---
193
+
135
194
  ## 6. Verify Seed Data Execution
136
195
 
137
196
  ```bash
@@ -182,6 +241,28 @@ if [ "$STARTED" = "true" ]; then
182
241
  fi
183
242
  fi
184
243
 
244
+ # --- DefaultTenantId cross-schema FK validation ---
245
+ # Verify that SeedConstants.DefaultTenantId exists in core.tenant_Tenants
246
+ SEED_CONSTANTS=$(find . -path "*/SeedConstants.cs" 2>/dev/null | head -1)
247
+ if [ -n "$SEED_CONSTANTS" ]; then
248
+ TENANT_GUID=$(grep -oP 'DefaultTenantId\s*=\s*Guid\.Parse\("([^"]+)"\)' "$SEED_CONSTANTS" | grep -oP '"[^"]+"' | tr -d '"')
249
+
250
+ if [ -n "$TENANT_GUID" ] && [ -n "$DB_NAME" ]; then
251
+ TENANT_EXISTS=$(sqlcmd -S "(localdb)\MSSQLLocalDB" -d "$DB_NAME" -Q "
252
+ SET NOCOUNT ON;
253
+ SELECT COUNT(*) FROM core.tenant_Tenants WHERE Id = '$TENANT_GUID'
254
+ " -h -1 2>/dev/null | tr -d ' ')
255
+
256
+ if [ "$TENANT_EXISTS" = "0" ]; then
257
+ echo "❌ FAIL: SeedConstants.DefaultTenantId ($TENANT_GUID) does NOT exist in core.tenant_Tenants"
258
+ echo "DevDataSeeder inserts rows with an invalid FK — causes runtime 500 errors"
259
+ echo "Fix: Use a TenantId that is seeded by InitializeSmartStackAsync()"
260
+ else
261
+ echo "✓ DefaultTenantId ($TENANT_GUID) exists in core.tenant_Tenants"
262
+ fi
263
+ fi
264
+ fi
265
+
185
266
  # Cleanup
186
267
  kill $SEED_PID 2>/dev/null
187
268
  wait $SEED_PID 2>/dev/null
@@ -224,6 +305,8 @@ fi
224
305
  | Pending model changes | PASS/FAIL | dotnet ef migrations has-pending |
225
306
  | Migrations apply cleanly | PASS/FAIL | dotnet ef database update on temp DB |
226
307
  | Integration tests (SQL Server) | PASS/FAIL | dotnet test --filter Integration |
308
+ | EnableDevSeeding active | PASS/WARN | Program.cs override or appsettings check |
309
+ | DefaultTenantId FK valid | PASS/FAIL | SeedConstants GUID exists in tenant_Tenants|
227
310
  | Seed data accessible | PASS/WARN | API endpoints return seeded data |
228
311
  | Temp DB cleanup | PASS/WARN | Database dropped |
229
312
 
@@ -231,7 +314,7 @@ DB Validation Result: {PASS / FAIL / SKIPPED}
231
314
  ```
232
315
 
233
316
  **Interpretation:**
234
- - **PASS**: All 6 gaps covered — migrations, SQL Server, LINQ→SQL, isolation, seed data verified
317
+ - **PASS**: All 8 checks covered — migrations, SQL Server, LINQ→SQL, isolation, seeding config, tenant FK, seed data verified
235
318
  - **FAIL**: One or more checks failed — fix before committing
236
319
  - **SKIPPED**: LocalDB not available — tests ran on SQLite only (partial coverage)
237
320
 
@@ -248,3 +331,5 @@ DB Validation Result: {PASS / FAIL / SKIPPED}
248
331
  | `Cannot insert the value NULL into column` | Missing NOT NULL | Add default value or make column nullable |
249
332
  | `Login failed for user` | LocalDB auth | Run `sqllocaldb start MSSQLLocalDB` |
250
333
  | `A network-related or instance-specific error` | LocalDB not running | Run `sqllocaldb start MSSQLLocalDB` |
334
+ | `DefaultTenantId does NOT exist in core.tenant_Tenants` | Phantom FK | Use a TenantId seeded by `InitializeSmartStackAsync()` |
335
+ | `EnableDevSeeding is FALSE` + empty tables | Seeding disabled | Add `options.EnableDevSeeding = builder.Environment.IsDevelopment()` in Program.cs |