superlocalmemory 3.6.13 → 3.6.15

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.
Files changed (147) hide show
  1. package/.claude-plugin/marketplace.json +17 -0
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +189 -740
  4. package/package.json +12 -5
  5. package/plugin/.claude-plugin/plugin.json +20 -0
  6. package/plugin/.mcp.json +12 -0
  7. package/plugin/CLAUDE.md +44 -0
  8. package/plugin/_GENERATED.md +6 -0
  9. package/plugin/agents/slm-memory-advisor.md +44 -0
  10. package/plugin/agents/slm-optimize-advisor.md +38 -0
  11. package/plugin/hooks/hooks.json +14 -0
  12. package/plugin/requirements.txt +1 -0
  13. package/plugin/scripts/ensure-venv.bat +122 -0
  14. package/plugin/scripts/ensure-venv.sh +105 -0
  15. package/plugin/scripts/slm-launch +15 -0
  16. package/plugin/scripts/slm-launch.bat +17 -0
  17. package/plugin/settings.json +16 -0
  18. package/plugin/skills/slm-cache/SKILL.md +140 -0
  19. package/plugin/skills/slm-compress/SKILL.md +143 -0
  20. package/plugin/skills/slm-graph/SKILL.md +300 -0
  21. package/plugin/skills/slm-recall/SKILL.md +204 -0
  22. package/plugin/skills/slm-remember/SKILL.md +194 -0
  23. package/plugin/skills/slm-session/SKILL.md +207 -0
  24. package/plugin/skills/slm-status/SKILL.md +149 -0
  25. package/plugin-src/.mcp.json +12 -0
  26. package/plugin-src/agents/slm-memory-advisor.md +44 -0
  27. package/plugin-src/agents/slm-optimize-advisor.md +38 -0
  28. package/plugin-src/commands/slm-optimize.md +22 -0
  29. package/plugin-src/commands/slm-recall.md +16 -0
  30. package/plugin-src/commands/slm-remember.md +16 -0
  31. package/plugin-src/commands/slm-status.md +15 -0
  32. package/plugin-src/hooks/.gitkeep +0 -0
  33. package/plugin-src/hooks/hooks.json +14 -0
  34. package/plugin-src/manifest.json +25 -0
  35. package/plugin-src/requirements.txt +1 -0
  36. package/plugin-src/rules/AGENTS.md +91 -0
  37. package/plugin-src/rules/CLAUDE.md.fragment +44 -0
  38. package/plugin-src/scripts/ensure-venv.bat +122 -0
  39. package/plugin-src/scripts/ensure-venv.sh +105 -0
  40. package/plugin-src/scripts/slm-launch +15 -0
  41. package/plugin-src/scripts/slm-launch.bat +17 -0
  42. package/plugin-src/settings.json +16 -0
  43. package/plugin-src/skills/slm-cache/SKILL.md +140 -0
  44. package/plugin-src/skills/slm-compress/SKILL.md +143 -0
  45. package/plugin-src/skills/slm-graph/SKILL.md +300 -0
  46. package/plugin-src/skills/slm-recall/SKILL.md +204 -0
  47. package/plugin-src/skills/slm-remember/SKILL.md +194 -0
  48. package/plugin-src/skills/slm-session/SKILL.md +207 -0
  49. package/plugin-src/skills/slm-status/SKILL.md +149 -0
  50. package/pyproject.toml +6 -2
  51. package/scripts/__tests__/build-plugin.test.mjs +613 -0
  52. package/scripts/_savings_math.py +270 -0
  53. package/scripts/build-plugin.js +742 -0
  54. package/scripts/dogfood_savings.py +490 -0
  55. package/scripts/install-skills.ps1 +4 -334
  56. package/scripts/install-skills.sh +4 -435
  57. package/scripts/postinstall-interactive.js +0 -27
  58. package/scripts/postinstall.js +21 -2
  59. package/src/superlocalmemory/__init__.py +1 -1
  60. package/src/superlocalmemory/cli/_lazy_init.py +115 -0
  61. package/src/superlocalmemory/cli/commands.py +439 -41
  62. package/src/superlocalmemory/cli/main.py +92 -4
  63. package/src/superlocalmemory/cli/setup_wizard.py +47 -6
  64. package/src/superlocalmemory/core/backend_orchestrator.py +12 -8
  65. package/src/superlocalmemory/core/config.py +194 -9
  66. package/src/superlocalmemory/core/embeddings.py +10 -5
  67. package/src/superlocalmemory/core/engine.py +76 -5
  68. package/src/superlocalmemory/core/fact_consolidator.py +20 -3
  69. package/src/superlocalmemory/core/platform_utils.py +8 -0
  70. package/src/superlocalmemory/core/recall_pipeline.py +7 -0
  71. package/src/superlocalmemory/core/recall_worker.py +7 -0
  72. package/src/superlocalmemory/core/store_pipeline.py +23 -1
  73. package/src/superlocalmemory/core/worker_pool.py +14 -2
  74. package/src/superlocalmemory/hooks/claude_code_hooks.py +27 -3
  75. package/src/superlocalmemory/hooks/portable_kit.py +506 -0
  76. package/src/superlocalmemory/hooks/session_registry.py +8 -4
  77. package/src/superlocalmemory/infra/cloud_backup.py +99 -23
  78. package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -2
  79. package/src/superlocalmemory/mcp/_pool_adapter.py +15 -6
  80. package/src/superlocalmemory/mcp/cli_fallback.py +602 -0
  81. package/src/superlocalmemory/mcp/server.py +75 -4
  82. package/src/superlocalmemory/mcp/tools_code_graph.py +3 -3
  83. package/src/superlocalmemory/mcp/tools_core.py +37 -4
  84. package/src/superlocalmemory/mcp/tools_v3.py +6 -1
  85. package/src/superlocalmemory/mcp/tools_v33.py +8 -4
  86. package/src/superlocalmemory/optimize/cache/boundary_store.py +25 -6
  87. package/src/superlocalmemory/optimize/cache/centroid_store.py +27 -4
  88. package/src/superlocalmemory/optimize/cache/manager.py +92 -6
  89. package/src/superlocalmemory/optimize/cache/semantic.py +20 -1
  90. package/src/superlocalmemory/optimize/compress/ccr.py +12 -0
  91. package/src/superlocalmemory/optimize/compress/router.py +46 -13
  92. package/src/superlocalmemory/optimize/config/schema.py +6 -0
  93. package/src/superlocalmemory/optimize/proxy/_helpers.py +111 -8
  94. package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +14 -4
  95. package/src/superlocalmemory/optimize/proxy/gemini_surface.py +23 -6
  96. package/src/superlocalmemory/optimize/proxy/openai_surface.py +10 -4
  97. package/src/superlocalmemory/optimize/proxy/server.py +11 -0
  98. package/src/superlocalmemory/optimize/proxy/vertex_surface.py +246 -0
  99. package/src/superlocalmemory/optimize/storage/db.py +30 -0
  100. package/src/superlocalmemory/retrieval/bm25_channel.py +12 -2
  101. package/src/superlocalmemory/retrieval/engine.py +36 -3
  102. package/src/superlocalmemory/retrieval/entity_channel.py +5 -5
  103. package/src/superlocalmemory/retrieval/hopfield_channel.py +10 -2
  104. package/src/superlocalmemory/retrieval/semantic_channel.py +10 -2
  105. package/src/superlocalmemory/server/recall_serializer.py +3 -1
  106. package/src/superlocalmemory/server/unified_daemon.py +156 -16
  107. package/src/superlocalmemory/storage/database.py +215 -43
  108. package/src/superlocalmemory/storage/migration_runner.py +17 -1
  109. package/src/superlocalmemory/storage/migrations/M016_add_scope_support.py +120 -0
  110. package/src/superlocalmemory/storage/models.py +10 -0
  111. package/src/superlocalmemory/storage/schema.py +15 -10
  112. package/src/superlocalmemory/ui/css/legacy-dashboard.css +18 -0
  113. package/src/superlocalmemory/ui/css/neural-glass.css +5 -0
  114. package/src/superlocalmemory/ui/index.html +2 -2
  115. package/src/superlocalmemory/ui/js/core.js +98 -0
  116. package/src/superlocalmemory/ui/js/dashboard.js +8 -1
  117. package/src/superlocalmemory/ui/js/ide-status.js +16 -3
  118. package/src/superlocalmemory/ui/js/math-health.js +15 -3
  119. package/src/superlocalmemory/ui/js/optimize.js +18 -2
  120. package/src/superlocalmemory/ui/js/trust-dashboard.js +10 -1
  121. package/src/superlocalmemory.egg-info/PKG-INFO +191 -741
  122. package/src/superlocalmemory.egg-info/SOURCES.txt +7 -9
  123. package/src/superlocalmemory.egg-info/requires.txt +1 -0
  124. package/ide/skills/slm-build-graph/SKILL.md +0 -423
  125. package/ide/skills/slm-list-recent/SKILL.md +0 -348
  126. package/ide/skills/slm-recall/SKILL.md +0 -326
  127. package/ide/skills/slm-remember/SKILL.md +0 -194
  128. package/ide/skills/slm-show-patterns/SKILL.md +0 -224
  129. package/ide/skills/slm-status/SKILL.md +0 -363
  130. package/ide/skills/slm-switch-profile/SKILL.md +0 -442
  131. package/skills/slm-build-graph/SKILL.md +0 -423
  132. package/skills/slm-list-recent/SKILL.md +0 -348
  133. package/skills/slm-optimize/README.md +0 -55
  134. package/skills/slm-optimize/SKILL.md +0 -139
  135. package/skills/slm-recall/SKILL.md +0 -343
  136. package/skills/slm-remember/SKILL.md +0 -194
  137. package/skills/slm-show-patterns/SKILL.md +0 -224
  138. package/skills/slm-status/SKILL.md +0 -363
  139. package/skills/slm-switch-profile/SKILL.md +0 -442
  140. package/src/superlocalmemory/cli/doctor_cmd.py +0 -152
  141. package/src/superlocalmemory/skills/slm-build-graph/SKILL.md +0 -423
  142. package/src/superlocalmemory/skills/slm-list-recent/SKILL.md +0 -348
  143. package/src/superlocalmemory/skills/slm-recall/SKILL.md +0 -343
  144. package/src/superlocalmemory/skills/slm-remember/SKILL.md +0 -194
  145. package/src/superlocalmemory/skills/slm-show-patterns/SKILL.md +0 -224
  146. package/src/superlocalmemory/skills/slm-status/SKILL.md +0 -363
  147. package/src/superlocalmemory/skills/slm-switch-profile/SKILL.md +0 -442
@@ -110,6 +110,8 @@ _SQL_MEMORIES: Final[str] = """
110
110
  CREATE TABLE IF NOT EXISTS memories (
111
111
  memory_id TEXT PRIMARY KEY,
112
112
  profile_id TEXT NOT NULL DEFAULT 'default',
113
+ scope TEXT NOT NULL DEFAULT 'personal',
114
+ shared_with TEXT,
113
115
  content TEXT NOT NULL,
114
116
  session_id TEXT NOT NULL DEFAULT '',
115
117
  speaker TEXT NOT NULL DEFAULT '',
@@ -127,8 +129,7 @@ CREATE INDEX IF NOT EXISTS idx_memories_profile
127
129
  CREATE INDEX IF NOT EXISTS idx_memories_session
128
130
  ON memories (profile_id, session_id);
129
131
  CREATE INDEX IF NOT EXISTS idx_memories_created
130
- ON memories (created_at);
131
- """
132
+ ON memories (created_at);"""
132
133
 
133
134
 
134
135
  # ---------------------------------------------------------------------------
@@ -140,6 +141,8 @@ CREATE TABLE IF NOT EXISTS atomic_facts (
140
141
  fact_id TEXT PRIMARY KEY,
141
142
  memory_id TEXT NOT NULL,
142
143
  profile_id TEXT NOT NULL DEFAULT 'default',
144
+ scope TEXT NOT NULL DEFAULT 'personal',
145
+ shared_with TEXT,
143
146
  content TEXT NOT NULL,
144
147
  fact_type TEXT NOT NULL DEFAULT 'semantic'
145
148
  CHECK (fact_type IN (
@@ -208,8 +211,7 @@ CREATE INDEX IF NOT EXISTS idx_facts_referenced_date
208
211
  WHERE referenced_date IS NOT NULL;
209
212
  CREATE INDEX IF NOT EXISTS idx_facts_interval
210
213
  ON atomic_facts (profile_id, interval_start, interval_end)
211
- WHERE interval_start IS NOT NULL;
212
- """
214
+ WHERE interval_start IS NOT NULL;"""
213
215
 
214
216
 
215
217
  # ---------------------------------------------------------------------------
@@ -287,6 +289,8 @@ _SQL_CANONICAL_ENTITIES: Final[str] = """
287
289
  CREATE TABLE IF NOT EXISTS canonical_entities (
288
290
  entity_id TEXT PRIMARY KEY,
289
291
  profile_id TEXT NOT NULL DEFAULT 'default',
292
+ scope TEXT NOT NULL DEFAULT 'personal',
293
+ shared_with TEXT,
290
294
  canonical_name TEXT NOT NULL,
291
295
  entity_type TEXT NOT NULL DEFAULT '',
292
296
  first_seen TEXT NOT NULL DEFAULT (datetime('now')),
@@ -302,8 +306,7 @@ CREATE INDEX IF NOT EXISTS idx_entities_profile
302
306
  CREATE INDEX IF NOT EXISTS idx_entities_name_lower
303
307
  ON canonical_entities (profile_id, canonical_name COLLATE NOCASE);
304
308
  CREATE INDEX IF NOT EXISTS idx_entities_type
305
- ON canonical_entities (profile_id, entity_type);
306
- """
309
+ ON canonical_entities (profile_id, entity_type);"""
307
310
 
308
311
 
309
312
  # ---------------------------------------------------------------------------
@@ -386,6 +389,8 @@ _SQL_TEMPORAL_EVENTS: Final[str] = """
386
389
  CREATE TABLE IF NOT EXISTS temporal_events (
387
390
  event_id TEXT PRIMARY KEY,
388
391
  profile_id TEXT NOT NULL DEFAULT 'default',
392
+ scope TEXT NOT NULL DEFAULT 'personal',
393
+ shared_with TEXT,
389
394
  entity_id TEXT NOT NULL,
390
395
  fact_id TEXT NOT NULL,
391
396
  observation_date TEXT,
@@ -408,8 +413,7 @@ CREATE INDEX IF NOT EXISTS idx_tevents_entity
408
413
  ON temporal_events (profile_id, entity_id);
409
414
  CREATE INDEX IF NOT EXISTS idx_tevents_date_range
410
415
  ON temporal_events (profile_id, referenced_date)
411
- WHERE referenced_date IS NOT NULL;
412
- """
416
+ WHERE referenced_date IS NOT NULL;"""
413
417
 
414
418
 
415
419
  # ---------------------------------------------------------------------------
@@ -420,6 +424,8 @@ _SQL_GRAPH_EDGES: Final[str] = """
420
424
  CREATE TABLE IF NOT EXISTS graph_edges (
421
425
  edge_id TEXT PRIMARY KEY,
422
426
  profile_id TEXT NOT NULL DEFAULT 'default',
427
+ scope TEXT NOT NULL DEFAULT 'personal',
428
+ shared_with TEXT,
423
429
  source_id TEXT NOT NULL,
424
430
  target_id TEXT NOT NULL,
425
431
  edge_type TEXT NOT NULL DEFAULT 'entity'
@@ -443,8 +449,7 @@ CREATE INDEX IF NOT EXISTS idx_edges_target
443
449
  CREATE INDEX IF NOT EXISTS idx_edges_type
444
450
  ON graph_edges (profile_id, edge_type);
445
451
  CREATE INDEX IF NOT EXISTS idx_edges_exists_check
446
- ON graph_edges (profile_id, source_id, target_id, edge_type);
447
- """
452
+ ON graph_edges (profile_id, source_id, target_id, edge_type);"""
448
453
 
449
454
 
450
455
  # ---------------------------------------------------------------------------
@@ -202,6 +202,24 @@
202
202
  margin-bottom: 12px;
203
203
  }
204
204
 
205
+ /* Pane error state — WP-12 */
206
+ .pane-error {
207
+ text-align: center;
208
+ padding: 40px;
209
+ color: var(--bs-danger, #dc3545);
210
+ }
211
+
212
+ .pane-error i {
213
+ font-size: 2rem;
214
+ display: block;
215
+ margin-bottom: 8px;
216
+ opacity: 0.7;
217
+ }
218
+
219
+ .pane-error p {
220
+ margin-bottom: 12px;
221
+ }
222
+
205
223
  .tooltip-custom {
206
224
  position: absolute;
207
225
  padding: 10px;
@@ -1586,3 +1586,8 @@ body.ng-privacy-blur::before {
1586
1586
  max-width: 1600px;
1587
1587
  }
1588
1588
  }
1589
+
1590
+ /* WP-12 — pane-error dark-mode override */
1591
+ .ng-dark .pane-error {
1592
+ color: var(--ng-status-error);
1593
+ }
@@ -1242,7 +1242,7 @@
1242
1242
  </div>
1243
1243
  <div class="card-body">
1244
1244
  <div class="row g-3">
1245
- <div class="col-md-6">
1245
+ <div class="col-md-6" id="optimize-config-card">
1246
1246
  <h6>Controls</h6>
1247
1247
  <div class="form-check form-switch mb-2">
1248
1248
  <input class="form-check-input" type="checkbox" id="opt-enabled">
@@ -1282,7 +1282,7 @@
1282
1282
  <label class="form-check-label" for="opt-compress-prose">Prose Compression</label>
1283
1283
  </div>
1284
1284
  </div>
1285
- <div class="col-md-6">
1285
+ <div class="col-md-6" id="optimize-savings-card">
1286
1286
  <h6>Savings</h6>
1287
1287
  <table class="table table-sm">
1288
1288
  <tr><td>Tokens Saved</td><td id="opt-tokens-saved">-</td></tr>
@@ -199,6 +199,104 @@ function showEmpty(containerId, icon, message) {
199
199
  el.appendChild(wrapper);
200
200
  }
201
201
 
202
+ // ============================================================================
203
+ // Pane error state — WP-12
204
+ // showPaneError / clearPaneError / paneErrorMessage
205
+ // ============================================================================
206
+
207
+ /**
208
+ * Map an HTTP status (or 0 for network failure) to a user-readable message.
209
+ * @param {number} status 0 = network/abort, ≥400 = HTTP error
210
+ * @returns {string}
211
+ */
212
+ function paneErrorMessage(status) {
213
+ if (!status || status === 0) {
214
+ return 'Service unavailable — check network connection';
215
+ }
216
+ if (status >= 500) {
217
+ return 'Server error ' + status + ' — daemon may be down';
218
+ }
219
+ return 'Request failed ' + status;
220
+ }
221
+
222
+ /**
223
+ * Render an inline error banner inside a pane container.
224
+ *
225
+ * slotMode=false (container panes — math-health, ide-status):
226
+ * Clears the container and appends the error div directly.
227
+ *
228
+ * slotMode=true (field-scatter panes — dashboard, trust, optimize):
229
+ * Inserts/replaces a #<containerId>-error-slot div at the top of the
230
+ * container, leaving existing field values visible.
231
+ *
232
+ * @param {string} containerId - getElementById target
233
+ * @param {string} message - user-facing message (set via textContent — XSS-safe)
234
+ * @param {Function|null} onRetry - callback for Retry button; null = no button
235
+ * @param {boolean} slotMode - true for field-scatter panes
236
+ */
237
+ function showPaneError(containerId, message, onRetry, slotMode) {
238
+ var el = document.getElementById(containerId);
239
+ if (!el) return;
240
+
241
+ // Build the error div
242
+ var errDiv = document.createElement('div');
243
+ errDiv.className = 'pane-error';
244
+ errDiv.setAttribute('role', 'alert');
245
+
246
+ var icon = document.createElement('i');
247
+ icon.className = 'bi bi-exclamation-triangle';
248
+ errDiv.appendChild(icon);
249
+
250
+ var p = document.createElement('p');
251
+ p.textContent = message; // textContent — no XSS risk
252
+ errDiv.appendChild(p);
253
+
254
+ if (typeof onRetry === 'function') {
255
+ var btn = document.createElement('button');
256
+ btn.className = 'btn btn-sm btn-outline-danger pane-error-retry';
257
+ btn.textContent = 'Retry';
258
+ // Capture onRetry lexically to avoid closure issues in IIFEs (CRIT-1)
259
+ (function(cb) {
260
+ btn.addEventListener('click', function() { cb(); });
261
+ }(onRetry));
262
+ errDiv.appendChild(btn);
263
+ }
264
+
265
+ if (slotMode) {
266
+ // Insert/replace error slot at top of container
267
+ var slotId = containerId + '-error-slot';
268
+ errDiv.id = slotId;
269
+ var existing = document.getElementById(slotId);
270
+ if (existing) {
271
+ existing.parentNode.replaceChild(errDiv, existing);
272
+ } else {
273
+ el.insertBefore(errDiv, el.firstChild);
274
+ }
275
+ } else {
276
+ // Replace container contents
277
+ el.textContent = '';
278
+ el.appendChild(errDiv);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Remove the error state previously set by showPaneError.
284
+ *
285
+ * @param {string} containerId - getElementById target
286
+ * @param {boolean} slotMode - must match the slotMode used in showPaneError
287
+ */
288
+ function clearPaneError(containerId, slotMode) {
289
+ if (slotMode) {
290
+ var slot = document.getElementById(containerId + '-error-slot');
291
+ if (slot && slot.parentNode) slot.parentNode.removeChild(slot);
292
+ } else {
293
+ var el = document.getElementById(containerId);
294
+ if (!el) return;
295
+ var err = el.querySelector('.pane-error');
296
+ if (err) el.removeChild(err);
297
+ }
298
+ }
299
+
202
300
  // ============================================================================
203
301
  // Safe HTML builder — tagged template for escaped interpolation
204
302
  // ============================================================================
@@ -17,11 +17,17 @@ window.addEventListener('hashchange', function() {
17
17
  window.addEventListener('focus', function() { loadDashboard(); });
18
18
 
19
19
  async function loadDashboard() {
20
+ var PANE_ID = 'dashboard-pane';
20
21
  try {
21
22
  var response = await fetch('/api/v3/dashboard');
22
- if (!response.ok) return;
23
+ if (!response.ok) {
24
+ showPaneError(PANE_ID, paneErrorMessage(response.status), loadDashboard, true);
25
+ return;
26
+ }
23
27
  var data = await response.json();
24
28
 
29
+ clearPaneError(PANE_ID, true);
30
+
25
31
  document.getElementById('dashboard-mode').textContent = 'Mode ' + data.mode.toUpperCase();
26
32
  document.getElementById('dashboard-mode-desc').textContent = data.mode_name + (data.provider !== 'none' ? ' — ' + data.provider : '');
27
33
  document.getElementById('dashboard-memory-count').textContent = data.fact_count || data.memory_count || '0';
@@ -44,6 +50,7 @@ async function loadDashboard() {
44
50
  btn.classList.toggle('active', btn.dataset.mode === data.mode);
45
51
  });
46
52
  } catch (e) {
53
+ showPaneError(PANE_ID, paneErrorMessage(0), loadDashboard, true);
47
54
  console.log('Dashboard load error:', e);
48
55
  }
49
56
  }
@@ -2,14 +2,26 @@
2
2
  // Displays detected IDEs and allows connecting them to SLM.
3
3
 
4
4
  async function loadIDEStatus() {
5
+ var CID = 'ide-list-body';
5
6
  try {
6
7
  var response = await fetch('/api/v3/ide/status');
7
- if (!response.ok) return;
8
+ if (!response.ok) {
9
+ showPaneError(CID, paneErrorMessage(response.status), loadIDEStatus, false);
10
+ return;
11
+ }
8
12
  var data = await response.json();
9
13
 
10
- var tbody = document.getElementById('ide-list-body');
14
+ var ides = data.ides || [];
15
+
16
+ // AC5: empty array → empty state
17
+ if (ides.length === 0) {
18
+ showEmpty(CID, 'laptop', 'No IDEs detected');
19
+ return;
20
+ }
21
+
22
+ var tbody = document.getElementById(CID);
11
23
  tbody.textContent = '';
12
- (data.ides || []).forEach(function(ide) {
24
+ ides.forEach(function(ide) {
13
25
  var tr = document.createElement('tr');
14
26
 
15
27
  // IDE name cell
@@ -52,6 +64,7 @@ async function loadIDEStatus() {
52
64
  tbody.appendChild(tr);
53
65
  });
54
66
  } catch (e) {
67
+ showPaneError('ide-list-body', paneErrorMessage(0), loadIDEStatus, false);
55
68
  console.log('IDE status error:', e);
56
69
  }
57
70
  }
@@ -2,15 +2,26 @@
2
2
  // Displays status of Fisher-Rao, sheaf cohomology, and Langevin dynamics layers.
3
3
 
4
4
  async function loadMathHealth() {
5
+ var CID = 'math-health-cards';
5
6
  try {
6
7
  var response = await fetch('/api/v3/math/health');
7
- if (!response.ok) return;
8
+ if (!response.ok) {
9
+ showPaneError(CID, paneErrorMessage(response.status), loadMathHealth, false);
10
+ return;
11
+ }
8
12
  var data = await response.json();
9
13
 
10
- var container = document.getElementById('math-health-cards');
14
+ var layers = data.health || {};
15
+
16
+ // AC5: empty object → empty state
17
+ if (!layers || Object.keys(layers).length === 0) {
18
+ showEmpty(CID, 'calculator', 'No math health data available');
19
+ return;
20
+ }
21
+
22
+ var container = document.getElementById(CID);
11
23
  container.textContent = '';
12
24
 
13
- var layers = data.health || {};
14
25
  var colors = { fisher: 'primary', sheaf: 'success', langevin: 'info' };
15
26
  var icons = { fisher: 'bi-graph-up', sheaf: 'bi-diagram-3', langevin: 'bi-activity' };
16
27
 
@@ -91,6 +102,7 @@ async function loadMathHealth() {
91
102
  container.appendChild(col);
92
103
  });
93
104
  } catch (e) {
105
+ showPaneError('math-health-cards', paneErrorMessage(0), loadMathHealth, false);
94
106
  console.log('Math health error:', e);
95
107
  }
96
108
  }
@@ -17,11 +17,17 @@
17
17
  _pollTimer = setInterval(_loadSavings, 10000);
18
18
  }
19
19
 
20
+ var CFG_CARD = 'optimize-config-card';
21
+
20
22
  async function _loadOptimizeConfig() {
21
23
  try {
22
24
  var resp = await fetch('/api/optimize/config');
23
- if (!resp.ok) return;
25
+ if (!resp.ok) {
26
+ showPaneError(CFG_CARD, paneErrorMessage(resp.status), _loadOptimizeConfig, true);
27
+ return;
28
+ }
24
29
  var cfg = await resp.json();
30
+ clearPaneError(CFG_CARD, true);
25
31
  _setToggle('opt-enabled', cfg.enabled);
26
32
  _setToggle('opt-proxy-enabled', cfg.proxy_enabled);
27
33
  _setToggle('opt-cache-enabled', cfg.cache_enabled);
@@ -32,15 +38,23 @@
32
38
  var verEl = document.getElementById('opt-config-version');
33
39
  if (verEl) verEl.textContent = cfg.config_version || '-';
34
40
  } catch (e) {
41
+ showPaneError(CFG_CARD, paneErrorMessage(0), _loadOptimizeConfig, true);
35
42
  console.log('Optimize config load error:', e);
36
43
  }
37
44
  }
38
45
 
46
+ var SAV_CARD = 'optimize-savings-card';
47
+
39
48
  async function _loadSavings() {
40
49
  try {
41
50
  var resp = await fetch('/api/optimize/savings');
42
- if (!resp.ok) return;
51
+ if (!resp.ok) {
52
+ // D-3: no Retry on polling loader \u2014 auto-heals on next poll
53
+ showPaneError(SAV_CARD, paneErrorMessage(resp.status), null, true);
54
+ return;
55
+ }
43
56
  var data = await resp.json();
57
+ clearPaneError(SAV_CARD, true);
44
58
  var tokensSaved = (data.tokens_saved_input || 0) + (data.tokens_saved_output || 0) + (data.tokens_saved_compress || 0);
45
59
  _setText('opt-tokens-saved', tokensSaved.toLocaleString());
46
60
  var costSaved = data.cost_saved || {};
@@ -57,6 +71,8 @@
57
71
  _setText('opt-stale-warning', 'Pricing data may be outdated');
58
72
  }
59
73
  } catch (e) {
74
+ // D-3: no Retry on polling loader
75
+ showPaneError(SAV_CARD, paneErrorMessage(0), null, true);
60
76
  console.log('Savings load error:', e);
61
77
  }
62
78
  }
@@ -127,12 +127,20 @@
127
127
  if (next) next.disabled = STATE.page >= maxPage;
128
128
  }
129
129
 
130
+ var TRUST_PANE_ID = 'operations-pane';
131
+
130
132
  async function loadTrustDashboard() {
131
133
  try {
132
134
  var resp = await fetch('/api/v3/trust/dashboard');
133
- if (!resp.ok) return;
135
+ if (!resp.ok) {
136
+ // CRIT-1: pass lexically-scoped loadTrustDashboard (inside IIFE)
137
+ showPaneError(TRUST_PANE_ID, paneErrorMessage(resp.status), loadTrustDashboard, true);
138
+ return;
139
+ }
134
140
  var data = await resp.json();
135
141
 
142
+ clearPaneError(TRUST_PANE_ID, true);
143
+
136
144
  var agents = data.agents || [];
137
145
  STATE.agents = agents;
138
146
  STATE.page = 0;
@@ -154,6 +162,7 @@
154
162
 
155
163
  _renderPage();
156
164
  } catch (e) {
165
+ showPaneError(TRUST_PANE_ID, paneErrorMessage(0), loadTrustDashboard, true);
157
166
  if (window.console && window.console.debug) {
158
167
  window.console.debug('Trust dashboard error:', e);
159
168
  }