@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.
- package/dist/index.js +17 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +155 -162
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/skills/apex/SKILL.md +21 -0
- package/templates/skills/apex/references/smartstack-api.md +481 -0
- package/templates/skills/apex/references/smartstack-layers.md +85 -15
- package/templates/skills/apex/steps/step-00-init.md +27 -14
- package/templates/skills/apex/steps/step-01-analyze.md +18 -0
- package/templates/skills/apex/steps/step-03-execute.md +8 -6
- package/templates/skills/apex/steps/step-04-validate.md +92 -0
- package/templates/skills/apex/steps/step-07-tests.md +29 -5
- package/templates/skills/application/references/application-roles-template.md +2 -2
- package/templates/skills/application/steps/step-05-frontend.md +40 -35
- package/templates/skills/application/templates-frontend.md +64 -36
- package/templates/skills/business-analyse/html/ba-interactive.html +80 -6
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +38 -6
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
- package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
- package/templates/skills/business-analyse/references/deploy-data-build.md +5 -3
- package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
- package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
- package/templates/skills/business-analyse/references/validate-incremental-html.md +26 -4
- package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
- package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
- package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
- package/templates/skills/business-analyse/steps/step-03c-compile.md +114 -0
- package/templates/skills/business-analyse/steps/step-03d-validate.md +144 -22
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +114 -2
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +28 -0
- package/templates/skills/ralph-loop/references/category-rules.md +5 -2
- package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
- package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
- package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
- 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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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
|
|
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
|
|
56
|
+
dotnet build
|
|
55
57
|
```
|
|
56
58
|
|
|
57
|
-
**MUST PASS before Layer 1. If
|
|
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
|
|
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
|
|
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
|
|
15
|
+
## 1. Ensure Test Projects Exist
|
|
16
|
+
|
|
17
|
+
### 1a. Unit Test Project
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
|
-
# Check for existing test project
|
|
19
|
-
|
|
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 "$
|
|
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
|
-
|
|
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
|
-
| `
|
|
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:**
|
|
115
|
+
**Step 4b:** Detect the App.tsx routing pattern:
|
|
116
116
|
|
|
117
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
|
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
|
-
**
|
|
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
|
-
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
573
|
+
### ⚠️ CRITICAL RULES (both patterns)
|
|
537
574
|
|
|
538
|
-
|
|
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
|
-
|
|
551
|
-
|
|
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
|
-
|
|
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
|
-
|
|
557
|
-
|
|
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
|
|
591
|
+
### Why nested/context routes?
|
|
563
592
|
|
|
564
|
-
| Aspect |
|
|
565
|
-
|
|
566
|
-
| Shell rendered |
|
|
567
|
-
| AvatarMenu visible |
|
|
568
|
-
|
|
|
569
|
-
|
|
|
570
|
-
|
|
|
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
|
|