bobs-workshop 0.3.2 → 3.1.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/LICENSE +2 -2
- package/README.md +199 -210
- package/bin/bobs-workshop.js +109 -0
- package/config/agents.json +27 -0
- package/dist/plugins/bobs-workshop.js +34 -0
- package/dist/tools/background-agent/cancel.d.ts +3 -0
- package/dist/tools/background-agent/cancel.d.ts.map +1 -0
- package/dist/tools/background-agent/cancel.js +52 -0
- package/dist/tools/background-agent/concurrency.d.ts +15 -0
- package/dist/tools/background-agent/concurrency.d.ts.map +1 -0
- package/dist/tools/background-agent/concurrency.js +61 -0
- package/dist/tools/background-agent/index.d.ts +8 -0
- package/dist/tools/background-agent/index.d.ts.map +1 -0
- package/dist/tools/background-agent/index.js +7 -0
- package/dist/tools/background-agent/launch.d.ts +6 -0
- package/dist/tools/background-agent/launch.d.ts.map +1 -0
- package/dist/tools/background-agent/launch.js +33 -0
- package/dist/tools/background-agent/list.d.ts +7 -0
- package/dist/tools/background-agent/list.d.ts.map +1 -0
- package/dist/tools/background-agent/list.js +40 -0
- package/dist/tools/background-agent/manager.d.ts +29 -0
- package/dist/tools/background-agent/manager.d.ts.map +1 -0
- package/dist/tools/background-agent/manager.js +377 -0
- package/dist/tools/background-agent/output.d.ts +3 -0
- package/dist/tools/background-agent/output.d.ts.map +1 -0
- package/dist/tools/background-agent/output.js +41 -0
- package/dist/tools/background-agent/types.d.ts +46 -0
- package/dist/tools/background-agent/types.d.ts.map +1 -0
- package/dist/tools/background-agent/types.js +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/manual/index.d.ts +3 -0
- package/dist/tools/manual/index.d.ts.map +1 -0
- package/dist/tools/manual/index.js +2 -0
- package/dist/tools/manual/manual-update.d.ts +4 -0
- package/dist/tools/manual/manual-update.d.ts.map +1 -0
- package/dist/tools/manual/manual-update.js +190 -0
- package/dist/tools/manual/verify-manual.d.ts +4 -0
- package/dist/tools/manual/verify-manual.d.ts.map +1 -0
- package/dist/tools/manual/verify-manual.js +46 -0
- package/package.json +35 -67
- package/postinstall.js +190 -0
- package/src/agents/alice.md +466 -0
- package/src/agents/bob-rev.md +493 -0
- package/src/agents/bob-send.md +277 -0
- package/src/agents/bob.md +442 -0
- package/src/agents/trace.md +451 -0
- package/src/plugins/bobs-workshop.ts +45 -0
- package/src/skills/api-patterns/SKILL.md +376 -0
- package/src/skills/architecture/SKILL.md +271 -0
- package/src/skills/bobs-workshop/performance/icon.svg +3 -0
- package/src/skills/brainstorming/SKILL.md +210 -0
- package/src/skills/clean-code/SKILL.md +151 -0
- package/src/skills/code-review-checklist/SKILL.md +220 -0
- package/src/skills/database-design/SKILL.md +271 -0
- package/src/skills/exploration/SKILL.md +257 -0
- package/src/skills/frontend-ui-ux/SKILL.md +78 -0
- package/src/skills/git-master/SKILL.md +1105 -0
- package/src/skills/performance/SKILL.md +144 -0
- package/src/skills/performance/icon.svg +3 -0
- package/src/skills/plan-writing/SKILL.md +225 -0
- package/src/skills/security/SKILL.md +410 -0
- package/src/skills/simplification/SKILL.md +238 -0
- package/src/skills/systematic-debugging/SKILL.md +175 -0
- package/src/skills/testing-patterns/SKILL.md +305 -0
- package/src/skills/verification/SKILL.md +286 -0
- package/src/tools/background-agent/cancel.ts +67 -0
- package/src/tools/background-agent/concurrency.ts +71 -0
- package/src/tools/background-agent/index.ts +7 -0
- package/src/tools/background-agent/launch.ts +39 -0
- package/src/tools/background-agent/list.ts +50 -0
- package/src/tools/background-agent/manager.ts +455 -0
- package/src/tools/background-agent/output.ts +57 -0
- package/src/tools/background-agent/types.ts +55 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/manual/index.ts +2 -0
- package/src/tools/manual/manual-update.ts +197 -0
- package/src/tools/manual/verify-manual.ts +55 -0
- package/uninstall.js +64 -0
- package/Claude.md +0 -162
- package/bin/bobs-mcp-server.js +0 -11
- package/bin/bobs-mcp.js +0 -130
- package/dist/api/taskLogger.js +0 -106
- package/dist/api/taskLogger.js.map +0 -1
- package/dist/cli/checker.js +0 -401
- package/dist/cli/checker.js.map +0 -1
- package/dist/cli/cleanup.js +0 -131
- package/dist/cli/cleanup.js.map +0 -1
- package/dist/cli/debug.js +0 -157
- package/dist/cli/debug.js.map +0 -1
- package/dist/cli/health.js +0 -97
- package/dist/cli/health.js.map +0 -1
- package/dist/cli/setup.js +0 -81
- package/dist/cli/setup.js.map +0 -1
- package/dist/cli/workshop.js +0 -42
- package/dist/cli/workshop.js.map +0 -1
- package/dist/dashboard/server.js +0 -1203
- package/dist/dashboard/server.js.map +0 -1
- package/dist/index.js +0 -960
- package/dist/index.js.map +0 -1
- package/dist/prompts/architect.js +0 -221
- package/dist/prompts/architect.js.map +0 -1
- package/dist/prompts/debugger.js +0 -257
- package/dist/prompts/debugger.js.map +0 -1
- package/dist/prompts/engineer.js +0 -249
- package/dist/prompts/engineer.js.map +0 -1
- package/dist/prompts/orchestrator.js +0 -304
- package/dist/prompts/orchestrator.js.map +0 -1
- package/dist/prompts/reviewer.js +0 -289
- package/dist/prompts/reviewer.js.map +0 -1
- package/dist/services/activitySummarizer.js +0 -388
- package/dist/services/activitySummarizer.js.map +0 -1
- package/dist/services/changeValidator.js +0 -396
- package/dist/services/changeValidator.js.map +0 -1
- package/dist/services/claudeOrchestrator.js +0 -343
- package/dist/services/claudeOrchestrator.js.map +0 -1
- package/dist/services/fileMonitor.js +0 -250
- package/dist/services/fileMonitor.js.map +0 -1
- package/dist/services/implementationSummarizer.js +0 -306
- package/dist/services/implementationSummarizer.js.map +0 -1
- package/dist/services/liveMonitor.js +0 -315
- package/dist/services/liveMonitor.js.map +0 -1
- package/dist/services/mcpAuditLogger.js +0 -104
- package/dist/services/mcpAuditLogger.js.map +0 -1
- package/dist/services/mcpLogger.js +0 -223
- package/dist/services/mcpLogger.js.map +0 -1
- package/dist/services/tmuxManager.js +0 -541
- package/dist/services/tmuxManager.js.map +0 -1
- package/dist/tools/approvalTools.js +0 -244
- package/dist/tools/approvalTools.js.map +0 -1
- package/dist/tools/autoDebugger.js +0 -147
- package/dist/tools/autoDebugger.js.map +0 -1
- package/dist/tools/cleanupService.js +0 -221
- package/dist/tools/cleanupService.js.map +0 -1
- package/dist/tools/dashboardTools.js +0 -342
- package/dist/tools/dashboardTools.js.map +0 -1
- package/dist/tools/developmentNudges.js +0 -336
- package/dist/tools/developmentNudges.js.map +0 -1
- package/dist/tools/gitTools.js +0 -741
- package/dist/tools/gitTools.js.map +0 -1
- package/dist/tools/orchestratorTools.js +0 -832
- package/dist/tools/orchestratorTools.js.map +0 -1
- package/dist/tools/searchCache.js +0 -64
- package/dist/tools/searchCache.js.map +0 -1
- package/dist/tools/searchTools.js +0 -1107
- package/dist/tools/searchTools.js.map +0 -1
- package/dist/tools/semgrep-patterns.js +0 -296
- package/dist/tools/semgrep-patterns.js.map +0 -1
- package/dist/tools/specTools.js +0 -332
- package/dist/tools/specTools.js.map +0 -1
- package/dist/tools/structural/__tests__/orchestrator.test.js +0 -61
- package/dist/tools/structural/__tests__/orchestrator.test.js.map +0 -1
- package/dist/tools/structural/cache.js +0 -226
- package/dist/tools/structural/cache.js.map +0 -1
- package/dist/tools/structural/engines/python/index.js +0 -118
- package/dist/tools/structural/engines/python/index.js.map +0 -1
- package/dist/tools/structural/engines/typescript/__tests__/typescript-engine.test.js +0 -97
- package/dist/tools/structural/engines/typescript/__tests__/typescript-engine.test.js.map +0 -1
- package/dist/tools/structural/engines/typescript/analyzer.js +0 -433
- package/dist/tools/structural/engines/typescript/analyzer.js.map +0 -1
- package/dist/tools/structural/engines/typescript/index.js +0 -381
- package/dist/tools/structural/engines/typescript/index.js.map +0 -1
- package/dist/tools/structural/engines/typescript/utils.js +0 -279
- package/dist/tools/structural/engines/typescript/utils.js.map +0 -1
- package/dist/tools/structural/index.js +0 -248
- package/dist/tools/structural/index.js.map +0 -1
- package/dist/tools/structural/types.js +0 -18
- package/dist/tools/structural/types.js.map +0 -1
- package/dist/tools/tmuxTools.js +0 -100
- package/dist/tools/tmuxTools.js.map +0 -1
- package/dist/tools/workRecorder.js +0 -215
- package/dist/tools/workRecorder.js.map +0 -1
- package/dist/tools/worktreeTools.js +0 -705
- package/dist/tools/worktreeTools.js.map +0 -1
- package/dist/utils/__tests__/integration.test.js +0 -57
- package/dist/utils/__tests__/integration.test.js.map +0 -1
- package/dist/utils/__tests__/serverDetection.test.js +0 -151
- package/dist/utils/__tests__/serverDetection.test.js.map +0 -1
- package/dist/utils/errorHandling.js +0 -336
- package/dist/utils/errorHandling.js.map +0 -1
- package/dist/utils/processManager.js +0 -172
- package/dist/utils/processManager.js.map +0 -1
- package/dist/utils/reliability.js +0 -263
- package/dist/utils/reliability.js.map +0 -1
- package/dist/utils/responseFormatter.js +0 -250
- package/dist/utils/responseFormatter.js.map +0 -1
- package/dist/utils/serverDetection.js +0 -133
- package/dist/utils/serverDetection.js.map +0 -1
- package/dist/utils/specMigration.js +0 -105
- package/dist/utils/specMigration.js.map +0 -1
- package/dist/validation/schemas.js +0 -299
- package/dist/validation/schemas.js.map +0 -1
- package/public/.well-known/mcp/manifest.json +0 -473
- package/public/index.html +0 -3157
- package/public/index.html.backup +0 -2805
- package/public/index.html.backup2 +0 -1292
- package/scripts/cleanup-system-logs.ts +0 -121
- package/scripts/init-workspace.js +0 -63
- package/scripts/install-search-tools.js +0 -116
package/public/index.html.backup
DELETED
|
@@ -1,2805 +0,0 @@
|
|
|
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%, #2d2d30 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-blue);
|
|
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
|
-
/* Status badges */
|
|
220
|
-
.status-badge {
|
|
221
|
-
padding: 0.25rem 0.75rem;
|
|
222
|
-
border-radius: 20px;
|
|
223
|
-
font-size: 0.75rem;
|
|
224
|
-
font-weight: 600;
|
|
225
|
-
text-transform: uppercase;
|
|
226
|
-
letter-spacing: 0.05em;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.status-draft { background: rgba(156, 163, 175, 0.2); color: #d1d5db; }
|
|
230
|
-
.status-planning { background: rgba(245, 158, 11, 0.2); color: #fbbf24; }
|
|
231
|
-
.status-implementation { background: rgba(59, 130, 246, 0.2); color: #60a5fa; }
|
|
232
|
-
.status-review { background: rgba(147, 51, 234, 0.2); color: #a855f7; }
|
|
233
|
-
.status-completed { background: rgba(16, 185, 129, 0.2); color: #34d399; }
|
|
234
|
-
/* Legacy support */
|
|
235
|
-
.status-in-progress { background: rgba(59, 130, 246, 0.2); color: #60a5fa; }
|
|
236
|
-
|
|
237
|
-
/* Buttons */
|
|
238
|
-
.btn {
|
|
239
|
-
padding: 0.75rem 1.5rem;
|
|
240
|
-
border-radius: 12px;
|
|
241
|
-
font-family: var(--font-heading);
|
|
242
|
-
font-weight: 600;
|
|
243
|
-
font-size: 0.875rem;
|
|
244
|
-
border: none;
|
|
245
|
-
cursor: pointer;
|
|
246
|
-
transition: all 0.3s ease;
|
|
247
|
-
display: inline-flex;
|
|
248
|
-
align-items: center;
|
|
249
|
-
gap: 0.5rem;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.btn-primary {
|
|
253
|
-
background: var(--accent-blue);
|
|
254
|
-
color: white;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.btn-primary:hover {
|
|
258
|
-
transform: translateY(-2px);
|
|
259
|
-
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.4);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.btn-secondary {
|
|
263
|
-
background: var(--bg-glass);
|
|
264
|
-
backdrop-filter: blur(12px);
|
|
265
|
-
color: var(--text-primary);
|
|
266
|
-
border: 1px solid var(--border-glass);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.btn-secondary:hover {
|
|
270
|
-
background: var(--bg-glass-hover);
|
|
271
|
-
transform: translateY(-2px);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/* Modal */
|
|
275
|
-
.modal-overlay {
|
|
276
|
-
position: fixed;
|
|
277
|
-
inset: 0;
|
|
278
|
-
background: rgba(0, 0, 0, 0.8);
|
|
279
|
-
backdrop-filter: blur(8px);
|
|
280
|
-
display: flex;
|
|
281
|
-
align-items: center;
|
|
282
|
-
justify-content: center;
|
|
283
|
-
z-index: 100;
|
|
284
|
-
padding: 1rem;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.modal-content {
|
|
288
|
-
background: var(--bg-glass);
|
|
289
|
-
backdrop-filter: blur(20px);
|
|
290
|
-
border: 1px solid var(--border-glass);
|
|
291
|
-
border-radius: 24px;
|
|
292
|
-
width: 99vw;
|
|
293
|
-
height: 92vh; /* Reduced from 98vh to 92vh */
|
|
294
|
-
max-width: none;
|
|
295
|
-
min-width: 1728px; /* Increased from 1440px by 20% (1440 * 1.2) */
|
|
296
|
-
min-height: 1000px; /* Reduced from 1152px */
|
|
297
|
-
display: grid;
|
|
298
|
-
grid-template-columns: 60% 40%;
|
|
299
|
-
grid-template-rows: auto 1fr;
|
|
300
|
-
overflow: hidden;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.modal-header {
|
|
304
|
-
grid-column: 1 / -1;
|
|
305
|
-
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(99, 102, 241, 0.1) 100%);
|
|
306
|
-
padding: 1.5rem 2rem;
|
|
307
|
-
border-bottom: 1px solid var(--border-glass);
|
|
308
|
-
display: flex;
|
|
309
|
-
justify-content: space-between;
|
|
310
|
-
align-items: center;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
.modal-title-section {
|
|
314
|
-
display: flex;
|
|
315
|
-
align-items: center;
|
|
316
|
-
gap: 1rem;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
.worktree-info {
|
|
320
|
-
background: rgba(255, 255, 255, 0.1);
|
|
321
|
-
padding: 0.5rem 1rem;
|
|
322
|
-
border-radius: 20px;
|
|
323
|
-
font-size: 0.875rem;
|
|
324
|
-
display: flex;
|
|
325
|
-
align-items: center;
|
|
326
|
-
gap: 0.5rem;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
.modal-main-container {
|
|
330
|
-
display: flex;
|
|
331
|
-
flex-direction: column;
|
|
332
|
-
overflow: hidden;
|
|
333
|
-
height: 100%; /* Ensure full height usage */
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
.modal-nav {
|
|
337
|
-
padding: 1rem 2rem 0;
|
|
338
|
-
border-bottom: 1px solid var(--border-glass);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
.nav-tabs {
|
|
342
|
-
display: flex;
|
|
343
|
-
gap: 0.5rem;
|
|
344
|
-
margin-bottom: 1rem;
|
|
345
|
-
overflow-x: auto;
|
|
346
|
-
scrollbar-width: thin;
|
|
347
|
-
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
|
348
|
-
padding-bottom: 0.5rem;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
.nav-tabs::-webkit-scrollbar {
|
|
352
|
-
height: 4px;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
.nav-tabs::-webkit-scrollbar-track {
|
|
356
|
-
background: rgba(255, 255, 255, 0.1);
|
|
357
|
-
border-radius: 2px;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
.nav-tabs::-webkit-scrollbar-thumb {
|
|
361
|
-
background: rgba(255, 255, 255, 0.3);
|
|
362
|
-
border-radius: 2px;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
.nav-tabs::-webkit-scrollbar-thumb:hover {
|
|
366
|
-
background: rgba(255, 255, 255, 0.5);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
.nav-tab {
|
|
370
|
-
padding: 0.5rem 1rem;
|
|
371
|
-
border-radius: 20px;
|
|
372
|
-
background: transparent;
|
|
373
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
374
|
-
color: var(--text-secondary);
|
|
375
|
-
font-family: var(--font-body);
|
|
376
|
-
font-size: 0.875rem;
|
|
377
|
-
cursor: pointer;
|
|
378
|
-
transition: all 0.2s ease;
|
|
379
|
-
white-space: nowrap;
|
|
380
|
-
flex-shrink: 0;
|
|
381
|
-
min-width: fit-content;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
.nav-tab:hover {
|
|
385
|
-
background: rgba(255, 255, 255, 0.05);
|
|
386
|
-
color: var(--text-primary);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
.nav-tab.active {
|
|
390
|
-
background: var(--accent-blue);
|
|
391
|
-
color: white;
|
|
392
|
-
border-color: var(--accent-blue);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
.modal-main {
|
|
396
|
-
padding: 2rem;
|
|
397
|
-
overflow-y: auto;
|
|
398
|
-
flex: 1; /* Take all available space */
|
|
399
|
-
min-height: 0; /* Allow flex shrinking */
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.spec-content {
|
|
403
|
-
background: rgba(255, 255, 255, 0.03);
|
|
404
|
-
padding: 2.5rem 3.5rem 2.5rem 3.5rem; /* Increased left padding for better breathing room */
|
|
405
|
-
border-radius: 12px;
|
|
406
|
-
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
407
|
-
font-family: var(--font-body);
|
|
408
|
-
line-height: 1.7; /* Improved line height for better readability */
|
|
409
|
-
overflow-y: auto;
|
|
410
|
-
height: 100%; /* Use full available height */
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
.spec-content h1, .spec-content h2, .spec-content h3 {
|
|
414
|
-
color: var(--text-primary);
|
|
415
|
-
margin-top: 2.5rem; /* Increased top margin */
|
|
416
|
-
margin-bottom: 1.5rem; /* Increased bottom margin */
|
|
417
|
-
font-family: var(--font-heading);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.spec-content h1:first-child,
|
|
421
|
-
.spec-content h2:first-child,
|
|
422
|
-
.spec-content h3:first-child {
|
|
423
|
-
margin-top: 0; /* Remove top margin from first heading */
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
.spec-content h1 { font-size: 1.5rem; }
|
|
427
|
-
.spec-content h2 { font-size: 1.3rem; }
|
|
428
|
-
.spec-content h3 { font-size: 1.1rem; }
|
|
429
|
-
|
|
430
|
-
.spec-content p {
|
|
431
|
-
margin-bottom: 1.5rem; /* Increased paragraph spacing */
|
|
432
|
-
color: var(--text-secondary);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
.spec-content ul, .spec-content ol {
|
|
436
|
-
margin-bottom: 1.5rem; /* Increased list spacing */
|
|
437
|
-
padding-left: 2rem; /* Better indentation */
|
|
438
|
-
color: var(--text-secondary);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
.spec-content li {
|
|
442
|
-
margin-bottom: 0.75rem; /* Increased list item spacing */
|
|
443
|
-
line-height: 1.6;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
.spec-content code {
|
|
447
|
-
background: rgba(255, 255, 255, 0.1);
|
|
448
|
-
padding: 0.2rem 0.4rem;
|
|
449
|
-
border-radius: 4px;
|
|
450
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
451
|
-
font-size: 0.9em;
|
|
452
|
-
color: var(--accent-blue);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
.spec-content pre {
|
|
456
|
-
background: rgba(0, 0, 0, 0.4);
|
|
457
|
-
padding: 1.5rem; /* Increased padding for code blocks */
|
|
458
|
-
border-radius: 8px;
|
|
459
|
-
overflow-x: auto;
|
|
460
|
-
margin: 2rem 0; /* Increased margin around code blocks */
|
|
461
|
-
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
462
|
-
line-height: 1.5;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
.spec-content pre code {
|
|
466
|
-
background: none;
|
|
467
|
-
padding: 0;
|
|
468
|
-
color: var(--text-primary);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
.spec-content blockquote {
|
|
472
|
-
border-left: 3px solid var(--accent-blue);
|
|
473
|
-
padding-left: 1rem;
|
|
474
|
-
margin: 1rem 0;
|
|
475
|
-
font-style: italic;
|
|
476
|
-
color: var(--text-secondary);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
.spec-content table {
|
|
480
|
-
width: 100%;
|
|
481
|
-
border-collapse: collapse;
|
|
482
|
-
margin: 1rem 0;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
.spec-content th, .spec-content td {
|
|
486
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
487
|
-
padding: 0.75rem;
|
|
488
|
-
text-align: left;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
.spec-content th {
|
|
492
|
-
background: rgba(255, 255, 255, 0.05);
|
|
493
|
-
font-weight: 600;
|
|
494
|
-
color: var(--text-primary);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
.spec-content a {
|
|
498
|
-
color: var(--accent-blue);
|
|
499
|
-
text-decoration: underline;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
.spec-content a:hover {
|
|
503
|
-
color: #60a5fa;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
.modal-activity {
|
|
507
|
-
border-left: 1px solid var(--border-glass);
|
|
508
|
-
overflow: hidden;
|
|
509
|
-
background: rgba(0, 0, 0, 0.1);
|
|
510
|
-
display: flex;
|
|
511
|
-
flex-direction: column;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
.activity-nav {
|
|
515
|
-
padding: 1rem 1.5rem 0.5rem;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
.activity-tabs {
|
|
519
|
-
display: flex;
|
|
520
|
-
gap: 0.5rem;
|
|
521
|
-
margin-bottom: 1rem;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
.activity-tab {
|
|
525
|
-
padding: 0.5rem 1rem;
|
|
526
|
-
border-radius: 20px;
|
|
527
|
-
background: transparent;
|
|
528
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
529
|
-
color: var(--text-secondary);
|
|
530
|
-
font-family: var(--font-body);
|
|
531
|
-
font-size: 0.875rem;
|
|
532
|
-
cursor: pointer;
|
|
533
|
-
transition: all 0.2s ease;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
.activity-tab:hover {
|
|
537
|
-
background: rgba(255, 255, 255, 0.05);
|
|
538
|
-
color: var(--text-primary);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
.activity-tab.active {
|
|
542
|
-
background: var(--accent-blue);
|
|
543
|
-
color: white;
|
|
544
|
-
border-color: var(--accent-blue);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
.activity-content {
|
|
548
|
-
flex: 1;
|
|
549
|
-
padding: 1.5rem;
|
|
550
|
-
overflow-y: auto;
|
|
551
|
-
height: 100%; /* Use full available height */
|
|
552
|
-
min-height: 0; /* Allow flex shrinking */
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
.sidebar-nav {
|
|
556
|
-
list-style: none;
|
|
557
|
-
padding: 0;
|
|
558
|
-
margin: 0;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
.sidebar-nav li {
|
|
562
|
-
margin-bottom: 0.5rem;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
.sidebar-nav button {
|
|
566
|
-
width: 100%;
|
|
567
|
-
text-align: left;
|
|
568
|
-
padding: 0.75rem;
|
|
569
|
-
border-radius: 8px;
|
|
570
|
-
background: transparent;
|
|
571
|
-
border: none;
|
|
572
|
-
color: var(--text-secondary);
|
|
573
|
-
font-family: var(--font-body);
|
|
574
|
-
cursor: pointer;
|
|
575
|
-
transition: all 0.2s ease;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
.sidebar-nav button:hover {
|
|
579
|
-
background: rgba(255, 255, 255, 0.05);
|
|
580
|
-
color: var(--text-primary);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
.sidebar-nav button.active {
|
|
584
|
-
background: var(--accent-blue);
|
|
585
|
-
color: white;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/* Timeline Activity Layout */
|
|
589
|
-
.timeline-container {
|
|
590
|
-
position: relative;
|
|
591
|
-
padding: 1rem 0;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
.timeline-line {
|
|
595
|
-
position: absolute;
|
|
596
|
-
left: 200px; /* Align with fixed left column width */
|
|
597
|
-
top: 0;
|
|
598
|
-
bottom: 0;
|
|
599
|
-
width: 2px;
|
|
600
|
-
background: linear-gradient(to bottom, transparent, var(--accent-blue), transparent);
|
|
601
|
-
z-index: 1;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
.timeline-item {
|
|
605
|
-
display: flex;
|
|
606
|
-
margin-bottom: 2.5rem; /* Increased spacing between timeline items */
|
|
607
|
-
position: relative;
|
|
608
|
-
align-items: flex-start; /* Better alignment */
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
.timeline-left {
|
|
612
|
-
flex: 0 0 200px; /* Fixed width for better alignment */
|
|
613
|
-
text-align: right;
|
|
614
|
-
padding-right: 1.5rem;
|
|
615
|
-
display: flex;
|
|
616
|
-
flex-direction: column;
|
|
617
|
-
justify-content: center;
|
|
618
|
-
min-height: 60px; /* Ensure minimum height */
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
.timeline-right {
|
|
622
|
-
flex: 1;
|
|
623
|
-
padding-left: 1.5rem;
|
|
624
|
-
min-width: 0; /* Allow content to shrink properly */
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
.timeline-marker {
|
|
628
|
-
position: absolute;
|
|
629
|
-
left: 200px; /* Align with timeline line */
|
|
630
|
-
top: 50%;
|
|
631
|
-
transform: translate(-50%, -50%);
|
|
632
|
-
width: 12px;
|
|
633
|
-
height: 12px;
|
|
634
|
-
border-radius: 50%;
|
|
635
|
-
background: var(--accent-blue);
|
|
636
|
-
border: 3px solid var(--bg-primary);
|
|
637
|
-
box-shadow: 0 0 0 2px var(--accent-blue);
|
|
638
|
-
z-index: 2;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
.timeline-marker.debugger { background: var(--accent-red); box-shadow: 0 0 0 2px var(--accent-red); }
|
|
642
|
-
.timeline-marker.engineer { background: var(--accent-green); box-shadow: 0 0 0 2px var(--accent-green); }
|
|
643
|
-
.timeline-marker.architect { background: var(--accent-orange); box-shadow: 0 0 0 2px var(--accent-orange); }
|
|
644
|
-
|
|
645
|
-
.timeline-time {
|
|
646
|
-
font-size: 0.8rem; /* Slightly larger font */
|
|
647
|
-
color: var(--text-muted);
|
|
648
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
649
|
-
margin-bottom: 0.75rem; /* More space between time and role */
|
|
650
|
-
line-height: 1.4;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
.timeline-role {
|
|
654
|
-
font-weight: 600;
|
|
655
|
-
color: var(--text-primary);
|
|
656
|
-
margin-bottom: 0.5rem; /* More space after role */
|
|
657
|
-
font-size: 0.9rem;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
.timeline-content {
|
|
661
|
-
background: rgba(255, 255, 255, 0.05);
|
|
662
|
-
border-radius: 12px;
|
|
663
|
-
padding: 1.5rem 1.75rem; /* Increased padding for better readability */
|
|
664
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
665
|
-
backdrop-filter: blur(8px);
|
|
666
|
-
transition: all 0.2s ease;
|
|
667
|
-
margin-bottom: 0.5rem; /* Space between content boxes */
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
.timeline-content:hover {
|
|
671
|
-
background: rgba(255, 255, 255, 0.08);
|
|
672
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
673
|
-
transform: translateY(-1px);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
.activity-role {
|
|
677
|
-
font-weight: 600;
|
|
678
|
-
color: var(--text-primary);
|
|
679
|
-
margin-bottom: 0.25rem;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
.activity-note {
|
|
683
|
-
font-size: 0.9rem; /* Slightly larger for better readability */
|
|
684
|
-
color: var(--text-secondary);
|
|
685
|
-
line-height: 1.5;
|
|
686
|
-
margin-bottom: 0.5rem;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/* Responsive design */
|
|
690
|
-
@media (max-width: 768px) {
|
|
691
|
-
.metrics-grid {
|
|
692
|
-
grid-template-columns: 1fr;
|
|
693
|
-
padding: 1rem;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
.spec-container {
|
|
697
|
-
margin: 1rem;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
.modal-content {
|
|
701
|
-
grid-template-columns: 1fr;
|
|
702
|
-
grid-template-rows: auto auto 1fr auto;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
.modal-sidebar,
|
|
706
|
-
.modal-activity {
|
|
707
|
-
border: none;
|
|
708
|
-
border-top: 1px solid var(--border-glass);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/* Loading animation */
|
|
713
|
-
.loading {
|
|
714
|
-
display: inline-block;
|
|
715
|
-
width: 20px;
|
|
716
|
-
height: 20px;
|
|
717
|
-
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
718
|
-
border-radius: 50%;
|
|
719
|
-
border-top-color: #ffffff;
|
|
720
|
-
animation: spin 1s ease-in-out infinite;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
@keyframes spin {
|
|
724
|
-
to { transform: rotate(360deg); }
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
/* Fade in animation */
|
|
728
|
-
.fade-in {
|
|
729
|
-
animation: fadeIn 0.5s ease-out;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
@keyframes fadeIn {
|
|
733
|
-
from { opacity: 0; transform: translateY(20px); }
|
|
734
|
-
to { opacity: 1; transform: translateY(0); }
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/* Status Banner */
|
|
738
|
-
.status-banner {
|
|
739
|
-
background: var(--bg-glass);
|
|
740
|
-
backdrop-filter: blur(12px);
|
|
741
|
-
border: 1px solid var(--border-glass);
|
|
742
|
-
border-left: 4px solid var(--accent-orange);
|
|
743
|
-
border-radius: 16px;
|
|
744
|
-
margin: 1.5rem 2rem;
|
|
745
|
-
padding: 1.5rem 2rem;
|
|
746
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
.status-content {
|
|
750
|
-
display: grid;
|
|
751
|
-
grid-template-columns: 1fr auto 1fr;
|
|
752
|
-
align-items: center;
|
|
753
|
-
gap: 1rem;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
.status-left {
|
|
757
|
-
display: flex;
|
|
758
|
-
align-items: center;
|
|
759
|
-
gap: 0.75rem;
|
|
760
|
-
font-size: 0.95rem;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
.workshop-icon {
|
|
764
|
-
font-size: 1.2rem;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
.workshop-name {
|
|
768
|
-
font-weight: 600;
|
|
769
|
-
color: var(--text-primary);
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
.status-separator {
|
|
773
|
-
color: var(--text-muted);
|
|
774
|
-
margin: 0 0.25rem;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
.build-status .building {
|
|
778
|
-
color: var(--accent-yellow);
|
|
779
|
-
display: flex;
|
|
780
|
-
align-items: center;
|
|
781
|
-
gap: 0.5rem;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
.build-status .idle {
|
|
785
|
-
color: var(--text-secondary);
|
|
786
|
-
display: flex;
|
|
787
|
-
align-items: center;
|
|
788
|
-
gap: 0.5rem;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
.building-icon {
|
|
792
|
-
animation: spin 2s linear infinite;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
.status-right {
|
|
796
|
-
font-size: 0.9rem;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
.worktree-stats {
|
|
800
|
-
display: flex;
|
|
801
|
-
align-items: center;
|
|
802
|
-
gap: 0.5rem;
|
|
803
|
-
flex-wrap: wrap;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
.stat-label {
|
|
807
|
-
color: var(--text-secondary);
|
|
808
|
-
font-weight: 600;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
.stat-value {
|
|
812
|
-
color: var(--text-primary);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
.stat-value.draft {
|
|
816
|
-
color: #9ca3af;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
.stat-value.in-progress {
|
|
820
|
-
color: var(--accent-yellow);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
.stat-value.done {
|
|
824
|
-
color: var(--accent-green);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
.stat-value.committed {
|
|
828
|
-
color: var(--accent-green);
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
.stat-value.dirty {
|
|
832
|
-
color: var(--accent-yellow);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
.stat-value.clean {
|
|
836
|
-
color: var(--text-secondary);
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
/* Responsive adjustments for status banner */
|
|
840
|
-
@media (max-width: 768px) {
|
|
841
|
-
.status-banner {
|
|
842
|
-
margin: 1rem;
|
|
843
|
-
padding: 1rem;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
.status-content {
|
|
847
|
-
flex-direction: column;
|
|
848
|
-
align-items: flex-start;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
.worktree-stats {
|
|
852
|
-
font-size: 0.8rem;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/* tmux Connect Modal Styles */
|
|
857
|
-
.session-item {
|
|
858
|
-
display: flex;
|
|
859
|
-
align-items: center;
|
|
860
|
-
justify-content: space-between;
|
|
861
|
-
padding: 1rem;
|
|
862
|
-
border-bottom: 1px solid var(--border-glass);
|
|
863
|
-
margin-bottom: 0.5rem;
|
|
864
|
-
background: rgba(0, 0, 0, 0.2);
|
|
865
|
-
border-radius: 8px;
|
|
866
|
-
transition: all 0.2s ease;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
.session-item:hover {
|
|
870
|
-
background: rgba(0, 0, 0, 0.3);
|
|
871
|
-
transform: translateY(-1px);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
.session-item.last-item {
|
|
875
|
-
border-bottom: none;
|
|
876
|
-
margin-bottom: 0;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
.session-info {
|
|
880
|
-
flex: 1;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
.session-name {
|
|
884
|
-
font-weight: 600;
|
|
885
|
-
color: var(--text-primary);
|
|
886
|
-
font-size: 1rem;
|
|
887
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
.session-details {
|
|
891
|
-
font-size: 0.85rem;
|
|
892
|
-
color: var(--text-secondary);
|
|
893
|
-
margin-top: 0.3rem;
|
|
894
|
-
display: flex;
|
|
895
|
-
gap: 1rem;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
.session-actions {
|
|
899
|
-
display: flex;
|
|
900
|
-
gap: 0.5rem;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
.session-stats {
|
|
904
|
-
font-size: 0.8rem;
|
|
905
|
-
color: var(--text-muted);
|
|
906
|
-
margin-top: 1rem;
|
|
907
|
-
text-align: center;
|
|
908
|
-
padding-top: 1rem;
|
|
909
|
-
border-top: 1px solid var(--border-glass);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
.btn {
|
|
913
|
-
padding: 0.5rem 1rem;
|
|
914
|
-
border-radius: 8px;
|
|
915
|
-
font-size: 0.85rem;
|
|
916
|
-
font-weight: 500;
|
|
917
|
-
cursor: pointer;
|
|
918
|
-
transition: all 0.2s ease;
|
|
919
|
-
border: 1px solid transparent;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
.btn-primary {
|
|
923
|
-
background: linear-gradient(135deg, rgba(59, 130, 246, 0.9) 0%, rgba(59, 130, 246, 1) 100%);
|
|
924
|
-
border: 1px solid rgba(59, 130, 246, 1);
|
|
925
|
-
color: white;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
.btn-primary:hover {
|
|
929
|
-
background: linear-gradient(135deg, rgba(59, 130, 246, 1) 0%, rgba(79, 150, 255, 1) 100%);
|
|
930
|
-
transform: translateY(-1px);
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
.btn-secondary {
|
|
934
|
-
background: rgba(34, 197, 94, 0.1);
|
|
935
|
-
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
936
|
-
color: rgba(34, 197, 94, 1);
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
.btn-secondary:hover {
|
|
940
|
-
background: rgba(34, 197, 94, 0.2);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
.btn-danger {
|
|
944
|
-
background: rgba(239, 68, 68, 0.1);
|
|
945
|
-
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
946
|
-
color: rgba(239, 68, 68, 1);
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
.btn-danger:hover {
|
|
950
|
-
background: rgba(239, 68, 68, 0.2);
|
|
951
|
-
}
|
|
952
|
-
</style>
|
|
953
|
-
</head>
|
|
954
|
-
|
|
955
|
-
<script>
|
|
956
|
-
document.addEventListener('alpine:init', () => {
|
|
957
|
-
Alpine.data('dashboard', () => ({
|
|
958
|
-
selectedSpec: null,
|
|
959
|
-
section: 'summary',
|
|
960
|
-
activityTab: 'activity',
|
|
961
|
-
specs: [],
|
|
962
|
-
filteredSpecs: [],
|
|
963
|
-
loading: false,
|
|
964
|
-
searchTerm: '',
|
|
965
|
-
categoryFilter: 'all',
|
|
966
|
-
statusFilter: 'all',
|
|
967
|
-
showActivityLog: false,
|
|
968
|
-
showWorktreeModal: false,
|
|
969
|
-
showTmuxModal: false,
|
|
970
|
-
activities: [],
|
|
971
|
-
|
|
972
|
-
// tmux integration
|
|
973
|
-
tmuxAvailable: false,
|
|
974
|
-
tmuxSessions: { sessions: [], total_sessions: 0, active_sessions: 0, detached_sessions: 0 },
|
|
975
|
-
selectedSpecForTmux: null,
|
|
976
|
-
claudePrompt: '',
|
|
977
|
-
|
|
978
|
-
// Toast notifications
|
|
979
|
-
toasts: [],
|
|
980
|
-
sortBy: 'created_at',
|
|
981
|
-
sortDirection: 'desc',
|
|
982
|
-
confirmDelete: null,
|
|
983
|
-
|
|
984
|
-
// Button state tracking for copy buttons
|
|
985
|
-
buttonStates: {},
|
|
986
|
-
hasContent(section) {
|
|
987
|
-
try {
|
|
988
|
-
if (!this.selectedSpec?.sections) return false;
|
|
989
|
-
const sectionData = this.selectedSpec.sections[section];
|
|
990
|
-
return sectionData && sectionData.content && sectionData.content.trim() !== '';
|
|
991
|
-
} catch (error) {
|
|
992
|
-
console.warn('Error checking content for section:', section, error);
|
|
993
|
-
return false;
|
|
994
|
-
}
|
|
995
|
-
},
|
|
996
|
-
getAvailableSections() {
|
|
997
|
-
try {
|
|
998
|
-
if (!this.selectedSpec?.sections) return [
|
|
999
|
-
{ id: 'summary', label: 'Summary', key: 'executive_summary' }
|
|
1000
|
-
];
|
|
1001
|
-
const sections = [
|
|
1002
|
-
{ id: 'summary', label: 'Summary', key: 'executive_summary' },
|
|
1003
|
-
{ id: 'product', label: 'Product', key: 'product_specifications' },
|
|
1004
|
-
{ id: 'architecture', label: 'Architecture', key: 'architecture_analysis' },
|
|
1005
|
-
{ id: 'implementation', label: 'Implementation', key: 'implementation_plan' },
|
|
1006
|
-
{ id: 'research', label: 'Research', key: 'research' },
|
|
1007
|
-
{ id: 'testing', label: 'Testing', key: 'testing' },
|
|
1008
|
-
{ id: 'review', label: 'Review', key: 'review' }
|
|
1009
|
-
];
|
|
1010
|
-
return sections.filter(section => this.hasContent(section.key));
|
|
1011
|
-
} catch (error) {
|
|
1012
|
-
console.warn('Error getting available sections:', error);
|
|
1013
|
-
return [{ id: 'summary', label: 'Summary', key: 'executive_summary' }];
|
|
1014
|
-
}
|
|
1015
|
-
},
|
|
1016
|
-
async fetchSpecs() {
|
|
1017
|
-
this.loading = true;
|
|
1018
|
-
try {
|
|
1019
|
-
const response = await fetch('/api/tools/bob.spec.list');
|
|
1020
|
-
const data = await response.json();
|
|
1021
|
-
this.specs = data.specs || [];
|
|
1022
|
-
this.filteredSpecs = [...this.specs];
|
|
1023
|
-
this.filterSpecs();
|
|
1024
|
-
} catch (error) {
|
|
1025
|
-
console.error('Failed to fetch specs:', error);
|
|
1026
|
-
} finally {
|
|
1027
|
-
this.loading = false;
|
|
1028
|
-
}
|
|
1029
|
-
},
|
|
1030
|
-
sortSpecs(column) {
|
|
1031
|
-
if (this.sortBy === column) {
|
|
1032
|
-
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
|
1033
|
-
} else {
|
|
1034
|
-
this.sortBy = column;
|
|
1035
|
-
this.sortDirection = column === 'created_at' ? 'desc' : 'asc';
|
|
1036
|
-
}
|
|
1037
|
-
this.filterSpecs();
|
|
1038
|
-
},
|
|
1039
|
-
filterSpecs() {
|
|
1040
|
-
this.filteredSpecs = this.specs.filter(spec => {
|
|
1041
|
-
const matchesSearch = this.searchTerm === '' ||
|
|
1042
|
-
spec.title.toLowerCase().includes(this.searchTerm.toLowerCase());
|
|
1043
|
-
const matchesCategory = this.categoryFilter === 'all' ||
|
|
1044
|
-
spec.category === this.categoryFilter;
|
|
1045
|
-
const matchesStatus = this.statusFilter === 'all' ||
|
|
1046
|
-
spec.state === this.statusFilter;
|
|
1047
|
-
return matchesSearch && matchesCategory && matchesStatus;
|
|
1048
|
-
});
|
|
1049
|
-
|
|
1050
|
-
// Sort filtered results
|
|
1051
|
-
this.filteredSpecs.sort((a, b) => {
|
|
1052
|
-
let valueA, valueB;
|
|
1053
|
-
|
|
1054
|
-
switch (this.sortBy) {
|
|
1055
|
-
case 'title':
|
|
1056
|
-
valueA = a.title?.toLowerCase() || '';
|
|
1057
|
-
valueB = b.title?.toLowerCase() || '';
|
|
1058
|
-
break;
|
|
1059
|
-
case 'category':
|
|
1060
|
-
valueA = a.category?.toLowerCase() || '';
|
|
1061
|
-
valueB = b.category?.toLowerCase() || '';
|
|
1062
|
-
break;
|
|
1063
|
-
case 'state':
|
|
1064
|
-
valueA = a.state?.toLowerCase() || '';
|
|
1065
|
-
valueB = b.state?.toLowerCase() || '';
|
|
1066
|
-
break;
|
|
1067
|
-
case 'created_at':
|
|
1068
|
-
case 'modified':
|
|
1069
|
-
valueA = new Date(a.created_at || a.updated_at || '').getTime();
|
|
1070
|
-
valueB = new Date(b.created_at || b.updated_at || '').getTime();
|
|
1071
|
-
break;
|
|
1072
|
-
default:
|
|
1073
|
-
return 0;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
if (this.sortBy === 'created_at' || this.sortBy === 'modified') {
|
|
1077
|
-
return this.sortDirection === 'desc' ? valueB - valueA : valueA - valueB;
|
|
1078
|
-
} else {
|
|
1079
|
-
if (valueA < valueB) return this.sortDirection === 'desc' ? 1 : -1;
|
|
1080
|
-
if (valueA > valueB) return this.sortDirection === 'desc' ? -1 : 1;
|
|
1081
|
-
return 0;
|
|
1082
|
-
}
|
|
1083
|
-
});
|
|
1084
|
-
},
|
|
1085
|
-
confirmDeleteSpec(specId, title) {
|
|
1086
|
-
console.log('Confirming delete for:', specId, title);
|
|
1087
|
-
this.confirmDelete = { id: specId, title: title };
|
|
1088
|
-
},
|
|
1089
|
-
cancelDelete() {
|
|
1090
|
-
this.confirmDelete = null;
|
|
1091
|
-
},
|
|
1092
|
-
async deleteSpec() {
|
|
1093
|
-
if (!this.confirmDelete) {
|
|
1094
|
-
console.error('No confirmDelete object found');
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
const specId = this.confirmDelete.id;
|
|
1099
|
-
const title = this.confirmDelete.title;
|
|
1100
|
-
|
|
1101
|
-
console.log('Deleting spec:', specId, title);
|
|
1102
|
-
|
|
1103
|
-
try {
|
|
1104
|
-
const response = await fetch('/api/tools/bob.spec.delete', {
|
|
1105
|
-
method: 'POST',
|
|
1106
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1107
|
-
body: JSON.stringify({ spec_id: specId })
|
|
1108
|
-
});
|
|
1109
|
-
|
|
1110
|
-
const result = await response.json();
|
|
1111
|
-
console.log('Delete response:', result);
|
|
1112
|
-
|
|
1113
|
-
if (response.ok && (result.status === 'deleted' || result.success)) {
|
|
1114
|
-
// Remove from local arrays
|
|
1115
|
-
this.specs = this.specs.filter(spec => spec.spec_id !== specId);
|
|
1116
|
-
this.filteredSpecs = this.filteredSpecs.filter(spec => spec.spec_id !== specId);
|
|
1117
|
-
this.confirmDelete = null; // Close the confirmation modal
|
|
1118
|
-
this.showToast(`✅ Manual "${title}" deleted successfully`, 'success');
|
|
1119
|
-
} else {
|
|
1120
|
-
console.error('Delete failed:', result);
|
|
1121
|
-
this.showToast(`❌ Failed to delete manual: ${result.error || result.message || 'Unknown error'}`, 'error');
|
|
1122
|
-
}
|
|
1123
|
-
} catch (error) {
|
|
1124
|
-
console.error('Error deleting spec:', error);
|
|
1125
|
-
this.showToast('❌ Error deleting manual. Please try again.', 'error');
|
|
1126
|
-
}
|
|
1127
|
-
},
|
|
1128
|
-
async openSpec(specId) {
|
|
1129
|
-
try {
|
|
1130
|
-
const response = await fetch('/api/tools/bob.spec.get', {
|
|
1131
|
-
method: 'POST',
|
|
1132
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1133
|
-
body: JSON.stringify({ spec_id: specId })
|
|
1134
|
-
});
|
|
1135
|
-
const spec = await response.json();
|
|
1136
|
-
this.selectedSpec = spec;
|
|
1137
|
-
// Set to first available section with content
|
|
1138
|
-
const availableSections = this.getAvailableSections();
|
|
1139
|
-
this.section = availableSections.length > 0 ? availableSections[0].id : 'summary';
|
|
1140
|
-
} catch (error) {
|
|
1141
|
-
console.error('Failed to fetch spec:', error);
|
|
1142
|
-
alert('Failed to load SPEC details');
|
|
1143
|
-
}
|
|
1144
|
-
},
|
|
1145
|
-
async exportToMarkdown(specId) {
|
|
1146
|
-
try {
|
|
1147
|
-
const response = await fetch('/api/tools/bob.spec.export', {
|
|
1148
|
-
method: 'POST',
|
|
1149
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1150
|
-
body: JSON.stringify({ spec_id: specId })
|
|
1151
|
-
});
|
|
1152
|
-
const data = await response.json();
|
|
1153
|
-
if (data.markdown_path) {
|
|
1154
|
-
alert(`✅ SPEC exported to: ${data.markdown_path}`);
|
|
1155
|
-
} else {
|
|
1156
|
-
alert('❌ Export failed');
|
|
1157
|
-
}
|
|
1158
|
-
} catch (error) {
|
|
1159
|
-
console.error('Export error:', error);
|
|
1160
|
-
alert('❌ Export failed');
|
|
1161
|
-
}
|
|
1162
|
-
},
|
|
1163
|
-
getWorktreeName(spec) {
|
|
1164
|
-
// Generate worktree name from spec title
|
|
1165
|
-
if (!spec || !spec.title) return 'none';
|
|
1166
|
-
return 'feature/' + spec.title.toLowerCase()
|
|
1167
|
-
.replace(/[^a-z0-9\s]/g, '')
|
|
1168
|
-
.replace(/\s+/g, '-')
|
|
1169
|
-
.substring(0, 20);
|
|
1170
|
-
},
|
|
1171
|
-
renderMarkdown(text) {
|
|
1172
|
-
if (!text) return '';
|
|
1173
|
-
|
|
1174
|
-
// Escape HTML first
|
|
1175
|
-
text = text.replace(/&/g, '&')
|
|
1176
|
-
.replace(/</g, '<')
|
|
1177
|
-
.replace(/>/g, '>');
|
|
1178
|
-
|
|
1179
|
-
// Handle code blocks with copy buttons first (before other processing)
|
|
1180
|
-
text = text.replace(/```bash\n([\s\S]*?)\n```/g, (match, code) => {
|
|
1181
|
-
const cleanCode = code.trim();
|
|
1182
|
-
const copyId = 'copy_' + Math.random().toString(36).substr(2, 9);
|
|
1183
|
-
return `<div class="code-block-container">
|
|
1184
|
-
<div class="code-block-header">
|
|
1185
|
-
<span class="code-lang">bash</span>
|
|
1186
|
-
<button class="copy-code-btn" onclick="copyToClipboard('${cleanCode}', '${copyId}')" id="${copyId}">📋 Copy</button>
|
|
1187
|
-
</div>
|
|
1188
|
-
<pre><code>${cleanCode}</code></pre>
|
|
1189
|
-
</div>`;
|
|
1190
|
-
});
|
|
1191
|
-
|
|
1192
|
-
// Handle generic code blocks
|
|
1193
|
-
text = text.replace(/```\n([\s\S]*?)\n```/g, (match, code) => {
|
|
1194
|
-
const cleanCode = code.trim();
|
|
1195
|
-
const copyId = 'copy_' + Math.random().toString(36).substr(2, 9);
|
|
1196
|
-
return `<div class="code-block-container">
|
|
1197
|
-
<div class="code-block-header">
|
|
1198
|
-
<span class="code-lang">code</span>
|
|
1199
|
-
<button class="copy-code-btn" onclick="copyToClipboard('${cleanCode}', '${copyId}')" id="${copyId}">📋 Copy</button>
|
|
1200
|
-
</div>
|
|
1201
|
-
<pre><code>${cleanCode}</code></pre>
|
|
1202
|
-
</div>`;
|
|
1203
|
-
});
|
|
1204
|
-
|
|
1205
|
-
// Simple markdown renderer
|
|
1206
|
-
let html = text
|
|
1207
|
-
// Headers
|
|
1208
|
-
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
|
1209
|
-
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
|
1210
|
-
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
|
1211
|
-
// Bold
|
|
1212
|
-
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
1213
|
-
// Italic
|
|
1214
|
-
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
1215
|
-
// Inline code
|
|
1216
|
-
.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>')
|
|
1217
|
-
// Line breaks
|
|
1218
|
-
.replace(/\n\n/g, '</p><p>')
|
|
1219
|
-
.replace(/\n/g, '<br>')
|
|
1220
|
-
// Lists
|
|
1221
|
-
.replace(/^\* (.*$)/gim, '<li>$1</li>')
|
|
1222
|
-
.replace(/^- (.*$)/gim, '<li>$1</li>')
|
|
1223
|
-
// Checkmarks and emojis
|
|
1224
|
-
.replace(/✅/g, '✅')
|
|
1225
|
-
.replace(/🚀/g, '🚀')
|
|
1226
|
-
.replace(/🔗/g, '🔗')
|
|
1227
|
-
.replace(/📋/g, '📋')
|
|
1228
|
-
// Wrap in paragraphs
|
|
1229
|
-
.replace(/^(?!<)/gm, '<p>')
|
|
1230
|
-
.replace(/$/gm, '</p>')
|
|
1231
|
-
// Clean up
|
|
1232
|
-
.replace(/<\/p><p>/g, '</p>\n<p>')
|
|
1233
|
-
.replace(/(<li>.*<\/li>)/gs, '<ul>$1</ul>')
|
|
1234
|
-
.replace(/<p><\/p>/g, '');
|
|
1235
|
-
|
|
1236
|
-
// Unescape the HTML tags we want
|
|
1237
|
-
return html.replace(/</g, '<').replace(/>/g, '>');
|
|
1238
|
-
},
|
|
1239
|
-
simplifyFilePath(fullPath) {
|
|
1240
|
-
if (!fullPath) return '';
|
|
1241
|
-
// Extract just the repo name and path after it
|
|
1242
|
-
const match = fullPath.match(/\/([^\/]+)\/(.+)$/);
|
|
1243
|
-
if (match && match[1] && match[2]) {
|
|
1244
|
-
return `${match[1]}/${match[2]}`;
|
|
1245
|
-
}
|
|
1246
|
-
return fullPath; // fallback to full path if pattern doesn't match
|
|
1247
|
-
},
|
|
1248
|
-
getActivityTimeline(spec) {
|
|
1249
|
-
if (!spec) return [];
|
|
1250
|
-
|
|
1251
|
-
const timeline = [];
|
|
1252
|
-
|
|
1253
|
-
// Add execution logs
|
|
1254
|
-
const executionLogs = spec.execution_logs || spec.execution_log || [];
|
|
1255
|
-
executionLogs.forEach(log => {
|
|
1256
|
-
timeline.push({
|
|
1257
|
-
...log,
|
|
1258
|
-
type: 'execution',
|
|
1259
|
-
role: 'Engineer'
|
|
1260
|
-
});
|
|
1261
|
-
});
|
|
1262
|
-
|
|
1263
|
-
// Add debug logs
|
|
1264
|
-
const debugLogs = spec.debug_logs || spec.debug_log || [];
|
|
1265
|
-
debugLogs.forEach(log => {
|
|
1266
|
-
timeline.push({
|
|
1267
|
-
...log,
|
|
1268
|
-
type: 'debug',
|
|
1269
|
-
role: 'Debugger'
|
|
1270
|
-
});
|
|
1271
|
-
});
|
|
1272
|
-
|
|
1273
|
-
// Add spec creation/update events if we have timeline data
|
|
1274
|
-
if (spec.timeline) {
|
|
1275
|
-
spec.timeline.forEach(event => {
|
|
1276
|
-
timeline.push({
|
|
1277
|
-
...event,
|
|
1278
|
-
type: event.type || 'system'
|
|
1279
|
-
});
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
// Add synthetic spec creation event if timeline is empty
|
|
1284
|
-
if (timeline.length === 0 && spec.created_at) {
|
|
1285
|
-
timeline.push({
|
|
1286
|
-
timestamp: spec.created_at,
|
|
1287
|
-
type: 'spec_created',
|
|
1288
|
-
role: 'Architect',
|
|
1289
|
-
action: 'Created SPEC',
|
|
1290
|
-
note: `SPEC ${spec.spec_id} created`
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
// Add worktree creation if it exists
|
|
1295
|
-
if (spec.worktree && timeline.length <= 2) {
|
|
1296
|
-
const workTreeTime = new Date(spec.modified_at || Date.now()).toISOString();
|
|
1297
|
-
timeline.push({
|
|
1298
|
-
timestamp: workTreeTime,
|
|
1299
|
-
type: 'worktree_created',
|
|
1300
|
-
role: 'System',
|
|
1301
|
-
action: 'worktree_created',
|
|
1302
|
-
note: `Created worktree: ${spec.worktree}`,
|
|
1303
|
-
branch: spec.worktree
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
// Sort by timestamp (newest first for better UX)
|
|
1308
|
-
return timeline.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).slice(0, 15);
|
|
1309
|
-
},
|
|
1310
|
-
async loadActivityLog() {
|
|
1311
|
-
try {
|
|
1312
|
-
// Load real activity from dashboard API
|
|
1313
|
-
const response = await fetch('/api/dashboard-data');
|
|
1314
|
-
const data = await response.json();
|
|
1315
|
-
|
|
1316
|
-
// Format recent changes for display
|
|
1317
|
-
this.activities = (data.recent_changes || []).slice(0, 20).map(change => ({
|
|
1318
|
-
timestamp: change.timestamp,
|
|
1319
|
-
type: change.event || change.type,
|
|
1320
|
-
author: change.role || 'system',
|
|
1321
|
-
message: this.formatActivityMessage(change),
|
|
1322
|
-
spec_id: change.spec_id,
|
|
1323
|
-
data: change.data
|
|
1324
|
-
}));
|
|
1325
|
-
|
|
1326
|
-
} catch (error) {
|
|
1327
|
-
console.error('Failed to load activity log:', error);
|
|
1328
|
-
// Fallback to empty state
|
|
1329
|
-
this.activities = [];
|
|
1330
|
-
}
|
|
1331
|
-
this.showActivityLog = true;
|
|
1332
|
-
},
|
|
1333
|
-
|
|
1334
|
-
formatActivityMessage(change) {
|
|
1335
|
-
if (change.event === 'orchestrator_routing') {
|
|
1336
|
-
return `Orchestrator routed to ${change.data?.target_mode} mode`;
|
|
1337
|
-
} else if (change.event === 'mcp_tool_workshop') {
|
|
1338
|
-
return `MCP tool executed: ${change.data?.tool_name} (${change.data?.duration_ms}ms)`;
|
|
1339
|
-
} else if (change.type === 'file') {
|
|
1340
|
-
return `File changed: ${change.path}`;
|
|
1341
|
-
} else {
|
|
1342
|
-
return change.event || change.type || 'Unknown activity';
|
|
1343
|
-
}
|
|
1344
|
-
},
|
|
1345
|
-
|
|
1346
|
-
showToast(message, type = 'info') {
|
|
1347
|
-
const toast = {
|
|
1348
|
-
id: Date.now() + Math.random(),
|
|
1349
|
-
message,
|
|
1350
|
-
type,
|
|
1351
|
-
visible: true
|
|
1352
|
-
};
|
|
1353
|
-
this.toasts.push(toast);
|
|
1354
|
-
|
|
1355
|
-
// Auto-remove after 4 seconds
|
|
1356
|
-
setTimeout(() => {
|
|
1357
|
-
this.removeToast(toast.id);
|
|
1358
|
-
}, 4000);
|
|
1359
|
-
},
|
|
1360
|
-
|
|
1361
|
-
removeToast(id) {
|
|
1362
|
-
const index = this.toasts.findIndex(toast => toast.id === id);
|
|
1363
|
-
if (index !== -1) {
|
|
1364
|
-
this.toasts.splice(index, 1);
|
|
1365
|
-
}
|
|
1366
|
-
},
|
|
1367
|
-
|
|
1368
|
-
// tmux integration methods
|
|
1369
|
-
async checkTmuxAvailability() {
|
|
1370
|
-
try {
|
|
1371
|
-
const response = await fetch('/api/tmux/check-availability');
|
|
1372
|
-
const data = await response.json();
|
|
1373
|
-
this.tmuxAvailable = data.available;
|
|
1374
|
-
return data.available;
|
|
1375
|
-
} catch (error) {
|
|
1376
|
-
console.error('Failed to check tmux availability:', error);
|
|
1377
|
-
this.tmuxAvailable = false;
|
|
1378
|
-
return false;
|
|
1379
|
-
}
|
|
1380
|
-
},
|
|
1381
|
-
|
|
1382
|
-
async loadTmuxSessions() {
|
|
1383
|
-
try {
|
|
1384
|
-
const response = await fetch('/api/tmux/list-sessions');
|
|
1385
|
-
const data = await response.json();
|
|
1386
|
-
this.tmuxSessions = data || { sessions: [], total_sessions: 0, active_sessions: 0, detached_sessions: 0 };
|
|
1387
|
-
} catch (error) {
|
|
1388
|
-
console.error('Failed to load tmux sessions:', error);
|
|
1389
|
-
this.tmuxSessions = { sessions: [], total_sessions: 0, active_sessions: 0, detached_sessions: 0 };
|
|
1390
|
-
}
|
|
1391
|
-
},
|
|
1392
|
-
|
|
1393
|
-
showTmuxModalForSpec(spec) {
|
|
1394
|
-
this.selectedSpecForTmux = spec;
|
|
1395
|
-
this.claudePrompt = `Create SPEC and build worktree for ${spec.title}`;
|
|
1396
|
-
this.showTmuxModal = true;
|
|
1397
|
-
this.loadTmuxSessions(); // Load sessions when modal opens
|
|
1398
|
-
},
|
|
1399
|
-
|
|
1400
|
-
async createMcpTmuxSession() {
|
|
1401
|
-
if (!this.claudePrompt.trim()) {
|
|
1402
|
-
this.addToast('Please enter a prompt for the MCP session', 'error');
|
|
1403
|
-
return;
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
try {
|
|
1407
|
-
const response = await fetch('/api/tmux/create-session', {
|
|
1408
|
-
method: 'POST',
|
|
1409
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1410
|
-
body: JSON.stringify({
|
|
1411
|
-
spec_id: 'mcp-workshop',
|
|
1412
|
-
worktree_id: 'workshop',
|
|
1413
|
-
worktree_path: '/Users/pawanraviee/Documents/GitHub/bobs-workshop',
|
|
1414
|
-
claude_prompt: this.claudePrompt
|
|
1415
|
-
})
|
|
1416
|
-
});
|
|
1417
|
-
|
|
1418
|
-
const result = await response.json();
|
|
1419
|
-
|
|
1420
|
-
if (response.ok) {
|
|
1421
|
-
this.addToast(`🚀 MCP tmux session created successfully!`, 'success');
|
|
1422
|
-
this.addToast(`Attach command: tmux attach -t "${result.session_name}"`, 'info');
|
|
1423
|
-
|
|
1424
|
-
// Copy attach command to clipboard
|
|
1425
|
-
try {
|
|
1426
|
-
await navigator.clipboard.writeText(`tmux attach -t "${result.session_name}"`);
|
|
1427
|
-
this.addToast('📋 Attach command copied to clipboard', 'info');
|
|
1428
|
-
} catch (clipError) {
|
|
1429
|
-
console.warn('Could not copy to clipboard:', clipError);
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
await this.loadTmuxSessions();
|
|
1433
|
-
this.showTmuxModal = false;
|
|
1434
|
-
this.claudePrompt = '';
|
|
1435
|
-
} else {
|
|
1436
|
-
throw new Error(result.error || 'Failed to create MCP tmux session');
|
|
1437
|
-
}
|
|
1438
|
-
} catch (error) {
|
|
1439
|
-
console.error('Failed to create MCP tmux session:', error);
|
|
1440
|
-
this.addToast(`Failed to create session: ${error.message}`, 'error');
|
|
1441
|
-
}
|
|
1442
|
-
},
|
|
1443
|
-
|
|
1444
|
-
async killTmuxSession(sessionName) {
|
|
1445
|
-
try {
|
|
1446
|
-
const response = await fetch('/api/tmux/kill-session', {
|
|
1447
|
-
method: 'POST',
|
|
1448
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1449
|
-
body: JSON.stringify({ session_name: sessionName })
|
|
1450
|
-
});
|
|
1451
|
-
|
|
1452
|
-
const result = await response.json();
|
|
1453
|
-
|
|
1454
|
-
if (response.ok) {
|
|
1455
|
-
this.addToast(`tmux session killed: ${sessionName}`, 'success');
|
|
1456
|
-
await this.loadTmuxSessions();
|
|
1457
|
-
} else {
|
|
1458
|
-
throw new Error(result.error || 'Failed to kill tmux session');
|
|
1459
|
-
}
|
|
1460
|
-
} catch (error) {
|
|
1461
|
-
console.error('Failed to kill tmux session:', error);
|
|
1462
|
-
this.addToast(`Failed to kill tmux session: ${error.message}`, 'error');
|
|
1463
|
-
}
|
|
1464
|
-
},
|
|
1465
|
-
async connectToTmuxSession(sessionName) {
|
|
1466
|
-
try {
|
|
1467
|
-
// For now, we'll just copy the attach command to clipboard
|
|
1468
|
-
// In the future, this could open a web-based terminal
|
|
1469
|
-
const attachCommand = `tmux attach -t "${sessionName}"`;
|
|
1470
|
-
await navigator.clipboard.writeText(attachCommand);
|
|
1471
|
-
this.addToast(`📋 Attach command copied: ${attachCommand}`, 'success');
|
|
1472
|
-
this.addToast('🌐 Web terminal connection coming soon! Use desktop tmux for now.', 'info');
|
|
1473
|
-
} catch (error) {
|
|
1474
|
-
console.error('Failed to connect to tmux session:', error);
|
|
1475
|
-
this.addToast(`Failed to connect: ${error.message}`, 'error');
|
|
1476
|
-
}
|
|
1477
|
-
},
|
|
1478
|
-
async copyAttachCommand(attachCommand) {
|
|
1479
|
-
try {
|
|
1480
|
-
await navigator.clipboard.writeText(attachCommand);
|
|
1481
|
-
this.addToast(`📋 Copied to clipboard: ${attachCommand}`, 'success');
|
|
1482
|
-
} catch (error) {
|
|
1483
|
-
console.error('Failed to copy attach command:', error);
|
|
1484
|
-
this.addToast(`Failed to copy command: ${error.message}`, 'error');
|
|
1485
|
-
}
|
|
1486
|
-
},
|
|
1487
|
-
async openDesktopTmux(sessionName) {
|
|
1488
|
-
try {
|
|
1489
|
-
// Try to open tmux directly using the system's default terminal
|
|
1490
|
-
const command = `tmux attach -t "${sessionName}"`;
|
|
1491
|
-
|
|
1492
|
-
// First copy the command to clipboard
|
|
1493
|
-
await navigator.clipboard.writeText(command);
|
|
1494
|
-
|
|
1495
|
-
// Use browser alert for now since this.addToast is not working
|
|
1496
|
-
alert(`📋 Command copied: ${command}`);
|
|
1497
|
-
alert('🖥️ Please run the copied command in your terminal');
|
|
1498
|
-
|
|
1499
|
-
// Close the modal
|
|
1500
|
-
this.showTmuxModal = false;
|
|
1501
|
-
|
|
1502
|
-
// Note: Direct terminal opening from web browsers is limited for security
|
|
1503
|
-
// The command is copied to clipboard as the most reliable method
|
|
1504
|
-
} catch (error) {
|
|
1505
|
-
console.error('Failed to open desktop tmux:', error);
|
|
1506
|
-
alert(`Failed to open terminal: ${error.message}`);
|
|
1507
|
-
}
|
|
1508
|
-
},
|
|
1509
|
-
|
|
1510
|
-
getTmuxSessionForSpec(specId) {
|
|
1511
|
-
const worktreeName = `feature/${specId}`;
|
|
1512
|
-
return this.tmuxSessions.find(session =>
|
|
1513
|
-
session.worktree_id === worktreeName ||
|
|
1514
|
-
session.session_name.includes(worktreeName)
|
|
1515
|
-
);
|
|
1516
|
-
},
|
|
1517
|
-
|
|
1518
|
-
getTmuxStatusIcon(status) {
|
|
1519
|
-
switch (status) {
|
|
1520
|
-
case 'active': return '🟢';
|
|
1521
|
-
case 'detached': return '🟡';
|
|
1522
|
-
case 'inactive': return '🔴';
|
|
1523
|
-
default: return '❓';
|
|
1524
|
-
}
|
|
1525
|
-
},
|
|
1526
|
-
|
|
1527
|
-
// Toast notification system
|
|
1528
|
-
toasts: [],
|
|
1529
|
-
toastId: 0,
|
|
1530
|
-
|
|
1531
|
-
addToast(message, type = 'info', duration = 4000) {
|
|
1532
|
-
const toast = {
|
|
1533
|
-
id: ++this.toastId,
|
|
1534
|
-
message,
|
|
1535
|
-
type,
|
|
1536
|
-
timestamp: Date.now()
|
|
1537
|
-
};
|
|
1538
|
-
|
|
1539
|
-
this.toasts.push(toast);
|
|
1540
|
-
console.log(`[${type.toUpperCase()}] ${message}`);
|
|
1541
|
-
|
|
1542
|
-
// Auto-remove toast after duration
|
|
1543
|
-
setTimeout(() => {
|
|
1544
|
-
this.removeToast(toast.id);
|
|
1545
|
-
}, duration);
|
|
1546
|
-
},
|
|
1547
|
-
|
|
1548
|
-
removeToast(id) {
|
|
1549
|
-
const index = this.toasts.findIndex(toast => toast.id === id);
|
|
1550
|
-
if (index > -1) {
|
|
1551
|
-
this.toasts.splice(index, 1);
|
|
1552
|
-
}
|
|
1553
|
-
},
|
|
1554
|
-
|
|
1555
|
-
getToastIcon(type) {
|
|
1556
|
-
switch (type) {
|
|
1557
|
-
case 'success': return '✅';
|
|
1558
|
-
case 'error': return '❌';
|
|
1559
|
-
case 'warning': return '⚠️';
|
|
1560
|
-
case 'info': return 'ℹ️';
|
|
1561
|
-
default: return '💬';
|
|
1562
|
-
}
|
|
1563
|
-
},
|
|
1564
|
-
|
|
1565
|
-
getToastColor(type) {
|
|
1566
|
-
switch (type) {
|
|
1567
|
-
case 'success': return 'rgba(34, 197, 94, 0.9)';
|
|
1568
|
-
case 'error': return 'rgba(239, 68, 68, 0.9)';
|
|
1569
|
-
case 'warning': return 'rgba(245, 158, 11, 0.9)';
|
|
1570
|
-
case 'info': return 'rgba(59, 130, 246, 0.9)';
|
|
1571
|
-
default: return 'rgba(75, 85, 99, 0.9)';
|
|
1572
|
-
}
|
|
1573
|
-
},
|
|
1574
|
-
|
|
1575
|
-
// Copy one-shot tmux launch command
|
|
1576
|
-
copyOneShotCommand() {
|
|
1577
|
-
const escapedPrompt = this.claudePrompt.replace(/"/g, '\\"');
|
|
1578
|
-
const command = `tmux new-session -d -s mcp-workshop -c '/Users/pawanraviee/Documents/GitHub/bobs-workshop' 'claude -p "${escapedPrompt}" --dangerously-skip-permissions'; tmux attach -t mcp-workshop`;
|
|
1579
|
-
|
|
1580
|
-
navigator.clipboard.writeText(command).then(() => {
|
|
1581
|
-
this.addToast('📋 One-shot command copied to clipboard!', 'success');
|
|
1582
|
-
}).catch(() => {
|
|
1583
|
-
this.addToast('Failed to copy command', 'error');
|
|
1584
|
-
});
|
|
1585
|
-
},
|
|
1586
|
-
|
|
1587
|
-
// Session management functions
|
|
1588
|
-
connectToTmuxDesktop(session) {
|
|
1589
|
-
// This would typically open a desktop tmux client or provide instructions
|
|
1590
|
-
this.addToast(`🖥️ Opening desktop connection for ${session.session_name}`, 'info');
|
|
1591
|
-
// For now, copy the attach command as fallback
|
|
1592
|
-
this.copyTmuxAttachCommand(session.session_name);
|
|
1593
|
-
},
|
|
1594
|
-
|
|
1595
|
-
copyTmuxAttachCommand(sessionName) {
|
|
1596
|
-
const command = `tmux attach -t "${sessionName}"`;
|
|
1597
|
-
navigator.clipboard.writeText(command).then(() => {
|
|
1598
|
-
this.addToast(`📋 Attach command copied: ${command}`, 'success');
|
|
1599
|
-
}).catch(() => {
|
|
1600
|
-
this.addToast('Failed to copy attach command', 'error');
|
|
1601
|
-
});
|
|
1602
|
-
},
|
|
1603
|
-
|
|
1604
|
-
// Enhanced copy method for modal quick connect (with button text change)
|
|
1605
|
-
copyTmuxAttachFromWorktree(worktreeName) {
|
|
1606
|
-
const command = `tmux attach -t "mcp-${worktreeName}"`;
|
|
1607
|
-
const buttonId = 'modal-quick-connect';
|
|
1608
|
-
|
|
1609
|
-
navigator.clipboard.writeText(command).then(() => {
|
|
1610
|
-
// Change button text instead of showing toast
|
|
1611
|
-
this.buttonStates[buttonId] = 'Copied!';
|
|
1612
|
-
|
|
1613
|
-
// Reset button text after 2 seconds
|
|
1614
|
-
setTimeout(() => {
|
|
1615
|
-
this.buttonStates[buttonId] = '📋';
|
|
1616
|
-
}, 2000);
|
|
1617
|
-
}).catch((error) => {
|
|
1618
|
-
console.error('Failed to copy command:', error);
|
|
1619
|
-
// Show error state briefly
|
|
1620
|
-
this.buttonStates[buttonId] = '❌';
|
|
1621
|
-
setTimeout(() => {
|
|
1622
|
-
this.buttonStates[buttonId] = '📋';
|
|
1623
|
-
}, 2000);
|
|
1624
|
-
});
|
|
1625
|
-
},
|
|
1626
|
-
|
|
1627
|
-
copyTmuxKillCommand(sessionName) {
|
|
1628
|
-
const command = `tmux kill-session -t "${sessionName}"`;
|
|
1629
|
-
navigator.clipboard.writeText(command).then(() => {
|
|
1630
|
-
this.addToast(`🗑 Kill command copied: ${command}`, 'success');
|
|
1631
|
-
}).catch(() => {
|
|
1632
|
-
this.addToast('Failed to copy kill command', 'error');
|
|
1633
|
-
});
|
|
1634
|
-
},
|
|
1635
|
-
|
|
1636
|
-
async killTmuxSession(sessionName) {
|
|
1637
|
-
if (!confirm(`Are you sure you want to kill the session "${sessionName}"?`)) {
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
try {
|
|
1642
|
-
const response = await fetch('/api/tmux/kill-session', {
|
|
1643
|
-
method: 'POST',
|
|
1644
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1645
|
-
body: JSON.stringify({ session_name: sessionName })
|
|
1646
|
-
});
|
|
1647
|
-
|
|
1648
|
-
if (response.ok) {
|
|
1649
|
-
this.addToast(`🗑 Session "${sessionName}" killed successfully`, 'success');
|
|
1650
|
-
// Reload sessions
|
|
1651
|
-
await this.loadTmuxSessions();
|
|
1652
|
-
} else {
|
|
1653
|
-
const error = await response.text();
|
|
1654
|
-
this.addToast(`Failed to kill session: ${error}`, 'error');
|
|
1655
|
-
}
|
|
1656
|
-
} catch (error) {
|
|
1657
|
-
this.addToast(`Error killing session: ${error.message}`, 'error');
|
|
1658
|
-
}
|
|
1659
|
-
},
|
|
1660
|
-
|
|
1661
|
-
// Load sessions when modal opens
|
|
1662
|
-
async loadTmuxSessionsForModal() {
|
|
1663
|
-
if (this.showTmuxModal) {
|
|
1664
|
-
await this.loadTmuxSessions();
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
}));
|
|
1668
|
-
});
|
|
1669
|
-
</script>
|
|
1670
|
-
|
|
1671
|
-
<body class="h-full" x-data="dashboard" x-init="fetchSpecs(); checkTmuxAvailability(); loadTmuxSessions()" @keydown.escape.window="selectedSpec = null; showActivityLog = false; showWorktreeModal = false; showTmuxModal = false">
|
|
1672
|
-
|
|
1673
|
-
<!-- Header -->
|
|
1674
|
-
<header class="header">
|
|
1675
|
-
<div class="logo">
|
|
1676
|
-
<span style="color: var(--accent-orange);">👷</span>
|
|
1677
|
-
<span class="text-gradient">Bob's Workshop</span>
|
|
1678
|
-
</div>
|
|
1679
|
-
|
|
1680
|
-
<!-- Separate Metric Boxes -->
|
|
1681
|
-
<div style="display: flex; align-items: center; gap: 1rem;"
|
|
1682
|
-
x-data="{
|
|
1683
|
-
worktrees: { committed: 0, dirty: 0, clean: 0 },
|
|
1684
|
-
specs: { draft: 0, 'in-progress': 0, completed: 0 },
|
|
1685
|
-
async loadMetrics() {
|
|
1686
|
-
try {
|
|
1687
|
-
const [worktreesRes, specsRes] = await Promise.all([
|
|
1688
|
-
fetch('/api/tools/bob.worktree.list').catch(() => ({ json: () => ({committed:0,dirty:0,clean:0}) })),
|
|
1689
|
-
fetch('/api/tools/bob.spec.list')
|
|
1690
|
-
]);
|
|
1691
|
-
const worktreesData = await worktreesRes.json();
|
|
1692
|
-
this.worktrees = worktreesData;
|
|
1693
|
-
const specsData = await specsRes.json();
|
|
1694
|
-
const allSpecs = specsData.specs || [];
|
|
1695
|
-
this.specs = {
|
|
1696
|
-
draft: allSpecs.filter(s => s.state === 'draft').length,
|
|
1697
|
-
'in-progress': allSpecs.filter(s => s.state === 'in-progress').length,
|
|
1698
|
-
completed: allSpecs.filter(s => s.state === 'completed').length
|
|
1699
|
-
};
|
|
1700
|
-
} catch (error) {
|
|
1701
|
-
console.error('Failed to load metrics:', error);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
}" x-init="loadMetrics()">
|
|
1705
|
-
|
|
1706
|
-
<!-- Manuals Box -->
|
|
1707
|
-
<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);">
|
|
1708
|
-
<span>📖 Manuals:</span>
|
|
1709
|
-
<span style="color: #9ca3af;" x-text="specs.draft + 'd'"></span>
|
|
1710
|
-
<span style="color: var(--text-muted);">|</span>
|
|
1711
|
-
<span style="color: var(--accent-orange);" x-text="specs['in-progress'] + 'p'"></span>
|
|
1712
|
-
<span style="color: var(--text-muted);">|</span>
|
|
1713
|
-
<span style="color: var(--accent-green);" x-text="specs.completed + '✓'"></span>
|
|
1714
|
-
</div>
|
|
1715
|
-
|
|
1716
|
-
<!-- Trees Box -->
|
|
1717
|
-
<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);">
|
|
1718
|
-
<span>🌿 Trees:</span>
|
|
1719
|
-
<span style="color: var(--accent-green);" x-text="(worktrees.committed?.length || 0) + 'c'"></span>
|
|
1720
|
-
<span style="color: var(--text-muted);">|</span>
|
|
1721
|
-
<span style="color: var(--accent-orange);" x-text="(worktrees.dirty?.length || 0) + '!'"></span>
|
|
1722
|
-
<span style="color: var(--text-muted);">|</span>
|
|
1723
|
-
<span style="color: var(--text-secondary);" x-text="(worktrees.clean?.length || 0) + '~'"></span>
|
|
1724
|
-
<button @click="showWorktreeModal = true"
|
|
1725
|
-
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;"
|
|
1726
|
-
@mouseenter="$el.style.background = '#e97b00'"
|
|
1727
|
-
@mouseleave="$el.style.background = 'var(--accent-orange)'"
|
|
1728
|
-
title="View Active Worktrees">
|
|
1729
|
-
🏗️ View
|
|
1730
|
-
</button>
|
|
1731
|
-
</div>
|
|
1732
|
-
|
|
1733
|
-
<!-- tmux Sessions Box -->
|
|
1734
|
-
<div x-show="tmuxAvailable" style="background: rgba(139, 69, 19, 0.12); backdrop-filter: blur(10px); border: 1px solid rgba(139, 69, 19, 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);">
|
|
1735
|
-
<span>🖥️ tmux:</span>
|
|
1736
|
-
<span style="color: var(--accent-green);" x-text="(tmuxSessions.sessions || []).filter(s => s.status === 'active').length + 'a'"></span>
|
|
1737
|
-
<span style="color: var(--text-muted);">|</span>
|
|
1738
|
-
<span style="color: var(--accent-orange);" x-text="(tmuxSessions.sessions || []).filter(s => s.status === 'detached').length + 'd'"></span>
|
|
1739
|
-
<span style="color: var(--text-muted);">|</span>
|
|
1740
|
-
<span style="color: var(--accent-red);" x-text="(tmuxSessions.sessions || []).filter(s => s.status === 'inactive').length + 'x'"></span>
|
|
1741
|
-
<button @click="loadTmuxSessions()"
|
|
1742
|
-
style="margin-left: 0.5rem; padding: 0.25rem 0.5rem; background: rgba(139, 69, 19, 0.8); border: 1px solid rgba(139, 69, 19, 0.8); border-radius: 6px; color: white; font-size: 0.75rem; cursor: pointer; transition: all 0.2s ease; font-weight: 600;"
|
|
1743
|
-
@mouseenter="$el.style.background = 'rgba(139, 69, 19, 1)'"
|
|
1744
|
-
@mouseleave="$el.style.background = 'rgba(139, 69, 19, 0.8)'"
|
|
1745
|
-
title="Refresh tmux Sessions">
|
|
1746
|
-
🔄 Sync
|
|
1747
|
-
</button>
|
|
1748
|
-
<button @click="showTmuxModal = true; claudePrompt = 'Help me work on this project using Bob\'s Workshop MCP tools'; loadTmuxSessions()"
|
|
1749
|
-
style="margin-left: 0.25rem; padding: 0.25rem 0.6rem; background: linear-gradient(135deg, rgba(139, 69, 19, 0.9) 0%, rgba(139, 69, 19, 1) 100%); border: 1px solid rgba(139, 69, 19, 1); border-radius: 6px; color: white; font-size: 0.75rem; cursor: pointer; transition: all 0.2s ease; font-weight: 600; box-shadow: 0 2px 8px rgba(139, 69, 19, 0.3);"
|
|
1750
|
-
@mouseenter="$el.style.background = 'linear-gradient(135deg, rgba(139, 69, 19, 1) 0%, rgba(160, 80, 20, 1) 100%)'; $el.style.transform = 'translateY(-1px)'"
|
|
1751
|
-
@mouseleave="$el.style.background = 'linear-gradient(135deg, rgba(139, 69, 19, 0.9) 0%, rgba(139, 69, 19, 1) 100%)'; $el.style.transform = 'translateY(0)'"
|
|
1752
|
-
title="Launch tmux Session - Create MCP development environment">
|
|
1753
|
-
🚀 Launch
|
|
1754
|
-
</button>
|
|
1755
|
-
</div>
|
|
1756
|
-
</div>
|
|
1757
|
-
|
|
1758
|
-
<div style="font-size: 0.9rem; color: var(--text-secondary); white-space: nowrap;"
|
|
1759
|
-
x-data="{
|
|
1760
|
-
projectName: 'bobs-workshop',
|
|
1761
|
-
workshopVerbs: ['servicing', 'working on', 'fixing', 'building', 'crafting', 'assembling', 'engineering', 'constructing', 'maintaining', 'upgrading'],
|
|
1762
|
-
currentVerb: 'servicing',
|
|
1763
|
-
init() {
|
|
1764
|
-
this.currentVerb = this.workshopVerbs[Math.floor(Math.random() * this.workshopVerbs.length)];
|
|
1765
|
-
}
|
|
1766
|
-
}">
|
|
1767
|
-
<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);">
|
|
1768
|
-
<span style="color: #fbbf24;">now</span>
|
|
1769
|
-
<span style="color: var(--accent-orange);" x-text="currentVerb"></span>
|
|
1770
|
-
<span style="color: var(--text-muted);">:</span>
|
|
1771
|
-
<span style="color: #fbbf24; font-weight: 600;" x-text="projectName"></span>
|
|
1772
|
-
</span>
|
|
1773
|
-
</div>
|
|
1774
|
-
</header>
|
|
1775
|
-
|
|
1776
|
-
<!-- Manual Table -->
|
|
1777
|
-
<div class="spec-container fade-in" style="margin: 2rem;">
|
|
1778
|
-
<!-- Header with integrated controls -->
|
|
1779
|
-
<div class="spec-header" style="padding: 1.5rem 2rem; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
|
|
1780
|
-
<div style="display: flex; align-items: center; gap: 1rem;">
|
|
1781
|
-
<h2 class="heading" style="margin: 0; font-size: 1.25rem;">🔧 Manual Explorer</h2>
|
|
1782
|
-
<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;"
|
|
1783
|
-
x-text="`${filteredSpecs.length} of ${specs.length} Manuals`"></span>
|
|
1784
|
-
<span x-show="loading" class="loading"></span>
|
|
1785
|
-
</div>
|
|
1786
|
-
|
|
1787
|
-
<!-- Integrated Controls Row -->
|
|
1788
|
-
<div style="display: flex; align-items: center; gap: 1rem; flex: 1; max-width: 800px;">
|
|
1789
|
-
<!-- Search Input -->
|
|
1790
|
-
<div style="flex: 2; min-width: 200px;">
|
|
1791
|
-
<input type="text"
|
|
1792
|
-
placeholder="🔍 Search Manuals..."
|
|
1793
|
-
x-model="searchTerm"
|
|
1794
|
-
@input="filterSpecs()"
|
|
1795
|
-
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;">
|
|
1796
|
-
</div>
|
|
1797
|
-
|
|
1798
|
-
<!-- Category Filter -->
|
|
1799
|
-
<select x-model="categoryFilter"
|
|
1800
|
-
@change="filterSpecs()"
|
|
1801
|
-
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;">
|
|
1802
|
-
<option value="all">All Categories</option>
|
|
1803
|
-
<option value="frontend">Frontend</option>
|
|
1804
|
-
<option value="backend">Backend</option>
|
|
1805
|
-
<option value="fullstack">Fullstack</option>
|
|
1806
|
-
<option value="general">General</option>
|
|
1807
|
-
</select>
|
|
1808
|
-
|
|
1809
|
-
<!-- Status Filter -->
|
|
1810
|
-
<select x-model="statusFilter"
|
|
1811
|
-
@change="filterSpecs()"
|
|
1812
|
-
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;">
|
|
1813
|
-
<option value="all">All Status</option>
|
|
1814
|
-
<option value="draft">Draft</option>
|
|
1815
|
-
<option value="planning">Planning</option>
|
|
1816
|
-
<option value="in-progress">In Progress</option>
|
|
1817
|
-
<option value="completed">Completed</option>
|
|
1818
|
-
</select>
|
|
1819
|
-
|
|
1820
|
-
<!-- Clear Filters -->
|
|
1821
|
-
<button class="btn btn-secondary"
|
|
1822
|
-
@click="searchTerm = ''; categoryFilter = 'all'; statusFilter = 'all'; filterSpecs()"
|
|
1823
|
-
style="padding: 0.5rem 0.75rem; font-size: 0.875rem; white-space: nowrap;">
|
|
1824
|
-
✕ Clear
|
|
1825
|
-
</button>
|
|
1826
|
-
|
|
1827
|
-
<!-- Refresh Button -->
|
|
1828
|
-
<button class="btn btn-secondary" @click="fetchSpecs()" style="padding: 0.5rem 0.75rem; font-size: 0.875rem; white-space: nowrap;">
|
|
1829
|
-
🔄 Refresh
|
|
1830
|
-
</button>
|
|
1831
|
-
</div>
|
|
1832
|
-
</div>
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
<table class="spec-table">
|
|
1836
|
-
<thead>
|
|
1837
|
-
<tr>
|
|
1838
|
-
<th class="sortable"
|
|
1839
|
-
:class="{ 'sorted-asc': sortBy === 'title' && sortDirection === 'asc', 'sorted-desc': sortBy === 'title' && sortDirection === 'desc' }"
|
|
1840
|
-
@click="sortSpecs('title')">Title</th>
|
|
1841
|
-
<th class="sortable"
|
|
1842
|
-
:class="{ 'sorted-asc': sortBy === 'category' && sortDirection === 'asc', 'sorted-desc': sortBy === 'category' && sortDirection === 'desc' }"
|
|
1843
|
-
@click="sortSpecs('category')">Category</th>
|
|
1844
|
-
<th class="sortable"
|
|
1845
|
-
:class="{ 'sorted-asc': sortBy === 'state' && sortDirection === 'asc', 'sorted-desc': sortBy === 'state' && sortDirection === 'desc' }"
|
|
1846
|
-
@click="sortSpecs('state')">Status</th>
|
|
1847
|
-
<th>Worktree</th>
|
|
1848
|
-
<th class="sortable"
|
|
1849
|
-
:class="{ 'sorted-asc': sortBy === 'modified' && sortDirection === 'asc', 'sorted-desc': sortBy === 'modified' && sortDirection === 'desc' }"
|
|
1850
|
-
@click="sortSpecs('modified')">Modified</th>
|
|
1851
|
-
<th>Actions</th>
|
|
1852
|
-
</tr>
|
|
1853
|
-
</thead>
|
|
1854
|
-
<tbody>
|
|
1855
|
-
<template x-for="spec in filteredSpecs" :key="spec.spec_id">
|
|
1856
|
-
<tr>
|
|
1857
|
-
<td>
|
|
1858
|
-
<div style="font-weight: 600; cursor: pointer; color: var(--text-primary);"
|
|
1859
|
-
x-text="spec.title"
|
|
1860
|
-
@click="openSpec(spec.spec_id)"></div>
|
|
1861
|
-
<div style="font-size: 0.75rem; color: var(--text-muted); margin-top: 0.25rem;"
|
|
1862
|
-
x-text="spec.spec_id"></div>
|
|
1863
|
-
</td>
|
|
1864
|
-
<td style="color: var(--text-secondary);" x-text="spec.category || '—'"></td>
|
|
1865
|
-
<td>
|
|
1866
|
-
<span class="status-badge"
|
|
1867
|
-
:class="`status-${spec.state}`"
|
|
1868
|
-
x-text="spec.state"></span>
|
|
1869
|
-
</td>
|
|
1870
|
-
<td style="color: var(--text-secondary); font-family: monospace; font-size: 0.875rem;">
|
|
1871
|
-
<div x-data="{ worktreeName: getWorktreeName(spec) }">
|
|
1872
|
-
<span x-show="worktreeName !== 'none'" x-text="worktreeName" style="color: var(--accent-blue);"></span>
|
|
1873
|
-
<span x-show="worktreeName === 'none'" style="color: var(--text-muted);">—</span>
|
|
1874
|
-
<div x-show="worktreeName !== 'none'" style="font-size: 0.75rem; margin-top: 0.25rem;">
|
|
1875
|
-
<span style="color: var(--accent-green);">✓ clean</span>
|
|
1876
|
-
</div>
|
|
1877
|
-
</div>
|
|
1878
|
-
</td>
|
|
1879
|
-
<td style="color: var(--text-secondary); font-size: 0.875rem;"
|
|
1880
|
-
x-text="new Date(spec.updated_at).toLocaleDateString()"></td>
|
|
1881
|
-
<td>
|
|
1882
|
-
<div style="display: flex; gap: 0.5rem;">
|
|
1883
|
-
<button class="btn btn-primary" style="padding: 0.5rem 1rem; font-size: 0.75rem;"
|
|
1884
|
-
@click="openSpec(spec.spec_id)">
|
|
1885
|
-
View
|
|
1886
|
-
</button>
|
|
1887
|
-
<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);"
|
|
1888
|
-
@click="confirmDeleteSpec(spec.spec_id, spec.title)"
|
|
1889
|
-
:title="`Delete ${spec.title}`">
|
|
1890
|
-
🗑
|
|
1891
|
-
</button>
|
|
1892
|
-
</div>
|
|
1893
|
-
</td>
|
|
1894
|
-
</tr>
|
|
1895
|
-
</template>
|
|
1896
|
-
</tbody>
|
|
1897
|
-
</table>
|
|
1898
|
-
|
|
1899
|
-
<div x-show="filteredSpecs.length === 0 && !loading"
|
|
1900
|
-
style="text-align: center; padding: 3rem; color: var(--text-muted);">
|
|
1901
|
-
<div x-show="specs.length === 0">No Manuals found. Create your first Manual to get started!</div>
|
|
1902
|
-
<div x-show="specs.length > 0">No Manuals match your current filters.</div>
|
|
1903
|
-
</div>
|
|
1904
|
-
|
|
1905
|
-
<!-- Activity Log Button -->
|
|
1906
|
-
<div style="padding: 1.5rem; border-top: 1px solid var(--border-glass); text-center;">
|
|
1907
|
-
<button class="btn btn-secondary" @click="loadActivityLog()">
|
|
1908
|
-
📊 View Activity Log
|
|
1909
|
-
</button>
|
|
1910
|
-
</div>
|
|
1911
|
-
</div>
|
|
1912
|
-
|
|
1913
|
-
<!-- Enhanced Modal -->
|
|
1914
|
-
<div class="modal-overlay"
|
|
1915
|
-
x-show="selectedSpec"
|
|
1916
|
-
x-transition:enter="transition ease-out duration-300"
|
|
1917
|
-
x-transition:enter-start="opacity-0"
|
|
1918
|
-
x-transition:enter-end="opacity-100"
|
|
1919
|
-
x-transition:leave="transition ease-in duration-200"
|
|
1920
|
-
x-transition:leave-start="opacity-100"
|
|
1921
|
-
x-transition:leave-end="opacity-0"
|
|
1922
|
-
@click.self="selectedSpec=null">
|
|
1923
|
-
|
|
1924
|
-
<div class="modal-content"
|
|
1925
|
-
x-show="selectedSpec"
|
|
1926
|
-
x-transition:enter="transition ease-out duration-300"
|
|
1927
|
-
x-transition:enter-start="opacity-0 scale-95"
|
|
1928
|
-
x-transition:enter-end="opacity-100 scale-100"
|
|
1929
|
-
x-transition:leave="transition ease-in duration-200"
|
|
1930
|
-
x-transition:leave-start="opacity-100 scale-100"
|
|
1931
|
-
x-transition:leave-end="opacity-0 scale-95">
|
|
1932
|
-
|
|
1933
|
-
<!-- Modal Header -->
|
|
1934
|
-
<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);">
|
|
1935
|
-
<!-- Top Row: Title, Status, and Worktree Info -->
|
|
1936
|
-
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
1937
|
-
<div class="modal-title-section">
|
|
1938
|
-
<h2 class="heading" style="margin: 0; font-size: 1.5rem;" x-text="selectedSpec?.title"></h2>
|
|
1939
|
-
<div style="display: flex; align-items: center; gap: 1rem;">
|
|
1940
|
-
<!-- Workflow Status Badge -->
|
|
1941
|
-
<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;">
|
|
1942
|
-
<span>📋</span>
|
|
1943
|
-
<span class="status-badge"
|
|
1944
|
-
:class="`status-${selectedSpec?.state}`"
|
|
1945
|
-
x-text="selectedSpec?.state || 'draft'"></span>
|
|
1946
|
-
</div>
|
|
1947
|
-
<!-- Worktree Info -->
|
|
1948
|
-
<div class="worktree-info" x-show="getWorktreeName(selectedSpec)">
|
|
1949
|
-
<span>🔧</span>
|
|
1950
|
-
<span x-text="getWorktreeName(selectedSpec)"></span>
|
|
1951
|
-
<span class="status-badge status-completed" style="font-size: 0.7rem; padding: 0.2rem 0.5rem;">✓ clean</span>
|
|
1952
|
-
</div>
|
|
1953
|
-
</div>
|
|
1954
|
-
</div>
|
|
1955
|
-
<div style="display: flex; gap: 1rem;">
|
|
1956
|
-
<button class="btn btn-primary" @click="exportToMarkdown(selectedSpec?.spec_id)">
|
|
1957
|
-
📄 Export
|
|
1958
|
-
</button>
|
|
1959
|
-
<button class="btn btn-secondary" @click="selectedSpec=null"
|
|
1960
|
-
style="background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.3); color: var(--accent-orange);"
|
|
1961
|
-
@mouseenter="$el.style.background = 'rgba(245, 158, 11, 0.2)'"
|
|
1962
|
-
@mouseleave="$el.style.background = 'rgba(245, 158, 11, 0.1)'">
|
|
1963
|
-
✕
|
|
1964
|
-
</button>
|
|
1965
|
-
</div>
|
|
1966
|
-
</div>
|
|
1967
|
-
|
|
1968
|
-
<!-- Second Row: Attach Command (Right-justified) -->
|
|
1969
|
-
<div x-show="getWorktreeName(selectedSpec)" style="display: flex; justify-content: flex-end;">
|
|
1970
|
-
<!-- Quick Connect -->
|
|
1971
|
-
<div style="background: rgba(34, 197, 94, 0.1); padding: 0.4rem 0.8rem; border-radius: 16px; font-size: 0.8rem; display: flex; align-items: center; gap: 0.4rem; border: 1px solid rgba(34, 197, 94, 0.3);">
|
|
1972
|
-
<span>💻</span>
|
|
1973
|
-
<span style="font-weight: 500; color: #22c55e; font-size: 0.75rem;">Attach</span>
|
|
1974
|
-
<span style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.7rem;" x-text="`tmux attach -t "mcp-${getWorktreeName(selectedSpec)}"`"></span>
|
|
1975
|
-
<button @click="copyTmuxAttachFromWorktree(getWorktreeName(selectedSpec))"
|
|
1976
|
-
id="modal-quick-connect"
|
|
1977
|
-
style="background: rgba(34, 197, 94, 0.2); border: 1px solid rgba(34, 197, 94, 0.4); color: #22c55e; padding: 0.15rem 0.3rem; border-radius: 4px; font-size: 0.65rem; cursor: pointer;"
|
|
1978
|
-
title="Copy tmux attach command"
|
|
1979
|
-
x-text="buttonStates['modal-quick-connect'] || '📋'">
|
|
1980
|
-
</button>
|
|
1981
|
-
</div>
|
|
1982
|
-
</div>
|
|
1983
|
-
</div>
|
|
1984
|
-
|
|
1985
|
-
<!-- Main Content Container -->
|
|
1986
|
-
<div class="modal-main-container">
|
|
1987
|
-
<!-- Navigation Tabs - Only show sections with content -->
|
|
1988
|
-
<div class="modal-nav">
|
|
1989
|
-
<div class="nav-tabs">
|
|
1990
|
-
<template x-for="sectionItem in getAvailableSections()" :key="sectionItem.id">
|
|
1991
|
-
<button class="nav-tab"
|
|
1992
|
-
:class="{'active': section === sectionItem.id}"
|
|
1993
|
-
@click="section = sectionItem.id"
|
|
1994
|
-
x-text="sectionItem.label">
|
|
1995
|
-
</button>
|
|
1996
|
-
</template>
|
|
1997
|
-
</div>
|
|
1998
|
-
</div>
|
|
1999
|
-
|
|
2000
|
-
<!-- Content Area -->
|
|
2001
|
-
<div class="modal-main">
|
|
2002
|
-
<template x-if="section==='summary'">
|
|
2003
|
-
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.executive_summary || selectedSpec?.sections?.executive_summary?.content || 'No executive summary yet')">
|
|
2004
|
-
</div>
|
|
2005
|
-
</template>
|
|
2006
|
-
|
|
2007
|
-
<template x-if="section==='product'">
|
|
2008
|
-
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.product_specifications?.content || 'No product specifications yet')">
|
|
2009
|
-
</div>
|
|
2010
|
-
</template>
|
|
2011
|
-
|
|
2012
|
-
<template x-if="section==='architecture'">
|
|
2013
|
-
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.architecture_analysis?.content || 'No architecture analysis yet')">
|
|
2014
|
-
</div>
|
|
2015
|
-
</template>
|
|
2016
|
-
|
|
2017
|
-
<template x-if="section==='implementation'">
|
|
2018
|
-
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.implementation_plan?.content || 'No implementation plan yet')">
|
|
2019
|
-
</div>
|
|
2020
|
-
</template>
|
|
2021
|
-
|
|
2022
|
-
<template x-if="section==='research'">
|
|
2023
|
-
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.research?.content || 'No research notes yet')">
|
|
2024
|
-
</div>
|
|
2025
|
-
</template>
|
|
2026
|
-
|
|
2027
|
-
<template x-if="section==='testing'">
|
|
2028
|
-
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.testing?.content || 'No testing notes yet')">
|
|
2029
|
-
</div>
|
|
2030
|
-
</template>
|
|
2031
|
-
|
|
2032
|
-
<template x-if="section==='review'">
|
|
2033
|
-
<div class="spec-content" x-html="renderMarkdown(selectedSpec?.sections?.review?.content || 'No review notes yet')">
|
|
2034
|
-
</div>
|
|
2035
|
-
</template>
|
|
2036
|
-
</div>
|
|
2037
|
-
</div>
|
|
2038
|
-
|
|
2039
|
-
<!-- Right Sidebar: Activity & Files -->
|
|
2040
|
-
<div class="modal-activity">
|
|
2041
|
-
<!-- Activity Navigation Tabs -->
|
|
2042
|
-
<div class="activity-nav">
|
|
2043
|
-
<div class="activity-tabs">
|
|
2044
|
-
<button class="activity-tab" :class="{'active': activityTab==='activity'}" @click="activityTab='activity'">📊 Activity</button>
|
|
2045
|
-
<button class="activity-tab" :class="{'active': activityTab==='files'}" @click="activityTab='files'">📁 Files</button>
|
|
2046
|
-
<button class="activity-tab" :class="{'active': activityTab==='tmux'}" @click="activityTab='tmux'" x-show="getWorktreeName(selectedSpec)">💻 tmux</button>
|
|
2047
|
-
</div>
|
|
2048
|
-
</div>
|
|
2049
|
-
|
|
2050
|
-
<!-- Activity Content -->
|
|
2051
|
-
<div class="activity-content">
|
|
2052
|
-
<!-- Activity Log Section - Timeline Visualization -->
|
|
2053
|
-
<div x-show="activityTab==='activity'">
|
|
2054
|
-
<div class="timeline-container" style="overflow-y: auto; height: 100%;">
|
|
2055
|
-
<div class="timeline-line"></div>
|
|
2056
|
-
|
|
2057
|
-
<!-- Combined Timeline -->
|
|
2058
|
-
<template x-for="event in getActivityTimeline(selectedSpec)" :key="event.timestamp + (event.type || 'unknown')">
|
|
2059
|
-
<div class="timeline-item">
|
|
2060
|
-
<!-- Left side: Time and Tool info -->
|
|
2061
|
-
<div class="timeline-left">
|
|
2062
|
-
<div class="timeline-time" x-text="new Date(event.timestamp).toLocaleString()"></div>
|
|
2063
|
-
<div style="display: flex; align-items: center; justify-content: flex-end; gap: 0.5rem;">
|
|
2064
|
-
<span class="status-badge"
|
|
2065
|
-
:class="{
|
|
2066
|
-
'status-completed': event.type === 'execution',
|
|
2067
|
-
'status-draft': event.type === 'debug',
|
|
2068
|
-
'status-planning': event.type === 'spec_created' || event.type === 'spec_updated',
|
|
2069
|
-
'status-warning': event.type === 'worktree_created' || event.type === 'system'
|
|
2070
|
-
}"
|
|
2071
|
-
x-text="event.role || 'System'">
|
|
2072
|
-
</span>
|
|
2073
|
-
<div style="font-size: 1.2rem;" x-text="event.role === 'DEBUGGER' ? '🐛' : event.role === 'ENGINEER' ? '⚙️' : event.role === 'ARCHITECT' ? '📐' : '🔧'"></div>
|
|
2074
|
-
</div>
|
|
2075
|
-
</div>
|
|
2076
|
-
|
|
2077
|
-
<!-- Timeline marker -->
|
|
2078
|
-
<div class="timeline-marker"
|
|
2079
|
-
:class="(event.role || 'system').toLowerCase()"></div>
|
|
2080
|
-
|
|
2081
|
-
<!-- Right side: Content -->
|
|
2082
|
-
<div class="timeline-right">
|
|
2083
|
-
<div class="timeline-content">
|
|
2084
|
-
<div class="timeline-role">
|
|
2085
|
-
<span x-text="event.action || event.note || event.message || event.issue || 'Activity recorded'"></span>
|
|
2086
|
-
</div>
|
|
2087
|
-
|
|
2088
|
-
<!-- Root cause & fix (debug logs) -->
|
|
2089
|
-
<div x-show="event.root_cause" style="margin: 0.75rem 0; font-size: 0.85rem; line-height: 1.5;">
|
|
2090
|
-
<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>
|
|
2091
|
-
<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>
|
|
2092
|
-
</div>
|
|
2093
|
-
|
|
2094
|
-
<!-- Task/Commit Info -->
|
|
2095
|
-
<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;">
|
|
2096
|
-
<span x-show="event.task_id" x-text="'Task: ' + event.task_id"></span>
|
|
2097
|
-
<span x-show="event.commit_hash" x-text="'Commit: ' + event.commit_hash.substring(0, 8)"></span>
|
|
2098
|
-
</div>
|
|
2099
|
-
|
|
2100
|
-
<!-- Files Modified -->
|
|
2101
|
-
<div x-show="event.files_changed && event.files_changed.length > 0" style="margin-top: 0.5rem;">
|
|
2102
|
-
<div style="color: var(--text-muted); margin-bottom: 0.25rem; font-size: 0.8rem;">
|
|
2103
|
-
<span x-text="event.files_changed.length + ' file(s) changed'"></span>
|
|
2104
|
-
</div>
|
|
2105
|
-
</div>
|
|
2106
|
-
|
|
2107
|
-
<!-- Branch Info -->
|
|
2108
|
-
<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;">
|
|
2109
|
-
<span x-text="'Branch: ' + event.branch"></span>
|
|
2110
|
-
</div>
|
|
2111
|
-
</div>
|
|
2112
|
-
</div>
|
|
2113
|
-
</div>
|
|
2114
|
-
</template>
|
|
2115
|
-
|
|
2116
|
-
<div x-show="getActivityTimeline(selectedSpec).length === 0"
|
|
2117
|
-
style="text-align: center; padding: 3rem; color: var(--text-muted); font-size: 0.875rem;">
|
|
2118
|
-
<div style="font-size: 2rem; margin-bottom: 1rem;">📋</div>
|
|
2119
|
-
<div>No activity recorded yet</div>
|
|
2120
|
-
</div>
|
|
2121
|
-
</div>
|
|
2122
|
-
</div>
|
|
2123
|
-
|
|
2124
|
-
</div>
|
|
2125
|
-
|
|
2126
|
-
<!-- Files Modified Section -->
|
|
2127
|
-
<div x-show="activityTab==='files'">
|
|
2128
|
-
<div style="overflow-y: auto; padding: 0.5rem 0;">
|
|
2129
|
-
<!-- Files from execution logs -->
|
|
2130
|
-
<template x-for="log in (selectedSpec?.execution_logs || selectedSpec?.execution_log || [])" :key="log.timestamp + 'exec'">
|
|
2131
|
-
<template x-for="file in (log.files_changed || [])" :key="log.timestamp + file">
|
|
2132
|
-
<div style="background: rgba(255, 255, 255, 0.02); border-radius: 6px; padding: 1rem; margin-bottom: 0.75rem; border: 1px solid rgba(255, 255, 255, 0.03);">
|
|
2133
|
-
<div style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.85rem; color: var(--text-primary); margin-bottom: 0.5rem;"
|
|
2134
|
-
x-text="simplifyFilePath(file)"></div>
|
|
2135
|
-
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
2136
|
-
<span class="status-badge status-completed"
|
|
2137
|
-
style="font-size: 0.7rem; padding: 0.2rem 0.6rem;">MODIFIED</span>
|
|
2138
|
-
<span style="font-size: 0.75rem; color: var(--text-muted);"
|
|
2139
|
-
x-text="new Date(log.timestamp).toLocaleTimeString()"></span>
|
|
2140
|
-
</div>
|
|
2141
|
-
<div x-show="log.action"
|
|
2142
|
-
style="margin-top: 0.75rem; font-size: 0.75rem; color: var(--text-secondary); background: rgba(255, 255, 255, 0.02); padding: 0.5rem; border-radius: 4px;">
|
|
2143
|
-
<span x-text="log.action"></span>
|
|
2144
|
-
</div>
|
|
2145
|
-
</div>
|
|
2146
|
-
</template>
|
|
2147
|
-
</template>
|
|
2148
|
-
|
|
2149
|
-
<!-- Direct file changes if available -->
|
|
2150
|
-
<template x-for="fileChange in (selectedSpec?.file_changes || []).slice(-15)" :key="fileChange.timestamp + fileChange.file">
|
|
2151
|
-
<div style="background: rgba(255, 255, 255, 0.02); border-radius: 6px; padding: 0.75rem; margin-bottom: 0.5rem; border: 1px solid rgba(255, 255, 255, 0.03);">
|
|
2152
|
-
<div style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.8rem; color: var(--text-primary); margin-bottom: 0.25rem;"
|
|
2153
|
-
x-text="fileChange.file"></div>
|
|
2154
|
-
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
2155
|
-
<span :class="{
|
|
2156
|
-
'status-completed': fileChange.type === 'modified',
|
|
2157
|
-
'status-in-progress': fileChange.type === 'created',
|
|
2158
|
-
'status-draft': fileChange.type === 'deleted'
|
|
2159
|
-
}"
|
|
2160
|
-
class="status-badge"
|
|
2161
|
-
style="font-size: 0.65rem; padding: 0.15rem 0.5rem;"
|
|
2162
|
-
x-text="fileChange.type || 'modified'"></span>
|
|
2163
|
-
<span style="font-size: 0.7rem; color: var(--text-muted);"
|
|
2164
|
-
x-text="new Date(fileChange.timestamp).toLocaleTimeString()"></span>
|
|
2165
|
-
</div>
|
|
2166
|
-
</div>
|
|
2167
|
-
</template>
|
|
2168
|
-
|
|
2169
|
-
<div x-show="(!selectedSpec?.execution_logs && !selectedSpec?.execution_log && !selectedSpec?.file_changes) ||
|
|
2170
|
-
((!selectedSpec?.execution_logs || selectedSpec.execution_logs.length === 0) &&
|
|
2171
|
-
(!selectedSpec?.execution_log || selectedSpec.execution_log.length === 0) &&
|
|
2172
|
-
(!selectedSpec?.file_changes || selectedSpec.file_changes.length === 0))"
|
|
2173
|
-
style="text-align: center; padding: 2rem; color: var(--text-muted); font-size: 0.875rem;">
|
|
2174
|
-
No files modified yet
|
|
2175
|
-
</div>
|
|
2176
|
-
</div>
|
|
2177
|
-
</div>
|
|
2178
|
-
|
|
2179
|
-
<!-- tmux Logs Section -->
|
|
2180
|
-
<div x-show="activityTab==='tmux' && getWorktreeName(selectedSpec)">
|
|
2181
|
-
<div style="overflow-y: auto; padding: 0.5rem 0; height: 100%;">
|
|
2182
|
-
<div 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);">
|
|
2183
|
-
<h4 style="margin: 0 0 0.75rem 0; color: #22c55e; font-size: 0.9rem; display: flex; align-items: center; gap: 0.5rem;">
|
|
2184
|
-
<span>💻</span>
|
|
2185
|
-
<span>Live tmux Session</span>
|
|
2186
|
-
</h4>
|
|
2187
|
-
<div style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.75rem;" x-text="`mcp-${getWorktreeName(selectedSpec)}`"></div>
|
|
2188
|
-
|
|
2189
|
-
<!-- Connect Instructions -->
|
|
2190
|
-
<div style="background: rgba(255, 255, 255, 0.02); padding: 0.75rem; border-radius: 6px; margin-bottom: 1rem;">
|
|
2191
|
-
<div style="font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.5rem;">Connect to view live logs:</div>
|
|
2192
|
-
<div style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.7rem; background: rgba(0, 0, 0, 0.3); padding: 0.5rem; border-radius: 4px; margin-bottom: 0.5rem;" x-text="`tmux attach -t \"mcp-${getWorktreeName(selectedSpec)}\"`"></div>
|
|
2193
|
-
<button @click="copyTmuxAttachFromWorktree(getWorktreeName(selectedSpec))"
|
|
2194
|
-
style="background: rgba(34, 197, 94, 0.2); border: 1px solid rgba(34, 197, 94, 0.4); color: #22c55e; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.7rem; cursor: pointer;">
|
|
2195
|
-
📋 Copy Command
|
|
2196
|
-
</button>
|
|
2197
|
-
</div>
|
|
2198
|
-
|
|
2199
|
-
<!-- Session Status -->
|
|
2200
|
-
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem; font-size: 0.75rem;">
|
|
2201
|
-
<div style="background: rgba(255, 255, 255, 0.02); padding: 0.5rem; border-radius: 4px;">
|
|
2202
|
-
<div style="color: var(--text-muted);">Status</div>
|
|
2203
|
-
<div style="color: #22c55e; font-weight: 500;">Active</div>
|
|
2204
|
-
</div>
|
|
2205
|
-
<div style="background: rgba(255, 255, 255, 0.02); padding: 0.5rem; border-radius: 4px;">
|
|
2206
|
-
<div style="color: var(--text-muted);">Location</div>
|
|
2207
|
-
<div style="font-family: monospace; font-size: 0.65rem;">./.bob/worktrees</div>
|
|
2208
|
-
</div>
|
|
2209
|
-
</div>
|
|
2210
|
-
</div>
|
|
2211
|
-
|
|
2212
|
-
<!-- Log Placeholder -->
|
|
2213
|
-
<div style="background: rgba(255, 255, 255, 0.02); border-radius: 6px; padding: 1rem; text-align: center; color: var(--text-muted);">
|
|
2214
|
-
<div style="font-size: 1.5rem; margin-bottom: 0.5rem;">📺</div>
|
|
2215
|
-
<div style="font-size: 0.875rem; margin-bottom: 0.5rem;">Real-time logs available in tmux session</div>
|
|
2216
|
-
<div style="font-size: 0.75rem;">Connect to the session above to view live output</div>
|
|
2217
|
-
</div>
|
|
2218
|
-
</div>
|
|
2219
|
-
</div>
|
|
2220
|
-
</div>
|
|
2221
|
-
</div>
|
|
2222
|
-
</div>
|
|
2223
|
-
</div>
|
|
2224
|
-
|
|
2225
|
-
<!-- Activity Log Modal -->
|
|
2226
|
-
<div class="modal-overlay"
|
|
2227
|
-
x-show="showActivityLog"
|
|
2228
|
-
x-transition:enter="transition ease-out duration-300"
|
|
2229
|
-
x-transition:enter-start="opacity-0"
|
|
2230
|
-
x-transition:enter-end="opacity-100"
|
|
2231
|
-
x-transition:leave="transition ease-in duration-200"
|
|
2232
|
-
x-transition:leave-start="opacity-100"
|
|
2233
|
-
x-transition:leave-end="opacity-0"
|
|
2234
|
-
@click.self="showActivityLog=false">
|
|
2235
|
-
|
|
2236
|
-
<div class="modal-content"
|
|
2237
|
-
x-show="showActivityLog"
|
|
2238
|
-
x-transition:enter="transition ease-out duration-300"
|
|
2239
|
-
x-transition:enter-start="opacity-0 scale-95"
|
|
2240
|
-
x-transition:enter-end="opacity-100 scale-100"
|
|
2241
|
-
x-transition:leave="transition ease-in duration-200"
|
|
2242
|
-
x-transition:leave-start="opacity-100 scale-100"
|
|
2243
|
-
x-transition:leave-end="opacity-0 scale-95"
|
|
2244
|
-
style="grid-template-columns: 1fr; grid-template-rows: auto 1fr;">
|
|
2245
|
-
|
|
2246
|
-
<!-- Activity Log Header -->
|
|
2247
|
-
<div class="modal-header">
|
|
2248
|
-
<h2 class="heading" style="margin: 0; font-size: 1.5rem;">📊 Activity Log</h2>
|
|
2249
|
-
<div style="display: flex; gap: 1rem;">
|
|
2250
|
-
<button class="btn btn-secondary" @click="showActivityLog=false">
|
|
2251
|
-
✕ Close
|
|
2252
|
-
</button>
|
|
2253
|
-
</div>
|
|
2254
|
-
</div>
|
|
2255
|
-
|
|
2256
|
-
<!-- Activity Log Content -->
|
|
2257
|
-
<div class="modal-main">
|
|
2258
|
-
<div style="margin-bottom: 1.5rem;">
|
|
2259
|
-
<h3 class="heading" style="margin: 0 0 1rem 0;">Recent Activity</h3>
|
|
2260
|
-
<div style="background: rgba(255, 255, 255, 0.03); padding: 1.5rem; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.05);">
|
|
2261
|
-
|
|
2262
|
-
<template x-for="activity in activities" :key="activity.timestamp">
|
|
2263
|
-
<div style="border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding: 1rem 0;">
|
|
2264
|
-
<div style="display: flex; justify-content: between; align-items: start; margin-bottom: 0.5rem;">
|
|
2265
|
-
<div>
|
|
2266
|
-
<span class="status-badge"
|
|
2267
|
-
:class="{
|
|
2268
|
-
'status-completed': activity.type === 'spec_created',
|
|
2269
|
-
'status-in-progress': activity.type === 'worktree_created',
|
|
2270
|
-
'status-planning': activity.type === 'commit',
|
|
2271
|
-
'status-draft': activity.type === 'spec_updated'
|
|
2272
|
-
}"
|
|
2273
|
-
x-text="activity.type.replace('_', ' ')"></span>
|
|
2274
|
-
<span style="margin-left: 1rem; color: var(--text-secondary); font-size: 0.875rem;"
|
|
2275
|
-
x-text="new Date(activity.timestamp).toLocaleString()"></span>
|
|
2276
|
-
</div>
|
|
2277
|
-
</div>
|
|
2278
|
-
<div style="color: var(--text-primary); margin-bottom: 0.5rem;"
|
|
2279
|
-
x-text="activity.message"></div>
|
|
2280
|
-
<div style="color: var(--text-secondary); font-size: 0.875rem;">
|
|
2281
|
-
<span>by </span>
|
|
2282
|
-
<span style="color: var(--accent-blue);" x-text="activity.author"></span>
|
|
2283
|
-
<template x-if="activity.files">
|
|
2284
|
-
<span>
|
|
2285
|
-
• Modified: <span x-text="activity.files.join(', ')"></span>
|
|
2286
|
-
</span>
|
|
2287
|
-
</template>
|
|
2288
|
-
</div>
|
|
2289
|
-
</div>
|
|
2290
|
-
</template>
|
|
2291
|
-
|
|
2292
|
-
<div x-show="activities.length === 0" style="text-align: center; padding: 2rem; color: var(--text-muted);">
|
|
2293
|
-
No recent activity
|
|
2294
|
-
</div>
|
|
2295
|
-
</div>
|
|
2296
|
-
</div>
|
|
2297
|
-
</div>
|
|
2298
|
-
</div>
|
|
2299
|
-
</div>
|
|
2300
|
-
|
|
2301
|
-
<!-- Worktree Modal -->
|
|
2302
|
-
<div class="modal-overlay"
|
|
2303
|
-
x-show="showWorktreeModal"
|
|
2304
|
-
x-transition:enter="transition ease-out duration-300"
|
|
2305
|
-
x-transition:enter-start="opacity-0"
|
|
2306
|
-
x-transition:enter-end="opacity-100"
|
|
2307
|
-
x-transition:leave="transition ease-in duration-200"
|
|
2308
|
-
x-transition:leave-start="opacity-100"
|
|
2309
|
-
x-transition:leave-end="opacity-0"
|
|
2310
|
-
@click.self="showWorktreeModal=false">
|
|
2311
|
-
<div class="modal-content"
|
|
2312
|
-
x-show="showWorktreeModal"
|
|
2313
|
-
x-transition:enter="transition ease-out duration-300"
|
|
2314
|
-
x-transition:enter-start="opacity-0 scale-95"
|
|
2315
|
-
x-transition:enter-end="opacity-100 scale-100"
|
|
2316
|
-
x-transition:leave="transition ease-in duration-200"
|
|
2317
|
-
x-transition:leave-start="opacity-100 scale-100"
|
|
2318
|
-
x-transition:leave-end="opacity-0 scale-95"
|
|
2319
|
-
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;">
|
|
2320
|
-
<!-- Worktree Modal Header -->
|
|
2321
|
-
<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);">
|
|
2322
|
-
<h2 class="heading" style="margin: 0; font-size: 1.5rem; color: var(--text-primary); display: flex; align-items: center; gap: 0.75rem;">
|
|
2323
|
-
<span style="color: var(--accent-orange); font-size: 1.75rem;">🏗️</span>
|
|
2324
|
-
<span>Active Worktrees</span>
|
|
2325
|
-
<span style="color: var(--text-muted); font-size: 0.9rem; font-weight: 400;">(Construction Zones)</span>
|
|
2326
|
-
</h2>
|
|
2327
|
-
<button class="btn btn-secondary" @click="showWorktreeModal=false"
|
|
2328
|
-
style="background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.3); color: var(--accent-orange);"
|
|
2329
|
-
@mouseenter="$el.style.background = 'rgba(245, 158, 11, 0.2)'"
|
|
2330
|
-
@mouseleave="$el.style.background = 'rgba(245, 158, 11, 0.1)'">
|
|
2331
|
-
🔧 Close
|
|
2332
|
-
</button>
|
|
2333
|
-
</div>
|
|
2334
|
-
|
|
2335
|
-
<!-- Worktree Modal Content -->
|
|
2336
|
-
<div class="modal-main" x-data="{
|
|
2337
|
-
worktreeDetails: [],
|
|
2338
|
-
async loadWorktreeDetails() {
|
|
2339
|
-
try {
|
|
2340
|
-
const response = await fetch('/api/tools/bob.worktree.list');
|
|
2341
|
-
const data = await response.json();
|
|
2342
|
-
this.worktreeDetails = [
|
|
2343
|
-
...data.committed?.map(name => ({ name, status: 'committed', color: 'var(--accent-green)', icon: '✅' })) || [],
|
|
2344
|
-
...data.dirty?.map(name => ({ name, status: 'dirty', color: 'var(--accent-yellow)', icon: '⚠️' })) || [],
|
|
2345
|
-
...data.clean?.map(name => ({ name, status: 'clean', color: 'var(--text-secondary)', icon: '💤' })) || []
|
|
2346
|
-
];
|
|
2347
|
-
} catch (error) {
|
|
2348
|
-
console.error('Failed to load worktree details:', error);
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
}" x-init="loadWorktreeDetails()">
|
|
2352
|
-
|
|
2353
|
-
<template x-if="worktreeDetails.length > 0">
|
|
2354
|
-
<div>
|
|
2355
|
-
<div style="display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));">
|
|
2356
|
-
<template x-for="worktree in worktreeDetails" :key="worktree.name">
|
|
2357
|
-
<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);"
|
|
2358
|
-
@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)'"
|
|
2359
|
-
@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)'"
|
|
2360
|
-
|
|
2361
|
-
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem;">
|
|
2362
|
-
<h3 style="margin: 0; font-size: 1rem; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; color: var(--text-primary);"
|
|
2363
|
-
x-text="worktree.name"></h3>
|
|
2364
|
-
<span style="font-size: 1.4rem; color: var(--accent-orange);">🏗️</span>
|
|
2365
|
-
</div>
|
|
2366
|
-
|
|
2367
|
-
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem;">
|
|
2368
|
-
<span class="status-badge"
|
|
2369
|
-
:class="{
|
|
2370
|
-
'status-completed': worktree.status === 'committed',
|
|
2371
|
-
'status-in-progress': worktree.status === 'dirty',
|
|
2372
|
-
'status-draft': worktree.status === 'clean'
|
|
2373
|
-
}"
|
|
2374
|
-
x-text="worktree.status"></span>
|
|
2375
|
-
</div>
|
|
2376
|
-
|
|
2377
|
-
<div style="font-size: 0.875rem; color: var(--text-muted);">
|
|
2378
|
-
<div>Branch: <span style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;" x-text="worktree.name"></span></div>
|
|
2379
|
-
<div style="margin-top: 0.5rem;">
|
|
2380
|
-
Status: <span :style="'color: ' + worktree.color" x-text="worktree.status"></span>
|
|
2381
|
-
</div>
|
|
2382
|
-
</div>
|
|
2383
|
-
</div>
|
|
2384
|
-
</template>
|
|
2385
|
-
</div>
|
|
2386
|
-
</div>
|
|
2387
|
-
</template>
|
|
2388
|
-
|
|
2389
|
-
<template x-if="worktreeDetails.length === 0">
|
|
2390
|
-
<div style="text-align: center; padding: 3rem; color: var(--text-muted);">
|
|
2391
|
-
<div style="font-size: 3rem; margin-bottom: 1rem; color: var(--accent-orange);">🏗️</div>
|
|
2392
|
-
<h3 style="color: var(--text-primary); font-family: var(--font-heading);">No Active Construction Zones</h3>
|
|
2393
|
-
<p style="color: var(--text-secondary);">All workshop activity is currently on the main branch.</p>
|
|
2394
|
-
<div style="margin-top: 1.5rem; font-size: 0.9rem; color: var(--text-muted);">
|
|
2395
|
-
<span style="color: var(--accent-orange);">🔧</span> Ready for new projects
|
|
2396
|
-
</div>
|
|
2397
|
-
</div>
|
|
2398
|
-
</template>
|
|
2399
|
-
</div>
|
|
2400
|
-
</div>
|
|
2401
|
-
</div>
|
|
2402
|
-
|
|
2403
|
-
<!-- tmux Session Creation Modal -->
|
|
2404
|
-
<div class="modal-overlay"
|
|
2405
|
-
x-show="showTmuxModal"
|
|
2406
|
-
x-transition:enter="transition ease-out duration-300"
|
|
2407
|
-
x-transition:enter-start="opacity-0"
|
|
2408
|
-
x-transition:enter-end="opacity-100"
|
|
2409
|
-
x-transition:leave="transition ease-in duration-200"
|
|
2410
|
-
x-transition:leave-start="opacity-100"
|
|
2411
|
-
x-transition:leave-end="opacity-0"
|
|
2412
|
-
@click.self="showTmuxModal=false">
|
|
2413
|
-
<div class="modal-content"
|
|
2414
|
-
x-show="showTmuxModal"
|
|
2415
|
-
x-transition:enter="transition ease-out duration-300"
|
|
2416
|
-
x-transition:enter-start="opacity-0 scale-95"
|
|
2417
|
-
x-transition:enter-end="opacity-100 scale-100"
|
|
2418
|
-
x-transition:leave="transition ease-in duration-200"
|
|
2419
|
-
x-transition:leave-start="opacity-100 scale-100"
|
|
2420
|
-
x-transition:leave-end="opacity-0 scale-95"
|
|
2421
|
-
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;">
|
|
2422
|
-
|
|
2423
|
-
<!-- tmux Modal Header -->
|
|
2424
|
-
<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);">
|
|
2425
|
-
<h2 class="heading" style="margin: 0; font-size: 1.5rem; color: var(--text-primary); display: flex; align-items: center; gap: 0.75rem;">
|
|
2426
|
-
<span style="color: var(--accent-orange); font-size: 1.75rem;">🚀</span>
|
|
2427
|
-
<span>Launch tmux MCP Session</span>
|
|
2428
|
-
<span style="color: var(--text-muted); font-size: 0.9rem; font-weight: 400;">(Create a persistent development session with Claude and MCP tools)</span>
|
|
2429
|
-
</h2>
|
|
2430
|
-
<button class="btn btn-secondary" @click="showTmuxModal=false; claudePrompt=''"
|
|
2431
|
-
style="background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.3); color: var(--accent-orange);"
|
|
2432
|
-
@mouseenter="$el.style.background = 'rgba(245, 158, 11, 0.2)'"
|
|
2433
|
-
@mouseleave="$el.style.background = 'rgba(245, 158, 11, 0.1)'">
|
|
2434
|
-
✕ Close
|
|
2435
|
-
</button>
|
|
2436
|
-
</div>
|
|
2437
|
-
|
|
2438
|
-
<!-- Combined Modal Content - Two Column Layout -->
|
|
2439
|
-
<div class="modal-main" style="display: flex; overflow: hidden;">
|
|
2440
|
-
<!-- Left Column: Launch Form -->
|
|
2441
|
-
<div style="flex: 1; padding: 2rem; border-right: 1px solid rgba(139, 69, 19, 0.2); background: rgba(0,0,0,0.02);">
|
|
2442
|
-
<h3 style="margin: 0 0 1rem 0; font-size: 1.1rem; font-weight: 600; color: var(--text-primary);">
|
|
2443
|
-
Use Bob's MCP to engineer the user's request below:
|
|
2444
|
-
</h3>
|
|
2445
|
-
|
|
2446
|
-
<textarea x-model="claudePrompt"
|
|
2447
|
-
placeholder="Enter your development task for Claude to execute with MCP tools... Examples: - Create a new React component with tests - Review and optimize my TypeScript codebase - Set up authentication with JWT tokens - Debug my API endpoints and fix issues - Create comprehensive documentation - Build a new feature from scratch"
|
|
2448
|
-
style="width: 100%; height: 140px; padding: 0.75rem; border: 1px solid rgba(139, 69, 19, 0.3); border-radius: 6px; background: rgba(0,0,0,0.15); color: var(--text-primary); font-family: var(--font-mono); resize: vertical; line-height: 1.4; margin-bottom: 1rem;"
|
|
2449
|
-
@focus="$el.style.borderColor = 'rgba(245, 158, 11, 0.5)'"
|
|
2450
|
-
@blur="$el.style.borderColor = 'rgba(139, 69, 19, 0.3)'"></textarea>
|
|
2451
|
-
|
|
2452
|
-
<div style="margin-bottom: 1rem; padding: 0.75rem; background: rgba(139, 69, 19, 0.1); border-left: 3px solid rgba(139, 69, 19, 0.4); border-radius: 0 4px 4px 0;">
|
|
2453
|
-
<p style="margin: 0; font-size: 0.85rem; color: var(--text-secondary); line-height: 1.4;">
|
|
2454
|
-
Claude will execute this prompt with full access to Bob's Workshop MCP tools in the persistent tmux session.
|
|
2455
|
-
</p>
|
|
2456
|
-
</div>
|
|
2457
|
-
|
|
2458
|
-
<!-- One-shot Launch Command -->
|
|
2459
|
-
<div style="background: linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(139, 69, 19, 0.08) 100%); border: 1px solid rgba(245, 158, 11, 0.3); border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem;">
|
|
2460
|
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
|
|
2461
|
-
<h4 style="margin: 0; color: var(--accent-orange); font-size: 0.9rem; font-weight: 600;">⚡ One-Shot Launch Command:</h4>
|
|
2462
|
-
<button @click="copyOneShotCommand()"
|
|
2463
|
-
style="background: rgba(245, 158, 11, 0.2); border: 1px solid rgba(245, 158, 11, 0.4); color: var(--accent-orange); padding: 0.4rem 0.6rem; border-radius: 4px; font-size: 0.7rem; cursor: pointer; font-weight: 500;"
|
|
2464
|
-
@mouseenter="$el.style.background = 'rgba(245, 158, 11, 0.3)'; $el.style.transform = 'translateY(-1px)'"
|
|
2465
|
-
@mouseleave="$el.style.background = 'rgba(245, 158, 11, 0.2)'; $el.style.transform = 'translateY(0)'">
|
|
2466
|
-
📋 Copy
|
|
2467
|
-
</button>
|
|
2468
|
-
</div>
|
|
2469
|
-
<div style="background: rgba(0,0,0,0.4); border-radius: 6px; padding: 0.75rem; margin: 0.5rem 0;">
|
|
2470
|
-
<code style="color: #e5e7eb; font-family: 'Monaco', 'Menlo', monospace; font-size: 0.75rem; word-break: break-all; line-height: 1.3; display: block;"
|
|
2471
|
-
x-text="`tmux new-session -d -s mcp-workshop -c '/Users/pawanraviee/Documents/GitHub/bobs-workshop' 'claude -p \"${claudePrompt.replace(/\"/g, '\\"')}\" --dangerously-skip-permissions'; tmux attach -t mcp-workshop`"></code>
|
|
2472
|
-
</div>
|
|
2473
|
-
<p style="margin: 0; color: var(--text-secondary); font-size: 0.8rem;">
|
|
2474
|
-
Copy and run this command in your terminal for instant session creation + attachment
|
|
2475
|
-
</p>
|
|
2476
|
-
</div>
|
|
2477
|
-
|
|
2478
|
-
<!-- What happens next -->
|
|
2479
|
-
<div style="background: rgba(139, 69, 19, 0.08); border: 1px solid rgba(139, 69, 19, 0.2); border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem;">
|
|
2480
|
-
<h4 style="margin: 0 0 0.5rem 0; color: var(--text-primary); font-size: 0.9rem;">🚀 What happens next:</h4>
|
|
2481
|
-
<ul style="margin: 0; padding-left: 1.5rem; color: var(--text-secondary); font-size: 0.8rem; line-height: 1.5;">
|
|
2482
|
-
<li>Creates persistent tmux session: <code style="color: var(--accent-orange); background: rgba(0,0,0,0.3); padding: 0.1rem 0.3rem; border-radius: 3px;">mcp-workshop</code></li>
|
|
2483
|
-
<li>Launches Claude with full MCP tool access</li>
|
|
2484
|
-
<li>Runs your prompt in the development environment</li>
|
|
2485
|
-
<li>You can attach via: <code style="color: var(--accent-blue); background: rgba(0,0,0,0.3); padding: 0.1rem 0.3rem; border-radius: 3px;">tmux attach -t mcp-workshop</code></li>
|
|
2486
|
-
<li>Session persists across disconnections for continuous work</li>
|
|
2487
|
-
</ul>
|
|
2488
|
-
</div>
|
|
2489
|
-
|
|
2490
|
-
</div>
|
|
2491
|
-
|
|
2492
|
-
<!-- Right Column: Connect to tmux Sessions -->
|
|
2493
|
-
<div style="flex: 1; padding: 2rem; background: rgba(0,0,0,0.05); overflow-y: auto; max-height: 600px;">
|
|
2494
|
-
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1.5rem;">
|
|
2495
|
-
<h3 style="margin: 0; font-size: 1.1rem; font-weight: 600; color: var(--text-primary);">
|
|
2496
|
-
🔗 Manage tmux Sessions
|
|
2497
|
-
</h3>
|
|
2498
|
-
</div>
|
|
2499
|
-
|
|
2500
|
-
<!-- Session List - Table Style -->
|
|
2501
|
-
<div style="display: flex; flex-direction: column; gap: 1px; border: 1px solid rgba(139, 69, 19, 0.2); border-radius: 8px; overflow: hidden; background: rgba(139, 69, 19, 0.1);">
|
|
2502
|
-
<!-- Table Header -->
|
|
2503
|
-
<div style="display: grid; grid-template-columns: 2fr 1fr 1fr 2fr; gap: 1rem; padding: 0.75rem 1rem; background: rgba(139, 69, 19, 0.15); font-weight: 600; font-size: 0.85rem; color: var(--text-primary);">
|
|
2504
|
-
<div>Session Name</div>
|
|
2505
|
-
<div>Status</div>
|
|
2506
|
-
<div>Created</div>
|
|
2507
|
-
<div>Actions</div>
|
|
2508
|
-
</div>
|
|
2509
|
-
|
|
2510
|
-
<!-- Table Rows -->
|
|
2511
|
-
<template x-for="session in tmuxSessions?.sessions || []" :key="session.session_name">
|
|
2512
|
-
<div style="display: grid; grid-template-columns: 2fr 1fr 1fr 2fr; gap: 1rem; padding: 0.75rem 1rem; background: rgba(255, 255, 255, 0.05); border-bottom: 1px solid rgba(139, 69, 19, 0.1); align-items: center;">
|
|
2513
|
-
<!-- Session Name -->
|
|
2514
|
-
<div style="font-weight: 600; color: var(--text-primary); font-size: 0.9rem; font-family: var(--font-mono);" x-text="session.session_name"></div>
|
|
2515
|
-
|
|
2516
|
-
<!-- Status -->
|
|
2517
|
-
<div style="display: flex; align-items: center; gap: 0.3rem;">
|
|
2518
|
-
<span x-text="getTmuxStatusIcon(session.status)" style="font-size: 0.8rem;"></span>
|
|
2519
|
-
<span style="font-size: 0.8rem; color: var(--text-secondary); text-transform: capitalize;" x-text="session.status"></span>
|
|
2520
|
-
</div>
|
|
2521
|
-
|
|
2522
|
-
<!-- Created Date -->
|
|
2523
|
-
<div style="color: var(--text-muted); font-size: 0.8rem;" x-text="new Date(session.created_at).toLocaleDateString()"></div>
|
|
2524
|
-
|
|
2525
|
-
<!-- Actions -->
|
|
2526
|
-
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
2527
|
-
<button @click="copyTmuxAttachCommand(session.session_name)"
|
|
2528
|
-
style="background: rgba(34, 197, 94, 0.9); border: none; color: white; padding: 0.4rem 0.6rem; border-radius: 4px; font-size: 0.75rem; cursor: pointer; font-weight: 500; font-family: var(--font-mono);"
|
|
2529
|
-
@mouseenter="$el.style.background = 'rgba(34, 197, 94, 1)'"
|
|
2530
|
-
@mouseleave="$el.style.background = 'rgba(34, 197, 94, 0.9)'">
|
|
2531
|
-
📋 Copy Command
|
|
2532
|
-
</button>
|
|
2533
|
-
<button @click="copyTmuxKillCommand(session.session_name)"
|
|
2534
|
-
style="background: rgba(239, 68, 68, 0.9); border: none; color: white; padding: 0.4rem 0.6rem; border-radius: 4px; font-size: 0.75rem; cursor: pointer; font-weight: 500; font-family: var(--font-mono);"
|
|
2535
|
-
@mouseenter="$el.style.background = 'rgba(239, 68, 68, 1)'"
|
|
2536
|
-
@mouseleave="$el.style.background = 'rgba(239, 68, 68, 0.9)'">
|
|
2537
|
-
🗑 Kill Command
|
|
2538
|
-
</button>
|
|
2539
|
-
</div>
|
|
2540
|
-
</div>
|
|
2541
|
-
</template>
|
|
2542
|
-
|
|
2543
|
-
<!-- No sessions message -->
|
|
2544
|
-
<div x-show="!tmuxSessions?.sessions || tmuxSessions.sessions.length === 0"
|
|
2545
|
-
style="text-align: center; padding: 2rem; color: var(--text-secondary); background: rgba(255, 255, 255, 0.05);">
|
|
2546
|
-
<div style="font-size: 2rem; margin-bottom: 0.5rem;">🚀</div>
|
|
2547
|
-
<p style="margin: 0; font-size: 0.9rem;">No tmux sessions found</p>
|
|
2548
|
-
<p style="margin: 0.25rem 0 0 0; font-size: 0.8rem;">Launch your first MCP session to get started!</p>
|
|
2549
|
-
</div>
|
|
2550
|
-
|
|
2551
|
-
<!-- Session Summary -->
|
|
2552
|
-
<div x-show="tmuxSessions?.sessions && tmuxSessions.sessions.length > 0"
|
|
2553
|
-
style="text-align: center; padding: 1rem; margin-top: 1rem; background: rgba(139, 69, 19, 0.08); border-radius: 6px; border-top: 2px solid rgba(245, 158, 11, 0.3);">
|
|
2554
|
-
<p style="margin: 0; font-size: 0.8rem; color: var(--text-secondary);">
|
|
2555
|
-
<span x-text="tmuxSessions?.total_sessions || 0"></span> total sessions •
|
|
2556
|
-
<span x-text="tmuxSessions?.active_sessions || 0"></span> active •
|
|
2557
|
-
<span x-text="tmuxSessions?.sessions?.filter(s => s.status === 'detached').length || 0"></span> detached
|
|
2558
|
-
</p>
|
|
2559
|
-
</div>
|
|
2560
|
-
</div>
|
|
2561
|
-
</div>
|
|
2562
|
-
</div>
|
|
2563
|
-
</div>
|
|
2564
|
-
</div>
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
<!-- Delete Confirmation Modal - Compact Top-Right -->
|
|
2568
|
-
<div x-show="confirmDelete"
|
|
2569
|
-
x-transition:enter="transition ease-out duration-300"
|
|
2570
|
-
x-transition:enter-start="opacity-0 translate-x-4"
|
|
2571
|
-
x-transition:enter-end="opacity-100 translate-x-0"
|
|
2572
|
-
x-transition:leave="transition ease-in duration-200"
|
|
2573
|
-
x-transition:leave-start="opacity-100 translate-x-0"
|
|
2574
|
-
x-transition:leave-end="opacity-0 translate-x-4"
|
|
2575
|
-
style="position: fixed; top: 1rem; right: 1rem; z-index: 1000; max-width: 400px;">
|
|
2576
|
-
<div class="glass"
|
|
2577
|
-
style="background: rgba(0, 0, 0, 0.95); border: 2px solid var(--accent-red); padding: 1.5rem; border-radius: 16px; backdrop-filter: blur(20px);">
|
|
2578
|
-
<div>
|
|
2579
|
-
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;">
|
|
2580
|
-
<div style="font-size: 1.5rem; color: var(--accent-red);">⚠️</div>
|
|
2581
|
-
<h3 class="heading" style="color: var(--text-primary); margin: 0; font-size: 1rem;">Confirm Deletion</h3>
|
|
2582
|
-
</div>
|
|
2583
|
-
<p style="color: var(--text-secondary); margin-bottom: 1.5rem; font-size: 0.9rem; line-height: 1.4;">
|
|
2584
|
-
Delete "<span style="color: var(--text-primary); font-weight: 600;" x-text="confirmDelete?.title"></span>"?
|
|
2585
|
-
<br>
|
|
2586
|
-
<strong style="color: var(--accent-red); font-size: 0.85rem;">This cannot be undone.</strong>
|
|
2587
|
-
</p>
|
|
2588
|
-
|
|
2589
|
-
<div style="display: flex; gap: 0.75rem; justify-content: flex-end;">
|
|
2590
|
-
<button class="btn btn-secondary"
|
|
2591
|
-
style="padding: 0.5rem 1rem; font-size: 0.85rem;"
|
|
2592
|
-
@click="cancelDelete()">
|
|
2593
|
-
Cancel
|
|
2594
|
-
</button>
|
|
2595
|
-
<button class="btn"
|
|
2596
|
-
style="padding: 0.5rem 1rem; font-size: 0.85rem; background: var(--accent-red); border: 1px solid var(--accent-red); color: white;"
|
|
2597
|
-
@click="deleteSpec()">
|
|
2598
|
-
🗑 Delete
|
|
2599
|
-
</button>
|
|
2600
|
-
</div>
|
|
2601
|
-
</div>
|
|
2602
|
-
</div>
|
|
2603
|
-
</div>
|
|
2604
|
-
|
|
2605
|
-
<!-- Toast Notifications -->
|
|
2606
|
-
<div class="toast-container"
|
|
2607
|
-
style="position: fixed; top: 1rem; right: 1rem; z-index: 9998; max-width: 400px; padding-top: 0;"
|
|
2608
|
-
:style="confirmDelete ? 'padding-top: 200px;' : 'padding-top: 0;'">
|
|
2609
|
-
<template x-for="toast in toasts" :key="toast.id">
|
|
2610
|
-
<div x-show="toast.visible"
|
|
2611
|
-
x-transition:enter="transition ease-out duration-300 transform"
|
|
2612
|
-
x-transition:enter-start="translate-x-full opacity-0"
|
|
2613
|
-
x-transition:enter-end="translate-x-0 opacity-100"
|
|
2614
|
-
x-transition:leave="transition ease-in duration-200 transform"
|
|
2615
|
-
x-transition:leave-start="translate-x-0 opacity-100"
|
|
2616
|
-
x-transition:leave-end="translate-x-full opacity-0"
|
|
2617
|
-
class="toast-notification"
|
|
2618
|
-
:class="{
|
|
2619
|
-
'toast-success': toast.type === 'success',
|
|
2620
|
-
'toast-error': toast.type === 'error',
|
|
2621
|
-
'toast-info': toast.type === 'info'
|
|
2622
|
-
}"
|
|
2623
|
-
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;"
|
|
2624
|
-
@click="removeToast(toast.id)">
|
|
2625
|
-
<span x-text="toast.message" style="flex: 1; margin-right: 0.75rem;"></span>
|
|
2626
|
-
<button @click.stop="removeToast(toast.id)"
|
|
2627
|
-
style="background: none; border: none; color: inherit; opacity: 0.7; font-size: 1.1em; cursor: pointer; padding: 0; margin: 0; line-height: 1;"
|
|
2628
|
-
@mouseover="$el.style.opacity = '1'"
|
|
2629
|
-
@mouseout="$el.style.opacity = '0.7'">×</button>
|
|
2630
|
-
</div>
|
|
2631
|
-
</template>
|
|
2632
|
-
</div>
|
|
2633
|
-
|
|
2634
|
-
<style>
|
|
2635
|
-
.toast-success {
|
|
2636
|
-
background: rgba(34, 197, 94, 0.15) !important;
|
|
2637
|
-
border-color: rgba(34, 197, 94, 0.3) !important;
|
|
2638
|
-
color: #22c55e !important;
|
|
2639
|
-
}
|
|
2640
|
-
.toast-error {
|
|
2641
|
-
background: rgba(239, 68, 68, 0.15) !important;
|
|
2642
|
-
border-color: rgba(239, 68, 68, 0.3) !important;
|
|
2643
|
-
color: #ef4444 !important;
|
|
2644
|
-
}
|
|
2645
|
-
.toast-info {
|
|
2646
|
-
background: rgba(59, 130, 246, 0.15) !important;
|
|
2647
|
-
border-color: rgba(59, 130, 246, 0.3) !important;
|
|
2648
|
-
color: #3b82f6 !important;
|
|
2649
|
-
}
|
|
2650
|
-
</style>
|
|
2651
|
-
|
|
2652
|
-
<!-- Toast Notification Container -->
|
|
2653
|
-
<div style="position: fixed; top: 1rem; right: 1rem; z-index: 9999; display: flex; flex-direction: column; gap: 0.5rem; max-width: 400px;">
|
|
2654
|
-
<template x-for="toast in toasts" :key="toast.id">
|
|
2655
|
-
<div x-show="true"
|
|
2656
|
-
x-transition:enter="transform transition ease-out duration-300"
|
|
2657
|
-
x-transition:enter-start="translate-x-full opacity-0"
|
|
2658
|
-
x-transition:enter-end="translate-x-0 opacity-100"
|
|
2659
|
-
x-transition:leave="transform transition ease-in duration-200"
|
|
2660
|
-
x-transition:leave-start="translate-x-0 opacity-100"
|
|
2661
|
-
x-transition:leave-end="translate-x-full opacity-0"
|
|
2662
|
-
: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;`"
|
|
2663
|
-
@click="removeToast(toast.id)">
|
|
2664
|
-
<span x-text="getToastIcon(toast.type)" style="font-size: 1.1rem;"></span>
|
|
2665
|
-
<span x-text="toast.message" style="flex: 1; line-height: 1.4;"></span>
|
|
2666
|
-
<button @click.stop="removeToast(toast.id)"
|
|
2667
|
-
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;"
|
|
2668
|
-
@mouseenter="$el.style.opacity = '1'"
|
|
2669
|
-
@mouseleave="$el.style.opacity = '0.8'">
|
|
2670
|
-
✕
|
|
2671
|
-
</button>
|
|
2672
|
-
</div>
|
|
2673
|
-
</template>
|
|
2674
|
-
</div>
|
|
2675
|
-
|
|
2676
|
-
<!-- Global copy functionality and enhanced code block styles -->
|
|
2677
|
-
<script>
|
|
2678
|
-
// Global function for copying code blocks
|
|
2679
|
-
function copyToClipboard(text, buttonId) {
|
|
2680
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
2681
|
-
const button = document.getElementById(buttonId);
|
|
2682
|
-
if (button) {
|
|
2683
|
-
const originalText = button.innerHTML;
|
|
2684
|
-
button.innerHTML = '✅ Copied!';
|
|
2685
|
-
button.style.background = 'rgba(16, 185, 129, 0.2)';
|
|
2686
|
-
button.style.borderColor = 'rgba(16, 185, 129, 0.4)';
|
|
2687
|
-
|
|
2688
|
-
setTimeout(() => {
|
|
2689
|
-
button.innerHTML = originalText;
|
|
2690
|
-
button.style.background = '';
|
|
2691
|
-
button.style.borderColor = '';
|
|
2692
|
-
}, 2000);
|
|
2693
|
-
}
|
|
2694
|
-
|
|
2695
|
-
// Also show toast notification if available
|
|
2696
|
-
try {
|
|
2697
|
-
window.Alpine.store('dashboard').addToast('📋 Command copied to clipboard!', 'success');
|
|
2698
|
-
} catch (e) {
|
|
2699
|
-
console.log('📋 Command copied to clipboard!');
|
|
2700
|
-
}
|
|
2701
|
-
}).catch(err => {
|
|
2702
|
-
console.error('Failed to copy:', err);
|
|
2703
|
-
try {
|
|
2704
|
-
window.Alpine.store('dashboard').addToast('❌ Failed to copy command', 'error');
|
|
2705
|
-
} catch (e) {
|
|
2706
|
-
console.error('Failed to copy command');
|
|
2707
|
-
}
|
|
2708
|
-
});
|
|
2709
|
-
}
|
|
2710
|
-
</script>
|
|
2711
|
-
|
|
2712
|
-
<style>
|
|
2713
|
-
/* Enhanced code block styles with copy buttons */
|
|
2714
|
-
.code-block-container {
|
|
2715
|
-
position: relative;
|
|
2716
|
-
margin: 1.5rem 0;
|
|
2717
|
-
background: rgba(0, 0, 0, 0.6);
|
|
2718
|
-
border-radius: 8px;
|
|
2719
|
-
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
2720
|
-
overflow: hidden;
|
|
2721
|
-
}
|
|
2722
|
-
|
|
2723
|
-
.code-block-header {
|
|
2724
|
-
display: flex;
|
|
2725
|
-
justify-content: space-between;
|
|
2726
|
-
align-items: center;
|
|
2727
|
-
padding: 0.75rem 1rem;
|
|
2728
|
-
background: rgba(255, 255, 255, 0.05);
|
|
2729
|
-
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
2730
|
-
}
|
|
2731
|
-
|
|
2732
|
-
.code-lang {
|
|
2733
|
-
font-size: 0.8rem;
|
|
2734
|
-
color: rgba(245, 158, 11, 0.9);
|
|
2735
|
-
font-weight: 500;
|
|
2736
|
-
text-transform: uppercase;
|
|
2737
|
-
letter-spacing: 0.5px;
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
.copy-code-btn {
|
|
2741
|
-
background: rgba(255, 255, 255, 0.1);
|
|
2742
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
2743
|
-
color: var(--text-primary);
|
|
2744
|
-
padding: 0.4rem 0.8rem;
|
|
2745
|
-
border-radius: 4px;
|
|
2746
|
-
font-size: 0.8rem;
|
|
2747
|
-
cursor: pointer;
|
|
2748
|
-
transition: all 0.2s ease;
|
|
2749
|
-
display: flex;
|
|
2750
|
-
align-items: center;
|
|
2751
|
-
gap: 0.3rem;
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
|
-
.copy-code-btn:hover {
|
|
2755
|
-
background: rgba(255, 255, 255, 0.15);
|
|
2756
|
-
border-color: rgba(255, 255, 255, 0.3);
|
|
2757
|
-
transform: translateY(-1px);
|
|
2758
|
-
}
|
|
2759
|
-
|
|
2760
|
-
.code-block-container pre {
|
|
2761
|
-
margin: 0;
|
|
2762
|
-
padding: 1.2rem;
|
|
2763
|
-
background: transparent;
|
|
2764
|
-
border: none;
|
|
2765
|
-
overflow-x: auto;
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
.code-block-container code {
|
|
2769
|
-
background: transparent;
|
|
2770
|
-
color: var(--text-primary);
|
|
2771
|
-
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
2772
|
-
font-size: 0.9rem;
|
|
2773
|
-
line-height: 1.5;
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
|
-
.inline-code {
|
|
2777
|
-
background: rgba(255, 255, 255, 0.1) !important;
|
|
2778
|
-
padding: 0.2rem 0.4rem !important;
|
|
2779
|
-
border-radius: 3px !important;
|
|
2780
|
-
font-family: 'Monaco', 'Menlo', 'Consolas', monospace !important;
|
|
2781
|
-
font-size: 0.85em !important;
|
|
2782
|
-
color: rgba(245, 158, 11, 0.9) !important;
|
|
2783
|
-
border: 1px solid rgba(255, 255, 255, 0.15) !important;
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
/* Quick Access section styling */
|
|
2787
|
-
.spec-content h3 {
|
|
2788
|
-
color: rgba(34, 197, 94, 0.9);
|
|
2789
|
-
border-bottom: 2px solid rgba(34, 197, 94, 0.3);
|
|
2790
|
-
padding-bottom: 0.5rem;
|
|
2791
|
-
margin-bottom: 1.5rem;
|
|
2792
|
-
}
|
|
2793
|
-
|
|
2794
|
-
.spec-content ul li {
|
|
2795
|
-
margin: 0.5rem 0;
|
|
2796
|
-
color: var(--text-secondary);
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2799
|
-
.spec-content ul li strong {
|
|
2800
|
-
color: var(--text-primary);
|
|
2801
|
-
}
|
|
2802
|
-
</style>
|
|
2803
|
-
|
|
2804
|
-
</body>
|
|
2805
|
-
</html>
|