slicejs-web-framework 4.0.2 → 4.1.1
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/LICENSE +21 -21
- package/README.md +97 -97
- package/Slice/Components/Structural/ContextManager/ContextManager.js +422 -422
- package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +470 -470
- package/Slice/Components/Structural/Controller/Controller.js +1406 -1406
- package/Slice/Components/Structural/Controller/allowedValuesValidation.js +52 -52
- package/Slice/Components/Structural/Debugger/Debugger.html +72 -72
- package/Slice/Components/Structural/Debugger/Debugger.js +1505 -1505
- package/Slice/Components/Structural/EventManager/EventManager.js +651 -651
- package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +985 -985
- package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -296
- package/Slice/Components/Structural/Logger/Log.js +10 -10
- package/Slice/Components/Structural/Logger/LogViewer/LogViewer.js +662 -662
- package/Slice/Components/Structural/Logger/Logger.js +225 -225
- package/Slice/Components/Structural/Router/Router.js +820 -810
- package/Slice/Components/Structural/Router/applyRouteMetadata.js +39 -0
- package/Slice/Components/Structural/StylesManager/StylesManager.js +82 -82
- package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +175 -175
- package/Slice/Slice.js +711 -710
- package/api/framework/devDepsOptimizer.js +335 -335
- package/api/framework/express.js +3 -3
- package/api/framework/precompressedStatic.js +102 -93
- package/api/framework/publicEnvResolver.js +123 -123
- package/api/framework/securityMiddleware.js +197 -197
- package/api/framework/seo.js +387 -0
- package/api/framework/server.js +777 -410
- package/api/index.js +14 -4
- package/package.json +6 -2
- package/sliceConfig.schema.json +122 -122
- package/src/App/index.html +69 -69
- package/src/App/index.js +20 -20
- package/src/App/style.css +11 -11
- package/src/Components/AppComponents/AboutSection/AboutSection.css +9 -9
- package/src/Components/AppComponents/AboutSection/AboutSection.html +8 -8
- package/src/Components/AppComponents/AboutSection/AboutSection.js +12 -12
- package/src/Components/AppComponents/AppShell/AppShell.css +10 -10
- package/src/Components/AppComponents/AppShell/AppShell.html +4 -4
- package/src/Components/AppComponents/AppShell/AppShell.js +36 -36
- package/src/Components/AppComponents/HomeSection/HomeSection.css +20 -20
- package/src/Components/AppComponents/HomeSection/HomeSection.html +10 -10
- package/src/Components/AppComponents/HomeSection/HomeSection.js +19 -19
- package/src/Components/components.js +8 -8
- package/src/public/Styles/sliceStyles.css +34 -34
- package/src/public/Themes/Dark.css +42 -42
- package/src/public/Themes/Light.css +31 -31
- package/src/public/Themes/Slice.css +47 -47
- package/src/public/manifest.json +15 -15
- package/src/public/robots.txt +4 -4
- package/src/public/sitemap.xml +8 -8
- package/src/routes.js +9 -9
- package/src/sliceConfig.json +72 -72
- package/types/index.d.ts +219 -219
- package/.opencode/opencode.json +0 -15
- package/Slice/tests/build-bundled-component-without-category.test.js +0 -103
- package/Slice/tests/build-js-only-visual-components.test.js +0 -144
- package/Slice/tests/build-singleton.test.js +0 -244
- package/Slice/tests/bundle-v2-runtime-contract.test.js +0 -747
- package/Slice/tests/context-patch-use.test.js +0 -95
- package/Slice/tests/destroy-cascade.test.js +0 -215
- package/Slice/tests/event-emitters.test.js +0 -79
- package/Slice/tests/event-registry.test.js +0 -141
- package/Slice/tests/fixtures/real-runtime-loader.mjs +0 -21
- package/Slice/tests/leak-orphans.test.js +0 -131
- package/Slice/tests/props-allowed-values-validation.test.js +0 -119
- package/Slice/tests/public-env-runtime-accessors.test.js +0 -44
- package/Slice/tests/public-env-typed-accessors.test.js +0 -66
- package/Slice/tests/router-loading-finally.test.js +0 -68
- package/api/tests/dev-deps-optimizer.test.js +0 -137
- package/api/tests/dev-server-external.test.js +0 -56
- package/api/tests/precompressed-static.test.js +0 -119
- package/api/tests/public-env-resolver.test.js +0 -193
- package/tests/router-build-failure.test.js +0 -125
- package/tests/router-emit-ordering.test.js +0 -63
|
@@ -1,985 +1,985 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EventManager debug panel.
|
|
3
|
-
*/
|
|
4
|
-
export default class EventManagerDebugger extends HTMLElement {
|
|
5
|
-
constructor() {
|
|
6
|
-
super();
|
|
7
|
-
this.isOpen = false;
|
|
8
|
-
this.filterText = '';
|
|
9
|
-
this.activeTab = 'subscribers';
|
|
10
|
-
this.refreshInterval = null;
|
|
11
|
-
this._autoRefreshTimer = null;
|
|
12
|
-
this._lastHistoryLength = 0;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Initialize panel UI.
|
|
17
|
-
* @returns {Promise<void>}
|
|
18
|
-
*/
|
|
19
|
-
async init() {
|
|
20
|
-
this.innerHTML = this.renderTemplate();
|
|
21
|
-
slice.stylesManager.registerComponentStyles('EventManagerDebugger', this.renderStyles());
|
|
22
|
-
this.cacheElements();
|
|
23
|
-
this.bindEvents();
|
|
24
|
-
this.makeDraggable();
|
|
25
|
-
this._loadStaticGraph();
|
|
26
|
-
this.renderList();
|
|
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
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Toggle panel visibility.
|
|
47
|
-
* @returns {void}
|
|
48
|
-
*/
|
|
49
|
-
toggle() {
|
|
50
|
-
this.isOpen = !this.isOpen;
|
|
51
|
-
this.container.classList.toggle('active', this.isOpen);
|
|
52
|
-
if (this.isOpen) {
|
|
53
|
-
this.renderList();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Show panel.
|
|
59
|
-
* @returns {void}
|
|
60
|
-
*/
|
|
61
|
-
open() {
|
|
62
|
-
this.isOpen = true;
|
|
63
|
-
this.container.classList.add('active');
|
|
64
|
-
this.activeTab = 'subscribers';
|
|
65
|
-
if (slice.events) {
|
|
66
|
-
slice.events.startRecording();
|
|
67
|
-
}
|
|
68
|
-
this._startAutoRefresh();
|
|
69
|
-
this.renderList();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Hide panel.
|
|
74
|
-
* @returns {void}
|
|
75
|
-
*/
|
|
76
|
-
close() {
|
|
77
|
-
this.isOpen = false;
|
|
78
|
-
this.container.classList.remove('active');
|
|
79
|
-
this._stopAutoRefresh();
|
|
80
|
-
if (slice.events) {
|
|
81
|
-
slice.events.stopRecording();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
_startAutoRefresh() {
|
|
86
|
-
this._stopAutoRefresh();
|
|
87
|
-
this._autoRefreshTimer = setInterval(() => {
|
|
88
|
-
if (!this.isOpen) return;
|
|
89
|
-
this._renderActive();
|
|
90
|
-
}, 1500);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
_stopAutoRefresh() {
|
|
94
|
-
if (this._autoRefreshTimer) {
|
|
95
|
-
clearInterval(this._autoRefreshTimer);
|
|
96
|
-
this._autoRefreshTimer = null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
cacheElements() {
|
|
101
|
-
this.container = this.querySelector('#events-debugger');
|
|
102
|
-
this.header = this.querySelector('.events-header');
|
|
103
|
-
this.list = this.querySelector('#events-list');
|
|
104
|
-
this.filterInput = this.querySelector('#events-filter');
|
|
105
|
-
this.countLabel = this.querySelector('#events-count');
|
|
106
|
-
this.refreshButton = this.querySelector('#events-refresh');
|
|
107
|
-
this.closeButton = this.querySelector('#events-close');
|
|
108
|
-
this.tabSubs = this.querySelector('#tab-subscribers');
|
|
109
|
-
this.tabRegistry = this.querySelector('#tab-registry');
|
|
110
|
-
this.tabHistory = this.querySelector('#tab-history');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
bindEvents() {
|
|
114
|
-
this.refreshButton.addEventListener('click', () => this._renderActive());
|
|
115
|
-
this.closeButton.addEventListener('click', () => this.close());
|
|
116
|
-
this.filterInput.addEventListener('input', (event) => {
|
|
117
|
-
this.filterText = event.target.value.trim().toLowerCase();
|
|
118
|
-
this._renderActive();
|
|
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();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
makeDraggable() {
|
|
142
|
-
if (!this.header || !this.container) return;
|
|
143
|
-
|
|
144
|
-
let offset = { x: 0, y: 0 };
|
|
145
|
-
let isDragging = false;
|
|
146
|
-
|
|
147
|
-
this.header.style.cursor = 'grab';
|
|
148
|
-
|
|
149
|
-
this.header.addEventListener('mousedown', (event) => {
|
|
150
|
-
if (event.target.closest('.btn')) return;
|
|
151
|
-
isDragging = true;
|
|
152
|
-
offset.x = event.clientX - this.container.getBoundingClientRect().left;
|
|
153
|
-
offset.y = event.clientY - this.container.getBoundingClientRect().top;
|
|
154
|
-
this.header.style.cursor = 'grabbing';
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
document.addEventListener('mousemove', (event) => {
|
|
158
|
-
if (!isDragging) return;
|
|
159
|
-
const rect = this.container.getBoundingClientRect();
|
|
160
|
-
const maxX = window.innerWidth - rect.width;
|
|
161
|
-
const maxY = window.innerHeight - rect.height;
|
|
162
|
-
const x = Math.min(Math.max(event.clientX - offset.x, 0), maxX);
|
|
163
|
-
const y = Math.min(Math.max(event.clientY - offset.y, 0), maxY);
|
|
164
|
-
this.container.style.left = `${x}px`;
|
|
165
|
-
this.container.style.top = `${y}px`;
|
|
166
|
-
this.container.style.right = 'auto';
|
|
167
|
-
this.container.style.bottom = 'auto';
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
document.addEventListener('mouseup', () => {
|
|
171
|
-
if (!isDragging) return;
|
|
172
|
-
isDragging = false;
|
|
173
|
-
this.header.style.cursor = 'grab';
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
renderList() {
|
|
178
|
-
if (!slice?.events?.subscriptions) {
|
|
179
|
-
this.list.textContent = 'EventManager not available.';
|
|
180
|
-
this.countLabel.textContent = '0';
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const items = [];
|
|
185
|
-
slice.events.subscriptions.forEach((subs, eventName) => {
|
|
186
|
-
const emitCount = slice.events.emitCounts?.get(eventName) || 0;
|
|
187
|
-
const entries = Array.from(subs.entries()).map(([id, sub]) => {
|
|
188
|
-
const componentSliceId = sub.componentSliceId || null;
|
|
189
|
-
const component = componentSliceId ? slice.controller.getComponent(componentSliceId) : null;
|
|
190
|
-
const componentName = component?.constructor?.name || null;
|
|
191
|
-
return {
|
|
192
|
-
id,
|
|
193
|
-
once: sub.once,
|
|
194
|
-
componentSliceId,
|
|
195
|
-
componentName
|
|
196
|
-
};
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
if (this.filterText && !eventName.toLowerCase().includes(this.filterText)) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
items.push({ eventName, count: subs.size, emitCount, entries, emitters: this._emittersFor(eventName) });
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
items.sort((a, b) => a.eventName.localeCompare(b.eventName));
|
|
207
|
-
|
|
208
|
-
this.countLabel.textContent = String(items.length);
|
|
209
|
-
this.list.innerHTML = items.length
|
|
210
|
-
? items.map((item) => {
|
|
211
|
-
const details = item.entries.map((entry) => {
|
|
212
|
-
const label = entry.componentName
|
|
213
|
-
? `${entry.componentName} (${entry.componentSliceId})`
|
|
214
|
-
: entry.componentSliceId || 'Global';
|
|
215
|
-
const onceBadge = entry.once ? '<span class="badge">once</span>' : '';
|
|
216
|
-
return `
|
|
217
|
-
<div class="subscriber-row">
|
|
218
|
-
<div class="subscriber-name">${this.escapeHtml(label)}</div>
|
|
219
|
-
<div class="subscriber-meta">${entry.id}${onceBadge}</div>
|
|
220
|
-
</div>
|
|
221
|
-
`;
|
|
222
|
-
}).join('');
|
|
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
|
-
|
|
233
|
-
return `
|
|
234
|
-
<details class="event-row">
|
|
235
|
-
<summary>
|
|
236
|
-
<div class="event-name">${this.escapeHtml(item.eventName)}</div>
|
|
237
|
-
<div class="event-metrics">
|
|
238
|
-
<span class="emit-count" title="Emits this session">⚡${item.emitCount}</span>
|
|
239
|
-
<span class="event-count">${item.count}</span>
|
|
240
|
-
</div>
|
|
241
|
-
</summary>
|
|
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>
|
|
248
|
-
${details || '<div class="empty">No subscribers</div>'}
|
|
249
|
-
</div>
|
|
250
|
-
</details>
|
|
251
|
-
`;
|
|
252
|
-
}).join('')
|
|
253
|
-
: '<div class="empty">No events</div>';
|
|
254
|
-
}
|
|
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
|
-
|
|
423
|
-
renderHistory() {
|
|
424
|
-
if (!slice?.events?.emitHistory) {
|
|
425
|
-
this.list.textContent = 'EventManager not available.';
|
|
426
|
-
this.countLabel.textContent = '0';
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const history = slice.events.emitHistory;
|
|
431
|
-
const filtered = this.filterText
|
|
432
|
-
? history.filter(e => e.eventName.toLowerCase().includes(this.filterText))
|
|
433
|
-
: history;
|
|
434
|
-
|
|
435
|
-
this.countLabel.textContent = String(filtered.length);
|
|
436
|
-
if (!filtered.length) {
|
|
437
|
-
this.list.innerHTML = '<div class="empty">No emits recorded yet</div>';
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const now = Date.now();
|
|
442
|
-
this.list.innerHTML = [...filtered].reverse().map((entry) => {
|
|
443
|
-
const diff = now - entry.timestamp;
|
|
444
|
-
const timeStr = diff < 1000 ? 'now'
|
|
445
|
-
: diff < 60000 ? `${Math.floor(diff / 1000)}s ago`
|
|
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
|
-
: '';
|
|
450
|
-
return `
|
|
451
|
-
<div class="history-row">
|
|
452
|
-
<div class="history-main">
|
|
453
|
-
<div class="history-event">${this.escapeHtml(entry.eventName)}</div>
|
|
454
|
-
${from}
|
|
455
|
-
</div>
|
|
456
|
-
<div class="history-time">${timeStr}</div>
|
|
457
|
-
</div>
|
|
458
|
-
`;
|
|
459
|
-
}).join('');
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
escapeHtml(value) {
|
|
463
|
-
return String(value)
|
|
464
|
-
.replace(/&/g, '&')
|
|
465
|
-
.replace(/</g, '<')
|
|
466
|
-
.replace(/>/g, '>')
|
|
467
|
-
.replace(/"/g, '"');
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
renderTemplate() {
|
|
471
|
-
return `
|
|
472
|
-
<div id="events-debugger">
|
|
473
|
-
<div class="events-header">
|
|
474
|
-
<div class="brand">
|
|
475
|
-
<span class="status-dot"></span>
|
|
476
|
-
<span class="glyph">◇</span>
|
|
477
|
-
<span class="title">EVENTS</span>
|
|
478
|
-
</div>
|
|
479
|
-
<div class="actions">
|
|
480
|
-
<button id="events-refresh" class="btn" title="Refresh" aria-label="Refresh">⟳</button>
|
|
481
|
-
<button id="events-close" class="btn" title="Close" aria-label="Close">✕</button>
|
|
482
|
-
</div>
|
|
483
|
-
</div>
|
|
484
|
-
<div class="events-tabs">
|
|
485
|
-
<button id="tab-subscribers" class="tab-btn active">Subscribers</button>
|
|
486
|
-
<button id="tab-registry" class="tab-btn">Registry</button>
|
|
487
|
-
<button id="tab-history" class="tab-btn">History</button>
|
|
488
|
-
</div>
|
|
489
|
-
<div class="events-toolbar">
|
|
490
|
-
<input id="events-filter" type="text" placeholder="filter events…" autocomplete="off" spellcheck="false" />
|
|
491
|
-
<div class="count"><span id="events-count">0</span></div>
|
|
492
|
-
</div>
|
|
493
|
-
<div class="events-list" id="events-list"></div>
|
|
494
|
-
</div>
|
|
495
|
-
`;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
renderStyles() {
|
|
499
|
-
return `
|
|
500
|
-
/* Slice Instruments — events console. All selectors scoped to the
|
|
501
|
-
<slice-eventmanager-debugger> tag so nothing clashes with app styles.
|
|
502
|
-
Every --si-* token reads the matching framework theme variable from
|
|
503
|
-
:root, falling back to the original hardcoded value if absent. */
|
|
504
|
-
slice-eventmanager-debugger {
|
|
505
|
-
--si-accent: var(--primary-color, #6ee7ff);
|
|
506
|
-
--si-accent-rgb: var(--primary-color-rgb, 110, 231, 255);
|
|
507
|
-
--si-surface: var(--primary-background-color, rgba(17, 19, 28, 0.86));
|
|
508
|
-
--si-raised: var(--secondary-background-color, rgba(255, 255, 255, 0.035));
|
|
509
|
-
--si-raised-2: var(--tertiary-background-color, rgba(255, 255, 255, 0.06));
|
|
510
|
-
--si-border: var(--medium-color, rgba(255, 255, 255, 0.09));
|
|
511
|
-
--si-text: var(--font-primary-color, #e8eaf2);
|
|
512
|
-
--si-dim: var(--font-secondary-color, #888fa6);
|
|
513
|
-
--si-danger: var(--danger-color, #ff6b6b);
|
|
514
|
-
--si-success: var(--success-color, #46d39a);
|
|
515
|
-
--si-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Code', Menlo, Consolas, monospace;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
slice-eventmanager-debugger #events-debugger {
|
|
519
|
-
position: fixed;
|
|
520
|
-
bottom: 20px;
|
|
521
|
-
right: 20px;
|
|
522
|
-
width: min(380px, calc(100vw - 40px));
|
|
523
|
-
max-height: 64vh;
|
|
524
|
-
background: var(--si-surface);
|
|
525
|
-
border: 1px solid var(--si-border);
|
|
526
|
-
border-radius: 14px;
|
|
527
|
-
box-shadow:
|
|
528
|
-
0 24px 60px -12px rgba(0, 0, 0, 0.55),
|
|
529
|
-
0 0 0 1px rgba(0, 0, 0, 0.2),
|
|
530
|
-
0 0 38px -18px rgba(var(--si-accent-rgb), 0.55);
|
|
531
|
-
-webkit-backdrop-filter: blur(22px) saturate(1.3);
|
|
532
|
-
backdrop-filter: blur(22px) saturate(1.3);
|
|
533
|
-
display: none;
|
|
534
|
-
flex-direction: column;
|
|
535
|
-
z-index: 10001;
|
|
536
|
-
overflow: hidden;
|
|
537
|
-
color: var(--si-text);
|
|
538
|
-
font-family: var(--si-mono);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
slice-eventmanager-debugger #events-debugger.active {
|
|
542
|
-
display: flex;
|
|
543
|
-
animation: si-events-in 0.26s cubic-bezier(0.16, 1, 0.3, 1);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
@keyframes si-events-in {
|
|
547
|
-
from { opacity: 0; transform: translateY(10px) scale(0.985); }
|
|
548
|
-
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
slice-eventmanager-debugger #events-debugger * { box-sizing: border-box; }
|
|
552
|
-
|
|
553
|
-
slice-eventmanager-debugger #events-debugger::before {
|
|
554
|
-
content: '';
|
|
555
|
-
position: absolute;
|
|
556
|
-
left: 0; top: 0; bottom: 0;
|
|
557
|
-
width: 2px;
|
|
558
|
-
background: linear-gradient(180deg, var(--si-accent), transparent 70%);
|
|
559
|
-
opacity: 0.85;
|
|
560
|
-
pointer-events: none;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
slice-eventmanager-debugger .events-header {
|
|
564
|
-
display: flex;
|
|
565
|
-
justify-content: space-between;
|
|
566
|
-
align-items: center;
|
|
567
|
-
padding: 12px 14px;
|
|
568
|
-
background:
|
|
569
|
-
radial-gradient(120% 140% at 0% 0%, rgba(var(--si-accent-rgb), 0.10), transparent 60%),
|
|
570
|
-
var(--si-raised);
|
|
571
|
-
border-bottom: 1px solid var(--si-border);
|
|
572
|
-
user-select: none;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
slice-eventmanager-debugger .brand { display: flex; align-items: center; gap: 9px; }
|
|
576
|
-
|
|
577
|
-
slice-eventmanager-debugger .status-dot {
|
|
578
|
-
width: 7px; height: 7px;
|
|
579
|
-
border-radius: 50%;
|
|
580
|
-
background: var(--si-accent);
|
|
581
|
-
animation: si-pulse-ev 2.4s ease-out infinite;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
@keyframes si-pulse-ev {
|
|
585
|
-
0% { box-shadow: 0 0 0 0 rgba(var(--si-accent-rgb), 0.55); }
|
|
586
|
-
70% { box-shadow: 0 0 0 7px rgba(var(--si-accent-rgb), 0); }
|
|
587
|
-
100% { box-shadow: 0 0 0 0 rgba(var(--si-accent-rgb), 0); }
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
slice-eventmanager-debugger .glyph { color: var(--si-accent); font-size: 12px; opacity: 0.9; }
|
|
591
|
-
|
|
592
|
-
slice-eventmanager-debugger .title {
|
|
593
|
-
font-weight: 600;
|
|
594
|
-
font-size: 11px;
|
|
595
|
-
letter-spacing: 0.18em;
|
|
596
|
-
color: var(--si-text);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
slice-eventmanager-debugger .actions { display: flex; gap: 6px; }
|
|
600
|
-
|
|
601
|
-
slice-eventmanager-debugger .btn {
|
|
602
|
-
width: 26px; height: 26px;
|
|
603
|
-
display: flex; align-items: center; justify-content: center;
|
|
604
|
-
border-radius: 7px;
|
|
605
|
-
border: 1px solid var(--si-border);
|
|
606
|
-
background: var(--si-raised);
|
|
607
|
-
color: var(--si-dim);
|
|
608
|
-
cursor: pointer;
|
|
609
|
-
font-size: 13px;
|
|
610
|
-
line-height: 1;
|
|
611
|
-
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
|
|
612
|
-
}
|
|
613
|
-
slice-eventmanager-debugger .btn:hover {
|
|
614
|
-
color: var(--si-text);
|
|
615
|
-
background: var(--si-raised-2);
|
|
616
|
-
border-color: rgba(var(--si-accent-rgb), 0.5);
|
|
617
|
-
}
|
|
618
|
-
slice-eventmanager-debugger .btn:active { transform: scale(0.92); }
|
|
619
|
-
slice-eventmanager-debugger #events-refresh:hover { color: var(--si-accent); }
|
|
620
|
-
|
|
621
|
-
slice-eventmanager-debugger .events-tabs {
|
|
622
|
-
display: flex;
|
|
623
|
-
gap: 0;
|
|
624
|
-
padding: 0 12px;
|
|
625
|
-
border-bottom: 1px solid var(--si-border);
|
|
626
|
-
background: var(--si-raised);
|
|
627
|
-
}
|
|
628
|
-
slice-eventmanager-debugger .tab-btn {
|
|
629
|
-
all: unset;
|
|
630
|
-
cursor: pointer;
|
|
631
|
-
font-size: 10px;
|
|
632
|
-
font-weight: 600;
|
|
633
|
-
letter-spacing: 0.06em;
|
|
634
|
-
text-transform: uppercase;
|
|
635
|
-
padding: 7px 12px;
|
|
636
|
-
color: var(--si-dim);
|
|
637
|
-
border-bottom: 2px solid transparent;
|
|
638
|
-
transition: color 0.15s ease, border-color 0.15s ease;
|
|
639
|
-
}
|
|
640
|
-
slice-eventmanager-debugger .tab-btn:hover { color: var(--si-text); }
|
|
641
|
-
slice-eventmanager-debugger .tab-btn.active {
|
|
642
|
-
color: var(--si-accent);
|
|
643
|
-
border-bottom-color: var(--si-accent);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
slice-eventmanager-debugger .events-toolbar {
|
|
647
|
-
display: flex;
|
|
648
|
-
gap: 10px;
|
|
649
|
-
align-items: center;
|
|
650
|
-
padding: 10px 12px;
|
|
651
|
-
border-bottom: 1px solid var(--si-border);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
slice-eventmanager-debugger .events-toolbar input {
|
|
655
|
-
flex: 1;
|
|
656
|
-
min-width: 0;
|
|
657
|
-
padding: 7px 10px 7px 30px;
|
|
658
|
-
border-radius: 8px;
|
|
659
|
-
border: 1px solid var(--si-border);
|
|
660
|
-
background:
|
|
661
|
-
var(--si-raised) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='13' viewBox='0 0 24 24' fill='none' stroke='%23888fa6' stroke-width='2.2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E") no-repeat 10px center;
|
|
662
|
-
color: var(--si-text);
|
|
663
|
-
font-family: var(--si-mono);
|
|
664
|
-
font-size: 12px;
|
|
665
|
-
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
666
|
-
}
|
|
667
|
-
slice-eventmanager-debugger .events-toolbar input::placeholder { color: var(--si-dim); }
|
|
668
|
-
slice-eventmanager-debugger .events-toolbar input:focus {
|
|
669
|
-
outline: none;
|
|
670
|
-
border-color: rgba(var(--si-accent-rgb), 0.6);
|
|
671
|
-
box-shadow: 0 0 0 3px rgba(var(--si-accent-rgb), 0.12);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
slice-eventmanager-debugger .events-toolbar .count { font-size: 11px; color: var(--si-dim); min-width: 22px; text-align: center; }
|
|
675
|
-
slice-eventmanager-debugger .events-toolbar .count span { color: var(--si-accent); font-weight: 600; }
|
|
676
|
-
|
|
677
|
-
slice-eventmanager-debugger .events-list {
|
|
678
|
-
padding: 10px 12px 12px;
|
|
679
|
-
overflow: auto;
|
|
680
|
-
display: flex;
|
|
681
|
-
flex-direction: column;
|
|
682
|
-
gap: 7px;
|
|
683
|
-
}
|
|
684
|
-
slice-eventmanager-debugger .events-list::-webkit-scrollbar { width: 8px; }
|
|
685
|
-
slice-eventmanager-debugger .events-list::-webkit-scrollbar-thumb {
|
|
686
|
-
background: var(--si-raised-2);
|
|
687
|
-
border-radius: 8px;
|
|
688
|
-
border: 2px solid transparent;
|
|
689
|
-
background-clip: padding-box;
|
|
690
|
-
}
|
|
691
|
-
slice-eventmanager-debugger .events-list::-webkit-scrollbar-thumb:hover { background: rgba(var(--si-accent-rgb), 0.4); background-clip: padding-box; }
|
|
692
|
-
|
|
693
|
-
slice-eventmanager-debugger .event-row {
|
|
694
|
-
display: block;
|
|
695
|
-
padding: 9px 11px;
|
|
696
|
-
background: var(--si-raised);
|
|
697
|
-
border-radius: 9px;
|
|
698
|
-
border: 1px solid var(--si-border);
|
|
699
|
-
border-left: 2px solid transparent;
|
|
700
|
-
transition: border-color 0.18s ease, background 0.18s ease;
|
|
701
|
-
}
|
|
702
|
-
slice-eventmanager-debugger .event-row:hover { background: var(--si-raised-2); border-left-color: var(--si-accent); }
|
|
703
|
-
|
|
704
|
-
slice-eventmanager-debugger .event-row summary {
|
|
705
|
-
display: flex;
|
|
706
|
-
align-items: center;
|
|
707
|
-
justify-content: space-between;
|
|
708
|
-
gap: 10px;
|
|
709
|
-
cursor: pointer;
|
|
710
|
-
list-style: none;
|
|
711
|
-
}
|
|
712
|
-
slice-eventmanager-debugger .event-row summary::-webkit-details-marker { display: none; }
|
|
713
|
-
slice-eventmanager-debugger .event-row summary::after {
|
|
714
|
-
content: '›';
|
|
715
|
-
margin-left: auto;
|
|
716
|
-
color: var(--si-dim);
|
|
717
|
-
font-size: 15px;
|
|
718
|
-
line-height: 1;
|
|
719
|
-
transition: transform 0.2s ease, color 0.2s ease;
|
|
720
|
-
}
|
|
721
|
-
slice-eventmanager-debugger .event-row[open] summary::after { transform: rotate(90deg); color: var(--si-accent); }
|
|
722
|
-
|
|
723
|
-
slice-eventmanager-debugger .event-name {
|
|
724
|
-
font-family: var(--si-mono);
|
|
725
|
-
font-size: 12px;
|
|
726
|
-
color: var(--si-text);
|
|
727
|
-
overflow: hidden;
|
|
728
|
-
text-overflow: ellipsis;
|
|
729
|
-
white-space: nowrap;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
slice-eventmanager-debugger .event-metrics {
|
|
733
|
-
display: flex;
|
|
734
|
-
align-items: center;
|
|
735
|
-
gap: 6px;
|
|
736
|
-
}
|
|
737
|
-
slice-eventmanager-debugger .emit-count {
|
|
738
|
-
font-weight: 600;
|
|
739
|
-
font-size: 10px;
|
|
740
|
-
color: var(--si-success);
|
|
741
|
-
background: rgba(70, 211, 154, 0.12);
|
|
742
|
-
border: 1px solid rgba(70, 211, 154, 0.25);
|
|
743
|
-
padding: 1px 6px;
|
|
744
|
-
border-radius: 999px;
|
|
745
|
-
white-space: nowrap;
|
|
746
|
-
}
|
|
747
|
-
slice-eventmanager-debugger .event-count {
|
|
748
|
-
font-weight: 600;
|
|
749
|
-
font-size: 11px;
|
|
750
|
-
color: var(--si-accent);
|
|
751
|
-
background: rgba(var(--si-accent-rgb), 0.12);
|
|
752
|
-
border: 1px solid rgba(var(--si-accent-rgb), 0.25);
|
|
753
|
-
padding: 1px 8px;
|
|
754
|
-
border-radius: 999px;
|
|
755
|
-
min-width: 22px;
|
|
756
|
-
text-align: center;
|
|
757
|
-
}
|
|
758
|
-
slice-eventmanager-debugger .history-row {
|
|
759
|
-
display: flex;
|
|
760
|
-
justify-content: space-between;
|
|
761
|
-
align-items: center;
|
|
762
|
-
gap: 10px;
|
|
763
|
-
padding: 6px 11px;
|
|
764
|
-
background: var(--si-raised);
|
|
765
|
-
border-radius: 7px;
|
|
766
|
-
border: 1px solid var(--si-border);
|
|
767
|
-
border-left: 2px solid transparent;
|
|
768
|
-
transition: border-color 0.15s ease;
|
|
769
|
-
}
|
|
770
|
-
slice-eventmanager-debugger .history-row:hover { border-left-color: var(--si-accent); }
|
|
771
|
-
slice-eventmanager-debugger .history-event {
|
|
772
|
-
font-family: var(--si-mono);
|
|
773
|
-
font-size: 11.5px;
|
|
774
|
-
color: var(--si-text);
|
|
775
|
-
overflow: hidden;
|
|
776
|
-
text-overflow: ellipsis;
|
|
777
|
-
white-space: nowrap;
|
|
778
|
-
}
|
|
779
|
-
slice-eventmanager-debugger .history-time {
|
|
780
|
-
font-size: 10px;
|
|
781
|
-
color: var(--si-dim);
|
|
782
|
-
white-space: nowrap;
|
|
783
|
-
flex-shrink: 0;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
slice-eventmanager-debugger .subscriber-list {
|
|
787
|
-
margin-top: 9px;
|
|
788
|
-
padding-top: 9px;
|
|
789
|
-
border-top: 1px dashed var(--si-border);
|
|
790
|
-
display: flex;
|
|
791
|
-
flex-direction: column;
|
|
792
|
-
gap: 6px;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
slice-eventmanager-debugger .subscriber-row {
|
|
796
|
-
display: flex;
|
|
797
|
-
justify-content: space-between;
|
|
798
|
-
align-items: center;
|
|
799
|
-
gap: 10px;
|
|
800
|
-
padding: 6px 9px;
|
|
801
|
-
border-radius: 7px;
|
|
802
|
-
background: rgba(0, 0, 0, 0.22);
|
|
803
|
-
border: 1px solid var(--si-border);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
slice-eventmanager-debugger .subscriber-name {
|
|
807
|
-
font-size: 11.5px;
|
|
808
|
-
color: var(--si-text);
|
|
809
|
-
overflow: hidden;
|
|
810
|
-
text-overflow: ellipsis;
|
|
811
|
-
white-space: nowrap;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
slice-eventmanager-debugger .subscriber-meta {
|
|
815
|
-
font-size: 10.5px;
|
|
816
|
-
color: var(--si-dim);
|
|
817
|
-
display: flex;
|
|
818
|
-
align-items: center;
|
|
819
|
-
gap: 6px;
|
|
820
|
-
white-space: nowrap;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
slice-eventmanager-debugger .badge {
|
|
824
|
-
padding: 1px 6px;
|
|
825
|
-
border-radius: 999px;
|
|
826
|
-
background: rgba(var(--si-accent-rgb), 0.16);
|
|
827
|
-
color: var(--si-accent);
|
|
828
|
-
border: 1px solid rgba(var(--si-accent-rgb), 0.3);
|
|
829
|
-
font-size: 9px;
|
|
830
|
-
letter-spacing: 0.06em;
|
|
831
|
-
text-transform: uppercase;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
slice-eventmanager-debugger .empty {
|
|
835
|
-
color: var(--si-dim);
|
|
836
|
-
font-size: 11px;
|
|
837
|
-
letter-spacing: 0.04em;
|
|
838
|
-
text-align: center;
|
|
839
|
-
padding: 22px 0;
|
|
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
|
-
}
|
|
976
|
-
|
|
977
|
-
@media (prefers-reduced-motion: reduce) {
|
|
978
|
-
slice-eventmanager-debugger #events-debugger.active { animation: none; }
|
|
979
|
-
slice-eventmanager-debugger .status-dot { animation: none; }
|
|
980
|
-
}
|
|
981
|
-
`;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
customElements.define('slice-eventmanager-debugger', EventManagerDebugger);
|
|
1
|
+
/**
|
|
2
|
+
* EventManager debug panel.
|
|
3
|
+
*/
|
|
4
|
+
export default class EventManagerDebugger extends HTMLElement {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
this.isOpen = false;
|
|
8
|
+
this.filterText = '';
|
|
9
|
+
this.activeTab = 'subscribers';
|
|
10
|
+
this.refreshInterval = null;
|
|
11
|
+
this._autoRefreshTimer = null;
|
|
12
|
+
this._lastHistoryLength = 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initialize panel UI.
|
|
17
|
+
* @returns {Promise<void>}
|
|
18
|
+
*/
|
|
19
|
+
async init() {
|
|
20
|
+
this.innerHTML = this.renderTemplate();
|
|
21
|
+
slice.stylesManager.registerComponentStyles('EventManagerDebugger', this.renderStyles());
|
|
22
|
+
this.cacheElements();
|
|
23
|
+
this.bindEvents();
|
|
24
|
+
this.makeDraggable();
|
|
25
|
+
this._loadStaticGraph();
|
|
26
|
+
this.renderList();
|
|
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
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Toggle panel visibility.
|
|
47
|
+
* @returns {void}
|
|
48
|
+
*/
|
|
49
|
+
toggle() {
|
|
50
|
+
this.isOpen = !this.isOpen;
|
|
51
|
+
this.container.classList.toggle('active', this.isOpen);
|
|
52
|
+
if (this.isOpen) {
|
|
53
|
+
this.renderList();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Show panel.
|
|
59
|
+
* @returns {void}
|
|
60
|
+
*/
|
|
61
|
+
open() {
|
|
62
|
+
this.isOpen = true;
|
|
63
|
+
this.container.classList.add('active');
|
|
64
|
+
this.activeTab = 'subscribers';
|
|
65
|
+
if (slice.events) {
|
|
66
|
+
slice.events.startRecording();
|
|
67
|
+
}
|
|
68
|
+
this._startAutoRefresh();
|
|
69
|
+
this.renderList();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Hide panel.
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/
|
|
76
|
+
close() {
|
|
77
|
+
this.isOpen = false;
|
|
78
|
+
this.container.classList.remove('active');
|
|
79
|
+
this._stopAutoRefresh();
|
|
80
|
+
if (slice.events) {
|
|
81
|
+
slice.events.stopRecording();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_startAutoRefresh() {
|
|
86
|
+
this._stopAutoRefresh();
|
|
87
|
+
this._autoRefreshTimer = setInterval(() => {
|
|
88
|
+
if (!this.isOpen) return;
|
|
89
|
+
this._renderActive();
|
|
90
|
+
}, 1500);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_stopAutoRefresh() {
|
|
94
|
+
if (this._autoRefreshTimer) {
|
|
95
|
+
clearInterval(this._autoRefreshTimer);
|
|
96
|
+
this._autoRefreshTimer = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
cacheElements() {
|
|
101
|
+
this.container = this.querySelector('#events-debugger');
|
|
102
|
+
this.header = this.querySelector('.events-header');
|
|
103
|
+
this.list = this.querySelector('#events-list');
|
|
104
|
+
this.filterInput = this.querySelector('#events-filter');
|
|
105
|
+
this.countLabel = this.querySelector('#events-count');
|
|
106
|
+
this.refreshButton = this.querySelector('#events-refresh');
|
|
107
|
+
this.closeButton = this.querySelector('#events-close');
|
|
108
|
+
this.tabSubs = this.querySelector('#tab-subscribers');
|
|
109
|
+
this.tabRegistry = this.querySelector('#tab-registry');
|
|
110
|
+
this.tabHistory = this.querySelector('#tab-history');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
bindEvents() {
|
|
114
|
+
this.refreshButton.addEventListener('click', () => this._renderActive());
|
|
115
|
+
this.closeButton.addEventListener('click', () => this.close());
|
|
116
|
+
this.filterInput.addEventListener('input', (event) => {
|
|
117
|
+
this.filterText = event.target.value.trim().toLowerCase();
|
|
118
|
+
this._renderActive();
|
|
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();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
makeDraggable() {
|
|
142
|
+
if (!this.header || !this.container) return;
|
|
143
|
+
|
|
144
|
+
let offset = { x: 0, y: 0 };
|
|
145
|
+
let isDragging = false;
|
|
146
|
+
|
|
147
|
+
this.header.style.cursor = 'grab';
|
|
148
|
+
|
|
149
|
+
this.header.addEventListener('mousedown', (event) => {
|
|
150
|
+
if (event.target.closest('.btn')) return;
|
|
151
|
+
isDragging = true;
|
|
152
|
+
offset.x = event.clientX - this.container.getBoundingClientRect().left;
|
|
153
|
+
offset.y = event.clientY - this.container.getBoundingClientRect().top;
|
|
154
|
+
this.header.style.cursor = 'grabbing';
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
document.addEventListener('mousemove', (event) => {
|
|
158
|
+
if (!isDragging) return;
|
|
159
|
+
const rect = this.container.getBoundingClientRect();
|
|
160
|
+
const maxX = window.innerWidth - rect.width;
|
|
161
|
+
const maxY = window.innerHeight - rect.height;
|
|
162
|
+
const x = Math.min(Math.max(event.clientX - offset.x, 0), maxX);
|
|
163
|
+
const y = Math.min(Math.max(event.clientY - offset.y, 0), maxY);
|
|
164
|
+
this.container.style.left = `${x}px`;
|
|
165
|
+
this.container.style.top = `${y}px`;
|
|
166
|
+
this.container.style.right = 'auto';
|
|
167
|
+
this.container.style.bottom = 'auto';
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
document.addEventListener('mouseup', () => {
|
|
171
|
+
if (!isDragging) return;
|
|
172
|
+
isDragging = false;
|
|
173
|
+
this.header.style.cursor = 'grab';
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
renderList() {
|
|
178
|
+
if (!slice?.events?.subscriptions) {
|
|
179
|
+
this.list.textContent = 'EventManager not available.';
|
|
180
|
+
this.countLabel.textContent = '0';
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const items = [];
|
|
185
|
+
slice.events.subscriptions.forEach((subs, eventName) => {
|
|
186
|
+
const emitCount = slice.events.emitCounts?.get(eventName) || 0;
|
|
187
|
+
const entries = Array.from(subs.entries()).map(([id, sub]) => {
|
|
188
|
+
const componentSliceId = sub.componentSliceId || null;
|
|
189
|
+
const component = componentSliceId ? slice.controller.getComponent(componentSliceId) : null;
|
|
190
|
+
const componentName = component?.constructor?.name || null;
|
|
191
|
+
return {
|
|
192
|
+
id,
|
|
193
|
+
once: sub.once,
|
|
194
|
+
componentSliceId,
|
|
195
|
+
componentName
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (this.filterText && !eventName.toLowerCase().includes(this.filterText)) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
items.push({ eventName, count: subs.size, emitCount, entries, emitters: this._emittersFor(eventName) });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
items.sort((a, b) => a.eventName.localeCompare(b.eventName));
|
|
207
|
+
|
|
208
|
+
this.countLabel.textContent = String(items.length);
|
|
209
|
+
this.list.innerHTML = items.length
|
|
210
|
+
? items.map((item) => {
|
|
211
|
+
const details = item.entries.map((entry) => {
|
|
212
|
+
const label = entry.componentName
|
|
213
|
+
? `${entry.componentName} (${entry.componentSliceId})`
|
|
214
|
+
: entry.componentSliceId || 'Global';
|
|
215
|
+
const onceBadge = entry.once ? '<span class="badge">once</span>' : '';
|
|
216
|
+
return `
|
|
217
|
+
<div class="subscriber-row">
|
|
218
|
+
<div class="subscriber-name">${this.escapeHtml(label)}</div>
|
|
219
|
+
<div class="subscriber-meta">${entry.id}${onceBadge}</div>
|
|
220
|
+
</div>
|
|
221
|
+
`;
|
|
222
|
+
}).join('');
|
|
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
|
+
|
|
233
|
+
return `
|
|
234
|
+
<details class="event-row">
|
|
235
|
+
<summary>
|
|
236
|
+
<div class="event-name">${this.escapeHtml(item.eventName)}</div>
|
|
237
|
+
<div class="event-metrics">
|
|
238
|
+
<span class="emit-count" title="Emits this session">⚡${item.emitCount}</span>
|
|
239
|
+
<span class="event-count">${item.count}</span>
|
|
240
|
+
</div>
|
|
241
|
+
</summary>
|
|
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>
|
|
248
|
+
${details || '<div class="empty">No subscribers</div>'}
|
|
249
|
+
</div>
|
|
250
|
+
</details>
|
|
251
|
+
`;
|
|
252
|
+
}).join('')
|
|
253
|
+
: '<div class="empty">No events</div>';
|
|
254
|
+
}
|
|
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
|
+
|
|
423
|
+
renderHistory() {
|
|
424
|
+
if (!slice?.events?.emitHistory) {
|
|
425
|
+
this.list.textContent = 'EventManager not available.';
|
|
426
|
+
this.countLabel.textContent = '0';
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const history = slice.events.emitHistory;
|
|
431
|
+
const filtered = this.filterText
|
|
432
|
+
? history.filter(e => e.eventName.toLowerCase().includes(this.filterText))
|
|
433
|
+
: history;
|
|
434
|
+
|
|
435
|
+
this.countLabel.textContent = String(filtered.length);
|
|
436
|
+
if (!filtered.length) {
|
|
437
|
+
this.list.innerHTML = '<div class="empty">No emits recorded yet</div>';
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const now = Date.now();
|
|
442
|
+
this.list.innerHTML = [...filtered].reverse().map((entry) => {
|
|
443
|
+
const diff = now - entry.timestamp;
|
|
444
|
+
const timeStr = diff < 1000 ? 'now'
|
|
445
|
+
: diff < 60000 ? `${Math.floor(diff / 1000)}s ago`
|
|
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
|
+
: '';
|
|
450
|
+
return `
|
|
451
|
+
<div class="history-row">
|
|
452
|
+
<div class="history-main">
|
|
453
|
+
<div class="history-event">${this.escapeHtml(entry.eventName)}</div>
|
|
454
|
+
${from}
|
|
455
|
+
</div>
|
|
456
|
+
<div class="history-time">${timeStr}</div>
|
|
457
|
+
</div>
|
|
458
|
+
`;
|
|
459
|
+
}).join('');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
escapeHtml(value) {
|
|
463
|
+
return String(value)
|
|
464
|
+
.replace(/&/g, '&')
|
|
465
|
+
.replace(/</g, '<')
|
|
466
|
+
.replace(/>/g, '>')
|
|
467
|
+
.replace(/"/g, '"');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
renderTemplate() {
|
|
471
|
+
return `
|
|
472
|
+
<div id="events-debugger">
|
|
473
|
+
<div class="events-header">
|
|
474
|
+
<div class="brand">
|
|
475
|
+
<span class="status-dot"></span>
|
|
476
|
+
<span class="glyph">◇</span>
|
|
477
|
+
<span class="title">EVENTS</span>
|
|
478
|
+
</div>
|
|
479
|
+
<div class="actions">
|
|
480
|
+
<button id="events-refresh" class="btn" title="Refresh" aria-label="Refresh">⟳</button>
|
|
481
|
+
<button id="events-close" class="btn" title="Close" aria-label="Close">✕</button>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="events-tabs">
|
|
485
|
+
<button id="tab-subscribers" class="tab-btn active">Subscribers</button>
|
|
486
|
+
<button id="tab-registry" class="tab-btn">Registry</button>
|
|
487
|
+
<button id="tab-history" class="tab-btn">History</button>
|
|
488
|
+
</div>
|
|
489
|
+
<div class="events-toolbar">
|
|
490
|
+
<input id="events-filter" type="text" placeholder="filter events…" autocomplete="off" spellcheck="false" />
|
|
491
|
+
<div class="count"><span id="events-count">0</span></div>
|
|
492
|
+
</div>
|
|
493
|
+
<div class="events-list" id="events-list"></div>
|
|
494
|
+
</div>
|
|
495
|
+
`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
renderStyles() {
|
|
499
|
+
return `
|
|
500
|
+
/* Slice Instruments — events console. All selectors scoped to the
|
|
501
|
+
<slice-eventmanager-debugger> tag so nothing clashes with app styles.
|
|
502
|
+
Every --si-* token reads the matching framework theme variable from
|
|
503
|
+
:root, falling back to the original hardcoded value if absent. */
|
|
504
|
+
slice-eventmanager-debugger {
|
|
505
|
+
--si-accent: var(--primary-color, #6ee7ff);
|
|
506
|
+
--si-accent-rgb: var(--primary-color-rgb, 110, 231, 255);
|
|
507
|
+
--si-surface: var(--primary-background-color, rgba(17, 19, 28, 0.86));
|
|
508
|
+
--si-raised: var(--secondary-background-color, rgba(255, 255, 255, 0.035));
|
|
509
|
+
--si-raised-2: var(--tertiary-background-color, rgba(255, 255, 255, 0.06));
|
|
510
|
+
--si-border: var(--medium-color, rgba(255, 255, 255, 0.09));
|
|
511
|
+
--si-text: var(--font-primary-color, #e8eaf2);
|
|
512
|
+
--si-dim: var(--font-secondary-color, #888fa6);
|
|
513
|
+
--si-danger: var(--danger-color, #ff6b6b);
|
|
514
|
+
--si-success: var(--success-color, #46d39a);
|
|
515
|
+
--si-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Code', Menlo, Consolas, monospace;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
slice-eventmanager-debugger #events-debugger {
|
|
519
|
+
position: fixed;
|
|
520
|
+
bottom: 20px;
|
|
521
|
+
right: 20px;
|
|
522
|
+
width: min(380px, calc(100vw - 40px));
|
|
523
|
+
max-height: 64vh;
|
|
524
|
+
background: var(--si-surface);
|
|
525
|
+
border: 1px solid var(--si-border);
|
|
526
|
+
border-radius: 14px;
|
|
527
|
+
box-shadow:
|
|
528
|
+
0 24px 60px -12px rgba(0, 0, 0, 0.55),
|
|
529
|
+
0 0 0 1px rgba(0, 0, 0, 0.2),
|
|
530
|
+
0 0 38px -18px rgba(var(--si-accent-rgb), 0.55);
|
|
531
|
+
-webkit-backdrop-filter: blur(22px) saturate(1.3);
|
|
532
|
+
backdrop-filter: blur(22px) saturate(1.3);
|
|
533
|
+
display: none;
|
|
534
|
+
flex-direction: column;
|
|
535
|
+
z-index: 10001;
|
|
536
|
+
overflow: hidden;
|
|
537
|
+
color: var(--si-text);
|
|
538
|
+
font-family: var(--si-mono);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
slice-eventmanager-debugger #events-debugger.active {
|
|
542
|
+
display: flex;
|
|
543
|
+
animation: si-events-in 0.26s cubic-bezier(0.16, 1, 0.3, 1);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
@keyframes si-events-in {
|
|
547
|
+
from { opacity: 0; transform: translateY(10px) scale(0.985); }
|
|
548
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
slice-eventmanager-debugger #events-debugger * { box-sizing: border-box; }
|
|
552
|
+
|
|
553
|
+
slice-eventmanager-debugger #events-debugger::before {
|
|
554
|
+
content: '';
|
|
555
|
+
position: absolute;
|
|
556
|
+
left: 0; top: 0; bottom: 0;
|
|
557
|
+
width: 2px;
|
|
558
|
+
background: linear-gradient(180deg, var(--si-accent), transparent 70%);
|
|
559
|
+
opacity: 0.85;
|
|
560
|
+
pointer-events: none;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
slice-eventmanager-debugger .events-header {
|
|
564
|
+
display: flex;
|
|
565
|
+
justify-content: space-between;
|
|
566
|
+
align-items: center;
|
|
567
|
+
padding: 12px 14px;
|
|
568
|
+
background:
|
|
569
|
+
radial-gradient(120% 140% at 0% 0%, rgba(var(--si-accent-rgb), 0.10), transparent 60%),
|
|
570
|
+
var(--si-raised);
|
|
571
|
+
border-bottom: 1px solid var(--si-border);
|
|
572
|
+
user-select: none;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
slice-eventmanager-debugger .brand { display: flex; align-items: center; gap: 9px; }
|
|
576
|
+
|
|
577
|
+
slice-eventmanager-debugger .status-dot {
|
|
578
|
+
width: 7px; height: 7px;
|
|
579
|
+
border-radius: 50%;
|
|
580
|
+
background: var(--si-accent);
|
|
581
|
+
animation: si-pulse-ev 2.4s ease-out infinite;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
@keyframes si-pulse-ev {
|
|
585
|
+
0% { box-shadow: 0 0 0 0 rgba(var(--si-accent-rgb), 0.55); }
|
|
586
|
+
70% { box-shadow: 0 0 0 7px rgba(var(--si-accent-rgb), 0); }
|
|
587
|
+
100% { box-shadow: 0 0 0 0 rgba(var(--si-accent-rgb), 0); }
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
slice-eventmanager-debugger .glyph { color: var(--si-accent); font-size: 12px; opacity: 0.9; }
|
|
591
|
+
|
|
592
|
+
slice-eventmanager-debugger .title {
|
|
593
|
+
font-weight: 600;
|
|
594
|
+
font-size: 11px;
|
|
595
|
+
letter-spacing: 0.18em;
|
|
596
|
+
color: var(--si-text);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
slice-eventmanager-debugger .actions { display: flex; gap: 6px; }
|
|
600
|
+
|
|
601
|
+
slice-eventmanager-debugger .btn {
|
|
602
|
+
width: 26px; height: 26px;
|
|
603
|
+
display: flex; align-items: center; justify-content: center;
|
|
604
|
+
border-radius: 7px;
|
|
605
|
+
border: 1px solid var(--si-border);
|
|
606
|
+
background: var(--si-raised);
|
|
607
|
+
color: var(--si-dim);
|
|
608
|
+
cursor: pointer;
|
|
609
|
+
font-size: 13px;
|
|
610
|
+
line-height: 1;
|
|
611
|
+
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
|
|
612
|
+
}
|
|
613
|
+
slice-eventmanager-debugger .btn:hover {
|
|
614
|
+
color: var(--si-text);
|
|
615
|
+
background: var(--si-raised-2);
|
|
616
|
+
border-color: rgba(var(--si-accent-rgb), 0.5);
|
|
617
|
+
}
|
|
618
|
+
slice-eventmanager-debugger .btn:active { transform: scale(0.92); }
|
|
619
|
+
slice-eventmanager-debugger #events-refresh:hover { color: var(--si-accent); }
|
|
620
|
+
|
|
621
|
+
slice-eventmanager-debugger .events-tabs {
|
|
622
|
+
display: flex;
|
|
623
|
+
gap: 0;
|
|
624
|
+
padding: 0 12px;
|
|
625
|
+
border-bottom: 1px solid var(--si-border);
|
|
626
|
+
background: var(--si-raised);
|
|
627
|
+
}
|
|
628
|
+
slice-eventmanager-debugger .tab-btn {
|
|
629
|
+
all: unset;
|
|
630
|
+
cursor: pointer;
|
|
631
|
+
font-size: 10px;
|
|
632
|
+
font-weight: 600;
|
|
633
|
+
letter-spacing: 0.06em;
|
|
634
|
+
text-transform: uppercase;
|
|
635
|
+
padding: 7px 12px;
|
|
636
|
+
color: var(--si-dim);
|
|
637
|
+
border-bottom: 2px solid transparent;
|
|
638
|
+
transition: color 0.15s ease, border-color 0.15s ease;
|
|
639
|
+
}
|
|
640
|
+
slice-eventmanager-debugger .tab-btn:hover { color: var(--si-text); }
|
|
641
|
+
slice-eventmanager-debugger .tab-btn.active {
|
|
642
|
+
color: var(--si-accent);
|
|
643
|
+
border-bottom-color: var(--si-accent);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
slice-eventmanager-debugger .events-toolbar {
|
|
647
|
+
display: flex;
|
|
648
|
+
gap: 10px;
|
|
649
|
+
align-items: center;
|
|
650
|
+
padding: 10px 12px;
|
|
651
|
+
border-bottom: 1px solid var(--si-border);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
slice-eventmanager-debugger .events-toolbar input {
|
|
655
|
+
flex: 1;
|
|
656
|
+
min-width: 0;
|
|
657
|
+
padding: 7px 10px 7px 30px;
|
|
658
|
+
border-radius: 8px;
|
|
659
|
+
border: 1px solid var(--si-border);
|
|
660
|
+
background:
|
|
661
|
+
var(--si-raised) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='13' viewBox='0 0 24 24' fill='none' stroke='%23888fa6' stroke-width='2.2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E") no-repeat 10px center;
|
|
662
|
+
color: var(--si-text);
|
|
663
|
+
font-family: var(--si-mono);
|
|
664
|
+
font-size: 12px;
|
|
665
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
666
|
+
}
|
|
667
|
+
slice-eventmanager-debugger .events-toolbar input::placeholder { color: var(--si-dim); }
|
|
668
|
+
slice-eventmanager-debugger .events-toolbar input:focus {
|
|
669
|
+
outline: none;
|
|
670
|
+
border-color: rgba(var(--si-accent-rgb), 0.6);
|
|
671
|
+
box-shadow: 0 0 0 3px rgba(var(--si-accent-rgb), 0.12);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
slice-eventmanager-debugger .events-toolbar .count { font-size: 11px; color: var(--si-dim); min-width: 22px; text-align: center; }
|
|
675
|
+
slice-eventmanager-debugger .events-toolbar .count span { color: var(--si-accent); font-weight: 600; }
|
|
676
|
+
|
|
677
|
+
slice-eventmanager-debugger .events-list {
|
|
678
|
+
padding: 10px 12px 12px;
|
|
679
|
+
overflow: auto;
|
|
680
|
+
display: flex;
|
|
681
|
+
flex-direction: column;
|
|
682
|
+
gap: 7px;
|
|
683
|
+
}
|
|
684
|
+
slice-eventmanager-debugger .events-list::-webkit-scrollbar { width: 8px; }
|
|
685
|
+
slice-eventmanager-debugger .events-list::-webkit-scrollbar-thumb {
|
|
686
|
+
background: var(--si-raised-2);
|
|
687
|
+
border-radius: 8px;
|
|
688
|
+
border: 2px solid transparent;
|
|
689
|
+
background-clip: padding-box;
|
|
690
|
+
}
|
|
691
|
+
slice-eventmanager-debugger .events-list::-webkit-scrollbar-thumb:hover { background: rgba(var(--si-accent-rgb), 0.4); background-clip: padding-box; }
|
|
692
|
+
|
|
693
|
+
slice-eventmanager-debugger .event-row {
|
|
694
|
+
display: block;
|
|
695
|
+
padding: 9px 11px;
|
|
696
|
+
background: var(--si-raised);
|
|
697
|
+
border-radius: 9px;
|
|
698
|
+
border: 1px solid var(--si-border);
|
|
699
|
+
border-left: 2px solid transparent;
|
|
700
|
+
transition: border-color 0.18s ease, background 0.18s ease;
|
|
701
|
+
}
|
|
702
|
+
slice-eventmanager-debugger .event-row:hover { background: var(--si-raised-2); border-left-color: var(--si-accent); }
|
|
703
|
+
|
|
704
|
+
slice-eventmanager-debugger .event-row summary {
|
|
705
|
+
display: flex;
|
|
706
|
+
align-items: center;
|
|
707
|
+
justify-content: space-between;
|
|
708
|
+
gap: 10px;
|
|
709
|
+
cursor: pointer;
|
|
710
|
+
list-style: none;
|
|
711
|
+
}
|
|
712
|
+
slice-eventmanager-debugger .event-row summary::-webkit-details-marker { display: none; }
|
|
713
|
+
slice-eventmanager-debugger .event-row summary::after {
|
|
714
|
+
content: '›';
|
|
715
|
+
margin-left: auto;
|
|
716
|
+
color: var(--si-dim);
|
|
717
|
+
font-size: 15px;
|
|
718
|
+
line-height: 1;
|
|
719
|
+
transition: transform 0.2s ease, color 0.2s ease;
|
|
720
|
+
}
|
|
721
|
+
slice-eventmanager-debugger .event-row[open] summary::after { transform: rotate(90deg); color: var(--si-accent); }
|
|
722
|
+
|
|
723
|
+
slice-eventmanager-debugger .event-name {
|
|
724
|
+
font-family: var(--si-mono);
|
|
725
|
+
font-size: 12px;
|
|
726
|
+
color: var(--si-text);
|
|
727
|
+
overflow: hidden;
|
|
728
|
+
text-overflow: ellipsis;
|
|
729
|
+
white-space: nowrap;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
slice-eventmanager-debugger .event-metrics {
|
|
733
|
+
display: flex;
|
|
734
|
+
align-items: center;
|
|
735
|
+
gap: 6px;
|
|
736
|
+
}
|
|
737
|
+
slice-eventmanager-debugger .emit-count {
|
|
738
|
+
font-weight: 600;
|
|
739
|
+
font-size: 10px;
|
|
740
|
+
color: var(--si-success);
|
|
741
|
+
background: rgba(70, 211, 154, 0.12);
|
|
742
|
+
border: 1px solid rgba(70, 211, 154, 0.25);
|
|
743
|
+
padding: 1px 6px;
|
|
744
|
+
border-radius: 999px;
|
|
745
|
+
white-space: nowrap;
|
|
746
|
+
}
|
|
747
|
+
slice-eventmanager-debugger .event-count {
|
|
748
|
+
font-weight: 600;
|
|
749
|
+
font-size: 11px;
|
|
750
|
+
color: var(--si-accent);
|
|
751
|
+
background: rgba(var(--si-accent-rgb), 0.12);
|
|
752
|
+
border: 1px solid rgba(var(--si-accent-rgb), 0.25);
|
|
753
|
+
padding: 1px 8px;
|
|
754
|
+
border-radius: 999px;
|
|
755
|
+
min-width: 22px;
|
|
756
|
+
text-align: center;
|
|
757
|
+
}
|
|
758
|
+
slice-eventmanager-debugger .history-row {
|
|
759
|
+
display: flex;
|
|
760
|
+
justify-content: space-between;
|
|
761
|
+
align-items: center;
|
|
762
|
+
gap: 10px;
|
|
763
|
+
padding: 6px 11px;
|
|
764
|
+
background: var(--si-raised);
|
|
765
|
+
border-radius: 7px;
|
|
766
|
+
border: 1px solid var(--si-border);
|
|
767
|
+
border-left: 2px solid transparent;
|
|
768
|
+
transition: border-color 0.15s ease;
|
|
769
|
+
}
|
|
770
|
+
slice-eventmanager-debugger .history-row:hover { border-left-color: var(--si-accent); }
|
|
771
|
+
slice-eventmanager-debugger .history-event {
|
|
772
|
+
font-family: var(--si-mono);
|
|
773
|
+
font-size: 11.5px;
|
|
774
|
+
color: var(--si-text);
|
|
775
|
+
overflow: hidden;
|
|
776
|
+
text-overflow: ellipsis;
|
|
777
|
+
white-space: nowrap;
|
|
778
|
+
}
|
|
779
|
+
slice-eventmanager-debugger .history-time {
|
|
780
|
+
font-size: 10px;
|
|
781
|
+
color: var(--si-dim);
|
|
782
|
+
white-space: nowrap;
|
|
783
|
+
flex-shrink: 0;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
slice-eventmanager-debugger .subscriber-list {
|
|
787
|
+
margin-top: 9px;
|
|
788
|
+
padding-top: 9px;
|
|
789
|
+
border-top: 1px dashed var(--si-border);
|
|
790
|
+
display: flex;
|
|
791
|
+
flex-direction: column;
|
|
792
|
+
gap: 6px;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
slice-eventmanager-debugger .subscriber-row {
|
|
796
|
+
display: flex;
|
|
797
|
+
justify-content: space-between;
|
|
798
|
+
align-items: center;
|
|
799
|
+
gap: 10px;
|
|
800
|
+
padding: 6px 9px;
|
|
801
|
+
border-radius: 7px;
|
|
802
|
+
background: rgba(0, 0, 0, 0.22);
|
|
803
|
+
border: 1px solid var(--si-border);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
slice-eventmanager-debugger .subscriber-name {
|
|
807
|
+
font-size: 11.5px;
|
|
808
|
+
color: var(--si-text);
|
|
809
|
+
overflow: hidden;
|
|
810
|
+
text-overflow: ellipsis;
|
|
811
|
+
white-space: nowrap;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
slice-eventmanager-debugger .subscriber-meta {
|
|
815
|
+
font-size: 10.5px;
|
|
816
|
+
color: var(--si-dim);
|
|
817
|
+
display: flex;
|
|
818
|
+
align-items: center;
|
|
819
|
+
gap: 6px;
|
|
820
|
+
white-space: nowrap;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
slice-eventmanager-debugger .badge {
|
|
824
|
+
padding: 1px 6px;
|
|
825
|
+
border-radius: 999px;
|
|
826
|
+
background: rgba(var(--si-accent-rgb), 0.16);
|
|
827
|
+
color: var(--si-accent);
|
|
828
|
+
border: 1px solid rgba(var(--si-accent-rgb), 0.3);
|
|
829
|
+
font-size: 9px;
|
|
830
|
+
letter-spacing: 0.06em;
|
|
831
|
+
text-transform: uppercase;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
slice-eventmanager-debugger .empty {
|
|
835
|
+
color: var(--si-dim);
|
|
836
|
+
font-size: 11px;
|
|
837
|
+
letter-spacing: 0.04em;
|
|
838
|
+
text-align: center;
|
|
839
|
+
padding: 22px 0;
|
|
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
|
+
}
|
|
976
|
+
|
|
977
|
+
@media (prefers-reduced-motion: reduce) {
|
|
978
|
+
slice-eventmanager-debugger #events-debugger.active { animation: none; }
|
|
979
|
+
slice-eventmanager-debugger .status-dot { animation: none; }
|
|
980
|
+
}
|
|
981
|
+
`;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
customElements.define('slice-eventmanager-debugger', EventManagerDebugger);
|