specsmd 0.1.52 → 0.1.53
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 +170 -48
- 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 {
|
|
@@ -2522,6 +2527,8 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
2522
2527
|
parts.push('b worktrees', 'u others');
|
|
2523
2528
|
}
|
|
2524
2529
|
parts.push('a current', 'f files');
|
|
2530
|
+
} else if (view === 'git') {
|
|
2531
|
+
parts.push('d changes', 'c/A/p/P git(soon)');
|
|
2525
2532
|
}
|
|
2526
2533
|
parts.push(`tab1 ${activeLabel}`);
|
|
2527
2534
|
|
|
@@ -2533,6 +2540,57 @@ function buildQuickHelpText(view, options = {}) {
|
|
|
2533
2540
|
return parts.join(' | ');
|
|
2534
2541
|
}
|
|
2535
2542
|
|
|
2543
|
+
function buildLazyGitCommandStrip(view, options = {}) {
|
|
2544
|
+
const {
|
|
2545
|
+
hasWorktrees = false,
|
|
2546
|
+
previewOpen = false
|
|
2547
|
+
} = options;
|
|
2548
|
+
|
|
2549
|
+
const parts = [];
|
|
2550
|
+
|
|
2551
|
+
if (view === 'runs') {
|
|
2552
|
+
if (hasWorktrees) {
|
|
2553
|
+
parts.push('b worktrees');
|
|
2554
|
+
}
|
|
2555
|
+
parts.push('a current', 'f files', 'enter expand');
|
|
2556
|
+
} else if (view === 'intents') {
|
|
2557
|
+
parts.push('n next', 'x completed', 'enter expand');
|
|
2558
|
+
} else if (view === 'completed') {
|
|
2559
|
+
parts.push('c completed', 'enter expand');
|
|
2560
|
+
} else if (view === 'health') {
|
|
2561
|
+
parts.push('s standards', 't stats', 'w warnings');
|
|
2562
|
+
} else if (view === 'git') {
|
|
2563
|
+
parts.push('d changes', 'space preview', 'c commit (soon)', 'A amend (soon)', 'p push (soon)', 'P pull (soon)');
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
if (previewOpen) {
|
|
2567
|
+
parts.push('tab pane', 'j/k scroll');
|
|
2568
|
+
} else {
|
|
2569
|
+
parts.push('v preview');
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
parts.push('1-5 views', 'g/G panels', 'r refresh', '? help', 'q quit');
|
|
2573
|
+
return parts.join(' | ');
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
function buildLazyGitCommandLogLine(options = {}) {
|
|
2577
|
+
const {
|
|
2578
|
+
statusLine = '',
|
|
2579
|
+
activeFlow = 'fire',
|
|
2580
|
+
watchEnabled = true,
|
|
2581
|
+
watchStatus = 'watching',
|
|
2582
|
+
selectedWorktreeLabel = null
|
|
2583
|
+
} = options;
|
|
2584
|
+
|
|
2585
|
+
if (typeof statusLine === 'string' && statusLine.trim() !== '') {
|
|
2586
|
+
return `Command Log | ${statusLine}`;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
const watchLabel = watchEnabled ? watchStatus : 'off';
|
|
2590
|
+
const worktreeSegment = selectedWorktreeLabel ? ` | wt:${selectedWorktreeLabel}` : '';
|
|
2591
|
+
return `Command Log | flow:${String(activeFlow || 'fire').toUpperCase()} | watch:${watchLabel}${worktreeSegment} | ready`;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2536
2594
|
function buildHelpOverlayLines(options = {}) {
|
|
2537
2595
|
const {
|
|
2538
2596
|
view = 'runs',
|
|
@@ -2592,6 +2650,7 @@ function buildHelpOverlayLines(options = {}) {
|
|
|
2592
2650
|
{ text: '', color: undefined, bold: false },
|
|
2593
2651
|
{ text: 'Tab 5 Git Changes', color: 'yellow', bold: true },
|
|
2594
2652
|
'select changed files and preview diffs',
|
|
2653
|
+
'commit/push/pull shortcuts are shown in footer for LazyGit-style hierarchy',
|
|
2595
2654
|
{ text: '', color: undefined, bold: false },
|
|
2596
2655
|
{ text: `Current view: ${String(view || 'runs').toUpperCase()}`, color: 'gray', bold: false }
|
|
2597
2656
|
);
|
|
@@ -2864,7 +2923,7 @@ function createDashboardApp(deps) {
|
|
|
2864
2923
|
focused
|
|
2865
2924
|
} = props;
|
|
2866
2925
|
|
|
2867
|
-
const contentWidth = Math.max(
|
|
2926
|
+
const contentWidth = Math.max(1, width - (dense ? 2 : 4));
|
|
2868
2927
|
const visibleLines = fitLines(lines, maxLines, contentWidth);
|
|
2869
2928
|
const panelBorderColor = focused ? 'cyan' : (borderColor || 'gray');
|
|
2870
2929
|
const titleColor = focused ? 'black' : 'cyan';
|
|
@@ -3636,6 +3695,22 @@ function createDashboardApp(deps) {
|
|
|
3636
3695
|
setSectionFocus((previous) => ({ ...previous, git: 'git-changes' }));
|
|
3637
3696
|
return;
|
|
3638
3697
|
}
|
|
3698
|
+
if (input === 'c') {
|
|
3699
|
+
setStatusLine('Git commit action is not wired yet (footer mirrors LazyGit command hierarchy).');
|
|
3700
|
+
return;
|
|
3701
|
+
}
|
|
3702
|
+
if (input === 'A') {
|
|
3703
|
+
setStatusLine('Git amend action is not wired yet (footer mirrors LazyGit command hierarchy).');
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
if (input === 'p') {
|
|
3707
|
+
setStatusLine('Git push action is not wired yet (footer mirrors LazyGit command hierarchy).');
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
3710
|
+
if (input === 'P') {
|
|
3711
|
+
setStatusLine('Git pull action is not wired yet (footer mirrors LazyGit command hierarchy).');
|
|
3712
|
+
return;
|
|
3713
|
+
}
|
|
3639
3714
|
}
|
|
3640
3715
|
|
|
3641
3716
|
if (key.escape) {
|
|
@@ -3845,16 +3920,16 @@ function createDashboardApp(deps) {
|
|
|
3845
3920
|
useEffect(() => {
|
|
3846
3921
|
if (!stdout || typeof stdout.on !== 'function') {
|
|
3847
3922
|
setTerminalSize({
|
|
3848
|
-
columns: process.stdout.columns || 120,
|
|
3849
|
-
rows: process.stdout.rows || 40
|
|
3923
|
+
columns: Math.max(1, process.stdout.columns || 120),
|
|
3924
|
+
rows: Math.max(1, process.stdout.rows || 40)
|
|
3850
3925
|
});
|
|
3851
3926
|
return undefined;
|
|
3852
3927
|
}
|
|
3853
3928
|
|
|
3854
3929
|
const updateSize = () => {
|
|
3855
3930
|
setTerminalSize({
|
|
3856
|
-
columns: stdout.columns || process.stdout.columns || 120,
|
|
3857
|
-
rows: stdout.rows || process.stdout.rows || 40
|
|
3931
|
+
columns: Math.max(1, stdout.columns || process.stdout.columns || 120),
|
|
3932
|
+
rows: Math.max(1, stdout.rows || process.stdout.rows || 40)
|
|
3858
3933
|
});
|
|
3859
3934
|
|
|
3860
3935
|
// Resize in some terminals can leave stale frame rows behind.
|
|
@@ -3866,6 +3941,9 @@ function createDashboardApp(deps) {
|
|
|
3866
3941
|
|
|
3867
3942
|
updateSize();
|
|
3868
3943
|
stdout.on('resize', updateSize);
|
|
3944
|
+
if (process.stdout !== stdout && typeof process.stdout.on === 'function') {
|
|
3945
|
+
process.stdout.on('resize', updateSize);
|
|
3946
|
+
}
|
|
3869
3947
|
|
|
3870
3948
|
return () => {
|
|
3871
3949
|
if (typeof stdout.off === 'function') {
|
|
@@ -3873,6 +3951,13 @@ function createDashboardApp(deps) {
|
|
|
3873
3951
|
} else if (typeof stdout.removeListener === 'function') {
|
|
3874
3952
|
stdout.removeListener('resize', updateSize);
|
|
3875
3953
|
}
|
|
3954
|
+
if (process.stdout !== stdout) {
|
|
3955
|
+
if (typeof process.stdout.off === 'function') {
|
|
3956
|
+
process.stdout.off('resize', updateSize);
|
|
3957
|
+
} else if (typeof process.stdout.removeListener === 'function') {
|
|
3958
|
+
process.stdout.removeListener('resize', updateSize);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3876
3961
|
};
|
|
3877
3962
|
}, [stdout]);
|
|
3878
3963
|
|
|
@@ -3937,37 +4022,31 @@ function createDashboardApp(deps) {
|
|
|
3937
4022
|
const cols = Number.isFinite(terminalSize.columns) ? terminalSize.columns : (process.stdout.columns || 120);
|
|
3938
4023
|
const rows = Number.isFinite(terminalSize.rows) ? terminalSize.rows : (process.stdout.rows || 40);
|
|
3939
4024
|
|
|
3940
|
-
const fullWidth =
|
|
4025
|
+
const fullWidth = resolveFrameWidth(cols);
|
|
3941
4026
|
const showFlowBar = availableFlowIds.length > 1;
|
|
3942
|
-
const
|
|
4027
|
+
const showCommandLogLine = rows >= 8;
|
|
4028
|
+
const showCommandStrip = rows >= 9;
|
|
3943
4029
|
const showErrorPanel = Boolean(error) && rows >= 18;
|
|
3944
4030
|
const showGlobalErrorPanel = showErrorPanel && ui.view !== 'health' && !ui.showHelp && !worktreeOverlayOpen;
|
|
3945
4031
|
const showErrorInline = Boolean(error) && !showErrorPanel && !worktreeOverlayOpen;
|
|
3946
4032
|
const showApprovalBanner = approvalGateLine !== '' && !ui.showHelp && !worktreeOverlayOpen;
|
|
3947
|
-
const
|
|
4033
|
+
const showLegacyStatusLine = statusLine !== '' && !showCommandLogLine;
|
|
3948
4034
|
const densePanels = rows <= 28 || cols <= 120;
|
|
3949
4035
|
|
|
3950
4036
|
const reservedRows =
|
|
3951
4037
|
2 +
|
|
3952
4038
|
(showFlowBar ? 1 : 0) +
|
|
3953
4039
|
(showApprovalBanner ? 1 : 0) +
|
|
3954
|
-
(
|
|
4040
|
+
(showCommandLogLine ? 1 : 0) +
|
|
4041
|
+
(showCommandStrip ? 1 : 0) +
|
|
3955
4042
|
(showGlobalErrorPanel ? 5 : 0) +
|
|
3956
4043
|
(showErrorInline ? 1 : 0) +
|
|
3957
|
-
(
|
|
4044
|
+
(showLegacyStatusLine ? 1 : 0);
|
|
3958
4045
|
const frameSafetyRows = 2;
|
|
3959
4046
|
const contentRowsBudget = Math.max(4, rows - reservedRows - frameSafetyRows);
|
|
3960
4047
|
const ultraCompact = rows <= 14;
|
|
3961
4048
|
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);
|
|
4049
|
+
const compactWidth = Math.max(18, fullWidth - 4);
|
|
3971
4050
|
|
|
3972
4051
|
const sectionLines = Object.fromEntries(
|
|
3973
4052
|
Object.entries(rowsBySection).map(([sectionKey, sectionRows]) => [
|
|
@@ -3976,14 +4055,14 @@ function createDashboardApp(deps) {
|
|
|
3976
4055
|
sectionRows,
|
|
3977
4056
|
selectionBySection[sectionKey] || 0,
|
|
3978
4057
|
icons,
|
|
3979
|
-
|
|
4058
|
+
compactWidth,
|
|
3980
4059
|
paneFocus === 'main' && focusedSection === sectionKey
|
|
3981
4060
|
)
|
|
3982
4061
|
])
|
|
3983
4062
|
);
|
|
3984
4063
|
const effectivePreviewTarget = previewTarget || selectedFocusedFile;
|
|
3985
4064
|
const previewLines = previewOpen
|
|
3986
|
-
? buildPreviewLines(effectivePreviewTarget,
|
|
4065
|
+
? buildPreviewLines(effectivePreviewTarget, compactWidth, previewScroll, {
|
|
3987
4066
|
fullDocument: overlayPreviewOpen
|
|
3988
4067
|
})
|
|
3989
4068
|
: [];
|
|
@@ -4004,6 +4083,17 @@ function createDashboardApp(deps) {
|
|
|
4004
4083
|
availableFlowCount: availableFlowIds.length,
|
|
4005
4084
|
hasWorktrees: worktreeSectionEnabled
|
|
4006
4085
|
});
|
|
4086
|
+
const commandStripText = buildLazyGitCommandStrip(ui.view, {
|
|
4087
|
+
hasWorktrees: worktreeSectionEnabled,
|
|
4088
|
+
previewOpen
|
|
4089
|
+
});
|
|
4090
|
+
const commandLogLine = buildLazyGitCommandLogLine({
|
|
4091
|
+
statusLine,
|
|
4092
|
+
activeFlow,
|
|
4093
|
+
watchEnabled,
|
|
4094
|
+
watchStatus,
|
|
4095
|
+
selectedWorktreeLabel
|
|
4096
|
+
});
|
|
4007
4097
|
|
|
4008
4098
|
let panelCandidates;
|
|
4009
4099
|
if (ui.showHelp) {
|
|
@@ -4124,7 +4214,7 @@ function createDashboardApp(deps) {
|
|
|
4124
4214
|
}
|
|
4125
4215
|
}
|
|
4126
4216
|
|
|
4127
|
-
if (!ui.showHelp && previewOpen && !overlayPreviewOpen
|
|
4217
|
+
if (!ui.showHelp && previewOpen && !overlayPreviewOpen) {
|
|
4128
4218
|
panelCandidates.push({
|
|
4129
4219
|
key: 'preview',
|
|
4130
4220
|
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
@@ -4133,7 +4223,7 @@ function createDashboardApp(deps) {
|
|
|
4133
4223
|
});
|
|
4134
4224
|
}
|
|
4135
4225
|
|
|
4136
|
-
if (ultraCompact
|
|
4226
|
+
if (ultraCompact) {
|
|
4137
4227
|
if (previewOpen) {
|
|
4138
4228
|
panelCandidates = panelCandidates.filter((panel) => panel && (panel.key === focusedSection || panel.key === 'preview'));
|
|
4139
4229
|
} else {
|
|
@@ -4143,6 +4233,12 @@ function createDashboardApp(deps) {
|
|
|
4143
4233
|
}
|
|
4144
4234
|
|
|
4145
4235
|
const panels = allocateSingleColumnPanels(panelCandidates, contentRowsBudget);
|
|
4236
|
+
const lazyGitHierarchyLayout = !ui.showHelp
|
|
4237
|
+
&& !worktreeOverlayOpen
|
|
4238
|
+
&& !overlayPreviewOpen
|
|
4239
|
+
&& !ultraCompact
|
|
4240
|
+
&& fullWidth >= 96
|
|
4241
|
+
&& panelCandidates.length > 1;
|
|
4146
4242
|
|
|
4147
4243
|
const renderPanel = (panel, index, width, isFocused) => React.createElement(SectionPanel, {
|
|
4148
4244
|
key: panel.key,
|
|
@@ -4157,14 +4253,21 @@ function createDashboardApp(deps) {
|
|
|
4157
4253
|
});
|
|
4158
4254
|
|
|
4159
4255
|
let contentNode;
|
|
4160
|
-
if (
|
|
4161
|
-
const
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4256
|
+
if (lazyGitHierarchyLayout) {
|
|
4257
|
+
const preferredRightPanel = previewOpen && !overlayPreviewOpen
|
|
4258
|
+
? panelCandidates.find((panel) => panel?.key === 'preview')
|
|
4259
|
+
: null;
|
|
4260
|
+
const focusedPanel = panelCandidates.find((panel) => panel?.key === focusedSection);
|
|
4261
|
+
const rightPanelBase = preferredRightPanel
|
|
4262
|
+
|| focusedPanel
|
|
4263
|
+
|| panelCandidates[panelCandidates.length - 1];
|
|
4264
|
+
const leftCandidates = panelCandidates.filter((panel) => panel?.key !== rightPanelBase?.key);
|
|
4265
|
+
const leftWidth = Math.max(30, Math.min(Math.floor(fullWidth * 0.38), fullWidth - 36));
|
|
4266
|
+
const rightWidth = Math.max(34, fullWidth - leftWidth - 1);
|
|
4267
|
+
const leftPanels = allocateSingleColumnPanels(leftCandidates, contentRowsBudget);
|
|
4268
|
+
const rightPanel = {
|
|
4269
|
+
...rightPanelBase,
|
|
4270
|
+
maxLines: Math.max(4, contentRowsBudget)
|
|
4168
4271
|
};
|
|
4169
4272
|
|
|
4170
4273
|
contentNode = React.createElement(
|
|
@@ -4172,33 +4275,39 @@ function createDashboardApp(deps) {
|
|
|
4172
4275
|
{ width: fullWidth, flexDirection: 'row' },
|
|
4173
4276
|
React.createElement(
|
|
4174
4277
|
Box,
|
|
4175
|
-
{ width:
|
|
4176
|
-
...
|
|
4278
|
+
{ width: leftWidth, flexDirection: 'column' },
|
|
4279
|
+
...leftPanels.map((panel, index) => React.createElement(SectionPanel, {
|
|
4177
4280
|
key: panel.key,
|
|
4178
4281
|
title: panel.title,
|
|
4179
4282
|
lines: panel.lines,
|
|
4180
|
-
width:
|
|
4283
|
+
width: leftWidth,
|
|
4181
4284
|
maxLines: panel.maxLines,
|
|
4182
4285
|
borderColor: panel.borderColor,
|
|
4183
|
-
marginBottom: densePanels ? 0 : (index ===
|
|
4286
|
+
marginBottom: densePanels ? 0 : (index === leftPanels.length - 1 ? 0 : 1),
|
|
4184
4287
|
dense: densePanels,
|
|
4185
4288
|
focused: paneFocus === 'main' && panel.key === focusedSection
|
|
4186
4289
|
}))
|
|
4187
4290
|
),
|
|
4188
|
-
React.createElement(Box, { width: 1 }, React.createElement(Text, null, ' ')),
|
|
4189
4291
|
React.createElement(
|
|
4190
4292
|
Box,
|
|
4191
|
-
{ width:
|
|
4293
|
+
{ width: 1, justifyContent: 'center' },
|
|
4294
|
+
React.createElement(Text, { color: 'gray' }, '│')
|
|
4295
|
+
),
|
|
4296
|
+
React.createElement(
|
|
4297
|
+
Box,
|
|
4298
|
+
{ width: rightWidth, flexDirection: 'column' },
|
|
4192
4299
|
React.createElement(SectionPanel, {
|
|
4193
|
-
key:
|
|
4194
|
-
title:
|
|
4195
|
-
lines:
|
|
4196
|
-
width:
|
|
4197
|
-
maxLines:
|
|
4198
|
-
borderColor:
|
|
4300
|
+
key: rightPanel.key,
|
|
4301
|
+
title: rightPanel.title,
|
|
4302
|
+
lines: rightPanel.lines,
|
|
4303
|
+
width: rightWidth,
|
|
4304
|
+
maxLines: rightPanel.maxLines,
|
|
4305
|
+
borderColor: rightPanel.borderColor,
|
|
4199
4306
|
marginBottom: 0,
|
|
4200
4307
|
dense: densePanels,
|
|
4201
|
-
focused:
|
|
4308
|
+
focused: rightPanel.key === 'preview'
|
|
4309
|
+
? paneFocus === 'preview'
|
|
4310
|
+
: (paneFocus === 'main' && rightPanel.key === focusedSection)
|
|
4202
4311
|
})
|
|
4203
4312
|
)
|
|
4204
4313
|
);
|
|
@@ -4248,12 +4357,25 @@ function createDashboardApp(deps) {
|
|
|
4248
4357
|
})
|
|
4249
4358
|
: null,
|
|
4250
4359
|
...(Array.isArray(contentNode) ? contentNode : [contentNode]),
|
|
4251
|
-
|
|
4360
|
+
showLegacyStatusLine
|
|
4252
4361
|
? React.createElement(Text, { color: 'yellow' }, truncate(statusLine, fullWidth))
|
|
4253
4362
|
: null,
|
|
4254
|
-
|
|
4255
|
-
? React.createElement(
|
|
4256
|
-
|
|
4363
|
+
showCommandLogLine
|
|
4364
|
+
? React.createElement(
|
|
4365
|
+
Text,
|
|
4366
|
+
{ color: 'white', backgroundColor: 'gray', bold: true },
|
|
4367
|
+
truncate(commandLogLine, fullWidth)
|
|
4368
|
+
)
|
|
4369
|
+
: null,
|
|
4370
|
+
showCommandStrip
|
|
4371
|
+
? React.createElement(
|
|
4372
|
+
Text,
|
|
4373
|
+
{ color: 'white', backgroundColor: 'blue', bold: true },
|
|
4374
|
+
truncate(commandStripText, fullWidth)
|
|
4375
|
+
)
|
|
4376
|
+
: (rows >= 10
|
|
4377
|
+
? React.createElement(Text, { color: 'gray' }, truncate(quickHelpText, fullWidth))
|
|
4378
|
+
: null)
|
|
4257
4379
|
);
|
|
4258
4380
|
}
|
|
4259
4381
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.53",
|
|
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": {
|