specsmd 0.1.38 → 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.
- package/lib/dashboard/index.js +7 -0
- package/lib/dashboard/tui/app.js +80 -23
- package/package.json +2 -1
package/lib/dashboard/index.js
CHANGED
|
@@ -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,
|
package/lib/dashboard/tui/app.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { spawnSync } = require('child_process');
|
|
4
|
+
const stringWidthModule = require('string-width');
|
|
5
|
+
const sliceAnsiModule = require('slice-ansi');
|
|
4
6
|
const { createWatchRuntime } = require('../runtime/watch-runtime');
|
|
5
7
|
const { createInitialUIState } = require('./store');
|
|
6
8
|
|
|
9
|
+
const stringWidth = typeof stringWidthModule === 'function'
|
|
10
|
+
? stringWidthModule
|
|
11
|
+
: stringWidthModule.default;
|
|
12
|
+
const sliceAnsi = typeof sliceAnsiModule === 'function'
|
|
13
|
+
? sliceAnsiModule
|
|
14
|
+
: sliceAnsiModule.default;
|
|
15
|
+
|
|
7
16
|
function toDashboardError(error, defaultCode = 'DASHBOARD_ERROR') {
|
|
8
17
|
if (!error) {
|
|
9
18
|
return {
|
|
@@ -87,15 +96,26 @@ function resolveIconSet() {
|
|
|
87
96
|
|
|
88
97
|
function truncate(value, width) {
|
|
89
98
|
const text = String(value ?? '');
|
|
90
|
-
if (!Number.isFinite(width)
|
|
99
|
+
if (!Number.isFinite(width)) {
|
|
91
100
|
return text;
|
|
92
101
|
}
|
|
93
102
|
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
const safeWidth = Math.max(0, Math.floor(width));
|
|
104
|
+
if (safeWidth === 0) {
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (stringWidth(text) <= safeWidth) {
|
|
109
|
+
return text;
|
|
96
110
|
}
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
if (safeWidth <= 3) {
|
|
113
|
+
return sliceAnsi(text, 0, safeWidth);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const ellipsis = '...';
|
|
117
|
+
const bodyWidth = Math.max(0, safeWidth - stringWidth(ellipsis));
|
|
118
|
+
return `${sliceAnsi(text, 0, bodyWidth)}${ellipsis}`;
|
|
99
119
|
}
|
|
100
120
|
|
|
101
121
|
function normalizePanelLine(line) {
|
|
@@ -104,7 +124,8 @@ function normalizePanelLine(line) {
|
|
|
104
124
|
text: typeof line.text === 'string' ? line.text : String(line.text ?? ''),
|
|
105
125
|
color: line.color,
|
|
106
126
|
bold: Boolean(line.bold),
|
|
107
|
-
selected: Boolean(line.selected)
|
|
127
|
+
selected: Boolean(line.selected),
|
|
128
|
+
loading: Boolean(line.loading)
|
|
108
129
|
};
|
|
109
130
|
}
|
|
110
131
|
|
|
@@ -112,7 +133,8 @@ function normalizePanelLine(line) {
|
|
|
112
133
|
text: String(line ?? ''),
|
|
113
134
|
color: undefined,
|
|
114
135
|
bold: false,
|
|
115
|
-
selected: false
|
|
136
|
+
selected: false,
|
|
137
|
+
loading: false
|
|
116
138
|
};
|
|
117
139
|
}
|
|
118
140
|
|
|
@@ -1245,6 +1267,15 @@ function toInfoRows(lines, keyPrefix, emptyLabel = 'No data') {
|
|
|
1245
1267
|
});
|
|
1246
1268
|
}
|
|
1247
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
|
+
|
|
1248
1279
|
function buildOverviewIntentGroups(snapshot, flow, filter = 'next') {
|
|
1249
1280
|
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
1250
1281
|
const normalizedFilter = filter === 'completed' ? 'completed' : 'next';
|
|
@@ -1582,6 +1613,16 @@ function buildInteractiveRowsLines(rows, selectedIndex, icons, width, isFocusedS
|
|
|
1582
1613
|
};
|
|
1583
1614
|
}
|
|
1584
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
|
+
|
|
1585
1626
|
return {
|
|
1586
1627
|
text: truncate(`${isSelected ? `${cursor} ` : ' '}${row.label || ''}`, width),
|
|
1587
1628
|
color: isSelected ? (isFocusedSection ? 'green' : 'cyan') : (row.color || 'gray'),
|
|
@@ -1921,6 +1962,7 @@ function createDashboardApp(deps) {
|
|
|
1921
1962
|
const {
|
|
1922
1963
|
React,
|
|
1923
1964
|
ink,
|
|
1965
|
+
inkUi,
|
|
1924
1966
|
parseSnapshot,
|
|
1925
1967
|
parseSnapshotForFlow,
|
|
1926
1968
|
workspacePath,
|
|
@@ -1936,6 +1978,9 @@ function createDashboardApp(deps) {
|
|
|
1936
1978
|
|
|
1937
1979
|
const { Box, Text, useApp, useInput, useStdout } = ink;
|
|
1938
1980
|
const { useState, useEffect, useCallback, useRef } = React;
|
|
1981
|
+
const Spinner = inkUi && typeof inkUi.Spinner === 'function'
|
|
1982
|
+
? inkUi.Spinner
|
|
1983
|
+
: null;
|
|
1939
1984
|
|
|
1940
1985
|
function SectionPanel(props) {
|
|
1941
1986
|
const {
|
|
@@ -1970,15 +2015,25 @@ function createDashboardApp(deps) {
|
|
|
1970
2015
|
{ bold: true, color: titleColor, backgroundColor: titleBackground },
|
|
1971
2016
|
truncate(title, contentWidth)
|
|
1972
2017
|
),
|
|
1973
|
-
...visibleLines.map((line, index) =>
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
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
|
+
})
|
|
1982
2037
|
);
|
|
1983
2038
|
}
|
|
1984
2039
|
|
|
@@ -2158,42 +2213,42 @@ function createDashboardApp(deps) {
|
|
|
2158
2213
|
expandedGroups
|
|
2159
2214
|
)
|
|
2160
2215
|
]
|
|
2161
|
-
:
|
|
2216
|
+
: toLoadingRows('Loading intents...', 'intent-loading');
|
|
2162
2217
|
const completedRows = shouldHydrateSecondaryTabs
|
|
2163
2218
|
? toExpandableRows(
|
|
2164
2219
|
buildCompletedGroups(snapshot, activeFlow),
|
|
2165
2220
|
getNoCompletedMessage(effectiveFlow),
|
|
2166
2221
|
expandedGroups
|
|
2167
2222
|
)
|
|
2168
|
-
:
|
|
2223
|
+
: toLoadingRows('Loading completed items...', 'completed-loading');
|
|
2169
2224
|
const standardsRows = shouldHydrateSecondaryTabs
|
|
2170
2225
|
? toExpandableRows(
|
|
2171
2226
|
buildStandardsGroups(snapshot, activeFlow),
|
|
2172
2227
|
effectiveFlow === 'simple' ? 'No standards for SIMPLE flow' : 'No standards found',
|
|
2173
2228
|
expandedGroups
|
|
2174
2229
|
)
|
|
2175
|
-
:
|
|
2230
|
+
: toLoadingRows('Loading standards...', 'standards-loading');
|
|
2176
2231
|
const statsRows = shouldHydrateSecondaryTabs
|
|
2177
2232
|
? toInfoRows(
|
|
2178
2233
|
buildStatsLines(snapshot, 200, activeFlow),
|
|
2179
2234
|
'stats',
|
|
2180
2235
|
'No stats available'
|
|
2181
2236
|
)
|
|
2182
|
-
:
|
|
2237
|
+
: toLoadingRows('Loading stats...', 'stats-loading');
|
|
2183
2238
|
const warningsRows = shouldHydrateSecondaryTabs
|
|
2184
2239
|
? toInfoRows(
|
|
2185
2240
|
buildWarningsLines(snapshot, 200),
|
|
2186
2241
|
'warnings',
|
|
2187
2242
|
'No warnings'
|
|
2188
2243
|
)
|
|
2189
|
-
:
|
|
2244
|
+
: toLoadingRows('Loading warnings...', 'warnings-loading');
|
|
2190
2245
|
const errorDetailsRows = shouldHydrateSecondaryTabs
|
|
2191
2246
|
? toInfoRows(
|
|
2192
2247
|
buildErrorLines(error, 200),
|
|
2193
2248
|
'error-details',
|
|
2194
2249
|
'No error details'
|
|
2195
2250
|
)
|
|
2196
|
-
:
|
|
2251
|
+
: toLoadingRows('Loading error details...', 'error-loading');
|
|
2197
2252
|
|
|
2198
2253
|
const rowsBySection = {
|
|
2199
2254
|
'current-run': currentRunRows,
|
|
@@ -2751,7 +2806,8 @@ function createDashboardApp(deps) {
|
|
|
2751
2806
|
(showGlobalErrorPanel ? 5 : 0) +
|
|
2752
2807
|
(showErrorInline ? 1 : 0) +
|
|
2753
2808
|
(showStatusLine ? 1 : 0);
|
|
2754
|
-
const
|
|
2809
|
+
const frameSafetyRows = 2;
|
|
2810
|
+
const contentRowsBudget = Math.max(4, rows - reservedRows - frameSafetyRows);
|
|
2755
2811
|
const ultraCompact = rows <= 14;
|
|
2756
2812
|
const panelTitles = getPanelTitles(activeFlow, snapshot);
|
|
2757
2813
|
const splitPreviewLayout = previewOpen && !overlayPreviewOpen && !ui.showHelp && cols >= 110 && rows >= 16;
|
|
@@ -2916,12 +2972,13 @@ function createDashboardApp(deps) {
|
|
|
2916
2972
|
|
|
2917
2973
|
let contentNode;
|
|
2918
2974
|
if (splitPreviewLayout && !overlayPreviewOpen) {
|
|
2975
|
+
const previewBodyLines = Math.max(1, contentRowsBudget - 3);
|
|
2919
2976
|
const previewPanel = {
|
|
2920
2977
|
key: 'preview-split',
|
|
2921
2978
|
title: `Preview: ${effectivePreviewTarget?.label || 'unknown'}`,
|
|
2922
2979
|
lines: previewLines,
|
|
2923
2980
|
borderColor: 'magenta',
|
|
2924
|
-
maxLines:
|
|
2981
|
+
maxLines: previewBodyLines
|
|
2925
2982
|
};
|
|
2926
2983
|
|
|
2927
2984
|
contentNode = React.createElement(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
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",
|