claude-code-hub 1.3.3 → 1.3.4

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": "claude-code-hub",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Unified hub for Claude Code tools — Marketplace + Kanban in one PWA",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -236,7 +236,6 @@ async function init() {
236
236
  bindPalette();
237
237
  registerSW();
238
238
  bindErrorToast();
239
- bindSlowToast();
240
239
  showUpdateToast();
241
240
  }
242
241
 
@@ -293,42 +292,6 @@ function hideToast(toast) {
293
292
  }, 200);
294
293
  }
295
294
 
296
- // The stalls belong to the port, so a dismissal holds while the app stays on it.
297
- const SLOW_POLL_MS = 30_000;
298
- const slowDismissed = new Map();
299
- const slowToast = { id: null, port: 0, confirm: false, busy: false };
300
-
301
- function hideSlowToast() {
302
- slowToast.id = null;
303
- hideToast(document.getElementById('slow-toast'));
304
- }
305
-
306
- function renderSlowToast(app) {
307
- const restart = document.getElementById('slow-restart');
308
- document.getElementById('slow-title').textContent = `${app.name} answers slowly`;
309
- document.getElementById('slow-detail').textContent =
310
- `Its port delays requests by up to ${(app.worstMs / 1000).toFixed(1)} s. A restart moves it to a new port.`;
311
- restart.textContent = 'Restart';
312
- restart.disabled = false;
313
- slowToast.id = app.id;
314
- slowToast.port = app.port;
315
- slowToast.confirm = false;
316
- showToast(document.getElementById('slow-toast'));
317
- }
318
-
319
- async function pollSlowApps() {
320
- if (document.hidden || slowToast.busy) return;
321
- let slow;
322
- try {
323
- ({ slow } = await sendJson('GET', '/api/apps/health'));
324
- } catch {
325
- return;
326
- }
327
- if (slowToast.id && !slow.some((a) => a.id === slowToast.id)) hideSlowToast();
328
- const next = slow.find((a) => slowDismissed.get(a.id) !== a.port);
329
- if (next && next.id !== slowToast.id) renderSlowToast(next);
330
- }
331
-
332
295
  // Null on success. A 409 with live terminals asks for force: true on the next call.
333
296
  async function requestRestart(id, force) {
334
297
  try {
@@ -343,37 +306,6 @@ async function requestRestart(id, force) {
343
306
  }
344
307
  }
345
308
 
346
- async function restartSlowApp() {
347
- const { id } = slowToast;
348
- if (!id) return;
349
- const restart = document.getElementById('slow-restart');
350
- slowToast.busy = true;
351
- restart.disabled = true;
352
- restart.textContent = 'Restarting…';
353
- const failed = await requestRestart(id, slowToast.confirm);
354
- slowToast.busy = false;
355
- if (!failed) {
356
- hideSlowToast();
357
- replaceIframe(id);
358
- return;
359
- }
360
- slowToast.confirm = !!failed.terminals;
361
- document.getElementById('slow-detail').textContent = failed.message;
362
- restart.textContent = failed.terminals ? 'Restart anyway' : 'Restart';
363
- restart.disabled = false;
364
- }
365
-
366
- function bindSlowToast() {
367
- document.getElementById('slow-restart').addEventListener('click', restartSlowApp);
368
- document.getElementById('slow-close').addEventListener('click', () => {
369
- if (slowToast.id) slowDismissed.set(slowToast.id, slowToast.port);
370
- hideSlowToast();
371
- });
372
- setInterval(pollSlowApps, SLOW_POLL_MS);
373
- document.addEventListener('visibilitychange', pollSlowApps);
374
- pollSlowApps();
375
- }
376
-
377
309
  const ERROR_TOAST_MS = 8_000;
378
310
  let errorToastTimer = 0;
379
311
 
@@ -1048,7 +980,12 @@ const PALETTE_MODES = {
1048
980
  fillRow(li, row, q) {
1049
981
  li.append(appIcon(row.app), markedSpan('name', row.app.name, q));
1050
982
  const s = palette.appStats?.[row.id];
1051
- const stats = el('span', 'stats', s ? `${Math.round(s.rss / 1048576)} MB · ${s.cpu}%` : '');
983
+ if (s?.slowMs) {
984
+ const badge = el('span', 'slow', 'slow port');
985
+ badge.title = `Its port delays requests by up to ${(s.slowMs / 1000).toFixed(1)} s. Restart (Ctrl+R) moves it to a new port.`;
986
+ li.append(badge);
987
+ }
988
+ const stats = el('span', 'stats', s?.rss ? `${Math.round(s.rss / 1048576)} MB · ${s.cpu}%` : '');
1052
989
  stats.title = 'Memory and CPU of the app process. Child processes are not counted. 100% is one core.';
1053
990
  li.append(stats);
1054
991
  const key = row.id === activeApp ? 'active' : row.index < 9 ? `Alt+${row.index + 1}` : '';
package/public/index.html CHANGED
@@ -272,6 +272,15 @@
272
272
 
273
273
  .palette-row .stats + .age { margin-left: 0; }
274
274
 
275
+ .palette-row .slow {
276
+ margin-left: 10px;
277
+ padding: 0 6px;
278
+ border-radius: 4px;
279
+ color: #f5a524;
280
+ border: 1px solid #f5a524;
281
+ font-size: 11px;
282
+ }
283
+
275
284
  .palette-row.literal {
276
285
  border-top: 1px solid var(--border);
277
286
  color: var(--accent);
@@ -385,7 +394,6 @@
385
394
 
386
395
  .update-toast-close:hover { color: var(--text); }
387
396
  .error-toast { z-index: 25; border-left-color: #e5484d; }
388
- .slow-toast { z-index: 20; border-left-color: #f5a524; }
389
397
  </style>
390
398
  </head>
391
399
  <body>
@@ -458,17 +466,6 @@
458
466
  <button class="update-toast-close" id="update-close" type="button" aria-label="Close">×</button>
459
467
  </div>
460
468
 
461
- <div class="update-toast slow-toast" id="slow-toast" role="status" hidden>
462
- <div class="update-toast-body">
463
- <div class="update-toast-title" id="slow-title"></div>
464
- <div class="update-toast-meta" id="slow-detail"></div>
465
- <div class="update-toast-actions">
466
- <button class="update-toast-skip" id="slow-restart" type="button">Restart</button>
467
- </div>
468
- </div>
469
- <button class="update-toast-close" id="slow-close" type="button" aria-label="Close">×</button>
470
- </div>
471
-
472
469
  <div class="update-toast error-toast" id="error-toast" role="alert" hidden>
473
470
  <div class="update-toast-body">
474
471
  <div class="update-toast-title" id="error-title"></div>
package/server.js CHANGED
@@ -953,16 +953,6 @@ app.post('/api/config-dirs/activate', async (req, res) => {
953
953
  res.json({ apps: appsConfig() });
954
954
  });
955
955
 
956
- const STATS_TIMEOUT_MS = 1000;
957
- app.get('/api/apps/stats', async (_req, res) => {
958
- res.setHeader('Cache-Control', 'no-store');
959
- const pool = activePool();
960
- const entries = await Promise.all(
961
- ENABLED_APPS.map(async (a) => [a.id, await stats(pool?.children.get(a.id), STATS_TIMEOUT_MS)]),
962
- );
963
- res.json(Object.fromEntries(entries));
964
- });
965
-
966
956
  // The stalls hit a few percent of requests, so each round sends several probes per app.
967
957
  const HEALTH_INTERVAL_MS = 30_000;
968
958
  const HEALTH_PROBES = 5;
@@ -1000,15 +990,18 @@ async function checkPortHealth() {
1000
990
  }
1001
991
  }
1002
992
 
1003
- app.get('/api/apps/health', (_req, res) => {
993
+ const STATS_TIMEOUT_MS = 1000;
994
+ app.get('/api/apps/stats', async (_req, res) => {
1004
995
  res.setHeader('Cache-Control', 'no-store');
1005
996
  const pool = activePool();
1006
- const slow = [];
1007
- for (const a of ENABLED_APPS) {
1008
- const f = pool?.children.has(a.id) && portHealth.flagged(pool.children.get(a.id));
1009
- if (f) slow.push({ id: a.id, name: a.name, port: pool.ports[a.id], worstMs: f.worstMs });
1010
- }
1011
- res.json({ slow });
997
+ const entries = await Promise.all(
998
+ ENABLED_APPS.map(async (a) => {
999
+ const child = pool?.children.get(a.id);
1000
+ const slowMs = (child && portHealth.flagged(child)?.worstMs) ?? null;
1001
+ return [a.id, { ...(await stats(child, STATS_TIMEOUT_MS)), slowMs }];
1002
+ }),
1003
+ );
1004
+ res.json(Object.fromEntries(entries));
1012
1005
  });
1013
1006
 
1014
1007
  // Restarting the terminal provider ends its live terminals, so that takes force: true.