claude-task-viewer 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/index.html CHANGED
@@ -3,222 +3,1118 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Claude 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>
114
- <label class="flex items-center gap-2 px-3 py-2 mt-1 text-xs text-gray-500 cursor-pointer hover:text-gray-400">
115
- <input type="checkbox" id="hide-inactive" onchange="toggleHideInactive()" class="rounded border-gray-600 bg-gray-800 text-claude-orange focus:ring-claude-orange focus:ring-offset-0">
116
- <span>Show active only</span>
117
- </label>
1016
+ <!-- Sessions -->
1017
+ <div class="sidebar-section flex-1">
1018
+ <div class="section-header">
1019
+ <span>Sessions</span>
1020
+ <label class="filter-toggle">
1021
+ <input type="checkbox" id="hide-inactive" onchange="toggleHideInactive()">
1022
+ <span>Active only</span>
1023
+ </label>
1024
+ </div>
1025
+ <div id="sessions-list" class="sessions-list">
1026
+ <button id="all-tasks-btn" class="nav-item" onclick="showAllTasks()">
1027
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1028
+ <path d="M4 6h16M4 12h16M4 18h16"/>
1029
+ </svg>
1030
+ <span>All Tasks</span>
1031
+ </button>
1032
+ </div>
118
1033
  </div>
119
1034
 
120
- <nav id="sessions-list" class="flex-1 overflow-y-auto p-2">
121
- <p class="text-gray-500 text-sm p-2">Loading sessions...</p>
122
- </nav>
123
-
124
- <footer class="p-3 border-t border-gray-800 text-xs text-gray-600">
125
- <a href="https://github.com/L1AD/claude-task-viewer" target="_blank" class="hover:text-gray-400">GitHub</a>
126
- <span class="mx-2">·</span>
127
- <a href="https://policylayer.com" target="_blank" class="hover:text-gray-400">PolicyLayer</a>
128
- <span class="mx-2">·</span>
129
- <span>v1.2.0</span>
1035
+ <footer class="sidebar-footer">
1036
+ <a href="https://github.com/L1AD/claude-task-viewer" target="_blank">GitHub</a>
1037
+ <span style="margin: 0 6px;">·</span>
1038
+ <a href="https://policylayer.com" target="_blank">PolicyLayer</a>
130
1039
  </footer>
131
1040
  </aside>
132
1041
 
133
- <!-- Main content -->
134
- <main class="flex-1 flex flex-col overflow-hidden">
135
- <div id="no-session" class="flex-1 flex items-center justify-center text-gray-600">
136
- <div class="text-center">
137
- <svg class="w-16 h-16 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
138
- <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"/>
139
- </svg>
140
- <p>Select a session to view tasks</p>
141
- </div>
1042
+ <!-- Main -->
1043
+ <main class="main">
1044
+ <div id="no-session" class="empty-state">
1045
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
1046
+ <path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
1047
+ </svg>
1048
+ <p>Select a session to view tasks</p>
142
1049
  </div>
143
1050
 
144
- <div id="session-view" class="flex-1 flex flex-col overflow-hidden hidden">
145
- <!-- Session header -->
146
- <header class="p-4 border-b border-gray-800 bg-gray-900/50 flex-shrink-0">
147
- <div class="flex items-center justify-between">
148
- <div>
149
- <h2 id="session-title" class="text-sm font-mono text-gray-400">Session</h2>
150
- <p id="session-meta" class="text-xs text-gray-500 mt-0.5"></p>
151
- </div>
152
- <div class="flex items-center gap-4">
153
- <div class="flex items-center gap-2">
154
- <div class="w-32 h-2 bg-gray-800 rounded-full overflow-hidden">
155
- <div id="progress-bar" class="h-full bg-gradient-to-r from-claude-orange to-orange-400 transition-all duration-500" style="width: 0%"></div>
156
- </div>
157
- <span id="progress-percent" class="text-sm font-medium text-claude-orange">0%</span>
1051
+ <div id="session-view" class="session-view">
1052
+ <header class="view-header">
1053
+ <div>
1054
+ <h1 id="session-title" class="view-title">Session</h1>
1055
+ <p id="session-meta" class="view-meta"></p>
1056
+ </div>
1057
+ <div class="view-actions">
1058
+ <div class="view-progress">
1059
+ <div class="progress-bar">
1060
+ <div id="progress-bar" class="progress-fill" style="width: 0%"></div>
158
1061
  </div>
159
- <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">
160
- <svg id="theme-icon-dark" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
161
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
162
- </svg>
163
- <svg id="theme-icon-light" class="w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
164
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
165
- </svg>
166
- </button>
1062
+ <span id="progress-percent" class="progress-text">0%</span>
167
1063
  </div>
1064
+ <button id="theme-toggle" class="icon-btn" onclick="toggleTheme()" title="Toggle theme">
1065
+ <svg id="theme-icon-dark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1066
+ <path d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
1067
+ </svg>
1068
+ <svg id="theme-icon-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:none">
1069
+ <circle cx="12" cy="12" r="5"/>
1070
+ <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
1071
+ </svg>
1072
+ </button>
168
1073
  </div>
169
1074
  </header>
170
1075
 
171
- <!-- Kanban board -->
172
- <div class="flex-1 overflow-x-auto p-4">
173
- <div class="flex gap-8 h-full min-w-max">
174
- <!-- Pending column -->
175
- <div class="w-80 flex flex-col">
176
- <div class="flex items-center gap-2 mb-3 px-1">
177
- <span class="w-3 h-3 rounded-full bg-gray-500"></span>
178
- <h3 class="font-medium text-gray-400">Pending</h3>
179
- <span id="pending-count" class="text-xs text-gray-600 bg-gray-800 px-2 py-0.5 rounded-full">0</span>
180
- </div>
181
- <div id="pending-tasks" class="flex-1 space-y-3 kanban-column overflow-y-auto pr-1">
182
- </div>
1076
+ <div class="kanban">
1077
+ <div class="kanban-column">
1078
+ <div class="column-header">
1079
+ <span class="column-dot pending"></span>
1080
+ <span class="column-title pending">Pending</span>
1081
+ <span id="pending-count" class="column-count pending">0</span>
183
1082
  </div>
1083
+ <div id="pending-tasks" class="column-tasks"></div>
1084
+ </div>
184
1085
 
185
- <!-- In Progress column -->
186
- <div class="w-80 flex flex-col">
187
- <div class="flex items-center gap-2 mb-3 px-1">
188
- <span class="w-3 h-3 rounded-full bg-claude-orange status-pulse"></span>
189
- <h3 class="font-medium text-claude-orange">In Progress</h3>
190
- <span id="in-progress-count" class="text-xs text-claude-orange/70 bg-claude-orange/20 px-2 py-0.5 rounded-full">0</span>
191
- </div>
192
- <div id="in-progress-tasks" class="flex-1 space-y-3 kanban-column overflow-y-auto pr-1">
193
- </div>
1086
+ <div class="kanban-column">
1087
+ <div class="column-header">
1088
+ <span class="column-dot in-progress"></span>
1089
+ <span class="column-title in-progress">In Progress</span>
1090
+ <span id="in-progress-count" class="column-count in-progress">0</span>
194
1091
  </div>
1092
+ <div id="in-progress-tasks" class="column-tasks"></div>
1093
+ </div>
195
1094
 
196
- <!-- Completed column -->
197
- <div class="w-80 flex flex-col">
198
- <div class="flex items-center gap-2 mb-3 px-1">
199
- <span class="w-3 h-3 rounded-full bg-green-500"></span>
200
- <h3 class="font-medium text-green-400">Completed</h3>
201
- <span id="completed-count" class="text-xs text-green-400/70 bg-green-500/20 px-2 py-0.5 rounded-full">0</span>
202
- </div>
203
- <div id="completed-tasks" class="flex-1 space-y-3 kanban-column overflow-y-auto pr-1">
204
- </div>
1095
+ <div class="kanban-column">
1096
+ <div class="column-header">
1097
+ <span class="column-dot completed"></span>
1098
+ <span class="column-title completed">Completed</span>
1099
+ <span id="completed-count" class="column-count completed">0</span>
205
1100
  </div>
1101
+ <div id="completed-tasks" class="column-tasks"></div>
206
1102
  </div>
207
1103
  </div>
208
1104
  </div>
209
1105
  </main>
210
1106
 
211
- <!-- Task detail panel -->
212
- <aside id="detail-panel" class="w-96 bg-gray-900 border-l border-gray-800 hidden overflow-y-auto flex-shrink-0">
213
- <header class="p-4 border-b border-gray-800 flex items-center justify-between sticky top-0 bg-gray-900 z-10">
214
- <h3 class="font-semibold">Task Details</h3>
215
- <button id="close-detail" class="text-gray-500 hover:text-gray-300">
216
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
217
- <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"/>
218
1114
  </svg>
219
1115
  </button>
220
1116
  </header>
221
- <div id="detail-content" class="p-4"></div>
1117
+ <div id="detail-content" class="detail-content"></div>
222
1118
  </aside>
223
1119
  </div>
224
1120
 
@@ -227,10 +1123,10 @@
227
1123
  let sessions = [];
228
1124
  let currentSessionId = null;
229
1125
  let currentTasks = [];
230
- let viewMode = 'session'; // 'session' or 'all'
1126
+ let viewMode = 'session';
231
1127
  let hideInactive = localStorage.getItem('hideInactive') === 'true';
232
1128
 
233
- // DOM elements
1129
+ // DOM
234
1130
  const sessionsList = document.getElementById('sessions-list');
235
1131
  const noSession = document.getElementById('no-session');
236
1132
  const sessionView = document.getElementById('session-view');
@@ -248,18 +1144,52 @@
248
1144
  const detailContent = document.getElementById('detail-content');
249
1145
  const connectionStatus = document.getElementById('connection-status');
250
1146
 
251
- // Fetch sessions
252
1147
  async function fetchSessions() {
253
1148
  try {
254
1149
  const res = await fetch('/api/sessions');
255
1150
  sessions = await res.json();
256
1151
  renderSessions();
1152
+ fetchLiveUpdates();
257
1153
  } catch (error) {
258
1154
  console.error('Failed to fetch sessions:', error);
259
1155
  }
260
1156
  }
261
1157
 
262
- // 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
+
263
1193
  async function fetchTasks(sessionId) {
264
1194
  try {
265
1195
  viewMode = 'session';
@@ -272,7 +1202,6 @@
272
1202
  }
273
1203
  }
274
1204
 
275
- // Show all tasks across all sessions
276
1205
  async function showAllTasks() {
277
1206
  try {
278
1207
  viewMode = 'all';
@@ -286,10 +1215,9 @@
286
1215
  }
287
1216
  }
288
1217
 
289
- // Render all tasks view
290
1218
  function renderAllTasks() {
291
- noSession.classList.add('hidden');
292
- sessionView.classList.remove('hidden');
1219
+ noSession.style.display = 'none';
1220
+ sessionView.classList.add('visible');
293
1221
 
294
1222
  const totalTasks = currentTasks.length;
295
1223
  const completed = currentTasks.filter(t => t.status === 'completed').length;
@@ -297,39 +1225,39 @@
297
1225
 
298
1226
  sessionTitle.textContent = 'All Tasks';
299
1227
  sessionMeta.textContent = `${totalTasks} tasks across ${sessions.length} sessions`;
300
-
301
1228
  progressPercent.textContent = `${percent}%`;
302
1229
  progressBar.style.width = `${percent}%`;
303
1230
 
304
1231
  renderKanban();
305
1232
  }
306
1233
 
307
- // Render sessions sidebar
308
1234
  function renderSessions() {
309
- // Update all tasks button state
310
1235
  const allTasksBtn = document.getElementById('all-tasks-btn');
311
- 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
- }
1236
+ allTasksBtn.classList.toggle('active', viewMode === 'all');
316
1237
 
317
- // Filter sessions based on hideInactive
318
1238
  const filteredSessions = hideInactive
319
1239
  ? sessions.filter(s => s.pending > 0 || s.inProgress > 0)
320
1240
  : sessions;
321
1241
 
1242
+ // Get container for session items (after All Tasks button)
1243
+ let sessionsContainer = document.getElementById('sessions-items');
1244
+ if (!sessionsContainer) {
1245
+ sessionsContainer = document.createElement('div');
1246
+ sessionsContainer.id = 'sessions-items';
1247
+ sessionsList.appendChild(sessionsContainer);
1248
+ }
1249
+
322
1250
  if (filteredSessions.length === 0) {
323
- sessionsList.innerHTML = `
324
- <div class="text-gray-500 text-sm p-4 text-center">
1251
+ sessionsContainer.innerHTML = `
1252
+ <div style="padding: 24px 12px; text-align: center; color: var(--text-muted); font-size: 12px;">
325
1253
  <p>${hideInactive ? 'No active sessions' : 'No sessions found'}</p>
326
- <p class="mt-2 text-xs">${hideInactive ? 'Uncheck "Show active only" to see all' : 'Tasks will appear here when you use Claude Code'}</p>
1254
+ <p style="margin-top: 8px; font-size: 11px;">${hideInactive ? 'Uncheck filter to see all' : 'Tasks appear when you use Claude Code'}</p>
327
1255
  </div>
328
1256
  `;
329
1257
  return;
330
1258
  }
331
1259
 
332
- sessionsList.innerHTML = filteredSessions.map(session => {
1260
+ sessionsContainer.innerHTML = filteredSessions.map(session => {
333
1261
  const total = session.taskCount;
334
1262
  const percent = total > 0 ? Math.round((session.completed / total) * 100) : 0;
335
1263
  const isActive = session.id === currentSessionId && viewMode === 'session';
@@ -338,31 +1266,25 @@
338
1266
  const projectName = session.project ? session.project.split('/').pop() : null;
339
1267
 
340
1268
  return `
341
- <button
342
- onclick="fetchTasks('${session.id}')"
343
- class="w-full text-left p-3 rounded-lg transition-colors ${isActive ? 'bg-gray-800' : 'hover:bg-gray-800/50'}"
344
- >
345
- <div class="flex items-center justify-between gap-2">
346
- <span class="text-sm text-gray-200 truncate flex-1 ${session.name ? '' : 'font-mono text-xs text-gray-400'}">${escapeHtml(displayName)}</span>
347
- ${hasInProgress ? '<span class="w-2 h-2 rounded-full bg-claude-orange status-pulse flex-shrink-0"></span>' : ''}
1269
+ <button onclick="fetchTasks('${session.id}')" class="session-item ${isActive ? 'active' : ''}">
1270
+ <div class="session-name">
1271
+ <span>${escapeHtml(displayName)}</span>
1272
+ ${hasInProgress ? '<span class="pulse"></span>' : ''}
348
1273
  </div>
349
- ${projectName ? `<p class="text-xs text-gray-500 mt-1 truncate">${escapeHtml(projectName)}</p>` : ''}
350
- <div class="flex items-center gap-2 mt-2">
351
- <div class="flex-1 h-1.5 bg-gray-700 rounded-full overflow-hidden">
352
- <div class="h-full bg-claude-orange transition-all" style="width: ${percent}%"></div>
353
- </div>
354
- <span class="text-xs text-gray-500">${session.completed}/${total}</span>
1274
+ ${projectName ? `<div class="session-project">${escapeHtml(projectName)}</div>` : ''}
1275
+ <div class="session-progress">
1276
+ <div class="progress-bar"><div class="progress-fill" style="width: ${percent}%"></div></div>
1277
+ <span class="progress-text">${session.completed}/${total}</span>
355
1278
  </div>
356
- <p class="text-xs text-gray-600 mt-1">Tasks updated ${formatDate(session.modifiedAt)}</p>
1279
+ <div class="session-time">${formatDate(session.modifiedAt)}</div>
357
1280
  </button>
358
1281
  `;
359
1282
  }).join('');
360
1283
  }
361
1284
 
362
- // Render current session
363
1285
  function renderSession() {
364
- noSession.classList.add('hidden');
365
- sessionView.classList.remove('hidden');
1286
+ noSession.style.display = 'none';
1287
+ sessionView.classList.add('visible');
366
1288
 
367
1289
  const session = sessions.find(s => s.id === currentSessionId);
368
1290
  if (!session) return;
@@ -370,7 +1292,7 @@
370
1292
  const displayName = session.name || currentSessionId;
371
1293
  sessionTitle.textContent = displayName;
372
1294
  const projectName = session.project ? session.project.split('/').pop() : null;
373
- sessionMeta.textContent = `${currentTasks.length} tasks${projectName ? ' · ' + projectName : ''} · Tasks updated ${formatDate(session.modifiedAt)}`;
1295
+ sessionMeta.textContent = `${currentTasks.length} tasks${projectName ? ' · ' + projectName : ''} · ${formatDate(session.modifiedAt)}`;
374
1296
 
375
1297
  const completed = currentTasks.filter(t => t.status === 'completed').length;
376
1298
  const percent = currentTasks.length > 0 ? Math.round((completed / currentTasks.length) * 100) : 0;
@@ -382,41 +1304,27 @@
382
1304
  renderSessions();
383
1305
  }
384
1306
 
385
- // Render task card
386
1307
  function renderTaskCard(task) {
387
1308
  const isBlocked = task.blockedBy && task.blockedBy.length > 0;
388
- const statusStyles = {
389
- pending: 'border-gray-700 bg-gray-800/50',
390
- in_progress: 'border-claude-orange/30 bg-claude-orange/10',
391
- completed: 'border-green-500/30 bg-green-500/10'
392
- };
393
1309
  const taskId = viewMode === 'all' ? `${task.sessionId?.slice(0,4)}-${task.id}` : task.id;
394
1310
  const sessionLabel = viewMode === 'all' && task.sessionName ? task.sessionName : null;
1311
+ const statusClass = task.status.replace('_', '-');
395
1312
 
396
1313
  return `
397
- <div
398
- onclick="showTaskDetail('${task.id}', '${task.sessionId || ''}')"
399
- class="p-3 rounded-lg border ${statusStyles[task.status] || statusStyles.pending} cursor-pointer hover:brightness-110 transition-all ${isBlocked ? 'opacity-60' : ''}"
400
- >
401
- <div class="flex items-center gap-2 mb-2">
402
- <span class="text-xs font-mono text-gray-500">#${taskId}</span>
403
- ${isBlocked ? '<span class="text-xs bg-yellow-500/20 text-yellow-400 px-1.5 py-0.5 rounded">blocked</span>' : ''}
1314
+ <div onclick="showTaskDetail('${task.id}', '${task.sessionId || ''}')" class="task-card ${statusClass} ${isBlocked ? 'blocked' : ''}">
1315
+ <div class="task-id">
1316
+ <span>#${taskId}</span>
1317
+ ${isBlocked ? '<span class="task-badge blocked">Blocked</span>' : ''}
404
1318
  </div>
405
- <h4 class="text-sm font-medium ${task.status === 'completed' ? 'line-through text-gray-500' : 'text-gray-200'}">${escapeHtml(task.subject)}</h4>
406
- ${sessionLabel ? `<p class="text-xs text-blue-400 mt-1">${escapeHtml(sessionLabel)}</p>` : ''}
407
- ${task.status === 'in_progress' && task.activeForm ? `
408
- <p class="text-xs text-claude-orange mt-2 flex items-center gap-1">
409
- <span class="status-pulse">●</span>
410
- ${escapeHtml(task.activeForm)}
411
- </p>
412
- ` : ''}
413
- ${isBlocked ? `<p class="text-xs text-gray-500 mt-2">Waiting on: ${task.blockedBy.map(id => '#' + id).join(', ')}</p>` : ''}
414
- ${task.description ? `<p class="text-xs text-gray-500 mt-2 line-clamp-2">${escapeHtml(task.description.split('\n')[0])}</p>` : ''}
1319
+ <div class="task-title">${escapeHtml(task.subject)}</div>
1320
+ ${sessionLabel ? `<div class="task-session">${escapeHtml(sessionLabel)}</div>` : ''}
1321
+ ${task.status === 'in_progress' && task.activeForm ? `<div class="task-active">${escapeHtml(task.activeForm)}</div>` : ''}
1322
+ ${isBlocked ? `<div class="task-blocked">Waiting on ${task.blockedBy.map(id => '#' + id).join(', ')}</div>` : ''}
1323
+ ${task.description ? `<div class="task-desc">${escapeHtml(task.description.split('\n')[0])}</div>` : ''}
415
1324
  </div>
416
1325
  `;
417
1326
  }
418
1327
 
419
- // Render kanban board
420
1328
  function renderKanban() {
421
1329
  const pending = currentTasks.filter(t => t.status === 'pending');
422
1330
  const inProgress = currentTasks.filter(t => t.status === 'in_progress');
@@ -428,112 +1336,160 @@
428
1336
 
429
1337
  pendingTasks.innerHTML = pending.length > 0
430
1338
  ? pending.map(renderTaskCard).join('')
431
- : '<p class="text-gray-600 text-sm text-center py-8">No pending tasks</p>';
1339
+ : '<div class="column-empty">No pending tasks</div>';
432
1340
 
433
1341
  inProgressTasks.innerHTML = inProgress.length > 0
434
1342
  ? inProgress.map(renderTaskCard).join('')
435
- : '<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>';
436
1344
 
437
1345
  completedTasks.innerHTML = completed.length > 0
438
1346
  ? completed.map(renderTaskCard).join('')
439
- : '<p class="text-gray-600 text-sm text-center py-8">No completed tasks</p>';
1347
+ : '<div class="column-empty">No completed tasks</div>';
440
1348
  }
441
1349
 
442
- // Show task detail
443
1350
  function showTaskDetail(taskId, sessionId = null) {
444
- const task = currentTasks.find(t =>
445
- t.id === taskId && (!sessionId || t.sessionId === sessionId)
446
- );
1351
+ const task = currentTasks.find(t => t.id === taskId && (!sessionId || t.sessionId === sessionId));
447
1352
  if (!task) return;
448
1353
 
449
- detailPanel.classList.remove('hidden');
1354
+ detailPanel.classList.add('visible');
450
1355
 
451
1356
  const statusLabels = {
452
- 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>',
453
- 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>',
454
- 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>'
455
1360
  };
456
1361
 
457
1362
  detailContent.innerHTML = `
458
- <div class="space-y-4">
459
- <div>
460
- <span class="text-xs text-gray-500">Task #${task.id}</span>
461
- <h3 class="text-lg font-semibold mt-1">${escapeHtml(task.subject)}</h3>
462
- </div>
1363
+ <div class="detail-section">
1364
+ <div class="detail-label">Task #${task.id}</div>
1365
+ <h2 class="detail-title">${escapeHtml(task.subject)}</h2>
1366
+ </div>
463
1367
 
464
- <div class="text-sm">
465
- ${statusLabels[task.status] || statusLabels.pending}
466
- </div>
1368
+ <div class="detail-section">
1369
+ ${statusLabels[task.status] || statusLabels.pending}
1370
+ </div>
467
1371
 
468
- ${task.activeForm && task.status === 'in_progress' ? `
469
- <div class="text-sm bg-claude-orange/10 border border-claude-orange/20 rounded-lg p-3">
470
- <span class="text-gray-400">Currently:</span>
471
- <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)}
472
1376
  </div>
473
- ` : ''}
1377
+ </div>
1378
+ ` : ''}
474
1379
 
475
- ${task.blockedBy && task.blockedBy.length > 0 ? `
476
- <div class="text-sm bg-yellow-500/10 border border-yellow-500/20 rounded-lg p-3">
477
- <span class="text-gray-400">Blocked by:</span>
478
- <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(', ')}
479
1384
  </div>
480
- ` : ''}
1385
+ </div>
1386
+ ` : ''}
481
1387
 
482
- ${task.blocks && task.blocks.length > 0 ? `
483
- <div class="text-sm bg-blue-500/10 border border-blue-500/20 rounded-lg p-3">
484
- <span class="text-gray-400">Blocks:</span>
485
- <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(', ')}
486
1392
  </div>
487
- ` : ''}
1393
+ </div>
1394
+ ` : ''}
488
1395
 
489
- ${task.description ? `
490
- <div class="border-t border-gray-800 pt-4 mt-4">
491
- <h4 class="text-sm font-medium text-gray-400 mb-3">Description</h4>
492
- <div class="prose prose-invert prose-sm max-w-none text-gray-300">
493
- ${marked.parse(task.description)}
494
- </div>
495
- </div>
496
- ` : ''}
1396
+ ${task.description ? `
1397
+ <div class="detail-section">
1398
+ <div class="detail-label">Description</div>
1399
+ <div class="detail-desc">${DOMPurify.sanitize(marked.parse(task.description))}</div>
1400
+ </div>
1401
+ ` : ''}
1402
+
1403
+ <div class="detail-section note-section">
1404
+ <div class="detail-label">Add Note</div>
1405
+ <form class="note-form" onsubmit="addNote(event, '${task.id}', '${task.sessionId || currentSessionId}')">
1406
+ <textarea id="note-input" class="note-input" placeholder="Add a note for Claude..." rows="3"></textarea>
1407
+ <button type="submit" class="note-submit">Add Note</button>
1408
+ </form>
497
1409
  </div>
498
1410
  `;
499
1411
  }
500
1412
 
501
- // Close detail panel
502
- document.getElementById('close-detail').onclick = () => {
503
- detailPanel.classList.add('hidden');
504
- };
1413
+ async function addNote(event, taskId, sessionId) {
1414
+ event.preventDefault();
1415
+ const input = document.getElementById('note-input');
1416
+ const note = input.value.trim();
1417
+ if (!note) return;
505
1418
 
506
- // Setup SSE for live updates
507
- function setupEventSource() {
508
- const eventSource = new EventSource('/api/events');
1419
+ try {
1420
+ const res = await fetch(`/api/tasks/${sessionId}/${taskId}/note`, {
1421
+ method: 'POST',
1422
+ headers: { 'Content-Type': 'application/json' },
1423
+ body: JSON.stringify({ note })
1424
+ });
1425
+
1426
+ if (res.ok) {
1427
+ input.value = '';
1428
+ // Refresh to show updated description
1429
+ if (viewMode === 'all') {
1430
+ const tasksRes = await fetch('/api/tasks/all');
1431
+ currentTasks = await tasksRes.json();
1432
+ } else {
1433
+ await fetchTasks(sessionId);
1434
+ }
1435
+ showTaskDetail(taskId, sessionId);
1436
+ }
1437
+ } catch (error) {
1438
+ console.error('Failed to add note:', error);
1439
+ }
1440
+ }
509
1441
 
510
- eventSource.onopen = () => {
511
- connectionStatus.innerHTML = `
512
- <span class="w-2 h-2 rounded-full bg-green-500"></span>
513
- <span class="text-gray-400">Live</span>
514
- `;
515
- };
1442
+ function closeDetailPanel() {
1443
+ detailPanel.classList.remove('visible');
1444
+ }
516
1445
 
517
- eventSource.onerror = () => {
518
- connectionStatus.innerHTML = `
519
- <span class="w-2 h-2 rounded-full bg-red-500"></span>
520
- <span class="text-gray-400">Disconnected</span>
521
- `;
522
- };
1446
+ document.getElementById('close-detail').onclick = closeDetailPanel;
523
1447
 
524
- eventSource.onmessage = (event) => {
525
- const data = JSON.parse(event.data);
1448
+ document.addEventListener('keydown', (e) => {
1449
+ if (e.key === 'Escape' && detailPanel.classList.contains('visible')) {
1450
+ closeDetailPanel();
1451
+ }
1452
+ });
526
1453
 
527
- if (data.type === 'update') {
528
- fetchSessions();
529
- if (data.sessionId === currentSessionId) {
530
- fetchTasks(currentSessionId);
1454
+ function setupEventSource() {
1455
+ let retryDelay = 1000;
1456
+ let eventSource;
1457
+
1458
+ function connect() {
1459
+ eventSource = new EventSource('/api/events');
1460
+
1461
+ eventSource.onopen = () => {
1462
+ retryDelay = 1000; // Reset on successful connection
1463
+ connectionStatus.innerHTML = `
1464
+ <span class="connection-dot live"></span>
1465
+ <span>Connected</span>
1466
+ `;
1467
+ };
1468
+
1469
+ eventSource.onerror = () => {
1470
+ eventSource.close();
1471
+ connectionStatus.innerHTML = `
1472
+ <span class="connection-dot error"></span>
1473
+ <span>Reconnecting...</span>
1474
+ `;
1475
+ setTimeout(connect, retryDelay);
1476
+ retryDelay = Math.min(retryDelay * 2, 30000); // Max 30s
1477
+ };
1478
+
1479
+ eventSource.onmessage = (event) => {
1480
+ const data = JSON.parse(event.data);
1481
+ if (data.type === 'update' || data.type === 'metadata-update') {
1482
+ fetchSessions();
1483
+ if (currentSessionId && data.sessionId === currentSessionId) {
1484
+ fetchTasks(currentSessionId);
1485
+ }
531
1486
  }
532
- }
533
- };
1487
+ };
1488
+ }
1489
+
1490
+ connect();
534
1491
  }
535
1492
 
536
- // Helpers
537
1493
  function formatDate(dateStr) {
538
1494
  const date = new Date(dateStr);
539
1495
  const now = new Date();
@@ -551,56 +1507,37 @@
551
1507
  return div.innerHTML;
552
1508
  }
553
1509
 
554
- // Toggle hide inactive sessions
555
1510
  function toggleHideInactive() {
556
1511
  hideInactive = document.getElementById('hide-inactive').checked;
557
1512
  localStorage.setItem('hideInactive', hideInactive);
558
1513
  renderSessions();
559
1514
  }
560
1515
 
561
- // Theme toggle
562
1516
  function toggleTheme() {
563
- const body = document.body;
564
- const isLight = body.classList.contains('light');
565
-
566
- if (isLight) {
567
- body.classList.remove('light');
568
- localStorage.setItem('theme', 'dark');
569
- document.getElementById('theme-icon-dark').classList.remove('hidden');
570
- document.getElementById('theme-icon-light').classList.add('hidden');
571
- } else {
572
- body.classList.add('light');
573
- localStorage.setItem('theme', 'light');
574
- document.getElementById('theme-icon-dark').classList.add('hidden');
575
- document.getElementById('theme-icon-light').classList.remove('hidden');
576
- }
1517
+ const isLight = document.body.classList.toggle('light');
1518
+ localStorage.setItem('theme', isLight ? 'light' : 'dark');
1519
+ document.getElementById('theme-icon-dark').style.display = isLight ? 'none' : 'block';
1520
+ document.getElementById('theme-icon-light').style.display = isLight ? 'block' : 'none';
577
1521
  }
578
1522
 
579
- // Load saved theme
580
1523
  function loadTheme() {
581
- const saved = localStorage.getItem('theme');
582
- if (saved === 'light') {
1524
+ if (localStorage.getItem('theme') === 'light') {
583
1525
  document.body.classList.add('light');
584
- document.getElementById('theme-icon-dark').classList.add('hidden');
585
- 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';
586
1528
  }
587
1529
  }
588
1530
 
589
- // Load saved preferences
590
1531
  function loadPreferences() {
591
1532
  document.getElementById('hide-inactive').checked = hideInactive;
592
1533
  }
593
1534
 
594
- // Initialize
1535
+ // Init
595
1536
  loadTheme();
596
1537
  loadPreferences();
597
1538
  fetchSessions();
598
1539
  setupEventSource();
599
-
600
- // Default to All Tasks view
601
- setTimeout(() => {
602
- showAllTasks();
603
- }, 500);
1540
+ setTimeout(showAllTasks, 500);
604
1541
  </script>
605
1542
  </body>
606
1543
  </html>