slicejs-web-framework 3.4.4 → 3.5.0

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 (101) hide show
  1. package/Slice/Components/Structural/Controller/Controller.js +135 -0
  2. package/Slice/Components/Structural/EventManager/EventManager.js +289 -4
  3. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +368 -30
  4. package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -0
  5. package/Slice/Slice.js +14 -1
  6. package/Slice/tests/event-emitters.test.js +79 -0
  7. package/Slice/tests/event-registry.test.js +141 -0
  8. package/Slice/tests/leak-orphans.test.js +131 -0
  9. package/api/index.js +35 -8
  10. package/package.json +1 -1
  11. package/sliceConfig.schema.json +10 -1
  12. package/src/sliceConfig.json +5 -1
  13. package/types/index.d.ts +12 -0
  14. package/slicejs-cli/.github/workflows/ci.yml +0 -43
  15. package/slicejs-cli/LICENSE +0 -21
  16. package/slicejs-cli/assets/Slice.js-logo.png +0 -0
  17. package/slicejs-cli/client.js +0 -740
  18. package/slicejs-cli/commands/Print.js +0 -164
  19. package/slicejs-cli/commands/Validations.js +0 -92
  20. package/slicejs-cli/commands/build/build.js +0 -40
  21. package/slicejs-cli/commands/buildProduction/buildProduction.js +0 -577
  22. package/slicejs-cli/commands/bundle/bundle.js +0 -236
  23. package/slicejs-cli/commands/createComponent/VisualComponentTemplate.js +0 -55
  24. package/slicejs-cli/commands/createComponent/createComponent.js +0 -128
  25. package/slicejs-cli/commands/deleteComponent/deleteComponent.js +0 -81
  26. package/slicejs-cli/commands/doctor/doctor.js +0 -516
  27. package/slicejs-cli/commands/getComponent/getComponent.js +0 -695
  28. package/slicejs-cli/commands/init/init.js +0 -473
  29. package/slicejs-cli/commands/listComponents/listComponents.js +0 -172
  30. package/slicejs-cli/commands/startServer/startServer.js +0 -240
  31. package/slicejs-cli/commands/startServer/watchServer.js +0 -66
  32. package/slicejs-cli/commands/types/types.js +0 -583
  33. package/slicejs-cli/commands/utils/LocalCliDelegation.js +0 -53
  34. package/slicejs-cli/commands/utils/PackageManager.js +0 -148
  35. package/slicejs-cli/commands/utils/PathHelper.js +0 -75
  36. package/slicejs-cli/commands/utils/VersionChecker.js +0 -169
  37. package/slicejs-cli/commands/utils/bundling/BundleGenerator.js +0 -2525
  38. package/slicejs-cli/commands/utils/bundling/DependencyAnalyzer.js +0 -933
  39. package/slicejs-cli/commands/utils/loadConfig.js +0 -31
  40. package/slicejs-cli/commands/utils/sliceScripts.js +0 -48
  41. package/slicejs-cli/commands/utils/updateManager.js +0 -471
  42. package/slicejs-cli/package.json +0 -71
  43. package/slicejs-cli/playwright.config.js +0 -51
  44. package/slicejs-cli/pnpm-lock.yaml +0 -1629
  45. package/slicejs-cli/tests/build-command-integration.test.js +0 -87
  46. package/slicejs-cli/tests/build-mangling-e2e.test.js +0 -290
  47. package/slicejs-cli/tests/build-production-e2e.test.js +0 -140
  48. package/slicejs-cli/tests/builder-edge-cases.test.js +0 -322
  49. package/slicejs-cli/tests/bundle-generate-e2e.test.js +0 -115
  50. package/slicejs-cli/tests/bundle-generator.test.js +0 -691
  51. package/slicejs-cli/tests/bundle-v2-register-output.test.js +0 -470
  52. package/slicejs-cli/tests/bundling-dependency-edges.test.js +0 -127
  53. package/slicejs-cli/tests/bundling-imports-unit.test.js +0 -267
  54. package/slicejs-cli/tests/client-launcher-contract.test.js +0 -211
  55. package/slicejs-cli/tests/commands-component-crud.test.js +0 -128
  56. package/slicejs-cli/tests/commands-doctor.test.js +0 -152
  57. package/slicejs-cli/tests/commands-version-checker.test.js +0 -37
  58. package/slicejs-cli/tests/component-registry-parse.test.js +0 -34
  59. package/slicejs-cli/tests/dependency-analyzer.test.js +0 -24
  60. package/slicejs-cli/tests/e2e/bundles.spec.js +0 -91
  61. package/slicejs-cli/tests/e2e/dependency-scenarios.spec.js +0 -56
  62. package/slicejs-cli/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
  63. package/slicejs-cli/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
  64. package/slicejs-cli/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
  65. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
  66. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
  67. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
  68. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
  69. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
  70. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
  71. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
  72. package/slicejs-cli/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
  73. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
  74. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
  75. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
  76. package/slicejs-cli/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
  77. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
  78. package/slicejs-cli/tests/e2e/fixtures/components/registry.json +0 -12
  79. package/slicejs-cli/tests/e2e/fixtures/vendor-components.mjs +0 -65
  80. package/slicejs-cli/tests/e2e/navigation.spec.js +0 -44
  81. package/slicejs-cli/tests/e2e/render.spec.js +0 -34
  82. package/slicejs-cli/tests/e2e/serve.mjs +0 -264
  83. package/slicejs-cli/tests/e2e/shared-deps.spec.js +0 -61
  84. package/slicejs-cli/tests/e2e/unminified.spec.js +0 -33
  85. package/slicejs-cli/tests/e2e-serve.test.js +0 -148
  86. package/slicejs-cli/tests/fixtures/components.js +0 -8
  87. package/slicejs-cli/tests/fixtures/sliceConfig.json +0 -74
  88. package/slicejs-cli/tests/getcomponent.test.js +0 -435
  89. package/slicejs-cli/tests/helpers/setup.js +0 -102
  90. package/slicejs-cli/tests/init-command-contract.test.js +0 -101
  91. package/slicejs-cli/tests/init-e2e.test.js +0 -82
  92. package/slicejs-cli/tests/init-project-isolation.test.js +0 -98
  93. package/slicejs-cli/tests/local-cli-delegation.test.js +0 -81
  94. package/slicejs-cli/tests/package-manager.test.js +0 -127
  95. package/slicejs-cli/tests/path-helper.test.js +0 -206
  96. package/slicejs-cli/tests/perf-budget.test.js +0 -84
  97. package/slicejs-cli/tests/postinstall-command.test.js +0 -103
  98. package/slicejs-cli/tests/property-mangling-regression.test.js +0 -182
  99. package/slicejs-cli/tests/types-breakage.test.js +0 -491
  100. package/slicejs-cli/tests/types-generator-errors.test.js +0 -361
  101. package/slicejs-cli/tests/types-generator.test.js +0 -348
@@ -22,9 +22,26 @@ export default class EventManagerDebugger extends HTMLElement {
22
22
  this.cacheElements();
23
23
  this.bindEvents();
24
24
  this.makeDraggable();
25
+ this._loadStaticGraph();
25
26
  this.renderList();
26
27
  }
27
28
 
29
+ /**
30
+ * Best-effort load of the generated static event graph (documentation layer).
31
+ * Produced by `slice types generate` at /slice-events.generated.js. Absent =>
32
+ * the Registry tab simply shows no static emitters/listeners (no error).
33
+ */
34
+ async _loadStaticGraph() {
35
+ if (slice.events?.graph) return;
36
+ try {
37
+ const module = await import(/* @vite-ignore */ '/slice-events.generated.js');
38
+ if (module?.default) slice.events.loadGraph(module.default);
39
+ if (this.isOpen && this.activeTab === 'registry') this.renderRegistry();
40
+ } catch {
41
+ /* manifest not generated — documentation layer stays empty, runtime tracing still works */
42
+ }
43
+ }
44
+
28
45
  /**
29
46
  * Toggle panel visibility.
30
47
  * @returns {void}
@@ -69,11 +86,7 @@ export default class EventManagerDebugger extends HTMLElement {
69
86
  this._stopAutoRefresh();
70
87
  this._autoRefreshTimer = setInterval(() => {
71
88
  if (!this.isOpen) return;
72
- if (this.activeTab === 'history') {
73
- this.renderHistory();
74
- } else {
75
- this.renderList();
76
- }
89
+ this._renderActive();
77
90
  }, 1500);
78
91
  }
79
92
 
@@ -93,34 +106,36 @@ export default class EventManagerDebugger extends HTMLElement {
93
106
  this.refreshButton = this.querySelector('#events-refresh');
94
107
  this.closeButton = this.querySelector('#events-close');
95
108
  this.tabSubs = this.querySelector('#tab-subscribers');
109
+ this.tabRegistry = this.querySelector('#tab-registry');
96
110
  this.tabHistory = this.querySelector('#tab-history');
97
111
  }
98
112
 
99
113
  bindEvents() {
100
- this.refreshButton.addEventListener('click', () => {
101
- if (this.activeTab === 'history') this.renderHistory();
102
- else this.renderList();
103
- });
114
+ this.refreshButton.addEventListener('click', () => this._renderActive());
104
115
  this.closeButton.addEventListener('click', () => this.close());
105
116
  this.filterInput.addEventListener('input', (event) => {
106
117
  this.filterText = event.target.value.trim().toLowerCase();
107
- if (this.activeTab === 'history') this.renderHistory();
108
- else this.renderList();
109
- });
110
- this.tabSubs.addEventListener('click', () => {
111
- this.activeTab = 'subscribers';
112
- this.tabSubs.classList.add('active');
113
- this.tabHistory.classList.remove('active');
114
- this.filterInput.placeholder = 'filter events…';
115
- this.renderList();
116
- });
117
- this.tabHistory.addEventListener('click', () => {
118
- this.activeTab = 'history';
119
- this.tabHistory.classList.add('active');
120
- this.tabSubs.classList.remove('active');
121
- this.filterInput.placeholder = 'filter history…';
122
- this.renderHistory();
118
+ this._renderActive();
123
119
  });
120
+ this.tabSubs.addEventListener('click', () => this._selectTab('subscribers'));
121
+ this.tabRegistry.addEventListener('click', () => this._selectTab('registry'));
122
+ this.tabHistory.addEventListener('click', () => this._selectTab('history'));
123
+ }
124
+
125
+ _selectTab(tab) {
126
+ this.activeTab = tab;
127
+ this.tabSubs.classList.toggle('active', tab === 'subscribers');
128
+ this.tabRegistry.classList.toggle('active', tab === 'registry');
129
+ this.tabHistory.classList.toggle('active', tab === 'history');
130
+ const placeholders = { subscribers: 'filter events…', registry: 'filter registry…', history: 'filter history…' };
131
+ this.filterInput.placeholder = placeholders[tab] || 'filter…';
132
+ this._renderActive();
133
+ }
134
+
135
+ _renderActive() {
136
+ if (this.activeTab === 'history') this.renderHistory();
137
+ else if (this.activeTab === 'registry') this.renderRegistry();
138
+ else this.renderList();
124
139
  }
125
140
 
126
141
  makeDraggable() {
@@ -185,7 +200,7 @@ export default class EventManagerDebugger extends HTMLElement {
185
200
  return;
186
201
  }
187
202
 
188
- items.push({ eventName, count: subs.size, emitCount, entries });
203
+ items.push({ eventName, count: subs.size, emitCount, entries, emitters: this._emittersFor(eventName) });
189
204
  });
190
205
 
191
206
  items.sort((a, b) => a.eventName.localeCompare(b.eventName));
@@ -200,22 +215,36 @@ export default class EventManagerDebugger extends HTMLElement {
200
215
  const onceBadge = entry.once ? '<span class="badge">once</span>' : '';
201
216
  return `
202
217
  <div class="subscriber-row">
203
- <div class="subscriber-name">${label}</div>
218
+ <div class="subscriber-name">${this.escapeHtml(label)}</div>
204
219
  <div class="subscriber-meta">${entry.id}${onceBadge}</div>
205
220
  </div>
206
221
  `;
207
222
  }).join('');
208
223
 
224
+ const emittersHtml = item.emitters.length
225
+ ? item.emitters.map((emitter) => `
226
+ <div class="subscriber-row emitter-row">
227
+ <div class="subscriber-name">${this.escapeHtml(this._emitterLabel(emitter))}</div>
228
+ <div class="subscriber-meta">×${emitter.count}</div>
229
+ </div>
230
+ `).join('')
231
+ : '<div class="empty">No emitters recorded</div>';
232
+
209
233
  return `
210
234
  <details class="event-row">
211
235
  <summary>
212
- <div class="event-name">${item.eventName}</div>
236
+ <div class="event-name">${this.escapeHtml(item.eventName)}</div>
213
237
  <div class="event-metrics">
214
238
  <span class="emit-count" title="Emits this session">⚡${item.emitCount}</span>
215
239
  <span class="event-count">${item.count}</span>
216
240
  </div>
217
241
  </summary>
218
- <div class="subscriber-list">
242
+ <div class="trace-section">
243
+ <div class="trace-label">emitted by</div>
244
+ ${emittersHtml}
245
+ </div>
246
+ <div class="trace-section">
247
+ <div class="trace-label">listeners</div>
219
248
  ${details || '<div class="empty">No subscribers</div>'}
220
249
  </div>
221
250
  </details>
@@ -224,6 +253,173 @@ export default class EventManagerDebugger extends HTMLElement {
224
253
  : '<div class="empty">No events</div>';
225
254
  }
226
255
 
256
+ /**
257
+ * Emitters recorded for an event (only while the debugger is open).
258
+ * @param {string} eventName
259
+ * @returns {Array<{ sliceId?: string, name?: string, source?: string, count: number }>}
260
+ */
261
+ _emittersFor(eventName) {
262
+ const map = slice?.events?.emitters;
263
+ if (!(map instanceof Map) || !map.has(eventName)) return [];
264
+ return Array.from(map.get(eventName).values()).sort((a, b) => b.count - a.count);
265
+ }
266
+
267
+ /**
268
+ * Human-readable label for an emitter (component name + sliceId, or source location).
269
+ */
270
+ _emitterLabel(emitter) {
271
+ if (!emitter) return 'unknown';
272
+ if (emitter.sliceId) return `${emitter.name || 'Component'} (${emitter.sliceId})`;
273
+ if (emitter.source) return emitter.source;
274
+ return 'unknown';
275
+ }
276
+
277
+ /**
278
+ * Render the declared event catalog (registry) merged with the live state:
279
+ * payload shape, current subscriber count, emit count, plus drift badges for
280
+ * declared-but-unused and used-but-undeclared events.
281
+ */
282
+ renderRegistry() {
283
+ if (!slice?.events) {
284
+ this.list.textContent = 'EventManager not available.';
285
+ this.countLabel.textContent = '0';
286
+ return;
287
+ }
288
+
289
+ const registry = slice.events.registry instanceof Map ? slice.events.registry : new Map();
290
+ const undeclared = slice.events.undeclared instanceof Set ? slice.events.undeclared : new Set();
291
+
292
+ if (registry.size === 0 && undeclared.size === 0) {
293
+ this.countLabel.textContent = '0';
294
+ this.list.innerHTML =
295
+ '<div class="empty">No events registered.<br/>Declare them with <code>slice.events.register({ ... })</code>.</div>';
296
+ return;
297
+ }
298
+
299
+ const matchesFilter = (name) => !this.filterText || name.toLowerCase().includes(this.filterText);
300
+
301
+ // Bucket declared events by namespace (segment before the first ':').
302
+ const groups = new Map();
303
+ registry.forEach((definition, eventName) => {
304
+ if (!matchesFilter(eventName)) return;
305
+ const ns = slice.events.namespaceOf?.(eventName) || '(no namespace)';
306
+ if (!groups.has(ns)) groups.set(ns, []);
307
+ groups.get(ns).push({
308
+ eventName,
309
+ description: definition?.description || '',
310
+ payload: definition?.payload ?? null,
311
+ subscriberCount: slice.events.subscriberCount?.(eventName) || 0,
312
+ emitCount: slice.events.emitCounts?.get(eventName) || 0
313
+ });
314
+ });
315
+
316
+ const sortedNamespaces = Array.from(groups.keys()).sort((a, b) => a.localeCompare(b));
317
+ const declaredCount = Array.from(groups.values()).reduce((sum, list) => sum + list.length, 0);
318
+ const drift = Array.from(undeclared).filter(matchesFilter).sort();
319
+
320
+ this.countLabel.textContent = String(declaredCount + drift.length);
321
+
322
+ const siteRows = (sites) => sites.map((s) => {
323
+ const label = s.component ? `${s.component} — ${s.file}:${s.line}` : `${s.file}:${s.line}`;
324
+ return `<div class="graph-site">${this.escapeHtml(label)}</div>`;
325
+ }).join('');
326
+
327
+ const eventRowHtml = (item) => {
328
+ const payloadStr = this.formatPayload(item.payload);
329
+ const idle = item.subscriberCount === 0
330
+ ? '<span class="badge badge-idle" title="No active listeners">idle</span>'
331
+ : '';
332
+ const desc = item.description
333
+ ? `<div class="registry-desc">${this.escapeHtml(item.description)}</div>`
334
+ : '';
335
+
336
+ // Static documentation layer (from the generated manifest).
337
+ const emitters = slice.events.staticEmittersOf?.(item.eventName) || [];
338
+ const listeners = slice.events.staticListenersOf?.(item.eventName) || [];
339
+ const graphHtml = (emitters.length || listeners.length)
340
+ ? `
341
+ <div class="graph-block">
342
+ <div class="graph-label">emitters <span class="graph-tag">static</span></div>
343
+ ${emitters.length ? siteRows(emitters) : '<div class="empty">none found in code</div>'}
344
+ <div class="graph-label">listeners <span class="graph-tag">static</span></div>
345
+ ${listeners.length ? siteRows(listeners) : '<div class="empty">none found in code</div>'}
346
+ </div>`
347
+ : '';
348
+
349
+ return `
350
+ <details class="event-row">
351
+ <summary>
352
+ <div class="event-name">${this.escapeHtml(item.eventName)}</div>
353
+ <div class="event-metrics">
354
+ ${idle}
355
+ <span class="emit-count" title="Emits this session">⚡${item.emitCount}</span>
356
+ <span class="event-count" title="Active listeners">${item.subscriberCount}</span>
357
+ </div>
358
+ </summary>
359
+ <div class="subscriber-list">
360
+ ${desc}
361
+ <div class="registry-payload"><span class="payload-key">payload</span> ${this.escapeHtml(payloadStr)}</div>
362
+ ${graphHtml}
363
+ </div>
364
+ </details>
365
+ `;
366
+ };
367
+
368
+ const declaredHtml = sortedNamespaces.map((ns) => {
369
+ const list = groups.get(ns).sort((a, b) => a.eventName.localeCompare(b.eventName));
370
+ const rows = list.map(eventRowHtml).join('');
371
+ return `
372
+ <details class="ns-group" open>
373
+ <summary class="ns-summary">
374
+ <span class="ns-name">${this.escapeHtml(ns)}</span>
375
+ <span class="ns-count">${list.length}</span>
376
+ </summary>
377
+ <div class="ns-events">${rows}</div>
378
+ </details>
379
+ `;
380
+ }).join('');
381
+
382
+ const driftHtml = drift.length
383
+ ? `<div class="registry-drift-title">Used but not declared (${drift.length})</div>` +
384
+ drift.map((name) => `
385
+ <div class="history-row drift-row">
386
+ <div class="history-event">${this.escapeHtml(name)}</div>
387
+ <div class="history-time"><span class="badge badge-drift">undeclared</span></div>
388
+ </div>
389
+ `).join('')
390
+ : '';
391
+
392
+ // Footer: dynamic emit/subscribe sites (computed names the static scan can't resolve).
393
+ const dynamic = slice.events.graph?.dynamic;
394
+ const dynamicCount = (dynamic?.emitters?.length || 0) + (dynamic?.listeners?.length || 0);
395
+ const dynamicHtml = dynamicCount
396
+ ? `<div class="registry-drift-title">Dynamic sites (computed names, ${dynamicCount}) — not statically resolvable</div>`
397
+ : '';
398
+
399
+ this.list.innerHTML =
400
+ (declaredHtml || (drift.length ? '' : '<div class="empty">No matching events</div>')) + driftHtml + dynamicHtml;
401
+ }
402
+
403
+ /**
404
+ * Render a payload mini-schema for display.
405
+ * @param {object|null} payload
406
+ * @returns {string}
407
+ */
408
+ formatPayload(payload) {
409
+ if (payload === null || payload === undefined) return 'void';
410
+ if (typeof payload === 'string') return payload;
411
+ if (typeof payload === 'object') {
412
+ const parts = Object.entries(payload).map(([key, value]) => {
413
+ const type = typeof value === 'string'
414
+ ? value
415
+ : (value && typeof value === 'object' && value.type) || 'any';
416
+ return `${key}: ${type}`;
417
+ });
418
+ return `{ ${parts.join(', ')} }`;
419
+ }
420
+ return String(payload);
421
+ }
422
+
227
423
  renderHistory() {
228
424
  if (!slice?.events?.emitHistory) {
229
425
  this.list.textContent = 'EventManager not available.';
@@ -248,9 +444,15 @@ export default class EventManagerDebugger extends HTMLElement {
248
444
  const timeStr = diff < 1000 ? 'now'
249
445
  : diff < 60000 ? `${Math.floor(diff / 1000)}s ago`
250
446
  : `${Math.floor(diff / 60000)}m ago`;
447
+ const from = entry.emitter
448
+ ? `<div class="history-from" title="Emitter">↑ ${this.escapeHtml(this._emitterLabel(entry.emitter))}</div>`
449
+ : '';
251
450
  return `
252
451
  <div class="history-row">
253
- <div class="history-event">${this.escapeHtml(entry.eventName)}</div>
452
+ <div class="history-main">
453
+ <div class="history-event">${this.escapeHtml(entry.eventName)}</div>
454
+ ${from}
455
+ </div>
254
456
  <div class="history-time">${timeStr}</div>
255
457
  </div>
256
458
  `;
@@ -281,6 +483,7 @@ export default class EventManagerDebugger extends HTMLElement {
281
483
  </div>
282
484
  <div class="events-tabs">
283
485
  <button id="tab-subscribers" class="tab-btn active">Subscribers</button>
486
+ <button id="tab-registry" class="tab-btn">Registry</button>
284
487
  <button id="tab-history" class="tab-btn">History</button>
285
488
  </div>
286
489
  <div class="events-toolbar">
@@ -635,6 +838,141 @@ slice-eventmanager-debugger .empty {
635
838
  text-align: center;
636
839
  padding: 22px 0;
637
840
  }
841
+ slice-eventmanager-debugger .empty code {
842
+ font-family: var(--si-mono);
843
+ color: var(--si-accent);
844
+ background: rgba(var(--si-accent-rgb), 0.1);
845
+ padding: 1px 5px;
846
+ border-radius: 5px;
847
+ }
848
+
849
+ slice-eventmanager-debugger .registry-desc {
850
+ font-size: 11px;
851
+ color: var(--si-dim);
852
+ padding: 4px 2px 2px;
853
+ line-height: 1.4;
854
+ }
855
+ slice-eventmanager-debugger .registry-payload {
856
+ font-family: var(--si-mono);
857
+ font-size: 11px;
858
+ color: var(--si-text);
859
+ padding: 6px 9px;
860
+ border-radius: 7px;
861
+ background: rgba(0, 0, 0, 0.22);
862
+ border: 1px solid var(--si-border);
863
+ word-break: break-word;
864
+ }
865
+ slice-eventmanager-debugger .payload-key {
866
+ color: var(--si-dim);
867
+ margin-right: 6px;
868
+ }
869
+ slice-eventmanager-debugger .badge-idle {
870
+ background: rgba(136, 143, 166, 0.14);
871
+ color: var(--si-dim);
872
+ border: 1px solid var(--si-border);
873
+ }
874
+ slice-eventmanager-debugger .badge-drift {
875
+ background: rgba(255, 107, 107, 0.14);
876
+ color: var(--si-danger);
877
+ border: 1px solid rgba(255, 107, 107, 0.3);
878
+ }
879
+ slice-eventmanager-debugger .registry-drift-title {
880
+ margin-top: 4px;
881
+ font-size: 9.5px;
882
+ letter-spacing: 0.1em;
883
+ text-transform: uppercase;
884
+ color: var(--si-danger);
885
+ opacity: 0.85;
886
+ }
887
+ slice-eventmanager-debugger .drift-row { border-left-color: rgba(255, 107, 107, 0.4); }
888
+
889
+ slice-eventmanager-debugger .ns-group { display: block; }
890
+ slice-eventmanager-debugger .ns-group + .ns-group { margin-top: 4px; }
891
+ slice-eventmanager-debugger .ns-summary {
892
+ display: flex; align-items: center; gap: 8px; cursor: pointer; list-style: none;
893
+ padding: 4px 2px 6px; user-select: none;
894
+ }
895
+ slice-eventmanager-debugger .ns-summary::-webkit-details-marker { display: none; }
896
+ slice-eventmanager-debugger .ns-summary::before {
897
+ content: '▾'; color: var(--si-dim); font-size: 10px; transition: transform 0.2s ease;
898
+ }
899
+ slice-eventmanager-debugger .ns-group:not([open]) .ns-summary::before { transform: rotate(-90deg); }
900
+ slice-eventmanager-debugger .ns-name {
901
+ font-size: 9.5px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--si-accent);
902
+ }
903
+ slice-eventmanager-debugger .ns-count {
904
+ font-size: 9.5px; color: var(--si-dim);
905
+ background: var(--si-raised-2); border: 1px solid var(--si-border);
906
+ border-radius: 999px; padding: 0 6px;
907
+ }
908
+ slice-eventmanager-debugger .ns-events { display: flex; flex-direction: column; gap: 7px; padding-left: 4px; }
909
+
910
+ slice-eventmanager-debugger .trace-section {
911
+ margin-top: 9px;
912
+ padding-top: 9px;
913
+ border-top: 1px dashed var(--si-border);
914
+ display: flex;
915
+ flex-direction: column;
916
+ gap: 6px;
917
+ }
918
+ slice-eventmanager-debugger .trace-label {
919
+ font-size: 9px;
920
+ font-weight: 700;
921
+ letter-spacing: 0.1em;
922
+ text-transform: uppercase;
923
+ color: var(--si-dim);
924
+ }
925
+ slice-eventmanager-debugger .emitter-row { border-left: 2px solid var(--si-success); }
926
+ slice-eventmanager-debugger .emitter-row .subscriber-name { color: var(--si-text); }
927
+
928
+ slice-eventmanager-debugger .history-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
929
+ slice-eventmanager-debugger .history-from {
930
+ font-size: 10px;
931
+ color: var(--si-success);
932
+ overflow: hidden;
933
+ text-overflow: ellipsis;
934
+ white-space: nowrap;
935
+ }
936
+
937
+ slice-eventmanager-debugger .graph-block {
938
+ margin-top: 8px;
939
+ padding-top: 8px;
940
+ border-top: 1px dashed var(--si-border);
941
+ display: flex;
942
+ flex-direction: column;
943
+ gap: 4px;
944
+ }
945
+ slice-eventmanager-debugger .graph-label {
946
+ font-size: 9px;
947
+ font-weight: 700;
948
+ letter-spacing: 0.08em;
949
+ text-transform: uppercase;
950
+ color: var(--si-dim);
951
+ margin-top: 4px;
952
+ display: flex;
953
+ align-items: center;
954
+ gap: 6px;
955
+ }
956
+ slice-eventmanager-debugger .graph-tag {
957
+ font-size: 8px;
958
+ font-weight: 600;
959
+ letter-spacing: 0.04em;
960
+ color: var(--si-accent);
961
+ background: rgba(var(--si-accent-rgb), 0.12);
962
+ border: 1px solid rgba(var(--si-accent-rgb), 0.25);
963
+ border-radius: 999px;
964
+ padding: 0 5px;
965
+ }
966
+ slice-eventmanager-debugger .graph-site {
967
+ font-family: var(--si-mono);
968
+ font-size: 10.5px;
969
+ color: var(--si-text);
970
+ padding: 3px 8px;
971
+ border-radius: 6px;
972
+ background: rgba(0, 0, 0, 0.2);
973
+ border: 1px solid var(--si-border);
974
+ word-break: break-word;
975
+ }
638
976
 
639
977
  @media (prefers-reduced-motion: reduce) {
640
978
  slice-eventmanager-debugger #events-debugger.active { animation: none; }