specsmd 0.1.37 → 0.1.39

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.
@@ -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) || width <= 0 || text.length <= width) {
99
+ if (!Number.isFinite(width)) {
91
100
  return text;
92
101
  }
93
102
 
94
- if (width <= 3) {
95
- return text.slice(0, width);
103
+ const safeWidth = Math.max(0, Math.floor(width));
104
+ if (safeWidth === 0) {
105
+ return '';
96
106
  }
97
107
 
98
- return `${text.slice(0, width - 3)}...`;
108
+ if (stringWidth(text) <= safeWidth) {
109
+ return text;
110
+ }
111
+
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) {
@@ -906,7 +926,8 @@ function collectSimpleSpecFiles(spec) {
906
926
  }));
907
927
  }
908
928
 
909
- function getRunFileEntries(snapshot, flow) {
929
+ function getRunFileEntries(snapshot, flow, options = {}) {
930
+ const includeBacklog = options.includeBacklog !== false;
910
931
  const effectiveFlow = getEffectiveFlow(flow, snapshot);
911
932
  const entries = [];
912
933
  const seenPaths = new Set();
@@ -917,6 +938,10 @@ function getRunFileEntries(snapshot, flow) {
917
938
  pushFileEntry(entries, seenPaths, { ...file, scope: 'active' });
918
939
  }
919
940
 
941
+ if (!includeBacklog) {
942
+ return entries;
943
+ }
944
+
920
945
  const pendingBolts = Array.isArray(snapshot?.pendingBolts) ? snapshot.pendingBolts : [];
921
946
  for (const pendingBolt of pendingBolts) {
922
947
  for (const file of collectAidlcBoltFiles(pendingBolt)) {
@@ -963,6 +988,10 @@ function getRunFileEntries(snapshot, flow) {
963
988
  pushFileEntry(entries, seenPaths, { ...file, scope: 'active' });
964
989
  }
965
990
 
991
+ if (!includeBacklog) {
992
+ return entries;
993
+ }
994
+
966
995
  const pendingSpecs = Array.isArray(snapshot?.pendingSpecs) ? snapshot.pendingSpecs : [];
967
996
  for (const pendingSpec of pendingSpecs) {
968
997
  for (const file of collectSimpleSpecFiles(pendingSpec)) {
@@ -985,6 +1014,10 @@ function getRunFileEntries(snapshot, flow) {
985
1014
  pushFileEntry(entries, seenPaths, { ...file, scope: 'active' });
986
1015
  }
987
1016
 
1017
+ if (!includeBacklog) {
1018
+ return entries;
1019
+ }
1020
+
988
1021
  const pendingItems = Array.isArray(snapshot?.pendingItems) ? snapshot.pendingItems : [];
989
1022
  for (const pendingItem of pendingItems) {
990
1023
  pushFileEntry(entries, seenPaths, {
@@ -1154,10 +1187,10 @@ function getFileEntityLabel(fileEntry, fallbackIndex = 0) {
1154
1187
  return `item-${fallbackIndex + 1}`;
1155
1188
  }
1156
1189
 
1157
- function buildRunFileEntityGroups(snapshot, flow) {
1190
+ function buildRunFileEntityGroups(snapshot, flow, options = {}) {
1158
1191
  const order = ['active', 'upcoming', 'completed', 'intent', 'other'];
1159
1192
  const rankByScope = new Map(order.map((scope, index) => [scope, index]));
1160
- const entries = filterExistingFiles(getRunFileEntries(snapshot, flow));
1193
+ const entries = filterExistingFiles(getRunFileEntries(snapshot, flow, options));
1161
1194
  const groupsByEntity = new Map();
1162
1195
 
1163
1196
  for (let index = 0; index < entries.length; index += 1) {
@@ -2115,7 +2148,9 @@ function createDashboardApp(deps) {
2115
2148
  currentExpandedGroups
2116
2149
  );
2117
2150
  const shouldHydrateSecondaryTabs = deferredTabsReady || ui.view !== 'runs';
2118
- const runFileGroups = buildRunFileEntityGroups(snapshot, activeFlow);
2151
+ const runFileGroups = buildRunFileEntityGroups(snapshot, activeFlow, {
2152
+ includeBacklog: shouldHydrateSecondaryTabs
2153
+ });
2119
2154
  const runFileExpandedGroups = { ...expandedGroups };
2120
2155
  for (const group of runFileGroups) {
2121
2156
  if (runFileExpandedGroups[group.key] == null) {
@@ -2592,14 +2627,19 @@ function createDashboardApp(deps) {
2592
2627
  }, [activeFlow, rowLengthSignature, snapshot?.generatedAt]);
2593
2628
 
2594
2629
  useEffect(() => {
2630
+ if (ui.view !== 'runs') {
2631
+ setDeferredTabsReady(true);
2632
+ return undefined;
2633
+ }
2634
+
2595
2635
  setDeferredTabsReady(false);
2596
2636
  const timer = setTimeout(() => {
2597
2637
  setDeferredTabsReady(true);
2598
- }, 0);
2638
+ }, 250);
2599
2639
  return () => {
2600
2640
  clearTimeout(timer);
2601
2641
  };
2602
- }, [activeFlow, snapshot?.generatedAt]);
2642
+ }, [activeFlow, snapshot?.generatedAt, ui.view]);
2603
2643
 
2604
2644
  useEffect(() => {
2605
2645
  setPaneFocus('main');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
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": {