@atlashub/smartstack-cli 3.28.0 → 3.29.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 (42) hide show
  1. package/dist/index.js +6 -7
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -3
  4. package/templates/project/api.ts.template +4 -2
  5. package/templates/project/appsettings.json.template +1 -1
  6. package/templates/skills/apex/_shared.md +13 -0
  7. package/templates/skills/apex/references/post-checks.md +228 -6
  8. package/templates/skills/apex/references/smartstack-api.md +67 -17
  9. package/templates/skills/apex/references/smartstack-frontend.md +41 -1
  10. package/templates/skills/apex/references/smartstack-layers.md +40 -10
  11. package/templates/skills/apex/steps/step-02-plan.md +16 -11
  12. package/templates/skills/apex/steps/step-03-execute.md +6 -0
  13. package/templates/skills/apex/steps/step-04-examine.md +4 -2
  14. package/templates/skills/application/references/frontend-verification.md +26 -1
  15. package/templates/skills/application/steps/step-03-roles.md +1 -1
  16. package/templates/skills/application/steps/step-05-frontend.md +24 -8
  17. package/templates/skills/application/templates-frontend.md +41 -22
  18. package/templates/skills/application/templates-seed.md +53 -16
  19. package/templates/skills/business-analyse/SKILL.md +4 -2
  20. package/templates/skills/business-analyse/_shared.md +17 -4
  21. package/templates/skills/business-analyse/react/schema.md +1 -1
  22. package/templates/skills/business-analyse/references/agent-module-prompt.md +11 -9
  23. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +4 -3
  24. package/templates/skills/business-analyse/references/deploy-modes.md +1 -1
  25. package/templates/skills/business-analyse/references/handoff-file-templates.md +4 -4
  26. package/templates/skills/business-analyse/references/robustness-checks.md +12 -9
  27. package/templates/skills/business-analyse/references/spec-auto-inference.md +3 -3
  28. package/templates/skills/business-analyse/references/ui-resource-cards.md +3 -3
  29. package/templates/skills/business-analyse/references/validation-checklist.md +21 -3
  30. package/templates/skills/business-analyse/schemas/sections/specification-schema.json +33 -5
  31. package/templates/skills/business-analyse/steps/step-03b-ui.md +2 -2
  32. package/templates/skills/business-analyse/steps/step-03c-compile.md +17 -9
  33. package/templates/skills/business-analyse/steps/step-03d-validate.md +1 -1
  34. package/templates/skills/business-analyse/steps/step-04b-analyze.md +5 -3
  35. package/templates/skills/business-analyse/steps/step-05a-handoff.md +23 -15
  36. package/templates/skills/business-analyse/templates/tpl-handoff.md +10 -8
  37. package/templates/skills/business-analyse/templates/tpl-progress.md +7 -6
  38. package/templates/skills/ralph-loop/references/category-rules.md +50 -6
  39. package/templates/skills/ralph-loop/references/compact-loop.md +16 -1
  40. package/templates/skills/ralph-loop/references/core-seed-data.md +158 -38
  41. package/templates/skills/ralph-loop/references/task-transform-legacy.md +3 -3
  42. package/templates/skills/ralph-loop/steps/step-02-execute.md +109 -1
@@ -63,7 +63,7 @@
63
63
 
64
64
  **Rules:**
65
65
  - **ALL services MUST inject `ICurrentUserService` + `ICurrentTenantService` and filter by `TenantId`** (see `smartstack-api.md` Service Pattern)
66
- - **ALL services MUST use guard clause:** `var tenantId = _currentTenant.TenantId ?? throw new UnauthorizedAccessException("Tenant context is required");`
66
+ - **ALL services MUST use guard clause:** `var tenantId = _currentTenant.TenantId ?? throw new TenantContextRequiredException();` (returns 400, NOT 401 — a missing tenant is a bad request, not an auth failure)
67
67
  - **ALL services MUST inject `ILogger<T>`** for production diagnostics
68
68
  - CQRS with MediatR
69
69
  - FluentValidation for all commands
@@ -112,17 +112,26 @@
112
112
  | Roles seed | Templates below |
113
113
  | Provider | Templates below |
114
114
 
115
- ### Seed Data Chain (7 files minimum)
115
+ ### Seed Data Chain (9 files minimum)
116
116
 
117
+ **Application-level (created ONCE, shared across modules):**
117
118
  1. **NavigationApplicationSeedData.cs** — Application-level navigation entry (MUST be first)
118
- 2. **NavigationModuleSeedData.cs** — Deterministic GUIDs (SHA256), 4 languages (fr, en, it, de)
119
- 3. **NavigationSectionSeedData.cs** — Section-level navigation (if sections defined)
120
- 4. **NavigationResourceSeedData.cs** — Resource-level navigation (if resources defined)
121
- 5. **PermissionsSeedData.cs** — MCP `generate_permissions` first, fallback template
122
- 6. **RolesSeedData.cs** — Code-based role mapping: Admin=CRUD, Manager=CRU, Contributor=CR, Viewer=R. **NEVER use deterministic GUIDs for roles** — system roles are pre-seeded by SmartStack core, look up by Code at runtime.
123
- 7. **{App}SeedDataProvider.cs** — Implements IClientSeedDataProvider
119
+ 2. **ApplicationRolesSeedData.cs** — Application-scoped roles (admin, manager, contributor, viewer) with deterministic GUIDs. Provides role entries for SeedRolesAsync().
120
+
121
+ **Per-module:**
122
+ 3. **NavigationModuleSeedData.cs** — Deterministic GUIDs (SHA256), 4 languages (fr, en, it, de)
123
+ 4. **NavigationSectionSeedData.cs** — Section-level navigation (if sections defined)
124
+ 5. **NavigationResourceSeedData.cs** — Resource-level navigation (if resources defined)
125
+ 6. **Permissions.cs** — Static permission constants: `public static class {Module} { public const string Read = "..."; }`. Referenced by `[RequirePermission(Permissions.{Module}.Read)]`.
126
+ 7. **PermissionsSeedData.cs** — MCP `generate_permissions` first, fallback template
127
+ 8. **RolesSeedData.cs** — Code-based role mapping: Admin=wildcard(*), Manager=CRU, Contributor=CR, Viewer=R. **NEVER use deterministic GUIDs for roles** — look up by Code at runtime.
128
+
129
+ **Infrastructure (created ONCE per application):**
130
+ 9. **{App}SeedDataProvider.cs** — Implements IClientSeedDataProvider
124
131
  - `SeedNavigationAsync()` — seeds Application → Module → Section → Resource + translations
125
- - `SeedPermissionsAsync()` + `SeedRolePermissionsAsync()` (roles resolved by Code, NOT by GUID)
132
+ - `SeedRolesAsync()` seeds application-scoped roles from ApplicationRolesSeedData
133
+ - `SeedPermissionsAsync()` — seeds permission entries from PermissionsSeedData
134
+ - `SeedRolePermissionsAsync()` — maps roles to permissions (roles resolved by Code, NOT by GUID)
126
135
  - DI: `services.AddScoped<IClientSeedDataProvider, {App}SeedDataProvider>()`
127
136
 
128
137
  ### Deterministic GUID Pattern
@@ -177,7 +186,7 @@ private static Guid GenerateDeterministicGuid(string input)
177
186
 
178
187
  ```csharp
179
188
  private static string ToKebabCase(string value)
180
- => System.Text.RegularExpressions.Regex.Replace(value, "(?<!^)([A-Z])", "-$1").ToLowerInvariant();
189
+ => System.Text.RegularExpressions.Regex.Replace(value, "([a-z])([A-Z])", "$1-$2").ToLowerInvariant();
181
190
 
182
191
  // Route construction:
183
192
  var appRoute = $"/{ToKebabCase(contextCode)}/{ToKebabCase(appCode)}";
@@ -190,6 +199,13 @@ var sectionRoute = $"{moduleRoute}/{ToKebabCase(sectionCode)}";
190
199
  // → "/business/human-resources/employees/departments"
191
200
  ```
192
201
 
202
+ **ROUTE SPECIAL CASES (list and detail sections):**
203
+ > `list` and `detail` are view modes of the module, NOT functional sub-areas.
204
+ > - `list` section route = module route (e.g., `/business/human-resources/employees`) — NO `/list` suffix
205
+ > - `detail` section route = module route + `/:id` (e.g., `/business/human-resources/employees/:id`) — NOT `/detail/:id`
206
+ > - FORBIDDEN: `/employees/list`, `/employees/detail/:id`
207
+ > - Other sections (dashboard, approve, import) = module route + `/{section-kebab}` (normal)
208
+
193
209
  **FORBIDDEN:**
194
210
  - `Guid.NewGuid()` → use deterministic GUIDs (SHA256)
195
211
  - `DeterministicGuid("nav:business")` for ContextId → context IDs are pre-seeded by SmartStack core, look up by code at runtime
@@ -259,6 +275,20 @@ const [loading, setLoading] = useState(true);
259
275
  **CSS:** Variables only → `bg-[var(--bg-card)]`, `text-[var(--text-primary)]`, `border-[var(--border-color)]`
260
276
  **Loader:** `Loader2` from `lucide-react` for spinners, `PageLoader` for Suspense fallback
261
277
 
278
+ ### Section Routes (when module has sections)
279
+
280
+ If the module defines `{sections}`, generate frontend routes for EACH section as children of the module route:
281
+
282
+ ```tsx
283
+ // Section routes are nested inside the module's children array
284
+ { path: '{section-kebab}', element: <Suspense fallback={<PageLoader />}><{Section}Page /></Suspense> },
285
+ { path: '{section-kebab}/create', element: <Suspense fallback={<PageLoader />}><Create{Section}Page /></Suspense> },
286
+ { path: '{section-kebab}/:id', element: <Suspense fallback={<PageLoader />}><{Section}DetailPage /></Suspense> },
287
+ { path: '{section-kebab}/:id/edit', element: <Suspense fallback={<PageLoader />}><Edit{Section}Page /></Suspense> },
288
+ ```
289
+
290
+ Section pages live in `src/pages/{ContextPascal}/{AppPascal}/{Module}/{Section}/`.
291
+
262
292
  ### FK Fields in Forms (CRITICAL)
263
293
 
264
294
  Any entity property that is a FK Guid (e.g., `EmployeeId`, `DepartmentId`) MUST use the `EntityLookup` component — NEVER a plain text input. Users cannot type GUIDs manually.
@@ -54,20 +54,25 @@ For EACH file in the plan, specify HOW it will be created/modified:
54
54
  | 4 | Application/Services/.../MyService.cs | create | MCP scaffold_extension |
55
55
  | 5 | Application/DTOs/.../MyDto.cs | create | MCP scaffold_extension |
56
56
  | 6 | Api/Controllers/.../MyController.cs | create | /controller skill |
57
- | 7 | Seeding/Data/.../NavigationModuleSeedData.cs | create | MCP generate_permissions |
58
- | 8 | Seeding/Data/.../PermissionsSeedData.cs | create | MCP generate_permissions |
59
- | 9 | Seeding/Data/.../RolesSeedData.cs | create | ref smartstack-layers.md |
57
+ | 7 | Seeding/Data/NavigationApplicationSeedData.cs | create | ref smartstack-layers.md (once per app) |
58
+ | 7b | Seeding/Data/ApplicationRolesSeedData.cs | create | ref smartstack-layers.md (once per app) |
59
+ | 8 | Seeding/Data/.../NavigationModuleSeedData.cs | create | ref smartstack-layers.md |
60
+ | 8b | Application/Authorization/Permissions.cs | create | MCP generate_permissions → static constants |
61
+ | 9 | Seeding/Data/.../PermissionsSeedData.cs | create | MCP generate_permissions |
62
+ | 10 | Seeding/Data/.../RolesSeedData.cs | create | ref smartstack-layers.md |
60
63
 
61
64
  ### Layer 2 — Frontend + I18n (parallel)
62
65
 
63
66
  | # | File | Action | Tool |
64
67
  |---|------|--------|------|
65
- | 10 | src/pages/{Ctx}/{App}/{Mod}/ListPage.tsx | create | /ui-components skill |
66
- | 10b | src/pages/{Ctx}/{App}/{Mod}/CreatePage.tsx | create | /ui-components skill (EntityLookup for FK fields) |
67
- | 10c | src/pages/{Ctx}/{App}/{Mod}/EditPage.tsx | create | /ui-components skill (EntityLookup for FK fields) |
68
- | 11 | src/services/api/{module}Api.ts | create | MCP scaffold_api_client |
69
- | 12 | src/routes/{module}.tsx | create | MCP scaffold_routes |
70
- | 13 | src/i18n/locales/{lang}/{module}.json | create | ref smartstack-frontend.md (4 languages: fr, en, it, de) |
68
+ | 11 | src/pages/{Ctx}/{App}/{Mod}/ListPage.tsx | create | /ui-components skill |
69
+ | 11b | src/pages/{Ctx}/{App}/{Mod}/CreatePage.tsx | create | /ui-components skill (EntityLookup for FK fields) |
70
+ | 11c | src/pages/{Ctx}/{App}/{Mod}/EditPage.tsx | create | /ui-components skill (EntityLookup for FK fields) |
71
+ | 11d | src/pages/{Ctx}/{App}/{Mod}/{Section}Page.tsx | create | /ui-components skill (per section in `{sections}`) |
72
+ | 11e | src/pages/{Ctx}/{App}/{Mod}/{Section}DetailPage.tsx | create | /ui-components skill (per section in `{sections}`) |
73
+ | 12 | src/services/api/{module}Api.ts | create | MCP scaffold_api_client |
74
+ | 13 | src/routes/{module}.tsx | create | MCP scaffold_routes |
75
+ | 14 | src/i18n/locales/{lang}/{module}.json | create | ref smartstack-frontend.md (4 languages: fr, en, it, de) |
71
76
 
72
77
  **FK Field Guidance:** If step-01 identified `fkFields[]`, every Create/Edit page MUST use `EntityLookup` for those fields (see `smartstack-frontend.md` section 6). The corresponding backend GetAll endpoints (Layer 1) MUST support `?search=` parameter.
73
78
 
@@ -75,8 +80,8 @@ For EACH file in the plan, specify HOW it will be created/modified:
75
80
 
76
81
  | # | File | Action | Tool |
77
82
  |---|------|--------|------|
78
- | 14 | tests/.../MyEntityTests.cs | create | MCP scaffold_tests |
79
- | 15 | tests/.../MyServiceTests.cs | create | MCP scaffold_tests |
83
+ | 15 | tests/.../MyEntityTests.cs | create | MCP scaffold_tests |
84
+ | 16 | tests/.../MyServiceTests.cs | create | MCP scaffold_tests |
80
85
  ```
81
86
 
82
87
  ---
@@ -77,6 +77,8 @@ Execute each item from the plan sequentially using skills and MCP.
77
77
  - **FK FIELDS (CRITICAL):** Any Guid FK property (e.g., EmployeeId, DepartmentId) MUST use `EntityLookup` component — NEVER a plain text input. See `smartstack-frontend.md` section 6.
78
78
  - **ZERO modals/popups/drawers for forms — ALL forms are full pages with their own URL**
79
79
  - **Form tests: Generate `EntityCreatePage.test.tsx` and `EntityEditPage.test.tsx` (co-located)**
80
+ - **SECTION PERMISSIONS:** After calling `MCP generate_permissions` for the module navRoute (3 segments: `{context}.{app}.{module}`), also call it for EACH section navRoute (4 segments: `{context}.{app}.{module}.{section}`)
81
+ - **SECTION ROUTES:** After generating module routes, add section child routes to the module's `children` array. Wire `PermissionGuard` for section routes with section-level permissions.
80
82
  - Read `references/smartstack-frontend.md` for mandatory patterns (sections 3b + 8)
81
83
  - Generate i18n JSON files for all 4 languages (fr, en, it, de) — `src/i18n/locales/{lang}/{module}.json`
82
84
  - All pages must follow loading → error → content pattern with CSS variables
@@ -134,6 +136,10 @@ Spawn 2 teammates (Opus, full tools):
134
136
  → EntityEditPage.test.tsx (co-located next to page)
135
137
  → Cover: rendering, validation, submit, pre-fill, navigation, errors
136
138
  → See smartstack-frontend.md section 8 for test templates
139
+ - **SECTION PERMISSIONS:** After MCP generate_permissions for the module navRoute (3 segments),
140
+ also call MCP generate_permissions for EACH section navRoute (4 segments: {context}.{app}.{module}.{section})
141
+ - **SECTION ROUTES:** Add section child routes to the module's children array.
142
+ Wire PermissionGuard for section routes with section-level permissions.
137
143
  - I18n: Generate 4 JSON files per module namespace (fr, en, it, de)
138
144
  → Follow JSON template from smartstack-frontend.md
139
145
  → Keys: actions, labels, errors, validation, columns, form, messages, empty
@@ -132,10 +132,12 @@ sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "IF DB_ID('$DB_NAME') IS NOT NULL BEGIN AL
132
132
  | File | Checks |
133
133
  |------|--------|
134
134
  | NavigationApplicationSeedData | MUST be first, deterministic GUID for application (NOT for context — context is looked up by code), 4 lang translations |
135
+ | ApplicationRolesSeedData | 4 roles (admin, manager, contributor, viewer), deterministic GUIDs, references NavigationApplicationSeedData.ApplicationId |
135
136
  | NavigationModuleSeedData | Deterministic GUIDs (SHA256), 4 languages, GetModuleEntry + GetTranslationEntries |
137
+ | Permissions.cs | Static constants class with `public static class {Module} { Read, Create, Update, Delete }`, paths match PermissionsSeedData |
136
138
  | PermissionsSeedData | MCP generate_permissions used, paths match Permissions.cs, wildcard + CRUD |
137
- | RolesSeedData | Admin=wildcard, Manager=CRU, Contributor=CR, Viewer=R |
138
- | IClientSeedDataProvider | 3 Seed methods, idempotent, factory methods, SaveChanges per group |
139
+ | RolesSeedData | Admin=wildcard(*), Manager=CRU, Contributor=CR, Viewer=R. Role-permission entries reference permission paths from PermissionsSeedData |
140
+ | IClientSeedDataProvider | 4 Seed methods (Navigation, Roles, Permissions, RolePermissions), idempotent, factory methods, SaveChanges per group |
139
141
  | DI Registration | `services.AddScoped<IClientSeedDataProvider, {App}SeedDataProvider>()` |
140
142
 
141
143
  ---
@@ -76,7 +76,7 @@ Verify exactly **4 language files** exist with identical key structures:
76
76
  Verify routes are:
77
77
  - **INSIDE** the Layout wrapper (`BusinessLayout`, `AdminLayout`, or `UserLayout`)
78
78
  - **NESTED** (not flat)
79
- - Following path convention: `/{context}/{application}/{module}`
79
+ - Following path convention: `/{context}/{application_kebab}/{module_kebab}` (kebab-case)
80
80
 
81
81
  ### 5b. Route Wiring Check (BLOCKING)
82
82
 
@@ -98,6 +98,31 @@ Args:
98
98
 
99
99
  If `appWiring.issues` is not empty, fix the wiring before proceeding.
100
100
 
101
+ ### 5c. Route Kebab-Case Check (BLOCKING)
102
+
103
+ Scan App.tsx route paths for non-kebab-case segments:
104
+
105
+ **FORBIDDEN** — concatenated multi-word segments without hyphens:
106
+ - `humanresources`, `timemanagement`, `mymodule` (when representing multi-word names)
107
+
108
+ **REQUIRED** — proper kebab-case for multi-word segments:
109
+ - `human-resources`, `time-management`
110
+
111
+ **Verification steps:**
112
+
113
+ 1. Extract all route path strings from App.tsx (both `contextRoutes` and `<Route path="...">`)
114
+ 2. For each multi-word path segment, verify it uses hyphens (kebab-case)
115
+ 3. Cross-reference route paths with navigation seed data routes to ensure exact match
116
+ 4. Single-word segments are fine as-is (e.g., `employees`, `products`)
117
+
118
+ **Grep check** — verify no multi-word segments without hyphens in route paths:
119
+ ```bash
120
+ # Should return NO matches for multi-word segments without hyphens
121
+ grep -E "path:\s*['\"]([a-z]+[A-Z]|[a-z]{2,}[a-z]{2,}/)" App.tsx
122
+ ```
123
+
124
+ If ANY non-kebab-case multi-word route segment is found → **FIX IT** before continuing.
125
+
101
126
  ### 6. States Check
102
127
 
103
128
  Verify the list page includes:
@@ -251,7 +251,7 @@ rolePermissions.Add(new { RoleId = platformAdminRoleId, PermissionId = {permissi
251
251
  // Application-scoped: Admin → wildcard
252
252
  rolePermissions.Add(new { RoleId = appAdminRoleId, PermissionId = {permission_guids.wildcard}, AssignedAt = seedDate });
253
253
 
254
- // Application-scoped: Manager → CRUD
254
+ // Application-scoped: Manager → CRU (read + create + update — no delete)
255
255
  rolePermissions.Add(new { RoleId = appManagerRoleId, PermissionId = {permission_guids.read}, AssignedAt = seedDate });
256
256
  rolePermissions.Add(new { RoleId = appManagerRoleId, PermissionId = {permission_guids.create}, AssignedAt = seedDate });
257
257
  rolePermissions.Add(new { RoleId = appManagerRoleId, PermissionId = {permission_guids.update}, AssignedAt = seedDate });
@@ -35,6 +35,22 @@ From previous steps:
35
35
  | `{entity_code}` | kebab-case code |
36
36
  | `{labels}` | Object with fr, en, it, de |
37
37
  | `{api_route}` | API endpoint path |
38
+ | `{application_kebab}` | kebab-case route segment for application (PascalCase → kebab-case, e.g., `HumanResources` → `human-resources`) |
39
+ | `{module_kebab}` | kebab-case route segment for module (PascalCase → kebab-case, e.g., `TimeManagement` → `time-management`) |
40
+
41
+ ---
42
+
43
+ ## ROUTE NAMING RULE
44
+
45
+ > **BLOCKING:** All route paths MUST use **kebab-case** to match navigation seed data exactly.
46
+ >
47
+ > - Transformation: `PascalCase` → `kebab-case` (e.g., `HumanResources` → `human-resources`)
48
+ > - Single words stay lowercase: `Employees` → `employees`
49
+ > - Multi-word segments MUST have hyphens: `TimeManagement` → `time-management`
50
+ > - **FORBIDDEN:** `humanresources`, `timemanagement` (concatenated words without hyphens)
51
+ > - **REQUIRED:** `human-resources`, `time-management` (proper kebab-case)
52
+ >
53
+ > Route paths MUST match the navigation seed data routes (which use `ToKebabCase()`).
38
54
 
39
55
  ---
40
56
 
@@ -123,10 +139,10 @@ Read App.tsx and detect which pattern is used:
123
139
  const contextRoutes: ContextRouteExtensions = {
124
140
  business: [
125
141
  // existing routes...
126
- { path: '{application}/{module}/list', element: <{EntityName}ListPage /> },
127
- { path: '{application}/{module}/new', element: <Create{EntityName}Page /> },
128
- { path: '{application}/{module}/:id', element: <{EntityName}DetailPage /> },
129
- { path: '{application}/{module}/:id/edit', element: <Create{EntityName}Page /> },
142
+ { path: '{application_kebab}/{module_kebab}', element: <{EntityName}ListPage /> },
143
+ { path: '{application_kebab}/{module_kebab}/new', element: <Create{EntityName}Page /> },
144
+ { path: '{application_kebab}/{module_kebab}/:id', element: <{EntityName}DetailPage /> },
145
+ { path: '{application_kebab}/{module_kebab}/:id/edit', element: <Create{EntityName}Page /> },
130
146
  ],
131
147
  };
132
148
  ```
@@ -139,7 +155,7 @@ Read App.tsx and detect which pattern is used:
139
155
  ```tsx
140
156
  <Route path="/business" element={<BusinessLayout />}>
141
157
  {/* ... existing routes ... */}
142
- <Route path="{application}/{module}" element={<{EntityName}Page />} />
158
+ <Route path="{application_kebab}/{module_kebab}" element={<{EntityName}Page />} />
143
159
  </Route>
144
160
  ```
145
161
 
@@ -211,9 +227,9 @@ If the generated translations need customization (e.g., replacing generic title
211
227
  ### Route Configuration
212
228
  ```tsx
213
229
  // In routes.tsx - NESTED routes (not flat!)
214
- <Route path="{application}">
215
- <Route index element={<Navigate to="{module}" replace />} />
216
- <Route path="{module}" element={<{EntityName}Page />} />
230
+ <Route path="{application_kebab}">
231
+ <Route index element={<Navigate to="{module_kebab}" replace />} />
232
+ <Route path="{module_kebab}" element={<{EntityName}Page />} />
217
233
  </Route>
218
234
  ```
219
235
  ```
@@ -32,6 +32,26 @@ web/smartstack-web/src/
32
32
 
33
33
  ---
34
34
 
35
+ ## ROUTE PATH VARIABLES
36
+
37
+ > **All route path segments MUST use kebab-case to match the navigation seed data.**
38
+
39
+ | Variable | Description | Example |
40
+ |----------|-------------|---------|
41
+ | `$APPLICATION_KEBAB` | kebab-case of application code | `human-resources` |
42
+ | `$MODULE_KEBAB` | kebab-case of module code | `time-management` |
43
+
44
+ **Transformation rule:** `PascalCase` → `kebab-case` via regex `([a-z])([A-Z])` → `$1-$2` + `toLowerCase()`
45
+
46
+ Examples:
47
+ - `HumanResources` → `human-resources`
48
+ - `TimeManagement` → `time-management`
49
+ - `Employees` → `employees` (single word, no change needed)
50
+
51
+ > **FORBIDDEN:** Using raw `$APPLICATION` or `$MODULE` in route paths — they may produce non-kebab-case URLs (e.g., `humanresources` instead of `human-resources`). Always use `$APPLICATION_KEBAB` and `$MODULE_KEBAB` for route paths.
52
+
53
+ ---
54
+
35
55
  ## TEMPLATE: MAIN PAGE
36
56
 
37
57
  ```tsx
@@ -47,8 +67,7 @@ export function $MODULE_PASCALPage() {
47
67
  <$MODULE_PASCALListView
48
68
  title={t('$module:title')}
49
69
  subtitle={t('$module:subtitle')}
50
- basePath="/$CONTEXT/$APPLICATION/$MODULE"
51
- createPath="/$CONTEXT/$APPLICATION/$MODULE/new"
70
+ createPath="new"
52
71
  />
53
72
  );
54
73
  }
@@ -91,7 +110,6 @@ interface PagedResult {
91
110
  interface $MODULE_PASCALListViewProps {
92
111
  title: string;
93
112
  subtitle?: string;
94
- basePath: string;
95
113
  createPath?: string;
96
114
  }
97
115
 
@@ -100,7 +118,6 @@ const DEFAULT_COLUMNS = ['name', 'description', 'isActive', 'createdAt'];
100
118
  export function $MODULE_PASCALListView({
101
119
  title,
102
120
  subtitle,
103
- basePath,
104
121
  createPath,
105
122
  }: $MODULE_PASCALListViewProps) {
106
123
  const { t } = useTranslation(['$module', 'common']);
@@ -270,7 +287,7 @@ export function $MODULE_PASCALListView({
270
287
  <tr
271
288
  key={item.id}
272
289
  className="border-b border-[var(--item-color-border)] hover:bg-[var(--bg-hover)] cursor-pointer"
273
- onClick={() => navigate(`${basePath}/${item.id}`)}
290
+ onClick={() => navigate(item.id)}
274
291
  >
275
292
  {visibleColumns.includes('name') && (
276
293
  <td className="px-4 py-3 text-sm text-[var(--text-primary)] font-medium">
@@ -306,7 +323,7 @@ export function $MODULE_PASCALListView({
306
323
  <button
307
324
  onClick={(e) => {
308
325
  e.stopPropagation();
309
- navigate(`${basePath}/${item.id}/edit`);
326
+ navigate(`${item.id}/edit`);
310
327
  }}
311
328
  className="p-1 hover:bg-[var(--bg-tertiary)]"
312
329
  style={{ borderRadius: 'var(--radius-button)' }}
@@ -346,9 +363,9 @@ export function $MODULE_PASCALListView({
346
363
  color: 'var(--success-text)'
347
364
  } : undefined}
348
365
  stats={`${t('common:createdAt')}: ${new Date(item.createdAt).toLocaleDateString()}`}
349
- onClick={() => navigate(`${basePath}/${item.id}`)}
366
+ onClick={() => navigate(item.id)}
350
367
  actions={[
351
- { label: t('common:actions.view'), onClick: () => navigate(`${basePath}/${item.id}`), variant: 'primary' },
368
+ { label: t('common:actions.view'), onClick: () => navigate(item.id), variant: 'primary' },
352
369
  ]}
353
370
  />
354
371
  ))}
@@ -512,15 +529,15 @@ import { Create$MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/
512
529
  const contextRoutes: ContextRouteExtensions = {
513
530
  $CONTEXT: [
514
531
  // ... existing routes ...
515
- { path: '$APPLICATION/$MODULE', element: <$MODULE_PASCALPage /> },
516
- { path: '$APPLICATION/$MODULE/new', element: <Create$MODULE_PASCALPage /> },
517
- { path: '$APPLICATION/$MODULE/:id', element: <$MODULE_PASCALDetailPage /> },
518
- { path: '$APPLICATION/$MODULE/:id/edit', element: <Create$MODULE_PASCALPage /> },
532
+ { path: '$APPLICATION_KEBAB/$MODULE_KEBAB', element: <$MODULE_PASCALPage /> },
533
+ { path: '$APPLICATION_KEBAB/$MODULE_KEBAB/new', element: <Create$MODULE_PASCALPage /> },
534
+ { path: '$APPLICATION_KEBAB/$MODULE_KEBAB/:id', element: <$MODULE_PASCALDetailPage /> },
535
+ { path: '$APPLICATION_KEBAB/$MODULE_KEBAB/:id/edit', element: <Create$MODULE_PASCALPage /> },
519
536
  ],
520
537
  };
521
538
  ```
522
539
 
523
- **mergeRoutes auto-generates redirects** for intermediate paths (e.g., `$APPLICATION` → `$APPLICATION/$DEFAULT_MODULE`) so you don't need to add index redirects manually.
540
+ **mergeRoutes auto-generates redirects** for intermediate paths (e.g., `$APPLICATION_KEBAB` → `$APPLICATION_KEBAB/$DEFAULT_MODULE_KEBAB`) so you don't need to add index redirects manually.
524
541
 
525
542
  **Context-to-Layout mapping (automatic via mergeRoutes):**
526
543
 
@@ -560,12 +577,12 @@ import { Create$MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/
560
577
  {/* ... existing routes stay here ... */}
561
578
 
562
579
  {/* NEW: $APPLICATION routes - added as children of the layout */}
563
- <Route path="$APPLICATION">
564
- <Route index element={<Navigate to="$DEFAULT_MODULE" replace />} />
565
- <Route path="$MODULE" element={<$MODULE_PASCALPage />} />
566
- <Route path="$MODULE/new" element={<Create$MODULE_PASCALPage />} />
567
- <Route path="$MODULE/:id" element={<$MODULE_PASCALDetailPage />} />
568
- <Route path="$MODULE/:id/edit" element={<Create$MODULE_PASCALPage />} />
580
+ <Route path="$APPLICATION_KEBAB">
581
+ <Route index element={<Navigate to="$DEFAULT_MODULE_KEBAB" replace />} />
582
+ <Route path="$MODULE_KEBAB" element={<$MODULE_PASCALPage />} />
583
+ <Route path="$MODULE_KEBAB/new" element={<Create$MODULE_PASCALPage />} />
584
+ <Route path="$MODULE_KEBAB/:id" element={<$MODULE_PASCALDetailPage />} />
585
+ <Route path="$MODULE_KEBAB/:id/edit" element={<Create$MODULE_PASCALPage />} />
569
586
  </Route>
570
587
  </Route>
571
588
  ```
@@ -576,7 +593,7 @@ import { Create$MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/
576
593
  ```tsx
577
594
  // ❌ WRONG — bypasses layout entirely, shell will NOT render
578
595
  const clientRoutes: RouteConfig[] = [
579
- { path: '/business/$APPLICATION/$MODULE', element: <$MODULE_PASCALPage /> },
596
+ { path: '/business/$APPLICATION_KEBAB/$MODULE_KEBAB', element: <$MODULE_PASCALPage /> },
580
597
  ];
581
598
  ```
582
599
 
@@ -585,7 +602,7 @@ const clientRoutes: RouteConfig[] = [
585
602
  **FORBIDDEN — Flat routes outside layout:**
586
603
  ```tsx
587
604
  // ❌ WRONG (Pattern B only) — flat route bypasses layout
588
- <Route path="/$CONTEXT/$APPLICATION/$MODULE" element={<$MODULE_PASCALPage />} />
605
+ <Route path="/$CONTEXT/$APPLICATION_KEBAB/$MODULE_KEBAB" element={<$MODULE_PASCALPage />} />
589
606
  ```
590
607
 
591
608
  ### Why nested/context routes?
@@ -612,6 +629,8 @@ These patterns are **strictly prohibited** in generated frontend code:
612
629
  | `rounded-lg`, `rounded-md` | `rounded-[var(--radius-card)]`, `rounded-[var(--radius-button)]` |
613
630
  | Custom `<div>` cards for entity lists | `<EntityCard>` component (MANDATORY) |
614
631
  | Flat routes outside Layout wrapper | Nested routes inside `<BusinessLayout>` / `<AdminLayout>` |
632
+ | Route paths with PascalCase or concatenated words (e.g., `humanresources`, `timeManagement`) | Kebab-case route paths (e.g., `human-resources`, `time-management`) |
633
+ | `navigate(\`${basePath}/${item.id}\`)` in list row click | `navigate(item.id)` (relative navigation, no path duplication) |
615
634
  | Only 2 languages (fr/en) | All 4 languages (fr/en/it/de) |
616
635
  | `[Authorize]` without permissions | `[RequirePermission("navRoute.action")]` per endpoint |
617
636
  | Missing error state | Error state with retry button (AlertCircle + underline) |
@@ -630,7 +649,7 @@ These patterns are **strictly prohibited** in generated frontend code:
630
649
  | ☐ Preferences hook created (`use$MODULE_PASCALPreferences.ts`) | |
631
650
  | ☐ API service created (uses `apiClient`, NOT raw axios) | |
632
651
  | ☐ Routes added inside Layout wrapper in App.tsx | |
633
- | ☐ Route path follows `/{context}/{application}/{module}` | |
652
+ | ☐ Route path follows `/{context}/{application_kebab}/{module_kebab}` (kebab-case) | |
634
653
 
635
654
  ### Theme Compliance
636
655
  | Check | Status |
@@ -843,7 +843,7 @@ services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
843
843
  InitializeSmartStackAsync()
844
844
  1. MigrateAsync() -> Core schema created
845
845
  2. IDatabaseSeeder.SeedAsync() -> System users, tenant
846
- 3. IClientSeedDataProvider.Seed*() -> Navigation + Permissions + Roles CLIENT
846
+ 3. IClientSeedDataProvider.Seed*() -> Navigation + Roles + Permissions + RolePermissions CLIENT
847
847
  4. IDevDataSeeder.SeedAsync() -> Dev data
848
848
  5. InitializeNavigationRoutingAsync -> Routes loaded (including client routes)
849
849
  ```
@@ -864,21 +864,37 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
864
864
 
865
865
  public async Task SeedNavigationAsync(ICoreDbContext context, CancellationToken ct)
866
866
  {
867
- var exists = await context.NavigationApplications
868
- .AnyAsync(a => a.Code == "{app_code}", ct);
869
- if (exists) return;
870
-
871
- var parentContext = await context.NavigationContexts
872
- .FirstAsync(c => c.Code == "{context_code}", ct);
873
-
874
- var app = NavigationApplication.Create(
875
- parentContext.Id, "{app_code}", "{app_label_en}",
876
- "{app_desc_en}", "{app_icon}", IconType.Lucide,
877
- "/{context_code}/{app_code}", {display_order});
878
- context.NavigationApplications.Add(app);
879
- await ((DbContext)context).SaveChangesAsync(ct);
867
+ // NOTE: Idempotence is at MODULE level (not application level).
868
+ // If the application already exists, we reuse it and seed any missing modules.
869
+ // This allows adding Module 2+ to an existing application.
870
+ var existingApp = await context.NavigationApplications
871
+ .FirstOrDefaultAsync(a => a.Code == "{app_code}", ct);
872
+
873
+ NavigationApplication app;
874
+ if (existingApp != null)
875
+ {
876
+ app = existingApp; // Application already seeded — reuse for module seeding below
877
+ }
878
+ else
879
+ {
880
+ var parentContext = await context.NavigationContexts
881
+ .FirstAsync(c => c.Code == "{context_code}", ct);
882
+
883
+ app = NavigationApplication.Create(
884
+ parentContext.Id, "{app_code}", "{app_label_en}",
885
+ "{app_desc_en}", "{app_icon}", IconType.Lucide,
886
+ "/{context_code}/{app_code}", {display_order});
887
+ context.NavigationApplications.Add(app);
888
+ await ((DbContext)context).SaveChangesAsync(ct);
889
+
890
+ // Application translations (only when creating new application)
891
+ // foreach (var t in NavigationApplicationSeedData.GetTranslationEntries()) { ... }
892
+ await ((DbContext)context).SaveChangesAsync(ct);
893
+ }
880
894
 
881
- // Create modules from {Module}NavigationSeedData
895
+ // Create modules (idempotent per-module — check each before inserting)
896
+ // var mod1Exists = await context.NavigationModules.AnyAsync(m => m.Code == "{module_code}" && m.ApplicationId == app.Id, ct);
897
+ // if (!mod1Exists) { var mod1 = NavigationModule.Create(...); context.NavigationModules.Add(mod1); }
882
898
  // Create module translations from {Module}NavigationSeedData.GetTranslationEntries()
883
899
 
884
900
  // Create sections from {Module}NavigationSeedData.GetSectionEntries(moduleId)
@@ -889,6 +905,27 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
889
905
  // Use NavigationResource.Create(sectionId, code, label, entityType, route, displayOrder)
890
906
  }
891
907
 
908
+ public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
909
+ {
910
+ // Check idempotence
911
+ var exists = await context.Roles
912
+ .AnyAsync(r => r.ApplicationId == ApplicationRolesSeedData.ApplicationId, ct);
913
+ if (exists) return;
914
+
915
+ // Create application-scoped roles (Admin, Manager, Contributor, Viewer)
916
+ foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
917
+ {
918
+ var role = Role.Create(
919
+ entry.Code,
920
+ entry.Name,
921
+ entry.Description,
922
+ entry.ApplicationId,
923
+ entry.IsSystem);
924
+ context.Roles.Add(role);
925
+ }
926
+ await ((DbContext)context).SaveChangesAsync(ct);
927
+ }
928
+
892
929
  public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
893
930
  {
894
931
  var exists = await context.Permissions
@@ -917,7 +954,7 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
917
954
  |------|-------------|
918
955
  | Factory methods | `NavigationModule.Create(...)`, `NavigationSection.Create(...)`, `NavigationResource.Create(...)`, `Permission.CreateForModule(...)` - NEVER `new Entity()` |
919
956
  | Idempotence | Each Seed method checks existence before inserting |
920
- | SaveChanges per group | Navigation (modules sections resources) -> save -> Permissions -> save -> RolePermissions -> save |
957
+ | SaveChanges per group | Navigation -> save -> Roles -> save -> Permissions -> save -> RolePermissions -> save |
921
958
  | Deterministic GUIDs | Use IDs from SeedData classes (not `Guid.NewGuid()`) |
922
959
  | FK resolution by Code | Parent modules found by `Code`, not hardcoded GUID |
923
960
  | Section/Resource conditionality | Only generate if `seedDataCore.navigationSections` / `seedDataCore.navigationResources` exist in feature.json |
@@ -94,15 +94,17 @@ docs/business/
94
94
 
95
95
  **SeedData generation (step-03c):**
96
96
 
97
- The `specification.seedDataCore` contains **7 mandatory arrays** for database seeding:
97
+ The `specification.seedDataCore` contains **9 mandatory arrays** for database seeding:
98
98
 
99
99
  | Array | Table | Source | Description |
100
100
  |-------|-------|--------|-------------|
101
+ | `navigationApplications` | `core.nav_Applications` | Manual (step-03c) | Application entry (Level 2) — created ONCE per application |
102
+ | `applicationRoles` | `core.auth_Roles` | Manual (step-03c) | Application-scoped roles (admin, manager, contributor, viewer) |
101
103
  | `navigationModules` | `core.nav_Modules` | Manual (step-03c) | Module entry (Level 3) |
102
104
  | **`navigationSections`** | **`core.nav_Sections`** | **Derived from `specification.sections[]`** | **Section entries (Level 4)** |
103
105
  | **`navigationResources`** | **`core.nav_Resources`** | **Derived from `specification.sections[].resources[]`** | **Resource entries (Level 5)** |
104
106
  | `navigationTranslations` | `core.nav_Translations` | Manual (i18n) | Multi-language labels |
105
- | `permissions` | `core.Permissions` | Manual (step-03c) | Permission definitions |
107
+ | `permissions` | `core.Permissions` | Manual (step-03c) | Permission definitions ({path, action, description}) |
106
108
  | `rolePermissions` | `core.RolePermissions` | Manual (step-03c) | Role-permission mappings |
107
109
  | `permissionConstants` | `PermissionConstants.cs` | Manual (step-03c) | C# permission constants |
108
110
 
@@ -84,17 +84,25 @@ Level 1: Context (business)
84
84
 
85
85
  ### Route patterns
86
86
  ```
87
- /business/{application}/{module}/{section} sidebar sections (list, dashboard, approve, etc.)
88
- /business/{application}/{module}/detail/:id ← detail page (hidden from sidebar, reached by row click)
87
+ /business/{application}/{module} list section (= module route, NO /list suffix)
88
+ /business/{application}/{module}/:id ← detail page (= module route + /:id, NO /detail prefix)
89
+ /business/{application}/{module}/{section} ← other sidebar sections (dashboard, approve, import, etc.)
89
90
  ```
90
91
 
91
92
  **Detail page routing:**
92
93
  - The detail page uses a parameterized route with `:id`
93
94
  - It is NOT registered as a sidebar navigation entry (`navigation: "hidden"`)
94
95
  - It is reached by clicking a row in the `list` section (action: `navigate:detail`)
95
- - The back button navigates to `/business/{application}/{module}/list`
96
+ - The back button navigates to `/business/{application}/{module}`
96
97
  - Route MUST be registered in frontend routing (App.tsx) alongside the list route
97
98
 
99
+ > **ROUTE SPECIAL CASES (list and detail sections):**
100
+ > `list` and `detail` are view modes of the module, NOT functional sub-areas.
101
+ > - `list` route = module route (NO `/list` suffix) — React Router index route
102
+ > - `detail` route = module route + `/:id` (NO `/detail/` prefix) — React Router `:id` child
103
+ > - FORBIDDEN in navigation seed: `/module/list`, `/module/detail/:id`
104
+ > - Other sections (dashboard, approve, import) add `/{section-kebab}` normally
105
+
98
106
  ### Standard Sections per Module
99
107
 
100
108
  > **RULE: Sections are functional zones, NOT CRUD operations.**
@@ -196,7 +204,12 @@ generate_feature_id():
196
204
 
197
205
  ## Permission Path Format
198
206
 
199
- **Format:** `business.{application}.{module}.{action}`
207
+ | Level | Format | Example |
208
+ |-------|--------|---------|
209
+ | Module | `{context}.{app}.{module}.{action}` | `business.sales.orders.read` |
210
+ | Section | `{context}.{app}.{module}.{section}.{action}` | `business.sales.orders.dashboard.read` |
211
+
212
+ > **Rule:** Use module-level permissions for standard CRUD. Use section-level permissions only when a section requires distinct access control (e.g., dashboard, approve, import).
200
213
 
201
214
  | Action | Permission | Usage |
202
215
  |--------|------------|-------|
@@ -489,7 +489,7 @@ export interface SeedDataNavTranslation {
489
489
  }
490
490
 
491
491
  export interface SeedDataPermission {
492
- path: string; // Full path: business.{app}.{module}.{resource}.{action}
492
+ path: string; // Module-level: business.{app}.{module}.{action} or section-level: business.{app}.{module}.{section}.{action}
493
493
  action: string;
494
494
  description?: string;
495
495
  }