@jakkrichm/create-nexus-devflow 2.12.4 → 2.14.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/README.md +4 -4
- package/dist/bin/create-nexus-devflow.d.ts +1 -1
- package/dist/bin/create-nexus-devflow.js +4 -3
- package/dist/bin/create-nexus-devflow.js.map +1 -1
- package/dist/lib/active-context-engine.d.ts +47 -0
- package/dist/lib/active-context-engine.js +174 -0
- package/dist/lib/active-context-engine.js.map +1 -0
- package/dist/lib/branch-context.d.ts +1 -0
- package/dist/lib/branch-context.js +1 -0
- package/dist/lib/branch-context.js.map +1 -1
- package/dist/lib/current-work.js +3 -28
- package/dist/lib/current-work.js.map +1 -1
- package/dist/lib/dashboard-engine.d.ts +54 -0
- package/dist/lib/dashboard-engine.js +109 -0
- package/dist/lib/dashboard-engine.js.map +1 -0
- package/dist/lib/dashboard-page.d.ts +8 -1
- package/dist/lib/dashboard-page.js +10 -1
- package/dist/lib/dashboard-page.js.map +1 -1
- package/dist/lib/dashboard.d.ts +2 -0
- package/dist/lib/dashboard.js +78 -1068
- package/dist/lib/dashboard.js.map +1 -1
- package/dist/lib/delivery-lifecycle-engine.d.ts +96 -0
- package/dist/lib/delivery-lifecycle-engine.js +388 -0
- package/dist/lib/delivery-lifecycle-engine.js.map +1 -0
- package/dist/lib/gatekeeper-engine.d.ts +65 -0
- package/dist/lib/gatekeeper-engine.js +155 -0
- package/dist/lib/gatekeeper-engine.js.map +1 -0
- package/dist/lib/gatekeeper.d.ts +3 -22
- package/dist/lib/gatekeeper.js +4 -67
- package/dist/lib/gatekeeper.js.map +1 -1
- package/dist/lib/history.d.ts +3 -1
- package/dist/lib/history.js +2 -1
- package/dist/lib/history.js.map +1 -1
- package/dist/lib/skill-manager.js +319 -7
- package/dist/lib/skill-manager.js.map +1 -1
- package/dist/lib/studio-view-renderer.d.ts +50 -0
- package/dist/lib/studio-view-renderer.js +773 -0
- package/dist/lib/studio-view-renderer.js.map +1 -0
- package/dist/lib/tooling/commands/link-project.d.ts +2 -0
- package/dist/lib/tooling/commands/link-project.js +22 -0
- package/dist/lib/tooling/commands/link-project.js.map +1 -0
- package/dist/lib/tooling/commands/migrate-stage-artifacts.d.ts +2 -0
- package/dist/lib/tooling/commands/migrate-stage-artifacts.js +61 -0
- package/dist/lib/tooling/commands/migrate-stage-artifacts.js.map +1 -0
- package/dist/lib/tooling/commands/render-html.d.ts +2 -0
- package/dist/lib/tooling/commands/render-html.js +78 -0
- package/dist/lib/tooling/commands/render-html.js.map +1 -0
- package/dist/lib/tooling/commands/report-html.d.ts +2 -0
- package/dist/lib/tooling/commands/report-html.js +27 -0
- package/dist/lib/tooling/commands/report-html.js.map +1 -0
- package/dist/lib/tooling/commands/scan-doc-contract.d.ts +2 -0
- package/dist/lib/tooling/commands/scan-doc-contract.js +78 -0
- package/dist/lib/tooling/commands/scan-doc-contract.js.map +1 -0
- package/dist/lib/tooling/commands/scan-security-hygiene.d.ts +2 -0
- package/dist/lib/tooling/commands/scan-security-hygiene.js +93 -0
- package/dist/lib/tooling/commands/scan-security-hygiene.js.map +1 -0
- package/dist/lib/tooling/commands/skill-descriptions.d.ts +2 -0
- package/dist/lib/tooling/commands/skill-descriptions.js +50 -0
- package/dist/lib/tooling/commands/skill-descriptions.js.map +1 -0
- package/dist/lib/tooling/commands/summarize-run-status.d.ts +2 -0
- package/dist/lib/tooling/commands/summarize-run-status.js +65 -0
- package/dist/lib/tooling/commands/summarize-run-status.js.map +1 -0
- package/dist/lib/tooling/commands/switch-artifact-language.d.ts +2 -0
- package/dist/lib/tooling/commands/switch-artifact-language.js +87 -0
- package/dist/lib/tooling/commands/switch-artifact-language.js.map +1 -0
- package/dist/lib/tooling/index.d.ts +4 -0
- package/dist/lib/tooling/index.js +23 -0
- package/dist/lib/tooling/index.js.map +1 -0
- package/dist/lib/tooling/registry.d.ts +6 -0
- package/dist/lib/tooling/registry.js +56 -0
- package/dist/lib/tooling/registry.js.map +1 -0
- package/dist/lib/tooling/types.d.ts +19 -0
- package/dist/lib/tooling/types.js +10 -0
- package/dist/lib/tooling/types.js.map +1 -0
- package/dist/lib/update.js +1 -1
- package/dist/lib/update.js.map +1 -1
- package/dist/lib/webview-studio.d.ts +5 -0
- package/dist/lib/webview-studio.js +11 -577
- package/dist/lib/webview-studio.js.map +1 -1
- package/package.json +1 -1
- package/template/AGENTS.md +1 -1
- package/template/devflow/docs/USAGE.md +145 -0
- package/template/devflow/docs/diagrams/nexus-devflow-architecture.html +14821 -0
- package/template/devflow/docs/diagrams/nexus-devflow-architecture.json +80 -0
- package/template/devflow/docs/diagrams/nexus-devflow-lifecycle.html +14860 -0
- package/template/devflow/docs/diagrams/nexus-devflow-lifecycle.json +88 -0
- package/template/devflow/docs/examples/living-spec/adr.example.md +43 -0
- package/template/devflow/docs/examples/living-spec/current-feature.example.md +113 -0
- package/template/devflow/docs/examples/living-spec/discovery.example.md +45 -0
- package/template/devflow/docs/examples/living-spec/ideas.example.md +22 -0
- package/template/devflow/docs/governance-rules.md +39 -0
- package/template/devflow/docs/manual-review-workflow-spec.md +116 -0
- package/template/devflow/docs/markdown-metadata-contract.md +140 -0
- package/template/devflow/docs/quickstart.md +119 -0
- package/template/devflow/docs/release-process.md +52 -0
- package/template/devflow/docs/skill-selection-policy.md +64 -0
- package/template/devflow/docs/workflow-surface-map.md +85 -0
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
import { DASHBOARD_PAGE_HTML } from "./dashboard-page.js";
|
|
2
|
+
const WEBVIEW_STUDIO_CSS = `
|
|
3
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
4
|
+
body {
|
|
5
|
+
font-family: var(--font-sans);
|
|
6
|
+
background-color: var(--bg);
|
|
7
|
+
color: var(--ink);
|
|
8
|
+
padding: 16px;
|
|
9
|
+
line-height: 1.5;
|
|
10
|
+
font-size: 13px;
|
|
11
|
+
overflow-x: hidden;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.studio-container {
|
|
15
|
+
max-width: 1280px;
|
|
16
|
+
margin: 0 auto;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 16px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.studio-header {
|
|
23
|
+
background: var(--bg-deep);
|
|
24
|
+
backdrop-filter: blur(12px);
|
|
25
|
+
border: 1px solid var(--line);
|
|
26
|
+
border-radius: 12px;
|
|
27
|
+
padding: 16px 20px;
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
flex-wrap: wrap;
|
|
32
|
+
gap: 12px;
|
|
33
|
+
}
|
|
34
|
+
.header-left {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: 12px;
|
|
38
|
+
flex-wrap: wrap;
|
|
39
|
+
}
|
|
40
|
+
.project-badge {
|
|
41
|
+
font-weight: 800;
|
|
42
|
+
font-size: 16px;
|
|
43
|
+
background: linear-gradient(135deg, var(--cyan) 0%, var(--violet) 100%);
|
|
44
|
+
-webkit-background-clip: text;
|
|
45
|
+
-webkit-text-fill-color: transparent;
|
|
46
|
+
letter-spacing: -0.5px;
|
|
47
|
+
}
|
|
48
|
+
.branch-pill {
|
|
49
|
+
font-family: var(--font-mono);
|
|
50
|
+
font-size: 11px;
|
|
51
|
+
background: var(--cyan-soft);
|
|
52
|
+
color: var(--cyan);
|
|
53
|
+
padding: 3px 10px;
|
|
54
|
+
border-radius: 20px;
|
|
55
|
+
border: 1px solid var(--line-strong);
|
|
56
|
+
}
|
|
57
|
+
.multitask-pill {
|
|
58
|
+
font-family: var(--font-mono);
|
|
59
|
+
font-size: 11px;
|
|
60
|
+
background: var(--violet-soft);
|
|
61
|
+
color: var(--violet);
|
|
62
|
+
padding: 3px 10px;
|
|
63
|
+
border-radius: 20px;
|
|
64
|
+
border: 1px solid rgba(184, 164, 255, 0.3);
|
|
65
|
+
font-weight: 700;
|
|
66
|
+
}
|
|
67
|
+
.header-right {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 10px;
|
|
71
|
+
}
|
|
72
|
+
.badge-pass {
|
|
73
|
+
background: var(--mint-soft);
|
|
74
|
+
color: var(--mint);
|
|
75
|
+
border: 1px solid rgba(111, 227, 180, 0.3);
|
|
76
|
+
padding: 4px 12px;
|
|
77
|
+
border-radius: 20px;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
font-size: 11px;
|
|
80
|
+
}
|
|
81
|
+
.badge-blocked {
|
|
82
|
+
background: var(--red-soft);
|
|
83
|
+
color: var(--red);
|
|
84
|
+
border: 1px solid rgba(255, 112, 97, 0.3);
|
|
85
|
+
padding: 4px 12px;
|
|
86
|
+
border-radius: 20px;
|
|
87
|
+
font-weight: 600;
|
|
88
|
+
font-size: 11px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.action-bar {
|
|
92
|
+
background: var(--bg-deep);
|
|
93
|
+
border: 1px solid var(--line);
|
|
94
|
+
border-radius: 10px;
|
|
95
|
+
padding: 10px 16px;
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 8px;
|
|
99
|
+
overflow-x: auto;
|
|
100
|
+
}
|
|
101
|
+
.action-label {
|
|
102
|
+
font-weight: 700;
|
|
103
|
+
color: var(--muted);
|
|
104
|
+
font-size: 10px;
|
|
105
|
+
text-transform: uppercase;
|
|
106
|
+
letter-spacing: 0.08em;
|
|
107
|
+
margin-right: 4px;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
}
|
|
110
|
+
.btn-action {
|
|
111
|
+
background: var(--panel);
|
|
112
|
+
border: 1px solid var(--line);
|
|
113
|
+
color: var(--ink);
|
|
114
|
+
padding: 5px 12px;
|
|
115
|
+
border-radius: 6px;
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
font-family: var(--font-mono);
|
|
118
|
+
font-size: 11px;
|
|
119
|
+
transition: all 0.2s ease;
|
|
120
|
+
white-space: nowrap;
|
|
121
|
+
}
|
|
122
|
+
.btn-action:hover {
|
|
123
|
+
background: var(--cyan-soft);
|
|
124
|
+
border-color: var(--cyan);
|
|
125
|
+
color: var(--cyan);
|
|
126
|
+
}
|
|
127
|
+
.btn-action.btn-primary {
|
|
128
|
+
background: linear-gradient(135deg, var(--cyan) 0%, var(--violet) 100%);
|
|
129
|
+
color: var(--bg-deep);
|
|
130
|
+
font-weight: 700;
|
|
131
|
+
border: none;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.stepper-section {
|
|
135
|
+
background: var(--bg-deep);
|
|
136
|
+
border: 1px solid var(--line);
|
|
137
|
+
border-radius: 10px;
|
|
138
|
+
padding: 14px 18px;
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
gap: 10px;
|
|
142
|
+
}
|
|
143
|
+
.stepper-header {
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: space-between;
|
|
147
|
+
font-size: 11px;
|
|
148
|
+
}
|
|
149
|
+
.badge-track {
|
|
150
|
+
padding: 2px 8px;
|
|
151
|
+
border-radius: 4px;
|
|
152
|
+
font-family: var(--font-mono);
|
|
153
|
+
font-weight: 700;
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
font-size: 10px;
|
|
156
|
+
}
|
|
157
|
+
.track-fast { background: var(--violet-soft); color: var(--violet); border: 1px solid rgba(184, 164, 255, 0.3); }
|
|
158
|
+
.track-pre-flight { background: var(--cyan-soft); color: var(--cyan); border: 1px solid rgba(125, 216, 255, 0.3); }
|
|
159
|
+
.track-idle { background: var(--panel); color: var(--muted); border: 1px solid var(--line); }
|
|
160
|
+
.stepper-track {
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
gap: 12px;
|
|
164
|
+
overflow-x: auto;
|
|
165
|
+
}
|
|
166
|
+
.step {
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-direction: column;
|
|
169
|
+
align-items: center;
|
|
170
|
+
text-align: center;
|
|
171
|
+
flex: 1;
|
|
172
|
+
min-width: 80px;
|
|
173
|
+
}
|
|
174
|
+
.step-dot {
|
|
175
|
+
width: 22px;
|
|
176
|
+
height: 22px;
|
|
177
|
+
border-radius: 50%;
|
|
178
|
+
display: grid;
|
|
179
|
+
place-items: center;
|
|
180
|
+
border: 2px solid var(--line-strong);
|
|
181
|
+
background: var(--bg-deep);
|
|
182
|
+
color: var(--muted);
|
|
183
|
+
font: 700 9px var(--font-mono);
|
|
184
|
+
margin-bottom: 4px;
|
|
185
|
+
}
|
|
186
|
+
.step.active .step-dot {
|
|
187
|
+
border-color: var(--gold);
|
|
188
|
+
color: var(--gold);
|
|
189
|
+
box-shadow: 0 0 0 4px var(--gold-soft);
|
|
190
|
+
}
|
|
191
|
+
.step-name { font-size: 10px; font-weight: 600; color: var(--soft); }
|
|
192
|
+
.step-cmd { font-size: 9px; color: var(--muted); font-family: var(--font-mono); }
|
|
193
|
+
|
|
194
|
+
.kanban-grid {
|
|
195
|
+
display: grid;
|
|
196
|
+
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
197
|
+
gap: 16px;
|
|
198
|
+
}
|
|
199
|
+
.kanban-col {
|
|
200
|
+
background: var(--bg-deep);
|
|
201
|
+
border: 1px solid var(--line);
|
|
202
|
+
border-radius: 12px;
|
|
203
|
+
padding: 16px;
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
gap: 12px;
|
|
207
|
+
}
|
|
208
|
+
.col-header {
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: space-between;
|
|
212
|
+
border-bottom: 1px solid var(--line);
|
|
213
|
+
padding-bottom: 10px;
|
|
214
|
+
}
|
|
215
|
+
.col-title {
|
|
216
|
+
font-size: 13px;
|
|
217
|
+
font-weight: 700;
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
gap: 6px;
|
|
221
|
+
}
|
|
222
|
+
.col-count {
|
|
223
|
+
font-size: 10px;
|
|
224
|
+
font-family: var(--font-mono);
|
|
225
|
+
font-weight: 600;
|
|
226
|
+
background: var(--panel);
|
|
227
|
+
padding: 2px 8px;
|
|
228
|
+
border-radius: 10px;
|
|
229
|
+
color: var(--muted);
|
|
230
|
+
border: 1px solid var(--line);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.col-content {
|
|
234
|
+
display: flex;
|
|
235
|
+
flex-direction: column;
|
|
236
|
+
gap: 10px;
|
|
237
|
+
max-height: 520px;
|
|
238
|
+
overflow-y: auto;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.card-item {
|
|
242
|
+
background: var(--panel);
|
|
243
|
+
border: 1px solid var(--line);
|
|
244
|
+
border-radius: 8px;
|
|
245
|
+
padding: 12px;
|
|
246
|
+
display: flex;
|
|
247
|
+
flex-direction: column;
|
|
248
|
+
gap: 6px;
|
|
249
|
+
transition: transform 0.15s ease, border-color 0.15s ease;
|
|
250
|
+
}
|
|
251
|
+
.card-item:hover {
|
|
252
|
+
border-color: var(--line-strong);
|
|
253
|
+
transform: translateY(-1px);
|
|
254
|
+
}
|
|
255
|
+
.card-item.active-item {
|
|
256
|
+
border-color: var(--line-strong);
|
|
257
|
+
box-shadow: 0 0 12px rgba(125, 216, 255, 0.12);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.item-header {
|
|
261
|
+
display: flex;
|
|
262
|
+
align-items: flex-start;
|
|
263
|
+
justify-content: space-between;
|
|
264
|
+
gap: 8px;
|
|
265
|
+
}
|
|
266
|
+
.badge-id {
|
|
267
|
+
font-family: var(--font-mono);
|
|
268
|
+
font-size: 10px;
|
|
269
|
+
font-weight: 700;
|
|
270
|
+
background: var(--cyan-soft);
|
|
271
|
+
color: var(--cyan);
|
|
272
|
+
padding: 2px 6px;
|
|
273
|
+
border-radius: 4px;
|
|
274
|
+
white-space: nowrap;
|
|
275
|
+
}
|
|
276
|
+
.badge-history {
|
|
277
|
+
background: var(--violet-soft);
|
|
278
|
+
color: var(--violet);
|
|
279
|
+
}
|
|
280
|
+
.badge-stage {
|
|
281
|
+
font-family: var(--font-mono);
|
|
282
|
+
font-size: 9px;
|
|
283
|
+
font-weight: 700;
|
|
284
|
+
text-transform: uppercase;
|
|
285
|
+
padding: 1px 6px;
|
|
286
|
+
border-radius: 4px;
|
|
287
|
+
background: var(--gold-soft);
|
|
288
|
+
color: var(--gold);
|
|
289
|
+
border: 1px solid rgba(242, 193, 78, 0.3);
|
|
290
|
+
}
|
|
291
|
+
.badge-stage.stage-check {
|
|
292
|
+
background: var(--mint-soft);
|
|
293
|
+
color: var(--mint);
|
|
294
|
+
border-color: rgba(111, 227, 180, 0.3);
|
|
295
|
+
}
|
|
296
|
+
.item-title {
|
|
297
|
+
font-weight: 600;
|
|
298
|
+
font-size: 12px;
|
|
299
|
+
flex: 1;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.progress-bar-container {
|
|
303
|
+
background: rgba(0, 0, 0, 0.3);
|
|
304
|
+
height: 6px;
|
|
305
|
+
border-radius: 3px;
|
|
306
|
+
overflow: hidden;
|
|
307
|
+
margin-top: 4px;
|
|
308
|
+
}
|
|
309
|
+
.progress-bar-fill {
|
|
310
|
+
background: linear-gradient(135deg, var(--cyan), var(--violet));
|
|
311
|
+
height: 100%;
|
|
312
|
+
transition: width 0.3s ease;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.item-tags, .item-meta {
|
|
316
|
+
display: flex;
|
|
317
|
+
align-items: center;
|
|
318
|
+
justify-content: space-between;
|
|
319
|
+
gap: 6px;
|
|
320
|
+
font-size: 11px;
|
|
321
|
+
color: var(--muted);
|
|
322
|
+
flex-wrap: wrap;
|
|
323
|
+
}
|
|
324
|
+
.tag {
|
|
325
|
+
font-size: 10px;
|
|
326
|
+
padding: 1px 6px;
|
|
327
|
+
border-radius: 4px;
|
|
328
|
+
background: rgba(255, 255, 255, 0.05);
|
|
329
|
+
border: 1px solid var(--line);
|
|
330
|
+
}
|
|
331
|
+
.tag-feasibility { color: var(--mint); }
|
|
332
|
+
.tag-value { color: var(--cyan); }
|
|
333
|
+
.tag-cat { color: var(--gold); }
|
|
334
|
+
.tag-finding-clean { color: var(--mint); }
|
|
335
|
+
.tag-finding-warn { color: var(--red); background: var(--red-soft); border-color: rgba(255, 112, 97, 0.3); }
|
|
336
|
+
|
|
337
|
+
.card-actions {
|
|
338
|
+
display: flex;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 6px;
|
|
341
|
+
margin-top: 4px;
|
|
342
|
+
padding-top: 6px;
|
|
343
|
+
border-top: 1px dashed var(--line);
|
|
344
|
+
flex-wrap: wrap;
|
|
345
|
+
}
|
|
346
|
+
.btn-card-action {
|
|
347
|
+
background: rgba(255, 255, 255, 0.04);
|
|
348
|
+
border: 1px solid var(--line);
|
|
349
|
+
color: var(--ink);
|
|
350
|
+
padding: 2px 7px;
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
font-family: var(--font-mono);
|
|
353
|
+
font-size: 10px;
|
|
354
|
+
cursor: pointer;
|
|
355
|
+
transition: all 0.15s ease;
|
|
356
|
+
}
|
|
357
|
+
.btn-card-action:hover {
|
|
358
|
+
background: var(--cyan-soft);
|
|
359
|
+
color: var(--cyan);
|
|
360
|
+
border-color: var(--cyan);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.empty-state {
|
|
364
|
+
text-align: center;
|
|
365
|
+
padding: 24px;
|
|
366
|
+
color: var(--muted);
|
|
367
|
+
font-size: 12px;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.pulse-panel {
|
|
371
|
+
background: var(--bg-deep);
|
|
372
|
+
border: 1px solid var(--line);
|
|
373
|
+
border-radius: 10px;
|
|
374
|
+
padding: 12px 16px;
|
|
375
|
+
display: flex;
|
|
376
|
+
align-items: center;
|
|
377
|
+
justify-content: space-between;
|
|
378
|
+
font-size: 12px;
|
|
379
|
+
flex-wrap: wrap;
|
|
380
|
+
gap: 8px;
|
|
381
|
+
}
|
|
382
|
+
`;
|
|
383
|
+
export class StudioViewRenderer {
|
|
384
|
+
defaultTheme;
|
|
385
|
+
constructor(options = {}) {
|
|
386
|
+
this.defaultTheme = options.defaultTheme || "auto";
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Escapes HTML characters safely to prevent XSS.
|
|
390
|
+
*/
|
|
391
|
+
escapeHtml(str) {
|
|
392
|
+
if (!str)
|
|
393
|
+
return "";
|
|
394
|
+
return String(str)
|
|
395
|
+
.replace(/&/g, "&")
|
|
396
|
+
.replace(/</g, "<")
|
|
397
|
+
.replace(/>/g, ">")
|
|
398
|
+
.replace(/"/g, """)
|
|
399
|
+
.replace(/'/g, "'");
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Safe JSON serializer for HTML script tags.
|
|
403
|
+
* Escapes `<` to `\u003c` and `>` to `\u003e` to prevent script tag injection and HTML breakout.
|
|
404
|
+
*/
|
|
405
|
+
escapeJsonForHtml(data) {
|
|
406
|
+
return JSON.stringify(data).replace(/</g, "\\u003c");
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Renders the top header block with project info, git branch, gate status, and next action.
|
|
410
|
+
*/
|
|
411
|
+
renderHeaderBlock(snapshot) {
|
|
412
|
+
const status = snapshot.status || {};
|
|
413
|
+
const projectName = status.project?.name || "Nexus-DevFlow Project";
|
|
414
|
+
const branch = status.git?.branch || "main";
|
|
415
|
+
const activeRuns = status.activeRuns || [];
|
|
416
|
+
const gateReport = snapshot.gatekeeper || { passed: true };
|
|
417
|
+
const nextActionCmd = typeof status.nextAction === "string" ? status.nextAction : status.nextAction?.command || "/feature";
|
|
418
|
+
const isGatePassed = gateReport.passed;
|
|
419
|
+
const gatePillClass = isGatePassed ? "badge-pass" : "badge-blocked";
|
|
420
|
+
const gatePillText = isGatePassed ? "✔ Gate Passed" : "✖ Gate Blocked";
|
|
421
|
+
return `
|
|
422
|
+
<header class="studio-header">
|
|
423
|
+
<div class="header-left">
|
|
424
|
+
<span class="project-badge">⚡ ${this.escapeHtml(projectName)}</span>
|
|
425
|
+
<span class="branch-pill">🌿 ${this.escapeHtml(branch)}</span>
|
|
426
|
+
${activeRuns.length > 0 ? `<span class="multitask-pill">🗂️ ${activeRuns.length} Active Workspaces</span>` : ""}
|
|
427
|
+
</div>
|
|
428
|
+
<div class="header-right">
|
|
429
|
+
<span class="${gatePillClass}">${gatePillText}</span>
|
|
430
|
+
<button class="btn-action btn-primary" onclick="dispatchCommand('${this.escapeHtml(nextActionCmd)}')">
|
|
431
|
+
Next: ${this.escapeHtml(nextActionCmd)}
|
|
432
|
+
</button>
|
|
433
|
+
</div>
|
|
434
|
+
</header>
|
|
435
|
+
`.trim();
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Renders the dual-track stepper reflecting current track and stage.
|
|
439
|
+
*/
|
|
440
|
+
renderDualTrackStepper(snapshot) {
|
|
441
|
+
const track = snapshot.workflow?.track || "fast";
|
|
442
|
+
const currentStage = snapshot.workflow?.stage || (snapshot.status?.currentWork?.status) || "feature";
|
|
443
|
+
const fastSteps = [
|
|
444
|
+
{ name: "Discover & Spec", cmd: "/feature", id: "feature" },
|
|
445
|
+
{ name: "Implement (TDD)", cmd: "/implement", id: "implement" },
|
|
446
|
+
{ name: "Verify & Gate", cmd: "/check", id: "check" },
|
|
447
|
+
{ name: "Release & Archive", cmd: "/complete", id: "complete" },
|
|
448
|
+
];
|
|
449
|
+
const preflightSteps = [
|
|
450
|
+
{ name: "SA Ingestion", cmd: "/analyze", id: "analyze" },
|
|
451
|
+
{ name: "Align & ADR", cmd: "/grill", id: "grill" },
|
|
452
|
+
{ name: "Brainstorm", cmd: "/brainstorm", id: "brainstorm" },
|
|
453
|
+
{ name: "Exploration", cmd: "/discovery", id: "discovery" },
|
|
454
|
+
];
|
|
455
|
+
const steps = track === "pre-flight" || track === "deep" ? preflightSteps : fastSteps;
|
|
456
|
+
const stepsHtml = steps.map((step, idx) => {
|
|
457
|
+
const isCurrent = currentStage.toLowerCase().includes(step.id);
|
|
458
|
+
const stepClass = isCurrent ? "step active" : "step";
|
|
459
|
+
return `
|
|
460
|
+
<div class="${stepClass}">
|
|
461
|
+
<div class="step-dot">${idx + 1}</div>
|
|
462
|
+
<div class="step-name">${this.escapeHtml(step.name)}</div>
|
|
463
|
+
<div class="step-cmd">${this.escapeHtml(step.cmd)}</div>
|
|
464
|
+
</div>
|
|
465
|
+
`;
|
|
466
|
+
}).join("");
|
|
467
|
+
return `
|
|
468
|
+
<section class="stepper-section">
|
|
469
|
+
<div class="stepper-header">
|
|
470
|
+
<span class="badge-track track-${this.escapeHtml(track)}">Track: ${this.escapeHtml(track.toUpperCase())}</span>
|
|
471
|
+
<span class="step-label">Stage: <strong>${this.escapeHtml(currentStage)}</strong></span>
|
|
472
|
+
</div>
|
|
473
|
+
<div class="stepper-track">
|
|
474
|
+
${stepsHtml}
|
|
475
|
+
</div>
|
|
476
|
+
</section>
|
|
477
|
+
`.trim();
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Renders the 3-Pillars cards (Future: Ideas, Present: Active Specs, Past: History).
|
|
481
|
+
*/
|
|
482
|
+
render3PillarsCards(snapshot, ideas) {
|
|
483
|
+
const status = snapshot.status || {};
|
|
484
|
+
const currentWork = status.currentWork || {};
|
|
485
|
+
const activeRuns = status.activeRuns || [];
|
|
486
|
+
const pendingIdeas = ideas?.pending || [];
|
|
487
|
+
const historyItems = snapshot.history?.items || [];
|
|
488
|
+
const historyTotal = snapshot.history?.total ?? historyItems.length;
|
|
489
|
+
// Pillar 1: Future Ideas HTML
|
|
490
|
+
const ideasHtml = pendingIdeas.length > 0
|
|
491
|
+
? pendingIdeas.map((idea) => `
|
|
492
|
+
<div class="card-item idea-item">
|
|
493
|
+
<div class="item-header">
|
|
494
|
+
<span class="badge-id">${this.escapeHtml(idea.id)}</span>
|
|
495
|
+
<span class="item-title">${this.escapeHtml(idea.title || idea.rawInput?.slice(0, 40))}</span>
|
|
496
|
+
</div>
|
|
497
|
+
<div class="item-tags">
|
|
498
|
+
<span class="tag tag-feasibility">${this.escapeHtml(idea.feasibility || "Feasible")}</span>
|
|
499
|
+
<span class="tag tag-value">${this.escapeHtml(idea.value || "Value: High")}</span>
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
`).join("")
|
|
503
|
+
: `<div class="empty-state">No pending ideas in inbox. Use <code>nexus-devflow idea add</code></div>`;
|
|
504
|
+
// Pillar 2: Present Active HTML
|
|
505
|
+
let presentHtml = "";
|
|
506
|
+
let totalActiveTasks = 0;
|
|
507
|
+
let totalActiveCompleted = 0;
|
|
508
|
+
if (activeRuns.length > 0) {
|
|
509
|
+
totalActiveTasks = activeRuns.reduce((acc, r) => acc + (r.totalTasks || 0), 0);
|
|
510
|
+
totalActiveCompleted = activeRuns.reduce((acc, r) => acc + (r.completedTasks || 0), 0);
|
|
511
|
+
presentHtml = activeRuns.map((run) => {
|
|
512
|
+
const pct = run.totalTasks > 0 ? Math.round((run.completedTasks / run.totalTasks) * 100) : 0;
|
|
513
|
+
const stageBadgeClass = (run.status || "").includes("check") ? "badge-stage stage-check" : "badge-stage";
|
|
514
|
+
const findingTag = run.hasOpenFindings
|
|
515
|
+
? `<span class="tag tag-finding-warn">⚠ Open Findings</span>`
|
|
516
|
+
: `<span class="tag tag-finding-clean">✔ Clean</span>`;
|
|
517
|
+
return `
|
|
518
|
+
<div class="card-item active-item">
|
|
519
|
+
<div class="item-header">
|
|
520
|
+
<span class="badge-id">${this.escapeHtml(run.runId)}</span>
|
|
521
|
+
<span class="${stageBadgeClass}">${this.escapeHtml(run.status || "active")}</span>
|
|
522
|
+
</div>
|
|
523
|
+
<div class="item-title">${this.escapeHtml(run.title || "Living Spec")}</div>
|
|
524
|
+
<div class="progress-bar-container">
|
|
525
|
+
<div class="progress-bar-fill" style="width: ${pct}%;"></div>
|
|
526
|
+
</div>
|
|
527
|
+
<div class="item-meta">
|
|
528
|
+
<span>🌿 <code>${this.escapeHtml(run.branch || "main")}</code></span>
|
|
529
|
+
${findingTag}
|
|
530
|
+
</div>
|
|
531
|
+
<div class="item-tags">
|
|
532
|
+
<span>Progress: <strong>${pct}%</strong> (${run.completedTasks}/${run.totalTasks})</span>
|
|
533
|
+
<span>Remaining: ${run.remainingTasks}</span>
|
|
534
|
+
</div>
|
|
535
|
+
<div class="card-actions">
|
|
536
|
+
<button class="btn-card-action" onclick="dispatchCommand('/implement ${this.escapeHtml(run.runId)}')">▶ /implement ${this.escapeHtml(run.runId)}</button>
|
|
537
|
+
<button class="btn-card-action" onclick="dispatchCommand('/check ${this.escapeHtml(run.runId)}')">🧪 /check ${this.escapeHtml(run.runId)}</button>
|
|
538
|
+
<button class="btn-card-action" onclick="dispatchCommand('/complete ${this.escapeHtml(run.runId)}')">📦 /complete ${this.escapeHtml(run.runId)}</button>
|
|
539
|
+
</div>
|
|
540
|
+
</div>
|
|
541
|
+
`;
|
|
542
|
+
}).join("");
|
|
543
|
+
}
|
|
544
|
+
else if (currentWork.state === "active") {
|
|
545
|
+
totalActiveTasks = currentWork.total || 0;
|
|
546
|
+
totalActiveCompleted = currentWork.completed || 0;
|
|
547
|
+
const progressPercent = totalActiveTasks > 0 ? Math.round((totalActiveCompleted / totalActiveTasks) * 100) : 0;
|
|
548
|
+
const runId = currentWork.runId || "ACTIVE";
|
|
549
|
+
presentHtml = `
|
|
550
|
+
<div class="card-item active-item">
|
|
551
|
+
<div class="item-header">
|
|
552
|
+
<span class="badge-id">${this.escapeHtml(runId)}</span>
|
|
553
|
+
<span class="badge-stage">${this.escapeHtml(currentWork.status || "active")}</span>
|
|
554
|
+
</div>
|
|
555
|
+
<div class="item-title">${this.escapeHtml(currentWork.title || "Active Spec")}</div>
|
|
556
|
+
<div class="progress-bar-container">
|
|
557
|
+
<div class="progress-bar-fill" style="width: ${progressPercent}%;"></div>
|
|
558
|
+
</div>
|
|
559
|
+
<div class="item-meta">
|
|
560
|
+
<span>Progress: <strong>${progressPercent}%</strong> (${currentWork.completed}/${currentWork.total})</span>
|
|
561
|
+
<span>Remaining: ${currentWork.remaining}</span>
|
|
562
|
+
</div>
|
|
563
|
+
<div class="card-actions">
|
|
564
|
+
<button class="btn-card-action" onclick="dispatchCommand('/implement ${this.escapeHtml(runId)}')">▶ /implement ${this.escapeHtml(runId)}</button>
|
|
565
|
+
<button class="btn-card-action" onclick="dispatchCommand('/check ${this.escapeHtml(runId)}')">🧪 /check ${this.escapeHtml(runId)}</button>
|
|
566
|
+
<button class="btn-card-action" onclick="dispatchCommand('/complete ${this.escapeHtml(runId)}')">📦 /complete ${this.escapeHtml(runId)}</button>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
`;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
presentHtml = `
|
|
573
|
+
<div class="empty-state">
|
|
574
|
+
Idle workspace.<br>Run <code>/feature</code> or <code>/fix</code> to start a run.
|
|
575
|
+
</div>
|
|
576
|
+
`;
|
|
577
|
+
}
|
|
578
|
+
const presentCountLabel = activeRuns.length > 0
|
|
579
|
+
? `${activeRuns.length} Active Runs · ${totalActiveCompleted}/${totalActiveTasks} Tasks`
|
|
580
|
+
: `${totalActiveCompleted}/${totalActiveTasks} Tasks`;
|
|
581
|
+
// Pillar 3: Past History HTML
|
|
582
|
+
const historyHtml = historyItems.length > 0
|
|
583
|
+
? historyItems.slice(0, 8).map((item) => {
|
|
584
|
+
const runId = item.buildPlanItem || item.file?.replace(/\.md$/, "") || "RELEASE";
|
|
585
|
+
return `
|
|
586
|
+
<div class="card-item history-item">
|
|
587
|
+
<div class="item-header">
|
|
588
|
+
<span class="badge-id badge-history">${this.escapeHtml(runId)}</span>
|
|
589
|
+
<span class="item-title">${this.escapeHtml(item.title)}</span>
|
|
590
|
+
</div>
|
|
591
|
+
<div class="item-meta">
|
|
592
|
+
<span class="tag tag-cat">${this.escapeHtml(item.type)}</span>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
`;
|
|
596
|
+
}).join("")
|
|
597
|
+
: `<div class="empty-state">No archived history records yet.</div>`;
|
|
598
|
+
return `
|
|
599
|
+
<main class="kanban-grid">
|
|
600
|
+
<!-- 🔮 1. Future (Ideas Inbox) -->
|
|
601
|
+
<section class="kanban-col" id="pillar-future">
|
|
602
|
+
<div class="col-header">
|
|
603
|
+
<span class="col-title">🔮 Future (Ideas Inbox)</span>
|
|
604
|
+
<span class="col-count">${pendingIdeas.length}</span>
|
|
605
|
+
</div>
|
|
606
|
+
<div class="col-content">
|
|
607
|
+
${ideasHtml}
|
|
608
|
+
</div>
|
|
609
|
+
</section>
|
|
610
|
+
|
|
611
|
+
<!-- ⚡ 2. Present (Active Living Context) -->
|
|
612
|
+
<section class="kanban-col" id="pillar-present">
|
|
613
|
+
<div class="col-header">
|
|
614
|
+
<span class="col-title">⚡ Present (Active Living Spec)</span>
|
|
615
|
+
<span class="col-count">${presentCountLabel}</span>
|
|
616
|
+
</div>
|
|
617
|
+
<div class="col-content">
|
|
618
|
+
${presentHtml}
|
|
619
|
+
</div>
|
|
620
|
+
</section>
|
|
621
|
+
|
|
622
|
+
<!-- 📦 3. Past (Release History Archive) -->
|
|
623
|
+
<section class="kanban-col" id="pillar-past">
|
|
624
|
+
<div class="col-header">
|
|
625
|
+
<span class="col-title">📦 Past (History Archives)</span>
|
|
626
|
+
<span class="col-count">${historyTotal} Releases</span>
|
|
627
|
+
</div>
|
|
628
|
+
<div class="col-content">
|
|
629
|
+
${historyHtml}
|
|
630
|
+
</div>
|
|
631
|
+
</section>
|
|
632
|
+
</main>
|
|
633
|
+
`.trim();
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Generates adaptive CSS styles for web or webview mode.
|
|
637
|
+
*/
|
|
638
|
+
renderStyles(options = {}) {
|
|
639
|
+
const mode = options.mode || "web";
|
|
640
|
+
const isWebview = mode === "webview";
|
|
641
|
+
if (isWebview) {
|
|
642
|
+
return `
|
|
643
|
+
:root {
|
|
644
|
+
--font-display: "Google Sans Thai", "Google Sans", "Noto Sans Thai", "Space Grotesk", sans-serif;
|
|
645
|
+
--font-sans: "Google Sans Thai", "Google Sans", "Noto Sans Thai", sans-serif;
|
|
646
|
+
--font-mono: "IBM Plex Mono", monospace;
|
|
647
|
+
--bg: var(--vscode-editor-background, #0a2540);
|
|
648
|
+
--bg-deep: var(--vscode-sideBar-background, #061a2e);
|
|
649
|
+
--panel: var(--vscode-editorWidget-background, rgba(255, 255, 255, 0.04));
|
|
650
|
+
--panel-strong: var(--vscode-editorGroupHeader-tabsBackground, #0c2b49);
|
|
651
|
+
--ink: var(--vscode-editor-foreground, #edf6ff);
|
|
652
|
+
--soft: var(--vscode-descriptionForeground, #b7d3ef);
|
|
653
|
+
--muted: var(--vscode-disabledForeground, #7598ba);
|
|
654
|
+
--line: var(--vscode-widget-border, rgba(191, 224, 255, 0.17));
|
|
655
|
+
--line-strong: var(--vscode-focusBorder, rgba(191, 224, 255, 0.36));
|
|
656
|
+
--cyan: #7dd8ff;
|
|
657
|
+
--mint: #6fe3b4;
|
|
658
|
+
--gold: #f2c14e;
|
|
659
|
+
--red: #ff7061;
|
|
660
|
+
--violet: #b8a4ff;
|
|
661
|
+
--cyan-soft: rgba(125, 216, 255, 0.14);
|
|
662
|
+
--mint-soft: rgba(111, 227, 180, 0.14);
|
|
663
|
+
--gold-soft: rgba(242, 193, 78, 0.14);
|
|
664
|
+
--red-soft: rgba(255, 112, 97, 0.14);
|
|
665
|
+
--violet-soft: rgba(184, 164, 255, 0.14);
|
|
666
|
+
}
|
|
667
|
+
`.trim();
|
|
668
|
+
}
|
|
669
|
+
return `
|
|
670
|
+
:root {
|
|
671
|
+
--font-display: "Google Sans Thai", "Google Sans", "Noto Sans Thai", "Space Grotesk", sans-serif;
|
|
672
|
+
--font-sans: "Google Sans Thai", "Google Sans", "Noto Sans Thai", sans-serif;
|
|
673
|
+
--font-mono: "IBM Plex Mono", monospace;
|
|
674
|
+
--bg: #0a2540;
|
|
675
|
+
--bg-deep: #061a2e;
|
|
676
|
+
--panel: rgba(255, 255, 255, 0.04);
|
|
677
|
+
--panel-strong: #0c2b49;
|
|
678
|
+
--ink: #edf6ff;
|
|
679
|
+
--soft: #b7d3ef;
|
|
680
|
+
--muted: #7598ba;
|
|
681
|
+
--line: rgba(191, 224, 255, 0.17);
|
|
682
|
+
--line-strong: rgba(191, 224, 255, 0.36);
|
|
683
|
+
--cyan: #7dd8ff;
|
|
684
|
+
--mint: #6fe3b4;
|
|
685
|
+
--gold: #f2c14e;
|
|
686
|
+
--red: #ff7061;
|
|
687
|
+
--violet: #b8a4ff;
|
|
688
|
+
--cyan-soft: rgba(125, 216, 255, 0.14);
|
|
689
|
+
--mint-soft: rgba(111, 227, 180, 0.14);
|
|
690
|
+
--gold-soft: rgba(242, 193, 78, 0.14);
|
|
691
|
+
--red-soft: rgba(255, 112, 97, 0.14);
|
|
692
|
+
--violet-soft: rgba(184, 164, 255, 0.14);
|
|
693
|
+
}
|
|
694
|
+
`.trim();
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Renders the complete web dashboard HTML document with optional initial snapshot injection.
|
|
698
|
+
*/
|
|
699
|
+
renderWebDashboard(snapshot) {
|
|
700
|
+
if (!snapshot) {
|
|
701
|
+
return DASHBOARD_PAGE_HTML;
|
|
702
|
+
}
|
|
703
|
+
const serialized = this.escapeJsonForHtml(snapshot);
|
|
704
|
+
return DASHBOARD_PAGE_HTML.replace("window.__INITIAL_SNAPSHOT__ = null;", `window.__INITIAL_SNAPSHOT__ = ${serialized};`);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Renders the complete Webview Studio HTML document with VS Code bridge scripts.
|
|
708
|
+
*/
|
|
709
|
+
renderWebviewStudio(snapshot, ideas, options = {}) {
|
|
710
|
+
const status = snapshot.status || {};
|
|
711
|
+
const projectName = status.project?.name || "Nexus-DevFlow Project";
|
|
712
|
+
const headerHtml = this.renderHeaderBlock(snapshot);
|
|
713
|
+
const stepperHtml = this.renderDualTrackStepper(snapshot);
|
|
714
|
+
const pillarsHtml = this.render3PillarsCards(snapshot, ideas);
|
|
715
|
+
const gateReport = snapshot.gatekeeper || { findingsBlockers: 0 };
|
|
716
|
+
const driftReport = snapshot.drift || { hasDrift: false };
|
|
717
|
+
const activeRuns = status.activeRuns || [];
|
|
718
|
+
const currentWork = status.currentWork || {};
|
|
719
|
+
return `<!DOCTYPE html>
|
|
720
|
+
<html lang="th" data-theme="${options.theme || this.defaultTheme}">
|
|
721
|
+
<head>
|
|
722
|
+
<meta charset="UTF-8">
|
|
723
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
724
|
+
<title>Nexus-DevFlow Studio: ${this.escapeHtml(projectName)}</title>
|
|
725
|
+
<style>
|
|
726
|
+
${this.renderStyles({ mode: "webview", theme: options.theme || this.defaultTheme })}
|
|
727
|
+
${WEBVIEW_STUDIO_CSS}
|
|
728
|
+
</style>
|
|
729
|
+
</head>
|
|
730
|
+
<body>
|
|
731
|
+
<div class="studio-container">
|
|
732
|
+
${headerHtml}
|
|
733
|
+
|
|
734
|
+
<!-- Quick Action Bar -->
|
|
735
|
+
<div class="action-bar">
|
|
736
|
+
<span class="action-label">Quick Actions:</span>
|
|
737
|
+
<button class="btn-action" onclick="dispatchCommand('/feature')">/feature</button>
|
|
738
|
+
<button class="btn-action" onclick="dispatchCommand('/implement')">/implement</button>
|
|
739
|
+
<button class="btn-action" onclick="dispatchCommand('/check')">/check</button>
|
|
740
|
+
<button class="btn-action" onclick="dispatchCommand('/complete')">/complete</button>
|
|
741
|
+
<button class="btn-action" onclick="dispatchCommand('/continuous')">/continuous</button>
|
|
742
|
+
<button class="btn-action" onclick="dispatchCommand('nexus-devflow check-gate')">check-gate</button>
|
|
743
|
+
<button class="btn-action" onclick="dispatchCommand('nexus-devflow drift')">drift</button>
|
|
744
|
+
<button class="btn-action" onclick="dispatchCommand('nexus-devflow doctor')">doctor</button>
|
|
745
|
+
</div>
|
|
746
|
+
|
|
747
|
+
${stepperHtml}
|
|
748
|
+
|
|
749
|
+
${pillarsHtml}
|
|
750
|
+
|
|
751
|
+
<!-- Bottom Pulse -->
|
|
752
|
+
<footer class="pulse-panel">
|
|
753
|
+
<div>Findings Blockers: <strong>${gateReport.findingsBlockers}</strong> | Active Findings: <strong>${status.findings?.total || 0}</strong></div>
|
|
754
|
+
<div>Git Drift: <strong>${driftReport.hasDrift ? "⚠ Drift Detected" : "✔ In Sync"}</strong> | Workspaces: <strong>${activeRuns.length || (currentWork.state === "active" ? 1 : 0)} Active</strong></div>
|
|
755
|
+
</footer>
|
|
756
|
+
</div>
|
|
757
|
+
|
|
758
|
+
<script>
|
|
759
|
+
function dispatchCommand(cmd) {
|
|
760
|
+
if (window.acquireVsCodeApi) {
|
|
761
|
+
const vscode = window.acquireVsCodeApi();
|
|
762
|
+
vscode.postMessage({ command: 'exec', text: cmd });
|
|
763
|
+
} else if (navigator.clipboard) {
|
|
764
|
+
navigator.clipboard.writeText(cmd);
|
|
765
|
+
alert('Copied command to clipboard: ' + cmd);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
</script>
|
|
769
|
+
</body>
|
|
770
|
+
</html>`;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
//# sourceMappingURL=studio-view-renderer.js.map
|