claude-task-viewer 1.1.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/public/index.html CHANGED
@@ -3,218 +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 Task Viewer</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
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
- .prose pre { background: #1f2937; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; }
65
- .prose code { background: #374151; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875em; }
66
- .prose pre code { background: transparent; padding: 0; }
67
- .status-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
68
- @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
69
- .kanban-column { min-height: calc(100vh - 200px); }
70
-
71
- /* Custom scrollbar - dark mode default */
72
- ::-webkit-scrollbar { width: 8px; height: 8px; }
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(--scrollbar-thumb, #374151); border-radius: 4px; }
75
- ::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover, #4b5563); }
76
- * { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb, #374151) transparent; }
77
- :root { --scrollbar-thumb: #374151; --scrollbar-thumb-hover: #4b5563; }
78
- body.light { --scrollbar-thumb: #d1d5db; --scrollbar-thumb-hover: #9ca3af; }
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 class="bg-gray-950 text-gray-100 min-h-screen">
82
- <div class="flex h-screen">
987
+ <body>
988
+ <div class="app">
83
989
  <!-- Sidebar -->
84
- <aside class="w-72 bg-gray-900 border-r border-gray-800 flex flex-col flex-shrink-0">
85
- <header class="p-4 border-b border-gray-800">
86
- <h1 class="text-lg font-semibold flex items-center gap-2">
87
- <svg class="w-6 h-6 text-claude-orange" viewBox="0 0 24 24" fill="currentColor">
88
- <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 2m-6 9l2 2 4-4"/>
89
- </svg>
90
- Claude Tasks
91
- </h1>
92
- <p class="text-xs text-gray-500 mt-1">~/.claude/tasks</p>
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
- <div class="p-3 border-b border-gray-800">
96
- <div id="connection-status" class="flex items-center gap-2 text-xs">
97
- <span class="w-2 h-2 rounded-full bg-yellow-500"></span>
98
- <span class="text-gray-400">Connecting...</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
- <!-- All Tasks button -->
103
- <div class="p-2 border-b border-gray-800">
104
- <button
105
- id="all-tasks-btn"
106
- onclick="showAllTasks()"
107
- class="w-full text-left p-3 rounded-lg transition-colors hover:bg-gray-800/50 flex items-center gap-2"
108
- >
109
- <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
110
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"/>
111
- </svg>
112
- <span class="text-sm text-gray-300">All Tasks</span>
113
- </button>
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>
114
1033
  </div>
115
1034
 
116
- <nav id="sessions-list" class="flex-1 overflow-y-auto p-2">
117
- <p class="text-gray-500 text-sm p-2">Loading sessions...</p>
118
- </nav>
119
-
120
- <footer class="p-3 border-t border-gray-800 text-xs text-gray-600">
121
- <a href="https://github.com/L1AD/claude-task-viewer" target="_blank" class="hover:text-gray-400">GitHub</a>
122
- <span class="mx-2">·</span>
123
- <a href="https://policylayer.com" target="_blank" class="hover:text-gray-400">PolicyLayer</a>
124
- <span class="mx-2">·</span>
125
- <span>v1.1.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>
126
1039
  </footer>
127
1040
  </aside>
128
1041
 
129
- <!-- Main content -->
130
- <main class="flex-1 flex flex-col overflow-hidden">
131
- <div id="no-session" class="flex-1 flex items-center justify-center text-gray-600">
132
- <div class="text-center">
133
- <svg class="w-16 h-16 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
134
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" 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"/>
135
- </svg>
136
- <p>Select a session to view tasks</p>
137
- </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>
138
1049
  </div>
139
1050
 
140
- <div id="session-view" class="flex-1 flex flex-col overflow-hidden hidden">
141
- <!-- Session header -->
142
- <header class="p-4 border-b border-gray-800 bg-gray-900/50 flex-shrink-0">
143
- <div class="flex items-center justify-between">
144
- <div>
145
- <h2 id="session-title" class="text-sm font-mono text-gray-400">Session</h2>
146
- <p id="session-meta" class="text-xs text-gray-500 mt-0.5"></p>
147
- </div>
148
- <div class="flex items-center gap-4">
149
- <div class="flex items-center gap-2">
150
- <div class="w-32 h-2 bg-gray-800 rounded-full overflow-hidden">
151
- <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>
152
- </div>
153
- <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>
154
1061
  </div>
155
- <button id="theme-toggle" onclick="toggleTheme()" class="p-2 rounded-lg hover:bg-gray-800 text-gray-400 hover:text-gray-200 transition-colors" title="Toggle theme">
156
- <svg id="theme-icon-dark" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
157
- <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"/>
158
- </svg>
159
- <svg id="theme-icon-light" class="w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
160
- <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"/>
161
- </svg>
162
- </button>
1062
+ <span id="progress-percent" class="progress-text">0%</span>
163
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>
164
1073
  </div>
165
1074
  </header>
166
1075
 
167
- <!-- Kanban board -->
168
- <div class="flex-1 overflow-x-auto p-4">
169
- <div class="flex gap-8 h-full min-w-max">
170
- <!-- Pending column -->
171
- <div class="w-80 flex flex-col">
172
- <div class="flex items-center gap-2 mb-3 px-1">
173
- <span class="w-3 h-3 rounded-full bg-gray-500"></span>
174
- <h3 class="font-medium text-gray-400">Pending</h3>
175
- <span id="pending-count" class="text-xs text-gray-600 bg-gray-800 px-2 py-0.5 rounded-full">0</span>
176
- </div>
177
- <div id="pending-tasks" class="flex-1 space-y-3 kanban-column overflow-y-auto pr-1">
178
- </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>
179
1082
  </div>
1083
+ <div id="pending-tasks" class="column-tasks"></div>
1084
+ </div>
180
1085
 
181
- <!-- In Progress column -->
182
- <div class="w-80 flex flex-col">
183
- <div class="flex items-center gap-2 mb-3 px-1">
184
- <span class="w-3 h-3 rounded-full bg-claude-orange status-pulse"></span>
185
- <h3 class="font-medium text-claude-orange">In Progress</h3>
186
- <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>
187
- </div>
188
- <div id="in-progress-tasks" class="flex-1 space-y-3 kanban-column overflow-y-auto pr-1">
189
- </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>
190
1091
  </div>
1092
+ <div id="in-progress-tasks" class="column-tasks"></div>
1093
+ </div>
191
1094
 
192
- <!-- Completed column -->
193
- <div class="w-80 flex flex-col">
194
- <div class="flex items-center gap-2 mb-3 px-1">
195
- <span class="w-3 h-3 rounded-full bg-green-500"></span>
196
- <h3 class="font-medium text-green-400">Completed</h3>
197
- <span id="completed-count" class="text-xs text-green-400/70 bg-green-500/20 px-2 py-0.5 rounded-full">0</span>
198
- </div>
199
- <div id="completed-tasks" class="flex-1 space-y-3 kanban-column overflow-y-auto pr-1">
200
- </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>
201
1100
  </div>
1101
+ <div id="completed-tasks" class="column-tasks"></div>
202
1102
  </div>
203
1103
  </div>
204
1104
  </div>
205
1105
  </main>
206
1106
 
207
- <!-- Task detail panel -->
208
- <aside id="detail-panel" class="w-96 bg-gray-900 border-l border-gray-800 hidden overflow-y-auto flex-shrink-0">
209
- <header class="p-4 border-b border-gray-800 flex items-center justify-between sticky top-0 bg-gray-900 z-10">
210
- <h3 class="font-semibold">Task Details</h3>
211
- <button id="close-detail" class="text-gray-500 hover:text-gray-300">
212
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
213
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
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"/>
214
1114
  </svg>
215
1115
  </button>
216
1116
  </header>
217
- <div id="detail-content" class="p-4"></div>
1117
+ <div id="detail-content" class="detail-content"></div>
218
1118
  </aside>
219
1119
  </div>
220
1120
 
@@ -223,9 +1123,10 @@
223
1123
  let sessions = [];
224
1124
  let currentSessionId = null;
225
1125
  let currentTasks = [];
226
- let viewMode = 'session'; // 'session' or 'all'
1126
+ let viewMode = 'session';
1127
+ let hideInactive = localStorage.getItem('hideInactive') === 'true';
227
1128
 
228
- // DOM elements
1129
+ // DOM
229
1130
  const sessionsList = document.getElementById('sessions-list');
230
1131
  const noSession = document.getElementById('no-session');
231
1132
  const sessionView = document.getElementById('session-view');
@@ -243,18 +1144,52 @@
243
1144
  const detailContent = document.getElementById('detail-content');
244
1145
  const connectionStatus = document.getElementById('connection-status');
245
1146
 
246
- // Fetch sessions
247
1147
  async function fetchSessions() {
248
1148
  try {
249
1149
  const res = await fetch('/api/sessions');
250
1150
  sessions = await res.json();
251
1151
  renderSessions();
1152
+ fetchLiveUpdates();
252
1153
  } catch (error) {
253
1154
  console.error('Failed to fetch sessions:', error);
254
1155
  }
255
1156
  }
256
1157
 
257
- // Fetch tasks for a session
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
+
258
1193
  async function fetchTasks(sessionId) {
259
1194
  try {
260
1195
  viewMode = 'session';
@@ -267,7 +1202,6 @@
267
1202
  }
268
1203
  }
269
1204
 
270
- // Show all tasks across all sessions
271
1205
  async function showAllTasks() {
272
1206
  try {
273
1207
  viewMode = 'all';
@@ -281,10 +1215,9 @@
281
1215
  }
282
1216
  }
283
1217
 
284
- // Render all tasks view
285
1218
  function renderAllTasks() {
286
- noSession.classList.add('hidden');
287
- sessionView.classList.remove('hidden');
1219
+ noSession.style.display = 'none';
1220
+ sessionView.classList.add('visible');
288
1221
 
289
1222
  const totalTasks = currentTasks.length;
290
1223
  const completed = currentTasks.filter(t => t.status === 'completed').length;
@@ -292,34 +1225,39 @@
292
1225
 
293
1226
  sessionTitle.textContent = 'All Tasks';
294
1227
  sessionMeta.textContent = `${totalTasks} tasks across ${sessions.length} sessions`;
295
-
296
1228
  progressPercent.textContent = `${percent}%`;
297
1229
  progressBar.style.width = `${percent}%`;
298
1230
 
299
1231
  renderKanban();
300
1232
  }
301
1233
 
302
- // Render sessions sidebar
303
1234
  function renderSessions() {
304
- // Update all tasks button state
305
1235
  const allTasksBtn = document.getElementById('all-tasks-btn');
306
- if (allTasksBtn) {
307
- allTasksBtn.className = `w-full text-left p-3 rounded-lg transition-colors flex items-center gap-2 ${
308
- viewMode === 'all' ? 'bg-gray-800' : 'hover:bg-gray-800/50'
309
- }`;
1236
+ allTasksBtn.classList.toggle('active', viewMode === 'all');
1237
+
1238
+ const filteredSessions = hideInactive
1239
+ ? sessions.filter(s => s.pending > 0 || s.inProgress > 0)
1240
+ : sessions;
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);
310
1248
  }
311
1249
 
312
- if (sessions.length === 0) {
313
- sessionsList.innerHTML = `
314
- <div class="text-gray-500 text-sm p-4 text-center">
315
- <p>No sessions found</p>
316
- <p class="mt-2 text-xs">Tasks will appear here when you use Claude Code</p>
1250
+ if (filteredSessions.length === 0) {
1251
+ sessionsContainer.innerHTML = `
1252
+ <div style="padding: 24px 12px; text-align: center; color: var(--text-muted); font-size: 12px;">
1253
+ <p>${hideInactive ? 'No active sessions' : 'No sessions found'}</p>
1254
+ <p style="margin-top: 8px; font-size: 11px;">${hideInactive ? 'Uncheck filter to see all' : 'Tasks appear when you use Claude Code'}</p>
317
1255
  </div>
318
1256
  `;
319
1257
  return;
320
1258
  }
321
1259
 
322
- sessionsList.innerHTML = sessions.map(session => {
1260
+ sessionsContainer.innerHTML = filteredSessions.map(session => {
323
1261
  const total = session.taskCount;
324
1262
  const percent = total > 0 ? Math.round((session.completed / total) * 100) : 0;
325
1263
  const isActive = session.id === currentSessionId && viewMode === 'session';
@@ -328,31 +1266,25 @@
328
1266
  const projectName = session.project ? session.project.split('/').pop() : null;
329
1267
 
330
1268
  return `
331
- <button
332
- onclick="fetchTasks('${session.id}')"
333
- class="w-full text-left p-3 rounded-lg transition-colors ${isActive ? 'bg-gray-800' : 'hover:bg-gray-800/50'}"
334
- >
335
- <div class="flex items-center justify-between gap-2">
336
- <span class="text-sm text-gray-200 truncate flex-1 ${session.name ? '' : 'font-mono text-xs text-gray-400'}">${escapeHtml(displayName)}</span>
337
- ${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>' : ''}
338
1273
  </div>
339
- ${projectName ? `<p class="text-xs text-gray-500 mt-1 truncate">${escapeHtml(projectName)}</p>` : ''}
340
- <div class="flex items-center gap-2 mt-2">
341
- <div class="flex-1 h-1.5 bg-gray-700 rounded-full overflow-hidden">
342
- <div class="h-full bg-claude-orange transition-all" style="width: ${percent}%"></div>
343
- </div>
344
- <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>
345
1278
  </div>
346
- <p class="text-xs text-gray-600 mt-1">Tasks updated ${formatDate(session.modifiedAt)}</p>
1279
+ <div class="session-time">${formatDate(session.modifiedAt)}</div>
347
1280
  </button>
348
1281
  `;
349
1282
  }).join('');
350
1283
  }
351
1284
 
352
- // Render current session
353
1285
  function renderSession() {
354
- noSession.classList.add('hidden');
355
- sessionView.classList.remove('hidden');
1286
+ noSession.style.display = 'none';
1287
+ sessionView.classList.add('visible');
356
1288
 
357
1289
  const session = sessions.find(s => s.id === currentSessionId);
358
1290
  if (!session) return;
@@ -360,7 +1292,7 @@
360
1292
  const displayName = session.name || currentSessionId;
361
1293
  sessionTitle.textContent = displayName;
362
1294
  const projectName = session.project ? session.project.split('/').pop() : null;
363
- sessionMeta.textContent = `${currentTasks.length} tasks${projectName ? ' · ' + projectName : ''} · Tasks updated ${formatDate(session.modifiedAt)}`;
1295
+ sessionMeta.textContent = `${currentTasks.length} tasks${projectName ? ' · ' + projectName : ''} · ${formatDate(session.modifiedAt)}`;
364
1296
 
365
1297
  const completed = currentTasks.filter(t => t.status === 'completed').length;
366
1298
  const percent = currentTasks.length > 0 ? Math.round((completed / currentTasks.length) * 100) : 0;
@@ -372,41 +1304,27 @@
372
1304
  renderSessions();
373
1305
  }
374
1306
 
375
- // Render task card
376
1307
  function renderTaskCard(task) {
377
1308
  const isBlocked = task.blockedBy && task.blockedBy.length > 0;
378
- const statusStyles = {
379
- pending: 'border-gray-700 bg-gray-800/50',
380
- in_progress: 'border-claude-orange/30 bg-claude-orange/10',
381
- completed: 'border-green-500/30 bg-green-500/10'
382
- };
383
1309
  const taskId = viewMode === 'all' ? `${task.sessionId?.slice(0,4)}-${task.id}` : task.id;
384
1310
  const sessionLabel = viewMode === 'all' && task.sessionName ? task.sessionName : null;
1311
+ const statusClass = task.status.replace('_', '-');
385
1312
 
386
1313
  return `
387
- <div
388
- onclick="showTaskDetail('${task.id}', '${task.sessionId || ''}')"
389
- class="p-3 rounded-lg border ${statusStyles[task.status] || statusStyles.pending} cursor-pointer hover:brightness-110 transition-all ${isBlocked ? 'opacity-60' : ''}"
390
- >
391
- <div class="flex items-center gap-2 mb-2">
392
- <span class="text-xs font-mono text-gray-500">#${taskId}</span>
393
- ${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>' : ''}
394
1318
  </div>
395
- <h4 class="text-sm font-medium ${task.status === 'completed' ? 'line-through text-gray-500' : 'text-gray-200'}">${escapeHtml(task.subject)}</h4>
396
- ${sessionLabel ? `<p class="text-xs text-blue-400 mt-1">${escapeHtml(sessionLabel)}</p>` : ''}
397
- ${task.status === 'in_progress' && task.activeForm ? `
398
- <p class="text-xs text-claude-orange mt-2 flex items-center gap-1">
399
- <span class="status-pulse">●</span>
400
- ${escapeHtml(task.activeForm)}
401
- </p>
402
- ` : ''}
403
- ${isBlocked ? `<p class="text-xs text-gray-500 mt-2">Waiting on: ${task.blockedBy.map(id => '#' + id).join(', ')}</p>` : ''}
404
- ${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>` : ''}
405
1324
  </div>
406
1325
  `;
407
1326
  }
408
1327
 
409
- // Render kanban board
410
1328
  function renderKanban() {
411
1329
  const pending = currentTasks.filter(t => t.status === 'pending');
412
1330
  const inProgress = currentTasks.filter(t => t.status === 'in_progress');
@@ -418,112 +1336,160 @@
418
1336
 
419
1337
  pendingTasks.innerHTML = pending.length > 0
420
1338
  ? pending.map(renderTaskCard).join('')
421
- : '<p class="text-gray-600 text-sm text-center py-8">No pending tasks</p>';
1339
+ : '<div class="column-empty">No pending tasks</div>';
422
1340
 
423
1341
  inProgressTasks.innerHTML = inProgress.length > 0
424
1342
  ? inProgress.map(renderTaskCard).join('')
425
- : '<p class="text-gray-600 text-sm text-center py-8">No tasks in progress</p>';
1343
+ : '<div class="column-empty">No active tasks</div>';
426
1344
 
427
1345
  completedTasks.innerHTML = completed.length > 0
428
1346
  ? completed.map(renderTaskCard).join('')
429
- : '<p class="text-gray-600 text-sm text-center py-8">No completed tasks</p>';
1347
+ : '<div class="column-empty">No completed tasks</div>';
430
1348
  }
431
1349
 
432
- // Show task detail
433
1350
  function showTaskDetail(taskId, sessionId = null) {
434
- const task = currentTasks.find(t =>
435
- t.id === taskId && (!sessionId || t.sessionId === sessionId)
436
- );
1351
+ const task = currentTasks.find(t => t.id === taskId && (!sessionId || t.sessionId === sessionId));
437
1352
  if (!task) return;
438
1353
 
439
- detailPanel.classList.remove('hidden');
1354
+ detailPanel.classList.add('visible');
440
1355
 
441
1356
  const statusLabels = {
442
- completed: '<span class="inline-flex items-center gap-1 text-green-400"><span class="w-2 h-2 rounded-full bg-green-500"></span>Completed</span>',
443
- in_progress: '<span class="inline-flex items-center gap-1 text-claude-orange"><span class="w-2 h-2 rounded-full bg-claude-orange status-pulse"></span>In Progress</span>',
444
- pending: '<span class="inline-flex items-center gap-1 text-gray-400"><span class="w-2 h-2 rounded-full bg-gray-500"></span>Pending</span>'
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>'
445
1360
  };
446
1361
 
447
1362
  detailContent.innerHTML = `
448
- <div class="space-y-4">
449
- <div>
450
- <span class="text-xs text-gray-500">Task #${task.id}</span>
451
- <h3 class="text-lg font-semibold mt-1">${escapeHtml(task.subject)}</h3>
452
- </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>
453
1367
 
454
- <div class="text-sm">
455
- ${statusLabels[task.status] || statusLabels.pending}
456
- </div>
1368
+ <div class="detail-section">
1369
+ ${statusLabels[task.status] || statusLabels.pending}
1370
+ </div>
457
1371
 
458
- ${task.activeForm && task.status === 'in_progress' ? `
459
- <div class="text-sm bg-claude-orange/10 border border-claude-orange/20 rounded-lg p-3">
460
- <span class="text-gray-400">Currently:</span>
461
- <span class="text-claude-orange ml-1">${escapeHtml(task.activeForm)}</span>
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)}
462
1376
  </div>
463
- ` : ''}
1377
+ </div>
1378
+ ` : ''}
464
1379
 
465
- ${task.blockedBy && task.blockedBy.length > 0 ? `
466
- <div class="text-sm bg-yellow-500/10 border border-yellow-500/20 rounded-lg p-3">
467
- <span class="text-gray-400">Blocked by:</span>
468
- <span class="text-yellow-400 ml-1">${task.blockedBy.map(id => '#' + id).join(', ')}</span>
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(', ')}
469
1384
  </div>
470
- ` : ''}
1385
+ </div>
1386
+ ` : ''}
471
1387
 
472
- ${task.blocks && task.blocks.length > 0 ? `
473
- <div class="text-sm bg-blue-500/10 border border-blue-500/20 rounded-lg p-3">
474
- <span class="text-gray-400">Blocks:</span>
475
- <span class="text-blue-400 ml-1">${task.blocks.map(id => '#' + id).join(', ')}</span>
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(', ')}
476
1392
  </div>
477
- ` : ''}
1393
+ </div>
1394
+ ` : ''}
478
1395
 
479
- ${task.description ? `
480
- <div class="border-t border-gray-800 pt-4 mt-4">
481
- <h4 class="text-sm font-medium text-gray-400 mb-3">Description</h4>
482
- <div class="prose prose-invert prose-sm max-w-none text-gray-300">
483
- ${marked.parse(task.description)}
484
- </div>
485
- </div>
486
- ` : ''}
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>
487
1409
  </div>
488
1410
  `;
489
1411
  }
490
1412
 
491
- // Close detail panel
492
- document.getElementById('close-detail').onclick = () => {
493
- detailPanel.classList.add('hidden');
494
- };
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;
1418
+
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
+ }
1441
+
1442
+ function closeDetailPanel() {
1443
+ detailPanel.classList.remove('visible');
1444
+ }
1445
+
1446
+ document.getElementById('close-detail').onclick = closeDetailPanel;
1447
+
1448
+ document.addEventListener('keydown', (e) => {
1449
+ if (e.key === 'Escape' && detailPanel.classList.contains('visible')) {
1450
+ closeDetailPanel();
1451
+ }
1452
+ });
495
1453
 
496
- // Setup SSE for live updates
497
1454
  function setupEventSource() {
498
- const eventSource = new EventSource('/api/events');
1455
+ let retryDelay = 1000;
1456
+ let eventSource;
499
1457
 
500
- eventSource.onopen = () => {
501
- connectionStatus.innerHTML = `
502
- <span class="w-2 h-2 rounded-full bg-green-500"></span>
503
- <span class="text-gray-400">Live</span>
504
- `;
505
- };
1458
+ function connect() {
1459
+ eventSource = new EventSource('/api/events');
506
1460
 
507
- eventSource.onerror = () => {
508
- connectionStatus.innerHTML = `
509
- <span class="w-2 h-2 rounded-full bg-red-500"></span>
510
- <span class="text-gray-400">Disconnected</span>
511
- `;
512
- };
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
+ };
513
1468
 
514
- eventSource.onmessage = (event) => {
515
- const data = JSON.parse(event.data);
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
+ };
516
1478
 
517
- if (data.type === 'update') {
518
- fetchSessions();
519
- if (data.sessionId === currentSessionId) {
520
- fetchTasks(currentSessionId);
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
+ }
521
1486
  }
522
- }
523
- };
1487
+ };
1488
+ }
1489
+
1490
+ connect();
524
1491
  }
525
1492
 
526
- // Helpers
527
1493
  function formatDate(dateStr) {
528
1494
  const date = new Date(dateStr);
529
1495
  const now = new Date();
@@ -541,43 +1507,37 @@
541
1507
  return div.innerHTML;
542
1508
  }
543
1509
 
544
- // Theme toggle
1510
+ function toggleHideInactive() {
1511
+ hideInactive = document.getElementById('hide-inactive').checked;
1512
+ localStorage.setItem('hideInactive', hideInactive);
1513
+ renderSessions();
1514
+ }
1515
+
545
1516
  function toggleTheme() {
546
- const body = document.body;
547
- const isLight = body.classList.contains('light');
548
-
549
- if (isLight) {
550
- body.classList.remove('light');
551
- localStorage.setItem('theme', 'dark');
552
- document.getElementById('theme-icon-dark').classList.remove('hidden');
553
- document.getElementById('theme-icon-light').classList.add('hidden');
554
- } else {
555
- body.classList.add('light');
556
- localStorage.setItem('theme', 'light');
557
- document.getElementById('theme-icon-dark').classList.add('hidden');
558
- document.getElementById('theme-icon-light').classList.remove('hidden');
559
- }
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';
560
1521
  }
561
1522
 
562
- // Load saved theme
563
1523
  function loadTheme() {
564
- const saved = localStorage.getItem('theme');
565
- if (saved === 'light') {
1524
+ if (localStorage.getItem('theme') === 'light') {
566
1525
  document.body.classList.add('light');
567
- document.getElementById('theme-icon-dark').classList.add('hidden');
568
- document.getElementById('theme-icon-light').classList.remove('hidden');
1526
+ document.getElementById('theme-icon-dark').style.display = 'none';
1527
+ document.getElementById('theme-icon-light').style.display = 'block';
569
1528
  }
570
1529
  }
571
1530
 
572
- // Initialize
1531
+ function loadPreferences() {
1532
+ document.getElementById('hide-inactive').checked = hideInactive;
1533
+ }
1534
+
1535
+ // Init
573
1536
  loadTheme();
1537
+ loadPreferences();
574
1538
  fetchSessions();
575
1539
  setupEventSource();
576
-
577
- // Default to All Tasks view
578
- setTimeout(() => {
579
- showAllTasks();
580
- }, 500);
1540
+ setTimeout(showAllTasks, 500);
581
1541
  </script>
582
1542
  </body>
583
1543
  </html>