create-claude-kanban 3.0.0 → 3.2.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.
@@ -3,11 +3,11 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Agent Kanban</title>
6
+ <title>APEX Agent Kanban</title>
7
7
  <link href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css" rel="stylesheet">
8
8
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
9
9
  <style>
10
- :root {
10
+ :root, [data-theme="dark"] {
11
11
  --bg: #09090B;
12
12
  --s1: #111113;
13
13
  --s2: #18181B;
@@ -25,7 +25,60 @@
25
25
  --am: #F59E0B;
26
26
  --vl: #8B5CF6;
27
27
  --r: 3px;
28
- }
28
+ --card-bg: #1A1A1E;
29
+ --card-hover: #222226;
30
+ }
31
+ [data-theme="navy"] {
32
+ --bg: #050A12;
33
+ --s1: #0B1220;
34
+ --s2: #101828;
35
+ --s3: #162035;
36
+ --b1: #1E2D45;
37
+ --b2: #2A3F5F;
38
+ --t1: #DDE5F0;
39
+ --t2: #8899B0;
40
+ --t3: #607088;
41
+ --t4: #455570;
42
+ --ac: #1A6FEF;
43
+ --ac2: #1558C0;
44
+ --gn: #22C55E;
45
+ --rd: #EF4444;
46
+ --am: #C8A24A;
47
+ --vl: #8B5CF6;
48
+ --card-bg: #0F1B30;
49
+ --card-hover: #142440;
50
+ }
51
+ [data-theme="light"] {
52
+ --bg: #F4F5F7;
53
+ --s1: #FFFFFF;
54
+ --s2: #F0F1F3;
55
+ --s3: #E8E9EC;
56
+ --b1: #D1D5DB;
57
+ --b2: #B0B7C3;
58
+ --t1: #111827;
59
+ --t2: #4B5563;
60
+ --t3: #6B7280;
61
+ --t4: #9CA3AF;
62
+ --ac: #2563EB;
63
+ --ac2: #1D4ED8;
64
+ --gn: #16A34A;
65
+ --rd: #DC2626;
66
+ --am: #D97706;
67
+ --vl: #7C3AED;
68
+ --card-bg: #FFFFFF;
69
+ --card-hover: #F0F1F3;
70
+ }
71
+ /* theme switcher */
72
+ .theme-switcher { display: flex; gap: 2px; margin-left: 12px; padding-left: 12px; border-left: 1px solid var(--b1); }
73
+ .theme-btn {
74
+ width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--b2);
75
+ cursor: pointer; transition: border-color 0.15s, transform 0.15s; padding: 0;
76
+ }
77
+ .theme-btn:hover { transform: scale(1.15); }
78
+ .theme-btn.active { border-color: var(--ac); box-shadow: 0 0 0 2px var(--ac); }
79
+ .theme-btn[data-t="dark"] { background: #09090B; }
80
+ .theme-btn[data-t="navy"] { background: #0B1220; }
81
+ .theme-btn[data-t="light"] { background: #F4F5F7; }
29
82
  *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
30
83
  body {
31
84
  font-family: 'Pretendard Variable', Pretendard, -apple-system, sans-serif;
@@ -88,16 +141,18 @@
88
141
 
89
142
  /* ── Toolbar ── */
90
143
  .toolbar {
91
- height: 40px;
92
- padding: 0 24px;
144
+ height: auto;
145
+ min-height: 36px;
146
+ padding: 4px 24px;
93
147
  display: flex;
94
148
  align-items: center;
95
- gap: 1px;
149
+ flex-wrap: wrap;
150
+ gap: 2px;
96
151
  border-bottom: 1px solid var(--b1);
97
152
  background: var(--s1);
98
153
  }
99
154
  .toolbar button {
100
- padding: 6px 12px;
155
+ padding: 4px 10px;
101
156
  border: none;
102
157
  background: transparent;
103
158
  cursor: pointer;
@@ -223,6 +278,19 @@
223
278
  align-items: center; justify-content: center; border-radius: var(--r);
224
279
  }
225
280
  .agent-panel .ap-close:hover { color: var(--t1); background: var(--s3); }
281
+ .ap-project-filter {
282
+ display: flex; gap: 0; padding: 0 12px;
283
+ border-bottom: 1px solid var(--b1); flex-shrink: 0;
284
+ overflow-x: auto;
285
+ }
286
+ .ap-project-btn {
287
+ padding: 6px 12px; font-size: 11px; font-weight: 500;
288
+ color: var(--t3); cursor: pointer; white-space: nowrap;
289
+ border: none; border-bottom: 2px solid transparent;
290
+ background: none; font-family: inherit;
291
+ }
292
+ .ap-project-btn:hover { color: var(--t1); }
293
+ .ap-project-btn.active { color: var(--t1); border-bottom-color: var(--ac); }
226
294
  .agent-tabs {
227
295
  display: flex; gap: 0; border-bottom: 1px solid var(--b1);
228
296
  overflow-x: auto; flex-shrink: 0; padding: 0 12px;
@@ -240,6 +308,27 @@
240
308
  font-size: 9px; color: var(--t4); margin-left: 4px;
241
309
  padding: 1px 4px; background: var(--s3); border-radius: 2px;
242
310
  }
311
+ /* Pipeline agent tabs — vertical card layout with role summary */
312
+ .agent-tabs:has(.agent-tab-pipeline) {
313
+ flex-direction: column; overflow-x: hidden; overflow-y: auto;
314
+ gap: 2px; padding: 8px 12px; max-height: 320px;
315
+ }
316
+ .agent-tab-pipeline {
317
+ display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
318
+ white-space: normal; text-align: left; padding: 6px 10px;
319
+ border-bottom: none; border-left: 2px solid transparent;
320
+ border-radius: 4px;
321
+ }
322
+ .agent-tab-pipeline:hover { background: var(--s2); }
323
+ .agent-tab-pipeline.active { border-left-color: var(--ac); background: var(--s2); color: var(--t1); border-bottom: none; }
324
+ .agent-tab-order {
325
+ font-size: 9px; font-weight: 700; color: var(--ac); min-width: 18px;
326
+ }
327
+ .agent-tab-name { font-weight: 600; }
328
+ .agent-tab-role {
329
+ display: block; width: 100%; font-size: 10px; color: var(--t4);
330
+ line-height: 1.3; margin-top: 1px; padding-left: 22px;
331
+ }
243
332
  .agent-prompt-view {
244
333
  flex: 1; overflow-y: auto; padding: 20px;
245
334
  }
@@ -269,9 +358,11 @@
269
358
  .board {
270
359
  display: grid;
271
360
  grid-template-columns: repeat(4, 1fr);
361
+ grid-template-rows: 1fr;
272
362
  gap: 1px;
273
363
  padding: 0;
274
- min-height: calc(100vh - 168px);
364
+ height: calc(100vh - var(--board-top, 168px));
365
+ overflow: hidden;
275
366
  background: var(--b1);
276
367
  }
277
368
  @media(max-width: 900px) { .board { grid-template-columns: repeat(2, 1fr); } }
@@ -279,7 +370,11 @@
279
370
  .column {
280
371
  background: var(--bg);
281
372
  padding: 0;
282
- min-height: 200px;
373
+ min-height: 0;
374
+ height: 100%;
375
+ display: flex;
376
+ flex-direction: column;
377
+ overflow: hidden;
283
378
  transition: background 0.1s;
284
379
  }
285
380
  .column.drag-over { background: var(--s1); }
@@ -288,8 +383,7 @@
288
383
  align-items: center;
289
384
  justify-content: space-between;
290
385
  padding: 12px 16px 10px;
291
- position: sticky;
292
- top: 0;
386
+ flex-shrink: 0;
293
387
  background: var(--bg);
294
388
  z-index: 2;
295
389
  }
@@ -317,21 +411,21 @@
317
411
  padding: 1px 6px;
318
412
  border-radius: var(--r);
319
413
  }
320
- .cards { display: flex; flex-direction: column; gap: 1px; padding: 0 8px 8px; }
414
+ .cards { display: flex; flex-direction: column; gap: 1px; padding: 0 8px 8px; flex: 1; min-height: 0; overflow-y: auto; }
321
415
 
322
416
  /* ── Card ── */
323
417
  .card {
324
- background: var(--s1);
418
+ background: var(--card-bg, var(--s2));
325
419
  border-radius: var(--r);
326
420
  padding: 10px 12px;
327
- border-left: 2px solid var(--b1);
421
+ border-left: 2px solid var(--b2);
328
422
  transition: background 0.1s, border-color 0.1s;
329
423
  cursor: grab;
330
424
  position: relative;
331
425
  }
332
- .card:hover { background: var(--s2); border-left-color: var(--t4); }
426
+ .card:hover { background: var(--card-hover, var(--s3)); border-left-color: var(--t3); }
333
427
  .card.dragging { opacity: 0.3; }
334
- .card.blocked { opacity: 0.4; cursor: default; }
428
+ .card.blocked { opacity: 0.55; cursor: default; }
335
429
  .card.readonly { cursor: default; }
336
430
  .card .top {
337
431
  display: flex;
@@ -342,7 +436,7 @@
342
436
  .card .id {
343
437
  font-size: 10px;
344
438
  font-family: 'JetBrains Mono', monospace;
345
- color: var(--t4);
439
+ color: var(--t3);
346
440
  }
347
441
  .card .badge {
348
442
  font-size: 9px;
@@ -402,13 +496,13 @@
402
496
  .card .deps {
403
497
  font-size: 9px;
404
498
  font-family: 'JetBrains Mono', monospace;
405
- color: var(--t4);
499
+ color: var(--t3);
406
500
  margin-top: 4px;
407
501
  }
408
502
  .card .time {
409
503
  font-size: 9px;
410
504
  font-family: 'JetBrains Mono', monospace;
411
- color: var(--t4);
505
+ color: var(--t3);
412
506
  }
413
507
  .card .session {
414
508
  font-size: 9px;
@@ -431,13 +525,102 @@
431
525
 
432
526
  .card .desc {
433
527
  font-size: 11px;
434
- color: var(--t3);
528
+ color: var(--t2);
435
529
  margin-top: 6px;
436
530
  padding-top: 6px;
437
531
  border-top: 1px solid var(--b1);
438
- line-height: 1.5;
532
+ line-height: 1.6;
439
533
  overflow: hidden;
440
534
  }
535
+ /* ── Card detail sections ── */
536
+ .card-section {
537
+ margin-top: 8px;
538
+ padding: 8px 10px;
539
+ background: var(--s1);
540
+ border: 1px solid var(--b1);
541
+ border-radius: var(--r);
542
+ }
543
+ .card-section-label {
544
+ font-size: 9px;
545
+ font-weight: 700;
546
+ text-transform: uppercase;
547
+ letter-spacing: 0.06em;
548
+ color: var(--t3);
549
+ margin-bottom: 6px;
550
+ display: flex;
551
+ align-items: center;
552
+ gap: 5px;
553
+ }
554
+ .card-section-label .dot {
555
+ width: 5px; height: 5px;
556
+ border-radius: 50%;
557
+ display: inline-block;
558
+ }
559
+ .card-section-body {
560
+ font-size: 11px;
561
+ color: var(--t2);
562
+ line-height: 1.65;
563
+ }
564
+ .card-section-body ul {
565
+ margin: 0; padding-left: 16px;
566
+ list-style: none;
567
+ }
568
+ .card-section-body ul li {
569
+ position: relative;
570
+ padding-left: 2px;
571
+ margin-bottom: 3px;
572
+ }
573
+ .card-section-body ul li::before {
574
+ content: "▸";
575
+ position: absolute;
576
+ left: -14px;
577
+ color: var(--t4);
578
+ font-size: 9px;
579
+ top: 1px;
580
+ }
581
+ .card-section-body .line-item {
582
+ padding: 2px 0;
583
+ border-bottom: 1px solid var(--b1);
584
+ }
585
+ .card-section-body .line-item:last-child { border-bottom: none; }
586
+ .card-dep-chip {
587
+ display: inline-flex;
588
+ align-items: center;
589
+ gap: 4px;
590
+ font-size: 10px;
591
+ font-family: 'JetBrains Mono', monospace;
592
+ padding: 2px 8px;
593
+ border-radius: 2px;
594
+ background: var(--s3);
595
+ color: var(--t2);
596
+ margin: 2px 2px 2px 0;
597
+ cursor: pointer;
598
+ }
599
+ .card-dep-chip:hover { background: var(--b1); color: var(--t1); }
600
+ .card-dep-chip .dep-dot {
601
+ width: 5px; height: 5px;
602
+ border-radius: 50%;
603
+ display: inline-block;
604
+ }
605
+ .card-timeline {
606
+ display: flex;
607
+ flex-wrap: wrap;
608
+ gap: 6px 16px;
609
+ font-size: 9px;
610
+ font-family: 'JetBrains Mono', monospace;
611
+ color: var(--t3);
612
+ margin-top: 6px;
613
+ }
614
+ .card-timeline .tl-label { color: var(--t4); margin-right: 3px; }
615
+ .card-report-summary {
616
+ font-size: 11px;
617
+ color: var(--t2);
618
+ line-height: 1.6;
619
+ white-space: pre-wrap;
620
+ word-break: break-word;
621
+ }
622
+ .card-report-summary strong,
623
+ .card-report-summary b { color: var(--t1); font-weight: 600; }
441
624
  .card .card-actions {
442
625
  position: absolute;
443
626
  top: 8px;
@@ -1060,6 +1243,174 @@
1060
1243
  .act-type.completed { color: var(--gn); }
1061
1244
  .act-type.updated { color: var(--t3); }
1062
1245
  .act-type.deleted { color: var(--rd); }
1246
+ .act-type.archived { color: var(--vl); }
1247
+
1248
+ /* ── Archive Panel ── */
1249
+ .archive-panel {
1250
+ position: fixed;
1251
+ top: 0;
1252
+ right: -480px;
1253
+ width: 480px;
1254
+ max-width: 100vw;
1255
+ height: 100vh;
1256
+ background: var(--s1);
1257
+ border-left: 1px solid var(--b1);
1258
+ z-index: 310;
1259
+ transition: right 0.2s ease;
1260
+ display: flex;
1261
+ flex-direction: column;
1262
+ }
1263
+ .archive-panel.open { right: 0; }
1264
+ .archive-panel .arch-header {
1265
+ padding: 14px 20px;
1266
+ border-bottom: 1px solid var(--b1);
1267
+ display: flex;
1268
+ align-items: center;
1269
+ justify-content: space-between;
1270
+ flex-shrink: 0;
1271
+ }
1272
+ .archive-panel .arch-header h3 {
1273
+ font-size: 13px;
1274
+ font-weight: 600;
1275
+ display: flex;
1276
+ align-items: center;
1277
+ gap: 8px;
1278
+ }
1279
+ .archive-panel .arch-close {
1280
+ background: none;
1281
+ border: 1px solid var(--b1);
1282
+ border-radius: var(--r);
1283
+ color: var(--t3);
1284
+ cursor: pointer;
1285
+ font-size: 14px;
1286
+ width: 28px; height: 28px;
1287
+ display: flex; align-items: center; justify-content: center;
1288
+ }
1289
+ .archive-panel .arch-close:hover { color: var(--t1); background: var(--s3); }
1290
+ .arch-dates {
1291
+ display: flex;
1292
+ gap: 4px;
1293
+ padding: 10px 20px;
1294
+ border-bottom: 1px solid var(--b1);
1295
+ overflow-x: auto;
1296
+ flex-shrink: 0;
1297
+ }
1298
+ .arch-date-btn {
1299
+ padding: 5px 12px;
1300
+ border: 1px solid var(--b1);
1301
+ background: var(--s2);
1302
+ color: var(--t3);
1303
+ font-size: 11px;
1304
+ font-weight: 500;
1305
+ font-family: inherit;
1306
+ cursor: pointer;
1307
+ border-radius: var(--r);
1308
+ white-space: nowrap;
1309
+ transition: all 0.15s;
1310
+ }
1311
+ .arch-date-btn:hover { color: var(--t1); border-color: var(--b2); }
1312
+ .arch-date-btn.active { background: var(--ac); color: #fff; border-color: var(--ac); }
1313
+ .arch-date-btn .arch-count {
1314
+ font-size: 9px;
1315
+ font-family: 'JetBrains Mono', monospace;
1316
+ margin-left: 4px;
1317
+ opacity: 0.7;
1318
+ }
1319
+ .arch-content {
1320
+ flex: 1;
1321
+ overflow-y: auto;
1322
+ padding: 12px 20px;
1323
+ }
1324
+ .arch-empty {
1325
+ text-align: center;
1326
+ padding: 40px 20px;
1327
+ color: var(--t4);
1328
+ font-size: 12px;
1329
+ }
1330
+ .arch-stats {
1331
+ font-size: 11px;
1332
+ color: var(--t4);
1333
+ margin-bottom: 12px;
1334
+ padding-bottom: 8px;
1335
+ border-bottom: 1px solid var(--b1);
1336
+ }
1337
+ .arch-task {
1338
+ padding: 10px 12px;
1339
+ border: 1px solid var(--b1);
1340
+ border-radius: var(--r);
1341
+ margin-bottom: 6px;
1342
+ background: var(--s2);
1343
+ transition: background 0.15s;
1344
+ }
1345
+ .arch-task:hover { background: var(--s3); }
1346
+ .arch-task.parent { border-left: 3px solid var(--ac); }
1347
+ .arch-task.child { margin-left: 20px; border-left: 2px dashed var(--b2); }
1348
+ .arch-task.orphan { border-left: 2px dashed var(--am); }
1349
+ .arch-task-header {
1350
+ display: flex;
1351
+ align-items: center;
1352
+ gap: 6px;
1353
+ margin-bottom: 3px;
1354
+ }
1355
+ .arch-task-id {
1356
+ font-size: 10px;
1357
+ font-family: 'JetBrains Mono', monospace;
1358
+ color: var(--ac);
1359
+ font-weight: 600;
1360
+ }
1361
+ .arch-task-subject {
1362
+ font-size: 12px;
1363
+ font-weight: 600;
1364
+ color: var(--t1);
1365
+ flex: 1;
1366
+ overflow: hidden;
1367
+ text-overflow: ellipsis;
1368
+ white-space: nowrap;
1369
+ }
1370
+ .arch-task-priority {
1371
+ font-size: 9px;
1372
+ font-weight: 600;
1373
+ padding: 1px 6px;
1374
+ border-radius: 2px;
1375
+ text-transform: uppercase;
1376
+ font-family: 'JetBrains Mono', monospace;
1377
+ }
1378
+ .arch-task-priority.high { background: rgba(239,68,68,0.15); color: var(--rd); }
1379
+ .arch-task-priority.critical { background: rgba(239,68,68,0.25); color: var(--rd); }
1380
+ .arch-task-meta {
1381
+ display: flex;
1382
+ gap: 8px;
1383
+ font-size: 10px;
1384
+ color: var(--t4);
1385
+ margin-top: 4px;
1386
+ flex-wrap: wrap;
1387
+ }
1388
+ .arch-task-meta span { display: flex; align-items: center; gap: 3px; }
1389
+ .arch-task-desc {
1390
+ font-size: 11px;
1391
+ color: var(--t3);
1392
+ margin-top: 4px;
1393
+ overflow: hidden;
1394
+ text-overflow: ellipsis;
1395
+ white-space: nowrap;
1396
+ }
1397
+ .arch-task-parent-ref {
1398
+ font-size: 9px;
1399
+ color: var(--t4);
1400
+ font-family: 'JetBrains Mono', monospace;
1401
+ }
1402
+ .arch-section-label {
1403
+ font-size: 10px;
1404
+ font-weight: 600;
1405
+ color: var(--t4);
1406
+ text-transform: uppercase;
1407
+ letter-spacing: 0.5px;
1408
+ margin: 16px 0 8px;
1409
+ padding-bottom: 4px;
1410
+ border-bottom: 1px solid var(--b1);
1411
+ }
1412
+ .archive-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 260; }
1413
+ .archive-overlay.open { display: block; }
1063
1414
 
1064
1415
  /* Activity toolbar button */
1065
1416
  .activity-btn {
@@ -1481,175 +1832,27 @@
1481
1832
  border-radius: 50%;
1482
1833
  animation: spin 0.8s linear infinite;
1483
1834
  }
1484
- /* v3: Stale/Zombie */
1485
- .stale-badge {
1486
- font-size: 10px; font-weight: 700; color: var(--rd);
1487
- font-family: 'JetBrains Mono', monospace;
1488
- margin-top: 4px; padding: 2px 6px;
1489
- background: rgba(239,68,68,0.1); border-radius: 2px;
1490
- display: inline-flex; align-items: center; gap: 4px;
1491
- animation: stalePulse 2s infinite;
1492
- }
1493
- .stale-badge .stale-reason { font-weight: 400; font-size: 9px; color: var(--t4); }
1494
- @keyframes stalePulse { 0%,100%{opacity:1}50%{opacity:0.5} }
1495
- .stale-actions { display: flex; gap: 6px; margin-top: 8px; }
1496
-
1497
- /* v3: Agent Dashboard Panel */
1498
- .agent-dash-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 500; }
1499
- .agent-dash-overlay.open { display: block; }
1500
- .agent-dash {
1501
- position: fixed; top: 0; right: -640px; width: 640px; max-width: 100vw; height: 100vh;
1502
- background: var(--s1); border-left: 1px solid var(--b1); z-index: 501;
1503
- transition: right 0.25s ease; display: flex; flex-direction: column;
1504
- }
1505
- .agent-dash.open { right: 0; }
1506
- .agent-dash .ad-header {
1507
- padding: 14px 20px; border-bottom: 1px solid var(--b1);
1508
- display: flex; align-items: center; justify-content: space-between; flex-shrink: 0;
1509
- }
1510
- .agent-dash .ad-header h3 { font-size: 14px; font-weight: 700; }
1511
- .agent-dash .ad-close {
1512
- background: none; border: 1px solid var(--b1); border-radius: var(--r);
1513
- color: var(--t3); cursor: pointer; font-size: 14px;
1514
- width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
1515
- }
1516
- .agent-dash .ad-close:hover { color: var(--t1); background: var(--s3); }
1517
- .agent-dash .ad-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
1518
- .ad-card {
1519
- background: var(--s2); border: 1px solid var(--b1); border-radius: 4px;
1520
- padding: 14px 16px; margin-bottom: 10px;
1521
- }
1522
- .ad-card-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
1523
- .ad-card-header .ad-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
1524
- .ad-card-header .ad-name { font-size: 13px; font-weight: 600; color: var(--t1); }
1525
- .ad-card-header .ad-status {
1526
- font-size: 10px; font-family: 'JetBrains Mono', monospace; padding: 2px 8px;
1527
- border-radius: 2px; font-weight: 600; margin-left: auto;
1528
- }
1529
- .ad-status-idle { background: var(--s3); color: var(--t3); }
1530
- .ad-status-working { background: rgba(59,130,246,0.15); color: var(--ac); }
1531
- .ad-status-stale { background: rgba(239,68,68,0.1); color: var(--rd); }
1532
- .ad-stats {
1533
- display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
1534
- font-size: 11px; font-family: 'JetBrains Mono', monospace;
1535
- }
1536
- .ad-stat { text-align: center; }
1537
- .ad-stat .ad-num { font-size: 16px; font-weight: 700; }
1538
- .ad-stat .ad-lbl { font-size: 9px; color: var(--t4); text-transform: uppercase; letter-spacing: 0.04em; }
1539
- .ad-current {
1540
- font-size: 11px; color: var(--t2); margin-top: 8px;
1541
- padding-top: 8px; border-top: 1px solid var(--b1);
1542
- }
1543
- .ad-current strong { color: var(--t1); }
1544
-
1545
- /* v3: Filter Bar */
1546
- .filter-bar {
1547
- display: flex; align-items: center; gap: 8px; padding: 8px 16px;
1548
- background: var(--s1); border-bottom: 1px solid var(--b1);
1549
- }
1550
- .filter-bar select, .filter-bar input {
1551
- background: var(--bg); border: 1px solid var(--b1); border-radius: var(--r);
1552
- padding: 5px 8px; color: var(--t1); font-size: 11px; font-family: inherit;
1553
- }
1554
- .filter-bar select { cursor: pointer; }
1555
- .filter-bar input { width: 180px; }
1556
- .filter-bar input::placeholder { color: var(--t4); }
1557
- .filter-bar label {
1558
- font-size: 10px; color: var(--t3); display: inline-flex; align-items: center; gap: 2px; cursor: pointer;
1559
- font-family: 'JetBrains Mono', monospace; white-space: nowrap;
1560
- }
1561
- .filter-bar label input[type="checkbox"] { accent-color: var(--ac); margin: 0; width: 12px; height: 12px; }
1562
- .filter-active { border-color: var(--ac) !important; }
1563
- .card.filter-hidden { display: none; }
1564
-
1565
- /* v3: Metrics Panel */
1566
- .metrics-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 400; backdrop-filter: blur(3px); }
1567
- .metrics-overlay.open { display: block; }
1568
- .metrics-popup {
1569
- display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
1570
- width: 720px; max-width: 94vw; max-height: 85vh;
1571
- background: var(--s1); border: 1px solid var(--b1); border-radius: 6px;
1572
- z-index: 410; flex-direction: column; box-shadow: 0 20px 60px rgba(0,0,0,0.5);
1573
- }
1574
- .metrics-popup.open { display: flex; }
1575
- .metrics-header {
1576
- padding: 16px 20px; border-bottom: 1px solid var(--b1);
1577
- display: flex; align-items: center; justify-content: space-between; flex-shrink: 0;
1578
- }
1579
- .metrics-header h2 { font-size: 15px; font-weight: 700; }
1580
- .metrics-body { flex: 1; overflow-y: auto; padding: 20px; }
1581
- .m-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 20px; }
1582
- .m-card {
1583
- background: var(--s2); border: 1px solid var(--b1); border-radius: 4px;
1584
- padding: 14px; text-align: center;
1585
- }
1586
- .m-card .m-num { font-size: 24px; font-weight: 700; font-family: 'JetBrains Mono', monospace; }
1587
- .m-card .m-lbl { font-size: 10px; color: var(--t4); text-transform: uppercase; letter-spacing: 0.04em; margin-top: 2px; }
1588
- .m-section { margin-bottom: 20px; }
1589
- .m-section-title { font-size: 12px; font-weight: 600; color: var(--t2); margin-bottom: 10px; text-transform: uppercase; letter-spacing: 0.04em; }
1590
- .m-bar-chart { display: flex; flex-direction: column; gap: 6px; }
1591
- .m-bar-row { display: flex; align-items: center; gap: 8px; }
1592
- .m-bar-label { font-size: 11px; font-family: 'JetBrains Mono', monospace; color: var(--t3); width: 80px; text-align: right; flex-shrink: 0; }
1593
- .m-bar-track { flex: 1; height: 16px; background: var(--s3); border-radius: 2px; overflow: hidden; position: relative; }
1594
- .m-bar-fill { height: 100%; border-radius: 2px; transition: width 0.3s; }
1595
- .m-bar-val { font-size: 10px; font-family: 'JetBrains Mono', monospace; color: var(--t4); width: 30px; }
1596
- .m-burndown { width: 100%; height: 120px; position: relative; }
1597
- .m-burndown svg { width: 100%; height: 100%; }
1598
- .m-progress-ring {
1599
- width: 100px; height: 100px; margin: 0 auto 10px;
1600
- }
1601
-
1602
- /* v3: Dependency Graph */
1603
- .dep-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 400; backdrop-filter: blur(3px); }
1604
- .dep-overlay.open { display: block; }
1605
- .dep-popup {
1606
- display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
1607
- width: 860px; max-width: 96vw; max-height: 90vh;
1608
- background: var(--s1); border: 1px solid var(--b1); border-radius: 6px;
1609
- z-index: 410; flex-direction: column; box-shadow: 0 20px 60px rgba(0,0,0,0.5);
1610
- }
1611
- .dep-popup.open { display: flex; }
1612
- .dep-header { padding: 14px 20px; border-bottom: 1px solid var(--b1); display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
1613
- .dep-header h2 { font-size: 15px; font-weight: 700; }
1614
- .dep-body { flex: 1; overflow: auto; padding: 20px; }
1615
- .dep-graph { position: relative; min-height: 200px; }
1616
- .dep-node {
1617
- display: inline-flex; align-items: center; gap: 6px;
1618
- padding: 6px 10px; margin: 4px;
1619
- background: var(--s2); border: 1px solid var(--b1); border-radius: 4px;
1620
- font-size: 11px; cursor: pointer; transition: all 0.15s;
1621
- }
1622
- .dep-node:hover { border-color: var(--ac); background: var(--s3); }
1623
- .dep-node .dn-id { font-family: 'JetBrains Mono', monospace; color: var(--t3); font-weight: 600; }
1624
- .dep-node .dn-title { color: var(--t2); max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1625
- .dep-node .dn-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
1626
- .dep-node[data-s="completed"] { opacity: 0.5; }
1627
- .dep-node[data-s="completed"] .dn-dot { background: var(--gn); }
1628
- .dep-node[data-s="in_progress"] .dn-dot { background: var(--ac); }
1629
- .dep-node[data-s="in_review"] .dn-dot { background: var(--vl); }
1630
- .dep-node[data-s="pending"] .dn-dot { background: var(--t4); }
1631
- .dep-node.critical { border-color: var(--rd); box-shadow: 0 0 0 1px rgba(239,68,68,0.3); }
1632
- .dep-tree { padding-left: 0; list-style: none; }
1633
- .dep-tree ul { padding-left: 24px; list-style: none; border-left: 1px solid var(--b1); margin-left: 8px; }
1634
- .dep-tree li { padding: 3px 0; }
1635
- .dep-arrow { color: var(--t4); font-size: 10px; margin: 0 4px; }
1636
- .dep-legend { display: flex; gap: 16px; margin-bottom: 16px; font-size: 10px; color: var(--t3); }
1637
- .dep-legend span { display: flex; align-items: center; gap: 4px; }
1638
- .dep-legend .dl-dot { width: 8px; height: 8px; border-radius: 50%; }
1639
1835
  </style>
1640
1836
  </head>
1641
1837
  <body>
1642
1838
 
1643
1839
  <div class="header">
1644
1840
  <div class="header-left">
1645
- <div class="logo">Agent<span class="logo-dot">.</span>Kanban</div>
1841
+ <div class="logo">APEX<span class="logo-dot">.</span>Kanban</div>
1646
1842
  <div class="header-sub" id="taskSource">~/.claude/tasks/</div>
1647
1843
  </div>
1648
- <div class="stats">
1649
- <div class="stat"><div class="num" id="s-total" style="color:var(--t3)">0</div><div class="lbl">Total</div></div>
1650
- <div class="stat"><div class="num" id="s-active" style="color:var(--am)">0</div><div class="lbl">Active</div></div>
1651
- <div class="stat"><div class="num" id="s-done" style="color:var(--gn)">0</div><div class="lbl">Done</div></div>
1652
- <div class="stat"><div class="num" id="s-blocked" style="color:var(--rd)">0</div><div class="lbl">Blocked</div></div>
1844
+ <div style="display:flex;align-items:center;gap:0">
1845
+ <div class="stats">
1846
+ <div class="stat"><div class="num" id="s-total" style="color:var(--t3)">0</div><div class="lbl">Total</div></div>
1847
+ <div class="stat"><div class="num" id="s-active" style="color:var(--am)">0</div><div class="lbl">Active</div></div>
1848
+ <div class="stat"><div class="num" id="s-done" style="color:var(--gn)">0</div><div class="lbl">Done</div></div>
1849
+ <div class="stat"><div class="num" id="s-blocked" style="color:var(--rd)">0</div><div class="lbl">Blocked</div></div>
1850
+ </div>
1851
+ <div class="theme-switcher">
1852
+ <button class="theme-btn active" data-t="dark" title="Dark"></button>
1853
+ <button class="theme-btn" data-t="navy" title="Navy"></button>
1854
+ <button class="theme-btn" data-t="light" title="Light"></button>
1855
+ </div>
1653
1856
  </div>
1654
1857
  </div>
1655
1858
 
@@ -1658,37 +1861,15 @@
1658
1861
  <div class="toolbar" id="toolbar">
1659
1862
  <span id="filterBtns"><button class="active" data-filter="all">All</button></span>
1660
1863
  <div class="right">
1661
- <button onclick="toggleDepGraph()">Deps</button>
1662
- <button onclick="toggleMetrics()">Metrics</button>
1663
- <button onclick="toggleAgentDash()">Dashboard</button>
1664
1864
  <button onclick="toggleAgentPanel()">Agents</button>
1665
1865
  <button onclick="toggleChat()">Chat</button>
1666
1866
  <button class="activity-btn" onclick="toggleActivity()">Activity <span class="activity-badge" id="actBadge">0</span></button>
1867
+ <button onclick="toggleArchive()">Archive</button>
1667
1868
  <button class="add-btn" onclick="openModal()">New Task</button>
1668
- <button class="clear-btn" onclick="clearDone()">Clear Done</button>
1869
+ <button class="clear-btn" onclick="archiveDone()">Archive Done</button>
1669
1870
  </div>
1670
1871
  </div>
1671
1872
 
1672
- <div class="filter-bar" id="filterBar">
1673
- <input type="text" id="filterSearch" placeholder="Search tasks..." oninput="applyFilters()">
1674
- <select id="filterAgent" onchange="applyFilters()"><option value="">All Agents</option></select>
1675
- <select id="filterStatus" onchange="applyFilters()">
1676
- <option value="">All Status</option>
1677
- <option value="pending">Pending</option>
1678
- <option value="in_progress">In Progress</option>
1679
- <option value="in_review">In Review</option>
1680
- <option value="completed">Completed</option>
1681
- </select>
1682
- <select id="filterPriority" onchange="applyFilters()">
1683
- <option value="">All Priority</option>
1684
- <option value="high">P0 Urgent</option>
1685
- <option value="medium">P1 Normal</option>
1686
- <option value="low">P2 Low</option>
1687
- </select>
1688
- <label><input type="checkbox" id="filterHideDone" onchange="applyFilters()"> Hide Done</label>
1689
- <label><input type="checkbox" id="filterStaleOnly" onchange="applyFilters()"> Stale Only</label>
1690
- </div>
1691
-
1692
1873
  <div class="board" id="board"></div>
1693
1874
 
1694
1875
  <div class="modal-overlay" id="modalOverlay">
@@ -1809,31 +1990,16 @@
1809
1990
  </div>
1810
1991
  </div>
1811
1992
 
1812
- <div class="dep-overlay" id="depOverlay" onclick="closeDepGraph()"></div>
1813
- <div class="dep-popup" id="depPopup">
1814
- <div class="dep-header">
1815
- <h2>Dependency Graph</h2>
1816
- <button class="td-close" onclick="closeDepGraph()">&times;</button>
1993
+ <div class="archive-overlay" id="archOverlay" onclick="closeArchive()"></div>
1994
+ <div class="archive-panel" id="archPanel">
1995
+ <div class="arch-header">
1996
+ <h3>Archive</h3>
1997
+ <button class="arch-close" onclick="closeArchive()">&times;</button>
1817
1998
  </div>
1818
- <div class="dep-body" id="depBody"><div style="color:var(--t4);text-align:center;padding:40px">Loading...</div></div>
1819
- </div>
1820
-
1821
- <div class="metrics-overlay" id="metricsOverlay" onclick="closeMetrics()"></div>
1822
- <div class="metrics-popup" id="metricsPopup">
1823
- <div class="metrics-header">
1824
- <h2>Progress Metrics</h2>
1825
- <button class="td-close" onclick="closeMetrics()">&times;</button>
1999
+ <div class="arch-dates" id="archDates"></div>
2000
+ <div class="arch-content" id="archContent">
2001
+ <div class="arch-empty">No archived tasks</div>
1826
2002
  </div>
1827
- <div class="metrics-body" id="metricsBody"><div style="color:var(--t4);text-align:center;padding:40px">Loading...</div></div>
1828
- </div>
1829
-
1830
- <div class="agent-dash-overlay" id="adOverlay" onclick="closeAgentDash()"></div>
1831
- <div class="agent-dash" id="adPanel">
1832
- <div class="ad-header">
1833
- <h3>Agent Dashboard</h3>
1834
- <button class="ad-close" onclick="closeAgentDash()">&times;</button>
1835
- </div>
1836
- <div class="ad-body" id="adBody"><div style="color:var(--t4);text-align:center;padding:40px">Loading...</div></div>
1837
2003
  </div>
1838
2004
 
1839
2005
  <div class="agent-overlay" id="agentOverlay" onclick="closeAgentPanel()"></div>
@@ -1842,6 +2008,7 @@
1842
2008
  <h3>Agent Prompts</h3>
1843
2009
  <button class="ap-close" onclick="closeAgentPanel()">&times;</button>
1844
2010
  </div>
2011
+ <div class="ap-project-filter" id="apProjectFilter"></div>
1845
2012
  <div class="agent-tabs" id="agentTabs"></div>
1846
2013
  <div class="agent-prompt-view" id="agentPromptView">
1847
2014
  <div style="color:var(--t4);text-align:center;padding:40px">로딩 중...</div>
@@ -1884,7 +2051,33 @@
1884
2051
  </div>
1885
2052
 
1886
2053
  <script>
2054
+ // ── Theme Switcher ──
2055
+ (function(){
2056
+ var saved = localStorage.getItem("kanban_theme") || "dark";
2057
+ document.documentElement.setAttribute("data-theme", saved);
2058
+ document.querySelectorAll(".theme-btn").forEach(function(btn){
2059
+ if(btn.getAttribute("data-t") === saved) btn.classList.add("active");
2060
+ else btn.classList.remove("active");
2061
+ btn.addEventListener("click", function(){
2062
+ var t = this.getAttribute("data-t");
2063
+ document.documentElement.setAttribute("data-theme", t);
2064
+ localStorage.setItem("kanban_theme", t);
2065
+ document.querySelectorAll(".theme-btn").forEach(function(b){ b.classList.remove("active"); });
2066
+ this.classList.add("active");
2067
+ });
2068
+ });
2069
+ })();
2070
+
1887
2071
  var AGENTS={orchestrator:{label:"Orchestrator",color:"var(--am)"},frontend:{label:"Frontend",color:"var(--ac)"},backend:{label:"Backend",color:"var(--gn)"},qa:{label:"QA",color:"var(--rd)"},devops:{label:"DevOps",color:"var(--t3)"},researcher:{label:"Researcher",color:"var(--vl)"},strategist:{label:"Strategist",color:"var(--t3)"},designer:{label:"Designer",color:"#EC4899"},content:{label:"Content",color:"#06B6D4"},itemauthor:{label:"Item Author",color:"#F97316"},claude:{label:"Claude",color:"#D4A574"},manual:{label:"Manual",color:"var(--t3)"},unknown:{label:"Unassigned",color:"var(--t4)"}};
2072
+ var DYNAMIC_COLORS=["#3B82F6","#10B981","#F59E0B","#EF4444","#8B5CF6","#EC4899","#06B6D4","#F97316","#14B8A6","#6366F1","#D946EF"];
2073
+ function getAgentEntry(key){
2074
+ if(AGENTS[key])return AGENTS[key];
2075
+ var hash=0;for(var i=0;i<key.length;i++)hash=((hash<<5)-hash)+key.charCodeAt(i);
2076
+ var color=DYNAMIC_COLORS[Math.abs(hash)%DYNAMIC_COLORS.length];
2077
+ var label=key.replace(/[-_]/g," ").replace(/\b\w/g,function(c){return c.toUpperCase();});
2078
+ AGENTS[key]={label:label,color:color};
2079
+ return AGENTS[key];
2080
+ }
1888
2081
  var COLUMNS=[{id:"pending",name:"To Do",dot:"var(--t4)"},{id:"in_progress",name:"In Progress",dot:"var(--am)"},{id:"in_review",name:"In Review",dot:"var(--vl)"},{id:"completed",name:"Done",dot:"var(--gn)"}];
1889
2082
  var allTasks=[], filter="all", prevDoneCount=0, editingId=null, dragId=null;
1890
2083
  var currentProject=localStorage.getItem("kanban_project")||null;
@@ -1923,6 +2116,7 @@ function switchProject(id){
1923
2116
  localStorage.setItem("kanban_project",id);
1924
2117
  renderProjectTabs();
1925
2118
  render();
2119
+ if(archiveOpen)loadArchiveList();
1926
2120
  }
1927
2121
 
1928
2122
  function addProject(){
@@ -1949,315 +2143,53 @@ function apiUpdate(id,d){return fetch("/api/tasks/"+id,{method:"PUT",headers:{"C
1949
2143
  function apiDelete(id){return fetch("/api/tasks/"+id,{method:"DELETE"});}
1950
2144
 
1951
2145
  // ── Agent Prompt Panel ──
1952
- var agentData=null, activeAgentTab=null;
1953
-
1954
- // ── Dependency Graph (v3) ──
1955
- function toggleDepGraph(){
1956
- var p=document.getElementById("depPopup");
1957
- if(p.classList.contains("open")){closeDepGraph();return;}
1958
- document.getElementById("depOverlay").classList.add("open");
1959
- p.classList.add("open");
1960
- renderDepGraph();
1961
- }
1962
- function closeDepGraph(){
1963
- document.getElementById("depPopup").classList.remove("open");
1964
- document.getElementById("depOverlay").classList.remove("open");
1965
- }
1966
- function renderDepGraph(){
1967
- var tasks=currentProject?allTasks.filter(function(t){return t.project===currentProject;}):allTasks;
1968
- var byId={};
1969
- tasks.forEach(function(t){byId[String(t.id)]=t;});
1970
- // Find tasks with dependencies
1971
- var hasDeps=tasks.filter(function(t){
1972
- return (t.blockedBy&&t.blockedBy.length>0)||(t.parentId)||(t.subtasks&&t.subtasks.length>0);
1973
- });
1974
- // Build parent-child tree
1975
- var childMap={};
1976
- tasks.forEach(function(t){
1977
- if(t.parentId){
1978
- if(!childMap[String(t.parentId)])childMap[String(t.parentId)]=[];
1979
- childMap[String(t.parentId)].push(t);
1980
- }
1981
- });
1982
- // Find critical path: tasks that are blocked and blocking others
1983
- var criticalIds={};
1984
- tasks.forEach(function(t){
1985
- if(t.blockedBy&&t.blockedBy.length>0&&getCol(t)!=="completed"){
1986
- criticalIds[String(t.id)]=true;
1987
- t.blockedBy.forEach(function(bid){if(byId[String(bid)]&&getCol(byId[String(bid)])!=="completed")criticalIds[String(bid)]=true;});
1988
- }
1989
- });
1990
- var h='';
1991
- // Legend
1992
- h+='<div class="dep-legend">';
1993
- h+='<span><span class="dl-dot" style="background:var(--gn)"></span>Done</span>';
1994
- h+='<span><span class="dl-dot" style="background:var(--ac)"></span>Active</span>';
1995
- h+='<span><span class="dl-dot" style="background:var(--vl)"></span>Review</span>';
1996
- h+='<span><span class="dl-dot" style="background:var(--t4)"></span>Pending</span>';
1997
- h+='<span><span class="dl-dot" style="background:var(--rd)"></span>Critical Path</span>';
1998
- h+='</div>';
1999
- // Dependency chains section
2000
- var depsShown={};
2001
- var chains=tasks.filter(function(t){return t.blockedBy&&t.blockedBy.length>0;});
2002
- if(chains.length>0){
2003
- h+='<div style="font-size:12px;font-weight:600;color:var(--t2);margin-bottom:8px">Dependency Chains</div>';
2004
- chains.forEach(function(t){
2005
- if(depsShown[t.id])return;
2006
- depsShown[t.id]=true;
2007
- t.blockedBy.forEach(function(bid){
2008
- var blocker=byId[String(bid)];
2009
- if(!blocker)return;
2010
- h+=makeDepNode(blocker,criticalIds);
2011
- h+='<span class="dep-arrow">&rarr;</span>';
2012
- });
2013
- h+=makeDepNode(t,criticalIds);
2014
- h+='<br>';
2015
- });
2016
- h+='<hr style="border:none;border-top:1px solid var(--b1);margin:16px 0">';
2017
- }
2018
- // Parent-child trees
2019
- var roots=tasks.filter(function(t){return !t.parentId&&childMap[String(t.id)];});
2020
- if(roots.length>0){
2021
- h+='<div style="font-size:12px;font-weight:600;color:var(--t2);margin-bottom:8px">Task Trees</div>';
2022
- h+='<ul class="dep-tree">';
2023
- roots.forEach(function(t){h+=renderDepTree(t,childMap,byId,criticalIds);});
2024
- h+='</ul>';
2025
- }
2026
- if(!chains.length&&!roots.length){
2027
- h+='<div style="color:var(--t4);text-align:center;padding:40px;font-size:12px">No dependencies found in current project</div>';
2028
- }
2029
- document.getElementById("depBody").innerHTML=h;
2030
- }
2031
- function makeDepNode(t,crit){
2032
- var s=getCol(t);
2033
- var cls="dep-node"+(crit[String(t.id)]?" critical":"");
2034
- return '<span class="'+cls+'" data-s="'+s+'" onclick="closeDepGraph();openTaskDetail(\''+t.id+'\')">'+
2035
- '<span class="dn-dot"></span><span class="dn-id">#'+esc(String(t.id))+'</span><span class="dn-title">'+esc((t.subject||"").slice(0,40))+'</span></span>';
2036
- }
2037
- function renderDepTree(t,childMap,byId,crit){
2038
- var h='<li>'+makeDepNode(t,crit);
2039
- var children=childMap[String(t.id)]||[];
2040
- if(children.length>0){
2041
- h+='<ul>';
2042
- children.forEach(function(c){h+=renderDepTree(c,childMap,byId,crit);});
2043
- h+='</ul>';
2044
- }
2045
- h+='</li>';
2046
- return h;
2047
- }
2048
-
2049
- // ── Filter/Search (v3) ──
2050
- function updateFilterAgentDropdown(){
2051
- var sel=document.getElementById("filterAgent");
2052
- var cur=sel.value;
2053
- var agents={};
2054
- allTasks.forEach(function(t){var a=guessAgent(t);agents[a]=true;});
2055
- var opts='<option value="">All Agents</option>';
2056
- Object.keys(agents).sort().forEach(function(a){
2057
- opts+='<option value="'+esc(a)+'"'+(a===cur?' selected':'')+'>'+esc(a)+'</option>';
2058
- });
2059
- sel.innerHTML=opts;
2060
- }
2061
- function applyFilters(){
2062
- var search=(document.getElementById("filterSearch").value||"").toLowerCase().trim();
2063
- var agent=document.getElementById("filterAgent").value;
2064
- var status=document.getElementById("filterStatus").value;
2065
- var priority=document.getElementById("filterPriority").value;
2066
- var hideDone=document.getElementById("filterHideDone").checked;
2067
- var staleOnly=document.getElementById("filterStaleOnly").checked;
2068
- var cards=document.querySelectorAll(".card");
2069
- var shown=0,hidden=0;
2070
- cards.forEach(function(card){
2071
- var id=card.dataset.id||"";
2072
- var cAgent=card.dataset.agent||"";
2073
- var cStatus=card.dataset.status||"";
2074
- var cPriority=card.dataset.priority||"";
2075
- var cStale=card.dataset.stale==="1";
2076
- var title=(card.querySelector(".title")||{}).textContent||"";
2077
- var show=true;
2078
- if(search&&title.toLowerCase().indexOf(search)<0&&id.indexOf(search)<0) show=false;
2079
- if(agent&&cAgent!==agent) show=false;
2080
- if(status&&cStatus!==status) show=false;
2081
- if(priority&&cPriority!==priority) show=false;
2082
- if(hideDone&&cStatus==="completed") show=false;
2083
- if(staleOnly&&!cStale) show=false;
2084
- card.classList.toggle("filter-hidden",!show);
2085
- if(show) shown++; else hidden++;
2086
- });
2087
- // Update column counts
2088
- document.querySelectorAll(".column").forEach(function(col){
2089
- var cnt=col.querySelectorAll(".card:not(.filter-hidden)").length;
2090
- var countEl=col.querySelector(".count");
2091
- if(countEl) countEl.textContent=cnt;
2092
- });
2093
- }
2146
+ var agentData=null, activeAgentTab=null, agentPanelProject=null;
2147
+ var AGENT_FILE_MAP={frontend:"frontend",backend:"backend",qa:"qa",devops:"devops",orchestrator:"orchestrator",designer:"designer",researcher:"researcher",strategist:"strategist",content:"content-planner",itemauthor:"item-author"};
2094
2148
 
2095
- // ── Metrics Panel (v3) ──
2096
- function toggleMetrics(){
2097
- var p=document.getElementById("metricsPopup");
2098
- if(p.classList.contains("open")){closeMetrics();return;}
2099
- document.getElementById("metricsOverlay").classList.add("open");
2100
- p.classList.add("open");
2101
- loadMetrics();
2102
- }
2103
- function closeMetrics(){
2104
- document.getElementById("metricsPopup").classList.remove("open");
2105
- document.getElementById("metricsOverlay").classList.remove("open");
2106
- }
2107
- function loadMetrics(){
2108
- var projectParam=currentProject?"?project="+encodeURIComponent(currentProject):"";
2109
- fetch("/api/metrics"+projectParam).then(function(r){return r.json();}).then(renderMetrics).catch(function(){
2110
- document.getElementById("metricsBody").innerHTML='<div style="color:var(--t4);text-align:center;padding:40px">Failed</div>';
2111
- });
2112
- }
2113
- function renderMetrics(m){
2114
- var h='';
2115
- // Overview cards
2116
- h+='<div class="m-grid">';
2117
- h+='<div class="m-card"><div class="m-num" style="color:var(--t2)">'+m.total+'</div><div class="m-lbl">Total</div></div>';
2118
- h+='<div class="m-card"><div class="m-num" style="color:var(--gn)">'+m.completed+'</div><div class="m-lbl">Done</div></div>';
2119
- h+='<div class="m-card"><div class="m-num" style="color:var(--am)">'+m.active+'</div><div class="m-lbl">Active</div></div>';
2120
- h+='<div class="m-card"><div class="m-num" style="color:var(--ac)">'+m.completionRate+'%</div><div class="m-lbl">Completion</div></div>';
2121
- h+='</div>';
2122
- // Second row
2123
- h+='<div class="m-grid">';
2124
- h+='<div class="m-card"><div class="m-num" style="color:var(--t3)">'+m.pending+'</div><div class="m-lbl">Pending</div></div>';
2125
- h+='<div class="m-card"><div class="m-num" style="color:var(--vl)">'+m.review+'</div><div class="m-lbl">Review</div></div>';
2126
- h+='<div class="m-card"><div class="m-num" style="color:var(--rd)">'+(m.blocked||0)+'</div><div class="m-lbl">Blocked</div></div>';
2127
- h+='<div class="m-card"><div class="m-num" style="color:var(--rd)">'+(m.stale||0)+'</div><div class="m-lbl">Stale</div></div>';
2128
- h+='</div>';
2129
- // Completion progress ring
2130
- h+='<div class="m-section"><div class="m-section-title">Completion</div>';
2131
- h+='<div class="m-progress-ring"><svg viewBox="0 0 36 36"><path d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" fill="none" stroke="var(--s3)" stroke-width="3"/>';
2132
- h+='<path d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" fill="none" stroke="var(--gn)" stroke-width="3" stroke-dasharray="'+m.completionRate+', 100" stroke-linecap="round"/>';
2133
- h+='<text x="18" y="20.5" text-anchor="middle" fill="var(--t1)" font-size="8" font-weight="700" font-family="JetBrains Mono, monospace">'+m.completionRate+'%</text></svg></div></div>';
2134
- // Agent bar chart
2135
- var agentKeys=Object.keys(m.byAgent||{}).sort();
2136
- if(agentKeys.length>0){
2137
- var maxT=Math.max.apply(null,agentKeys.map(function(k){return m.byAgent[k].total;}));
2138
- h+='<div class="m-section"><div class="m-section-title">By Agent</div><div class="m-bar-chart">';
2139
- agentKeys.forEach(function(k){
2140
- var s=m.byAgent[k];
2141
- var pct=maxT>0?Math.round(s.completed/maxT*100):0;
2142
- var pctA=maxT>0?Math.round(s.in_progress/maxT*100):0;
2143
- var color=(AGENTS[k]||AGENTS.unknown).color;
2144
- h+='<div class="m-bar-row"><span class="m-bar-label">'+esc(k)+'</span>';
2145
- h+='<div class="m-bar-track"><div class="m-bar-fill" style="width:'+(pct+pctA)+'%;background:'+color+';opacity:0.3;position:absolute"></div>';
2146
- h+='<div class="m-bar-fill" style="width:'+pct+'%;background:'+color+';position:relative;z-index:1"></div></div>';
2147
- h+='<span class="m-bar-val">'+s.completed+'/'+s.total+'</span></div>';
2148
- });
2149
- h+='</div></div>';
2149
+ function updateBoardTop(){
2150
+ var board=document.querySelector(".board");
2151
+ if(board){
2152
+ var top=board.getBoundingClientRect().top;
2153
+ var feed=document.querySelector(".live-feed");
2154
+ var bottom=feed?feed.offsetHeight:0;
2155
+ board.style.height="calc(100vh - "+top+"px - "+bottom+"px)";
2150
2156
  }
2151
- // Daily completed trend
2152
- var dailyKeys=Object.keys(m.dailyCompleted||{}).sort().slice(-14);
2153
- if(dailyKeys.length>1){
2154
- var maxD=Math.max.apply(null,dailyKeys.map(function(k){return m.dailyCompleted[k];}));
2155
- h+='<div class="m-section"><div class="m-section-title">Daily Completed (last 14 days)</div><div class="m-bar-chart">';
2156
- dailyKeys.forEach(function(k){
2157
- var v=m.dailyCompleted[k];
2158
- var pct=maxD>0?Math.round(v/maxD*100):0;
2159
- h+='<div class="m-bar-row"><span class="m-bar-label" style="width:60px">'+k.slice(5)+'</span>';
2160
- h+='<div class="m-bar-track"><div class="m-bar-fill" style="width:'+pct+'%;background:var(--gn)"></div></div>';
2161
- h+='<span class="m-bar-val">'+v+'</span></div>';
2162
- });
2163
- h+='</div></div>';
2164
- }
2165
- // Priority breakdown
2166
- h+='<div class="m-section"><div class="m-section-title">By Priority</div><div class="m-bar-chart">';
2167
- [{k:"high",l:"P0 Urgent",c:"var(--rd)"},{k:"medium",l:"P1 Normal",c:"var(--am)"},{k:"low",l:"P2 Low",c:"var(--t3)"}].forEach(function(p){
2168
- var v=m.byPriority[p.k]||0;
2169
- var pct=m.total>0?Math.round(v/m.total*100):0;
2170
- h+='<div class="m-bar-row"><span class="m-bar-label" style="width:70px">'+p.l+'</span>';
2171
- h+='<div class="m-bar-track"><div class="m-bar-fill" style="width:'+pct+'%;background:'+p.c+'"></div></div>';
2172
- h+='<span class="m-bar-val">'+v+'</span></div>';
2173
- });
2174
- h+='</div></div>';
2175
- document.getElementById("metricsBody").innerHTML=h;
2176
2157
  }
2158
+ window.addEventListener("resize",updateBoardTop);
2177
2159
 
2178
- // ── Agent Dashboard (v3) ──
2179
- function toggleAgentDash(){
2180
- var panel=document.getElementById("adPanel");
2181
- if(panel.classList.contains("open")){closeAgentDash();return;}
2182
- document.getElementById("adOverlay").classList.add("open");
2183
- panel.classList.add("open");
2184
- loadAgentDash();
2185
- }
2186
- function closeAgentDash(){
2187
- document.getElementById("adPanel").classList.remove("open");
2188
- document.getElementById("adOverlay").classList.remove("open");
2189
- }
2190
- function loadAgentDash(){
2191
- fetch("/api/agents/status").then(function(r){return r.json();}).then(function(data){
2192
- renderAgentDash(data);
2193
- }).catch(function(){
2194
- document.getElementById("adBody").innerHTML='<div style="color:var(--t4);text-align:center;padding:40px">Failed to load</div>';
2160
+ function renderApProjectFilter(){
2161
+ var container=document.getElementById("apProjectFilter");
2162
+ if(!container||projects.length===0)return;
2163
+ var h='<button class="ap-project-btn'+(agentPanelProject===null?' active':'')+'" onclick="setApProject(null)">All</button>';
2164
+ projects.forEach(function(p){
2165
+ h+='<button class="ap-project-btn'+(agentPanelProject===p.id?' active':'')+'" onclick="setApProject(\''+esc(p.id)+'\')">'+esc(p.name)+'</button>';
2195
2166
  });
2167
+ container.innerHTML=h;
2196
2168
  }
2197
- function renderAgentDash(data){
2198
- var agents=data.agents||{};
2199
- var tracked=data.tracked||{};
2200
- var activeId=data.activeExec?data.activeExec.taskId:null;
2201
- // Build per-agent current task info
2202
- var agentCurrentTask={};
2203
- Object.keys(tracked).forEach(function(tid){
2204
- var t=tracked[tid];
2205
- agentCurrentTask[t.agent]=agentCurrentTask[t.agent]||[];
2206
- agentCurrentTask[t.agent].push({taskId:tid,subject:t.subject,alive:t.alive,lastActivity:t.lastActivity,startedAt:t.startedAt});
2207
- });
2208
- var keys=Object.keys(agents).filter(function(k){return k!=="unassigned";}).sort();
2209
- if(agents["unassigned"])keys.push("unassigned");
2210
- var html='';
2211
- keys.forEach(function(agentName){
2212
- var s=agents[agentName];
2213
- var current=agentCurrentTask[agentName]||[];
2214
- var status="idle";var statusCls="ad-status-idle";
2215
- if(current.length>0){
2216
- var hasStale=current.some(function(c){return !c.alive;});
2217
- if(hasStale){status="STALE";statusCls="ad-status-stale";}
2218
- else{status="WORKING";statusCls="ad-status-working";}
2219
- }
2220
- var color=(AGENTS[agentName]||AGENTS.unknown).color;
2221
- html+='<div class="ad-card" style="border-left:3px solid '+color+'">';
2222
- html+='<div class="ad-card-header">';
2223
- html+='<span class="ad-dot" style="background:'+color+'"></span>';
2224
- html+='<span class="ad-name">'+esc(agentName)+'</span>';
2225
- html+='<span class="ad-status '+statusCls+'">'+status+'</span>';
2226
- html+='</div>';
2227
- html+='<div class="ad-stats">';
2228
- html+='<div class="ad-stat"><div class="ad-num" style="color:var(--t2)">'+(s.total||0)+'</div><div class="ad-lbl">Total</div></div>';
2229
- html+='<div class="ad-stat"><div class="ad-num" style="color:var(--am)">'+(s.in_progress||0)+'</div><div class="ad-lbl">Active</div></div>';
2230
- html+='<div class="ad-stat"><div class="ad-num" style="color:var(--gn)">'+(s.completed||0)+'</div><div class="ad-lbl">Done</div></div>';
2231
- html+='<div class="ad-stat"><div class="ad-num" style="color:var(--t3)">'+(s.pending||0)+'</div><div class="ad-lbl">Pending</div></div>';
2232
- html+='</div>';
2233
- if(current.length>0){
2234
- current.forEach(function(c){
2235
- var elapsed=Math.round((Date.now()-c.startedAt)/60000);
2236
- html+='<div class="ad-current"><strong>#'+esc(c.taskId)+'</strong> '+esc(c.subject||"")+'<br>';
2237
- html+='<span style="color:var(--t4)">'+elapsed+'min elapsed</span>';
2238
- if(!c.alive) html+=' <span style="color:var(--rd);font-weight:600">DEAD</span>';
2239
- html+='</div>';
2240
- });
2241
- }
2242
- html+='</div>';
2243
- });
2244
- if(!html) html='<div style="color:var(--t4);text-align:center;padding:40px">No agents found</div>';
2245
- document.getElementById("adBody").innerHTML=html;
2169
+ function setApProject(projectId){
2170
+ agentPanelProject=projectId;
2171
+ renderApProjectFilter();
2172
+ // Reload agents for the selected project
2173
+ loadAgentData(projectId);
2246
2174
  }
2175
+
2247
2176
  function toggleAgentPanel(){
2248
2177
  var panel=document.getElementById("agentPanel");
2249
2178
  var overlay=document.getElementById("agentOverlay");
2250
2179
  if(panel.classList.contains("show")){closeAgentPanel();return;}
2251
2180
  overlay.classList.add("show");
2252
2181
  panel.classList.add("show");
2182
+ renderApProjectFilter();
2253
2183
  if(!agentData) loadAgentData();
2254
2184
  }
2255
2185
  function closeAgentPanel(){
2256
2186
  document.getElementById("agentPanel").classList.remove("show");
2257
2187
  document.getElementById("agentOverlay").classList.remove("show");
2258
2188
  }
2259
- function loadAgentData(){
2260
- fetch("/api/agents").then(function(r){return r.json();}).then(function(data){
2189
+ function loadAgentData(projectId){
2190
+ var url="/api/agents";
2191
+ if(projectId) url+="?project="+encodeURIComponent(projectId);
2192
+ fetch(url).then(function(r){return r.json();}).then(function(data){
2261
2193
  agentData=data;
2262
2194
  renderAgentTabs("_project");
2263
2195
  });
@@ -2267,21 +2199,54 @@ function renderAgentTabs(selected){
2267
2199
  var container=document.getElementById("agentTabs");
2268
2200
  var MODEL_LABELS={opus:"Opus",sonnet:"Sonnet",haiku:"Haiku"};
2269
2201
  var h='<button class="agent-tab'+(selected==="_project"?" active":"")+'" onclick="showAgentPrompt(\'_project\')">Project Context</button>';
2270
- var order=["orchestrator","frontend","backend","designer","qa","devops","researcher","strategist","content-planner","item-author","doc-writer"];
2271
- order.forEach(function(name){
2272
- var agent=agentData.agents.find(function(a){return a.name===name;});
2273
- if(!agent)return;
2274
- var ml=MODEL_LABELS[agent.model]||agent.model;
2275
- h+='<button class="agent-tab'+(selected===name?" active":"")+'" onclick="showAgentPrompt(\''+name+'\')">';
2276
- h+=esc(agent.name)+'<span class="tab-model">'+ml+'</span></button>';
2277
- });
2278
- // Any remaining agents not in order
2279
- agentData.agents.forEach(function(agent){
2280
- if(order.indexOf(agent.name)>=0)return;
2281
- var ml=MODEL_LABELS[agent.model]||agent.model;
2282
- h+='<button class="agent-tab'+(selected===agent.name?" active":"")+'" onclick="showAgentPrompt(\''+agent.name+'\')">';
2283
- h+=esc(agent.name)+'<span class="tab-model">'+ml+'</span></button>';
2284
- });
2202
+
2203
+ // When project filter is active AND the server returned project-specific agents,
2204
+ // skip the old allowedFiles filtering — just show all returned agents in order.
2205
+ var hasProjectAgents = agentPanelProject && agentData.agents.length > 0 && agentData.agents[0].project;
2206
+
2207
+ if (hasProjectAgents) {
2208
+ // Pipeline agents — show all in order with role summary
2209
+ agentData.agents.forEach(function(agent){
2210
+ var ml=MODEL_LABELS[agent.model]||agent.model;
2211
+ var roleSummary = (agent.role||'').substring(0, 60);
2212
+ if((agent.role||'').length > 60) roleSummary += '…';
2213
+ h+='<button class="agent-tab agent-tab-pipeline'+(selected===agent.name?" active":"")+'" data-agent="'+esc(agent.name)+'" onclick="showAgentPrompt(\''+esc(agent.name)+'\')">';
2214
+ h+='<span class="agent-tab-order">#'+esc(String(agent.order||0))+'</span>';
2215
+ h+='<span class="agent-tab-name">'+esc(agent.name)+'</span><span class="tab-model">'+ml+'</span>';
2216
+ if(roleSummary) h+='<span class="agent-tab-role">'+esc(roleSummary)+'</span>';
2217
+ h+='</button>';
2218
+ });
2219
+ } else {
2220
+ // Default: APEX .md agents with filtering
2221
+ var allowedFiles=null;
2222
+ if(agentPanelProject){
2223
+ var pTasks=allTasks.filter(function(t){return t.project===agentPanelProject;});
2224
+ var usedKeys={};
2225
+ pTasks.forEach(function(t){var k=guessAgent(t);usedKeys[k]=true;});
2226
+ allowedFiles={};
2227
+ Object.keys(usedKeys).forEach(function(k){
2228
+ var fname=AGENT_FILE_MAP[k];
2229
+ if(fname) allowedFiles[fname]=true;
2230
+ });
2231
+ }
2232
+ var order=["orchestrator","frontend","backend","designer","qa","devops","researcher","strategist","content-planner","item-author","doc-writer"];
2233
+ order.forEach(function(name){
2234
+ var agent=agentData.agents.find(function(a){return a.name===name;});
2235
+ if(!agent)return;
2236
+ if(allowedFiles&&!allowedFiles[name])return;
2237
+ var ml=MODEL_LABELS[agent.model]||agent.model;
2238
+ h+='<button class="agent-tab'+(selected===name?" active":"")+'" onclick="showAgentPrompt(\''+name+'\')">';
2239
+ h+=esc(agent.name)+'<span class="tab-model">'+ml+'</span></button>';
2240
+ });
2241
+ agentData.agents.forEach(function(agent){
2242
+ if(order.indexOf(agent.name)>=0)return;
2243
+ if(allowedFiles&&!allowedFiles[agent.name])return;
2244
+ var ml=MODEL_LABELS[agent.model]||agent.model;
2245
+ h+='<button class="agent-tab'+(selected===agent.name?" active":"")+'" onclick="showAgentPrompt(\''+agent.name+'\')">';
2246
+ h+=esc(agent.name)+'<span class="tab-model">'+ml+'</span></button>';
2247
+ });
2248
+ }
2249
+
2285
2250
  container.innerHTML=h;
2286
2251
  activeAgentTab=selected;
2287
2252
  showAgentPrompt(selected);
@@ -2292,7 +2257,10 @@ function showAgentPrompt(name){
2292
2257
  // Update tab active state
2293
2258
  document.querySelectorAll(".agent-tab").forEach(function(t){t.classList.remove("active");});
2294
2259
  document.querySelectorAll(".agent-tab").forEach(function(t){
2295
- if((name==="_project"&&t.textContent.indexOf("Project Context")>=0)||t.textContent.indexOf(name)===0) t.classList.add("active");
2260
+ var da=t.getAttribute("data-agent");
2261
+ if(da===name) { t.classList.add("active"); return; }
2262
+ if(name==="_project"&&t.textContent.indexOf("Project Context")>=0) t.classList.add("active");
2263
+ else if(!da&&t.textContent.indexOf(name)===0) t.classList.add("active");
2296
2264
  });
2297
2265
  var view=document.getElementById("agentPromptView");
2298
2266
  if(name==="_project"){
@@ -2307,7 +2275,12 @@ function showAgentPrompt(name){
2307
2275
  var h='<div class="agent-meta">';
2308
2276
  h+='<div class="agent-meta-item"><strong>Agent:</strong>'+esc(agent.name)+'</div>';
2309
2277
  h+='<div class="agent-meta-item" style="border-left:2px solid '+mc+'"><strong>Model:</strong>'+esc(agent.model)+'</div>';
2310
- h+='<div class="agent-meta-item"><strong>파일:</strong>.claude/agents/'+esc(agent.name)+'.md</div>';
2278
+ if(agent.project){
2279
+ h+='<div class="agent-meta-item"><strong>Order:</strong>#'+esc(String(agent.order||0))+'</div>';
2280
+ if(agent.role) h+='<div class="agent-meta-item" style="grid-column:1/-1"><strong>Role:</strong>'+esc(agent.role)+'</div>';
2281
+ } else {
2282
+ h+='<div class="agent-meta-item"><strong>파일:</strong>.claude/agents/'+esc(agent.name)+'.md</div>';
2283
+ }
2311
2284
  h+='</div>';
2312
2285
  h+='<div class="prompt-section"><div class="prompt-section-label">Agent Role Prompt</div><pre>'+esc(agent.prompt)+'</pre></div>';
2313
2286
  view.innerHTML=h;
@@ -2433,10 +2406,17 @@ function rebuildFilterButtons(){
2433
2406
  pTasks.forEach(function(t){var a=guessAgent(t);counts[a]=(counts[a]||0)+1;});
2434
2407
  var container=document.getElementById("filterBtns");
2435
2408
  var html='<button class="'+(filter==="all"?"active":"")+'" data-filter="all">All ('+pTasks.length+')</button>';
2436
- var order=["orchestrator","frontend","backend","qa","devops","researcher","strategist","designer","content","itemauthor","claude","manual","unknown"];
2437
- order.forEach(function(key){
2438
- if(!counts[key])return;
2439
- var agent=AGENTS[key]||AGENTS.unknown;
2409
+ var knownOrder=["orchestrator","frontend","backend","qa","devops","researcher","strategist","designer","content","itemauthor","claude","manual"];
2410
+ var keys=Object.keys(counts);
2411
+ keys.sort(function(a,b){
2412
+ var ia=knownOrder.indexOf(a),ib=knownOrder.indexOf(b);
2413
+ if(ia===-1&&ib===-1)return a.localeCompare(b);
2414
+ if(ia===-1)return 1;if(ib===-1)return -1;
2415
+ return ia-ib;
2416
+ });
2417
+ keys.forEach(function(key){
2418
+ if(key==="unknown"&&keys.length>1&&!counts[key])return;
2419
+ var agent=getAgentEntry(key);
2440
2420
  html+='<button class="'+(filter===key?"active":"")+'" data-filter="'+key+'" style="border-bottom:2px solid '+(filter===key?agent.color:"transparent")+'">'+agent.label+' ('+counts[key]+')</button>';
2441
2421
  });
2442
2422
  container.innerHTML=html;
@@ -2445,6 +2425,7 @@ function render(){
2445
2425
  renderProjectTabs();
2446
2426
  rebuildFilterButtons();
2447
2427
  var projectTasks=currentProject?allTasks.filter(function(t){return t.project===currentProject;}):allTasks;
2428
+ window._allTasks=projectTasks;
2448
2429
  var filtered=filter==="all"?projectTasks:projectTasks.filter(function(t){return guessAgent(t)===filter;});
2449
2430
  var total=projectTasks.length;
2450
2431
  var active=projectTasks.filter(function(t){return getCol(t)==="in_progress";}).length;
@@ -2493,13 +2474,11 @@ function render(){
2493
2474
  var c=board.querySelector('.card[data-id="'+eid+'"]');
2494
2475
  if(c)c.classList.add("expanded");
2495
2476
  });
2496
- // v3: update filter agent dropdown + apply filters
2497
- updateFilterAgentDropdown();
2498
- applyFilters();
2477
+ updateBoardTop();
2499
2478
  }
2500
2479
 
2501
2480
  function renderCard(t,isParent,children,isSub){
2502
- var agentKey=guessAgent(t),agent=AGENTS[agentKey]||AGENTS.unknown;
2481
+ var agentKey=guessAgent(t),agent=getAgentEntry(agentKey);
2503
2482
  var pri=t.priority||"medium",blocked=isBlocked(t);
2504
2483
  var id=t.id||t._file||"",title=t.subject||t.title||"Untitled";
2505
2484
  var desc=t.description||"",activeForm=t.activeForm||"";
@@ -2507,8 +2486,8 @@ function renderCard(t,isParent,children,isSub){
2507
2486
  var sessionShort=(t._session||"").slice(0,8),elapsed=getElapsed(t);
2508
2487
  var editable=t._editable,draggable=!blocked;
2509
2488
  children=children||[];
2510
- var cls="card"+(blocked?" blocked":"")+(editable?"":" readonly")+(isParent?" parent":"")+(isSub?" subtask":"")+(t._stale?" stale":"");
2511
- var h='<div class="'+cls+'" data-id="'+esc(id)+'" data-agent="'+esc(agentKey)+'" data-priority="'+esc(pri)+'" data-status="'+esc(getCol(t))+'" data-stale="'+(t._stale?"1":"0")+'"'+(draggable?' draggable="true" ondragstart="onDragStart(event)" ondragend="onDragEnd(event)"':'')+' style="border-left-color:'+agent.color+'">';
2489
+ var cls="card"+(blocked?" blocked":"")+(editable?"":" readonly")+(isParent?" parent":"")+(isSub?" subtask":"");
2490
+ var h='<div class="'+cls+'" data-id="'+esc(id)+'"'+(draggable?' draggable="true" ondragstart="onDragStart(event)" ondragend="onDragEnd(event)"':'')+' style="border-left-color:'+agent.color+'">';
2512
2491
  h+='<div class="card-actions">';
2513
2492
  if(editable) h+='<button title="Edit" onclick="event.stopPropagation();editTask(\''+esc(id)+'\')">&#9998;</button>';
2514
2493
  h+='<button class="del" title="Delete" onclick="event.stopPropagation();quickDelete(\''+esc(id)+'\')">&#10005;</button>';
@@ -2522,18 +2501,19 @@ function renderCard(t,isParent,children,isSub){
2522
2501
  if(activeForm&&getCol(t)==="in_progress") h+='<div class="active-form-inline"><div class="spinner"></div>'+esc(activeForm)+'</div>';
2523
2502
  // Exec indicator
2524
2503
  if(getCol(t)==="in_progress"&&String(t.id)===String(currentExecId)) h+='<div class="exec-indicator"><div class="spinner"></div>Executing...</div>';
2525
- // Zombie/stale indicator
2526
- if(t._stale&&getCol(t)==="in_progress") h+='<div class="stale-badge">STALE<span class="stale-reason">'+(t._staleReason==="process_dead"?" — process dead":" — no activity 30min")+'</span></div>';
2527
2504
  // Review badge (collapsed)
2528
2505
  if(getCol(t)==="in_review") h+='<div style="font-size:10px;color:var(--vl);margin-top:2px;font-weight:600;">Awaiting Review</div>';
2529
2506
  // Expanded body
2530
2507
  h+='<div class="card-body">';
2531
2508
  if(activeForm&&getCol(t)==="in_progress") h+='<div class="active-form"><div class="spinner"></div>'+esc(activeForm)+'</div>';
2509
+
2510
+ // ── Header row: agent + status ──
2532
2511
  h+='<div class="agent-chip"><span class="agent-dot" style="background:'+agent.color+'"></span>'+esc(agent.label);
2533
2512
  if(owner) h+=' / '+esc(owner);
2534
2513
  if(blocked) h+=' / <span style="color:var(--rd)">blocked</span>';
2535
2514
  h+='</div>';
2536
- if(elapsed) h+='<div class="time">'+elapsed+'</div>';
2515
+
2516
+ // ── Subtask bar ──
2537
2517
  if(isParent&&children.length>0){
2538
2518
  var doneCount=children.filter(function(c){return getCol(c)==="completed";}).length;
2539
2519
  var inpCount=children.filter(function(c){return getCol(c)==="in_progress";}).length;
@@ -2543,12 +2523,67 @@ function renderCard(t,isParent,children,isSub){
2543
2523
  if(inpCount>0) h+=inpCount+' active';
2544
2524
  h+='</span></div>';
2545
2525
  }
2546
- if(t.blockedBy&&t.blockedBy.length) h+='<div class="deps">blocked by '+t.blockedBy.join(", ")+'</div>';
2547
- if(t.blocks&&t.blocks.length) h+='<div class="deps" style="color:var(--am)">blocks '+t.blocks.join(", ")+'</div>';
2548
- if(sessionShort&&!editable) h+='<div class="session">'+sessionShort+'</div>';
2549
- if(desc) h+='<div class="desc" style="max-height:200px;">'+esc(desc)+'</div>';
2526
+
2527
+ // ── Dependencies section ──
2528
+ if((t.blockedBy&&t.blockedBy.length)||(t.blocks&&t.blocks.length)){
2529
+ h+='<div class="card-section">';
2530
+ h+='<div class="card-section-label"><span class="dot" style="background:var(--rd)"></span>Dependencies</div>';
2531
+ h+='<div class="card-section-body">';
2532
+ if(t.blockedBy&&t.blockedBy.length){
2533
+ h+='<div style="margin-bottom:4px;font-size:9px;color:var(--t4);text-transform:uppercase">Blocked by</div>';
2534
+ var allT=window._allTasks||[];
2535
+ t.blockedBy.forEach(function(depId){
2536
+ var dep=allT.find(function(d){return String(d.id)===String(depId);});
2537
+ var depCol=dep?getCol(dep):"pending";
2538
+ var dotColor=depCol==="completed"?"var(--gn)":depCol==="in_progress"?"var(--am)":"var(--t4)";
2539
+ var depTitle=dep?(dep.subject||dep.title||""):"";
2540
+ if(depTitle.length>40)depTitle=depTitle.slice(0,40)+"…";
2541
+ h+='<span class="card-dep-chip" onclick="event.stopPropagation();openTaskDetail(\''+esc(String(depId))+'\')">';
2542
+ h+='<span class="dep-dot" style="background:'+dotColor+'"></span>#'+esc(String(depId));
2543
+ if(depTitle) h+=' '+esc(depTitle);
2544
+ h+='</span>';
2545
+ });
2546
+ }
2547
+ if(t.blocks&&t.blocks.length){
2548
+ h+='<div style="margin-top:4px;margin-bottom:4px;font-size:9px;color:var(--t4);text-transform:uppercase">Blocks</div>';
2549
+ t.blocks.forEach(function(bId){
2550
+ h+='<span class="card-dep-chip" onclick="event.stopPropagation();openTaskDetail(\''+esc(String(bId))+'\')">';
2551
+ h+='<span class="dep-dot" style="background:var(--am)"></span>#'+esc(String(bId));
2552
+ h+='</span>';
2553
+ });
2554
+ }
2555
+ h+='</div></div>';
2556
+ }
2557
+
2558
+ // ── Plan / Description section ──
2559
+ if(desc){
2560
+ h+='<div class="card-section">';
2561
+ h+='<div class="card-section-label"><span class="dot" style="background:var(--ac)"></span>작업 계획</div>';
2562
+ h+='<div class="card-section-body">'+formatDesc(desc)+'</div>';
2563
+ h+='</div>';
2564
+ }
2565
+
2566
+ // ── Report / Result section ──
2567
+ if(t.reportSummary){
2568
+ h+='<div class="card-section">';
2569
+ h+='<div class="card-section-label"><span class="dot" style="background:var(--gn)"></span>작업 결과</div>';
2570
+ h+='<div class="card-section-body"><div class="card-report-summary">'+formatDesc(t.reportSummary)+'</div></div>';
2571
+ h+='</div>';
2572
+ }
2573
+
2574
+ // ── Timeline ──
2575
+ var hasTimes=t.createdAt||t.startedAt||t.completedAt;
2576
+ if(hasTimes||elapsed){
2577
+ h+='<div class="card-timeline">';
2578
+ if(t.createdAt) h+='<span><span class="tl-label">Created</span>'+new Date(t.createdAt).toLocaleString("ko-KR",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})+'</span>';
2579
+ if(t.startedAt) h+='<span><span class="tl-label">Started</span>'+new Date(t.startedAt).toLocaleString("ko-KR",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})+'</span>';
2580
+ if(t.completedAt) h+='<span><span class="tl-label">Done</span>'+new Date(t.completedAt).toLocaleString("ko-KR",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})+'</span>';
2581
+ if(elapsed) h+='<span><span class="tl-label">Elapsed</span>'+elapsed+'</span>';
2582
+ h+='</div>';
2583
+ }
2584
+
2585
+ // ── Review actions ──
2550
2586
  if(getCol(t)==="in_review"){
2551
- if(t.reportSummary) h+='<div class="desc" style="max-height:120px;margin-top:6px;border-top:1px solid var(--b1);padding-top:6px;font-size:11px;color:var(--t3);">'+esc(t.reportSummary)+'</div>';
2552
2587
  h+='<div class="review-actions">';
2553
2588
  h+='<button class="review-btn approve" onclick="event.stopPropagation();approveTask(\''+esc(id)+'\')">Approve</button>';
2554
2589
  h+='<button class="review-btn reject" onclick="event.stopPropagation();rejectTask(\''+esc(id)+'\')">Reject</button>';
@@ -2558,13 +2593,6 @@ function renderCard(t,isParent,children,isSub){
2558
2593
  if(getCol(t)==="completed"&&(t.reportPath||t.reportSummary)){
2559
2594
  h+='<button class="report-btn" onclick="event.stopPropagation();openReport(\''+esc(id)+'\')">View Report</button>';
2560
2595
  }
2561
- // Zombie recover button
2562
- if(t._stale&&getCol(t)==="in_progress"){
2563
- h+='<div class="stale-actions">';
2564
- h+='<button class="review-btn reject" onclick="event.stopPropagation();recoverTask(\''+esc(id)+'\')">Recover (→ pending)</button>';
2565
- h+='<button class="review-btn approve" onclick="event.stopPropagation();rerunTask(\''+esc(id)+'\')">Re-run</button>';
2566
- h+='</div>';
2567
- }
2568
2596
  h+='</div>';
2569
2597
  h+='</div>';
2570
2598
  return h;
@@ -2577,14 +2605,6 @@ function quickDelete(id){
2577
2605
  if(!confirm("Delete #"+id+"?"))return;
2578
2606
  apiDelete(id).then(function(){updateLive("Deleted #"+id);});
2579
2607
  }
2580
- function recoverTask(id){
2581
- fetch("/api/tasks/"+id+"/recover",{method:"POST"}).then(function(){updateLive("Recovered #"+id);});
2582
- }
2583
- function rerunTask(id){
2584
- fetch("/api/tasks/"+id+"/recover",{method:"POST"}).then(function(){
2585
- return fetch("/api/exec",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:id})});
2586
- }).then(function(){updateLive("Re-running #"+id);});
2587
- }
2588
2608
 
2589
2609
  document.addEventListener("click",function(e){
2590
2610
  var card=e.target.closest(".card");
@@ -2606,11 +2626,20 @@ function getCol(t){
2606
2626
  if(s==="done"||s==="completed"||s==="finished")return "completed";
2607
2627
  return "pending";
2608
2628
  }
2609
- function isBlocked(t){return t.blockedBy&&t.blockedBy.length>0&&getCol(t)!=="completed";}
2629
+ function isBlocked(t){
2630
+ if(!t.blockedBy||t.blockedBy.length===0||getCol(t)==="completed")return false;
2631
+ // Only blocked if any dependency is actually not completed
2632
+ var allTasks=window._allTasks||[];
2633
+ for(var i=0;i<t.blockedBy.length;i++){
2634
+ var depId=String(t.blockedBy[i]);
2635
+ var dep=allTasks.find(function(d){return String(d.id)===depId;});
2636
+ if(!dep||getCol(dep)!=="completed")return true;
2637
+ }
2638
+ return false;
2639
+ }
2610
2640
  function guessAgent(t){
2611
- if(t.agent&&AGENTS[t.agent])return t.agent;
2612
- if(t.agent==="claude"||(!t.agent&&t._session&&!t._editable))return "claude";
2613
- if(t.agent==="manual")return "manual";
2641
+ if(t.agent&&t.agent.length>0)return t.agent;
2642
+ if(!t.agent&&t._session&&!t._editable)return "claude";
2614
2643
  var name=(t.owner||t.agentName||t.teammate||t.assignee||"").toLowerCase();
2615
2644
  var subj=(t.subject||"").toLowerCase();
2616
2645
  var combined=name+" "+subj;
@@ -2636,6 +2665,53 @@ function getElapsed(t){
2636
2665
  }
2637
2666
  function esc(s){return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;");}
2638
2667
 
2668
+ function formatDesc(raw){
2669
+ var s=esc(raw);
2670
+ // Split by newlines
2671
+ var lines=s.split(/\n/);
2672
+ var html="",inList=false;
2673
+ for(var i=0;i<lines.length;i++){
2674
+ var line=lines[i].trim();
2675
+ if(!line){
2676
+ if(inList){html+="</ul>";inList=false;}
2677
+ continue;
2678
+ }
2679
+ // Numbered list: "1. xxx" or "1) xxx"
2680
+ var numMatch=line.match(/^(\d+)[.)]\s+(.+)/);
2681
+ // Bullet: "- xxx" or "• xxx" or "* xxx"
2682
+ var bulletMatch=!numMatch&&line.match(/^[-•*]\s+(.+)/);
2683
+ // Section header: ends with ":" or starts with "[" or "##" or all caps short line
2684
+ var isHeader=!numMatch&&!bulletMatch&&(
2685
+ (line.length<60&&line.match(/[:]$/)) ||
2686
+ line.match(/^#{1,3}\s/) ||
2687
+ line.match(/^\[.+\]$/)
2688
+ );
2689
+ // Key-value: "Key: Value" pattern
2690
+ var kvMatch=!numMatch&&!bulletMatch&&!isHeader&&line.match(/^([^:]{2,30}):\s+(.+)/);
2691
+
2692
+ if(numMatch||bulletMatch){
2693
+ if(!inList){html+="<ul>";inList=true;}
2694
+ var content=numMatch?numMatch[2]:bulletMatch[1];
2695
+ // Bold **text** patterns
2696
+ content=content.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>");
2697
+ html+="<li>"+content+"</li>";
2698
+ } else {
2699
+ if(inList){html+="</ul>";inList=false;}
2700
+ if(isHeader){
2701
+ var hText=line.replace(/^#{1,3}\s/,"").replace(/[:]$/,"").replace(/^\[|\]$/g,"");
2702
+ html+='<div style="font-size:10px;font-weight:700;color:var(--t1);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.03em;">'+hText+'</div>';
2703
+ } else if(kvMatch){
2704
+ html+='<div class="line-item"><span style="color:var(--t3);font-size:10px;">'+kvMatch[1]+'</span> <span style="color:var(--t1);">'+kvMatch[2].replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>")+'</span></div>';
2705
+ } else {
2706
+ line=line.replace(/\*\*(.+?)\*\*/g,"<strong style='color:var(--t1)'>$1</strong>");
2707
+ html+="<div>"+line+"</div>";
2708
+ }
2709
+ }
2710
+ }
2711
+ if(inList) html+="</ul>";
2712
+ return html;
2713
+ }
2714
+
2639
2715
  document.getElementById("toolbar").addEventListener("click",function(e){
2640
2716
  if(e.target.tagName!=="BUTTON"||!e.target.dataset.filter)return;
2641
2717
  filter=e.target.dataset.filter;
@@ -2675,8 +2751,6 @@ function connectSSE(){
2675
2751
  var data=JSON.parse(e.data);
2676
2752
  if(data.type==="activity"&&data.event){
2677
2753
  onActivityEvent(data.event);
2678
- } else if(data.type==="zombie"){
2679
- updateLive("Zombie detected: #"+data.taskId);
2680
2754
  } else if(data.type==="exec_start"){
2681
2755
  onExecStart(data);
2682
2756
  } else if(data.type==="exec"){
@@ -2710,12 +2784,17 @@ function updateLive(msg){
2710
2784
  document.getElementById("liveMsg").textContent=msg;
2711
2785
  document.getElementById("liveTime").textContent=new Date().toLocaleTimeString("ko-KR",{hour:"2-digit",minute:"2-digit",second:"2-digit"});
2712
2786
  }
2713
- function clearDone(){
2787
+ function archiveDone(){
2714
2788
  var pTasks=getProjectTasks();
2715
2789
  var done=pTasks.filter(function(t){return getCol(t)==="completed"&&t._editable;});
2716
- if(!done.length)return;
2717
- if(!confirm(done.length+" completed tasks will be deleted."))return;
2718
- Promise.all(done.map(function(t){return apiDelete(t.id);})).then(function(){updateLive("Cleared "+done.length+" tasks");});
2790
+ if(!done.length){updateLive("No completed tasks to archive");return;}
2791
+ if(!confirm(done.length+" completed tasks will be archived."))return;
2792
+ fetch("/api/archive",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:currentProject})}).then(function(r){return r.json();}).then(function(data){
2793
+ if(data.ok){
2794
+ updateLive("Archived "+data.count+" tasks → "+data.date);
2795
+ if(archiveOpen)loadArchiveList();
2796
+ }else{updateLive("Archive error: "+(data.error||"unknown"));}
2797
+ }).catch(function(e){updateLive("Archive failed: "+e.message);});
2719
2798
  }
2720
2799
 
2721
2800
  document.addEventListener("keydown",function(e){
@@ -2723,8 +2802,9 @@ document.addEventListener("keydown",function(e){
2723
2802
  if(e.ctrlKey&&e.shiftKey&&(e.key==="K"||e.key==="k")){e.preventDefault();stopExecution();return;}
2724
2803
  if(e.target.closest("input,select,textarea"))return;
2725
2804
  if(e.key==="n"||e.key==="N")openModal();
2726
- if(e.key==="Escape"){if(tdCurrentId){closeTaskDetail();return;}closeModal();closeReport();closeActivity();closeChat();}
2805
+ if(e.key==="Escape"){if(tdCurrentId){closeTaskDetail();return;}closeModal();closeReport();closeActivity();closeArchive();closeChat();}
2727
2806
  if(e.key==="a"||e.key==="A")toggleActivity();
2807
+ if(e.key==="r"||e.key==="R")toggleArchive();
2728
2808
  if(e.key==="c"||e.key==="C")toggleChat();
2729
2809
  });
2730
2810
  document.getElementById("modalOverlay").addEventListener("click",function(e){if(e.target===e.currentTarget)closeModal();});
@@ -2851,16 +2931,8 @@ function renderTaskDetail(t){
2851
2931
  html+='<div style="font-size:11px;font-family:JetBrains Mono,monospace;color:var(--t4);line-height:1.8">'+times.join("<br>")+'</div>';
2852
2932
  html+='</div>';
2853
2933
  }
2854
- // Comments section
2855
- html+='<div class="td-section"><div class="td-section-label">Comments</div>';
2856
- html+='<div id="tdComments"><div style="color:var(--t4);font-size:11px">Loading...</div></div>';
2857
- html+='<div class="td-comment-form">';
2858
- html+='<textarea id="tdCommentInput" placeholder="Add a comment..." rows="2" style="width:100%;background:var(--bg);border:1px solid var(--b1);border-radius:var(--r);padding:8px 10px;color:var(--t1);font-size:12px;font-family:inherit;resize:vertical"></textarea>';
2859
- html+='<button class="btn btn-primary" style="margin-top:6px;font-size:11px;padding:5px 12px" onclick="addComment()">Post</button>';
2860
- html+='</div></div>';
2861
2934
  if(!html)html='<div class="td-empty">No details available</div>';
2862
2935
  document.getElementById("tdBody").innerHTML=html;
2863
- loadComments(String(t.id));
2864
2936
  // Footer buttons
2865
2937
  document.getElementById("tdEditBtn").style.display=t._editable!==false?"inline-flex":"none";
2866
2938
  }
@@ -2869,29 +2941,6 @@ function closeTaskDetail(){
2869
2941
  document.getElementById("tdPopup").classList.remove("open");
2870
2942
  tdCurrentId=null;
2871
2943
  }
2872
- function loadComments(id){
2873
- fetch("/api/tasks/"+id+"/comments").then(function(r){return r.json();}).then(function(comments){
2874
- var el=document.getElementById("tdComments");
2875
- if(!el)return;
2876
- if(!comments||comments.length===0){el.innerHTML='<div style="color:var(--t4);font-size:11px">No comments yet</div>';return;}
2877
- el.innerHTML=comments.map(function(c){
2878
- return '<div style="padding:6px 0;border-bottom:1px solid var(--b1);font-size:12px;line-height:1.6">'+
2879
- '<div style="display:flex;justify-content:space-between"><strong style="color:var(--t1);font-size:11px">'+esc(c.author)+'</strong>'+
2880
- '<span style="font-size:9px;color:var(--t4);font-family:JetBrains Mono,monospace">'+new Date(c.createdAt).toLocaleString("ko-KR")+'</span></div>'+
2881
- '<div style="color:var(--t2);margin-top:2px">'+renderMarkdown(c.text)+'</div></div>';
2882
- }).join("");
2883
- }).catch(function(){});
2884
- }
2885
- function addComment(){
2886
- if(!tdCurrentId)return;
2887
- var input=document.getElementById("tdCommentInput");
2888
- var text=(input.value||"").trim();
2889
- if(!text)return;
2890
- fetch("/api/tasks/"+tdCurrentId+"/comments",{
2891
- method:"POST",headers:{"Content-Type":"application/json"},
2892
- body:JSON.stringify({author:"user",text:text})
2893
- }).then(function(){input.value="";loadComments(tdCurrentId);}).catch(function(){});
2894
- }
2895
2944
 
2896
2945
  function renderMarkdown(md){
2897
2946
  var html=esc(md);
@@ -2932,7 +2981,7 @@ function renderMarkdown(md){
2932
2981
 
2933
2982
  // ── Activity Panel ──
2934
2983
  var activityEvents=[], activityUnread=0, activityOpen=false;
2935
- var ACT_ICONS={created:"🆕",started:"▶️",completed:"✅",updated:"📝",deleted:"🗑️"};
2984
+ var ACT_ICONS={created:"🆕",started:"▶️",completed:"✅",updated:"📝",deleted:"🗑️",archived:"📦"};
2936
2985
 
2937
2986
  function toggleActivity(){
2938
2987
  if(activityOpen){closeActivity();}else{openActivity();}
@@ -3027,6 +3076,140 @@ function onActivityEvent(evt){
3027
3076
  var icon=ACT_ICONS[evt.type]||"";
3028
3077
  updateLive(icon+" #"+evt.taskId+" "+evt.type+": "+(evt.subject||""));
3029
3078
  }
3079
+ // ── Archive Panel ──
3080
+ var archiveOpen=false, archiveList=[], archiveSelectedDate=null, archiveData=null;
3081
+
3082
+ function toggleArchive(){if(archiveOpen)closeArchive();else openArchive();}
3083
+ function openArchive(){
3084
+ archiveOpen=true;
3085
+ document.getElementById("archPanel").classList.add("open");
3086
+ document.getElementById("archOverlay").classList.add("open");
3087
+ loadArchiveList();
3088
+ }
3089
+ function closeArchive(){
3090
+ archiveOpen=false;
3091
+ document.getElementById("archPanel").classList.remove("open");
3092
+ document.getElementById("archOverlay").classList.remove("open");
3093
+ }
3094
+ function loadArchiveList(){
3095
+ var proj=currentProject||(projects.length?projects[0].id:"apex");
3096
+ fetch("/api/archives?project="+encodeURIComponent(proj)).then(function(r){return r.json();}).then(function(list){
3097
+ archiveList=list;
3098
+ renderArchiveDates();
3099
+ if(list.length>0)loadArchiveDay(list[0].date);
3100
+ else{archiveData=null;archiveSelectedDate=null;renderArchiveContent();}
3101
+ }).catch(function(){});
3102
+ }
3103
+ function renderArchiveDates(){
3104
+ var container=document.getElementById("archDates");
3105
+ if(!archiveList.length){container.innerHTML='';return;}
3106
+ var today=new Date().toISOString().slice(0,10);
3107
+ var yesterday=new Date(Date.now()-86400000).toISOString().slice(0,10);
3108
+ container.innerHTML=archiveList.map(function(item){
3109
+ var label=item.date;
3110
+ if(item.date===today)label="Today";
3111
+ else if(item.date===yesterday)label="Yesterday";
3112
+ else{var d=new Date(item.date+"T00:00:00");label=(d.getMonth()+1)+"/"+ d.getDate();}
3113
+ var active=item.date===archiveSelectedDate?" active":"";
3114
+ return '<button class="arch-date-btn'+active+'" onclick="loadArchiveDay(\''+item.date+'\')">'+label+'<span class="arch-count">'+item.count+'</span></button>';
3115
+ }).join("");
3116
+ }
3117
+ function loadArchiveDay(date){
3118
+ archiveSelectedDate=date;
3119
+ renderArchiveDates();
3120
+ var proj=currentProject||(projects.length?projects[0].id:"apex");
3121
+ fetch("/api/archives/"+date+"?project="+encodeURIComponent(proj)).then(function(r){return r.json();}).then(function(data){
3122
+ archiveData=data;
3123
+ renderArchiveContent();
3124
+ }).catch(function(){});
3125
+ }
3126
+ function renderArchiveContent(){
3127
+ var container=document.getElementById("archContent");
3128
+ if(!archiveData||!archiveData.tasks||archiveData.tasks.length===0){
3129
+ container.innerHTML='<div class="arch-empty">No archived tasks</div>';
3130
+ return;
3131
+ }
3132
+ var tasks=archiveData.tasks;
3133
+ var taskMap={};
3134
+ tasks.forEach(function(t){taskMap[t.id]=t;});
3135
+
3136
+ // Categorize: parents, children, orphans, standalone
3137
+ var parents=[], children={}, orphans=[], standalone=[];
3138
+ tasks.forEach(function(t){
3139
+ if(t.children&&t.children.length>0){
3140
+ parents.push(t);
3141
+ }else if(t.parentId){
3142
+ if(taskMap[t.parentId]){
3143
+ if(!children[t.parentId])children[t.parentId]=[];
3144
+ children[t.parentId].push(t);
3145
+ }else{
3146
+ orphans.push(t);
3147
+ }
3148
+ }else{
3149
+ standalone.push(t);
3150
+ }
3151
+ });
3152
+
3153
+ var html='';
3154
+ // Stats
3155
+ html+='<div class="arch-stats">'+archiveData.tasks.length+' tasks';
3156
+ if(archiveData.stats&&archiveData.stats.agents&&archiveData.stats.agents.length){
3157
+ html+=' &middot; agents: '+archiveData.stats.agents.join(", ");
3158
+ }
3159
+ if(archiveData.archivedAt){
3160
+ html+=' &middot; '+new Date(archiveData.archivedAt).toLocaleString("ko-KR");
3161
+ }
3162
+ html+='</div>';
3163
+
3164
+ // Render parent trees
3165
+ parents.forEach(function(p){
3166
+ html+=renderArchiveTask(p,"parent");
3167
+ var kids=children[p.id]||[];
3168
+ kids.forEach(function(c){html+=renderArchiveTask(c,"child");});
3169
+ });
3170
+
3171
+ // Standalone
3172
+ standalone.forEach(function(t){html+=renderArchiveTask(t,"");});
3173
+
3174
+ // Orphans
3175
+ if(orphans.length>0){
3176
+ html+='<div class="arch-section-label">Orphan subtasks</div>';
3177
+ orphans.forEach(function(t){html+=renderArchiveTask(t,"orphan");});
3178
+ }
3179
+
3180
+ container.innerHTML=html;
3181
+ }
3182
+ function renderArchiveTask(t,type){
3183
+ var cls="arch-task";
3184
+ if(type)cls+=" "+type;
3185
+ var h='<div class="'+cls+'">';
3186
+ h+='<div class="arch-task-header">';
3187
+ h+='<span class="arch-task-id">#'+esc(t.id)+'</span>';
3188
+ h+='<span class="arch-task-subject">'+esc(t.subject)+'</span>';
3189
+ if(t.priority==="high"||t.priority==="critical"){
3190
+ h+='<span class="arch-task-priority '+esc(t.priority)+'">'+esc(t.priority)+'</span>';
3191
+ }
3192
+ h+='</div>';
3193
+ h+='<div class="arch-task-meta">';
3194
+ if(t.agent)h+='<span>'+esc(t.agent)+'</span>';
3195
+ if(t.completedAt)h+='<span>'+new Date(t.completedAt).toLocaleTimeString("ko-KR",{hour:"2-digit",minute:"2-digit"})+'</span>';
3196
+ if(t.elapsedSec!==null&&t.elapsedSec!==undefined){
3197
+ var dur=t.elapsedSec;
3198
+ var durStr;
3199
+ if(dur<60)durStr=dur+"s";
3200
+ else if(dur<3600)durStr=Math.floor(dur/60)+"m "+dur%60+"s";
3201
+ else durStr=Math.floor(dur/3600)+"h "+Math.floor((dur%3600)/60)+"m";
3202
+ h+='<span>'+durStr+'</span>';
3203
+ }
3204
+ if(type==="child"&&t.parentId)h+='<span class="arch-task-parent-ref">&larr; #'+esc(t.parentId)+'</span>';
3205
+ if(type==="orphan"&&t.parentId)h+='<span class="arch-task-parent-ref">&larr; #'+esc(t.parentId)+' (missing)</span>';
3206
+ h+='</div>';
3207
+ var desc=t.reportSummary||(t.description?t.description.split("\n")[0]:"");
3208
+ if(desc)h+='<div class="arch-task-desc">'+esc(desc)+'</div>';
3209
+ h+='</div>';
3210
+ return h;
3211
+ }
3212
+
3030
3213
  // ── Chat Panel ──
3031
3214
  var chatMessages=[], chatOpen=false, chatStreaming=false;
3032
3215
 
@@ -3036,9 +3219,9 @@ function openChat(){
3036
3219
  var panel=document.getElementById("chatPanel");
3037
3220
  panel.classList.add("open");
3038
3221
  // restore saved size
3039
- var savedH=localStorage.getItem("kanban_chat_height");
3040
- var savedW=localStorage.getItem("kanban_chat_width");
3041
- var savedExp=localStorage.getItem("kanban_chat_expanded")==="1";
3222
+ var savedH=localStorage.getItem("apex_chat_height");
3223
+ var savedW=localStorage.getItem("apex_chat_width");
3224
+ var savedExp=localStorage.getItem("apex_chat_expanded")==="1";
3042
3225
  if(savedExp){chatExpanded=true;panel.classList.add("expanded");}
3043
3226
  else{
3044
3227
  if(savedH)panel.style.height=savedH;
@@ -3079,11 +3262,11 @@ function loadChatHistory(){
3079
3262
  }
3080
3263
  chatLoaded=true;
3081
3264
  }).catch(function(){chatLoaded=true;});
3082
- var savedModel=localStorage.getItem("kanban_chat_model");
3265
+ var savedModel=localStorage.getItem("apex_chat_model");
3083
3266
  if(savedModel){var sel=document.getElementById("chatModel");if(sel)sel.value=savedModel;}
3084
3267
  }
3085
3268
  function onChatModelChange(el){
3086
- localStorage.setItem("kanban_chat_model",el.value);
3269
+ localStorage.setItem("apex_chat_model",el.value);
3087
3270
  fetch("/api/chat/session",{method:"DELETE"}).catch(function(){});
3088
3271
  var badge=document.getElementById("chatSessionBadge");
3089
3272
  if(badge)badge.style.display="none";
@@ -3100,7 +3283,7 @@ function toggleChatExpand(){
3100
3283
  chatExpanded=!chatExpanded;
3101
3284
  panel.classList.toggle("expanded",chatExpanded);
3102
3285
  if(chatExpanded){panel.style.height="";panel.style.width="";}
3103
- localStorage.setItem("kanban_chat_expanded",chatExpanded?"1":"0");
3286
+ localStorage.setItem("apex_chat_expanded",chatExpanded?"1":"0");
3104
3287
  }
3105
3288
  (function(){
3106
3289
  var panel=document.getElementById("chatPanel");
@@ -3120,7 +3303,7 @@ function toggleChatExpand(){
3120
3303
  function onUp(){
3121
3304
  hHandle.classList.remove("active");
3122
3305
  panel.style.transition="";
3123
- localStorage.setItem("kanban_chat_height",panel.style.height);
3306
+ localStorage.setItem("apex_chat_height",panel.style.height);
3124
3307
  document.removeEventListener("mousemove",onMove);
3125
3308
  document.removeEventListener("mouseup",onUp);
3126
3309
  }
@@ -3143,7 +3326,7 @@ function toggleChatExpand(){
3143
3326
  function onUp(){
3144
3327
  wHandle.classList.remove("active");
3145
3328
  panel.style.transition="";
3146
- localStorage.setItem("kanban_chat_width",panel.style.width);
3329
+ localStorage.setItem("apex_chat_width",panel.style.width);
3147
3330
  document.removeEventListener("mousemove",onMove);
3148
3331
  document.removeEventListener("mouseup",onUp);
3149
3332
  }
@@ -3353,24 +3536,10 @@ function onExecError(data){
3353
3536
  }
3354
3537
 
3355
3538
  function approveTask(id){
3356
- var feedback=prompt("Approve #"+id+":\nOptional comment (leave empty to skip):");
3357
- if(feedback===null)return; // cancelled
3358
- var p=apiUpdate(id,{status:"completed"});
3359
- if(feedback.trim()){
3360
- p.then(function(){
3361
- return fetch("/api/tasks/"+id+"/comments",{method:"POST",headers:{"Content-Type":"application/json"},
3362
- body:JSON.stringify({author:"reviewer",text:"**Approved** — "+feedback.trim()})});
3363
- });
3364
- }
3365
- p.then(function(){updateLive("Approved #"+id);});
3539
+ apiUpdate(id,{status:"completed"}).then(function(){updateLive("Approved #"+id);});
3366
3540
  }
3367
3541
  function rejectTask(id){
3368
- var feedback=prompt("Reject #"+id+":\nReason / feedback (required):");
3369
- if(!feedback||!feedback.trim())return;
3370
- apiUpdate(id,{status:"pending"}).then(function(){
3371
- return fetch("/api/tasks/"+id+"/comments",{method:"POST",headers:{"Content-Type":"application/json"},
3372
- body:JSON.stringify({author:"reviewer",text:"**Rejected** — "+feedback.trim()})});
3373
- }).then(function(){updateLive("Rejected #"+id+" → pending with feedback");});
3542
+ apiUpdate(id,{status:"pending"}).then(function(){updateLive("Rejected #"+id+" moved to To Do");});
3374
3543
  }
3375
3544
 
3376
3545
  function stopExecution(){