@cluesmith/codev 1.4.0 → 1.4.2
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/bin/af.js +0 -0
- package/bin/codev.js +0 -0
- package/bin/consult.js +0 -0
- package/bin/generate-image.js +0 -0
- package/dist/agent-farm/servers/dashboard-server.js +487 -5
- package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +10 -0
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +8 -0
- package/dist/commands/init.js.map +1 -1
- package/package.json +1 -1
- package/skeleton/templates/projectlist-archive.md +21 -0
- package/skeleton/templates/projectlist.md +17 -0
- package/templates/dashboard/css/activity.css +151 -0
- package/templates/dashboard/css/dialogs.css +149 -0
- package/templates/dashboard/css/files.css +530 -0
- package/templates/dashboard/css/layout.css +124 -0
- package/templates/dashboard/css/projects.css +501 -0
- package/templates/dashboard/css/statusbar.css +23 -0
- package/templates/dashboard/css/tabs.css +314 -0
- package/templates/dashboard/css/utilities.css +50 -0
- package/templates/dashboard/css/variables.css +45 -0
- package/templates/dashboard/index.html +158 -0
- package/templates/dashboard/js/activity.js +238 -0
- package/templates/dashboard/js/dialogs.js +328 -0
- package/templates/dashboard/js/files.js +436 -0
- package/templates/dashboard/js/main.js +487 -0
- package/templates/dashboard/js/projects.js +544 -0
- package/templates/dashboard/js/state.js +91 -0
- package/templates/dashboard/js/tabs.js +500 -0
- package/templates/dashboard/js/utils.js +57 -0
- package/templates/dashboard-split.html +1186 -171
- package/templates/open.html +7 -2
|
@@ -651,6 +651,156 @@
|
|
|
651
651
|
color: #ef4444;
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
+
/* Activity Summary Modal (Spec 0059) */
|
|
655
|
+
.activity-dialog {
|
|
656
|
+
width: 600px;
|
|
657
|
+
max-width: 90vw;
|
|
658
|
+
max-height: 80vh;
|
|
659
|
+
display: flex;
|
|
660
|
+
flex-direction: column;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.activity-dialog-header {
|
|
664
|
+
display: flex;
|
|
665
|
+
justify-content: space-between;
|
|
666
|
+
align-items: center;
|
|
667
|
+
margin-bottom: 16px;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.activity-dialog-header h3 {
|
|
671
|
+
margin: 0;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.activity-close-btn {
|
|
675
|
+
background: none;
|
|
676
|
+
border: none;
|
|
677
|
+
font-size: 24px;
|
|
678
|
+
color: var(--text-muted);
|
|
679
|
+
cursor: pointer;
|
|
680
|
+
padding: 0 8px;
|
|
681
|
+
line-height: 1;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.activity-close-btn:hover {
|
|
685
|
+
color: var(--text-primary);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.activity-dialog-content {
|
|
689
|
+
flex: 1;
|
|
690
|
+
overflow-y: auto;
|
|
691
|
+
max-height: 50vh;
|
|
692
|
+
margin-bottom: 16px;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.activity-loading {
|
|
696
|
+
display: flex;
|
|
697
|
+
align-items: center;
|
|
698
|
+
justify-content: center;
|
|
699
|
+
gap: 12px;
|
|
700
|
+
padding: 40px 20px;
|
|
701
|
+
color: var(--text-muted);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.activity-spinner {
|
|
705
|
+
width: 20px;
|
|
706
|
+
height: 20px;
|
|
707
|
+
border: 2px solid var(--border);
|
|
708
|
+
border-top-color: var(--accent);
|
|
709
|
+
border-radius: 50%;
|
|
710
|
+
animation: spin 1s linear infinite;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
@keyframes spin {
|
|
714
|
+
to { transform: rotate(360deg); }
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.activity-empty {
|
|
718
|
+
text-align: center;
|
|
719
|
+
padding: 40px 20px;
|
|
720
|
+
color: var(--text-muted);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.activity-error {
|
|
724
|
+
text-align: center;
|
|
725
|
+
padding: 40px 20px;
|
|
726
|
+
color: #ef4444;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.activity-summary {
|
|
730
|
+
line-height: 1.6;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.activity-ai-summary {
|
|
734
|
+
background: var(--bg-tertiary);
|
|
735
|
+
border-left: 3px solid var(--accent);
|
|
736
|
+
padding: 12px 16px;
|
|
737
|
+
margin-bottom: 20px;
|
|
738
|
+
font-style: italic;
|
|
739
|
+
color: var(--text-secondary);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.activity-section {
|
|
743
|
+
margin-bottom: 16px;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.activity-section h4 {
|
|
747
|
+
font-size: 13px;
|
|
748
|
+
text-transform: uppercase;
|
|
749
|
+
color: var(--text-muted);
|
|
750
|
+
margin: 0 0 8px 0;
|
|
751
|
+
letter-spacing: 0.5px;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.activity-section ul {
|
|
755
|
+
margin: 0;
|
|
756
|
+
padding-left: 20px;
|
|
757
|
+
color: var(--text-secondary);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.activity-section li {
|
|
761
|
+
margin-bottom: 4px;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.activity-section p {
|
|
765
|
+
margin: 4px 0;
|
|
766
|
+
color: var(--text-secondary);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.activity-time-value {
|
|
770
|
+
font-size: 18px;
|
|
771
|
+
font-weight: 500;
|
|
772
|
+
color: var(--text-primary);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.activity-dialog-footer {
|
|
776
|
+
display: flex;
|
|
777
|
+
justify-content: flex-end;
|
|
778
|
+
gap: 8px;
|
|
779
|
+
padding-top: 12px;
|
|
780
|
+
border-top: 1px solid var(--border);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/* Activity Tab Styles (Spec 0059) */
|
|
784
|
+
.activity-tab-container {
|
|
785
|
+
padding: 24px;
|
|
786
|
+
max-width: 700px;
|
|
787
|
+
margin: 0 auto;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.activity-tab-container .activity-summary {
|
|
791
|
+
background: var(--bg-secondary);
|
|
792
|
+
border-radius: 8px;
|
|
793
|
+
padding: 20px;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
.activity-tab-container .activity-actions {
|
|
797
|
+
margin-top: 20px;
|
|
798
|
+
padding-top: 16px;
|
|
799
|
+
border-top: 1px solid var(--border);
|
|
800
|
+
display: flex;
|
|
801
|
+
justify-content: flex-end;
|
|
802
|
+
}
|
|
803
|
+
|
|
654
804
|
/* Projects Tab Styles (Spec 0045) */
|
|
655
805
|
.projects-container {
|
|
656
806
|
flex: 1;
|
|
@@ -1157,56 +1307,7 @@
|
|
|
1157
1307
|
display: none;
|
|
1158
1308
|
}
|
|
1159
1309
|
|
|
1160
|
-
/*
|
|
1161
|
-
.files-container {
|
|
1162
|
-
flex: 1;
|
|
1163
|
-
overflow-y: auto;
|
|
1164
|
-
display: flex;
|
|
1165
|
-
flex-direction: column;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
.files-header {
|
|
1169
|
-
display: flex;
|
|
1170
|
-
justify-content: space-between;
|
|
1171
|
-
align-items: center;
|
|
1172
|
-
padding: 8px 12px;
|
|
1173
|
-
background: var(--bg-secondary);
|
|
1174
|
-
border-bottom: 1px solid var(--border);
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
.files-header-title {
|
|
1178
|
-
font-size: 12px;
|
|
1179
|
-
color: var(--text-muted);
|
|
1180
|
-
text-transform: uppercase;
|
|
1181
|
-
letter-spacing: 0.5px;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
.files-header-actions {
|
|
1185
|
-
display: flex;
|
|
1186
|
-
gap: 4px;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
.files-header-actions button {
|
|
1190
|
-
padding: 4px 8px;
|
|
1191
|
-
border-radius: 4px;
|
|
1192
|
-
border: 1px solid var(--border);
|
|
1193
|
-
background: var(--bg-tertiary);
|
|
1194
|
-
color: var(--text-secondary);
|
|
1195
|
-
cursor: pointer;
|
|
1196
|
-
font-size: 11px;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
.files-header-actions button:hover {
|
|
1200
|
-
background: var(--tab-hover);
|
|
1201
|
-
color: var(--text-primary);
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
.files-tree {
|
|
1205
|
-
flex: 1;
|
|
1206
|
-
overflow-y: auto;
|
|
1207
|
-
padding: 8px 0;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1310
|
+
/* Tree Styles (used by dashboard file browser) */
|
|
1210
1311
|
.tree-item {
|
|
1211
1312
|
display: flex;
|
|
1212
1313
|
align-items: center;
|
|
@@ -1270,25 +1371,6 @@
|
|
|
1270
1371
|
display: none;
|
|
1271
1372
|
}
|
|
1272
1373
|
|
|
1273
|
-
.files-loading {
|
|
1274
|
-
display: flex;
|
|
1275
|
-
align-items: center;
|
|
1276
|
-
justify-content: center;
|
|
1277
|
-
padding: 24px;
|
|
1278
|
-
color: var(--text-muted);
|
|
1279
|
-
font-size: 13px;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
.files-error {
|
|
1283
|
-
padding: 16px;
|
|
1284
|
-
margin: 8px;
|
|
1285
|
-
background: rgba(239, 68, 68, 0.1);
|
|
1286
|
-
border: 1px solid var(--status-error);
|
|
1287
|
-
border-radius: 6px;
|
|
1288
|
-
color: var(--text-secondary);
|
|
1289
|
-
font-size: 13px;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
1374
|
/* Dashboard Tab Styles (Spec 0057) */
|
|
1293
1375
|
.dashboard-container {
|
|
1294
1376
|
flex: 1;
|
|
@@ -1298,8 +1380,7 @@
|
|
|
1298
1380
|
}
|
|
1299
1381
|
|
|
1300
1382
|
.dashboard-header {
|
|
1301
|
-
display:
|
|
1302
|
-
grid-template-columns: 1fr 1fr;
|
|
1383
|
+
display: flex;
|
|
1303
1384
|
gap: 16px;
|
|
1304
1385
|
padding: 16px;
|
|
1305
1386
|
flex-shrink: 0;
|
|
@@ -1307,10 +1388,118 @@
|
|
|
1307
1388
|
|
|
1308
1389
|
@media (max-width: 900px) {
|
|
1309
1390
|
.dashboard-header {
|
|
1310
|
-
|
|
1391
|
+
flex-direction: column;
|
|
1311
1392
|
}
|
|
1312
1393
|
}
|
|
1313
1394
|
|
|
1395
|
+
/* Collapsible section styles */
|
|
1396
|
+
.dashboard-section {
|
|
1397
|
+
background: var(--bg-secondary);
|
|
1398
|
+
border: 1px solid var(--border);
|
|
1399
|
+
border-radius: 8px;
|
|
1400
|
+
overflow: hidden;
|
|
1401
|
+
display: flex;
|
|
1402
|
+
flex-direction: column;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
.dashboard-section.section-tabs,
|
|
1406
|
+
.dashboard-section.section-files {
|
|
1407
|
+
flex: 1;
|
|
1408
|
+
max-height: 280px;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
.dashboard-section.section-projects {
|
|
1412
|
+
flex: 0 0 auto;
|
|
1413
|
+
margin: 0 16px 16px 16px;
|
|
1414
|
+
max-height: 50%;
|
|
1415
|
+
overflow-y: auto;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
.dashboard-section.section-projects .dashboard-section-content {
|
|
1419
|
+
flex: 0 0 auto;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/* Tabs/Files expand to fill remaining space above Projects */
|
|
1423
|
+
.dashboard-header {
|
|
1424
|
+
flex: 1;
|
|
1425
|
+
min-height: 0;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
.dashboard-section.section-tabs,
|
|
1429
|
+
.dashboard-section.section-files {
|
|
1430
|
+
max-height: none;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
.dashboard-section-header {
|
|
1434
|
+
display: flex;
|
|
1435
|
+
justify-content: space-between;
|
|
1436
|
+
align-items: center;
|
|
1437
|
+
padding: 8px 12px;
|
|
1438
|
+
cursor: pointer;
|
|
1439
|
+
user-select: none;
|
|
1440
|
+
flex-shrink: 0;
|
|
1441
|
+
border-bottom: 1px solid var(--border);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
.dashboard-section-header:hover {
|
|
1445
|
+
background: var(--bg-tertiary);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
.dashboard-section-header h3 {
|
|
1449
|
+
font-size: 12px;
|
|
1450
|
+
text-transform: uppercase;
|
|
1451
|
+
color: var(--text-muted);
|
|
1452
|
+
letter-spacing: 0.5px;
|
|
1453
|
+
margin: 0;
|
|
1454
|
+
display: flex;
|
|
1455
|
+
align-items: center;
|
|
1456
|
+
gap: 6px;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
.dashboard-section-header .collapse-icon {
|
|
1460
|
+
font-size: 10px;
|
|
1461
|
+
transition: transform 0.2s;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
.dashboard-section.collapsed .collapse-icon {
|
|
1465
|
+
transform: rotate(-90deg);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
.dashboard-section.collapsed .dashboard-section-header {
|
|
1469
|
+
border-bottom: none;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
.dashboard-section-header .header-actions {
|
|
1473
|
+
display: flex;
|
|
1474
|
+
gap: 4px;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
.dashboard-section-header .header-actions button {
|
|
1478
|
+
padding: 4px 8px;
|
|
1479
|
+
border-radius: 4px;
|
|
1480
|
+
border: 1px solid var(--border);
|
|
1481
|
+
background: var(--bg-tertiary);
|
|
1482
|
+
color: var(--text-secondary);
|
|
1483
|
+
cursor: pointer;
|
|
1484
|
+
font-size: 11px;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
.dashboard-section-header .header-actions button:hover {
|
|
1488
|
+
background: var(--tab-hover);
|
|
1489
|
+
color: var(--text-primary);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
.dashboard-section-content {
|
|
1493
|
+
flex: 1;
|
|
1494
|
+
overflow-y: auto;
|
|
1495
|
+
padding: 8px 12px;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
.dashboard-section.collapsed .dashboard-section-content {
|
|
1499
|
+
display: none;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
/* Legacy support */
|
|
1314
1503
|
.dashboard-column {
|
|
1315
1504
|
background: var(--bg-secondary);
|
|
1316
1505
|
border: 1px solid var(--border);
|
|
@@ -1437,17 +1626,183 @@
|
|
|
1437
1626
|
font-size: 12px;
|
|
1438
1627
|
}
|
|
1439
1628
|
|
|
1440
|
-
|
|
1629
|
+
/* File search styles (Spec 0058) */
|
|
1630
|
+
.files-search-container {
|
|
1631
|
+
display: flex;
|
|
1632
|
+
align-items: center;
|
|
1633
|
+
padding: 6px 8px;
|
|
1634
|
+
gap: 6px;
|
|
1635
|
+
border-bottom: 1px solid var(--border);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.files-search-input {
|
|
1639
|
+
flex: 1;
|
|
1640
|
+
background: var(--bg-tertiary);
|
|
1641
|
+
border: 1px solid var(--border);
|
|
1642
|
+
border-radius: 4px;
|
|
1643
|
+
padding: 6px 10px;
|
|
1644
|
+
font-size: 12px;
|
|
1645
|
+
color: var(--text-primary);
|
|
1646
|
+
outline: none;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
.files-search-input:focus {
|
|
1650
|
+
border-color: var(--accent);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
.files-search-input::placeholder {
|
|
1654
|
+
color: var(--text-muted);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
.files-search-clear {
|
|
1658
|
+
background: transparent;
|
|
1659
|
+
border: none;
|
|
1660
|
+
color: var(--text-muted);
|
|
1661
|
+
cursor: pointer;
|
|
1662
|
+
font-size: 14px;
|
|
1663
|
+
padding: 2px 6px;
|
|
1664
|
+
border-radius: 4px;
|
|
1665
|
+
line-height: 1;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
.files-search-clear:hover {
|
|
1669
|
+
color: var(--text-primary);
|
|
1670
|
+
background: var(--bg-tertiary);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
.files-search-clear.hidden {
|
|
1674
|
+
display: none;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
.files-search-results {
|
|
1678
|
+
flex: 1;
|
|
1679
|
+
overflow-y: auto;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
.files-search-result {
|
|
1683
|
+
padding: 6px 12px;
|
|
1684
|
+
cursor: pointer;
|
|
1685
|
+
display: flex;
|
|
1686
|
+
flex-direction: column;
|
|
1687
|
+
gap: 2px;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
.files-search-result:hover,
|
|
1691
|
+
.files-search-result.selected {
|
|
1692
|
+
background: var(--bg-tertiary);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
.files-search-result-name {
|
|
1696
|
+
font-size: 12px;
|
|
1697
|
+
color: var(--text-primary);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
.files-search-result-path {
|
|
1701
|
+
font-size: 11px;
|
|
1702
|
+
color: var(--text-muted);
|
|
1703
|
+
overflow: hidden;
|
|
1704
|
+
text-overflow: ellipsis;
|
|
1705
|
+
white-space: nowrap;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
.files-search-highlight {
|
|
1709
|
+
color: var(--accent);
|
|
1710
|
+
font-weight: 500;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
/* Cmd+P Palette styles (Spec 0058) */
|
|
1714
|
+
.file-palette {
|
|
1715
|
+
position: fixed;
|
|
1716
|
+
inset: 0;
|
|
1717
|
+
z-index: 1000;
|
|
1718
|
+
display: flex;
|
|
1719
|
+
justify-content: center;
|
|
1720
|
+
padding-top: 80px;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.file-palette.hidden {
|
|
1724
|
+
display: none;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
.file-palette-backdrop {
|
|
1728
|
+
position: absolute;
|
|
1729
|
+
inset: 0;
|
|
1730
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
.file-palette-container {
|
|
1734
|
+
position: relative;
|
|
1735
|
+
width: 500px;
|
|
1736
|
+
max-width: 90vw;
|
|
1737
|
+
max-height: 450px;
|
|
1738
|
+
background: var(--bg-secondary);
|
|
1739
|
+
border: 1px solid var(--border);
|
|
1740
|
+
border-radius: 8px;
|
|
1741
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
1742
|
+
display: flex;
|
|
1743
|
+
flex-direction: column;
|
|
1744
|
+
overflow: hidden;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
.file-palette-input {
|
|
1748
|
+
width: 100%;
|
|
1749
|
+
padding: 14px 16px;
|
|
1750
|
+
background: var(--bg-tertiary);
|
|
1751
|
+
border: none;
|
|
1752
|
+
border-bottom: 1px solid var(--border);
|
|
1753
|
+
font-size: 14px;
|
|
1754
|
+
color: var(--text-primary);
|
|
1755
|
+
outline: none;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
.file-palette-input::placeholder {
|
|
1441
1759
|
color: var(--text-muted);
|
|
1442
|
-
font-size: 13px;
|
|
1443
|
-
padding: 12px;
|
|
1444
|
-
text-align: center;
|
|
1445
1760
|
}
|
|
1446
1761
|
|
|
1447
|
-
.
|
|
1762
|
+
.file-palette-results {
|
|
1448
1763
|
flex: 1;
|
|
1449
1764
|
overflow-y: auto;
|
|
1450
|
-
|
|
1765
|
+
max-height: 380px;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
.file-palette-result {
|
|
1769
|
+
padding: 10px 16px;
|
|
1770
|
+
cursor: pointer;
|
|
1771
|
+
display: flex;
|
|
1772
|
+
flex-direction: column;
|
|
1773
|
+
gap: 2px;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
.file-palette-result:hover,
|
|
1777
|
+
.file-palette-result.selected {
|
|
1778
|
+
background: var(--bg-tertiary);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
.file-palette-result-name {
|
|
1782
|
+
font-size: 13px;
|
|
1783
|
+
color: var(--text-primary);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
.file-palette-result-path {
|
|
1787
|
+
font-size: 12px;
|
|
1788
|
+
color: var(--text-muted);
|
|
1789
|
+
overflow: hidden;
|
|
1790
|
+
text-overflow: ellipsis;
|
|
1791
|
+
white-space: nowrap;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
.file-palette-empty {
|
|
1795
|
+
padding: 16px;
|
|
1796
|
+
text-align: center;
|
|
1797
|
+
color: var(--text-muted);
|
|
1798
|
+
font-size: 13px;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
.dashboard-empty-state {
|
|
1802
|
+
color: var(--text-muted);
|
|
1803
|
+
font-size: 13px;
|
|
1804
|
+
padding: 12px;
|
|
1805
|
+
text-align: center;
|
|
1451
1806
|
}
|
|
1452
1807
|
|
|
1453
1808
|
/* Status indicators in dashboard tab list */
|
|
@@ -1485,6 +1840,11 @@
|
|
|
1485
1840
|
<body>
|
|
1486
1841
|
<header class="header">
|
|
1487
1842
|
<h1>Agent Farm - {{PROJECT_NAME}}</h1>
|
|
1843
|
+
<div class="header-actions">
|
|
1844
|
+
<button class="btn activity-summary-btn" onclick="showActivitySummary()" title="What did I do today?">
|
|
1845
|
+
🕐 Today
|
|
1846
|
+
</button>
|
|
1847
|
+
</div>
|
|
1488
1848
|
</header>
|
|
1489
1849
|
|
|
1490
1850
|
<main class="main">
|
|
@@ -1506,10 +1866,6 @@
|
|
|
1506
1866
|
<span class="overflow-count" id="overflow-count">+0</span>
|
|
1507
1867
|
</button>
|
|
1508
1868
|
<div class="overflow-menu hidden" id="overflow-menu" role="menu"></div>
|
|
1509
|
-
<div class="add-buttons">
|
|
1510
|
-
<button class="add-btn" onclick="spawnBuilder()" title="Spawn worktree builder">+ 🔨</button>
|
|
1511
|
-
<button class="add-btn" onclick="spawnShell()" title="New shell">+ >_</button>
|
|
1512
|
-
</div>
|
|
1513
1869
|
</div>
|
|
1514
1870
|
<div class="tab-content" id="tab-content"></div>
|
|
1515
1871
|
</div>
|
|
@@ -1563,6 +1919,7 @@
|
|
|
1563
1919
|
<!-- Context menu -->
|
|
1564
1920
|
<div class="context-menu hidden" id="context-menu" role="menu">
|
|
1565
1921
|
<div class="context-menu-item" role="menuitem" tabindex="0" data-action="openContextTab" onclick="openContextTab()" onkeydown="handleContextMenuKeydown(event)">Open in New Tab</div>
|
|
1922
|
+
<div class="context-menu-item" role="menuitem" tabindex="-1" data-action="reloadContextTab" id="context-reload" onclick="reloadContextTab()" onkeydown="handleContextMenuKeydown(event)">Reload</div>
|
|
1566
1923
|
<div class="context-menu-item" role="menuitem" tabindex="-1" data-action="closeActiveTab" onclick="closeActiveTab()" onkeydown="handleContextMenuKeydown(event)">Close</div>
|
|
1567
1924
|
<div class="context-menu-item" role="menuitem" tabindex="-1" data-action="closeOtherTabs" onclick="closeOtherTabs()" onkeydown="handleContextMenuKeydown(event)">Close Others</div>
|
|
1568
1925
|
<div class="context-menu-item danger" role="menuitem" tabindex="-1" data-action="closeAllTabs" onclick="closeAllTabs()" onkeydown="handleContextMenuKeydown(event)">Close All</div>
|
|
@@ -1571,6 +1928,40 @@
|
|
|
1571
1928
|
<!-- Toast container -->
|
|
1572
1929
|
<div class="toast-container" id="toast-container"></div>
|
|
1573
1930
|
|
|
1931
|
+
<!-- Activity Summary Modal (Spec 0059) -->
|
|
1932
|
+
<div class="dialog-overlay hidden" id="activity-modal">
|
|
1933
|
+
<div class="dialog activity-dialog">
|
|
1934
|
+
<div class="activity-dialog-header">
|
|
1935
|
+
<h3>Today's Summary</h3>
|
|
1936
|
+
<button class="activity-close-btn" onclick="closeActivityModal()" title="Close (Esc)">×</button>
|
|
1937
|
+
</div>
|
|
1938
|
+
<div class="activity-dialog-content" id="activity-content">
|
|
1939
|
+
<div class="activity-loading">
|
|
1940
|
+
<span class="activity-spinner"></span>
|
|
1941
|
+
Loading activity...
|
|
1942
|
+
</div>
|
|
1943
|
+
</div>
|
|
1944
|
+
<div class="activity-dialog-footer">
|
|
1945
|
+
<button class="btn" onclick="copyActivitySummary()">📋 Copy to Clipboard</button>
|
|
1946
|
+
<button class="btn" onclick="closeActivityModal()">Close</button>
|
|
1947
|
+
</div>
|
|
1948
|
+
</div>
|
|
1949
|
+
</div>
|
|
1950
|
+
|
|
1951
|
+
<!-- File search palette (Cmd+P) - Spec 0058 -->
|
|
1952
|
+
<div id="file-palette" class="file-palette hidden">
|
|
1953
|
+
<div class="file-palette-backdrop" onclick="closePalette()"></div>
|
|
1954
|
+
<div class="file-palette-container">
|
|
1955
|
+
<input type="text"
|
|
1956
|
+
id="palette-input"
|
|
1957
|
+
class="file-palette-input"
|
|
1958
|
+
placeholder="Search files by name..."
|
|
1959
|
+
oninput="onPaletteInput(this.value)"
|
|
1960
|
+
onkeydown="onPaletteKeydown(event)" />
|
|
1961
|
+
<div id="palette-results" class="file-palette-results"></div>
|
|
1962
|
+
</div>
|
|
1963
|
+
</div>
|
|
1964
|
+
|
|
1574
1965
|
<script>
|
|
1575
1966
|
// STATE_INJECTION_POINT
|
|
1576
1967
|
|
|
@@ -1588,6 +1979,30 @@
|
|
|
1588
1979
|
let pendingCloseTabId = null;
|
|
1589
1980
|
let contextMenuTabId = null;
|
|
1590
1981
|
|
|
1982
|
+
// Collapsible section state (persisted to localStorage)
|
|
1983
|
+
const SECTION_STATE_KEY = 'codev-dashboard-sections';
|
|
1984
|
+
let sectionState = loadSectionState();
|
|
1985
|
+
|
|
1986
|
+
function loadSectionState() {
|
|
1987
|
+
try {
|
|
1988
|
+
const saved = localStorage.getItem(SECTION_STATE_KEY);
|
|
1989
|
+
if (saved) return JSON.parse(saved);
|
|
1990
|
+
} catch (e) { /* ignore */ }
|
|
1991
|
+
return { tabs: true, files: true, projects: true };
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
function saveSectionState() {
|
|
1995
|
+
try {
|
|
1996
|
+
localStorage.setItem(SECTION_STATE_KEY, JSON.stringify(sectionState));
|
|
1997
|
+
} catch (e) { /* ignore */ }
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
function toggleSection(section) {
|
|
2001
|
+
sectionState[section] = !sectionState[section];
|
|
2002
|
+
saveSectionState();
|
|
2003
|
+
renderDashboardTabContent();
|
|
2004
|
+
}
|
|
2005
|
+
|
|
1591
2006
|
// Initialize
|
|
1592
2007
|
function init() {
|
|
1593
2008
|
buildTabsFromState();
|
|
@@ -1652,8 +2067,9 @@
|
|
|
1652
2067
|
// Check if file is already open
|
|
1653
2068
|
const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
1654
2069
|
if (existingTab) {
|
|
1655
|
-
//
|
|
2070
|
+
// Switch to the existing tab and refresh content
|
|
1656
2071
|
selectTab(existingTab.id);
|
|
2072
|
+
refreshFileTab(existingTab.id); // Refresh content in case file changed
|
|
1657
2073
|
showToast(`Switched to ${getFileName(filePath)}`, 'success');
|
|
1658
2074
|
// TODO: scroll to line if lineNumber provided
|
|
1659
2075
|
return;
|
|
@@ -1703,9 +2119,25 @@
|
|
|
1703
2119
|
let filesTreeError = null;
|
|
1704
2120
|
let filesTreeLoaded = false;
|
|
1705
2121
|
|
|
2122
|
+
// File search state (Spec 0058)
|
|
2123
|
+
let filesTreeFlat = []; // Flattened array of {name, path} objects for searching
|
|
2124
|
+
let filesSearchQuery = '';
|
|
2125
|
+
let filesSearchResults = [];
|
|
2126
|
+
let filesSearchIndex = 0;
|
|
2127
|
+
let filesSearchDebounceTimer = null;
|
|
2128
|
+
|
|
2129
|
+
// Cmd+P palette state (Spec 0058)
|
|
2130
|
+
let paletteOpen = false;
|
|
2131
|
+
let paletteQuery = '';
|
|
2132
|
+
let paletteResults = [];
|
|
2133
|
+
let paletteIndex = 0;
|
|
2134
|
+
let paletteDebounceTimer = null;
|
|
2135
|
+
|
|
1706
2136
|
// Build tabs from initial state
|
|
1707
2137
|
function buildTabsFromState() {
|
|
1708
2138
|
const previousTabIds = new Set(tabs.map(t => t.id));
|
|
2139
|
+
// Preserve client-side-only tabs (like activity)
|
|
2140
|
+
const clientSideTabs = tabs.filter(t => t.type === 'activity');
|
|
1709
2141
|
tabs = [];
|
|
1710
2142
|
|
|
1711
2143
|
// Dashboard tab is ALWAYS first and uncloseable (Spec 0045, 0057)
|
|
@@ -1716,14 +2148,6 @@
|
|
|
1716
2148
|
closeable: false
|
|
1717
2149
|
});
|
|
1718
2150
|
|
|
1719
|
-
// Files tab is second and uncloseable (Spec 0055)
|
|
1720
|
-
tabs.push({
|
|
1721
|
-
id: 'files',
|
|
1722
|
-
type: 'files',
|
|
1723
|
-
name: 'Files',
|
|
1724
|
-
closeable: false
|
|
1725
|
-
});
|
|
1726
|
-
|
|
1727
2151
|
// Add file tabs from annotations
|
|
1728
2152
|
for (const annotation of state.annotations || []) {
|
|
1729
2153
|
tabs.push({
|
|
@@ -1759,6 +2183,11 @@
|
|
|
1759
2183
|
});
|
|
1760
2184
|
}
|
|
1761
2185
|
|
|
2186
|
+
// Re-add preserved client-side tabs
|
|
2187
|
+
for (const tab of clientSideTabs) {
|
|
2188
|
+
tabs.push(tab);
|
|
2189
|
+
}
|
|
2190
|
+
|
|
1762
2191
|
// Detect new tabs and auto-switch to them (skip projects tab)
|
|
1763
2192
|
for (const tab of tabs) {
|
|
1764
2193
|
if (tab.id !== 'dashboard' && tab.id !== 'files' && !knownTabIds.has(tab.id) && previousTabIds.size > 0) {
|
|
@@ -1777,10 +2206,13 @@
|
|
|
1777
2206
|
}
|
|
1778
2207
|
}
|
|
1779
2208
|
|
|
1780
|
-
// Get filename from path
|
|
2209
|
+
// Get filename from path (includes parent dir for context)
|
|
1781
2210
|
function getFileName(path) {
|
|
1782
|
-
const parts = path.split('/');
|
|
1783
|
-
|
|
2211
|
+
const parts = path.split('/').filter(p => p);
|
|
2212
|
+
if (parts.length >= 2) {
|
|
2213
|
+
return parts.slice(-2).join('/');
|
|
2214
|
+
}
|
|
2215
|
+
return parts[parts.length - 1] || path;
|
|
1784
2216
|
}
|
|
1785
2217
|
|
|
1786
2218
|
// Track current architect port to avoid re-rendering iframe unnecessarily
|
|
@@ -1855,7 +2287,7 @@
|
|
|
1855
2287
|
// Get tab icon
|
|
1856
2288
|
function getTabIcon(type) {
|
|
1857
2289
|
switch (type) {
|
|
1858
|
-
case 'dashboard': return '
|
|
2290
|
+
case 'dashboard': return '🏠';
|
|
1859
2291
|
case 'files': return '📁';
|
|
1860
2292
|
case 'file': return '📄';
|
|
1861
2293
|
case 'builder': return '🔨';
|
|
@@ -1964,12 +2396,12 @@
|
|
|
1964
2396
|
return;
|
|
1965
2397
|
}
|
|
1966
2398
|
|
|
1967
|
-
// Handle
|
|
1968
|
-
if (tab.type === '
|
|
1969
|
-
if (currentTabType !== '
|
|
1970
|
-
currentTabType = '
|
|
2399
|
+
// Handle activity tab specially (no iframe, inline content)
|
|
2400
|
+
if (tab.type === 'activity') {
|
|
2401
|
+
if (currentTabType !== 'activity') {
|
|
2402
|
+
currentTabType = 'activity';
|
|
1971
2403
|
currentTabPort = null;
|
|
1972
|
-
|
|
2404
|
+
renderActivityTab();
|
|
1973
2405
|
}
|
|
1974
2406
|
return;
|
|
1975
2407
|
}
|
|
@@ -1982,6 +2414,22 @@
|
|
|
1982
2414
|
}
|
|
1983
2415
|
}
|
|
1984
2416
|
|
|
2417
|
+
// Force refresh the iframe for a file tab (reloads content from server)
|
|
2418
|
+
function refreshFileTab(tabId) {
|
|
2419
|
+
const tab = tabs.find(t => t.id === tabId);
|
|
2420
|
+
if (!tab || tab.type !== 'file' || !tab.port) return;
|
|
2421
|
+
|
|
2422
|
+
// If this tab is currently active, force iframe reload
|
|
2423
|
+
if (activeTabId === tabId) {
|
|
2424
|
+
const content = document.getElementById('tab-content');
|
|
2425
|
+
const iframe = content.querySelector('iframe');
|
|
2426
|
+
if (iframe) {
|
|
2427
|
+
// Add cache-busting query param to force reload
|
|
2428
|
+
iframe.src = `http://localhost:${tab.port}?t=${Date.now()}`;
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
|
|
1985
2433
|
// Update status bar
|
|
1986
2434
|
function updateStatusBar() {
|
|
1987
2435
|
// Architect status
|
|
@@ -2260,6 +2708,13 @@
|
|
|
2260
2708
|
menu.style.top = event.clientY + 'px';
|
|
2261
2709
|
menu.classList.remove('hidden');
|
|
2262
2710
|
|
|
2711
|
+
// Show/hide reload option based on tab type
|
|
2712
|
+
const tab = tabs.find(t => t.id === tabId);
|
|
2713
|
+
const reloadItem = document.getElementById('context-reload');
|
|
2714
|
+
if (reloadItem) {
|
|
2715
|
+
reloadItem.style.display = (tab && tab.type === 'file') ? 'block' : 'none';
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2263
2718
|
// Focus first item for keyboard navigation
|
|
2264
2719
|
const firstItem = menu.querySelector('.context-menu-item');
|
|
2265
2720
|
if (firstItem) firstItem.focus();
|
|
@@ -2270,6 +2725,15 @@
|
|
|
2270
2725
|
}, 0);
|
|
2271
2726
|
}
|
|
2272
2727
|
|
|
2728
|
+
// Reload file tab content
|
|
2729
|
+
function reloadContextTab() {
|
|
2730
|
+
if (contextMenuTabId) {
|
|
2731
|
+
refreshFileTab(contextMenuTabId);
|
|
2732
|
+
showToast('Reloaded', 'success');
|
|
2733
|
+
}
|
|
2734
|
+
hideContextMenu();
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2273
2737
|
function hideContextMenu() {
|
|
2274
2738
|
document.getElementById('context-menu').classList.add('hidden');
|
|
2275
2739
|
contextMenuTabId = null;
|
|
@@ -2534,6 +2998,11 @@
|
|
|
2534
2998
|
hideCloseDialog();
|
|
2535
2999
|
hideContextMenu();
|
|
2536
3000
|
hideOverflowMenu();
|
|
3001
|
+
// Activity modal (Spec 0059)
|
|
3002
|
+
const activityModal = document.getElementById('activity-modal');
|
|
3003
|
+
if (activityModal && !activityModal.classList.contains('hidden')) {
|
|
3004
|
+
closeActivityModal();
|
|
3005
|
+
}
|
|
2537
3006
|
}
|
|
2538
3007
|
|
|
2539
3008
|
// Enter in dialogs
|
|
@@ -3172,58 +3641,61 @@
|
|
|
3172
3641
|
filesTreeData = await response.json();
|
|
3173
3642
|
filesTreeError = null;
|
|
3174
3643
|
filesTreeLoaded = true;
|
|
3644
|
+
// Flatten tree for search (Spec 0058)
|
|
3645
|
+
filesTreeFlat = flattenFilesTree(filesTreeData);
|
|
3175
3646
|
} catch (err) {
|
|
3176
3647
|
console.error('Failed to load files tree:', err);
|
|
3177
3648
|
filesTreeError = 'Could not load file tree: ' + err.message;
|
|
3178
3649
|
filesTreeData = [];
|
|
3650
|
+
filesTreeFlat = [];
|
|
3179
3651
|
}
|
|
3180
3652
|
}
|
|
3181
3653
|
|
|
3182
|
-
//
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
return;
|
|
3654
|
+
// Flatten the file tree into a searchable array (Spec 0058)
|
|
3655
|
+
function flattenFilesTree(nodes, result = []) {
|
|
3656
|
+
for (const node of nodes) {
|
|
3657
|
+
if (node.type === 'file') {
|
|
3658
|
+
result.push({ name: node.name, path: node.path });
|
|
3659
|
+
} else if (node.children) {
|
|
3660
|
+
flattenFilesTree(node.children, result);
|
|
3661
|
+
}
|
|
3191
3662
|
}
|
|
3192
|
-
|
|
3193
|
-
// First load - show loading state and fetch
|
|
3194
|
-
content.innerHTML = '<div class="files-loading">Loading files...</div>';
|
|
3195
|
-
await loadFilesTree();
|
|
3196
|
-
renderFilesTabContent();
|
|
3663
|
+
return result;
|
|
3197
3664
|
}
|
|
3198
3665
|
|
|
3199
|
-
//
|
|
3200
|
-
function
|
|
3201
|
-
|
|
3666
|
+
// Search files with relevance sorting (Spec 0058)
|
|
3667
|
+
function searchFiles(query) {
|
|
3668
|
+
if (!query) return [];
|
|
3669
|
+
const q = query.toLowerCase();
|
|
3202
3670
|
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
<div class="files-error">${escapeHtml(filesTreeError)}</div>
|
|
3207
|
-
</div>
|
|
3208
|
-
`;
|
|
3209
|
-
return;
|
|
3210
|
-
}
|
|
3671
|
+
const matches = filesTreeFlat.filter(f =>
|
|
3672
|
+
f.path.toLowerCase().includes(q)
|
|
3673
|
+
);
|
|
3211
3674
|
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3675
|
+
// Sort by relevance: exact filename > filename prefix > filename contains > path
|
|
3676
|
+
matches.sort((a, b) => {
|
|
3677
|
+
const aName = a.name.toLowerCase();
|
|
3678
|
+
const bName = b.name.toLowerCase();
|
|
3679
|
+
const aPath = a.path.toLowerCase();
|
|
3680
|
+
const bPath = b.path.toLowerCase();
|
|
3681
|
+
|
|
3682
|
+
// Exact filename match first
|
|
3683
|
+
if (aName === q && bName !== q) return -1;
|
|
3684
|
+
if (bName === q && aName !== q) return 1;
|
|
3685
|
+
|
|
3686
|
+
// Filename starts with query
|
|
3687
|
+
if (aName.startsWith(q) && !bName.startsWith(q)) return -1;
|
|
3688
|
+
if (bName.startsWith(q) && !aName.startsWith(q)) return 1;
|
|
3689
|
+
|
|
3690
|
+
// Filename contains query
|
|
3691
|
+
if (aName.includes(q) && !bName.includes(q)) return -1;
|
|
3692
|
+
if (bName.includes(q) && !aName.includes(q)) return 1;
|
|
3693
|
+
|
|
3694
|
+
// Alphabetical by path
|
|
3695
|
+
return aPath.localeCompare(bPath);
|
|
3696
|
+
});
|
|
3697
|
+
|
|
3698
|
+
return matches.slice(0, 15);
|
|
3227
3699
|
}
|
|
3228
3700
|
|
|
3229
3701
|
// Escape a string for use inside a JavaScript string literal in onclick handlers
|
|
@@ -3311,16 +3783,273 @@
|
|
|
3311
3783
|
// Re-render file browser in current context (dashboard or files tab)
|
|
3312
3784
|
function rerenderFilesBrowser() {
|
|
3313
3785
|
if (activeTabId === 'dashboard') {
|
|
3314
|
-
// Re-render just the files
|
|
3315
|
-
const
|
|
3316
|
-
if (
|
|
3317
|
-
|
|
3786
|
+
// Re-render just the files content in dashboard
|
|
3787
|
+
const filesContentEl = document.getElementById('dashboard-files-content');
|
|
3788
|
+
if (filesContentEl) {
|
|
3789
|
+
filesContentEl.innerHTML = filesSearchQuery
|
|
3790
|
+
? renderFilesSearchResults()
|
|
3791
|
+
: renderDashboardFilesBrowserWithWrapper();
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
// Wrapper for file browser that includes the list element ID (Spec 0058)
|
|
3797
|
+
function renderDashboardFilesBrowserWithWrapper() {
|
|
3798
|
+
return `<div class="dashboard-files-list" id="dashboard-files-list">${renderDashboardFilesBrowser()}</div>`;
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
// ========================================
|
|
3802
|
+
// File Search Functions (Spec 0058)
|
|
3803
|
+
// ========================================
|
|
3804
|
+
|
|
3805
|
+
// Debounced search input handler for Files column
|
|
3806
|
+
function onFilesSearchInput(value) {
|
|
3807
|
+
clearTimeout(filesSearchDebounceTimer);
|
|
3808
|
+
filesSearchDebounceTimer = setTimeout(() => {
|
|
3809
|
+
filesSearchQuery = value;
|
|
3810
|
+
filesSearchResults = searchFiles(value);
|
|
3811
|
+
filesSearchIndex = 0;
|
|
3812
|
+
rerenderFilesSearch();
|
|
3813
|
+
}, 100);
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
// Clear files search and restore tree view
|
|
3817
|
+
function clearFilesSearch() {
|
|
3818
|
+
filesSearchQuery = '';
|
|
3819
|
+
filesSearchResults = [];
|
|
3820
|
+
filesSearchIndex = 0;
|
|
3821
|
+
const input = document.getElementById('files-search-input');
|
|
3822
|
+
if (input) {
|
|
3823
|
+
input.value = '';
|
|
3824
|
+
}
|
|
3825
|
+
rerenderFilesSearch();
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
// Re-render the files search area (results or tree)
|
|
3829
|
+
function rerenderFilesSearch() {
|
|
3830
|
+
const filesContentEl = document.getElementById('dashboard-files-content');
|
|
3831
|
+
if (filesContentEl) {
|
|
3832
|
+
filesContentEl.innerHTML = filesSearchQuery
|
|
3833
|
+
? renderFilesSearchResults()
|
|
3834
|
+
: renderDashboardFilesBrowserWithWrapper();
|
|
3835
|
+
}
|
|
3836
|
+
// Update clear button visibility
|
|
3837
|
+
const clearBtn = document.querySelector('.files-search-clear');
|
|
3838
|
+
if (clearBtn) {
|
|
3839
|
+
clearBtn.classList.toggle('hidden', !filesSearchQuery);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
// Render search results for Files column
|
|
3844
|
+
function renderFilesSearchResults() {
|
|
3845
|
+
if (!filesSearchResults.length) {
|
|
3846
|
+
return '<div class="dashboard-empty-state">No files found</div>';
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
return `<div class="files-search-results">${filesSearchResults.map((file, index) =>
|
|
3850
|
+
renderSearchResult(file, index, index === filesSearchIndex, filesSearchQuery, 'files')
|
|
3851
|
+
).join('')}</div>`;
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
// Highlight matching text in search results
|
|
3855
|
+
function highlightMatch(text, query) {
|
|
3856
|
+
if (!query) return escapeHtml(text);
|
|
3857
|
+
const q = query.toLowerCase();
|
|
3858
|
+
const t = text.toLowerCase();
|
|
3859
|
+
const idx = t.indexOf(q);
|
|
3860
|
+
if (idx === -1) return escapeHtml(text);
|
|
3861
|
+
|
|
3862
|
+
return escapeHtml(text.substring(0, idx)) +
|
|
3863
|
+
'<span class="files-search-highlight">' + escapeHtml(text.substring(idx, idx + query.length)) + '</span>' +
|
|
3864
|
+
escapeHtml(text.substring(idx + query.length));
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
// Render a single search result (shared by Files column and palette)
|
|
3868
|
+
function renderSearchResult(file, index, isSelected, query, context) {
|
|
3869
|
+
const classPrefix = context === 'palette' ? 'file-palette' : 'files-search';
|
|
3870
|
+
const jsPath = escapeJsString(file.path);
|
|
3871
|
+
|
|
3872
|
+
return `
|
|
3873
|
+
<div class="${classPrefix}-result ${isSelected ? 'selected' : ''}"
|
|
3874
|
+
data-index="${index}"
|
|
3875
|
+
onclick="openFileFromSearch('${jsPath}', '${context}')">
|
|
3876
|
+
<div class="${classPrefix}-result-name">${highlightMatch(file.name, query)}</div>
|
|
3877
|
+
<div class="${classPrefix}-result-path">${highlightMatch(file.path, query)}</div>
|
|
3878
|
+
</div>
|
|
3879
|
+
`;
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
// Keyboard handler for Files search input
|
|
3883
|
+
function onFilesSearchKeydown(event) {
|
|
3884
|
+
if (!filesSearchResults.length) {
|
|
3885
|
+
if (event.key === 'Escape') {
|
|
3886
|
+
clearFilesSearch();
|
|
3887
|
+
event.target.blur();
|
|
3888
|
+
}
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
if (event.key === 'ArrowDown') {
|
|
3893
|
+
event.preventDefault();
|
|
3894
|
+
filesSearchIndex = Math.min(filesSearchIndex + 1, filesSearchResults.length - 1);
|
|
3895
|
+
rerenderFilesSearch();
|
|
3896
|
+
scrollSelectedIntoView('files');
|
|
3897
|
+
} else if (event.key === 'ArrowUp') {
|
|
3898
|
+
event.preventDefault();
|
|
3899
|
+
filesSearchIndex = Math.max(filesSearchIndex - 1, 0);
|
|
3900
|
+
rerenderFilesSearch();
|
|
3901
|
+
scrollSelectedIntoView('files');
|
|
3902
|
+
} else if (event.key === 'Enter') {
|
|
3903
|
+
event.preventDefault();
|
|
3904
|
+
if (filesSearchResults[filesSearchIndex]) {
|
|
3905
|
+
openFileFromSearch(filesSearchResults[filesSearchIndex].path, 'files');
|
|
3906
|
+
}
|
|
3907
|
+
} else if (event.key === 'Escape') {
|
|
3908
|
+
clearFilesSearch();
|
|
3909
|
+
event.target.blur();
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
// Scroll selected result into view
|
|
3914
|
+
function scrollSelectedIntoView(context) {
|
|
3915
|
+
const selector = context === 'palette'
|
|
3916
|
+
? '.file-palette-result.selected'
|
|
3917
|
+
: '.files-search-result.selected';
|
|
3918
|
+
const selected = document.querySelector(selector);
|
|
3919
|
+
if (selected) {
|
|
3920
|
+
selected.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
// Open file from search result (shared by Files column and palette)
|
|
3925
|
+
function openFileFromSearch(filePath, context) {
|
|
3926
|
+
// Check if file is already open
|
|
3927
|
+
const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
3928
|
+
if (existingTab) {
|
|
3929
|
+
selectTab(existingTab.id);
|
|
3930
|
+
refreshFileTab(existingTab.id); // Refresh content in case file changed
|
|
3931
|
+
} else {
|
|
3932
|
+
openFileFromTree(filePath);
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
// Clear search / close palette
|
|
3936
|
+
if (context === 'palette') {
|
|
3937
|
+
closePalette();
|
|
3938
|
+
} else {
|
|
3939
|
+
clearFilesSearch();
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
// ========================================
|
|
3944
|
+
// Cmd+P Palette Functions (Spec 0058)
|
|
3945
|
+
// ========================================
|
|
3946
|
+
|
|
3947
|
+
// Open the file search palette
|
|
3948
|
+
function openPalette() {
|
|
3949
|
+
paletteOpen = true;
|
|
3950
|
+
paletteQuery = '';
|
|
3951
|
+
paletteResults = [];
|
|
3952
|
+
paletteIndex = 0;
|
|
3953
|
+
document.getElementById('file-palette').classList.remove('hidden');
|
|
3954
|
+
const input = document.getElementById('palette-input');
|
|
3955
|
+
input.value = '';
|
|
3956
|
+
input.focus();
|
|
3957
|
+
rerenderPaletteResults();
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
// Close the file search palette
|
|
3961
|
+
function closePalette() {
|
|
3962
|
+
paletteOpen = false;
|
|
3963
|
+
paletteQuery = '';
|
|
3964
|
+
paletteResults = [];
|
|
3965
|
+
paletteIndex = 0;
|
|
3966
|
+
document.getElementById('file-palette').classList.add('hidden');
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
// Debounced palette input handler
|
|
3970
|
+
function onPaletteInput(value) {
|
|
3971
|
+
clearTimeout(paletteDebounceTimer);
|
|
3972
|
+
paletteDebounceTimer = setTimeout(() => {
|
|
3973
|
+
paletteQuery = value;
|
|
3974
|
+
paletteResults = searchFiles(value);
|
|
3975
|
+
paletteIndex = 0;
|
|
3976
|
+
rerenderPaletteResults();
|
|
3977
|
+
}, 100);
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
// Re-render palette results
|
|
3981
|
+
function rerenderPaletteResults() {
|
|
3982
|
+
const resultsEl = document.getElementById('palette-results');
|
|
3983
|
+
if (!resultsEl) return;
|
|
3984
|
+
|
|
3985
|
+
if (!paletteQuery) {
|
|
3986
|
+
resultsEl.innerHTML = '<div class="file-palette-empty">Type to search files...</div>';
|
|
3987
|
+
return;
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3990
|
+
if (!paletteResults.length) {
|
|
3991
|
+
resultsEl.innerHTML = '<div class="file-palette-empty">No files found</div>';
|
|
3992
|
+
return;
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
resultsEl.innerHTML = paletteResults.map((file, index) =>
|
|
3996
|
+
renderSearchResult(file, index, index === paletteIndex, paletteQuery, 'palette')
|
|
3997
|
+
).join('');
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
// Keyboard handler for palette input
|
|
4001
|
+
function onPaletteKeydown(event) {
|
|
4002
|
+
if (event.key === 'Escape') {
|
|
4003
|
+
closePalette();
|
|
4004
|
+
return;
|
|
4005
|
+
}
|
|
4006
|
+
|
|
4007
|
+
if (!paletteResults.length) return;
|
|
4008
|
+
|
|
4009
|
+
if (event.key === 'ArrowDown') {
|
|
4010
|
+
event.preventDefault();
|
|
4011
|
+
paletteIndex = Math.min(paletteIndex + 1, paletteResults.length - 1);
|
|
4012
|
+
rerenderPaletteResults();
|
|
4013
|
+
scrollSelectedIntoView('palette');
|
|
4014
|
+
} else if (event.key === 'ArrowUp') {
|
|
4015
|
+
event.preventDefault();
|
|
4016
|
+
paletteIndex = Math.max(paletteIndex - 1, 0);
|
|
4017
|
+
rerenderPaletteResults();
|
|
4018
|
+
scrollSelectedIntoView('palette');
|
|
4019
|
+
} else if (event.key === 'Enter') {
|
|
4020
|
+
event.preventDefault();
|
|
4021
|
+
if (paletteResults[paletteIndex]) {
|
|
4022
|
+
openFileFromSearch(paletteResults[paletteIndex].path, 'palette');
|
|
3318
4023
|
}
|
|
3319
|
-
} else if (activeTabId === 'files') {
|
|
3320
|
-
renderFilesTabContent();
|
|
3321
4024
|
}
|
|
3322
4025
|
}
|
|
3323
4026
|
|
|
4027
|
+
// Global keyboard handler for Cmd+P / Ctrl+P and Escape
|
|
4028
|
+
document.addEventListener('keydown', (e) => {
|
|
4029
|
+
// Global Escape handler for palette (works even if input loses focus)
|
|
4030
|
+
if (e.key === 'Escape' && paletteOpen) {
|
|
4031
|
+
closePalette();
|
|
4032
|
+
return;
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
// Cmd+P (macOS) or Ctrl+P (Windows/Linux)
|
|
4036
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'p') {
|
|
4037
|
+
// Skip if user is typing in an input/textarea (except our search inputs)
|
|
4038
|
+
const active = document.activeElement;
|
|
4039
|
+
const isOurInput = active?.id === 'palette-input' || active?.id === 'files-search-input';
|
|
4040
|
+
const isEditable = active?.tagName === 'INPUT' || active?.tagName === 'TEXTAREA' || active?.isContentEditable;
|
|
4041
|
+
|
|
4042
|
+
if (!isOurInput && isEditable) return; // Let native behavior happen
|
|
4043
|
+
|
|
4044
|
+
e.preventDefault(); // Prevent browser Print dialog
|
|
4045
|
+
if (paletteOpen) {
|
|
4046
|
+
closePalette();
|
|
4047
|
+
} else {
|
|
4048
|
+
openPalette();
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
});
|
|
4052
|
+
|
|
3324
4053
|
// Collapse all folders
|
|
3325
4054
|
function collapseAllFolders() {
|
|
3326
4055
|
filesTreeExpanded.clear();
|
|
@@ -3357,6 +4086,7 @@
|
|
|
3357
4086
|
const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
3358
4087
|
if (existingTab) {
|
|
3359
4088
|
selectTab(existingTab.id);
|
|
4089
|
+
refreshFileTab(existingTab.id); // Refresh content in case file changed
|
|
3360
4090
|
return;
|
|
3361
4091
|
}
|
|
3362
4092
|
|
|
@@ -3394,10 +4124,9 @@
|
|
|
3394
4124
|
function renderInfoHeader() {
|
|
3395
4125
|
return `
|
|
3396
4126
|
<div class="projects-info">
|
|
3397
|
-
<h1 style="font-size: 20px; margin-bottom: 12px; color: var(--text-primary);">
|
|
3398
|
-
<p>
|
|
3399
|
-
<p>
|
|
3400
|
-
<p>Docs: <a href="#" onclick="openProjectFile('codev/resources/cheatsheet.md'); return false;">Cheatsheet</a> · <a href="#" onclick="openProjectFile('codev/docs/lifecycle.md'); return false;">Lifecycle</a> · <a href="#" onclick="openProjectFile('codev/docs/commands/overview.md'); return false;">CLI Reference</a> · <a href="#" onclick="openProjectFile('codev/protocols/spider/protocol.md'); return false;">SPIDER Protocol</a> · <a href="https://github.com/cluesmith/codev#readme" target="_blank">README</a></p>
|
|
4127
|
+
<h1 style="font-size: 20px; margin-bottom: 12px; color: var(--text-primary);">Agent Farm Dashboard</h1>
|
|
4128
|
+
<p>Coordinate AI builders working on your codebase. The left panel shows the Architect terminal – tell it what you want to build. <strong>Tabs</strong> shows open terminals (Architect, Builders, utility shells). <strong>Files</strong> lets you browse and open project files. <strong>Projects</strong> tracks work as it moves from conception to integration.</p>
|
|
4129
|
+
<p>Docs: <a href="#" onclick="openProjectFile('codev/resources/cheatsheet.md'); return false;">Cheatsheet</a> · <a href="#" onclick="openProjectFile('codev/resources/lifecycle.md'); return false;">Lifecycle</a> · <a href="#" onclick="openProjectFile('codev/resources/commands/overview.md'); return false;">CLI Reference</a> · <a href="#" onclick="openProjectFile('codev/protocols/spider/protocol.md'); return false;">SPIDER Protocol</a> · <a href="https://github.com/cluesmith/codev#readme" target="_blank">README</a> · <a href="https://discord.gg/mJ92DhDa6n" target="_blank">Discord</a></p>
|
|
3401
4130
|
</div>
|
|
3402
4131
|
`;
|
|
3403
4132
|
}
|
|
@@ -3408,36 +4137,60 @@
|
|
|
3408
4137
|
|
|
3409
4138
|
content.innerHTML = `
|
|
3410
4139
|
<div class="dashboard-container">
|
|
4140
|
+
${renderInfoHeader()}
|
|
3411
4141
|
<div class="dashboard-header">
|
|
3412
|
-
<!--
|
|
3413
|
-
<div class="dashboard-
|
|
3414
|
-
<div class="dashboard-
|
|
3415
|
-
<h3>Tabs</h3>
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
4142
|
+
<!-- Tabs Section -->
|
|
4143
|
+
<div class="dashboard-section section-tabs ${sectionState.tabs ? '' : 'collapsed'}">
|
|
4144
|
+
<div class="dashboard-section-header" onclick="toggleSection('tabs')">
|
|
4145
|
+
<h3><span class="collapse-icon">▼</span> Tabs</h3>
|
|
4146
|
+
<div class="header-actions" onclick="event.stopPropagation()">
|
|
4147
|
+
<button onclick="spawnBuilder()" title="New Worktree">+ Worktree</button>
|
|
4148
|
+
<button onclick="spawnShell()" title="New Shell">+ Shell</button>
|
|
4149
|
+
</div>
|
|
3419
4150
|
</div>
|
|
3420
|
-
<div class="dashboard-
|
|
3421
|
-
<
|
|
3422
|
-
|
|
4151
|
+
<div class="dashboard-section-content">
|
|
4152
|
+
<div class="dashboard-tabs-list" id="dashboard-tabs-list">
|
|
4153
|
+
${renderDashboardTabsList()}
|
|
4154
|
+
</div>
|
|
3423
4155
|
</div>
|
|
3424
4156
|
</div>
|
|
3425
|
-
<!--
|
|
3426
|
-
<div class="dashboard-
|
|
3427
|
-
<div class="dashboard-
|
|
3428
|
-
<h3>Files</h3>
|
|
3429
|
-
<div class="header-actions">
|
|
4157
|
+
<!-- Files Section -->
|
|
4158
|
+
<div class="dashboard-section section-files ${sectionState.files ? '' : 'collapsed'}">
|
|
4159
|
+
<div class="dashboard-section-header" onclick="toggleSection('files')">
|
|
4160
|
+
<h3><span class="collapse-icon">▼</span> Files</h3>
|
|
4161
|
+
<div class="header-actions" onclick="event.stopPropagation()">
|
|
4162
|
+
<button onclick="refreshFilesTree()" title="Refresh">↻</button>
|
|
3430
4163
|
<button onclick="collapseAllFolders()" title="Collapse All">⊟</button>
|
|
3431
4164
|
<button onclick="expandAllFolders()" title="Expand All">⊞</button>
|
|
3432
4165
|
</div>
|
|
3433
4166
|
</div>
|
|
3434
|
-
<div class="dashboard-
|
|
3435
|
-
|
|
4167
|
+
<div class="dashboard-section-content">
|
|
4168
|
+
<div class="files-search-container" onclick="event.stopPropagation()">
|
|
4169
|
+
<input type="text"
|
|
4170
|
+
id="files-search-input"
|
|
4171
|
+
class="files-search-input"
|
|
4172
|
+
placeholder="Search files by name..."
|
|
4173
|
+
oninput="onFilesSearchInput(this.value)"
|
|
4174
|
+
onkeydown="onFilesSearchKeydown(event)"
|
|
4175
|
+
value="${escapeHtml(filesSearchQuery)}" />
|
|
4176
|
+
<button class="files-search-clear ${filesSearchQuery ? '' : 'hidden'}"
|
|
4177
|
+
onclick="clearFilesSearch()"
|
|
4178
|
+
title="Clear search">×</button>
|
|
4179
|
+
</div>
|
|
4180
|
+
<div id="dashboard-files-content">
|
|
4181
|
+
${filesSearchQuery ? renderFilesSearchResults() : renderDashboardFilesBrowserWithWrapper()}
|
|
4182
|
+
</div>
|
|
3436
4183
|
</div>
|
|
3437
4184
|
</div>
|
|
3438
4185
|
</div>
|
|
3439
|
-
|
|
3440
|
-
|
|
4186
|
+
<!-- Projects Section -->
|
|
4187
|
+
<div class="dashboard-section section-projects ${sectionState.projects ? '' : 'collapsed'}">
|
|
4188
|
+
<div class="dashboard-section-header" onclick="toggleSection('projects')">
|
|
4189
|
+
<h3><span class="collapse-icon">▼</span> Projects</h3>
|
|
4190
|
+
</div>
|
|
4191
|
+
<div class="dashboard-section-content" id="dashboard-projects">
|
|
4192
|
+
${renderDashboardProjectsSection()}
|
|
4193
|
+
</div>
|
|
3441
4194
|
</div>
|
|
3442
4195
|
</div>
|
|
3443
4196
|
`;
|
|
@@ -3516,9 +4269,8 @@
|
|
|
3516
4269
|
`;
|
|
3517
4270
|
}
|
|
3518
4271
|
|
|
3519
|
-
// Render the existing project view
|
|
4272
|
+
// Render the existing project view
|
|
3520
4273
|
return `
|
|
3521
|
-
${renderInfoHeader()}
|
|
3522
4274
|
${renderKanbanGrid(projectsData)}
|
|
3523
4275
|
${renderTerminalProjects(projectsData)}
|
|
3524
4276
|
`;
|
|
@@ -3719,6 +4471,269 @@
|
|
|
3719
4471
|
// Start projectlist polling (separate from main state polling)
|
|
3720
4472
|
setInterval(pollProjectlist, 5000);
|
|
3721
4473
|
|
|
4474
|
+
// ========================================
|
|
4475
|
+
// Activity Summary (Spec 0059)
|
|
4476
|
+
// ========================================
|
|
4477
|
+
|
|
4478
|
+
let activityData = null;
|
|
4479
|
+
|
|
4480
|
+
// Show activity summary modal
|
|
4481
|
+
async function showActivitySummary() {
|
|
4482
|
+
// Check if activity tab already exists
|
|
4483
|
+
let activityTab = tabs.find(t => t.type === 'activity');
|
|
4484
|
+
|
|
4485
|
+
if (!activityTab) {
|
|
4486
|
+
// Create new activity tab
|
|
4487
|
+
activityTab = {
|
|
4488
|
+
id: 'activity-today',
|
|
4489
|
+
type: 'activity',
|
|
4490
|
+
name: 'Today'
|
|
4491
|
+
};
|
|
4492
|
+
tabs.push(activityTab);
|
|
4493
|
+
}
|
|
4494
|
+
|
|
4495
|
+
// Switch to activity tab
|
|
4496
|
+
activeTabId = activityTab.id;
|
|
4497
|
+
currentTabType = null; // Force re-render
|
|
4498
|
+
renderTabs();
|
|
4499
|
+
renderTabContent();
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
// Render the activity tab content
|
|
4503
|
+
async function renderActivityTab() {
|
|
4504
|
+
const content = document.getElementById('tab-content');
|
|
4505
|
+
|
|
4506
|
+
// Show loading state
|
|
4507
|
+
content.innerHTML = `
|
|
4508
|
+
<div class="activity-tab-container">
|
|
4509
|
+
<div class="activity-loading">
|
|
4510
|
+
<span class="activity-spinner"></span>
|
|
4511
|
+
Loading activity...
|
|
4512
|
+
</div>
|
|
4513
|
+
</div>
|
|
4514
|
+
`;
|
|
4515
|
+
|
|
4516
|
+
try {
|
|
4517
|
+
const response = await fetch('/api/activity-summary');
|
|
4518
|
+
if (!response.ok) {
|
|
4519
|
+
throw new Error(await response.text());
|
|
4520
|
+
}
|
|
4521
|
+
activityData = await response.json();
|
|
4522
|
+
renderActivityTabContent(activityData);
|
|
4523
|
+
} catch (err) {
|
|
4524
|
+
content.innerHTML = `
|
|
4525
|
+
<div class="activity-tab-container">
|
|
4526
|
+
<div class="activity-error">
|
|
4527
|
+
Failed to load activity: ${escapeHtml(err.message)}
|
|
4528
|
+
</div>
|
|
4529
|
+
</div>
|
|
4530
|
+
`;
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
|
|
4534
|
+
// Render activity tab content (similar to modal but in tab)
|
|
4535
|
+
function renderActivityTabContent(data) {
|
|
4536
|
+
const content = document.getElementById('tab-content');
|
|
4537
|
+
|
|
4538
|
+
// Check for zero activity
|
|
4539
|
+
if (data.commits.length === 0 && data.prs.length === 0 && data.builders.length === 0) {
|
|
4540
|
+
content.innerHTML = `
|
|
4541
|
+
<div class="activity-tab-container">
|
|
4542
|
+
<div class="activity-empty">
|
|
4543
|
+
<p>No activity recorded today</p>
|
|
4544
|
+
<p style="font-size: 12px; margin-top: 8px;">Make some commits or create PRs to see your daily summary!</p>
|
|
4545
|
+
</div>
|
|
4546
|
+
</div>
|
|
4547
|
+
`;
|
|
4548
|
+
return;
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4551
|
+
const hours = Math.floor(data.timeTracking.activeMinutes / 60);
|
|
4552
|
+
const mins = data.timeTracking.activeMinutes % 60;
|
|
4553
|
+
const uniqueBranches = new Set(data.commits.map(c => c.branch)).size;
|
|
4554
|
+
const mergedPrs = data.prs.filter(p => p.state === 'MERGED').length;
|
|
4555
|
+
|
|
4556
|
+
// Format time strings
|
|
4557
|
+
const formatTime = (isoString) => {
|
|
4558
|
+
if (!isoString) return '--';
|
|
4559
|
+
const date = new Date(isoString);
|
|
4560
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
4561
|
+
};
|
|
4562
|
+
|
|
4563
|
+
let html = '<div class="activity-tab-container"><div class="activity-summary">';
|
|
4564
|
+
|
|
4565
|
+
// AI Summary (if available)
|
|
4566
|
+
if (data.aiSummary) {
|
|
4567
|
+
html += `<div class="activity-ai-summary">${escapeHtml(data.aiSummary)}</div>`;
|
|
4568
|
+
}
|
|
4569
|
+
|
|
4570
|
+
// Activity section
|
|
4571
|
+
html += `
|
|
4572
|
+
<div class="activity-section">
|
|
4573
|
+
<h4>Activity</h4>
|
|
4574
|
+
<ul>
|
|
4575
|
+
<li>${data.commits.length} commits across ${uniqueBranches} branch${uniqueBranches !== 1 ? 'es' : ''}</li>
|
|
4576
|
+
<li>${data.files.length} files modified</li>
|
|
4577
|
+
<li>${data.prs.length} PR${data.prs.length !== 1 ? 's' : ''} created${mergedPrs > 0 ? `, ${mergedPrs} merged` : ''}</li>
|
|
4578
|
+
</ul>
|
|
4579
|
+
</div>
|
|
4580
|
+
`;
|
|
4581
|
+
|
|
4582
|
+
// Projects section (if any status changes)
|
|
4583
|
+
if (data.projectChanges && data.projectChanges.length > 0) {
|
|
4584
|
+
html += `
|
|
4585
|
+
<div class="activity-section">
|
|
4586
|
+
<h4>Projects Touched</h4>
|
|
4587
|
+
<ul>
|
|
4588
|
+
${data.projectChanges.map(p => `<li>${escapeHtml(p.id)}: ${escapeHtml(p.title)} (${escapeHtml(p.oldStatus)} → ${escapeHtml(p.newStatus)})</li>`).join('')}
|
|
4589
|
+
</ul>
|
|
4590
|
+
</div>
|
|
4591
|
+
`;
|
|
4592
|
+
}
|
|
4593
|
+
|
|
4594
|
+
// Time section
|
|
4595
|
+
html += `
|
|
4596
|
+
<div class="activity-section">
|
|
4597
|
+
<h4>Time</h4>
|
|
4598
|
+
<p><span class="activity-time-value">~${hours}h ${mins}m</span> active time</p>
|
|
4599
|
+
<p>First activity: ${formatTime(data.timeTracking.firstActivity)}</p>
|
|
4600
|
+
<p>Last activity: ${formatTime(data.timeTracking.lastActivity)}</p>
|
|
4601
|
+
</div>
|
|
4602
|
+
`;
|
|
4603
|
+
|
|
4604
|
+
// Copy button
|
|
4605
|
+
html += `
|
|
4606
|
+
<div class="activity-actions">
|
|
4607
|
+
<button class="btn" onclick="copyActivityToClipboard()">Copy to Clipboard</button>
|
|
4608
|
+
</div>
|
|
4609
|
+
`;
|
|
4610
|
+
|
|
4611
|
+
html += '</div></div>';
|
|
4612
|
+
content.innerHTML = html;
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4615
|
+
// Render activity summary content
|
|
4616
|
+
function renderActivitySummary(data) {
|
|
4617
|
+
const content = document.getElementById('activity-content');
|
|
4618
|
+
|
|
4619
|
+
// Check for zero activity
|
|
4620
|
+
if (data.commits.length === 0 && data.prs.length === 0 && data.builders.length === 0) {
|
|
4621
|
+
content.innerHTML = `
|
|
4622
|
+
<div class="activity-empty">
|
|
4623
|
+
<p>No activity recorded today</p>
|
|
4624
|
+
<p style="font-size: 12px; margin-top: 8px;">Make some commits or create PRs to see your daily summary!</p>
|
|
4625
|
+
</div>
|
|
4626
|
+
`;
|
|
4627
|
+
return;
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
const hours = Math.floor(data.timeTracking.activeMinutes / 60);
|
|
4631
|
+
const mins = data.timeTracking.activeMinutes % 60;
|
|
4632
|
+
const uniqueBranches = new Set(data.commits.map(c => c.branch)).size;
|
|
4633
|
+
const mergedPrs = data.prs.filter(p => p.state === 'MERGED').length;
|
|
4634
|
+
|
|
4635
|
+
// Format time strings
|
|
4636
|
+
const formatTime = (isoString) => {
|
|
4637
|
+
if (!isoString) return '--';
|
|
4638
|
+
const date = new Date(isoString);
|
|
4639
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
4640
|
+
};
|
|
4641
|
+
|
|
4642
|
+
let html = '<div class="activity-summary">';
|
|
4643
|
+
|
|
4644
|
+
// AI Summary (if available)
|
|
4645
|
+
if (data.aiSummary) {
|
|
4646
|
+
html += `<div class="activity-ai-summary">${escapeHtml(data.aiSummary)}</div>`;
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
// Activity section
|
|
4650
|
+
html += `
|
|
4651
|
+
<div class="activity-section">
|
|
4652
|
+
<h4>Activity</h4>
|
|
4653
|
+
<ul>
|
|
4654
|
+
<li>${data.commits.length} commits across ${uniqueBranches} branch${uniqueBranches !== 1 ? 'es' : ''}</li>
|
|
4655
|
+
<li>${data.files.length} files modified</li>
|
|
4656
|
+
<li>${data.prs.length} PR${data.prs.length !== 1 ? 's' : ''} created${mergedPrs > 0 ? `, ${mergedPrs} merged` : ''}</li>
|
|
4657
|
+
</ul>
|
|
4658
|
+
</div>
|
|
4659
|
+
`;
|
|
4660
|
+
|
|
4661
|
+
// Projects section (if any status changes)
|
|
4662
|
+
if (data.projectChanges && data.projectChanges.length > 0) {
|
|
4663
|
+
html += `
|
|
4664
|
+
<div class="activity-section">
|
|
4665
|
+
<h4>Projects Touched</h4>
|
|
4666
|
+
<ul>
|
|
4667
|
+
${data.projectChanges.map(p => `<li>${escapeHtml(p.id)}: ${escapeHtml(p.title)} (${escapeHtml(p.oldStatus)} → ${escapeHtml(p.newStatus)})</li>`).join('')}
|
|
4668
|
+
</ul>
|
|
4669
|
+
</div>
|
|
4670
|
+
`;
|
|
4671
|
+
}
|
|
4672
|
+
|
|
4673
|
+
// Time section
|
|
4674
|
+
html += `
|
|
4675
|
+
<div class="activity-section">
|
|
4676
|
+
<h4>Time</h4>
|
|
4677
|
+
<p><span class="activity-time-value">~${hours}h ${mins}m</span> active time</p>
|
|
4678
|
+
<p>First activity: ${formatTime(data.timeTracking.firstActivity)}</p>
|
|
4679
|
+
<p>Last activity: ${formatTime(data.timeTracking.lastActivity)}</p>
|
|
4680
|
+
</div>
|
|
4681
|
+
`;
|
|
4682
|
+
|
|
4683
|
+
html += '</div>';
|
|
4684
|
+
content.innerHTML = html;
|
|
4685
|
+
}
|
|
4686
|
+
|
|
4687
|
+
// Close activity modal
|
|
4688
|
+
function closeActivityModal() {
|
|
4689
|
+
document.getElementById('activity-modal').classList.add('hidden');
|
|
4690
|
+
}
|
|
4691
|
+
|
|
4692
|
+
// Copy activity summary to clipboard
|
|
4693
|
+
function copyActivitySummary() {
|
|
4694
|
+
if (!activityData) return;
|
|
4695
|
+
|
|
4696
|
+
const hours = Math.floor(activityData.timeTracking.activeMinutes / 60);
|
|
4697
|
+
const mins = activityData.timeTracking.activeMinutes % 60;
|
|
4698
|
+
const uniqueBranches = new Set(activityData.commits.map(c => c.branch)).size;
|
|
4699
|
+
const mergedPrs = activityData.prs.filter(p => p.state === 'MERGED').length;
|
|
4700
|
+
|
|
4701
|
+
let markdown = `## Today's Summary\n\n`;
|
|
4702
|
+
|
|
4703
|
+
if (activityData.aiSummary) {
|
|
4704
|
+
markdown += `${activityData.aiSummary}\n\n`;
|
|
4705
|
+
}
|
|
4706
|
+
|
|
4707
|
+
markdown += `### Activity\n`;
|
|
4708
|
+
markdown += `- ${activityData.commits.length} commits across ${uniqueBranches} branches\n`;
|
|
4709
|
+
markdown += `- ${activityData.files.length} files modified\n`;
|
|
4710
|
+
markdown += `- ${activityData.prs.length} PRs${mergedPrs > 0 ? ` (${mergedPrs} merged)` : ''}\n\n`;
|
|
4711
|
+
|
|
4712
|
+
if (activityData.projectChanges && activityData.projectChanges.length > 0) {
|
|
4713
|
+
markdown += `### Projects Touched\n`;
|
|
4714
|
+
activityData.projectChanges.forEach(p => {
|
|
4715
|
+
markdown += `- ${p.id}: ${p.title} (${p.oldStatus} → ${p.newStatus})\n`;
|
|
4716
|
+
});
|
|
4717
|
+
markdown += '\n';
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
markdown += `### Time\n`;
|
|
4721
|
+
markdown += `Active time: ~${hours}h ${mins}m\n`;
|
|
4722
|
+
|
|
4723
|
+
navigator.clipboard.writeText(markdown).then(() => {
|
|
4724
|
+
showToast('Copied to clipboard', 'success');
|
|
4725
|
+
}).catch(() => {
|
|
4726
|
+
showToast('Failed to copy', 'error');
|
|
4727
|
+
});
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
// Close activity modal when clicking backdrop
|
|
4731
|
+
document.getElementById('activity-modal').addEventListener('click', (e) => {
|
|
4732
|
+
if (e.target.id === 'activity-modal') {
|
|
4733
|
+
closeActivityModal();
|
|
4734
|
+
}
|
|
4735
|
+
});
|
|
4736
|
+
|
|
3722
4737
|
// Initialize on load
|
|
3723
4738
|
init();
|
|
3724
4739
|
</script>
|