specsmd 0.1.52 → 0.1.54
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 +450 -72
- package/package.json +1 -1
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -121,6 +121,11 @@ function truncate(value, width) {
|
|
|
121
121
|
return `${sliceAnsi(text, 0, bodyWidth)}${ellipsis}`;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
function resolveFrameWidth(columns) {
|
|
125
|
+
const safeColumns = Number.isFinite(columns) ? Math.max(1, Math.floor(columns)) : 120;
|
|
126
|
+
return safeColumns > 24 ? safeColumns - 1 : safeColumns;
|
|
127
|
+
}
|
|
128
|
+
|
|
124
129
|
function normalizePanelLine(line) {
|
|
125
130
|
if (line && typeof line === 'object' && !Array.isArray(line)) {
|
|
126
131
|
return {
|
|
@@ -1167,6 +1172,215 @@ function getGitChangesSnapshot(snapshot) {
|
|
|
1167
1172
|
};
|
|
1168
1173
|
}
|
|
1169
1174
|
|
|
1175
|
+
function readGitCommandLines(repoRoot, args, options = {}) {
|
|
1176
|
+
if (typeof repoRoot !== 'string' || repoRoot.trim() === '' || !Array.isArray(args) || args.length === 0) {
|
|
1177
|
+
return [];
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
const acceptedStatuses = Array.isArray(options.acceptedStatuses) && options.acceptedStatuses.length > 0
|
|
1181
|
+
? options.acceptedStatuses
|
|
1182
|
+
: [0];
|
|
1183
|
+
|
|
1184
|
+
const result = spawnSync('git', args, {
|
|
1185
|
+
cwd: repoRoot,
|
|
1186
|
+
encoding: 'utf8',
|
|
1187
|
+
maxBuffer: 8 * 1024 * 1024
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
if (result.error) {
|
|
1191
|
+
return [];
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (typeof result.status === 'number' && !acceptedStatuses.includes(result.status)) {
|
|
1195
|
+
return [];
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const lines = String(result.stdout || '')
|
|
1199
|
+
.split(/\r?\n/)
|
|
1200
|
+
.map((line) => line.trim())
|
|
1201
|
+
.filter(Boolean);
|
|
1202
|
+
|
|
1203
|
+
const limit = Number.isFinite(options.limit) ? Math.max(1, Math.floor(options.limit)) : null;
|
|
1204
|
+
if (limit == null || lines.length <= limit) {
|
|
1205
|
+
return lines;
|
|
1206
|
+
}
|
|
1207
|
+
return lines.slice(0, limit);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function buildGitStatusPanelLines(snapshot) {
|
|
1211
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
1212
|
+
if (!git.available) {
|
|
1213
|
+
return [{
|
|
1214
|
+
text: 'Repository unavailable in selected worktree',
|
|
1215
|
+
color: 'red',
|
|
1216
|
+
bold: true
|
|
1217
|
+
}];
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const tracking = git.upstream
|
|
1221
|
+
? `${git.upstream} (${git.ahead > 0 ? `ahead ${git.ahead}` : 'ahead 0'}, ${git.behind > 0 ? `behind ${git.behind}` : 'behind 0'})`
|
|
1222
|
+
: 'no upstream';
|
|
1223
|
+
|
|
1224
|
+
return [
|
|
1225
|
+
{
|
|
1226
|
+
text: `branch: ${git.branch}${git.detached ? ' [detached]' : ''}`,
|
|
1227
|
+
color: 'green',
|
|
1228
|
+
bold: true
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
text: `tracking: ${tracking}`,
|
|
1232
|
+
color: 'gray',
|
|
1233
|
+
bold: false
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
text: `changes: ${git.counts.total || 0} total`,
|
|
1237
|
+
color: 'gray',
|
|
1238
|
+
bold: false
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
text: `staged ${git.counts.staged || 0} | unstaged ${git.counts.unstaged || 0}`,
|
|
1242
|
+
color: 'yellow',
|
|
1243
|
+
bold: false
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
text: `untracked ${git.counts.untracked || 0} | conflicts ${git.counts.conflicted || 0}`,
|
|
1247
|
+
color: 'yellow',
|
|
1248
|
+
bold: false
|
|
1249
|
+
}
|
|
1250
|
+
];
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function buildGitBranchesPanelLines(snapshot) {
|
|
1254
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
1255
|
+
if (!git.available) {
|
|
1256
|
+
return [{
|
|
1257
|
+
text: 'No branch list (git unavailable)',
|
|
1258
|
+
color: 'gray',
|
|
1259
|
+
bold: false
|
|
1260
|
+
}];
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const branchLines = readGitCommandLines(git.rootPath, [
|
|
1264
|
+
'-c',
|
|
1265
|
+
'color.ui=false',
|
|
1266
|
+
'for-each-ref',
|
|
1267
|
+
'--sort=-committerdate',
|
|
1268
|
+
'--format=%(if)%(HEAD)%(then)*%(else) %(end) %(refname:short)',
|
|
1269
|
+
'refs/heads'
|
|
1270
|
+
], { limit: 8 });
|
|
1271
|
+
|
|
1272
|
+
const lines = [{
|
|
1273
|
+
text: 'Local branches · Remotes · Tags',
|
|
1274
|
+
color: 'cyan',
|
|
1275
|
+
bold: true
|
|
1276
|
+
}];
|
|
1277
|
+
|
|
1278
|
+
if (branchLines.length === 0) {
|
|
1279
|
+
lines.push({
|
|
1280
|
+
text: 'No local branches found',
|
|
1281
|
+
color: 'gray',
|
|
1282
|
+
bold: false
|
|
1283
|
+
});
|
|
1284
|
+
return lines;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
for (const line of branchLines) {
|
|
1288
|
+
const isCurrent = line.startsWith('*');
|
|
1289
|
+
lines.push({
|
|
1290
|
+
text: line,
|
|
1291
|
+
color: isCurrent ? 'green' : 'gray',
|
|
1292
|
+
bold: isCurrent
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
return lines;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function buildGitCommitsPanelLines(snapshot) {
|
|
1300
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
1301
|
+
if (!git.available) {
|
|
1302
|
+
return [{
|
|
1303
|
+
text: 'No commit log (git unavailable)',
|
|
1304
|
+
color: 'gray',
|
|
1305
|
+
bold: false
|
|
1306
|
+
}];
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const reflogLines = readGitCommandLines(git.rootPath, [
|
|
1310
|
+
'-c',
|
|
1311
|
+
'color.ui=false',
|
|
1312
|
+
'reflog',
|
|
1313
|
+
'--date=relative',
|
|
1314
|
+
'--pretty=format:%h %gs'
|
|
1315
|
+
], { limit: 8 });
|
|
1316
|
+
|
|
1317
|
+
const lines = [{
|
|
1318
|
+
text: 'Commits · Reflog',
|
|
1319
|
+
color: 'cyan',
|
|
1320
|
+
bold: true
|
|
1321
|
+
}];
|
|
1322
|
+
|
|
1323
|
+
if (reflogLines.length === 0) {
|
|
1324
|
+
lines.push({
|
|
1325
|
+
text: 'No reflog entries',
|
|
1326
|
+
color: 'gray',
|
|
1327
|
+
bold: false
|
|
1328
|
+
});
|
|
1329
|
+
return lines;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
lines.push(...reflogLines.map((line) => ({
|
|
1333
|
+
text: line,
|
|
1334
|
+
color: 'gray',
|
|
1335
|
+
bold: false
|
|
1336
|
+
})));
|
|
1337
|
+
return lines;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function buildGitStashPanelLines(snapshot) {
|
|
1341
|
+
const git = getGitChangesSnapshot(snapshot);
|
|
1342
|
+
if (!git.available) {
|
|
1343
|
+
return [{
|
|
1344
|
+
text: 'No stash info (git unavailable)',
|
|
1345
|
+
color: 'gray',
|
|
1346
|
+
bold: false
|
|
1347
|
+
}];
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
const stashLines = readGitCommandLines(git.rootPath, [
|
|
1351
|
+
'-c',
|
|
1352
|
+
'color.ui=false',
|
|
1353
|
+
'stash',
|
|
1354
|
+
'list',
|
|
1355
|
+
'--pretty=format:%gd %s'
|
|
1356
|
+
], {
|
|
1357
|
+
acceptedStatuses: [0],
|
|
1358
|
+
limit: 4
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
const lines = [{
|
|
1362
|
+
text: 'Stash',
|
|
1363
|
+
color: 'cyan',
|
|
1364
|
+
bold: true
|
|
1365
|
+
}];
|
|
1366
|
+
|
|
1367
|
+
if (stashLines.length === 0) {
|
|
1368
|
+
lines.push({
|
|
1369
|
+
text: 'No stashes',
|
|
1370
|
+
color: 'gray',
|
|
1371
|
+
bold: false
|
|
1372
|
+
});
|
|
1373
|
+
return lines;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
lines.push(...stashLines.map((line) => ({
|
|
1377
|
+
text: line,
|
|
1378
|
+
color: 'gray',
|
|
1379
|
+
bold: false
|
|
1380
|
+
})));
|
|
1381
|
+
return lines;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1170
1384
|
function getDashboardWorktreeMeta(snapshot) {
|
|
1171
1385
|
if (!snapshot || typeof snapshot !== 'object') {
|
|
1172
1386
|
return null;
|
|
@@ -2415,6 +2629,21 @@ function rowToFileEntry(row) {
|
|
|
2415
2629
|
};
|
|
2416
2630
|
}
|
|
2417
2631
|
|
|
2632
|
+
function firstFileEntryFromRows(rows) {
|
|
2633
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
2634
|
+
return null;
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
for (const row of rows) {
|
|
2638
|
+
const entry = rowToFileEntry(row);
|
|
2639
|
+
if (entry) {
|
|
2640
|
+
return entry;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
return null;
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2418
2647
|
function rowToWorktreeId(row) {
|
|
2419
2648
|
if (!row || typeof row.key !== 'string') {
|
|
2420
2649
|
return null;
|
|
@@ -2522,6 +2751,8 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
2522
2751
|
parts.push('b worktrees', 'u others');
|
|
2523
2752
|
}
|
|
2524
2753
|
parts.push('a current', 'f files');
|
|
2754
|
+
} else if (view === 'git') {
|
|
2755
|
+
parts.push('d changes', 'c/A/p/P git(soon)');
|
|
2525
2756
|
}
|
|
2526
2757
|
parts.push(`tab1 ${activeLabel}`);
|
|
2527
2758
|
|
|
@@ -2533,6 +2764,57 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
2533
2764
|
return parts.join(' | ');
|
|
2534
2765
|
}
|
|
2535
2766
|
|
|
2767
|
+
function buildLazyGitCommandStrip(view, options = {}) {
|
|
2768
|
+
const {
|
|
2769
|
+
hasWorktrees = false,
|
|
2770
|
+
previewOpen = false
|
|
2771
|
+
} = options;
|
|
2772
|
+
|
|
2773
|
+
const parts = [];
|
|
2774
|
+
|
|
2775
|
+
if (view === 'runs') {
|
|
2776
|
+
if (hasWorktrees) {
|
|
2777
|
+
parts.push('b worktrees');
|
|
2778
|
+
}
|
|
2779
|
+
parts.push('a current', 'f files', 'enter expand');
|
|
2780
|
+
} else if (view === 'intents') {
|
|
2781
|
+
parts.push('n next', 'x completed', 'enter expand');
|
|
2782
|
+
} else if (view === 'completed') {
|
|
2783
|
+
parts.push('c completed', 'enter expand');
|
|
2784
|
+
} else if (view === 'health') {
|
|
2785
|
+
parts.push('s standards', 't stats', 'w warnings');
|
|
2786
|
+
} else if (view === 'git') {
|
|
2787
|
+
parts.push('d changes', 'space preview', 'c commit (soon)', 'A amend (soon)', 'p push (soon)', 'P pull (soon)');
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
if (previewOpen) {
|
|
2791
|
+
parts.push('tab pane', 'j/k scroll');
|
|
2792
|
+
} else {
|
|
2793
|
+
parts.push('v preview');
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
parts.push('1-5 views', 'g/G panels', 'r refresh', '? help', 'q quit');
|
|
2797
|
+
return parts.join(' | ');
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
function buildLazyGitCommandLogLine(options = {}) {
|
|
2801
|
+
const {
|
|
2802
|
+
statusLine = '',
|
|
2803
|
+
activeFlow = 'fire',
|
|
2804
|
+
watchEnabled = true,
|
|
2805
|
+
watchStatus = 'watching',
|
|
2806
|
+
selectedWorktreeLabel = null
|
|
2807
|
+
} = options;
|
|
2808
|
+
|
|
2809
|
+
if (typeof statusLine === 'string' && statusLine.trim() !== '') {
|
|
2810
|
+
return `Command Log | ${statusLine}`;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
const watchLabel = watchEnabled ? watchStatus : 'off';
|
|
2814
|
+
const worktreeSegment = selectedWorktreeLabel ? ` | wt:${selectedWorktreeLabel}` : '';
|
|
2815
|
+
return `Command Log | flow:${String(activeFlow || 'fire').toUpperCase()} | watch:${watchLabel}${worktreeSegment} | ready`;
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2536
2818
|
function buildHelpOverlayLines(options = {}) {
|
|
2537
2819
|
const {
|
|
2538
2820
|
view = 'runs',
|
|
@@ -2592,6 +2874,7 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
2592
2874
|
{ text: '', color: undefined, bold: false },
|
|
2593
2875
|
{ text: 'Tab 5 Git Changes', color: 'yellow', bold: true },
|
|
2594
2876
|
'select changed files and preview diffs',
|
|
2877
|
+
'commit/push/pull shortcuts are shown in footer for LazyGit-style hierarchy',
|
|
2595
2878
|
{ text: '', color: undefined, bold: false },
|
|
2596
2879
|
{ text: `Current view: ${String(view || 'runs').toUpperCase()}`, color: 'gray', bold: false }
|
|
2597
2880
|
);
|
|
@@ -2864,7 +3147,7 @@ function createDashboardApp(deps) {
|
|
|
2864
3147
|
focused
|
|
2865
3148
|
} = props;
|
|
2866
3149
|
|
|
2867
|
-
const contentWidth = Math.max(
|
|
3150
|
+
const contentWidth = Math.max(1, width - (dense ? 2 : 4));
|
|
2868
3151
|
const visibleLines = fitLines(lines, maxLines, contentWidth);
|
|
2869
3152
|
const panelBorderColor = focused ? 'cyan' : (borderColor || 'gray');
|
|
2870
3153
|
const titleColor = focused ? 'black' : 'cyan';
|
|
@@ -3218,32 +3501,11 @@ function createDashboardApp(deps) {
|
|
|
3218
3501
|
const gitRows = shouldHydrateSecondaryTabs
|
|
3219
3502
|
? (() => {
|
|
3220
3503
|
const git = getGitChangesSnapshot(snapshot);
|
|
3221
|
-
|
|
3222
|
-
? `${git.upstream} (${git.ahead > 0 ? `ahead ${git.ahead}` : 'ahead 0'}, ${git.behind > 0 ? `behind ${git.behind}` : 'behind 0'})`
|
|
3223
|
-
: 'no upstream';
|
|
3224
|
-
const headerRows = [{
|
|
3225
|
-
kind: 'info',
|
|
3226
|
-
key: 'git:branch',
|
|
3227
|
-
label: git.available
|
|
3228
|
-
? `branch ${git.branch}${git.detached ? ' [detached]' : ''} | ${tracking}`
|
|
3229
|
-
: 'git: repository unavailable in selected worktree',
|
|
3230
|
-
color: git.available ? 'cyan' : 'red',
|
|
3231
|
-
bold: true,
|
|
3232
|
-
selectable: false
|
|
3233
|
-
}, {
|
|
3234
|
-
kind: 'info',
|
|
3235
|
-
key: 'git:counts',
|
|
3236
|
-
label: `changes ${git.counts.total || 0} | staged ${git.counts.staged || 0} | unstaged ${git.counts.unstaged || 0} | untracked ${git.counts.untracked || 0} | conflicts ${git.counts.conflicted || 0}`,
|
|
3237
|
-
color: 'gray',
|
|
3238
|
-
bold: false,
|
|
3239
|
-
selectable: false
|
|
3240
|
-
}];
|
|
3241
|
-
const groups = toExpandableRows(
|
|
3504
|
+
return toExpandableRows(
|
|
3242
3505
|
buildGitChangeGroups(snapshot),
|
|
3243
3506
|
git.available ? 'Working tree clean' : 'No git changes',
|
|
3244
3507
|
expandedGroups
|
|
3245
3508
|
);
|
|
3246
|
-
return [...headerRows, ...groups];
|
|
3247
3509
|
})()
|
|
3248
3510
|
: toLoadingRows('Loading git changes...', 'git-loading');
|
|
3249
3511
|
|
|
@@ -3279,6 +3541,9 @@ function createDashboardApp(deps) {
|
|
|
3279
3541
|
const focusedIndex = selectionBySection[focusedSection] || 0;
|
|
3280
3542
|
const selectedFocusedRow = getSelectedRow(focusedRows, focusedIndex);
|
|
3281
3543
|
const selectedFocusedFile = rowToFileEntry(selectedFocusedRow);
|
|
3544
|
+
const selectedGitRow = getSelectedRow(gitRows, selectionBySection['git-changes'] || 0);
|
|
3545
|
+
const selectedGitFile = rowToFileEntry(selectedGitRow);
|
|
3546
|
+
const firstGitFile = firstFileEntryFromRows(gitRows);
|
|
3282
3547
|
|
|
3283
3548
|
const refresh = useCallback(async (overrideSelectedWorktreeId = null) => {
|
|
3284
3549
|
const now = new Date().toISOString();
|
|
@@ -3636,6 +3901,22 @@ function createDashboardApp(deps) {
|
|
|
3636
3901
|
setSectionFocus((previous) => ({ ...previous, git: 'git-changes' }));
|
|
3637
3902
|
return;
|
|
3638
3903
|
}
|
|
3904
|
+
if (input === 'c') {
|
|
3905
|
+
setStatusLine('Git commit action is not wired yet (footer mirrors LazyGit command hierarchy).');
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
if (input === 'A') {
|
|
3909
|
+
setStatusLine('Git amend action is not wired yet (footer mirrors LazyGit command hierarchy).');
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
if (input === 'p') {
|
|
3913
|
+
setStatusLine('Git push action is not wired yet (footer mirrors LazyGit command hierarchy).');
|
|
3914
|
+
return;
|
|
3915
|
+
}
|
|
3916
|
+
if (input === 'P') {
|
|
3917
|
+
setStatusLine('Git pull action is not wired yet (footer mirrors LazyGit command hierarchy).');
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3639
3920
|
}
|
|
3640
3921
|
|
|
3641
3922
|
if (key.escape) {
|
|
@@ -3845,16 +4126,16 @@ function createDashboardApp(deps) {
|
|
|
3845
4126
|
useEffect(() => {
|
|
3846
4127
|
if (!stdout || typeof stdout.on !== 'function') {
|
|
3847
4128
|
setTerminalSize({
|
|
3848
|
-
columns: process.stdout.columns || 120,
|
|
3849
|
-
rows: process.stdout.rows || 40
|
|
4129
|
+
columns: Math.max(1, process.stdout.columns || 120),
|
|
4130
|
+
rows: Math.max(1, process.stdout.rows || 40)
|
|
3850
4131
|
});
|
|
3851
4132
|
return undefined;
|
|
3852
4133
|
}
|
|
3853
4134
|
|
|
3854
4135
|
const updateSize = () => {
|
|
3855
4136
|
setTerminalSize({
|
|
3856
|
-
columns: stdout.columns || process.stdout.columns || 120,
|
|
3857
|
-
rows: stdout.rows || process.stdout.rows || 40
|
|
4137
|
+
columns: Math.max(1, stdout.columns || process.stdout.columns || 120),
|
|
4138
|
+
rows: Math.max(1, stdout.rows || process.stdout.rows || 40)
|
|
3858
4139
|
});
|
|
3859
4140
|
|
|
3860
4141
|
// Resize in some terminals can leave stale frame rows behind.
|
|
@@ -3866,6 +4147,9 @@ function createDashboardApp(deps) {
|
|
|
3866
4147
|
|
|
3867
4148
|
updateSize();
|
|
3868
4149
|
stdout.on('resize', updateSize);
|
|
4150
|
+
if (process.stdout !== stdout && typeof process.stdout.on === 'function') {
|
|
4151
|
+
process.stdout.on('resize', updateSize);
|
|
4152
|
+
}
|
|
3869
4153
|
|
|
3870
4154
|
return () => {
|
|
3871
4155
|
if (typeof stdout.off === 'function') {
|
|
@@ -3873,6 +4157,13 @@ function createDashboardApp(deps) {
|
|
|
3873
4157
|
} else if (typeof stdout.removeListener === 'function') {
|
|
3874
4158
|
stdout.removeListener('resize', updateSize);
|
|
3875
4159
|
}
|
|
4160
|
+
if (process.stdout !== stdout) {
|
|
4161
|
+
if (typeof process.stdout.off === 'function') {
|
|
4162
|
+
process.stdout.off('resize', updateSize);
|
|
4163
|
+
} else if (typeof process.stdout.removeListener === 'function') {
|
|
4164
|
+
process.stdout.removeListener('resize', updateSize);
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
3876
4167
|
};
|
|
3877
4168
|
}, [stdout]);
|
|
3878
4169
|
|
|
@@ -3937,37 +4228,31 @@ function createDashboardApp(deps) {
|
|
|
3937
4228
|
const cols = Number.isFinite(terminalSize.columns) ? terminalSize.columns : (process.stdout.columns || 120);
|
|
3938
4229
|
const rows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
|
|
3939
4230
|
|
|
3940
|
-
const fullWidth =
|
|
4231
|
+
const fullWidth = resolveFrameWidth(cols);
|
|
3941
4232
|
const showFlowBar = availableFlowIds.length > 1;
|
|
3942
|
-
const
|
|
4233
|
+
const showCommandLogLine = rows >= 8;
|
|
4234
|
+
const showCommandStrip = rows >= 9;
|
|
3943
4235
|
const showErrorPanel = Boolean(error) && rows >= 18;
|
|
3944
4236
|
const showGlobalErrorPanel = showErrorPanel && ui.view !== 'health' && !ui.showHelp && !worktreeOverlayOpen;
|
|
3945
4237
|
const showErrorInline = Boolean(error) && !showErrorPanel && !worktreeOverlayOpen;
|
|
3946
4238
|
const showApprovalBanner = approvalGateLine !== '' && !ui.showHelp && !worktreeOverlayOpen;
|
|
3947
|
-
const
|
|
4239
|
+
const showLegacyStatusLine = statusLine !== '' && !showCommandLogLine;
|
|
3948
4240
|
const densePanels = rows <= 28 || cols <= 120;
|
|
3949
4241
|
|
|
3950
4242
|
const reservedRows =
|
|
3951
4243
|
2 +
|
|
3952
4244
|
(showFlowBar ? 1 : 0) +
|
|
3953
4245
|
(showApprovalBanner ? 1 : 0) +
|
|
3954
|
-
(
|
|
4246
|
+
(showCommandLogLine ? 1 : 0) +
|
|
4247
|
+
(showCommandStrip ? 1 : 0) +
|
|
3955
4248
|
(showGlobalErrorPanel ? 5 : 0) +
|
|
3956
4249
|
(showErrorInline ? 1 : 0) +
|
|
3957
|
-
(
|
|
4250
|
+
(showLegacyStatusLine ? 1 : 0);
|
|
3958
4251
|
const frameSafetyRows = 2;
|
|
3959
4252
|
const contentRowsBudget = Math.max(4, rows - reservedRows - frameSafetyRows);
|
|
3960
4253
|
const ultraCompact = rows <= 14;
|
|
3961
4254
|
const panelTitles = getPanelTitles(activeFlow, snapshot);
|
|
3962
|
-
const
|
|
3963
|
-
const mainPaneWidth = splitPreviewLayout
|
|
3964
|
-
? Math.max(34, Math.floor((fullWidth - 1) * 0.52))
|
|
3965
|
-
: fullWidth;
|
|
3966
|
-
const previewPaneWidth = splitPreviewLayout
|
|
3967
|
-
? Math.max(30, fullWidth - mainPaneWidth - 1)
|
|
3968
|
-
: fullWidth;
|
|
3969
|
-
const mainCompactWidth = Math.max(18, mainPaneWidth - 4);
|
|
3970
|
-
const previewCompactWidth = Math.max(18, previewPaneWidth - 4);
|
|
4255
|
+
const compactWidth = Math.max(18, fullWidth - 4);
|
|
3971
4256
|
|
|
3972
4257
|
const sectionLines = Object.fromEntries(
|
|
3973
4258
|
Object.entries(rowsBySection).map(([sectionKey, sectionRows]) => [
|
|
@@ -3976,17 +4261,27 @@ function createDashboardApp(deps) {
|
|
|
3976
4261
|
sectionRows,
|
|
3977
4262
|
selectionBySection[sectionKey] || 0,
|
|
3978
4263
|
icons,
|
|
3979
|
-
|
|
4264
|
+
compactWidth,
|
|
3980
4265
|
paneFocus === 'main' && focusedSection === sectionKey
|
|
3981
4266
|
)
|
|
3982
4267
|
])
|
|
3983
4268
|
);
|
|
3984
4269
|
const effectivePreviewTarget = previewTarget || selectedFocusedFile;
|
|
3985
4270
|
const previewLines = previewOpen
|
|
3986
|
-
? buildPreviewLines(effectivePreviewTarget,
|
|
4271
|
+
? buildPreviewLines(effectivePreviewTarget, compactWidth, previewScroll, {
|
|
3987
4272
|
fullDocument: overlayPreviewOpen
|
|
3988
4273
|
})
|
|
3989
4274
|
: [];
|
|
4275
|
+
const gitInlineDiffTarget = selectedGitFile || firstGitFile || previewTarget || null;
|
|
4276
|
+
const gitInlineDiffLines = ui.view === 'git'
|
|
4277
|
+
? buildPreviewLines(gitInlineDiffTarget, compactWidth, previewOpen && paneFocus === 'preview' ? previewScroll : 0, {
|
|
4278
|
+
fullDocument: false
|
|
4279
|
+
})
|
|
4280
|
+
: [];
|
|
4281
|
+
const gitStatusPanelLines = ui.view === 'git' ? buildGitStatusPanelLines(snapshot) : [];
|
|
4282
|
+
const gitBranchesPanelLines = ui.view === 'git' ? buildGitBranchesPanelLines(snapshot) : [];
|
|
4283
|
+
const gitCommitsPanelLines = ui.view === 'git' ? buildGitCommitsPanelLines(snapshot) : [];
|
|
4284
|
+
const gitStashPanelLines = ui.view === 'git' ? buildGitStashPanelLines(snapshot) : [];
|
|
3990
4285
|
|
|
3991
4286
|
const shortcutsOverlayLines = buildHelpOverlayLines({
|
|
3992
4287
|
view: ui.view,
|
|
@@ -4004,6 +4299,17 @@ function createDashboardApp(deps) {
|
|
|
4004
4299
|
availableFlowCount: availableFlowIds.length,
|
|
4005
4300
|
hasWorktrees: worktreeSectionEnabled
|
|
4006
4301
|
});
|
|
4302
|
+
const commandStripText = buildLazyGitCommandStrip(ui.view, {
|
|
4303
|
+
hasWorktrees: worktreeSectionEnabled,
|
|
4304
|
+
previewOpen
|
|
4305
|
+
});
|
|
4306
|
+
const commandLogLine = buildLazyGitCommandLogLine({
|
|
4307
|
+
statusLine,
|
|
4308
|
+
activeFlow,
|
|
4309
|
+
watchEnabled,
|
|
4310
|
+
watchStatus,
|
|
4311
|
+
selectedWorktreeLabel
|
|
4312
|
+
});
|
|
4007
4313
|
|
|
4008
4314
|
let panelCandidates;
|
|
4009
4315
|
if (ui.showHelp) {
|
|
@@ -4083,11 +4389,41 @@ function createDashboardApp(deps) {
|
|
|
4083
4389
|
}
|
|
4084
4390
|
} else if (ui.view === 'git') {
|
|
4085
4391
|
panelCandidates = [
|
|
4392
|
+
{
|
|
4393
|
+
key: 'git-status',
|
|
4394
|
+
title: '[1]-Status',
|
|
4395
|
+
lines: gitStatusPanelLines,
|
|
4396
|
+
borderColor: 'green'
|
|
4397
|
+
},
|
|
4086
4398
|
{
|
|
4087
4399
|
key: 'git-changes',
|
|
4088
|
-
title:
|
|
4400
|
+
title: '[2]-Files',
|
|
4089
4401
|
lines: sectionLines['git-changes'],
|
|
4090
4402
|
borderColor: 'yellow'
|
|
4403
|
+
},
|
|
4404
|
+
{
|
|
4405
|
+
key: 'git-branches',
|
|
4406
|
+
title: '[3]-Local branches',
|
|
4407
|
+
lines: gitBranchesPanelLines,
|
|
4408
|
+
borderColor: 'magenta'
|
|
4409
|
+
},
|
|
4410
|
+
{
|
|
4411
|
+
key: 'git-commits',
|
|
4412
|
+
title: '[4]-Commits',
|
|
4413
|
+
lines: gitCommitsPanelLines,
|
|
4414
|
+
borderColor: 'cyan'
|
|
4415
|
+
},
|
|
4416
|
+
{
|
|
4417
|
+
key: 'git-stash',
|
|
4418
|
+
title: '[5]-Stash',
|
|
4419
|
+
lines: gitStashPanelLines,
|
|
4420
|
+
borderColor: 'blue'
|
|
4421
|
+
},
|
|
4422
|
+
{
|
|
4423
|
+
key: 'git-diff',
|
|
4424
|
+
title: '[0]-Unstaged changes',
|
|
4425
|
+
lines: gitInlineDiffLines,
|
|
4426
|
+
borderColor: 'yellow'
|
|
4091
4427
|
}
|
|
4092
4428
|
];
|
|
4093
4429
|
} else {
|
|
@@ -4124,7 +4460,7 @@ function createDashboardApp(deps) {
|
|
|
4124
4460
|
}
|
|
4125
4461
|
}
|
|
4126
4462
|
|
|
4127
|
-
if (!ui.showHelp && previewOpen && !overlayPreviewOpen &&
|
|
4463
|
+
if (!ui.showHelp && previewOpen && !overlayPreviewOpen && ui.view !== 'git') {
|
|
4128
4464
|
panelCandidates.push({
|
|
4129
4465
|
key: 'preview',
|
|
4130
4466
|
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
@@ -4133,9 +4469,15 @@ function createDashboardApp(deps) {
|
|
|
4133
4469
|
});
|
|
4134
4470
|
}
|
|
4135
4471
|
|
|
4136
|
-
if (ultraCompact
|
|
4472
|
+
if (ultraCompact) {
|
|
4137
4473
|
if (previewOpen) {
|
|
4138
|
-
panelCandidates = panelCandidates.filter((panel) =>
|
|
4474
|
+
panelCandidates = panelCandidates.filter((panel) =>
|
|
4475
|
+
panel && (
|
|
4476
|
+
panel.key === focusedSection
|
|
4477
|
+
|| panel.key === 'preview'
|
|
4478
|
+
|| (ui.view === 'git' && panel.key === 'git-diff')
|
|
4479
|
+
)
|
|
4480
|
+
);
|
|
4139
4481
|
} else {
|
|
4140
4482
|
const focusedPanel = panelCandidates.find((panel) => panel?.key === focusedSection);
|
|
4141
4483
|
panelCandidates = [focusedPanel || panelCandidates[0]];
|
|
@@ -4143,6 +4485,13 @@ function createDashboardApp(deps) {
|
|
|
4143
4485
|
}
|
|
4144
4486
|
|
|
4145
4487
|
const panels = allocateSingleColumnPanels(panelCandidates, contentRowsBudget);
|
|
4488
|
+
const lazyGitHierarchyLayout = ui.view === 'git'
|
|
4489
|
+
&& !ui.showHelp
|
|
4490
|
+
&& !worktreeOverlayOpen
|
|
4491
|
+
&& !overlayPreviewOpen
|
|
4492
|
+
&& !ultraCompact
|
|
4493
|
+
&& fullWidth >= 96
|
|
4494
|
+
&& panelCandidates.length > 1;
|
|
4146
4495
|
|
|
4147
4496
|
const renderPanel = (panel, index, width, isFocused) => React.createElement(SectionPanel, {
|
|
4148
4497
|
key: panel.key,
|
|
@@ -4157,14 +4506,22 @@ function createDashboardApp(deps) {
|
|
|
4157
4506
|
});
|
|
4158
4507
|
|
|
4159
4508
|
let contentNode;
|
|
4160
|
-
if (
|
|
4161
|
-
const
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4509
|
+
if (lazyGitHierarchyLayout) {
|
|
4510
|
+
const preferredRightPanel = panelCandidates.find((panel) => panel?.key === 'git-diff')
|
|
4511
|
+
|| (previewOpen && !overlayPreviewOpen
|
|
4512
|
+
? panelCandidates.find((panel) => panel?.key === 'preview')
|
|
4513
|
+
: null);
|
|
4514
|
+
const focusedPanel = panelCandidates.find((panel) => panel?.key === focusedSection);
|
|
4515
|
+
const rightPanelBase = preferredRightPanel
|
|
4516
|
+
|| focusedPanel
|
|
4517
|
+
|| panelCandidates[panelCandidates.length - 1];
|
|
4518
|
+
const leftCandidates = panelCandidates.filter((panel) => panel?.key !== rightPanelBase?.key);
|
|
4519
|
+
const leftWidth = Math.max(30, Math.min(Math.floor(fullWidth * 0.38), fullWidth - 36));
|
|
4520
|
+
const rightWidth = Math.max(34, fullWidth - leftWidth - 1);
|
|
4521
|
+
const leftPanels = allocateSingleColumnPanels(leftCandidates, contentRowsBudget);
|
|
4522
|
+
const rightPanel = {
|
|
4523
|
+
...rightPanelBase,
|
|
4524
|
+
maxLines: Math.max(4, contentRowsBudget)
|
|
4168
4525
|
};
|
|
4169
4526
|
|
|
4170
4527
|
contentNode = React.createElement(
|
|
@@ -4172,33 +4529,41 @@ function createDashboardApp(deps) {
|
|
|
4172
4529
|
{ width: fullWidth, flexDirection: 'row' },
|
|
4173
4530
|
React.createElement(
|
|
4174
4531
|
Box,
|
|
4175
|
-
{ width:
|
|
4176
|
-
...
|
|
4532
|
+
{ width: leftWidth, flexDirection: 'column' },
|
|
4533
|
+
...leftPanels.map((panel, index) => React.createElement(SectionPanel, {
|
|
4177
4534
|
key: panel.key,
|
|
4178
4535
|
title: panel.title,
|
|
4179
4536
|
lines: panel.lines,
|
|
4180
|
-
width:
|
|
4537
|
+
width: leftWidth,
|
|
4181
4538
|
maxLines: panel.maxLines,
|
|
4182
4539
|
borderColor: panel.borderColor,
|
|
4183
|
-
marginBottom: densePanels ? 0 : (index ===
|
|
4540
|
+
marginBottom: densePanels ? 0 : (index === leftPanels.length - 1 ? 0 : 1),
|
|
4184
4541
|
dense: densePanels,
|
|
4185
4542
|
focused: paneFocus === 'main' && panel.key === focusedSection
|
|
4186
4543
|
}))
|
|
4187
4544
|
),
|
|
4188
|
-
React.createElement(Box, { width: 1 }, React.createElement(Text, null, ' ')),
|
|
4189
4545
|
React.createElement(
|
|
4190
4546
|
Box,
|
|
4191
|
-
{ width:
|
|
4547
|
+
{ width: 1, justifyContent: 'center' },
|
|
4548
|
+
React.createElement(Text, { color: 'gray' }, '│')
|
|
4549
|
+
),
|
|
4550
|
+
React.createElement(
|
|
4551
|
+
Box,
|
|
4552
|
+
{ width: rightWidth, flexDirection: 'column' },
|
|
4192
4553
|
React.createElement(SectionPanel, {
|
|
4193
|
-
key:
|
|
4194
|
-
title:
|
|
4195
|
-
lines:
|
|
4196
|
-
width:
|
|
4197
|
-
maxLines:
|
|
4198
|
-
borderColor:
|
|
4554
|
+
key: rightPanel.key,
|
|
4555
|
+
title: rightPanel.title,
|
|
4556
|
+
lines: rightPanel.lines,
|
|
4557
|
+
width: rightWidth,
|
|
4558
|
+
maxLines: rightPanel.maxLines,
|
|
4559
|
+
borderColor: rightPanel.borderColor,
|
|
4199
4560
|
marginBottom: 0,
|
|
4200
4561
|
dense: densePanels,
|
|
4201
|
-
focused:
|
|
4562
|
+
focused: rightPanel.key === 'preview'
|
|
4563
|
+
? paneFocus === 'preview'
|
|
4564
|
+
: (rightPanel.key === 'git-diff'
|
|
4565
|
+
? true
|
|
4566
|
+
: (paneFocus === 'main' && rightPanel.key === focusedSection))
|
|
4202
4567
|
})
|
|
4203
4568
|
)
|
|
4204
4569
|
);
|
|
@@ -4248,12 +4613,25 @@ function createDashboardApp(deps) {
|
|
|
4248
4613
|
})
|
|
4249
4614
|
: null,
|
|
4250
4615
|
...(Array.isArray(contentNode) ? contentNode : [contentNode]),
|
|
4251
|
-
|
|
4616
|
+
showLegacyStatusLine
|
|
4252
4617
|
? React.createElement(Text, { color: 'yellow' }, truncate(statusLine, fullWidth))
|
|
4253
4618
|
: null,
|
|
4254
|
-
|
|
4255
|
-
? React.createElement(
|
|
4256
|
-
|
|
4619
|
+
showCommandLogLine
|
|
4620
|
+
? React.createElement(
|
|
4621
|
+
Text,
|
|
4622
|
+
{ color: 'white', backgroundColor: 'gray', bold: true },
|
|
4623
|
+
truncate(commandLogLine, fullWidth)
|
|
4624
|
+
)
|
|
4625
|
+
: null,
|
|
4626
|
+
showCommandStrip
|
|
4627
|
+
? React.createElement(
|
|
4628
|
+
Text,
|
|
4629
|
+
{ color: 'white', backgroundColor: 'blue', bold: true },
|
|
4630
|
+
truncate(commandStripText, fullWidth)
|
|
4631
|
+
)
|
|
4632
|
+
: (rows >= 10
|
|
4633
|
+
? React.createElement(Text, { color: 'gray' }, truncate(quickHelpText, fullWidth))
|
|
4634
|
+
: null)
|
|
4257
4635
|
);
|
|
4258
4636
|
}
|
|
4259
4637
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
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": {
|