@cluesmith/codev 1.3.0 → 1.4.1

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.
Files changed (63) hide show
  1. package/bin/af.js +0 -0
  2. package/bin/codev.js +0 -0
  3. package/bin/consult.js +0 -0
  4. package/bin/generate-image.js +0 -0
  5. package/dist/agent-farm/cli.d.ts.map +1 -1
  6. package/dist/agent-farm/cli.js +2 -1
  7. package/dist/agent-farm/cli.js.map +1 -1
  8. package/dist/agent-farm/commands/cleanup.js +12 -51
  9. package/dist/agent-farm/commands/cleanup.js.map +1 -1
  10. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  11. package/dist/agent-farm/commands/spawn.js +13 -1
  12. package/dist/agent-farm/commands/spawn.js.map +1 -1
  13. package/dist/agent-farm/servers/dashboard-server.js +107 -5
  14. package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
  15. package/dist/agent-farm/types.d.ts +1 -0
  16. package/dist/agent-farm/types.d.ts.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +2 -17
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/adopt.d.ts.map +1 -1
  21. package/dist/commands/adopt.js +27 -2
  22. package/dist/commands/adopt.js.map +1 -1
  23. package/dist/commands/consult/index.d.ts.map +1 -1
  24. package/dist/commands/consult/index.js +23 -7
  25. package/dist/commands/consult/index.js.map +1 -1
  26. package/dist/commands/doctor.d.ts.map +1 -1
  27. package/dist/commands/doctor.js +51 -0
  28. package/dist/commands/doctor.js.map +1 -1
  29. package/dist/commands/init.d.ts.map +1 -1
  30. package/dist/commands/init.js +23 -2
  31. package/dist/commands/init.js.map +1 -1
  32. package/dist/version.d.ts +3 -0
  33. package/dist/version.d.ts.map +1 -0
  34. package/dist/version.js +23 -0
  35. package/dist/version.js.map +1 -0
  36. package/package.json +1 -1
  37. package/skeleton/DEPENDENCIES.md +3 -3
  38. package/skeleton/protocols/maintain/protocol.md +2 -2
  39. package/skeleton/{docs → resources}/commands/codev.md +0 -39
  40. package/skeleton/{docs → resources}/commands/consult.md +12 -2
  41. package/skeleton/{docs → resources}/commands/overview.md +0 -1
  42. package/skeleton/roles/architect.md +22 -0
  43. package/skeleton/roles/builder.md +22 -0
  44. package/skeleton/templates/arch.md +56 -0
  45. package/skeleton/templates/pr-overview.md +73 -0
  46. package/templates/dashboard-split.html +526 -164
  47. package/templates/open.html +285 -2
  48. package/templates/tower.html +71 -12
  49. package/dist/agent-farm/index.d.ts +0 -7
  50. package/dist/agent-farm/index.d.ts.map +0 -1
  51. package/dist/agent-farm/index.js +0 -373
  52. package/dist/agent-farm/index.js.map +0 -1
  53. package/skeleton/bin/agent-farm +0 -7
  54. package/skeleton/bin/codev-doctor +0 -335
  55. package/skeleton/resources/lessons-learned.md +0 -30
  56. /package/skeleton/{roles/review-types → consult-types}/impl-review.md +0 -0
  57. /package/skeleton/{roles/review-types → consult-types}/integration-review.md +0 -0
  58. /package/skeleton/{roles/review-types → consult-types}/plan-review.md +0 -0
  59. /package/skeleton/{roles/review-types → consult-types}/pr-ready.md +0 -0
  60. /package/skeleton/{roles/review-types → consult-types}/spec-review.md +0 -0
  61. /package/skeleton/{docs → resources}/commands/agent-farm.md +0 -0
  62. /package/skeleton/{AGENTS.md.template → templates/AGENTS.md} +0 -0
  63. /package/skeleton/{CLAUDE.md.template → templates/CLAUDE.md} +0 -0
@@ -1157,36 +1157,174 @@
1157
1157
  display: none;
1158
1158
  }
1159
1159
 
1160
- /* Files Tab Styles (Spec 0055) */
1161
- .files-container {
1160
+ /* Tree Styles (used by dashboard file browser) */
1161
+ .tree-item {
1162
+ display: flex;
1163
+ align-items: center;
1164
+ padding: 4px 8px;
1165
+ cursor: pointer;
1166
+ user-select: none;
1167
+ }
1168
+
1169
+ .tree-item:hover {
1170
+ background: var(--bg-secondary);
1171
+ }
1172
+
1173
+ .tree-item.selected {
1174
+ background: var(--tab-active);
1175
+ }
1176
+
1177
+ .tree-item-icon {
1178
+ width: 16px;
1179
+ height: 16px;
1180
+ margin-right: 4px;
1181
+ display: flex;
1182
+ align-items: center;
1183
+ justify-content: center;
1184
+ font-size: 10px;
1185
+ color: var(--text-muted);
1186
+ }
1187
+
1188
+ .tree-item-icon.folder-toggle {
1189
+ cursor: pointer;
1190
+ }
1191
+
1192
+ .tree-item-icon.folder-toggle:hover {
1193
+ color: var(--text-secondary);
1194
+ }
1195
+
1196
+ .tree-item-name {
1197
+ font-size: 13px;
1198
+ color: var(--text-secondary);
1199
+ overflow: hidden;
1200
+ text-overflow: ellipsis;
1201
+ white-space: nowrap;
1202
+ }
1203
+
1204
+ .tree-item:hover .tree-item-name {
1205
+ color: var(--text-primary);
1206
+ }
1207
+
1208
+ .tree-item[data-type="dir"] .tree-item-name {
1209
+ color: var(--text-primary);
1210
+ }
1211
+
1212
+ .tree-item[data-type="file"]:hover .tree-item-name {
1213
+ color: var(--accent);
1214
+ }
1215
+
1216
+ .tree-children {
1217
+ overflow: hidden;
1218
+ }
1219
+
1220
+ .tree-children.collapsed {
1221
+ display: none;
1222
+ }
1223
+
1224
+ /* Dashboard Tab Styles (Spec 0057) */
1225
+ .dashboard-container {
1162
1226
  flex: 1;
1163
1227
  overflow-y: auto;
1164
1228
  display: flex;
1165
1229
  flex-direction: column;
1166
1230
  }
1167
1231
 
1168
- .files-header {
1232
+ .dashboard-header {
1233
+ display: flex;
1234
+ gap: 16px;
1235
+ padding: 16px;
1236
+ flex-shrink: 0;
1237
+ }
1238
+
1239
+ @media (max-width: 900px) {
1240
+ .dashboard-header {
1241
+ flex-direction: column;
1242
+ }
1243
+ }
1244
+
1245
+ /* Collapsible section styles */
1246
+ .dashboard-section {
1247
+ background: var(--bg-secondary);
1248
+ border: 1px solid var(--border);
1249
+ border-radius: 8px;
1250
+ overflow: hidden;
1251
+ display: flex;
1252
+ flex-direction: column;
1253
+ }
1254
+
1255
+ .dashboard-section.section-tabs,
1256
+ .dashboard-section.section-files {
1257
+ flex: 1;
1258
+ max-height: 280px;
1259
+ }
1260
+
1261
+ .dashboard-section.section-projects {
1262
+ flex: 0 0 auto;
1263
+ margin: 0 16px 16px 16px;
1264
+ max-height: 50%;
1265
+ overflow-y: auto;
1266
+ }
1267
+
1268
+ .dashboard-section.section-projects .dashboard-section-content {
1269
+ flex: 0 0 auto;
1270
+ }
1271
+
1272
+ /* Tabs/Files expand to fill remaining space above Projects */
1273
+ .dashboard-header {
1274
+ flex: 1;
1275
+ min-height: 0;
1276
+ }
1277
+
1278
+ .dashboard-section.section-tabs,
1279
+ .dashboard-section.section-files {
1280
+ max-height: none;
1281
+ }
1282
+
1283
+ .dashboard-section-header {
1169
1284
  display: flex;
1170
1285
  justify-content: space-between;
1171
1286
  align-items: center;
1172
1287
  padding: 8px 12px;
1173
- background: var(--bg-secondary);
1288
+ cursor: pointer;
1289
+ user-select: none;
1290
+ flex-shrink: 0;
1174
1291
  border-bottom: 1px solid var(--border);
1175
1292
  }
1176
1293
 
1177
- .files-header-title {
1294
+ .dashboard-section-header:hover {
1295
+ background: var(--bg-tertiary);
1296
+ }
1297
+
1298
+ .dashboard-section-header h3 {
1178
1299
  font-size: 12px;
1179
- color: var(--text-muted);
1180
1300
  text-transform: uppercase;
1301
+ color: var(--text-muted);
1181
1302
  letter-spacing: 0.5px;
1303
+ margin: 0;
1304
+ display: flex;
1305
+ align-items: center;
1306
+ gap: 6px;
1307
+ }
1308
+
1309
+ .dashboard-section-header .collapse-icon {
1310
+ font-size: 10px;
1311
+ transition: transform 0.2s;
1312
+ }
1313
+
1314
+ .dashboard-section.collapsed .collapse-icon {
1315
+ transform: rotate(-90deg);
1316
+ }
1317
+
1318
+ .dashboard-section.collapsed .dashboard-section-header {
1319
+ border-bottom: none;
1182
1320
  }
1183
1321
 
1184
- .files-header-actions {
1322
+ .dashboard-section-header .header-actions {
1185
1323
  display: flex;
1186
1324
  gap: 4px;
1187
1325
  }
1188
1326
 
1189
- .files-header-actions button {
1327
+ .dashboard-section-header .header-actions button {
1190
1328
  padding: 4px 8px;
1191
1329
  border-radius: 4px;
1192
1330
  border: 1px solid var(--border);
@@ -1196,97 +1334,184 @@
1196
1334
  font-size: 11px;
1197
1335
  }
1198
1336
 
1199
- .files-header-actions button:hover {
1337
+ .dashboard-section-header .header-actions button:hover {
1200
1338
  background: var(--tab-hover);
1201
1339
  color: var(--text-primary);
1202
1340
  }
1203
1341
 
1204
- .files-tree {
1342
+ .dashboard-section-content {
1205
1343
  flex: 1;
1206
1344
  overflow-y: auto;
1207
- padding: 8px 0;
1345
+ padding: 8px 12px;
1208
1346
  }
1209
1347
 
1210
- .tree-item {
1211
- display: flex;
1212
- align-items: center;
1213
- padding: 4px 8px;
1214
- cursor: pointer;
1215
- user-select: none;
1348
+ .dashboard-section.collapsed .dashboard-section-content {
1349
+ display: none;
1216
1350
  }
1217
1351
 
1218
- .tree-item:hover {
1352
+ /* Legacy support */
1353
+ .dashboard-column {
1219
1354
  background: var(--bg-secondary);
1355
+ border: 1px solid var(--border);
1356
+ border-radius: 8px;
1357
+ padding: 12px;
1358
+ overflow: hidden;
1359
+ display: flex;
1360
+ flex-direction: column;
1361
+ max-height: 280px;
1220
1362
  }
1221
1363
 
1222
- .tree-item.selected {
1223
- background: var(--tab-active);
1224
- }
1225
-
1226
- .tree-item-icon {
1227
- width: 16px;
1228
- height: 16px;
1229
- margin-right: 4px;
1364
+ .dashboard-column-header {
1230
1365
  display: flex;
1366
+ justify-content: space-between;
1231
1367
  align-items: center;
1232
- justify-content: center;
1233
- font-size: 10px;
1368
+ margin-bottom: 8px;
1369
+ flex-shrink: 0;
1370
+ }
1371
+
1372
+ .dashboard-column-header h3 {
1373
+ font-size: 12px;
1374
+ text-transform: uppercase;
1234
1375
  color: var(--text-muted);
1376
+ letter-spacing: 0.5px;
1377
+ margin: 0;
1235
1378
  }
1236
1379
 
1237
- .tree-item-icon.folder-toggle {
1238
- cursor: pointer;
1380
+ .dashboard-column-header .header-actions {
1381
+ display: flex;
1382
+ gap: 4px;
1239
1383
  }
1240
1384
 
1241
- .tree-item-icon.folder-toggle:hover {
1385
+ .dashboard-column-header .header-actions button {
1386
+ padding: 4px 8px;
1387
+ border-radius: 4px;
1388
+ border: 1px solid var(--border);
1389
+ background: var(--bg-tertiary);
1242
1390
  color: var(--text-secondary);
1391
+ cursor: pointer;
1392
+ font-size: 11px;
1243
1393
  }
1244
1394
 
1245
- .tree-item-name {
1395
+ .dashboard-column-header .header-actions button:hover {
1396
+ background: var(--tab-hover);
1397
+ color: var(--text-primary);
1398
+ }
1399
+
1400
+ .dashboard-tabs-list {
1401
+ flex: 1;
1402
+ overflow-y: auto;
1403
+ margin-bottom: 8px;
1404
+ }
1405
+
1406
+ .dashboard-tab-item {
1407
+ display: flex;
1408
+ align-items: center;
1409
+ gap: 8px;
1410
+ padding: 6px 8px;
1411
+ border-radius: 4px;
1412
+ cursor: pointer;
1246
1413
  font-size: 13px;
1247
1414
  color: var(--text-secondary);
1248
- overflow: hidden;
1249
- text-overflow: ellipsis;
1250
- white-space: nowrap;
1251
1415
  }
1252
1416
 
1253
- .tree-item:hover .tree-item-name {
1254
- color: var(--text-primary);
1417
+ .dashboard-tab-item:hover {
1418
+ background: var(--bg-tertiary);
1255
1419
  }
1256
1420
 
1257
- .tree-item[data-type="dir"] .tree-item-name {
1258
- color: var(--text-primary);
1421
+ .dashboard-tab-item.active {
1422
+ background: var(--accent);
1423
+ color: white;
1259
1424
  }
1260
1425
 
1261
- .tree-item[data-type="file"]:hover .tree-item-name {
1262
- color: var(--accent);
1426
+ .dashboard-tab-item .tab-icon {
1427
+ font-size: 14px;
1428
+ flex-shrink: 0;
1263
1429
  }
1264
1430
 
1265
- .tree-children {
1431
+ .dashboard-tab-item .tab-name {
1432
+ flex: 1;
1266
1433
  overflow: hidden;
1434
+ text-overflow: ellipsis;
1435
+ white-space: nowrap;
1267
1436
  }
1268
1437
 
1269
- .tree-children.collapsed {
1270
- display: none;
1438
+ .dashboard-actions {
1439
+ flex-shrink: 0;
1440
+ display: flex;
1441
+ gap: 8px;
1271
1442
  }
1272
1443
 
1273
- .files-loading {
1444
+ .dashboard-actions .btn-action {
1445
+ flex: 1;
1446
+ padding: 8px 12px;
1447
+ border-radius: 4px;
1448
+ border: 1px dashed var(--border);
1449
+ background: transparent;
1450
+ color: var(--text-muted);
1451
+ cursor: pointer;
1452
+ font-size: 12px;
1274
1453
  display: flex;
1275
1454
  align-items: center;
1276
1455
  justify-content: center;
1277
- padding: 24px;
1278
- color: var(--text-muted);
1279
- font-size: 13px;
1456
+ gap: 4px;
1280
1457
  }
1281
1458
 
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;
1459
+ .dashboard-actions .btn-action:hover {
1460
+ border-style: solid;
1288
1461
  color: var(--text-secondary);
1462
+ background: var(--bg-tertiary);
1463
+ }
1464
+
1465
+ .dashboard-files-list {
1466
+ flex: 1;
1467
+ overflow-y: auto;
1468
+ }
1469
+
1470
+ .dashboard-files-list .tree-item {
1471
+ padding: 3px 6px;
1472
+ font-size: 12px;
1473
+ }
1474
+
1475
+ .dashboard-files-list .tree-item-name {
1476
+ font-size: 12px;
1477
+ }
1478
+
1479
+ .dashboard-empty-state {
1480
+ color: var(--text-muted);
1289
1481
  font-size: 13px;
1482
+ padding: 12px;
1483
+ text-align: center;
1484
+ }
1485
+
1486
+ /* Status indicators in dashboard tab list */
1487
+ .dashboard-status-indicator {
1488
+ width: 8px;
1489
+ height: 8px;
1490
+ border-radius: 50%;
1491
+ flex-shrink: 0;
1492
+ }
1493
+
1494
+ .dashboard-status-working {
1495
+ background: var(--status-active);
1496
+ animation: status-pulse 2s ease-in-out infinite;
1497
+ }
1498
+
1499
+ .dashboard-status-idle {
1500
+ background: var(--status-waiting);
1501
+ animation: status-blink-slow 3s ease-in-out infinite;
1502
+ }
1503
+
1504
+ .dashboard-status-blocked {
1505
+ background: var(--status-error);
1506
+ animation: status-blink-fast 0.8s ease-in-out infinite;
1507
+ }
1508
+
1509
+ @media (prefers-reduced-motion: reduce) {
1510
+ .dashboard-status-working,
1511
+ .dashboard-status-idle,
1512
+ .dashboard-status-blocked {
1513
+ animation: none;
1514
+ }
1290
1515
  }
1291
1516
  </style>
1292
1517
  </head>
@@ -1396,6 +1621,30 @@
1396
1621
  let pendingCloseTabId = null;
1397
1622
  let contextMenuTabId = null;
1398
1623
 
1624
+ // Collapsible section state (persisted to localStorage)
1625
+ const SECTION_STATE_KEY = 'codev-dashboard-sections';
1626
+ let sectionState = loadSectionState();
1627
+
1628
+ function loadSectionState() {
1629
+ try {
1630
+ const saved = localStorage.getItem(SECTION_STATE_KEY);
1631
+ if (saved) return JSON.parse(saved);
1632
+ } catch (e) { /* ignore */ }
1633
+ return { tabs: true, files: true, projects: true };
1634
+ }
1635
+
1636
+ function saveSectionState() {
1637
+ try {
1638
+ localStorage.setItem(SECTION_STATE_KEY, JSON.stringify(sectionState));
1639
+ } catch (e) { /* ignore */ }
1640
+ }
1641
+
1642
+ function toggleSection(section) {
1643
+ sectionState[section] = !sectionState[section];
1644
+ saveSectionState();
1645
+ renderDashboardTabContent();
1646
+ }
1647
+
1399
1648
  // Initialize
1400
1649
  function init() {
1401
1650
  buildTabsFromState();
@@ -1516,19 +1765,11 @@
1516
1765
  const previousTabIds = new Set(tabs.map(t => t.id));
1517
1766
  tabs = [];
1518
1767
 
1519
- // Projects tab is ALWAYS first and uncloseable (Spec 0045)
1768
+ // Dashboard tab is ALWAYS first and uncloseable (Spec 0045, 0057)
1520
1769
  tabs.push({
1521
- id: 'projects',
1522
- type: 'projects',
1523
- name: 'Projects',
1524
- closeable: false
1525
- });
1526
-
1527
- // Files tab is second and uncloseable (Spec 0055)
1528
- tabs.push({
1529
- id: 'files',
1530
- type: 'files',
1531
- name: 'Files',
1770
+ id: 'dashboard',
1771
+ type: 'dashboard',
1772
+ name: 'Dashboard',
1532
1773
  closeable: false
1533
1774
  });
1534
1775
 
@@ -1569,7 +1810,7 @@
1569
1810
 
1570
1811
  // Detect new tabs and auto-switch to them (skip projects tab)
1571
1812
  for (const tab of tabs) {
1572
- if (tab.id !== 'projects' && tab.id !== 'files' && !knownTabIds.has(tab.id) && previousTabIds.size > 0) {
1813
+ if (tab.id !== 'dashboard' && tab.id !== 'files' && !knownTabIds.has(tab.id) && previousTabIds.size > 0) {
1573
1814
  // This is a new tab - switch to it
1574
1815
  activeTabId = tab.id;
1575
1816
  break;
@@ -1579,9 +1820,9 @@
1579
1820
  // Update known tab IDs
1580
1821
  knownTabIds = new Set(tabs.map(t => t.id));
1581
1822
 
1582
- // Set active tab to Projects on first load if none selected
1823
+ // Set active tab to Dashboard on first load if none selected
1583
1824
  if (!activeTabId) {
1584
- activeTabId = 'projects';
1825
+ activeTabId = 'dashboard';
1585
1826
  }
1586
1827
  }
1587
1828
 
@@ -1663,7 +1904,7 @@
1663
1904
  // Get tab icon
1664
1905
  function getTabIcon(type) {
1665
1906
  switch (type) {
1666
- case 'projects': return '📋';
1907
+ case 'dashboard': return '🏠';
1667
1908
  case 'files': return '📁';
1668
1909
  case 'file': return '📄';
1669
1910
  case 'builder': return '🔨';
@@ -1762,22 +2003,12 @@
1762
2003
  return;
1763
2004
  }
1764
2005
 
1765
- // Handle projects tab specially (no iframe, inline content)
1766
- if (tab.type === 'projects') {
1767
- if (currentTabType !== 'projects') {
1768
- currentTabType = 'projects';
1769
- currentTabPort = null;
1770
- renderProjectsTab();
1771
- }
1772
- return;
1773
- }
1774
-
1775
- // Handle files tab specially (no iframe, inline content)
1776
- if (tab.type === 'files') {
1777
- if (currentTabType !== 'files') {
1778
- currentTabType = 'files';
2006
+ // Handle dashboard tab specially (no iframe, inline content)
2007
+ if (tab.type === 'dashboard') {
2008
+ if (currentTabType !== 'dashboard') {
2009
+ currentTabType = 'dashboard';
1779
2010
  currentTabPort = null;
1780
- renderFilesTab();
2011
+ renderDashboardTab();
1781
2012
  }
1782
2013
  return;
1783
2014
  }
@@ -2987,53 +3218,6 @@
2987
3218
  }
2988
3219
  }
2989
3220
 
2990
- // Render the files tab (entry point)
2991
- // Only fetches on first load; use refreshFilesTree() to force reload
2992
- async function renderFilesTab() {
2993
- const content = document.getElementById('tab-content');
2994
-
2995
- // If already loaded, just render cached data (no network request)
2996
- if (filesTreeLoaded) {
2997
- renderFilesTabContent();
2998
- return;
2999
- }
3000
-
3001
- // First load - show loading state and fetch
3002
- content.innerHTML = '<div class="files-loading">Loading files...</div>';
3003
- await loadFilesTree();
3004
- renderFilesTabContent();
3005
- }
3006
-
3007
- // Render the files tab content (internal - called after data is loaded)
3008
- function renderFilesTabContent() {
3009
- const content = document.getElementById('tab-content');
3010
-
3011
- if (filesTreeError) {
3012
- content.innerHTML = `
3013
- <div class="files-container">
3014
- <div class="files-error">${escapeHtml(filesTreeError)}</div>
3015
- </div>
3016
- `;
3017
- return;
3018
- }
3019
-
3020
- content.innerHTML = `
3021
- <div class="files-container">
3022
- <div class="files-header">
3023
- <span class="files-header-title">Explorer</span>
3024
- <div class="files-header-actions">
3025
- <button onclick="collapseAllFolders()" title="Collapse All">⊟</button>
3026
- <button onclick="expandAllFolders()" title="Expand All">⊞</button>
3027
- <button onclick="refreshFilesTree()" title="Refresh">↻</button>
3028
- </div>
3029
- </div>
3030
- <div class="files-tree" id="files-tree">
3031
- ${renderTreeNodes(filesTreeData, 0)}
3032
- </div>
3033
- </div>
3034
- `;
3035
- }
3036
-
3037
3221
  // Escape a string for use inside a JavaScript string literal in onclick handlers
3038
3222
  // This handles quotes, backslashes, and other special characters
3039
3223
  function escapeJsString(str) {
@@ -3113,13 +3297,24 @@
3113
3297
  } else {
3114
3298
  filesTreeExpanded.add(path);
3115
3299
  }
3116
- renderFilesTabContent();
3300
+ rerenderFilesBrowser();
3301
+ }
3302
+
3303
+ // Re-render file browser in current context (dashboard or files tab)
3304
+ function rerenderFilesBrowser() {
3305
+ if (activeTabId === 'dashboard') {
3306
+ // Re-render just the files column in dashboard
3307
+ const filesListEl = document.getElementById('dashboard-files-list');
3308
+ if (filesListEl) {
3309
+ filesListEl.innerHTML = renderDashboardFilesBrowser();
3310
+ }
3311
+ }
3117
3312
  }
3118
3313
 
3119
3314
  // Collapse all folders
3120
3315
  function collapseAllFolders() {
3121
3316
  filesTreeExpanded.clear();
3122
- renderFilesTabContent();
3317
+ rerenderFilesBrowser();
3123
3318
  }
3124
3319
 
3125
3320
  // Expand all folders
@@ -3135,13 +3330,13 @@
3135
3330
  }
3136
3331
  }
3137
3332
  collectPaths(filesTreeData);
3138
- renderFilesTabContent();
3333
+ rerenderFilesBrowser();
3139
3334
  }
3140
3335
 
3141
3336
  // Refresh files tree
3142
3337
  async function refreshFilesTree() {
3143
3338
  await loadFilesTree();
3144
- renderFilesTabContent();
3339
+ rerenderFilesBrowser();
3145
3340
  showToast('Files refreshed', 'success');
3146
3341
  }
3147
3342
 
@@ -3189,55 +3384,222 @@
3189
3384
  function renderInfoHeader() {
3190
3385
  return `
3191
3386
  <div class="projects-info">
3192
- <h1 style="font-size: 20px; margin-bottom: 12px; color: var(--text-primary);">Codev: Project View</h1>
3193
- <p>This shows the state of all projects. Our goal is to move each project through all the stages until it reaches INTGR'D (integrated). Hover over column headers to learn about each stage.</p>
3194
- <p>To add projects, update status, or approve stages, use the <strong>Architect</strong> terminal on the left.</p>
3195
- <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>
3387
+ <h1 style="font-size: 20px; margin-bottom: 12px; color: var(--text-primary);">Agent Farm Dashboard</h1>
3388
+ <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>
3389
+ <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>
3196
3390
  </div>
3197
3391
  `;
3198
3392
  }
3199
3393
 
3200
- // Render the projects tab content (internal - called after data is loaded)
3201
- function renderProjectsTabContent() {
3394
+ // Render the dashboard tab content (internal - called after data is loaded)
3395
+ function renderDashboardTabContent() {
3202
3396
  const content = document.getElementById('tab-content');
3203
3397
 
3204
- if (projectlistError) {
3205
- content.innerHTML = `
3206
- <div class="projects-container">
3207
- ${renderErrorBanner(projectlistError)}
3398
+ content.innerHTML = `
3399
+ <div class="dashboard-container">
3400
+ ${renderInfoHeader()}
3401
+ <div class="dashboard-header">
3402
+ <!-- Tabs Section -->
3403
+ <div class="dashboard-section section-tabs ${sectionState.tabs ? '' : 'collapsed'}">
3404
+ <div class="dashboard-section-header" onclick="toggleSection('tabs')">
3405
+ <h3><span class="collapse-icon">▼</span> Tabs</h3>
3406
+ </div>
3407
+ <div class="dashboard-section-content">
3408
+ <div class="dashboard-tabs-list" id="dashboard-tabs-list">
3409
+ ${renderDashboardTabsList()}
3410
+ </div>
3411
+ </div>
3412
+ </div>
3413
+ <!-- Files Section -->
3414
+ <div class="dashboard-section section-files ${sectionState.files ? '' : 'collapsed'}">
3415
+ <div class="dashboard-section-header" onclick="toggleSection('files')">
3416
+ <h3><span class="collapse-icon">▼</span> Files</h3>
3417
+ <div class="header-actions" onclick="event.stopPropagation()">
3418
+ <button onclick="collapseAllFolders()" title="Collapse All">⊟</button>
3419
+ <button onclick="expandAllFolders()" title="Expand All">⊞</button>
3420
+ </div>
3421
+ </div>
3422
+ <div class="dashboard-section-content">
3423
+ <div class="dashboard-files-list" id="dashboard-files-list">
3424
+ ${renderDashboardFilesBrowser()}
3425
+ </div>
3426
+ </div>
3427
+ </div>
3428
+ </div>
3429
+ <!-- Projects Section -->
3430
+ <div class="dashboard-section section-projects ${sectionState.projects ? '' : 'collapsed'}">
3431
+ <div class="dashboard-section-header" onclick="toggleSection('projects')">
3432
+ <h3><span class="collapse-icon">▼</span> Projects</h3>
3433
+ </div>
3434
+ <div class="dashboard-section-content" id="dashboard-projects">
3435
+ ${renderDashboardProjectsSection()}
3436
+ </div>
3437
+ </div>
3438
+ </div>
3439
+ `;
3440
+ }
3441
+
3442
+ // Render the tabs list for dashboard
3443
+ function renderDashboardTabsList() {
3444
+ // Filter to show terminal tabs only (not Dashboard/Files tabs)
3445
+ const terminalTabs = tabs.filter(t => t.type !== 'dashboard' && t.type !== 'files');
3446
+
3447
+ if (terminalTabs.length === 0) {
3448
+ return '<div class="dashboard-empty-state">No tabs open</div>';
3449
+ }
3450
+
3451
+ return terminalTabs.map(tab => {
3452
+ const isActive = tab.id === activeTabId;
3453
+ const icon = getTabIcon(tab.type);
3454
+ const statusIndicator = getDashboardStatusIndicator(tab);
3455
+
3456
+ return `
3457
+ <div class="dashboard-tab-item ${isActive ? 'active' : ''}" onclick="selectTab('${tab.id}')">
3458
+ ${statusIndicator}
3459
+ <span class="tab-icon">${icon}</span>
3460
+ <span class="tab-name">${escapeHtml(tab.name)}</span>
3208
3461
  </div>
3209
3462
  `;
3210
- return;
3463
+ }).join('');
3464
+ }
3465
+
3466
+ // Get status indicator for dashboard tab list
3467
+ function getDashboardStatusIndicator(tab) {
3468
+ if (tab.type !== 'builder') return '';
3469
+
3470
+ // Use builder status from state
3471
+ const builderState = (state.builders || []).find(b => `builder-${b.id}` === tab.id);
3472
+ if (!builderState) return '';
3473
+
3474
+ const status = builderState.status;
3475
+ if (['spawning', 'implementing'].includes(status)) {
3476
+ return '<span class="dashboard-status-indicator dashboard-status-working" title="Working"></span>';
3477
+ }
3478
+ if (status === 'blocked') {
3479
+ return '<span class="dashboard-status-indicator dashboard-status-blocked" title="Blocked"></span>';
3480
+ }
3481
+ if (['pr-ready', 'complete'].includes(status)) {
3482
+ return '<span class="dashboard-status-indicator dashboard-status-idle" title="Idle"></span>';
3483
+ }
3484
+ return '';
3485
+ }
3486
+
3487
+ // Render compact file browser for dashboard
3488
+ function renderDashboardFilesBrowser() {
3489
+ if (filesTreeError) {
3490
+ return `<div class="dashboard-empty-state">${escapeHtml(filesTreeError)}</div>`;
3491
+ }
3492
+
3493
+ if (!filesTreeLoaded || filesTreeData.length === 0) {
3494
+ return '<div class="dashboard-empty-state">Loading files...</div>';
3495
+ }
3496
+
3497
+ return renderTreeNodes(filesTreeData, 0);
3498
+ }
3499
+
3500
+ // Render the projects section for dashboard
3501
+ function renderDashboardProjectsSection() {
3502
+ if (projectlistError) {
3503
+ return renderErrorBanner(projectlistError);
3211
3504
  }
3212
3505
 
3213
3506
  if (projectsData.length === 0) {
3214
- content.innerHTML = `
3215
- <div class="projects-container">
3216
- ${renderWelcomeScreen()}
3507
+ // No welcome screen - just a helpful message
3508
+ return `
3509
+ <div class="dashboard-empty-state" style="padding: 24px;">
3510
+ No projects yet. Ask the Architect to create your first project.
3217
3511
  </div>
3218
3512
  `;
3219
- return;
3220
3513
  }
3221
3514
 
3222
- content.innerHTML = `
3223
- <div class="projects-container">
3224
- ${renderInfoHeader()}
3225
- ${renderKanbanGrid(projectsData)}
3226
- ${renderTerminalProjects(projectsData)}
3227
- </div>
3515
+ // Render the existing project view
3516
+ return `
3517
+ ${renderKanbanGrid(projectsData)}
3518
+ ${renderTerminalProjects(projectsData)}
3228
3519
  `;
3229
3520
  }
3230
3521
 
3231
- // Render the projects tab (entry point - loads data first)
3232
- async function renderProjectsTab() {
3522
+ // Create new utility shell (quick action button)
3523
+ async function createNewShell() {
3524
+ try {
3525
+ const response = await fetch('/api/tabs/shell', { method: 'POST' });
3526
+ const data = await response.json();
3527
+ if (!data.success && data.error) {
3528
+ showToast(data.error || 'Failed to create shell', 'error');
3529
+ return;
3530
+ }
3531
+ await refresh();
3532
+ if (data.id) {
3533
+ selectTab(`shell-${data.id}`);
3534
+ }
3535
+ showToast('Shell created', 'success');
3536
+ } catch (err) {
3537
+ showToast('Network error: ' + err.message, 'error');
3538
+ }
3539
+ }
3540
+
3541
+ // Create new worktree shell (quick action button)
3542
+ async function createNewWorktreeShell() {
3543
+ const branch = prompt('Branch name (leave empty for temp worktree):');
3544
+ if (branch === null) return; // User cancelled
3545
+
3546
+ try {
3547
+ const response = await fetch('/api/tabs/shell', {
3548
+ method: 'POST',
3549
+ headers: { 'Content-Type': 'application/json' },
3550
+ body: JSON.stringify({ worktree: true, branch: branch || undefined })
3551
+ });
3552
+ const data = await response.json();
3553
+ if (!data.success && data.error) {
3554
+ showToast(data.error || 'Failed to create worktree shell', 'error');
3555
+ return;
3556
+ }
3557
+ await refresh();
3558
+ // Auto-select the newly created tab (consistent with createNewShell behavior)
3559
+ if (data.id) {
3560
+ selectTab(`shell-${data.id}`);
3561
+ }
3562
+ showToast('Worktree shell created', 'success');
3563
+ } catch (err) {
3564
+ showToast('Network error: ' + err.message, 'error');
3565
+ }
3566
+ }
3567
+
3568
+ // Render the dashboard tab (entry point - loads data first)
3569
+ async function renderDashboardTab() {
3233
3570
  const content = document.getElementById('tab-content');
3234
- content.innerHTML = '<div class="projects-container"><p style="color: var(--text-muted);">Loading projects...</p></div>';
3571
+ content.innerHTML = '<div class="dashboard-container"><p style="color: var(--text-muted); padding: 16px;">Loading dashboard...</p></div>';
3235
3572
 
3236
- await loadProjectlist();
3237
- renderProjectsTabContent();
3573
+ // Load both projectlist and files tree in parallel
3574
+ await Promise.all([
3575
+ loadProjectlist(),
3576
+ loadFilesTreeIfNeeded()
3577
+ ]);
3578
+
3579
+ renderDashboardTabContent();
3238
3580
  checkStarterMode(); // Update polling state after initial load
3239
3581
  }
3240
3582
 
3583
+ // Load files tree if not already loaded
3584
+ async function loadFilesTreeIfNeeded() {
3585
+ if (!filesTreeLoaded) {
3586
+ await loadFilesTree();
3587
+ }
3588
+ }
3589
+
3590
+ // Legacy function for backward compatibility (still used by polling)
3591
+ function renderProjectsTabContent() {
3592
+ // If dashboard tab is active, re-render dashboard instead
3593
+ if (activeTabId === 'dashboard') {
3594
+ renderDashboardTabContent();
3595
+ }
3596
+ }
3597
+
3598
+ // Legacy function alias
3599
+ async function renderProjectsTab() {
3600
+ await renderDashboardTab();
3601
+ }
3602
+
3241
3603
  // Load projectlist.md from disk
3242
3604
  async function loadProjectlist() {
3243
3605
  try {
@@ -3282,8 +3644,8 @@
3282
3644
 
3283
3645
  // Poll projectlist for changes (every 5 seconds)
3284
3646
  async function pollProjectlist() {
3285
- // Only poll if projects tab is active
3286
- if (activeTabId !== 'projects') return;
3647
+ // Only poll if dashboard tab is active
3648
+ if (activeTabId !== 'dashboard') return;
3287
3649
 
3288
3650
  try {
3289
3651
  const response = await fetch('/file?path=codev/projectlist.md');