@atlashub/smartstack-cli 3.21.0 → 3.23.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 (36) hide show
  1. package/dist/index.js +17 -5
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +155 -162
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/skills/apex/SKILL.md +21 -0
  7. package/templates/skills/apex/references/smartstack-api.md +481 -0
  8. package/templates/skills/apex/references/smartstack-layers.md +85 -15
  9. package/templates/skills/apex/steps/step-00-init.md +27 -14
  10. package/templates/skills/apex/steps/step-01-analyze.md +18 -0
  11. package/templates/skills/apex/steps/step-03-execute.md +8 -6
  12. package/templates/skills/apex/steps/step-04-validate.md +92 -0
  13. package/templates/skills/apex/steps/step-07-tests.md +29 -5
  14. package/templates/skills/application/references/application-roles-template.md +2 -2
  15. package/templates/skills/application/steps/step-05-frontend.md +40 -35
  16. package/templates/skills/application/templates-frontend.md +64 -36
  17. package/templates/skills/business-analyse/html/ba-interactive.html +80 -6
  18. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +38 -6
  19. package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
  20. package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
  21. package/templates/skills/business-analyse/references/deploy-data-build.md +5 -3
  22. package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
  23. package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
  24. package/templates/skills/business-analyse/references/validate-incremental-html.md +26 -4
  25. package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
  26. package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
  27. package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
  28. package/templates/skills/business-analyse/steps/step-03c-compile.md +114 -0
  29. package/templates/skills/business-analyse/steps/step-03d-validate.md +144 -22
  30. package/templates/skills/business-analyse/steps/step-05a-handoff.md +114 -2
  31. package/templates/skills/business-analyse/steps/step-05b-deploy.md +28 -0
  32. package/templates/skills/ralph-loop/references/category-rules.md +5 -2
  33. package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
  34. package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
  35. package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
  36. package/templates/skills/ralph-loop/steps/step-02-execute.md +81 -0
@@ -7,6 +7,14 @@ next_step: steps/step-01-analyze.md
7
7
 
8
8
  # Step 0: Initialize
9
9
 
10
+ **Before anything else**, display the version banner:
11
+
12
+ ```
13
+ ═══════════════════════════════════════════════════════════════
14
+ SmartStack APEX — v{{SMARTSTACK_VERSION}}
15
+ ═══════════════════════════════════════════════════════════════
16
+ ```
17
+
10
18
  ## LOAD SHARED
11
19
 
12
20
  Read `_shared.md` for SmartStack detection patterns and MCP tools reference.
@@ -133,20 +141,25 @@ IF save_mode:
133
141
  ## 7. Display Context Summary
134
142
 
135
143
  ```
136
- **APEX SmartStack - Initialization Complete**
137
-
138
- **Task:** {task_description}
139
- **Context:** {context_code} / {app_name} / {module_code}
140
- **Sections:** {sections}
141
-
142
- **Sources:**
143
- - PRD: {prd_path || "none"}
144
- - Feature: {feature_path || "none"}
145
-
146
- **Flags:** {active_flags}
147
- **MCP:** {available|degraded}
148
- **Needs migration:** {yes|no}
149
- **Needs seed data:** {yes|no}
144
+ ═══════════════════════════════════════════════════════════════
145
+ APEX INITIALIZATION COMPLETE
146
+ SmartStack CLI v{{SMARTSTACK_VERSION}}
147
+ ═══════════════════════════════════════════════════════════════
148
+
149
+ | Field | Value |
150
+ |--------------------|----------------------------------------------|
151
+ | Task | {task_description} |
152
+ | Context | {context_code} / {app_name} / {module_code} |
153
+ | Sections | {sections} |
154
+ | PRD | {prd_path || "none"} |
155
+ | Feature | {feature_path || "none"} |
156
+ | Flags | {active_flags} |
157
+ | MCP | {available|degraded} |
158
+ | Needs migration | {yes|no} |
159
+ | Needs seed data | {yes|no} |
160
+
161
+ NEXT STEP: step-01-analyze
162
+ ═══════════════════════════════════════════════════════════════
150
163
  ```
151
164
 
152
165
  ---
@@ -12,10 +12,28 @@ next_step: steps/step-02-plan.md
12
12
 
13
13
  ## LOAD CONDITIONALLY
14
14
 
15
+ - **ALWAYS** read `references/smartstack-api.md` — BaseEntity API, entity/config/controller patterns
15
16
  - If NOT `{economy_mode}`: read `references/agent-teams-protocol.md`
16
17
 
17
18
  ---
18
19
 
20
+ ## 0. Fresh Project Detection (Early Exit)
21
+
22
+ ```
23
+ IF .smartstack/init-state.json exists:
24
+ Read it → check if project was recently initialized
25
+ Glob("src/**/Entities/**/*.cs") → count entity files
26
+
27
+ IF entity count == 0 AND no PRD AND no feature.json:
28
+ → SKIP full code exploration (sections 2-3)
29
+ → Declare: "Fresh project — all elements need to be CREATED"
30
+ → Jump directly to section 4 (Gap Analysis) with all items marked "create"
31
+ ```
32
+
33
+ This saves ~2 minutes of Glob searches on an empty project.
34
+
35
+ ---
36
+
19
37
  ## 1. Context Sources
20
38
 
21
39
  Read available context (in order of priority):
@@ -13,6 +13,7 @@ All code goes through skills (/controller, /application, /ui-components, /efcore
13
13
 
14
14
  ## LOAD CONDITIONALLY
15
15
 
16
+ - **ALWAYS** read `references/smartstack-api.md` — BaseEntity API, entity/config/controller patterns
16
17
  - Read `references/smartstack-layers.md` for execution rules per layer
17
18
  - If NOT `{economy_mode}` AND Layer 1 has parallel work: read `references/agent-teams-protocol.md`
18
19
 
@@ -25,7 +26,8 @@ All code goes through skills (/controller, /application, /ui-components, /efcore
25
26
  ```
26
27
  For each entity to create/modify:
27
28
  → MCP scaffold_extension (type: "entity", target: entity_name)
28
- → Verify: inherits AuditableEntity, has IHasData if multi-tenant
29
+ → Verify: inherits BaseEntity, implements ITenantEntity + IAuditableEntity
30
+ → Verify entity matches patterns in references/smartstack-api.md
29
31
  ```
30
32
 
31
33
  ### EF Core Configurations
@@ -43,7 +45,7 @@ For each entity:
43
45
  1. MCP suggest_migration → get standardized name
44
46
  2. dotnet ef migrations add {Name} --project src/{Infra}.csproj --startup-project src/{Api}.csproj -o Persistence/Migrations
45
47
  3. dotnet ef database update (if local DB)
46
- 4. dotnet build --no-restore → MUST PASS
48
+ 4. dotnet build → MUST PASS
47
49
  ```
48
50
 
49
51
  **BLOCKING:** If build fails after migration, fix EF configs before proceeding.
@@ -51,10 +53,10 @@ For each entity:
51
53
  ### Post-Layer 0 Build Gate
52
54
 
53
55
  ```bash
54
- dotnet build --no-restore
56
+ dotnet build
55
57
  ```
56
58
 
57
- **MUST PASS before Layer 1. If failure, fix and retry.**
59
+ **MUST PASS before Layer 1. If NuGet error, run `dotnet restore` first. If file lock (MSB3021), use `--output /tmp/{project}_build`.**
58
60
 
59
61
  ---
60
62
 
@@ -102,7 +104,7 @@ Spawn 2 teammates (Opus, full tools):
102
104
  Wait for both teammates to report completion.
103
105
 
104
106
  Build verification:
105
- dotnet build --no-restore (backend)
107
+ dotnet build (backend)
106
108
  npm run typecheck (frontend, if applicable)
107
109
 
108
110
  shutdown_request → shutdown_response → TeamDelete("apex-exec")
@@ -136,7 +138,7 @@ After Layer 1 completes:
136
138
  - DI registration added
137
139
 
138
140
  2. Build gate:
139
- dotnet build --no-restore → MUST PASS
141
+ dotnet build → MUST PASS
140
142
  npm run typecheck → MUST PASS (if frontend)
141
143
  ```
142
144
 
@@ -126,6 +126,7 @@ sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "IF DB_ID('$DB_NAME') IS NOT NULL BEGIN AL
126
126
 
127
127
  | File | Checks |
128
128
  |------|--------|
129
+ | NavigationApplicationSeedData | MUST be first, deterministic GUID, 4 lang translations |
129
130
  | NavigationModuleSeedData | Deterministic GUIDs (SHA256), 4 languages, GetModuleEntry + GetTranslationEntries |
130
131
  | PermissionsSeedData | MCP generate_permissions used, paths match Permissions.cs, wildcard + CRUD |
131
132
  | RolesSeedData | Admin=wildcard, Manager=CRU, Contributor=CR, Viewer=R |
@@ -134,6 +135,97 @@ sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "IF DB_ID('$DB_NAME') IS NOT NULL BEGIN AL
134
135
 
135
136
  ---
136
137
 
138
+ ## 6b. BLOCKING POST-CHECKs (bash verification on real files)
139
+
140
+ > These checks run on the actual generated files. Model-interpreted checks are unreliable.
141
+
142
+ ### POST-CHECK 1: Navigation routes must be full paths starting with /
143
+
144
+ ```bash
145
+ # Find all seed data files and check route values
146
+ SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
147
+ if [ -n "$SEED_FILES" ]; then
148
+ # Check for short routes (no leading /) in Create() calls for navigation entities
149
+ BAD_ROUTES=$(grep -Pn 'NavigationApplication\.Create\(|NavigationModule\.Create\(|NavigationSection\.Create\(|NavigationResource\.Create\(' $SEED_FILES | grep -v '"/[a-z]')
150
+ if [ -n "$BAD_ROUTES" ]; then
151
+ echo "BLOCKING: Navigation routes must be full paths starting with /"
152
+ echo "$BAD_ROUTES"
153
+ echo "Expected: \"/business/human-resources\" NOT \"humanresources\""
154
+ exit 1
155
+ fi
156
+ fi
157
+ ```
158
+
159
+ ### POST-CHECK 2: All services must filter by TenantId (OWASP A01)
160
+
161
+ ```bash
162
+ # Find all service implementation files
163
+ SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
164
+ if [ -n "$SERVICE_FILES" ]; then
165
+ # Check each service file has TenantId reference (either _currentUser.TenantId or TenantId filter)
166
+ for f in $SERVICE_FILES; do
167
+ if ! grep -q "TenantId" "$f"; then
168
+ echo "BLOCKING (OWASP A01): Service missing TenantId filter: $f"
169
+ echo "Every service query MUST filter by _currentUser.TenantId"
170
+ exit 1
171
+ fi
172
+ if grep -q "Guid.Empty" "$f"; then
173
+ echo "BLOCKING (OWASP A01): Service uses Guid.Empty instead of _currentUser.TenantId: $f"
174
+ exit 1
175
+ fi
176
+ done
177
+ fi
178
+ ```
179
+
180
+ ### POST-CHECK 3: Controllers must use [RequirePermission], not just [Authorize]
181
+
182
+ ```bash
183
+ # Find all controller files
184
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
185
+ if [ -n "$CTRL_FILES" ]; then
186
+ for f in $CTRL_FILES; do
187
+ # Check controller has at least one RequirePermission attribute
188
+ if grep -q "\[Authorize\]" "$f" && ! grep -q "\[RequirePermission" "$f"; then
189
+ echo "WARNING: Controller uses [Authorize] without [RequirePermission]: $f"
190
+ echo "Use [RequirePermission(Permissions.{Module}.{Action})] on each endpoint"
191
+ fi
192
+ done
193
+ fi
194
+ ```
195
+
196
+ ### POST-CHECK 4: Seed data must not use Guid.NewGuid()
197
+
198
+ ```bash
199
+ SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
200
+ if [ -n "$SEED_FILES" ]; then
201
+ BAD_GUIDS=$(grep -n "Guid.NewGuid()" $SEED_FILES 2>/dev/null)
202
+ if [ -n "$BAD_GUIDS" ]; then
203
+ echo "BLOCKING: Seed data must use deterministic GUIDs (SHA256), not Guid.NewGuid()"
204
+ echo "$BAD_GUIDS"
205
+ exit 1
206
+ fi
207
+ fi
208
+ ```
209
+
210
+ ### POST-CHECK 5: Services must inject ICurrentUser
211
+
212
+ ```bash
213
+ SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
214
+ if [ -n "$SERVICE_FILES" ]; then
215
+ for f in $SERVICE_FILES; do
216
+ if ! grep -q "ICurrentUser" "$f"; then
217
+ echo "BLOCKING: Service missing ICurrentUser injection: $f"
218
+ echo "All services MUST inject ICurrentUser for tenant isolation"
219
+ exit 1
220
+ fi
221
+ done
222
+ fi
223
+ ```
224
+
225
+ **If ANY POST-CHECK fails → fix in step-03, re-validate.**
226
+
227
+ ---
228
+
137
229
  ## 7. Acceptance Criteria POST-CHECK
138
230
 
139
231
  For each AC inferred in step-01:
@@ -12,14 +12,15 @@ next_step: steps/step-08-run-tests.md
12
12
 
13
13
  ---
14
14
 
15
- ## 1. Ensure Test Project Exists
15
+ ## 1. Ensure Test Projects Exist
16
+
17
+ ### 1a. Unit Test Project
16
18
 
17
19
  ```bash
18
- # Check for existing test project
19
- TEST_PROJECT=$(find tests/ -name "*.Tests.Unit.csproj" 2>/dev/null | head -1)
20
+ # Check for existing unit test project
21
+ UNIT_PROJECT=$(find tests/ -name "*.Tests.Unit.csproj" 2>/dev/null | head -1)
20
22
 
21
- if [ -z "$TEST_PROJECT" ]; then
22
- # Create test project
23
+ if [ -z "$UNIT_PROJECT" ]; then
23
24
  PROJECT_NAME=$(basename *.sln .sln)
24
25
  dotnet new xunit -n "${PROJECT_NAME}.Tests.Unit" -o "tests/${PROJECT_NAME}.Tests.Unit"
25
26
  dotnet add "tests/${PROJECT_NAME}.Tests.Unit" package Moq
@@ -31,6 +32,29 @@ if [ -z "$TEST_PROJECT" ]; then
31
32
  fi
32
33
  ```
33
34
 
35
+ ### 1b. Integration Test Project (for step-04 DB validation)
36
+
37
+ ```bash
38
+ # Check for existing integration test project
39
+ INT_PROJECT=$(find tests/ -name "*.Tests.Integration.csproj" 2>/dev/null | head -1)
40
+
41
+ if [ -z "$INT_PROJECT" ]; then
42
+ PROJECT_NAME=$(basename *.sln .sln)
43
+ dotnet new xunit -n "${PROJECT_NAME}.Tests.Integration" -o "tests/${PROJECT_NAME}.Tests.Integration"
44
+ dotnet add "tests/${PROJECT_NAME}.Tests.Integration" package Moq
45
+ dotnet add "tests/${PROJECT_NAME}.Tests.Integration" package FluentAssertions
46
+ dotnet add "tests/${PROJECT_NAME}.Tests.Integration" package Respawn
47
+ dotnet add "tests/${PROJECT_NAME}.Tests.Integration" package Microsoft.EntityFrameworkCore.SqlServer
48
+ for proj in src/*/*.csproj; do
49
+ dotnet add "tests/${PROJECT_NAME}.Tests.Integration" reference "$proj"
50
+ done
51
+ dotnet sln add "tests/${PROJECT_NAME}.Tests.Integration/${PROJECT_NAME}.Tests.Integration.csproj"
52
+ fi
53
+ ```
54
+
55
+ > **Note:** Integration tests use `DatabaseFixture` + `Respawn` with SQL Server LocalDB.
56
+ > See step-04 section 5c for details.
57
+
34
58
  ---
35
59
 
36
60
  ## 2. Scaffold Tests via MCP
@@ -50,7 +50,7 @@ public static class ApplicationRolesSeedData
50
50
  {
51
51
  // Deterministic GUIDs for application roles
52
52
  // Generated from: "role-{applicationId}-{roleType}"
53
- private static readonly Guid ApplicationId = {ApplicationGuid}; // From NavigationApplicationSeedData
53
+ public static readonly Guid ApplicationId = NavigationApplicationSeedData.ApplicationId;
54
54
 
55
55
  public static readonly Guid AdminRoleId = GenerateRoleGuid("admin");
56
56
  public static readonly Guid ManagerRoleId = GenerateRoleGuid("manager");
@@ -141,7 +141,7 @@ public class ApplicationRoleSeedEntry
141
141
  |-------------|-------------|---------|
142
142
  | `{BaseNamespace}` | Root namespace of the client project | `SmartStack.Modules.RessourcesHumaines` |
143
143
  | `{AppLabel}` | Human-readable application label (EN) | `Human Resources` |
144
- | `{ApplicationGuid}` | GUID of the application (from NavigationApplicationSeedData) | `30f1fbba-e8c3-4879-9a49-d18deaa70a83` |
144
+ | `ApplicationId` | Deterministic GUID from `NavigationApplicationSeedData.ApplicationId` NO placeholder needed | Auto-resolved |
145
145
 
146
146
  ---
147
147
 
@@ -102,7 +102,7 @@ This generates:
102
102
 
103
103
  > **CRITICAL:** This step is MANDATORY. Without it, routes exist as files but are invisible to the React Router. The page will be BLANK.
104
104
 
105
- After `scaffold_routes` generates the route files, you MUST manually insert the routes into `App.tsx`:
105
+ After `scaffold_routes` generates the route files, you MUST manually insert the routes into `App.tsx`.
106
106
 
107
107
  **Step 4a:** Import the page components at the top of App.tsx:
108
108
 
@@ -112,33 +112,49 @@ import { {EntityName}Page } from '@/pages/{Context}/{Application}/{Module}/{Enti
112
112
  const {EntityName}Page = lazy(() => import('@/pages/{Context}/{Application}/{Module}/{EntityName}Page'));
113
113
  ```
114
114
 
115
- **Step 4b:** Find the correct Layout wrapper block in App.tsx:
115
+ **Step 4b:** Detect the App.tsx routing pattern:
116
116
 
117
- | Context | Layout block to find |
118
- |---------|---------------------|
119
- | `platform.*` | `<Route path="/platform" element={<AdminLayout />}>` |
120
- | `business.*` | `<Route path="/business" element={<BusinessLayout />}>` |
121
- | `personal.*` | `<Route path="/personal/myspace" element={<UserLayout />}>` |
117
+ Read App.tsx and detect which pattern is used:
122
118
 
123
- **Step 4c:** Insert route entries INSIDE the Layout block (as children, using relative paths):
119
+ **Pattern A** If App.tsx contains `contextRoutes: ContextRouteExtensions`:
120
+ → Add routes to `contextRoutes.{context}[]` with **RELATIVE** paths (no leading `/`)
124
121
 
125
- ```tsx
126
- // CORRECT - Route added INSIDE the layout wrapper
127
- <Route path="/business" element={<BusinessLayout />}>
128
- {/* ... existing routes ... */}
129
- <Route path="{application}/{module}" element={<{EntityName}Page />} />
130
- </Route>
122
+ ```tsx
123
+ const contextRoutes: ContextRouteExtensions = {
124
+ business: [
125
+ // 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 /> },
130
+ ],
131
+ };
132
+ ```
131
133
 
132
- // FORBIDDEN - Route added OUTSIDE layout (shell will NOT render!)
133
- <Route path="/business/{application}/{module}" element={<{EntityName}Page />} />
134
- ```
134
+ Routes are automatically injected into BOTH standard (`/business/...`) and tenant-prefixed (`/t/:slug/business/...`) route trees by `mergeRoutes()`. No manual duplication needed.
135
+
136
+ **Pattern B** — If App.tsx contains `<Route path="/{context}" element={<{Layout} />}>` (JSX Routes):
137
+ → Insert `<Route>` children inside the Layout wrapper (see below)
135
138
 
136
- **Step 4d:** ALSO add the same routes inside the tenant-prefixed block:
139
+ ```tsx
140
+ <Route path="/business" element={<BusinessLayout />}>
141
+ {/* ... existing routes ... */}
142
+ <Route path="{application}/{module}" element={<{EntityName}Page />} />
143
+ </Route>
144
+ ```
137
145
 
138
- Find `<Route path="/t/:slug">` and locate the matching `<Route path="{context}" element={<{Layout} />}>` inside it.
139
- Add the **same route entries** there.
146
+ ALSO add the same routes inside the tenant-prefixed block:
147
+ Find `<Route path="/t/:slug">` and add the **same route entries** there.
140
148
 
141
- **Step 4e:** Verify wiring:
149
+ **Step 4c:** Context-to-Layout mapping:
150
+
151
+ | Context | Layout Component | Route path |
152
+ |---------|------------------|------------|
153
+ | `platform.*` | `AdminLayout` | `/platform` |
154
+ | `business.*` | `BusinessLayout` | `/business` |
155
+ | `personal.*` | `UserLayout` | `/personal/myspace` |
156
+
157
+ **Step 4d:** Verify wiring:
142
158
 
143
159
  ```
144
160
  Tool: mcp__smartstack__validate_frontend_routes
@@ -148,21 +164,10 @@ Args:
148
164
 
149
165
  If `appWiring.issues` is not empty, fix the wiring before proceeding.
150
166
 
151
- **For new applications (no existing layout route for the context):**
152
-
153
- If the context doesn't have a layout route yet, create one wrapping all child routes:
154
-
155
- ```tsx
156
- <Route path="/{context}/{application}" element={<{Context}Layout />}>
157
- <Route index element={<Navigate to="{default_module}" replace />} />
158
- <Route path="{module}" element={<{EntityName}Page />} />
159
- </Route>
160
- ```
161
-
162
- **FORBIDDEN:**
167
+ **FORBIDDEN (both patterns):**
168
+ - Adding business/platform/personal routes to `clientRoutes[]` with absolute paths — `clientRoutes` is ONLY for routes outside SmartStack contexts (e.g., `/about`, `/pricing`)
163
169
  - Adding routes OUTSIDE the Layout wrapper (shell will not render)
164
- - Adding routes only in the standard block but not in the tenant block
165
- - Using `createBrowserRouter` (SmartStack uses `BrowserRouter` with JSX `<Routes>`)
170
+ - Using `createBrowserRouter` (SmartStack uses `useRoutes()` + `mergeRoutes()`)
166
171
 
167
172
  ### 5. Verify i18n Files
168
173
 
@@ -488,7 +488,51 @@ export const $moduleApi = {
488
488
 
489
489
  ## TEMPLATE: ROUTES (App.tsx)
490
490
 
491
- ### ⚠️ CRITICAL RULE 1: ROUTES MUST BE INSIDE LAYOUT WRAPPER
491
+ ### Detect App.tsx Routing Pattern FIRST
492
+
493
+ Before adding routes, **read App.tsx** and detect which pattern is used:
494
+
495
+ | Pattern | How to detect | Action |
496
+ |---------|---------------|--------|
497
+ | **Pattern A** (mergeRoutes) | `contextRoutes: ContextRouteExtensions` present | Add to `contextRoutes.{context}[]` array |
498
+ | **Pattern B** (JSX Routes) | `<Route path="/{context}" element={<{Layout} />}>` present | Insert `<Route>` children inside Layout wrapper |
499
+
500
+ ### Pattern A: mergeRoutes (contextRoutes array)
501
+
502
+ > **This is the DEFAULT pattern** generated by `smartstack init`.
503
+ > Routes added to `contextRoutes` are automatically injected into BOTH standard and tenant-prefixed route trees by `mergeRoutes()`. No manual duplication needed.
504
+
505
+ ```tsx
506
+ // Add to App.tsx — imports at top
507
+ import { $MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/$MODULE_PASCALPage';
508
+ import { $MODULE_PASCALDetailPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage';
509
+ import { Create$MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/Create$MODULE_PASCALPage';
510
+
511
+ // Add routes to contextRoutes.{context}[] with RELATIVE paths (no leading /)
512
+ const contextRoutes: ContextRouteExtensions = {
513
+ $CONTEXT: [
514
+ // ... 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 /> },
519
+ ],
520
+ };
521
+ ```
522
+
523
+ **mergeRoutes auto-generates redirects** for intermediate paths (e.g., `$APPLICATION` → `$APPLICATION/$DEFAULT_MODULE`) so you don't need to add index redirects manually.
524
+
525
+ **Context-to-Layout mapping (automatic via mergeRoutes):**
526
+
527
+ | Context key | Injected into Layout | Standard path | Tenant path |
528
+ |-------------|---------------------|---------------|-------------|
529
+ | `platform` | `AdminLayout` | `/platform/...` | `/t/:slug/platform/...` |
530
+ | `business` | `BusinessLayout` | `/business/...` | `/t/:slug/business/...` |
531
+ | `personal` | `UserLayout` | `/personal/myspace/...` | `/t/:slug/personal/myspace/...` |
532
+
533
+ ### Pattern B: JSX Routes (inside Layout wrapper)
534
+
535
+ > **Legacy pattern** — only used if App.tsx was manually restructured with JSX `<Route>` elements.
492
536
 
493
537
  SmartStack layouts (`AdminLayout`, `BusinessLayout`, `UserLayout`) provide the application shell: **header with AvatarMenu**, sidebar, navigation. They render child pages via React Router's `<Outlet />`.
494
538
 
@@ -504,13 +548,6 @@ SmartStack layouts (`AdminLayout`, `BusinessLayout`, `UserLayout`) provide the a
504
548
  3. Add the new routes **INSIDE** that `<Route>` block
505
549
  4. If a tenant-prefixed block exists (`/t/:slug/...`), add the routes there too
506
550
 
507
- **❌ FORBIDDEN - Flat routes OUTSIDE layout (AvatarMenu disappears!)**
508
- ```tsx
509
- // These routes bypass the layout system entirely = NO header, NO sidebar, NO AvatarMenu
510
- <Route path="/$CONTEXT/$APPLICATION/$MODULE" element={<$MODULE_PASCALPage />} />
511
- ```
512
-
513
- **✅ MANDATORY - Routes INSIDE the existing layout wrapper**
514
551
  ```tsx
515
552
  // Add to App.tsx
516
553
 
@@ -533,42 +570,33 @@ import { Create$MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/
533
570
  </Route>
534
571
  ```
535
572
 
536
- ### Context-to-Layout mapping
573
+ ### ⚠️ CRITICAL RULES (both patterns)
537
574
 
538
- | Context | Layout Component | Layout Route Path |
539
- |---------|------------------|-------------------|
540
- | `platform` | `AdminLayout` | `/platform` |
541
- | `business` | `BusinessLayout` | `/business` |
542
- | `personal` | `UserLayout` | `/personal/myspace` |
543
-
544
- ### ⚠️ CRITICAL RULE 2: NESTED ROUTES MANDATORY
545
-
546
- React Router v7 **requires** nested routes for multi-module applications.
547
-
548
- **❌ FORBIDDEN - Flat routes (cause redirects to Home)**
575
+ **FORBIDDEN Adding to clientRoutes[] with absolute paths:**
549
576
  ```tsx
550
- <Route path="$APPLICATION" element={<Navigate to="/$CONTEXT/$APPLICATION/$DEFAULT_MODULE" replace />} />
551
- <Route path="$APPLICATION/$MODULE" element={<$MODULE_PASCALPage />} />
577
+ // WRONG bypasses layout entirely, shell will NOT render
578
+ const clientRoutes: RouteConfig[] = [
579
+ { path: '/business/$APPLICATION/$MODULE', element: <$MODULE_PASCALPage /> },
580
+ ];
552
581
  ```
553
582
 
554
- **✅ MANDATORY - Nested routes with index**
583
+ `clientRoutes` is ONLY for routes **outside** SmartStack locked contexts (e.g., `/about`, `/pricing`).
584
+
585
+ **FORBIDDEN — Flat routes outside layout:**
555
586
  ```tsx
556
- <Route path="$APPLICATION">
557
- <Route index element={<Navigate to="$DEFAULT_MODULE" replace />} />
558
- <Route path="$MODULE" element={<$MODULE_PASCALPage />} />
559
- </Route>
587
+ // ❌ WRONG (Pattern B only) — flat route bypasses layout
588
+ <Route path="/$CONTEXT/$APPLICATION/$MODULE" element={<$MODULE_PASCALPage />} />
560
589
  ```
561
590
 
562
- ### Why this structure?
591
+ ### Why nested/context routes?
563
592
 
564
- | Aspect | Flat routes | Nested routes |
565
- |--------|------------|----------------|
566
- | Shell rendered | No (bypasses layout) | Yes (Outlet pattern) |
567
- | AvatarMenu visible | No | Yes |
568
- | Matching | Ambiguous between siblings | Hierarchical clear |
569
- | Navigate | Must be absolute | Can be relative |
570
- | Outlet | Not supported | Supported |
571
- | Redirect | Can fail | Always works |
593
+ | Aspect | clientRoutes (wrong) | contextRoutes / nested (correct) |
594
+ |--------|---------------------|----------------------------------|
595
+ | Shell rendered | No (bypasses layout) | Yes (Outlet pattern) |
596
+ | AvatarMenu visible | No | Yes |
597
+ | Tenant prefix | Manual duplication | Automatic (mergeRoutes) |
598
+ | Auto-redirects | None | Generated for intermediate paths |
599
+ | Permission check | Bypassed (no RouteGuard) | Enforced by RouteGuard |
572
600
 
573
601
  ---
574
602