tlc-claude-code 1.4.1 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dashboard/dist/App.js +229 -35
  2. package/dashboard/dist/components/AgentRegistryPane.d.ts +35 -0
  3. package/dashboard/dist/components/AgentRegistryPane.js +89 -0
  4. package/dashboard/dist/components/AgentRegistryPane.test.d.ts +1 -0
  5. package/dashboard/dist/components/AgentRegistryPane.test.js +200 -0
  6. package/dashboard/dist/components/RouterPane.d.ts +5 -0
  7. package/dashboard/dist/components/RouterPane.js +65 -0
  8. package/dashboard/dist/components/RouterPane.test.d.ts +1 -0
  9. package/dashboard/dist/components/RouterPane.test.js +176 -0
  10. package/dashboard/dist/components/accessibility.test.d.ts +1 -0
  11. package/dashboard/dist/components/accessibility.test.js +116 -0
  12. package/dashboard/dist/components/layout/MobileNav.d.ts +16 -0
  13. package/dashboard/dist/components/layout/MobileNav.js +31 -0
  14. package/dashboard/dist/components/layout/MobileNav.test.d.ts +1 -0
  15. package/dashboard/dist/components/layout/MobileNav.test.js +111 -0
  16. package/dashboard/dist/components/performance.test.d.ts +1 -0
  17. package/dashboard/dist/components/performance.test.js +114 -0
  18. package/dashboard/dist/components/responsive.test.d.ts +1 -0
  19. package/dashboard/dist/components/responsive.test.js +114 -0
  20. package/dashboard/dist/components/ui/Dropdown.d.ts +22 -0
  21. package/dashboard/dist/components/ui/Dropdown.js +109 -0
  22. package/dashboard/dist/components/ui/Dropdown.test.d.ts +1 -0
  23. package/dashboard/dist/components/ui/Dropdown.test.js +105 -0
  24. package/dashboard/dist/components/ui/Modal.d.ts +13 -0
  25. package/dashboard/dist/components/ui/Modal.js +25 -0
  26. package/dashboard/dist/components/ui/Modal.test.d.ts +1 -0
  27. package/dashboard/dist/components/ui/Modal.test.js +91 -0
  28. package/dashboard/dist/components/ui/Skeleton.d.ts +32 -0
  29. package/dashboard/dist/components/ui/Skeleton.js +48 -0
  30. package/dashboard/dist/components/ui/Skeleton.test.d.ts +1 -0
  31. package/dashboard/dist/components/ui/Skeleton.test.js +125 -0
  32. package/dashboard/dist/components/ui/Toast.d.ts +32 -0
  33. package/dashboard/dist/components/ui/Toast.js +21 -0
  34. package/dashboard/dist/components/ui/Toast.test.d.ts +1 -0
  35. package/dashboard/dist/components/ui/Toast.test.js +118 -0
  36. package/dashboard/dist/hooks/useTheme.d.ts +37 -0
  37. package/dashboard/dist/hooks/useTheme.js +96 -0
  38. package/dashboard/dist/hooks/useTheme.test.d.ts +1 -0
  39. package/dashboard/dist/hooks/useTheme.test.js +94 -0
  40. package/dashboard/dist/hooks/useWebSocket.d.ts +17 -0
  41. package/dashboard/dist/hooks/useWebSocket.js +100 -0
  42. package/dashboard/dist/hooks/useWebSocket.test.d.ts +1 -0
  43. package/dashboard/dist/hooks/useWebSocket.test.js +115 -0
  44. package/dashboard/dist/stores/projectStore.d.ts +44 -0
  45. package/dashboard/dist/stores/projectStore.js +76 -0
  46. package/dashboard/dist/stores/projectStore.test.d.ts +1 -0
  47. package/dashboard/dist/stores/projectStore.test.js +114 -0
  48. package/dashboard/dist/stores/uiStore.d.ts +29 -0
  49. package/dashboard/dist/stores/uiStore.js +72 -0
  50. package/dashboard/dist/stores/uiStore.test.d.ts +1 -0
  51. package/dashboard/dist/stores/uiStore.test.js +93 -0
  52. package/dashboard/package.json +3 -3
  53. package/docker-compose.dev.yml +6 -1
  54. package/package.json +5 -2
  55. package/server/dashboard/index.html +1336 -779
  56. package/server/index.js +178 -0
  57. package/server/lib/agent-cleanup.js +177 -0
  58. package/server/lib/agent-cleanup.test.js +359 -0
  59. package/server/lib/agent-hooks.js +126 -0
  60. package/server/lib/agent-hooks.test.js +303 -0
  61. package/server/lib/agent-metadata.js +179 -0
  62. package/server/lib/agent-metadata.test.js +383 -0
  63. package/server/lib/agent-persistence.js +191 -0
  64. package/server/lib/agent-persistence.test.js +475 -0
  65. package/server/lib/agent-registry-command.js +340 -0
  66. package/server/lib/agent-registry-command.test.js +334 -0
  67. package/server/lib/agent-registry.js +155 -0
  68. package/server/lib/agent-registry.test.js +239 -0
  69. package/server/lib/agent-state.js +236 -0
  70. package/server/lib/agent-state.test.js +375 -0
  71. package/server/lib/api-provider.js +186 -0
  72. package/server/lib/api-provider.test.js +336 -0
  73. package/server/lib/cli-detector.js +166 -0
  74. package/server/lib/cli-detector.test.js +269 -0
  75. package/server/lib/cli-provider.js +212 -0
  76. package/server/lib/cli-provider.test.js +349 -0
  77. package/server/lib/debug.test.js +62 -0
  78. package/server/lib/devserver-router-api.js +249 -0
  79. package/server/lib/devserver-router-api.test.js +426 -0
  80. package/server/lib/model-router.js +245 -0
  81. package/server/lib/model-router.test.js +313 -0
  82. package/server/lib/output-schemas.js +269 -0
  83. package/server/lib/output-schemas.test.js +307 -0
  84. package/server/lib/provider-interface.js +153 -0
  85. package/server/lib/provider-interface.test.js +394 -0
  86. package/server/lib/provider-queue.js +158 -0
  87. package/server/lib/provider-queue.test.js +315 -0
  88. package/server/lib/router-config.js +221 -0
  89. package/server/lib/router-config.test.js +237 -0
  90. package/server/lib/router-setup-command.js +419 -0
  91. package/server/lib/router-setup-command.test.js +375 -0
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>TLC Dev Server</title>
6
+ <title>TLC Dashboard</title>
7
7
  <style>
8
8
  * { box-sizing: border-box; margin: 0; padding: 0; }
9
9
  body {
@@ -14,303 +14,446 @@
14
14
  overflow: hidden;
15
15
  }
16
16
 
17
+ /* Main Layout - Coolify Style */
18
+ .app {
19
+ display: flex;
20
+ flex-direction: column;
21
+ height: 100vh;
22
+ }
23
+
24
+ /* Header */
17
25
  .header {
18
26
  background: #161b22;
19
- padding: 12px 20px;
27
+ padding: 8px 16px;
20
28
  display: flex;
21
29
  justify-content: space-between;
22
30
  align-items: center;
23
31
  border-bottom: 1px solid #30363d;
32
+ height: 48px;
24
33
  }
25
- .header h1 {
26
- font-size: 18px;
27
- color: #58a6ff;
34
+ .header-left {
28
35
  display: flex;
29
36
  align-items: center;
30
- gap: 10px;
37
+ gap: 16px;
31
38
  }
32
- .header h1 .logo {
39
+ .logo {
33
40
  font-family: monospace;
34
- font-size: 16px;
35
- color: #7ee787;
41
+ font-size: 18px;
36
42
  font-weight: bold;
43
+ color: #7ee787;
44
+ }
45
+ .header-title {
46
+ font-size: 14px;
47
+ color: #8b949e;
48
+ }
49
+ .header-subtitle {
50
+ color: #58a6ff;
51
+ font-weight: 500;
37
52
  }
38
- .header .status {
53
+ .header-right {
39
54
  display: flex;
55
+ align-items: center;
40
56
  gap: 12px;
57
+ }
58
+ .connection-status {
59
+ display: flex;
41
60
  align-items: center;
61
+ gap: 6px;
62
+ font-size: 12px;
42
63
  }
43
- .header .status .dot {
64
+ .status-dot {
44
65
  width: 8px;
45
66
  height: 8px;
46
67
  border-radius: 50%;
47
68
  }
48
- .header .status .dot.running { background: #3fb950; }
49
- .header .status .dot.stopped { background: #f85149; }
50
- .header .phase-badge {
51
- padding: 4px 10px;
52
- background: #238636;
53
- border-radius: 12px;
69
+ .status-dot.connected { background: #3fb950; }
70
+ .status-dot.disconnected { background: #f85149; }
71
+ .version {
54
72
  font-size: 12px;
55
- font-weight: 500;
73
+ color: #6e7681;
56
74
  }
57
75
 
76
+ /* Main Container */
58
77
  .container {
59
- display: grid;
60
- grid-template-columns: 1fr 300px;
61
- height: calc(100vh - 54px);
62
- }
63
-
64
- .main-content {
65
78
  display: flex;
66
- flex-direction: column;
79
+ flex: 1;
67
80
  overflow: hidden;
68
81
  }
69
82
 
70
- .tabs {
71
- display: flex;
72
- gap: 0;
83
+ /* Sidebar - Coolify Style */
84
+ .sidebar {
85
+ width: 200px;
73
86
  background: #161b22;
87
+ border-right: 1px solid #30363d;
88
+ display: flex;
89
+ flex-direction: column;
90
+ transition: width 0.2s;
91
+ }
92
+ .sidebar.collapsed {
93
+ width: 56px;
94
+ }
95
+ .sidebar-header {
96
+ padding: 16px;
74
97
  border-bottom: 1px solid #30363d;
75
98
  }
76
- .tab {
77
- padding: 12px 20px;
78
- background: transparent;
79
- border: none;
80
- color: #8b949e;
81
- cursor: pointer;
99
+ .sidebar-title {
82
100
  font-size: 14px;
83
- border-bottom: 2px solid transparent;
84
- transition: all 0.2s;
101
+ font-weight: 600;
102
+ color: #58a6ff;
85
103
  }
86
- .tab:hover {
87
- color: #e6edf3;
88
- background: #21262d;
104
+ .sidebar.collapsed .sidebar-title {
105
+ display: none;
89
106
  }
90
- .tab.active {
91
- color: #58a6ff;
92
- border-bottom-color: #58a6ff;
107
+ .nav-items {
108
+ flex: 1;
109
+ padding: 8px;
110
+ overflow-y: auto;
93
111
  }
94
- .tab .badge {
95
- background: #f85149;
112
+ .nav-item {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 12px;
116
+ padding: 10px 12px;
117
+ border-radius: 6px;
118
+ cursor: pointer;
119
+ color: #8b949e;
120
+ transition: all 0.15s;
121
+ margin-bottom: 2px;
122
+ }
123
+ .nav-item:hover {
124
+ background: #21262d;
125
+ color: #e6edf3;
126
+ }
127
+ .nav-item.active {
128
+ background: #1f6feb;
96
129
  color: white;
130
+ }
131
+ .nav-item .icon {
132
+ font-size: 16px;
133
+ width: 24px;
134
+ text-align: center;
135
+ }
136
+ .nav-item .label {
137
+ flex: 1;
138
+ font-size: 13px;
139
+ }
140
+ .nav-item .shortcut {
141
+ font-size: 11px;
142
+ color: #6e7681;
97
143
  padding: 2px 6px;
98
- border-radius: 10px;
144
+ background: #21262d;
145
+ border-radius: 4px;
146
+ }
147
+ .nav-item.active .shortcut {
148
+ background: rgba(255,255,255,0.2);
149
+ color: rgba(255,255,255,0.8);
150
+ }
151
+ .sidebar.collapsed .nav-item .label,
152
+ .sidebar.collapsed .nav-item .shortcut {
153
+ display: none;
154
+ }
155
+ .sidebar-footer {
156
+ padding: 12px;
157
+ border-top: 1px solid #30363d;
99
158
  font-size: 11px;
100
- margin-left: 6px;
159
+ color: #6e7681;
160
+ }
161
+ .sidebar.collapsed .sidebar-footer span {
162
+ display: none;
101
163
  }
102
164
 
103
- .tab-content {
165
+ /* Main Content */
166
+ .main {
104
167
  flex: 1;
168
+ display: flex;
169
+ flex-direction: column;
105
170
  overflow: hidden;
106
171
  }
107
- .tab-panel {
108
- display: none;
109
- height: 100%;
172
+
173
+ /* View Header */
174
+ .view-header {
175
+ padding: 12px 20px;
176
+ border-bottom: 1px solid #30363d;
177
+ background: #0d1117;
178
+ }
179
+ .view-title {
180
+ font-size: 16px;
181
+ font-weight: 600;
182
+ color: #58a6ff;
183
+ }
184
+ .breadcrumb {
185
+ color: #6e7681;
186
+ font-size: 14px;
187
+ }
188
+
189
+ /* View Content */
190
+ .view-content {
191
+ flex: 1;
110
192
  overflow-y: auto;
111
193
  padding: 20px;
112
194
  }
113
- .tab-panel.active {
114
- display: block;
195
+
196
+ /* Footer */
197
+ .footer {
198
+ padding: 8px 16px;
199
+ background: #161b22;
200
+ border-top: 1px solid #30363d;
201
+ display: flex;
202
+ justify-content: space-between;
203
+ font-size: 11px;
204
+ color: #6e7681;
115
205
  }
116
206
 
117
- /* Plan Panel */
118
- .plan-content {
119
- font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
120
- font-size: 13px;
121
- line-height: 1.6;
122
- white-space: pre-wrap;
207
+ /* Panels - Hidden by default */
208
+ .panel {
209
+ display: none;
210
+ height: 100%;
123
211
  }
124
- .plan-content h1, .plan-content h2, .plan-content h3 {
125
- font-family: system-ui, sans-serif;
126
- color: #58a6ff;
127
- margin: 20px 0 10px 0;
212
+ .panel.active {
213
+ display: block;
128
214
  }
129
- .plan-content h1 { font-size: 20px; }
130
- .plan-content h2 { font-size: 16px; }
131
- .plan-content h3 { font-size: 14px; }
132
- .plan-content .task-done { color: #3fb950; }
133
- .plan-content .task-working { color: #58a6ff; }
134
- .plan-content .task-todo { color: #8b949e; }
135
215
 
136
- /* Tests Panel */
137
- .test-section {
138
- margin-bottom: 30px;
216
+ /* Projects View */
217
+ .project-card {
218
+ background: #161b22;
219
+ border: 1px solid #30363d;
220
+ border-radius: 8px;
221
+ padding: 20px;
222
+ margin-bottom: 16px;
139
223
  }
140
- .test-section h3 {
141
- font-size: 14px;
142
- color: #58a6ff;
143
- margin-bottom: 15px;
144
- padding-bottom: 8px;
145
- border-bottom: 1px solid #30363d;
224
+ .project-card:hover {
225
+ border-color: #58a6ff;
146
226
  }
147
- .test-item {
148
- padding: 12px 15px;
149
- background: #161b22;
150
- border-radius: 6px;
227
+ .project-name {
228
+ font-size: 18px;
229
+ font-weight: 600;
151
230
  margin-bottom: 8px;
152
- display: flex;
153
- align-items: center;
154
- gap: 12px;
155
231
  }
156
- .test-item .checkbox {
157
- width: 18px;
158
- height: 18px;
159
- border: 2px solid #30363d;
160
- border-radius: 4px;
161
- cursor: pointer;
232
+ .project-desc {
233
+ color: #8b949e;
234
+ font-size: 14px;
235
+ margin-bottom: 16px;
236
+ }
237
+ .project-stats {
162
238
  display: flex;
163
- align-items: center;
164
- justify-content: center;
239
+ gap: 24px;
165
240
  }
166
- .test-item .checkbox.checked {
167
- background: #238636;
168
- border-color: #238636;
241
+ .project-stat {
242
+ text-align: center;
169
243
  }
170
- .test-item .checkbox.checked::after {
171
- content: '✓';
172
- color: white;
244
+ .project-stat-value {
245
+ font-size: 24px;
246
+ font-weight: bold;
247
+ }
248
+ .project-stat-value.green { color: #3fb950; }
249
+ .project-stat-value.red { color: #f85149; }
250
+ .project-stat-value.blue { color: #58a6ff; }
251
+ .project-stat-label {
252
+ font-size: 11px;
253
+ color: #8b949e;
254
+ text-transform: uppercase;
255
+ }
256
+ .phase-badge {
257
+ display: inline-block;
258
+ padding: 4px 12px;
259
+ background: #238636;
260
+ border-radius: 12px;
173
261
  font-size: 12px;
262
+ margin-bottom: 16px;
174
263
  }
175
- .test-item .text {
176
- flex: 1;
264
+ .progress-bar {
265
+ height: 8px;
266
+ background: #21262d;
267
+ border-radius: 4px;
268
+ overflow: hidden;
269
+ margin-top: 16px;
177
270
  }
178
- .test-actions {
179
- display: flex;
180
- gap: 10px;
181
- margin-bottom: 20px;
271
+ .progress-fill {
272
+ height: 100%;
273
+ background: linear-gradient(90deg, #238636, #3fb950);
274
+ border-radius: 4px;
275
+ transition: width 0.3s;
182
276
  }
183
277
 
184
- /* Bugs Panel */
185
- .bug-item {
186
- padding: 15px;
278
+ /* Tasks View */
279
+ .task-board {
280
+ display: flex;
281
+ gap: 16px;
282
+ }
283
+ .task-column {
284
+ flex: 1;
285
+ min-width: 250px;
286
+ }
287
+ .task-column-header {
288
+ font-size: 12px;
289
+ font-weight: 600;
290
+ text-transform: uppercase;
291
+ color: #8b949e;
292
+ margin-bottom: 12px;
293
+ padding-bottom: 8px;
294
+ border-bottom: 2px solid #30363d;
295
+ }
296
+ .task-column-header.pending { border-color: #6e7681; }
297
+ .task-column-header.in-progress { border-color: #58a6ff; }
298
+ .task-column-header.completed { border-color: #3fb950; }
299
+ .task-item {
187
300
  background: #161b22;
301
+ border: 1px solid #30363d;
188
302
  border-radius: 6px;
189
- margin-bottom: 10px;
190
- border-left: 3px solid #f85149;
303
+ padding: 12px;
304
+ margin-bottom: 8px;
305
+ cursor: pointer;
191
306
  }
192
- .bug-item.closed {
193
- border-left-color: #3fb950;
194
- opacity: 0.6;
307
+ .task-item:hover {
308
+ border-color: #58a6ff;
195
309
  }
196
- .bug-item .bug-header {
197
- display: flex;
198
- justify-content: space-between;
310
+ .task-title {
311
+ font-size: 14px;
199
312
  margin-bottom: 8px;
200
313
  }
201
- .bug-item .bug-id {
202
- font-weight: bold;
203
- color: #f85149;
314
+ .task-meta {
315
+ font-size: 11px;
316
+ color: #6e7681;
204
317
  }
205
- .bug-item.closed .bug-id { color: #3fb950; }
206
- .bug-item .bug-date {
207
- color: #8b949e;
208
- font-size: 12px;
318
+
319
+ /* Chat View */
320
+ .chat-container {
321
+ display: flex;
322
+ flex-direction: column;
323
+ height: 100%;
209
324
  }
210
- .bug-item .bug-desc {
211
- font-size: 14px;
212
- line-height: 1.5;
325
+ .chat-messages {
326
+ flex: 1;
327
+ overflow-y: auto;
328
+ padding: 16px;
213
329
  }
214
-
215
- .bug-form {
216
- background: #161b22;
217
- padding: 20px;
218
- border-radius: 8px;
219
- margin-bottom: 20px;
330
+ .chat-message {
331
+ margin-bottom: 16px;
220
332
  }
221
- .bug-form h3 {
333
+ .chat-message.user .chat-bubble {
334
+ background: #1f6feb;
335
+ margin-left: 40px;
336
+ }
337
+ .chat-message.assistant .chat-bubble {
338
+ background: #21262d;
339
+ margin-right: 40px;
340
+ }
341
+ .chat-bubble {
342
+ padding: 12px 16px;
343
+ border-radius: 12px;
222
344
  font-size: 14px;
223
- margin-bottom: 15px;
224
- color: #e6edf3;
345
+ line-height: 1.5;
225
346
  }
226
- .bug-form textarea {
347
+ .chat-input-container {
348
+ padding: 16px;
349
+ border-top: 1px solid #30363d;
350
+ }
351
+ .chat-input {
227
352
  width: 100%;
228
- height: 80px;
229
- padding: 10px;
230
- background: #0d1117;
353
+ padding: 12px 16px;
354
+ background: #161b22;
231
355
  border: 1px solid #30363d;
232
- border-radius: 6px;
356
+ border-radius: 8px;
233
357
  color: #e6edf3;
234
- resize: vertical;
235
- margin-bottom: 10px;
236
- font-family: inherit;
237
358
  font-size: 14px;
359
+ resize: none;
238
360
  }
239
- .bug-form textarea:focus {
361
+ .chat-input:focus {
240
362
  outline: none;
241
363
  border-color: #58a6ff;
242
364
  }
243
- .bug-form select {
244
- padding: 8px 12px;
245
- background: #21262d;
246
- border: 1px solid #30363d;
247
- border-radius: 6px;
248
- color: #e6edf3;
249
- margin-right: 10px;
365
+
366
+ /* Agents View */
367
+ .agent-grid {
368
+ display: grid;
369
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
370
+ gap: 16px;
250
371
  }
251
- .image-drop-zone {
252
- border: 2px dashed #30363d;
372
+ .agent-card {
373
+ background: #161b22;
374
+ border: 1px solid #30363d;
253
375
  border-radius: 8px;
254
- padding: 15px;
255
- margin-bottom: 10px;
256
- text-align: center;
257
- cursor: pointer;
258
- transition: all 0.2s;
259
- min-height: 60px;
376
+ padding: 16px;
260
377
  }
261
- .image-drop-zone:hover, .image-drop-zone.dragover {
262
- border-color: #58a6ff;
263
- background: rgba(88, 166, 255, 0.1);
378
+ .agent-header {
379
+ display: flex;
380
+ justify-content: space-between;
381
+ align-items: center;
382
+ margin-bottom: 12px;
383
+ }
384
+ .agent-name {
385
+ font-weight: 600;
386
+ }
387
+ .agent-status {
388
+ font-size: 12px;
389
+ padding: 4px 8px;
390
+ border-radius: 4px;
391
+ }
392
+ .agent-status.running {
393
+ background: rgba(63, 185, 80, 0.2);
394
+ color: #3fb950;
264
395
  }
265
- .image-drop-zone .drop-hint {
396
+ .agent-status.idle {
397
+ background: rgba(139, 148, 158, 0.2);
266
398
  color: #8b949e;
267
- font-size: 13px;
268
399
  }
269
- .image-previews {
270
- display: flex;
271
- flex-wrap: wrap;
272
- gap: 8px;
273
- margin-top: 10px;
400
+ .agent-task {
401
+ font-size: 13px;
402
+ color: #8b949e;
403
+ margin-bottom: 8px;
274
404
  }
275
- .image-preview {
276
- position: relative;
277
- width: 80px;
278
- height: 80px;
279
- border-radius: 6px;
405
+ .agent-progress {
406
+ height: 4px;
407
+ background: #21262d;
408
+ border-radius: 2px;
280
409
  overflow: hidden;
281
- border: 1px solid #30363d;
282
410
  }
283
- .image-preview img {
284
- width: 100%;
411
+ .agent-progress-fill {
285
412
  height: 100%;
286
- object-fit: cover;
287
- }
288
- .image-preview .remove-btn {
289
- position: absolute;
290
- top: 2px;
291
- right: 2px;
292
- width: 20px;
293
- height: 20px;
294
- background: rgba(248, 81, 73, 0.9);
295
- border: none;
296
- border-radius: 50%;
297
- color: white;
298
- cursor: pointer;
413
+ background: #58a6ff;
414
+ }
415
+
416
+ /* Preview View */
417
+ .preview-container {
418
+ height: 100%;
419
+ display: flex;
420
+ flex-direction: column;
421
+ }
422
+ .preview-toolbar {
423
+ display: flex;
424
+ gap: 8px;
425
+ padding: 12px 0;
426
+ border-bottom: 1px solid #30363d;
427
+ align-items: center;
428
+ }
429
+ .preview-url {
430
+ flex: 1;
431
+ text-align: right;
432
+ font-family: monospace;
299
433
  font-size: 12px;
300
- line-height: 18px;
434
+ color: #8b949e;
301
435
  }
302
- .image-preview .remove-btn:hover {
303
- background: #f85149;
436
+ .preview-iframe {
437
+ flex: 1;
438
+ border: 1px solid #30363d;
439
+ border-radius: 8px;
440
+ background: white;
441
+ margin-top: 12px;
304
442
  }
305
443
 
306
- /* Logs Panel */
307
- .logs-tabs {
444
+ /* Logs View */
445
+ .logs-container {
446
+ height: 100%;
447
+ display: flex;
448
+ flex-direction: column;
449
+ }
450
+ .logs-filter {
308
451
  display: flex;
309
452
  gap: 8px;
310
- margin-bottom: 15px;
453
+ margin-bottom: 16px;
311
454
  }
312
- .logs-tabs button {
313
- padding: 6px 14px;
455
+ .logs-filter button {
456
+ padding: 6px 12px;
314
457
  background: #21262d;
315
458
  border: 1px solid #30363d;
316
459
  border-radius: 4px;
@@ -318,224 +461,408 @@
318
461
  cursor: pointer;
319
462
  font-size: 12px;
320
463
  }
321
- .logs-tabs button.active {
464
+ .logs-filter button.active {
322
465
  background: #30363d;
323
466
  color: #e6edf3;
324
467
  border-color: #58a6ff;
325
468
  }
326
- .logs-content {
469
+ .logs-output {
470
+ flex: 1;
327
471
  background: #010409;
328
472
  border-radius: 6px;
329
- padding: 15px;
473
+ padding: 16px;
330
474
  font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
331
475
  font-size: 12px;
332
476
  line-height: 1.6;
333
- height: calc(100% - 60px);
334
477
  overflow-y: auto;
335
478
  }
336
- .log-line { padding: 2px 0; white-space: pre-wrap; word-break: break-word; }
479
+ .log-line { padding: 2px 0; white-space: pre-wrap; }
337
480
  .log-line.error { color: #f85149; }
338
481
  .log-line.success { color: #3fb950; }
339
482
  .log-line.info { color: #58a6ff; }
340
483
  .log-line.warn { color: #d29922; }
341
484
 
342
- /* Changelog Panel */
343
- .commit {
344
- padding: 15px;
485
+ /* GitHub View */
486
+ .github-section {
487
+ margin-bottom: 24px;
488
+ }
489
+ .github-section-title {
490
+ font-size: 14px;
491
+ font-weight: 600;
492
+ color: #58a6ff;
493
+ margin-bottom: 12px;
494
+ padding-bottom: 8px;
495
+ border-bottom: 1px solid #30363d;
496
+ }
497
+ .commit-item, .pr-item {
345
498
  background: #161b22;
499
+ border: 1px solid #30363d;
346
500
  border-radius: 6px;
347
- margin-bottom: 10px;
348
- display: flex;
349
- gap: 15px;
501
+ padding: 12px;
502
+ margin-bottom: 8px;
350
503
  }
351
- .commit .hash {
504
+ .commit-hash {
352
505
  font-family: monospace;
353
- color: #58a6ff;
354
506
  font-size: 12px;
507
+ color: #58a6ff;
355
508
  }
356
- .commit .message {
357
- flex: 1;
509
+ .commit-message {
510
+ margin-top: 4px;
358
511
  }
359
- .commit .message .title {
360
- font-weight: 500;
512
+ .commit-meta {
513
+ font-size: 11px;
514
+ color: #6e7681;
515
+ margin-top: 8px;
516
+ }
517
+
518
+ /* Health View */
519
+ .health-grid {
520
+ display: grid;
521
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
522
+ gap: 16px;
523
+ }
524
+ .health-card {
525
+ background: #161b22;
526
+ border: 1px solid #30363d;
527
+ border-radius: 8px;
528
+ padding: 20px;
529
+ text-align: center;
530
+ }
531
+ .health-icon {
532
+ font-size: 32px;
533
+ margin-bottom: 12px;
534
+ }
535
+ .health-metric {
536
+ font-size: 28px;
537
+ font-weight: bold;
361
538
  margin-bottom: 4px;
362
539
  }
363
- .commit .message .meta {
540
+ .health-metric.good { color: #3fb950; }
541
+ .health-metric.warning { color: #d29922; }
542
+ .health-metric.bad { color: #f85149; }
543
+ .health-label {
364
544
  font-size: 12px;
365
545
  color: #8b949e;
546
+ text-transform: uppercase;
366
547
  }
367
-
368
- /* Sidebar */
369
- .sidebar {
548
+ .services-list {
549
+ margin-top: 24px;
550
+ }
551
+ .service-item {
552
+ display: flex;
553
+ justify-content: space-between;
554
+ align-items: center;
555
+ padding: 12px 16px;
370
556
  background: #161b22;
371
- border-left: 1px solid #30363d;
557
+ border: 1px solid #30363d;
558
+ border-radius: 6px;
559
+ margin-bottom: 8px;
560
+ }
561
+ .service-name {
562
+ font-weight: 500;
563
+ }
564
+ .service-status {
372
565
  display: flex;
373
- flex-direction: column;
566
+ align-items: center;
567
+ gap: 8px;
568
+ font-size: 12px;
374
569
  }
570
+ .service-status.running { color: #3fb950; }
571
+ .service-status.stopped { color: #f85149; }
375
572
 
376
- .sidebar-section {
377
- padding: 20px;
378
- border-bottom: 1px solid #30363d;
573
+ /* Router View */
574
+ .router-section {
575
+ margin-bottom: 24px;
576
+ }
577
+ .router-section-title {
578
+ font-size: 14px;
579
+ font-weight: 600;
580
+ color: #58a6ff;
581
+ margin-bottom: 12px;
582
+ }
583
+ .provider-row {
584
+ display: flex;
585
+ align-items: center;
586
+ gap: 12px;
587
+ padding: 12px 16px;
588
+ background: #161b22;
589
+ border: 1px solid #30363d;
590
+ border-radius: 6px;
591
+ margin-bottom: 8px;
592
+ }
593
+ .provider-indicator {
594
+ width: 10px;
595
+ height: 10px;
596
+ border-radius: 50%;
597
+ }
598
+ .provider-indicator.healthy { background: #3fb950; }
599
+ .provider-indicator.unhealthy { background: #f85149; }
600
+ .provider-name {
601
+ font-weight: 500;
602
+ min-width: 100px;
603
+ }
604
+ .provider-version {
605
+ color: #6e7681;
606
+ font-size: 12px;
607
+ min-width: 80px;
379
608
  }
380
- .sidebar-section h3 {
609
+ .provider-badge {
610
+ padding: 2px 8px;
611
+ border-radius: 4px;
381
612
  font-size: 11px;
382
- text-transform: uppercase;
383
- color: #8b949e;
384
- margin-bottom: 15px;
385
- letter-spacing: 0.5px;
386
613
  }
387
-
388
- .stats-grid {
389
- display: grid;
390
- grid-template-columns: 1fr 1fr;
391
- gap: 10px;
614
+ .provider-badge.local {
615
+ background: rgba(63, 185, 80, 0.2);
616
+ color: #3fb950;
392
617
  }
393
- .stat {
394
- text-align: center;
395
- padding: 15px 10px;
396
- background: #0d1117;
618
+ .provider-badge.devserver {
619
+ background: rgba(210, 153, 34, 0.2);
620
+ color: #d29922;
621
+ }
622
+ .routing-table {
623
+ background: #161b22;
624
+ border: 1px solid #30363d;
397
625
  border-radius: 6px;
626
+ overflow: hidden;
398
627
  }
399
- .stat-value {
400
- font-size: 24px;
401
- font-weight: bold;
402
- font-variant-numeric: tabular-nums;
403
- }
404
- .stat-value.green { color: #3fb950; }
405
- .stat-value.red { color: #f85149; }
406
- .stat-value.blue { color: #58a6ff; }
407
- .stat-value.yellow { color: #d29922; }
408
- .stat-label {
409
- font-size: 10px;
628
+ .routing-row {
629
+ display: flex;
630
+ padding: 10px 16px;
631
+ border-bottom: 1px solid #30363d;
632
+ }
633
+ .routing-row:last-child {
634
+ border-bottom: none;
635
+ }
636
+ .routing-capability {
637
+ min-width: 120px;
638
+ font-weight: 500;
639
+ }
640
+ .routing-providers {
410
641
  color: #8b949e;
411
- text-transform: uppercase;
412
- margin-top: 4px;
413
642
  }
643
+ .routing-providers .local { color: #3fb950; }
644
+ .routing-providers .devserver { color: #d29922; }
414
645
 
415
- .quick-actions {
646
+ /* Settings View */
647
+ .settings-section {
648
+ margin-bottom: 32px;
649
+ }
650
+ .settings-section-title {
651
+ font-size: 14px;
652
+ font-weight: 600;
653
+ margin-bottom: 16px;
654
+ padding-bottom: 8px;
655
+ border-bottom: 1px solid #30363d;
656
+ }
657
+ .settings-item {
416
658
  display: flex;
417
- flex-direction: column;
418
- gap: 8px;
659
+ justify-content: space-between;
660
+ align-items: center;
661
+ padding: 12px 0;
419
662
  }
420
- .action-btn {
421
- padding: 10px 15px;
422
- border-radius: 6px;
423
- border: none;
424
- cursor: pointer;
663
+ .settings-label {
664
+ color: #e6edf3;
665
+ }
666
+ .settings-desc {
667
+ font-size: 12px;
668
+ color: #6e7681;
669
+ margin-top: 4px;
670
+ }
671
+ .settings-value {
672
+ color: #8b949e;
673
+ font-family: monospace;
425
674
  font-size: 13px;
426
- font-weight: 500;
675
+ }
676
+ .quick-links {
427
677
  display: flex;
428
- align-items: center;
429
678
  gap: 8px;
430
- transition: all 0.2s;
431
- }
432
- .action-btn.primary {
433
- background: #238636;
434
- color: white;
679
+ flex-wrap: wrap;
435
680
  }
436
- .action-btn.primary:hover { background: #2ea043; }
437
- .action-btn.secondary {
681
+ .quick-link {
682
+ padding: 8px 16px;
438
683
  background: #21262d;
439
- color: #e6edf3;
440
684
  border: 1px solid #30363d;
685
+ border-radius: 6px;
686
+ color: #8b949e;
687
+ text-decoration: none;
688
+ font-size: 13px;
689
+ cursor: pointer;
690
+ }
691
+ .quick-link:hover {
692
+ background: #30363d;
693
+ color: #e6edf3;
694
+ }
695
+ .quick-link kbd {
696
+ background: #161b22;
697
+ padding: 2px 6px;
698
+ border-radius: 3px;
699
+ margin-right: 8px;
441
700
  }
442
- .action-btn.secondary:hover { background: #30363d; }
443
- .action-btn .icon { font-size: 16px; }
444
701
 
445
- .links {
702
+ /* Command Palette */
703
+ .command-palette-overlay {
704
+ display: none;
705
+ position: fixed;
706
+ top: 0;
707
+ left: 0;
708
+ right: 0;
709
+ bottom: 0;
710
+ background: rgba(0, 0, 0, 0.5);
711
+ z-index: 1000;
712
+ align-items: flex-start;
713
+ justify-content: center;
714
+ padding-top: 100px;
715
+ }
716
+ .command-palette-overlay.active {
446
717
  display: flex;
447
- flex-direction: column;
448
- gap: 8px;
449
718
  }
450
- .link {
719
+ .command-palette {
720
+ width: 600px;
721
+ max-width: 90vw;
722
+ background: #161b22;
723
+ border: 1px solid #30363d;
724
+ border-radius: 12px;
725
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
726
+ overflow: hidden;
727
+ }
728
+ .command-palette-input {
729
+ width: 100%;
730
+ padding: 16px 20px;
731
+ background: transparent;
732
+ border: none;
733
+ border-bottom: 1px solid #30363d;
734
+ color: #e6edf3;
735
+ font-size: 16px;
736
+ }
737
+ .command-palette-input:focus {
738
+ outline: none;
739
+ }
740
+ .command-palette-results {
741
+ max-height: 400px;
742
+ overflow-y: auto;
743
+ }
744
+ .command-item {
451
745
  display: flex;
452
746
  justify-content: space-between;
453
747
  align-items: center;
454
- padding: 10px;
455
- background: #0d1117;
456
- border-radius: 6px;
457
- text-decoration: none;
458
- color: #e6edf3;
459
- font-size: 13px;
748
+ padding: 12px 20px;
749
+ cursor: pointer;
460
750
  }
461
- .link:hover {
751
+ .command-item:hover, .command-item.selected {
462
752
  background: #21262d;
463
753
  }
464
- .link .url {
465
- color: #8b949e;
466
- font-family: monospace;
467
- font-size: 11px;
754
+ .command-name {
755
+ font-size: 14px;
468
756
  }
469
-
470
- .empty-state {
471
- text-align: center;
472
- padding: 40px;
473
- color: #8b949e;
757
+ .command-desc {
758
+ font-size: 12px;
759
+ color: #6e7681;
474
760
  }
475
- .empty-state .icon {
476
- font-size: 48px;
477
- margin-bottom: 15px;
478
- opacity: 0.5;
761
+ .command-shortcut {
762
+ font-size: 11px;
763
+ padding: 2px 6px;
764
+ background: #21262d;
765
+ border-radius: 4px;
766
+ color: #8b949e;
479
767
  }
480
768
 
481
- /* App Preview */
482
- .app-preview-container {
769
+ /* Help Modal */
770
+ .help-overlay {
771
+ display: none;
772
+ position: fixed;
773
+ top: 0;
774
+ left: 0;
775
+ right: 0;
776
+ bottom: 0;
777
+ background: rgba(0, 0, 0, 0.5);
778
+ z-index: 1000;
779
+ align-items: center;
780
+ justify-content: center;
781
+ }
782
+ .help-overlay.active {
483
783
  display: flex;
484
- flex-direction: column;
485
- height: 100%;
486
784
  }
487
- .app-preview-toolbar {
785
+ .help-modal {
786
+ width: 500px;
787
+ max-width: 90vw;
788
+ max-height: 80vh;
789
+ background: #161b22;
790
+ border: 1px solid #30363d;
791
+ border-radius: 12px;
792
+ overflow: hidden;
793
+ }
794
+ .help-header {
795
+ padding: 16px 20px;
796
+ border-bottom: 1px solid #30363d;
488
797
  display: flex;
489
- gap: 10px;
490
- padding: 10px 0;
798
+ justify-content: space-between;
491
799
  align-items: center;
492
- border-bottom: 1px solid #30363d;
493
- margin-bottom: 10px;
494
800
  }
495
- .app-preview-toolbar .app-url {
496
- margin-left: auto;
497
- font-family: monospace;
498
- font-size: 12px;
801
+ .help-title {
802
+ font-size: 16px;
803
+ font-weight: 600;
804
+ color: #d29922;
805
+ }
806
+ .help-close {
807
+ background: none;
808
+ border: none;
499
809
  color: #8b949e;
810
+ cursor: pointer;
811
+ font-size: 20px;
500
812
  }
501
- .app-iframe {
502
- flex: 1;
503
- border: 1px solid #30363d;
504
- border-radius: 8px;
505
- background: white;
506
- width: 100%;
507
- min-height: 500px;
813
+ .help-content {
814
+ padding: 20px;
815
+ overflow-y: auto;
816
+ max-height: calc(80vh - 60px);
508
817
  }
509
-
510
- /* Progress Bar */
511
- .progress-container {
818
+ .help-section {
512
819
  margin-bottom: 20px;
513
820
  }
514
- .progress-header {
821
+ .help-section-title {
822
+ font-size: 12px;
823
+ text-transform: uppercase;
824
+ color: #8b949e;
825
+ margin-bottom: 12px;
826
+ }
827
+ .shortcut-row {
515
828
  display: flex;
516
829
  justify-content: space-between;
517
- margin-bottom: 8px;
518
- font-size: 13px;
519
- }
520
- .progress-header .phase-name {
521
- color: #58a6ff;
522
- font-weight: 500;
830
+ padding: 8px 0;
523
831
  }
524
- .progress-header .progress-pct {
525
- color: #3fb950;
526
- }
527
- .progress-bar {
528
- height: 8px;
832
+ .shortcut-keys {
833
+ font-family: monospace;
529
834
  background: #21262d;
835
+ padding: 2px 8px;
530
836
  border-radius: 4px;
531
- overflow: hidden;
837
+ font-size: 12px;
838
+ }
839
+ .shortcut-desc {
840
+ color: #8b949e;
841
+ font-size: 13px;
842
+ }
843
+
844
+ /* Usage Stats Panel */
845
+ .usage-grid {
846
+ display: grid;
847
+ grid-template-columns: repeat(2, 1fr);
848
+ gap: 16px;
849
+ }
850
+ .usage-card {
851
+ background: #161b22;
852
+ border: 1px solid #30363d;
853
+ border-radius: 8px;
854
+ padding: 16px;
532
855
  }
533
- .progress-fill {
534
- height: 100%;
535
- background: linear-gradient(90deg, #238636, #3fb950);
536
- border-radius: 4px;
537
- transition: width 0.3s ease;
856
+ .usage-label {
857
+ font-size: 12px;
858
+ color: #8b949e;
859
+ margin-bottom: 8px;
860
+ }
861
+ .usage-value {
862
+ font-size: 24px;
863
+ font-weight: bold;
538
864
  }
865
+ .usage-value.cost { color: #d29922; }
539
866
 
540
867
  /* Buttons */
541
868
  .btn {
@@ -545,6 +872,7 @@
545
872
  cursor: pointer;
546
873
  font-size: 13px;
547
874
  font-weight: 500;
875
+ transition: all 0.15s;
548
876
  }
549
877
  .btn-primary {
550
878
  background: #238636;
@@ -557,234 +885,635 @@
557
885
  border: 1px solid #30363d;
558
886
  }
559
887
  .btn-secondary:hover { background: #30363d; }
888
+
889
+ /* Empty State */
890
+ .empty-state {
891
+ text-align: center;
892
+ padding: 60px 20px;
893
+ color: #6e7681;
894
+ }
895
+ .empty-state .icon {
896
+ font-size: 48px;
897
+ margin-bottom: 16px;
898
+ opacity: 0.5;
899
+ }
900
+ .empty-state p {
901
+ font-size: 14px;
902
+ }
903
+
904
+ /* Split View for Router */
905
+ .split-view {
906
+ display: flex;
907
+ gap: 24px;
908
+ }
909
+ .split-view > div {
910
+ flex: 1;
911
+ }
560
912
  </style>
561
913
  </head>
562
914
  <body>
563
- <div class="header">
564
- <h1>
565
- <span class="logo">TLC</span>
566
- Dev Server
567
- </h1>
568
- <div class="status">
569
- <span class="phase-badge" id="phase-badge">Phase 1</span>
570
- <span class="dot" id="status-dot"></span>
571
- <span id="status-text">Connecting...</span>
572
- </div>
573
- </div>
574
-
575
- <div class="container">
576
- <div class="main-content">
577
- <div class="tabs">
578
- <button class="tab active" data-tab="app">🖥️ App</button>
579
- <button class="tab" data-tab="plan">Plan</button>
580
- <button class="tab" data-tab="tests">Tests</button>
581
- <button class="tab" data-tab="bugs">Bugs <span class="badge" id="bugs-badge" style="display:none">0</span></button>
582
- <button class="tab" data-tab="logs">Logs</button>
583
- <button class="tab" data-tab="changelog">Changelog</button>
915
+ <div class="app">
916
+ <!-- Header -->
917
+ <header class="header">
918
+ <div class="header-left">
919
+ <span class="logo">TLC</span>
920
+ <span class="header-title">Dashboard</span>
921
+ <span class="header-subtitle" id="current-view-title">Projects</span>
922
+ </div>
923
+ <div class="header-right">
924
+ <div class="connection-status">
925
+ <span class="status-dot" id="status-dot"></span>
926
+ <span id="status-text">Connecting...</span>
927
+ </div>
928
+ <span style="color: #30363d">|</span>
929
+ <span class="version">v1.4.2</span>
584
930
  </div>
931
+ </header>
585
932
 
586
- <div class="tab-content">
587
- <!-- App Preview Panel -->
588
- <div class="tab-panel active" id="panel-app">
589
- <div class="app-preview-container">
590
- <div class="app-preview-toolbar">
591
- <button class="btn btn-secondary" onclick="reloadApp()">🔄 Reload</button>
592
- <button class="btn btn-secondary" onclick="openAppNewTab()">↗️ Open in New Tab</button>
593
- <button class="btn btn-primary" onclick="captureScreenshot()">📸 Screenshot for Bug</button>
594
- <span class="app-url" id="app-url">Loading...</span>
595
- </div>
596
- <iframe id="app-iframe" src="/app" class="app-iframe"></iframe>
933
+ <div class="container">
934
+ <!-- Sidebar -->
935
+ <nav class="sidebar" id="sidebar">
936
+ <div class="sidebar-header">
937
+ <span class="sidebar-title">TLC</span>
938
+ </div>
939
+ <div class="nav-items">
940
+ <div class="nav-item active" data-view="projects" onclick="switchView('projects')">
941
+ <span class="icon">📁</span>
942
+ <span class="label">Projects</span>
943
+ <span class="shortcut">1</span>
944
+ </div>
945
+ <div class="nav-item" data-view="tasks" onclick="switchView('tasks')">
946
+ <span class="icon">📋</span>
947
+ <span class="label">Tasks</span>
948
+ <span class="shortcut">2</span>
949
+ </div>
950
+ <div class="nav-item" data-view="chat" onclick="switchView('chat')">
951
+ <span class="icon">💬</span>
952
+ <span class="label">Chat</span>
953
+ <span class="shortcut">3</span>
954
+ </div>
955
+ <div class="nav-item" data-view="agents" onclick="switchView('agents')">
956
+ <span class="icon">🤖</span>
957
+ <span class="label">Agents</span>
958
+ <span class="shortcut">4</span>
959
+ </div>
960
+ <div class="nav-item" data-view="preview" onclick="switchView('preview')">
961
+ <span class="icon">👁</span>
962
+ <span class="label">Preview</span>
963
+ <span class="shortcut">5</span>
964
+ </div>
965
+ <div class="nav-item" data-view="logs" onclick="switchView('logs')">
966
+ <span class="icon">📜</span>
967
+ <span class="label">Logs</span>
968
+ <span class="shortcut">6</span>
969
+ </div>
970
+ <div class="nav-item" data-view="github" onclick="switchView('github')">
971
+ <span class="icon">🐙</span>
972
+ <span class="label">GitHub</span>
973
+ <span class="shortcut">7</span>
974
+ </div>
975
+ <div class="nav-item" data-view="health" onclick="switchView('health')">
976
+ <span class="icon">💚</span>
977
+ <span class="label">Health</span>
978
+ <span class="shortcut">8</span>
979
+ </div>
980
+ <div class="nav-item" data-view="router" onclick="switchView('router')">
981
+ <span class="icon">🔀</span>
982
+ <span class="label">Router</span>
983
+ <span class="shortcut">9</span>
984
+ </div>
985
+ <div class="nav-item" data-view="settings" onclick="switchView('settings')">
986
+ <span class="icon">⚙️</span>
987
+ <span class="label">Settings</span>
988
+ <span class="shortcut">0</span>
597
989
  </div>
598
990
  </div>
991
+ <div class="sidebar-footer">
992
+ <span>Ctrl+K: Commands | ?: Help</span>
993
+ </div>
994
+ </nav>
995
+
996
+ <!-- Main Content -->
997
+ <main class="main">
998
+ <div class="view-header">
999
+ <span class="view-title" id="view-title">📁 Projects</span>
1000
+ </div>
1001
+ <div class="view-content">
1002
+
1003
+ <!-- Projects Panel -->
1004
+ <div class="panel active" id="panel-projects">
1005
+ <div class="project-card" id="project-card">
1006
+ <div class="project-name" id="project-name">TLC</div>
1007
+ <div class="project-desc" id="project-desc">Test-Led Coding framework</div>
1008
+ <span class="phase-badge" id="phase-badge">Phase 33: Multi-Model Router</span>
1009
+ <div class="project-stats">
1010
+ <div class="project-stat">
1011
+ <div class="project-stat-value green" id="stat-passing">0</div>
1012
+ <div class="project-stat-label">Passing</div>
1013
+ </div>
1014
+ <div class="project-stat">
1015
+ <div class="project-stat-value red" id="stat-failing">0</div>
1016
+ <div class="project-stat-label">Failing</div>
1017
+ </div>
1018
+ <div class="project-stat">
1019
+ <div class="project-stat-value blue" id="stat-coverage">0%</div>
1020
+ <div class="project-stat-label">Coverage</div>
1021
+ </div>
1022
+ </div>
1023
+ <div class="progress-bar">
1024
+ <div class="progress-fill" id="progress-fill" style="width: 82%"></div>
1025
+ </div>
1026
+ </div>
1027
+ </div>
599
1028
 
600
- <!-- Plan Panel -->
601
- <div class="tab-panel" id="panel-plan">
602
- <div class="plan-content" id="plan-content">
603
- <div class="empty-state">
604
- <div class="icon">📋</div>
605
- <p>Loading plan...</p>
1029
+ <!-- Tasks Panel -->
1030
+ <div class="panel" id="panel-tasks">
1031
+ <div class="task-board">
1032
+ <div class="task-column">
1033
+ <div class="task-column-header pending">Pending</div>
1034
+ <div id="tasks-pending"></div>
1035
+ </div>
1036
+ <div class="task-column">
1037
+ <div class="task-column-header in-progress">In Progress</div>
1038
+ <div id="tasks-in-progress"></div>
1039
+ </div>
1040
+ <div class="task-column">
1041
+ <div class="task-column-header completed">Completed</div>
1042
+ <div id="tasks-completed"></div>
1043
+ </div>
606
1044
  </div>
607
1045
  </div>
608
- </div>
609
1046
 
610
- <!-- Tests Panel -->
611
- <div class="tab-panel" id="panel-tests">
612
- <div class="test-actions">
613
- <button class="btn btn-primary" onclick="runTests()">▶ Run Unit Tests</button>
614
- <button class="btn btn-secondary" onclick="runPlaywright()">🎭 Run Playwright</button>
1047
+ <!-- Chat Panel -->
1048
+ <div class="panel" id="panel-chat">
1049
+ <div class="chat-container">
1050
+ <div class="chat-messages" id="chat-messages">
1051
+ <div class="empty-state">
1052
+ <div class="icon">💬</div>
1053
+ <p>Chat with Claude about your project</p>
1054
+ </div>
1055
+ </div>
1056
+ <div class="chat-input-container">
1057
+ <textarea class="chat-input" placeholder="Type a message... (Enter to send)" rows="3" id="chat-input"></textarea>
1058
+ </div>
1059
+ </div>
615
1060
  </div>
616
- <div class="test-section">
617
- <h3>QA Test Checklist</h3>
618
- <div id="test-checklist">
1061
+
1062
+ <!-- Agents Panel -->
1063
+ <div class="panel" id="panel-agents">
1064
+ <div class="agent-grid" id="agents-grid">
619
1065
  <div class="empty-state">
620
- <div class="icon">✓</div>
621
- <p>No test plan found. Create .planning/phases/{N}-TESTS.md</p>
1066
+ <div class="icon">🤖</div>
1067
+ <p>No active agents</p>
622
1068
  </div>
623
1069
  </div>
624
1070
  </div>
625
- <div class="test-section">
626
- <h3>Test Results</h3>
627
- <div id="test-results" class="logs-content" style="height: 300px;">
628
- <div class="log-line info">Run tests to see results...</div>
1071
+
1072
+ <!-- Preview Panel -->
1073
+ <div class="panel" id="panel-preview">
1074
+ <div class="preview-container">
1075
+ <div class="preview-toolbar">
1076
+ <button class="btn btn-secondary" onclick="reloadPreview()">🔄 Reload</button>
1077
+ <button class="btn btn-secondary" onclick="openInNewTab()">↗️ Open in New Tab</button>
1078
+ <button class="btn btn-primary" onclick="captureScreenshot()">📸 Screenshot</button>
1079
+ <span class="preview-url" id="preview-url">localhost:5001</span>
1080
+ </div>
1081
+ <iframe class="preview-iframe" id="preview-iframe" src="/app"></iframe>
629
1082
  </div>
630
1083
  </div>
631
- </div>
632
1084
 
633
- <!-- Bugs Panel -->
634
- <div class="tab-panel" id="panel-bugs">
635
- <div class="bug-form">
636
- <h3>Report New Bug</h3>
637
- <textarea id="bug-desc" placeholder="Describe the bug..."></textarea>
638
- <div class="image-drop-zone" id="image-drop-zone" onclick="document.getElementById('image-input').click()">
639
- <input type="file" id="image-input" accept="image/*" multiple style="display:none" onchange="handleImageSelect(event)">
640
- <div class="drop-hint" id="drop-hint">
641
- 📷 Paste (Ctrl+V) or drop images here
1085
+ <!-- Logs Panel -->
1086
+ <div class="panel" id="panel-logs">
1087
+ <div class="logs-container">
1088
+ <div class="logs-filter">
1089
+ <button class="active" data-logtype="app" onclick="showLogs('app')">App</button>
1090
+ <button data-logtype="test" onclick="showLogs('test')">Tests</button>
1091
+ <button data-logtype="git" onclick="showLogs('git')">Git</button>
1092
+ <button data-logtype="system" onclick="showLogs('system')">System</button>
642
1093
  </div>
643
- <div class="image-previews" id="image-previews"></div>
1094
+ <div class="logs-output" id="logs-output"></div>
1095
+ </div>
1096
+ </div>
1097
+
1098
+ <!-- GitHub Panel -->
1099
+ <div class="panel" id="panel-github">
1100
+ <div class="github-section">
1101
+ <div class="github-section-title">Recent Commits</div>
1102
+ <div id="commits-list"></div>
644
1103
  </div>
645
- <div>
646
- <select id="bug-severity">
647
- <option value="low">Low</option>
648
- <option value="medium" selected>Medium</option>
649
- <option value="high">High</option>
650
- <option value="critical">Critical</option>
651
- </select>
652
- <button class="btn btn-primary" onclick="submitBug()">Submit Bug</button>
653
- <button class="btn" onclick="clearImages()" id="clear-images-btn" style="display:none">Clear Images</button>
1104
+ <div class="github-section">
1105
+ <div class="github-section-title">Pull Requests</div>
1106
+ <div id="prs-list">
1107
+ <div class="empty-state">
1108
+ <div class="icon">🔀</div>
1109
+ <p>No open pull requests</p>
1110
+ </div>
1111
+ </div>
654
1112
  </div>
655
1113
  </div>
656
- <div id="bugs-list">
657
- <div class="empty-state">
658
- <div class="icon">🐛</div>
659
- <p>No bugs reported</p>
1114
+
1115
+ <!-- Health Panel -->
1116
+ <div class="panel" id="panel-health">
1117
+ <div class="health-grid">
1118
+ <div class="health-card">
1119
+ <div class="health-icon">💚</div>
1120
+ <div class="health-metric good" id="health-status">Healthy</div>
1121
+ <div class="health-label">System Status</div>
1122
+ </div>
1123
+ <div class="health-card">
1124
+ <div class="health-icon">🧠</div>
1125
+ <div class="health-metric" id="health-memory">0 MB</div>
1126
+ <div class="health-label">Memory</div>
1127
+ </div>
1128
+ <div class="health-card">
1129
+ <div class="health-icon">⚡</div>
1130
+ <div class="health-metric" id="health-cpu">0%</div>
1131
+ <div class="health-label">CPU</div>
1132
+ </div>
1133
+ <div class="health-card">
1134
+ <div class="health-icon">💾</div>
1135
+ <div class="health-metric" id="health-disk">0%</div>
1136
+ <div class="health-label">Disk</div>
1137
+ </div>
1138
+ </div>
1139
+ <div class="services-list">
1140
+ <h3 style="margin-bottom: 16px; color: #58a6ff;">Services</h3>
1141
+ <div id="services-list"></div>
660
1142
  </div>
661
1143
  </div>
662
- </div>
663
1144
 
664
- <!-- Logs Panel -->
665
- <div class="tab-panel" id="panel-logs">
666
- <div class="logs-tabs">
667
- <button class="active" data-logtype="app" onclick="showLogs('app')">App</button>
668
- <button data-logtype="test" onclick="showLogs('test')">Tests</button>
669
- <button data-logtype="git" onclick="showLogs('git')">Git</button>
1145
+ <!-- Router Panel -->
1146
+ <div class="panel" id="panel-router">
1147
+ <div class="split-view">
1148
+ <div>
1149
+ <div class="router-section">
1150
+ <div class="router-section-title">Providers</div>
1151
+ <div id="providers-list"></div>
1152
+ </div>
1153
+ <div class="router-section">
1154
+ <div class="router-section-title">Devserver</div>
1155
+ <div id="devserver-status">
1156
+ <div class="provider-row">
1157
+ <span class="provider-indicator unhealthy"></span>
1158
+ <span>Not configured - run </span>
1159
+ <code style="color: #58a6ff;">tlc router setup</code>
1160
+ </div>
1161
+ </div>
1162
+ </div>
1163
+ <div class="router-section">
1164
+ <div class="router-section-title">Routing Table</div>
1165
+ <div class="routing-table" id="routing-table"></div>
1166
+ </div>
1167
+ </div>
1168
+ <div>
1169
+ <div class="router-section">
1170
+ <div class="router-section-title">Usage Statistics</div>
1171
+ <div class="usage-grid">
1172
+ <div class="usage-card">
1173
+ <div class="usage-label">Requests Today</div>
1174
+ <div class="usage-value" id="usage-requests">0</div>
1175
+ </div>
1176
+ <div class="usage-card">
1177
+ <div class="usage-label">Tokens Used</div>
1178
+ <div class="usage-value" id="usage-tokens">0</div>
1179
+ </div>
1180
+ <div class="usage-card">
1181
+ <div class="usage-label">Local Requests</div>
1182
+ <div class="usage-value" style="color: #3fb950" id="usage-local">0</div>
1183
+ </div>
1184
+ <div class="usage-card">
1185
+ <div class="usage-label">Est. Cost Saved</div>
1186
+ <div class="usage-value cost" id="usage-saved">$0.00</div>
1187
+ </div>
1188
+ </div>
1189
+ </div>
1190
+ </div>
1191
+ </div>
670
1192
  </div>
671
- <div class="logs-content" id="logs-output"></div>
672
- </div>
673
1193
 
674
- <!-- Changelog Panel -->
675
- <div class="tab-panel" id="panel-changelog">
676
- <div id="changelog-content">
677
- <div class="empty-state">
678
- <div class="icon">📝</div>
679
- <p>Loading changelog...</p>
1194
+ <!-- Settings Panel -->
1195
+ <div class="panel" id="panel-settings">
1196
+ <div class="settings-section">
1197
+ <div class="settings-section-title">Project Configuration</div>
1198
+ <div class="settings-item">
1199
+ <div>
1200
+ <div class="settings-label">Project Name</div>
1201
+ <div class="settings-desc">Name from .tlc.json or package.json</div>
1202
+ </div>
1203
+ <span class="settings-value" id="settings-project">TLC</span>
1204
+ </div>
1205
+ <div class="settings-item">
1206
+ <div>
1207
+ <div class="settings-label">Test Framework</div>
1208
+ <div class="settings-desc">Primary test runner</div>
1209
+ </div>
1210
+ <span class="settings-value" id="settings-test-framework">vitest</span>
1211
+ </div>
1212
+ </div>
1213
+ <div class="settings-section">
1214
+ <div class="settings-section-title">Router Configuration</div>
1215
+ <div class="settings-item">
1216
+ <div>
1217
+ <div class="settings-label">Default Provider</div>
1218
+ <div class="settings-desc">Primary model provider</div>
1219
+ </div>
1220
+ <span class="settings-value" id="settings-provider">claude</span>
1221
+ </div>
1222
+ </div>
1223
+ <div class="settings-section">
1224
+ <div class="settings-section-title">Quick Links</div>
1225
+ <div class="quick-links">
1226
+ <a class="quick-link" onclick="switchView('health')"><kbd>U</kbd> Usage</a>
1227
+ <a class="quick-link" onclick="switchView('router')"><kbd>Q</kbd> Quality</a>
1228
+ <a class="quick-link" href="#" target="_blank"><kbd>D</kbd> Docs</a>
1229
+ <a class="quick-link" onclick="switchView('projects')"><kbd>W</kbd> Workspace</a>
1230
+ <a class="quick-link" onclick="switchView('logs')"><kbd>A</kbd> Audit</a>
1231
+ </div>
680
1232
  </div>
681
1233
  </div>
1234
+
682
1235
  </div>
683
- </div>
1236
+
1237
+ <!-- Footer -->
1238
+ <footer class="footer">
1239
+ <span>Tab: cycle | 1-0: jump | Ctrl+B: sidebar | Ctrl+K: commands | ?: help | Ctrl+Q: quit</span>
1240
+ <span id="footer-status">Ready</span>
1241
+ </footer>
1242
+ </main>
1243
+ </div>
1244
+ </div>
1245
+
1246
+ <!-- Command Palette -->
1247
+ <div class="command-palette-overlay" id="command-palette-overlay" onclick="closeCommandPalette(event)">
1248
+ <div class="command-palette" onclick="event.stopPropagation()">
1249
+ <input type="text" class="command-palette-input" id="command-input" placeholder="Type a command..." oninput="filterCommands()" onkeydown="handleCommandKey(event)">
1250
+ <div class="command-palette-results" id="command-results"></div>
684
1251
  </div>
1252
+ </div>
685
1253
 
686
- <div class="sidebar">
687
- <div class="sidebar-section">
688
- <h3>Progress</h3>
689
- <div class="progress-container">
690
- <div class="progress-header">
691
- <span class="phase-name" id="progress-phase">Phase 1</span>
692
- <span class="progress-pct" id="progress-pct">0%</span>
1254
+ <!-- Help Modal -->
1255
+ <div class="help-overlay" id="help-overlay" onclick="closeHelp(event)">
1256
+ <div class="help-modal" onclick="event.stopPropagation()">
1257
+ <div class="help-header">
1258
+ <span class="help-title">⌨️ Keyboard Shortcuts</span>
1259
+ <button class="help-close" onclick="toggleHelp()">&times;</button>
1260
+ </div>
1261
+ <div class="help-content">
1262
+ <div class="help-section">
1263
+ <div class="help-section-title">Global</div>
1264
+ <div class="shortcut-row">
1265
+ <span class="shortcut-keys">1-0</span>
1266
+ <span class="shortcut-desc">Jump to view</span>
693
1267
  </div>
694
- <div class="progress-bar">
695
- <div class="progress-fill" id="progress-fill" style="width: 0%"></div>
1268
+ <div class="shortcut-row">
1269
+ <span class="shortcut-keys">Tab</span>
1270
+ <span class="shortcut-desc">Cycle views</span>
696
1271
  </div>
697
- </div>
698
- </div>
699
-
700
- <div class="sidebar-section">
701
- <h3>Stats</h3>
702
- <div class="stats-grid">
703
- <div class="stat">
704
- <div class="stat-value green" id="stat-pass">-</div>
705
- <div class="stat-label">Passing</div>
1272
+ <div class="shortcut-row">
1273
+ <span class="shortcut-keys">Ctrl+K</span>
1274
+ <span class="shortcut-desc">Command palette</span>
706
1275
  </div>
707
- <div class="stat">
708
- <div class="stat-value red" id="stat-fail">-</div>
709
- <div class="stat-label">Failing</div>
1276
+ <div class="shortcut-row">
1277
+ <span class="shortcut-keys">Ctrl+B</span>
1278
+ <span class="shortcut-desc">Toggle sidebar</span>
710
1279
  </div>
711
- <div class="stat">
712
- <div class="stat-value blue" id="stat-tasks">-</div>
713
- <div class="stat-label">Tasks</div>
1280
+ <div class="shortcut-row">
1281
+ <span class="shortcut-keys">?</span>
1282
+ <span class="shortcut-desc">Show help</span>
714
1283
  </div>
715
- <div class="stat">
716
- <div class="stat-value yellow" id="stat-bugs">-</div>
717
- <div class="stat-label">Open Bugs</div>
1284
+ <div class="shortcut-row">
1285
+ <span class="shortcut-keys">Esc</span>
1286
+ <span class="shortcut-desc">Go back / Close</span>
718
1287
  </div>
719
1288
  </div>
720
- </div>
721
-
722
- <div class="sidebar-section">
723
- <h3>Quick Actions</h3>
724
- <div class="quick-actions">
725
- <button class="action-btn primary" onclick="runTests()">
726
- <span class="icon">▶</span> Run Tests
727
- </button>
728
- <button class="action-btn secondary" onclick="runPlaywright()">
729
- <span class="icon">🎭</span> Run Playwright
730
- </button>
731
- <button class="action-btn secondary" onclick="refreshAll()">
732
- <span class="icon">🔄</span> Refresh All
733
- </button>
734
- </div>
735
- </div>
736
-
737
- <div class="sidebar-section">
738
- <h3>Links</h3>
739
- <div class="links">
740
- <a class="link" id="link-app" href="#" target="_blank">
741
- <span>App (Direct)</span>
742
- <span class="url" id="link-app-port">:3000</span>
743
- </a>
744
- <a class="link" href="http://localhost:8080" target="_blank">
745
- <span>Database Admin</span>
746
- <span class="url">:8080</span>
747
- </a>
748
- <a class="link" href="#" onclick="event.preventDefault(); alert('Connect with: postgres://postgres:postgres@localhost:5433/app')">
749
- <span>PostgreSQL</span>
750
- <span class="url">:5433</span>
751
- </a>
1289
+ <div class="help-section">
1290
+ <div class="help-section-title">Lists</div>
1291
+ <div class="shortcut-row">
1292
+ <span class="shortcut-keys">j/k</span>
1293
+ <span class="shortcut-desc">Navigate list</span>
1294
+ </div>
1295
+ <div class="shortcut-row">
1296
+ <span class="shortcut-keys">Enter</span>
1297
+ <span class="shortcut-desc">Select item</span>
1298
+ </div>
752
1299
  </div>
753
1300
  </div>
754
1301
  </div>
755
1302
  </div>
756
1303
 
757
1304
  <script>
1305
+ // State
758
1306
  let ws;
759
- const logs = { app: [], test: [], git: [] };
760
- let currentLogType = 'app';
1307
+ let currentView = 'projects';
1308
+ let sidebarCollapsed = false;
761
1309
  let reconnectAttempts = 0;
762
- let appPort = 3000;
763
- let screenshotData = null;
764
-
765
- // Tab switching
766
- document.querySelectorAll('.tab').forEach(tab => {
767
- tab.addEventListener('click', () => {
768
- document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
769
- document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
770
- tab.classList.add('active');
771
- document.getElementById('panel-' + tab.dataset.tab).classList.add('active');
1310
+ const logs = { app: [], test: [], git: [], system: [] };
1311
+ let currentLogType = 'app';
1312
+ let appPort = 5001;
1313
+
1314
+ const views = ['projects', 'tasks', 'chat', 'agents', 'preview', 'logs', 'github', 'health', 'router', 'settings'];
1315
+ const viewTitles = {
1316
+ projects: '📁 Projects',
1317
+ tasks: '📋 Tasks',
1318
+ chat: '💬 Chat',
1319
+ agents: '🤖 Agents',
1320
+ preview: '👁 Preview',
1321
+ logs: '📜 Logs',
1322
+ github: '🐙 GitHub',
1323
+ health: '💚 Health',
1324
+ router: '🔀 Router',
1325
+ settings: '⚙️ Settings'
1326
+ };
1327
+
1328
+ const commands = [
1329
+ { id: 'view:projects', name: 'Go to Projects', desc: 'View all projects', shortcut: '1' },
1330
+ { id: 'view:tasks', name: 'Go to Tasks', desc: 'View task board', shortcut: '2' },
1331
+ { id: 'view:chat', name: 'Go to Chat', desc: 'Open chat', shortcut: '3' },
1332
+ { id: 'view:agents', name: 'Go to Agents', desc: 'View agents', shortcut: '4' },
1333
+ { id: 'view:preview', name: 'Go to Preview', desc: 'App preview', shortcut: '5' },
1334
+ { id: 'view:logs', name: 'Go to Logs', desc: 'View logs', shortcut: '6' },
1335
+ { id: 'view:github', name: 'Go to GitHub', desc: 'View GitHub', shortcut: '7' },
1336
+ { id: 'view:health', name: 'Go to Health', desc: 'System health', shortcut: '8' },
1337
+ { id: 'view:router', name: 'Go to Router', desc: 'Model router', shortcut: '9' },
1338
+ { id: 'view:settings', name: 'Go to Settings', desc: 'Settings', shortcut: '0' },
1339
+ { id: 'cmd:run-tests', name: 'Run Tests', desc: 'Run test suite' },
1340
+ { id: 'cmd:build', name: 'Build Project', desc: 'Build current project' },
1341
+ { id: 'cmd:refresh', name: 'Refresh All', desc: 'Refresh all data' },
1342
+ ];
1343
+
1344
+ // View Switching
1345
+ function switchView(view) {
1346
+ currentView = view;
1347
+
1348
+ // Update nav items
1349
+ document.querySelectorAll('.nav-item').forEach(item => {
1350
+ item.classList.toggle('active', item.dataset.view === view);
1351
+ });
1352
+
1353
+ // Update panels
1354
+ document.querySelectorAll('.panel').forEach(panel => {
1355
+ panel.classList.toggle('active', panel.id === `panel-${view}`);
772
1356
  });
1357
+
1358
+ // Update titles
1359
+ document.getElementById('view-title').textContent = viewTitles[view];
1360
+ document.getElementById('current-view-title').textContent = viewTitles[view].split(' ').slice(1).join(' ');
1361
+ }
1362
+
1363
+ // Keyboard Shortcuts
1364
+ document.addEventListener('keydown', (e) => {
1365
+ // Check if typing in input
1366
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
1367
+ if (e.key === 'Escape') {
1368
+ e.target.blur();
1369
+ }
1370
+ return;
1371
+ }
1372
+
1373
+ // Ctrl+K - Command Palette
1374
+ if (e.ctrlKey && e.key === 'k') {
1375
+ e.preventDefault();
1376
+ toggleCommandPalette();
1377
+ return;
1378
+ }
1379
+
1380
+ // Ctrl+B - Toggle Sidebar
1381
+ if (e.ctrlKey && e.key === 'b') {
1382
+ e.preventDefault();
1383
+ toggleSidebar();
1384
+ return;
1385
+ }
1386
+
1387
+ // ? - Help
1388
+ if (e.key === '?') {
1389
+ e.preventDefault();
1390
+ toggleHelp();
1391
+ return;
1392
+ }
1393
+
1394
+ // Escape - Close overlays
1395
+ if (e.key === 'Escape') {
1396
+ closeAllOverlays();
1397
+ return;
1398
+ }
1399
+
1400
+ // Number keys 1-0 for views
1401
+ const num = parseInt(e.key);
1402
+ if (!isNaN(num)) {
1403
+ const viewIndex = num === 0 ? 9 : num - 1;
1404
+ if (viewIndex < views.length) {
1405
+ switchView(views[viewIndex]);
1406
+ }
1407
+ return;
1408
+ }
1409
+
1410
+ // Tab - Cycle views
1411
+ if (e.key === 'Tab' && !e.ctrlKey && !e.shiftKey) {
1412
+ e.preventDefault();
1413
+ const currentIndex = views.indexOf(currentView);
1414
+ const nextIndex = (currentIndex + 1) % views.length;
1415
+ switchView(views[nextIndex]);
1416
+ }
773
1417
  });
774
1418
 
1419
+ // Command Palette
1420
+ function toggleCommandPalette() {
1421
+ const overlay = document.getElementById('command-palette-overlay');
1422
+ const isActive = overlay.classList.toggle('active');
1423
+ if (isActive) {
1424
+ document.getElementById('command-input').focus();
1425
+ renderCommands();
1426
+ }
1427
+ }
1428
+
1429
+ function closeCommandPalette(e) {
1430
+ if (e.target === e.currentTarget) {
1431
+ document.getElementById('command-palette-overlay').classList.remove('active');
1432
+ }
1433
+ }
1434
+
1435
+ function filterCommands() {
1436
+ renderCommands();
1437
+ }
1438
+
1439
+ function renderCommands() {
1440
+ const query = document.getElementById('command-input').value.toLowerCase();
1441
+ const filtered = commands.filter(cmd =>
1442
+ cmd.name.toLowerCase().includes(query) ||
1443
+ cmd.desc.toLowerCase().includes(query)
1444
+ );
1445
+
1446
+ document.getElementById('command-results').innerHTML = filtered.map((cmd, i) => `
1447
+ <div class="command-item ${i === 0 ? 'selected' : ''}" onclick="executeCommand('${cmd.id}')">
1448
+ <div>
1449
+ <div class="command-name">${cmd.name}</div>
1450
+ <div class="command-desc">${cmd.desc}</div>
1451
+ </div>
1452
+ ${cmd.shortcut ? `<span class="command-shortcut">${cmd.shortcut}</span>` : ''}
1453
+ </div>
1454
+ `).join('');
1455
+ }
1456
+
1457
+ function handleCommandKey(e) {
1458
+ if (e.key === 'Enter') {
1459
+ const selected = document.querySelector('.command-item.selected');
1460
+ if (selected) {
1461
+ selected.click();
1462
+ }
1463
+ } else if (e.key === 'Escape') {
1464
+ document.getElementById('command-palette-overlay').classList.remove('active');
1465
+ }
1466
+ }
1467
+
1468
+ function executeCommand(id) {
1469
+ document.getElementById('command-palette-overlay').classList.remove('active');
1470
+ document.getElementById('command-input').value = '';
1471
+
1472
+ if (id.startsWith('view:')) {
1473
+ switchView(id.replace('view:', ''));
1474
+ } else if (id === 'cmd:run-tests') {
1475
+ runTests();
1476
+ } else if (id === 'cmd:refresh') {
1477
+ refreshAll();
1478
+ }
1479
+ }
1480
+
1481
+ // Sidebar Toggle
1482
+ function toggleSidebar() {
1483
+ sidebarCollapsed = !sidebarCollapsed;
1484
+ document.getElementById('sidebar').classList.toggle('collapsed', sidebarCollapsed);
1485
+ }
1486
+
1487
+ // Help Modal
1488
+ function toggleHelp() {
1489
+ document.getElementById('help-overlay').classList.toggle('active');
1490
+ }
1491
+
1492
+ function closeHelp(e) {
1493
+ if (e.target === e.currentTarget) {
1494
+ document.getElementById('help-overlay').classList.remove('active');
1495
+ }
1496
+ }
1497
+
1498
+ function closeAllOverlays() {
1499
+ document.getElementById('command-palette-overlay').classList.remove('active');
1500
+ document.getElementById('help-overlay').classList.remove('active');
1501
+ }
1502
+
1503
+ // WebSocket Connection
775
1504
  function connect() {
776
1505
  ws = new WebSocket(`ws://${location.host}`);
777
1506
 
778
1507
  ws.onopen = () => {
779
1508
  console.log('Connected to TLC server');
780
1509
  reconnectAttempts = 0;
781
- updateStatus(true);
782
- addLog('app', 'Connected to TLC Dev Server', 'success');
1510
+ updateConnectionStatus(true);
1511
+ addLog('system', 'Connected to TLC Dashboard', 'success');
783
1512
  };
784
1513
 
785
1514
  ws.onclose = () => {
786
1515
  console.log('Disconnected');
787
- updateStatus(false);
1516
+ updateConnectionStatus(false);
788
1517
  if (reconnectAttempts < 10) {
789
1518
  setTimeout(connect, Math.min(1000 * Math.pow(2, reconnectAttempts), 30000));
790
1519
  reconnectAttempts++;
@@ -801,15 +1530,13 @@
801
1530
  switch(msg.type) {
802
1531
  case 'init':
803
1532
  Object.assign(logs, msg.data.logs || {});
804
- if (msg.data.appPort) {
805
- updateAppPort(msg.data.appPort);
806
- }
1533
+ if (msg.data.appPort) updateAppPort(msg.data.appPort);
807
1534
  renderLogs();
808
1535
  break;
809
1536
  case 'app-start':
810
1537
  if (msg.data.port) {
811
1538
  updateAppPort(msg.data.port);
812
- reloadApp();
1539
+ reloadPreview();
813
1540
  }
814
1541
  break;
815
1542
  case 'app-log':
@@ -817,8 +1544,6 @@
817
1544
  break;
818
1545
  case 'test-output':
819
1546
  addLog('test', msg.data.data, msg.data.stream === 'stderr' ? 'error' : '');
820
- document.getElementById('test-results').innerHTML +=
821
- `<div class="log-line ${msg.data.stream === 'stderr' ? 'error' : ''}">${escapeHtml(msg.data.data)}</div>`;
822
1547
  break;
823
1548
  case 'test-complete':
824
1549
  const result = msg.data.exitCode === 0 ? 'passed' : 'failed';
@@ -827,23 +1552,19 @@
827
1552
  break;
828
1553
  case 'git-activity':
829
1554
  addLog('git', msg.data.entry, 'info');
830
- refreshChangelog();
831
- break;
832
- case 'bug-created':
833
- addLog('app', `Bug ${msg.data.bugId} created`, 'warn');
834
- refreshBugs();
835
- refreshStats();
1555
+ refreshGitHub();
836
1556
  break;
837
1557
  }
838
1558
  }
839
1559
 
840
- function updateStatus(connected) {
1560
+ function updateConnectionStatus(connected) {
841
1561
  const dot = document.getElementById('status-dot');
842
1562
  const text = document.getElementById('status-text');
843
- dot.className = 'dot ' + (connected ? 'running' : 'stopped');
844
- text.textContent = connected ? 'Running' : 'Disconnected';
1563
+ dot.className = 'status-dot ' + (connected ? 'connected' : 'disconnected');
1564
+ text.textContent = connected ? 'Connected' : 'Disconnected';
845
1565
  }
846
1566
 
1567
+ // Logs
847
1568
  function detectLogLevel(text) {
848
1569
  if (/error|fail|exception/i.test(text)) return 'error';
849
1570
  if (/warn/i.test(text)) return 'warn';
@@ -873,7 +1594,7 @@
873
1594
 
874
1595
  function showLogs(type) {
875
1596
  currentLogType = type;
876
- document.querySelectorAll('.logs-tabs button').forEach(b => {
1597
+ document.querySelectorAll('.logs-filter button').forEach(b => {
877
1598
  b.classList.toggle('active', b.dataset.logtype === type);
878
1599
  });
879
1600
  renderLogs();
@@ -885,8 +1606,8 @@
885
1606
  return div.innerHTML;
886
1607
  }
887
1608
 
1609
+ // API Functions
888
1610
  async function runTests() {
889
- document.getElementById('test-results').innerHTML = '<div class="log-line info">Running tests...</div>';
890
1611
  addLog('test', '--- Running tests ---', 'info');
891
1612
  try {
892
1613
  await fetch('/api/test', { method: 'POST' });
@@ -895,328 +1616,164 @@
895
1616
  }
896
1617
  }
897
1618
 
898
- async function runPlaywright() {
899
- document.getElementById('test-results').innerHTML = '<div class="log-line info">Running Playwright tests...</div>';
900
- addLog('test', '--- Running Playwright ---', 'info');
901
- try {
902
- await fetch('/api/playwright', { method: 'POST' });
903
- } catch (e) {
904
- addLog('test', 'Playwright not configured. Add playwright container to docker-compose.', 'warn');
905
- }
906
- }
907
-
908
- // Bug image handling
909
- let bugImages = [];
910
-
911
- function setupImageHandlers() {
912
- const dropZone = document.getElementById('image-drop-zone');
913
-
914
- // Paste handler (global)
915
- document.addEventListener('paste', (e) => {
916
- const items = e.clipboardData?.items;
917
- if (!items) return;
918
-
919
- for (const item of items) {
920
- if (item.type.startsWith('image/')) {
921
- e.preventDefault();
922
- const file = item.getAsFile();
923
- if (file) addImage(file);
924
- }
925
- }
926
- });
927
-
928
- // Drag & drop
929
- dropZone.addEventListener('dragover', (e) => {
930
- e.preventDefault();
931
- dropZone.classList.add('dragover');
932
- });
933
- dropZone.addEventListener('dragleave', () => {
934
- dropZone.classList.remove('dragover');
935
- });
936
- dropZone.addEventListener('drop', (e) => {
937
- e.preventDefault();
938
- dropZone.classList.remove('dragover');
939
- const files = e.dataTransfer.files;
940
- for (const file of files) {
941
- if (file.type.startsWith('image/')) addImage(file);
942
- }
943
- });
944
- }
945
-
946
- function handleImageSelect(e) {
947
- const files = e.target.files;
948
- for (const file of files) {
949
- if (file.type.startsWith('image/')) addImage(file);
950
- }
951
- e.target.value = ''; // Reset for re-selection
952
- }
953
-
954
- function addImage(file) {
955
- const reader = new FileReader();
956
- reader.onload = (e) => {
957
- bugImages.push({ name: file.name, data: e.target.result });
958
- renderImagePreviews();
959
- };
960
- reader.readAsDataURL(file);
961
- }
962
-
963
- function removeImage(index) {
964
- bugImages.splice(index, 1);
965
- renderImagePreviews();
966
- }
967
-
968
- function clearImages() {
969
- bugImages = [];
970
- renderImagePreviews();
971
- }
972
-
973
- function renderImagePreviews() {
974
- const container = document.getElementById('image-previews');
975
- const hint = document.getElementById('drop-hint');
976
- const clearBtn = document.getElementById('clear-images-btn');
977
-
978
- if (bugImages.length === 0) {
979
- container.innerHTML = '';
980
- hint.style.display = 'block';
981
- clearBtn.style.display = 'none';
982
- return;
983
- }
984
-
985
- hint.style.display = 'none';
986
- clearBtn.style.display = 'inline-block';
987
- container.innerHTML = bugImages.map((img, i) => `
988
- <div class="image-preview">
989
- <img src="${img.data}" alt="${img.name}" title="${img.name}">
990
- <button class="remove-btn" onclick="event.stopPropagation(); removeImage(${i})">×</button>
991
- </div>
992
- `).join('');
993
- }
994
-
995
- async function submitBug() {
996
- const desc = document.getElementById('bug-desc').value.trim();
997
- const severity = document.getElementById('bug-severity').value;
998
- if (!desc) { alert('Please describe the bug'); return; }
999
-
1619
+ async function refreshStats() {
1000
1620
  try {
1001
- const res = await fetch('/api/bug', {
1002
- method: 'POST',
1003
- headers: { 'Content-Type': 'application/json' },
1004
- body: JSON.stringify({
1005
- description: desc,
1006
- severity,
1007
- images: bugImages.map(img => img.data)
1008
- })
1009
- });
1621
+ const res = await fetch('/api/status');
1010
1622
  const data = await res.json();
1011
- if (data.success) {
1012
- alert(`Bug ${data.bugId} created!` + (bugImages.length ? ` (${bugImages.length} image${bugImages.length > 1 ? 's' : ''} attached)` : ''));
1013
- document.getElementById('bug-desc').value = '';
1014
- clearImages();
1015
- refreshBugs();
1623
+ document.getElementById('stat-passing').textContent = data.testsPass || 0;
1624
+ document.getElementById('stat-failing').textContent = data.testsFail || 0;
1625
+ if (data.coverage) {
1626
+ document.getElementById('stat-coverage').textContent = data.coverage + '%';
1016
1627
  }
1017
1628
  } catch (e) {
1018
- alert('Failed to submit bug');
1629
+ console.error('Failed to load stats:', e);
1019
1630
  }
1020
1631
  }
1021
1632
 
1022
- async function refreshPlan() {
1633
+ async function refreshTasks() {
1023
1634
  try {
1024
- const res = await fetch('/api/plan');
1635
+ const res = await fetch('/api/tasks');
1025
1636
  const data = await res.json();
1026
1637
 
1027
- document.getElementById('phase-badge').textContent = `Phase ${data.phase || '?'}`;
1028
-
1029
- if (data.content) {
1030
- // Simple markdown-ish rendering
1031
- let html = escapeHtml(data.content)
1032
- .replace(/^### (.+)$/gm, '<h3>$1</h3>')
1033
- .replace(/^## (.+)$/gm, '<h2>$1</h2>')
1034
- .replace(/^# (.+)$/gm, '<h1>$1</h1>')
1035
- .replace(/\[x@(\w+)\]/g, '<span class="task-done">[✓ @$1]</span>')
1036
- .replace(/\[>@(\w+)\]/g, '<span class="task-working">[→ @$1]</span>')
1037
- .replace(/\[ \]/g, '<span class="task-todo">[ ]</span>');
1038
- document.getElementById('plan-content').innerHTML = html;
1039
- } else {
1040
- document.getElementById('plan-content').innerHTML = `
1041
- <div class="empty-state">
1042
- <div class="icon">📋</div>
1043
- <p>No plan found. Run /tlc:plan to create one.</p>
1044
- </div>`;
1045
- }
1638
+ const pending = data.filter(t => t.status === 'pending');
1639
+ const inProgress = data.filter(t => t.status === 'in_progress');
1640
+ const completed = data.filter(t => t.status === 'completed');
1641
+
1642
+ document.getElementById('tasks-pending').innerHTML = pending.map(t => `
1643
+ <div class="task-item">
1644
+ <div class="task-title">${escapeHtml(t.title)}</div>
1645
+ <div class="task-meta">${t.owner || 'Unassigned'}</div>
1646
+ </div>
1647
+ `).join('') || '<div class="empty-state"><p>No pending tasks</p></div>';
1648
+
1649
+ document.getElementById('tasks-in-progress').innerHTML = inProgress.map(t => `
1650
+ <div class="task-item">
1651
+ <div class="task-title">${escapeHtml(t.title)}</div>
1652
+ <div class="task-meta">@${t.owner || 'unknown'}</div>
1653
+ </div>
1654
+ `).join('') || '<div class="empty-state"><p>No tasks in progress</p></div>';
1655
+
1656
+ document.getElementById('tasks-completed').innerHTML = completed.map(t => `
1657
+ <div class="task-item">
1658
+ <div class="task-title">${escapeHtml(t.title)}</div>
1659
+ <div class="task-meta">@${t.owner || 'unknown'}</div>
1660
+ </div>
1661
+ `).join('') || '<div class="empty-state"><p>No completed tasks</p></div>';
1046
1662
  } catch (e) {
1047
- console.error('Failed to load plan:', e);
1663
+ console.error('Failed to load tasks:', e);
1048
1664
  }
1049
1665
  }
1050
1666
 
1051
- async function refreshTests() {
1667
+ async function refreshGitHub() {
1052
1668
  try {
1053
- const res = await fetch('/api/tests');
1669
+ const res = await fetch('/api/changelog');
1054
1670
  const data = await res.json();
1055
1671
 
1056
- if (data.items?.length) {
1057
- document.getElementById('test-checklist').innerHTML = data.items.map((t, i) => `
1058
- <div class="test-item">
1059
- <div class="checkbox ${t.checked ? 'checked' : ''}" onclick="toggleTest(${i})"></div>
1060
- <span class="text">${escapeHtml(t.text)}</span>
1672
+ if (data.commits?.length) {
1673
+ document.getElementById('commits-list').innerHTML = data.commits.slice(0, 10).map(c => `
1674
+ <div class="commit-item">
1675
+ <span class="commit-hash">${c.hash?.slice(0, 7) || '---'}</span>
1676
+ <div class="commit-message">${escapeHtml(c.message || '')}</div>
1677
+ <div class="commit-meta">${c.author || ''} • ${c.date || ''}</div>
1061
1678
  </div>
1062
1679
  `).join('');
1063
1680
  }
1064
1681
  } catch (e) {
1065
- console.error('Failed to load tests:', e);
1682
+ console.error('Failed to load GitHub data:', e);
1066
1683
  }
1067
1684
  }
1068
1685
 
1069
- async function refreshBugs() {
1686
+ async function refreshRouter() {
1070
1687
  try {
1071
- const res = await fetch('/api/bugs');
1688
+ const res = await fetch('/api/router/status');
1072
1689
  const data = await res.json();
1073
1690
 
1074
- const openBugs = data.filter(b => b.status === 'open').length;
1075
- const badge = document.getElementById('bugs-badge');
1076
- if (openBugs > 0) {
1077
- badge.textContent = openBugs;
1078
- badge.style.display = 'inline';
1079
- } else {
1080
- badge.style.display = 'none';
1691
+ if (data.providers) {
1692
+ document.getElementById('providers-list').innerHTML = Object.entries(data.providers).map(([name, p]) => {
1693
+ const isLocal = p.type === 'cli' && p.detected;
1694
+ return `
1695
+ <div class="provider-row">
1696
+ <span class="provider-indicator ${p.healthy !== false ? 'healthy' : 'unhealthy'}"></span>
1697
+ <span class="provider-name">${name}</span>
1698
+ <span class="provider-version">${p.version || ''}</span>
1699
+ <span class="provider-badge ${isLocal ? 'local' : 'devserver'}">${isLocal ? 'local' : 'devserver'}</span>
1700
+ </div>
1701
+ `;
1702
+ }).join('');
1081
1703
  }
1082
1704
 
1083
- if (data.length) {
1084
- document.getElementById('bugs-list').innerHTML = data.map(b => `
1085
- <div class="bug-item ${b.status}">
1086
- <div class="bug-header">
1087
- <span class="bug-id">${b.id}</span>
1088
- <span class="bug-date">${b.date || ''}</span>
1089
- </div>
1090
- <div class="bug-desc">${escapeHtml(b.description)}</div>
1705
+ if (data.capabilities) {
1706
+ document.getElementById('routing-table').innerHTML = Object.entries(data.capabilities).map(([cap, info]) => `
1707
+ <div class="routing-row">
1708
+ <span class="routing-capability">${cap}</span>
1709
+ <span class="routing-providers">→ ${info.providers.map(p => {
1710
+ const provider = data.providers[p];
1711
+ const isLocal = provider?.type === 'cli' && provider?.detected;
1712
+ return `<span class="${isLocal ? 'local' : 'devserver'}">${p}</span>`;
1713
+ }).join(', ')}</span>
1091
1714
  </div>
1092
1715
  `).join('');
1093
1716
  }
1094
1717
  } catch (e) {
1095
- console.error('Failed to load bugs:', e);
1718
+ console.error('Failed to load router status:', e);
1096
1719
  }
1097
1720
  }
1098
1721
 
1099
- async function refreshChangelog() {
1722
+ async function refreshHealth() {
1100
1723
  try {
1101
- const res = await fetch('/api/changelog');
1724
+ const res = await fetch('/api/health');
1102
1725
  const data = await res.json();
1103
1726
 
1104
- if (data.commits?.length) {
1105
- document.getElementById('changelog-content').innerHTML = data.commits.map(c => `
1106
- <div class="commit">
1107
- <span class="hash">${c.hash?.slice(0, 7) || '---'}</span>
1108
- <div class="message">
1109
- <div class="title">${escapeHtml(c.message || '')}</div>
1110
- <div class="meta">${c.author || ''} • ${c.date || ''}</div>
1111
- </div>
1727
+ if (data.memory) document.getElementById('health-memory').textContent = Math.round(data.memory / 1024 / 1024) + ' MB';
1728
+ if (data.cpu) document.getElementById('health-cpu').textContent = data.cpu + '%';
1729
+ if (data.disk) document.getElementById('health-disk').textContent = data.disk + '%';
1730
+
1731
+ if (data.services) {
1732
+ document.getElementById('services-list').innerHTML = data.services.map(s => `
1733
+ <div class="service-item">
1734
+ <span class="service-name">${s.name}</span>
1735
+ <span class="service-status ${s.state}">${s.state === 'running' ? '● Running' : '○ Stopped'} ${s.port ? ':' + s.port : ''}</span>
1112
1736
  </div>
1113
1737
  `).join('');
1114
1738
  }
1115
1739
  } catch (e) {
1116
- console.error('Failed to load changelog:', e);
1740
+ console.error('Failed to load health:', e);
1117
1741
  }
1118
1742
  }
1119
1743
 
1120
- async function refreshStats() {
1121
- try {
1122
- const res = await fetch('/api/status');
1123
- const data = await res.json();
1124
-
1125
- document.getElementById('stat-pass').textContent = data.testsPass || 0;
1126
- document.getElementById('stat-fail').textContent = data.testsFail || 0;
1127
- document.getElementById('stat-tasks').textContent = data.tasks || 0;
1128
- document.getElementById('stat-bugs').textContent = data.bugsOpen || 0;
1129
- } catch (e) {
1130
- console.error('Failed to load stats:', e);
1131
- }
1132
- }
1133
-
1134
- function refreshAll() {
1135
- refreshPlan();
1136
- refreshTests();
1137
- refreshBugs();
1138
- refreshChangelog();
1139
- refreshStats();
1140
- refreshProgress();
1744
+ // Preview Functions
1745
+ function updateAppPort(port) {
1746
+ appPort = port;
1747
+ document.getElementById('preview-url').textContent = `localhost:${port}`;
1141
1748
  }
1142
1749
 
1143
- // App Preview functions
1144
- function reloadApp() {
1145
- const iframe = document.getElementById('app-iframe');
1750
+ function reloadPreview() {
1751
+ const iframe = document.getElementById('preview-iframe');
1146
1752
  iframe.src = iframe.src;
1147
1753
  }
1148
1754
 
1149
- function openAppNewTab() {
1755
+ function openInNewTab() {
1150
1756
  window.open(`http://localhost:${appPort}`, '_blank');
1151
1757
  }
1152
1758
 
1153
- async function captureScreenshot() {
1154
- try {
1155
- // Use html2canvas-like approach via the iframe
1156
- const iframe = document.getElementById('app-iframe');
1157
-
1158
- // For cross-origin iframes, we can't capture directly
1159
- // Instead, prompt user to use browser screenshot or provide manual upload
1160
- const desc = prompt('Describe the bug (screenshot will be from current app view):');
1161
- if (!desc) return;
1162
-
1163
- // Create bug with current URL
1164
- const res = await fetch('/api/bug', {
1165
- method: 'POST',
1166
- headers: { 'Content-Type': 'application/json' },
1167
- body: JSON.stringify({
1168
- description: desc,
1169
- severity: 'medium',
1170
- url: iframe.src
1171
- })
1172
- });
1173
- const data = await res.json();
1174
- if (data.success) {
1175
- alert(`Bug ${data.bugId} created!\n\nTip: Use browser's screenshot tool (Cmd+Shift+4 on Mac, Win+Shift+S on Windows) to capture and attach manually.`);
1176
- refreshBugs();
1177
- }
1178
- } catch (e) {
1179
- alert('Failed to create bug: ' + e.message);
1180
- }
1181
- }
1182
-
1183
- async function refreshProgress() {
1184
- try {
1185
- const res = await fetch('/api/progress');
1186
- const data = await res.json();
1187
-
1188
- const phaseName = `Phase ${data.phase || '?'}: ${data.phaseName || 'Unknown'}`;
1189
- const pct = data.progress || 0;
1190
-
1191
- document.getElementById('progress-phase').textContent = phaseName;
1192
- document.getElementById('progress-pct').textContent = `${pct}%`;
1193
- document.getElementById('progress-fill').style.width = `${pct}%`;
1194
- } catch (e) {
1195
- // Fallback to stats-based progress
1196
- try {
1197
- const res = await fetch('/api/status');
1198
- const data = await res.json();
1199
- const phaseName = `Phase ${data.phase || '?'}`;
1200
- document.getElementById('progress-phase').textContent = phaseName;
1201
- } catch (e2) {
1202
- console.error('Failed to load progress:', e2);
1203
- }
1204
- }
1759
+ function captureScreenshot() {
1760
+ alert('Use your browser\'s screenshot tool (Cmd+Shift+4 on Mac, Win+Shift+S on Windows)');
1205
1761
  }
1206
1762
 
1207
- function updateAppPort(port) {
1208
- appPort = port;
1209
- document.getElementById('app-url').textContent = `localhost:${port}`;
1210
- document.getElementById('link-app').href = `http://localhost:${port}`;
1211
- document.getElementById('link-app-port').textContent = `:${port}`;
1763
+ // Refresh All
1764
+ function refreshAll() {
1765
+ refreshStats();
1766
+ refreshTasks();
1767
+ refreshGitHub();
1768
+ refreshRouter();
1769
+ refreshHealth();
1212
1770
  }
1213
1771
 
1214
1772
  // Initialize
1215
1773
  connect();
1216
1774
  refreshAll();
1217
- setupImageHandlers();
1218
1775
  setInterval(refreshStats, 30000);
1219
- setInterval(refreshProgress, 30000);
1776
+ setInterval(refreshHealth, 60000);
1220
1777
  </script>
1221
1778
  </body>
1222
1779
  </html>