@atlashub/smartstack-cli 3.14.0 → 3.16.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 +26 -28
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +626 -141
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/efcore/migration.md +15 -0
- package/templates/skills/apex/steps/step-04-validate.md +64 -5
- package/templates/skills/application/references/frontend-verification.md +20 -0
- package/templates/skills/application/steps/step-04-backend.md +17 -1
- package/templates/skills/application/steps/step-05-frontend.md +49 -23
- package/templates/skills/application/templates-seed.md +14 -4
- package/templates/skills/business-analyse/html/ba-interactive.html +165 -0
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +2 -0
- package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +85 -0
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +65 -0
- package/templates/skills/business-analyse/html/src/template.html +13 -0
- package/templates/skills/business-analyse/schemas/application-schema.json +5 -0
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +1 -0
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +90 -0
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +4 -0
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +39 -0
- package/templates/skills/efcore/steps/shared/step-00-init.md +55 -0
- package/templates/skills/ralph-loop/SKILL.md +1 -0
- package/templates/skills/ralph-loop/references/category-rules.md +131 -27
- package/templates/skills/ralph-loop/references/compact-loop.md +61 -3
- package/templates/skills/ralph-loop/references/core-seed-data.md +251 -5
- package/templates/skills/ralph-loop/references/error-classification.md +143 -0
- package/templates/skills/ralph-loop/steps/step-05-report.md +54 -0
- package/templates/skills/review-code/references/smartstack-conventions.md +16 -0
- package/templates/skills/validate-feature/SKILL.md +11 -1
- package/templates/skills/validate-feature/steps/step-00-dependencies.md +121 -0
- package/templates/skills/validate-feature/steps/step-04-api-smoke.md +61 -13
- package/templates/skills/validate-feature/steps/step-05-db-validation.md +250 -0
package/package.json
CHANGED
|
@@ -77,6 +77,21 @@ MCP returns the fully compliant name: `{context}_v{version}_{sequence}_{Descript
|
|
|
77
77
|
|
|
78
78
|
## Commands
|
|
79
79
|
|
|
80
|
+
**Prerequisite — ensure `dotnet ef` is on PATH (MANDATORY before any command):**
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Platform-aware dotnet-ef PATH fix (Windows Git Bash / Linux / macOS)
|
|
84
|
+
if ! dotnet ef --version &>/dev/null; then
|
|
85
|
+
for TOOLS_DIR in "$USERPROFILE/.dotnet/tools" "$HOME/.dotnet/tools" "$LOCALAPPDATA/Microsoft/dotnet/tools"; do
|
|
86
|
+
[ -n "$TOOLS_DIR" ] && [ -d "$TOOLS_DIR" ] && export PATH="$TOOLS_DIR:$PATH"
|
|
87
|
+
done
|
|
88
|
+
dotnet ef --version &>/dev/null || { echo "ERROR: dotnet-ef not found"; exit 1; }
|
|
89
|
+
fi
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> **Windows pitfall:** `$USERPROFILE` (→ `C:\Users\xxx`) is tried FIRST.
|
|
93
|
+
> NEVER rely on `$HOME` alone — in WSL it resolves to `/home/{user}` where .NET SDK is absent.
|
|
94
|
+
|
|
80
95
|
```bash
|
|
81
96
|
# Existing migrations for Core
|
|
82
97
|
find Migrations -name "core_*.cs" | grep -v Designer | grep -v Snapshot
|
|
@@ -65,7 +65,64 @@ npm run typecheck
|
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
68
|
-
## 5.
|
|
68
|
+
## 5. Database & Migration Validation (if needs_migration)
|
|
69
|
+
|
|
70
|
+
### 5a. Pending Model Changes Check
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)
|
|
74
|
+
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
75
|
+
|
|
76
|
+
dotnet ef migrations has-pending-model-changes \
|
|
77
|
+
--project "$INFRA_PROJECT" \
|
|
78
|
+
--startup-project "$API_PROJECT"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**BLOCKING** if pending changes detected → migration is missing.
|
|
82
|
+
|
|
83
|
+
### 5b. Migration Application Test (SQL Server LocalDB)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
DB_NAME="SmartStack_Apex_Validate_$(date +%s)"
|
|
87
|
+
CONN_STRING="Server=(localdb)\\MSSQLLocalDB;Database=$DB_NAME;Integrated Security=true;TrustServerCertificate=true;Connect Timeout=120;"
|
|
88
|
+
|
|
89
|
+
dotnet ef database update \
|
|
90
|
+
--connection "$CONN_STRING" \
|
|
91
|
+
--project "$INFRA_PROJECT" \
|
|
92
|
+
--startup-project "$API_PROJECT"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**BLOCKING** if migration fails on SQL Server. Common issues:
|
|
96
|
+
- SQLite-only syntax in migrations (fix: regenerate migration)
|
|
97
|
+
- Column type mismatches (fix: update EF configuration)
|
|
98
|
+
- Missing foreign key targets (fix: reorder migrations)
|
|
99
|
+
|
|
100
|
+
### 5c. Integration Tests on Real SQL Server
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Integration tests use DatabaseFixture → real SQL Server LocalDB
|
|
104
|
+
# This validates: LINQ→SQL, multi-tenant isolation, soft delete, EF configs
|
|
105
|
+
INT_TEST_PROJECT=$(ls tests/*Tests.Integration*/*.csproj 2>/dev/null | head -1)
|
|
106
|
+
if [ -n "$INT_TEST_PROJECT" ]; then
|
|
107
|
+
dotnet test "$INT_TEST_PROJECT" --no-build --verbosity normal
|
|
108
|
+
fi
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Tests running against SQL Server catch issues that SQLite misses:
|
|
112
|
+
- Case sensitivity in string comparisons
|
|
113
|
+
- Date/time function differences
|
|
114
|
+
- IDENTITY vs AUTOINCREMENT behavior
|
|
115
|
+
- Global query filter translation to T-SQL
|
|
116
|
+
|
|
117
|
+
### 5d. Cleanup
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "IF DB_ID('$DB_NAME') IS NOT NULL BEGIN ALTER DATABASE [$DB_NAME] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [$DB_NAME]; END" 2>/dev/null
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 6. Seed Data Completeness Check (if needs_seed_data)
|
|
69
126
|
|
|
70
127
|
| File | Checks |
|
|
71
128
|
|------|--------|
|
|
@@ -77,7 +134,7 @@ npm run typecheck
|
|
|
77
134
|
|
|
78
135
|
---
|
|
79
136
|
|
|
80
|
-
##
|
|
137
|
+
## 7. Acceptance Criteria POST-CHECK
|
|
81
138
|
|
|
82
139
|
For each AC inferred in step-01:
|
|
83
140
|
|
|
@@ -91,7 +148,7 @@ AC2: {criterion} → PASS / FAIL (evidence: {file:line or test})
|
|
|
91
148
|
|
|
92
149
|
---
|
|
93
150
|
|
|
94
|
-
##
|
|
151
|
+
## 8. Validation Summary
|
|
95
152
|
|
|
96
153
|
```
|
|
97
154
|
**APEX SmartStack - Validation Complete**
|
|
@@ -100,6 +157,8 @@ AC2: {criterion} → PASS / FAIL (evidence: {file:line or test})
|
|
|
100
157
|
|-------|--------|
|
|
101
158
|
| MCP validate_conventions | PASS (0 errors) |
|
|
102
159
|
| EF Core migrations | PASS / N/A |
|
|
160
|
+
| DB: Migrations apply (SQL Server) | PASS / N/A |
|
|
161
|
+
| DB: Integration tests (SQL Server) | PASS / N/A |
|
|
103
162
|
| Frontend routes | PASS / N/A |
|
|
104
163
|
| dotnet build | PASS |
|
|
105
164
|
| npm typecheck | PASS / N/A |
|
|
@@ -109,13 +168,13 @@ AC2: {criterion} → PASS / FAIL (evidence: {file:line or test})
|
|
|
109
168
|
|
|
110
169
|
---
|
|
111
170
|
|
|
112
|
-
##
|
|
171
|
+
## 9. Save Output (if save_mode)
|
|
113
172
|
|
|
114
173
|
Write to `{output_dir}/04-validate.md` with validation results.
|
|
115
174
|
|
|
116
175
|
---
|
|
117
176
|
|
|
118
|
-
##
|
|
177
|
+
## 10. Route to Next Step
|
|
119
178
|
|
|
120
179
|
```
|
|
121
180
|
IF examine_mode = true:
|
|
@@ -78,6 +78,26 @@ Verify routes are:
|
|
|
78
78
|
- **NESTED** (not flat)
|
|
79
79
|
- Following path convention: `/{context}/{application}/{module}`
|
|
80
80
|
|
|
81
|
+
### 5b. Route Wiring Check (BLOCKING)
|
|
82
|
+
|
|
83
|
+
Verify routes are actually wired into App.tsx (not just generated in standalone files):
|
|
84
|
+
|
|
85
|
+
1. Open `App.tsx` (or `main.tsx`)
|
|
86
|
+
2. Verify the new route path appears **inside** the correct Layout wrapper
|
|
87
|
+
3. Verify the route also appears inside the **tenant-prefixed** block (`/t/:slug/...`)
|
|
88
|
+
4. Verify the page component is imported (lazy-loaded) at the top of the file
|
|
89
|
+
|
|
90
|
+
**If routes are only in `clientRoutes.generated.tsx` or `routes/index.tsx` but NOT in App.tsx, the routes will NOT work at runtime — page will be BLANK.**
|
|
91
|
+
|
|
92
|
+
Run validation:
|
|
93
|
+
```
|
|
94
|
+
Tool: mcp__smartstack__validate_frontend_routes
|
|
95
|
+
Args:
|
|
96
|
+
scope: "routes"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If `appWiring.issues` is not empty, fix the wiring before proceeding.
|
|
100
|
+
|
|
81
101
|
### 6. States Check
|
|
82
102
|
|
|
83
103
|
Verify the list page includes:
|
|
@@ -53,7 +53,11 @@ code: "user-profiles" → entityName: "UserProfile"
|
|
|
53
53
|
|
|
54
54
|
### 2. Determine Table Prefix and Controller Folder
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
**Table Prefix priority:**
|
|
57
|
+
1. **If a `feature.json` exists** with `metadata.tablePrefix` → use that prefix (e.g., `rh_`, `fi_`)
|
|
58
|
+
2. **Otherwise**, derive from navigation context using the fallback table below
|
|
59
|
+
|
|
60
|
+
**Fallback table (when no feature.json tablePrefix is available):**
|
|
57
61
|
|
|
58
62
|
| Context | Table Prefix | Controller Folder |
|
|
59
63
|
|---------|--------|-------------------|
|
|
@@ -62,6 +66,18 @@ Based on navigation context:
|
|
|
62
66
|
| business.* | `ref_` or domain-specific | `Business` |
|
|
63
67
|
| personal.* | `usr_` | `User` |
|
|
64
68
|
|
|
69
|
+
```
|
|
70
|
+
# Check for feature.json tablePrefix
|
|
71
|
+
Search for feature.json in:
|
|
72
|
+
- docs/business/{application}/business-analyse/*/feature.json
|
|
73
|
+
- Read metadata.tablePrefix if exists
|
|
74
|
+
|
|
75
|
+
IF metadata.tablePrefix exists:
|
|
76
|
+
{prefix} = metadata.tablePrefix # e.g., "rh_"
|
|
77
|
+
ELSE:
|
|
78
|
+
{prefix} = derived from context table above
|
|
79
|
+
```
|
|
80
|
+
|
|
65
81
|
Store: `{controller_folder}` = the Controller Folder from the table above.
|
|
66
82
|
|
|
67
83
|
### 3. Call MCP scaffold_extension
|
|
@@ -81,7 +81,7 @@ This generates:
|
|
|
81
81
|
- `types/{entityName}.types.ts` - TypeScript interfaces
|
|
82
82
|
- `hooks/use{EntityName}Api.ts` - React Query hook (optional)
|
|
83
83
|
|
|
84
|
-
### 3.
|
|
84
|
+
### 3. Generate Route Configuration
|
|
85
85
|
|
|
86
86
|
```
|
|
87
87
|
Tool: mcp__smartstack__scaffold_routes
|
|
@@ -91,30 +91,36 @@ Args:
|
|
|
91
91
|
options:
|
|
92
92
|
includeGuards: true
|
|
93
93
|
generateRegistry: true
|
|
94
|
+
outputFormat: "clientRoutes"
|
|
94
95
|
```
|
|
95
96
|
|
|
96
|
-
This
|
|
97
|
-
- `navRoutes.generated.ts` - Route registry
|
|
98
|
-
- `
|
|
97
|
+
This generates:
|
|
98
|
+
- `navRoutes.generated.ts` - Route registry (NavRoute to API/web path mapping)
|
|
99
|
+
- `clientRoutes.generated.tsx` - Route fragments grouped by context with page imports
|
|
100
|
+
|
|
101
|
+
### 4. Wire Routes to App.tsx (BLOCKING)
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
> **CRITICAL:** This step is MANDATORY. Without it, routes exist as files but are invisible to the React Router. The page will be BLANK.
|
|
101
104
|
|
|
102
|
-
|
|
105
|
+
After `scaffold_routes` generates the route files, you MUST manually insert the routes into `App.tsx`:
|
|
103
106
|
|
|
104
|
-
**
|
|
107
|
+
**Step 4a:** Import the page components at the top of App.tsx:
|
|
105
108
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
```tsx
|
|
110
|
+
import { {EntityName}Page } from '@/pages/{Context}/{Application}/{Module}/{EntityName}Page';
|
|
111
|
+
// Or lazy-loaded:
|
|
112
|
+
const {EntityName}Page = lazy(() => import('@/pages/{Context}/{Application}/{Module}/{EntityName}Page'));
|
|
113
|
+
```
|
|
111
114
|
|
|
112
|
-
**
|
|
115
|
+
**Step 4b:** Find the correct Layout wrapper block in App.tsx:
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 />}>` |
|
|
122
|
+
|
|
123
|
+
**Step 4c:** Insert route entries INSIDE the Layout block (as children, using relative paths):
|
|
118
124
|
|
|
119
125
|
```tsx
|
|
120
126
|
// ✅ CORRECT - Route added INSIDE the layout wrapper
|
|
@@ -123,10 +129,25 @@ This updates:
|
|
|
123
129
|
<Route path="{application}/{module}" element={<{EntityName}Page />} />
|
|
124
130
|
</Route>
|
|
125
131
|
|
|
126
|
-
// ❌ FORBIDDEN - Route added OUTSIDE layout (
|
|
132
|
+
// ❌ FORBIDDEN - Route added OUTSIDE layout (shell will NOT render!)
|
|
127
133
|
<Route path="/business/{application}/{module}" element={<{EntityName}Page />} />
|
|
128
134
|
```
|
|
129
135
|
|
|
136
|
+
**Step 4d:** ALSO add the same routes inside the tenant-prefixed block:
|
|
137
|
+
|
|
138
|
+
Find `<Route path="/t/:slug">` and locate the matching `<Route path="{context}" element={<{Layout} />}>` inside it.
|
|
139
|
+
Add the **same route entries** there.
|
|
140
|
+
|
|
141
|
+
**Step 4e:** Verify wiring:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
Tool: mcp__smartstack__validate_frontend_routes
|
|
145
|
+
Args:
|
|
146
|
+
scope: "routes"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
If `appWiring.issues` is not empty, fix the wiring before proceeding.
|
|
150
|
+
|
|
130
151
|
**For new applications (no existing layout route for the context):**
|
|
131
152
|
|
|
132
153
|
If the context doesn't have a layout route yet, create one wrapping all child routes:
|
|
@@ -138,9 +159,12 @@ If the context doesn't have a layout route yet, create one wrapping all child ro
|
|
|
138
159
|
</Route>
|
|
139
160
|
```
|
|
140
161
|
|
|
141
|
-
**
|
|
162
|
+
**FORBIDDEN:**
|
|
163
|
+
- 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>`)
|
|
142
166
|
|
|
143
|
-
###
|
|
167
|
+
### 5. Verify i18n Files
|
|
144
168
|
|
|
145
169
|
> **Note:** i18n files are now auto-generated by `type: "page"` scaffold (step 1).
|
|
146
170
|
> Verify the 4 language files were created and customize the `title`/`subtitle` with the proper labels.
|
|
@@ -151,7 +175,7 @@ If the generated translations need customization (e.g., replacing generic title
|
|
|
151
175
|
- `i18n/locales/it/{entityCode}.json` → set `"title": "{labels.it}"`
|
|
152
176
|
- `i18n/locales/de/{entityCode}.json` → set `"title": "{labels.de}"`
|
|
153
177
|
|
|
154
|
-
###
|
|
178
|
+
### 6. Present Output to User
|
|
155
179
|
|
|
156
180
|
```markdown
|
|
157
181
|
## Frontend Code Generated
|
|
@@ -170,7 +194,8 @@ If the generated translations need customization (e.g., replacing generic title
|
|
|
170
194
|
|
|
171
195
|
### Routes
|
|
172
196
|
- Updated `navRoutes.generated.ts`
|
|
173
|
-
-
|
|
197
|
+
- Generated `clientRoutes.generated.tsx`
|
|
198
|
+
- Wired routes in `App.tsx` (standard + tenant blocks)
|
|
174
199
|
|
|
175
200
|
### i18n
|
|
176
201
|
- `locales/fr/{entityCode}.json`
|
|
@@ -236,8 +261,9 @@ See [references/frontend-verification.md](../references/frontend-verification.md
|
|
|
236
261
|
- React component generated
|
|
237
262
|
- API client generated with types
|
|
238
263
|
- Routes updated (nested structure)
|
|
264
|
+
- Routes wired in App.tsx (standard + tenant blocks)
|
|
239
265
|
- i18n files created (4 languages)
|
|
240
|
-
- Post-generation verification passed (all 7 checks
|
|
266
|
+
- Post-generation verification passed (all 7 checks + route wiring check)
|
|
241
267
|
- Proceeded to step-06-migration.md
|
|
242
268
|
|
|
243
269
|
## FAILURE MODES
|
|
@@ -879,7 +879,14 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
|
879
879
|
await ((DbContext)context).SaveChangesAsync(ct);
|
|
880
880
|
|
|
881
881
|
// Create modules from {Module}NavigationSeedData
|
|
882
|
-
// Create translations from {Module}
|
|
882
|
+
// Create module translations from {Module}NavigationSeedData.GetTranslationEntries()
|
|
883
|
+
|
|
884
|
+
// Create sections from {Module}NavigationSeedData.GetSectionEntries(moduleId)
|
|
885
|
+
// Use NavigationSection.Create(moduleId, code, label, description, icon, iconType, route, displayOrder)
|
|
886
|
+
// Create section translations from {Module}NavigationSeedData.GetSectionTranslationEntries()
|
|
887
|
+
|
|
888
|
+
// Create resources from {Module}NavigationSeedData.GetResourceEntries(sectionId)
|
|
889
|
+
// Use NavigationResource.Create(sectionId, code, label, entityType, route, displayOrder)
|
|
883
890
|
}
|
|
884
891
|
|
|
885
892
|
public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
|
|
@@ -908,11 +915,12 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
|
908
915
|
|
|
909
916
|
| Rule | Description |
|
|
910
917
|
|------|-------------|
|
|
911
|
-
| Factory methods | `NavigationModule.Create(...)`, `Permission.CreateForModule(...)` - NEVER `new Entity()` |
|
|
918
|
+
| Factory methods | `NavigationModule.Create(...)`, `NavigationSection.Create(...)`, `NavigationResource.Create(...)`, `Permission.CreateForModule(...)` - NEVER `new Entity()` |
|
|
912
919
|
| Idempotence | Each Seed method checks existence before inserting |
|
|
913
|
-
| SaveChanges per group | Navigation -> save -> Permissions -> save -> RolePermissions -> save |
|
|
920
|
+
| SaveChanges per group | Navigation (modules → sections → resources) -> save -> Permissions -> save -> RolePermissions -> save |
|
|
914
921
|
| Deterministic GUIDs | Use IDs from SeedData classes (not `Guid.NewGuid()`) |
|
|
915
922
|
| FK resolution by Code | Parent modules found by `Code`, not hardcoded GUID |
|
|
923
|
+
| Section/Resource conditionality | Only generate if `seedDataCore.navigationSections` / `seedDataCore.navigationResources` exist in feature.json |
|
|
916
924
|
|
|
917
925
|
---
|
|
918
926
|
|
|
@@ -921,12 +929,14 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
|
921
929
|
| Check | Status |
|
|
922
930
|
|-------|--------|
|
|
923
931
|
| ☐ Deterministic GUID (not NewGuid) | |
|
|
924
|
-
| ☐ 4 languages for each navigation entity | |
|
|
932
|
+
| ☐ 4 languages for each navigation entity (modules, sections, resources) | |
|
|
925
933
|
| ☐ Index translations continue existing sequence | |
|
|
926
934
|
| ☐ Route aligned with permission path | |
|
|
927
935
|
| ☐ DisplayOrder consistent | |
|
|
928
936
|
| ☐ CRUD permissions created (Level 3 - Module) | |
|
|
929
937
|
| ☐ Section permissions if sub-pages (Level 4) | |
|
|
938
|
+
| ☐ NavigationSections seeded (if `seedDataCore.navigationSections` in feature.json) | |
|
|
939
|
+
| ☐ NavigationResources seeded (if `seedDataCore.navigationResources` in feature.json) | |
|
|
930
940
|
| ☐ Resource permissions if sub-resources (Level 5) | |
|
|
931
941
|
| ☐ Bulk Operations permissions created | |
|
|
932
942
|
| ☐ RolePermissions assigned | |
|
|
@@ -741,6 +741,71 @@ body {
|
|
|
741
741
|
.e2e-step-module { font-weight: 600; color: var(--primary-light); font-size: 0.65rem; }
|
|
742
742
|
.e2e-step-action { color: var(--text-bright); }
|
|
743
743
|
|
|
744
|
+
/* ============================================
|
|
745
|
+
DATA MODEL (Consolidation)
|
|
746
|
+
============================================ */
|
|
747
|
+
.dm-summary {
|
|
748
|
+
display: flex; gap: 1.5rem; margin-bottom: 1.5rem;
|
|
749
|
+
padding: 1rem 1.5rem; background: var(--bg-card);
|
|
750
|
+
border: 1px solid var(--border); border-radius: 10px;
|
|
751
|
+
}
|
|
752
|
+
.dm-summary-item { display: flex; align-items: baseline; gap: 0.4rem; }
|
|
753
|
+
.dm-summary-value { font-size: 1.4rem; font-weight: 700; color: var(--text-bright); }
|
|
754
|
+
.dm-summary-label { font-size: 0.8rem; color: var(--text-muted); }
|
|
755
|
+
.dm-module-group { margin-bottom: 1.5rem; }
|
|
756
|
+
.dm-module-header {
|
|
757
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
758
|
+
padding: 0.5rem 0; margin-bottom: 0.75rem;
|
|
759
|
+
border-bottom: 2px solid var(--primary);
|
|
760
|
+
}
|
|
761
|
+
.dm-module-name { font-weight: 700; color: var(--primary-light); font-size: 1rem; }
|
|
762
|
+
.dm-module-count { font-size: 0.75rem; color: var(--text-muted); }
|
|
763
|
+
.dm-entity-grid {
|
|
764
|
+
display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 1rem;
|
|
765
|
+
}
|
|
766
|
+
.dm-entity-card {
|
|
767
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
768
|
+
border-radius: 10px; overflow: hidden;
|
|
769
|
+
transition: border-color var(--transition-fast);
|
|
770
|
+
}
|
|
771
|
+
.dm-entity-card:hover { border-color: var(--border-light); }
|
|
772
|
+
.dm-entity-header {
|
|
773
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
774
|
+
padding: 0.6rem 0.75rem; background: var(--bg-hover);
|
|
775
|
+
border-bottom: 1px solid var(--border);
|
|
776
|
+
}
|
|
777
|
+
.dm-entity-name { font-weight: 600; color: var(--text-bright); font-size: 0.95rem; }
|
|
778
|
+
.dm-entity-attr-count {
|
|
779
|
+
font-size: 0.65rem; color: var(--text-muted);
|
|
780
|
+
background: rgba(99,102,241,0.1); padding: 0.1rem 0.4rem; border-radius: 4px;
|
|
781
|
+
}
|
|
782
|
+
.dm-entity-desc {
|
|
783
|
+
padding: 0.5rem 0.75rem; font-size: 0.8rem; color: var(--text-muted);
|
|
784
|
+
border-bottom: 1px solid var(--border);
|
|
785
|
+
}
|
|
786
|
+
.dm-attr-table { width: 100%; border-collapse: collapse; }
|
|
787
|
+
.dm-attr-table th {
|
|
788
|
+
text-align: left; font-size: 0.65rem; text-transform: uppercase;
|
|
789
|
+
letter-spacing: 0.05em; color: var(--text-muted); padding: 0.4rem 0.75rem;
|
|
790
|
+
border-bottom: 1px solid var(--border); font-weight: 600;
|
|
791
|
+
}
|
|
792
|
+
.dm-attr-table td { padding: 0.35rem 0.75rem; font-size: 0.8rem; border-bottom: 1px solid rgba(51,65,85,0.2); }
|
|
793
|
+
.dm-attr-name { font-weight: 500; color: var(--text-bright); white-space: nowrap; }
|
|
794
|
+
.dm-attr-desc { color: var(--text-muted); }
|
|
795
|
+
.dm-relations {
|
|
796
|
+
padding: 0.5rem 0.75rem; border-top: 1px solid var(--border);
|
|
797
|
+
background: rgba(6,182,212,0.03);
|
|
798
|
+
}
|
|
799
|
+
.dm-relations-title {
|
|
800
|
+
font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em;
|
|
801
|
+
color: var(--accent); font-weight: 600; margin-bottom: 0.3rem;
|
|
802
|
+
}
|
|
803
|
+
.dm-relation-item {
|
|
804
|
+
font-size: 0.8rem; color: var(--text); padding: 0.15rem 0;
|
|
805
|
+
padding-left: 0.75rem; border-left: 2px solid var(--accent);
|
|
806
|
+
margin-bottom: 0.2rem;
|
|
807
|
+
}
|
|
808
|
+
|
|
744
809
|
|
|
745
810
|
/* --- 06-wireframes.css --- */
|
|
746
811
|
/* ============================================
|
|
@@ -1495,6 +1560,10 @@ body {
|
|
|
1495
1560
|
<!-- Phase 4 : Consolidation -->
|
|
1496
1561
|
<div class="nav-group">
|
|
1497
1562
|
<div class="nav-group-title">4. Consolidation</div>
|
|
1563
|
+
<a class="nav-item" onclick="showSection('consol-datamodel')" data-section="consol-datamodel">
|
|
1564
|
+
<span class="nav-icon">●</span> Modele de donnees
|
|
1565
|
+
<span class="nav-badge" id="entityCount">0</span>
|
|
1566
|
+
</a>
|
|
1498
1567
|
<a class="nav-item" onclick="showSection('consol-interactions')" data-section="consol-interactions">
|
|
1499
1568
|
<span class="nav-icon">●</span> Interactions
|
|
1500
1569
|
</a>
|
|
@@ -1899,6 +1968,15 @@ body {
|
|
|
1899
1968
|
PHASE 4 : CONSOLIDATION
|
|
1900
1969
|
================================================================ -->
|
|
1901
1970
|
|
|
1971
|
+
<!-- SECTION: Modele de donnees -->
|
|
1972
|
+
<div class="section" id="consol-datamodel" style="display:none;">
|
|
1973
|
+
<h2 class="section-title">Modele de donnees</h2>
|
|
1974
|
+
<p class="section-subtitle">Vue d'ensemble de toutes les entites metier, leurs attributs et leurs relations entre domaines.</p>
|
|
1975
|
+
<div id="dataModelContainer">
|
|
1976
|
+
<p style="color:var(--text-muted);text-align:center;padding:2rem;">Le modele de donnees sera genere a partir des specifications de chaque domaine.</p>
|
|
1977
|
+
</div>
|
|
1978
|
+
</div>
|
|
1979
|
+
|
|
1902
1980
|
<!-- SECTION: Interactions cross-module -->
|
|
1903
1981
|
<div class="section" id="consol-interactions" style="display:none;">
|
|
1904
1982
|
<h2 class="section-title">Interactions entre domaines</h2>
|
|
@@ -2095,6 +2173,8 @@ function setNestedValue(obj, path, value) {
|
|
|
2095
2173
|
function updateCounts() {
|
|
2096
2174
|
document.getElementById('stakeholderCount').textContent = data.cadrage.stakeholders.length;
|
|
2097
2175
|
document.getElementById('moduleCount').textContent = data.modules.length;
|
|
2176
|
+
const totalEntities = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.entities || []).length, 0);
|
|
2177
|
+
document.getElementById('entityCount').textContent = totalEntities;
|
|
2098
2178
|
updateModulesNav();
|
|
2099
2179
|
updateDepSelects();
|
|
2100
2180
|
}
|
|
@@ -3161,10 +3241,95 @@ function switchTab(code, tabId) {
|
|
|
3161
3241
|
CONSOLIDATION
|
|
3162
3242
|
============================================ */
|
|
3163
3243
|
function renderConsolidation() {
|
|
3244
|
+
renderDataModel();
|
|
3164
3245
|
renderConsolInteractions();
|
|
3165
3246
|
renderConsolPermissions();
|
|
3166
3247
|
}
|
|
3167
3248
|
|
|
3249
|
+
/* ============================================
|
|
3250
|
+
DATA MODEL (cross-module entity overview)
|
|
3251
|
+
============================================ */
|
|
3252
|
+
function renderDataModel() {
|
|
3253
|
+
const container = document.getElementById('dataModelContainer');
|
|
3254
|
+
if (!container) return;
|
|
3255
|
+
|
|
3256
|
+
// Collect all entities from all modules
|
|
3257
|
+
const allEntities = [];
|
|
3258
|
+
data.modules.forEach(m => {
|
|
3259
|
+
const spec = data.moduleSpecs[m.code] || {};
|
|
3260
|
+
(spec.entities || []).forEach(ent => {
|
|
3261
|
+
allEntities.push({ ...ent, moduleCode: m.code, moduleName: m.name || m.code });
|
|
3262
|
+
});
|
|
3263
|
+
});
|
|
3264
|
+
|
|
3265
|
+
if (allEntities.length === 0) {
|
|
3266
|
+
container.innerHTML = '<p style="color:var(--text-muted);text-align:center;padding:2rem;">Aucune entite definie. Specifiez les donnees dans chaque domaine (Phase 3 > onglet Donnees).</p>';
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
// Build relationship index for cross-module links
|
|
3271
|
+
const entityModuleMap = {};
|
|
3272
|
+
allEntities.forEach(e => { entityModuleMap[e.name] = e.moduleName; });
|
|
3273
|
+
|
|
3274
|
+
// Render grouped by module
|
|
3275
|
+
let html = '';
|
|
3276
|
+
|
|
3277
|
+
// Summary bar
|
|
3278
|
+
const moduleCount = data.modules.filter(m => (data.moduleSpecs[m.code]?.entities || []).length > 0).length;
|
|
3279
|
+
const relCount = allEntities.reduce((sum, e) => sum + (e.relationships || []).length, 0);
|
|
3280
|
+
html += `
|
|
3281
|
+
<div class="dm-summary">
|
|
3282
|
+
<div class="dm-summary-item"><span class="dm-summary-value">${allEntities.length}</span><span class="dm-summary-label">Entites</span></div>
|
|
3283
|
+
<div class="dm-summary-item"><span class="dm-summary-value">${moduleCount}</span><span class="dm-summary-label">Domaines</span></div>
|
|
3284
|
+
<div class="dm-summary-item"><span class="dm-summary-value">${relCount}</span><span class="dm-summary-label">Relations</span></div>
|
|
3285
|
+
</div>`;
|
|
3286
|
+
|
|
3287
|
+
// Entity cards grouped by module
|
|
3288
|
+
data.modules.forEach(m => {
|
|
3289
|
+
const spec = data.moduleSpecs[m.code] || {};
|
|
3290
|
+
const entities = spec.entities || [];
|
|
3291
|
+
if (entities.length === 0) return;
|
|
3292
|
+
|
|
3293
|
+
html += `<div class="dm-module-group">`;
|
|
3294
|
+
html += `<div class="dm-module-header">
|
|
3295
|
+
<span class="dm-module-name">${m.name || m.code}</span>
|
|
3296
|
+
<span class="dm-module-count">${entities.length} entite${entities.length > 1 ? 's' : ''}</span>
|
|
3297
|
+
</div>`;
|
|
3298
|
+
|
|
3299
|
+
html += `<div class="dm-entity-grid">`;
|
|
3300
|
+
entities.forEach(ent => {
|
|
3301
|
+
const attrs = ent.attributes || [];
|
|
3302
|
+
const rels = ent.relationships || [];
|
|
3303
|
+
html += `
|
|
3304
|
+
<div class="dm-entity-card">
|
|
3305
|
+
<div class="dm-entity-header">
|
|
3306
|
+
<span class="dm-entity-name">${ent.name}</span>
|
|
3307
|
+
<span class="dm-entity-attr-count">${attrs.length} champ${attrs.length > 1 ? 's' : ''}</span>
|
|
3308
|
+
</div>
|
|
3309
|
+
${ent.description ? `<div class="dm-entity-desc">${ent.description}</div>` : ''}
|
|
3310
|
+
${attrs.length > 0 ? `
|
|
3311
|
+
<table class="dm-attr-table">
|
|
3312
|
+
<thead><tr><th>Champ</th><th>Description</th></tr></thead>
|
|
3313
|
+
<tbody>
|
|
3314
|
+
${attrs.map(a => `<tr><td class="dm-attr-name">${a.name}</td><td class="dm-attr-desc">${a.description || ''}</td></tr>`).join('')}
|
|
3315
|
+
</tbody>
|
|
3316
|
+
</table>` : ''}
|
|
3317
|
+
${rels.length > 0 ? `
|
|
3318
|
+
<div class="dm-relations">
|
|
3319
|
+
<div class="dm-relations-title">Relations</div>
|
|
3320
|
+
${rels.map(r => {
|
|
3321
|
+
const relText = typeof r === 'string' ? r : (r.target + ' (' + r.type + ') - ' + (r.description || ''));
|
|
3322
|
+
return `<div class="dm-relation-item">${relText}</div>`;
|
|
3323
|
+
}).join('')}
|
|
3324
|
+
</div>` : ''}
|
|
3325
|
+
</div>`;
|
|
3326
|
+
});
|
|
3327
|
+
html += `</div></div>`;
|
|
3328
|
+
});
|
|
3329
|
+
|
|
3330
|
+
container.innerHTML = html;
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3168
3333
|
function renderConsolInteractions() {
|
|
3169
3334
|
const container = document.getElementById('consolInteractions');
|
|
3170
3335
|
if (!container || data.dependencies.length === 0) return;
|
|
@@ -76,6 +76,8 @@ function setNestedValue(obj, path, value) {
|
|
|
76
76
|
function updateCounts() {
|
|
77
77
|
document.getElementById('stakeholderCount').textContent = data.cadrage.stakeholders.length;
|
|
78
78
|
document.getElementById('moduleCount').textContent = data.modules.length;
|
|
79
|
+
const totalEntities = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.entities || []).length, 0);
|
|
80
|
+
document.getElementById('entityCount').textContent = totalEntities;
|
|
79
81
|
updateModulesNav();
|
|
80
82
|
updateDepSelects();
|
|
81
83
|
}
|