specsmd 0.1.39 → 0.1.40

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.
@@ -172,12 +172,19 @@ async function runFlowDashboard(options, flow, availableFlows = []) {
172
172
  }
173
173
 
174
174
  const ink = await import('ink');
175
+ let inkUi = null;
176
+ try {
177
+ inkUi = await import('@inkjs/ui');
178
+ } catch {
179
+ inkUi = null;
180
+ }
175
181
  const reactNamespace = await import('react');
176
182
  const React = reactNamespace.default || reactNamespace;
177
183
 
178
184
  const App = createDashboardApp({
179
185
  React,
180
186
  ink,
187
+ inkUi,
181
188
  parseSnapshotForFlow,
182
189
  workspacePath,
183
190
  flow,
@@ -124,7 +124,8 @@ function normalizePanelLine(line) {
124
124
  text: typeof line.text === 'string' ? line.text : String(line.text ?? ''),
125
125
  color: line.color,
126
126
  bold: Boolean(line.bold),
127
- selected: Boolean(line.selected)
127
+ selected: Boolean(line.selected),
128
+ loading: Boolean(line.loading)
128
129
  };
129
130
  }
130
131
 
@@ -132,7 +133,8 @@ function normalizePanelLine(line) {
132
133
  text: String(line ?? ''),
133
134
  color: undefined,
134
135
  bold: false,
135
- selected: false
136
+ selected: false,
137
+ loading: false
136
138
  };
137
139
  }
138
140
 
@@ -1265,6 +1267,15 @@ function toInfoRows(lines, keyPrefix, emptyLabel = 'No data') {
1265
1267
  });
1266
1268
  }
1267
1269
 
1270
+ function toLoadingRows(label, keyPrefix = 'loading') {
1271
+ return [{
1272
+ kind: 'loading',
1273
+ key: `${keyPrefix}:row`,
1274
+ label: typeof label === 'string' && label !== '' ? label : 'Loading...',
1275
+ selectable: false
1276
+ }];
1277
+ }
1278
+
1268
1279
  function buildOverviewIntentGroups(snapshot, flow, filter = 'next') {
1269
1280
  const effectiveFlow = getEffectiveFlow(flow, snapshot);
1270
1281
  const normalizedFilter = filter === 'completed' ? 'completed' : 'next';
@@ -1602,6 +1613,16 @@ function buildInteractiveRowsLines(rows, selectedIndex, icons, width, isFocusedS
1602
1613
  };
1603
1614
  }
1604
1615
 
1616
+ if (row.kind === 'loading') {
1617
+ return {
1618
+ text: truncate(row.label || 'Loading...', width),
1619
+ color: 'cyan',
1620
+ bold: false,
1621
+ selected: false,
1622
+ loading: true
1623
+ };
1624
+ }
1625
+
1605
1626
  return {
1606
1627
  text: truncate(`${isSelected ? `${cursor} ` : ' '}${row.label || ''}`, width),
1607
1628
  color: isSelected ? (isFocusedSection ? 'green' : 'cyan') : (row.color || 'gray'),
@@ -1941,6 +1962,7 @@ function createDashboardApp(deps) {
1941
1962
  const {
1942
1963
  React,
1943
1964
  ink,
1965
+ inkUi,
1944
1966
  parseSnapshot,
1945
1967
  parseSnapshotForFlow,
1946
1968
  workspacePath,
@@ -1956,6 +1978,9 @@ function createDashboardApp(deps) {
1956
1978
 
1957
1979
  const { Box, Text, useApp, useInput, useStdout } = ink;
1958
1980
  const { useState, useEffect, useCallback, useRef } = React;
1981
+ const Spinner = inkUi && typeof inkUi.Spinner === 'function'
1982
+ ? inkUi.Spinner
1983
+ : null;
1959
1984
 
1960
1985
  function SectionPanel(props) {
1961
1986
  const {
@@ -1990,15 +2015,25 @@ function createDashboardApp(deps) {
1990
2015
  { bold: true, color: titleColor, backgroundColor: titleBackground },
1991
2016
  truncate(title, contentWidth)
1992
2017
  ),
1993
- ...visibleLines.map((line, index) => React.createElement(
1994
- Text,
1995
- {
1996
- key: `${title}-${index}`,
1997
- color: line.color,
1998
- bold: line.bold
1999
- },
2000
- line.text
2001
- ))
2018
+ ...visibleLines.map((line, index) => {
2019
+ if (line.loading && Spinner) {
2020
+ return React.createElement(
2021
+ Box,
2022
+ { key: `${title}-${index}` },
2023
+ React.createElement(Spinner, { label: truncate(line.text, contentWidth) })
2024
+ );
2025
+ }
2026
+
2027
+ return React.createElement(
2028
+ Text,
2029
+ {
2030
+ key: `${title}-${index}`,
2031
+ color: line.color,
2032
+ bold: line.bold
2033
+ },
2034
+ line.text
2035
+ );
2036
+ })
2002
2037
  );
2003
2038
  }
2004
2039
 
@@ -2178,42 +2213,42 @@ function createDashboardApp(deps) {
2178
2213
  expandedGroups
2179
2214
  )
2180
2215
  ]
2181
- : toInfoRows(['Loading intents...'], 'intent-loading');
2216
+ : toLoadingRows('Loading intents...', 'intent-loading');
2182
2217
  const completedRows = shouldHydrateSecondaryTabs
2183
2218
  ? toExpandableRows(
2184
2219
  buildCompletedGroups(snapshot, activeFlow),
2185
2220
  getNoCompletedMessage(effectiveFlow),
2186
2221
  expandedGroups
2187
2222
  )
2188
- : toInfoRows(['Loading completed items...'], 'completed-loading');
2223
+ : toLoadingRows('Loading completed items...', 'completed-loading');
2189
2224
  const standardsRows = shouldHydrateSecondaryTabs
2190
2225
  ? toExpandableRows(
2191
2226
  buildStandardsGroups(snapshot, activeFlow),
2192
2227
  effectiveFlow === 'simple' ? 'No standards for SIMPLE flow' : 'No standards found',
2193
2228
  expandedGroups
2194
2229
  )
2195
- : toInfoRows(['Loading standards...'], 'standards-loading');
2230
+ : toLoadingRows('Loading standards...', 'standards-loading');
2196
2231
  const statsRows = shouldHydrateSecondaryTabs
2197
2232
  ? toInfoRows(
2198
2233
  buildStatsLines(snapshot, 200, activeFlow),
2199
2234
  'stats',
2200
2235
  'No stats available'
2201
2236
  )
2202
- : toInfoRows(['Loading stats...'], 'stats-loading');
2237
+ : toLoadingRows('Loading stats...', 'stats-loading');
2203
2238
  const warningsRows = shouldHydrateSecondaryTabs
2204
2239
  ? toInfoRows(
2205
2240
  buildWarningsLines(snapshot, 200),
2206
2241
  'warnings',
2207
2242
  'No warnings'
2208
2243
  )
2209
- : toInfoRows(['Loading warnings...'], 'warnings-loading');
2244
+ : toLoadingRows('Loading warnings...', 'warnings-loading');
2210
2245
  const errorDetailsRows = shouldHydrateSecondaryTabs
2211
2246
  ? toInfoRows(
2212
2247
  buildErrorLines(error, 200),
2213
2248
  'error-details',
2214
2249
  'No error details'
2215
2250
  )
2216
- : toInfoRows(['Loading error details...'], 'error-loading');
2251
+ : toLoadingRows('Loading error details...', 'error-loading');
2217
2252
 
2218
2253
  const rowsBySection = {
2219
2254
  'current-run': currentRunRows,
@@ -2771,7 +2806,8 @@ function createDashboardApp(deps) {
2771
2806
  (showGlobalErrorPanel ? 5 : 0) +
2772
2807
  (showErrorInline ? 1 : 0) +
2773
2808
  (showStatusLine ? 1 : 0);
2774
- const contentRowsBudget = Math.max(4, rows - reservedRows);
2809
+ const frameSafetyRows = 2;
2810
+ const contentRowsBudget = Math.max(4, rows - reservedRows - frameSafetyRows);
2775
2811
  const ultraCompact = rows <= 14;
2776
2812
  const panelTitles = getPanelTitles(activeFlow, snapshot);
2777
2813
  const splitPreviewLayout = previewOpen && !overlayPreviewOpen && !ui.showHelp && cols >= 110 && rows >= 16;
@@ -2936,12 +2972,13 @@ function createDashboardApp(deps) {
2936
2972
 
2937
2973
  let contentNode;
2938
2974
  if (splitPreviewLayout && !overlayPreviewOpen) {
2975
+ const previewBodyLines = Math.max(1, contentRowsBudget - 3);
2939
2976
  const previewPanel = {
2940
2977
  key: 'preview-split',
2941
2978
  title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
2942
2979
  lines: previewLines,
2943
2980
  borderColor: 'magenta',
2944
- maxLines: Math.max(4, contentRowsBudget)
2981
+ maxLines: previewBodyLines
2945
2982
  };
2946
2983
 
2947
2984
  contentNode = React.createElement(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
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": {
@@ -40,6 +40,7 @@
40
40
  "README.md"
41
41
  ],
42
42
  "dependencies": {
43
+ "@inkjs/ui": "^2.0.0",
43
44
  "chalk": "^4.1.2",
44
45
  "chokidar": "^4.0.3",
45
46
  "commander": "^11.1.0",