@atlashub/smartstack-cli 3.22.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 (34) hide show
  1. package/dist/mcp-entry.mjs +143 -174
  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/SKILL.md +21 -0
  6. package/templates/skills/apex/references/smartstack-api.md +507 -0
  7. package/templates/skills/apex/references/smartstack-frontend.md +1081 -0
  8. package/templates/skills/apex/references/smartstack-layers.md +166 -20
  9. package/templates/skills/apex/steps/step-00-init.md +27 -14
  10. package/templates/skills/apex/steps/step-01-analyze.md +45 -3
  11. package/templates/skills/apex/steps/step-02-plan.md +5 -1
  12. package/templates/skills/apex/steps/step-03-execute.md +51 -9
  13. package/templates/skills/apex/steps/step-04-validate.md +251 -0
  14. package/templates/skills/apex/steps/step-05-examine.md +7 -0
  15. package/templates/skills/apex/steps/step-07-tests.md +48 -5
  16. package/templates/skills/business-analyse/_shared.md +6 -6
  17. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +1 -1
  18. package/templates/skills/business-analyse/questionnaire/07-ui.md +3 -3
  19. package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +1 -1
  20. package/templates/skills/business-analyse/references/entity-architecture-decision.md +3 -3
  21. package/templates/skills/business-analyse/references/handoff-file-templates.md +13 -5
  22. package/templates/skills/business-analyse/references/spec-auto-inference.md +14 -14
  23. package/templates/skills/business-analyse/steps/step-01-cadrage.md +2 -2
  24. package/templates/skills/business-analyse/steps/step-02-decomposition.md +1 -1
  25. package/templates/skills/business-analyse/steps/step-03a1-setup.md +2 -2
  26. package/templates/skills/business-analyse/steps/step-03b-ui.md +2 -1
  27. package/templates/skills/business-analyse/steps/step-05a-handoff.md +15 -4
  28. package/templates/skills/business-analyse/templates/tpl-frd.md +2 -2
  29. package/templates/skills/business-analyse/templates-frd.md +2 -2
  30. package/templates/skills/ralph-loop/references/category-rules.md +45 -7
  31. package/templates/skills/ralph-loop/references/compact-loop.md +2 -2
  32. package/templates/skills/ralph-loop/references/core-seed-data.md +10 -0
  33. package/templates/skills/ralph-loop/steps/step-02-execute.md +110 -1
  34. package/templates/skills/validate-feature/steps/step-05-db-validation.md +86 -1
@@ -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 |