@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.
Files changed (33) hide show
  1. package/dist/index.js +26 -28
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +626 -141
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/agents/efcore/migration.md +15 -0
  7. package/templates/skills/apex/steps/step-04-validate.md +64 -5
  8. package/templates/skills/application/references/frontend-verification.md +20 -0
  9. package/templates/skills/application/steps/step-04-backend.md +17 -1
  10. package/templates/skills/application/steps/step-05-frontend.md +49 -23
  11. package/templates/skills/application/templates-seed.md +14 -4
  12. package/templates/skills/business-analyse/html/ba-interactive.html +165 -0
  13. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +2 -0
  14. package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +85 -0
  15. package/templates/skills/business-analyse/html/src/styles/05-modules.css +65 -0
  16. package/templates/skills/business-analyse/html/src/template.html +13 -0
  17. package/templates/skills/business-analyse/schemas/application-schema.json +5 -0
  18. package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +1 -0
  19. package/templates/skills/business-analyse/steps/step-01-cadrage.md +90 -0
  20. package/templates/skills/business-analyse/steps/step-02-decomposition.md +4 -0
  21. package/templates/skills/business-analyse/steps/step-03a1-setup.md +39 -0
  22. package/templates/skills/efcore/steps/shared/step-00-init.md +55 -0
  23. package/templates/skills/ralph-loop/SKILL.md +1 -0
  24. package/templates/skills/ralph-loop/references/category-rules.md +131 -27
  25. package/templates/skills/ralph-loop/references/compact-loop.md +61 -3
  26. package/templates/skills/ralph-loop/references/core-seed-data.md +251 -5
  27. package/templates/skills/ralph-loop/references/error-classification.md +143 -0
  28. package/templates/skills/ralph-loop/steps/step-05-report.md +54 -0
  29. package/templates/skills/review-code/references/smartstack-conventions.md +16 -0
  30. package/templates/skills/validate-feature/SKILL.md +11 -1
  31. package/templates/skills/validate-feature/steps/step-00-dependencies.md +121 -0
  32. package/templates/skills/validate-feature/steps/step-04-api-smoke.md +61 -13
  33. package/templates/skills/validate-feature/steps/step-05-db-validation.md +250 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "3.14.0",
3
+ "version": "3.16.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -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. Seed Data Completeness Check (if needs_seed_data)
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
- ## 6. Acceptance Criteria POST-CHECK
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
- ## 7. Validation Summary
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
- ## 8. Save Output (if save_mode)
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
- ## 9. Route to Next Step
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
- Based on navigation context:
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. Update Route Configuration
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 updates:
97
- - `navRoutes.generated.ts` - Route registry
98
- - `routes.tsx` / `App.tsx` - React Router configuration
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
- #### ⚠️ CRITICAL: Route Placement Inside Layout Wrapper
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
- **BLOCKING RULE:** New routes MUST be added as **children** of the existing layout route for the target context. SmartStack layouts (`AdminLayout`, `BusinessLayout`, `UserLayout`) provide the shell (header with AvatarMenu, sidebar, navigation) via React Router's `<Outlet />`.
105
+ After `scaffold_routes` generates the route files, you MUST manually insert the routes into `App.tsx`:
103
106
 
104
- **If routes are placed OUTSIDE the layout wrapper, the entire shell (header, sidebar, AvatarMenu) will NOT render.**
107
+ **Step 4a:** Import the page components at the top of App.tsx:
105
108
 
106
- | Context | Layout Route to find in App.tsx | Layout Component |
107
- |---------|--------------------------------|------------------|
108
- | `platform.*` | `<Route path="/platform" element={<AdminLayout />}>` | `AdminLayout` |
109
- | `business.*` | `<Route path="/business" element={<BusinessLayout />}>` | `BusinessLayout` |
110
- | `personal.*` | `<Route path="/personal/myspace" element={<UserLayout />}>` | `UserLayout` |
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
- **Insertion rules:**
115
+ **Step 4b:** Find the correct Layout wrapper block in App.tsx:
113
116
 
114
- 1. Open `App.tsx` (or `routes.tsx`)
115
- 2. Find the existing `<Route path="/{context}" element={<{Context}Layout />}>` block
116
- 3. Add the new routes **INSIDE** that block as children
117
- 4. Use **relative paths** (not absolute) since they are children of the context route
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 (AvatarMenu will disappear!)
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
- **Remember:** Also add the routes inside the tenant-prefixed block (`/t/:slug/...`) if it exists.
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
- ### 4. Verify i18n Files
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
- ### 5. Present Output to User
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
- - Updated `routes.tsx` with nested route
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 including TypeScript build)
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}NavigationTranslationSeedData
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">&#9679;</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">&#9679;</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
  }