bobs-workshop 0.1.8 → 0.2.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 +34 -26
- package/dist/dashboard/server.js +20 -23
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +81 -35
- package/dist/index.js.map +1 -1
- package/dist/prompts/architect.js +72 -13
- package/dist/prompts/architect.js.map +1 -1
- package/dist/prompts/debugger.js +69 -18
- package/dist/prompts/debugger.js.map +1 -1
- package/dist/prompts/engineer.js +89 -16
- package/dist/prompts/engineer.js.map +1 -1
- package/dist/prompts/orchestrator.js +125 -46
- package/dist/prompts/orchestrator.js.map +1 -1
- package/dist/prompts/reviewer.js +87 -2
- package/dist/prompts/reviewer.js.map +1 -1
- package/dist/services/activitySummarizer.js +36 -1
- package/dist/services/activitySummarizer.js.map +1 -1
- package/dist/tools/dashboardTools.js +6 -23
- package/dist/tools/dashboardTools.js.map +1 -1
- package/dist/tools/orchestratorTools.js +96 -29
- package/dist/tools/orchestratorTools.js.map +1 -1
- package/dist/tools/specTools.js +1 -19
- package/dist/tools/specTools.js.map +1 -1
- package/package.json +4 -2
- package/public/.well-known/mcp/manifest.json +471 -0
- package/public/index.html +3157 -0
- package/public/index.html.backup +2805 -0
- package/public/index.html.backup2 +1292 -0
- package/scripts/cleanup-system-logs.ts +121 -0
|
@@ -0,0 +1,3157 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="h-full">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>🔧 Bob's Workshop - Manual Management Dashboard</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
|
8
|
+
|
|
9
|
+
<!-- Google Fonts: Poppins and Outfit -->
|
|
10
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
11
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
12
|
+
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
:root {
|
|
16
|
+
/* Color System */
|
|
17
|
+
--bg-primary: linear-gradient(to bottom, #000000 0%, #1a1a1a 50%, #1f1f21 100%);
|
|
18
|
+
--bg-glass: rgba(255, 255, 255, 0.1);
|
|
19
|
+
--bg-glass-hover: rgba(255, 255, 255, 0.15);
|
|
20
|
+
--border-glass: rgba(255, 255, 255, 0.2);
|
|
21
|
+
--text-primary: #ffffff;
|
|
22
|
+
--text-secondary: rgba(255, 255, 255, 0.8);
|
|
23
|
+
--text-muted: rgba(255, 255, 255, 0.6);
|
|
24
|
+
--accent-blue: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%);
|
|
25
|
+
--accent-green: #10b981;
|
|
26
|
+
--accent-orange: #f59e0b;
|
|
27
|
+
--accent-yellow: #f59e0b;
|
|
28
|
+
--accent-red: #ef4444;
|
|
29
|
+
|
|
30
|
+
/* Typography */
|
|
31
|
+
--font-heading: 'Poppins', sans-serif;
|
|
32
|
+
--font-body: 'Outfit', sans-serif;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
* {
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
body {
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
font-family: var(--font-body);
|
|
43
|
+
background: var(--bg-primary);
|
|
44
|
+
color: var(--text-primary);
|
|
45
|
+
min-height: 100vh;
|
|
46
|
+
overflow-x: hidden;
|
|
47
|
+
zoom: 0.69;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Glass morphism utility classes */
|
|
51
|
+
.glass {
|
|
52
|
+
background: var(--bg-glass);
|
|
53
|
+
backdrop-filter: blur(12px);
|
|
54
|
+
-webkit-backdrop-filter: blur(12px);
|
|
55
|
+
border: 1px solid var(--border-glass);
|
|
56
|
+
border-radius: 16px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.glass-hover {
|
|
60
|
+
transition: all 0.3s ease;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.glass-hover:hover {
|
|
64
|
+
background: var(--bg-glass-hover);
|
|
65
|
+
transform: translateY(-2px);
|
|
66
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Typography */
|
|
70
|
+
.heading {
|
|
71
|
+
font-family: var(--font-heading);
|
|
72
|
+
font-weight: 600;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.text-gradient {
|
|
76
|
+
background: var(--accent-orange);
|
|
77
|
+
-webkit-background-clip: text;
|
|
78
|
+
-webkit-text-fill-color: transparent;
|
|
79
|
+
background-clip: text;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Header */
|
|
83
|
+
.header {
|
|
84
|
+
background: var(--bg-glass);
|
|
85
|
+
backdrop-filter: blur(20px);
|
|
86
|
+
border-bottom: 1px solid var(--border-glass);
|
|
87
|
+
padding: 1.5rem 2rem;
|
|
88
|
+
display: flex;
|
|
89
|
+
justify-content: space-between;
|
|
90
|
+
align-items: center;
|
|
91
|
+
position: sticky;
|
|
92
|
+
top: 0;
|
|
93
|
+
z-index: 50;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.logo {
|
|
97
|
+
font-family: var(--font-heading);
|
|
98
|
+
font-size: 1.5rem;
|
|
99
|
+
font-weight: 700;
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 0.5rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Metrics Cards */
|
|
106
|
+
.metrics-grid {
|
|
107
|
+
display: grid;
|
|
108
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
109
|
+
gap: 1.5rem;
|
|
110
|
+
padding: 2rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.metric-card {
|
|
114
|
+
padding: 1.5rem;
|
|
115
|
+
background: var(--bg-glass);
|
|
116
|
+
backdrop-filter: blur(12px);
|
|
117
|
+
border-radius: 16px;
|
|
118
|
+
border: 1px solid var(--border-glass);
|
|
119
|
+
transition: all 0.3s ease;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.metric-card:hover {
|
|
123
|
+
background: var(--bg-glass-hover);
|
|
124
|
+
transform: translateY(-4px);
|
|
125
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.metric-value {
|
|
129
|
+
font-family: var(--font-heading);
|
|
130
|
+
font-size: 2.5rem;
|
|
131
|
+
font-weight: 700;
|
|
132
|
+
margin-bottom: 0.5rem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.metric-label {
|
|
136
|
+
font-size: 0.875rem;
|
|
137
|
+
color: var(--text-secondary);
|
|
138
|
+
text-transform: uppercase;
|
|
139
|
+
letter-spacing: 0.05em;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Spec Table */
|
|
143
|
+
.spec-container {
|
|
144
|
+
margin: 2rem;
|
|
145
|
+
background: var(--bg-glass);
|
|
146
|
+
backdrop-filter: blur(12px);
|
|
147
|
+
border-radius: 20px;
|
|
148
|
+
border: 1px solid var(--border-glass);
|
|
149
|
+
border-top: 3px solid var(--accent-orange);
|
|
150
|
+
overflow: hidden;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.spec-header {
|
|
154
|
+
padding: 1.5rem 2rem;
|
|
155
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(99, 102, 241, 0.1) 100%);
|
|
156
|
+
border-bottom: 1px solid var(--border-glass);
|
|
157
|
+
display: flex;
|
|
158
|
+
justify-content: space-between;
|
|
159
|
+
align-items: center;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.spec-table {
|
|
163
|
+
width: 100%;
|
|
164
|
+
border-collapse: collapse;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.spec-table th {
|
|
168
|
+
background: rgba(255, 255, 255, 0.05);
|
|
169
|
+
padding: 1rem 1.5rem;
|
|
170
|
+
text-align: left;
|
|
171
|
+
font-family: var(--font-heading);
|
|
172
|
+
font-weight: 600;
|
|
173
|
+
font-size: 0.875rem;
|
|
174
|
+
color: var(--text-secondary);
|
|
175
|
+
text-transform: uppercase;
|
|
176
|
+
letter-spacing: 0.05em;
|
|
177
|
+
border-bottom: 1px solid var(--border-glass);
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
user-select: none;
|
|
180
|
+
position: relative;
|
|
181
|
+
transition: all 0.2s ease;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.spec-table th:hover {
|
|
185
|
+
background: rgba(255, 255, 255, 0.1);
|
|
186
|
+
color: var(--text-primary);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.spec-table th.sortable::after {
|
|
190
|
+
content: '↕';
|
|
191
|
+
position: absolute;
|
|
192
|
+
right: 0.5rem;
|
|
193
|
+
opacity: 0.3;
|
|
194
|
+
font-size: 0.8rem;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.spec-table th.sorted-asc::after {
|
|
198
|
+
content: '↑';
|
|
199
|
+
opacity: 1;
|
|
200
|
+
color: var(--accent-blue);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.spec-table th.sorted-desc::after {
|
|
204
|
+
content: '↓';
|
|
205
|
+
opacity: 1;
|
|
206
|
+
color: var(--accent-blue);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.spec-table td {
|
|
210
|
+
padding: 1rem 1.5rem;
|
|
211
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
212
|
+
transition: all 0.2s ease;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.spec-table tr:hover td {
|
|
216
|
+
background: rgba(255, 255, 255, 0.02);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Table gradient - darker at top, lighter only for last 4-5 rows */
|
|
220
|
+
.spec-table tbody {
|
|
221
|
+
background: linear-gradient(to bottom,
|
|
222
|
+
rgba(0, 0, 0, 0.4) 0%,
|
|
223
|
+
rgba(0, 0, 0, 0.3) 70%,
|
|
224
|
+
rgba(255, 255, 255, 0.02) 85%,
|
|
225
|
+
rgba(255, 255, 255, 0.05) 100%);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Status badges */
|
|
229
|
+
.status-badge {
|
|
230
|
+
padding: 0.25rem 0.75rem;
|
|
231
|
+
border-radius: 20px;
|
|
232
|
+
font-size: 0.75rem;
|
|
233
|
+
font-weight: 600;
|
|
234
|
+
text-transform: uppercase;
|
|
235
|
+
letter-spacing: 0.05em;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* New 4-state system with improved colors */
|
|
239
|
+
.status-draft { background: rgba(156, 163, 175, 0.2); color: #9ca3af; border: 1px solid rgba(156, 163, 175, 0.3); }
|
|
240
|
+
.status-ready { background: rgba(245, 158, 11, 0.2); color: #f59e0b; border: 1px solid rgba(245, 158, 11, 0.3); }
|
|
241
|
+
.status-engineered { background: rgba(59, 130, 246, 0.2); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.3); }
|
|
242
|
+
.status-done { background: rgba(16, 185, 129, 0.2); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.3); }
|
|
243
|
+
|
|
244
|
+
/* Legacy state mapping for backward compatibility */
|
|
245
|
+
.status-planning { background: rgba(245, 158, 11, 0.2); color: #f59e0b; border: 1px solid rgba(245, 158, 11, 0.3); }
|
|
246
|
+
.status-implementation { background: rgba(59, 130, 246, 0.2); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.3); }
|
|
247
|
+
.status-review { background: rgba(147, 51, 234, 0.2); color: #a855f7; border: 1px solid rgba(147, 51, 234, 0.3); }
|
|
248
|
+
.status-completed { background: rgba(16, 185, 129, 0.2); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.3); }
|
|
249
|
+
.status-in-progress { background: rgba(59, 130, 246, 0.2); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.3); }
|
|
250
|
+
|
|
251
|
+
/* Buttons */
|
|
252
|
+
.btn {
|
|
253
|
+
padding: 0.75rem 1.5rem;
|
|
254
|
+
border-radius: 12px;
|
|
255
|
+
font-family: var(--font-heading);
|
|
256
|
+
font-weight: 600;
|
|
257
|
+
font-size: 0.875rem;
|
|
258
|
+
border: none;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
transition: all 0.3s ease;
|
|
261
|
+
display: inline-flex;
|
|
262
|
+
align-items: center;
|
|
263
|
+
gap: 0.5rem;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.btn-primary {
|
|
267
|
+
background: var(--accent-blue);
|
|
268
|
+
color: white;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.btn-primary:hover {
|
|
272
|
+
transform: translateY(-2px);
|
|
273
|
+
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.4);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.btn-secondary {
|
|
277
|
+
background: var(--bg-glass);
|
|
278
|
+
backdrop-filter: blur(12px);
|
|
279
|
+
color: var(--text-primary);
|
|
280
|
+
border: 1px solid var(--border-glass);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.btn-secondary:hover {
|
|
284
|
+
background: var(--bg-glass-hover);
|
|
285
|
+
transform: translateY(-2px);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* Modal */
|
|
289
|
+
.modal-overlay {
|
|
290
|
+
position: fixed;
|
|
291
|
+
inset: 0;
|
|
292
|
+
background: rgba(0, 0, 0, 0.8);
|
|
293
|
+
backdrop-filter: blur(8px);
|
|
294
|
+
display: flex;
|
|
295
|
+
align-items: flex-start;
|
|
296
|
+
justify-content: center;
|
|
297
|
+
z-index: 100;
|
|
298
|
+
padding: 1rem;
|
|
299
|
+
padding-top: 5vh; /* Move up by 10% from center (50vh - 10% = 40vh, but flex-start means 5vh works) */
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.modal-content {
|
|
303
|
+
background: var(--bg-glass);
|
|
304
|
+
backdrop-filter: blur(20px);
|
|
305
|
+
border: 1px solid var(--border-glass);
|
|
306
|
+
border-radius: 24px;
|
|
307
|
+
width: 99vw;
|
|
308
|
+
height: 95vh;
|
|
309
|
+
max-width: none;
|
|
310
|
+
min-width: 1728px; /* Increased from 1440px by 20% (1440 * 1.2) */
|
|
311
|
+
min-height: 1000px;
|
|
312
|
+
display: grid;
|
|
313
|
+
grid-template-columns: 60% 40%;
|
|
314
|
+
grid-template-rows: auto 1fr;
|
|
315
|
+
overflow: hidden;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.modal-header {
|
|
319
|
+
grid-column: 1 / -1;
|
|
320
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(99, 102, 241, 0.1) 100%);
|
|
321
|
+
padding: 1.5rem 2rem;
|
|
322
|
+
border-bottom: 1px solid var(--border-glass);
|
|
323
|
+
display: flex;
|
|
324
|
+
justify-content: space-between;
|
|
325
|
+
align-items: center;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.modal-title-section {
|
|
329
|
+
display: flex;
|
|
330
|
+
align-items: center;
|
|
331
|
+
gap: 1rem;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.worktree-info {
|
|
335
|
+
background: rgba(255, 255, 255, 0.1);
|
|
336
|
+
padding: 0.5rem 1rem;
|
|
337
|
+
border-radius: 20px;
|
|
338
|
+
font-size: 0.875rem;
|
|
339
|
+
display: flex;
|
|
340
|
+
align-items: center;
|
|
341
|
+
gap: 0.5rem;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.modal-main-container {
|
|
345
|
+
display: flex;
|
|
346
|
+
flex-direction: column;
|
|
347
|
+
overflow: hidden;
|
|
348
|
+
height: 100%; /* Ensure full height usage */
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.modal-nav {
|
|
352
|
+
padding: 1rem 2rem 0;
|
|
353
|
+
border-bottom: 1px solid var(--border-glass);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.nav-tabs {
|
|
357
|
+
display: flex;
|
|
358
|
+
gap: 0.5rem;
|
|
359
|
+
margin-bottom: 1rem;
|
|
360
|
+
overflow-x: auto;
|
|
361
|
+
scrollbar-width: thin;
|
|
362
|
+
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
|
363
|
+
padding-bottom: 0.5rem;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.nav-tabs::-webkit-scrollbar {
|
|
367
|
+
height: 4px;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.nav-tabs::-webkit-scrollbar-track {
|
|
371
|
+
background: rgba(255, 255, 255, 0.1);
|
|
372
|
+
border-radius: 2px;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.nav-tabs::-webkit-scrollbar-thumb {
|
|
376
|
+
background: rgba(255, 255, 255, 0.3);
|
|
377
|
+
border-radius: 2px;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.nav-tabs::-webkit-scrollbar-thumb:hover {
|
|
381
|
+
background: rgba(255, 255, 255, 0.5);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.nav-tab {
|
|
385
|
+
padding: 0.5rem 1rem;
|
|
386
|
+
border-radius: 20px;
|
|
387
|
+
background: transparent;
|
|
388
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
389
|
+
color: var(--text-secondary);
|
|
390
|
+
font-family: var(--font-body);
|
|
391
|
+
font-size: 0.875rem;
|
|
392
|
+
cursor: pointer;
|
|
393
|
+
transition: all 0.2s ease;
|
|
394
|
+
white-space: nowrap;
|
|
395
|
+
flex-shrink: 0;
|
|
396
|
+
min-width: fit-content;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.nav-tab:hover {
|
|
400
|
+
background: rgba(255, 255, 255, 0.05);
|
|
401
|
+
color: var(--text-primary);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.nav-tab.active {
|
|
405
|
+
background: var(--accent-blue);
|
|
406
|
+
color: white;
|
|
407
|
+
border-color: var(--accent-blue);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.nav-tab.keyboard-focus {
|
|
411
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4);
|
|
412
|
+
outline: none;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.modal-main {
|
|
416
|
+
padding: 2rem;
|
|
417
|
+
overflow-y: auto;
|
|
418
|
+
flex: 1; /* Take all available space */
|
|
419
|
+
min-height: 0; /* Allow flex shrinking */
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.spec-content {
|
|
423
|
+
background: rgba(255, 255, 255, 0.03);
|
|
424
|
+
padding: 2.5rem 3.5rem 2.5rem 3.5rem; /* Increased left padding for better breathing room */
|
|
425
|
+
border-radius: 12px;
|
|
426
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
427
|
+
font-family: var(--font-body);
|
|
428
|
+
line-height: 1.7; /* Improved line height for better readability */
|
|
429
|
+
overflow-y: auto;
|
|
430
|
+
height: 100%; /* Use full available height */
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.spec-content h1, .spec-content h2, .spec-content h3 {
|
|
434
|
+
color: var(--text-primary);
|
|
435
|
+
margin-top: 2.5rem; /* Increased top margin */
|
|
436
|
+
margin-bottom: 1.5rem; /* Increased bottom margin */
|
|
437
|
+
font-family: var(--font-heading);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.spec-content h1:first-child,
|
|
441
|
+
.spec-content h2:first-child,
|
|
442
|
+
.spec-content h3:first-child {
|
|
443
|
+
margin-top: 0; /* Remove top margin from first heading */
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.spec-content h1 { font-size: 1.5rem; }
|
|
447
|
+
.spec-content h2 { font-size: 1.3rem; }
|
|
448
|
+
.spec-content h3 { font-size: 1.1rem; }
|
|
449
|
+
|
|
450
|
+
.spec-content p {
|
|
451
|
+
margin-bottom: 1.5rem; /* Increased paragraph spacing */
|
|
452
|
+
color: var(--text-secondary);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.spec-content ul, .spec-content ol {
|
|
456
|
+
margin-bottom: 1.5rem; /* Increased list spacing */
|
|
457
|
+
padding-left: 2rem; /* Better indentation */
|
|
458
|
+
color: var(--text-secondary);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.spec-content li {
|
|
462
|
+
margin-bottom: 0.75rem; /* Increased list item spacing */
|
|
463
|
+
line-height: 1.6;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.spec-content code {
|
|
467
|
+
background: rgba(255, 255, 255, 0.1);
|
|
468
|
+
padding: 0.2rem 0.4rem;
|
|
469
|
+
border-radius: 4px;
|
|
470
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
471
|
+
font-size: 0.9em;
|
|
472
|
+
color: var(--accent-blue);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.spec-content pre {
|
|
476
|
+
background: rgba(0, 0, 0, 0.4);
|
|
477
|
+
padding: 1.5rem; /* Increased padding for code blocks */
|
|
478
|
+
border-radius: 8px;
|
|
479
|
+
overflow-x: auto;
|
|
480
|
+
margin: 2rem 0; /* Increased margin around code blocks */
|
|
481
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
482
|
+
line-height: 1.5;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.spec-content pre code {
|
|
486
|
+
background: none;
|
|
487
|
+
padding: 0;
|
|
488
|
+
color: var(--text-primary);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.spec-content blockquote {
|
|
492
|
+
border-left: 3px solid var(--accent-blue);
|
|
493
|
+
padding-left: 1rem;
|
|
494
|
+
margin: 1rem 0;
|
|
495
|
+
font-style: italic;
|
|
496
|
+
color: var(--text-secondary);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.spec-content table {
|
|
500
|
+
width: 100%;
|
|
501
|
+
border-collapse: collapse;
|
|
502
|
+
margin: 1rem 0;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.spec-content th, .spec-content td {
|
|
506
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
507
|
+
padding: 0.75rem;
|
|
508
|
+
text-align: left;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.spec-content th {
|
|
512
|
+
background: rgba(255, 255, 255, 0.05);
|
|
513
|
+
font-weight: 600;
|
|
514
|
+
color: var(--text-primary);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.spec-content a {
|
|
518
|
+
color: var(--accent-blue);
|
|
519
|
+
text-decoration: underline;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.spec-content a:hover {
|
|
523
|
+
color: #60a5fa;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.modal-activity {
|
|
527
|
+
border-left: 1px solid var(--border-glass);
|
|
528
|
+
overflow: hidden;
|
|
529
|
+
background: rgba(0, 0, 0, 0.1);
|
|
530
|
+
display: flex;
|
|
531
|
+
flex-direction: column;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.activity-nav {
|
|
535
|
+
padding: 1rem 1.5rem 0.5rem;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.activity-tabs {
|
|
539
|
+
display: flex;
|
|
540
|
+
gap: 0.5rem;
|
|
541
|
+
margin-bottom: 1rem;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.activity-tab {
|
|
545
|
+
padding: 0.5rem 1rem;
|
|
546
|
+
border-radius: 20px;
|
|
547
|
+
background: transparent;
|
|
548
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
549
|
+
color: var(--text-secondary);
|
|
550
|
+
font-family: var(--font-body);
|
|
551
|
+
font-size: 0.875rem;
|
|
552
|
+
cursor: pointer;
|
|
553
|
+
transition: all 0.2s ease;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.activity-tab:hover {
|
|
557
|
+
background: rgba(255, 255, 255, 0.05);
|
|
558
|
+
color: var(--text-primary);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.activity-tab.active {
|
|
562
|
+
background: var(--accent-blue);
|
|
563
|
+
color: white;
|
|
564
|
+
border-color: var(--accent-blue);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.activity-content {
|
|
568
|
+
overflow-y: auto;
|
|
569
|
+
min-height: 0; /* Allow flex shrinking */
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.sidebar-nav {
|
|
573
|
+
list-style: none;
|
|
574
|
+
padding: 0;
|
|
575
|
+
margin: 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.sidebar-nav li {
|
|
579
|
+
margin-bottom: 0.5rem;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.sidebar-nav button {
|
|
583
|
+
width: 100%;
|
|
584
|
+
text-align: left;
|
|
585
|
+
padding: 0.75rem;
|
|
586
|
+
border-radius: 8px;
|
|
587
|
+
background: transparent;
|
|
588
|
+
border: none;
|
|
589
|
+
color: var(--text-secondary);
|
|
590
|
+
font-family: var(--font-body);
|
|
591
|
+
cursor: pointer;
|
|
592
|
+
transition: all 0.2s ease;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.sidebar-nav button:hover {
|
|
596
|
+
background: rgba(255, 255, 255, 0.05);
|
|
597
|
+
color: var(--text-primary);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.sidebar-nav button.active {
|
|
601
|
+
background: var(--accent-blue);
|
|
602
|
+
color: white;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/* Timeline Activity Layout */
|
|
606
|
+
.timeline-container {
|
|
607
|
+
position: relative;
|
|
608
|
+
padding: 1rem 0;
|
|
609
|
+
min-height: 60vh;
|
|
610
|
+
height: 100%;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.timeline-line {
|
|
614
|
+
position: absolute;
|
|
615
|
+
left: 200px; /* Align with fixed left column width */
|
|
616
|
+
top: 0;
|
|
617
|
+
height: 100%; /* Dynamic height to extend through all content */
|
|
618
|
+
width: 2px;
|
|
619
|
+
background: linear-gradient(to bottom,
|
|
620
|
+
transparent 0%,
|
|
621
|
+
rgba(59, 130, 246, 0.3) 2%,
|
|
622
|
+
rgba(59, 130, 246, 0.6) 10%,
|
|
623
|
+
rgba(59, 130, 246, 0.6) 90%,
|
|
624
|
+
rgba(59, 130, 246, 0.3) 98%,
|
|
625
|
+
transparent 100%);
|
|
626
|
+
z-index: 1;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.timeline-item {
|
|
630
|
+
display: flex;
|
|
631
|
+
margin-bottom: 2.5rem; /* Increased spacing between timeline items */
|
|
632
|
+
position: relative;
|
|
633
|
+
align-items: flex-start; /* Better alignment */
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.timeline-left {
|
|
637
|
+
flex: 0 0 200px; /* Fixed width for better alignment */
|
|
638
|
+
text-align: right;
|
|
639
|
+
padding-right: 1.5rem;
|
|
640
|
+
display: flex;
|
|
641
|
+
flex-direction: column;
|
|
642
|
+
justify-content: center;
|
|
643
|
+
min-height: 60px; /* Ensure minimum height */
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.timeline-right {
|
|
647
|
+
flex: 1;
|
|
648
|
+
padding-left: 1.5rem;
|
|
649
|
+
min-width: 0; /* Allow content to shrink properly */
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.timeline-marker {
|
|
653
|
+
position: absolute;
|
|
654
|
+
left: 200px; /* Align with timeline line */
|
|
655
|
+
top: 50%;
|
|
656
|
+
transform: translate(-50%, -50%);
|
|
657
|
+
width: 12px;
|
|
658
|
+
height: 12px;
|
|
659
|
+
border-radius: 50%;
|
|
660
|
+
background: var(--accent-blue);
|
|
661
|
+
border: 3px solid var(--bg-primary);
|
|
662
|
+
box-shadow: 0 0 0 2px var(--accent-blue);
|
|
663
|
+
z-index: 2;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.timeline-marker.debugger { background: var(--accent-red); box-shadow: 0 0 0 2px var(--accent-red); }
|
|
667
|
+
.timeline-marker.engineer { background: var(--accent-green); box-shadow: 0 0 0 2px var(--accent-green); }
|
|
668
|
+
.timeline-marker.architect { background: var(--accent-orange); box-shadow: 0 0 0 2px var(--accent-orange); }
|
|
669
|
+
.timeline-marker.reviewer { background: var(--accent-purple); box-shadow: 0 0 0 2px var(--accent-purple); }
|
|
670
|
+
.timeline-marker.orchestrator { background: #6366f1; box-shadow: 0 0 0 2px #6366f1; }
|
|
671
|
+
.timeline-marker.system { background: var(--text-muted); box-shadow: 0 0 0 2px var(--text-muted); }
|
|
672
|
+
|
|
673
|
+
/* Role-based status badge colors */
|
|
674
|
+
.status-badge.role-system { background: rgba(156, 163, 175, 0.2); color: var(--text-muted); }
|
|
675
|
+
.status-badge.role-engineer { background: rgba(16, 185, 129, 0.2); color: var(--accent-green); }
|
|
676
|
+
.status-badge.role-architect { background: rgba(245, 158, 11, 0.2); color: var(--accent-orange); }
|
|
677
|
+
.status-badge.role-debugger { background: rgba(239, 68, 68, 0.2); color: var(--accent-red); }
|
|
678
|
+
.status-badge.role-reviewer { background: rgba(139, 92, 246, 0.2); color: var(--accent-purple); }
|
|
679
|
+
.status-badge.role-orchestrator { background: rgba(99, 102, 241, 0.2); color: #6366f1; }
|
|
680
|
+
|
|
681
|
+
.timeline-time {
|
|
682
|
+
font-size: 0.8rem; /* Slightly larger font */
|
|
683
|
+
color: var(--text-muted);
|
|
684
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
685
|
+
margin-bottom: 0.75rem; /* More space between time and role */
|
|
686
|
+
line-height: 1.4;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.timeline-role {
|
|
690
|
+
font-weight: 700; /* Bold headings only */
|
|
691
|
+
color: var(--text-primary);
|
|
692
|
+
margin-bottom: 0.5rem; /* More space after role */
|
|
693
|
+
font-size: 0.9rem;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.timeline-content {
|
|
697
|
+
background: rgba(255, 255, 255, 0.03);
|
|
698
|
+
border-radius: 12px;
|
|
699
|
+
padding: 1rem 1.25rem;
|
|
700
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
701
|
+
backdrop-filter: blur(12px);
|
|
702
|
+
transition: all 0.2s ease;
|
|
703
|
+
margin-bottom: 0.5rem;
|
|
704
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.timeline-content:hover {
|
|
708
|
+
background: rgba(255, 255, 255, 0.06);
|
|
709
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
710
|
+
transform: translateY(-1px);
|
|
711
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.activity-role {
|
|
715
|
+
font-weight: 600;
|
|
716
|
+
color: var(--text-primary);
|
|
717
|
+
margin-bottom: 0.25rem;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.activity-note {
|
|
721
|
+
font-size: 0.9rem; /* Slightly larger for better readability */
|
|
722
|
+
color: var(--text-secondary);
|
|
723
|
+
line-height: 1.5;
|
|
724
|
+
margin-bottom: 0.5rem;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/* Compliance Status Design System */
|
|
728
|
+
.compliance-compliant {
|
|
729
|
+
background: rgba(34, 197, 94, 0.1);
|
|
730
|
+
border-color: rgba(34, 197, 94, 0.3);
|
|
731
|
+
color: #22c55e;
|
|
732
|
+
}
|
|
733
|
+
.compliance-deviation {
|
|
734
|
+
background: rgba(245, 158, 11, 0.1);
|
|
735
|
+
border-color: rgba(245, 158, 11, 0.3);
|
|
736
|
+
color: #f59e0b;
|
|
737
|
+
}
|
|
738
|
+
.compliance-unexpected {
|
|
739
|
+
background: rgba(249, 115, 22, 0.1);
|
|
740
|
+
border-color: rgba(249, 115, 22, 0.3);
|
|
741
|
+
color: #f97316;
|
|
742
|
+
}
|
|
743
|
+
.compliance-unknown {
|
|
744
|
+
background: rgba(107, 114, 128, 0.1);
|
|
745
|
+
border-color: rgba(107, 114, 128, 0.3);
|
|
746
|
+
color: #6b7280;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/* Compliance Badge */
|
|
750
|
+
.compliance-badge {
|
|
751
|
+
display: inline-flex;
|
|
752
|
+
align-items: center;
|
|
753
|
+
gap: 0.25rem;
|
|
754
|
+
padding: 0.125rem 0.5rem;
|
|
755
|
+
border-radius: 12px;
|
|
756
|
+
font-size: 0.75rem;
|
|
757
|
+
font-weight: 500;
|
|
758
|
+
border: 1px solid;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* Enhanced File Change Cards */
|
|
762
|
+
.file-change-card {
|
|
763
|
+
background: rgba(255, 255, 255, 0.02);
|
|
764
|
+
border-radius: 8px;
|
|
765
|
+
padding: 1rem;
|
|
766
|
+
margin-bottom: 0.75rem;
|
|
767
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
768
|
+
transition: all 0.2s ease;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.file-change-card:hover {
|
|
772
|
+
background: rgba(255, 255, 255, 0.04);
|
|
773
|
+
transform: translateY(-1px);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.file-header {
|
|
777
|
+
display: flex;
|
|
778
|
+
align-items: center;
|
|
779
|
+
gap: 0.5rem;
|
|
780
|
+
margin-bottom: 0.5rem;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.file-icon {
|
|
784
|
+
font-size: 1rem;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.file-name {
|
|
788
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
789
|
+
font-size: 0.85rem;
|
|
790
|
+
color: var(--text-primary);
|
|
791
|
+
flex: 1;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
.file-description {
|
|
795
|
+
color: var(--text-secondary);
|
|
796
|
+
font-size: 0.85rem;
|
|
797
|
+
margin-bottom: 0.5rem;
|
|
798
|
+
line-height: 1.4;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
.file-meta {
|
|
802
|
+
display: flex;
|
|
803
|
+
align-items: center;
|
|
804
|
+
gap: 1rem;
|
|
805
|
+
font-size: 0.75rem;
|
|
806
|
+
color: var(--text-muted);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.change-type {
|
|
810
|
+
padding: 0.125rem 0.5rem;
|
|
811
|
+
border-radius: 12px;
|
|
812
|
+
background: rgba(99, 102, 241, 0.1);
|
|
813
|
+
color: #818cf8;
|
|
814
|
+
border: 1px solid rgba(99, 102, 241, 0.2);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
.lines-changed {
|
|
818
|
+
color: var(--text-secondary);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/* Responsive design */
|
|
822
|
+
@media (max-width: 768px) {
|
|
823
|
+
.metrics-grid {
|
|
824
|
+
grid-template-columns: 1fr;
|
|
825
|
+
padding: 1rem;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.spec-container {
|
|
829
|
+
margin: 1rem;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.modal-content {
|
|
833
|
+
grid-template-columns: 1fr;
|
|
834
|
+
grid-template-rows: auto auto 1fr auto;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.modal-sidebar,
|
|
838
|
+
.modal-activity {
|
|
839
|
+
border: none;
|
|
840
|
+
border-top: 1px solid var(--border-glass);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/* Loading animation */
|
|
845
|
+
.loading {
|
|
846
|
+
display: inline-block;
|
|
847
|
+
width: 20px;
|
|
848
|
+
height: 20px;
|
|
849
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
850
|
+
border-radius: 50%;
|
|
851
|
+
border-top-color: #ffffff;
|
|
852
|
+
animation: spin 1s ease-in-out infinite;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
@keyframes spin {
|
|
856
|
+
to { transform: rotate(360deg); }
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/* Fade in animation */
|
|
860
|
+
.fade-in {
|
|
861
|
+
animation: fadeIn 0.5s ease-out;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
@keyframes fadeIn {
|
|
865
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
866
|
+
to { opacity: 1; transform: translateY(0); }
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/* Status Banner */
|
|
870
|
+
.status-banner {
|
|
871
|
+
background: var(--bg-glass);
|
|
872
|
+
backdrop-filter: blur(12px);
|
|
873
|
+
border: 1px solid var(--border-glass);
|
|
874
|
+
border-left: 4px solid var(--accent-orange);
|
|
875
|
+
border-radius: 16px;
|
|
876
|
+
margin: 1.5rem 2rem;
|
|
877
|
+
padding: 1.5rem 2rem;
|
|
878
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.status-content {
|
|
882
|
+
display: grid;
|
|
883
|
+
grid-template-columns: 1fr auto 1fr;
|
|
884
|
+
align-items: center;
|
|
885
|
+
gap: 1rem;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
.status-left {
|
|
889
|
+
display: flex;
|
|
890
|
+
align-items: center;
|
|
891
|
+
gap: 0.75rem;
|
|
892
|
+
font-size: 0.95rem;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.workshop-icon {
|
|
896
|
+
font-size: 1.2rem;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
.workshop-name {
|
|
900
|
+
font-weight: 600;
|
|
901
|
+
color: var(--text-primary);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.status-separator {
|
|
905
|
+
color: var(--text-muted);
|
|
906
|
+
margin: 0 0.25rem;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
.build-status .building {
|
|
910
|
+
color: var(--accent-yellow);
|
|
911
|
+
display: flex;
|
|
912
|
+
align-items: center;
|
|
913
|
+
gap: 0.5rem;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
.build-status .idle {
|
|
917
|
+
color: var(--text-secondary);
|
|
918
|
+
display: flex;
|
|
919
|
+
align-items: center;
|
|
920
|
+
gap: 0.5rem;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.building-icon {
|
|
924
|
+
animation: spin 2s linear infinite;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.status-right {
|
|
928
|
+
font-size: 0.9rem;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.worktree-stats {
|
|
932
|
+
display: flex;
|
|
933
|
+
align-items: center;
|
|
934
|
+
gap: 0.5rem;
|
|
935
|
+
flex-wrap: wrap;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.stat-label {
|
|
939
|
+
color: var(--text-secondary);
|
|
940
|
+
font-weight: 600;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.stat-value {
|
|
944
|
+
color: var(--text-primary);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.stat-value.draft {
|
|
948
|
+
color: #9ca3af;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.stat-value.in-progress {
|
|
952
|
+
color: var(--accent-yellow);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
.stat-value.done {
|
|
956
|
+
color: var(--accent-green);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
.stat-value.committed {
|
|
960
|
+
color: var(--accent-green);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
.stat-value.dirty {
|
|
964
|
+
color: var(--accent-yellow);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
.stat-value.clean {
|
|
968
|
+
color: var(--text-secondary);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/* Responsive adjustments for status banner */
|
|
972
|
+
@media (max-width: 768px) {
|
|
973
|
+
.status-banner {
|
|
974
|
+
margin: 1rem;
|
|
975
|
+
padding: 1rem;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.status-content {
|
|
979
|
+
flex-direction: column;
|
|
980
|
+
align-items: flex-start;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
.worktree-stats {
|
|
984
|
+
font-size: 0.8rem;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
.btn {
|
|
990
|
+
padding: 0.5rem 1rem;
|
|
991
|
+
border-radius: 8px;
|
|
992
|
+
font-size: 0.85rem;
|
|
993
|
+
font-weight: 500;
|
|
994
|
+
cursor: pointer;
|
|
995
|
+
transition: all 0.2s ease;
|
|
996
|
+
border: 1px solid transparent;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.btn-primary {
|
|
1000
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.9) 0%, rgba(59, 130, 246, 1) 100%);
|
|
1001
|
+
border: 1px solid rgba(59, 130, 246, 1);
|
|
1002
|
+
color: white;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.btn-primary:hover {
|
|
1006
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 1) 0%, rgba(79, 150, 255, 1) 100%);
|
|
1007
|
+
transform: translateY(-1px);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
.btn-secondary {
|
|
1011
|
+
background: rgba(34, 197, 94, 0.1);
|
|
1012
|
+
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
1013
|
+
color: rgba(34, 197, 94, 1);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.btn-secondary:hover {
|
|
1017
|
+
background: rgba(34, 197, 94, 0.2);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.btn-danger {
|
|
1021
|
+
background: rgba(239, 68, 68, 0.1);
|
|
1022
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
1023
|
+
color: rgba(239, 68, 68, 1);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
.btn-danger:hover {
|
|
1027
|
+
background: rgba(239, 68, 68, 0.2);
|
|
1028
|
+
}
|
|
1029
|
+
</style>
|
|
1030
|
+
</head>
|
|
1031
|
+
|
|
1032
|
+
<script>
|
|
1033
|
+
document.addEventListener('alpine:init', () => {
|
|
1034
|
+
Alpine.data('dashboard', () => ({
|
|
1035
|
+
selectedSpec: null,
|
|
1036
|
+
section: 'summary',
|
|
1037
|
+
activityTab: 'activity',
|
|
1038
|
+
specs: [],
|
|
1039
|
+
filteredSpecs: [],
|
|
1040
|
+
loading: false,
|
|
1041
|
+
searchTerm: '',
|
|
1042
|
+
categoryFilter: 'all',
|
|
1043
|
+
statusFilter: 'all',
|
|
1044
|
+
showActivityLog: false,
|
|
1045
|
+
showWorktreeModal: false,
|
|
1046
|
+
activities: [],
|
|
1047
|
+
|
|
1048
|
+
// Main prompt interface
|
|
1049
|
+
mainPrompt: '',
|
|
1050
|
+
selectedTemplate: '',
|
|
1051
|
+
uploadedImages: [], // Array of {id, filename, path, placeholder}
|
|
1052
|
+
imageCounter: 0,
|
|
1053
|
+
|
|
1054
|
+
// Toast notifications
|
|
1055
|
+
toasts: [],
|
|
1056
|
+
sortBy: 'modified',
|
|
1057
|
+
sortDirection: 'desc',
|
|
1058
|
+
confirmDelete: null,
|
|
1059
|
+
|
|
1060
|
+
// Button state tracking for copy buttons
|
|
1061
|
+
buttonStates: {},
|
|
1062
|
+
|
|
1063
|
+
// Keyboard navigation
|
|
1064
|
+
selectedRowIndex: 0,
|
|
1065
|
+
keyboardMode: false,
|
|
1066
|
+
|
|
1067
|
+
// Section navigation
|
|
1068
|
+
sectionKeyboardMode: false,
|
|
1069
|
+
hasContent(section) {
|
|
1070
|
+
try {
|
|
1071
|
+
// Special handling for log sections (stored as arrays, not in sections object)
|
|
1072
|
+
if (section === 'execution_logs' || section === 'execution_log') {
|
|
1073
|
+
const logs = this.selectedSpec?.execution_logs || this.selectedSpec?.execution_log || [];
|
|
1074
|
+
return Array.isArray(logs) && logs.length > 0;
|
|
1075
|
+
}
|
|
1076
|
+
if (section === 'debug_logs' || section === 'debug_log') {
|
|
1077
|
+
const logs = this.selectedSpec?.debug_logs || this.selectedSpec?.debug_log || [];
|
|
1078
|
+
return Array.isArray(logs) && logs.length > 0;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// Regular section content check
|
|
1082
|
+
if (!this.selectedSpec?.sections) return false;
|
|
1083
|
+
const sectionData = this.selectedSpec.sections[section];
|
|
1084
|
+
return sectionData && sectionData.content && sectionData.content.trim() !== '';
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
console.warn('Error checking content for section:', section, error);
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
getAvailableSections() {
|
|
1091
|
+
try {
|
|
1092
|
+
if (!this.selectedSpec?.sections) return [
|
|
1093
|
+
{ id: 'summary', label: 'Summary', key: 'executive_summary' }
|
|
1094
|
+
];
|
|
1095
|
+
const sections = [
|
|
1096
|
+
{ id: 'summary', label: 'Summary', key: 'executive_summary' },
|
|
1097
|
+
{ id: 'product', label: 'Product', key: 'product_specifications' },
|
|
1098
|
+
{ id: 'architecture', label: 'Architecture', key: 'architecture_analysis' },
|
|
1099
|
+
{ id: 'implementation', label: 'Implementation', key: 'implementation_plan' },
|
|
1100
|
+
{ id: 'research', label: 'Research', key: 'research' },
|
|
1101
|
+
{ id: 'testing', label: 'Testing', key: 'testing' },
|
|
1102
|
+
{ id: 'execution', label: '🛠️ Execution Logs', key: 'execution_logs' },
|
|
1103
|
+
{ id: 'debug', label: '🐛 Debug Logs', key: 'debug_logs' },
|
|
1104
|
+
{ id: 'review', label: 'Review', key: 'review' }
|
|
1105
|
+
];
|
|
1106
|
+
return sections.filter(section => this.hasContent(section.key));
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
console.warn('Error getting available sections:', error);
|
|
1109
|
+
return [{ id: 'summary', label: 'Summary', key: 'executive_summary' }];
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
async fetchSpecs() {
|
|
1113
|
+
this.loading = true;
|
|
1114
|
+
try {
|
|
1115
|
+
const response = await fetch('/api/tools/bob.spec.list');
|
|
1116
|
+
const data = await response.json();
|
|
1117
|
+
this.specs = data.specs || [];
|
|
1118
|
+
this.filterSpecs();
|
|
1119
|
+
// Initialize real-time events after first load
|
|
1120
|
+
this.initRealTimeEvents();
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
console.error('Failed to fetch specs:', error);
|
|
1123
|
+
} finally {
|
|
1124
|
+
this.loading = false;
|
|
1125
|
+
}
|
|
1126
|
+
},
|
|
1127
|
+
initRealTimeEvents() {
|
|
1128
|
+
if (this.eventSource) return; // Already initialized
|
|
1129
|
+
|
|
1130
|
+
// Real-time event streaming
|
|
1131
|
+
this.eventSource = new EventSource('/api/events/stream');
|
|
1132
|
+
this.activeOperations = new Map();
|
|
1133
|
+
|
|
1134
|
+
this.eventSource.onmessage = (event) => {
|
|
1135
|
+
const operation = JSON.parse(event.data);
|
|
1136
|
+
|
|
1137
|
+
if (operation.type === 'connected') return;
|
|
1138
|
+
|
|
1139
|
+
// Update timeline card with sub-events
|
|
1140
|
+
this.updateTimelineCardWithSubEvents(operation);
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
this.eventSource.onerror = (error) => {
|
|
1144
|
+
console.warn('SSE connection error:', error);
|
|
1145
|
+
// Reconnect after 5 seconds
|
|
1146
|
+
setTimeout(() => {
|
|
1147
|
+
this.eventSource.close();
|
|
1148
|
+
this.eventSource = null;
|
|
1149
|
+
this.initRealTimeEvents();
|
|
1150
|
+
}, 5000);
|
|
1151
|
+
};
|
|
1152
|
+
},
|
|
1153
|
+
updateTimelineCardWithSubEvents(operation) {
|
|
1154
|
+
// Find or create timeline card for this operation
|
|
1155
|
+
const existingActivity = this.activities.find(activity =>
|
|
1156
|
+
activity.spec_id === operation.spec_id &&
|
|
1157
|
+
activity.operation_id === operation.id
|
|
1158
|
+
);
|
|
1159
|
+
|
|
1160
|
+
if (existingActivity) {
|
|
1161
|
+
// Update existing activity with sub-events
|
|
1162
|
+
existingActivity.sub_events = operation.sub_events || [];
|
|
1163
|
+
existingActivity.operation = operation.operation;
|
|
1164
|
+
} else if (operation.type !== 'tool_call') {
|
|
1165
|
+
// Add new activity for non-tool-call operations (role transitions, etc.)
|
|
1166
|
+
this.activities.unshift({
|
|
1167
|
+
timestamp: operation.timestamp,
|
|
1168
|
+
spec_id: operation.spec_id,
|
|
1169
|
+
operation_id: operation.id,
|
|
1170
|
+
operation: operation.operation,
|
|
1171
|
+
type: operation.type,
|
|
1172
|
+
sub_events: operation.sub_events || [],
|
|
1173
|
+
role: this.getRoleFromOperationType(operation.type)
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
// Keep only last 50 activities
|
|
1177
|
+
if (this.activities.length > 50) {
|
|
1178
|
+
this.activities = this.activities.slice(0, 50);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
},
|
|
1182
|
+
getRoleFromOperationType(type) {
|
|
1183
|
+
const roleMap = {
|
|
1184
|
+
'role_transition': 'orchestrator',
|
|
1185
|
+
'engineer_session': 'engineer',
|
|
1186
|
+
'debug_session': 'debugger',
|
|
1187
|
+
'review_session': 'reviewer',
|
|
1188
|
+
'tool_call': 'system'
|
|
1189
|
+
};
|
|
1190
|
+
return roleMap[type] || 'system';
|
|
1191
|
+
},
|
|
1192
|
+
sortSpecs(column) {
|
|
1193
|
+
if (this.sortBy === column) {
|
|
1194
|
+
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
|
1195
|
+
} else {
|
|
1196
|
+
this.sortBy = column;
|
|
1197
|
+
this.sortDirection = column === 'created_at' ? 'desc' : 'asc';
|
|
1198
|
+
}
|
|
1199
|
+
this.filterSpecs();
|
|
1200
|
+
},
|
|
1201
|
+
filterSpecs() {
|
|
1202
|
+
this.filteredSpecs = this.specs.filter(spec => {
|
|
1203
|
+
const matchesSearch = this.searchTerm === '' ||
|
|
1204
|
+
spec.title.toLowerCase().includes(this.searchTerm.toLowerCase());
|
|
1205
|
+
const matchesCategory = this.categoryFilter === 'all' ||
|
|
1206
|
+
spec.category === this.categoryFilter;
|
|
1207
|
+
const matchesStatus = this.statusFilter === 'all' ||
|
|
1208
|
+
spec.state === this.statusFilter;
|
|
1209
|
+
return matchesSearch && matchesCategory && matchesStatus;
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
// Sort filtered results
|
|
1213
|
+
this.filteredSpecs.sort((a, b) => {
|
|
1214
|
+
let valueA, valueB;
|
|
1215
|
+
|
|
1216
|
+
switch (this.sortBy) {
|
|
1217
|
+
case 'title':
|
|
1218
|
+
valueA = a.title?.toLowerCase() || '';
|
|
1219
|
+
valueB = b.title?.toLowerCase() || '';
|
|
1220
|
+
break;
|
|
1221
|
+
case 'category':
|
|
1222
|
+
valueA = a.category?.toLowerCase() || '';
|
|
1223
|
+
valueB = b.category?.toLowerCase() || '';
|
|
1224
|
+
break;
|
|
1225
|
+
case 'state':
|
|
1226
|
+
valueA = a.state?.toLowerCase() || '';
|
|
1227
|
+
valueB = b.state?.toLowerCase() || '';
|
|
1228
|
+
break;
|
|
1229
|
+
case 'created_at':
|
|
1230
|
+
valueA = new Date(a.created_at || '').getTime();
|
|
1231
|
+
valueB = new Date(b.created_at || '').getTime();
|
|
1232
|
+
break;
|
|
1233
|
+
case 'modified':
|
|
1234
|
+
valueA = new Date(a.updated_at || a.created_at || '').getTime();
|
|
1235
|
+
valueB = new Date(b.updated_at || b.created_at || '').getTime();
|
|
1236
|
+
break;
|
|
1237
|
+
default:
|
|
1238
|
+
return 0;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if (this.sortBy === 'created_at' || this.sortBy === 'modified') {
|
|
1242
|
+
return this.sortDirection === 'desc' ? valueB - valueA : valueA - valueB;
|
|
1243
|
+
} else {
|
|
1244
|
+
if (valueA < valueB) return this.sortDirection === 'desc' ? 1 : -1;
|
|
1245
|
+
if (valueA > valueB) return this.sortDirection === 'desc' ? -1 : 1;
|
|
1246
|
+
return 0;
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
},
|
|
1250
|
+
confirmDeleteSpec(specId, title) {
|
|
1251
|
+
console.log('Confirming delete for:', specId, title);
|
|
1252
|
+
this.confirmDelete = { id: specId, title: title };
|
|
1253
|
+
},
|
|
1254
|
+
cancelDelete() {
|
|
1255
|
+
this.confirmDelete = null;
|
|
1256
|
+
},
|
|
1257
|
+
async deleteSpec() {
|
|
1258
|
+
if (!this.confirmDelete) {
|
|
1259
|
+
console.error('No confirmDelete object found');
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const specId = this.confirmDelete.id;
|
|
1264
|
+
const title = this.confirmDelete.title;
|
|
1265
|
+
|
|
1266
|
+
console.log('Deleting spec:', specId, title);
|
|
1267
|
+
|
|
1268
|
+
try {
|
|
1269
|
+
const response = await fetch('/api/tools/bob.spec.delete', {
|
|
1270
|
+
method: 'POST',
|
|
1271
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1272
|
+
body: JSON.stringify({ spec_id: specId })
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
const result = await response.json();
|
|
1276
|
+
console.log('Delete response:', result);
|
|
1277
|
+
|
|
1278
|
+
if (response.ok && (result.status === 'deleted' || result.success)) {
|
|
1279
|
+
// Remove from local arrays
|
|
1280
|
+
this.specs = this.specs.filter(spec => spec.spec_id !== specId);
|
|
1281
|
+
this.filteredSpecs = this.filteredSpecs.filter(spec => spec.spec_id !== specId);
|
|
1282
|
+
this.confirmDelete = null; // Close the confirmation modal
|
|
1283
|
+
this.showToast(`✅ Manual "${title}" deleted successfully`, 'success');
|
|
1284
|
+
} else {
|
|
1285
|
+
console.error('Delete failed:', result);
|
|
1286
|
+
this.showToast(`❌ Failed to delete manual: ${result.error || result.message || 'Unknown error'}`, 'error');
|
|
1287
|
+
}
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
console.error('Error deleting spec:', error);
|
|
1290
|
+
this.showToast('❌ Error deleting manual. Please try again.', 'error');
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
async openSpec(specId) {
|
|
1294
|
+
try {
|
|
1295
|
+
const response = await fetch('/api/tools/bob.spec.get', {
|
|
1296
|
+
method: 'POST',
|
|
1297
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1298
|
+
body: JSON.stringify({ spec_id: specId })
|
|
1299
|
+
});
|
|
1300
|
+
const spec = await response.json();
|
|
1301
|
+
this.selectedSpec = spec;
|
|
1302
|
+
// Set to first available section with content
|
|
1303
|
+
const availableSections = this.getAvailableSections();
|
|
1304
|
+
this.section = availableSections.length > 0 ? availableSections[0].id : 'summary';
|
|
1305
|
+
} catch (error) {
|
|
1306
|
+
console.error('Failed to fetch spec:', error);
|
|
1307
|
+
alert('Failed to load MANUAL details');
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1310
|
+
async exportToMarkdown(specId) {
|
|
1311
|
+
try {
|
|
1312
|
+
const response = await fetch('/api/tools/bob.spec.export', {
|
|
1313
|
+
method: 'POST',
|
|
1314
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1315
|
+
body: JSON.stringify({ spec_id: specId })
|
|
1316
|
+
});
|
|
1317
|
+
const data = await response.json();
|
|
1318
|
+
if (data.markdown_path) {
|
|
1319
|
+
alert(`✅ MANUAL exported to: ${data.markdown_path}`);
|
|
1320
|
+
} else {
|
|
1321
|
+
alert('❌ Export failed');
|
|
1322
|
+
}
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
console.error('Export error:', error);
|
|
1325
|
+
alert('❌ Export failed');
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
getWorktreeName(spec) {
|
|
1329
|
+
// Generate worktree name from spec title
|
|
1330
|
+
if (!spec || !spec.title) return 'none';
|
|
1331
|
+
return 'feature/' + spec.title.toLowerCase()
|
|
1332
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
1333
|
+
.replace(/\s+/g, '-')
|
|
1334
|
+
.substring(0, 20);
|
|
1335
|
+
},
|
|
1336
|
+
renderMarkdown(text) {
|
|
1337
|
+
if (!text) return '';
|
|
1338
|
+
|
|
1339
|
+
// Escape HTML first
|
|
1340
|
+
text = text.replace(/&/g, '&')
|
|
1341
|
+
.replace(/</g, '<')
|
|
1342
|
+
.replace(/>/g, '>');
|
|
1343
|
+
|
|
1344
|
+
// Handle language-specific code blocks with copy buttons
|
|
1345
|
+
text = text.replace(/```(\w+)?\n?([\s\S]*?)\n?```/g, (match, language, code) => {
|
|
1346
|
+
const cleanCode = code.trim();
|
|
1347
|
+
const copyId = 'copy_' + Math.random().toString(36).substr(2, 9);
|
|
1348
|
+
const lang = language || 'code';
|
|
1349
|
+
|
|
1350
|
+
// Special handling for JSON - format it nicely
|
|
1351
|
+
let formattedCode = cleanCode;
|
|
1352
|
+
if (lang === 'json') {
|
|
1353
|
+
try {
|
|
1354
|
+
const parsed = JSON.parse(cleanCode);
|
|
1355
|
+
formattedCode = JSON.stringify(parsed, null, 2);
|
|
1356
|
+
} catch (e) {
|
|
1357
|
+
// If JSON parsing fails, use original code
|
|
1358
|
+
formattedCode = cleanCode;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// Language display names
|
|
1363
|
+
const languageNames = {
|
|
1364
|
+
'js': 'JavaScript',
|
|
1365
|
+
'javascript': 'JavaScript',
|
|
1366
|
+
'ts': 'TypeScript',
|
|
1367
|
+
'typescript': 'TypeScript',
|
|
1368
|
+
'json': 'JSON',
|
|
1369
|
+
'bash': 'Bash',
|
|
1370
|
+
'sh': 'Shell',
|
|
1371
|
+
'python': 'Python',
|
|
1372
|
+
'py': 'Python',
|
|
1373
|
+
'html': 'HTML',
|
|
1374
|
+
'css': 'CSS',
|
|
1375
|
+
'yaml': 'YAML',
|
|
1376
|
+
'yml': 'YAML',
|
|
1377
|
+
'xml': 'XML',
|
|
1378
|
+
'sql': 'SQL',
|
|
1379
|
+
'java': 'Java',
|
|
1380
|
+
'go': 'Go',
|
|
1381
|
+
'rust': 'Rust',
|
|
1382
|
+
'cpp': 'C++',
|
|
1383
|
+
'c': 'C',
|
|
1384
|
+
'php': 'PHP',
|
|
1385
|
+
'ruby': 'Ruby',
|
|
1386
|
+
'swift': 'Swift',
|
|
1387
|
+
'kotlin': 'Kotlin',
|
|
1388
|
+
'dart': 'Dart',
|
|
1389
|
+
'scala': 'Scala',
|
|
1390
|
+
'r': 'R',
|
|
1391
|
+
'matlab': 'MATLAB'
|
|
1392
|
+
};
|
|
1393
|
+
|
|
1394
|
+
const displayName = languageNames[lang.toLowerCase()] || lang.charAt(0).toUpperCase() + lang.slice(1);
|
|
1395
|
+
|
|
1396
|
+
return `<div class="code-block-container">
|
|
1397
|
+
<div class="code-block-header">
|
|
1398
|
+
<span class="code-lang">${displayName}</span>
|
|
1399
|
+
<button class="copy-code-btn" onclick="copyToClipboard('${formattedCode.replace(/'/g, "\\'")}', '${copyId}')" id="${copyId}">📋 Copy</button>
|
|
1400
|
+
</div>
|
|
1401
|
+
<pre><code>${formattedCode}</code></pre>
|
|
1402
|
+
</div>`;
|
|
1403
|
+
});
|
|
1404
|
+
|
|
1405
|
+
// Protect code block content from further processing
|
|
1406
|
+
const codeBlockPlaceholders = [];
|
|
1407
|
+
text = text.replace(/(<div class="code-block-container">[\s\S]*?<\/div>)/g, (match) => {
|
|
1408
|
+
const placeholder = `__CODE_BLOCK_${codeBlockPlaceholders.length}__`;
|
|
1409
|
+
codeBlockPlaceholders.push(match);
|
|
1410
|
+
return placeholder;
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
// Simple markdown renderer (now safe from code block interference)
|
|
1414
|
+
let html = text
|
|
1415
|
+
// Headers
|
|
1416
|
+
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
|
1417
|
+
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
|
1418
|
+
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
|
1419
|
+
// Bold
|
|
1420
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
1421
|
+
// Italic
|
|
1422
|
+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
1423
|
+
// Inline code
|
|
1424
|
+
.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>')
|
|
1425
|
+
// Line breaks
|
|
1426
|
+
.replace(/\n\n/g, '</p><p>')
|
|
1427
|
+
.replace(/\n/g, '<br>')
|
|
1428
|
+
// Lists
|
|
1429
|
+
.replace(/^\* (.*$)/gim, '<li>$1</li>')
|
|
1430
|
+
.replace(/^- (.*$)/gim, '<li>$1</li>')
|
|
1431
|
+
// Checkmarks and emojis
|
|
1432
|
+
.replace(/✅/g, '✅')
|
|
1433
|
+
.replace(/🚀/g, '🚀')
|
|
1434
|
+
.replace(/🔗/g, '🔗')
|
|
1435
|
+
.replace(/📋/g, '📋')
|
|
1436
|
+
// Wrap in paragraphs
|
|
1437
|
+
.replace(/^(?!<)/gm, '<p>')
|
|
1438
|
+
.replace(/$/gm, '</p>')
|
|
1439
|
+
// Clean up
|
|
1440
|
+
.replace(/<\/p><p>/g, '</p>\n<p>')
|
|
1441
|
+
.replace(/(<li>.*<\/li>)/gs, '<ul>$1</ul>')
|
|
1442
|
+
.replace(/<p><\/p>/g, '');
|
|
1443
|
+
|
|
1444
|
+
// Restore code blocks
|
|
1445
|
+
codeBlockPlaceholders.forEach((codeBlock, index) => {
|
|
1446
|
+
html = html.replace(`__CODE_BLOCK_${index}__`, codeBlock);
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
// Unescape the HTML tags we want
|
|
1450
|
+
return html.replace(/</g, '<').replace(/>/g, '>');
|
|
1451
|
+
},
|
|
1452
|
+
simplifyFilePath(fullPath) {
|
|
1453
|
+
if (!fullPath) return '';
|
|
1454
|
+
// Extract just the repo name and path after it
|
|
1455
|
+
const match = fullPath.match(/\/([^\/]+)\/(.+)$/);
|
|
1456
|
+
if (match && match[1] && match[2]) {
|
|
1457
|
+
return `${match[1]}/${match[2]}`;
|
|
1458
|
+
}
|
|
1459
|
+
return fullPath; // fallback to full path if pattern doesn't match
|
|
1460
|
+
},
|
|
1461
|
+
|
|
1462
|
+
// Helper functions for enhanced file change visualization
|
|
1463
|
+
getFileName(filePath) {
|
|
1464
|
+
if (!filePath) return '';
|
|
1465
|
+
return filePath.split('/').pop() || filePath;
|
|
1466
|
+
},
|
|
1467
|
+
|
|
1468
|
+
getFileIcon(filePath) {
|
|
1469
|
+
if (!filePath) return '📄';
|
|
1470
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
1471
|
+
const iconMap = {
|
|
1472
|
+
'ts': '🟦', 'tsx': '🔷', 'js': '🟨', 'jsx': '🔸',
|
|
1473
|
+
'json': '📋', 'md': '📖', 'css': '🎨', 'html': '🌐',
|
|
1474
|
+
'vue': '💚', 'py': '🐍', 'go': '🐹', 'rs': '🦀',
|
|
1475
|
+
'yml': '⚙️', 'yaml': '⚙️', 'xml': '📜', 'sql': '🗃️'
|
|
1476
|
+
};
|
|
1477
|
+
return iconMap[ext] || '📄';
|
|
1478
|
+
},
|
|
1479
|
+
|
|
1480
|
+
getComplianceClass(fileChange) {
|
|
1481
|
+
const status = fileChange?.compliance_status || 'unknown';
|
|
1482
|
+
return `compliance-${status}`;
|
|
1483
|
+
},
|
|
1484
|
+
|
|
1485
|
+
getComplianceCardClass(fileChange) {
|
|
1486
|
+
const status = fileChange?.compliance_status || 'unknown';
|
|
1487
|
+
return `compliance-${status}-border`;
|
|
1488
|
+
},
|
|
1489
|
+
|
|
1490
|
+
getComplianceBadge(fileChange) {
|
|
1491
|
+
const status = fileChange?.compliance_status || 'unknown';
|
|
1492
|
+
const badgeMap = {
|
|
1493
|
+
'compliant': '✅ Compliant',
|
|
1494
|
+
'deviation': '⚠️ Deviation',
|
|
1495
|
+
'unexpected': '❓ Unexpected',
|
|
1496
|
+
'unknown': '⚪ Unknown'
|
|
1497
|
+
};
|
|
1498
|
+
return badgeMap[status] || '⚪ Unknown';
|
|
1499
|
+
},
|
|
1500
|
+
getActivityTimeline(spec) {
|
|
1501
|
+
if (!spec) return [];
|
|
1502
|
+
|
|
1503
|
+
// PRIORITY 1: Use pre-built timeline from backend (includes enhanced activities)
|
|
1504
|
+
// Backend already prioritizes enhanced_activities and enriches with git diff
|
|
1505
|
+
if (spec.timeline && spec.timeline.length > 0) {
|
|
1506
|
+
// Timeline already sorted by backend, reverse for newest first
|
|
1507
|
+
return spec.timeline.slice().reverse().slice(0, 50);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// PRIORITY 2: Build timeline from enhanced_activities if available
|
|
1511
|
+
const timeline = [];
|
|
1512
|
+
const enhancedActivities = spec.enhanced_activities || [];
|
|
1513
|
+
|
|
1514
|
+
if (enhancedActivities.length > 0) {
|
|
1515
|
+
enhancedActivities.forEach(activity => {
|
|
1516
|
+
timeline.push({
|
|
1517
|
+
...activity,
|
|
1518
|
+
human_description: activity.summary,
|
|
1519
|
+
is_enhanced: true
|
|
1520
|
+
});
|
|
1521
|
+
});
|
|
1522
|
+
|
|
1523
|
+
// Sort by timestamp and return
|
|
1524
|
+
return timeline.sort((a, b) =>
|
|
1525
|
+
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
1526
|
+
).slice(0, 50);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
// FALLBACK: Build timeline client-side from legacy logs
|
|
1530
|
+
// Add execution logs
|
|
1531
|
+
const executionLogs = spec.execution_logs || spec.execution_log || [];
|
|
1532
|
+
executionLogs.forEach(log => {
|
|
1533
|
+
timeline.push({
|
|
1534
|
+
...log,
|
|
1535
|
+
type: 'execution',
|
|
1536
|
+
role: 'Engineer',
|
|
1537
|
+
is_enhanced: false
|
|
1538
|
+
});
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
// Add debug logs
|
|
1542
|
+
const debugLogs = spec.debug_logs || spec.debug_log || [];
|
|
1543
|
+
debugLogs.forEach(log => {
|
|
1544
|
+
timeline.push({
|
|
1545
|
+
...log,
|
|
1546
|
+
type: 'debug',
|
|
1547
|
+
role: 'Debugger',
|
|
1548
|
+
is_enhanced: false
|
|
1549
|
+
});
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
// Add activity logs (new field for spec activity tracking)
|
|
1553
|
+
if (spec.activity) {
|
|
1554
|
+
spec.activity.forEach(activity => {
|
|
1555
|
+
timeline.push({
|
|
1556
|
+
...activity,
|
|
1557
|
+
type: activity.type || 'activity',
|
|
1558
|
+
action: activity.action || activity.note || 'Activity logged',
|
|
1559
|
+
is_enhanced: false
|
|
1560
|
+
});
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// Add synthetic spec creation event if timeline is empty
|
|
1565
|
+
if (timeline.length === 0 && spec.created_at) {
|
|
1566
|
+
timeline.push({
|
|
1567
|
+
timestamp: spec.created_at,
|
|
1568
|
+
type: 'spec_created',
|
|
1569
|
+
role: 'Architect',
|
|
1570
|
+
action: 'Created MANUAL',
|
|
1571
|
+
note: `MANUAL ${spec.spec_id} created`,
|
|
1572
|
+
is_enhanced: false
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// Add worktree creation if it exists
|
|
1577
|
+
if (spec.worktree && timeline.length <= 2) {
|
|
1578
|
+
const workTreeTime = new Date(spec.modified_at || Date.now()).toISOString();
|
|
1579
|
+
timeline.push({
|
|
1580
|
+
timestamp: workTreeTime,
|
|
1581
|
+
type: 'worktree_created',
|
|
1582
|
+
role: 'System',
|
|
1583
|
+
action: 'worktree_created',
|
|
1584
|
+
note: `Created worktree: ${spec.worktree}`,
|
|
1585
|
+
branch: spec.worktree,
|
|
1586
|
+
is_enhanced: false
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// Sort by timestamp (newest first for better UX)
|
|
1591
|
+
return timeline.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).slice(0, 50);
|
|
1592
|
+
},
|
|
1593
|
+
async loadActivityLog() {
|
|
1594
|
+
try {
|
|
1595
|
+
// Load real activity from dashboard API
|
|
1596
|
+
const response = await fetch('/api/dashboard-data');
|
|
1597
|
+
const data = await response.json();
|
|
1598
|
+
|
|
1599
|
+
// Format recent changes for display
|
|
1600
|
+
this.activities = (data.recent_changes || []).slice(0, 20).map(change => ({
|
|
1601
|
+
timestamp: change.timestamp,
|
|
1602
|
+
type: change.event || change.type,
|
|
1603
|
+
author: change.role || 'system',
|
|
1604
|
+
message: this.formatActivityMessage(change),
|
|
1605
|
+
spec_id: change.spec_id,
|
|
1606
|
+
data: change.data
|
|
1607
|
+
}));
|
|
1608
|
+
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
console.error('Failed to load activity log:', error);
|
|
1611
|
+
// Fallback to empty state
|
|
1612
|
+
this.activities = [];
|
|
1613
|
+
}
|
|
1614
|
+
this.showActivityLog = true;
|
|
1615
|
+
},
|
|
1616
|
+
|
|
1617
|
+
formatActivityMessage(change) {
|
|
1618
|
+
if (change.event === 'orchestrator_routing') {
|
|
1619
|
+
return `Orchestrator routed to ${change.data?.target_mode} mode`;
|
|
1620
|
+
} else if (change.event === 'mcp_tool_workshop') {
|
|
1621
|
+
return `MCP tool executed: ${change.data?.tool_name} (${change.data?.duration_ms}ms)`;
|
|
1622
|
+
} else if (change.type === 'file') {
|
|
1623
|
+
return `File changed: ${change.path}`;
|
|
1624
|
+
} else {
|
|
1625
|
+
return change.event || change.type || 'Unknown activity';
|
|
1626
|
+
}
|
|
1627
|
+
},
|
|
1628
|
+
|
|
1629
|
+
showToast(message, type = 'info') {
|
|
1630
|
+
console.log('🔔 showToast called:', message, type);
|
|
1631
|
+
const toast = {
|
|
1632
|
+
id: Date.now() + Math.random(),
|
|
1633
|
+
message,
|
|
1634
|
+
type,
|
|
1635
|
+
visible: true
|
|
1636
|
+
};
|
|
1637
|
+
console.log('🔔 Toast object created:', toast);
|
|
1638
|
+
this.toasts.push(toast);
|
|
1639
|
+
console.log('🔔 Toasts array after push:', this.toasts.length, this.toasts);
|
|
1640
|
+
|
|
1641
|
+
// Auto-remove after 4 seconds
|
|
1642
|
+
setTimeout(() => {
|
|
1643
|
+
console.log('🔔 Removing toast:', toast.id);
|
|
1644
|
+
this.removeToast(toast.id);
|
|
1645
|
+
}, 4000);
|
|
1646
|
+
},
|
|
1647
|
+
|
|
1648
|
+
removeToast(id) {
|
|
1649
|
+
const index = this.toasts.findIndex(toast => toast.id === id);
|
|
1650
|
+
if (index !== -1) {
|
|
1651
|
+
this.toasts.splice(index, 1);
|
|
1652
|
+
}
|
|
1653
|
+
},
|
|
1654
|
+
|
|
1655
|
+
|
|
1656
|
+
async handleImagePaste(event) {
|
|
1657
|
+
const items = event.clipboardData?.items;
|
|
1658
|
+
if (!items) return;
|
|
1659
|
+
|
|
1660
|
+
for (let i = 0; i < items.length; i++) {
|
|
1661
|
+
const item = items[i];
|
|
1662
|
+
if (item.type.startsWith('image/')) {
|
|
1663
|
+
event.preventDefault();
|
|
1664
|
+
const file = item.getAsFile();
|
|
1665
|
+
if (file) {
|
|
1666
|
+
await this.uploadImageFile(file);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
},
|
|
1671
|
+
|
|
1672
|
+
async uploadImageFile(file) {
|
|
1673
|
+
try {
|
|
1674
|
+
const formData = new FormData();
|
|
1675
|
+
formData.append('image', file);
|
|
1676
|
+
|
|
1677
|
+
const response = await fetch('/api/images/upload-inline', {
|
|
1678
|
+
method: 'POST',
|
|
1679
|
+
body: formData
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1682
|
+
const result = await response.json();
|
|
1683
|
+
|
|
1684
|
+
if (response.ok && result.success) {
|
|
1685
|
+
// Add image to uploaded list
|
|
1686
|
+
this.uploadedImages.push(result.placeholder);
|
|
1687
|
+
|
|
1688
|
+
// Insert placeholder into prompt text at cursor position
|
|
1689
|
+
const placeholder = `[🖼️ ${result.placeholder.filename}]`;
|
|
1690
|
+
this.insertTextAtCursor(placeholder);
|
|
1691
|
+
|
|
1692
|
+
this.addToast(`📎 Image uploaded: ${result.placeholder.filename}`, 'success');
|
|
1693
|
+
} else {
|
|
1694
|
+
throw new Error(result.error || 'Upload failed');
|
|
1695
|
+
}
|
|
1696
|
+
} catch (error) {
|
|
1697
|
+
console.error('Image upload error:', error);
|
|
1698
|
+
this.addToast(`Failed to upload image: ${error.message}`, 'error');
|
|
1699
|
+
}
|
|
1700
|
+
},
|
|
1701
|
+
|
|
1702
|
+
async handleImageDrop(event) {
|
|
1703
|
+
const files = event.dataTransfer?.files;
|
|
1704
|
+
if (!files) return;
|
|
1705
|
+
|
|
1706
|
+
for (let i = 0; i < files.length; i++) {
|
|
1707
|
+
const file = files[i];
|
|
1708
|
+
if (file.type.startsWith('image/')) {
|
|
1709
|
+
await this.uploadImageFile(file);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
},
|
|
1713
|
+
|
|
1714
|
+
insertTextAtCursor(text) {
|
|
1715
|
+
// Simple text insertion - append to current prompt
|
|
1716
|
+
if (this.mainPrompt) {
|
|
1717
|
+
this.mainPrompt += ' ' + text;
|
|
1718
|
+
} else {
|
|
1719
|
+
this.mainPrompt = text;
|
|
1720
|
+
}
|
|
1721
|
+
},
|
|
1722
|
+
|
|
1723
|
+
removeImage(imageId) {
|
|
1724
|
+
// Remove from uploaded images
|
|
1725
|
+
this.uploadedImages = this.uploadedImages.filter(img => img.id !== imageId);
|
|
1726
|
+
|
|
1727
|
+
// Remove placeholder from prompt text
|
|
1728
|
+
const placeholderPattern = new RegExp(`\\[🖼️ ${imageId}\\.(png|jpg|jpeg|gif|webp)\\]`, 'gi');
|
|
1729
|
+
this.mainPrompt = this.mainPrompt.replace(placeholderPattern, '').trim();
|
|
1730
|
+
|
|
1731
|
+
this.addToast('Image removed', 'info');
|
|
1732
|
+
},
|
|
1733
|
+
|
|
1734
|
+
translatePromptWithImages() {
|
|
1735
|
+
let translatedPrompt = this.mainPrompt;
|
|
1736
|
+
|
|
1737
|
+
// Replace image placeholders with file paths
|
|
1738
|
+
this.uploadedImages.forEach(image => {
|
|
1739
|
+
const placeholderPattern = new RegExp(`\\[🖼️ ${image.filename}\\]`, 'g');
|
|
1740
|
+
translatedPrompt = translatedPrompt.replace(placeholderPattern, image.path);
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
return translatedPrompt;
|
|
1744
|
+
},
|
|
1745
|
+
|
|
1746
|
+
|
|
1747
|
+
|
|
1748
|
+
// Duplicate toast system removed - using main toast system at line 1020
|
|
1749
|
+
|
|
1750
|
+
getToastIcon(type) {
|
|
1751
|
+
switch (type) {
|
|
1752
|
+
case 'success': return '✅';
|
|
1753
|
+
case 'error': return '❌';
|
|
1754
|
+
case 'warning': return '⚠️';
|
|
1755
|
+
case 'info': return 'ℹ️';
|
|
1756
|
+
default: return '💬';
|
|
1757
|
+
}
|
|
1758
|
+
},
|
|
1759
|
+
|
|
1760
|
+
getToastColor(type) {
|
|
1761
|
+
switch (type) {
|
|
1762
|
+
case 'success': return 'rgba(34, 197, 94, 0.9)';
|
|
1763
|
+
case 'error': return 'rgba(239, 68, 68, 0.9)';
|
|
1764
|
+
case 'warning': return 'rgba(245, 158, 11, 0.9)';
|
|
1765
|
+
case 'info': return 'rgba(59, 130, 246, 0.9)';
|
|
1766
|
+
default: return 'rgba(75, 85, 99, 0.9)';
|
|
1767
|
+
}
|
|
1768
|
+
},
|
|
1769
|
+
|
|
1770
|
+
// Keyboard navigation methods
|
|
1771
|
+
handleKeydown(event) {
|
|
1772
|
+
// Handle Tab navigation when in a manual modal
|
|
1773
|
+
if (this.selectedSpec) {
|
|
1774
|
+
switch(event.key) {
|
|
1775
|
+
case 'Tab':
|
|
1776
|
+
event.preventDefault();
|
|
1777
|
+
this.navigateToNextSection(event.shiftKey);
|
|
1778
|
+
break;
|
|
1779
|
+
}
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// Only handle keys when not in other modals and when we have specs
|
|
1784
|
+
if (this.showActivityLog || this.showWorktreeModal || this.filteredSpecs.length === 0) {
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
switch(event.key) {
|
|
1789
|
+
case 'ArrowDown':
|
|
1790
|
+
event.preventDefault();
|
|
1791
|
+
this.keyboardMode = true;
|
|
1792
|
+
this.selectedRowIndex = Math.min(this.selectedRowIndex + 1, this.filteredSpecs.length - 1);
|
|
1793
|
+
this.scrollToSelectedRow();
|
|
1794
|
+
break;
|
|
1795
|
+
case 'ArrowUp':
|
|
1796
|
+
event.preventDefault();
|
|
1797
|
+
this.keyboardMode = true;
|
|
1798
|
+
this.selectedRowIndex = Math.max(this.selectedRowIndex - 1, 0);
|
|
1799
|
+
this.scrollToSelectedRow();
|
|
1800
|
+
break;
|
|
1801
|
+
case 'Enter':
|
|
1802
|
+
event.preventDefault();
|
|
1803
|
+
if (this.keyboardMode && this.filteredSpecs[this.selectedRowIndex]) {
|
|
1804
|
+
this.openSpec(this.filteredSpecs[this.selectedRowIndex].spec_id);
|
|
1805
|
+
}
|
|
1806
|
+
break;
|
|
1807
|
+
case 'Delete':
|
|
1808
|
+
case 'Backspace':
|
|
1809
|
+
event.preventDefault();
|
|
1810
|
+
if (this.keyboardMode && this.filteredSpecs[this.selectedRowIndex]) {
|
|
1811
|
+
const spec = this.filteredSpecs[this.selectedRowIndex];
|
|
1812
|
+
this.confirmDeleteSpec(spec.spec_id, spec.title);
|
|
1813
|
+
}
|
|
1814
|
+
break;
|
|
1815
|
+
}
|
|
1816
|
+
},
|
|
1817
|
+
|
|
1818
|
+
scrollToSelectedRow() {
|
|
1819
|
+
this.$nextTick(() => {
|
|
1820
|
+
const table = document.querySelector('.spec-table tbody');
|
|
1821
|
+
const rows = table?.querySelectorAll('tr');
|
|
1822
|
+
if (rows && rows[this.selectedRowIndex]) {
|
|
1823
|
+
rows[this.selectedRowIndex].scrollIntoView({
|
|
1824
|
+
behavior: 'smooth',
|
|
1825
|
+
block: 'nearest'
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
},
|
|
1830
|
+
|
|
1831
|
+
isRowSelected(index) {
|
|
1832
|
+
return this.keyboardMode && this.selectedRowIndex === index;
|
|
1833
|
+
},
|
|
1834
|
+
|
|
1835
|
+
// Section navigation methods
|
|
1836
|
+
navigateToNextSection(reverse = false) {
|
|
1837
|
+
const sections = this.getAvailableSections();
|
|
1838
|
+
if (sections.length <= 1) return; // No navigation needed with 0 or 1 sections
|
|
1839
|
+
|
|
1840
|
+
const currentIndex = sections.findIndex(s => s.id === this.section);
|
|
1841
|
+
let nextIndex;
|
|
1842
|
+
|
|
1843
|
+
if (reverse) {
|
|
1844
|
+
// Shift+Tab: go to previous section
|
|
1845
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : sections.length - 1;
|
|
1846
|
+
} else {
|
|
1847
|
+
// Tab: go to next section
|
|
1848
|
+
nextIndex = currentIndex < sections.length - 1 ? currentIndex + 1 : 0;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
this.section = sections[nextIndex].id;
|
|
1852
|
+
this.sectionKeyboardMode = true;
|
|
1853
|
+
},
|
|
1854
|
+
|
|
1855
|
+
|
|
1856
|
+
}));
|
|
1857
|
+
});
|
|
1858
|
+
</script>
|
|
1859
|
+
|
|
1860
|
+
<body class="h-full" x-data="dashboard" x-init="fetchSpecs()" @keydown.escape.window="selectedSpec = null; showActivityLog = false; showWorktreeModal = false" @keydown.window="handleKeydown($event)">
|
|
1861
|
+
|
|
1862
|
+
<!-- Header -->
|
|
1863
|
+
<header class="header">
|
|
1864
|
+
<div class="logo">
|
|
1865
|
+
<span style="color: var(--accent-orange);">👷</span>
|
|
1866
|
+
<span class="text-gradient" style="text-transform: lowercase;">Bob's Workshop</span>
|
|
1867
|
+
</div>
|
|
1868
|
+
|
|
1869
|
+
<!-- Separate Metric Boxes -->
|
|
1870
|
+
<div style="display: flex; align-items: center; gap: 1rem;"
|
|
1871
|
+
x-data="{
|
|
1872
|
+
worktrees: { committed: 0, dirty: 0, clean: 0 },
|
|
1873
|
+
specs: { draft: 0, 'in-progress': 0, completed: 0 },
|
|
1874
|
+
async loadMetrics() {
|
|
1875
|
+
try {
|
|
1876
|
+
const [worktreesRes, specsRes] = await Promise.all([
|
|
1877
|
+
fetch('/api/tools/bob.worktree.list').catch(() => ({ json: () => ({committed:0,dirty:0,clean:0}) })),
|
|
1878
|
+
fetch('/api/tools/bob.spec.list')
|
|
1879
|
+
]);
|
|
1880
|
+
const worktreesData = await worktreesRes.json();
|
|
1881
|
+
this.worktrees = worktreesData;
|
|
1882
|
+
const specsData = await specsRes.json();
|
|
1883
|
+
const allSpecs = specsData.specs || [];
|
|
1884
|
+
this.specs = {
|
|
1885
|
+
draft: allSpecs.filter(s => s.state === 'draft').length,
|
|
1886
|
+
'in-progress': allSpecs.filter(s => s.state === 'in-progress').length,
|
|
1887
|
+
completed: allSpecs.filter(s => s.state === 'completed').length
|
|
1888
|
+
};
|
|
1889
|
+
} catch (error) {
|
|
1890
|
+
console.error('Failed to load metrics:', error);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}" x-init="loadMetrics()">
|
|
1894
|
+
|
|
1895
|
+
<!-- Manuals Box -->
|
|
1896
|
+
<div style="background: rgba(59, 130, 246, 0.12); backdrop-filter: blur(10px); border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 10px; padding: 0.6rem 0.9rem; display: flex; align-items: center; gap: 0.6rem; font-size: 0.85rem; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; color: var(--text-secondary);">
|
|
1897
|
+
<span>📖 Manuals:</span>
|
|
1898
|
+
<span style="color: #9ca3af;" x-text="specs.draft + 'd'"></span>
|
|
1899
|
+
<span style="color: var(--text-muted);">|</span>
|
|
1900
|
+
<span style="color: var(--accent-orange);" x-text="specs['in-progress'] + 'p'"></span>
|
|
1901
|
+
<span style="color: var(--text-muted);">|</span>
|
|
1902
|
+
<span style="color: var(--accent-green);" x-text="specs.completed + '✓'"></span>
|
|
1903
|
+
</div>
|
|
1904
|
+
|
|
1905
|
+
<!-- Trees Box -->
|
|
1906
|
+
<div style="background: rgba(16, 185, 129, 0.12); backdrop-filter: blur(10px); border: 1px solid rgba(16, 185, 129, 0.2); border-radius: 10px; padding: 0.6rem 0.9rem; display: flex; align-items: center; gap: 0.6rem; font-size: 0.85rem; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; color: var(--text-secondary);">
|
|
1907
|
+
<span>🌿 Trees:</span>
|
|
1908
|
+
<span style="color: var(--accent-green);" x-text="(worktrees.committed?.length || 0) + 'c'"></span>
|
|
1909
|
+
<span style="color: var(--text-muted);">|</span>
|
|
1910
|
+
<span style="color: var(--accent-orange);" x-text="(worktrees.dirty?.length || 0) + '!'"></span>
|
|
1911
|
+
<span style="color: var(--text-muted);">|</span>
|
|
1912
|
+
<span style="color: var(--text-secondary);" x-text="(worktrees.clean?.length || 0) + '~'"></span>
|
|
1913
|
+
<button @click="showWorktreeModal = true"
|
|
1914
|
+
style="margin-left: 0.5rem; padding: 0.25rem 0.5rem; background: var(--accent-orange); border: 1px solid var(--accent-orange); border-radius: 6px; color: white; font-size: 0.75rem; cursor: pointer; transition: all 0.2s ease; font-weight: 600;"
|
|
1915
|
+
@mouseenter="$el.style.background = '#e97b00'"
|
|
1916
|
+
@mouseleave="$el.style.background = 'var(--accent-orange)'"
|
|
1917
|
+
title="View Active Worktrees">
|
|
1918
|
+
🏗️ View
|
|
1919
|
+
</button>
|
|
1920
|
+
</div>
|
|
1921
|
+
|
|
1922
|
+
</div>
|
|
1923
|
+
|
|
1924
|
+
<div style="font-size: 0.9rem; color: var(--text-secondary); white-space: nowrap;"
|
|
1925
|
+
x-data="{
|
|
1926
|
+
projectName: 'loading...',
|
|
1927
|
+
workshopVerbs: ['servicing', 'working on', 'fixing', 'building', 'crafting', 'assembling', 'engineering', 'constructing', 'maintaining', 'upgrading'],
|
|
1928
|
+
currentVerb: 'servicing',
|
|
1929
|
+
async init() {
|
|
1930
|
+
this.currentVerb = this.workshopVerbs[Math.floor(Math.random() * this.workshopVerbs.length)];
|
|
1931
|
+
// Fetch dynamic repo name from API
|
|
1932
|
+
try {
|
|
1933
|
+
const response = await fetch('/api/project-info');
|
|
1934
|
+
const data = await response.json();
|
|
1935
|
+
this.projectName = data.repoName || 'unknown';
|
|
1936
|
+
} catch (error) {
|
|
1937
|
+
console.error('Failed to fetch project info:', error);
|
|
1938
|
+
this.projectName = 'unknown';
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}">
|
|
1942
|
+
<span style="background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(245, 158, 11, 0.08) 100%); backdrop-filter: blur(8px); border: 1px solid rgba(245, 158, 11, 0.25); border-radius: 12px; padding: 0.7rem 1.1rem; display: inline-flex; align-items: center; gap: 0.5rem; font-family: var(--font-heading); font-weight: 500; font-size: 0.9rem; color: var(--text-primary); box-shadow: 0 2px 8px rgba(245, 158, 11, 0.1);">
|
|
1943
|
+
<span style="color: #fbbf24;">now</span>
|
|
1944
|
+
<span style="color: var(--accent-orange);" x-text="currentVerb"></span>
|
|
1945
|
+
<span style="color: var(--text-muted);">:</span>
|
|
1946
|
+
<span style="color: #fbbf24; font-weight: 600;" x-text="projectName"></span>
|
|
1947
|
+
</span>
|
|
1948
|
+
</div>
|
|
1949
|
+
</header>
|
|
1950
|
+
|
|
1951
|
+
|
|
1952
|
+
|
|
1953
|
+
<!-- Manual Table -->
|
|
1954
|
+
<div class="spec-container fade-in" style="margin: 2rem;">
|
|
1955
|
+
<!-- Header with integrated controls -->
|
|
1956
|
+
<div class="spec-header" style="padding: 1.5rem 2rem; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
|
|
1957
|
+
<div style="display: flex; align-items: center; gap: 1rem;">
|
|
1958
|
+
<h2 class="heading" style="margin: 0; font-size: 1.25rem; color: white; text-transform: lowercase;">📄 manual explorer</h2>
|
|
1959
|
+
<span style="background: rgba(255, 255, 255, 0.1); color: var(--text-secondary); padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.75rem; font-weight: 600; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;"
|
|
1960
|
+
x-text="`${filteredSpecs.length} of ${specs.length} Manuals`"></span>
|
|
1961
|
+
<span x-show="loading" class="loading"></span>
|
|
1962
|
+
</div>
|
|
1963
|
+
|
|
1964
|
+
<!-- Integrated Controls Row -->
|
|
1965
|
+
<div style="display: flex; align-items: center; gap: 1rem; flex: 1; max-width: 800px;">
|
|
1966
|
+
<!-- Search Input -->
|
|
1967
|
+
<div style="flex: 2; min-width: 200px;">
|
|
1968
|
+
<input type="text"
|
|
1969
|
+
placeholder="🔍 Search Manuals..."
|
|
1970
|
+
x-model="searchTerm"
|
|
1971
|
+
@input="filterSpecs()"
|
|
1972
|
+
style="width: 100%; padding: 0.5rem 0.75rem; background: var(--bg-glass); border: 1px solid var(--border-glass); border-radius: 6px; color: var(--text-primary); font-family: var(--font-body); font-size: 0.875rem;">
|
|
1973
|
+
</div>
|
|
1974
|
+
|
|
1975
|
+
<!-- Category Filter -->
|
|
1976
|
+
<select x-model="categoryFilter"
|
|
1977
|
+
@change="filterSpecs()"
|
|
1978
|
+
style="padding: 0.5rem 0.75rem; background: var(--bg-glass); border: 1px solid var(--border-glass); border-radius: 6px; color: var(--text-primary); font-family: var(--font-body); font-size: 0.875rem;">
|
|
1979
|
+
<option value="all">All Categories</option>
|
|
1980
|
+
<option value="frontend">Frontend</option>
|
|
1981
|
+
<option value="backend">Backend</option>
|
|
1982
|
+
<option value="fullstack">Fullstack</option>
|
|
1983
|
+
<option value="general">General</option>
|
|
1984
|
+
</select>
|
|
1985
|
+
|
|
1986
|
+
<!-- Status Filter -->
|
|
1987
|
+
<select x-model="statusFilter"
|
|
1988
|
+
@change="filterSpecs()"
|
|
1989
|
+
style="padding: 0.5rem 0.75rem; background: var(--bg-glass); border: 1px solid var(--border-glass); border-radius: 6px; color: var(--text-primary); font-family: var(--font-body); font-size: 0.875rem;">
|
|
1990
|
+
<option value="all">All Status</option>
|
|
1991
|
+
<option value="draft">Draft</option>
|
|
1992
|
+
<option value="planning">Planning</option>
|
|
1993
|
+
<option value="in-progress">In Progress</option>
|
|
1994
|
+
<option value="completed">Completed</option>
|
|
1995
|
+
</select>
|
|
1996
|
+
|
|
1997
|
+
<!-- Clear Filters -->
|
|
1998
|
+
<button class="btn btn-secondary"
|
|
1999
|
+
@click="searchTerm = ''; categoryFilter = 'all'; statusFilter = 'all'; filterSpecs()"
|
|
2000
|
+
style="padding: 0.5rem 0.75rem; font-size: 0.875rem; white-space: nowrap;">
|
|
2001
|
+
✕ Clear
|
|
2002
|
+
</button>
|
|
2003
|
+
|
|
2004
|
+
<!-- Refresh Button -->
|
|
2005
|
+
<button class="btn btn-secondary" @click="fetchSpecs()" style="padding: 0.5rem 0.75rem; font-size: 0.875rem; white-space: nowrap; font-family: var(--font-body);">
|
|
2006
|
+
🔄 Refresh
|
|
2007
|
+
</button>
|
|
2008
|
+
</div>
|
|
2009
|
+
</div>
|
|
2010
|
+
|
|
2011
|
+
|
|
2012
|
+
<table class="spec-table">
|
|
2013
|
+
<thead>
|
|
2014
|
+
<tr>
|
|
2015
|
+
<th class="sortable"
|
|
2016
|
+
:class="{ 'sorted-asc': sortBy === 'title' && sortDirection === 'asc', 'sorted-desc': sortBy === 'title' && sortDirection === 'desc' }"
|
|
2017
|
+
@click="sortSpecs('title')">Title</th>
|
|
2018
|
+
<th class="sortable"
|
|
2019
|
+
:class="{ 'sorted-asc': sortBy === 'category' && sortDirection === 'asc', 'sorted-desc': sortBy === 'category' && sortDirection === 'desc' }"
|
|
2020
|
+
@click="sortSpecs('category')">Category</th>
|
|
2021
|
+
<th class="sortable"
|
|
2022
|
+
:class="{ 'sorted-asc': sortBy === 'state' && sortDirection === 'asc', 'sorted-desc': sortBy === 'state' && sortDirection === 'desc' }"
|
|
2023
|
+
@click="sortSpecs('state')">Status</th>
|
|
2024
|
+
<th>Worktree</th>
|
|
2025
|
+
<th class="sortable"
|
|
2026
|
+
:class="{ 'sorted-asc': sortBy === 'modified' && sortDirection === 'asc', 'sorted-desc': sortBy === 'modified' && sortDirection === 'desc' }"
|
|
2027
|
+
@click="sortSpecs('modified')">Modified</th>
|
|
2028
|
+
<th>Actions</th>
|
|
2029
|
+
</tr>
|
|
2030
|
+
</thead>
|
|
2031
|
+
<tbody>
|
|
2032
|
+
<template x-for="(spec, index) in filteredSpecs" :key="spec.spec_id">
|
|
2033
|
+
<tr :class="{ 'keyboard-selected': isRowSelected(index) }"
|
|
2034
|
+
@click="keyboardMode = false; selectedRowIndex = index"
|
|
2035
|
+
:style="isRowSelected(index) ? 'background: rgba(245, 158, 11, 0.1); border-left: 3px solid var(--accent-orange);' : ''"
|
|
2036
|
+
tabindex="0">
|
|
2037
|
+
<td>
|
|
2038
|
+
<div style="font-weight: 600; cursor: pointer; color: var(--text-primary);"
|
|
2039
|
+
x-text="spec.title"
|
|
2040
|
+
@click="openSpec(spec.spec_id)"></div>
|
|
2041
|
+
<div style="font-size: 0.75rem; color: var(--text-muted); margin-top: 0.25rem;"
|
|
2042
|
+
x-text="spec.spec_id"></div>
|
|
2043
|
+
</td>
|
|
2044
|
+
<td style="color: var(--text-secondary);" x-text="spec.category || '—'"></td>
|
|
2045
|
+
<td>
|
|
2046
|
+
<span class="status-badge"
|
|
2047
|
+
:class="`status-${spec.state}`"
|
|
2048
|
+
x-text="spec.state"></span>
|
|
2049
|
+
</td>
|
|
2050
|
+
<td style="color: var(--text-secondary); font-family: monospace; font-size: 0.875rem;">
|
|
2051
|
+
<div x-data="{ worktreeName: getWorktreeName(spec) }">
|
|
2052
|
+
<span x-show="worktreeName !== 'none'" x-text="worktreeName" style="color: var(--accent-blue);"></span>
|
|
2053
|
+
<span x-show="worktreeName === 'none'" style="color: var(--text-muted);">—</span>
|
|
2054
|
+
<div x-show="worktreeName !== 'none'" style="font-size: 0.75rem; margin-top: 0.25rem;">
|
|
2055
|
+
<span style="color: var(--accent-green);">✓ clean</span>
|
|
2056
|
+
</div>
|
|
2057
|
+
</div>
|
|
2058
|
+
</td>
|
|
2059
|
+
<td style="color: var(--text-secondary); font-size: 0.875rem;"
|
|
2060
|
+
x-text="new Date(spec.updated_at).toLocaleDateString()"></td>
|
|
2061
|
+
<td>
|
|
2062
|
+
<div style="display: flex; gap: 0.5rem;">
|
|
2063
|
+
<button class="btn btn-primary" style="padding: 0.5rem 1rem; font-size: 0.75rem; font-family: var(--font-body);"
|
|
2064
|
+
@click="openSpec(spec.spec_id)">
|
|
2065
|
+
View
|
|
2066
|
+
</button>
|
|
2067
|
+
<button class="btn btn-danger" style="padding: 0.5rem 0.75rem; font-size: 0.75rem; background: var(--accent-red); border: 1px solid var(--accent-red); font-family: var(--font-body);"
|
|
2068
|
+
@click="confirmDeleteSpec(spec.spec_id, spec.title)"
|
|
2069
|
+
:title="`Delete ${spec.title}`">
|
|
2070
|
+
🗑
|
|
2071
|
+
</button>
|
|
2072
|
+
</div>
|
|
2073
|
+
</td>
|
|
2074
|
+
</tr>
|
|
2075
|
+
</template>
|
|
2076
|
+
</tbody>
|
|
2077
|
+
</table>
|
|
2078
|
+
|
|
2079
|
+
<div x-show="filteredSpecs.length === 0 && !loading"
|
|
2080
|
+
style="text-align: center; padding: 3rem; color: var(--text-muted);">
|
|
2081
|
+
<div x-show="specs.length === 0">No Manuals found. Create your first Manual to get started!</div>
|
|
2082
|
+
<div x-show="specs.length > 0">No Manuals match your current filters.</div>
|
|
2083
|
+
</div>
|
|
2084
|
+
|
|
2085
|
+
<!-- Activity Log Button -->
|
|
2086
|
+
<div style="padding: 1.5rem; border-top: 1px solid var(--border-glass); text-center;">
|
|
2087
|
+
<button class="btn btn-secondary" @click="loadActivityLog()">
|
|
2088
|
+
📊 View Activity Log
|
|
2089
|
+
</button>
|
|
2090
|
+
</div>
|
|
2091
|
+
</div>
|
|
2092
|
+
|
|
2093
|
+
<!-- Enhanced Modal -->
|
|
2094
|
+
<div class="modal-overlay"
|
|
2095
|
+
x-show="selectedSpec"
|
|
2096
|
+
x-transition:enter="transition ease-out duration-300"
|
|
2097
|
+
x-transition:enter-start="opacity-0"
|
|
2098
|
+
x-transition:enter-end="opacity-100"
|
|
2099
|
+
x-transition:leave="transition ease-in duration-200"
|
|
2100
|
+
x-transition:leave-start="opacity-100"
|
|
2101
|
+
x-transition:leave-end="opacity-0"
|
|
2102
|
+
@click.self="selectedSpec=null">
|
|
2103
|
+
|
|
2104
|
+
<div class="modal-content"
|
|
2105
|
+
x-show="selectedSpec"
|
|
2106
|
+
x-transition:enter="transition ease-out duration-300"
|
|
2107
|
+
x-transition:enter-start="opacity-0 scale-95"
|
|
2108
|
+
x-transition:enter-end="opacity-100 scale-100"
|
|
2109
|
+
x-transition:leave="transition ease-in duration-200"
|
|
2110
|
+
x-transition:leave-start="opacity-100 scale-100"
|
|
2111
|
+
x-transition:leave-end="opacity-0 scale-95">
|
|
2112
|
+
|
|
2113
|
+
<!-- Modal Header -->
|
|
2114
|
+
<div class="modal-header" style="flex-direction: column; align-items: stretch; gap: 0.75rem; background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(245, 158, 11, 0.08) 100%); border-bottom: 1px solid rgba(245, 158, 11, 0.2);">
|
|
2115
|
+
<!-- Top Row: Title, Status, and Worktree Info -->
|
|
2116
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
2117
|
+
<div class="modal-title-section">
|
|
2118
|
+
<h2 class="heading" style="margin: 0; font-size: 1.5rem;" x-text="selectedSpec?.title"></h2>
|
|
2119
|
+
<div style="display: flex; align-items: center; gap: 1rem;">
|
|
2120
|
+
<!-- Workflow Status Badge -->
|
|
2121
|
+
<div style="background: rgba(255, 255, 255, 0.1); padding: 0.5rem 1rem; border-radius: 20px; font-size: 0.875rem; display: flex; align-items: center; gap: 0.5rem;">
|
|
2122
|
+
<span>📋</span>
|
|
2123
|
+
<span class="status-badge"
|
|
2124
|
+
:class="`status-${selectedSpec?.state}`"
|
|
2125
|
+
x-text="selectedSpec?.state || 'draft'"></span>
|
|
2126
|
+
</div>
|
|
2127
|
+
<!-- Worktree Info -->
|
|
2128
|
+
<div class="worktree-info" x-show="getWorktreeName(selectedSpec)">
|
|
2129
|
+
<span>🔧</span>
|
|
2130
|
+
<span x-text="getWorktreeName(selectedSpec)"></span>
|
|
2131
|
+
<span class="status-badge status-completed" style="font-size: 0.7rem; padding: 0.2rem 0.5rem;">✓ clean</span>
|
|
2132
|
+
</div>
|
|
2133
|
+
</div>
|
|
2134
|
+
</div>
|
|
2135
|
+
<div style="display: flex; gap: 1rem;">
|
|
2136
|
+
<button class="btn btn-primary" @click="exportToMarkdown(selectedSpec?.spec_id)">
|
|
2137
|
+
📄 Export
|
|
2138
|
+
</button>
|
|
2139
|
+
<button class="btn btn-secondary" @click="selectedSpec=null"
|
|
2140
|
+
style="background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.3); color: var(--accent-orange);"
|
|
2141
|
+
@mouseenter="$el.style.background = 'rgba(245, 158, 11, 0.2)'"
|
|
2142
|
+
@mouseleave="$el.style.background = 'rgba(245, 158, 11, 0.1)'">
|
|
2143
|
+
✕
|
|
2144
|
+
</button>
|
|
2145
|
+
</div>
|
|
2146
|
+
</div>
|
|
2147
|
+
|
|
2148
|
+
</div>
|
|
2149
|
+
|
|
2150
|
+
<!-- Main Content Container -->
|
|
2151
|
+
<div class="modal-main-container">
|
|
2152
|
+
<!-- Navigation Tabs - Only show sections with content -->
|
|
2153
|
+
<div class="modal-nav">
|
|
2154
|
+
<div class="nav-tabs">
|
|
2155
|
+
<template x-for="sectionItem in getAvailableSections()" :key="sectionItem.id">
|
|
2156
|
+
<button class="nav-tab"
|
|
2157
|
+
:class="{'active': section === sectionItem.id, 'keyboard-focus': sectionKeyboardMode && section === sectionItem.id}"
|
|
2158
|
+
@click="section = sectionItem.id; sectionKeyboardMode = false"
|
|
2159
|
+
x-text="sectionItem.label"
|
|
2160
|
+
tabindex="-1">
|
|
2161
|
+
</button>
|
|
2162
|
+
</template>
|
|
2163
|
+
</div>
|
|
2164
|
+
</div>
|
|
2165
|
+
|
|
2166
|
+
<!-- Content Area -->
|
|
2167
|
+
<div class="modal-main">
|
|
2168
|
+
<template x-if="section==='summary'">
|
|
2169
|
+
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.executive_summary || selectedSpec?.sections?.executive_summary?.content || 'No executive summary yet')">
|
|
2170
|
+
</div>
|
|
2171
|
+
</template>
|
|
2172
|
+
|
|
2173
|
+
<template x-if="section==='product'">
|
|
2174
|
+
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.product_specifications?.content || 'No product specifications yet')">
|
|
2175
|
+
</div>
|
|
2176
|
+
</template>
|
|
2177
|
+
|
|
2178
|
+
<template x-if="section==='architecture'">
|
|
2179
|
+
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.architecture_analysis?.content || 'No architecture analysis yet')">
|
|
2180
|
+
</div>
|
|
2181
|
+
</template>
|
|
2182
|
+
|
|
2183
|
+
<template x-if="section==='implementation'">
|
|
2184
|
+
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.implementation_plan?.content || 'No implementation plan yet')">
|
|
2185
|
+
</div>
|
|
2186
|
+
</template>
|
|
2187
|
+
|
|
2188
|
+
<template x-if="section==='research'">
|
|
2189
|
+
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.research?.content || 'No research notes yet')">
|
|
2190
|
+
</div>
|
|
2191
|
+
</template>
|
|
2192
|
+
|
|
2193
|
+
<template x-if="section==='testing'">
|
|
2194
|
+
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.testing?.content || 'No testing notes yet')">
|
|
2195
|
+
</div>
|
|
2196
|
+
</template>
|
|
2197
|
+
|
|
2198
|
+
<template x-if="section==='review'">
|
|
2199
|
+
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.review?.content || 'No review notes yet')">
|
|
2200
|
+
</div>
|
|
2201
|
+
</template>
|
|
2202
|
+
|
|
2203
|
+
<template x-if="section==='execution'">
|
|
2204
|
+
<div class="spec-content">
|
|
2205
|
+
<h3 style="color: var(--text-primary); margin-bottom: 1rem;">🛠️ Execution Logs</h3>
|
|
2206
|
+
<template x-for="(log, index) in (selectedSpec?.execution_logs || selectedSpec?.execution_log || [])" :key="'exec_' + index">
|
|
2207
|
+
<div style="background: rgba(59, 130, 246, 0.1); border-left: 3px solid var(--accent-blue); padding: 1rem; margin-bottom: 1rem; border-radius: 6px;">
|
|
2208
|
+
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.5rem;">
|
|
2209
|
+
<span style="font-size: 0.75rem; color: var(--text-muted);" x-text="new Date(log.timestamp).toLocaleString()"></span>
|
|
2210
|
+
<span x-show="log.engineer" style="background: rgba(59, 130, 246, 0.2); color: var(--accent-blue); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem;" x-text="log.engineer"></span>
|
|
2211
|
+
</div>
|
|
2212
|
+
<div style="margin-bottom: 0.5rem;">
|
|
2213
|
+
<strong style="color: var(--accent-blue);">Action:</strong>
|
|
2214
|
+
<span x-text="log.action" style="color: var(--text-primary); margin-left: 0.5rem;"></span>
|
|
2215
|
+
</div>
|
|
2216
|
+
<div x-show="log.task_id" style="margin-bottom: 0.5rem;">
|
|
2217
|
+
<strong style="color: var(--accent-blue);">Task ID:</strong>
|
|
2218
|
+
<span x-text="log.task_id" style="color: var(--text-primary); margin-left: 0.5rem; font-family: monospace;"></span>
|
|
2219
|
+
</div>
|
|
2220
|
+
<div x-show="log.note" style="margin-bottom: 0.5rem;">
|
|
2221
|
+
<strong style="color: var(--accent-blue);">Note:</strong>
|
|
2222
|
+
<span x-text="log.note" style="color: var(--text-primary); margin-left: 0.5rem;"></span>
|
|
2223
|
+
</div>
|
|
2224
|
+
<div x-show="log.files_changed && log.files_changed.length > 0" style="margin-bottom: 0.5rem;">
|
|
2225
|
+
<strong style="color: var(--accent-blue);">Files Changed:</strong>
|
|
2226
|
+
<div style="margin-top: 0.25rem;">
|
|
2227
|
+
<template x-for="file in log.files_changed" :key="file">
|
|
2228
|
+
<span style="background: rgba(0, 0, 0, 0.2); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-family: monospace; display: inline-block; margin: 0.25rem 0.25rem 0 0;" x-text="file"></span>
|
|
2229
|
+
</template>
|
|
2230
|
+
</div>
|
|
2231
|
+
</div>
|
|
2232
|
+
<div x-show="log.commit_hash" style="font-size: 0.75rem; color: var(--text-muted);">
|
|
2233
|
+
<strong>Commit:</strong> <span x-text="log.commit_hash" style="font-family: monospace;"></span>
|
|
2234
|
+
</div>
|
|
2235
|
+
</div>
|
|
2236
|
+
</template>
|
|
2237
|
+
</div>
|
|
2238
|
+
</template>
|
|
2239
|
+
|
|
2240
|
+
<template x-if="section==='debug'">
|
|
2241
|
+
<div class="spec-content">
|
|
2242
|
+
<h3 style="color: var(--text-primary); margin-bottom: 1rem;">🐛 Debug Logs</h3>
|
|
2243
|
+
<template x-for="(log, index) in (selectedSpec?.debug_logs || selectedSpec?.debug_log || [])" :key="'debug_' + index">
|
|
2244
|
+
<div style="background: rgba(239, 68, 68, 0.1); border-left: 3px solid var(--accent-red); padding: 1rem; margin-bottom: 1rem; border-radius: 6px;">
|
|
2245
|
+
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.5rem;">
|
|
2246
|
+
<span style="font-size: 0.75rem; color: var(--text-muted);" x-text="new Date(log.timestamp).toLocaleString()"></span>
|
|
2247
|
+
<span x-show="log.confidence" style="padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; text-transform: capitalize;"
|
|
2248
|
+
:style="log.confidence === 'High' || log.confidence === 'high' ? 'background: rgba(16, 185, 129, 0.2); color: var(--accent-green);' : log.confidence === 'Medium' || log.confidence === 'medium' ? 'background: rgba(245, 158, 11, 0.2); color: var(--accent-orange);' : 'background: rgba(239, 68, 68, 0.2); color: var(--accent-red);'"
|
|
2249
|
+
x-text="log.confidence"></span>
|
|
2250
|
+
</div>
|
|
2251
|
+
<div style="margin-bottom: 0.75rem;">
|
|
2252
|
+
<strong style="color: var(--accent-red);">Issue:</strong>
|
|
2253
|
+
<div x-text="log.issue" style="color: var(--text-primary); margin-top: 0.25rem;"></div>
|
|
2254
|
+
</div>
|
|
2255
|
+
<div style="margin-bottom: 0.75rem;">
|
|
2256
|
+
<strong style="color: var(--accent-orange);">Root Cause:</strong>
|
|
2257
|
+
<div x-text="log.root_cause" style="color: var(--text-primary); margin-top: 0.25rem;"></div>
|
|
2258
|
+
</div>
|
|
2259
|
+
<div>
|
|
2260
|
+
<strong style="color: var(--accent-green);">Fix:</strong>
|
|
2261
|
+
<div x-text="log.fix" style="color: var(--text-primary); margin-top: 0.25rem;"></div>
|
|
2262
|
+
</div>
|
|
2263
|
+
</div>
|
|
2264
|
+
</template>
|
|
2265
|
+
</div>
|
|
2266
|
+
</template>
|
|
2267
|
+
|
|
2268
|
+
</div>
|
|
2269
|
+
</div>
|
|
2270
|
+
|
|
2271
|
+
<!-- Right Sidebar: Activity & Files -->
|
|
2272
|
+
<div class="modal-activity">
|
|
2273
|
+
<!-- Activity Navigation Tabs -->
|
|
2274
|
+
<div class="activity-nav">
|
|
2275
|
+
<div class="activity-tabs">
|
|
2276
|
+
<button class="activity-tab" :class="{'active': activityTab==='activity'}" @click="activityTab='activity'">📊 Activity</button>
|
|
2277
|
+
<button class="activity-tab" :class="{'active': activityTab==='insights'}" @click="activityTab='insights'" x-show="selectedSpec?.implementation_summary || selectedSpec?.change_validation">🔍 Insights</button>
|
|
2278
|
+
</div>
|
|
2279
|
+
</div>
|
|
2280
|
+
<!-- Activity Content -->
|
|
2281
|
+
<!-- Activity Log Section - Timeline Visualization -->
|
|
2282
|
+
<div x-show="activityTab==='activity'" class="activity-content" style="padding: 1.5rem; flex: 1; display: flex; flex-direction: column;"
|
|
2283
|
+
>
|
|
2284
|
+
<div class="timeline-container" style="overflow-y: auto; flex: 1; min-height: fit-content;">
|
|
2285
|
+
<!-- Combined Timeline -->
|
|
2286
|
+
<template x-for="(event, index) in getActivityTimeline(selectedSpec)" :key="'timeline_' + index">
|
|
2287
|
+
<div class="timeline-item">
|
|
2288
|
+
<!-- Left side: Time and Tool info -->
|
|
2289
|
+
<div class="timeline-left">
|
|
2290
|
+
<div class="timeline-time" x-text="new Date(event.timestamp).toLocaleString()"></div>
|
|
2291
|
+
<div style="display: flex; align-items: center; justify-content: flex-end; gap: 0.5rem;">
|
|
2292
|
+
<span class="status-badge"
|
|
2293
|
+
:class="'role-' + (event.role || 'system').toLowerCase()"
|
|
2294
|
+
x-text="(event.role || 'System').toUpperCase()"
|
|
2295
|
+
</span>
|
|
2296
|
+
<div style="font-size: 1.2rem;" x-text="event.role === 'DEBUGGER' ? '🐛' : event.role === 'ENGINEER' ? '⚙️' : event.role === 'ARCHITECT' ? '📐' : event.role === 'ORCHESTRATOR' ? '🎯' : event.role === 'REVIEWER' ? '👁️' : event.role === 'system' ? '⚙️' : '🔧'"></div>
|
|
2297
|
+
</div>
|
|
2298
|
+
</div>
|
|
2299
|
+
|
|
2300
|
+
<!-- Timeline marker -->
|
|
2301
|
+
<div class="timeline-marker"
|
|
2302
|
+
:class="(event.role || 'system').toLowerCase()"></div>
|
|
2303
|
+
|
|
2304
|
+
<!-- Right side: Content -->
|
|
2305
|
+
<div class="timeline-right">
|
|
2306
|
+
<div class="timeline-content">
|
|
2307
|
+
<div class="timeline-role">
|
|
2308
|
+
<span x-text="event.human_description || event.action || event.note || event.message || event.issue || 'Activity recorded'"></span>
|
|
2309
|
+
</div>
|
|
2310
|
+
|
|
2311
|
+
<!-- Enhanced Engineer Data -->
|
|
2312
|
+
<div x-show="event.is_enhanced && event.engineer_data" style="margin: 0.75rem 0; font-size: 0.85rem; line-height: 1.5;">
|
|
2313
|
+
<!-- Layer Progress -->
|
|
2314
|
+
<div x-show="event.engineer_data.layer" style="background: rgba(59, 130, 246, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-blue);">
|
|
2315
|
+
<strong style="color: var(--accent-blue);">Layer:</strong>
|
|
2316
|
+
<span x-text="event.engineer_data.layer.charAt(0).toUpperCase() + event.engineer_data.layer.slice(1)" style="color: var(--text-primary); text-transform: capitalize;"></span>
|
|
2317
|
+
<span x-show="event.engineer_data.completion_percentage !== undefined" style="color: var(--text-muted); margin-left: 0.5rem;" x-text="'(' + event.engineer_data.completion_percentage + '% complete)'"></span>
|
|
2318
|
+
</div>
|
|
2319
|
+
|
|
2320
|
+
<!-- Build/Test Status -->
|
|
2321
|
+
<div x-show="event.engineer_data.build_status || event.engineer_data.test_status" style="display: flex; gap: 0.5rem; margin-bottom: 0.5rem;">
|
|
2322
|
+
<span x-show="event.engineer_data.build_status === 'success'" style="background: rgba(16, 185, 129, 0.2); color: var(--accent-green); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem;">✅ Build</span>
|
|
2323
|
+
<span x-show="event.engineer_data.build_status === 'failed'" style="background: rgba(239, 68, 68, 0.2); color: var(--accent-red); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem;">❌ Build</span>
|
|
2324
|
+
<span x-show="event.engineer_data.test_status === 'passed'" style="background: rgba(16, 185, 129, 0.2); color: var(--accent-green); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem;">🧪 Tests</span>
|
|
2325
|
+
<span x-show="event.engineer_data.test_status === 'failed'" style="background: rgba(239, 68, 68, 0.2); color: var(--accent-red); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem;">🧪 Tests</span>
|
|
2326
|
+
</div>
|
|
2327
|
+
</div>
|
|
2328
|
+
|
|
2329
|
+
<!-- Enhanced Debugger Data -->
|
|
2330
|
+
<div x-show="event.is_enhanced && event.debugger_data" style="margin: 0.75rem 0; font-size: 0.85rem; line-height: 1.5;">
|
|
2331
|
+
<div x-show="event.debugger_data.issue" style="background: rgba(239, 68, 68, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-red);">
|
|
2332
|
+
<strong style="color: var(--accent-red);">Issue:</strong> <span x-text="event.debugger_data.issue" style="color: var(--text-primary);"></span>
|
|
2333
|
+
</div>
|
|
2334
|
+
<div x-show="event.debugger_data.root_cause" style="background: rgba(245, 158, 11, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-orange);">
|
|
2335
|
+
<strong style="color: var(--accent-orange);">Root Cause:</strong> <span x-text="event.debugger_data.root_cause" style="color: var(--text-primary);"></span>
|
|
2336
|
+
</div>
|
|
2337
|
+
<div x-show="event.debugger_data.fix" style="background: rgba(16, 185, 129, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-green);">
|
|
2338
|
+
<strong style="color: var(--accent-green);">Fix:</strong> <span x-text="event.debugger_data.fix" style="color: var(--text-primary);"></span>
|
|
2339
|
+
</div>
|
|
2340
|
+
<div x-show="event.debugger_data.confidence" style="display: flex; align-items: center; gap: 0.5rem;">
|
|
2341
|
+
<span x-text="event.debugger_data.confidence === 'high' ? '🎯' : event.debugger_data.confidence === 'medium' ? '⚖️' : '❓'"></span>
|
|
2342
|
+
<span style="font-size: 0.75rem; color: var(--text-muted);">Confidence: </span>
|
|
2343
|
+
<span x-text="event.debugger_data.confidence" style="font-size: 0.75rem; color: var(--text-primary); text-transform: capitalize;"></span>
|
|
2344
|
+
</div>
|
|
2345
|
+
</div>
|
|
2346
|
+
|
|
2347
|
+
<!-- Enhanced Architect Data -->
|
|
2348
|
+
<div x-show="event.is_enhanced && event.architect_data" style="margin: 0.75rem 0; font-size: 0.85rem; line-height: 1.5;">
|
|
2349
|
+
<div x-show="event.architect_data.phase" style="background: rgba(139, 92, 246, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid #8b5cf6;">
|
|
2350
|
+
<strong style="color: #8b5cf6;">Phase:</strong>
|
|
2351
|
+
<span x-text="event.architect_data.phase.charAt(0).toUpperCase() + event.architect_data.phase.slice(1)" style="color: var(--text-primary); text-transform: capitalize;"></span>
|
|
2352
|
+
</div>
|
|
2353
|
+
|
|
2354
|
+
<!-- Clarifications with Status Badges -->
|
|
2355
|
+
<div x-show="event.architect_data.clarifications && event.architect_data.clarifications.length > 0" style="background: rgba(245, 158, 11, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-orange);">
|
|
2356
|
+
<strong style="color: var(--accent-orange);">Clarifications:</strong>
|
|
2357
|
+
<div style="margin-top: 0.5rem;">
|
|
2358
|
+
<template x-for="clarification in event.architect_data.clarifications">
|
|
2359
|
+
<div style="margin-bottom: 0.5rem; padding: 0.5rem; background: rgba(0, 0, 0, 0.2); border-radius: 4px;">
|
|
2360
|
+
<div style="display: flex; align-items: start; gap: 0.5rem;">
|
|
2361
|
+
<span x-show="clarification.status === 'pending'" style="background: rgba(245, 158, 11, 0.2); color: var(--accent-orange); padding: 0.125rem 0.375rem; border-radius: 3px; font-size: 0.65rem; flex-shrink: 0;">⏳ Awaiting</span>
|
|
2362
|
+
<span x-show="clarification.status === 'answered'" style="background: rgba(16, 185, 129, 0.2); color: var(--accent-green); padding: 0.125rem 0.375rem; border-radius: 3px; font-size: 0.65rem; flex-shrink: 0;">✅ Answered</span>
|
|
2363
|
+
<div style="flex: 1;">
|
|
2364
|
+
<div x-text="clarification.question" style="color: var(--text-primary); font-size: 0.8rem; margin-bottom: 0.25rem;"></div>
|
|
2365
|
+
<div x-show="clarification.answer" x-text="'Answer: ' + clarification.answer" style="color: var(--text-muted); font-size: 0.75rem; font-style: italic;"></div>
|
|
2366
|
+
</div>
|
|
2367
|
+
</div>
|
|
2368
|
+
</div>
|
|
2369
|
+
</template>
|
|
2370
|
+
</div>
|
|
2371
|
+
</div>
|
|
2372
|
+
|
|
2373
|
+
<div x-show="event.architect_data.design_decisions && event.architect_data.design_decisions.length > 0" style="background: rgba(59, 130, 246, 0.1); padding: 0.75rem; border-radius: 6px; border-left: 3px solid var(--accent-blue);">
|
|
2374
|
+
<strong style="color: var(--accent-blue);">Design Decisions:</strong>
|
|
2375
|
+
<ul style="margin: 0.5rem 0 0 1rem; color: var(--text-primary);">
|
|
2376
|
+
<template x-for="decision in event.architect_data.design_decisions">
|
|
2377
|
+
<li x-text="decision" style="margin-bottom: 0.25rem;"></li>
|
|
2378
|
+
</template>
|
|
2379
|
+
</ul>
|
|
2380
|
+
</div>
|
|
2381
|
+
</div>
|
|
2382
|
+
|
|
2383
|
+
<!-- Enhanced Orchestrator Data -->
|
|
2384
|
+
<div x-show="event.is_enhanced && event.orchestrator_data" style="margin: 0.75rem 0; font-size: 0.85rem; line-height: 1.5;">
|
|
2385
|
+
<div x-show="event.orchestrator_data.workflow_action" style="background: rgba(99, 102, 241, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid #6366f1;">
|
|
2386
|
+
<strong style="color: #6366f1;">Workflow:</strong>
|
|
2387
|
+
<span x-text="event.orchestrator_data.workflow_action" style="color: var(--text-primary);"></span>
|
|
2388
|
+
</div>
|
|
2389
|
+
|
|
2390
|
+
<div x-show="event.orchestrator_data.coordinated_roles && event.orchestrator_data.coordinated_roles.length > 0" style="background: rgba(139, 92, 246, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid #8b5cf6;">
|
|
2391
|
+
<strong style="color: #8b5cf6;">Coordinated Roles:</strong>
|
|
2392
|
+
<span x-text="event.orchestrator_data.coordinated_roles.join(', ')" style="color: var(--text-primary); margin-left: 0.5rem;"></span>
|
|
2393
|
+
</div>
|
|
2394
|
+
|
|
2395
|
+
<div x-show="event.orchestrator_data.milestones && event.orchestrator_data.milestones.length > 0" style="background: rgba(16, 185, 129, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-green);">
|
|
2396
|
+
<strong style="color: var(--accent-green);">Milestones:</strong>
|
|
2397
|
+
<ul style="margin: 0.5rem 0 0 1rem; color: var(--text-primary);">
|
|
2398
|
+
<template x-for="milestone in event.orchestrator_data.milestones">
|
|
2399
|
+
<li x-text="milestone" style="margin-bottom: 0.25rem;"></li>
|
|
2400
|
+
</template>
|
|
2401
|
+
</ul>
|
|
2402
|
+
</div>
|
|
2403
|
+
|
|
2404
|
+
<div x-show="event.orchestrator_data.blockers && event.orchestrator_data.blockers.length > 0" style="background: rgba(239, 68, 68, 0.1); padding: 0.75rem; border-radius: 6px; border-left: 3px solid var(--accent-red);">
|
|
2405
|
+
<strong style="color: var(--accent-red);">Blockers:</strong>
|
|
2406
|
+
<ul style="margin: 0.5rem 0 0 1rem; color: var(--text-primary);">
|
|
2407
|
+
<template x-for="blocker in event.orchestrator_data.blockers">
|
|
2408
|
+
<li x-text="blocker" style="margin-bottom: 0.25rem;"></li>
|
|
2409
|
+
</template>
|
|
2410
|
+
</ul>
|
|
2411
|
+
</div>
|
|
2412
|
+
</div>
|
|
2413
|
+
|
|
2414
|
+
<!-- Legacy Root cause & fix (debug logs) -->
|
|
2415
|
+
<div x-show="!event.is_enhanced && event.root_cause" style="margin: 0.75rem 0; font-size: 0.85rem; line-height: 1.5;">
|
|
2416
|
+
<div style="color: var(--text-secondary); background: rgba(239, 68, 68, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-red);"><strong style="color: var(--accent-red);">Root cause:</strong> <span x-text="event.root_cause" style="color: var(--text-primary);"></span></div>
|
|
2417
|
+
<div x-show="event.fix" style="color: var(--text-secondary); background: rgba(16, 185, 129, 0.1); padding: 0.75rem; border-radius: 6px; border-left: 3px solid var(--accent-green);"><strong style="color: var(--accent-green);">Fix:</strong> <span x-text="event.fix" style="color: var(--text-primary);"></span></div>
|
|
2418
|
+
</div>
|
|
2419
|
+
|
|
2420
|
+
<!-- Enhanced Verbose Activity Details -->
|
|
2421
|
+
<div x-show="event.data?.verbose_details" style="margin: 0.75rem 0;">
|
|
2422
|
+
|
|
2423
|
+
<!-- Engineer Mode Details -->
|
|
2424
|
+
<div x-show="event.data?.mode === 'engineer' && event.data?.verbose_details?.engineer" style="font-size: 0.85rem; line-height: 1.5;">
|
|
2425
|
+
<div x-show="event.data.verbose_details.engineer.completion_percentage !== undefined" style="background: rgba(59, 130, 246, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-blue);">
|
|
2426
|
+
<strong style="color: var(--accent-blue);">Progress:</strong>
|
|
2427
|
+
<span x-text="event.data.verbose_details.engineer.completion_percentage + '% complete'" style="color: var(--text-primary);"></span>
|
|
2428
|
+
</div>
|
|
2429
|
+
|
|
2430
|
+
<div x-show="event.data.verbose_details.engineer.tasks_completed?.length" style="background: rgba(16, 185, 129, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-green);">
|
|
2431
|
+
<strong style="color: var(--accent-green);">Tasks Completed:</strong>
|
|
2432
|
+
<ul style="margin: 0.5rem 0 0 1rem; color: var(--text-primary);">
|
|
2433
|
+
<template x-for="task in event.data.verbose_details.engineer.tasks_completed">
|
|
2434
|
+
<li x-text="task" style="margin-bottom: 0.25rem;"></li>
|
|
2435
|
+
</template>
|
|
2436
|
+
</ul>
|
|
2437
|
+
</div>
|
|
2438
|
+
|
|
2439
|
+
<div x-show="event.data.verbose_details.engineer.tasks_remaining?.length" style="background: rgba(245, 158, 11, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-orange);">
|
|
2440
|
+
<strong style="color: var(--accent-orange);">Remaining Tasks:</strong>
|
|
2441
|
+
<ul style="margin: 0.5rem 0 0 1rem; color: var(--text-primary);">
|
|
2442
|
+
<template x-for="task in event.data.verbose_details.engineer.tasks_remaining">
|
|
2443
|
+
<li x-text="task" style="margin-bottom: 0.25rem;"></li>
|
|
2444
|
+
</template>
|
|
2445
|
+
</ul>
|
|
2446
|
+
</div>
|
|
2447
|
+
|
|
2448
|
+
<div x-show="event.data.verbose_details.engineer.build_status || event.data.verbose_details.engineer.test_status" style="display: flex; gap: 0.5rem; margin-bottom: 0.5rem;">
|
|
2449
|
+
<span x-show="event.data.verbose_details.engineer.build_status === 'success'" style="background: rgba(16, 185, 129, 0.2); color: var(--accent-green); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem;">✅ Build Passed</span>
|
|
2450
|
+
<span x-show="event.data.verbose_details.engineer.build_status === 'failed'" style="background: rgba(239, 68, 68, 0.2); color: var(--accent-red); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem;">❌ Build Failed</span>
|
|
2451
|
+
<span x-show="event.data.verbose_details.engineer.test_status === 'passed'" style="background: rgba(16, 185, 129, 0.2); color: var(--accent-green); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem;">🧪 Tests Passed</span>
|
|
2452
|
+
<span x-show="event.data.verbose_details.engineer.test_status === 'failed'" style="background: rgba(239, 68, 68, 0.2); color: var(--accent-red); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem;">🧪 Tests Failed</span>
|
|
2453
|
+
</div>
|
|
2454
|
+
</div>
|
|
2455
|
+
|
|
2456
|
+
<!-- Debugger Mode Details -->
|
|
2457
|
+
<div x-show="event.data?.mode === 'debugger' && event.data?.verbose_details?.debugger" style="font-size: 0.85rem; line-height: 1.5;">
|
|
2458
|
+
<div x-show="event.data.verbose_details.debugger.issue_description" style="background: rgba(239, 68, 68, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-red);">
|
|
2459
|
+
<strong style="color: var(--accent-red);">Issue:</strong>
|
|
2460
|
+
<span x-text="event.data.verbose_details.debugger.issue_description" style="color: var(--text-primary);"></span>
|
|
2461
|
+
</div>
|
|
2462
|
+
|
|
2463
|
+
<div x-show="event.data.verbose_details.debugger.reproduction_steps?.length" style="background: rgba(156, 163, 175, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--text-muted);">
|
|
2464
|
+
<strong style="color: var(--text-muted);">Reproduction Steps:</strong>
|
|
2465
|
+
<ol style="margin: 0.5rem 0 0 1rem; color: var(--text-primary);">
|
|
2466
|
+
<template x-for="step in event.data.verbose_details.debugger.reproduction_steps">
|
|
2467
|
+
<li x-text="step" style="margin-bottom: 0.25rem;"></li>
|
|
2468
|
+
</template>
|
|
2469
|
+
</ol>
|
|
2470
|
+
</div>
|
|
2471
|
+
|
|
2472
|
+
<div x-show="event.data.verbose_details.debugger.confidence_level" style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
|
|
2473
|
+
<span x-text="event.data.verbose_details.debugger.confidence_level === 'high' ? '🎯' : event.data.verbose_details.debugger.confidence_level === 'medium' ? '⚖️' : '❓'"></span>
|
|
2474
|
+
<span style="font-size: 0.75rem; color: var(--text-muted);">Confidence: </span>
|
|
2475
|
+
<span x-text="event.data.verbose_details.debugger.confidence_level" style="font-size: 0.75rem; color: var(--text-primary); text-transform: capitalize;"></span>
|
|
2476
|
+
</div>
|
|
2477
|
+
</div>
|
|
2478
|
+
|
|
2479
|
+
<!-- Architect Mode Details -->
|
|
2480
|
+
<div x-show="event.data?.mode === 'architect' && event.data?.verbose_details?.architect" style="font-size: 0.85rem; line-height: 1.5;">
|
|
2481
|
+
<div x-show="event.data.verbose_details.architect.research_findings?.length" style="background: rgba(139, 92, 246, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid #8b5cf6;">
|
|
2482
|
+
<strong style="color: #8b5cf6;">Research Findings:</strong>
|
|
2483
|
+
<ul style="margin: 0.5rem 0 0 1rem; color: var(--text-primary);">
|
|
2484
|
+
<template x-for="finding in event.data.verbose_details.architect.research_findings">
|
|
2485
|
+
<li x-text="finding" style="margin-bottom: 0.25rem;"></li>
|
|
2486
|
+
</template>
|
|
2487
|
+
</ul>
|
|
2488
|
+
</div>
|
|
2489
|
+
|
|
2490
|
+
<div x-show="event.data.verbose_details.architect.design_decisions?.length" style="background: rgba(245, 158, 11, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-orange);">
|
|
2491
|
+
<strong style="color: var(--accent-orange);">Design Decisions:</strong>
|
|
2492
|
+
<ul style="margin: 0.5rem 0 0 1rem; color: var(--text-primary);">
|
|
2493
|
+
<template x-for="decision in event.data.verbose_details.architect.design_decisions">
|
|
2494
|
+
<li x-text="decision" style="margin-bottom: 0.25rem;"></li>
|
|
2495
|
+
</template>
|
|
2496
|
+
</ul>
|
|
2497
|
+
</div>
|
|
2498
|
+
|
|
2499
|
+
<div x-show="event.data.verbose_details.architect.architecture_patterns?.length" style="background: rgba(59, 130, 246, 0.1); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-blue);">
|
|
2500
|
+
<strong style="color: var(--accent-blue);">Architecture Patterns:</strong>
|
|
2501
|
+
<div style="margin: 0.5rem 0 0 0; color: var(--text-primary);">
|
|
2502
|
+
<span x-text="event.data.verbose_details.architect.architecture_patterns.join(', ')"></span>
|
|
2503
|
+
</div>
|
|
2504
|
+
</div>
|
|
2505
|
+
</div>
|
|
2506
|
+
|
|
2507
|
+
</div>
|
|
2508
|
+
|
|
2509
|
+
<!-- Task/Commit Info -->
|
|
2510
|
+
<div x-show="event.task_id || event.commit_hash" style="display: flex; align-items: center; gap: 1rem; font-size: 0.75rem; color: var(--text-muted); margin-top: 0.5rem;">
|
|
2511
|
+
<span x-show="event.task_id" x-text="'Task: ' + event.task_id"></span>
|
|
2512
|
+
<span x-show="event.commit_hash" x-text="'Commit: ' + event.commit_hash.substring(0, 8)"></span>
|
|
2513
|
+
</div>
|
|
2514
|
+
|
|
2515
|
+
<!-- Files Modified - Compact Git Style (Enhanced Activities) -->
|
|
2516
|
+
<div x-show="event.is_enhanced && event.files_changed && event.files_changed.length > 0" style="margin-top: 0.75rem;">
|
|
2517
|
+
<div style="color: var(--text-muted); margin-bottom: 0.5rem; font-size: 0.75rem; font-weight: 600;">
|
|
2518
|
+
📁 <span x-text="event.files_changed.length + ' file' + (event.files_changed.length > 1 ? 's' : '') + ' changed'"></span>
|
|
2519
|
+
</div>
|
|
2520
|
+
|
|
2521
|
+
<!-- Compact File List - Git Style -->
|
|
2522
|
+
<div style="font-family: 'Monaco', 'Menlo', monospace; font-size: 0.7rem; color: var(--text-muted); line-height: 1.6;">
|
|
2523
|
+
<template x-for="(file, fileIdx) in event.files_changed" :key="event.id + '_file_' + fileIdx">
|
|
2524
|
+
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
|
2525
|
+
<!-- Change Type (M/A/D) -->
|
|
2526
|
+
<span style="font-weight: 700; min-width: 12px;"
|
|
2527
|
+
:style="file.change_type === 'added' ? 'color: #22c55e;' :
|
|
2528
|
+
file.change_type === 'deleted' ? 'color: #ef4444;' :
|
|
2529
|
+
'color: #f59e0b;'"
|
|
2530
|
+
x-text="file.change_type === 'added' ? 'A' : file.change_type === 'deleted' ? 'D' : 'M'">
|
|
2531
|
+
</span>
|
|
2532
|
+
|
|
2533
|
+
<!-- File Path -->
|
|
2534
|
+
<span style="color: var(--text-secondary);" x-text="file.path"></span>
|
|
2535
|
+
|
|
2536
|
+
<!-- Git Diff Summary (+x / -y) -->
|
|
2537
|
+
<span x-show="file.lines_added > 0 || file.lines_removed > 0" style="color: var(--text-muted);">
|
|
2538
|
+
<span x-show="file.lines_added > 0" style="color: #22c55e;" x-text="'+' + file.lines_added"></span><span x-show="file.lines_added > 0 && file.lines_removed > 0"> / </span><span x-show="file.lines_removed > 0" style="color: #ef4444;" x-text="'-' + file.lines_removed"></span>
|
|
2539
|
+
</span>
|
|
2540
|
+
</div>
|
|
2541
|
+
</template>
|
|
2542
|
+
</div>
|
|
2543
|
+
</div>
|
|
2544
|
+
|
|
2545
|
+
<!-- Files Modified - Legacy Format (Fallback) -->
|
|
2546
|
+
<div x-show="!event.is_enhanced && event.files_changed && event.files_changed.length > 0" style="margin-top: 0.5rem;">
|
|
2547
|
+
<div style="color: var(--text-muted); margin-bottom: 0.25rem; font-size: 0.8rem;">
|
|
2548
|
+
<span x-text="event.files_changed.length + ' file(s) changed'"></span>
|
|
2549
|
+
</div>
|
|
2550
|
+
</div>
|
|
2551
|
+
|
|
2552
|
+
<!-- Branch Info -->
|
|
2553
|
+
<div x-show="event.branch" style="margin-top: 0.5rem; font-size: 0.7rem; color: var(--text-muted); font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;">
|
|
2554
|
+
<span x-text="'Branch: ' + event.branch"></span>
|
|
2555
|
+
</div>
|
|
2556
|
+
</div>
|
|
2557
|
+
</div>
|
|
2558
|
+
</div>
|
|
2559
|
+
</template>
|
|
2560
|
+
|
|
2561
|
+
<!-- Timeline line positioned after all items are rendered -->
|
|
2562
|
+
<div class="timeline-line"></div>
|
|
2563
|
+
|
|
2564
|
+
<div x-show="getActivityTimeline(selectedSpec).length === 0"
|
|
2565
|
+
style="text-align: center; padding: 3rem; color: var(--text-muted); font-size: 0.875rem;">
|
|
2566
|
+
<div style="font-size: 2rem; margin-bottom: 1rem;">📋</div>
|
|
2567
|
+
<div>No activity recorded yet</div>
|
|
2568
|
+
</div>
|
|
2569
|
+
</div>
|
|
2570
|
+
</div>
|
|
2571
|
+
|
|
2572
|
+
<!-- Files Modified Section -->
|
|
2573
|
+
<div x-show="activityTab==='files'" class="activity-content" style="padding: 1.5rem; flex: 1;">
|
|
2574
|
+
<div style="max-height: 500px; overflow-y: auto; padding: 0.5rem 0;">
|
|
2575
|
+
<!-- GitHub Desktop Style File List -->
|
|
2576
|
+
<div style="background: rgba(255, 255, 255, 0.02); border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.05); overflow: visible; max-height: none;">
|
|
2577
|
+
|
|
2578
|
+
<!-- Files from execution logs -->
|
|
2579
|
+
<template x-for="(log, logIndex) in (selectedSpec?.execution_logs || selectedSpec?.execution_log || [])" :key="log.timestamp + 'exec'">
|
|
2580
|
+
<template x-for="(file, fileIndex) in (log.files_changed || [])" :key="log.timestamp + file + fileIndex">
|
|
2581
|
+
<div style="display: flex; align-items: center; padding: 0.75rem 1rem; border-bottom: 1px solid rgba(255, 255, 255, 0.05); transition: background 0.2s ease;"
|
|
2582
|
+
@mouseenter="$el.style.background = 'rgba(255, 255, 255, 0.05)'"
|
|
2583
|
+
@mouseleave="$el.style.background = 'transparent'">
|
|
2584
|
+
|
|
2585
|
+
<!-- Status Indicator (GitHub Desktop style) -->
|
|
2586
|
+
<div style="width: 18px; height: 18px; margin-right: 0.75rem; display: flex; align-items: center; justify-content: center; border-radius: 3px; background: rgba(34, 197, 94, 0.15); color: #22c55e; font-size: 0.7rem; font-weight: 600; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;">
|
|
2587
|
+
M
|
|
2588
|
+
</div>
|
|
2589
|
+
|
|
2590
|
+
<!-- File Icon and Name -->
|
|
2591
|
+
<div style="flex: 1; min-width: 0; display: flex; align-items: center; gap: 0.5rem;">
|
|
2592
|
+
<div style="font-size: 1rem;" x-text="getFileIcon(file)"></div>
|
|
2593
|
+
<div style="min-width: 0; flex: 1;">
|
|
2594
|
+
<div style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.875rem; color: var(--text-primary); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" x-text="getFileName(file)"></div>
|
|
2595
|
+
<div style="font-size: 0.75rem; color: var(--text-muted); margin-top: 0.125rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" x-text="simplifyFilePath(file)"></div>
|
|
2596
|
+
</div>
|
|
2597
|
+
</div>
|
|
2598
|
+
|
|
2599
|
+
<!-- Git Diff Statistics (GitHub Desktop style) -->
|
|
2600
|
+
<div style="margin-left: auto; display: flex; align-items: center; gap: 0.5rem; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.75rem;"
|
|
2601
|
+
x-data="{ gitDiffData: null, loading: true }"
|
|
2602
|
+
x-init="
|
|
2603
|
+
(async () => {
|
|
2604
|
+
try {
|
|
2605
|
+
const response = await fetch(`/api/git-diff?file=${encodeURIComponent(file)}`);
|
|
2606
|
+
if (response.ok) {
|
|
2607
|
+
gitDiffData = await response.json();
|
|
2608
|
+
}
|
|
2609
|
+
} catch (e) {
|
|
2610
|
+
console.log('Could not fetch git diff for', file);
|
|
2611
|
+
} finally {
|
|
2612
|
+
loading = false;
|
|
2613
|
+
}
|
|
2614
|
+
})()
|
|
2615
|
+
">
|
|
2616
|
+
<div x-show="loading" style="color: var(--text-muted); width: 60px; text-align: center; font-size: 0.65rem;">...</div>
|
|
2617
|
+
<div x-show="!loading && gitDiffData && (gitDiffData.lines_added > 0 || gitDiffData.lines_removed > 0)" style="display: flex; align-items: center; gap: 0.375rem;">
|
|
2618
|
+
<span x-show="gitDiffData.lines_added > 0" style="color: #22c55e; background: rgba(34, 197, 94, 0.1); padding: 0.125rem 0.375rem; border-radius: 12px; font-size: 0.7rem;" x-text="`+${gitDiffData.lines_added}`"></span>
|
|
2619
|
+
<span x-show="gitDiffData.lines_removed > 0" style="color: #ef4444; background: rgba(239, 68, 68, 0.1); padding: 0.125rem 0.375rem; border-radius: 12px; font-size: 0.7rem;" x-text="`−${gitDiffData.lines_removed}`"></span>
|
|
2620
|
+
</div>
|
|
2621
|
+
<div x-show="!loading && (!gitDiffData || (gitDiffData.lines_added === 0 && gitDiffData.lines_removed === 0))" style="color: var(--text-muted); width: 60px; text-align: center; font-size: 0.65rem;">—</div>
|
|
2622
|
+
</div>
|
|
2623
|
+
|
|
2624
|
+
<!-- Timestamp -->
|
|
2625
|
+
<div style="margin-left: 1rem; font-size: 0.7rem; color: var(--text-muted); min-width: 60px; text-align: right;" x-text="new Date(log.timestamp).toLocaleTimeString('en-US', {hour: '2-digit', minute: '2-digit'})"></div>
|
|
2626
|
+
</div>
|
|
2627
|
+
</template>
|
|
2628
|
+
</template>
|
|
2629
|
+
|
|
2630
|
+
<!-- Empty state -->
|
|
2631
|
+
<div x-show="(!selectedSpec?.execution_logs || selectedSpec.execution_logs.length === 0) && (!selectedSpec?.execution_log || selectedSpec.execution_log.length === 0)" style="text-align: center; padding: 2rem; color: var(--text-muted); font-size: 0.875rem; display: flex; flex-direction: column; align-items: center; gap: 0.5rem;">
|
|
2632
|
+
<div style="font-size: 1.5rem;">📁</div>
|
|
2633
|
+
<div>No files modified yet</div>
|
|
2634
|
+
<div style="font-size: 0.75rem; opacity: 0.7;">File changes will appear here during implementation</div>
|
|
2635
|
+
</div>
|
|
2636
|
+
</div>
|
|
2637
|
+
|
|
2638
|
+
</div>
|
|
2639
|
+
</div>
|
|
2640
|
+
|
|
2641
|
+
<!-- Insights Section -->
|
|
2642
|
+
<div x-show="activityTab==='insights'" class="activity-content" style="padding: 1.5rem; flex: 1;">
|
|
2643
|
+
<div style="max-height: 500px; overflow-y: auto; padding: 0.5rem 0;">
|
|
2644
|
+
|
|
2645
|
+
<!-- Compliance Overview -->
|
|
2646
|
+
<div x-show="selectedSpec?.change_validation" style="background: rgba(34, 197, 94, 0.05); border-radius: 8px; padding: 1rem; margin-bottom: 1rem; border: 1px solid rgba(34, 197, 94, 0.2);">
|
|
2647
|
+
<h4 style="margin: 0 0 0.75rem 0; color: #22c55e; font-size: 0.9rem; display: flex; align-items: center; gap: 0.5rem;">
|
|
2648
|
+
<span>🔍</span>
|
|
2649
|
+
<span>Compliance Status</span>
|
|
2650
|
+
</h4>
|
|
2651
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 0.5rem; margin-bottom: 1rem;">
|
|
2652
|
+
<div style="text-align: center; padding: 0.5rem; background: rgba(255, 255, 255, 0.05); border-radius: 6px;">
|
|
2653
|
+
<div style="font-size: 1.5rem; margin-bottom: 0.25rem;" x-text="selectedSpec?.change_validation?.overall_compliance === 'compliant' ? '✅' : selectedSpec?.change_validation?.overall_compliance === 'partial' ? '⚠️' : '❌'"></div>
|
|
2654
|
+
<div style="font-size: 0.8rem; color: var(--text-secondary);" x-text="selectedSpec?.change_validation?.overall_compliance || 'Unknown'"></div>
|
|
2655
|
+
</div>
|
|
2656
|
+
</div>
|
|
2657
|
+
<div x-show="selectedSpec?.change_validation?.recommendations?.length > 0">
|
|
2658
|
+
<div style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem;">Recommendations:</div>
|
|
2659
|
+
<template x-for="rec in selectedSpec?.change_validation?.recommendations" :key="rec">
|
|
2660
|
+
<div style="font-size: 0.8rem; color: var(--text-primary); margin-bottom: 0.25rem; padding-left: 1rem;" x-text="rec"></div>
|
|
2661
|
+
</template>
|
|
2662
|
+
</div>
|
|
2663
|
+
</div>
|
|
2664
|
+
|
|
2665
|
+
<!-- Implementation Summary -->
|
|
2666
|
+
<div x-show="selectedSpec?.implementation_summary" style="background: rgba(99, 102, 241, 0.05); border-radius: 8px; padding: 1rem; margin-bottom: 1rem; border: 1px solid rgba(99, 102, 241, 0.2);">
|
|
2667
|
+
<h4 style="margin: 0 0 0.75rem 0; color: #818cf8; font-size: 0.9rem; display: flex; align-items: center; gap: 0.5rem;">
|
|
2668
|
+
<span>📊</span>
|
|
2669
|
+
<span>Implementation Progress</span>
|
|
2670
|
+
</h4>
|
|
2671
|
+
|
|
2672
|
+
<!-- Progress Stats -->
|
|
2673
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); gap: 0.5rem; margin-bottom: 1rem;">
|
|
2674
|
+
<div style="text-align: center; padding: 0.5rem; background: rgba(255, 255, 255, 0.05); border-radius: 6px;">
|
|
2675
|
+
<div style="font-size: 1.2rem; font-weight: 600; color: var(--text-primary);" x-text="selectedSpec?.implementation_summary?.files_changed_count || 0"></div>
|
|
2676
|
+
<div style="font-size: 0.7rem; color: var(--text-secondary);">Files</div>
|
|
2677
|
+
</div>
|
|
2678
|
+
<div style="text-align: center; padding: 0.5rem; background: rgba(255, 255, 255, 0.05); border-radius: 6px;">
|
|
2679
|
+
<div style="font-size: 1.2rem; font-weight: 600; color: var(--text-primary);" x-text="selectedSpec?.implementation_summary?.git_commits_count || 0"></div>
|
|
2680
|
+
<div style="font-size: 0.7rem; color: var(--text-secondary);">Commits</div>
|
|
2681
|
+
</div>
|
|
2682
|
+
<div style="text-align: center; padding: 0.5rem; background: rgba(255, 255, 255, 0.05); border-radius: 6px;">
|
|
2683
|
+
<div style="font-size: 1.2rem; font-weight: 600;" :style="`color: ${selectedSpec?.implementation_summary?.compliance_overview?.overall_status === 'compliant' ? '#22c55e' : selectedSpec?.implementation_summary?.compliance_overview?.overall_status === 'partial' ? '#f59e0b' : '#ef4444'}`" x-text="Math.round((selectedSpec?.implementation_summary?.compliance_overview?.compliant_files || 0) / Math.max(selectedSpec?.implementation_summary?.compliance_overview?.total_files || 1, 1) * 100) + '%'"></div>
|
|
2684
|
+
<div style="font-size: 0.7rem; color: var(--text-secondary);">Compliant</div>
|
|
2685
|
+
</div>
|
|
2686
|
+
</div>
|
|
2687
|
+
|
|
2688
|
+
<!-- Implementation Notes -->
|
|
2689
|
+
<div x-show="selectedSpec?.implementation_summary?.implementation_notes?.length > 0">
|
|
2690
|
+
<div style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem;">Recent Progress:</div>
|
|
2691
|
+
<template x-for="note in selectedSpec?.implementation_summary?.implementation_notes.slice(0, 3)" :key="note">
|
|
2692
|
+
<div style="font-size: 0.8rem; color: var(--text-primary); margin-bottom: 0.5rem; padding: 0.5rem; background: rgba(255, 255, 255, 0.02); border-radius: 4px;" x-text="note"></div>
|
|
2693
|
+
</template>
|
|
2694
|
+
</div>
|
|
2695
|
+
|
|
2696
|
+
<!-- Actionable Insights -->
|
|
2697
|
+
<div x-show="selectedSpec?.implementation_summary?.actionable_insights?.length > 0">
|
|
2698
|
+
<div style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem;">Actionable Insights:</div>
|
|
2699
|
+
<template x-for="insight in selectedSpec?.implementation_summary?.actionable_insights" :key="insight">
|
|
2700
|
+
<div style="font-size: 0.8rem; color: var(--accent-blue); margin-bottom: 0.25rem; padding-left: 1rem;" x-text="insight"></div>
|
|
2701
|
+
</template>
|
|
2702
|
+
</div>
|
|
2703
|
+
</div>
|
|
2704
|
+
|
|
2705
|
+
<!-- Empty State -->
|
|
2706
|
+
<div x-show="!selectedSpec?.implementation_summary && !selectedSpec?.change_validation" style="text-align: center; padding: 3rem; color: var(--text-muted); font-size: 0.875rem;">
|
|
2707
|
+
<div style="font-size: 2rem; margin-bottom: 1rem;">🔍</div>
|
|
2708
|
+
<div>No insights available yet</div>
|
|
2709
|
+
<div style="font-size: 0.8rem; margin-top: 0.5rem;">Implementation summaries and validation results will appear here</div>
|
|
2710
|
+
</div>
|
|
2711
|
+
</div>
|
|
2712
|
+
</div>
|
|
2713
|
+
|
|
2714
|
+
</div>
|
|
2715
|
+
</div>
|
|
2716
|
+
</div>
|
|
2717
|
+
</div>
|
|
2718
|
+
|
|
2719
|
+
<!-- Activity Log Modal -->
|
|
2720
|
+
<div class="modal-overlay"
|
|
2721
|
+
x-show="showActivityLog"
|
|
2722
|
+
x-transition:enter="transition ease-out duration-300"
|
|
2723
|
+
x-transition:enter-start="opacity-0"
|
|
2724
|
+
x-transition:enter-end="opacity-100"
|
|
2725
|
+
x-transition:leave="transition ease-in duration-200"
|
|
2726
|
+
x-transition:leave-start="opacity-100"
|
|
2727
|
+
x-transition:leave-end="opacity-0"
|
|
2728
|
+
@click.self="showActivityLog=false">
|
|
2729
|
+
|
|
2730
|
+
<div class="modal-content"
|
|
2731
|
+
x-show="showActivityLog"
|
|
2732
|
+
x-transition:enter="transition ease-out duration-300"
|
|
2733
|
+
x-transition:enter-start="opacity-0 scale-95"
|
|
2734
|
+
x-transition:enter-end="opacity-100 scale-100"
|
|
2735
|
+
x-transition:leave="transition ease-in duration-200"
|
|
2736
|
+
x-transition:leave-start="opacity-100 scale-100"
|
|
2737
|
+
x-transition:leave-end="opacity-0 scale-95"
|
|
2738
|
+
style="grid-template-columns: 1fr; grid-template-rows: auto 1fr;">
|
|
2739
|
+
|
|
2740
|
+
<!-- Activity Log Header -->
|
|
2741
|
+
<div class="modal-header">
|
|
2742
|
+
<h2 class="heading" style="margin: 0; font-size: 1.5rem;">📊 Activity Log</h2>
|
|
2743
|
+
<div style="display: flex; gap: 1rem;">
|
|
2744
|
+
<button class="btn btn-secondary" @click="showActivityLog=false">
|
|
2745
|
+
✕ Close
|
|
2746
|
+
</button>
|
|
2747
|
+
</div>
|
|
2748
|
+
</div>
|
|
2749
|
+
|
|
2750
|
+
<!-- Activity Log Content -->
|
|
2751
|
+
<div class="modal-main">
|
|
2752
|
+
<div style="margin-bottom: 1.5rem;">
|
|
2753
|
+
<h3 class="heading" style="margin: 0 0 1rem 0;">Recent Activity</h3>
|
|
2754
|
+
<div style="background: rgba(255, 255, 255, 0.03); padding: 1.5rem; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.05);">
|
|
2755
|
+
|
|
2756
|
+
<template x-for="activity in activities" :key="activity.timestamp">
|
|
2757
|
+
<div style="border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding: 1rem 0;">
|
|
2758
|
+
<div style="display: flex; justify-content: between; align-items: start; margin-bottom: 0.5rem;">
|
|
2759
|
+
<div>
|
|
2760
|
+
<span class="status-badge"
|
|
2761
|
+
:class="{
|
|
2762
|
+
'status-completed': activity.type === 'spec_created',
|
|
2763
|
+
'status-in-progress': activity.type === 'worktree_created',
|
|
2764
|
+
'status-planning': activity.type === 'commit',
|
|
2765
|
+
'status-draft': activity.type === 'spec_updated'
|
|
2766
|
+
}"
|
|
2767
|
+
x-text="activity.type.replace('_', ' ')"></span>
|
|
2768
|
+
<span style="margin-left: 1rem; color: var(--text-secondary); font-size: 0.875rem;"
|
|
2769
|
+
x-text="new Date(activity.timestamp).toLocaleString()"></span>
|
|
2770
|
+
</div>
|
|
2771
|
+
</div>
|
|
2772
|
+
<div style="color: var(--text-primary); margin-bottom: 0.5rem;"
|
|
2773
|
+
x-text="activity.message"></div>
|
|
2774
|
+
<div style="color: var(--text-secondary); font-size: 0.875rem;">
|
|
2775
|
+
<span>by </span>
|
|
2776
|
+
<span style="color: var(--accent-blue);" x-text="activity.author"></span>
|
|
2777
|
+
<template x-if="activity.files">
|
|
2778
|
+
<span>
|
|
2779
|
+
• Modified: <span x-text="activity.files.join(', ')"></span>
|
|
2780
|
+
</span>
|
|
2781
|
+
</template>
|
|
2782
|
+
</div>
|
|
2783
|
+
</div>
|
|
2784
|
+
</template>
|
|
2785
|
+
|
|
2786
|
+
<div x-show="activities.length === 0" style="text-align: center; padding: 2rem; color: var(--text-muted);">
|
|
2787
|
+
No recent activity
|
|
2788
|
+
</div>
|
|
2789
|
+
</div>
|
|
2790
|
+
</div>
|
|
2791
|
+
</div>
|
|
2792
|
+
</div>
|
|
2793
|
+
</div>
|
|
2794
|
+
|
|
2795
|
+
<!-- Worktree Modal -->
|
|
2796
|
+
<div class="modal-overlay"
|
|
2797
|
+
x-show="showWorktreeModal"
|
|
2798
|
+
x-transition:enter="transition ease-out duration-300"
|
|
2799
|
+
x-transition:enter-start="opacity-0"
|
|
2800
|
+
x-transition:enter-end="opacity-100"
|
|
2801
|
+
x-transition:leave="transition ease-in duration-200"
|
|
2802
|
+
x-transition:leave-start="opacity-100"
|
|
2803
|
+
x-transition:leave-end="opacity-0"
|
|
2804
|
+
@click.self="showWorktreeModal=false">
|
|
2805
|
+
<div class="modal-content"
|
|
2806
|
+
x-show="showWorktreeModal"
|
|
2807
|
+
x-transition:enter="transition ease-out duration-300"
|
|
2808
|
+
x-transition:enter-start="opacity-0 scale-95"
|
|
2809
|
+
x-transition:enter-end="opacity-100 scale-100"
|
|
2810
|
+
x-transition:leave="transition ease-in duration-200"
|
|
2811
|
+
x-transition:leave-start="opacity-100 scale-100"
|
|
2812
|
+
x-transition:leave-end="opacity-0 scale-95"
|
|
2813
|
+
style="width: 99vw; height: 98vh; max-width: none; min-width: 1440px; min-height: 960px; display: grid; grid-template-columns: 1fr; grid-template-rows: auto 1fr; overflow: hidden;">
|
|
2814
|
+
<!-- Worktree Modal Header -->
|
|
2815
|
+
<div class="modal-header" style="background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(245, 158, 11, 0.08) 100%); border-bottom: 1px solid rgba(245, 158, 11, 0.2);">
|
|
2816
|
+
<h2 class="heading" style="margin: 0; font-size: 1.5rem; color: var(--text-primary); display: flex; align-items: center; gap: 0.75rem;">
|
|
2817
|
+
<span style="color: var(--accent-orange); font-size: 1.75rem;">🏗️</span>
|
|
2818
|
+
<span>Active Worktrees</span>
|
|
2819
|
+
<span style="color: var(--text-muted); font-size: 0.9rem; font-weight: 400;">(Construction Zones)</span>
|
|
2820
|
+
</h2>
|
|
2821
|
+
<button class="btn btn-secondary" @click="showWorktreeModal=false"
|
|
2822
|
+
style="background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.3); color: var(--accent-orange);"
|
|
2823
|
+
@mouseenter="$el.style.background = 'rgba(245, 158, 11, 0.2)'"
|
|
2824
|
+
@mouseleave="$el.style.background = 'rgba(245, 158, 11, 0.1)'">
|
|
2825
|
+
🔧 Close
|
|
2826
|
+
</button>
|
|
2827
|
+
</div>
|
|
2828
|
+
|
|
2829
|
+
<!-- Worktree Modal Content -->
|
|
2830
|
+
<div class="modal-main" x-data="{
|
|
2831
|
+
worktreeDetails: [],
|
|
2832
|
+
async loadWorktreeDetails() {
|
|
2833
|
+
try {
|
|
2834
|
+
const response = await fetch('/api/tools/bob.worktree.list');
|
|
2835
|
+
const data = await response.json();
|
|
2836
|
+
this.worktreeDetails = [
|
|
2837
|
+
...data.committed?.map(item => ({ name: item.name, status: 'committed', color: 'var(--accent-green)', icon: '✅' })) || [],
|
|
2838
|
+
...data.dirty?.map(item => ({ name: item.name, status: 'dirty', color: 'var(--accent-yellow)', icon: '⚠️' })) || [],
|
|
2839
|
+
...data.clean?.map(item => ({ name: item.name, status: 'clean', color: 'var(--text-secondary)', icon: '💤' })) || []
|
|
2840
|
+
];
|
|
2841
|
+
} catch (error) {
|
|
2842
|
+
console.error('Failed to load worktree details:', error);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
}" x-init="loadWorktreeDetails()">
|
|
2846
|
+
|
|
2847
|
+
<template x-if="worktreeDetails.length > 0">
|
|
2848
|
+
<div>
|
|
2849
|
+
<div style="display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));">
|
|
2850
|
+
<template x-for="(worktree, index) in worktreeDetails" :key="index">
|
|
2851
|
+
<div style="background: linear-gradient(135deg, rgba(245, 158, 11, 0.08) 0%, rgba(245, 158, 11, 0.04) 100%); border-radius: 12px; padding: 1.5rem; border: 1px solid rgba(245, 158, 11, 0.2); transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(245, 158, 11, 0.1);"
|
|
2852
|
+
@mouseenter="$el.style.background = 'linear-gradient(135deg, rgba(245, 158, 11, 0.12) 0%, rgba(245, 158, 11, 0.06) 100%)'; $el.style.borderColor = 'rgba(245, 158, 11, 0.3)'"
|
|
2853
|
+
@mouseleave="$el.style.background = 'linear-gradient(135deg, rgba(245, 158, 11, 0.08) 0%, rgba(245, 158, 11, 0.04) 100%)'; $el.style.borderColor = 'rgba(245, 158, 11, 0.2)'"
|
|
2854
|
+
|
|
2855
|
+
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem;">
|
|
2856
|
+
<h3 style="margin: 0; font-size: 1rem; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; color: var(--text-primary);"
|
|
2857
|
+
x-text="worktree.name"></h3>
|
|
2858
|
+
<span style="font-size: 1.4rem; color: var(--accent-orange);">🏗️</span>
|
|
2859
|
+
</div>
|
|
2860
|
+
|
|
2861
|
+
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem;">
|
|
2862
|
+
<span class="status-badge"
|
|
2863
|
+
:class="{
|
|
2864
|
+
'status-completed': worktree.status === 'committed',
|
|
2865
|
+
'status-in-progress': worktree.status === 'dirty',
|
|
2866
|
+
'status-draft': worktree.status === 'clean'
|
|
2867
|
+
}"
|
|
2868
|
+
x-text="worktree.status"></span>
|
|
2869
|
+
</div>
|
|
2870
|
+
|
|
2871
|
+
<div style="font-size: 0.875rem; color: var(--text-muted);">
|
|
2872
|
+
<div>Branch: <span style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;" x-text="worktree.name"></span></div>
|
|
2873
|
+
<div style="margin-top: 0.5rem;">
|
|
2874
|
+
Status: <span :style="'color: ' + worktree.color" x-text="worktree.status"></span>
|
|
2875
|
+
</div>
|
|
2876
|
+
</div>
|
|
2877
|
+
</div>
|
|
2878
|
+
</template>
|
|
2879
|
+
</div>
|
|
2880
|
+
</div>
|
|
2881
|
+
</template>
|
|
2882
|
+
|
|
2883
|
+
<template x-if="worktreeDetails.length === 0">
|
|
2884
|
+
<div style="text-align: center; padding: 3rem; color: var(--text-muted);">
|
|
2885
|
+
<div style="font-size: 3rem; margin-bottom: 1rem; color: var(--accent-orange);">🏗️</div>
|
|
2886
|
+
<h3 style="color: var(--text-primary); font-family: var(--font-heading);">No Active Construction Zones</h3>
|
|
2887
|
+
<p style="color: var(--text-secondary);">All workshop activity is currently on the main branch.</p>
|
|
2888
|
+
<div style="margin-top: 1.5rem; font-size: 0.9rem; color: var(--text-muted);">
|
|
2889
|
+
<span style="color: var(--accent-orange);">🔧</span> Ready for new projects
|
|
2890
|
+
</div>
|
|
2891
|
+
</div>
|
|
2892
|
+
</template>
|
|
2893
|
+
</div>
|
|
2894
|
+
</div>
|
|
2895
|
+
</div>
|
|
2896
|
+
|
|
2897
|
+
|
|
2898
|
+
|
|
2899
|
+
<!-- Delete Confirmation Modal - Compact Top-Right -->
|
|
2900
|
+
<div x-show="confirmDelete"
|
|
2901
|
+
x-transition:enter="transition ease-out duration-300"
|
|
2902
|
+
x-transition:enter-start="opacity-0 translate-x-4"
|
|
2903
|
+
x-transition:enter-end="opacity-100 translate-x-0"
|
|
2904
|
+
x-transition:leave="transition ease-in duration-200"
|
|
2905
|
+
x-transition:leave-start="opacity-100 translate-x-0"
|
|
2906
|
+
x-transition:leave-end="opacity-0 translate-x-4"
|
|
2907
|
+
style="position: fixed; top: 1rem; right: 1rem; z-index: 1000; max-width: 400px;">
|
|
2908
|
+
<div class="glass"
|
|
2909
|
+
style="background: rgba(0, 0, 0, 0.95); border: 2px solid var(--accent-red); padding: 1.5rem; border-radius: 16px; backdrop-filter: blur(20px);">
|
|
2910
|
+
<div>
|
|
2911
|
+
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;">
|
|
2912
|
+
<div style="font-size: 1.5rem; color: var(--accent-red);">⚠️</div>
|
|
2913
|
+
<h3 class="heading" style="color: var(--text-primary); margin: 0; font-size: 1rem;">Confirm Deletion</h3>
|
|
2914
|
+
</div>
|
|
2915
|
+
<p style="color: var(--text-secondary); margin-bottom: 1.5rem; font-size: 0.9rem; line-height: 1.4;">
|
|
2916
|
+
Delete "<span style="color: var(--text-primary); font-weight: 600;" x-text="confirmDelete?.title"></span>"?
|
|
2917
|
+
<br>
|
|
2918
|
+
<strong style="color: var(--accent-red); font-size: 0.85rem;">This cannot be undone.</strong>
|
|
2919
|
+
</p>
|
|
2920
|
+
|
|
2921
|
+
<div style="display: flex; gap: 0.75rem; justify-content: flex-end;">
|
|
2922
|
+
<button class="btn btn-secondary"
|
|
2923
|
+
style="padding: 0.5rem 1rem; font-size: 0.85rem;"
|
|
2924
|
+
@click="cancelDelete()">
|
|
2925
|
+
Cancel
|
|
2926
|
+
</button>
|
|
2927
|
+
<button class="btn"
|
|
2928
|
+
style="padding: 0.5rem 1rem; font-size: 0.85rem; background: var(--accent-red); border: 1px solid var(--accent-red); color: white;"
|
|
2929
|
+
@click="deleteSpec()">
|
|
2930
|
+
🗑 Delete
|
|
2931
|
+
</button>
|
|
2932
|
+
</div>
|
|
2933
|
+
</div>
|
|
2934
|
+
</div>
|
|
2935
|
+
</div>
|
|
2936
|
+
|
|
2937
|
+
<!-- Toast Notifications -->
|
|
2938
|
+
<div class="toast-container"
|
|
2939
|
+
style="position: fixed; top: 1rem; right: 1rem; z-index: 9998; max-width: 400px; padding-top: 0;"
|
|
2940
|
+
:style="confirmDelete ? 'padding-top: 200px;' : 'padding-top: 0;'">
|
|
2941
|
+
<template x-for="toast in toasts" :key="toast.id">
|
|
2942
|
+
<div x-show="toast.visible"
|
|
2943
|
+
x-transition:enter="transition ease-out duration-300 transform"
|
|
2944
|
+
x-transition:enter-start="translate-x-full opacity-0"
|
|
2945
|
+
x-transition:enter-end="translate-x-0 opacity-100"
|
|
2946
|
+
x-transition:leave="transition ease-in duration-200 transform"
|
|
2947
|
+
x-transition:leave-start="translate-x-0 opacity-100"
|
|
2948
|
+
x-transition:leave-end="translate-x-full opacity-0"
|
|
2949
|
+
class="toast-notification"
|
|
2950
|
+
:class="{
|
|
2951
|
+
'toast-success': toast.type === 'success',
|
|
2952
|
+
'toast-error': toast.type === 'error',
|
|
2953
|
+
'toast-info': toast.type === 'info'
|
|
2954
|
+
}"
|
|
2955
|
+
style="margin-bottom: 0.75rem; padding: 1rem 1.5rem; border-radius: 8px; backdrop-filter: blur(10px); border: 1px solid; box-shadow: 0 4px 20px rgba(0,0,0,0.15); cursor: pointer; font-family: var(--font-body); font-size: 0.9rem; line-height: 1.4; display: flex; align-items: center; justify-content: space-between; min-width: 300px;"
|
|
2956
|
+
@click="removeToast(toast.id)">
|
|
2957
|
+
<span x-text="toast.message" style="flex: 1; margin-right: 0.75rem;"></span>
|
|
2958
|
+
<button @click.stop="removeToast(toast.id)"
|
|
2959
|
+
style="background: none; border: none; color: inherit; opacity: 0.7; font-size: 1.1em; cursor: pointer; padding: 0; margin: 0; line-height: 1;"
|
|
2960
|
+
@mouseover="$el.style.opacity = '1'"
|
|
2961
|
+
@mouseout="$el.style.opacity = '0.7'">×</button>
|
|
2962
|
+
</div>
|
|
2963
|
+
</template>
|
|
2964
|
+
</div>
|
|
2965
|
+
|
|
2966
|
+
<style>
|
|
2967
|
+
/* Modern Toast Notifications */
|
|
2968
|
+
.toast-notification {
|
|
2969
|
+
font-weight: 500;
|
|
2970
|
+
font-size: 0.875rem;
|
|
2971
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
2972
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
2973
|
+
backdrop-filter: blur(12px);
|
|
2974
|
+
-webkit-backdrop-filter: blur(12px);
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
.toast-success {
|
|
2978
|
+
background: linear-gradient(135deg, rgba(34, 197, 94, 0.95) 0%, rgba(21, 128, 61, 0.95) 100%) !important;
|
|
2979
|
+
border: 1px solid rgba(34, 197, 94, 0.4) !important;
|
|
2980
|
+
color: white !important;
|
|
2981
|
+
box-shadow: 0 8px 32px rgba(34, 197, 94, 0.3) !important;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
.toast-error {
|
|
2985
|
+
background: linear-gradient(135deg, rgba(239, 68, 68, 0.95) 0%, rgba(185, 28, 28, 0.95) 100%) !important;
|
|
2986
|
+
border: 1px solid rgba(239, 68, 68, 0.4) !important;
|
|
2987
|
+
color: white !important;
|
|
2988
|
+
box-shadow: 0 8px 32px rgba(239, 68, 68, 0.3) !important;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
.toast-info {
|
|
2992
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.95) 0%, rgba(29, 78, 216, 0.95) 100%) !important;
|
|
2993
|
+
border: 1px solid rgba(59, 130, 246, 0.4) !important;
|
|
2994
|
+
color: white !important;
|
|
2995
|
+
box-shadow: 0 8px 32px rgba(59, 130, 246, 0.3) !important;
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
.toast-notification:hover {
|
|
2999
|
+
transform: translateY(-2px);
|
|
3000
|
+
box-shadow: 0 15px 35px -5px rgba(0, 0, 0, 0.15), 0 15px 15px -5px rgba(0, 0, 0, 0.06);
|
|
3001
|
+
}
|
|
3002
|
+
</style>
|
|
3003
|
+
|
|
3004
|
+
<!-- Toast Notification Container -->
|
|
3005
|
+
<div style="position: fixed; top: 1rem; right: 1rem; z-index: 9999; display: flex; flex-direction: column; gap: 0.5rem; max-width: 400px;">
|
|
3006
|
+
<template x-for="toast in toasts" :key="toast.id">
|
|
3007
|
+
<div x-show="true"
|
|
3008
|
+
x-transition:enter="transform transition ease-out duration-300"
|
|
3009
|
+
x-transition:enter-start="translate-x-full opacity-0"
|
|
3010
|
+
x-transition:enter-end="translate-x-0 opacity-100"
|
|
3011
|
+
x-transition:leave="transform transition ease-in duration-200"
|
|
3012
|
+
x-transition:leave-start="translate-x-0 opacity-100"
|
|
3013
|
+
x-transition:leave-end="translate-x-full opacity-0"
|
|
3014
|
+
:style="`background: ${getToastColor(toast.type)}; backdrop-filter: blur(10px); border-radius: 8px; padding: 1rem; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border: 1px solid rgba(255,255,255,0.2); color: white; font-weight: 500; display: flex; align-items: center; gap: 0.75rem; cursor: pointer;`"
|
|
3015
|
+
@click="removeToast(toast.id)">
|
|
3016
|
+
<span x-text="getToastIcon(toast.type)" style="font-size: 1.1rem;"></span>
|
|
3017
|
+
<span x-text="toast.message" style="flex: 1; line-height: 1.4;"></span>
|
|
3018
|
+
<button @click.stop="removeToast(toast.id)"
|
|
3019
|
+
style="background: rgba(255,255,255,0.2); border: none; color: white; border-radius: 4px; padding: 0.25rem 0.5rem; font-size: 0.8rem; cursor: pointer; opacity: 0.8; transition: opacity 0.2s;"
|
|
3020
|
+
@mouseenter="$el.style.opacity = '1'"
|
|
3021
|
+
@mouseleave="$el.style.opacity = '0.8'">
|
|
3022
|
+
✕
|
|
3023
|
+
</button>
|
|
3024
|
+
</div>
|
|
3025
|
+
</template>
|
|
3026
|
+
</div>
|
|
3027
|
+
|
|
3028
|
+
<!-- Global copy functionality and enhanced code block styles -->
|
|
3029
|
+
<script>
|
|
3030
|
+
// Global function for copying code blocks
|
|
3031
|
+
function copyToClipboard(text, buttonId) {
|
|
3032
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
3033
|
+
const button = document.getElementById(buttonId);
|
|
3034
|
+
if (button) {
|
|
3035
|
+
const originalText = button.innerHTML;
|
|
3036
|
+
button.innerHTML = '✅ Copied!';
|
|
3037
|
+
button.style.background = 'rgba(16, 185, 129, 0.2)';
|
|
3038
|
+
button.style.borderColor = 'rgba(16, 185, 129, 0.4)';
|
|
3039
|
+
|
|
3040
|
+
setTimeout(() => {
|
|
3041
|
+
button.innerHTML = originalText;
|
|
3042
|
+
button.style.background = '';
|
|
3043
|
+
button.style.borderColor = '';
|
|
3044
|
+
}, 2000);
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// Also show toast notification if available
|
|
3048
|
+
try {
|
|
3049
|
+
window.Alpine.store('dashboard').addToast('📋 Command copied to clipboard!', 'success');
|
|
3050
|
+
} catch (e) {
|
|
3051
|
+
console.log('📋 Command copied to clipboard!');
|
|
3052
|
+
}
|
|
3053
|
+
}).catch(err => {
|
|
3054
|
+
console.error('Failed to copy:', err);
|
|
3055
|
+
try {
|
|
3056
|
+
window.Alpine.store('dashboard').addToast('❌ Failed to copy command', 'error');
|
|
3057
|
+
} catch (e) {
|
|
3058
|
+
console.error('Failed to copy command');
|
|
3059
|
+
}
|
|
3060
|
+
});
|
|
3061
|
+
}
|
|
3062
|
+
</script>
|
|
3063
|
+
|
|
3064
|
+
<style>
|
|
3065
|
+
/* Enhanced code block styles with copy buttons */
|
|
3066
|
+
.code-block-container {
|
|
3067
|
+
position: relative;
|
|
3068
|
+
margin: 1.5rem 0;
|
|
3069
|
+
background: rgba(0, 0, 0, 0.6);
|
|
3070
|
+
border-radius: 8px;
|
|
3071
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
3072
|
+
overflow: hidden;
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
.code-block-header {
|
|
3076
|
+
display: flex;
|
|
3077
|
+
justify-content: space-between;
|
|
3078
|
+
align-items: center;
|
|
3079
|
+
padding: 0.75rem 1rem;
|
|
3080
|
+
background: rgba(255, 255, 255, 0.05);
|
|
3081
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
.code-lang {
|
|
3085
|
+
font-size: 0.8rem;
|
|
3086
|
+
color: rgba(245, 158, 11, 0.9);
|
|
3087
|
+
font-weight: 500;
|
|
3088
|
+
text-transform: uppercase;
|
|
3089
|
+
letter-spacing: 0.5px;
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
.copy-code-btn {
|
|
3093
|
+
background: rgba(255, 255, 255, 0.1);
|
|
3094
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
3095
|
+
color: var(--text-primary);
|
|
3096
|
+
padding: 0.4rem 0.8rem;
|
|
3097
|
+
border-radius: 4px;
|
|
3098
|
+
font-size: 0.8rem;
|
|
3099
|
+
cursor: pointer;
|
|
3100
|
+
transition: all 0.2s ease;
|
|
3101
|
+
display: flex;
|
|
3102
|
+
align-items: center;
|
|
3103
|
+
gap: 0.3rem;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
.copy-code-btn:hover {
|
|
3107
|
+
background: rgba(255, 255, 255, 0.15);
|
|
3108
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
3109
|
+
transform: translateY(-1px);
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
.code-block-container pre {
|
|
3113
|
+
margin: 0;
|
|
3114
|
+
padding: 1.2rem;
|
|
3115
|
+
background: transparent;
|
|
3116
|
+
border: none;
|
|
3117
|
+
overflow-x: auto;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
.code-block-container code {
|
|
3121
|
+
background: transparent;
|
|
3122
|
+
color: var(--text-primary);
|
|
3123
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
3124
|
+
font-size: 0.9rem;
|
|
3125
|
+
line-height: 1.5;
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
.inline-code {
|
|
3129
|
+
background: rgba(255, 255, 255, 0.1) !important;
|
|
3130
|
+
padding: 0.2rem 0.4rem !important;
|
|
3131
|
+
border-radius: 3px !important;
|
|
3132
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', monospace !important;
|
|
3133
|
+
font-size: 0.85em !important;
|
|
3134
|
+
color: rgba(245, 158, 11, 0.9) !important;
|
|
3135
|
+
border: 1px solid rgba(255, 255, 255, 0.15) !important;
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
/* Quick Access section styling */
|
|
3139
|
+
.spec-content h3 {
|
|
3140
|
+
color: rgba(34, 197, 94, 0.9);
|
|
3141
|
+
border-bottom: 2px solid rgba(34, 197, 94, 0.3);
|
|
3142
|
+
padding-bottom: 0.5rem;
|
|
3143
|
+
margin-bottom: 1.5rem;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
.spec-content ul li {
|
|
3147
|
+
margin: 0.5rem 0;
|
|
3148
|
+
color: var(--text-secondary);
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
.spec-content ul li strong {
|
|
3152
|
+
color: var(--text-primary);
|
|
3153
|
+
}
|
|
3154
|
+
</style>
|
|
3155
|
+
|
|
3156
|
+
</body>
|
|
3157
|
+
</html>
|