claude-task-viewer 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -34
- package/package.json +1 -1
- package/public/index.html +1278 -341
- package/server.js +36 -0
package/public/index.html
CHANGED
|
@@ -3,222 +3,1118 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Claude
|
|
7
|
-
<
|
|
6
|
+
<title>Claude Tasks</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Playfair+Display:wght@400;500;600&display=swap" rel="stylesheet">
|
|
8
10
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
9
|
-
<script>
|
|
10
|
-
tailwind.config = {
|
|
11
|
-
darkMode: 'class',
|
|
12
|
-
theme: {
|
|
13
|
-
extend: {
|
|
14
|
-
colors: {
|
|
15
|
-
claude: {
|
|
16
|
-
orange: '#E86F33',
|
|
17
|
-
cream: '#F5F0E8'
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
</script>
|
|
24
|
-
<style id="theme-styles">
|
|
25
|
-
/* Light mode overrides */
|
|
26
|
-
body.light { background: #f8fafc !important; color: #1e293b !important; }
|
|
27
|
-
.light .bg-gray-900 { background: #ffffff !important; }
|
|
28
|
-
.light .bg-gray-950 { background: #f8fafc !important; }
|
|
29
|
-
.light .bg-gray-900\/50 { background: rgba(255,255,255,0.9) !important; }
|
|
30
|
-
.light .bg-gray-800 { background: #f1f5f9 !important; }
|
|
31
|
-
.light .bg-gray-800\/50 { background: rgba(241,245,249,0.7) !important; }
|
|
32
|
-
.light .bg-gray-700 { background: #e2e8f0 !important; }
|
|
33
|
-
.light .bg-gray-700\/50 { background: rgba(226,232,240,0.5) !important; }
|
|
34
|
-
.light .border-gray-800 { border-color: #e2e8f0 !important; }
|
|
35
|
-
.light .border-gray-700 { border-color: #cbd5e1 !important; }
|
|
36
|
-
.light .text-gray-100 { color: #1e293b !important; }
|
|
37
|
-
.light .text-gray-200 { color: #334155 !important; }
|
|
38
|
-
.light .text-gray-300 { color: #475569 !important; }
|
|
39
|
-
.light .text-gray-400 { color: #64748b !important; }
|
|
40
|
-
.light .text-gray-500 { color: #64748b !important; }
|
|
41
|
-
.light .text-gray-600 { color: #94a3b8 !important; }
|
|
42
|
-
.light .hover\:text-gray-300:hover { color: #334155 !important; }
|
|
43
|
-
.light .hover\:text-gray-400:hover { color: #475569 !important; }
|
|
44
|
-
.light .hover\:bg-gray-800:hover { background: #f1f5f9 !important; }
|
|
45
|
-
.light .hover\:bg-gray-800\/50:hover { background: rgba(241,245,249,0.5) !important; }
|
|
46
|
-
.light .border-green-500\/30 { border-color: rgba(34,197,94,0.4) !important; }
|
|
47
|
-
.light .bg-green-500\/10 { background: rgba(34,197,94,0.15) !important; }
|
|
48
|
-
.light .bg-green-500\/20 { background: rgba(34,197,94,0.2) !important; }
|
|
49
|
-
.light .border-claude-orange\/30 { border-color: rgba(232,111,51,0.4) !important; }
|
|
50
|
-
.light .bg-claude-orange\/10 { background: rgba(232,111,51,0.12) !important; }
|
|
51
|
-
.light .bg-claude-orange\/20 { background: rgba(232,111,51,0.2) !important; }
|
|
52
|
-
.light .border-yellow-500\/20 { border-color: rgba(234,179,8,0.4) !important; }
|
|
53
|
-
.light .bg-yellow-500\/10 { background: rgba(234,179,8,0.15) !important; }
|
|
54
|
-
.light .bg-yellow-500\/20 { background: rgba(234,179,8,0.25) !important; }
|
|
55
|
-
.light .border-blue-500\/20 { border-color: rgba(59,130,246,0.4) !important; }
|
|
56
|
-
.light .bg-blue-500\/10 { background: rgba(59,130,246,0.15) !important; }
|
|
57
|
-
.light .prose pre { background: #f1f5f9 !important; }
|
|
58
|
-
.light .prose code { background: #e2e8f0 !important; }
|
|
59
|
-
.light ::-webkit-scrollbar-thumb { background: #e2e8f0 !important; }
|
|
60
|
-
.light ::-webkit-scrollbar-thumb:hover { background: #cbd5e1 !important; }
|
|
61
|
-
body.light { scrollbar-color: #e2e8f0 transparent; }
|
|
62
|
-
</style>
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
|
|
63
12
|
<style>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
13
|
+
:root {
|
|
14
|
+
--bg-deep: #08090a;
|
|
15
|
+
--bg-surface: #0d0e10;
|
|
16
|
+
--bg-elevated: #131416;
|
|
17
|
+
--bg-hover: #1a1b1e;
|
|
18
|
+
--border: #1e2023;
|
|
19
|
+
--text-primary: #e8e8e8;
|
|
20
|
+
--text-secondary: #8b8d91;
|
|
21
|
+
--text-tertiary: #5a5c60;
|
|
22
|
+
--text-muted: #3d3f42;
|
|
23
|
+
--accent: #E86F33;
|
|
24
|
+
--accent-dim: rgba(232, 111, 51, 0.15);
|
|
25
|
+
--accent-glow: rgba(232, 111, 51, 0.4);
|
|
26
|
+
--success: #3ecf8e;
|
|
27
|
+
--success-dim: rgba(62, 207, 142, 0.12);
|
|
28
|
+
--warning: #f0b429;
|
|
29
|
+
--warning-dim: rgba(240, 180, 41, 0.12);
|
|
30
|
+
--mono: 'IBM Plex Mono', monospace;
|
|
31
|
+
--serif: 'Playfair Display', serif;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
35
|
+
|
|
36
|
+
body {
|
|
37
|
+
font-family: var(--mono);
|
|
38
|
+
font-size: 13px;
|
|
39
|
+
background: var(--bg-deep);
|
|
40
|
+
color: var(--text-primary);
|
|
41
|
+
line-height: 1.5;
|
|
42
|
+
min-height: 100vh;
|
|
43
|
+
-webkit-font-smoothing: antialiased;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Subtle scan-line texture */
|
|
47
|
+
body::before {
|
|
48
|
+
content: '';
|
|
49
|
+
position: fixed;
|
|
50
|
+
inset: 0;
|
|
51
|
+
background: repeating-linear-gradient(
|
|
52
|
+
0deg,
|
|
53
|
+
transparent,
|
|
54
|
+
transparent 2px,
|
|
55
|
+
rgba(0,0,0,0.03) 2px,
|
|
56
|
+
rgba(0,0,0,0.03) 4px
|
|
57
|
+
);
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
z-index: 9999;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Scrollbar */
|
|
63
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
73
64
|
::-webkit-scrollbar-track { background: transparent; }
|
|
74
|
-
::-webkit-scrollbar-thumb { background: var(--
|
|
75
|
-
::-webkit-scrollbar-thumb:hover { background: var(--
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
66
|
+
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
|
|
67
|
+
|
|
68
|
+
/* Layout */
|
|
69
|
+
.app { display: flex; height: 100vh; }
|
|
70
|
+
|
|
71
|
+
/* Sidebar */
|
|
72
|
+
.sidebar {
|
|
73
|
+
width: 280px;
|
|
74
|
+
background: var(--bg-surface);
|
|
75
|
+
border-right: 1px solid var(--border);
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
flex-shrink: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.sidebar-header {
|
|
82
|
+
padding: 20px;
|
|
83
|
+
border-bottom: 1px solid var(--border);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.logo {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 10px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.logo-mark {
|
|
93
|
+
width: 24px;
|
|
94
|
+
height: 24px;
|
|
95
|
+
background: var(--accent);
|
|
96
|
+
border-radius: 4px;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.logo-mark svg {
|
|
103
|
+
width: 14px;
|
|
104
|
+
height: 14px;
|
|
105
|
+
color: white;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.logo-text {
|
|
109
|
+
font-family: var(--serif);
|
|
110
|
+
font-size: 16px;
|
|
111
|
+
font-weight: 500;
|
|
112
|
+
letter-spacing: -0.02em;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.connection {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 6px;
|
|
119
|
+
margin-top: 12px;
|
|
120
|
+
font-size: 11px;
|
|
121
|
+
color: var(--text-tertiary);
|
|
122
|
+
text-transform: uppercase;
|
|
123
|
+
letter-spacing: 0.05em;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.connection-dot {
|
|
127
|
+
width: 6px;
|
|
128
|
+
height: 6px;
|
|
129
|
+
border-radius: 50%;
|
|
130
|
+
background: var(--warning);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.connection-dot.live {
|
|
134
|
+
background: var(--success);
|
|
135
|
+
box-shadow: 0 0 8px var(--success);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.connection-dot.error {
|
|
139
|
+
background: #ef4444;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Nav item */
|
|
143
|
+
.nav-item {
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: 10px;
|
|
147
|
+
width: 100%;
|
|
148
|
+
padding: 10px 12px;
|
|
149
|
+
background: transparent;
|
|
150
|
+
border: none;
|
|
151
|
+
border-radius: 6px;
|
|
152
|
+
color: var(--text-secondary);
|
|
153
|
+
font-family: var(--mono);
|
|
154
|
+
font-size: 12px;
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
transition: all 0.15s ease;
|
|
157
|
+
text-align: left;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.nav-item:hover {
|
|
161
|
+
background: var(--bg-hover);
|
|
162
|
+
color: var(--text-primary);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.nav-item.active {
|
|
166
|
+
background: var(--bg-elevated);
|
|
167
|
+
color: var(--text-primary);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.nav-item svg {
|
|
171
|
+
width: 16px;
|
|
172
|
+
height: 16px;
|
|
173
|
+
opacity: 0.6;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Sidebar sections */
|
|
177
|
+
.sidebar-section {
|
|
178
|
+
display: flex;
|
|
179
|
+
flex-direction: column;
|
|
180
|
+
border-bottom: 1px solid var(--border);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.sidebar-section.flex-1 {
|
|
184
|
+
flex: 1;
|
|
185
|
+
border-bottom: none;
|
|
186
|
+
overflow: hidden;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.section-header {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
justify-content: space-between;
|
|
193
|
+
padding: 12px 16px;
|
|
194
|
+
font-size: 10px;
|
|
195
|
+
font-weight: 600;
|
|
196
|
+
text-transform: uppercase;
|
|
197
|
+
letter-spacing: 0.1em;
|
|
198
|
+
color: var(--text-muted);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.filter-toggle {
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
gap: 6px;
|
|
205
|
+
font-weight: 400;
|
|
206
|
+
color: var(--text-tertiary);
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.filter-toggle input[type="checkbox"] {
|
|
211
|
+
appearance: none;
|
|
212
|
+
width: 10px;
|
|
213
|
+
height: 10px;
|
|
214
|
+
border: 1px solid var(--text-muted);
|
|
215
|
+
border-radius: 2px;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.filter-toggle input[type="checkbox"]:checked {
|
|
220
|
+
background: var(--accent);
|
|
221
|
+
border-color: var(--accent);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* Live Updates */
|
|
225
|
+
.live-updates {
|
|
226
|
+
padding: 0 12px 12px;
|
|
227
|
+
max-height: 180px;
|
|
228
|
+
overflow-y: auto;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.live-empty {
|
|
232
|
+
padding: 16px;
|
|
233
|
+
text-align: center;
|
|
234
|
+
font-size: 11px;
|
|
235
|
+
color: var(--text-muted);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.live-item {
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: flex-start;
|
|
241
|
+
gap: 10px;
|
|
242
|
+
padding: 10px 12px;
|
|
243
|
+
background: var(--bg-elevated);
|
|
244
|
+
border: 1px solid var(--border);
|
|
245
|
+
border-radius: 6px;
|
|
246
|
+
margin-bottom: 6px;
|
|
247
|
+
cursor: pointer;
|
|
248
|
+
transition: all 0.15s ease;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.live-item:hover {
|
|
252
|
+
border-color: var(--text-muted);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.live-item .pulse {
|
|
256
|
+
width: 8px;
|
|
257
|
+
height: 8px;
|
|
258
|
+
margin-top: 4px;
|
|
259
|
+
background: var(--accent);
|
|
260
|
+
border-radius: 50%;
|
|
261
|
+
flex-shrink: 0;
|
|
262
|
+
animation: pulse 2s ease-in-out infinite;
|
|
263
|
+
box-shadow: 0 0 12px var(--accent-glow);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.live-item-content {
|
|
267
|
+
flex: 1;
|
|
268
|
+
min-width: 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.live-item-action {
|
|
272
|
+
font-size: 12px;
|
|
273
|
+
color: var(--text-primary);
|
|
274
|
+
white-space: nowrap;
|
|
275
|
+
overflow: hidden;
|
|
276
|
+
text-overflow: ellipsis;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.live-item-session {
|
|
280
|
+
font-size: 10px;
|
|
281
|
+
color: var(--text-tertiary);
|
|
282
|
+
margin-top: 2px;
|
|
283
|
+
white-space: nowrap;
|
|
284
|
+
overflow: hidden;
|
|
285
|
+
text-overflow: ellipsis;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* Sessions */
|
|
289
|
+
.sessions-list {
|
|
290
|
+
flex: 1;
|
|
291
|
+
overflow-y: auto;
|
|
292
|
+
padding: 0 12px 12px;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.sessions-list .nav-item {
|
|
296
|
+
margin-bottom: 8px;
|
|
297
|
+
border-bottom: 1px solid var(--border);
|
|
298
|
+
padding-bottom: 12px;
|
|
299
|
+
border-radius: 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.sessions-list .nav-item:hover {
|
|
303
|
+
background: transparent;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.session-item {
|
|
307
|
+
display: block;
|
|
308
|
+
width: 100%;
|
|
309
|
+
padding: 12px;
|
|
310
|
+
margin-bottom: 4px;
|
|
311
|
+
background: transparent;
|
|
312
|
+
border: 1px solid transparent;
|
|
313
|
+
border-radius: 6px;
|
|
314
|
+
text-align: left;
|
|
315
|
+
cursor: pointer;
|
|
316
|
+
transition: all 0.15s ease;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.session-item:hover {
|
|
320
|
+
background: var(--bg-hover);
|
|
321
|
+
border-color: var(--border);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.session-item.active {
|
|
325
|
+
background: var(--bg-elevated);
|
|
326
|
+
border-color: var(--border);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.session-name {
|
|
330
|
+
display: flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
justify-content: space-between;
|
|
333
|
+
gap: 8px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.session-name span {
|
|
337
|
+
font-size: 13px;
|
|
338
|
+
color: var(--text-primary);
|
|
339
|
+
white-space: nowrap;
|
|
340
|
+
overflow: hidden;
|
|
341
|
+
text-overflow: ellipsis;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.session-name .pulse {
|
|
345
|
+
width: 8px;
|
|
346
|
+
height: 8px;
|
|
347
|
+
background: var(--accent);
|
|
348
|
+
border-radius: 50%;
|
|
349
|
+
animation: pulse 2s ease-in-out infinite;
|
|
350
|
+
box-shadow: 0 0 12px var(--accent-glow);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@keyframes pulse {
|
|
354
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
355
|
+
50% { opacity: 0.6; transform: scale(0.9); }
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.session-project {
|
|
359
|
+
font-size: 11px;
|
|
360
|
+
color: var(--text-tertiary);
|
|
361
|
+
margin-top: 4px;
|
|
362
|
+
white-space: nowrap;
|
|
363
|
+
overflow: hidden;
|
|
364
|
+
text-overflow: ellipsis;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.session-progress {
|
|
368
|
+
display: flex;
|
|
369
|
+
align-items: center;
|
|
370
|
+
gap: 8px;
|
|
371
|
+
margin-top: 8px;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.progress-bar {
|
|
375
|
+
flex: 1;
|
|
376
|
+
height: 2px;
|
|
377
|
+
background: var(--border);
|
|
378
|
+
border-radius: 1px;
|
|
379
|
+
overflow: hidden;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.progress-fill {
|
|
383
|
+
height: 100%;
|
|
384
|
+
background: var(--accent);
|
|
385
|
+
transition: width 0.3s ease;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.progress-text {
|
|
389
|
+
font-size: 10px;
|
|
390
|
+
color: var(--text-muted);
|
|
391
|
+
font-variant-numeric: tabular-nums;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.session-time {
|
|
395
|
+
font-size: 10px;
|
|
396
|
+
color: var(--text-muted);
|
|
397
|
+
margin-top: 6px;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* Footer */
|
|
401
|
+
.sidebar-footer {
|
|
402
|
+
padding: 12px 20px;
|
|
403
|
+
border-top: 1px solid var(--border);
|
|
404
|
+
font-size: 10px;
|
|
405
|
+
color: var(--text-muted);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.sidebar-footer a {
|
|
409
|
+
color: var(--text-tertiary);
|
|
410
|
+
text-decoration: none;
|
|
411
|
+
transition: color 0.15s;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.sidebar-footer a:hover {
|
|
415
|
+
color: var(--text-secondary);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/* Main */
|
|
419
|
+
.main {
|
|
420
|
+
flex: 1;
|
|
421
|
+
display: flex;
|
|
422
|
+
flex-direction: column;
|
|
423
|
+
overflow: hidden;
|
|
424
|
+
background: var(--bg-deep);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/* Empty state */
|
|
428
|
+
.empty-state {
|
|
429
|
+
flex: 1;
|
|
430
|
+
display: flex;
|
|
431
|
+
align-items: center;
|
|
432
|
+
justify-content: center;
|
|
433
|
+
flex-direction: column;
|
|
434
|
+
gap: 16px;
|
|
435
|
+
color: var(--text-muted);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.empty-state svg {
|
|
439
|
+
width: 48px;
|
|
440
|
+
height: 48px;
|
|
441
|
+
opacity: 0.3;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.empty-state p {
|
|
445
|
+
font-size: 13px;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* Session view */
|
|
449
|
+
.session-view {
|
|
450
|
+
flex: 1;
|
|
451
|
+
display: none;
|
|
452
|
+
flex-direction: column;
|
|
453
|
+
overflow: hidden;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.session-view.visible {
|
|
457
|
+
display: flex;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* Header */
|
|
461
|
+
.view-header {
|
|
462
|
+
padding: 16px 24px;
|
|
463
|
+
border-bottom: 1px solid var(--border);
|
|
464
|
+
background: var(--bg-surface);
|
|
465
|
+
display: flex;
|
|
466
|
+
align-items: center;
|
|
467
|
+
justify-content: space-between;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.view-title {
|
|
471
|
+
font-family: var(--serif);
|
|
472
|
+
font-size: 20px;
|
|
473
|
+
font-weight: 400;
|
|
474
|
+
letter-spacing: -0.02em;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.view-meta {
|
|
478
|
+
font-size: 11px;
|
|
479
|
+
color: var(--text-tertiary);
|
|
480
|
+
margin-top: 4px;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.view-actions {
|
|
484
|
+
display: flex;
|
|
485
|
+
align-items: center;
|
|
486
|
+
gap: 16px;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.view-progress {
|
|
490
|
+
display: flex;
|
|
491
|
+
align-items: center;
|
|
492
|
+
gap: 10px;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.view-progress .progress-bar {
|
|
496
|
+
width: 120px;
|
|
497
|
+
height: 3px;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.view-progress .progress-text {
|
|
501
|
+
font-size: 13px;
|
|
502
|
+
font-weight: 500;
|
|
503
|
+
color: var(--accent);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.icon-btn {
|
|
507
|
+
width: 32px;
|
|
508
|
+
height: 32px;
|
|
509
|
+
display: flex;
|
|
510
|
+
align-items: center;
|
|
511
|
+
justify-content: center;
|
|
512
|
+
background: transparent;
|
|
513
|
+
border: 1px solid var(--border);
|
|
514
|
+
border-radius: 6px;
|
|
515
|
+
color: var(--text-tertiary);
|
|
516
|
+
cursor: pointer;
|
|
517
|
+
transition: all 0.15s ease;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.icon-btn:hover {
|
|
521
|
+
background: var(--bg-hover);
|
|
522
|
+
color: var(--text-primary);
|
|
523
|
+
border-color: var(--text-muted);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.icon-btn svg {
|
|
527
|
+
width: 16px;
|
|
528
|
+
height: 16px;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/* Kanban */
|
|
532
|
+
.kanban {
|
|
533
|
+
flex: 1;
|
|
534
|
+
display: flex;
|
|
535
|
+
gap: 24px;
|
|
536
|
+
padding: 24px;
|
|
537
|
+
overflow-x: auto;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.kanban-column {
|
|
541
|
+
width: 320px;
|
|
542
|
+
flex-shrink: 0;
|
|
543
|
+
display: flex;
|
|
544
|
+
flex-direction: column;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.column-header {
|
|
548
|
+
display: flex;
|
|
549
|
+
align-items: center;
|
|
550
|
+
gap: 10px;
|
|
551
|
+
padding-bottom: 16px;
|
|
552
|
+
border-bottom: 1px solid var(--border);
|
|
553
|
+
margin-bottom: 16px;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.column-dot {
|
|
557
|
+
width: 8px;
|
|
558
|
+
height: 8px;
|
|
559
|
+
border-radius: 50%;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.column-dot.pending { background: var(--text-muted); }
|
|
563
|
+
.column-dot.in-progress {
|
|
564
|
+
background: var(--accent);
|
|
565
|
+
box-shadow: 0 0 12px var(--accent-glow);
|
|
566
|
+
animation: pulse 2s ease-in-out infinite;
|
|
567
|
+
}
|
|
568
|
+
.column-dot.completed { background: var(--success); }
|
|
569
|
+
|
|
570
|
+
.column-title {
|
|
571
|
+
font-size: 12px;
|
|
572
|
+
font-weight: 500;
|
|
573
|
+
text-transform: uppercase;
|
|
574
|
+
letter-spacing: 0.05em;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.column-title.pending { color: var(--text-tertiary); }
|
|
578
|
+
.column-title.in-progress { color: var(--accent); }
|
|
579
|
+
.column-title.completed { color: var(--success); }
|
|
580
|
+
|
|
581
|
+
.column-count {
|
|
582
|
+
font-size: 10px;
|
|
583
|
+
padding: 2px 8px;
|
|
584
|
+
border-radius: 10px;
|
|
585
|
+
font-variant-numeric: tabular-nums;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.column-count.pending {
|
|
589
|
+
background: var(--bg-elevated);
|
|
590
|
+
color: var(--text-muted);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.column-count.in-progress {
|
|
594
|
+
background: var(--accent-dim);
|
|
595
|
+
color: var(--accent);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.column-count.completed {
|
|
599
|
+
background: var(--success-dim);
|
|
600
|
+
color: var(--success);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.column-tasks {
|
|
604
|
+
flex: 1;
|
|
605
|
+
overflow-y: auto;
|
|
606
|
+
display: flex;
|
|
607
|
+
flex-direction: column;
|
|
608
|
+
gap: 8px;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.column-empty {
|
|
612
|
+
text-align: center;
|
|
613
|
+
padding: 32px 16px;
|
|
614
|
+
color: var(--text-muted);
|
|
615
|
+
font-size: 12px;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/* Task card */
|
|
619
|
+
.task-card {
|
|
620
|
+
padding: 14px;
|
|
621
|
+
background: var(--bg-surface);
|
|
622
|
+
border: 1px solid var(--border);
|
|
623
|
+
border-radius: 8px;
|
|
624
|
+
cursor: pointer;
|
|
625
|
+
transition: all 0.15s ease;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.task-card:hover {
|
|
629
|
+
background: var(--bg-elevated);
|
|
630
|
+
border-color: var(--text-muted);
|
|
631
|
+
transform: translateY(-1px);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.task-card.in-progress {
|
|
635
|
+
border-color: var(--accent);
|
|
636
|
+
box-shadow: 0 0 20px var(--accent-dim), inset 0 0 20px var(--accent-dim);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.task-card.completed {
|
|
640
|
+
opacity: 0.6;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.task-card.blocked {
|
|
644
|
+
opacity: 0.5;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.task-id {
|
|
648
|
+
font-size: 10px;
|
|
649
|
+
color: var(--text-muted);
|
|
650
|
+
margin-bottom: 6px;
|
|
651
|
+
display: flex;
|
|
652
|
+
align-items: center;
|
|
653
|
+
gap: 8px;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.task-badge {
|
|
657
|
+
font-size: 9px;
|
|
658
|
+
padding: 2px 6px;
|
|
659
|
+
border-radius: 3px;
|
|
660
|
+
text-transform: uppercase;
|
|
661
|
+
letter-spacing: 0.03em;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.task-badge.blocked {
|
|
665
|
+
background: var(--warning-dim);
|
|
666
|
+
color: var(--warning);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.task-title {
|
|
670
|
+
font-size: 13px;
|
|
671
|
+
color: var(--text-primary);
|
|
672
|
+
line-height: 1.4;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.task-card.completed .task-title {
|
|
676
|
+
text-decoration: line-through;
|
|
677
|
+
color: var(--text-tertiary);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.task-session {
|
|
681
|
+
font-size: 11px;
|
|
682
|
+
color: var(--accent);
|
|
683
|
+
margin-top: 6px;
|
|
684
|
+
opacity: 0.8;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.task-active {
|
|
688
|
+
display: flex;
|
|
689
|
+
align-items: center;
|
|
690
|
+
gap: 6px;
|
|
691
|
+
margin-top: 10px;
|
|
692
|
+
padding-top: 10px;
|
|
693
|
+
border-top: 1px solid var(--border);
|
|
694
|
+
font-size: 11px;
|
|
695
|
+
color: var(--accent);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.task-active::before {
|
|
699
|
+
content: '';
|
|
700
|
+
width: 6px;
|
|
701
|
+
height: 6px;
|
|
702
|
+
background: var(--accent);
|
|
703
|
+
border-radius: 50%;
|
|
704
|
+
animation: pulse 2s ease-in-out infinite;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.task-blocked {
|
|
708
|
+
font-size: 10px;
|
|
709
|
+
color: var(--text-muted);
|
|
710
|
+
margin-top: 8px;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.task-desc {
|
|
714
|
+
font-size: 11px;
|
|
715
|
+
color: var(--text-tertiary);
|
|
716
|
+
margin-top: 8px;
|
|
717
|
+
display: -webkit-box;
|
|
718
|
+
-webkit-line-clamp: 2;
|
|
719
|
+
-webkit-box-orient: vertical;
|
|
720
|
+
overflow: hidden;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/* Detail panel */
|
|
724
|
+
.detail-panel {
|
|
725
|
+
width: 400px;
|
|
726
|
+
background: var(--bg-surface);
|
|
727
|
+
border-left: 1px solid var(--border);
|
|
728
|
+
display: none;
|
|
729
|
+
flex-direction: column;
|
|
730
|
+
flex-shrink: 0;
|
|
731
|
+
overflow: hidden;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.detail-panel.visible {
|
|
735
|
+
display: flex;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.detail-header {
|
|
739
|
+
padding: 16px 20px;
|
|
740
|
+
border-bottom: 1px solid var(--border);
|
|
741
|
+
display: flex;
|
|
742
|
+
align-items: center;
|
|
743
|
+
justify-content: space-between;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.detail-header h3 {
|
|
747
|
+
font-family: var(--serif);
|
|
748
|
+
font-size: 14px;
|
|
749
|
+
font-weight: 500;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.detail-close {
|
|
753
|
+
width: 28px;
|
|
754
|
+
height: 28px;
|
|
755
|
+
display: flex;
|
|
756
|
+
align-items: center;
|
|
757
|
+
justify-content: center;
|
|
758
|
+
background: transparent;
|
|
759
|
+
border: none;
|
|
760
|
+
color: var(--text-muted);
|
|
761
|
+
cursor: pointer;
|
|
762
|
+
border-radius: 4px;
|
|
763
|
+
transition: all 0.15s;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.detail-close:hover {
|
|
767
|
+
background: var(--bg-hover);
|
|
768
|
+
color: var(--text-primary);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.detail-close svg {
|
|
772
|
+
width: 16px;
|
|
773
|
+
height: 16px;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.detail-content {
|
|
777
|
+
flex: 1;
|
|
778
|
+
overflow-y: auto;
|
|
779
|
+
padding: 20px;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.detail-section {
|
|
783
|
+
margin-bottom: 20px;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.detail-label {
|
|
787
|
+
font-size: 10px;
|
|
788
|
+
font-weight: 500;
|
|
789
|
+
text-transform: uppercase;
|
|
790
|
+
letter-spacing: 0.08em;
|
|
791
|
+
color: var(--text-muted);
|
|
792
|
+
margin-bottom: 8px;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.detail-title {
|
|
796
|
+
font-family: var(--serif);
|
|
797
|
+
font-size: 18px;
|
|
798
|
+
line-height: 1.4;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
.detail-status {
|
|
802
|
+
display: inline-flex;
|
|
803
|
+
align-items: center;
|
|
804
|
+
gap: 6px;
|
|
805
|
+
font-size: 12px;
|
|
806
|
+
padding: 6px 10px;
|
|
807
|
+
border-radius: 4px;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.detail-status.pending {
|
|
811
|
+
background: var(--bg-elevated);
|
|
812
|
+
color: var(--text-tertiary);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.detail-status.in_progress {
|
|
816
|
+
background: var(--accent-dim);
|
|
817
|
+
color: var(--accent);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.detail-status.completed {
|
|
821
|
+
background: var(--success-dim);
|
|
822
|
+
color: var(--success);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.detail-status .dot {
|
|
826
|
+
width: 6px;
|
|
827
|
+
height: 6px;
|
|
828
|
+
border-radius: 50%;
|
|
829
|
+
background: currentColor;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.detail-status.in_progress .dot {
|
|
833
|
+
animation: pulse 2s ease-in-out infinite;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.detail-box {
|
|
837
|
+
padding: 12px;
|
|
838
|
+
border-radius: 6px;
|
|
839
|
+
font-size: 12px;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.detail-box.active {
|
|
843
|
+
background: var(--accent-dim);
|
|
844
|
+
border: 1px solid rgba(232, 111, 51, 0.2);
|
|
845
|
+
color: var(--accent);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.detail-box.blocked {
|
|
849
|
+
background: var(--warning-dim);
|
|
850
|
+
border: 1px solid rgba(240, 180, 41, 0.2);
|
|
851
|
+
color: var(--warning);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.detail-box.blocks {
|
|
855
|
+
background: rgba(96, 165, 250, 0.1);
|
|
856
|
+
border: 1px solid rgba(96, 165, 250, 0.2);
|
|
857
|
+
color: #60a5fa;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.detail-desc {
|
|
861
|
+
font-size: 13px;
|
|
862
|
+
line-height: 1.7;
|
|
863
|
+
color: var(--text-secondary);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.detail-desc pre {
|
|
867
|
+
background: var(--bg-elevated);
|
|
868
|
+
padding: 12px;
|
|
869
|
+
border-radius: 6px;
|
|
870
|
+
overflow-x: auto;
|
|
871
|
+
margin: 12px 0;
|
|
872
|
+
font-size: 12px;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.detail-desc code {
|
|
876
|
+
background: var(--bg-elevated);
|
|
877
|
+
padding: 2px 6px;
|
|
878
|
+
border-radius: 3px;
|
|
879
|
+
font-size: 0.9em;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
.detail-desc pre code {
|
|
883
|
+
background: transparent;
|
|
884
|
+
padding: 0;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.detail-desc hr {
|
|
888
|
+
border: none;
|
|
889
|
+
border-top: 1px solid var(--border);
|
|
890
|
+
margin: 16px 0;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
.detail-desc h4 {
|
|
894
|
+
font-size: 10px;
|
|
895
|
+
font-weight: 600;
|
|
896
|
+
text-transform: uppercase;
|
|
897
|
+
letter-spacing: 0.05em;
|
|
898
|
+
color: var(--accent);
|
|
899
|
+
margin: 0 0 8px 0;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
.detail-desc p {
|
|
903
|
+
margin: 0 0 12px 0;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.detail-desc p:last-child {
|
|
907
|
+
margin-bottom: 0;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/* Note form */
|
|
911
|
+
.note-section {
|
|
912
|
+
margin-top: 24px;
|
|
913
|
+
padding-top: 20px;
|
|
914
|
+
border-top: 1px solid var(--border);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.note-form {
|
|
918
|
+
display: flex;
|
|
919
|
+
flex-direction: column;
|
|
920
|
+
gap: 10px;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.note-input {
|
|
924
|
+
width: 100%;
|
|
925
|
+
padding: 10px 12px;
|
|
926
|
+
background: var(--bg-elevated);
|
|
927
|
+
border: 1px solid var(--border);
|
|
928
|
+
border-radius: 6px;
|
|
929
|
+
color: var(--text-primary);
|
|
930
|
+
font-family: var(--mono);
|
|
931
|
+
font-size: 12px;
|
|
932
|
+
line-height: 1.5;
|
|
933
|
+
resize: vertical;
|
|
934
|
+
min-height: 60px;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.note-input:focus {
|
|
938
|
+
outline: none;
|
|
939
|
+
border-color: var(--accent);
|
|
940
|
+
box-shadow: 0 0 0 2px var(--accent-dim);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.note-input::placeholder {
|
|
944
|
+
color: var(--text-muted);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.note-submit {
|
|
948
|
+
align-self: flex-end;
|
|
949
|
+
padding: 8px 16px;
|
|
950
|
+
background: var(--accent);
|
|
951
|
+
border: none;
|
|
952
|
+
border-radius: 5px;
|
|
953
|
+
color: white;
|
|
954
|
+
font-family: var(--mono);
|
|
955
|
+
font-size: 11px;
|
|
956
|
+
font-weight: 500;
|
|
957
|
+
cursor: pointer;
|
|
958
|
+
transition: all 0.15s ease;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
.note-submit:hover {
|
|
962
|
+
filter: brightness(1.1);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/* Light mode */
|
|
966
|
+
body.light {
|
|
967
|
+
--bg-deep: #fafafa;
|
|
968
|
+
--bg-surface: #ffffff;
|
|
969
|
+
--bg-elevated: #f5f5f5;
|
|
970
|
+
--bg-hover: #efefef;
|
|
971
|
+
--border: #e5e5e5;
|
|
972
|
+
--text-primary: #171717;
|
|
973
|
+
--text-secondary: #525252;
|
|
974
|
+
--text-tertiary: #737373;
|
|
975
|
+
--text-muted: #a3a3a3;
|
|
976
|
+
--accent-dim: rgba(232, 111, 51, 0.1);
|
|
977
|
+
--accent-glow: rgba(232, 111, 51, 0.3);
|
|
978
|
+
--success-dim: rgba(62, 207, 142, 0.1);
|
|
979
|
+
--warning-dim: rgba(240, 180, 41, 0.1);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
body.light::before {
|
|
983
|
+
display: none;
|
|
984
|
+
}
|
|
79
985
|
</style>
|
|
80
986
|
</head>
|
|
81
|
-
<body
|
|
82
|
-
<div class="
|
|
987
|
+
<body>
|
|
988
|
+
<div class="app">
|
|
83
989
|
<!-- Sidebar -->
|
|
84
|
-
<aside class="
|
|
85
|
-
<header class="
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
990
|
+
<aside class="sidebar">
|
|
991
|
+
<header class="sidebar-header">
|
|
992
|
+
<div class="logo">
|
|
993
|
+
<div class="logo-mark">
|
|
994
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
995
|
+
<path d="M5 13l4 4L19 7"/>
|
|
996
|
+
</svg>
|
|
997
|
+
</div>
|
|
998
|
+
<span class="logo-text">Claude Tasks</span>
|
|
999
|
+
</div>
|
|
1000
|
+
<div id="connection-status" class="connection">
|
|
1001
|
+
<span class="connection-dot"></span>
|
|
1002
|
+
<span>Connecting</span>
|
|
1003
|
+
</div>
|
|
93
1004
|
</header>
|
|
94
1005
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<span
|
|
1006
|
+
<!-- Live Updates -->
|
|
1007
|
+
<div class="sidebar-section">
|
|
1008
|
+
<div class="section-header">
|
|
1009
|
+
<span>Live Updates</span>
|
|
1010
|
+
</div>
|
|
1011
|
+
<div id="live-updates" class="live-updates">
|
|
1012
|
+
<div class="live-empty">No active tasks</div>
|
|
99
1013
|
</div>
|
|
100
1014
|
</div>
|
|
101
1015
|
|
|
102
|
-
<!--
|
|
103
|
-
<div class="
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
1016
|
+
<!-- Sessions -->
|
|
1017
|
+
<div class="sidebar-section flex-1">
|
|
1018
|
+
<div class="section-header">
|
|
1019
|
+
<span>Sessions</span>
|
|
1020
|
+
<label class="filter-toggle">
|
|
1021
|
+
<input type="checkbox" id="hide-inactive" onchange="toggleHideInactive()">
|
|
1022
|
+
<span>Active only</span>
|
|
1023
|
+
</label>
|
|
1024
|
+
</div>
|
|
1025
|
+
<div id="sessions-list" class="sessions-list">
|
|
1026
|
+
<button id="all-tasks-btn" class="nav-item" onclick="showAllTasks()">
|
|
1027
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1028
|
+
<path d="M4 6h16M4 12h16M4 18h16"/>
|
|
1029
|
+
</svg>
|
|
1030
|
+
<span>All Tasks</span>
|
|
1031
|
+
</button>
|
|
1032
|
+
</div>
|
|
118
1033
|
</div>
|
|
119
1034
|
|
|
120
|
-
<
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
<footer class="p-3 border-t border-gray-800 text-xs text-gray-600">
|
|
125
|
-
<a href="https://github.com/L1AD/claude-task-viewer" target="_blank" class="hover:text-gray-400">GitHub</a>
|
|
126
|
-
<span class="mx-2">·</span>
|
|
127
|
-
<a href="https://policylayer.com" target="_blank" class="hover:text-gray-400">PolicyLayer</a>
|
|
128
|
-
<span class="mx-2">·</span>
|
|
129
|
-
<span>v1.2.0</span>
|
|
1035
|
+
<footer class="sidebar-footer">
|
|
1036
|
+
<a href="https://github.com/L1AD/claude-task-viewer" target="_blank">GitHub</a>
|
|
1037
|
+
<span style="margin: 0 6px;">·</span>
|
|
1038
|
+
<a href="https://policylayer.com" target="_blank">PolicyLayer</a>
|
|
130
1039
|
</footer>
|
|
131
1040
|
</aside>
|
|
132
1041
|
|
|
133
|
-
<!-- Main
|
|
134
|
-
<main class="
|
|
135
|
-
<div id="no-session" class="
|
|
136
|
-
<
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<p>Select a session to view tasks</p>
|
|
141
|
-
</div>
|
|
1042
|
+
<!-- Main -->
|
|
1043
|
+
<main class="main">
|
|
1044
|
+
<div id="no-session" class="empty-state">
|
|
1045
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
1046
|
+
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
|
|
1047
|
+
</svg>
|
|
1048
|
+
<p>Select a session to view tasks</p>
|
|
142
1049
|
</div>
|
|
143
1050
|
|
|
144
|
-
<div id="session-view" class="
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<div class="w-32 h-2 bg-gray-800 rounded-full overflow-hidden">
|
|
155
|
-
<div id="progress-bar" class="h-full bg-gradient-to-r from-claude-orange to-orange-400 transition-all duration-500" style="width: 0%"></div>
|
|
156
|
-
</div>
|
|
157
|
-
<span id="progress-percent" class="text-sm font-medium text-claude-orange">0%</span>
|
|
1051
|
+
<div id="session-view" class="session-view">
|
|
1052
|
+
<header class="view-header">
|
|
1053
|
+
<div>
|
|
1054
|
+
<h1 id="session-title" class="view-title">Session</h1>
|
|
1055
|
+
<p id="session-meta" class="view-meta"></p>
|
|
1056
|
+
</div>
|
|
1057
|
+
<div class="view-actions">
|
|
1058
|
+
<div class="view-progress">
|
|
1059
|
+
<div class="progress-bar">
|
|
1060
|
+
<div id="progress-bar" class="progress-fill" style="width: 0%"></div>
|
|
158
1061
|
</div>
|
|
159
|
-
<
|
|
160
|
-
<svg id="theme-icon-dark" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
161
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
|
|
162
|
-
</svg>
|
|
163
|
-
<svg id="theme-icon-light" class="w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
164
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
|
|
165
|
-
</svg>
|
|
166
|
-
</button>
|
|
1062
|
+
<span id="progress-percent" class="progress-text">0%</span>
|
|
167
1063
|
</div>
|
|
1064
|
+
<button id="theme-toggle" class="icon-btn" onclick="toggleTheme()" title="Toggle theme">
|
|
1065
|
+
<svg id="theme-icon-dark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1066
|
+
<path d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
|
|
1067
|
+
</svg>
|
|
1068
|
+
<svg id="theme-icon-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:none">
|
|
1069
|
+
<circle cx="12" cy="12" r="5"/>
|
|
1070
|
+
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
|
|
1071
|
+
</svg>
|
|
1072
|
+
</button>
|
|
168
1073
|
</div>
|
|
169
1074
|
</header>
|
|
170
1075
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<
|
|
177
|
-
<span class="w-3 h-3 rounded-full bg-gray-500"></span>
|
|
178
|
-
<h3 class="font-medium text-gray-400">Pending</h3>
|
|
179
|
-
<span id="pending-count" class="text-xs text-gray-600 bg-gray-800 px-2 py-0.5 rounded-full">0</span>
|
|
180
|
-
</div>
|
|
181
|
-
<div id="pending-tasks" class="flex-1 space-y-3 kanban-column overflow-y-auto pr-1">
|
|
182
|
-
</div>
|
|
1076
|
+
<div class="kanban">
|
|
1077
|
+
<div class="kanban-column">
|
|
1078
|
+
<div class="column-header">
|
|
1079
|
+
<span class="column-dot pending"></span>
|
|
1080
|
+
<span class="column-title pending">Pending</span>
|
|
1081
|
+
<span id="pending-count" class="column-count pending">0</span>
|
|
183
1082
|
</div>
|
|
1083
|
+
<div id="pending-tasks" class="column-tasks"></div>
|
|
1084
|
+
</div>
|
|
184
1085
|
|
|
185
|
-
|
|
186
|
-
<div class="
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<span id="in-progress-count" class="text-xs text-claude-orange/70 bg-claude-orange/20 px-2 py-0.5 rounded-full">0</span>
|
|
191
|
-
</div>
|
|
192
|
-
<div id="in-progress-tasks" class="flex-1 space-y-3 kanban-column overflow-y-auto pr-1">
|
|
193
|
-
</div>
|
|
1086
|
+
<div class="kanban-column">
|
|
1087
|
+
<div class="column-header">
|
|
1088
|
+
<span class="column-dot in-progress"></span>
|
|
1089
|
+
<span class="column-title in-progress">In Progress</span>
|
|
1090
|
+
<span id="in-progress-count" class="column-count in-progress">0</span>
|
|
194
1091
|
</div>
|
|
1092
|
+
<div id="in-progress-tasks" class="column-tasks"></div>
|
|
1093
|
+
</div>
|
|
195
1094
|
|
|
196
|
-
|
|
197
|
-
<div class="
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<span id="completed-count" class="text-xs text-green-400/70 bg-green-500/20 px-2 py-0.5 rounded-full">0</span>
|
|
202
|
-
</div>
|
|
203
|
-
<div id="completed-tasks" class="flex-1 space-y-3 kanban-column overflow-y-auto pr-1">
|
|
204
|
-
</div>
|
|
1095
|
+
<div class="kanban-column">
|
|
1096
|
+
<div class="column-header">
|
|
1097
|
+
<span class="column-dot completed"></span>
|
|
1098
|
+
<span class="column-title completed">Completed</span>
|
|
1099
|
+
<span id="completed-count" class="column-count completed">0</span>
|
|
205
1100
|
</div>
|
|
1101
|
+
<div id="completed-tasks" class="column-tasks"></div>
|
|
206
1102
|
</div>
|
|
207
1103
|
</div>
|
|
208
1104
|
</div>
|
|
209
1105
|
</main>
|
|
210
1106
|
|
|
211
|
-
<!--
|
|
212
|
-
<aside id="detail-panel" class="
|
|
213
|
-
<header class="
|
|
214
|
-
<h3
|
|
215
|
-
<button id="close-detail" class="
|
|
216
|
-
<svg
|
|
217
|
-
<path
|
|
1107
|
+
<!-- Detail panel -->
|
|
1108
|
+
<aside id="detail-panel" class="detail-panel">
|
|
1109
|
+
<header class="detail-header">
|
|
1110
|
+
<h3>Task Details</h3>
|
|
1111
|
+
<button id="close-detail" class="detail-close">
|
|
1112
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1113
|
+
<path d="M6 18L18 6M6 6l12 12"/>
|
|
218
1114
|
</svg>
|
|
219
1115
|
</button>
|
|
220
1116
|
</header>
|
|
221
|
-
<div id="detail-content" class="
|
|
1117
|
+
<div id="detail-content" class="detail-content"></div>
|
|
222
1118
|
</aside>
|
|
223
1119
|
</div>
|
|
224
1120
|
|
|
@@ -227,10 +1123,10 @@
|
|
|
227
1123
|
let sessions = [];
|
|
228
1124
|
let currentSessionId = null;
|
|
229
1125
|
let currentTasks = [];
|
|
230
|
-
let viewMode = 'session';
|
|
1126
|
+
let viewMode = 'session';
|
|
231
1127
|
let hideInactive = localStorage.getItem('hideInactive') === 'true';
|
|
232
1128
|
|
|
233
|
-
// DOM
|
|
1129
|
+
// DOM
|
|
234
1130
|
const sessionsList = document.getElementById('sessions-list');
|
|
235
1131
|
const noSession = document.getElementById('no-session');
|
|
236
1132
|
const sessionView = document.getElementById('session-view');
|
|
@@ -248,18 +1144,52 @@
|
|
|
248
1144
|
const detailContent = document.getElementById('detail-content');
|
|
249
1145
|
const connectionStatus = document.getElementById('connection-status');
|
|
250
1146
|
|
|
251
|
-
// Fetch sessions
|
|
252
1147
|
async function fetchSessions() {
|
|
253
1148
|
try {
|
|
254
1149
|
const res = await fetch('/api/sessions');
|
|
255
1150
|
sessions = await res.json();
|
|
256
1151
|
renderSessions();
|
|
1152
|
+
fetchLiveUpdates();
|
|
257
1153
|
} catch (error) {
|
|
258
1154
|
console.error('Failed to fetch sessions:', error);
|
|
259
1155
|
}
|
|
260
1156
|
}
|
|
261
1157
|
|
|
262
|
-
|
|
1158
|
+
async function fetchLiveUpdates() {
|
|
1159
|
+
try {
|
|
1160
|
+
const res = await fetch('/api/tasks/all');
|
|
1161
|
+
const allTasks = await res.json();
|
|
1162
|
+
const activeTasks = allTasks.filter(t => t.status === 'in_progress');
|
|
1163
|
+
renderLiveUpdates(activeTasks);
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
console.error('Failed to fetch live updates:', error);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function renderLiveUpdates(activeTasks) {
|
|
1170
|
+
const container = document.getElementById('live-updates');
|
|
1171
|
+
|
|
1172
|
+
if (activeTasks.length === 0) {
|
|
1173
|
+
container.innerHTML = '<div class="live-empty">No active tasks</div>';
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
container.innerHTML = activeTasks.map(task => `
|
|
1178
|
+
<div class="live-item" onclick="openLiveTask('${task.sessionId}', '${task.id}')">
|
|
1179
|
+
<span class="pulse"></span>
|
|
1180
|
+
<div class="live-item-content">
|
|
1181
|
+
<div class="live-item-action">${escapeHtml(task.activeForm || task.subject)}</div>
|
|
1182
|
+
<div class="live-item-session">${escapeHtml(task.sessionName || task.sessionId.slice(0, 8))}</div>
|
|
1183
|
+
</div>
|
|
1184
|
+
</div>
|
|
1185
|
+
`).join('');
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
async function openLiveTask(sessionId, taskId) {
|
|
1189
|
+
await fetchTasks(sessionId);
|
|
1190
|
+
showTaskDetail(taskId, sessionId);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
263
1193
|
async function fetchTasks(sessionId) {
|
|
264
1194
|
try {
|
|
265
1195
|
viewMode = 'session';
|
|
@@ -272,7 +1202,6 @@
|
|
|
272
1202
|
}
|
|
273
1203
|
}
|
|
274
1204
|
|
|
275
|
-
// Show all tasks across all sessions
|
|
276
1205
|
async function showAllTasks() {
|
|
277
1206
|
try {
|
|
278
1207
|
viewMode = 'all';
|
|
@@ -286,10 +1215,9 @@
|
|
|
286
1215
|
}
|
|
287
1216
|
}
|
|
288
1217
|
|
|
289
|
-
// Render all tasks view
|
|
290
1218
|
function renderAllTasks() {
|
|
291
|
-
noSession.
|
|
292
|
-
sessionView.classList.
|
|
1219
|
+
noSession.style.display = 'none';
|
|
1220
|
+
sessionView.classList.add('visible');
|
|
293
1221
|
|
|
294
1222
|
const totalTasks = currentTasks.length;
|
|
295
1223
|
const completed = currentTasks.filter(t => t.status === 'completed').length;
|
|
@@ -297,39 +1225,39 @@
|
|
|
297
1225
|
|
|
298
1226
|
sessionTitle.textContent = 'All Tasks';
|
|
299
1227
|
sessionMeta.textContent = `${totalTasks} tasks across ${sessions.length} sessions`;
|
|
300
|
-
|
|
301
1228
|
progressPercent.textContent = `${percent}%`;
|
|
302
1229
|
progressBar.style.width = `${percent}%`;
|
|
303
1230
|
|
|
304
1231
|
renderKanban();
|
|
305
1232
|
}
|
|
306
1233
|
|
|
307
|
-
// Render sessions sidebar
|
|
308
1234
|
function renderSessions() {
|
|
309
|
-
// Update all tasks button state
|
|
310
1235
|
const allTasksBtn = document.getElementById('all-tasks-btn');
|
|
311
|
-
|
|
312
|
-
allTasksBtn.className = `w-full text-left p-3 rounded-lg transition-colors flex items-center gap-2 ${
|
|
313
|
-
viewMode === 'all' ? 'bg-gray-800' : 'hover:bg-gray-800/50'
|
|
314
|
-
}`;
|
|
315
|
-
}
|
|
1236
|
+
allTasksBtn.classList.toggle('active', viewMode === 'all');
|
|
316
1237
|
|
|
317
|
-
// Filter sessions based on hideInactive
|
|
318
1238
|
const filteredSessions = hideInactive
|
|
319
1239
|
? sessions.filter(s => s.pending > 0 || s.inProgress > 0)
|
|
320
1240
|
: sessions;
|
|
321
1241
|
|
|
1242
|
+
// Get container for session items (after All Tasks button)
|
|
1243
|
+
let sessionsContainer = document.getElementById('sessions-items');
|
|
1244
|
+
if (!sessionsContainer) {
|
|
1245
|
+
sessionsContainer = document.createElement('div');
|
|
1246
|
+
sessionsContainer.id = 'sessions-items';
|
|
1247
|
+
sessionsList.appendChild(sessionsContainer);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
322
1250
|
if (filteredSessions.length === 0) {
|
|
323
|
-
|
|
324
|
-
<div
|
|
1251
|
+
sessionsContainer.innerHTML = `
|
|
1252
|
+
<div style="padding: 24px 12px; text-align: center; color: var(--text-muted); font-size: 12px;">
|
|
325
1253
|
<p>${hideInactive ? 'No active sessions' : 'No sessions found'}</p>
|
|
326
|
-
<p
|
|
1254
|
+
<p style="margin-top: 8px; font-size: 11px;">${hideInactive ? 'Uncheck filter to see all' : 'Tasks appear when you use Claude Code'}</p>
|
|
327
1255
|
</div>
|
|
328
1256
|
`;
|
|
329
1257
|
return;
|
|
330
1258
|
}
|
|
331
1259
|
|
|
332
|
-
|
|
1260
|
+
sessionsContainer.innerHTML = filteredSessions.map(session => {
|
|
333
1261
|
const total = session.taskCount;
|
|
334
1262
|
const percent = total > 0 ? Math.round((session.completed / total) * 100) : 0;
|
|
335
1263
|
const isActive = session.id === currentSessionId && viewMode === 'session';
|
|
@@ -338,31 +1266,25 @@
|
|
|
338
1266
|
const projectName = session.project ? session.project.split('/').pop() : null;
|
|
339
1267
|
|
|
340
1268
|
return `
|
|
341
|
-
<button
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
<div class="flex items-center justify-between gap-2">
|
|
346
|
-
<span class="text-sm text-gray-200 truncate flex-1 ${session.name ? '' : 'font-mono text-xs text-gray-400'}">${escapeHtml(displayName)}</span>
|
|
347
|
-
${hasInProgress ? '<span class="w-2 h-2 rounded-full bg-claude-orange status-pulse flex-shrink-0"></span>' : ''}
|
|
1269
|
+
<button onclick="fetchTasks('${session.id}')" class="session-item ${isActive ? 'active' : ''}">
|
|
1270
|
+
<div class="session-name">
|
|
1271
|
+
<span>${escapeHtml(displayName)}</span>
|
|
1272
|
+
${hasInProgress ? '<span class="pulse"></span>' : ''}
|
|
348
1273
|
</div>
|
|
349
|
-
${projectName ? `<
|
|
350
|
-
<div class="
|
|
351
|
-
<div class="
|
|
352
|
-
|
|
353
|
-
</div>
|
|
354
|
-
<span class="text-xs text-gray-500">${session.completed}/${total}</span>
|
|
1274
|
+
${projectName ? `<div class="session-project">${escapeHtml(projectName)}</div>` : ''}
|
|
1275
|
+
<div class="session-progress">
|
|
1276
|
+
<div class="progress-bar"><div class="progress-fill" style="width: ${percent}%"></div></div>
|
|
1277
|
+
<span class="progress-text">${session.completed}/${total}</span>
|
|
355
1278
|
</div>
|
|
356
|
-
<
|
|
1279
|
+
<div class="session-time">${formatDate(session.modifiedAt)}</div>
|
|
357
1280
|
</button>
|
|
358
1281
|
`;
|
|
359
1282
|
}).join('');
|
|
360
1283
|
}
|
|
361
1284
|
|
|
362
|
-
// Render current session
|
|
363
1285
|
function renderSession() {
|
|
364
|
-
noSession.
|
|
365
|
-
sessionView.classList.
|
|
1286
|
+
noSession.style.display = 'none';
|
|
1287
|
+
sessionView.classList.add('visible');
|
|
366
1288
|
|
|
367
1289
|
const session = sessions.find(s => s.id === currentSessionId);
|
|
368
1290
|
if (!session) return;
|
|
@@ -370,7 +1292,7 @@
|
|
|
370
1292
|
const displayName = session.name || currentSessionId;
|
|
371
1293
|
sessionTitle.textContent = displayName;
|
|
372
1294
|
const projectName = session.project ? session.project.split('/').pop() : null;
|
|
373
|
-
sessionMeta.textContent = `${currentTasks.length} tasks${projectName ? ' · ' + projectName : ''} ·
|
|
1295
|
+
sessionMeta.textContent = `${currentTasks.length} tasks${projectName ? ' · ' + projectName : ''} · ${formatDate(session.modifiedAt)}`;
|
|
374
1296
|
|
|
375
1297
|
const completed = currentTasks.filter(t => t.status === 'completed').length;
|
|
376
1298
|
const percent = currentTasks.length > 0 ? Math.round((completed / currentTasks.length) * 100) : 0;
|
|
@@ -382,41 +1304,27 @@
|
|
|
382
1304
|
renderSessions();
|
|
383
1305
|
}
|
|
384
1306
|
|
|
385
|
-
// Render task card
|
|
386
1307
|
function renderTaskCard(task) {
|
|
387
1308
|
const isBlocked = task.blockedBy && task.blockedBy.length > 0;
|
|
388
|
-
const statusStyles = {
|
|
389
|
-
pending: 'border-gray-700 bg-gray-800/50',
|
|
390
|
-
in_progress: 'border-claude-orange/30 bg-claude-orange/10',
|
|
391
|
-
completed: 'border-green-500/30 bg-green-500/10'
|
|
392
|
-
};
|
|
393
1309
|
const taskId = viewMode === 'all' ? `${task.sessionId?.slice(0,4)}-${task.id}` : task.id;
|
|
394
1310
|
const sessionLabel = viewMode === 'all' && task.sessionName ? task.sessionName : null;
|
|
1311
|
+
const statusClass = task.status.replace('_', '-');
|
|
395
1312
|
|
|
396
1313
|
return `
|
|
397
|
-
<div
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
<div class="flex items-center gap-2 mb-2">
|
|
402
|
-
<span class="text-xs font-mono text-gray-500">#${taskId}</span>
|
|
403
|
-
${isBlocked ? '<span class="text-xs bg-yellow-500/20 text-yellow-400 px-1.5 py-0.5 rounded">blocked</span>' : ''}
|
|
1314
|
+
<div onclick="showTaskDetail('${task.id}', '${task.sessionId || ''}')" class="task-card ${statusClass} ${isBlocked ? 'blocked' : ''}">
|
|
1315
|
+
<div class="task-id">
|
|
1316
|
+
<span>#${taskId}</span>
|
|
1317
|
+
${isBlocked ? '<span class="task-badge blocked">Blocked</span>' : ''}
|
|
404
1318
|
</div>
|
|
405
|
-
<
|
|
406
|
-
${sessionLabel ? `<
|
|
407
|
-
${task.status === 'in_progress' && task.activeForm ?
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
${escapeHtml(task.activeForm)}
|
|
411
|
-
</p>
|
|
412
|
-
` : ''}
|
|
413
|
-
${isBlocked ? `<p class="text-xs text-gray-500 mt-2">Waiting on: ${task.blockedBy.map(id => '#' + id).join(', ')}</p>` : ''}
|
|
414
|
-
${task.description ? `<p class="text-xs text-gray-500 mt-2 line-clamp-2">${escapeHtml(task.description.split('\n')[0])}</p>` : ''}
|
|
1319
|
+
<div class="task-title">${escapeHtml(task.subject)}</div>
|
|
1320
|
+
${sessionLabel ? `<div class="task-session">${escapeHtml(sessionLabel)}</div>` : ''}
|
|
1321
|
+
${task.status === 'in_progress' && task.activeForm ? `<div class="task-active">${escapeHtml(task.activeForm)}</div>` : ''}
|
|
1322
|
+
${isBlocked ? `<div class="task-blocked">Waiting on ${task.blockedBy.map(id => '#' + id).join(', ')}</div>` : ''}
|
|
1323
|
+
${task.description ? `<div class="task-desc">${escapeHtml(task.description.split('\n')[0])}</div>` : ''}
|
|
415
1324
|
</div>
|
|
416
1325
|
`;
|
|
417
1326
|
}
|
|
418
1327
|
|
|
419
|
-
// Render kanban board
|
|
420
1328
|
function renderKanban() {
|
|
421
1329
|
const pending = currentTasks.filter(t => t.status === 'pending');
|
|
422
1330
|
const inProgress = currentTasks.filter(t => t.status === 'in_progress');
|
|
@@ -428,112 +1336,160 @@
|
|
|
428
1336
|
|
|
429
1337
|
pendingTasks.innerHTML = pending.length > 0
|
|
430
1338
|
? pending.map(renderTaskCard).join('')
|
|
431
|
-
: '<
|
|
1339
|
+
: '<div class="column-empty">No pending tasks</div>';
|
|
432
1340
|
|
|
433
1341
|
inProgressTasks.innerHTML = inProgress.length > 0
|
|
434
1342
|
? inProgress.map(renderTaskCard).join('')
|
|
435
|
-
: '<
|
|
1343
|
+
: '<div class="column-empty">No active tasks</div>';
|
|
436
1344
|
|
|
437
1345
|
completedTasks.innerHTML = completed.length > 0
|
|
438
1346
|
? completed.map(renderTaskCard).join('')
|
|
439
|
-
: '<
|
|
1347
|
+
: '<div class="column-empty">No completed tasks</div>';
|
|
440
1348
|
}
|
|
441
1349
|
|
|
442
|
-
// Show task detail
|
|
443
1350
|
function showTaskDetail(taskId, sessionId = null) {
|
|
444
|
-
const task = currentTasks.find(t =>
|
|
445
|
-
t.id === taskId && (!sessionId || t.sessionId === sessionId)
|
|
446
|
-
);
|
|
1351
|
+
const task = currentTasks.find(t => t.id === taskId && (!sessionId || t.sessionId === sessionId));
|
|
447
1352
|
if (!task) return;
|
|
448
1353
|
|
|
449
|
-
detailPanel.classList.
|
|
1354
|
+
detailPanel.classList.add('visible');
|
|
450
1355
|
|
|
451
1356
|
const statusLabels = {
|
|
452
|
-
completed: '<span class="
|
|
453
|
-
in_progress: '<span class="
|
|
454
|
-
pending: '<span class="
|
|
1357
|
+
completed: '<span class="detail-status completed"><span class="dot"></span>Completed</span>',
|
|
1358
|
+
in_progress: '<span class="detail-status in_progress"><span class="dot"></span>In Progress</span>',
|
|
1359
|
+
pending: '<span class="detail-status pending"><span class="dot"></span>Pending</span>'
|
|
455
1360
|
};
|
|
456
1361
|
|
|
457
1362
|
detailContent.innerHTML = `
|
|
458
|
-
<div class="
|
|
459
|
-
<div>
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
</div>
|
|
1363
|
+
<div class="detail-section">
|
|
1364
|
+
<div class="detail-label">Task #${task.id}</div>
|
|
1365
|
+
<h2 class="detail-title">${escapeHtml(task.subject)}</h2>
|
|
1366
|
+
</div>
|
|
463
1367
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
1368
|
+
<div class="detail-section">
|
|
1369
|
+
${statusLabels[task.status] || statusLabels.pending}
|
|
1370
|
+
</div>
|
|
467
1371
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
<
|
|
1372
|
+
${task.activeForm && task.status === 'in_progress' ? `
|
|
1373
|
+
<div class="detail-section">
|
|
1374
|
+
<div class="detail-box active">
|
|
1375
|
+
<strong>Currently:</strong> ${escapeHtml(task.activeForm)}
|
|
472
1376
|
</div>
|
|
473
|
-
|
|
1377
|
+
</div>
|
|
1378
|
+
` : ''}
|
|
474
1379
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
<
|
|
1380
|
+
${task.blockedBy && task.blockedBy.length > 0 ? `
|
|
1381
|
+
<div class="detail-section">
|
|
1382
|
+
<div class="detail-box blocked">
|
|
1383
|
+
<strong>Blocked by:</strong> ${task.blockedBy.map(id => '#' + id).join(', ')}
|
|
479
1384
|
</div>
|
|
480
|
-
|
|
1385
|
+
</div>
|
|
1386
|
+
` : ''}
|
|
481
1387
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
<
|
|
1388
|
+
${task.blocks && task.blocks.length > 0 ? `
|
|
1389
|
+
<div class="detail-section">
|
|
1390
|
+
<div class="detail-box blocks">
|
|
1391
|
+
<strong>Blocks:</strong> ${task.blocks.map(id => '#' + id).join(', ')}
|
|
486
1392
|
</div>
|
|
487
|
-
|
|
1393
|
+
</div>
|
|
1394
|
+
` : ''}
|
|
488
1395
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
1396
|
+
${task.description ? `
|
|
1397
|
+
<div class="detail-section">
|
|
1398
|
+
<div class="detail-label">Description</div>
|
|
1399
|
+
<div class="detail-desc">${DOMPurify.sanitize(marked.parse(task.description))}</div>
|
|
1400
|
+
</div>
|
|
1401
|
+
` : ''}
|
|
1402
|
+
|
|
1403
|
+
<div class="detail-section note-section">
|
|
1404
|
+
<div class="detail-label">Add Note</div>
|
|
1405
|
+
<form class="note-form" onsubmit="addNote(event, '${task.id}', '${task.sessionId || currentSessionId}')">
|
|
1406
|
+
<textarea id="note-input" class="note-input" placeholder="Add a note for Claude..." rows="3"></textarea>
|
|
1407
|
+
<button type="submit" class="note-submit">Add Note</button>
|
|
1408
|
+
</form>
|
|
497
1409
|
</div>
|
|
498
1410
|
`;
|
|
499
1411
|
}
|
|
500
1412
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
1413
|
+
async function addNote(event, taskId, sessionId) {
|
|
1414
|
+
event.preventDefault();
|
|
1415
|
+
const input = document.getElementById('note-input');
|
|
1416
|
+
const note = input.value.trim();
|
|
1417
|
+
if (!note) return;
|
|
505
1418
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
1419
|
+
try {
|
|
1420
|
+
const res = await fetch(`/api/tasks/${sessionId}/${taskId}/note`, {
|
|
1421
|
+
method: 'POST',
|
|
1422
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1423
|
+
body: JSON.stringify({ note })
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
if (res.ok) {
|
|
1427
|
+
input.value = '';
|
|
1428
|
+
// Refresh to show updated description
|
|
1429
|
+
if (viewMode === 'all') {
|
|
1430
|
+
const tasksRes = await fetch('/api/tasks/all');
|
|
1431
|
+
currentTasks = await tasksRes.json();
|
|
1432
|
+
} else {
|
|
1433
|
+
await fetchTasks(sessionId);
|
|
1434
|
+
}
|
|
1435
|
+
showTaskDetail(taskId, sessionId);
|
|
1436
|
+
}
|
|
1437
|
+
} catch (error) {
|
|
1438
|
+
console.error('Failed to add note:', error);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
509
1441
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
<span class="text-gray-400">Live</span>
|
|
514
|
-
`;
|
|
515
|
-
};
|
|
1442
|
+
function closeDetailPanel() {
|
|
1443
|
+
detailPanel.classList.remove('visible');
|
|
1444
|
+
}
|
|
516
1445
|
|
|
517
|
-
|
|
518
|
-
connectionStatus.innerHTML = `
|
|
519
|
-
<span class="w-2 h-2 rounded-full bg-red-500"></span>
|
|
520
|
-
<span class="text-gray-400">Disconnected</span>
|
|
521
|
-
`;
|
|
522
|
-
};
|
|
1446
|
+
document.getElementById('close-detail').onclick = closeDetailPanel;
|
|
523
1447
|
|
|
524
|
-
|
|
525
|
-
|
|
1448
|
+
document.addEventListener('keydown', (e) => {
|
|
1449
|
+
if (e.key === 'Escape' && detailPanel.classList.contains('visible')) {
|
|
1450
|
+
closeDetailPanel();
|
|
1451
|
+
}
|
|
1452
|
+
});
|
|
526
1453
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
1454
|
+
function setupEventSource() {
|
|
1455
|
+
let retryDelay = 1000;
|
|
1456
|
+
let eventSource;
|
|
1457
|
+
|
|
1458
|
+
function connect() {
|
|
1459
|
+
eventSource = new EventSource('/api/events');
|
|
1460
|
+
|
|
1461
|
+
eventSource.onopen = () => {
|
|
1462
|
+
retryDelay = 1000; // Reset on successful connection
|
|
1463
|
+
connectionStatus.innerHTML = `
|
|
1464
|
+
<span class="connection-dot live"></span>
|
|
1465
|
+
<span>Connected</span>
|
|
1466
|
+
`;
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
eventSource.onerror = () => {
|
|
1470
|
+
eventSource.close();
|
|
1471
|
+
connectionStatus.innerHTML = `
|
|
1472
|
+
<span class="connection-dot error"></span>
|
|
1473
|
+
<span>Reconnecting...</span>
|
|
1474
|
+
`;
|
|
1475
|
+
setTimeout(connect, retryDelay);
|
|
1476
|
+
retryDelay = Math.min(retryDelay * 2, 30000); // Max 30s
|
|
1477
|
+
};
|
|
1478
|
+
|
|
1479
|
+
eventSource.onmessage = (event) => {
|
|
1480
|
+
const data = JSON.parse(event.data);
|
|
1481
|
+
if (data.type === 'update' || data.type === 'metadata-update') {
|
|
1482
|
+
fetchSessions();
|
|
1483
|
+
if (currentSessionId && data.sessionId === currentSessionId) {
|
|
1484
|
+
fetchTasks(currentSessionId);
|
|
1485
|
+
}
|
|
531
1486
|
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
connect();
|
|
534
1491
|
}
|
|
535
1492
|
|
|
536
|
-
// Helpers
|
|
537
1493
|
function formatDate(dateStr) {
|
|
538
1494
|
const date = new Date(dateStr);
|
|
539
1495
|
const now = new Date();
|
|
@@ -551,56 +1507,37 @@
|
|
|
551
1507
|
return div.innerHTML;
|
|
552
1508
|
}
|
|
553
1509
|
|
|
554
|
-
// Toggle hide inactive sessions
|
|
555
1510
|
function toggleHideInactive() {
|
|
556
1511
|
hideInactive = document.getElementById('hide-inactive').checked;
|
|
557
1512
|
localStorage.setItem('hideInactive', hideInactive);
|
|
558
1513
|
renderSessions();
|
|
559
1514
|
}
|
|
560
1515
|
|
|
561
|
-
// Theme toggle
|
|
562
1516
|
function toggleTheme() {
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
body.classList.remove('light');
|
|
568
|
-
localStorage.setItem('theme', 'dark');
|
|
569
|
-
document.getElementById('theme-icon-dark').classList.remove('hidden');
|
|
570
|
-
document.getElementById('theme-icon-light').classList.add('hidden');
|
|
571
|
-
} else {
|
|
572
|
-
body.classList.add('light');
|
|
573
|
-
localStorage.setItem('theme', 'light');
|
|
574
|
-
document.getElementById('theme-icon-dark').classList.add('hidden');
|
|
575
|
-
document.getElementById('theme-icon-light').classList.remove('hidden');
|
|
576
|
-
}
|
|
1517
|
+
const isLight = document.body.classList.toggle('light');
|
|
1518
|
+
localStorage.setItem('theme', isLight ? 'light' : 'dark');
|
|
1519
|
+
document.getElementById('theme-icon-dark').style.display = isLight ? 'none' : 'block';
|
|
1520
|
+
document.getElementById('theme-icon-light').style.display = isLight ? 'block' : 'none';
|
|
577
1521
|
}
|
|
578
1522
|
|
|
579
|
-
// Load saved theme
|
|
580
1523
|
function loadTheme() {
|
|
581
|
-
|
|
582
|
-
if (saved === 'light') {
|
|
1524
|
+
if (localStorage.getItem('theme') === 'light') {
|
|
583
1525
|
document.body.classList.add('light');
|
|
584
|
-
document.getElementById('theme-icon-dark').
|
|
585
|
-
document.getElementById('theme-icon-light').
|
|
1526
|
+
document.getElementById('theme-icon-dark').style.display = 'none';
|
|
1527
|
+
document.getElementById('theme-icon-light').style.display = 'block';
|
|
586
1528
|
}
|
|
587
1529
|
}
|
|
588
1530
|
|
|
589
|
-
// Load saved preferences
|
|
590
1531
|
function loadPreferences() {
|
|
591
1532
|
document.getElementById('hide-inactive').checked = hideInactive;
|
|
592
1533
|
}
|
|
593
1534
|
|
|
594
|
-
//
|
|
1535
|
+
// Init
|
|
595
1536
|
loadTheme();
|
|
596
1537
|
loadPreferences();
|
|
597
1538
|
fetchSessions();
|
|
598
1539
|
setupEventSource();
|
|
599
|
-
|
|
600
|
-
// Default to All Tasks view
|
|
601
|
-
setTimeout(() => {
|
|
602
|
-
showAllTasks();
|
|
603
|
-
}, 500);
|
|
1540
|
+
setTimeout(showAllTasks, 500);
|
|
604
1541
|
</script>
|
|
605
1542
|
</body>
|
|
606
1543
|
</html>
|