instar 0.24.16 → 0.24.17

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.
@@ -205,6 +205,11 @@
205
205
  .type-badge.job { background: #1b2e1b; color: #86efac; }
206
206
  .type-badge.interactive { background: #2e2e1b; color: #fde047; }
207
207
 
208
+ .platform-badge {
209
+ font-size: 12px;
210
+ cursor: default;
211
+ }
212
+
208
213
  .session-telemetry {
209
214
  display: flex;
210
215
  align-items: center;
@@ -1957,139 +1962,269 @@
1957
1962
  color: var(--text);
1958
1963
  }
1959
1964
 
1960
- .systems-summary {
1965
+ /* Health Banner */
1966
+ .health-banner {
1961
1967
  display: flex;
1968
+ align-items: center;
1962
1969
  gap: 16px;
1970
+ padding: 20px 24px;
1971
+ border-radius: 10px;
1963
1972
  margin-bottom: 24px;
1964
- flex-wrap: wrap;
1973
+ border: 1px solid var(--border);
1974
+ }
1975
+
1976
+ .health-banner.healthy {
1977
+ background: rgba(76, 175, 80, 0.08);
1978
+ border-color: rgba(76, 175, 80, 0.25);
1965
1979
  }
1966
1980
 
1967
- .systems-summary-card {
1981
+ .health-banner.error {
1982
+ background: rgba(244, 67, 54, 0.08);
1983
+ border-color: rgba(244, 67, 54, 0.25);
1984
+ }
1985
+
1986
+ .health-banner.degraded {
1987
+ background: rgba(255, 152, 0, 0.08);
1988
+ border-color: rgba(255, 152, 0, 0.25);
1989
+ }
1990
+
1991
+ .health-icon {
1992
+ font-size: 28px;
1993
+ line-height: 1;
1994
+ flex-shrink: 0;
1995
+ }
1996
+
1997
+ .health-banner.healthy .health-icon { color: var(--accent); }
1998
+ .health-banner.error .health-icon { color: var(--red); }
1999
+ .health-banner.degraded .health-icon { color: var(--orange); }
2000
+
2001
+ .health-text {
1968
2002
  flex: 1;
1969
- min-width: 140px;
1970
- padding: 14px 18px;
2003
+ }
2004
+
2005
+ .health-title {
2006
+ font-size: 16px;
2007
+ font-weight: 600;
2008
+ color: var(--text-bright);
2009
+ margin-bottom: 4px;
2010
+ }
2011
+
2012
+ .health-meta {
2013
+ font-size: 12px;
2014
+ color: var(--text-dim);
2015
+ }
2016
+
2017
+ /* Issues Panel */
2018
+ .issues-panel {
2019
+ margin-bottom: 24px;
2020
+ }
2021
+
2022
+ .issue-card {
2023
+ display: flex;
2024
+ align-items: flex-start;
2025
+ gap: 12px;
2026
+ padding: 12px 16px;
1971
2027
  background: var(--bg-panel);
1972
2028
  border: 1px solid var(--border);
1973
- border-radius: 8px;
1974
- text-align: center;
2029
+ border-left: 3px solid var(--red);
2030
+ border-radius: 6px;
2031
+ margin-bottom: 8px;
1975
2032
  }
1976
2033
 
1977
- .systems-summary-card .summary-value {
1978
- font-size: 22px;
1979
- font-weight: 700;
2034
+ .issue-card.warning {
2035
+ border-left-color: var(--orange);
2036
+ }
2037
+
2038
+ .issue-card-icon {
2039
+ font-size: 14px;
2040
+ margin-top: 1px;
2041
+ flex-shrink: 0;
2042
+ }
2043
+
2044
+ .issue-card-content {
2045
+ flex: 1;
2046
+ }
2047
+
2048
+ .issue-card-label {
2049
+ font-size: 13px;
2050
+ font-weight: 500;
1980
2051
  color: var(--text-bright);
2052
+ margin-bottom: 2px;
1981
2053
  }
1982
2054
 
1983
- .systems-summary-card .summary-label {
1984
- font-size: 11px;
2055
+ .issue-card-desc {
2056
+ font-size: 12px;
1985
2057
  color: var(--text-dim);
1986
- margin-top: 4px;
1987
2058
  }
1988
2059
 
1989
- .systems-section {
2060
+ /* Capabilities Grid */
2061
+ .capabilities-section {
1990
2062
  margin-bottom: 24px;
1991
2063
  }
1992
2064
 
1993
- .systems-section h3 {
1994
- font-size: 13px;
2065
+ .capabilities-section-title {
2066
+ font-size: 12px;
1995
2067
  font-weight: 600;
1996
- color: var(--text-bright);
2068
+ color: var(--text-dim);
2069
+ text-transform: uppercase;
2070
+ letter-spacing: 0.5px;
1997
2071
  margin-bottom: 12px;
1998
2072
  }
1999
2073
 
2000
- .systems-category {
2074
+ /* Capability Cards */
2075
+ .capability-card {
2001
2076
  background: var(--bg-panel);
2002
2077
  border: 1px solid var(--border);
2003
2078
  border-radius: 8px;
2004
- margin-bottom: 8px;
2079
+ margin-bottom: 6px;
2005
2080
  overflow: hidden;
2081
+ transition: border-color 0.15s;
2082
+ }
2083
+
2084
+ .capability-card:hover {
2085
+ border-color: rgba(255,255,255,0.12);
2006
2086
  }
2007
2087
 
2008
- .systems-category-header {
2088
+ .capability-card.expanded {
2089
+ border-color: var(--accent);
2090
+ }
2091
+
2092
+ .capability-card-header {
2009
2093
  display: flex;
2010
2094
  align-items: center;
2011
- gap: 10px;
2012
- padding: 12px 16px;
2095
+ gap: 12px;
2096
+ padding: 14px 16px;
2013
2097
  cursor: pointer;
2014
2098
  user-select: none;
2015
- transition: background 0.15s;
2016
2099
  }
2017
2100
 
2018
- .systems-category-header:hover {
2019
- background: var(--bg-hover);
2101
+ .cap-dot {
2102
+ width: 8px;
2103
+ height: 8px;
2104
+ border-radius: 50%;
2105
+ flex-shrink: 0;
2106
+ }
2107
+
2108
+ .cap-dot.active { background: var(--accent); }
2109
+ .cap-dot.error { background: var(--red); }
2110
+
2111
+ .cap-info {
2112
+ flex: 1;
2113
+ min-width: 0;
2114
+ }
2115
+
2116
+ .cap-label {
2117
+ font-size: 13px;
2118
+ color: var(--text-bright);
2119
+ font-weight: 500;
2120
+ margin-bottom: 2px;
2020
2121
  }
2021
2122
 
2022
- .systems-category-arrow {
2123
+ .cap-metric {
2124
+ font-size: 11px;
2125
+ color: var(--text-dim);
2126
+ white-space: nowrap;
2127
+ overflow: hidden;
2128
+ text-overflow: ellipsis;
2129
+ }
2130
+
2131
+ .cap-arrow {
2023
2132
  font-size: 10px;
2024
2133
  color: var(--text-dim);
2025
2134
  transition: transform 0.2s;
2026
- width: 12px;
2135
+ flex-shrink: 0;
2027
2136
  }
2028
2137
 
2029
- .systems-category.expanded .systems-category-arrow {
2138
+ .capability-card.expanded .cap-arrow {
2030
2139
  transform: rotate(90deg);
2031
2140
  }
2032
2141
 
2033
- .systems-category-dot {
2034
- width: 8px;
2035
- height: 8px;
2036
- border-radius: 50%;
2037
- flex-shrink: 0;
2142
+ /* Capability Detail (expandable) */
2143
+ .cap-detail {
2144
+ display: none;
2145
+ padding: 0 16px 14px;
2146
+ border-top: 1px solid var(--border);
2038
2147
  }
2039
2148
 
2040
- .systems-category-name {
2041
- font-size: 13px;
2042
- font-weight: 500;
2043
- color: var(--text-bright);
2044
- flex: 1;
2149
+ .capability-card.expanded .cap-detail {
2150
+ display: block;
2045
2151
  }
2046
2152
 
2047
- .systems-category-count {
2048
- font-size: 11px;
2153
+ .cap-detail-desc {
2154
+ font-size: 12px;
2049
2155
  color: var(--text-dim);
2156
+ margin: 12px 0 10px;
2157
+ line-height: 1.5;
2050
2158
  }
2051
2159
 
2052
- .systems-category-body {
2053
- display: none;
2054
- padding: 0 16px 12px;
2055
- gap: 8px;
2160
+ .cap-detail-label {
2161
+ font-size: 10px;
2162
+ font-weight: 600;
2163
+ color: var(--text-dim);
2164
+ text-transform: uppercase;
2165
+ letter-spacing: 0.5px;
2166
+ margin-bottom: 6px;
2056
2167
  }
2057
2168
 
2058
- .systems-category.expanded .systems-category-body {
2169
+ .cap-detail-processes {
2059
2170
  display: flex;
2060
2171
  flex-wrap: wrap;
2172
+ gap: 6px;
2061
2173
  }
2062
2174
 
2063
- .systems-process-card {
2175
+ .cap-detail-proc {
2064
2176
  display: flex;
2065
2177
  align-items: center;
2066
- gap: 8px;
2067
- padding: 8px 12px;
2178
+ gap: 6px;
2179
+ padding: 4px 10px;
2068
2180
  background: var(--bg);
2069
2181
  border: 1px solid var(--border);
2070
- border-radius: 6px;
2071
- min-width: 200px;
2182
+ border-radius: 4px;
2183
+ font-size: 11px;
2184
+ color: var(--text);
2072
2185
  }
2073
2186
 
2074
- .systems-process-dot {
2075
- width: 6px;
2076
- height: 6px;
2187
+ .cap-detail-proc-dot {
2188
+ width: 5px;
2189
+ height: 5px;
2077
2190
  border-radius: 50%;
2078
- flex-shrink: 0;
2079
2191
  }
2080
2192
 
2081
- .systems-process-name {
2193
+ /* Events Disclosure */
2194
+ .events-disclosure {
2195
+ margin-top: 8px;
2196
+ }
2197
+
2198
+ .events-disclosure-toggle {
2199
+ display: flex;
2200
+ align-items: center;
2201
+ gap: 8px;
2202
+ padding: 10px 0;
2203
+ cursor: pointer;
2204
+ user-select: none;
2082
2205
  font-size: 12px;
2206
+ color: var(--text-dim);
2207
+ border: none;
2208
+ background: none;
2209
+ width: 100%;
2210
+ text-align: left;
2211
+ }
2212
+
2213
+ .events-disclosure-toggle:hover {
2083
2214
  color: var(--text);
2084
2215
  }
2085
2216
 
2086
- .systems-process-status {
2217
+ .events-disclosure-arrow {
2087
2218
  font-size: 10px;
2088
- color: var(--text-dim);
2089
- margin-left: auto;
2219
+ transition: transform 0.2s;
2220
+ }
2221
+
2222
+ .events-disclosure.expanded .events-disclosure-arrow {
2223
+ transform: rotate(90deg);
2090
2224
  }
2091
2225
 
2092
- .systems-events {
2226
+ .events-disclosure-body {
2227
+ display: none;
2093
2228
  background: var(--bg-panel);
2094
2229
  border: 1px solid var(--border);
2095
2230
  border-radius: 8px;
@@ -2097,6 +2232,10 @@
2097
2232
  overflow-y: auto;
2098
2233
  }
2099
2234
 
2235
+ .events-disclosure.expanded .events-disclosure-body {
2236
+ display: block;
2237
+ }
2238
+
2100
2239
  .systems-event-row {
2101
2240
  display: flex;
2102
2241
  align-items: center;
@@ -2776,12 +2915,17 @@
2776
2915
  onkeydown="if(event.key==='Enter')createNewSession()" autofocus>
2777
2916
  </div>
2778
2917
  <div class="modal-field">
2779
- <label class="modal-toggle">
2780
- <input type="checkbox" id="newSessionHeadless">
2781
- <span>Headless (no Telegram topic)</span>
2782
- </label>
2783
- <div class="modal-hint">Headless sessions still appear in the dashboard but won't create a Telegram topic.</div>
2918
+ <label for="newSessionPlatform">Platform</label>
2919
+ <select id="newSessionPlatform" style="width:100%;padding:8px 12px;background:var(--bg-panel);color:var(--text-bright);border:1px solid var(--border);border-radius:6px;font-size:14px;">
2920
+ <option value="auto">Auto (best available)</option>
2921
+ <option value="telegram">✈️ Telegram</option>
2922
+ <option value="slack">💬 Slack</option>
2923
+ <option value="headless">🖥️ Headless (no messaging)</option>
2924
+ </select>
2925
+ <div class="modal-hint">Choose which messaging platform to link this session to.</div>
2784
2926
  </div>
2927
+ <!-- Keep hidden checkbox for backward compat -->
2928
+ <input type="hidden" id="newSessionHeadless">
2785
2929
  <div class="modal-actions">
2786
2930
  <button class="modal-btn-cancel" onclick="closeNewSessionModal()">Cancel</button>
2787
2931
  <button class="modal-btn-create" id="createSessionBtn" onclick="createNewSession()">Create Session</button>
@@ -2864,19 +3008,15 @@
2864
3008
  <div class="systems-container" id="systemsTab" style="display:none">
2865
3009
  <div class="systems-main">
2866
3010
  <div class="systems-header">
2867
- <h2>Systems Status</h2>
3011
+ <h2>Systems</h2>
2868
3012
  <button class="systems-refresh" onclick="loadSystems()">Refresh</button>
2869
3013
  </div>
2870
- <div class="systems-summary" id="systemsSummary">
3014
+ <div id="systemsBanner">
2871
3015
  <div style="padding:20px;color:var(--text-dim);text-align:center">Loading...</div>
2872
3016
  </div>
2873
- <div class="systems-categories" id="systemsCategories"></div>
2874
- <div class="systems-section">
2875
- <h3>Recent Degradation Events</h3>
2876
- <div class="systems-events" id="systemsEvents">
2877
- <div style="padding:12px;color:var(--text-dim);text-align:center">No events</div>
2878
- </div>
2879
- </div>
3017
+ <div id="systemsIssues"></div>
3018
+ <div id="systemsCapabilities"></div>
3019
+ <div id="systemsEvents"></div>
2880
3020
  </div>
2881
3021
  </div>
2882
3022
 
@@ -3236,6 +3376,10 @@
3236
3376
  }
3237
3377
  }
3238
3378
 
3379
+ const platform = session.platform || 'headless';
3380
+ const platformIcon = platform === 'telegram' ? '✈️' : platform === 'slack' ? '💬' : platform === 'whatsapp' ? '📱' : '🖥️';
3381
+ const platformLabel = platform.charAt(0).toUpperCase() + platform.slice(1);
3382
+
3239
3383
  el.innerHTML = `
3240
3384
  <div class="session-name-row">
3241
3385
  <div class="session-name">${escapeHtml(session.name)}</div>
@@ -3244,6 +3388,7 @@
3244
3388
  <div class="session-meta">
3245
3389
  <span class="type-badge ${sType}">${sType}</span>
3246
3390
  <span class="model-badge ${model}">${model}</span>
3391
+ <span class="platform-badge ${platform}" title="${platformLabel}">${platformIcon}</span>
3247
3392
  <span>${elapsed}</span>
3248
3393
  </div>
3249
3394
  ${telemetryHtml}
@@ -3556,7 +3701,9 @@
3556
3701
 
3557
3702
  async function createNewSession() {
3558
3703
  const nameInput = document.getElementById('newSessionName');
3559
- const headless = document.getElementById('newSessionHeadless').checked;
3704
+ const platformSelect = document.getElementById('newSessionPlatform');
3705
+ const platform = platformSelect ? platformSelect.value : 'auto';
3706
+ const headless = platform === 'headless';
3560
3707
  const name = nameInput.value.trim();
3561
3708
  const btn = document.getElementById('createSessionBtn');
3562
3709
 
@@ -3577,7 +3724,7 @@
3577
3724
  'Authorization': `Bearer ${token}`,
3578
3725
  'Content-Type': 'application/json',
3579
3726
  },
3580
- body: JSON.stringify({ name, headless }),
3727
+ body: JSON.stringify({ name, headless, platform: platform === 'auto' ? undefined : platform }),
3581
3728
  });
3582
3729
  const data = await resp.json();
3583
3730
 
@@ -5474,96 +5621,189 @@
5474
5621
  async function loadSystems() {
5475
5622
  try {
5476
5623
  systemsData = await apiFetch('/systems/status');
5477
- renderSystems();
5624
+ showSystemsOverview();
5625
+ renderSystemsOverview();
5478
5626
  } catch (e) {
5479
- document.getElementById('systemsSummary').innerHTML =
5627
+ document.getElementById('systemsBanner').innerHTML =
5480
5628
  '<div style="padding:20px;color:var(--red);text-align:center">Failed to load systems status</div>';
5481
5629
  }
5482
5630
  }
5483
5631
 
5484
- function renderSystems() {
5632
+ function renderSystemsOverview() {
5485
5633
  if (!systemsData) return;
5486
- const { uptime, categories, recentEvents } = systemsData;
5487
-
5488
- // Summary
5489
- let totalProcesses = 0;
5490
- let healthyProcesses = 0;
5491
- let errorProcesses = 0;
5492
- for (const cat of categories) {
5493
- for (const p of cat.processes) {
5494
- totalProcesses++;
5495
- if (p.status === 'running') healthyProcesses++;
5496
- if (p.status === 'error') errorProcesses++;
5497
- }
5498
- }
5634
+ const { uptime, health, healthSummary, activeCapabilities, issues, recentEvents } = systemsData;
5499
5635
  const uptimeStr = formatUptimeClient(uptime);
5500
5636
 
5501
- document.getElementById('systemsSummary').innerHTML = `
5502
- <div class="systems-summary-card">
5503
- <div class="summary-value" style="color:var(--accent)">${healthyProcesses}/${totalProcesses}</div>
5504
- <div class="summary-label">Processes Healthy</div>
5505
- </div>
5506
- <div class="systems-summary-card">
5507
- <div class="summary-value" style="color:${errorProcesses > 0 ? 'var(--red)' : 'var(--text-dim)'}">${errorProcesses}</div>
5508
- <div class="summary-label">Errors</div>
5509
- </div>
5510
- <div class="systems-summary-card">
5511
- <div class="summary-value">${esc(uptimeStr)}</div>
5512
- <div class="summary-label">Uptime</div>
5513
- </div>
5514
- <div class="systems-summary-card">
5515
- <div class="summary-value">${recentEvents.length}</div>
5516
- <div class="summary-label">Recent Events</div>
5637
+ // Health Banner
5638
+ const bannerClass = health === 'healthy' ? 'healthy' : health === 'error' ? 'error' : 'degraded';
5639
+ const bannerIcon = health === 'healthy' ? '&#10003;' : '&#9888;';
5640
+ document.getElementById('systemsBanner').innerHTML = `
5641
+ <div class="health-banner ${bannerClass}">
5642
+ <span class="health-icon">${bannerIcon}</span>
5643
+ <div class="health-text">
5644
+ <div class="health-title">${esc(healthSummary)}</div>
5645
+ <div class="health-meta">Uptime: ${esc(uptimeStr)} &middot; ${activeCapabilities.length} capabilities active${issues.length > 0 ? ' &middot; ' + issues.length + ' issue' + (issues.length > 1 ? 's' : '') : ''}</div>
5646
+ </div>
5517
5647
  </div>`;
5518
5648
 
5519
- // Categories
5520
- const catEl = document.getElementById('systemsCategories');
5521
- catEl.innerHTML = categories.map(cat => {
5522
- const running = cat.processes.filter(p => p.status === 'running').length;
5523
- const hasError = cat.processes.some(p => p.status === 'error');
5524
- const hasDisabled = cat.processes.some(p => p.status === 'not tracked' || p.status === 'disabled');
5525
- const dotColor = hasError ? 'var(--red)' : (running === 0 ? 'var(--text-dim)' : (hasDisabled ? 'var(--orange)' : 'var(--accent)'));
5526
-
5527
- const processCards = cat.processes.map(p => {
5528
- const pDotColor = p.status === 'running' ? 'var(--accent)' :
5529
- p.status === 'error' ? 'var(--red)' :
5530
- p.status === 'disabled' ? 'var(--orange)' : 'var(--text-dim)';
5531
- return `<div class="systems-process-card">
5532
- <span class="systems-process-dot" style="background:${pDotColor}"></span>
5533
- <span class="systems-process-name">${esc(p.name)}</span>
5534
- <span class="systems-process-status">${esc(p.status)}</span>
5649
+ // Issues (only if any)
5650
+ const issuesEl = document.getElementById('systemsIssues');
5651
+ if (issues.length > 0) {
5652
+ issuesEl.innerHTML = '<div class="issues-panel">' + issues.map(iss => {
5653
+ const cls = iss.severity === 'warning' ? ' warning' : '';
5654
+ const ic = iss.severity === 'warning' ? '&#9888;' : '&#10060;';
5655
+ return `<div class="issue-card${cls}">
5656
+ <span class="issue-card-icon">${ic}</span>
5657
+ <div class="issue-card-content">
5658
+ <div class="issue-card-label">${esc(iss.label)}</div>
5659
+ <div class="issue-card-desc">${esc(iss.description)}</div>
5660
+ </div>
5661
+ </div>`;
5662
+ }).join('') + '</div>';
5663
+ } else {
5664
+ issuesEl.innerHTML = '';
5665
+ }
5666
+
5667
+ // Card Grid
5668
+ const capsEl = document.getElementById('systemsCapabilities');
5669
+ if (activeCapabilities.length > 0) {
5670
+ const cards = activeCapabilities.map(cap => {
5671
+ const dotClass = cap.status === 'error' ? 'error' : 'active';
5672
+ const statusClass = cap.status === 'error' ? ' error' : '';
5673
+ const previewStats = buildPreviewStats(cap.stats || {});
5674
+ return `<div class="cap-card${statusClass}" onclick="showCapabilityDetail('${esc(cap.id)}')">
5675
+ <div class="cap-card-top">
5676
+ <span class="cap-dot ${dotClass}"></span>
5677
+ <span class="cap-card-name">${esc(cap.label)}</span>
5678
+ <span class="cap-card-arrow">&#9654;</span>
5679
+ </div>
5680
+ <div class="cap-card-desc">${esc(cap.description)}</div>
5681
+ ${previewStats ? '<div class="cap-card-stats">' + previewStats + '</div>' : ''}
5682
+ <div class="cap-card-metric">${esc(cap.metric)}</div>
5535
5683
  </div>`;
5536
5684
  }).join('');
5537
-
5538
- return `<div class="systems-category" data-cat="${esc(cat.id)}">
5539
- <div class="systems-category-header" onclick="toggleSystemsCategory(this)">
5540
- <span class="systems-category-arrow">&#9654;</span>
5541
- <span class="systems-category-dot" style="background:${dotColor}"></span>
5542
- <span class="systems-category-name">${esc(cat.name)}</span>
5543
- <span class="systems-category-count">${running}/${cat.processes.length} active</span>
5544
- </div>
5545
- <div class="systems-category-body">${processCards}</div>
5546
- </div>`;
5547
- }).join('');
5685
+ capsEl.innerHTML = `<div class="cap-section-title">Active Capabilities</div><div class="cap-grid">${cards}</div>`;
5686
+ } else {
5687
+ capsEl.innerHTML = '';
5688
+ }
5548
5689
 
5549
5690
  // Events
5550
5691
  const eventsEl = document.getElementById('systemsEvents');
5551
- if (recentEvents.length === 0) {
5552
- eventsEl.innerHTML = '<div style="padding:12px;color:var(--text-dim);text-align:center">No degradation events</div>';
5553
- } else {
5554
- eventsEl.innerHTML = recentEvents.map(e => {
5692
+ if (recentEvents.length > 0) {
5693
+ const rows = recentEvents.map(e => {
5555
5694
  const time = e.timestamp ? new Date(e.timestamp).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '';
5556
- return `<div class="systems-event-row">
5557
- <span class="systems-event-time">${esc(time)}</span>
5558
- <span class="systems-event-text">${esc(e.narrative || e.subsystem || 'Unknown event')}</span>
5559
- </div>`;
5695
+ return `<div class="systems-event-row"><span class="systems-event-time">${esc(time)}</span><span class="systems-event-text">${esc(e.narrative || e.subsystem || '')}</span></div>`;
5560
5696
  }).join('');
5697
+ eventsEl.innerHTML = `<div class="events-disclosure" id="eventsDisclosure">
5698
+ <button class="events-disclosure-toggle" onclick="document.getElementById('eventsDisclosure').classList.toggle('expanded')">
5699
+ <span class="events-disclosure-arrow">&#9654;</span> Recent Events (${recentEvents.length})
5700
+ </button>
5701
+ <div class="events-disclosure-body">${rows}</div></div>`;
5702
+ } else {
5703
+ eventsEl.innerHTML = '';
5704
+ }
5705
+ }
5706
+
5707
+ function buildPreviewStats(stats) {
5708
+ if (!stats || typeof stats !== 'object') return '';
5709
+ const map = {
5710
+ recoveries: 'Recoveries', interventions: 'Interventions', activeCases: 'Active',
5711
+ coherencePassed: 'Passed', coherenceFailed: 'Failed', memoryPercent: 'Memory',
5712
+ enabledJobs: 'Jobs', activeJobSessions: 'Running', queueLength: 'Queued',
5713
+ weeklyUsage: 'Weekly', fiveHourRate: '5h Rate',
5714
+ topicMappings: 'Topics', totalMessages: 'Messages',
5715
+ proposals: 'Proposals', gaps: 'Gaps', learningsApplied: 'Applied',
5716
+ };
5717
+ const chips = [];
5718
+ for (const [key, label] of Object.entries(map)) {
5719
+ if (stats[key] != null && stats[key] !== 0 && stats[key] !== false) {
5720
+ const suf = ['weeklyUsage','fiveHourRate','memoryPercent'].includes(key) ? '%' : '';
5721
+ chips.push(`<span class="cap-stat"><span class="cap-stat-value">${esc(String(stats[key]))}${suf}</span>${esc(label)}</span>`);
5722
+ }
5723
+ if (chips.length >= 3) break;
5724
+ }
5725
+ return chips.join('');
5726
+ }
5727
+
5728
+ function showCapabilityDetail(capId) {
5729
+ if (!systemsData) return;
5730
+ const cap = systemsData.activeCapabilities.find(c => c.id === capId);
5731
+ if (!cap) return;
5732
+ document.getElementById('systemsOverview').style.display = 'none';
5733
+ document.getElementById('systemsDetailView').classList.add('active');
5734
+ const content = document.getElementById('systemsDetailContent');
5735
+
5736
+ const statCards = buildStatCards(cap.stats || {});
5737
+ const processRows = (cap.processes || []).map(p => {
5738
+ const dotColor = p.status === 'running' ? 'var(--accent)' : 'var(--red)';
5739
+ return `<div class="cap-process-row">
5740
+ <span class="cap-process-dot" style="background:${dotColor}"></span>
5741
+ <span class="cap-process-name">${esc(p.name)}</span>
5742
+ <span class="cap-process-status">${esc(p.status === 'running' ? 'Running' : 'Error')}</span>
5743
+ </div>`;
5744
+ }).join('');
5745
+
5746
+ const lastAct = cap.lastActivity
5747
+ ? '<div class="cap-last-activity">Last activity: ' + esc(timeAgo(cap.lastActivity)) + '</div>' : '';
5748
+
5749
+ content.innerHTML = `
5750
+ <div class="cap-detail-header">
5751
+ <div class="cap-detail-title">
5752
+ <span class="cap-dot ${cap.status === 'error' ? 'error' : 'active'}"></span>
5753
+ <h3>${esc(cap.label)}</h3>
5754
+ </div>
5755
+ <div class="cap-detail-description">${esc(cap.description)}</div>
5756
+ ${lastAct}
5757
+ </div>
5758
+ ${statCards ? '<div class="cap-detail-section"><div class="cap-detail-section-title">Metrics</div><div class="cap-stats-grid">' + statCards + '</div></div>' : ''}
5759
+ <div class="cap-detail-section">
5760
+ <div class="cap-detail-section-title">Components (${(cap.processes || []).length})</div>
5761
+ <div class="cap-process-list">${processRows}</div>
5762
+ </div>
5763
+ <div class="cap-detail-section">
5764
+ <div class="cap-detail-section-title">Status</div>
5765
+ <div style="font-size:13px;color:var(--accent);font-weight:500;padding:8px 0">${esc(cap.metric)}</div>
5766
+ </div>`;
5767
+ }
5768
+
5769
+ function buildStatCards(stats) {
5770
+ if (!stats || typeof stats !== 'object') return '';
5771
+ const map = {
5772
+ interventions: 'Interventions', recoveries: 'Recoveries', sessionDeaths: 'Deaths',
5773
+ llmOverrides: 'LLM Overrides', activeCases: 'Active Cases', totalTriages: 'Triages',
5774
+ cooldowns: 'Cooldowns',
5775
+ coherenceChecks: 'Checks', coherencePassed: 'Passed', coherenceFailed: 'Failed', coherenceCorrected: 'Corrected',
5776
+ memoryPercent: 'Memory %', memoryFreeGB: 'Free GB', memoryTotalGB: 'Total GB',
5777
+ trackedProcesses: 'Tracked', orphanProcesses: 'Orphans', totalMemoryMB: 'Memory MB',
5778
+ totalJobs: 'Total Jobs', enabledJobs: 'Enabled', queueLength: 'Queued', activeJobSessions: 'Running',
5779
+ weeklyUsage: 'Weekly %', fiveHourRate: '5h Rate %',
5780
+ topicMappings: 'Topics', pendingStalls: 'Pending', totalMessages: 'Messages',
5781
+ proposals: 'Proposals', learnings: 'Learnings', learningsApplied: 'Applied',
5782
+ gaps: 'Gaps', actions: 'Actions', overdueActions: 'Overdue',
5783
+ };
5784
+ const cards = [];
5785
+ for (const [key, label] of Object.entries(map)) {
5786
+ if (stats[key] != null && typeof stats[key] === 'number') {
5787
+ cards.push(`<div class="cap-stat-card"><div class="cap-stat-card-value">${stats[key]}</div><div class="cap-stat-card-label">${esc(label)}</div></div>`);
5788
+ }
5561
5789
  }
5790
+ return cards.join('');
5791
+ }
5792
+
5793
+ function showSystemsOverview() {
5794
+ document.getElementById('systemsOverview').style.display = '';
5795
+ document.getElementById('systemsDetailView').classList.remove('active');
5562
5796
  }
5563
5797
 
5564
- function toggleSystemsCategory(headerEl) {
5565
- const cat = headerEl.closest('.systems-category');
5566
- cat.classList.toggle('expanded');
5798
+ function timeAgo(isoStr) {
5799
+ const ms = Date.now() - new Date(isoStr).getTime();
5800
+ const s = Math.floor(ms / 1000);
5801
+ if (s < 60) return 'just now';
5802
+ const m = Math.floor(s / 60);
5803
+ if (m < 60) return m + 'm ago';
5804
+ const h = Math.floor(m / 60);
5805
+ if (h < 24) return h + 'h ago';
5806
+ return Math.floor(h / 24) + 'd ago';
5567
5807
  }
5568
5808
 
5569
5809
  function formatUptimeClient(ms) {