@cluesmith/codev 1.2.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/generate-image.js +7 -0
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +2 -37
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +12 -51
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +13 -1
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/start.js +32 -0
- package/dist/agent-farm/commands/start.js.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.d.ts +0 -2
- package/dist/agent-farm/servers/dashboard-server.d.ts.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.js +196 -9
- package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
- package/dist/agent-farm/servers/open-server.d.ts +0 -2
- package/dist/agent-farm/servers/open-server.d.ts.map +1 -1
- package/dist/agent-farm/servers/open-server.js +12 -7
- package/dist/agent-farm/servers/open-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.d.ts +0 -2
- package/dist/agent-farm/servers/tower-server.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +16 -4
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/types.d.ts +1 -0
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/shell.d.ts +2 -1
- package/dist/agent-farm/utils/shell.d.ts.map +1 -1
- package/dist/agent-farm/utils/shell.js +35 -2
- package/dist/agent-farm/utils/shell.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -17
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +27 -2
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +23 -7
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +51 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate-image.d.ts +13 -0
- package/dist/commands/generate-image.d.ts.map +1 -0
- package/dist/commands/generate-image.js +155 -0
- package/dist/commands/generate-image.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +23 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/package.json +5 -3
- package/skeleton/DEPENDENCIES.md +3 -3
- package/skeleton/protocols/maintain/protocol.md +2 -2
- package/skeleton/{docs → resources}/commands/codev.md +0 -39
- package/skeleton/{docs → resources}/commands/consult.md +12 -2
- package/skeleton/{docs → resources}/commands/overview.md +0 -1
- package/skeleton/roles/architect.md +22 -0
- package/skeleton/roles/builder.md +22 -0
- package/skeleton/templates/arch.md +56 -0
- package/skeleton/templates/pr-overview.md +73 -0
- package/templates/dashboard-split.html +781 -39
- package/templates/open.html +278 -0
- package/templates/tower.html +71 -12
- package/dist/agent-farm/index.d.ts +0 -7
- package/dist/agent-farm/index.d.ts.map +0 -1
- package/dist/agent-farm/index.js +0 -373
- package/dist/agent-farm/index.js.map +0 -1
- package/skeleton/bin/agent-farm +0 -7
- package/skeleton/bin/codev-doctor +0 -335
- package/skeleton/resources/lessons-learned.md +0 -30
- /package/skeleton/{roles/review-types → consult-types}/impl-review.md +0 -0
- /package/skeleton/{roles/review-types → consult-types}/integration-review.md +0 -0
- /package/skeleton/{roles/review-types → consult-types}/plan-review.md +0 -0
- /package/skeleton/{roles/review-types → consult-types}/pr-ready.md +0 -0
- /package/skeleton/{roles/review-types → consult-types}/spec-review.md +0 -0
- /package/skeleton/{docs → resources}/commands/agent-farm.md +0 -0
- /package/skeleton/{AGENTS.md.template → templates/AGENTS.md} +0 -0
- /package/skeleton/{CLAUDE.md.template → templates/CLAUDE.md} +0 -0
|
@@ -1156,6 +1156,330 @@
|
|
|
1156
1156
|
.tab.tab-uncloseable .close {
|
|
1157
1157
|
display: none;
|
|
1158
1158
|
}
|
|
1159
|
+
|
|
1160
|
+
/* Files Tab Styles (Spec 0055) */
|
|
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
|
+
|
|
1210
|
+
.tree-item {
|
|
1211
|
+
display: flex;
|
|
1212
|
+
align-items: center;
|
|
1213
|
+
padding: 4px 8px;
|
|
1214
|
+
cursor: pointer;
|
|
1215
|
+
user-select: none;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
.tree-item:hover {
|
|
1219
|
+
background: var(--bg-secondary);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
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;
|
|
1230
|
+
display: flex;
|
|
1231
|
+
align-items: center;
|
|
1232
|
+
justify-content: center;
|
|
1233
|
+
font-size: 10px;
|
|
1234
|
+
color: var(--text-muted);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
.tree-item-icon.folder-toggle {
|
|
1238
|
+
cursor: pointer;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
.tree-item-icon.folder-toggle:hover {
|
|
1242
|
+
color: var(--text-secondary);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.tree-item-name {
|
|
1246
|
+
font-size: 13px;
|
|
1247
|
+
color: var(--text-secondary);
|
|
1248
|
+
overflow: hidden;
|
|
1249
|
+
text-overflow: ellipsis;
|
|
1250
|
+
white-space: nowrap;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.tree-item:hover .tree-item-name {
|
|
1254
|
+
color: var(--text-primary);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.tree-item[data-type="dir"] .tree-item-name {
|
|
1258
|
+
color: var(--text-primary);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
.tree-item[data-type="file"]:hover .tree-item-name {
|
|
1262
|
+
color: var(--accent);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.tree-children {
|
|
1266
|
+
overflow: hidden;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
.tree-children.collapsed {
|
|
1270
|
+
display: none;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
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
|
+
/* Dashboard Tab Styles (Spec 0057) */
|
|
1293
|
+
.dashboard-container {
|
|
1294
|
+
flex: 1;
|
|
1295
|
+
overflow-y: auto;
|
|
1296
|
+
display: flex;
|
|
1297
|
+
flex-direction: column;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
.dashboard-header {
|
|
1301
|
+
display: grid;
|
|
1302
|
+
grid-template-columns: 1fr 1fr;
|
|
1303
|
+
gap: 16px;
|
|
1304
|
+
padding: 16px;
|
|
1305
|
+
flex-shrink: 0;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
@media (max-width: 900px) {
|
|
1309
|
+
.dashboard-header {
|
|
1310
|
+
grid-template-columns: 1fr;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
.dashboard-column {
|
|
1315
|
+
background: var(--bg-secondary);
|
|
1316
|
+
border: 1px solid var(--border);
|
|
1317
|
+
border-radius: 8px;
|
|
1318
|
+
padding: 12px;
|
|
1319
|
+
overflow: hidden;
|
|
1320
|
+
display: flex;
|
|
1321
|
+
flex-direction: column;
|
|
1322
|
+
max-height: 280px;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
.dashboard-column-header {
|
|
1326
|
+
display: flex;
|
|
1327
|
+
justify-content: space-between;
|
|
1328
|
+
align-items: center;
|
|
1329
|
+
margin-bottom: 8px;
|
|
1330
|
+
flex-shrink: 0;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
.dashboard-column-header h3 {
|
|
1334
|
+
font-size: 12px;
|
|
1335
|
+
text-transform: uppercase;
|
|
1336
|
+
color: var(--text-muted);
|
|
1337
|
+
letter-spacing: 0.5px;
|
|
1338
|
+
margin: 0;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
.dashboard-column-header .header-actions {
|
|
1342
|
+
display: flex;
|
|
1343
|
+
gap: 4px;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
.dashboard-column-header .header-actions button {
|
|
1347
|
+
padding: 4px 8px;
|
|
1348
|
+
border-radius: 4px;
|
|
1349
|
+
border: 1px solid var(--border);
|
|
1350
|
+
background: var(--bg-tertiary);
|
|
1351
|
+
color: var(--text-secondary);
|
|
1352
|
+
cursor: pointer;
|
|
1353
|
+
font-size: 11px;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
.dashboard-column-header .header-actions button:hover {
|
|
1357
|
+
background: var(--tab-hover);
|
|
1358
|
+
color: var(--text-primary);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.dashboard-tabs-list {
|
|
1362
|
+
flex: 1;
|
|
1363
|
+
overflow-y: auto;
|
|
1364
|
+
margin-bottom: 8px;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
.dashboard-tab-item {
|
|
1368
|
+
display: flex;
|
|
1369
|
+
align-items: center;
|
|
1370
|
+
gap: 8px;
|
|
1371
|
+
padding: 6px 8px;
|
|
1372
|
+
border-radius: 4px;
|
|
1373
|
+
cursor: pointer;
|
|
1374
|
+
font-size: 13px;
|
|
1375
|
+
color: var(--text-secondary);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
.dashboard-tab-item:hover {
|
|
1379
|
+
background: var(--bg-tertiary);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
.dashboard-tab-item.active {
|
|
1383
|
+
background: var(--accent);
|
|
1384
|
+
color: white;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
.dashboard-tab-item .tab-icon {
|
|
1388
|
+
font-size: 14px;
|
|
1389
|
+
flex-shrink: 0;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
.dashboard-tab-item .tab-name {
|
|
1393
|
+
flex: 1;
|
|
1394
|
+
overflow: hidden;
|
|
1395
|
+
text-overflow: ellipsis;
|
|
1396
|
+
white-space: nowrap;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
.dashboard-actions {
|
|
1400
|
+
flex-shrink: 0;
|
|
1401
|
+
display: flex;
|
|
1402
|
+
gap: 8px;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
.dashboard-actions .btn-action {
|
|
1406
|
+
flex: 1;
|
|
1407
|
+
padding: 8px 12px;
|
|
1408
|
+
border-radius: 4px;
|
|
1409
|
+
border: 1px dashed var(--border);
|
|
1410
|
+
background: transparent;
|
|
1411
|
+
color: var(--text-muted);
|
|
1412
|
+
cursor: pointer;
|
|
1413
|
+
font-size: 12px;
|
|
1414
|
+
display: flex;
|
|
1415
|
+
align-items: center;
|
|
1416
|
+
justify-content: center;
|
|
1417
|
+
gap: 4px;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
.dashboard-actions .btn-action:hover {
|
|
1421
|
+
border-style: solid;
|
|
1422
|
+
color: var(--text-secondary);
|
|
1423
|
+
background: var(--bg-tertiary);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
.dashboard-files-list {
|
|
1427
|
+
flex: 1;
|
|
1428
|
+
overflow-y: auto;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
.dashboard-files-list .tree-item {
|
|
1432
|
+
padding: 3px 6px;
|
|
1433
|
+
font-size: 12px;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
.dashboard-files-list .tree-item-name {
|
|
1437
|
+
font-size: 12px;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
.dashboard-empty-state {
|
|
1441
|
+
color: var(--text-muted);
|
|
1442
|
+
font-size: 13px;
|
|
1443
|
+
padding: 12px;
|
|
1444
|
+
text-align: center;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
.dashboard-projects {
|
|
1448
|
+
flex: 1;
|
|
1449
|
+
overflow-y: auto;
|
|
1450
|
+
padding: 0 16px 16px 16px;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/* Status indicators in dashboard tab list */
|
|
1454
|
+
.dashboard-status-indicator {
|
|
1455
|
+
width: 8px;
|
|
1456
|
+
height: 8px;
|
|
1457
|
+
border-radius: 50%;
|
|
1458
|
+
flex-shrink: 0;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
.dashboard-status-working {
|
|
1462
|
+
background: var(--status-active);
|
|
1463
|
+
animation: status-pulse 2s ease-in-out infinite;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
.dashboard-status-idle {
|
|
1467
|
+
background: var(--status-waiting);
|
|
1468
|
+
animation: status-blink-slow 3s ease-in-out infinite;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
.dashboard-status-blocked {
|
|
1472
|
+
background: var(--status-error);
|
|
1473
|
+
animation: status-blink-fast 0.8s ease-in-out infinite;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1477
|
+
.dashboard-status-working,
|
|
1478
|
+
.dashboard-status-idle,
|
|
1479
|
+
.dashboard-status-blocked {
|
|
1480
|
+
animation: none;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1159
1483
|
</style>
|
|
1160
1484
|
</head>
|
|
1161
1485
|
<body>
|
|
@@ -1183,7 +1507,6 @@
|
|
|
1183
1507
|
</button>
|
|
1184
1508
|
<div class="overflow-menu hidden" id="overflow-menu" role="menu"></div>
|
|
1185
1509
|
<div class="add-buttons">
|
|
1186
|
-
<button class="add-btn" onclick="showFileDialog()" title="Open file">+ 📄</button>
|
|
1187
1510
|
<button class="add-btn" onclick="spawnBuilder()" title="Spawn worktree builder">+ 🔨</button>
|
|
1188
1511
|
<button class="add-btn" onclick="spawnShell()" title="New shell">+ >_</button>
|
|
1189
1512
|
</div>
|
|
@@ -1374,16 +1697,30 @@
|
|
|
1374
1697
|
let projectlistError = null;
|
|
1375
1698
|
let projectlistDebounce = null;
|
|
1376
1699
|
|
|
1700
|
+
// Files tab state (Spec 0055)
|
|
1701
|
+
let filesTreeData = [];
|
|
1702
|
+
let filesTreeExpanded = new Set(); // Set of expanded folder paths
|
|
1703
|
+
let filesTreeError = null;
|
|
1704
|
+
let filesTreeLoaded = false;
|
|
1705
|
+
|
|
1377
1706
|
// Build tabs from initial state
|
|
1378
1707
|
function buildTabsFromState() {
|
|
1379
1708
|
const previousTabIds = new Set(tabs.map(t => t.id));
|
|
1380
1709
|
tabs = [];
|
|
1381
1710
|
|
|
1382
|
-
//
|
|
1711
|
+
// Dashboard tab is ALWAYS first and uncloseable (Spec 0045, 0057)
|
|
1383
1712
|
tabs.push({
|
|
1384
|
-
id: '
|
|
1385
|
-
type: '
|
|
1386
|
-
name: '
|
|
1713
|
+
id: 'dashboard',
|
|
1714
|
+
type: 'dashboard',
|
|
1715
|
+
name: 'Dashboard',
|
|
1716
|
+
closeable: false
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
// Files tab is second and uncloseable (Spec 0055)
|
|
1720
|
+
tabs.push({
|
|
1721
|
+
id: 'files',
|
|
1722
|
+
type: 'files',
|
|
1723
|
+
name: 'Files',
|
|
1387
1724
|
closeable: false
|
|
1388
1725
|
});
|
|
1389
1726
|
|
|
@@ -1424,7 +1761,7 @@
|
|
|
1424
1761
|
|
|
1425
1762
|
// Detect new tabs and auto-switch to them (skip projects tab)
|
|
1426
1763
|
for (const tab of tabs) {
|
|
1427
|
-
if (tab.id !== '
|
|
1764
|
+
if (tab.id !== 'dashboard' && tab.id !== 'files' && !knownTabIds.has(tab.id) && previousTabIds.size > 0) {
|
|
1428
1765
|
// This is a new tab - switch to it
|
|
1429
1766
|
activeTabId = tab.id;
|
|
1430
1767
|
break;
|
|
@@ -1434,9 +1771,9 @@
|
|
|
1434
1771
|
// Update known tab IDs
|
|
1435
1772
|
knownTabIds = new Set(tabs.map(t => t.id));
|
|
1436
1773
|
|
|
1437
|
-
// Set active tab to
|
|
1774
|
+
// Set active tab to Dashboard on first load if none selected
|
|
1438
1775
|
if (!activeTabId) {
|
|
1439
|
-
activeTabId = '
|
|
1776
|
+
activeTabId = 'dashboard';
|
|
1440
1777
|
}
|
|
1441
1778
|
}
|
|
1442
1779
|
|
|
@@ -1518,7 +1855,8 @@
|
|
|
1518
1855
|
// Get tab icon
|
|
1519
1856
|
function getTabIcon(type) {
|
|
1520
1857
|
switch (type) {
|
|
1521
|
-
case '
|
|
1858
|
+
case 'dashboard': return '📋';
|
|
1859
|
+
case 'files': return '📁';
|
|
1522
1860
|
case 'file': return '📄';
|
|
1523
1861
|
case 'builder': return '🔨';
|
|
1524
1862
|
case 'shell': return '>_';
|
|
@@ -1616,12 +1954,22 @@
|
|
|
1616
1954
|
return;
|
|
1617
1955
|
}
|
|
1618
1956
|
|
|
1619
|
-
// Handle
|
|
1620
|
-
if (tab.type === '
|
|
1621
|
-
if (currentTabType !== '
|
|
1622
|
-
currentTabType = '
|
|
1957
|
+
// Handle dashboard tab specially (no iframe, inline content)
|
|
1958
|
+
if (tab.type === 'dashboard') {
|
|
1959
|
+
if (currentTabType !== 'dashboard') {
|
|
1960
|
+
currentTabType = 'dashboard';
|
|
1623
1961
|
currentTabPort = null;
|
|
1624
|
-
|
|
1962
|
+
renderDashboardTab();
|
|
1963
|
+
}
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// Handle files tab specially (no iframe, inline content)
|
|
1968
|
+
if (tab.type === 'files') {
|
|
1969
|
+
if (currentTabType !== 'files') {
|
|
1970
|
+
currentTabType = 'files';
|
|
1971
|
+
currentTabPort = null;
|
|
1972
|
+
renderFilesTab();
|
|
1625
1973
|
}
|
|
1626
1974
|
return;
|
|
1627
1975
|
}
|
|
@@ -2810,6 +3158,238 @@
|
|
|
2810
3158
|
`;
|
|
2811
3159
|
}
|
|
2812
3160
|
|
|
3161
|
+
// ========================================
|
|
3162
|
+
// Files Tab Functions (Spec 0055)
|
|
3163
|
+
// ========================================
|
|
3164
|
+
|
|
3165
|
+
// Load the file tree from the API
|
|
3166
|
+
async function loadFilesTree() {
|
|
3167
|
+
try {
|
|
3168
|
+
const response = await fetch('/api/files');
|
|
3169
|
+
if (!response.ok) {
|
|
3170
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
3171
|
+
}
|
|
3172
|
+
filesTreeData = await response.json();
|
|
3173
|
+
filesTreeError = null;
|
|
3174
|
+
filesTreeLoaded = true;
|
|
3175
|
+
} catch (err) {
|
|
3176
|
+
console.error('Failed to load files tree:', err);
|
|
3177
|
+
filesTreeError = 'Could not load file tree: ' + err.message;
|
|
3178
|
+
filesTreeData = [];
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
// Render the files tab (entry point)
|
|
3183
|
+
// Only fetches on first load; use refreshFilesTree() to force reload
|
|
3184
|
+
async function renderFilesTab() {
|
|
3185
|
+
const content = document.getElementById('tab-content');
|
|
3186
|
+
|
|
3187
|
+
// If already loaded, just render cached data (no network request)
|
|
3188
|
+
if (filesTreeLoaded) {
|
|
3189
|
+
renderFilesTabContent();
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
// First load - show loading state and fetch
|
|
3194
|
+
content.innerHTML = '<div class="files-loading">Loading files...</div>';
|
|
3195
|
+
await loadFilesTree();
|
|
3196
|
+
renderFilesTabContent();
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
// Render the files tab content (internal - called after data is loaded)
|
|
3200
|
+
function renderFilesTabContent() {
|
|
3201
|
+
const content = document.getElementById('tab-content');
|
|
3202
|
+
|
|
3203
|
+
if (filesTreeError) {
|
|
3204
|
+
content.innerHTML = `
|
|
3205
|
+
<div class="files-container">
|
|
3206
|
+
<div class="files-error">${escapeHtml(filesTreeError)}</div>
|
|
3207
|
+
</div>
|
|
3208
|
+
`;
|
|
3209
|
+
return;
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3212
|
+
content.innerHTML = `
|
|
3213
|
+
<div class="files-container">
|
|
3214
|
+
<div class="files-header">
|
|
3215
|
+
<span class="files-header-title">Explorer</span>
|
|
3216
|
+
<div class="files-header-actions">
|
|
3217
|
+
<button onclick="collapseAllFolders()" title="Collapse All">⊟</button>
|
|
3218
|
+
<button onclick="expandAllFolders()" title="Expand All">⊞</button>
|
|
3219
|
+
<button onclick="refreshFilesTree()" title="Refresh">↻</button>
|
|
3220
|
+
</div>
|
|
3221
|
+
</div>
|
|
3222
|
+
<div class="files-tree" id="files-tree">
|
|
3223
|
+
${renderTreeNodes(filesTreeData, 0)}
|
|
3224
|
+
</div>
|
|
3225
|
+
</div>
|
|
3226
|
+
`;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
// Escape a string for use inside a JavaScript string literal in onclick handlers
|
|
3230
|
+
// This handles quotes, backslashes, and other special characters
|
|
3231
|
+
function escapeJsString(str) {
|
|
3232
|
+
return str
|
|
3233
|
+
.replace(/\\/g, '\\\\')
|
|
3234
|
+
.replace(/'/g, "\\'")
|
|
3235
|
+
.replace(/"/g, '\\"')
|
|
3236
|
+
.replace(/\n/g, '\\n')
|
|
3237
|
+
.replace(/\r/g, '\\r');
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
// Render tree nodes recursively
|
|
3241
|
+
function renderTreeNodes(nodes, depth) {
|
|
3242
|
+
if (!nodes || nodes.length === 0) return '';
|
|
3243
|
+
|
|
3244
|
+
return nodes.map(node => {
|
|
3245
|
+
const indent = depth * 16;
|
|
3246
|
+
const isExpanded = filesTreeExpanded.has(node.path);
|
|
3247
|
+
// Use escapeJsString for onclick handlers (handles quotes correctly)
|
|
3248
|
+
// Use escapeHtml for data attributes and display text (handles XSS)
|
|
3249
|
+
const jsPath = escapeJsString(node.path);
|
|
3250
|
+
|
|
3251
|
+
if (node.type === 'dir') {
|
|
3252
|
+
const icon = isExpanded ? '▼' : '▶';
|
|
3253
|
+
const childrenHtml = node.children && node.children.length > 0
|
|
3254
|
+
? `<div class="tree-children ${isExpanded ? '' : 'collapsed'}" data-path="${escapeHtml(node.path)}">${renderTreeNodes(node.children, depth + 1)}</div>`
|
|
3255
|
+
: '';
|
|
3256
|
+
|
|
3257
|
+
return `
|
|
3258
|
+
<div class="tree-item" data-type="dir" data-path="${escapeHtml(node.path)}" style="padding-left: ${indent + 8}px;" onclick="toggleFolder('${jsPath}')">
|
|
3259
|
+
<span class="tree-item-icon folder-toggle">${icon}</span>
|
|
3260
|
+
<span class="tree-item-name">${escapeHtml(node.name)}</span>
|
|
3261
|
+
</div>
|
|
3262
|
+
${childrenHtml}
|
|
3263
|
+
`;
|
|
3264
|
+
} else {
|
|
3265
|
+
return `
|
|
3266
|
+
<div class="tree-item" data-type="file" data-path="${escapeHtml(node.path)}" style="padding-left: ${indent + 8}px;" onclick="openFileFromTree('${jsPath}')">
|
|
3267
|
+
<span class="tree-item-icon">${getFileIcon(node.name)}</span>
|
|
3268
|
+
<span class="tree-item-name">${escapeHtml(node.name)}</span>
|
|
3269
|
+
</div>
|
|
3270
|
+
`;
|
|
3271
|
+
}
|
|
3272
|
+
}).join('');
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
// Get file icon based on extension
|
|
3276
|
+
function getFileIcon(filename) {
|
|
3277
|
+
const ext = filename.split('.').pop().toLowerCase();
|
|
3278
|
+
const iconMap = {
|
|
3279
|
+
'js': '📜',
|
|
3280
|
+
'ts': '📜',
|
|
3281
|
+
'jsx': '⚛️',
|
|
3282
|
+
'tsx': '⚛️',
|
|
3283
|
+
'json': '{}',
|
|
3284
|
+
'md': '📝',
|
|
3285
|
+
'html': '🌐',
|
|
3286
|
+
'css': '🎨',
|
|
3287
|
+
'py': '🐍',
|
|
3288
|
+
'sh': '⚙️',
|
|
3289
|
+
'bash': '⚙️',
|
|
3290
|
+
'yml': '⚙️',
|
|
3291
|
+
'yaml': '⚙️',
|
|
3292
|
+
'png': '🖼️',
|
|
3293
|
+
'jpg': '🖼️',
|
|
3294
|
+
'jpeg': '🖼️',
|
|
3295
|
+
'gif': '🖼️',
|
|
3296
|
+
'svg': '🖼️',
|
|
3297
|
+
};
|
|
3298
|
+
return iconMap[ext] || '📄';
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
// Toggle folder expanded/collapsed state
|
|
3302
|
+
function toggleFolder(path) {
|
|
3303
|
+
if (filesTreeExpanded.has(path)) {
|
|
3304
|
+
filesTreeExpanded.delete(path);
|
|
3305
|
+
} else {
|
|
3306
|
+
filesTreeExpanded.add(path);
|
|
3307
|
+
}
|
|
3308
|
+
rerenderFilesBrowser();
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
// Re-render file browser in current context (dashboard or files tab)
|
|
3312
|
+
function rerenderFilesBrowser() {
|
|
3313
|
+
if (activeTabId === 'dashboard') {
|
|
3314
|
+
// Re-render just the files column in dashboard
|
|
3315
|
+
const filesListEl = document.getElementById('dashboard-files-list');
|
|
3316
|
+
if (filesListEl) {
|
|
3317
|
+
filesListEl.innerHTML = renderDashboardFilesBrowser();
|
|
3318
|
+
}
|
|
3319
|
+
} else if (activeTabId === 'files') {
|
|
3320
|
+
renderFilesTabContent();
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
// Collapse all folders
|
|
3325
|
+
function collapseAllFolders() {
|
|
3326
|
+
filesTreeExpanded.clear();
|
|
3327
|
+
rerenderFilesBrowser();
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
// Expand all folders
|
|
3331
|
+
function expandAllFolders() {
|
|
3332
|
+
function collectPaths(nodes) {
|
|
3333
|
+
for (const node of nodes) {
|
|
3334
|
+
if (node.type === 'dir') {
|
|
3335
|
+
filesTreeExpanded.add(node.path);
|
|
3336
|
+
if (node.children) {
|
|
3337
|
+
collectPaths(node.children);
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
collectPaths(filesTreeData);
|
|
3343
|
+
rerenderFilesBrowser();
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
// Refresh files tree
|
|
3347
|
+
async function refreshFilesTree() {
|
|
3348
|
+
await loadFilesTree();
|
|
3349
|
+
rerenderFilesBrowser();
|
|
3350
|
+
showToast('Files refreshed', 'success');
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
// Open file from tree click
|
|
3354
|
+
async function openFileFromTree(filePath) {
|
|
3355
|
+
try {
|
|
3356
|
+
// Check if file is already open
|
|
3357
|
+
const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
3358
|
+
if (existingTab) {
|
|
3359
|
+
selectTab(existingTab.id);
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
// Open the file via API
|
|
3364
|
+
const response = await fetch('/api/tabs/file', {
|
|
3365
|
+
method: 'POST',
|
|
3366
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3367
|
+
body: JSON.stringify({ path: filePath })
|
|
3368
|
+
});
|
|
3369
|
+
|
|
3370
|
+
if (!response.ok) {
|
|
3371
|
+
throw new Error(await response.text());
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
// Refresh state and switch to the new tab
|
|
3375
|
+
await refresh();
|
|
3376
|
+
|
|
3377
|
+
// Find and select the new file tab
|
|
3378
|
+
const newTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
3379
|
+
if (newTab) {
|
|
3380
|
+
selectTab(newTab.id);
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
showToast(`Opened ${getFileName(filePath)}`, 'success');
|
|
3384
|
+
} catch (err) {
|
|
3385
|
+
showToast('Failed to open file: ' + err.message, 'error');
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
// ========================================
|
|
3390
|
+
// Projects Tab Functions (Spec 0045)
|
|
3391
|
+
// ========================================
|
|
3392
|
+
|
|
2813
3393
|
// Render the info header with helpful links
|
|
2814
3394
|
function renderInfoHeader() {
|
|
2815
3395
|
return `
|
|
@@ -2817,52 +3397,214 @@
|
|
|
2817
3397
|
<h1 style="font-size: 20px; margin-bottom: 12px; color: var(--text-primary);">Codev: Project View</h1>
|
|
2818
3398
|
<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>
|
|
2819
3399
|
<p>To add projects, update status, or approve stages, use the <strong>Architect</strong> terminal on the left.</p>
|
|
2820
|
-
<p>Docs: <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>
|
|
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>
|
|
2821
3401
|
</div>
|
|
2822
3402
|
`;
|
|
2823
3403
|
}
|
|
2824
3404
|
|
|
2825
|
-
// Render the
|
|
2826
|
-
function
|
|
3405
|
+
// Render the dashboard tab content (internal - called after data is loaded)
|
|
3406
|
+
function renderDashboardTabContent() {
|
|
2827
3407
|
const content = document.getElementById('tab-content');
|
|
2828
3408
|
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
<div class="
|
|
2832
|
-
|
|
3409
|
+
content.innerHTML = `
|
|
3410
|
+
<div class="dashboard-container">
|
|
3411
|
+
<div class="dashboard-header">
|
|
3412
|
+
<!-- Left Column: Tabs -->
|
|
3413
|
+
<div class="dashboard-column">
|
|
3414
|
+
<div class="dashboard-column-header">
|
|
3415
|
+
<h3>Tabs</h3>
|
|
3416
|
+
</div>
|
|
3417
|
+
<div class="dashboard-tabs-list" id="dashboard-tabs-list">
|
|
3418
|
+
${renderDashboardTabsList()}
|
|
3419
|
+
</div>
|
|
3420
|
+
<div class="dashboard-actions">
|
|
3421
|
+
<button class="btn-action" onclick="createNewShell()" title="New utility shell">+ New Shell</button>
|
|
3422
|
+
<button class="btn-action" onclick="createNewWorktreeShell()" title="New worktree shell">+ New Worktree</button>
|
|
3423
|
+
</div>
|
|
3424
|
+
</div>
|
|
3425
|
+
<!-- Right Column: Files -->
|
|
3426
|
+
<div class="dashboard-column">
|
|
3427
|
+
<div class="dashboard-column-header">
|
|
3428
|
+
<h3>Files</h3>
|
|
3429
|
+
<div class="header-actions">
|
|
3430
|
+
<button onclick="collapseAllFolders()" title="Collapse All">⊟</button>
|
|
3431
|
+
<button onclick="expandAllFolders()" title="Expand All">⊞</button>
|
|
3432
|
+
</div>
|
|
3433
|
+
</div>
|
|
3434
|
+
<div class="dashboard-files-list" id="dashboard-files-list">
|
|
3435
|
+
${renderDashboardFilesBrowser()}
|
|
3436
|
+
</div>
|
|
3437
|
+
</div>
|
|
3438
|
+
</div>
|
|
3439
|
+
<div class="dashboard-projects" id="dashboard-projects">
|
|
3440
|
+
${renderDashboardProjectsSection()}
|
|
3441
|
+
</div>
|
|
3442
|
+
</div>
|
|
3443
|
+
`;
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
// Render the tabs list for dashboard
|
|
3447
|
+
function renderDashboardTabsList() {
|
|
3448
|
+
// Filter to show terminal tabs only (not Dashboard/Files tabs)
|
|
3449
|
+
const terminalTabs = tabs.filter(t => t.type !== 'dashboard' && t.type !== 'files');
|
|
3450
|
+
|
|
3451
|
+
if (terminalTabs.length === 0) {
|
|
3452
|
+
return '<div class="dashboard-empty-state">No tabs open</div>';
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
return terminalTabs.map(tab => {
|
|
3456
|
+
const isActive = tab.id === activeTabId;
|
|
3457
|
+
const icon = getTabIcon(tab.type);
|
|
3458
|
+
const statusIndicator = getDashboardStatusIndicator(tab);
|
|
3459
|
+
|
|
3460
|
+
return `
|
|
3461
|
+
<div class="dashboard-tab-item ${isActive ? 'active' : ''}" onclick="selectTab('${tab.id}')">
|
|
3462
|
+
${statusIndicator}
|
|
3463
|
+
<span class="tab-icon">${icon}</span>
|
|
3464
|
+
<span class="tab-name">${escapeHtml(tab.name)}</span>
|
|
2833
3465
|
</div>
|
|
2834
3466
|
`;
|
|
2835
|
-
|
|
3467
|
+
}).join('');
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
// Get status indicator for dashboard tab list
|
|
3471
|
+
function getDashboardStatusIndicator(tab) {
|
|
3472
|
+
if (tab.type !== 'builder') return '';
|
|
3473
|
+
|
|
3474
|
+
// Use builder status from state
|
|
3475
|
+
const builderState = (state.builders || []).find(b => `builder-${b.id}` === tab.id);
|
|
3476
|
+
if (!builderState) return '';
|
|
3477
|
+
|
|
3478
|
+
const status = builderState.status;
|
|
3479
|
+
if (['spawning', 'implementing'].includes(status)) {
|
|
3480
|
+
return '<span class="dashboard-status-indicator dashboard-status-working" title="Working"></span>';
|
|
3481
|
+
}
|
|
3482
|
+
if (status === 'blocked') {
|
|
3483
|
+
return '<span class="dashboard-status-indicator dashboard-status-blocked" title="Blocked"></span>';
|
|
3484
|
+
}
|
|
3485
|
+
if (['pr-ready', 'complete'].includes(status)) {
|
|
3486
|
+
return '<span class="dashboard-status-indicator dashboard-status-idle" title="Idle"></span>';
|
|
3487
|
+
}
|
|
3488
|
+
return '';
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
// Render compact file browser for dashboard
|
|
3492
|
+
function renderDashboardFilesBrowser() {
|
|
3493
|
+
if (filesTreeError) {
|
|
3494
|
+
return `<div class="dashboard-empty-state">${escapeHtml(filesTreeError)}</div>`;
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
if (!filesTreeLoaded || filesTreeData.length === 0) {
|
|
3498
|
+
return '<div class="dashboard-empty-state">Loading files...</div>';
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
return renderTreeNodes(filesTreeData, 0);
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
// Render the projects section for dashboard
|
|
3505
|
+
function renderDashboardProjectsSection() {
|
|
3506
|
+
if (projectlistError) {
|
|
3507
|
+
return renderErrorBanner(projectlistError);
|
|
2836
3508
|
}
|
|
2837
3509
|
|
|
2838
3510
|
if (projectsData.length === 0) {
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
3511
|
+
// No welcome screen - just a helpful message
|
|
3512
|
+
return `
|
|
3513
|
+
<div class="dashboard-empty-state" style="padding: 24px;">
|
|
3514
|
+
No projects yet. Ask the Architect to create your first project.
|
|
2842
3515
|
</div>
|
|
2843
3516
|
`;
|
|
2844
|
-
return;
|
|
2845
3517
|
}
|
|
2846
3518
|
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
</div>
|
|
3519
|
+
// Render the existing project view (unchanged)
|
|
3520
|
+
return `
|
|
3521
|
+
${renderInfoHeader()}
|
|
3522
|
+
${renderKanbanGrid(projectsData)}
|
|
3523
|
+
${renderTerminalProjects(projectsData)}
|
|
2853
3524
|
`;
|
|
2854
3525
|
}
|
|
2855
3526
|
|
|
2856
|
-
//
|
|
2857
|
-
async function
|
|
3527
|
+
// Create new utility shell (quick action button)
|
|
3528
|
+
async function createNewShell() {
|
|
3529
|
+
try {
|
|
3530
|
+
const response = await fetch('/api/tabs/shell', { method: 'POST' });
|
|
3531
|
+
const data = await response.json();
|
|
3532
|
+
if (!data.success && data.error) {
|
|
3533
|
+
showToast(data.error || 'Failed to create shell', 'error');
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
await refresh();
|
|
3537
|
+
if (data.id) {
|
|
3538
|
+
selectTab(`shell-${data.id}`);
|
|
3539
|
+
}
|
|
3540
|
+
showToast('Shell created', 'success');
|
|
3541
|
+
} catch (err) {
|
|
3542
|
+
showToast('Network error: ' + err.message, 'error');
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
// Create new worktree shell (quick action button)
|
|
3547
|
+
async function createNewWorktreeShell() {
|
|
3548
|
+
const branch = prompt('Branch name (leave empty for temp worktree):');
|
|
3549
|
+
if (branch === null) return; // User cancelled
|
|
3550
|
+
|
|
3551
|
+
try {
|
|
3552
|
+
const response = await fetch('/api/tabs/shell', {
|
|
3553
|
+
method: 'POST',
|
|
3554
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3555
|
+
body: JSON.stringify({ worktree: true, branch: branch || undefined })
|
|
3556
|
+
});
|
|
3557
|
+
const data = await response.json();
|
|
3558
|
+
if (!data.success && data.error) {
|
|
3559
|
+
showToast(data.error || 'Failed to create worktree shell', 'error');
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
await refresh();
|
|
3563
|
+
// Auto-select the newly created tab (consistent with createNewShell behavior)
|
|
3564
|
+
if (data.id) {
|
|
3565
|
+
selectTab(`shell-${data.id}`);
|
|
3566
|
+
}
|
|
3567
|
+
showToast('Worktree shell created', 'success');
|
|
3568
|
+
} catch (err) {
|
|
3569
|
+
showToast('Network error: ' + err.message, 'error');
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
// Render the dashboard tab (entry point - loads data first)
|
|
3574
|
+
async function renderDashboardTab() {
|
|
2858
3575
|
const content = document.getElementById('tab-content');
|
|
2859
|
-
content.innerHTML = '<div class="
|
|
3576
|
+
content.innerHTML = '<div class="dashboard-container"><p style="color: var(--text-muted); padding: 16px;">Loading dashboard...</p></div>';
|
|
2860
3577
|
|
|
2861
|
-
|
|
2862
|
-
|
|
3578
|
+
// Load both projectlist and files tree in parallel
|
|
3579
|
+
await Promise.all([
|
|
3580
|
+
loadProjectlist(),
|
|
3581
|
+
loadFilesTreeIfNeeded()
|
|
3582
|
+
]);
|
|
3583
|
+
|
|
3584
|
+
renderDashboardTabContent();
|
|
2863
3585
|
checkStarterMode(); // Update polling state after initial load
|
|
2864
3586
|
}
|
|
2865
3587
|
|
|
3588
|
+
// Load files tree if not already loaded
|
|
3589
|
+
async function loadFilesTreeIfNeeded() {
|
|
3590
|
+
if (!filesTreeLoaded) {
|
|
3591
|
+
await loadFilesTree();
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
// Legacy function for backward compatibility (still used by polling)
|
|
3596
|
+
function renderProjectsTabContent() {
|
|
3597
|
+
// If dashboard tab is active, re-render dashboard instead
|
|
3598
|
+
if (activeTabId === 'dashboard') {
|
|
3599
|
+
renderDashboardTabContent();
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
// Legacy function alias
|
|
3604
|
+
async function renderProjectsTab() {
|
|
3605
|
+
await renderDashboardTab();
|
|
3606
|
+
}
|
|
3607
|
+
|
|
2866
3608
|
// Load projectlist.md from disk
|
|
2867
3609
|
async function loadProjectlist() {
|
|
2868
3610
|
try {
|
|
@@ -2907,8 +3649,8 @@
|
|
|
2907
3649
|
|
|
2908
3650
|
// Poll projectlist for changes (every 5 seconds)
|
|
2909
3651
|
async function pollProjectlist() {
|
|
2910
|
-
// Only poll if
|
|
2911
|
-
if (activeTabId !== '
|
|
3652
|
+
// Only poll if dashboard tab is active
|
|
3653
|
+
if (activeTabId !== 'dashboard') return;
|
|
2912
3654
|
|
|
2913
3655
|
try {
|
|
2914
3656
|
const response = await fetch('/file?path=codev/projectlist.md');
|