specsmd 0.1.46 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1 -0
- package/flows/fire/agents/builder/agent.md +2 -2
- package/flows/fire/agents/builder/skills/code-review/SKILL.md +1 -1
- package/flows/fire/agents/builder/skills/run-execute/SKILL.md +7 -7
- package/flows/fire/agents/builder/skills/run-status/SKILL.md +1 -1
- package/flows/fire/agents/orchestrator/agent.md +1 -1
- package/flows/fire/agents/orchestrator/skills/status/SKILL.md +2 -2
- package/flows/fire/memory-bank.yaml +4 -4
- package/lib/dashboard/git/worktrees.js +248 -0
- package/lib/dashboard/index.js +473 -7
- package/lib/dashboard/runtime/watch-runtime.js +18 -9
- package/lib/dashboard/tui/app.js +472 -30
- package/package.json +1 -1
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -206,11 +206,11 @@ function buildShortStats(snapshot, flow) {
|
|
|
206
206
|
return `runs ${stats.activeRunsCount || 0}/${stats.completedRuns || 0} | intents ${stats.completedIntents || 0}/${stats.totalIntents || 0} | work ${stats.completedWorkItems || 0}/${stats.totalWorkItems || 0}`;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
function buildHeaderLine(snapshot, flow, watchEnabled, watchStatus, lastRefreshAt, view, width) {
|
|
209
|
+
function buildHeaderLine(snapshot, flow, watchEnabled, watchStatus, lastRefreshAt, view, width, worktreeLabel = null) {
|
|
210
210
|
const projectName = snapshot?.project?.name || 'Unnamed project';
|
|
211
211
|
const shortStats = buildShortStats(snapshot, flow);
|
|
212
|
-
|
|
213
|
-
const line = `${flow.toUpperCase()} | ${projectName} | ${shortStats} | watch:${watchEnabled ? watchStatus : 'off'} | ${view} | ${formatTime(lastRefreshAt)}`;
|
|
212
|
+
const worktreeSegment = worktreeLabel ? ` | wt:${worktreeLabel}` : '';
|
|
213
|
+
const line = `${flow.toUpperCase()} | ${projectName} | ${shortStats} | watch:${watchEnabled ? watchStatus : 'off'}${worktreeSegment} | ${view} | ${formatTime(lastRefreshAt)}`;
|
|
214
214
|
|
|
215
215
|
return truncate(line, width);
|
|
216
216
|
}
|
|
@@ -1102,7 +1102,8 @@ function getPanelTitles(flow, snapshot) {
|
|
|
1102
1102
|
current: 'Current Bolt',
|
|
1103
1103
|
files: 'Bolt Files',
|
|
1104
1104
|
pending: 'Queued Bolts',
|
|
1105
|
-
completed: 'Recent Completed Bolts'
|
|
1105
|
+
completed: 'Recent Completed Bolts',
|
|
1106
|
+
otherWorktrees: 'Other Worktrees: Active Bolts'
|
|
1106
1107
|
};
|
|
1107
1108
|
}
|
|
1108
1109
|
if (effectiveFlow === 'simple') {
|
|
@@ -1110,18 +1111,186 @@ function getPanelTitles(flow, snapshot) {
|
|
|
1110
1111
|
current: 'Current Spec',
|
|
1111
1112
|
files: 'Spec Files',
|
|
1112
1113
|
pending: 'Pending Specs',
|
|
1113
|
-
completed: 'Recent Completed Specs'
|
|
1114
|
+
completed: 'Recent Completed Specs',
|
|
1115
|
+
otherWorktrees: 'Other Worktrees: Active Specs'
|
|
1114
1116
|
};
|
|
1115
1117
|
}
|
|
1116
1118
|
return {
|
|
1117
1119
|
current: 'Current Run',
|
|
1118
1120
|
files: 'Run Files',
|
|
1119
1121
|
pending: 'Pending Queue',
|
|
1120
|
-
completed: 'Recent Completed Runs'
|
|
1122
|
+
completed: 'Recent Completed Runs',
|
|
1123
|
+
otherWorktrees: 'Other Worktrees: Active Runs'
|
|
1121
1124
|
};
|
|
1122
1125
|
}
|
|
1123
1126
|
|
|
1124
|
-
function
|
|
1127
|
+
function getDashboardWorktreeMeta(snapshot) {
|
|
1128
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
const meta = snapshot.dashboardWorktrees;
|
|
1132
|
+
if (!meta || typeof meta !== 'object') {
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
const items = Array.isArray(meta.items) ? meta.items : [];
|
|
1136
|
+
if (items.length === 0) {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
...meta,
|
|
1141
|
+
items
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function getWorktreeItems(snapshot) {
|
|
1146
|
+
return getDashboardWorktreeMeta(snapshot)?.items || [];
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function getSelectedWorktree(snapshot) {
|
|
1150
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
1151
|
+
if (!meta) {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
return meta.items.find((item) => item.id === meta.selectedWorktreeId) || null;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function hasMultipleWorktrees(snapshot) {
|
|
1158
|
+
return getWorktreeItems(snapshot).length > 1;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function isSelectedWorktreeMain(snapshot) {
|
|
1162
|
+
const selected = getSelectedWorktree(snapshot);
|
|
1163
|
+
return Boolean(selected?.isMainBranch);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
function getWorktreeDisplayName(worktree) {
|
|
1167
|
+
if (!worktree || typeof worktree !== 'object') {
|
|
1168
|
+
return 'unknown';
|
|
1169
|
+
}
|
|
1170
|
+
if (typeof worktree.displayBranch === 'string' && worktree.displayBranch.trim() !== '') {
|
|
1171
|
+
return worktree.displayBranch;
|
|
1172
|
+
}
|
|
1173
|
+
if (typeof worktree.branch === 'string' && worktree.branch.trim() !== '') {
|
|
1174
|
+
return worktree.branch;
|
|
1175
|
+
}
|
|
1176
|
+
if (typeof worktree.name === 'string' && worktree.name.trim() !== '') {
|
|
1177
|
+
return worktree.name;
|
|
1178
|
+
}
|
|
1179
|
+
return path.basename(worktree.path || '') || 'unknown';
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function buildWorktreeRows(snapshot, flow) {
|
|
1183
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
1184
|
+
if (!meta) {
|
|
1185
|
+
return [{
|
|
1186
|
+
kind: 'info',
|
|
1187
|
+
key: 'worktrees:none',
|
|
1188
|
+
label: 'No git worktrees detected',
|
|
1189
|
+
selectable: false
|
|
1190
|
+
}];
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1194
|
+
const entityLabel = effectiveFlow === 'aidlc'
|
|
1195
|
+
? 'active bolts'
|
|
1196
|
+
: (effectiveFlow === 'simple' ? 'active specs' : 'active runs');
|
|
1197
|
+
|
|
1198
|
+
const rows = [];
|
|
1199
|
+
for (const item of meta.items) {
|
|
1200
|
+
const currentLabel = item.isSelected ? '[CURRENT] ' : '';
|
|
1201
|
+
const mainLabel = item.isMainBranch && !item.detached ? '[MAIN] ' : '';
|
|
1202
|
+
const availabilityLabel = item.flowAvailable ? '' : ' (flow unavailable)';
|
|
1203
|
+
const statusLabel = item.status === 'loading'
|
|
1204
|
+
? ' loading...'
|
|
1205
|
+
: (item.status === 'error' ? ' error' : ` ${item.activeCount || 0} ${entityLabel}`);
|
|
1206
|
+
const scopeLabel = item.name ? ` (${item.name})` : '';
|
|
1207
|
+
|
|
1208
|
+
rows.push({
|
|
1209
|
+
kind: 'info',
|
|
1210
|
+
key: `worktree:item:${item.id}`,
|
|
1211
|
+
label: `${currentLabel}${mainLabel}${getWorktreeDisplayName(item)}${scopeLabel}${availabilityLabel}${statusLabel}`,
|
|
1212
|
+
color: item.isSelected ? 'green' : (item.flowAvailable ? 'gray' : 'red'),
|
|
1213
|
+
bold: item.isSelected,
|
|
1214
|
+
selectable: true
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
return rows;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function buildOtherWorktreeActiveGroups(snapshot, flow) {
|
|
1222
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1223
|
+
if (effectiveFlow === 'simple') {
|
|
1224
|
+
return [];
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
1228
|
+
if (!meta) {
|
|
1229
|
+
return [];
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const selectedWorktree = getSelectedWorktree(snapshot);
|
|
1233
|
+
if (!selectedWorktree || !selectedWorktree.isMainBranch) {
|
|
1234
|
+
return [];
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const groups = [];
|
|
1238
|
+
const otherItems = meta.items.filter((item) => item.id !== meta.selectedWorktreeId);
|
|
1239
|
+
for (const item of otherItems) {
|
|
1240
|
+
if (!item.flowAvailable || item.status === 'unavailable' || item.status === 'error') {
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
if (effectiveFlow === 'aidlc') {
|
|
1245
|
+
const activeBolts = Array.isArray(item.activity?.activeBolts) ? item.activity.activeBolts : [];
|
|
1246
|
+
for (const bolt of activeBolts) {
|
|
1247
|
+
const stages = Array.isArray(bolt?.stages) ? bolt.stages : [];
|
|
1248
|
+
const completedStages = stages.filter((stage) => stage?.status === 'completed').length;
|
|
1249
|
+
groups.push({
|
|
1250
|
+
key: `other:wt:${item.id}:bolt:${bolt.id}`,
|
|
1251
|
+
label: `[WT ${getWorktreeDisplayName(item)}] ${bolt.id} [${bolt.type || 'bolt'}] ${completedStages}/${stages.length || 0} stages`,
|
|
1252
|
+
files: collectAidlcBoltFiles(bolt).map((file) => ({ ...file, scope: 'active' }))
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
const activeRuns = Array.isArray(item.activity?.activeRuns) ? item.activity.activeRuns : [];
|
|
1259
|
+
for (const run of activeRuns) {
|
|
1260
|
+
const workItems = Array.isArray(run?.workItems) ? run.workItems : [];
|
|
1261
|
+
const completed = workItems.filter((workItem) => workItem?.status === 'completed').length;
|
|
1262
|
+
groups.push({
|
|
1263
|
+
key: `other:wt:${item.id}:run:${run.id}`,
|
|
1264
|
+
label: `[WT ${getWorktreeDisplayName(item)}] ${run.id} [${run.scope || 'single'}] ${completed}/${workItems.length} items`,
|
|
1265
|
+
files: collectFireRunFiles(run).map((file) => ({ ...file, scope: 'active' }))
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
return groups;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function getOtherWorktreeEmptyMessage(snapshot, flow) {
|
|
1274
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1275
|
+
if (!hasMultipleWorktrees(snapshot)) {
|
|
1276
|
+
return 'No additional worktrees';
|
|
1277
|
+
}
|
|
1278
|
+
if (!isSelectedWorktreeMain(snapshot)) {
|
|
1279
|
+
return 'Switch to main worktree to view active items from other worktrees';
|
|
1280
|
+
}
|
|
1281
|
+
if (effectiveFlow === 'aidlc') {
|
|
1282
|
+
return 'No active bolts in other worktrees';
|
|
1283
|
+
}
|
|
1284
|
+
if (effectiveFlow === 'simple') {
|
|
1285
|
+
return 'No active specs in other worktrees';
|
|
1286
|
+
}
|
|
1287
|
+
return 'No active runs in other worktrees';
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function getSectionOrderForView(view, options = {}) {
|
|
1291
|
+
const includeWorktrees = options.includeWorktrees === true;
|
|
1292
|
+
const includeOtherWorktrees = options.includeOtherWorktrees === true;
|
|
1293
|
+
|
|
1125
1294
|
if (view === 'intents') {
|
|
1126
1295
|
return ['intent-status'];
|
|
1127
1296
|
}
|
|
@@ -1131,7 +1300,15 @@ function getSectionOrderForView(view) {
|
|
|
1131
1300
|
if (view === 'health') {
|
|
1132
1301
|
return ['standards', 'stats', 'warnings', 'error-details'];
|
|
1133
1302
|
}
|
|
1134
|
-
|
|
1303
|
+
const sections = [];
|
|
1304
|
+
if (includeWorktrees) {
|
|
1305
|
+
sections.push('worktrees');
|
|
1306
|
+
}
|
|
1307
|
+
sections.push('current-run', 'run-files');
|
|
1308
|
+
if (includeOtherWorktrees) {
|
|
1309
|
+
sections.push('other-worktrees-active');
|
|
1310
|
+
}
|
|
1311
|
+
return sections;
|
|
1135
1312
|
}
|
|
1136
1313
|
|
|
1137
1314
|
function cycleSection(view, currentSectionKey, direction = 1, availableSections = null) {
|
|
@@ -2131,6 +2308,20 @@ function rowToFileEntry(row) {
|
|
|
2131
2308
|
};
|
|
2132
2309
|
}
|
|
2133
2310
|
|
|
2311
|
+
function rowToWorktreeId(row) {
|
|
2312
|
+
if (!row || typeof row.key !== 'string') {
|
|
2313
|
+
return null;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
const prefix = 'worktree:item:';
|
|
2317
|
+
if (!row.key.startsWith(prefix)) {
|
|
2318
|
+
return null;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
const worktreeId = row.key.slice(prefix.length).trim();
|
|
2322
|
+
return worktreeId === '' ? null : worktreeId;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2134
2325
|
function moveRowSelection(rows, currentIndex, direction) {
|
|
2135
2326
|
if (!Array.isArray(rows) || rows.length === 0) {
|
|
2136
2327
|
return 0;
|
|
@@ -2203,7 +2394,8 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
2203
2394
|
const {
|
|
2204
2395
|
flow = 'fire',
|
|
2205
2396
|
previewOpen = false,
|
|
2206
|
-
availableFlowCount = 1
|
|
2397
|
+
availableFlowCount = 1,
|
|
2398
|
+
hasWorktrees = false
|
|
2207
2399
|
} = options;
|
|
2208
2400
|
const isAidlc = String(flow || '').toLowerCase() === 'aidlc';
|
|
2209
2401
|
const isSimple = String(flow || '').toLowerCase() === 'simple';
|
|
@@ -2219,7 +2411,13 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
2219
2411
|
}
|
|
2220
2412
|
}
|
|
2221
2413
|
if (view === 'runs') {
|
|
2414
|
+
if (hasWorktrees) {
|
|
2415
|
+
parts.push('b worktrees', 'u others');
|
|
2416
|
+
}
|
|
2222
2417
|
parts.push('a current', 'f files');
|
|
2418
|
+
if (hasWorktrees) {
|
|
2419
|
+
parts.push('w worktree');
|
|
2420
|
+
}
|
|
2223
2421
|
}
|
|
2224
2422
|
parts.push(`tab1 ${activeLabel}`);
|
|
2225
2423
|
|
|
@@ -2238,7 +2436,8 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
2238
2436
|
previewOpen = false,
|
|
2239
2437
|
paneFocus = 'main',
|
|
2240
2438
|
availableFlowCount = 1,
|
|
2241
|
-
showErrorSection = false
|
|
2439
|
+
showErrorSection = false,
|
|
2440
|
+
hasWorktrees = false
|
|
2242
2441
|
} = options;
|
|
2243
2442
|
const isAidlc = String(flow || '').toLowerCase() === 'aidlc';
|
|
2244
2443
|
const isSimple = String(flow || '').toLowerCase() === 'simple';
|
|
@@ -2255,8 +2454,10 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
2255
2454
|
'esc close overlays (help/preview/fullscreen)',
|
|
2256
2455
|
{ text: '', color: undefined, bold: false },
|
|
2257
2456
|
{ text: 'Tab 1 Active', color: 'yellow', bold: true },
|
|
2457
|
+
...(hasWorktrees ? ['b focus worktrees section', 'u focus other-worktrees section'] : []),
|
|
2258
2458
|
`a focus active ${itemLabel}`,
|
|
2259
2459
|
`f focus ${itemLabel} files`,
|
|
2460
|
+
...(hasWorktrees ? ['w open worktree switcher'] : []),
|
|
2260
2461
|
'up/down or j/k move selection',
|
|
2261
2462
|
'enter expand/collapse selected folder row',
|
|
2262
2463
|
'v or space preview selected file',
|
|
@@ -2292,6 +2493,53 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
2292
2493
|
return lines;
|
|
2293
2494
|
}
|
|
2294
2495
|
|
|
2496
|
+
function buildWorktreeOverlayLines(snapshot, selectedIndex, width) {
|
|
2497
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
2498
|
+
if (!meta) {
|
|
2499
|
+
return [{
|
|
2500
|
+
text: truncate('No worktrees available', width),
|
|
2501
|
+
color: 'gray',
|
|
2502
|
+
bold: false
|
|
2503
|
+
}];
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
const items = Array.isArray(meta.items) ? meta.items : [];
|
|
2507
|
+
const clampedIndex = clampIndex(selectedIndex, items.length || 1);
|
|
2508
|
+
const lines = [{
|
|
2509
|
+
text: truncate('Use ↑/↓ and Enter to switch. Esc closes.', width),
|
|
2510
|
+
color: 'gray',
|
|
2511
|
+
bold: false
|
|
2512
|
+
}];
|
|
2513
|
+
|
|
2514
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
2515
|
+
const item = items[index];
|
|
2516
|
+
const marker = index === clampedIndex ? '>' : ' ';
|
|
2517
|
+
const current = item.isSelected ? '[CURRENT] ' : '';
|
|
2518
|
+
const main = item.isMainBranch && !item.detached ? '[MAIN] ' : '';
|
|
2519
|
+
const status = item.status === 'loading'
|
|
2520
|
+
? 'loading'
|
|
2521
|
+
: (item.status === 'error'
|
|
2522
|
+
? 'error'
|
|
2523
|
+
: (item.flowAvailable ? `${item.activeCount || 0} active` : 'flow unavailable'));
|
|
2524
|
+
const pathLabel = item.path ? path.basename(item.path) : 'unknown';
|
|
2525
|
+
lines.push({
|
|
2526
|
+
text: truncate(`${marker} ${current}${main}${getWorktreeDisplayName(item)} (${pathLabel}) | ${status}`, width),
|
|
2527
|
+
color: index === clampedIndex ? 'green' : (item.isSelected ? 'cyan' : 'gray'),
|
|
2528
|
+
bold: index === clampedIndex || item.isSelected
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
if (meta.hasPendingScans) {
|
|
2533
|
+
lines.push({
|
|
2534
|
+
text: truncate('Background scan in progress for additional worktrees...', width),
|
|
2535
|
+
color: 'yellow',
|
|
2536
|
+
bold: false
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
return lines;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2295
2543
|
function colorizeMarkdownLine(line, inCodeBlock) {
|
|
2296
2544
|
const text = String(line ?? '');
|
|
2297
2545
|
|
|
@@ -2451,6 +2699,7 @@ function createDashboardApp(deps) {
|
|
|
2451
2699
|
flow,
|
|
2452
2700
|
availableFlows,
|
|
2453
2701
|
resolveRootPathForFlow,
|
|
2702
|
+
resolveRootPathsForFlow,
|
|
2454
2703
|
refreshMs,
|
|
2455
2704
|
watchEnabled,
|
|
2456
2705
|
initialSnapshot,
|
|
@@ -2660,8 +2909,10 @@ function createDashboardApp(deps) {
|
|
|
2660
2909
|
health: 'standards'
|
|
2661
2910
|
});
|
|
2662
2911
|
const [selectionBySection, setSelectionBySection] = useState({
|
|
2912
|
+
worktrees: 0,
|
|
2663
2913
|
'current-run': 0,
|
|
2664
2914
|
'run-files': 0,
|
|
2915
|
+
'other-worktrees-active': 0,
|
|
2665
2916
|
'intent-status': 0,
|
|
2666
2917
|
'completed-runs': 0,
|
|
2667
2918
|
standards: 0,
|
|
@@ -2676,6 +2927,11 @@ function createDashboardApp(deps) {
|
|
|
2676
2927
|
const [previewOpen, setPreviewOpen] = useState(false);
|
|
2677
2928
|
const [paneFocus, setPaneFocus] = useState('main');
|
|
2678
2929
|
const [overlayPreviewOpen, setOverlayPreviewOpen] = useState(false);
|
|
2930
|
+
const [worktreeOverlayOpen, setWorktreeOverlayOpen] = useState(false);
|
|
2931
|
+
const [worktreeOverlayIndex, setWorktreeOverlayIndex] = useState(0);
|
|
2932
|
+
const [selectedWorktreeId, setSelectedWorktreeId] = useState(
|
|
2933
|
+
initialSnapshot?.dashboardWorktrees?.selectedWorktreeId || null
|
|
2934
|
+
);
|
|
2679
2935
|
const [previewScroll, setPreviewScroll] = useState(0);
|
|
2680
2936
|
const [statusLine, setStatusLine] = useState('');
|
|
2681
2937
|
const [lastRefreshAt, setLastRefreshAt] = useState(new Date().toISOString());
|
|
@@ -2685,9 +2941,9 @@ function createDashboardApp(deps) {
|
|
|
2685
2941
|
rows: stdout?.rows || process.stdout.rows || 40
|
|
2686
2942
|
}));
|
|
2687
2943
|
const icons = resolveIconSet();
|
|
2688
|
-
const parseSnapshotForActiveFlow = useCallback(async (flowId) => {
|
|
2944
|
+
const parseSnapshotForActiveFlow = useCallback(async (flowId, context = {}) => {
|
|
2689
2945
|
if (typeof parseSnapshotForFlow === 'function') {
|
|
2690
|
-
return parseSnapshotForFlow(flowId);
|
|
2946
|
+
return parseSnapshotForFlow(flowId, context);
|
|
2691
2947
|
}
|
|
2692
2948
|
if (typeof parseSnapshot === 'function') {
|
|
2693
2949
|
return parseSnapshot();
|
|
@@ -2703,10 +2959,18 @@ function createDashboardApp(deps) {
|
|
|
2703
2959
|
|
|
2704
2960
|
const previewVisibleRows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
|
|
2705
2961
|
const showErrorPanelForSections = Boolean(error) && previewVisibleRows >= 18;
|
|
2962
|
+
const worktreeSectionEnabled = hasMultipleWorktrees(snapshot);
|
|
2963
|
+
const otherWorktreesSectionEnabled = worktreeSectionEnabled
|
|
2964
|
+
&& isSelectedWorktreeMain(snapshot)
|
|
2965
|
+
&& getEffectiveFlow(activeFlow, snapshot) !== 'simple';
|
|
2966
|
+
|
|
2706
2967
|
const getAvailableSections = useCallback((viewId) => {
|
|
2707
|
-
const base = getSectionOrderForView(viewId
|
|
2968
|
+
const base = getSectionOrderForView(viewId, {
|
|
2969
|
+
includeWorktrees: worktreeSectionEnabled,
|
|
2970
|
+
includeOtherWorktrees: otherWorktreesSectionEnabled
|
|
2971
|
+
});
|
|
2708
2972
|
return base.filter((sectionKey) => sectionKey !== 'error-details' || showErrorPanelForSections);
|
|
2709
|
-
}, [showErrorPanelForSections]);
|
|
2973
|
+
}, [showErrorPanelForSections, worktreeSectionEnabled, otherWorktreesSectionEnabled]);
|
|
2710
2974
|
|
|
2711
2975
|
const effectiveFlow = getEffectiveFlow(activeFlow, snapshot);
|
|
2712
2976
|
const approvalGate = detectDashboardApprovalGate(snapshot, activeFlow);
|
|
@@ -2739,6 +3003,8 @@ function createDashboardApp(deps) {
|
|
|
2739
3003
|
...currentRunRowsBase
|
|
2740
3004
|
]
|
|
2741
3005
|
: currentRunRowsBase;
|
|
3006
|
+
const worktreeRows = buildWorktreeRows(snapshot, activeFlow);
|
|
3007
|
+
|
|
2742
3008
|
const shouldHydrateSecondaryTabs = deferredTabsReady || ui.view !== 'runs';
|
|
2743
3009
|
const runFileGroups = buildRunFileEntityGroups(snapshot, activeFlow, {
|
|
2744
3010
|
includeBacklog: shouldHydrateSecondaryTabs
|
|
@@ -2754,6 +3020,12 @@ function createDashboardApp(deps) {
|
|
|
2754
3020
|
getNoFileMessage(effectiveFlow),
|
|
2755
3021
|
runFileExpandedGroups
|
|
2756
3022
|
);
|
|
3023
|
+
const otherWorktreeGroups = buildOtherWorktreeActiveGroups(snapshot, activeFlow);
|
|
3024
|
+
const otherWorktreeRows = toExpandableRows(
|
|
3025
|
+
otherWorktreeGroups,
|
|
3026
|
+
getOtherWorktreeEmptyMessage(snapshot, activeFlow),
|
|
3027
|
+
expandedGroups
|
|
3028
|
+
);
|
|
2757
3029
|
const intentRows = shouldHydrateSecondaryTabs
|
|
2758
3030
|
? [
|
|
2759
3031
|
{
|
|
@@ -2804,8 +3076,10 @@ function createDashboardApp(deps) {
|
|
|
2804
3076
|
: toLoadingRows('Loading error details...', 'error-loading');
|
|
2805
3077
|
|
|
2806
3078
|
const rowsBySection = {
|
|
3079
|
+
worktrees: worktreeRows,
|
|
2807
3080
|
'current-run': currentRunRows,
|
|
2808
3081
|
'run-files': runFileRows,
|
|
3082
|
+
'other-worktrees-active': otherWorktreeRows,
|
|
2809
3083
|
'intent-status': intentRows,
|
|
2810
3084
|
'completed-runs': completedRows,
|
|
2811
3085
|
standards: standardsRows,
|
|
@@ -2813,6 +3087,12 @@ function createDashboardApp(deps) {
|
|
|
2813
3087
|
warnings: warningsRows,
|
|
2814
3088
|
'error-details': errorDetailsRows
|
|
2815
3089
|
};
|
|
3090
|
+
const worktreeItems = getWorktreeItems(snapshot);
|
|
3091
|
+
const selectedWorktree = getSelectedWorktree(snapshot);
|
|
3092
|
+
const selectedWorktreeLabel = selectedWorktree ? getWorktreeDisplayName(selectedWorktree) : null;
|
|
3093
|
+
const worktreeWatchSignature = `${snapshot?.dashboardWorktrees?.selectedWorktreeId || ''}|${worktreeItems
|
|
3094
|
+
.map((item) => `${item.id}:${item.status}:${item.activeCount}:${item.flowAvailable ? '1' : '0'}`)
|
|
3095
|
+
.join(',')}`;
|
|
2816
3096
|
const rowLengthSignature = Object.entries(rowsBySection)
|
|
2817
3097
|
.map(([key, rowsForSection]) => `${key}:${Array.isArray(rowsForSection) ? rowsForSection.length : 0}`)
|
|
2818
3098
|
.join('|');
|
|
@@ -2827,11 +3107,16 @@ function createDashboardApp(deps) {
|
|
|
2827
3107
|
const selectedFocusedRow = getSelectedRow(focusedRows, focusedIndex);
|
|
2828
3108
|
const selectedFocusedFile = rowToFileEntry(selectedFocusedRow);
|
|
2829
3109
|
|
|
2830
|
-
const refresh = useCallback(async () => {
|
|
3110
|
+
const refresh = useCallback(async (overrideSelectedWorktreeId = null) => {
|
|
2831
3111
|
const now = new Date().toISOString();
|
|
3112
|
+
const requestedWorktreeId = typeof overrideSelectedWorktreeId === 'string' && overrideSelectedWorktreeId.trim() !== ''
|
|
3113
|
+
? overrideSelectedWorktreeId.trim()
|
|
3114
|
+
: selectedWorktreeId;
|
|
2832
3115
|
|
|
2833
3116
|
try {
|
|
2834
|
-
const result = await parseSnapshotForActiveFlow(activeFlow
|
|
3117
|
+
const result = await parseSnapshotForActiveFlow(activeFlow, {
|
|
3118
|
+
selectedWorktreeId: requestedWorktreeId
|
|
3119
|
+
});
|
|
2835
3120
|
|
|
2836
3121
|
if (result?.ok) {
|
|
2837
3122
|
const nextSnapshot = result.snapshot
|
|
@@ -2845,6 +3130,11 @@ function createDashboardApp(deps) {
|
|
|
2845
3130
|
setLastRefreshAt(now);
|
|
2846
3131
|
}
|
|
2847
3132
|
|
|
3133
|
+
const nextSelectedWorktreeId = nextSnapshot?.dashboardWorktrees?.selectedWorktreeId;
|
|
3134
|
+
if (typeof nextSelectedWorktreeId === 'string' && nextSelectedWorktreeId !== '' && nextSelectedWorktreeId !== selectedWorktreeId) {
|
|
3135
|
+
setSelectedWorktreeId(nextSelectedWorktreeId);
|
|
3136
|
+
}
|
|
3137
|
+
|
|
2848
3138
|
if (errorHashRef.current !== null) {
|
|
2849
3139
|
errorHashRef.current = null;
|
|
2850
3140
|
setError(null);
|
|
@@ -2874,7 +3164,41 @@ function createDashboardApp(deps) {
|
|
|
2874
3164
|
setLastRefreshAt(now);
|
|
2875
3165
|
}
|
|
2876
3166
|
}
|
|
2877
|
-
}, [activeFlow, parseSnapshotForActiveFlow, watchEnabled]);
|
|
3167
|
+
}, [activeFlow, parseSnapshotForActiveFlow, selectedWorktreeId, watchEnabled]);
|
|
3168
|
+
|
|
3169
|
+
const switchToWorktree = useCallback((nextWorktreeId, options = {}) => {
|
|
3170
|
+
const normalizedNextId = typeof nextWorktreeId === 'string' ? nextWorktreeId.trim() : '';
|
|
3171
|
+
if (normalizedNextId === '') {
|
|
3172
|
+
setStatusLine('No worktree selected.');
|
|
3173
|
+
return false;
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
const nextItem = worktreeItems.find((item) => item.id === normalizedNextId);
|
|
3177
|
+
if (!nextItem) {
|
|
3178
|
+
setStatusLine('Selected worktree is no longer available.');
|
|
3179
|
+
return false;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
if (!nextItem.flowAvailable) {
|
|
3183
|
+
setStatusLine(`Flow is unavailable in worktree: ${getWorktreeDisplayName(nextItem)}`);
|
|
3184
|
+
return false;
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
const changed = normalizedNextId !== selectedWorktreeId;
|
|
3188
|
+
setSelectedWorktreeId(normalizedNextId);
|
|
3189
|
+
setPreviewTarget(null);
|
|
3190
|
+
setPreviewOpen(false);
|
|
3191
|
+
setOverlayPreviewOpen(false);
|
|
3192
|
+
setPreviewScroll(0);
|
|
3193
|
+
setPaneFocus('main');
|
|
3194
|
+
|
|
3195
|
+
if (changed || options.forceRefresh) {
|
|
3196
|
+
setStatusLine(`Switched to worktree: ${getWorktreeDisplayName(nextItem)}`);
|
|
3197
|
+
void refresh(normalizedNextId);
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
return true;
|
|
3201
|
+
}, [refresh, selectedWorktreeId, worktreeItems]);
|
|
2878
3202
|
|
|
2879
3203
|
useInput((input, key) => {
|
|
2880
3204
|
if ((key.ctrl && input === 'c') || input === 'q') {
|
|
@@ -2901,6 +3225,32 @@ function createDashboardApp(deps) {
|
|
|
2901
3225
|
return;
|
|
2902
3226
|
}
|
|
2903
3227
|
|
|
3228
|
+
if (worktreeOverlayOpen) {
|
|
3229
|
+
if (key.escape) {
|
|
3230
|
+
setWorktreeOverlayOpen(false);
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
if (key.upArrow || input === 'k') {
|
|
3235
|
+
setWorktreeOverlayIndex((previous) => Math.max(0, previous - 1));
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
if (key.downArrow || input === 'j') {
|
|
3240
|
+
setWorktreeOverlayIndex((previous) => Math.min(Math.max(0, worktreeItems.length - 1), previous + 1));
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
if (key.return || key.enter) {
|
|
3245
|
+
const selectedOverlayItem = worktreeItems[clampIndex(worktreeOverlayIndex, worktreeItems.length || 1)];
|
|
3246
|
+
switchToWorktree(selectedOverlayItem?.id || '', { forceRefresh: true });
|
|
3247
|
+
setWorktreeOverlayOpen(false);
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
return;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
2904
3254
|
if (input === '1') {
|
|
2905
3255
|
setUi((previous) => ({ ...previous, view: 'runs' }));
|
|
2906
3256
|
setPaneFocus('main');
|
|
@@ -2925,6 +3275,13 @@ function createDashboardApp(deps) {
|
|
|
2925
3275
|
return;
|
|
2926
3276
|
}
|
|
2927
3277
|
|
|
3278
|
+
if (ui.view === 'runs' && input === 'w' && worktreeSectionEnabled) {
|
|
3279
|
+
setWorktreeOverlayIndex(clampIndex(worktreeItems.findIndex((item) => item.id === selectedWorktreeId), worktreeItems.length || 1));
|
|
3280
|
+
setWorktreeOverlayOpen(true);
|
|
3281
|
+
setPaneFocus('main');
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
|
|
2928
3285
|
if ((input === ']' || input === 'm') && availableFlowIds.length > 1) {
|
|
2929
3286
|
snapshotHashRef.current = safeJsonHash(null);
|
|
2930
3287
|
errorHashRef.current = null;
|
|
@@ -2938,8 +3295,10 @@ function createDashboardApp(deps) {
|
|
|
2938
3295
|
return availableFlowIds[nextIndex];
|
|
2939
3296
|
});
|
|
2940
3297
|
setSelectionBySection({
|
|
3298
|
+
worktrees: 0,
|
|
2941
3299
|
'current-run': 0,
|
|
2942
3300
|
'run-files': 0,
|
|
3301
|
+
'other-worktrees-active': 0,
|
|
2943
3302
|
'intent-status': 0,
|
|
2944
3303
|
'completed-runs': 0,
|
|
2945
3304
|
standards: 0,
|
|
@@ -2958,6 +3317,7 @@ function createDashboardApp(deps) {
|
|
|
2958
3317
|
setPreviewTarget(null);
|
|
2959
3318
|
setPreviewOpen(false);
|
|
2960
3319
|
setOverlayPreviewOpen(false);
|
|
3320
|
+
setWorktreeOverlayOpen(false);
|
|
2961
3321
|
setPreviewScroll(0);
|
|
2962
3322
|
setPaneFocus('main');
|
|
2963
3323
|
return;
|
|
@@ -2976,8 +3336,10 @@ function createDashboardApp(deps) {
|
|
|
2976
3336
|
return availableFlowIds[nextIndex];
|
|
2977
3337
|
});
|
|
2978
3338
|
setSelectionBySection({
|
|
3339
|
+
worktrees: 0,
|
|
2979
3340
|
'current-run': 0,
|
|
2980
3341
|
'run-files': 0,
|
|
3342
|
+
'other-worktrees-active': 0,
|
|
2981
3343
|
'intent-status': 0,
|
|
2982
3344
|
'completed-runs': 0,
|
|
2983
3345
|
standards: 0,
|
|
@@ -2996,6 +3358,7 @@ function createDashboardApp(deps) {
|
|
|
2996
3358
|
setPreviewTarget(null);
|
|
2997
3359
|
setPreviewOpen(false);
|
|
2998
3360
|
setOverlayPreviewOpen(false);
|
|
3361
|
+
setWorktreeOverlayOpen(false);
|
|
2999
3362
|
setPreviewScroll(0);
|
|
3000
3363
|
setPaneFocus('main');
|
|
3001
3364
|
return;
|
|
@@ -3045,6 +3408,11 @@ function createDashboardApp(deps) {
|
|
|
3045
3408
|
}
|
|
3046
3409
|
|
|
3047
3410
|
if (ui.view === 'runs') {
|
|
3411
|
+
if (input === 'b' && worktreeSectionEnabled) {
|
|
3412
|
+
setSectionFocus((previous) => ({ ...previous, runs: 'worktrees' }));
|
|
3413
|
+
setPaneFocus('main');
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3048
3416
|
if (input === 'a') {
|
|
3049
3417
|
setSectionFocus((previous) => ({ ...previous, runs: 'current-run' }));
|
|
3050
3418
|
setPaneFocus('main');
|
|
@@ -3055,6 +3423,11 @@ function createDashboardApp(deps) {
|
|
|
3055
3423
|
setPaneFocus('main');
|
|
3056
3424
|
return;
|
|
3057
3425
|
}
|
|
3426
|
+
if (input === 'u' && otherWorktreesSectionEnabled) {
|
|
3427
|
+
setSectionFocus((previous) => ({ ...previous, runs: 'other-worktrees-active' }));
|
|
3428
|
+
setPaneFocus('main');
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3058
3431
|
} else if (ui.view === 'intents') {
|
|
3059
3432
|
if (input === 'i') {
|
|
3060
3433
|
setSectionFocus((previous) => ({ ...previous, intents: 'intent-status' }));
|
|
@@ -3126,6 +3499,15 @@ function createDashboardApp(deps) {
|
|
|
3126
3499
|
...previous,
|
|
3127
3500
|
[targetSection]: nextIndex
|
|
3128
3501
|
}));
|
|
3502
|
+
|
|
3503
|
+
if (targetSection === 'worktrees' && worktreeSectionEnabled) {
|
|
3504
|
+
const nextRow = getSelectedRow(targetRows, nextIndex);
|
|
3505
|
+
const nextWorktreeId = rowToWorktreeId(nextRow);
|
|
3506
|
+
if (nextWorktreeId && nextWorktreeId !== selectedWorktreeId) {
|
|
3507
|
+
switchToWorktree(nextWorktreeId);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3129
3511
|
return;
|
|
3130
3512
|
}
|
|
3131
3513
|
|
|
@@ -3193,6 +3575,28 @@ function createDashboardApp(deps) {
|
|
|
3193
3575
|
void refresh();
|
|
3194
3576
|
}, [refresh]);
|
|
3195
3577
|
|
|
3578
|
+
useEffect(() => {
|
|
3579
|
+
const snapshotSelected = snapshot?.dashboardWorktrees?.selectedWorktreeId;
|
|
3580
|
+
if (typeof snapshotSelected !== 'string' || snapshotSelected === '') {
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
3583
|
+
if (snapshotSelected !== selectedWorktreeId) {
|
|
3584
|
+
setSelectedWorktreeId(snapshotSelected);
|
|
3585
|
+
}
|
|
3586
|
+
}, [snapshot?.dashboardWorktrees?.selectedWorktreeId, selectedWorktreeId]);
|
|
3587
|
+
|
|
3588
|
+
useEffect(() => {
|
|
3589
|
+
if (!snapshot?.dashboardWorktrees?.hasPendingScans) {
|
|
3590
|
+
return undefined;
|
|
3591
|
+
}
|
|
3592
|
+
const timer = setTimeout(() => {
|
|
3593
|
+
void refresh();
|
|
3594
|
+
}, 350);
|
|
3595
|
+
return () => {
|
|
3596
|
+
clearTimeout(timer);
|
|
3597
|
+
};
|
|
3598
|
+
}, [snapshot?.dashboardWorktrees?.hasPendingScans, snapshot?.generatedAt, refresh]);
|
|
3599
|
+
|
|
3196
3600
|
useEffect(() => {
|
|
3197
3601
|
setSelectionBySection((previous) => {
|
|
3198
3602
|
let changed = false;
|
|
@@ -3226,6 +3630,7 @@ function createDashboardApp(deps) {
|
|
|
3226
3630
|
|
|
3227
3631
|
useEffect(() => {
|
|
3228
3632
|
setPaneFocus('main');
|
|
3633
|
+
setWorktreeOverlayOpen(false);
|
|
3229
3634
|
}, [ui.view]);
|
|
3230
3635
|
|
|
3231
3636
|
useEffect(() => {
|
|
@@ -3295,12 +3700,17 @@ function createDashboardApp(deps) {
|
|
|
3295
3700
|
return undefined;
|
|
3296
3701
|
}
|
|
3297
3702
|
|
|
3298
|
-
const
|
|
3703
|
+
const resolvedRootCandidates = typeof resolveRootPathsForFlow === 'function'
|
|
3704
|
+
? resolveRootPathsForFlow(activeFlow, snapshot?.dashboardWorktrees, selectedWorktreeId)
|
|
3705
|
+
: null;
|
|
3706
|
+
const candidateRoots = Array.isArray(resolvedRootCandidates) ? resolvedRootCandidates : [];
|
|
3707
|
+
const fallbackRoot = resolveRootPathForFlow
|
|
3299
3708
|
? resolveRootPathForFlow(activeFlow)
|
|
3300
3709
|
: (rootPath || `${workspacePath}/.specs-fire`);
|
|
3710
|
+
const watchRoots = candidateRoots.length > 0 ? candidateRoots : [fallbackRoot];
|
|
3301
3711
|
|
|
3302
3712
|
const runtime = createWatchRuntime({
|
|
3303
|
-
|
|
3713
|
+
rootPaths: watchRoots,
|
|
3304
3714
|
debounceMs: 200,
|
|
3305
3715
|
onRefresh: () => {
|
|
3306
3716
|
void refresh();
|
|
@@ -3329,7 +3739,7 @@ function createDashboardApp(deps) {
|
|
|
3329
3739
|
clearInterval(interval);
|
|
3330
3740
|
void runtime.close();
|
|
3331
3741
|
};
|
|
3332
|
-
}, [watchEnabled, refreshMs, refresh, rootPath, workspacePath, resolveRootPathForFlow, activeFlow]);
|
|
3742
|
+
}, [watchEnabled, refreshMs, refresh, rootPath, workspacePath, resolveRootPathForFlow, resolveRootPathsForFlow, activeFlow, worktreeWatchSignature, selectedWorktreeId]);
|
|
3333
3743
|
|
|
3334
3744
|
useEffect(() => {
|
|
3335
3745
|
if (!stdout || typeof stdout.write !== 'function') {
|
|
@@ -3348,9 +3758,9 @@ function createDashboardApp(deps) {
|
|
|
3348
3758
|
const showFlowBar = availableFlowIds.length > 1;
|
|
3349
3759
|
const showFooterHelpLine = rows >= 10;
|
|
3350
3760
|
const showErrorPanel = Boolean(error) && rows >= 18;
|
|
3351
|
-
const showGlobalErrorPanel = showErrorPanel && ui.view !== 'health' && !ui.showHelp;
|
|
3352
|
-
const showErrorInline = Boolean(error) && !showErrorPanel;
|
|
3353
|
-
const showApprovalBanner = approvalGateLine !== '' && !ui.showHelp;
|
|
3761
|
+
const showGlobalErrorPanel = showErrorPanel && ui.view !== 'health' && !ui.showHelp && !worktreeOverlayOpen;
|
|
3762
|
+
const showErrorInline = Boolean(error) && !showErrorPanel && !worktreeOverlayOpen;
|
|
3763
|
+
const showApprovalBanner = approvalGateLine !== '' && !ui.showHelp && !worktreeOverlayOpen;
|
|
3354
3764
|
const showStatusLine = statusLine !== '';
|
|
3355
3765
|
const densePanels = rows <= 28 || cols <= 120;
|
|
3356
3766
|
|
|
@@ -3366,7 +3776,7 @@ function createDashboardApp(deps) {
|
|
|
3366
3776
|
const contentRowsBudget = Math.max(4, rows - reservedRows - frameSafetyRows);
|
|
3367
3777
|
const ultraCompact = rows <= 14;
|
|
3368
3778
|
const panelTitles = getPanelTitles(activeFlow, snapshot);
|
|
3369
|
-
const splitPreviewLayout = previewOpen && !overlayPreviewOpen && !ui.showHelp && cols >= 110 && rows >= 16;
|
|
3779
|
+
const splitPreviewLayout = previewOpen && !overlayPreviewOpen && !ui.showHelp && !worktreeOverlayOpen && cols >= 110 && rows >= 16;
|
|
3370
3780
|
const mainPaneWidth = splitPreviewLayout
|
|
3371
3781
|
? Math.max(34, Math.floor((fullWidth - 1) * 0.52))
|
|
3372
3782
|
: fullWidth;
|
|
@@ -3401,13 +3811,15 @@ function createDashboardApp(deps) {
|
|
|
3401
3811
|
previewOpen,
|
|
3402
3812
|
paneFocus,
|
|
3403
3813
|
availableFlowCount: availableFlowIds.length,
|
|
3404
|
-
showErrorSection: showErrorPanel
|
|
3814
|
+
showErrorSection: showErrorPanel,
|
|
3815
|
+
hasWorktrees: worktreeSectionEnabled
|
|
3405
3816
|
});
|
|
3406
3817
|
const quickHelpText = buildQuickHelpText(ui.view, {
|
|
3407
3818
|
flow: activeFlow,
|
|
3408
3819
|
previewOpen,
|
|
3409
3820
|
paneFocus,
|
|
3410
|
-
availableFlowCount: availableFlowIds.length
|
|
3821
|
+
availableFlowCount: availableFlowIds.length,
|
|
3822
|
+
hasWorktrees: worktreeSectionEnabled
|
|
3411
3823
|
});
|
|
3412
3824
|
|
|
3413
3825
|
let panelCandidates;
|
|
@@ -3420,6 +3832,15 @@ function createDashboardApp(deps) {
|
|
|
3420
3832
|
borderColor: 'cyan'
|
|
3421
3833
|
}
|
|
3422
3834
|
];
|
|
3835
|
+
} else if (worktreeOverlayOpen) {
|
|
3836
|
+
panelCandidates = [
|
|
3837
|
+
{
|
|
3838
|
+
key: 'worktree-overlay',
|
|
3839
|
+
title: 'Switch Worktree',
|
|
3840
|
+
lines: buildWorktreeOverlayLines(snapshot, worktreeOverlayIndex, Math.max(18, fullWidth - 4)),
|
|
3841
|
+
borderColor: 'yellow'
|
|
3842
|
+
}
|
|
3843
|
+
];
|
|
3423
3844
|
} else if (previewOpen && overlayPreviewOpen) {
|
|
3424
3845
|
panelCandidates = [
|
|
3425
3846
|
{
|
|
@@ -3478,7 +3899,16 @@ function createDashboardApp(deps) {
|
|
|
3478
3899
|
});
|
|
3479
3900
|
}
|
|
3480
3901
|
} else {
|
|
3481
|
-
panelCandidates = [
|
|
3902
|
+
panelCandidates = [];
|
|
3903
|
+
if (worktreeSectionEnabled) {
|
|
3904
|
+
panelCandidates.push({
|
|
3905
|
+
key: 'worktrees',
|
|
3906
|
+
title: 'Worktrees',
|
|
3907
|
+
lines: sectionLines.worktrees,
|
|
3908
|
+
borderColor: 'magenta'
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
panelCandidates.push(
|
|
3482
3912
|
{
|
|
3483
3913
|
key: 'current-run',
|
|
3484
3914
|
title: panelTitles.current,
|
|
@@ -3491,7 +3921,15 @@ function createDashboardApp(deps) {
|
|
|
3491
3921
|
lines: sectionLines['run-files'],
|
|
3492
3922
|
borderColor: 'yellow'
|
|
3493
3923
|
}
|
|
3494
|
-
|
|
3924
|
+
);
|
|
3925
|
+
if (otherWorktreesSectionEnabled) {
|
|
3926
|
+
panelCandidates.push({
|
|
3927
|
+
key: 'other-worktrees-active',
|
|
3928
|
+
title: panelTitles.otherWorktrees,
|
|
3929
|
+
lines: sectionLines['other-worktrees-active'],
|
|
3930
|
+
borderColor: 'blue'
|
|
3931
|
+
});
|
|
3932
|
+
}
|
|
3495
3933
|
}
|
|
3496
3934
|
|
|
3497
3935
|
if (!ui.showHelp && previewOpen && !overlayPreviewOpen && !splitPreviewLayout) {
|
|
@@ -3577,7 +4015,7 @@ function createDashboardApp(deps) {
|
|
|
3577
4015
|
panel,
|
|
3578
4016
|
index,
|
|
3579
4017
|
fullWidth,
|
|
3580
|
-
ui.showHelp
|
|
4018
|
+
(ui.showHelp || worktreeOverlayOpen)
|
|
3581
4019
|
? true
|
|
3582
4020
|
: ((panel.key === 'preview' || panel.key === 'preview-overlay')
|
|
3583
4021
|
? paneFocus === 'preview'
|
|
@@ -3588,7 +4026,11 @@ function createDashboardApp(deps) {
|
|
|
3588
4026
|
return React.createElement(
|
|
3589
4027
|
Box,
|
|
3590
4028
|
{ flexDirection: 'column', width: fullWidth },
|
|
3591
|
-
React.createElement(
|
|
4029
|
+
React.createElement(
|
|
4030
|
+
Text,
|
|
4031
|
+
{ color: 'cyan' },
|
|
4032
|
+
buildHeaderLine(snapshot, activeFlow, watchEnabled, watchStatus, lastRefreshAt, ui.view, fullWidth, selectedWorktreeLabel)
|
|
4033
|
+
),
|
|
3592
4034
|
React.createElement(FlowBar, { activeFlow, width: fullWidth, flowIds: availableFlowIds }),
|
|
3593
4035
|
React.createElement(TabsBar, { view: ui.view, width: fullWidth, icons, flow: activeFlow }),
|
|
3594
4036
|
showApprovalBanner
|