@shardworks/spider-apparatus 0.1.153 → 0.1.155

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardworks/spider-apparatus",
3
- "version": "0.1.153",
3
+ "version": "0.1.155",
4
4
  "license": "ISC",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,13 +19,13 @@
19
19
  "hono": "^4.7.11",
20
20
  "yaml": "^2.0.0",
21
21
  "zod": "4.3.6",
22
- "@shardworks/nexus-core": "0.1.153",
23
- "@shardworks/fabricator-apparatus": "0.1.153",
24
- "@shardworks/clerk-apparatus": "0.1.153",
25
- "@shardworks/stacks-apparatus": "0.1.153",
26
- "@shardworks/tools-apparatus": "0.1.153",
27
- "@shardworks/codexes-apparatus": "0.1.153",
28
- "@shardworks/animator-apparatus": "0.1.153"
22
+ "@shardworks/nexus-core": "0.1.155",
23
+ "@shardworks/fabricator-apparatus": "0.1.155",
24
+ "@shardworks/clerk-apparatus": "0.1.155",
25
+ "@shardworks/animator-apparatus": "0.1.155",
26
+ "@shardworks/tools-apparatus": "0.1.155",
27
+ "@shardworks/stacks-apparatus": "0.1.155",
28
+ "@shardworks/codexes-apparatus": "0.1.155"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "25.5.0"
@@ -220,3 +220,177 @@ describe('index.html status filter includes cancelled', () => {
220
220
  assert.ok(cancelledIdx > blockedIdx, 'cancelled option should appear after blocked');
221
221
  });
222
222
  });
223
+
224
+ // ── Rig auto-polling ────────────────────────────────────────────────────
225
+
226
+ describe('spider.js rig list polling', () => {
227
+ it('declares rigListPollTimer state variable', () => {
228
+ assert.match(
229
+ spiderJs,
230
+ /var rigListPollTimer\s*=\s*null/,
231
+ 'should declare rigListPollTimer state variable',
232
+ );
233
+ });
234
+
235
+ it('declares currentRigPollTimer state variable', () => {
236
+ assert.match(
237
+ spiderJs,
238
+ /var currentRigPollTimer\s*=\s*null/,
239
+ 'should declare currentRigPollTimer state variable',
240
+ );
241
+ });
242
+
243
+ it('declares RIG_POLL_INTERVAL constant', () => {
244
+ assert.match(
245
+ spiderJs,
246
+ /var RIG_POLL_INTERVAL\s*=\s*2000/,
247
+ 'should declare RIG_POLL_INTERVAL at 2000ms',
248
+ );
249
+ });
250
+
251
+ it('defines isRigInFlight helper checking running and blocked', () => {
252
+ assert.match(
253
+ spiderJs,
254
+ /function isRigInFlight\(rig\)/,
255
+ 'should define isRigInFlight helper',
256
+ );
257
+ assert.match(
258
+ spiderJs,
259
+ /rig\.status === 'running' \|\| rig\.status === 'blocked'/,
260
+ 'isRigInFlight should check running or blocked',
261
+ );
262
+ });
263
+
264
+ it('defines stopRigListPoll that clears the interval', () => {
265
+ assert.match(
266
+ spiderJs,
267
+ /function stopRigListPoll\(\)/,
268
+ 'should define stopRigListPoll',
269
+ );
270
+ assert.match(
271
+ spiderJs,
272
+ /clearInterval\(rigListPollTimer\)/,
273
+ 'stopRigListPoll should clear rigListPollTimer',
274
+ );
275
+ });
276
+
277
+ it('defines stopCurrentRigPoll that clears the interval', () => {
278
+ assert.match(
279
+ spiderJs,
280
+ /function stopCurrentRigPoll\(\)/,
281
+ 'should define stopCurrentRigPoll',
282
+ );
283
+ assert.match(
284
+ spiderJs,
285
+ /clearInterval\(currentRigPollTimer\)/,
286
+ 'stopCurrentRigPoll should clear currentRigPollTimer',
287
+ );
288
+ });
289
+
290
+ it('starts rig list polling after fetchRigs succeeds', () => {
291
+ // The fetchRigs promise chain should call startRigListPollIfNeeded
292
+ const fetchRigsBlock = spiderJs.match(
293
+ /function fetchRigs\([\s\S]*?\.catch/,
294
+ );
295
+ assert.ok(fetchRigsBlock, 'should find fetchRigs function');
296
+ assert.match(
297
+ fetchRigsBlock[0],
298
+ /startRigListPollIfNeeded\(\)/,
299
+ 'fetchRigs should call startRigListPollIfNeeded after rendering',
300
+ );
301
+ });
302
+
303
+ it('starts current rig polling in showRigDetail', () => {
304
+ const showRigBlock = spiderJs.match(
305
+ /function showRigDetail\(rig\)[\s\S]*?startCurrentRigPoll\(\)/,
306
+ );
307
+ assert.ok(
308
+ showRigBlock,
309
+ 'showRigDetail should call startCurrentRigPoll',
310
+ );
311
+ });
312
+
313
+ it('stops current rig poll when navigating back to list', () => {
314
+ const backBlock = spiderJs.match(
315
+ /function backToList\(\)[\s\S]*?stopCurrentRigPoll\(\)/,
316
+ );
317
+ assert.ok(
318
+ backBlock,
319
+ 'backToList should call stopCurrentRigPoll',
320
+ );
321
+ });
322
+
323
+ it('stops current rig poll when selecting a new rig', () => {
324
+ const showRigBlock = spiderJs.match(
325
+ /function showRigDetail\(rig\)[\s\S]*?stopCurrentRigPoll\(\)/,
326
+ );
327
+ assert.ok(
328
+ showRigBlock,
329
+ 'showRigDetail should call stopCurrentRigPoll before starting new poll',
330
+ );
331
+ });
332
+
333
+ it('fetchCurrentRigQuiet fetches /api/rig/show with rig id', () => {
334
+ assert.match(
335
+ spiderJs,
336
+ /function fetchCurrentRigQuiet\(\)/,
337
+ 'should define fetchCurrentRigQuiet',
338
+ );
339
+ assert.match(
340
+ spiderJs,
341
+ /\/api\/rig\/show\?id=/,
342
+ 'fetchCurrentRigQuiet should fetch /api/rig/show',
343
+ );
344
+ });
345
+
346
+ it('fetchCurrentRigQuiet preserves selectedEngineId across polls', () => {
347
+ // The poll handler should find the updated engine by selectedEngineId.
348
+ // Use a greedy match to capture the whole function body up to the next
349
+ // top-level function declaration.
350
+ const pollBlock = spiderJs.match(
351
+ /function fetchCurrentRigQuiet[\s\S]*?(?=\n function )/,
352
+ );
353
+ assert.ok(pollBlock, 'should find fetchCurrentRigQuiet');
354
+ assert.match(
355
+ pollBlock[0],
356
+ /selectedEngineId/,
357
+ 'should reference selectedEngineId to preserve selection',
358
+ );
359
+ });
360
+
361
+ it('fetchCurrentRigQuiet stops polling on terminal status', () => {
362
+ const pollBlock = spiderJs.match(
363
+ /function fetchCurrentRigQuiet[\s\S]*?(?=\n function )/,
364
+ );
365
+ assert.ok(pollBlock, 'should find fetchCurrentRigQuiet');
366
+ assert.match(
367
+ pollBlock[0],
368
+ /isRigInFlight/,
369
+ 'should check isRigInFlight and stop when terminal',
370
+ );
371
+ });
372
+
373
+ it('refresh button resets rig list poll cycle', () => {
374
+ const refreshBlock = spiderJs.match(
375
+ /refresh-btn[\s\S]*?fetchRigs/,
376
+ );
377
+ assert.ok(refreshBlock, 'should find refresh button handler');
378
+ assert.match(
379
+ refreshBlock[0],
380
+ /stopRigListPoll\(\)/,
381
+ 'refresh button should stop existing poll before fetching',
382
+ );
383
+ });
384
+
385
+ it('rig list quiet poll stops itself when no in-flight rigs remain', () => {
386
+ const quietBlock = spiderJs.match(
387
+ /function fetchRigListQuiet[\s\S]*?stopRigListPoll/,
388
+ );
389
+ assert.ok(quietBlock, 'should find fetchRigListQuiet');
390
+ assert.match(
391
+ quietBlock[0],
392
+ /hasInFlight/,
393
+ 'should check for in-flight rigs and stop poll if none',
394
+ );
395
+ });
396
+ });
@@ -18,6 +18,10 @@
18
18
  var sessionPollTimer = null;
19
19
  var sessionEventSource = null;
20
20
  var selectedTemplateName = null;
21
+ var rigListPollTimer = null;
22
+ var currentRigPollTimer = null;
23
+
24
+ var RIG_POLL_INTERVAL = 2000;
21
25
 
22
26
  // ── Badge mapping ──────────────────────────────────────────────────────
23
27
 
@@ -129,6 +133,112 @@
129
133
  stopSessionPoll();
130
134
  }
131
135
 
136
+ // ── Rig polling helpers ────────────────────────────────────────────────
137
+
138
+ function isRigInFlight(rig) {
139
+ return rig.status === 'running' || rig.status === 'blocked';
140
+ }
141
+
142
+ function stopRigListPoll() {
143
+ if (rigListPollTimer !== null) {
144
+ clearInterval(rigListPollTimer);
145
+ rigListPollTimer = null;
146
+ }
147
+ }
148
+
149
+ function stopCurrentRigPoll() {
150
+ if (currentRigPollTimer !== null) {
151
+ clearInterval(currentRigPollTimer);
152
+ currentRigPollTimer = null;
153
+ }
154
+ }
155
+
156
+ function startRigListPollIfNeeded() {
157
+ stopRigListPoll();
158
+ var hasInFlight = rigs.some(isRigInFlight);
159
+ if (!hasInFlight) return;
160
+ rigListPollTimer = setInterval(function () {
161
+ fetchRigListQuiet();
162
+ }, RIG_POLL_INTERVAL);
163
+ }
164
+
165
+ /** Refetch rig list without resetting filters — silent background refresh. */
166
+ function fetchRigListQuiet() {
167
+ var rigUrl = '/api/rig/list?limit=100';
168
+ if (currentStatusFilter) {
169
+ rigUrl += '&status=' + encodeURIComponent(currentStatusFilter);
170
+ }
171
+
172
+ var rigPromise = fetch(rigUrl).then(function (r) { return r.json(); });
173
+ var writPromise = fetch('/api/writ/list?limit=100').then(function (r) { return r.json(); });
174
+
175
+ Promise.all([rigPromise, writPromise]).then(function (results) {
176
+ rigs = Array.isArray(results[0]) ? results[0] : [];
177
+ buildWritLookup(Array.isArray(results[1]) ? results[1] : []);
178
+ renderRigList();
179
+
180
+ // Re-evaluate whether polling should continue
181
+ var hasInFlight = rigs.some(isRigInFlight);
182
+ if (!hasInFlight) {
183
+ stopRigListPoll();
184
+ }
185
+ }).catch(function (err) {
186
+ console.error('[spider] rig list poll error:', err);
187
+ });
188
+ }
189
+
190
+ function startCurrentRigPoll() {
191
+ stopCurrentRigPoll();
192
+ if (!currentRig || !isRigInFlight(currentRig)) return;
193
+ currentRigPollTimer = setInterval(function () {
194
+ fetchCurrentRigQuiet();
195
+ }, RIG_POLL_INTERVAL);
196
+ }
197
+
198
+ /** Refetch the currently-viewed rig and update detail + pipeline in place. */
199
+ function fetchCurrentRigQuiet() {
200
+ if (!currentRig) { stopCurrentRigPoll(); return; }
201
+ fetch('/api/rig/show?id=' + encodeURIComponent(currentRig.id))
202
+ .then(function (r) { return r.json(); })
203
+ .then(function (rig) {
204
+ if (!currentRig || currentRig.id !== rig.id) return; // navigated away
205
+ currentRig = rig;
206
+
207
+ // Update the meta table
208
+ var metaTable = document.getElementById('detail-meta');
209
+ if (metaTable) {
210
+ metaTable.innerHTML =
211
+ '<tbody>' +
212
+ '<tr><th>ID</th><td>' + esc(rig.id) + '</td></tr>' +
213
+ '<tr><th>Writ</th><td><a href="/pages/clerk/?writ=' + esc(rig.writId) + '">' + esc(rig.writId) + '</a></td></tr>' +
214
+ '<tr><th>Status</th><td>' + badgeHtml(rig.status) + '</td></tr>' +
215
+ '<tr><th>Created</th><td>' + esc(formatDate(rig.createdAt)) + '</td></tr>' +
216
+ '</tbody>';
217
+ }
218
+
219
+ // Re-render pipeline, preserving selection
220
+ renderPipeline(rig);
221
+
222
+ // If an engine was selected, update engine detail with fresh data
223
+ if (selectedEngineId) {
224
+ var updatedEngine = (rig.engines || []).find(function (e) {
225
+ return e.id === selectedEngineId;
226
+ });
227
+ if (updatedEngine) {
228
+ showEngineDetail(updatedEngine);
229
+ }
230
+ }
231
+
232
+ // Stop polling once terminal
233
+ if (!isRigInFlight(rig)) {
234
+ stopCurrentRigPoll();
235
+ }
236
+ })
237
+ .catch(function (err) {
238
+ console.error('[spider] current rig poll error:', err);
239
+ });
240
+ }
241
+
132
242
  // ── Fetch rigs ─────────────────────────────────────────────────────────
133
243
 
134
244
  function fetchRigs(statusFilter) {
@@ -145,6 +255,7 @@
145
255
  rigs = Array.isArray(results[0]) ? results[0] : [];
146
256
  buildWritLookup(Array.isArray(results[1]) ? results[1] : []);
147
257
  renderRigList();
258
+ startRigListPollIfNeeded();
148
259
  }).catch(function (err) {
149
260
  console.error('Failed to fetch rigs/writs:', err);
150
261
  rigs = [];
@@ -236,6 +347,7 @@
236
347
  selectedEngineId = null;
237
348
 
238
349
  stopSessionStream();
350
+ stopCurrentRigPoll();
239
351
 
240
352
  document.getElementById('rig-list-view').style.display = 'none';
241
353
  document.getElementById('rig-detail-view').style.display = '';
@@ -279,6 +391,8 @@
279
391
 
280
392
  var engineDetail = document.getElementById('engine-detail');
281
393
  if (engineDetail) engineDetail.style.display = 'none';
394
+
395
+ startCurrentRigPoll();
282
396
  }
283
397
 
284
398
  // ── Render pipeline (generic) ──────────────────────────────────────────
@@ -646,6 +760,7 @@
646
760
 
647
761
  function backToList() {
648
762
  stopSessionStream();
763
+ stopCurrentRigPoll();
649
764
  currentRig = null;
650
765
  selectedEngineId = null;
651
766
  document.getElementById('rig-detail-view').style.display = 'none';
@@ -877,6 +992,7 @@
877
992
  var refreshBtn = document.getElementById('refresh-btn');
878
993
  if (refreshBtn) {
879
994
  refreshBtn.addEventListener('click', function () {
995
+ stopRigListPoll();
880
996
  fetchRigs(currentStatusFilter);
881
997
  });
882
998
  }