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