@atlashub/smartstack-cli 3.20.0 → 3.22.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 +70 -6
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +69 -3
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/project/api.ts.template +8 -29
- package/templates/project/appsettings.json.template +1 -0
- 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 +642 -156
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
- package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +95 -8
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
- package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
- package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
- package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
- package/templates/skills/business-analyse/html/src/template.html +8 -76
- package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
- package/templates/skills/business-analyse/references/deploy-data-build.md +13 -9
- package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
- package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
- package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
- package/templates/skills/business-analyse/references/validate-incremental-html.md +28 -5
- 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 +169 -2
- package/templates/skills/business-analyse/steps/step-03d-validate.md +217 -28
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +189 -3
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +55 -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
|
@@ -139,7 +139,7 @@ body {
|
|
|
139
139
|
PRINT
|
|
140
140
|
============================================ */
|
|
141
141
|
@media print {
|
|
142
|
-
.sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove
|
|
142
|
+
.sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove { display: none !important; }
|
|
143
143
|
.main { max-width: 100%; padding: 0; }
|
|
144
144
|
.section { display: block !important; page-break-inside: avoid; }
|
|
145
145
|
body { background: #fff; color: #1a1a1a; }
|
|
@@ -152,67 +152,125 @@ body {
|
|
|
152
152
|
|
|
153
153
|
/* --- 03-navigation.css --- */
|
|
154
154
|
/* ============================================
|
|
155
|
-
SIDEBAR -
|
|
155
|
+
SIDEBAR - Hierarchical Tree Navigation
|
|
156
156
|
============================================ */
|
|
157
|
-
|
|
157
|
+
|
|
158
|
+
/* Sidebar header */
|
|
159
|
+
.sidebar-header {
|
|
160
|
+
padding: 0.75rem 1rem;
|
|
161
|
+
border-bottom: 1px solid var(--border);
|
|
162
|
+
background: var(--bg-hover);
|
|
163
|
+
}
|
|
164
|
+
.sidebar-app-name {
|
|
165
|
+
font-size: 0.95rem;
|
|
166
|
+
font-weight: 600;
|
|
167
|
+
color: var(--primary-light);
|
|
168
|
+
display: block;
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
text-overflow: ellipsis;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Nav groups (top-level: Cadrage, Modules, Consolidation, Synthese) */
|
|
175
|
+
.nav-group { padding: 0.4rem 0; }
|
|
158
176
|
.nav-group + .nav-group { border-top: 1px solid var(--border); }
|
|
159
177
|
|
|
160
178
|
.nav-group-title {
|
|
161
|
-
font-size: 0.
|
|
179
|
+
font-size: 0.7rem;
|
|
162
180
|
text-transform: uppercase;
|
|
163
181
|
letter-spacing: 0.1em;
|
|
164
182
|
color: var(--text-muted);
|
|
165
|
-
padding: 0 1rem;
|
|
166
|
-
margin-bottom: 0.
|
|
183
|
+
padding: 0.4rem 1rem;
|
|
184
|
+
margin-bottom: 0.15rem;
|
|
167
185
|
font-weight: 600;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 0.3rem;
|
|
190
|
+
user-select: none;
|
|
191
|
+
transition: color var(--transition-fast);
|
|
168
192
|
}
|
|
193
|
+
.nav-group-title:hover { color: var(--text-bright); }
|
|
169
194
|
|
|
195
|
+
/* Chevron icon for expand/collapse */
|
|
196
|
+
.nav-chevron {
|
|
197
|
+
font-size: 0.6rem;
|
|
198
|
+
display: inline-block;
|
|
199
|
+
transition: transform var(--transition-fast);
|
|
200
|
+
color: var(--text-muted);
|
|
201
|
+
width: 12px;
|
|
202
|
+
text-align: center;
|
|
203
|
+
flex-shrink: 0;
|
|
204
|
+
}
|
|
205
|
+
.nav-chevron.expanded { transform: rotate(90deg); }
|
|
206
|
+
|
|
207
|
+
/* Nav items (leaf nodes) */
|
|
170
208
|
.nav-item {
|
|
171
209
|
display: flex;
|
|
172
210
|
align-items: center;
|
|
173
|
-
gap: 0.
|
|
174
|
-
padding: 0.
|
|
211
|
+
gap: 0.4rem;
|
|
212
|
+
padding: 0.35rem 1rem;
|
|
175
213
|
color: var(--text);
|
|
176
214
|
text-decoration: none;
|
|
177
|
-
font-size: 0.
|
|
215
|
+
font-size: 0.82rem;
|
|
178
216
|
cursor: pointer;
|
|
179
217
|
transition: all var(--transition-fast);
|
|
180
218
|
border-left: 3px solid transparent;
|
|
181
219
|
}
|
|
182
220
|
.nav-item:hover { background: var(--bg-hover); color: var(--text-bright); }
|
|
183
|
-
.nav-item.active {
|
|
221
|
+
.nav-item.active {
|
|
222
|
+
background: rgba(99,102,241,0.1);
|
|
223
|
+
border-left-color: var(--primary);
|
|
224
|
+
color: var(--primary-light);
|
|
225
|
+
font-weight: 500;
|
|
226
|
+
}
|
|
184
227
|
|
|
185
|
-
.nav-item .nav-icon {
|
|
228
|
+
.nav-item .nav-icon {
|
|
229
|
+
font-size: 0.45rem;
|
|
230
|
+
width: 12px;
|
|
231
|
+
text-align: center;
|
|
232
|
+
color: var(--border-light);
|
|
233
|
+
flex-shrink: 0;
|
|
234
|
+
}
|
|
186
235
|
.nav-item .nav-badge {
|
|
187
236
|
margin-left: auto;
|
|
188
|
-
font-size: 0.
|
|
237
|
+
font-size: 0.6rem;
|
|
189
238
|
background: var(--bg-hover);
|
|
190
|
-
padding: 0.1rem 0.
|
|
239
|
+
padding: 0.1rem 0.35rem;
|
|
191
240
|
border-radius: 10px;
|
|
192
241
|
color: var(--text-muted);
|
|
242
|
+
min-width: 16px;
|
|
243
|
+
text-align: center;
|
|
193
244
|
}
|
|
194
245
|
|
|
195
|
-
|
|
196
|
-
.nav-children
|
|
246
|
+
/* Nested children (indented) */
|
|
247
|
+
.nav-children { margin-left: 0; }
|
|
248
|
+
.nav-children .nav-item { padding-left: 1.6rem; font-size: 0.8rem; }
|
|
249
|
+
.nav-children .nav-children .nav-item { padding-left: 2.4rem; font-size: 0.78rem; }
|
|
250
|
+
.nav-children .nav-children .nav-children .nav-item { padding-left: 3rem; font-size: 0.75rem; }
|
|
197
251
|
|
|
198
|
-
/*
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
padding: 0.75rem 1rem; border-bottom: 1px solid var(--border);
|
|
252
|
+
/* Module header in nav (collapsible) */
|
|
253
|
+
.nav-module-header {
|
|
254
|
+
font-weight: 500;
|
|
255
|
+
color: var(--text-bright);
|
|
256
|
+
font-size: 0.85rem;
|
|
204
257
|
}
|
|
205
|
-
.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
258
|
+
.nav-module-header:hover { color: var(--primary-light); }
|
|
259
|
+
|
|
260
|
+
/* Section/Resource items in nav */
|
|
261
|
+
.nav-section-item { margin-top: 0.1rem; }
|
|
262
|
+
.nav-section-link { font-style: italic; }
|
|
263
|
+
.nav-icon-section { font-size: 0.5rem; color: var(--accent); }
|
|
264
|
+
.nav-icon-resource { font-size: 0.7rem; color: var(--border-light); }
|
|
265
|
+
.nav-resource-link {
|
|
266
|
+
cursor: default;
|
|
267
|
+
font-size: 0.72rem;
|
|
268
|
+
color: var(--text-muted);
|
|
269
|
+
padding-top: 0.2rem;
|
|
270
|
+
padding-bottom: 0.2rem;
|
|
211
271
|
}
|
|
212
|
-
.
|
|
213
|
-
.
|
|
214
|
-
.phase-line { flex: 1; height: 2px; background: var(--border); }
|
|
215
|
-
.phase-line.completed { background: var(--success); }
|
|
272
|
+
.nav-resource-link:hover { background: transparent; color: var(--text-muted); }
|
|
273
|
+
.nav-resources { margin-left: 0.5rem; }
|
|
216
274
|
|
|
217
275
|
|
|
218
276
|
/* --- 04-cards.css --- */
|
|
@@ -806,6 +864,70 @@ body {
|
|
|
806
864
|
margin-bottom: 0.2rem;
|
|
807
865
|
}
|
|
808
866
|
|
|
867
|
+
/* ============================================
|
|
868
|
+
STRUCTURE TAB (Sections/Resources)
|
|
869
|
+
============================================ */
|
|
870
|
+
.struct-section {
|
|
871
|
+
background: var(--bg-card);
|
|
872
|
+
border: 1px solid var(--border);
|
|
873
|
+
border-radius: 10px;
|
|
874
|
+
margin-bottom: 1rem;
|
|
875
|
+
overflow: hidden;
|
|
876
|
+
}
|
|
877
|
+
.struct-section-header {
|
|
878
|
+
display: flex;
|
|
879
|
+
align-items: center;
|
|
880
|
+
gap: 0.75rem;
|
|
881
|
+
padding: 0.75rem 1rem;
|
|
882
|
+
background: var(--bg-hover);
|
|
883
|
+
border-bottom: 1px solid var(--border);
|
|
884
|
+
}
|
|
885
|
+
.struct-section-code {
|
|
886
|
+
font-weight: 600;
|
|
887
|
+
color: var(--text-bright);
|
|
888
|
+
font-size: 0.95rem;
|
|
889
|
+
}
|
|
890
|
+
.struct-section-desc {
|
|
891
|
+
flex: 1;
|
|
892
|
+
font-size: 0.8rem;
|
|
893
|
+
color: var(--text-muted);
|
|
894
|
+
}
|
|
895
|
+
.struct-section-badge {
|
|
896
|
+
font-size: 0.65rem;
|
|
897
|
+
color: var(--text-muted);
|
|
898
|
+
background: rgba(99,102,241,0.1);
|
|
899
|
+
padding: 0.1rem 0.5rem;
|
|
900
|
+
border-radius: 4px;
|
|
901
|
+
white-space: nowrap;
|
|
902
|
+
}
|
|
903
|
+
.struct-resources {
|
|
904
|
+
padding: 0.5rem 0;
|
|
905
|
+
}
|
|
906
|
+
.struct-resource {
|
|
907
|
+
display: flex;
|
|
908
|
+
align-items: baseline;
|
|
909
|
+
gap: 0.5rem;
|
|
910
|
+
padding: 0.35rem 1rem 0.35rem 1.5rem;
|
|
911
|
+
font-size: 0.85rem;
|
|
912
|
+
transition: background var(--transition-fast);
|
|
913
|
+
}
|
|
914
|
+
.struct-resource:hover {
|
|
915
|
+
background: var(--bg-hover);
|
|
916
|
+
}
|
|
917
|
+
.struct-resource-icon {
|
|
918
|
+
color: var(--primary-light);
|
|
919
|
+
font-size: 0.7rem;
|
|
920
|
+
flex-shrink: 0;
|
|
921
|
+
}
|
|
922
|
+
.struct-resource-name {
|
|
923
|
+
font-weight: 500;
|
|
924
|
+
color: var(--text-bright);
|
|
925
|
+
}
|
|
926
|
+
.struct-resource-desc {
|
|
927
|
+
font-size: 0.8rem;
|
|
928
|
+
color: var(--text-muted);
|
|
929
|
+
}
|
|
930
|
+
|
|
809
931
|
|
|
810
932
|
/* --- 06-wireframes.css --- */
|
|
811
933
|
/* ============================================
|
|
@@ -871,6 +993,48 @@ body {
|
|
|
871
993
|
.svg-wireframe svg {
|
|
872
994
|
max-width: 100%;
|
|
873
995
|
height: auto;
|
|
996
|
+
display: block;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/* ============================================
|
|
1000
|
+
WIREFRAME VIEW TOGGLE (SVG / ASCII)
|
|
1001
|
+
============================================ */
|
|
1002
|
+
.wireframe-toggle {
|
|
1003
|
+
display: flex;
|
|
1004
|
+
gap: 2px;
|
|
1005
|
+
background: var(--bg-dark);
|
|
1006
|
+
border-radius: 6px;
|
|
1007
|
+
padding: 2px;
|
|
1008
|
+
border: 1px solid var(--border);
|
|
1009
|
+
margin-left: auto;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.wireframe-toggle-btn {
|
|
1013
|
+
padding: 0.25rem 0.6rem;
|
|
1014
|
+
font-size: 0.7rem;
|
|
1015
|
+
font-weight: 500;
|
|
1016
|
+
letter-spacing: 0.02em;
|
|
1017
|
+
border: none;
|
|
1018
|
+
border-radius: 4px;
|
|
1019
|
+
cursor: pointer;
|
|
1020
|
+
background: transparent;
|
|
1021
|
+
color: var(--text-muted);
|
|
1022
|
+
transition: all 0.15s ease;
|
|
1023
|
+
font-family: inherit;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
.wireframe-toggle-btn:hover {
|
|
1027
|
+
color: var(--text-bright);
|
|
1028
|
+
background: var(--bg-hover);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
.wireframe-toggle-btn.active {
|
|
1032
|
+
background: var(--primary);
|
|
1033
|
+
color: #ffffff;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
.wireframe-view:not(.active) {
|
|
1037
|
+
display: none;
|
|
874
1038
|
}
|
|
875
1039
|
|
|
876
1040
|
.wireframe-description {
|
|
@@ -1484,6 +1648,7 @@ body {
|
|
|
1484
1648
|
<span class="header-app-name" id="appName">{{APPLICATION_NAME}}</span>
|
|
1485
1649
|
<div class="header-spacer"></div>
|
|
1486
1650
|
<div class="header-actions">
|
|
1651
|
+
<button class="btn btn-sm" onclick="resetToEmbedded()" title="Reinitialiser depuis les donnees d'origine (supprime les modifications locales)">Reset</button>
|
|
1487
1652
|
<button class="btn btn-sm" onclick="saveToLocalStorage()" title="Sauvegarder les modifications dans le navigateur">Sauvegarder</button>
|
|
1488
1653
|
<button class="btn btn-sm btn-review" onclick="saveReviewJSON()" title="Sauvegarder les corrections pour creer une nouvelle version">Sauvegarder corrections</button>
|
|
1489
1654
|
<button class="btn btn-sm btn-primary" onclick="exportJSON()" title="Exporter les donnees au format JSON pour l'extraction">Exporter JSON</button>
|
|
@@ -1496,85 +1661,16 @@ body {
|
|
|
1496
1661
|
|
|
1497
1662
|
<div class="body" id="appBody">
|
|
1498
1663
|
<!-- ============================================
|
|
1499
|
-
SIDEBAR - Navigation
|
|
1664
|
+
SIDEBAR - Navigation hierarchique
|
|
1500
1665
|
============================================ -->
|
|
1501
1666
|
<aside class="sidebar">
|
|
1502
|
-
<!--
|
|
1503
|
-
<div class="
|
|
1504
|
-
<
|
|
1505
|
-
<div class="phase-line" id="pline-1"></div>
|
|
1506
|
-
<div class="phase-dot" id="phase-2" title="Decomposition">2</div>
|
|
1507
|
-
<div class="phase-line" id="pline-2"></div>
|
|
1508
|
-
<div class="phase-dot" id="phase-3" title="Specification">3</div>
|
|
1509
|
-
<div class="phase-line" id="pline-3"></div>
|
|
1510
|
-
<div class="phase-dot" id="phase-4" title="Consolidation">4</div>
|
|
1511
|
-
<div class="phase-line" id="pline-4"></div>
|
|
1512
|
-
<div class="phase-dot" id="phase-5" title="Synthese">5</div>
|
|
1513
|
-
</div>
|
|
1514
|
-
|
|
1515
|
-
<!-- Phase 1 : Cadrage -->
|
|
1516
|
-
<div class="nav-group">
|
|
1517
|
-
<div class="nav-group-title">1. Cadrage</div>
|
|
1518
|
-
<a class="nav-item active" onclick="showSection('cadrage-context')" data-section="cadrage-context">
|
|
1519
|
-
<span class="nav-icon">●</span> Contexte
|
|
1520
|
-
</a>
|
|
1521
|
-
<a class="nav-item" onclick="showSection('cadrage-stakeholders')" data-section="cadrage-stakeholders">
|
|
1522
|
-
<span class="nav-icon">●</span> Parties prenantes
|
|
1523
|
-
<span class="nav-badge" id="stakeholderCount">0</span>
|
|
1524
|
-
</a>
|
|
1525
|
-
<a class="nav-item" onclick="showSection('cadrage-scope')" data-section="cadrage-scope">
|
|
1526
|
-
<span class="nav-icon">●</span> Perimetre fonctionnel
|
|
1527
|
-
</a>
|
|
1528
|
-
<a class="nav-item" onclick="showSection('cadrage-risks')" data-section="cadrage-risks">
|
|
1529
|
-
<span class="nav-icon">●</span> Risques et hypotheses
|
|
1530
|
-
</a>
|
|
1531
|
-
<a class="nav-item" onclick="showSection('cadrage-success')" data-section="cadrage-success">
|
|
1532
|
-
<span class="nav-icon">●</span> Criteres de reussite
|
|
1533
|
-
</a>
|
|
1534
|
-
</div>
|
|
1535
|
-
|
|
1536
|
-
<!-- Phase 2 : Decomposition -->
|
|
1537
|
-
<div class="nav-group">
|
|
1538
|
-
<div class="nav-group-title">2. Decomposition</div>
|
|
1539
|
-
<a class="nav-item" onclick="showSection('decomp-modules')" data-section="decomp-modules">
|
|
1540
|
-
<span class="nav-icon">●</span> Domaines fonctionnels
|
|
1541
|
-
<span class="nav-badge" id="moduleCount">0</span>
|
|
1542
|
-
</a>
|
|
1543
|
-
<a class="nav-item" onclick="showSection('decomp-dependencies')" data-section="decomp-dependencies">
|
|
1544
|
-
<span class="nav-icon">●</span> Dependances
|
|
1545
|
-
</a>
|
|
1546
|
-
</div>
|
|
1547
|
-
|
|
1548
|
-
<!-- Phase 3 : Specification par module -->
|
|
1549
|
-
<div class="nav-group" id="modulesNav">
|
|
1550
|
-
<div class="nav-group-title">3. Specification</div>
|
|
1551
|
-
<!-- Populated dynamically per module -->
|
|
1552
|
-
</div>
|
|
1553
|
-
|
|
1554
|
-
<!-- Phase 4 : Consolidation -->
|
|
1555
|
-
<div class="nav-group">
|
|
1556
|
-
<div class="nav-group-title">4. Consolidation</div>
|
|
1557
|
-
<a class="nav-item" onclick="showSection('consol-datamodel')" data-section="consol-datamodel">
|
|
1558
|
-
<span class="nav-icon">●</span> Modele de donnees
|
|
1559
|
-
<span class="nav-badge" id="entityCount">0</span>
|
|
1560
|
-
</a>
|
|
1561
|
-
<a class="nav-item" onclick="showSection('consol-interactions')" data-section="consol-interactions">
|
|
1562
|
-
<span class="nav-icon">●</span> Interactions
|
|
1563
|
-
</a>
|
|
1564
|
-
<a class="nav-item" onclick="showSection('consol-permissions')" data-section="consol-permissions">
|
|
1565
|
-
<span class="nav-icon">●</span> Coherence des acces
|
|
1566
|
-
</a>
|
|
1567
|
-
<a class="nav-item" onclick="showSection('consol-flows')" data-section="consol-flows">
|
|
1568
|
-
<span class="nav-icon">●</span> Parcours bout en bout
|
|
1569
|
-
</a>
|
|
1667
|
+
<!-- Application Name -->
|
|
1668
|
+
<div class="sidebar-header">
|
|
1669
|
+
<span class="sidebar-app-name" id="sidebarAppName">{{APPLICATION_NAME}}</span>
|
|
1570
1670
|
</div>
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
<div class="nav-group-title">5. Synthese</div>
|
|
1575
|
-
<a class="nav-item" onclick="showSection('handoff-summary')" data-section="handoff-summary">
|
|
1576
|
-
<span class="nav-icon">●</span> Vue d'ensemble
|
|
1577
|
-
</a>
|
|
1671
|
+
<!-- Dynamic Tree Navigation -->
|
|
1672
|
+
<div id="sidebarNav">
|
|
1673
|
+
<!-- Populated by buildNavTree() -->
|
|
1578
1674
|
</div>
|
|
1579
1675
|
</aside>
|
|
1580
1676
|
|
|
@@ -2092,6 +2188,14 @@ data.cadrage.scope = data.cadrage.scope || { vital: [], important: [], optional:
|
|
|
2092
2188
|
|
|
2093
2189
|
// Defensive init: moduleSpecs (may be missing if LLM didn't follow mapping)
|
|
2094
2190
|
data.moduleSpecs = data.moduleSpecs || {};
|
|
2191
|
+
// Ensure ALL modules have a moduleSpecs entry (prevents missing schemas)
|
|
2192
|
+
(data.modules || []).forEach(function(m) {
|
|
2193
|
+
if (!data.moduleSpecs[m.code]) {
|
|
2194
|
+
data.moduleSpecs[m.code] = { useCases: [], businessRules: [], entities: [], permissions: [], notes: '' };
|
|
2195
|
+
}
|
|
2196
|
+
// Ensure anticipatedSections array exists
|
|
2197
|
+
m.anticipatedSections = m.anticipatedSections || [];
|
|
2198
|
+
});
|
|
2095
2199
|
|
|
2096
2200
|
// Vibe coding mode: hide non-relevant sections
|
|
2097
2201
|
const isVibeCoding = data.metadata?.vibeCoding === true;
|
|
@@ -2132,11 +2236,7 @@ function setNestedValue(obj, path, value) {
|
|
|
2132
2236
|
}
|
|
2133
2237
|
|
|
2134
2238
|
function updateCounts() {
|
|
2135
|
-
|
|
2136
|
-
document.getElementById('moduleCount').textContent = data.modules.length;
|
|
2137
|
-
const totalEntities = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.entities || []).length, 0);
|
|
2138
|
-
document.getElementById('entityCount').textContent = totalEntities;
|
|
2139
|
-
updateModulesNav();
|
|
2239
|
+
buildNavTree();
|
|
2140
2240
|
updateDepSelects();
|
|
2141
2241
|
}
|
|
2142
2242
|
|
|
@@ -2184,7 +2284,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
2184
2284
|
renderConsolidation();
|
|
2185
2285
|
renderHandoff();
|
|
2186
2286
|
renderE2EFlows();
|
|
2187
|
-
|
|
2287
|
+
buildNavTree();
|
|
2288
|
+
updateDepSelects();
|
|
2188
2289
|
initInlineComments();
|
|
2189
2290
|
renderReviewPanel();
|
|
2190
2291
|
});
|
|
@@ -2192,9 +2293,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
2192
2293
|
|
|
2193
2294
|
/* --- 02-navigation.js --- */
|
|
2194
2295
|
/* ============================================
|
|
2195
|
-
NAVIGATION
|
|
2296
|
+
NAVIGATION - Hierarchical Tree
|
|
2196
2297
|
============================================ */
|
|
2197
2298
|
let currentSectionId = 'cadrage-context';
|
|
2299
|
+
let navCollapseState = {};
|
|
2300
|
+
|
|
2301
|
+
/* ---------- Core ---------- */
|
|
2198
2302
|
|
|
2199
2303
|
function showSection(sectionId) {
|
|
2200
2304
|
currentSectionId = sectionId;
|
|
@@ -2202,9 +2306,30 @@ function showSection(sectionId) {
|
|
|
2202
2306
|
const section = document.getElementById(sectionId);
|
|
2203
2307
|
if (section) section.style.display = 'block';
|
|
2204
2308
|
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2309
|
+
// Highlight active nav item
|
|
2310
|
+
document.querySelectorAll('#sidebarNav .nav-item').forEach(n => n.classList.remove('active'));
|
|
2311
|
+
const navItem = document.querySelector('#sidebarNav [data-section="' + sectionId + '"]');
|
|
2312
|
+
if (navItem) {
|
|
2313
|
+
navItem.classList.add('active');
|
|
2314
|
+
// Auto-expand parent groups to reveal active item
|
|
2315
|
+
let parent = navItem.parentElement;
|
|
2316
|
+
while (parent && parent.id !== 'sidebarNav') {
|
|
2317
|
+
if (parent.classList.contains('nav-children') && parent.style.display === 'none') {
|
|
2318
|
+
parent.style.display = '';
|
|
2319
|
+
const groupEl = parent.parentElement;
|
|
2320
|
+
if (groupEl) {
|
|
2321
|
+
const groupId = groupEl.dataset.groupId;
|
|
2322
|
+
if (groupId) {
|
|
2323
|
+
navCollapseState[groupId] = false;
|
|
2324
|
+
const chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
|
|
2325
|
+
if (chevron) chevron.classList.add('expanded');
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
parent = parent.parentElement;
|
|
2330
|
+
}
|
|
2331
|
+
saveNavState();
|
|
2332
|
+
}
|
|
2208
2333
|
}
|
|
2209
2334
|
|
|
2210
2335
|
function restoreCurrentSection() {
|
|
@@ -2214,6 +2339,187 @@ function restoreCurrentSection() {
|
|
|
2214
2339
|
}
|
|
2215
2340
|
}
|
|
2216
2341
|
|
|
2342
|
+
/* ---------- Tree Builder ---------- */
|
|
2343
|
+
|
|
2344
|
+
function buildNavTree() {
|
|
2345
|
+
const nav = document.getElementById('sidebarNav');
|
|
2346
|
+
if (!nav) return;
|
|
2347
|
+
|
|
2348
|
+
nav.innerHTML =
|
|
2349
|
+
renderNavGroup('cadrage', 'Cadrage', buildCadrageItems()) +
|
|
2350
|
+
renderNavGroup('modules', 'Modules (' + data.modules.length + ')', buildModuleItems()) +
|
|
2351
|
+
renderNavGroup('consolidation', 'Consolidation', buildConsolidationItems()) +
|
|
2352
|
+
renderNavGroup('synthese', 'Synthese', buildSyntheseItems());
|
|
2353
|
+
|
|
2354
|
+
restoreNavState();
|
|
2355
|
+
highlightActiveNavItem();
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
function renderNavGroup(id, title, itemsHtml) {
|
|
2359
|
+
const collapsed = navCollapseState[id] === true;
|
|
2360
|
+
return '<div class="nav-group" data-group-id="' + id + '">' +
|
|
2361
|
+
'<div class="nav-group-title" onclick="toggleNavGroup(\'' + id + '\')">' +
|
|
2362
|
+
'<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">▸</span> ' +
|
|
2363
|
+
title +
|
|
2364
|
+
'</div>' +
|
|
2365
|
+
'<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>' +
|
|
2366
|
+
itemsHtml +
|
|
2367
|
+
'</div>' +
|
|
2368
|
+
'</div>';
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
function renderNavItem(sectionId, label, badge) {
|
|
2372
|
+
return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\')" data-section="' + sectionId + '">' +
|
|
2373
|
+
'<span class="nav-icon">●</span> ' + label +
|
|
2374
|
+
(badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
|
|
2375
|
+
'</a>';
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
/* ---------- Group Builders ---------- */
|
|
2379
|
+
|
|
2380
|
+
function buildCadrageItems() {
|
|
2381
|
+
return renderNavItem('cadrage-context', 'Contexte') +
|
|
2382
|
+
renderNavItem('cadrage-stakeholders', 'Parties prenantes', data.cadrage.stakeholders.length) +
|
|
2383
|
+
renderNavItem('cadrage-scope', 'Perimetre fonctionnel') +
|
|
2384
|
+
renderNavItem('cadrage-risks', 'Risques et hypotheses', data.cadrage.risks.length) +
|
|
2385
|
+
renderNavItem('cadrage-success', 'Criteres de reussite');
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
function buildModuleItems() {
|
|
2389
|
+
let html = '';
|
|
2390
|
+
data.modules.forEach(function(mod) {
|
|
2391
|
+
html += renderModuleNavItem(mod);
|
|
2392
|
+
});
|
|
2393
|
+
// Global module views at bottom
|
|
2394
|
+
html += renderNavItem('decomp-modules', 'Vue d\'ensemble', data.modules.length);
|
|
2395
|
+
html += renderNavItem('decomp-dependencies', 'Dependances', data.dependencies.length);
|
|
2396
|
+
return html;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
function buildConsolidationItems() {
|
|
2400
|
+
var totalEntities = data.modules.reduce(function(sum, m) {
|
|
2401
|
+
return sum + (data.moduleSpecs[m.code]?.entities || []).length;
|
|
2402
|
+
}, 0);
|
|
2403
|
+
return renderNavItem('consol-datamodel', 'Modele de donnees', totalEntities) +
|
|
2404
|
+
renderNavItem('consol-interactions', 'Interactions') +
|
|
2405
|
+
renderNavItem('consol-permissions', 'Coherence des acces') +
|
|
2406
|
+
renderNavItem('consol-flows', 'Parcours bout en bout', data.consolidation.e2eFlows.length);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
function buildSyntheseItems() {
|
|
2410
|
+
return renderNavItem('handoff-summary', 'Vue d\'ensemble');
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
/* ---------- Module Sub-Tree ---------- */
|
|
2414
|
+
|
|
2415
|
+
function renderModuleNavItem(mod) {
|
|
2416
|
+
var code = mod.code;
|
|
2417
|
+
var spec = data.moduleSpecs[code] || {};
|
|
2418
|
+
var ucCount = (spec.useCases || []).length;
|
|
2419
|
+
var brCount = (spec.businessRules || []).length;
|
|
2420
|
+
var entCount = (spec.entities || []).length;
|
|
2421
|
+
var sections = mod.anticipatedSections || [];
|
|
2422
|
+
var groupId = 'mod-' + code;
|
|
2423
|
+
var collapsed = navCollapseState[groupId] === true;
|
|
2424
|
+
|
|
2425
|
+
var html = '<div class="nav-module" data-group-id="' + groupId + '">';
|
|
2426
|
+
|
|
2427
|
+
// Module header (clickable to expand + navigate to module spec)
|
|
2428
|
+
html += '<a class="nav-item nav-module-header" onclick="toggleNavGroup(\'' + groupId + '\');showSection(\'module-spec-' + code + '\')" data-section="module-spec-' + code + '">';
|
|
2429
|
+
html += '<span class="nav-chevron ' + (collapsed ? '' : 'expanded') + '">▸</span> ';
|
|
2430
|
+
html += (mod.name || mod.code);
|
|
2431
|
+
html += '</a>';
|
|
2432
|
+
|
|
2433
|
+
// Children: tabs
|
|
2434
|
+
html += '<div class="nav-children"' + (collapsed ? ' style="display:none;"' : '') + '>';
|
|
2435
|
+
html += renderModuleTabNavItem(code, 'uc', 'Cas d\'utilisation', ucCount);
|
|
2436
|
+
html += renderModuleTabNavItem(code, 'br', 'Regles metier', brCount);
|
|
2437
|
+
html += renderModuleTabNavItem(code, 'ent', 'Donnees', entCount);
|
|
2438
|
+
html += renderModuleTabNavItem(code, 'perm', 'Droits d\'acces');
|
|
2439
|
+
html += renderModuleTabNavItem(code, 'mock', 'Maquettes');
|
|
2440
|
+
html += renderModuleTabNavItem(code, 'struct', 'Structure', sections.length);
|
|
2441
|
+
|
|
2442
|
+
// Children: sections/resources (deeper tree)
|
|
2443
|
+
if (sections.length > 0) {
|
|
2444
|
+
sections.forEach(function(section) {
|
|
2445
|
+
var resources = section.resources || [];
|
|
2446
|
+
html += '<div class="nav-section-item">';
|
|
2447
|
+
html += '<a class="nav-item nav-section-link" onclick="showSection(\'module-spec-' + code + '\');switchTab(\'' + code + '\',\'struct\')" data-section="module-struct-' + code + '-' + section.code + '">';
|
|
2448
|
+
html += '<span class="nav-icon nav-icon-section">▷</span> ' + section.code;
|
|
2449
|
+
if (resources.length > 0) html += ' <span class="nav-badge">' + resources.length + '</span>';
|
|
2450
|
+
html += '</a>';
|
|
2451
|
+
if (resources.length > 0) {
|
|
2452
|
+
html += '<div class="nav-children nav-resources">';
|
|
2453
|
+
resources.forEach(function(res) {
|
|
2454
|
+
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
2455
|
+
html += '<a class="nav-item nav-resource-link">';
|
|
2456
|
+
html += '<span class="nav-icon nav-icon-resource">•</span> ' + resName;
|
|
2457
|
+
html += '</a>';
|
|
2458
|
+
});
|
|
2459
|
+
html += '</div>';
|
|
2460
|
+
}
|
|
2461
|
+
html += '</div>';
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
html += '</div>'; // nav-children
|
|
2466
|
+
html += '</div>'; // nav-module
|
|
2467
|
+
return html;
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
function renderModuleTabNavItem(code, tabId, label, badge) {
|
|
2471
|
+
var sectionId = 'module-spec-' + code;
|
|
2472
|
+
return '<a class="nav-item" onclick="showSection(\'' + sectionId + '\');switchTab(\'' + code + '\',\'' + tabId + '\')" data-section="' + sectionId + '-' + tabId + '">' +
|
|
2473
|
+
'<span class="nav-icon">●</span> ' + label +
|
|
2474
|
+
(badge !== undefined ? ' <span class="nav-badge">' + badge + '</span>' : '') +
|
|
2475
|
+
'</a>';
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
/* ---------- Collapse/Expand ---------- */
|
|
2479
|
+
|
|
2480
|
+
function toggleNavGroup(groupId) {
|
|
2481
|
+
navCollapseState[groupId] = !navCollapseState[groupId];
|
|
2482
|
+
var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
|
|
2483
|
+
if (groupEl) {
|
|
2484
|
+
var children = groupEl.querySelector(':scope > .nav-children');
|
|
2485
|
+
var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
|
|
2486
|
+
if (children) children.style.display = navCollapseState[groupId] ? 'none' : '';
|
|
2487
|
+
if (chevron) chevron.classList.toggle('expanded', !navCollapseState[groupId]);
|
|
2488
|
+
}
|
|
2489
|
+
saveNavState();
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
function saveNavState() {
|
|
2493
|
+
try { localStorage.setItem(APP_KEY + '-nav', JSON.stringify(navCollapseState)); } catch(e) {}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
function restoreNavState() {
|
|
2497
|
+
try {
|
|
2498
|
+
var saved = localStorage.getItem(APP_KEY + '-nav');
|
|
2499
|
+
if (saved) {
|
|
2500
|
+
navCollapseState = JSON.parse(saved);
|
|
2501
|
+
// Apply collapse state to all groups
|
|
2502
|
+
Object.keys(navCollapseState).forEach(function(groupId) {
|
|
2503
|
+
if (navCollapseState[groupId]) {
|
|
2504
|
+
var groupEl = document.querySelector('[data-group-id="' + groupId + '"]');
|
|
2505
|
+
if (groupEl) {
|
|
2506
|
+
var children = groupEl.querySelector(':scope > .nav-children');
|
|
2507
|
+
var chevron = groupEl.querySelector(':scope > .nav-group-title .nav-chevron, :scope > .nav-item.nav-module-header .nav-chevron');
|
|
2508
|
+
if (children) children.style.display = 'none';
|
|
2509
|
+
if (chevron) chevron.classList.remove('expanded');
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
} catch(e) {}
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function highlightActiveNavItem() {
|
|
2518
|
+
if (!currentSectionId) return;
|
|
2519
|
+
var navItem = document.querySelector('#sidebarNav [data-section="' + currentSectionId + '"]');
|
|
2520
|
+
if (navItem) navItem.classList.add('active');
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2217
2523
|
|
|
2218
2524
|
/* --- 03-render-cadrage.js --- */
|
|
2219
2525
|
/* ============================================
|
|
@@ -2433,6 +2739,7 @@ function addModule() {
|
|
|
2433
2739
|
featureType: document.getElementById('mod-type').value,
|
|
2434
2740
|
priority: document.getElementById('mod-priority').value,
|
|
2435
2741
|
entities: document.getElementById('mod-entities').value.split('\n').filter(e => e.trim()),
|
|
2742
|
+
anticipatedSections: [],
|
|
2436
2743
|
status: 'pending'
|
|
2437
2744
|
});
|
|
2438
2745
|
|
|
@@ -2489,14 +2796,7 @@ function renderModules() {
|
|
|
2489
2796
|
}
|
|
2490
2797
|
|
|
2491
2798
|
function updateModulesNav() {
|
|
2492
|
-
|
|
2493
|
-
const navItems = data.modules.map(m => `
|
|
2494
|
-
<a class="nav-item" onclick="showSection('module-spec-${m.code}')" data-section="module-spec-${m.code}">
|
|
2495
|
-
<span class="nav-icon">●</span> ${m.name}
|
|
2496
|
-
<span class="nav-badge">${(data.moduleSpecs[m.code]?.useCases || []).length}</span>
|
|
2497
|
-
</a>
|
|
2498
|
-
`).join('');
|
|
2499
|
-
nav.innerHTML = '<div class="nav-group-title">3. Specification</div>' + (navItems || '<div style="padding:0.3rem 1rem;font-size:0.8rem;color:var(--text-muted);font-style:italic;">Aucun domaine</div>');
|
|
2799
|
+
buildNavTree();
|
|
2500
2800
|
}
|
|
2501
2801
|
|
|
2502
2802
|
/* ============================================
|
|
@@ -2671,6 +2971,7 @@ function renderModuleSpecSection(mod) {
|
|
|
2671
2971
|
<button class="tab-btn" onclick="switchTab('${code}', 'perm')">Droits d'acces</button>
|
|
2672
2972
|
<button class="tab-btn" onclick="switchTab('${code}', 'mock')">Maquettes</button>
|
|
2673
2973
|
<button class="tab-btn" onclick="switchTab('${code}', 'notes')">Notes</button>
|
|
2974
|
+
<button class="tab-btn" onclick="switchTab('${code}', 'struct')">Structure</button>
|
|
2674
2975
|
</div>
|
|
2675
2976
|
|
|
2676
2977
|
<!-- TAB: Cas d'utilisation -->
|
|
@@ -2747,7 +3048,12 @@ function renderModuleSpecSection(mod) {
|
|
|
2747
3048
|
<div class="tab-panel" id="tab-${code}-ent">
|
|
2748
3049
|
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Les types de donnees que ce domaine gere. Decrivez les informations importantes a enregistrer pour chaque type.</p>
|
|
2749
3050
|
<div id="entList-${code}">
|
|
2750
|
-
${spec.entities.
|
|
3051
|
+
${spec.entities.length > 0
|
|
3052
|
+
? spec.entities.map((ent, i) => renderEntity(code, ent, i)).join('')
|
|
3053
|
+
: (mod.entities || []).length > 0
|
|
3054
|
+
? '<div class="card" style="color:var(--text-muted);padding:1.5rem;"><p style="margin-bottom:0.75rem;">Les schemas detailles ne sont pas encore disponibles. Entites identifiees :</p><ul style="list-style:disc;padding-left:1.5rem;">' + mod.entities.map(e => '<li>' + e + '</li>').join('') + '</ul><p style="font-size:0.8rem;margin-top:0.75rem;font-style:italic;">Utilisez le bouton ci-dessous pour ajouter les schemas detailles.</p></div>'
|
|
3055
|
+
: ''
|
|
3056
|
+
}
|
|
2751
3057
|
</div>
|
|
2752
3058
|
<button class="add-btn" onclick="toggleForm('addEntForm-${code}')">+ Ajouter un type de donnees</button>
|
|
2753
3059
|
<div class="inline-form" id="addEntForm-${code}">
|
|
@@ -2824,6 +3130,14 @@ function renderModuleSpecSection(mod) {
|
|
|
2824
3130
|
<div class="editable" contenteditable="true" data-module-code="${code}" data-module-field="notes" data-placeholder="Notez ici tout ce qui concerne ce domaine : questions, precisions, contraintes particulieres...">${spec.notes || ''}</div>
|
|
2825
3131
|
</div>
|
|
2826
3132
|
</div>
|
|
3133
|
+
|
|
3134
|
+
<!-- TAB: Structure (sections/resources) -->
|
|
3135
|
+
<div class="tab-panel" id="tab-${code}-struct">
|
|
3136
|
+
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:1rem;">Organisation des ecrans et ressources de ce domaine. Structure hierarchique : sections regroupant des ressources.</p>
|
|
3137
|
+
<div id="structContainer-${code}">
|
|
3138
|
+
${renderModuleStructure(code)}
|
|
3139
|
+
</div>
|
|
3140
|
+
</div>
|
|
2827
3141
|
</div>`;
|
|
2828
3142
|
}
|
|
2829
3143
|
|
|
@@ -3007,18 +3321,30 @@ function renderModuleMockups(code) {
|
|
|
3007
3321
|
</div>`;
|
|
3008
3322
|
}
|
|
3009
3323
|
|
|
3010
|
-
return wireframes.map((wf, i) =>
|
|
3324
|
+
return wireframes.map((wf, i) => {
|
|
3325
|
+
const hasSvg = !!wf.svgContent;
|
|
3326
|
+
const wireframeId = `wf-${code}-${i}`;
|
|
3327
|
+
|
|
3328
|
+
return `
|
|
3011
3329
|
<div class="mockup-frame" style="${i > 0 ? 'margin-top:1.5rem;' : ''}">
|
|
3012
3330
|
<div class="mockup-toolbar">
|
|
3013
3331
|
<div class="mockup-dot mockup-dot-red"></div>
|
|
3014
3332
|
<div class="mockup-dot mockup-dot-yellow"></div>
|
|
3015
3333
|
<div class="mockup-dot mockup-dot-green"></div>
|
|
3016
3334
|
<span class="mockup-title">${wf.screen || wf.section}</span>
|
|
3335
|
+
${hasSvg ? `
|
|
3336
|
+
<div class="wireframe-toggle">
|
|
3337
|
+
<button class="wireframe-toggle-btn active" data-target="${wireframeId}" data-view="svg" onclick="toggleWireframeView('${wireframeId}', 'svg')">SVG</button>
|
|
3338
|
+
<button class="wireframe-toggle-btn" data-target="${wireframeId}" data-view="ascii" onclick="toggleWireframeView('${wireframeId}', 'ascii')">ASCII</button>
|
|
3339
|
+
</div>` : ''}
|
|
3017
3340
|
</div>
|
|
3018
|
-
<div class="mockup-content">
|
|
3019
|
-
${
|
|
3020
|
-
? `<
|
|
3021
|
-
|
|
3341
|
+
<div class="mockup-content" id="${wireframeId}">
|
|
3342
|
+
${hasSvg
|
|
3343
|
+
? `<div class="svg-wireframe wireframe-view active" data-view="svg">${wf.svgContent}</div>
|
|
3344
|
+
<pre class="ascii-wireframe wireframe-view" data-view="ascii" style="display:none;">${wf.content || ''}</pre>`
|
|
3345
|
+
: (wf.format === 'ascii'
|
|
3346
|
+
? `<pre class="ascii-wireframe">${wf.content || ''}</pre>`
|
|
3347
|
+
: `<div class="svg-wireframe">${wf.content || ''}</div>`)}
|
|
3022
3348
|
</div>
|
|
3023
3349
|
${wf.description ? `
|
|
3024
3350
|
<div class="wireframe-description">
|
|
@@ -3057,7 +3383,27 @@ function renderModuleMockups(code) {
|
|
|
3057
3383
|
>${getWireframeComment(code, wf.screen)}</textarea>
|
|
3058
3384
|
</div>
|
|
3059
3385
|
</div>
|
|
3060
|
-
`).join('');
|
|
3386
|
+
`}).join('');
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
function toggleWireframeView(wireframeId, view) {
|
|
3390
|
+
const container = document.getElementById(wireframeId);
|
|
3391
|
+
if (!container) return;
|
|
3392
|
+
|
|
3393
|
+
// Toggle visibility of wireframe views
|
|
3394
|
+
container.querySelectorAll('.wireframe-view').forEach(el => {
|
|
3395
|
+
const isTarget = el.dataset.view === view;
|
|
3396
|
+
el.style.display = isTarget ? '' : 'none';
|
|
3397
|
+
el.classList.toggle('active', isTarget);
|
|
3398
|
+
});
|
|
3399
|
+
|
|
3400
|
+
// Toggle button active states
|
|
3401
|
+
const toolbar = container.closest('.mockup-frame').querySelector('.wireframe-toggle');
|
|
3402
|
+
if (toolbar) {
|
|
3403
|
+
toolbar.querySelectorAll('.wireframe-toggle-btn').forEach(btn => {
|
|
3404
|
+
btn.classList.toggle('active', btn.dataset.view === view);
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3061
3407
|
}
|
|
3062
3408
|
|
|
3063
3409
|
function getPermRoles() {
|
|
@@ -3181,6 +3527,47 @@ function refreshAllPermGrids() {
|
|
|
3181
3527
|
});
|
|
3182
3528
|
}
|
|
3183
3529
|
|
|
3530
|
+
function renderModuleStructure(code) {
|
|
3531
|
+
const mod = data.modules.find(m => m.code === code);
|
|
3532
|
+
const sections = mod ? (mod.anticipatedSections || []) : [];
|
|
3533
|
+
|
|
3534
|
+
if (sections.length === 0) {
|
|
3535
|
+
return '<div class="card" style="text-align:center;padding:2rem;color:var(--text-muted);">' +
|
|
3536
|
+
'<p>Aucune section definie pour ce module.</p>' +
|
|
3537
|
+
'<p style="font-size:0.85rem;margin-top:0.5rem;">Les sections et ressources seront identifiees lors de l\'analyse approfondie.</p>' +
|
|
3538
|
+
'</div>';
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
return sections.map(function(section) {
|
|
3542
|
+
var resources = section.resources || [];
|
|
3543
|
+
var html = '<div class="struct-section">';
|
|
3544
|
+
html += '<div class="struct-section-header">';
|
|
3545
|
+
html += '<span class="struct-section-code">' + (section.code || section.name || '') + '</span>';
|
|
3546
|
+
if (section.description) {
|
|
3547
|
+
html += '<span class="struct-section-desc">' + section.description + '</span>';
|
|
3548
|
+
}
|
|
3549
|
+
html += '<span class="struct-section-badge">' + resources.length + ' ressource' + (resources.length !== 1 ? 's' : '') + '</span>';
|
|
3550
|
+
html += '</div>';
|
|
3551
|
+
|
|
3552
|
+
if (resources.length > 0) {
|
|
3553
|
+
html += '<div class="struct-resources">';
|
|
3554
|
+
resources.forEach(function(res) {
|
|
3555
|
+
var resName = typeof res === 'string' ? res : (res.code || res.name || '');
|
|
3556
|
+
var resDesc = typeof res === 'object' ? (res.description || '') : '';
|
|
3557
|
+
html += '<div class="struct-resource">';
|
|
3558
|
+
html += '<span class="struct-resource-icon">•</span>';
|
|
3559
|
+
html += '<span class="struct-resource-name">' + resName + '</span>';
|
|
3560
|
+
if (resDesc) html += '<span class="struct-resource-desc">' + resDesc + '</span>';
|
|
3561
|
+
html += '</div>';
|
|
3562
|
+
});
|
|
3563
|
+
html += '</div>';
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
html += '</div>';
|
|
3567
|
+
return html;
|
|
3568
|
+
}).join('');
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3184
3571
|
function switchTab(code, tabId) {
|
|
3185
3572
|
const section = document.getElementById('module-spec-' + code);
|
|
3186
3573
|
if (!section) return;
|
|
@@ -3189,7 +3576,7 @@ function switchTab(code, tabId) {
|
|
|
3189
3576
|
const targetPanel = document.getElementById('tab-' + code + '-' + tabId);
|
|
3190
3577
|
if (targetPanel) targetPanel.classList.add('active');
|
|
3191
3578
|
const buttons = section.querySelectorAll('.tab-btn');
|
|
3192
|
-
const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5 }[tabId];
|
|
3579
|
+
const tabIndex = { uc: 0, br: 1, ent: 2, perm: 3, mock: 4, notes: 5, struct: 6 }[tabId];
|
|
3193
3580
|
if (buttons[tabIndex]) buttons[tabIndex].classList.add('active');
|
|
3194
3581
|
}
|
|
3195
3582
|
|
|
@@ -3471,7 +3858,9 @@ function renderCoverageMatrix() {
|
|
|
3471
3858
|
<thead><tr><th>Besoin</th><th>Priorite</th><th>Domaine</th><th>Couvert</th></tr></thead>
|
|
3472
3859
|
<tbody>
|
|
3473
3860
|
${allScope.map(item => {
|
|
3474
|
-
const moduleName =
|
|
3861
|
+
const moduleName = item.module
|
|
3862
|
+
? (data.modules.find(m => m.code === item.module)?.name || item.module)
|
|
3863
|
+
: (data.modules.length > 0 ? data.modules.map(m => m.name).join(', ') : 'A definir');
|
|
3475
3864
|
return `
|
|
3476
3865
|
<tr>
|
|
3477
3866
|
<td>${item.name}</td>
|
|
@@ -3499,37 +3888,127 @@ function saveToLocalStorage() {
|
|
|
3499
3888
|
showNotification('Modifications sauvegardees');
|
|
3500
3889
|
}
|
|
3501
3890
|
|
|
3891
|
+
function resetToEmbedded() {
|
|
3892
|
+
if (!confirm('Reinitialiser toutes les donnees depuis la version d\'origine ? Vos modifications locales (commentaires, notes) seront perdues.')) return;
|
|
3893
|
+
localStorage.removeItem(APP_KEY);
|
|
3894
|
+
// Restore data from ORIGINAL_DATA
|
|
3895
|
+
Object.keys(data).forEach(k => delete data[k]);
|
|
3896
|
+
Object.assign(data, JSON.parse(JSON.stringify(ORIGINAL_DATA)));
|
|
3897
|
+
// Re-render everything
|
|
3898
|
+
initEditableFields();
|
|
3899
|
+
renderStakeholders();
|
|
3900
|
+
renderScope();
|
|
3901
|
+
renderRisks();
|
|
3902
|
+
renderCriteria();
|
|
3903
|
+
renderModules();
|
|
3904
|
+
renderDependencies();
|
|
3905
|
+
renderAllModuleSpecs();
|
|
3906
|
+
renderConsolidation();
|
|
3907
|
+
renderHandoff();
|
|
3908
|
+
renderE2EFlows();
|
|
3909
|
+
updateCounts();
|
|
3910
|
+
renderReviewPanel();
|
|
3911
|
+
showNotification('Donnees reinitialisees depuis la version d\'origine');
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3502
3914
|
function loadFromLocalStorage() {
|
|
3503
3915
|
const saved = localStorage.getItem(APP_KEY);
|
|
3504
|
-
if (saved)
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3916
|
+
if (!saved) return;
|
|
3917
|
+
|
|
3918
|
+
try {
|
|
3919
|
+
const parsed = JSON.parse(saved);
|
|
3920
|
+
|
|
3921
|
+
// Build fingerprint of embedded structural data to detect HTML regeneration
|
|
3922
|
+
const embeddedFingerprint = ORIGINAL_DATA.modules.map(m => m.code).sort().join(',')
|
|
3923
|
+
+ '|' + (ORIGINAL_DATA.metadata?.createdAt || '')
|
|
3924
|
+
+ '|' + ORIGINAL_DATA.modules.length;
|
|
3925
|
+
const cachedFingerprint = parsed._structureFingerprint || '';
|
|
3926
|
+
|
|
3927
|
+
const structureChanged = embeddedFingerprint !== cachedFingerprint;
|
|
3928
|
+
const embeddedModuleCount = ORIGINAL_DATA.modules?.length || 0;
|
|
3929
|
+
const cachedModuleCount = (parsed.modules || []).length;
|
|
3930
|
+
const hasNewModules = embeddedModuleCount > cachedModuleCount;
|
|
3931
|
+
const embeddedModuleCodes = new Set(ORIGINAL_DATA.modules.map(m => m.code));
|
|
3932
|
+
const cachedModuleCodes = new Set((parsed.modules || []).map(m => m.code));
|
|
3933
|
+
const missingModules = [...embeddedModuleCodes].filter(c => !cachedModuleCodes.has(c));
|
|
3934
|
+
|
|
3935
|
+
if (structureChanged || hasNewModules || missingModules.length > 0) {
|
|
3936
|
+
// HTML was regenerated or has new modules — keep embedded structural data
|
|
3937
|
+
// Only restore user-specific edits (comments, custom roles, notes)
|
|
3938
|
+
data.wireframeComments = parsed.wireframeComments || {};
|
|
3939
|
+
data.specComments = parsed.specComments || {};
|
|
3940
|
+
data.customRoles = parsed.customRoles || [];
|
|
3941
|
+
data.customActions = parsed.customActions || [];
|
|
3942
|
+
data.comments = parsed.comments || [];
|
|
3943
|
+
|
|
3944
|
+
// Merge user-added notes per module (preserve existing module notes)
|
|
3945
|
+
for (const code of Object.keys(parsed.moduleSpecs || {})) {
|
|
3946
|
+
if (data.moduleSpecs[code] && parsed.moduleSpecs[code]?.notes) {
|
|
3947
|
+
data.moduleSpecs[code].notes = parsed.moduleSpecs[code].notes;
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
// Save fresh embedded data with fingerprint
|
|
3952
|
+
data._structureFingerprint = embeddedFingerprint;
|
|
3953
|
+
autoSave();
|
|
3954
|
+
} else {
|
|
3955
|
+
// Cache matches current structure — safe to restore user edits on cadrage/notes
|
|
3956
|
+
// IMPORTANT: Always keep embedded modules and moduleSpecs as structural source of truth
|
|
3957
|
+
// Only merge cadrage user-editable fields and notes
|
|
3958
|
+
if (parsed.cadrage) {
|
|
3959
|
+
// Merge cadrage context (user may have edited text fields)
|
|
3960
|
+
if (parsed.cadrage.context) {
|
|
3961
|
+
data.cadrage.context = { ...data.cadrage.context, ...parsed.cadrage.context };
|
|
3962
|
+
}
|
|
3963
|
+
// Merge scope only if user added items interactively
|
|
3964
|
+
if (parsed.cadrage.scope) {
|
|
3965
|
+
data.cadrage.scope = { ...data.cadrage.scope, ...parsed.cadrage.scope };
|
|
3966
|
+
}
|
|
3967
|
+
// Merge stakeholders and risks if user edited them
|
|
3968
|
+
if (parsed.cadrage.stakeholders) data.cadrage.stakeholders = parsed.cadrage.stakeholders;
|
|
3969
|
+
if (parsed.cadrage.risks) data.cadrage.risks = parsed.cadrage.risks;
|
|
3970
|
+
}
|
|
3971
|
+
data.dependencies = parsed.dependencies || data.dependencies;
|
|
3517
3972
|
data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
|
|
3518
|
-
|
|
3973
|
+
|
|
3974
|
+
// Merge moduleSpecs: keep embedded structure, overlay user-editable fields (notes, custom permissions)
|
|
3975
|
+
for (const code of Object.keys(data.moduleSpecs)) {
|
|
3976
|
+
const cached = parsed.moduleSpecs?.[code];
|
|
3977
|
+
if (cached) {
|
|
3978
|
+
// Preserve user notes
|
|
3979
|
+
if (cached.notes) data.moduleSpecs[code].notes = cached.notes;
|
|
3980
|
+
// Preserve user-added use cases/rules/entities (merged with embedded)
|
|
3981
|
+
// Only add items that don't exist in embedded (by name match)
|
|
3982
|
+
const embeddedUcNames = new Set((data.moduleSpecs[code].useCases || []).map(uc => uc.name));
|
|
3983
|
+
(cached.useCases || []).forEach(uc => {
|
|
3984
|
+
if (!embeddedUcNames.has(uc.name)) data.moduleSpecs[code].useCases.push(uc);
|
|
3985
|
+
});
|
|
3986
|
+
const embeddedBrNames = new Set((data.moduleSpecs[code].businessRules || []).map(br => br.name));
|
|
3987
|
+
(cached.businessRules || []).forEach(br => {
|
|
3988
|
+
if (!embeddedBrNames.has(br.name)) data.moduleSpecs[code].businessRules.push(br);
|
|
3989
|
+
});
|
|
3990
|
+
// Preserve user permissions edits
|
|
3991
|
+
if (cached.permissions) data.moduleSpecs[code].permissions = cached.permissions;
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3519
3995
|
data.wireframeComments = parsed.wireframeComments || {};
|
|
3520
3996
|
data.specComments = parsed.specComments || {};
|
|
3521
3997
|
data.customRoles = parsed.customRoles || [];
|
|
3522
3998
|
data.customActions = parsed.customActions || [];
|
|
3523
3999
|
data.comments = parsed.comments || [];
|
|
3524
4000
|
|
|
3525
|
-
//
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
4001
|
+
// Update fingerprint
|
|
4002
|
+
data._structureFingerprint = embeddedFingerprint;
|
|
4003
|
+
autoSave();
|
|
4004
|
+
}
|
|
4005
|
+
|
|
4006
|
+
// Restore editable fields
|
|
4007
|
+
document.querySelectorAll('.editable[data-field]').forEach(el => {
|
|
4008
|
+
const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
|
|
4009
|
+
if (value) el.textContent = value;
|
|
4010
|
+
});
|
|
4011
|
+
} catch (e) { console.error('Error loading saved data:', e); }
|
|
3533
4012
|
}
|
|
3534
4013
|
|
|
3535
4014
|
|
|
@@ -3999,6 +4478,13 @@ function getSectionLabel(sectionId) {
|
|
|
3999
4478
|
const mod = data.modules.find(m => m.code === code);
|
|
4000
4479
|
return mod ? mod.name : code;
|
|
4001
4480
|
}
|
|
4481
|
+
// Handle module structure sections (module-struct-{code}-{section})
|
|
4482
|
+
const structMatch = sectionId.match(/^module-struct-(.+?)-(.+)$/);
|
|
4483
|
+
if (structMatch) {
|
|
4484
|
+
const mod = data.modules.find(m => m.code === structMatch[1]);
|
|
4485
|
+
const modName = mod ? mod.name : structMatch[1];
|
|
4486
|
+
return modName + ' > Structure > ' + structMatch[2];
|
|
4487
|
+
}
|
|
4002
4488
|
// Handle list-based sectionIds (ucList-*, brList-*, entList-*)
|
|
4003
4489
|
const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
|
|
4004
4490
|
if (listMatch) {
|