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.
@@ -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(18, width - 4);
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 = Math.max(40, cols - 1);
4025
+ const fullWidth = resolveFrameWidth(cols);
3941
4026
  const showFlowBar = availableFlowIds.length > 1;
3942
- const showFooterHelpLine = rows >= 10;
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 showStatusLine = statusLine !== '';
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
- (showFooterHelpLine ? 1 : 0) +
4040
+ (showCommandLogLine ? 1 : 0) +
4041
+ (showCommandStrip ? 1 : 0) +
3955
4042
  (showGlobalErrorPanel ? 5 : 0) +
3956
4043
  (showErrorInline ? 1 : 0) +
3957
- (showStatusLine ? 1 : 0);
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 splitPreviewLayout = previewOpen && !overlayPreviewOpen && !ui.showHelp && !worktreeOverlayOpen && cols >= 110 && rows >= 16;
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
- mainCompactWidth,
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, previewCompactWidth, previewScroll, {
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 && !splitPreviewLayout) {
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 && !splitPreviewLayout) {
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 (splitPreviewLayout && !overlayPreviewOpen) {
4161
- const previewBodyLines = Math.max(1, contentRowsBudget - 3);
4162
- const previewPanel = {
4163
- key: 'preview-split',
4164
- title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
4165
- lines: previewLines,
4166
- borderColor: 'magenta',
4167
- maxLines: previewBodyLines
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: mainPaneWidth, flexDirection: 'column' },
4176
- ...panels.map((panel, index) => React.createElement(SectionPanel, {
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: mainPaneWidth,
4283
+ width: leftWidth,
4181
4284
  maxLines: panel.maxLines,
4182
4285
  borderColor: panel.borderColor,
4183
- marginBottom: densePanels ? 0 : (index === panels.length - 1 ? 0 : 1),
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: previewPaneWidth, flexDirection: 'column' },
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: previewPanel.key,
4194
- title: previewPanel.title,
4195
- lines: previewPanel.lines,
4196
- width: previewPaneWidth,
4197
- maxLines: previewPanel.maxLines,
4198
- borderColor: previewPanel.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: paneFocus === 'preview'
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
- statusLine !== ''
4360
+ showLegacyStatusLine
4252
4361
  ? React.createElement(Text, { color: 'yellow' }, truncate(statusLine, fullWidth))
4253
4362
  : null,
4254
- showFooterHelpLine
4255
- ? React.createElement(Text, { color: 'gray' }, truncate(quickHelpText, fullWidth))
4256
- : null
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.52",
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": {