@atlashub/smartstack-cli 4.18.0 → 4.20.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/package.json +1 -1
- package/templates/agents/ba-reader.md +86 -80
- package/templates/agents/ba-writer.md +318 -415
- package/templates/agents/docs-context-reader.md +3 -3
- package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +133 -0
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +126 -0
- package/templates/skills/apex/SKILL.md +29 -16
- package/templates/skills/apex/_shared.md +62 -9
- package/templates/skills/apex/references/analysis-methods.md +8 -6
- package/templates/skills/apex/references/challenge-questions.md +5 -5
- package/templates/skills/apex/references/core-seed-data.md +68 -45
- package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +26 -21
- package/templates/skills/apex/references/parallel-execution.md +156 -0
- package/templates/skills/apex/references/person-extension-pattern.md +12 -12
- package/templates/skills/apex/references/post-checks.md +1748 -1726
- package/templates/skills/apex/references/smartstack-api.md +63 -57
- package/templates/skills/apex/references/smartstack-frontend-compliance.md +594 -0
- package/templates/skills/apex/references/smartstack-frontend.md +1246 -1842
- package/templates/skills/apex/references/smartstack-layers.md +98 -145
- package/templates/skills/apex/steps/step-00-init.md +30 -6
- package/templates/skills/apex/steps/step-01-analyze.md +27 -23
- package/templates/skills/apex/steps/step-02-plan.md +12 -12
- package/templates/skills/apex/steps/step-03-execute.md +198 -143
- package/templates/skills/apex/steps/step-04-examine.md +24 -93
- package/templates/skills/apex/steps/step-05-deep-review.md +16 -16
- package/templates/skills/apex/steps/step-06-resolve.md +9 -9
- package/templates/skills/apex/steps/step-07-tests.md +3 -1
- package/templates/skills/apex/steps/step-08-run-tests.md +1 -1
- package/templates/skills/business-analyse/SKILL.md +182 -301
- package/templates/skills/business-analyse/_shared.md +119 -336
- package/templates/skills/business-analyse/html/ba-interactive.html +706 -85
- package/templates/skills/business-analyse/html/build-html.js +41 -3
- package/templates/skills/business-analyse/html/src/partials/cadrage-context.html +34 -0
- package/templates/skills/business-analyse/html/src/partials/cadrage-risks.html +48 -0
- package/templates/skills/business-analyse/html/src/partials/cadrage-scope.html +49 -0
- package/templates/skills/business-analyse/html/src/partials/cadrage-stakeholders.html +55 -0
- package/templates/skills/business-analyse/html/src/partials/cadrage-success.html +34 -0
- package/templates/skills/business-analyse/html/src/partials/consol-datamodel.html +8 -0
- package/templates/skills/business-analyse/html/src/partials/consol-flows.html +29 -0
- package/templates/skills/business-analyse/html/src/partials/consol-interactions.html +8 -0
- package/templates/skills/business-analyse/html/src/partials/consol-permissions.html +8 -0
- package/templates/skills/business-analyse/html/src/partials/decomp-dependencies.html +38 -0
- package/templates/skills/business-analyse/html/src/partials/decomp-modules.html +51 -0
- package/templates/skills/business-analyse/html/src/partials/handoff-summary.html +24 -0
- package/templates/skills/business-analyse/html/src/partials/module-spec-container.html +4 -0
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +17 -1
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +32 -6
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +100 -63
- package/templates/skills/business-analyse/html/src/scripts/06-render-mockups.js +372 -0
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +1 -1
- package/templates/skills/business-analyse/html/src/scripts/10-comments.js +41 -13
- package/templates/skills/business-analyse/html/src/styles/09-mockups-html.css +136 -0
- package/templates/skills/business-analyse/html/src/template.html +1 -1
- package/templates/skills/business-analyse/patterns/suggestion-catalog.md +7 -5
- package/templates/skills/business-analyse/questionnaire/01-context.md +11 -157
- package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +101 -0
- package/templates/skills/business-analyse/questionnaire/03-data-ui.md +92 -0
- package/templates/skills/business-analyse/questionnaire/04-risks-metrics.md +6 -0
- package/templates/skills/business-analyse/questionnaire/05-cross-module.md +69 -0
- package/templates/skills/business-analyse/questionnaire.md +22 -280
- package/templates/skills/business-analyse/react/application-viewer.md +2 -2
- package/templates/skills/business-analyse/react/components.md +4 -4
- package/templates/skills/business-analyse/react/i18n-template.md +1 -1
- package/templates/skills/business-analyse/react/schema.md +14 -14
- package/templates/skills/business-analyse/references/acceptance-criteria.md +21 -21
- package/templates/skills/business-analyse/references/analysis-semantic-checks.md +3 -3
- package/templates/skills/business-analyse/references/compilation-structure-cards.md +1 -1
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +5 -5
- package/templates/skills/business-analyse/references/deploy-data-build.md +12 -11
- package/templates/skills/business-analyse/references/deploy-modes.md +10 -10
- package/templates/skills/business-analyse/references/detection-strategies.md +6 -6
- package/templates/skills/business-analyse/references/html-data-mapping.md +15 -15
- package/templates/skills/business-analyse/references/naming-conventions.md +4 -4
- package/templates/skills/business-analyse/references/review-data-mapping.md +29 -29
- package/templates/skills/business-analyse/references/robustness-checks.md +36 -36
- package/templates/skills/business-analyse/references/spec-auto-inference.md +2 -2
- package/templates/skills/business-analyse/references/ui-dashboard-spec.md +1 -1
- package/templates/skills/business-analyse/references/ui-resource-cards.md +1 -1
- package/templates/skills/business-analyse/references/validation-checklist.md +3 -3
- package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +2 -2
- package/templates/skills/business-analyse/schemas/application-schema.json +8 -8
- package/templates/skills/business-analyse/schemas/feature-schema.json +3 -3
- package/templates/skills/business-analyse/schemas/index-schema.json +47 -0
- package/templates/skills/business-analyse/schemas/project-schema.json +6 -6
- package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +1 -1
- package/templates/skills/business-analyse/schemas/sections/handoff-schema.json +5 -3
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +4 -4
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +1 -1
- package/templates/skills/business-analyse/schemas/shared/common-defs.json +4 -4
- package/templates/skills/business-analyse/steps/step-00-init.md +68 -77
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +50 -216
- package/templates/skills/business-analyse/steps/step-02-structure.md +175 -0
- package/templates/skills/business-analyse/steps/step-03-specify.md +198 -0
- package/templates/skills/business-analyse/steps/step-04-consolidate.md +478 -0
- package/templates/skills/business-analyse/steps/step-05-deploy.md +220 -0
- package/templates/skills/business-analyse/steps/step-06-review.md +51 -69
- package/templates/skills/business-analyse/templates/tpl-frd.md +1 -1
- package/templates/skills/business-analyse/templates/tpl-handoff.md +20 -17
- package/templates/skills/business-analyse/templates/tpl-launch-displays.md +2 -2
- package/templates/skills/business-analyse/templates-react.md +2 -2
- package/templates/skills/derive-prd/SKILL.md +92 -0
- package/templates/skills/derive-prd/references/acceptance-criteria.md +169 -0
- package/templates/skills/derive-prd/references/entity-domain-mapping.md +115 -0
- package/templates/skills/{business-analyse → derive-prd}/references/handoff-file-templates.md +131 -120
- package/templates/skills/{business-analyse → derive-prd}/references/handoff-mappings.md +95 -95
- package/templates/skills/{business-analyse → derive-prd}/references/handoff-seeddata-generation.md +312 -312
- package/templates/skills/{business-analyse → derive-prd}/references/prd-generation.md +262 -263
- package/templates/skills/derive-prd/references/readiness-scoring.md +104 -0
- package/templates/skills/derive-prd/schemas/handoff-schema.json +95 -0
- package/templates/skills/derive-prd/steps/step-00-validate.md +130 -0
- package/templates/skills/derive-prd/steps/step-01-transform.md +206 -0
- package/templates/skills/derive-prd/steps/step-02-export.md +181 -0
- package/templates/skills/{business-analyse → derive-prd}/templates/tpl-progress.md +172 -172
- package/templates/skills/documentation/SKILL.md +7 -0
- package/templates/skills/ralph-loop/SKILL.md +2 -1
- package/templates/skills/ralph-loop/references/init-resume-recovery.md +1 -1
- package/templates/skills/ralph-loop/steps/step-01-task.md +2 -2
- package/templates/skills/apex/references/agent-teams-protocol.md +0 -203
- package/templates/skills/business-analyse/_architecture.md +0 -124
- package/templates/skills/business-analyse/_elicitation.md +0 -206
- package/templates/skills/business-analyse/_module-loop.md +0 -115
- package/templates/skills/business-analyse/_rules.md +0 -142
- package/templates/skills/business-analyse/_suggestions.md +0 -34
- package/templates/skills/business-analyse/questionnaire/00-application.md +0 -160
- package/templates/skills/business-analyse/questionnaire/00b-project.md +0 -85
- package/templates/skills/business-analyse/questionnaire/02-stakeholders.md +0 -189
- package/templates/skills/business-analyse/questionnaire/03-scope.md +0 -164
- package/templates/skills/business-analyse/questionnaire/04-data.md +0 -88
- package/templates/skills/business-analyse/questionnaire/05-integrations.md +0 -58
- package/templates/skills/business-analyse/questionnaire/06-security.md +0 -68
- package/templates/skills/business-analyse/questionnaire/07-ui.md +0 -76
- package/templates/skills/business-analyse/questionnaire/08-performance.md +0 -42
- package/templates/skills/business-analyse/questionnaire/09-constraints.md +0 -45
- package/templates/skills/business-analyse/questionnaire/10-documentation.md +0 -58
- package/templates/skills/business-analyse/questionnaire/11-data-lifecycle.md +0 -59
- package/templates/skills/business-analyse/questionnaire/12-migration.md +0 -58
- package/templates/skills/business-analyse/questionnaire/13-cross-module.md +0 -69
- package/templates/skills/business-analyse/questionnaire/14-risk-assumptions.md +0 -135
- package/templates/skills/business-analyse/questionnaire/15-success-metrics.md +0 -136
- package/templates/skills/business-analyse/references/agent-module-prompt.md +0 -366
- package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +0 -557
- package/templates/skills/business-analyse/references/cache-warming-strategy.md +0 -566
- package/templates/skills/business-analyse/references/cadrage-challenge-patterns.md +0 -41
- package/templates/skills/business-analyse/references/cadrage-coverage-matrix.md +0 -74
- package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +0 -115
- package/templates/skills/business-analyse/references/cadrage-shared-modules.md +0 -68
- package/templates/skills/business-analyse/references/cadrage-structure-cards.md +0 -85
- package/templates/skills/business-analyse/references/team-orchestration.md +0 -1093
- package/templates/skills/business-analyse/references/validate-incremental-html.md +0 -121
- package/templates/skills/business-analyse/steps/step-01b-applications.md +0 -419
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +0 -387
- package/templates/skills/business-analyse/steps/step-03a-data.md +0 -16
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +0 -486
- package/templates/skills/business-analyse/steps/step-03a2-analysis.md +0 -300
- package/templates/skills/business-analyse/steps/step-03b-ui.md +0 -405
- package/templates/skills/business-analyse/steps/step-03c-compile.md +0 -516
- package/templates/skills/business-analyse/steps/step-03d-validate.md +0 -691
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +0 -17
- package/templates/skills/business-analyse/steps/step-04a-collect.md +0 -415
- package/templates/skills/business-analyse/steps/step-04b-analyze.md +0 -163
- package/templates/skills/business-analyse/steps/step-04c-decide.md +0 -186
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +0 -937
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +0 -522
- package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +0 -703
|
@@ -231,9 +231,9 @@ public class {Name} : BaseEntity, IScopedTenantEntity, IAuditableEntity
|
|
|
231
231
|
|
|
232
232
|
### Entity Pattern — Person Extension (User-linked roles)
|
|
233
233
|
|
|
234
|
-
For entities representing a "person role" of a User (Employee, Customer, Contact).
|
|
234
|
+
For entities representing a "person role" of a User (Employee, Customer, Contact). Links entity to User instead of duplicating personal data.
|
|
235
235
|
|
|
236
|
-
> **Full reference:** See `references/person-extension-pattern.md` for complete entity, config, service, DTO, and frontend patterns.
|
|
236
|
+
> **Full reference:** See `references/person-extension-pattern.md` for complete entity, config, service, DTO, and frontend patterns with C# templates, decision guide, and examples.
|
|
237
237
|
|
|
238
238
|
**Two variants:**
|
|
239
239
|
|
|
@@ -242,9 +242,18 @@ For entities representing a "person role" of a User (Employee, Customer, Contact
|
|
|
242
242
|
| **Mandatory** | `Guid UserId` | Employee, Manager — always a User |
|
|
243
243
|
| **Optional** | `Guid? UserId` | Customer, Contact — may or may not have account |
|
|
244
244
|
|
|
245
|
+
**IPersonExtension interface:**
|
|
246
|
+
```csharp
|
|
247
|
+
public interface IPersonExtension
|
|
248
|
+
{
|
|
249
|
+
Guid? UserId { get; }
|
|
250
|
+
User? User { get; }
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
245
254
|
**Mandatory variant (entity):**
|
|
246
255
|
```csharp
|
|
247
|
-
public class Employee : BaseEntity, ITenantEntity, IAuditableEntity
|
|
256
|
+
public class Employee : BaseEntity, ITenantEntity, IAuditableEntity, IPersonExtension
|
|
248
257
|
{
|
|
249
258
|
public Guid TenantId { get; private set; }
|
|
250
259
|
public string? CreatedBy { get; set; }
|
|
@@ -254,7 +263,7 @@ public class Employee : BaseEntity, ITenantEntity, IAuditableEntity
|
|
|
254
263
|
public Guid UserId { get; private set; }
|
|
255
264
|
public User? User { get; private set; }
|
|
256
265
|
|
|
257
|
-
// Business properties ONLY —
|
|
266
|
+
// Business properties ONLY — no duplicate person fields
|
|
258
267
|
public string Code { get; private set; } = null!;
|
|
259
268
|
public DateOnly HireDate { get; private set; }
|
|
260
269
|
}
|
|
@@ -262,7 +271,7 @@ public class Employee : BaseEntity, ITenantEntity, IAuditableEntity
|
|
|
262
271
|
|
|
263
272
|
**Optional variant (entity):**
|
|
264
273
|
```csharp
|
|
265
|
-
public class Customer : BaseEntity, ITenantEntity, IAuditableEntity
|
|
274
|
+
public class Customer : BaseEntity, ITenantEntity, IAuditableEntity, IPersonExtension
|
|
266
275
|
{
|
|
267
276
|
public Guid TenantId { get; private set; }
|
|
268
277
|
public string? CreatedBy { get; set; }
|
|
@@ -272,7 +281,7 @@ public class Customer : BaseEntity, ITenantEntity, IAuditableEntity
|
|
|
272
281
|
public Guid? UserId { get; private set; }
|
|
273
282
|
public User? User { get; private set; }
|
|
274
283
|
|
|
275
|
-
// Person fields (
|
|
284
|
+
// Person fields (used only when UserId is null)
|
|
276
285
|
public string FirstName { get; private set; } = null!;
|
|
277
286
|
public string LastName { get; private set; } = null!;
|
|
278
287
|
public string? Email { get; private set; }
|
|
@@ -284,14 +293,11 @@ public class Customer : BaseEntity, ITenantEntity, IAuditableEntity
|
|
|
284
293
|
}
|
|
285
294
|
```
|
|
286
295
|
|
|
287
|
-
**
|
|
288
|
-
**EF Config (
|
|
289
|
-
|
|
290
|
-
**Service
|
|
291
|
-
|
|
292
|
-
**DTO rules:** Mandatory CreateDto has `Guid UserId` + business fields only. Optional CreateDto has `Guid? UserId` + person fields. ResponseDto (both) includes `Display*` fields.
|
|
293
|
-
|
|
294
|
-
**FORBIDDEN (mandatory variant):** `FirstName`, `LastName`, `Email`, `PhoneNumber` properties in entity — these come from User.
|
|
296
|
+
**Key rules:**
|
|
297
|
+
- **EF Config (mandatory):** Unique index on `(TenantId, UserId)` without filter
|
|
298
|
+
- **EF Config (optional):** Unique index on `(TenantId, UserId)` with `.HasFilter("[UserId] IS NOT NULL")`
|
|
299
|
+
- **Service:** Include User on all queries. Verify User exists and no duplicate on CreateAsync.
|
|
300
|
+
- **DTO:** Mandatory CreateDto requires `UserId`. Optional CreateDto requires person fields when `UserId` is null.
|
|
295
301
|
|
|
296
302
|
---
|
|
297
303
|
|
|
@@ -354,9 +360,9 @@ public class {Name}Configuration : IEntityTypeConfiguration<{Name}>
|
|
|
354
360
|
|
|
355
361
|
---
|
|
356
362
|
|
|
357
|
-
## Service Pattern (tenant-scoped,
|
|
363
|
+
## Service Pattern (tenant-scoped, required)
|
|
358
364
|
|
|
359
|
-
>
|
|
365
|
+
> All services must inject `ICurrentUserService` + `ICurrentTenantService` and filter by `TenantId`. Missing TenantId is an OWASP A01 vulnerability.
|
|
360
366
|
|
|
361
367
|
```csharp
|
|
362
368
|
using Microsoft.EntityFrameworkCore;
|
|
@@ -392,12 +398,12 @@ public class {Name}Service : I{Name}Service
|
|
|
392
398
|
int pageSize = 20,
|
|
393
399
|
CancellationToken ct = default)
|
|
394
400
|
{
|
|
395
|
-
//
|
|
401
|
+
// Guard clause — throws 400 if no tenant context (e.g., missing X-Tenant-Slug header)
|
|
396
402
|
var tenantId = _currentTenant.TenantId
|
|
397
403
|
?? throw new TenantContextRequiredException();
|
|
398
404
|
|
|
399
405
|
var query = _db.{Name}s
|
|
400
|
-
.Where(x => x.TenantId == tenantId) //
|
|
406
|
+
.Where(x => x.TenantId == tenantId) // Required tenant filter
|
|
401
407
|
.AsNoTracking();
|
|
402
408
|
|
|
403
409
|
// Search filter — enables EntityLookup on frontend
|
|
@@ -425,7 +431,7 @@ public class {Name}Service : I{Name}Service
|
|
|
425
431
|
?? throw new TenantContextRequiredException();
|
|
426
432
|
|
|
427
433
|
return await _db.{Name}s
|
|
428
|
-
.Where(x => x.Id == id && x.TenantId == tenantId) //
|
|
434
|
+
.Where(x => x.Id == id && x.TenantId == tenantId) // Required
|
|
429
435
|
.AsNoTracking()
|
|
430
436
|
.Select(x => new {Name}ResponseDto(x.Id, x.Code, x.Name, x.CreatedAt))
|
|
431
437
|
.FirstOrDefaultAsync(ct);
|
|
@@ -437,7 +443,7 @@ public class {Name}Service : I{Name}Service
|
|
|
437
443
|
?? throw new TenantContextRequiredException();
|
|
438
444
|
|
|
439
445
|
var entity = {Name}.Create(
|
|
440
|
-
tenantId: tenantId, //
|
|
446
|
+
tenantId: tenantId, // Required — never Guid.Empty
|
|
441
447
|
code: dto.Code,
|
|
442
448
|
name: dto.Name);
|
|
443
449
|
|
|
@@ -472,7 +478,7 @@ public class {Name}Service : I{Name}Service
|
|
|
472
478
|
- `ICurrentTenantService` (from `SmartStack.Application.Common.Interfaces.Tenants`): provides `TenantId` (Guid?), `HasTenant` (bool), `TenantSlug` (string?)
|
|
473
479
|
- `IExtensionsDbContext` (for client extensions) or `ICoreDbContext` (for platform)
|
|
474
480
|
|
|
475
|
-
**
|
|
481
|
+
**Guard clause (first line of every method):**
|
|
476
482
|
```csharp
|
|
477
483
|
var tenantId = _currentTenant.TenantId
|
|
478
484
|
?? throw new TenantContextRequiredException();
|
|
@@ -480,7 +486,7 @@ var tenantId = _currentTenant.TenantId
|
|
|
480
486
|
This converts a null TenantId into a clean 400 Bad Request response via `GlobalExceptionHandlerMiddleware`.
|
|
481
487
|
**IMPORTANT:** Uses `TenantContextRequiredException` (400), NOT `UnauthorizedAccessException` (401). A missing tenant is a bad request, not an auth failure — the JWT is valid, `[Authorize]` passed.
|
|
482
488
|
|
|
483
|
-
|
|
489
|
+
Do not use in services:
|
|
484
490
|
- `_currentTenant.TenantId!.Value` — throws `InvalidOperationException` (500) instead of clean 400
|
|
485
491
|
- `UnauthorizedAccessException("Tenant context is required")` — throws 401, triggers frontend token clearing
|
|
486
492
|
- `tenantId: Guid.Empty` — always use validated tenantId from guard clause
|
|
@@ -557,18 +563,18 @@ public class {Name}Controller : ControllerBase
|
|
|
557
563
|
}
|
|
558
564
|
```
|
|
559
565
|
|
|
560
|
-
|
|
566
|
+
Route attribute rules:
|
|
561
567
|
- `[NavRoute]` is the ONLY route attribute needed — it resolves routes dynamically from Navigation entities at startup
|
|
562
|
-
-
|
|
563
|
-
-
|
|
568
|
+
- Do not use `[Route("api/...")]` alongside `[NavRoute]` — causes route conflicts and 404s at runtime
|
|
569
|
+
- Do not use `[Route("api/[controller]")]` — this is standard ASP.NET Core, NOT SmartStack
|
|
564
570
|
- If a controller has `[NavRoute]`, there must be NO `[Route]` attribute on the class
|
|
565
571
|
|
|
566
|
-
|
|
572
|
+
Use `[RequirePermission(Permissions.{Module}.{Action})]` on every endpoint — do not use `[Authorize]` alone (no RBAC enforcement).
|
|
567
573
|
|
|
568
|
-
|
|
574
|
+
Permission paths must use identical segments to NavRoute codes (kebab-case):
|
|
569
575
|
- NavRoute: `human-resources.employees` → Permission: `human-resources.employees.read`
|
|
570
576
|
- NavRoute: `human-resources.employees.leaves` → Permission: `human-resources.employees.leaves.read`
|
|
571
|
-
-
|
|
577
|
+
- Do not use: `humanresources.employees.read` (no kebab-case — mismatches NavRoute)
|
|
572
578
|
- SmartStack.app convention: `support-client.my-tickets.read` (always kebab-case)
|
|
573
579
|
|
|
574
580
|
### Section-Level Controller (NavRoute with 3 segments)
|
|
@@ -636,16 +642,16 @@ public class AiPromptsController : ControllerBase
|
|
|
636
642
|
public async Task<ActionResult<PaginatedResult<LeaveTypeResponseDto>>> GetAllLeaveTypes(...)
|
|
637
643
|
```
|
|
638
644
|
|
|
639
|
-
>
|
|
645
|
+
> Sub-resource frontend completeness:
|
|
640
646
|
> If a parent page has a button (e.g., "Manage Leave Types") that `navigate()`s to a sub-resource route,
|
|
641
|
-
> the frontend
|
|
647
|
+
> the frontend must include a page component for that route. Otherwise → dead link → white screen.
|
|
642
648
|
> - Either create a dedicated sub-resource ListPage (e.g., `LeaveTypesPage.tsx`)
|
|
643
|
-
> - Or
|
|
644
|
-
> -
|
|
649
|
+
> - Or do not include the navigate() button if pages won't be created
|
|
650
|
+
> - Prefer separate controllers (with Suffix) over sub-endpoints in parent controller — easier to route
|
|
645
651
|
|
|
646
652
|
---
|
|
647
653
|
|
|
648
|
-
## Navigation Seed Data Pattern (
|
|
654
|
+
## Navigation Seed Data Pattern (routes must be full paths)
|
|
649
655
|
|
|
650
656
|
> **The navigation seed data defines menu routes stored in DB. These routes MUST be full paths starting with `/`.**
|
|
651
657
|
> Short routes (e.g., `humanresources`) cause 400 Bad Request on application-tracking.
|
|
@@ -659,12 +665,12 @@ public async Task<ActionResult<PaginatedResult<LeaveTypeResponseDto>>> GetAllLea
|
|
|
659
665
|
| Section | `/{app-kebab}/{module-kebab}/{section-kebab}` | `/human-resources/employees/departments` |
|
|
660
666
|
| Resource | `/{app-kebab}/{module-kebab}/{section-kebab}/{resource-kebab}` | `/human-resources/employees/departments/export` |
|
|
661
667
|
|
|
662
|
-
**
|
|
668
|
+
**Route special cases (list and detail sections):**
|
|
663
669
|
> The `list` and `detail` sections are NOT functional sub-areas — they are view modes of the module itself.
|
|
664
|
-
> Their navigation routes
|
|
670
|
+
> Their navigation routes must NOT add extra segments:
|
|
665
671
|
> - `list` section route = module route (e.g., `/human-resources/employees`)
|
|
666
672
|
> - `detail` section route = module route + `/:id` (e.g., `/human-resources/employees/:id`)
|
|
667
|
-
> -
|
|
673
|
+
> - Do not use: `/employees/list`, `/employees/detail/:id`
|
|
668
674
|
> - Other sections (dashboard, approve, import, etc.) = module route + `/{section-kebab}` (normal behavior)
|
|
669
675
|
|
|
670
676
|
**Rules:**
|
|
@@ -716,13 +722,13 @@ var section = NavigationSection.Create(
|
|
|
716
722
|
10);
|
|
717
723
|
```
|
|
718
724
|
|
|
719
|
-
###
|
|
725
|
+
### Avoid in Seed Data
|
|
720
726
|
|
|
721
727
|
| Mistake | Reality |
|
|
722
728
|
|---------|---------|
|
|
723
729
|
| `"humanresources"` as route | Must be `"/human-resources"` (full path, kebab-case) |
|
|
724
730
|
| `"employees"` as route | Must be `"/human-resources/employees"` (includes parent) |
|
|
725
|
-
| Deterministic/sequential/fixed GUIDs in seed data |
|
|
731
|
+
| Deterministic/sequential/fixed GUIDs in seed data | Use `Guid.NewGuid()` instead |
|
|
726
732
|
| Missing translations | Must have 4 languages: fr, en, it, de |
|
|
727
733
|
| Missing NavigationApplicationSeedData | Menu invisible without Application level |
|
|
728
734
|
|
|
@@ -750,7 +756,7 @@ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
|
|
|
750
756
|
|
|
751
757
|
---
|
|
752
758
|
|
|
753
|
-
## DTO Type Mapping
|
|
759
|
+
## DTO Type Mapping
|
|
754
760
|
|
|
755
761
|
> **Use the correct .NET type for each property.** Incorrect types cause runtime parsing errors.
|
|
756
762
|
|
|
@@ -762,7 +768,7 @@ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
|
|
|
762
768
|
| Duration, hours | `decimal` | `8.5` | `public decimal HoursWorked { get; set; }` |
|
|
763
769
|
| FK reference | `Guid` | `"uuid-string"` | `public Guid EmployeeId { get; set; }` |
|
|
764
770
|
|
|
765
|
-
|
|
771
|
+
Do not use in DTOs:
|
|
766
772
|
- `string Date` / `string StartDate` — use `DateOnly`
|
|
767
773
|
- `string Time` — use `TimeOnly`
|
|
768
774
|
- `DateTime BirthDate` — use `DateOnly` (no time component needed)
|
|
@@ -780,7 +786,7 @@ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
|
|
|
780
786
|
| `e.IsDeleted` filter | Does NOT exist — no soft delete |
|
|
781
787
|
| `SmartStack.Api.Core.Routing` | Wrong — use `SmartStack.Api.Routing` |
|
|
782
788
|
| `SystemEntity` base class | Does NOT exist — use `BaseEntity` for all |
|
|
783
|
-
| `[Route("api/...")] + [NavRoute]` |
|
|
789
|
+
| `[Route("api/...")] + [NavRoute]` | Do not combine — causes 404s. Only `[NavRoute]` needed (resolves route from DB at startup). Remove `[Route]` when `[NavRoute]` is present. |
|
|
784
790
|
| `SmartStack.Domain.Common.Interfaces` | Wrong — interfaces are in `SmartStack.Domain.Common` directly |
|
|
785
791
|
| `[Authorize]` without `[RequirePermission]` | No RBAC enforcement — always use `[RequirePermission]` |
|
|
786
792
|
| `tenantId: Guid.Empty` in services | OWASP A01 — always use validated `_currentTenant.TenantId` |
|
|
@@ -793,11 +799,11 @@ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
|
|
|
793
799
|
| `humanresources.employees.read` in permissions | Permission segments MUST match NavRoute kebab-case: `human-resources.employees.read` |
|
|
794
800
|
| `Permission.Create()` | Does NOT exist — use `CreateForModule()`, `CreateForSection()`, etc. |
|
|
795
801
|
| `GetAllAsync()` without search param | ALL GetAll endpoints MUST support `?search=` for EntityLookup |
|
|
796
|
-
| `string Date` in DTO | Date-only fields
|
|
802
|
+
| `string Date` in DTO | Date-only fields must use `DateOnly`, not `string` |
|
|
797
803
|
| `DateTime` for date-only | Use `DateOnly` when no time component needed |
|
|
798
804
|
| FK field as plain text input | Frontend MUST use `EntityLookup` component for Guid FK fields |
|
|
799
|
-
| `PagedResult<T>` / `PaginatedResultDto<T>` |
|
|
800
|
-
| Controller injects `DbContext` |
|
|
805
|
+
| `PagedResult<T>` / `PaginatedResultDto<T>` | Use `PaginatedResult<T>` instead |
|
|
806
|
+
| Controller injects `DbContext` | Create an Application service and inject it instead (Clean Architecture) |
|
|
801
807
|
| Domain entity has `[Table]` attribute | Infrastructure concern in Domain — move to `IEntityTypeConfiguration<T>` in Infrastructure |
|
|
802
808
|
| `using Microsoft.EntityFrameworkCore` in Domain | EF Core belongs in Infrastructure, not Domain — Domain must be persistence-ignorant |
|
|
803
809
|
| Controller returns `Employee` entity | Domain leak in API response — return `EmployeeResponseDto` instead |
|
|
@@ -949,10 +955,10 @@ export interface PaginationParams {
|
|
|
949
955
|
}
|
|
950
956
|
```
|
|
951
957
|
|
|
952
|
-
###
|
|
958
|
+
### Type Names to Avoid
|
|
953
959
|
|
|
954
|
-
|
|
|
955
|
-
|
|
960
|
+
| Avoid | Use Instead |
|
|
961
|
+
|-------|------------|
|
|
956
962
|
| `PagedResult<T>` | `PaginatedResult<T>` |
|
|
957
963
|
| `PaginatedResultDto<T>` | `PaginatedResult<T>` |
|
|
958
964
|
| `PaginatedResponse<T>` | `PaginatedResult<T>` |
|
|
@@ -968,8 +974,8 @@ export interface PaginationParams {
|
|
|
968
974
|
- **Max pageSize = 100** — enforced via `Math.Clamp(pageSize, 1, 100)` or extension method
|
|
969
975
|
- **Default page = 1, pageSize = 20** — all GetAll endpoints
|
|
970
976
|
- **Search param mandatory** — enables `EntityLookup` on frontend
|
|
971
|
-
- **POST-CHECK
|
|
972
|
-
- **POST-CHECK
|
|
977
|
+
- **POST-CHECK C12** blocks `List<T>` returns on GetAll
|
|
978
|
+
- **POST-CHECK C28** blocks non-canonical pagination type names
|
|
973
979
|
|
|
974
980
|
---
|
|
975
981
|
|
|
@@ -977,7 +983,7 @@ export interface PaginationParams {
|
|
|
977
983
|
|
|
978
984
|
> **These are the most common and dangerous mistakes.** Each one has been observed in production code generation.
|
|
979
985
|
|
|
980
|
-
### Anti-Pattern 1: HasQueryFilter with `!= Guid.Empty` (
|
|
986
|
+
### Anti-Pattern 1: HasQueryFilter with `!= Guid.Empty` (Security vulnerability)
|
|
981
987
|
|
|
982
988
|
The `HasQueryFilter` in EF Core should use **runtime tenant resolution**, NOT a static comparison against `Guid.Empty`.
|
|
983
989
|
|
|
@@ -992,11 +998,11 @@ public void Configure(EntityTypeBuilder<MyEntity> builder)
|
|
|
992
998
|
|
|
993
999
|
**CORRECT — Tenant isolation via service:**
|
|
994
1000
|
```csharp
|
|
995
|
-
//
|
|
1001
|
+
// Correct: In SmartStack, tenant filtering is done in the SERVICE layer, not via HasQueryFilter.
|
|
996
1002
|
public async Task<PaginatedResult<MyEntityDto>> GetAllAsync(...)
|
|
997
1003
|
{
|
|
998
1004
|
var query = _db.MyEntities
|
|
999
|
-
.Where(x => x.TenantId == _currentUser.TenantId) //
|
|
1005
|
+
.Where(x => x.TenantId == _currentUser.TenantId) // Required runtime filter
|
|
1000
1006
|
.AsNoTracking();
|
|
1001
1007
|
// ...
|
|
1002
1008
|
}
|
|
@@ -1022,7 +1028,7 @@ public async Task<List<MyEntityDto>> GetAllAsync(CancellationToken ct)
|
|
|
1022
1028
|
|
|
1023
1029
|
**CORRECT — Paginated with search:**
|
|
1024
1030
|
```csharp
|
|
1025
|
-
//
|
|
1031
|
+
// Correct: Returns PaginatedResult<T> with search, page, pageSize
|
|
1026
1032
|
public async Task<PaginatedResult<MyEntityDto>> GetAllAsync(
|
|
1027
1033
|
string? search = null, int page = 1, int pageSize = 20, CancellationToken ct = default)
|
|
1028
1034
|
{
|
|
@@ -1128,16 +1134,16 @@ public class UpdateMyEntityDtoValidator : AbstractValidator<UpdateMyEntityDto>
|
|
|
1128
1134
|
|
|
1129
1135
|
---
|
|
1130
1136
|
|
|
1131
|
-
### Anti-Pattern 6: `TenantId!.Value` null-forgiving operator
|
|
1137
|
+
### Anti-Pattern 6: `TenantId!.Value` null-forgiving operator
|
|
1132
1138
|
|
|
1133
1139
|
The `!` (null-forgiving) operator followed by `.Value` on a `Guid?` suppresses compiler warnings but **throws `InvalidOperationException` at runtime** when TenantId is null.
|
|
1134
1140
|
|
|
1135
1141
|
**INCORRECT — Crashes with 500 Internal Server Error:**
|
|
1136
1142
|
```csharp
|
|
1137
|
-
//
|
|
1143
|
+
// Wrong: Throws InvalidOperationException("Nullable object must have a value") → 500 error
|
|
1138
1144
|
public async Task<PaginatedResult<MyEntityDto>> GetAllAsync(...)
|
|
1139
1145
|
{
|
|
1140
|
-
var tenantId = _currentTenant.TenantId!.Value; //
|
|
1146
|
+
var tenantId = _currentTenant.TenantId!.Value; // Crashes if no tenant context
|
|
1141
1147
|
var query = _db.MyEntities.Where(x => x.TenantId == tenantId);
|
|
1142
1148
|
// ...
|
|
1143
1149
|
}
|
|
@@ -1145,7 +1151,7 @@ public async Task<PaginatedResult<MyEntityDto>> GetAllAsync(...)
|
|
|
1145
1151
|
|
|
1146
1152
|
**CORRECT — Clean 400 via GlobalExceptionHandlerMiddleware:**
|
|
1147
1153
|
```csharp
|
|
1148
|
-
//
|
|
1154
|
+
// Correct: Throws TenantContextRequiredException → middleware converts to 400 Bad Request
|
|
1149
1155
|
public async Task<PaginatedResult<MyEntityDto>> GetAllAsync(...)
|
|
1150
1156
|
{
|
|
1151
1157
|
var tenantId = _currentTenant.TenantId
|