@nockdev/hsa 1.2.5 → 1.2.7

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/dashboard.html CHANGED
@@ -1157,6 +1157,33 @@
1157
1157
  opacity: 0.4;
1158
1158
  }
1159
1159
 
1160
+ /* ERROR BANNER */
1161
+ .error-banner {
1162
+ display: none;
1163
+ align-items: center;
1164
+ gap: 8px;
1165
+ padding: 8px 16px;
1166
+ background: var(--red-subtle);
1167
+ border-bottom: 1px solid rgba(248, 81, 73, 0.3);
1168
+ color: var(--red);
1169
+ font-size: 12px;
1170
+ font-family: var(--font-mono);
1171
+ grid-area: header;
1172
+ }
1173
+ .error-banner.visible {
1174
+ display: flex;
1175
+ }
1176
+ .error-banner .error-dismiss {
1177
+ margin-left: auto;
1178
+ cursor: pointer;
1179
+ opacity: 0.6;
1180
+ background: none;
1181
+ border: none;
1182
+ color: var(--red);
1183
+ font-size: 14px;
1184
+ }
1185
+ .error-banner .error-dismiss:hover { opacity: 1; }
1186
+
1160
1187
  /* RESPONSIVE */
1161
1188
  @media (max-width: 1200px) {
1162
1189
  .kpi-grid {
@@ -1240,6 +1267,12 @@
1240
1267
  </button>
1241
1268
  </div>
1242
1269
  </header>
1270
+ <!-- ERROR BANNER -->
1271
+ <div class="error-banner" id="errorBanner">
1272
+ <span>⚠</span>
1273
+ <span id="errorBannerMsg">Connection failed</span>
1274
+ <button class="error-dismiss" onclick="hideErrorBanner()">✕</button>
1275
+ </div>
1243
1276
 
1244
1277
  <!-- CONTEXT SELECTOR BAR -->
1245
1278
  <div class="context-bar">
@@ -1495,6 +1528,7 @@
1495
1528
  const MAX_SSE_RETRIES = 5;
1496
1529
  let sseReconnectTimer = null;
1497
1530
  let fetchErrorCount = 0;
1531
+ let dashboardAbortCtrl = null;
1498
1532
  const BASE_REFRESH_MS = 5000;
1499
1533
  const MAX_REFRESH_MS = 30000;
1500
1534
 
@@ -1524,6 +1558,16 @@
1524
1558
  function setStatus(status) {
1525
1559
  const dot = document.getElementById("statusDot");
1526
1560
  dot.className = "logo-dot " + (status === "connected" ? "" : status);
1561
+ if (status === "connected") hideErrorBanner();
1562
+ }
1563
+
1564
+ function showErrorBanner(msg) {
1565
+ const banner = document.getElementById("errorBanner");
1566
+ document.getElementById("errorBannerMsg").textContent = msg || "Connection failed";
1567
+ banner.classList.add("visible");
1568
+ }
1569
+ function hideErrorBanner() {
1570
+ document.getElementById("errorBanner").classList.remove("visible");
1527
1571
  }
1528
1572
 
1529
1573
  function toggleAutoRefresh() {
@@ -1626,10 +1670,16 @@
1626
1670
  FETCH & RENDER
1627
1671
  ================================================================ */
1628
1672
  async function fetchDashboard() {
1673
+ // Cancel any in-flight request to prevent NS_BINDING_ABORTED
1674
+ if (dashboardAbortCtrl) dashboardAbortCtrl.abort();
1675
+ dashboardAbortCtrl = new AbortController();
1676
+ const ctrl = dashboardAbortCtrl;
1629
1677
  try {
1630
1678
  let url = endpoint + "/api/dashboard";
1631
1679
  // Note: /api/dashboard doesn't support session filtering yet, handled client-side
1632
- const res = await fetch(url, { signal: AbortSignal.timeout(15000) });
1680
+ const timeoutId = setTimeout(() => ctrl.abort(), 8000);
1681
+ const res = await fetch(url, { signal: ctrl.signal });
1682
+ clearTimeout(timeoutId);
1633
1683
  if (!res.ok) throw new Error("HTTP " + res.status);
1634
1684
  const data = await res.json();
1635
1685
  setStatus("connected");
@@ -1639,10 +1689,13 @@
1639
1689
  }
1640
1690
  render(data);
1641
1691
  } catch (err) {
1692
+ if (err.name === "AbortError") return; // Intentional cancel, ignore
1642
1693
  setStatus("error");
1643
1694
  fetchErrorCount++;
1644
1695
  adjustRefreshInterval();
1645
- console.warn("HSA fetch error:", err.message);
1696
+ const msg = err.message || "Connection failed";
1697
+ console.warn("HSA fetch error:", msg);
1698
+ showErrorBanner(msg);
1646
1699
  }
1647
1700
  }
1648
1701
 
@@ -2247,7 +2300,7 @@
2247
2300
  renderSidebar(allSessions);
2248
2301
  populateContextSelectors(allSessions);
2249
2302
  })
2250
- .catch(() => {});
2303
+ .catch((err) => { console.warn("fetchSessions failed:", err.message || err); });
2251
2304
  }
2252
2305
 
2253
2306
  function connectSessionSSE() {
@@ -2406,9 +2459,15 @@
2406
2459
  INIT
2407
2460
  ================================================================ */
2408
2461
  document.getElementById("refreshToggle").classList.add("active");
2409
- connect();
2410
- startRefresh();
2411
- connectSessionSSE();
2462
+ // Fast health check first, then full data load
2463
+ (async () => {
2464
+ try {
2465
+ const res = await fetch(endpoint + "/api/health", { signal: AbortSignal.timeout(3000) });
2466
+ if (res.ok) { setStatus("connected"); hideErrorBanner(); }
2467
+ } catch { /* will fall through to connect() below */ }
2468
+ connect();
2469
+ startRefresh();
2470
+ })();
2412
2471
  window.addEventListener("resize", drawCacheChart);
2413
2472
  </script>
2414
2473
  </body>
package/logs.html CHANGED
@@ -628,6 +628,8 @@
628
628
  let filterTimeout = null;
629
629
  let allSessions = [];
630
630
  let sseRetryMs = 1000;
631
+ let sseRetryCount = 0;
632
+ const MAX_SSE_RETRIES = 5;
631
633
  let statsThrottleTimer = null;
632
634
 
633
635
  // ── Init ──────────────────────────────────────────────────
@@ -651,7 +653,9 @@
651
653
 
652
654
  setStatus("pending");
653
655
  try {
654
- const res = await fetch(baseUrl + "/api/logs?limit=200");
656
+ const res = await fetch(baseUrl + "/api/logs?limit=200", {
657
+ signal: AbortSignal.timeout(8000),
658
+ });
655
659
  if (!res.ok) throw new Error(res.statusText);
656
660
  const data = await res.json();
657
661
  entries = data.entries || [];
@@ -758,6 +762,14 @@
758
762
  eventSource.close();
759
763
  eventSource = null;
760
764
  }
765
+ sseRetryCount++;
766
+ if (sseRetryCount >= MAX_SSE_RETRIES) {
767
+ console.warn("SSE: Max retries reached, stopping log stream reconnect");
768
+ isStreaming = false;
769
+ document.getElementById("streamBtn").textContent = "▶ Stream";
770
+ document.getElementById("streamBtn").classList.remove("active");
771
+ return;
772
+ }
761
773
  // FE-01: Auto-reconnect with exponential backoff
762
774
  setTimeout(() => {
763
775
  if (isStreaming) startStream();
@@ -767,6 +779,7 @@
767
779
  eventSource.onopen = () => {
768
780
  setStatus("ok");
769
781
  sseRetryMs = 1000;
782
+ sseRetryCount = 0;
770
783
  };
771
784
  isStreaming = true;
772
785
  document.getElementById("streamBtn").textContent = "⏸ Pause";
@@ -804,7 +817,9 @@
804
817
  document.getElementById("statErrors").textContent = s.errorCount;
805
818
  document.getElementById("statAvg").textContent = s.avgDurationMs;
806
819
  document.getElementById("statRate").textContent = s.callsPerMinute;
807
- } catch {}
820
+ } catch (err) {
821
+ console.warn("fetchStats error:", err.message || err);
822
+ }
808
823
  }
809
824
 
810
825
  // ── Filters ───────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nockdev/hsa",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "HSA - Hierarchical Semantic Analysis MCP Server for AI Code Agents",
5
5
  "type": "module",
6
6
  "main": "dist/hsa-lib.bundle.js",