specsmd 0.1.35 → 0.1.37
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/lib/dashboard/tui/app.js +565 -172
- package/lib/dashboard/tui/store.js +11 -5
- package/package.json +1 -1
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -793,13 +793,16 @@ function getPanelTitles(flow, snapshot) {
|
|
|
793
793
|
}
|
|
794
794
|
|
|
795
795
|
function getSectionOrderForView(view) {
|
|
796
|
-
if (view === '
|
|
797
|
-
return ['intent-status'
|
|
796
|
+
if (view === 'intents') {
|
|
797
|
+
return ['intent-status'];
|
|
798
|
+
}
|
|
799
|
+
if (view === 'completed') {
|
|
800
|
+
return ['completed-runs'];
|
|
798
801
|
}
|
|
799
802
|
if (view === 'health') {
|
|
800
|
-
return ['stats', 'warnings', 'error-details'];
|
|
803
|
+
return ['standards', 'stats', 'warnings', 'error-details'];
|
|
801
804
|
}
|
|
802
|
-
return ['current-run', 'run-files'
|
|
805
|
+
return ['current-run', 'run-files'];
|
|
803
806
|
}
|
|
804
807
|
|
|
805
808
|
function cycleSection(view, currentSectionKey, direction = 1, availableSections = null) {
|
|
@@ -1054,24 +1057,288 @@ function getNoCompletedMessage(flow) {
|
|
|
1054
1057
|
return 'No completed runs yet';
|
|
1055
1058
|
}
|
|
1056
1059
|
|
|
1057
|
-
function
|
|
1058
|
-
if (
|
|
1060
|
+
function getNoCurrentMessage(flow) {
|
|
1061
|
+
if (flow === 'aidlc') return 'No active bolt';
|
|
1062
|
+
if (flow === 'simple') return 'No active spec';
|
|
1063
|
+
return 'No active run';
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function buildCurrentGroups(snapshot, flow) {
|
|
1067
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1068
|
+
|
|
1069
|
+
if (effectiveFlow === 'aidlc') {
|
|
1070
|
+
const bolt = getCurrentBolt(snapshot);
|
|
1071
|
+
if (!bolt) {
|
|
1072
|
+
return [];
|
|
1073
|
+
}
|
|
1074
|
+
const stages = Array.isArray(bolt.stages) ? bolt.stages : [];
|
|
1075
|
+
const completedStages = stages.filter((stage) => stage.status === 'completed').length;
|
|
1076
|
+
return [{
|
|
1077
|
+
key: `current:bolt:${bolt.id}`,
|
|
1078
|
+
label: `${bolt.id} [${bolt.type}] ${completedStages}/${stages.length} stages`,
|
|
1079
|
+
files: filterExistingFiles([
|
|
1080
|
+
...collectAidlcBoltFiles(bolt),
|
|
1081
|
+
...collectAidlcIntentContextFiles(snapshot, bolt.intent)
|
|
1082
|
+
])
|
|
1083
|
+
}];
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (effectiveFlow === 'simple') {
|
|
1087
|
+
const spec = getCurrentSpec(snapshot);
|
|
1088
|
+
if (!spec) {
|
|
1089
|
+
return [];
|
|
1090
|
+
}
|
|
1091
|
+
return [{
|
|
1092
|
+
key: `current:spec:${spec.name}`,
|
|
1093
|
+
label: `${spec.name} [${spec.state}] ${spec.tasksCompleted}/${spec.tasksTotal} tasks`,
|
|
1094
|
+
files: filterExistingFiles(collectSimpleSpecFiles(spec))
|
|
1095
|
+
}];
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const run = getCurrentRun(snapshot);
|
|
1099
|
+
if (!run) {
|
|
1100
|
+
return [];
|
|
1101
|
+
}
|
|
1102
|
+
const workItems = Array.isArray(run.workItems) ? run.workItems : [];
|
|
1103
|
+
const completed = workItems.filter((item) => item.status === 'completed').length;
|
|
1104
|
+
return [{
|
|
1105
|
+
key: `current:run:${run.id}`,
|
|
1106
|
+
label: `${run.id} [${run.scope}] ${completed}/${workItems.length} items`,
|
|
1107
|
+
files: filterExistingFiles(collectFireRunFiles(run).map((file) => ({ ...file, scope: 'active' })))
|
|
1108
|
+
}];
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function buildRunFileGroups(fileEntries) {
|
|
1112
|
+
const order = ['active', 'upcoming', 'completed', 'intent', 'other'];
|
|
1113
|
+
const buckets = new Map(order.map((scope) => [scope, []]));
|
|
1114
|
+
|
|
1115
|
+
for (const fileEntry of Array.isArray(fileEntries) ? fileEntries : []) {
|
|
1116
|
+
const scope = order.includes(fileEntry?.scope) ? fileEntry.scope : 'other';
|
|
1117
|
+
buckets.get(scope).push(fileEntry);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const groups = [];
|
|
1121
|
+
for (const scope of order) {
|
|
1122
|
+
const files = buckets.get(scope) || [];
|
|
1123
|
+
if (files.length === 0) {
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
groups.push({
|
|
1127
|
+
key: `run-files:scope:${scope}`,
|
|
1128
|
+
label: `${formatScope(scope)} files (${files.length})`,
|
|
1129
|
+
files: filterExistingFiles(files)
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
return groups;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function getFileEntityLabel(fileEntry, fallbackIndex = 0) {
|
|
1136
|
+
const rawLabel = typeof fileEntry?.label === 'string' ? fileEntry.label : '';
|
|
1137
|
+
if (rawLabel.includes('/')) {
|
|
1138
|
+
return rawLabel.split('/')[0] || `item-${fallbackIndex + 1}`;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
const filePath = typeof fileEntry?.path === 'string' ? fileEntry.path : '';
|
|
1142
|
+
if (filePath !== '') {
|
|
1143
|
+
const parentDir = path.basename(path.dirname(filePath));
|
|
1144
|
+
if (parentDir && parentDir !== '.' && parentDir !== path.sep) {
|
|
1145
|
+
return parentDir;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const baseName = path.basename(filePath);
|
|
1149
|
+
if (baseName) {
|
|
1150
|
+
return baseName;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return `item-${fallbackIndex + 1}`;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function buildRunFileEntityGroups(snapshot, flow) {
|
|
1158
|
+
const order = ['active', 'upcoming', 'completed', 'intent', 'other'];
|
|
1159
|
+
const rankByScope = new Map(order.map((scope, index) => [scope, index]));
|
|
1160
|
+
const entries = filterExistingFiles(getRunFileEntries(snapshot, flow));
|
|
1161
|
+
const groupsByEntity = new Map();
|
|
1162
|
+
|
|
1163
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
1164
|
+
const fileEntry = entries[index];
|
|
1165
|
+
const entity = getFileEntityLabel(fileEntry, index);
|
|
1166
|
+
const scope = order.includes(fileEntry?.scope) ? fileEntry.scope : 'other';
|
|
1167
|
+
const scopeRank = rankByScope.get(scope) ?? rankByScope.get('other');
|
|
1168
|
+
|
|
1169
|
+
if (!groupsByEntity.has(entity)) {
|
|
1170
|
+
groupsByEntity.set(entity, {
|
|
1171
|
+
entity,
|
|
1172
|
+
files: [],
|
|
1173
|
+
scope,
|
|
1174
|
+
scopeRank
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const group = groupsByEntity.get(entity);
|
|
1179
|
+
group.files.push(fileEntry);
|
|
1180
|
+
|
|
1181
|
+
if (scopeRank < group.scopeRank) {
|
|
1182
|
+
group.scopeRank = scopeRank;
|
|
1183
|
+
group.scope = scope;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
return Array.from(groupsByEntity.values())
|
|
1188
|
+
.sort((a, b) => {
|
|
1189
|
+
if (a.scopeRank !== b.scopeRank) {
|
|
1190
|
+
return a.scopeRank - b.scopeRank;
|
|
1191
|
+
}
|
|
1192
|
+
return String(a.entity).localeCompare(String(b.entity));
|
|
1193
|
+
})
|
|
1194
|
+
.map((group) => ({
|
|
1195
|
+
key: `run-files:entity:${group.entity}`,
|
|
1196
|
+
label: `${group.entity} [${formatScope(group.scope)}] (${group.files.length})`,
|
|
1197
|
+
files: filterExistingFiles(group.files)
|
|
1198
|
+
}))
|
|
1199
|
+
.filter((group) => group.files.length > 0);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function normalizeInfoLine(line) {
|
|
1203
|
+
const normalized = normalizePanelLine(line);
|
|
1204
|
+
return {
|
|
1205
|
+
label: normalized.text,
|
|
1206
|
+
color: normalized.color,
|
|
1207
|
+
bold: normalized.bold
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function toInfoRows(lines, keyPrefix, emptyLabel = 'No data') {
|
|
1212
|
+
const safe = Array.isArray(lines) ? lines : [];
|
|
1213
|
+
if (safe.length === 0) {
|
|
1059
1214
|
return [{
|
|
1060
1215
|
kind: 'info',
|
|
1061
|
-
key:
|
|
1062
|
-
label:
|
|
1216
|
+
key: `${keyPrefix}:empty`,
|
|
1217
|
+
label: emptyLabel,
|
|
1063
1218
|
selectable: false
|
|
1064
1219
|
}];
|
|
1065
1220
|
}
|
|
1066
1221
|
|
|
1067
|
-
return
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1222
|
+
return safe.map((line, index) => {
|
|
1223
|
+
const normalized = normalizeInfoLine(line);
|
|
1224
|
+
return {
|
|
1225
|
+
kind: 'info',
|
|
1226
|
+
key: `${keyPrefix}:${index}`,
|
|
1227
|
+
label: normalized.label,
|
|
1228
|
+
color: normalized.color,
|
|
1229
|
+
bold: normalized.bold,
|
|
1230
|
+
selectable: true
|
|
1231
|
+
};
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function buildOverviewIntentGroups(snapshot, flow, filter = 'next') {
|
|
1236
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1237
|
+
const normalizedFilter = filter === 'completed' ? 'completed' : 'next';
|
|
1238
|
+
const isIncluded = (status) => {
|
|
1239
|
+
if (normalizedFilter === 'completed') {
|
|
1240
|
+
return status === 'completed';
|
|
1241
|
+
}
|
|
1242
|
+
return status !== 'completed';
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
if (effectiveFlow === 'aidlc') {
|
|
1246
|
+
const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
|
|
1247
|
+
return intents
|
|
1248
|
+
.filter((intent) => isIncluded(intent?.status || 'pending'))
|
|
1249
|
+
.map((intent, index) => ({
|
|
1250
|
+
key: `overview:intent:${intent?.id || index}`,
|
|
1251
|
+
label: `${intent?.id || 'unknown'}: ${intent?.status || 'pending'} (${intent?.completedStories || 0}/${intent?.storyCount || 0} stories, ${intent?.completedUnits || 0}/${intent?.unitCount || 0} units)`,
|
|
1252
|
+
files: filterExistingFiles(collectAidlcIntentContextFiles(snapshot, intent?.id))
|
|
1253
|
+
}));
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (effectiveFlow === 'simple') {
|
|
1257
|
+
const specs = Array.isArray(snapshot?.specs) ? snapshot.specs : [];
|
|
1258
|
+
return specs
|
|
1259
|
+
.filter((spec) => isIncluded(spec?.state || 'pending'))
|
|
1260
|
+
.map((spec, index) => ({
|
|
1261
|
+
key: `overview:spec:${spec?.name || index}`,
|
|
1262
|
+
label: `${spec?.name || 'unknown'}: ${spec?.state || 'pending'} (${spec?.tasksCompleted || 0}/${spec?.tasksTotal || 0} tasks)`,
|
|
1263
|
+
files: filterExistingFiles(collectSimpleSpecFiles(spec))
|
|
1264
|
+
}));
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
|
|
1268
|
+
return intents
|
|
1269
|
+
.filter((intent) => isIncluded(intent?.status || 'pending'))
|
|
1270
|
+
.map((intent, index) => {
|
|
1271
|
+
const workItems = Array.isArray(intent?.workItems) ? intent.workItems : [];
|
|
1272
|
+
const done = workItems.filter((item) => item.status === 'completed').length;
|
|
1273
|
+
const files = [{
|
|
1274
|
+
label: `${intent?.id || 'intent'}/brief.md`,
|
|
1275
|
+
path: intent?.filePath,
|
|
1276
|
+
scope: 'intent'
|
|
1277
|
+
}, ...workItems.map((item) => ({
|
|
1278
|
+
label: `${intent?.id || 'intent'}/${item?.id || 'work-item'}.md`,
|
|
1279
|
+
path: item?.filePath,
|
|
1280
|
+
scope: item?.status === 'completed' ? 'completed' : 'upcoming'
|
|
1281
|
+
}))];
|
|
1282
|
+
return {
|
|
1283
|
+
key: `overview:intent:${intent?.id || index}`,
|
|
1284
|
+
label: `${intent?.id || 'unknown'}: ${intent?.status || 'pending'} (${done}/${workItems.length} work items)`,
|
|
1285
|
+
files: filterExistingFiles(files)
|
|
1286
|
+
};
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function buildStandardsGroups(snapshot, flow) {
|
|
1291
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1292
|
+
if (effectiveFlow === 'simple') {
|
|
1293
|
+
return [];
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const standards = Array.isArray(snapshot?.standards) ? snapshot.standards : [];
|
|
1297
|
+
return standards.map((standard, index) => {
|
|
1298
|
+
const id = standard?.type || standard?.name || String(index);
|
|
1299
|
+
const name = `${standard?.name || standard?.type || 'standard'}.md`;
|
|
1300
|
+
return {
|
|
1301
|
+
key: `standards:${id}`,
|
|
1302
|
+
label: name,
|
|
1303
|
+
files: filterExistingFiles([{
|
|
1304
|
+
label: name,
|
|
1305
|
+
path: standard?.filePath,
|
|
1306
|
+
scope: 'file'
|
|
1307
|
+
}])
|
|
1308
|
+
};
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function buildProjectGroups(snapshot, flow) {
|
|
1313
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1314
|
+
const files = [];
|
|
1315
|
+
|
|
1316
|
+
if (effectiveFlow === 'aidlc') {
|
|
1317
|
+
files.push({
|
|
1318
|
+
label: 'memory-bank/project.yaml',
|
|
1319
|
+
path: path.join(snapshot?.rootPath || '', 'project.yaml'),
|
|
1320
|
+
scope: 'file'
|
|
1321
|
+
});
|
|
1322
|
+
} else if (effectiveFlow === 'simple') {
|
|
1323
|
+
files.push({
|
|
1324
|
+
label: 'package.json',
|
|
1325
|
+
path: path.join(snapshot?.workspacePath || '', 'package.json'),
|
|
1326
|
+
scope: 'file'
|
|
1327
|
+
});
|
|
1328
|
+
} else {
|
|
1329
|
+
files.push({
|
|
1330
|
+
label: '.specs-fire/state.yaml',
|
|
1331
|
+
path: path.join(snapshot?.rootPath || '', 'state.yaml'),
|
|
1332
|
+
scope: 'file'
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const projectName = snapshot?.project?.name || 'unknown-project';
|
|
1337
|
+
return [{
|
|
1338
|
+
key: `project:${projectName}`,
|
|
1339
|
+
label: `project ${projectName}`,
|
|
1340
|
+
files: filterExistingFiles(files)
|
|
1341
|
+
}];
|
|
1075
1342
|
}
|
|
1076
1343
|
|
|
1077
1344
|
function collectAidlcIntentContextFiles(snapshot, intentId) {
|
|
@@ -1303,10 +1570,10 @@ function buildInteractiveRowsLines(rows, selectedIndex, icons, width, isFocusedS
|
|
|
1303
1570
|
}
|
|
1304
1571
|
|
|
1305
1572
|
return {
|
|
1306
|
-
text: truncate(` ${row.label || ''}`, width),
|
|
1307
|
-
color: 'gray',
|
|
1308
|
-
bold:
|
|
1309
|
-
selected:
|
|
1573
|
+
text: truncate(`${isSelected ? `${cursor} ` : ' '}${row.label || ''}`, width),
|
|
1574
|
+
color: isSelected ? (isFocusedSection ? 'green' : 'cyan') : (row.color || 'gray'),
|
|
1575
|
+
bold: isSelected || Boolean(row.bold),
|
|
1576
|
+
selected: isSelected
|
|
1310
1577
|
};
|
|
1311
1578
|
});
|
|
1312
1579
|
}
|
|
@@ -1399,19 +1666,27 @@ function openFileWithDefaultApp(filePath) {
|
|
|
1399
1666
|
|
|
1400
1667
|
function buildQuickHelpText(view, options = {}) {
|
|
1401
1668
|
const {
|
|
1669
|
+
flow = 'fire',
|
|
1402
1670
|
previewOpen = false,
|
|
1403
1671
|
availableFlowCount = 1
|
|
1404
1672
|
} = options;
|
|
1673
|
+
const isAidlc = String(flow || '').toLowerCase() === 'aidlc';
|
|
1674
|
+
const isSimple = String(flow || '').toLowerCase() === 'simple';
|
|
1675
|
+
const activeLabel = isAidlc ? 'active bolt' : (isSimple ? 'active spec' : 'active run');
|
|
1405
1676
|
|
|
1406
|
-
const parts = ['1/2/3
|
|
1677
|
+
const parts = ['1/2/3/4 tabs', 'g/G sections'];
|
|
1407
1678
|
|
|
1408
|
-
if (view === 'runs') {
|
|
1679
|
+
if (view === 'runs' || view === 'intents' || view === 'completed' || view === 'health') {
|
|
1409
1680
|
if (previewOpen) {
|
|
1410
|
-
parts.push('tab pane', '↑/↓ nav/scroll', 'v close');
|
|
1681
|
+
parts.push('tab pane', '↑/↓ nav/scroll', 'v/space close');
|
|
1411
1682
|
} else {
|
|
1412
|
-
parts.push('↑/↓ navigate', 'enter expand', 'v preview');
|
|
1683
|
+
parts.push('↑/↓ navigate', 'enter expand', 'v/space preview');
|
|
1413
1684
|
}
|
|
1414
1685
|
}
|
|
1686
|
+
if (view === 'runs') {
|
|
1687
|
+
parts.push('a current', 'f files');
|
|
1688
|
+
}
|
|
1689
|
+
parts.push(`tab1 ${activeLabel}`);
|
|
1415
1690
|
|
|
1416
1691
|
if (availableFlowCount > 1) {
|
|
1417
1692
|
parts.push('[/] flow');
|
|
@@ -1424,26 +1699,32 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
1424
1699
|
function buildHelpOverlayLines(options = {}) {
|
|
1425
1700
|
const {
|
|
1426
1701
|
view = 'runs',
|
|
1702
|
+
flow = 'fire',
|
|
1427
1703
|
previewOpen = false,
|
|
1428
1704
|
paneFocus = 'main',
|
|
1429
1705
|
availableFlowCount = 1,
|
|
1430
1706
|
showErrorSection = false
|
|
1431
1707
|
} = options;
|
|
1708
|
+
const isAidlc = String(flow || '').toLowerCase() === 'aidlc';
|
|
1709
|
+
const isSimple = String(flow || '').toLowerCase() === 'simple';
|
|
1710
|
+
const itemLabel = isAidlc ? 'bolt' : (isSimple ? 'spec' : 'run');
|
|
1711
|
+
const itemPlural = isAidlc ? 'bolts' : (isSimple ? 'specs' : 'runs');
|
|
1432
1712
|
|
|
1433
1713
|
const lines = [
|
|
1434
1714
|
{ text: 'Global', color: 'cyan', bold: true },
|
|
1435
1715
|
'q or Ctrl+C quit',
|
|
1436
1716
|
'r refresh snapshot',
|
|
1437
|
-
|
|
1717
|
+
`1 active ${itemLabel} | 2 intents | 3 completed ${itemPlural} | 4 standards/health`,
|
|
1438
1718
|
'g next section | G previous section',
|
|
1439
1719
|
'h/? toggle this shortcuts overlay',
|
|
1440
1720
|
'esc close overlays (help/preview/fullscreen)',
|
|
1441
1721
|
{ text: '', color: undefined, bold: false },
|
|
1442
|
-
{ text: '
|
|
1443
|
-
|
|
1722
|
+
{ text: 'Tab 1 Active', color: 'yellow', bold: true },
|
|
1723
|
+
`a focus active ${itemLabel}`,
|
|
1724
|
+
`f focus ${itemLabel} files`,
|
|
1444
1725
|
'up/down or j/k move selection',
|
|
1445
|
-
'enter expand/collapse
|
|
1446
|
-
'v preview selected file',
|
|
1726
|
+
'enter expand/collapse selected folder row',
|
|
1727
|
+
'v or space preview selected file',
|
|
1447
1728
|
'v twice quickly opens fullscreen preview overlay',
|
|
1448
1729
|
'tab switch focus between main and preview panes',
|
|
1449
1730
|
'o open selected file in system default app'
|
|
@@ -1459,12 +1740,16 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
1459
1740
|
|
|
1460
1741
|
lines.push(
|
|
1461
1742
|
{ text: '', color: undefined, bold: false },
|
|
1462
|
-
{ text: '
|
|
1463
|
-
'i intents
|
|
1464
|
-
'
|
|
1743
|
+
{ text: 'Tab 2 Intents', color: 'green', bold: true },
|
|
1744
|
+
'i focus intents',
|
|
1745
|
+
'n next intents | x completed intents',
|
|
1746
|
+
'left/right toggles next/completed when intents is focused',
|
|
1747
|
+
{ text: '', color: undefined, bold: false },
|
|
1748
|
+
{ text: 'Tab 3 Completed', color: 'blue', bold: true },
|
|
1749
|
+
'c focus completed items',
|
|
1465
1750
|
{ text: '', color: undefined, bold: false },
|
|
1466
|
-
{ text: 'Health
|
|
1467
|
-
`t stats | w warnings${showErrorSection ? ' | e errors' : ''}`,
|
|
1751
|
+
{ text: 'Tab 4 Standards/Health', color: 'magenta', bold: true },
|
|
1752
|
+
`s standards | t stats | w warnings${showErrorSection ? ' | e errors' : ''}`,
|
|
1468
1753
|
{ text: '', color: undefined, bold: false },
|
|
1469
1754
|
{ text: `Current view: ${String(view || 'runs').toUpperCase()}`, color: 'gray', bold: false }
|
|
1470
1755
|
);
|
|
@@ -1685,11 +1970,15 @@ function createDashboardApp(deps) {
|
|
|
1685
1970
|
}
|
|
1686
1971
|
|
|
1687
1972
|
function TabsBar(props) {
|
|
1688
|
-
const { view, width, icons } = props;
|
|
1973
|
+
const { view, width, icons, flow: activeFlow } = props;
|
|
1974
|
+
const effectiveFlow = String(activeFlow || '').toLowerCase();
|
|
1975
|
+
const primaryLabel = effectiveFlow === 'aidlc' ? 'BOLTS' : (effectiveFlow === 'simple' ? 'SPECS' : 'RUNS');
|
|
1976
|
+
const completedLabel = effectiveFlow === 'aidlc' ? 'COMPLETED BOLTS' : (effectiveFlow === 'simple' ? 'COMPLETED SPECS' : 'COMPLETED RUNS');
|
|
1689
1977
|
const tabs = [
|
|
1690
|
-
{ id: 'runs', label: ` 1 ${icons.runs}
|
|
1691
|
-
{ id: '
|
|
1692
|
-
{ id: '
|
|
1978
|
+
{ id: 'runs', label: ` 1 ${icons.runs} ${primaryLabel} ` },
|
|
1979
|
+
{ id: 'intents', label: ` 2 ${icons.overview} INTENTS ` },
|
|
1980
|
+
{ id: 'completed', label: ` 3 ${icons.runs} ${completedLabel} ` },
|
|
1981
|
+
{ id: 'health', label: ` 4 ${icons.health} STANDARDS/HEALTH ` }
|
|
1693
1982
|
];
|
|
1694
1983
|
|
|
1695
1984
|
return React.createElement(
|
|
@@ -1757,18 +2046,25 @@ function createDashboardApp(deps) {
|
|
|
1757
2046
|
const [error, setError] = useState(initialNormalizedError);
|
|
1758
2047
|
const [ui, setUi] = useState(createInitialUIState());
|
|
1759
2048
|
const [sectionFocus, setSectionFocus] = useState({
|
|
1760
|
-
runs: 'run
|
|
1761
|
-
|
|
1762
|
-
|
|
2049
|
+
runs: 'current-run',
|
|
2050
|
+
intents: 'intent-status',
|
|
2051
|
+
completed: 'completed-runs',
|
|
2052
|
+
health: 'standards'
|
|
1763
2053
|
});
|
|
1764
2054
|
const [selectionBySection, setSelectionBySection] = useState({
|
|
2055
|
+
'current-run': 0,
|
|
1765
2056
|
'run-files': 0,
|
|
1766
|
-
|
|
1767
|
-
completed: 0
|
|
2057
|
+
'intent-status': 0,
|
|
2058
|
+
'completed-runs': 0,
|
|
2059
|
+
standards: 0,
|
|
2060
|
+
stats: 0,
|
|
2061
|
+
warnings: 0,
|
|
2062
|
+
'error-details': 0
|
|
1768
2063
|
});
|
|
1769
2064
|
const [expandedGroups, setExpandedGroups] = useState({});
|
|
1770
2065
|
const [previewTarget, setPreviewTarget] = useState(null);
|
|
1771
2066
|
const [overviewIntentFilter, setOverviewIntentFilter] = useState('next');
|
|
2067
|
+
const [deferredTabsReady, setDeferredTabsReady] = useState(false);
|
|
1772
2068
|
const [previewOpen, setPreviewOpen] = useState(false);
|
|
1773
2069
|
const [paneFocus, setPaneFocus] = useState('main');
|
|
1774
2070
|
const [overlayPreviewOpen, setOverlayPreviewOpen] = useState(false);
|
|
@@ -1804,24 +2100,99 @@ function createDashboardApp(deps) {
|
|
|
1804
2100
|
return base.filter((sectionKey) => sectionKey !== 'error-details' || showErrorPanelForSections);
|
|
1805
2101
|
}, [showErrorPanelForSections]);
|
|
1806
2102
|
|
|
1807
|
-
const
|
|
1808
|
-
const
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
2103
|
+
const effectiveFlow = getEffectiveFlow(activeFlow, snapshot);
|
|
2104
|
+
const currentGroups = buildCurrentGroups(snapshot, activeFlow);
|
|
2105
|
+
const currentExpandedGroups = { ...expandedGroups };
|
|
2106
|
+
for (const group of currentGroups) {
|
|
2107
|
+
if (currentExpandedGroups[group.key] == null) {
|
|
2108
|
+
currentExpandedGroups[group.key] = true;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
const currentRunRows = toExpandableRows(
|
|
2113
|
+
currentGroups,
|
|
2114
|
+
getNoCurrentMessage(effectiveFlow),
|
|
2115
|
+
currentExpandedGroups
|
|
1813
2116
|
);
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
2117
|
+
const shouldHydrateSecondaryTabs = deferredTabsReady || ui.view !== 'runs';
|
|
2118
|
+
const runFileGroups = buildRunFileEntityGroups(snapshot, activeFlow);
|
|
2119
|
+
const runFileExpandedGroups = { ...expandedGroups };
|
|
2120
|
+
for (const group of runFileGroups) {
|
|
2121
|
+
if (runFileExpandedGroups[group.key] == null) {
|
|
2122
|
+
runFileExpandedGroups[group.key] = true;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
const runFileRows = toExpandableRows(
|
|
2126
|
+
runFileGroups,
|
|
2127
|
+
getNoFileMessage(effectiveFlow),
|
|
2128
|
+
runFileExpandedGroups
|
|
1818
2129
|
);
|
|
2130
|
+
const intentRows = shouldHydrateSecondaryTabs
|
|
2131
|
+
? [
|
|
2132
|
+
{
|
|
2133
|
+
kind: 'info',
|
|
2134
|
+
key: 'intent-filter',
|
|
2135
|
+
label: `filter ${overviewIntentFilter === 'completed' ? 'next | [COMPLETED]' : '[NEXT] | completed'} (n/x)`,
|
|
2136
|
+
color: 'cyan',
|
|
2137
|
+
bold: true,
|
|
2138
|
+
selectable: false
|
|
2139
|
+
},
|
|
2140
|
+
...toExpandableRows(
|
|
2141
|
+
buildOverviewIntentGroups(snapshot, activeFlow, overviewIntentFilter),
|
|
2142
|
+
overviewIntentFilter === 'completed' ? 'No completed intents yet' : 'No upcoming intents',
|
|
2143
|
+
expandedGroups
|
|
2144
|
+
)
|
|
2145
|
+
]
|
|
2146
|
+
: toInfoRows(['Loading intents...'], 'intent-loading');
|
|
2147
|
+
const completedRows = shouldHydrateSecondaryTabs
|
|
2148
|
+
? toExpandableRows(
|
|
2149
|
+
buildCompletedGroups(snapshot, activeFlow),
|
|
2150
|
+
getNoCompletedMessage(effectiveFlow),
|
|
2151
|
+
expandedGroups
|
|
2152
|
+
)
|
|
2153
|
+
: toInfoRows(['Loading completed items...'], 'completed-loading');
|
|
2154
|
+
const standardsRows = shouldHydrateSecondaryTabs
|
|
2155
|
+
? toExpandableRows(
|
|
2156
|
+
buildStandardsGroups(snapshot, activeFlow),
|
|
2157
|
+
effectiveFlow === 'simple' ? 'No standards for SIMPLE flow' : 'No standards found',
|
|
2158
|
+
expandedGroups
|
|
2159
|
+
)
|
|
2160
|
+
: toInfoRows(['Loading standards...'], 'standards-loading');
|
|
2161
|
+
const statsRows = shouldHydrateSecondaryTabs
|
|
2162
|
+
? toInfoRows(
|
|
2163
|
+
buildStatsLines(snapshot, 200, activeFlow),
|
|
2164
|
+
'stats',
|
|
2165
|
+
'No stats available'
|
|
2166
|
+
)
|
|
2167
|
+
: toInfoRows(['Loading stats...'], 'stats-loading');
|
|
2168
|
+
const warningsRows = shouldHydrateSecondaryTabs
|
|
2169
|
+
? toInfoRows(
|
|
2170
|
+
buildWarningsLines(snapshot, 200),
|
|
2171
|
+
'warnings',
|
|
2172
|
+
'No warnings'
|
|
2173
|
+
)
|
|
2174
|
+
: toInfoRows(['Loading warnings...'], 'warnings-loading');
|
|
2175
|
+
const errorDetailsRows = shouldHydrateSecondaryTabs
|
|
2176
|
+
? toInfoRows(
|
|
2177
|
+
buildErrorLines(error, 200),
|
|
2178
|
+
'error-details',
|
|
2179
|
+
'No error details'
|
|
2180
|
+
)
|
|
2181
|
+
: toInfoRows(['Loading error details...'], 'error-loading');
|
|
1819
2182
|
|
|
1820
2183
|
const rowsBySection = {
|
|
2184
|
+
'current-run': currentRunRows,
|
|
1821
2185
|
'run-files': runFileRows,
|
|
1822
|
-
|
|
1823
|
-
completed: completedRows
|
|
2186
|
+
'intent-status': intentRows,
|
|
2187
|
+
'completed-runs': completedRows,
|
|
2188
|
+
standards: standardsRows,
|
|
2189
|
+
stats: statsRows,
|
|
2190
|
+
warnings: warningsRows,
|
|
2191
|
+
'error-details': errorDetailsRows
|
|
1824
2192
|
};
|
|
2193
|
+
const rowLengthSignature = Object.entries(rowsBySection)
|
|
2194
|
+
.map(([key, rowsForSection]) => `${key}:${Array.isArray(rowsForSection) ? rowsForSection.length : 0}`)
|
|
2195
|
+
.join('|');
|
|
1825
2196
|
|
|
1826
2197
|
const currentSectionOrder = getAvailableSections(ui.view);
|
|
1827
2198
|
const focusedSection = currentSectionOrder.includes(sectionFocus[ui.view])
|
|
@@ -1914,12 +2285,18 @@ function createDashboardApp(deps) {
|
|
|
1914
2285
|
}
|
|
1915
2286
|
|
|
1916
2287
|
if (input === '2') {
|
|
1917
|
-
setUi((previous) => ({ ...previous, view: '
|
|
2288
|
+
setUi((previous) => ({ ...previous, view: 'intents' }));
|
|
1918
2289
|
setPaneFocus('main');
|
|
1919
2290
|
return;
|
|
1920
2291
|
}
|
|
1921
2292
|
|
|
1922
2293
|
if (input === '3') {
|
|
2294
|
+
setUi((previous) => ({ ...previous, view: 'completed' }));
|
|
2295
|
+
setPaneFocus('main');
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
if (input === '4') {
|
|
1923
2300
|
setUi((previous) => ({ ...previous, view: 'health' }));
|
|
1924
2301
|
setPaneFocus('main');
|
|
1925
2302
|
return;
|
|
@@ -1938,14 +2315,20 @@ function createDashboardApp(deps) {
|
|
|
1938
2315
|
return availableFlowIds[nextIndex];
|
|
1939
2316
|
});
|
|
1940
2317
|
setSelectionBySection({
|
|
2318
|
+
'current-run': 0,
|
|
1941
2319
|
'run-files': 0,
|
|
1942
|
-
|
|
1943
|
-
completed: 0
|
|
2320
|
+
'intent-status': 0,
|
|
2321
|
+
'completed-runs': 0,
|
|
2322
|
+
standards: 0,
|
|
2323
|
+
stats: 0,
|
|
2324
|
+
warnings: 0,
|
|
2325
|
+
'error-details': 0
|
|
1944
2326
|
});
|
|
1945
2327
|
setSectionFocus({
|
|
1946
|
-
runs: 'run
|
|
1947
|
-
|
|
1948
|
-
|
|
2328
|
+
runs: 'current-run',
|
|
2329
|
+
intents: 'intent-status',
|
|
2330
|
+
completed: 'completed-runs',
|
|
2331
|
+
health: 'standards'
|
|
1949
2332
|
});
|
|
1950
2333
|
setOverviewIntentFilter('next');
|
|
1951
2334
|
setExpandedGroups({});
|
|
@@ -1970,14 +2353,20 @@ function createDashboardApp(deps) {
|
|
|
1970
2353
|
return availableFlowIds[nextIndex];
|
|
1971
2354
|
});
|
|
1972
2355
|
setSelectionBySection({
|
|
2356
|
+
'current-run': 0,
|
|
1973
2357
|
'run-files': 0,
|
|
1974
|
-
|
|
1975
|
-
completed: 0
|
|
2358
|
+
'intent-status': 0,
|
|
2359
|
+
'completed-runs': 0,
|
|
2360
|
+
standards: 0,
|
|
2361
|
+
stats: 0,
|
|
2362
|
+
warnings: 0,
|
|
2363
|
+
'error-details': 0
|
|
1976
2364
|
});
|
|
1977
2365
|
setSectionFocus({
|
|
1978
|
-
runs: 'run
|
|
1979
|
-
|
|
1980
|
-
|
|
2366
|
+
runs: 'current-run',
|
|
2367
|
+
intents: 'intent-status',
|
|
2368
|
+
completed: 'completed-runs',
|
|
2369
|
+
health: 'standards'
|
|
1981
2370
|
});
|
|
1982
2371
|
setOverviewIntentFilter('next');
|
|
1983
2372
|
setExpandedGroups({});
|
|
@@ -1994,12 +2383,12 @@ function createDashboardApp(deps) {
|
|
|
1994
2383
|
? sectionFocus[ui.view]
|
|
1995
2384
|
: (availableSections[0] || 'current-run');
|
|
1996
2385
|
|
|
1997
|
-
if (key.tab &&
|
|
2386
|
+
if (key.tab && previewOpen) {
|
|
1998
2387
|
setPaneFocus((previous) => (previous === 'main' ? 'preview' : 'main'));
|
|
1999
2388
|
return;
|
|
2000
2389
|
}
|
|
2001
2390
|
|
|
2002
|
-
if (ui.view === '
|
|
2391
|
+
if (ui.view === 'intents' && activeSection === 'intent-status') {
|
|
2003
2392
|
if (input === 'n') {
|
|
2004
2393
|
setOverviewIntentFilter('next');
|
|
2005
2394
|
return;
|
|
@@ -2043,29 +2432,21 @@ function createDashboardApp(deps) {
|
|
|
2043
2432
|
setPaneFocus('main');
|
|
2044
2433
|
return;
|
|
2045
2434
|
}
|
|
2046
|
-
|
|
2047
|
-
setSectionFocus((previous) => ({ ...previous, runs: 'pending' }));
|
|
2048
|
-
setPaneFocus('main');
|
|
2049
|
-
return;
|
|
2050
|
-
}
|
|
2051
|
-
} else if (ui.view === 'overview') {
|
|
2052
|
-
if (input === 'p') {
|
|
2053
|
-
setSectionFocus((previous) => ({ ...previous, overview: 'project' }));
|
|
2054
|
-
return;
|
|
2055
|
-
}
|
|
2435
|
+
} else if (ui.view === 'intents') {
|
|
2056
2436
|
if (input === 'i') {
|
|
2057
|
-
setSectionFocus((previous) => ({ ...previous,
|
|
2058
|
-
return;
|
|
2059
|
-
}
|
|
2060
|
-
if (input === 's') {
|
|
2061
|
-
setSectionFocus((previous) => ({ ...previous, overview: 'standards' }));
|
|
2437
|
+
setSectionFocus((previous) => ({ ...previous, intents: 'intent-status' }));
|
|
2062
2438
|
return;
|
|
2063
2439
|
}
|
|
2440
|
+
} else if (ui.view === 'completed') {
|
|
2064
2441
|
if (input === 'c') {
|
|
2065
|
-
setSectionFocus((previous) => ({ ...previous,
|
|
2442
|
+
setSectionFocus((previous) => ({ ...previous, completed: 'completed-runs' }));
|
|
2066
2443
|
return;
|
|
2067
2444
|
}
|
|
2068
2445
|
} else if (ui.view === 'health') {
|
|
2446
|
+
if (input === 's') {
|
|
2447
|
+
setSectionFocus((previous) => ({ ...previous, health: 'standards' }));
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2069
2450
|
if (input === 't') {
|
|
2070
2451
|
setSectionFocus((previous) => ({ ...previous, health: 'stats' }));
|
|
2071
2452
|
return;
|
|
@@ -2094,7 +2475,7 @@ function createDashboardApp(deps) {
|
|
|
2094
2475
|
}
|
|
2095
2476
|
}
|
|
2096
2477
|
|
|
2097
|
-
if (
|
|
2478
|
+
if (key.upArrow || key.downArrow || input === 'j' || input === 'k') {
|
|
2098
2479
|
const moveDown = key.downArrow || input === 'j';
|
|
2099
2480
|
const moveUp = key.upArrow || input === 'k';
|
|
2100
2481
|
|
|
@@ -2107,11 +2488,7 @@ function createDashboardApp(deps) {
|
|
|
2107
2488
|
return;
|
|
2108
2489
|
}
|
|
2109
2490
|
|
|
2110
|
-
const targetSection = activeSection
|
|
2111
|
-
if (targetSection !== activeSection) {
|
|
2112
|
-
setSectionFocus((previous) => ({ ...previous, runs: targetSection }));
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2491
|
+
const targetSection = activeSection;
|
|
2115
2492
|
const targetRows = rowsBySection[targetSection] || [];
|
|
2116
2493
|
if (targetRows.length === 0) {
|
|
2117
2494
|
return;
|
|
@@ -2129,24 +2506,22 @@ function createDashboardApp(deps) {
|
|
|
2129
2506
|
return;
|
|
2130
2507
|
}
|
|
2131
2508
|
|
|
2132
|
-
if (
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
}));
|
|
2141
|
-
}
|
|
2509
|
+
if (key.return || key.enter) {
|
|
2510
|
+
const rowsForSection = rowsBySection[activeSection] || [];
|
|
2511
|
+
const selectedRow = getSelectedRow(rowsForSection, selectionBySection[activeSection] || 0);
|
|
2512
|
+
if (selectedRow?.kind === 'group' && selectedRow.expandable) {
|
|
2513
|
+
setExpandedGroups((previous) => ({
|
|
2514
|
+
...previous,
|
|
2515
|
+
[selectedRow.key]: !previous[selectedRow.key]
|
|
2516
|
+
}));
|
|
2142
2517
|
}
|
|
2143
2518
|
return;
|
|
2144
2519
|
}
|
|
2145
2520
|
|
|
2146
|
-
if (input === 'v'
|
|
2521
|
+
if (input === 'v' || input === ' ' || key.space) {
|
|
2147
2522
|
const target = selectedFocusedFile || previewTarget;
|
|
2148
2523
|
if (!target) {
|
|
2149
|
-
setStatusLine('Select a file row first
|
|
2524
|
+
setStatusLine('Select a file row first.');
|
|
2150
2525
|
return;
|
|
2151
2526
|
}
|
|
2152
2527
|
|
|
@@ -2184,7 +2559,7 @@ function createDashboardApp(deps) {
|
|
|
2184
2559
|
return;
|
|
2185
2560
|
}
|
|
2186
2561
|
|
|
2187
|
-
if (input === 'o'
|
|
2562
|
+
if (input === 'o') {
|
|
2188
2563
|
const target = selectedFocusedFile || previewTarget;
|
|
2189
2564
|
const result = openFileWithDefaultApp(target?.path);
|
|
2190
2565
|
setStatusLine(result.message);
|
|
@@ -2196,21 +2571,38 @@ function createDashboardApp(deps) {
|
|
|
2196
2571
|
}, [refresh]);
|
|
2197
2572
|
|
|
2198
2573
|
useEffect(() => {
|
|
2199
|
-
setSelectionBySection((previous) =>
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2574
|
+
setSelectionBySection((previous) => {
|
|
2575
|
+
let changed = false;
|
|
2576
|
+
const next = { ...previous };
|
|
2577
|
+
|
|
2578
|
+
for (const [sectionKey, sectionRows] of Object.entries(rowsBySection)) {
|
|
2579
|
+
const previousValue = Number.isFinite(previous[sectionKey]) ? previous[sectionKey] : 0;
|
|
2580
|
+
const clampedValue = clampIndex(previousValue, sectionRows.length);
|
|
2581
|
+
if (previousValue !== clampedValue) {
|
|
2582
|
+
next[sectionKey] = clampedValue;
|
|
2583
|
+
changed = true;
|
|
2584
|
+
} else if (!(sectionKey in next)) {
|
|
2585
|
+
next[sectionKey] = clampedValue;
|
|
2586
|
+
changed = true;
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
return changed ? next : previous;
|
|
2591
|
+
});
|
|
2592
|
+
}, [activeFlow, rowLengthSignature, snapshot?.generatedAt]);
|
|
2206
2593
|
|
|
2207
2594
|
useEffect(() => {
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2595
|
+
setDeferredTabsReady(false);
|
|
2596
|
+
const timer = setTimeout(() => {
|
|
2597
|
+
setDeferredTabsReady(true);
|
|
2598
|
+
}, 0);
|
|
2599
|
+
return () => {
|
|
2600
|
+
clearTimeout(timer);
|
|
2601
|
+
};
|
|
2602
|
+
}, [activeFlow, snapshot?.generatedAt]);
|
|
2603
|
+
|
|
2604
|
+
useEffect(() => {
|
|
2605
|
+
setPaneFocus('main');
|
|
2214
2606
|
}, [ui.view]);
|
|
2215
2607
|
|
|
2216
2608
|
useEffect(() => {
|
|
@@ -2324,16 +2716,25 @@ function createDashboardApp(deps) {
|
|
|
2324
2716
|
const rows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
|
|
2325
2717
|
|
|
2326
2718
|
const fullWidth = Math.max(40, cols - 1);
|
|
2719
|
+
const showFlowBar = availableFlowIds.length > 1;
|
|
2327
2720
|
const showFooterHelpLine = rows >= 10;
|
|
2328
2721
|
const showErrorPanel = Boolean(error) && rows >= 18;
|
|
2722
|
+
const showGlobalErrorPanel = showErrorPanel && ui.view !== 'health' && !ui.showHelp;
|
|
2329
2723
|
const showErrorInline = Boolean(error) && !showErrorPanel;
|
|
2724
|
+
const showStatusLine = statusLine !== '';
|
|
2330
2725
|
const densePanels = rows <= 28 || cols <= 120;
|
|
2331
2726
|
|
|
2332
|
-
const reservedRows =
|
|
2727
|
+
const reservedRows =
|
|
2728
|
+
2 +
|
|
2729
|
+
(showFlowBar ? 1 : 0) +
|
|
2730
|
+
(showFooterHelpLine ? 1 : 0) +
|
|
2731
|
+
(showGlobalErrorPanel ? 5 : 0) +
|
|
2732
|
+
(showErrorInline ? 1 : 0) +
|
|
2733
|
+
(showStatusLine ? 1 : 0);
|
|
2333
2734
|
const contentRowsBudget = Math.max(4, rows - reservedRows);
|
|
2334
2735
|
const ultraCompact = rows <= 14;
|
|
2335
2736
|
const panelTitles = getPanelTitles(activeFlow, snapshot);
|
|
2336
|
-
const splitPreviewLayout =
|
|
2737
|
+
const splitPreviewLayout = previewOpen && !overlayPreviewOpen && !ui.showHelp && cols >= 110 && rows >= 16;
|
|
2337
2738
|
const mainPaneWidth = splitPreviewLayout
|
|
2338
2739
|
? Math.max(34, Math.floor((fullWidth - 1) * 0.52))
|
|
2339
2740
|
: fullWidth;
|
|
@@ -2343,19 +2744,17 @@ function createDashboardApp(deps) {
|
|
|
2343
2744
|
const mainCompactWidth = Math.max(18, mainPaneWidth - 4);
|
|
2344
2745
|
const previewCompactWidth = Math.max(18, previewPaneWidth - 4);
|
|
2345
2746
|
|
|
2346
|
-
const
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
mainCompactWidth,
|
|
2358
|
-
ui.view === 'runs' && focusedSection === 'pending' && paneFocus === 'main'
|
|
2747
|
+
const sectionLines = Object.fromEntries(
|
|
2748
|
+
Object.entries(rowsBySection).map(([sectionKey, sectionRows]) => [
|
|
2749
|
+
sectionKey,
|
|
2750
|
+
buildInteractiveRowsLines(
|
|
2751
|
+
sectionRows,
|
|
2752
|
+
selectionBySection[sectionKey] || 0,
|
|
2753
|
+
icons,
|
|
2754
|
+
mainCompactWidth,
|
|
2755
|
+
paneFocus === 'main' && focusedSection === sectionKey
|
|
2756
|
+
)
|
|
2757
|
+
])
|
|
2359
2758
|
);
|
|
2360
2759
|
const effectivePreviewTarget = previewTarget || selectedFocusedFile;
|
|
2361
2760
|
const previewLines = previewOpen
|
|
@@ -2366,12 +2765,14 @@ function createDashboardApp(deps) {
|
|
|
2366
2765
|
|
|
2367
2766
|
const shortcutsOverlayLines = buildHelpOverlayLines({
|
|
2368
2767
|
view: ui.view,
|
|
2768
|
+
flow: activeFlow,
|
|
2369
2769
|
previewOpen,
|
|
2370
2770
|
paneFocus,
|
|
2371
2771
|
availableFlowCount: availableFlowIds.length,
|
|
2372
2772
|
showErrorSection: showErrorPanel
|
|
2373
2773
|
});
|
|
2374
2774
|
const quickHelpText = buildQuickHelpText(ui.view, {
|
|
2775
|
+
flow: activeFlow,
|
|
2375
2776
|
previewOpen,
|
|
2376
2777
|
paneFocus,
|
|
2377
2778
|
availableFlowCount: availableFlowIds.length
|
|
@@ -2387,7 +2788,7 @@ function createDashboardApp(deps) {
|
|
|
2387
2788
|
borderColor: 'cyan'
|
|
2388
2789
|
}
|
|
2389
2790
|
];
|
|
2390
|
-
} else if (
|
|
2791
|
+
} else if (previewOpen && overlayPreviewOpen) {
|
|
2391
2792
|
panelCandidates = [
|
|
2392
2793
|
{
|
|
2393
2794
|
key: 'preview-overlay',
|
|
@@ -2396,45 +2797,42 @@ function createDashboardApp(deps) {
|
|
|
2396
2797
|
borderColor: 'magenta'
|
|
2397
2798
|
}
|
|
2398
2799
|
];
|
|
2399
|
-
} else if (ui.view === '
|
|
2800
|
+
} else if (ui.view === 'intents') {
|
|
2400
2801
|
panelCandidates = [
|
|
2401
2802
|
{
|
|
2402
2803
|
key: 'intent-status',
|
|
2403
2804
|
title: 'Intents',
|
|
2404
|
-
lines:
|
|
2805
|
+
lines: sectionLines['intent-status'],
|
|
2405
2806
|
borderColor: 'yellow'
|
|
2406
|
-
}
|
|
2807
|
+
}
|
|
2808
|
+
];
|
|
2809
|
+
} else if (ui.view === 'completed') {
|
|
2810
|
+
panelCandidates = [
|
|
2407
2811
|
{
|
|
2408
2812
|
key: 'completed-runs',
|
|
2409
2813
|
title: panelTitles.completed,
|
|
2410
|
-
lines:
|
|
2814
|
+
lines: sectionLines['completed-runs'],
|
|
2411
2815
|
borderColor: 'blue'
|
|
2412
|
-
}
|
|
2816
|
+
}
|
|
2817
|
+
];
|
|
2818
|
+
} else if (ui.view === 'health') {
|
|
2819
|
+
panelCandidates = [
|
|
2413
2820
|
{
|
|
2414
2821
|
key: 'standards',
|
|
2415
2822
|
title: 'Standards',
|
|
2416
|
-
lines:
|
|
2823
|
+
lines: sectionLines.standards,
|
|
2417
2824
|
borderColor: 'blue'
|
|
2418
2825
|
},
|
|
2419
|
-
{
|
|
2420
|
-
key: 'project',
|
|
2421
|
-
title: 'Project + Workspace',
|
|
2422
|
-
lines: buildOverviewProjectLines(snapshot, mainCompactWidth, activeFlow),
|
|
2423
|
-
borderColor: 'green'
|
|
2424
|
-
}
|
|
2425
|
-
];
|
|
2426
|
-
} else if (ui.view === 'health') {
|
|
2427
|
-
panelCandidates = [
|
|
2428
2826
|
{
|
|
2429
2827
|
key: 'stats',
|
|
2430
2828
|
title: 'Stats',
|
|
2431
|
-
lines:
|
|
2829
|
+
lines: sectionLines.stats,
|
|
2432
2830
|
borderColor: 'magenta'
|
|
2433
2831
|
},
|
|
2434
2832
|
{
|
|
2435
2833
|
key: 'warnings',
|
|
2436
2834
|
title: 'Warnings',
|
|
2437
|
-
lines:
|
|
2835
|
+
lines: sectionLines.warnings,
|
|
2438
2836
|
borderColor: 'red'
|
|
2439
2837
|
}
|
|
2440
2838
|
];
|
|
@@ -2443,47 +2841,42 @@ function createDashboardApp(deps) {
|
|
|
2443
2841
|
panelCandidates.push({
|
|
2444
2842
|
key: 'error-details',
|
|
2445
2843
|
title: 'Error Details',
|
|
2446
|
-
lines:
|
|
2844
|
+
lines: sectionLines['error-details'],
|
|
2447
2845
|
borderColor: 'red'
|
|
2448
2846
|
});
|
|
2449
2847
|
}
|
|
2450
2848
|
} else {
|
|
2451
|
-
const includeInlinePreviewPanel = previewOpen && !splitPreviewLayout;
|
|
2452
2849
|
panelCandidates = [
|
|
2453
2850
|
{
|
|
2454
2851
|
key: 'current-run',
|
|
2455
2852
|
title: panelTitles.current,
|
|
2456
|
-
lines:
|
|
2853
|
+
lines: sectionLines['current-run'],
|
|
2457
2854
|
borderColor: 'green'
|
|
2458
2855
|
},
|
|
2459
2856
|
{
|
|
2460
2857
|
key: 'run-files',
|
|
2461
2858
|
title: panelTitles.files,
|
|
2462
|
-
lines:
|
|
2463
|
-
borderColor: 'yellow'
|
|
2464
|
-
},
|
|
2465
|
-
includeInlinePreviewPanel
|
|
2466
|
-
? {
|
|
2467
|
-
key: 'preview',
|
|
2468
|
-
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
2469
|
-
lines: previewLines,
|
|
2470
|
-
borderColor: 'magenta'
|
|
2471
|
-
}
|
|
2472
|
-
: null,
|
|
2473
|
-
{
|
|
2474
|
-
key: 'pending',
|
|
2475
|
-
title: panelTitles.pending,
|
|
2476
|
-
lines: pendingLines,
|
|
2859
|
+
lines: sectionLines['run-files'],
|
|
2477
2860
|
borderColor: 'yellow'
|
|
2478
2861
|
}
|
|
2479
2862
|
];
|
|
2480
2863
|
}
|
|
2481
2864
|
|
|
2865
|
+
if (!ui.showHelp && previewOpen && !overlayPreviewOpen && !splitPreviewLayout) {
|
|
2866
|
+
panelCandidates.push({
|
|
2867
|
+
key: 'preview',
|
|
2868
|
+
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
2869
|
+
lines: previewLines,
|
|
2870
|
+
borderColor: 'magenta'
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2482
2874
|
if (ultraCompact && !splitPreviewLayout) {
|
|
2483
2875
|
if (previewOpen) {
|
|
2484
|
-
panelCandidates = panelCandidates.filter((panel) => panel && (panel.key ===
|
|
2876
|
+
panelCandidates = panelCandidates.filter((panel) => panel && (panel.key === focusedSection || panel.key === 'preview'));
|
|
2485
2877
|
} else {
|
|
2486
|
-
|
|
2878
|
+
const focusedPanel = panelCandidates.find((panel) => panel?.key === focusedSection);
|
|
2879
|
+
panelCandidates = [focusedPanel || panelCandidates[0]];
|
|
2487
2880
|
}
|
|
2488
2881
|
}
|
|
2489
2882
|
|
|
@@ -2502,7 +2895,7 @@ function createDashboardApp(deps) {
|
|
|
2502
2895
|
});
|
|
2503
2896
|
|
|
2504
2897
|
let contentNode;
|
|
2505
|
-
if (splitPreviewLayout &&
|
|
2898
|
+
if (splitPreviewLayout && !overlayPreviewOpen) {
|
|
2506
2899
|
const previewPanel = {
|
|
2507
2900
|
key: 'preview-split',
|
|
2508
2901
|
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
@@ -2564,11 +2957,11 @@ function createDashboardApp(deps) {
|
|
|
2564
2957
|
{ flexDirection: 'column', width: fullWidth },
|
|
2565
2958
|
React.createElement(Text, { color: 'cyan' }, buildHeaderLine(snapshot, activeFlow, watchEnabled, watchStatus, lastRefreshAt, ui.view, fullWidth)),
|
|
2566
2959
|
React.createElement(FlowBar, { activeFlow, width: fullWidth, flowIds: availableFlowIds }),
|
|
2567
|
-
React.createElement(TabsBar, { view: ui.view, width: fullWidth, icons }),
|
|
2960
|
+
React.createElement(TabsBar, { view: ui.view, width: fullWidth, icons, flow: activeFlow }),
|
|
2568
2961
|
showErrorInline
|
|
2569
2962
|
? React.createElement(Text, { color: 'red' }, truncate(buildErrorLines(error, fullWidth)[0] || 'Error', fullWidth))
|
|
2570
2963
|
: null,
|
|
2571
|
-
|
|
2964
|
+
showGlobalErrorPanel
|
|
2572
2965
|
? React.createElement(SectionPanel, {
|
|
2573
2966
|
title: 'Errors',
|
|
2574
2967
|
lines: buildErrorLines(error, Math.max(18, fullWidth - 4)),
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
function createInitialUIState() {
|
|
2
2
|
return {
|
|
3
3
|
view: 'runs',
|
|
4
|
-
showHelp:
|
|
4
|
+
showHelp: false
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function cycleView(current) {
|
|
9
9
|
if (current === 'runs') {
|
|
10
|
-
return '
|
|
10
|
+
return 'intents';
|
|
11
11
|
}
|
|
12
|
-
if (current === '
|
|
12
|
+
if (current === 'intents') {
|
|
13
|
+
return 'completed';
|
|
14
|
+
}
|
|
15
|
+
if (current === 'completed') {
|
|
13
16
|
return 'health';
|
|
14
17
|
}
|
|
15
18
|
return 'runs';
|
|
@@ -19,10 +22,13 @@ function cycleViewBackward(current) {
|
|
|
19
22
|
if (current === 'runs') {
|
|
20
23
|
return 'health';
|
|
21
24
|
}
|
|
22
|
-
if (current === '
|
|
25
|
+
if (current === 'intents') {
|
|
23
26
|
return 'runs';
|
|
24
27
|
}
|
|
25
|
-
|
|
28
|
+
if (current === 'completed') {
|
|
29
|
+
return 'intents';
|
|
30
|
+
}
|
|
31
|
+
return 'completed';
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"description": "Multi-agent orchestration system for AI-native software development. Delivers AI-DLC, Agile, and custom SDLC flows as markdown-based agent systems.",
|
|
5
5
|
"main": "lib/installer.js",
|
|
6
6
|
"bin": {
|