specsmd 0.1.32 → 0.1.34
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 +939 -126
- package/package.json +1 -1
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
3
4
|
const { createWatchRuntime } = require('../runtime/watch-runtime');
|
|
4
|
-
const { createInitialUIState
|
|
5
|
+
const { createInitialUIState } = require('./store');
|
|
5
6
|
|
|
6
7
|
function toDashboardError(error, defaultCode = 'DASHBOARD_ERROR') {
|
|
7
8
|
if (!error) {
|
|
@@ -54,14 +55,20 @@ function resolveIconSet() {
|
|
|
54
55
|
runs: '[R]',
|
|
55
56
|
overview: '[O]',
|
|
56
57
|
health: '[H]',
|
|
57
|
-
runFile: '*'
|
|
58
|
+
runFile: '*',
|
|
59
|
+
activeFile: '>',
|
|
60
|
+
groupCollapsed: '>',
|
|
61
|
+
groupExpanded: 'v'
|
|
58
62
|
};
|
|
59
63
|
|
|
60
64
|
const nerd = {
|
|
61
65
|
runs: '',
|
|
62
66
|
overview: '',
|
|
63
67
|
health: '',
|
|
64
|
-
runFile: ''
|
|
68
|
+
runFile: '',
|
|
69
|
+
activeFile: '',
|
|
70
|
+
groupCollapsed: '',
|
|
71
|
+
groupExpanded: ''
|
|
65
72
|
};
|
|
66
73
|
|
|
67
74
|
if (mode === 'ascii') {
|
|
@@ -682,15 +689,70 @@ function buildOverviewProjectLines(snapshot, width, flow) {
|
|
|
682
689
|
return buildFireOverviewProjectLines(snapshot, width);
|
|
683
690
|
}
|
|
684
691
|
|
|
685
|
-
function
|
|
692
|
+
function listOverviewIntentEntries(snapshot, flow) {
|
|
686
693
|
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
687
694
|
if (effectiveFlow === 'aidlc') {
|
|
688
|
-
|
|
695
|
+
const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
|
|
696
|
+
return intents.map((intent) => ({
|
|
697
|
+
id: intent?.id || 'unknown',
|
|
698
|
+
status: intent?.status || 'pending',
|
|
699
|
+
line: `${intent?.id || 'unknown'}: ${intent?.status || 'pending'} (${intent?.completedStories || 0}/${intent?.storyCount || 0} stories, ${intent?.completedUnits || 0}/${intent?.unitCount || 0} units)`
|
|
700
|
+
}));
|
|
689
701
|
}
|
|
690
702
|
if (effectiveFlow === 'simple') {
|
|
691
|
-
|
|
703
|
+
const specs = Array.isArray(snapshot?.specs) ? snapshot.specs : [];
|
|
704
|
+
return specs.map((spec) => ({
|
|
705
|
+
id: spec?.name || 'unknown',
|
|
706
|
+
status: spec?.state || 'pending',
|
|
707
|
+
line: `${spec?.name || 'unknown'}: ${spec?.state || 'pending'} (${spec?.tasksCompleted || 0}/${spec?.tasksTotal || 0} tasks)`
|
|
708
|
+
}));
|
|
692
709
|
}
|
|
693
|
-
|
|
710
|
+
const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
|
|
711
|
+
return intents.map((intent) => {
|
|
712
|
+
const workItems = Array.isArray(intent?.workItems) ? intent.workItems : [];
|
|
713
|
+
const done = workItems.filter((item) => item.status === 'completed').length;
|
|
714
|
+
return {
|
|
715
|
+
id: intent?.id || 'unknown',
|
|
716
|
+
status: intent?.status || 'pending',
|
|
717
|
+
line: `${intent?.id || 'unknown'}: ${intent?.status || 'pending'} (${done}/${workItems.length} work items)`
|
|
718
|
+
};
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function buildOverviewIntentLines(snapshot, width, flow, filter = 'next') {
|
|
723
|
+
const entries = listOverviewIntentEntries(snapshot, flow);
|
|
724
|
+
const normalizedFilter = filter === 'completed' ? 'completed' : 'next';
|
|
725
|
+
const isNextFilter = normalizedFilter === 'next';
|
|
726
|
+
const nextLabel = isNextFilter ? '[NEXT]' : ' next ';
|
|
727
|
+
const completedLabel = !isNextFilter ? '[COMPLETED]' : ' completed ';
|
|
728
|
+
|
|
729
|
+
const filtered = entries.filter((entry) => {
|
|
730
|
+
if (normalizedFilter === 'completed') {
|
|
731
|
+
return entry.status === 'completed';
|
|
732
|
+
}
|
|
733
|
+
return entry.status !== 'completed';
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
const lines = [{
|
|
737
|
+
text: truncate(`filter ${nextLabel} | ${completedLabel} (←/→ or n/x)`, width),
|
|
738
|
+
color: 'cyan',
|
|
739
|
+
bold: true
|
|
740
|
+
}];
|
|
741
|
+
|
|
742
|
+
if (filtered.length === 0) {
|
|
743
|
+
lines.push({
|
|
744
|
+
text: truncate(
|
|
745
|
+
normalizedFilter === 'completed' ? 'No completed intents yet' : 'No upcoming intents',
|
|
746
|
+
width
|
|
747
|
+
),
|
|
748
|
+
color: 'gray',
|
|
749
|
+
bold: false
|
|
750
|
+
});
|
|
751
|
+
return lines;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
lines.push(...filtered.map((entry) => truncate(entry.line, width)));
|
|
755
|
+
return lines;
|
|
694
756
|
}
|
|
695
757
|
|
|
696
758
|
function buildOverviewStandardsLines(snapshot, width, flow) {
|
|
@@ -730,6 +792,30 @@ function getPanelTitles(flow, snapshot) {
|
|
|
730
792
|
};
|
|
731
793
|
}
|
|
732
794
|
|
|
795
|
+
function getSectionOrderForView(view) {
|
|
796
|
+
if (view === 'overview') {
|
|
797
|
+
return ['intent-status', 'completed-runs', 'standards', 'project'];
|
|
798
|
+
}
|
|
799
|
+
if (view === 'health') {
|
|
800
|
+
return ['stats', 'warnings', 'error-details'];
|
|
801
|
+
}
|
|
802
|
+
return ['current-run', 'run-files', 'pending'];
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function cycleSection(view, currentSectionKey, direction = 1, availableSections = null) {
|
|
806
|
+
const order = Array.isArray(availableSections) && availableSections.length > 0
|
|
807
|
+
? availableSections
|
|
808
|
+
: getSectionOrderForView(view);
|
|
809
|
+
if (order.length === 0) {
|
|
810
|
+
return currentSectionKey;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const currentIndex = order.indexOf(currentSectionKey);
|
|
814
|
+
const safeIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
815
|
+
const nextIndex = (safeIndex + direction + order.length) % order.length;
|
|
816
|
+
return order[nextIndex];
|
|
817
|
+
}
|
|
818
|
+
|
|
733
819
|
function fileExists(filePath) {
|
|
734
820
|
try {
|
|
735
821
|
return fs.statSync(filePath).isFile();
|
|
@@ -956,25 +1042,361 @@ function formatScope(scope) {
|
|
|
956
1042
|
return 'FILE';
|
|
957
1043
|
}
|
|
958
1044
|
|
|
959
|
-
function
|
|
1045
|
+
function getNoPendingMessage(flow) {
|
|
1046
|
+
if (flow === 'aidlc') return 'No queued bolts';
|
|
1047
|
+
if (flow === 'simple') return 'No pending specs';
|
|
1048
|
+
return 'No pending work items';
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function getNoCompletedMessage(flow) {
|
|
1052
|
+
if (flow === 'aidlc') return 'No completed bolts yet';
|
|
1053
|
+
if (flow === 'simple') return 'No completed specs yet';
|
|
1054
|
+
return 'No completed runs yet';
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function toRunFileRows(fileEntries, flow) {
|
|
960
1058
|
if (!Array.isArray(fileEntries) || fileEntries.length === 0) {
|
|
961
|
-
return [
|
|
1059
|
+
return [{
|
|
1060
|
+
kind: 'info',
|
|
1061
|
+
key: 'run-files:empty',
|
|
1062
|
+
label: getNoFileMessage(flow),
|
|
1063
|
+
selectable: false
|
|
1064
|
+
}];
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
return fileEntries.map((file, index) => ({
|
|
1068
|
+
kind: 'file',
|
|
1069
|
+
key: `run-files:${file.path}:${index}`,
|
|
1070
|
+
label: file.label,
|
|
1071
|
+
path: file.path,
|
|
1072
|
+
scope: file.scope || 'file',
|
|
1073
|
+
selectable: true
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function collectAidlcIntentContextFiles(snapshot, intentId) {
|
|
1078
|
+
if (!snapshot || typeof intentId !== 'string' || intentId.trim() === '') {
|
|
1079
|
+
return [];
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
const intentPath = path.join(snapshot.rootPath || '', 'intents', intentId);
|
|
1083
|
+
return [
|
|
1084
|
+
{
|
|
1085
|
+
label: `${intentId}/requirements.md`,
|
|
1086
|
+
path: path.join(intentPath, 'requirements.md'),
|
|
1087
|
+
scope: 'intent'
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
label: `${intentId}/system-context.md`,
|
|
1091
|
+
path: path.join(intentPath, 'system-context.md'),
|
|
1092
|
+
scope: 'intent'
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
label: `${intentId}/units.md`,
|
|
1096
|
+
path: path.join(intentPath, 'units.md'),
|
|
1097
|
+
scope: 'intent'
|
|
1098
|
+
}
|
|
1099
|
+
];
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function filterExistingFiles(files) {
|
|
1103
|
+
return (Array.isArray(files) ? files : []).filter((file) =>
|
|
1104
|
+
file && typeof file.path === 'string' && typeof file.label === 'string' && fileExists(file.path)
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function buildPendingGroups(snapshot, flow) {
|
|
1109
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1110
|
+
|
|
1111
|
+
if (effectiveFlow === 'aidlc') {
|
|
1112
|
+
const pendingBolts = Array.isArray(snapshot?.pendingBolts) ? snapshot.pendingBolts : [];
|
|
1113
|
+
return pendingBolts.map((bolt, index) => {
|
|
1114
|
+
const deps = Array.isArray(bolt?.blockedBy) && bolt.blockedBy.length > 0
|
|
1115
|
+
? ` blocked_by:${bolt.blockedBy.join(',')}`
|
|
1116
|
+
: '';
|
|
1117
|
+
const location = `${bolt?.intent || 'unknown'}/${bolt?.unit || 'unknown'}`;
|
|
1118
|
+
const boltFiles = collectAidlcBoltFiles(bolt);
|
|
1119
|
+
const intentFiles = collectAidlcIntentContextFiles(snapshot, bolt?.intent);
|
|
1120
|
+
return {
|
|
1121
|
+
key: `pending:bolt:${bolt?.id || index}`,
|
|
1122
|
+
label: `${bolt?.id || 'unknown'} (${bolt?.status || 'pending'}) in ${location}${deps}`,
|
|
1123
|
+
files: filterExistingFiles([...boltFiles, ...intentFiles])
|
|
1124
|
+
};
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
if (effectiveFlow === 'simple') {
|
|
1129
|
+
const pendingSpecs = Array.isArray(snapshot?.pendingSpecs) ? snapshot.pendingSpecs : [];
|
|
1130
|
+
return pendingSpecs.map((spec, index) => ({
|
|
1131
|
+
key: `pending:spec:${spec?.name || index}`,
|
|
1132
|
+
label: `${spec?.name || 'unknown'} (${spec?.state || 'pending'}) ${spec?.tasksCompleted || 0}/${spec?.tasksTotal || 0} tasks`,
|
|
1133
|
+
files: filterExistingFiles(collectSimpleSpecFiles(spec))
|
|
1134
|
+
}));
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const pendingItems = Array.isArray(snapshot?.pendingItems) ? snapshot.pendingItems : [];
|
|
1138
|
+
return pendingItems.map((item, index) => {
|
|
1139
|
+
const deps = Array.isArray(item?.dependencies) && item.dependencies.length > 0
|
|
1140
|
+
? ` deps:${item.dependencies.join(',')}`
|
|
1141
|
+
: '';
|
|
1142
|
+
const intentTitle = item?.intentTitle || item?.intentId || 'unknown-intent';
|
|
1143
|
+
const files = [];
|
|
1144
|
+
|
|
1145
|
+
if (item?.filePath) {
|
|
1146
|
+
files.push({
|
|
1147
|
+
label: `${item?.intentId || 'intent'}/${item?.id || 'work-item'}.md`,
|
|
1148
|
+
path: item.filePath,
|
|
1149
|
+
scope: 'upcoming'
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
if (item?.intentId) {
|
|
1153
|
+
files.push({
|
|
1154
|
+
label: `${item.intentId}/brief.md`,
|
|
1155
|
+
path: path.join(snapshot?.rootPath || '', 'intents', item.intentId, 'brief.md'),
|
|
1156
|
+
scope: 'intent'
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
return {
|
|
1161
|
+
key: `pending:item:${item?.intentId || 'intent'}:${item?.id || index}`,
|
|
1162
|
+
label: `${item?.id || 'work-item'} (${item?.mode || 'confirm'}/${item?.complexity || 'medium'}) in ${intentTitle}${deps}`,
|
|
1163
|
+
files: filterExistingFiles(files)
|
|
1164
|
+
};
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function buildCompletedGroups(snapshot, flow) {
|
|
1169
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1170
|
+
|
|
1171
|
+
if (effectiveFlow === 'aidlc') {
|
|
1172
|
+
const completedBolts = Array.isArray(snapshot?.completedBolts) ? snapshot.completedBolts : [];
|
|
1173
|
+
return completedBolts.map((bolt, index) => {
|
|
1174
|
+
const boltFiles = collectAidlcBoltFiles(bolt);
|
|
1175
|
+
const intentFiles = collectAidlcIntentContextFiles(snapshot, bolt?.intent);
|
|
1176
|
+
return {
|
|
1177
|
+
key: `completed:bolt:${bolt?.id || index}`,
|
|
1178
|
+
label: `${bolt?.id || 'unknown'} [${bolt?.type || 'bolt'}] done at ${bolt?.completedAt || 'unknown'}`,
|
|
1179
|
+
files: filterExistingFiles([...boltFiles, ...intentFiles])
|
|
1180
|
+
};
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (effectiveFlow === 'simple') {
|
|
1185
|
+
const completedSpecs = Array.isArray(snapshot?.completedSpecs) ? snapshot.completedSpecs : [];
|
|
1186
|
+
return completedSpecs.map((spec, index) => ({
|
|
1187
|
+
key: `completed:spec:${spec?.name || index}`,
|
|
1188
|
+
label: `${spec?.name || 'unknown'} done at ${spec?.updatedAt || 'unknown'} (${spec?.tasksCompleted || 0}/${spec?.tasksTotal || 0})`,
|
|
1189
|
+
files: filterExistingFiles(collectSimpleSpecFiles(spec))
|
|
1190
|
+
}));
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const groups = [];
|
|
1194
|
+
const completedRuns = Array.isArray(snapshot?.completedRuns) ? snapshot.completedRuns : [];
|
|
1195
|
+
for (let index = 0; index < completedRuns.length; index += 1) {
|
|
1196
|
+
const run = completedRuns[index];
|
|
1197
|
+
const workItems = Array.isArray(run?.workItems) ? run.workItems : [];
|
|
1198
|
+
const completed = workItems.filter((item) => item.status === 'completed').length;
|
|
1199
|
+
groups.push({
|
|
1200
|
+
key: `completed:run:${run?.id || index}`,
|
|
1201
|
+
label: `${run?.id || 'run'} [${run?.scope || 'single'}] ${completed}/${workItems.length} done at ${run?.completedAt || 'unknown'}`,
|
|
1202
|
+
files: filterExistingFiles(collectFireRunFiles(run).map((file) => ({ ...file, scope: 'completed' })))
|
|
1203
|
+
});
|
|
962
1204
|
}
|
|
963
1205
|
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
const
|
|
1206
|
+
const completedIntents = Array.isArray(snapshot?.intents)
|
|
1207
|
+
? snapshot.intents.filter((intent) => intent?.status === 'completed')
|
|
1208
|
+
: [];
|
|
1209
|
+
for (let index = 0; index < completedIntents.length; index += 1) {
|
|
1210
|
+
const intent = completedIntents[index];
|
|
1211
|
+
groups.push({
|
|
1212
|
+
key: `completed:intent:${intent?.id || index}`,
|
|
1213
|
+
label: `intent ${intent?.id || 'unknown'} [completed]`,
|
|
1214
|
+
files: filterExistingFiles([{
|
|
1215
|
+
label: `${intent?.id || 'intent'}/brief.md`,
|
|
1216
|
+
path: path.join(snapshot?.rootPath || '', 'intents', intent?.id || '', 'brief.md'),
|
|
1217
|
+
scope: 'intent'
|
|
1218
|
+
}])
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
return groups;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function toExpandableRows(groups, emptyLabel, expandedGroups) {
|
|
1226
|
+
if (!Array.isArray(groups) || groups.length === 0) {
|
|
1227
|
+
return [{
|
|
1228
|
+
kind: 'info',
|
|
1229
|
+
key: 'section:empty',
|
|
1230
|
+
label: emptyLabel,
|
|
1231
|
+
selectable: false
|
|
1232
|
+
}];
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const rows = [];
|
|
1236
|
+
|
|
1237
|
+
for (const group of groups) {
|
|
1238
|
+
const files = filterExistingFiles(group?.files);
|
|
1239
|
+
const expandable = files.length > 0;
|
|
1240
|
+
const expanded = expandable && Boolean(expandedGroups?.[group.key]);
|
|
1241
|
+
|
|
1242
|
+
rows.push({
|
|
1243
|
+
kind: 'group',
|
|
1244
|
+
key: group.key,
|
|
1245
|
+
label: group.label,
|
|
1246
|
+
expandable,
|
|
1247
|
+
expanded,
|
|
1248
|
+
selectable: true
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
if (expanded) {
|
|
1252
|
+
for (let index = 0; index < files.length; index += 1) {
|
|
1253
|
+
const file = files[index];
|
|
1254
|
+
rows.push({
|
|
1255
|
+
kind: 'file',
|
|
1256
|
+
key: `${group.key}:file:${file.path}:${index}`,
|
|
1257
|
+
label: file.label,
|
|
1258
|
+
path: file.path,
|
|
1259
|
+
scope: file.scope || 'file',
|
|
1260
|
+
selectable: true
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
return rows;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function buildInteractiveRowsLines(rows, selectedIndex, icons, width, isFocusedSection) {
|
|
1270
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
1271
|
+
return [{ text: '', color: undefined, bold: false, selected: false }];
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const clampedIndex = clampIndex(selectedIndex, rows.length);
|
|
1275
|
+
|
|
1276
|
+
return rows.map((row, index) => {
|
|
1277
|
+
const selectable = row?.selectable !== false;
|
|
1278
|
+
const isSelected = selectable && index === clampedIndex;
|
|
1279
|
+
const cursor = isSelected
|
|
1280
|
+
? (isFocusedSection ? (icons.activeFile || '>') : '•')
|
|
1281
|
+
: ' ';
|
|
1282
|
+
|
|
1283
|
+
if (row.kind === 'group') {
|
|
1284
|
+
const marker = row.expandable
|
|
1285
|
+
? (row.expanded ? (icons.groupExpanded || 'v') : (icons.groupCollapsed || '>'))
|
|
1286
|
+
: '-';
|
|
1287
|
+
return {
|
|
1288
|
+
text: truncate(`${cursor} ${marker} ${row.label}`, width),
|
|
1289
|
+
color: isSelected ? (isFocusedSection ? 'green' : 'cyan') : undefined,
|
|
1290
|
+
bold: isSelected,
|
|
1291
|
+
selected: isSelected
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (row.kind === 'file') {
|
|
1296
|
+
const scope = row.scope ? `[${formatScope(row.scope)}] ` : '';
|
|
1297
|
+
return {
|
|
1298
|
+
text: truncate(`${cursor} ${icons.runFile} ${scope}${row.label}`, width),
|
|
1299
|
+
color: isSelected ? (isFocusedSection ? 'green' : 'cyan') : 'gray',
|
|
1300
|
+
bold: isSelected,
|
|
1301
|
+
selected: isSelected
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
|
|
969
1305
|
return {
|
|
970
|
-
text: truncate(
|
|
971
|
-
color:
|
|
972
|
-
bold:
|
|
973
|
-
selected:
|
|
1306
|
+
text: truncate(` ${row.label || ''}`, width),
|
|
1307
|
+
color: 'gray',
|
|
1308
|
+
bold: false,
|
|
1309
|
+
selected: false
|
|
974
1310
|
};
|
|
975
1311
|
});
|
|
976
1312
|
}
|
|
977
1313
|
|
|
1314
|
+
function getSelectedRow(rows, selectedIndex) {
|
|
1315
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1318
|
+
return rows[clampIndex(selectedIndex, rows.length)] || null;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function rowToFileEntry(row) {
|
|
1322
|
+
if (!row || row.kind !== 'file' || typeof row.path !== 'string') {
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
return {
|
|
1326
|
+
label: row.label || path.basename(row.path),
|
|
1327
|
+
path: row.path,
|
|
1328
|
+
scope: row.scope || 'file'
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
function moveRowSelection(rows, currentIndex, direction) {
|
|
1333
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
1334
|
+
return 0;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const clamped = clampIndex(currentIndex, rows.length);
|
|
1338
|
+
const step = direction >= 0 ? 1 : -1;
|
|
1339
|
+
let next = clamped + step;
|
|
1340
|
+
|
|
1341
|
+
while (next >= 0 && next < rows.length) {
|
|
1342
|
+
if (rows[next]?.selectable !== false) {
|
|
1343
|
+
return next;
|
|
1344
|
+
}
|
|
1345
|
+
next += step;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
return clamped;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function openFileWithDefaultApp(filePath) {
|
|
1352
|
+
if (typeof filePath !== 'string' || filePath.trim() === '') {
|
|
1353
|
+
return {
|
|
1354
|
+
ok: false,
|
|
1355
|
+
message: 'No file selected to open.'
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (!fileExists(filePath)) {
|
|
1360
|
+
return {
|
|
1361
|
+
ok: false,
|
|
1362
|
+
message: `File not found: ${filePath}`
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
let command = null;
|
|
1367
|
+
let args = [];
|
|
1368
|
+
|
|
1369
|
+
if (process.platform === 'darwin') {
|
|
1370
|
+
command = 'open';
|
|
1371
|
+
args = [filePath];
|
|
1372
|
+
} else if (process.platform === 'win32') {
|
|
1373
|
+
command = 'cmd';
|
|
1374
|
+
args = ['/c', 'start', '', filePath];
|
|
1375
|
+
} else {
|
|
1376
|
+
command = 'xdg-open';
|
|
1377
|
+
args = [filePath];
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const result = spawnSync(command, args, { stdio: 'ignore' });
|
|
1381
|
+
if (result.error) {
|
|
1382
|
+
return {
|
|
1383
|
+
ok: false,
|
|
1384
|
+
message: `Unable to open file: ${result.error.message}`
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
if (typeof result.status === 'number' && result.status !== 0) {
|
|
1388
|
+
return {
|
|
1389
|
+
ok: false,
|
|
1390
|
+
message: `Open command failed with exit code ${result.status}.`
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
return {
|
|
1395
|
+
ok: true,
|
|
1396
|
+
message: `Opened ${filePath}`
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
|
|
978
1400
|
function colorizeMarkdownLine(line, inCodeBlock) {
|
|
979
1401
|
const text = String(line ?? '');
|
|
980
1402
|
|
|
@@ -1033,7 +1455,9 @@ function colorizeMarkdownLine(line, inCodeBlock) {
|
|
|
1033
1455
|
};
|
|
1034
1456
|
}
|
|
1035
1457
|
|
|
1036
|
-
function buildPreviewLines(fileEntry, width, scrollOffset) {
|
|
1458
|
+
function buildPreviewLines(fileEntry, width, scrollOffset, options = {}) {
|
|
1459
|
+
const fullDocument = options?.fullDocument === true;
|
|
1460
|
+
|
|
1037
1461
|
if (!fileEntry || typeof fileEntry.path !== 'string') {
|
|
1038
1462
|
return [{ text: truncate('No file selected', width), color: 'gray', bold: false }];
|
|
1039
1463
|
}
|
|
@@ -1056,8 +1480,8 @@ function buildPreviewLines(fileEntry, width, scrollOffset) {
|
|
|
1056
1480
|
bold: true
|
|
1057
1481
|
};
|
|
1058
1482
|
|
|
1059
|
-
const cappedLines = rawLines.slice(0, 300);
|
|
1060
|
-
const hiddenLineCount = Math.max(0, rawLines.length - cappedLines.length);
|
|
1483
|
+
const cappedLines = fullDocument ? rawLines : rawLines.slice(0, 300);
|
|
1484
|
+
const hiddenLineCount = fullDocument ? 0 : Math.max(0, rawLines.length - cappedLines.length);
|
|
1061
1485
|
let inCodeBlock = false;
|
|
1062
1486
|
|
|
1063
1487
|
const highlighted = cappedLines.map((rawLine, index) => {
|
|
@@ -1148,23 +1572,31 @@ function createDashboardApp(deps) {
|
|
|
1148
1572
|
maxLines,
|
|
1149
1573
|
borderColor,
|
|
1150
1574
|
marginBottom,
|
|
1151
|
-
dense
|
|
1575
|
+
dense,
|
|
1576
|
+
focused
|
|
1152
1577
|
} = props;
|
|
1153
1578
|
|
|
1154
1579
|
const contentWidth = Math.max(18, width - 4);
|
|
1155
1580
|
const visibleLines = fitLines(lines, maxLines, contentWidth);
|
|
1581
|
+
const panelBorderColor = focused ? 'cyan' : (borderColor || 'gray');
|
|
1582
|
+
const titleColor = focused ? 'black' : 'cyan';
|
|
1583
|
+
const titleBackground = focused ? 'cyan' : undefined;
|
|
1156
1584
|
|
|
1157
1585
|
return React.createElement(
|
|
1158
1586
|
Box,
|
|
1159
1587
|
{
|
|
1160
1588
|
flexDirection: 'column',
|
|
1161
1589
|
borderStyle: dense ? 'single' : 'round',
|
|
1162
|
-
borderColor:
|
|
1590
|
+
borderColor: panelBorderColor,
|
|
1163
1591
|
paddingX: dense ? 0 : 1,
|
|
1164
1592
|
width,
|
|
1165
1593
|
marginBottom: marginBottom || 0
|
|
1166
1594
|
},
|
|
1167
|
-
React.createElement(
|
|
1595
|
+
React.createElement(
|
|
1596
|
+
Text,
|
|
1597
|
+
{ bold: true, color: titleColor, backgroundColor: titleBackground },
|
|
1598
|
+
truncate(title, contentWidth)
|
|
1599
|
+
),
|
|
1168
1600
|
...visibleLines.map((line, index) => React.createElement(
|
|
1169
1601
|
Text,
|
|
1170
1602
|
{
|
|
@@ -1195,8 +1627,8 @@ function createDashboardApp(deps) {
|
|
|
1195
1627
|
{
|
|
1196
1628
|
key: tab.id,
|
|
1197
1629
|
bold: isActive,
|
|
1198
|
-
color: isActive ? '
|
|
1199
|
-
backgroundColor: isActive ? '
|
|
1630
|
+
color: isActive ? 'white' : 'gray',
|
|
1631
|
+
backgroundColor: isActive ? 'blue' : undefined
|
|
1200
1632
|
},
|
|
1201
1633
|
tab.label
|
|
1202
1634
|
);
|
|
@@ -1243,14 +1675,30 @@ function createDashboardApp(deps) {
|
|
|
1243
1675
|
const initialNormalizedError = initialError ? toDashboardError(initialError) : null;
|
|
1244
1676
|
const snapshotHashRef = useRef(safeJsonHash(initialSnapshot || null));
|
|
1245
1677
|
const errorHashRef = useRef(initialNormalizedError ? safeJsonHash(initialNormalizedError) : null);
|
|
1678
|
+
const lastVPressRef = useRef(0);
|
|
1246
1679
|
|
|
1247
1680
|
const [activeFlow, setActiveFlow] = useState(fallbackFlow);
|
|
1248
1681
|
const [snapshot, setSnapshot] = useState(initialSnapshot || null);
|
|
1249
1682
|
const [error, setError] = useState(initialNormalizedError);
|
|
1250
1683
|
const [ui, setUi] = useState(createInitialUIState());
|
|
1251
|
-
const [
|
|
1684
|
+
const [sectionFocus, setSectionFocus] = useState({
|
|
1685
|
+
runs: 'run-files',
|
|
1686
|
+
overview: 'project',
|
|
1687
|
+
health: 'stats'
|
|
1688
|
+
});
|
|
1689
|
+
const [selectionBySection, setSelectionBySection] = useState({
|
|
1690
|
+
'run-files': 0,
|
|
1691
|
+
pending: 0,
|
|
1692
|
+
completed: 0
|
|
1693
|
+
});
|
|
1694
|
+
const [expandedGroups, setExpandedGroups] = useState({});
|
|
1695
|
+
const [previewTarget, setPreviewTarget] = useState(null);
|
|
1696
|
+
const [overviewIntentFilter, setOverviewIntentFilter] = useState('next');
|
|
1252
1697
|
const [previewOpen, setPreviewOpen] = useState(false);
|
|
1698
|
+
const [paneFocus, setPaneFocus] = useState('main');
|
|
1699
|
+
const [overlayPreviewOpen, setOverlayPreviewOpen] = useState(false);
|
|
1253
1700
|
const [previewScroll, setPreviewScroll] = useState(0);
|
|
1701
|
+
const [statusLine, setStatusLine] = useState('');
|
|
1254
1702
|
const [lastRefreshAt, setLastRefreshAt] = useState(new Date().toISOString());
|
|
1255
1703
|
const [watchStatus, setWatchStatus] = useState(watchEnabled ? 'watching' : 'off');
|
|
1256
1704
|
const [terminalSize, setTerminalSize] = useState(() => ({
|
|
@@ -1273,9 +1721,42 @@ function createDashboardApp(deps) {
|
|
|
1273
1721
|
}
|
|
1274
1722
|
};
|
|
1275
1723
|
}, [parseSnapshotForFlow, parseSnapshot]);
|
|
1724
|
+
|
|
1725
|
+
const previewVisibleRows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
|
|
1726
|
+
const showErrorPanelForSections = Boolean(error) && previewVisibleRows >= 18;
|
|
1727
|
+
const getAvailableSections = useCallback((viewId) => {
|
|
1728
|
+
const base = getSectionOrderForView(viewId);
|
|
1729
|
+
return base.filter((sectionKey) => sectionKey !== 'error-details' || showErrorPanelForSections);
|
|
1730
|
+
}, [showErrorPanelForSections]);
|
|
1731
|
+
|
|
1276
1732
|
const runFileEntries = getRunFileEntries(snapshot, activeFlow);
|
|
1277
|
-
const
|
|
1278
|
-
const
|
|
1733
|
+
const runFileRows = toRunFileRows(runFileEntries, activeFlow);
|
|
1734
|
+
const pendingRows = toExpandableRows(
|
|
1735
|
+
buildPendingGroups(snapshot, activeFlow),
|
|
1736
|
+
getNoPendingMessage(getEffectiveFlow(activeFlow, snapshot)),
|
|
1737
|
+
expandedGroups
|
|
1738
|
+
);
|
|
1739
|
+
const completedRows = toExpandableRows(
|
|
1740
|
+
buildCompletedGroups(snapshot, activeFlow),
|
|
1741
|
+
getNoCompletedMessage(getEffectiveFlow(activeFlow, snapshot)),
|
|
1742
|
+
expandedGroups
|
|
1743
|
+
);
|
|
1744
|
+
|
|
1745
|
+
const rowsBySection = {
|
|
1746
|
+
'run-files': runFileRows,
|
|
1747
|
+
pending: pendingRows,
|
|
1748
|
+
completed: completedRows
|
|
1749
|
+
};
|
|
1750
|
+
|
|
1751
|
+
const currentSectionOrder = getAvailableSections(ui.view);
|
|
1752
|
+
const focusedSection = currentSectionOrder.includes(sectionFocus[ui.view])
|
|
1753
|
+
? sectionFocus[ui.view]
|
|
1754
|
+
: (currentSectionOrder[0] || 'current-run');
|
|
1755
|
+
|
|
1756
|
+
const focusedRows = rowsBySection[focusedSection] || [];
|
|
1757
|
+
const focusedIndex = selectionBySection[focusedSection] || 0;
|
|
1758
|
+
const selectedFocusedRow = getSelectedRow(focusedRows, focusedIndex);
|
|
1759
|
+
const selectedFocusedFile = rowToFileEntry(selectedFocusedRow);
|
|
1279
1760
|
|
|
1280
1761
|
const refresh = useCallback(async () => {
|
|
1281
1762
|
const now = new Date().toISOString();
|
|
@@ -1337,41 +1818,6 @@ function createDashboardApp(deps) {
|
|
|
1337
1818
|
return;
|
|
1338
1819
|
}
|
|
1339
1820
|
|
|
1340
|
-
if (input === 'v' && ui.view === 'runs') {
|
|
1341
|
-
if (selectedFile) {
|
|
1342
|
-
setPreviewOpen((previous) => !previous);
|
|
1343
|
-
setPreviewScroll(0);
|
|
1344
|
-
}
|
|
1345
|
-
return;
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
if (key.escape && previewOpen) {
|
|
1349
|
-
setPreviewOpen(false);
|
|
1350
|
-
setPreviewScroll(0);
|
|
1351
|
-
return;
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
if (ui.view === 'runs' && (key.upArrow || key.downArrow || input === 'j' || input === 'k')) {
|
|
1355
|
-
const moveDown = key.downArrow || input === 'j';
|
|
1356
|
-
const moveUp = key.upArrow || input === 'k';
|
|
1357
|
-
|
|
1358
|
-
if (previewOpen) {
|
|
1359
|
-
if (moveDown) {
|
|
1360
|
-
setPreviewScroll((previous) => previous + 1);
|
|
1361
|
-
} else if (moveUp) {
|
|
1362
|
-
setPreviewScroll((previous) => Math.max(0, previous - 1));
|
|
1363
|
-
}
|
|
1364
|
-
return;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
if (moveDown) {
|
|
1368
|
-
setSelectedFileIndex((previous) => clampIndex(previous + 1, runFileEntries.length));
|
|
1369
|
-
} else if (moveUp) {
|
|
1370
|
-
setSelectedFileIndex((previous) => clampIndex(previous - 1, runFileEntries.length));
|
|
1371
|
-
}
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
1821
|
if (input === 'h' || input === '?') {
|
|
1376
1822
|
setUi((previous) => ({ ...previous, showHelp: !previous.showHelp }));
|
|
1377
1823
|
return;
|
|
@@ -1379,31 +1825,19 @@ function createDashboardApp(deps) {
|
|
|
1379
1825
|
|
|
1380
1826
|
if (input === '1') {
|
|
1381
1827
|
setUi((previous) => ({ ...previous, view: 'runs' }));
|
|
1828
|
+
setPaneFocus('main');
|
|
1382
1829
|
return;
|
|
1383
1830
|
}
|
|
1384
1831
|
|
|
1385
1832
|
if (input === '2') {
|
|
1386
1833
|
setUi((previous) => ({ ...previous, view: 'overview' }));
|
|
1834
|
+
setPaneFocus('main');
|
|
1387
1835
|
return;
|
|
1388
1836
|
}
|
|
1389
1837
|
|
|
1390
1838
|
if (input === '3') {
|
|
1391
1839
|
setUi((previous) => ({ ...previous, view: 'health' }));
|
|
1392
|
-
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
if (key.tab) {
|
|
1396
|
-
setUi((previous) => ({ ...previous, view: cycleView(previous.view) }));
|
|
1397
|
-
return;
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
if (key.rightArrow) {
|
|
1401
|
-
setUi((previous) => ({ ...previous, view: cycleView(previous.view) }));
|
|
1402
|
-
return;
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
if (key.leftArrow) {
|
|
1406
|
-
setUi((previous) => ({ ...previous, view: cycleViewBackward(previous.view) }));
|
|
1840
|
+
setPaneFocus('main');
|
|
1407
1841
|
return;
|
|
1408
1842
|
}
|
|
1409
1843
|
|
|
@@ -1419,8 +1853,23 @@ function createDashboardApp(deps) {
|
|
|
1419
1853
|
: 0;
|
|
1420
1854
|
return availableFlowIds[nextIndex];
|
|
1421
1855
|
});
|
|
1856
|
+
setSelectionBySection({
|
|
1857
|
+
'run-files': 0,
|
|
1858
|
+
pending: 0,
|
|
1859
|
+
completed: 0
|
|
1860
|
+
});
|
|
1861
|
+
setSectionFocus({
|
|
1862
|
+
runs: 'run-files',
|
|
1863
|
+
overview: 'project',
|
|
1864
|
+
health: 'stats'
|
|
1865
|
+
});
|
|
1866
|
+
setOverviewIntentFilter('next');
|
|
1867
|
+
setExpandedGroups({});
|
|
1868
|
+
setPreviewTarget(null);
|
|
1422
1869
|
setPreviewOpen(false);
|
|
1870
|
+
setOverlayPreviewOpen(false);
|
|
1423
1871
|
setPreviewScroll(0);
|
|
1872
|
+
setPaneFocus('main');
|
|
1424
1873
|
return;
|
|
1425
1874
|
}
|
|
1426
1875
|
|
|
@@ -1436,8 +1885,225 @@ function createDashboardApp(deps) {
|
|
|
1436
1885
|
: 0;
|
|
1437
1886
|
return availableFlowIds[nextIndex];
|
|
1438
1887
|
});
|
|
1888
|
+
setSelectionBySection({
|
|
1889
|
+
'run-files': 0,
|
|
1890
|
+
pending: 0,
|
|
1891
|
+
completed: 0
|
|
1892
|
+
});
|
|
1893
|
+
setSectionFocus({
|
|
1894
|
+
runs: 'run-files',
|
|
1895
|
+
overview: 'project',
|
|
1896
|
+
health: 'stats'
|
|
1897
|
+
});
|
|
1898
|
+
setOverviewIntentFilter('next');
|
|
1899
|
+
setExpandedGroups({});
|
|
1900
|
+
setPreviewTarget(null);
|
|
1901
|
+
setPreviewOpen(false);
|
|
1902
|
+
setOverlayPreviewOpen(false);
|
|
1903
|
+
setPreviewScroll(0);
|
|
1904
|
+
setPaneFocus('main');
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
const availableSections = getAvailableSections(ui.view);
|
|
1909
|
+
const activeSection = availableSections.includes(sectionFocus[ui.view])
|
|
1910
|
+
? sectionFocus[ui.view]
|
|
1911
|
+
: (availableSections[0] || 'current-run');
|
|
1912
|
+
|
|
1913
|
+
if (key.tab && ui.view === 'runs' && previewOpen) {
|
|
1914
|
+
setPaneFocus((previous) => (previous === 'main' ? 'preview' : 'main'));
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
if (ui.view === 'overview' && activeSection === 'intent-status') {
|
|
1919
|
+
if (input === 'n') {
|
|
1920
|
+
setOverviewIntentFilter('next');
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
if (input === 'x') {
|
|
1924
|
+
setOverviewIntentFilter('completed');
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
if (key.rightArrow || key.leftArrow) {
|
|
1928
|
+
setOverviewIntentFilter((previous) => (previous === 'completed' ? 'next' : 'completed'));
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
if (input === 'g' || key.rightArrow) {
|
|
1934
|
+
setSectionFocus((previous) => ({
|
|
1935
|
+
...previous,
|
|
1936
|
+
[ui.view]: cycleSection(ui.view, activeSection, 1, availableSections)
|
|
1937
|
+
}));
|
|
1938
|
+
setPaneFocus('main');
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
if (input === 'G' || key.leftArrow) {
|
|
1943
|
+
setSectionFocus((previous) => ({
|
|
1944
|
+
...previous,
|
|
1945
|
+
[ui.view]: cycleSection(ui.view, activeSection, -1, availableSections)
|
|
1946
|
+
}));
|
|
1947
|
+
setPaneFocus('main');
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
if (ui.view === 'runs') {
|
|
1952
|
+
if (input === 'a') {
|
|
1953
|
+
setSectionFocus((previous) => ({ ...previous, runs: 'current-run' }));
|
|
1954
|
+
setPaneFocus('main');
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
if (input === 'f') {
|
|
1958
|
+
setSectionFocus((previous) => ({ ...previous, runs: 'run-files' }));
|
|
1959
|
+
setPaneFocus('main');
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
if (input === 'p') {
|
|
1963
|
+
setSectionFocus((previous) => ({ ...previous, runs: 'pending' }));
|
|
1964
|
+
setPaneFocus('main');
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
} else if (ui.view === 'overview') {
|
|
1968
|
+
if (input === 'p') {
|
|
1969
|
+
setSectionFocus((previous) => ({ ...previous, overview: 'project' }));
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
if (input === 'i') {
|
|
1973
|
+
setSectionFocus((previous) => ({ ...previous, overview: 'intent-status' }));
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
if (input === 's') {
|
|
1977
|
+
setSectionFocus((previous) => ({ ...previous, overview: 'standards' }));
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
if (input === 'c') {
|
|
1981
|
+
setSectionFocus((previous) => ({ ...previous, overview: 'completed-runs' }));
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
} else if (ui.view === 'health') {
|
|
1985
|
+
if (input === 't') {
|
|
1986
|
+
setSectionFocus((previous) => ({ ...previous, health: 'stats' }));
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
if (input === 'w') {
|
|
1990
|
+
setSectionFocus((previous) => ({ ...previous, health: 'warnings' }));
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
if (input === 'e' && showErrorPanelForSections) {
|
|
1994
|
+
setSectionFocus((previous) => ({ ...previous, health: 'error-details' }));
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
if (key.escape) {
|
|
2000
|
+
if (overlayPreviewOpen) {
|
|
2001
|
+
setOverlayPreviewOpen(false);
|
|
2002
|
+
setPaneFocus('preview');
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
if (previewOpen) {
|
|
2006
|
+
setPreviewOpen(false);
|
|
2007
|
+
setPreviewScroll(0);
|
|
2008
|
+
setPaneFocus('main');
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
if (ui.view === 'runs' && (key.upArrow || key.downArrow || input === 'j' || input === 'k')) {
|
|
2014
|
+
const moveDown = key.downArrow || input === 'j';
|
|
2015
|
+
const moveUp = key.upArrow || input === 'k';
|
|
2016
|
+
|
|
2017
|
+
if (overlayPreviewOpen || (previewOpen && paneFocus === 'preview')) {
|
|
2018
|
+
if (moveDown) {
|
|
2019
|
+
setPreviewScroll((previous) => previous + 1);
|
|
2020
|
+
} else if (moveUp) {
|
|
2021
|
+
setPreviewScroll((previous) => Math.max(0, previous - 1));
|
|
2022
|
+
}
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
const targetSection = activeSection === 'current-run' ? 'run-files' : activeSection;
|
|
2027
|
+
if (targetSection !== activeSection) {
|
|
2028
|
+
setSectionFocus((previous) => ({ ...previous, runs: targetSection }));
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
const targetRows = rowsBySection[targetSection] || [];
|
|
2032
|
+
if (targetRows.length === 0) {
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
const currentIndex = selectionBySection[targetSection] || 0;
|
|
2037
|
+
const nextIndex = moveDown
|
|
2038
|
+
? moveRowSelection(targetRows, currentIndex, 1)
|
|
2039
|
+
: moveRowSelection(targetRows, currentIndex, -1);
|
|
2040
|
+
|
|
2041
|
+
setSelectionBySection((previous) => ({
|
|
2042
|
+
...previous,
|
|
2043
|
+
[targetSection]: nextIndex
|
|
2044
|
+
}));
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
if (ui.view === 'runs' && (key.return || key.enter)) {
|
|
2049
|
+
if (activeSection === 'pending' || activeSection === 'completed') {
|
|
2050
|
+
const rowsForSection = rowsBySection[activeSection] || [];
|
|
2051
|
+
const selectedRow = getSelectedRow(rowsForSection, selectionBySection[activeSection] || 0);
|
|
2052
|
+
if (selectedRow?.kind === 'group' && selectedRow.expandable) {
|
|
2053
|
+
setExpandedGroups((previous) => ({
|
|
2054
|
+
...previous,
|
|
2055
|
+
[selectedRow.key]: !previous[selectedRow.key]
|
|
2056
|
+
}));
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
if (input === 'v' && ui.view === 'runs') {
|
|
2063
|
+
const target = selectedFocusedFile || previewTarget;
|
|
2064
|
+
if (!target) {
|
|
2065
|
+
setStatusLine('Select a file row first (run files, pending, or completed).');
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
const now = Date.now();
|
|
2070
|
+
const isDoublePress = (now - lastVPressRef.current) <= 320;
|
|
2071
|
+
lastVPressRef.current = now;
|
|
2072
|
+
|
|
2073
|
+
if (isDoublePress) {
|
|
2074
|
+
setPreviewTarget(target);
|
|
2075
|
+
setPreviewOpen(true);
|
|
2076
|
+
setOverlayPreviewOpen(true);
|
|
2077
|
+
setPreviewScroll(0);
|
|
2078
|
+
setPaneFocus('preview');
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
if (!previewOpen) {
|
|
2083
|
+
setPreviewTarget(target);
|
|
2084
|
+
setPreviewOpen(true);
|
|
2085
|
+
setOverlayPreviewOpen(false);
|
|
2086
|
+
setPreviewScroll(0);
|
|
2087
|
+
setPaneFocus('main');
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
if (overlayPreviewOpen) {
|
|
2092
|
+
setOverlayPreviewOpen(false);
|
|
2093
|
+
setPaneFocus('preview');
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
1439
2097
|
setPreviewOpen(false);
|
|
1440
2098
|
setPreviewScroll(0);
|
|
2099
|
+
setPaneFocus('main');
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
if (input === 'o' && ui.view === 'runs') {
|
|
2104
|
+
const target = selectedFocusedFile || previewTarget;
|
|
2105
|
+
const result = openFileWithDefaultApp(target?.path);
|
|
2106
|
+
setStatusLine(result.message);
|
|
1441
2107
|
}
|
|
1442
2108
|
});
|
|
1443
2109
|
|
|
@@ -1446,20 +2112,51 @@ function createDashboardApp(deps) {
|
|
|
1446
2112
|
}, [refresh]);
|
|
1447
2113
|
|
|
1448
2114
|
useEffect(() => {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
2115
|
+
setSelectionBySection((previous) => ({
|
|
2116
|
+
...previous,
|
|
2117
|
+
'run-files': clampIndex(previous['run-files'] || 0, runFileRows.length),
|
|
2118
|
+
pending: clampIndex(previous.pending || 0, pendingRows.length),
|
|
2119
|
+
completed: clampIndex(previous.completed || 0, completedRows.length)
|
|
2120
|
+
}));
|
|
2121
|
+
}, [activeFlow, runFileRows.length, pendingRows.length, completedRows.length, snapshot?.generatedAt]);
|
|
1455
2122
|
|
|
1456
2123
|
useEffect(() => {
|
|
1457
2124
|
if (ui.view !== 'runs') {
|
|
1458
2125
|
setPreviewOpen(false);
|
|
2126
|
+
setOverlayPreviewOpen(false);
|
|
1459
2127
|
setPreviewScroll(0);
|
|
2128
|
+
setPaneFocus('main');
|
|
1460
2129
|
}
|
|
1461
2130
|
}, [ui.view]);
|
|
1462
2131
|
|
|
2132
|
+
useEffect(() => {
|
|
2133
|
+
if (!previewOpen || overlayPreviewOpen || paneFocus !== 'main') {
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
if (!selectedFocusedFile?.path) {
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
if (previewTarget?.path === selectedFocusedFile.path) {
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
setPreviewTarget(selectedFocusedFile);
|
|
2143
|
+
setPreviewScroll(0);
|
|
2144
|
+
}, [previewOpen, overlayPreviewOpen, paneFocus, selectedFocusedFile?.path, previewTarget?.path]);
|
|
2145
|
+
|
|
2146
|
+
useEffect(() => {
|
|
2147
|
+
if (statusLine === '') {
|
|
2148
|
+
return undefined;
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
const timeout = setTimeout(() => {
|
|
2152
|
+
setStatusLine('');
|
|
2153
|
+
}, 3500);
|
|
2154
|
+
|
|
2155
|
+
return () => {
|
|
2156
|
+
clearTimeout(timeout);
|
|
2157
|
+
};
|
|
2158
|
+
}, [statusLine]);
|
|
2159
|
+
|
|
1463
2160
|
useEffect(() => {
|
|
1464
2161
|
if (!stdout || typeof stdout.on !== 'function') {
|
|
1465
2162
|
setTerminalSize({
|
|
@@ -1529,12 +2226,20 @@ function createDashboardApp(deps) {
|
|
|
1529
2226
|
};
|
|
1530
2227
|
}, [watchEnabled, refreshMs, refresh, rootPath, workspacePath, resolveRootPathForFlow, activeFlow]);
|
|
1531
2228
|
|
|
2229
|
+
useEffect(() => {
|
|
2230
|
+
if (!stdout || typeof stdout.write !== 'function') {
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
if (stdout.isTTY === false) {
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
stdout.write('\u001B[2J\u001B[3J\u001B[H');
|
|
2237
|
+
}, [stdout]);
|
|
2238
|
+
|
|
1532
2239
|
const cols = Number.isFinite(terminalSize.columns) ? terminalSize.columns : (process.stdout.columns || 120);
|
|
1533
2240
|
const rows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
|
|
1534
2241
|
|
|
1535
2242
|
const fullWidth = Math.max(40, cols - 1);
|
|
1536
|
-
const compactWidth = Math.max(18, fullWidth - 4);
|
|
1537
|
-
|
|
1538
2243
|
const showHelpLine = ui.showHelp && rows >= 14;
|
|
1539
2244
|
const showErrorPanel = Boolean(error) && rows >= 18;
|
|
1540
2245
|
const showErrorInline = Boolean(error) && !showErrorPanel;
|
|
@@ -1544,29 +2249,72 @@ function createDashboardApp(deps) {
|
|
|
1544
2249
|
const contentRowsBudget = Math.max(4, rows - reservedRows);
|
|
1545
2250
|
const ultraCompact = rows <= 14;
|
|
1546
2251
|
const panelTitles = getPanelTitles(activeFlow, snapshot);
|
|
1547
|
-
const
|
|
1548
|
-
const
|
|
2252
|
+
const splitPreviewLayout = ui.view === 'runs' && previewOpen && !overlayPreviewOpen && cols >= 110 && rows >= 16;
|
|
2253
|
+
const mainPaneWidth = splitPreviewLayout
|
|
2254
|
+
? Math.max(34, Math.floor((fullWidth - 1) * 0.52))
|
|
2255
|
+
: fullWidth;
|
|
2256
|
+
const previewPaneWidth = splitPreviewLayout
|
|
2257
|
+
? Math.max(30, fullWidth - mainPaneWidth - 1)
|
|
2258
|
+
: fullWidth;
|
|
2259
|
+
const mainCompactWidth = Math.max(18, mainPaneWidth - 4);
|
|
2260
|
+
const previewCompactWidth = Math.max(18, previewPaneWidth - 4);
|
|
2261
|
+
|
|
2262
|
+
const runFileLines = buildInteractiveRowsLines(
|
|
2263
|
+
runFileRows,
|
|
2264
|
+
selectionBySection['run-files'] || 0,
|
|
2265
|
+
icons,
|
|
2266
|
+
mainCompactWidth,
|
|
2267
|
+
ui.view === 'runs' && focusedSection === 'run-files' && paneFocus === 'main'
|
|
2268
|
+
);
|
|
2269
|
+
const pendingLines = buildInteractiveRowsLines(
|
|
2270
|
+
pendingRows,
|
|
2271
|
+
selectionBySection.pending || 0,
|
|
2272
|
+
icons,
|
|
2273
|
+
mainCompactWidth,
|
|
2274
|
+
ui.view === 'runs' && focusedSection === 'pending' && paneFocus === 'main'
|
|
2275
|
+
);
|
|
2276
|
+
const effectivePreviewTarget = previewTarget || selectedFocusedFile;
|
|
2277
|
+
const previewLines = previewOpen
|
|
2278
|
+
? buildPreviewLines(effectivePreviewTarget, previewCompactWidth, previewScroll, {
|
|
2279
|
+
fullDocument: overlayPreviewOpen
|
|
2280
|
+
})
|
|
2281
|
+
: [];
|
|
1549
2282
|
|
|
1550
2283
|
let panelCandidates;
|
|
1551
|
-
if (ui.view === '
|
|
2284
|
+
if (ui.view === 'runs' && previewOpen && overlayPreviewOpen) {
|
|
1552
2285
|
panelCandidates = [
|
|
1553
2286
|
{
|
|
1554
|
-
key: '
|
|
1555
|
-
title:
|
|
1556
|
-
lines:
|
|
1557
|
-
borderColor: '
|
|
1558
|
-
}
|
|
2287
|
+
key: 'preview-overlay',
|
|
2288
|
+
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
2289
|
+
lines: previewLines,
|
|
2290
|
+
borderColor: 'magenta'
|
|
2291
|
+
}
|
|
2292
|
+
];
|
|
2293
|
+
} else if (ui.view === 'overview') {
|
|
2294
|
+
panelCandidates = [
|
|
1559
2295
|
{
|
|
1560
2296
|
key: 'intent-status',
|
|
1561
|
-
title: '
|
|
1562
|
-
lines: buildOverviewIntentLines(snapshot,
|
|
2297
|
+
title: 'Intents',
|
|
2298
|
+
lines: buildOverviewIntentLines(snapshot, mainCompactWidth, activeFlow, overviewIntentFilter),
|
|
1563
2299
|
borderColor: 'yellow'
|
|
1564
2300
|
},
|
|
2301
|
+
{
|
|
2302
|
+
key: 'completed-runs',
|
|
2303
|
+
title: panelTitles.completed,
|
|
2304
|
+
lines: buildCompletedLines(snapshot, mainCompactWidth, activeFlow),
|
|
2305
|
+
borderColor: 'blue'
|
|
2306
|
+
},
|
|
1565
2307
|
{
|
|
1566
2308
|
key: 'standards',
|
|
1567
2309
|
title: 'Standards',
|
|
1568
|
-
lines: buildOverviewStandardsLines(snapshot,
|
|
2310
|
+
lines: buildOverviewStandardsLines(snapshot, mainCompactWidth, activeFlow),
|
|
1569
2311
|
borderColor: 'blue'
|
|
2312
|
+
},
|
|
2313
|
+
{
|
|
2314
|
+
key: 'project',
|
|
2315
|
+
title: 'Project + Workspace',
|
|
2316
|
+
lines: buildOverviewProjectLines(snapshot, mainCompactWidth, activeFlow),
|
|
2317
|
+
borderColor: 'green'
|
|
1570
2318
|
}
|
|
1571
2319
|
];
|
|
1572
2320
|
} else if (ui.view === 'health') {
|
|
@@ -1574,13 +2322,13 @@ function createDashboardApp(deps) {
|
|
|
1574
2322
|
{
|
|
1575
2323
|
key: 'stats',
|
|
1576
2324
|
title: 'Stats',
|
|
1577
|
-
lines: buildStatsLines(snapshot,
|
|
2325
|
+
lines: buildStatsLines(snapshot, mainCompactWidth, activeFlow),
|
|
1578
2326
|
borderColor: 'magenta'
|
|
1579
2327
|
},
|
|
1580
2328
|
{
|
|
1581
2329
|
key: 'warnings',
|
|
1582
2330
|
title: 'Warnings',
|
|
1583
|
-
lines: buildWarningsLines(snapshot,
|
|
2331
|
+
lines: buildWarningsLines(snapshot, mainCompactWidth),
|
|
1584
2332
|
borderColor: 'red'
|
|
1585
2333
|
}
|
|
1586
2334
|
];
|
|
@@ -1589,16 +2337,17 @@ function createDashboardApp(deps) {
|
|
|
1589
2337
|
panelCandidates.push({
|
|
1590
2338
|
key: 'error-details',
|
|
1591
2339
|
title: 'Error Details',
|
|
1592
|
-
lines: buildErrorLines(error,
|
|
2340
|
+
lines: buildErrorLines(error, mainCompactWidth),
|
|
1593
2341
|
borderColor: 'red'
|
|
1594
2342
|
});
|
|
1595
2343
|
}
|
|
1596
2344
|
} else {
|
|
2345
|
+
const includeInlinePreviewPanel = previewOpen && !splitPreviewLayout;
|
|
1597
2346
|
panelCandidates = [
|
|
1598
2347
|
{
|
|
1599
2348
|
key: 'current-run',
|
|
1600
2349
|
title: panelTitles.current,
|
|
1601
|
-
lines: buildCurrentRunLines(snapshot,
|
|
2350
|
+
lines: buildCurrentRunLines(snapshot, mainCompactWidth, activeFlow),
|
|
1602
2351
|
borderColor: 'green'
|
|
1603
2352
|
},
|
|
1604
2353
|
{
|
|
@@ -1607,10 +2356,10 @@ function createDashboardApp(deps) {
|
|
|
1607
2356
|
lines: runFileLines,
|
|
1608
2357
|
borderColor: 'yellow'
|
|
1609
2358
|
},
|
|
1610
|
-
|
|
2359
|
+
includeInlinePreviewPanel
|
|
1611
2360
|
? {
|
|
1612
2361
|
key: 'preview',
|
|
1613
|
-
title: `Preview: ${
|
|
2362
|
+
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
1614
2363
|
lines: previewLines,
|
|
1615
2364
|
borderColor: 'magenta'
|
|
1616
2365
|
}
|
|
@@ -1618,19 +2367,13 @@ function createDashboardApp(deps) {
|
|
|
1618
2367
|
{
|
|
1619
2368
|
key: 'pending',
|
|
1620
2369
|
title: panelTitles.pending,
|
|
1621
|
-
lines:
|
|
2370
|
+
lines: pendingLines,
|
|
1622
2371
|
borderColor: 'yellow'
|
|
1623
|
-
},
|
|
1624
|
-
{
|
|
1625
|
-
key: 'completed',
|
|
1626
|
-
title: panelTitles.completed,
|
|
1627
|
-
lines: buildCompletedLines(snapshot, compactWidth, activeFlow),
|
|
1628
|
-
borderColor: 'blue'
|
|
1629
2372
|
}
|
|
1630
2373
|
];
|
|
1631
2374
|
}
|
|
1632
2375
|
|
|
1633
|
-
if (ultraCompact) {
|
|
2376
|
+
if (ultraCompact && !splitPreviewLayout) {
|
|
1634
2377
|
if (previewOpen) {
|
|
1635
2378
|
panelCandidates = panelCandidates.filter((panel) => panel && (panel.key === 'current-run' || panel.key === 'preview'));
|
|
1636
2379
|
} else {
|
|
@@ -1640,8 +2383,83 @@ function createDashboardApp(deps) {
|
|
|
1640
2383
|
|
|
1641
2384
|
const panels = allocateSingleColumnPanels(panelCandidates, contentRowsBudget);
|
|
1642
2385
|
const flowSwitchHint = availableFlowIds.length > 1 ? ' | [ or ] switch flow' : '';
|
|
1643
|
-
const
|
|
1644
|
-
|
|
2386
|
+
const sectionHint = ui.view === 'runs'
|
|
2387
|
+
? ' | a active | f files | p pending'
|
|
2388
|
+
: (ui.view === 'overview' ? ' | i intents | c completed | s standards | p project | n/x intent filter' : ' | t stats | w warnings | e errors');
|
|
2389
|
+
const previewHint = ui.view === 'runs'
|
|
2390
|
+
? (previewOpen
|
|
2391
|
+
? ` | tab ${paneFocus === 'preview' ? 'main' : 'preview'} | ↑/↓ ${paneFocus === 'preview' ? 'scroll' : 'navigate'} | v close | vv fullscreen`
|
|
2392
|
+
: ' | ↑/↓ navigate | enter expand | v preview | vv fullscreen | o open')
|
|
2393
|
+
: '';
|
|
2394
|
+
const helpText = `q quit | r refresh | h/? help | 1 runs | 2 overview | 3 health | g/G section${sectionHint}${previewHint}${flowSwitchHint}`;
|
|
2395
|
+
|
|
2396
|
+
const renderPanel = (panel, index, width, isFocused) => React.createElement(SectionPanel, {
|
|
2397
|
+
key: panel.key,
|
|
2398
|
+
title: panel.title,
|
|
2399
|
+
lines: panel.lines,
|
|
2400
|
+
width,
|
|
2401
|
+
maxLines: panel.maxLines,
|
|
2402
|
+
borderColor: panel.borderColor,
|
|
2403
|
+
marginBottom: densePanels ? 0 : (index === panels.length - 1 ? 0 : 1),
|
|
2404
|
+
dense: densePanels,
|
|
2405
|
+
focused: isFocused
|
|
2406
|
+
});
|
|
2407
|
+
|
|
2408
|
+
let contentNode;
|
|
2409
|
+
if (splitPreviewLayout && ui.view === 'runs' && !overlayPreviewOpen) {
|
|
2410
|
+
const previewPanel = {
|
|
2411
|
+
key: 'preview-split',
|
|
2412
|
+
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
2413
|
+
lines: previewLines,
|
|
2414
|
+
borderColor: 'magenta',
|
|
2415
|
+
maxLines: Math.max(4, contentRowsBudget)
|
|
2416
|
+
};
|
|
2417
|
+
|
|
2418
|
+
contentNode = React.createElement(
|
|
2419
|
+
Box,
|
|
2420
|
+
{ width: fullWidth, flexDirection: 'row' },
|
|
2421
|
+
React.createElement(
|
|
2422
|
+
Box,
|
|
2423
|
+
{ width: mainPaneWidth, flexDirection: 'column' },
|
|
2424
|
+
...panels.map((panel, index) => React.createElement(SectionPanel, {
|
|
2425
|
+
key: panel.key,
|
|
2426
|
+
title: panel.title,
|
|
2427
|
+
lines: panel.lines,
|
|
2428
|
+
width: mainPaneWidth,
|
|
2429
|
+
maxLines: panel.maxLines,
|
|
2430
|
+
borderColor: panel.borderColor,
|
|
2431
|
+
marginBottom: densePanels ? 0 : (index === panels.length - 1 ? 0 : 1),
|
|
2432
|
+
dense: densePanels,
|
|
2433
|
+
focused: paneFocus === 'main' && panel.key === focusedSection
|
|
2434
|
+
}))
|
|
2435
|
+
),
|
|
2436
|
+
React.createElement(Box, { width: 1 }, React.createElement(Text, null, ' ')),
|
|
2437
|
+
React.createElement(
|
|
2438
|
+
Box,
|
|
2439
|
+
{ width: previewPaneWidth, flexDirection: 'column' },
|
|
2440
|
+
React.createElement(SectionPanel, {
|
|
2441
|
+
key: previewPanel.key,
|
|
2442
|
+
title: previewPanel.title,
|
|
2443
|
+
lines: previewPanel.lines,
|
|
2444
|
+
width: previewPaneWidth,
|
|
2445
|
+
maxLines: previewPanel.maxLines,
|
|
2446
|
+
borderColor: previewPanel.borderColor,
|
|
2447
|
+
marginBottom: 0,
|
|
2448
|
+
dense: densePanels,
|
|
2449
|
+
focused: paneFocus === 'preview'
|
|
2450
|
+
})
|
|
2451
|
+
)
|
|
2452
|
+
);
|
|
2453
|
+
} else {
|
|
2454
|
+
contentNode = panels.map((panel, index) => renderPanel(
|
|
2455
|
+
panel,
|
|
2456
|
+
index,
|
|
2457
|
+
fullWidth,
|
|
2458
|
+
(panel.key === 'preview' || panel.key === 'preview-overlay')
|
|
2459
|
+
? paneFocus === 'preview'
|
|
2460
|
+
: (paneFocus === 'main' && panel.key === focusedSection)
|
|
2461
|
+
));
|
|
2462
|
+
}
|
|
1645
2463
|
|
|
1646
2464
|
return React.createElement(
|
|
1647
2465
|
Box,
|
|
@@ -1655,24 +2473,19 @@ function createDashboardApp(deps) {
|
|
|
1655
2473
|
showErrorPanel
|
|
1656
2474
|
? React.createElement(SectionPanel, {
|
|
1657
2475
|
title: 'Errors',
|
|
1658
|
-
lines: buildErrorLines(error,
|
|
2476
|
+
lines: buildErrorLines(error, Math.max(18, fullWidth - 4)),
|
|
1659
2477
|
width: fullWidth,
|
|
1660
2478
|
maxLines: 2,
|
|
1661
2479
|
borderColor: 'red',
|
|
1662
2480
|
marginBottom: densePanels ? 0 : 1,
|
|
1663
|
-
dense: densePanels
|
|
2481
|
+
dense: densePanels,
|
|
2482
|
+
focused: paneFocus === 'main' && focusedSection === 'error-details'
|
|
1664
2483
|
})
|
|
1665
2484
|
: null,
|
|
1666
|
-
...
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
width: fullWidth,
|
|
1671
|
-
maxLines: panel.maxLines,
|
|
1672
|
-
borderColor: panel.borderColor,
|
|
1673
|
-
marginBottom: densePanels ? 0 : (index === panels.length - 1 ? 0 : 1),
|
|
1674
|
-
dense: densePanels
|
|
1675
|
-
})),
|
|
2485
|
+
...(Array.isArray(contentNode) ? contentNode : [contentNode]),
|
|
2486
|
+
statusLine !== ''
|
|
2487
|
+
? React.createElement(Text, { color: 'yellow' }, truncate(statusLine, fullWidth))
|
|
2488
|
+
: null,
|
|
1676
2489
|
showHelpLine
|
|
1677
2490
|
? React.createElement(Text, { color: 'gray' }, truncate(helpText, fullWidth))
|
|
1678
2491
|
: null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
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": {
|