slicejs-web-framework 3.4.4 → 3.5.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/Slice/Components/Structural/Controller/Controller.js +135 -0
- package/Slice/Components/Structural/EventManager/EventManager.js +289 -4
- package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +368 -30
- package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -0
- package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +56 -0
- package/Slice/Slice.js +14 -1
- package/Slice/tests/event-emitters.test.js +79 -0
- package/Slice/tests/event-registry.test.js +141 -0
- package/Slice/tests/leak-orphans.test.js +131 -0
- package/api/{middleware → framework}/securityMiddleware.js +18 -74
- package/api/framework/server.js +333 -0
- package/api/index.js +5 -279
- package/api/tests/public-env-resolver.test.js +1 -1
- package/package.json +4 -2
- package/sliceConfig.schema.json +10 -1
- package/src/App/index.html +31 -1
- package/src/manifest.json +15 -0
- package/src/robots.txt +4 -0
- package/src/sitemap.xml +8 -0
- package/src/sliceConfig.json +5 -1
- package/types/index.d.ts +12 -0
- package/slicejs-cli/.github/workflows/ci.yml +0 -43
- package/slicejs-cli/LICENSE +0 -21
- package/slicejs-cli/assets/Slice.js-logo.png +0 -0
- package/slicejs-cli/client.js +0 -740
- package/slicejs-cli/commands/Print.js +0 -164
- package/slicejs-cli/commands/Validations.js +0 -92
- package/slicejs-cli/commands/build/build.js +0 -40
- package/slicejs-cli/commands/buildProduction/buildProduction.js +0 -577
- package/slicejs-cli/commands/bundle/bundle.js +0 -236
- package/slicejs-cli/commands/createComponent/VisualComponentTemplate.js +0 -55
- package/slicejs-cli/commands/createComponent/createComponent.js +0 -128
- package/slicejs-cli/commands/deleteComponent/deleteComponent.js +0 -81
- package/slicejs-cli/commands/doctor/doctor.js +0 -516
- package/slicejs-cli/commands/getComponent/getComponent.js +0 -695
- package/slicejs-cli/commands/init/init.js +0 -473
- package/slicejs-cli/commands/listComponents/listComponents.js +0 -172
- package/slicejs-cli/commands/startServer/startServer.js +0 -240
- package/slicejs-cli/commands/startServer/watchServer.js +0 -66
- package/slicejs-cli/commands/types/types.js +0 -583
- package/slicejs-cli/commands/utils/LocalCliDelegation.js +0 -53
- package/slicejs-cli/commands/utils/PackageManager.js +0 -148
- package/slicejs-cli/commands/utils/PathHelper.js +0 -75
- package/slicejs-cli/commands/utils/VersionChecker.js +0 -169
- package/slicejs-cli/commands/utils/bundling/BundleGenerator.js +0 -2525
- package/slicejs-cli/commands/utils/bundling/DependencyAnalyzer.js +0 -933
- package/slicejs-cli/commands/utils/loadConfig.js +0 -31
- package/slicejs-cli/commands/utils/sliceScripts.js +0 -48
- package/slicejs-cli/commands/utils/updateManager.js +0 -471
- package/slicejs-cli/package.json +0 -71
- package/slicejs-cli/playwright.config.js +0 -51
- package/slicejs-cli/pnpm-lock.yaml +0 -1629
- package/slicejs-cli/tests/build-command-integration.test.js +0 -87
- package/slicejs-cli/tests/build-mangling-e2e.test.js +0 -290
- package/slicejs-cli/tests/build-production-e2e.test.js +0 -140
- package/slicejs-cli/tests/builder-edge-cases.test.js +0 -322
- package/slicejs-cli/tests/bundle-generate-e2e.test.js +0 -115
- package/slicejs-cli/tests/bundle-generator.test.js +0 -691
- package/slicejs-cli/tests/bundle-v2-register-output.test.js +0 -470
- package/slicejs-cli/tests/bundling-dependency-edges.test.js +0 -127
- package/slicejs-cli/tests/bundling-imports-unit.test.js +0 -267
- package/slicejs-cli/tests/client-launcher-contract.test.js +0 -211
- package/slicejs-cli/tests/commands-component-crud.test.js +0 -128
- package/slicejs-cli/tests/commands-doctor.test.js +0 -152
- package/slicejs-cli/tests/commands-version-checker.test.js +0 -37
- package/slicejs-cli/tests/component-registry-parse.test.js +0 -34
- package/slicejs-cli/tests/dependency-analyzer.test.js +0 -24
- package/slicejs-cli/tests/e2e/bundles.spec.js +0 -91
- package/slicejs-cli/tests/e2e/dependency-scenarios.spec.js +0 -56
- package/slicejs-cli/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
- package/slicejs-cli/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
- package/slicejs-cli/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
- package/slicejs-cli/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
- package/slicejs-cli/tests/e2e/fixtures/components/registry.json +0 -12
- package/slicejs-cli/tests/e2e/fixtures/vendor-components.mjs +0 -65
- package/slicejs-cli/tests/e2e/navigation.spec.js +0 -44
- package/slicejs-cli/tests/e2e/render.spec.js +0 -34
- package/slicejs-cli/tests/e2e/serve.mjs +0 -264
- package/slicejs-cli/tests/e2e/shared-deps.spec.js +0 -61
- package/slicejs-cli/tests/e2e/unminified.spec.js +0 -33
- package/slicejs-cli/tests/e2e-serve.test.js +0 -148
- package/slicejs-cli/tests/fixtures/components.js +0 -8
- package/slicejs-cli/tests/fixtures/sliceConfig.json +0 -74
- package/slicejs-cli/tests/getcomponent.test.js +0 -435
- package/slicejs-cli/tests/helpers/setup.js +0 -102
- package/slicejs-cli/tests/init-command-contract.test.js +0 -101
- package/slicejs-cli/tests/init-e2e.test.js +0 -82
- package/slicejs-cli/tests/init-project-isolation.test.js +0 -98
- package/slicejs-cli/tests/local-cli-delegation.test.js +0 -81
- package/slicejs-cli/tests/package-manager.test.js +0 -127
- package/slicejs-cli/tests/path-helper.test.js +0 -206
- package/slicejs-cli/tests/perf-budget.test.js +0 -84
- package/slicejs-cli/tests/postinstall-command.test.js +0 -103
- package/slicejs-cli/tests/property-mangling-regression.test.js +0 -182
- package/slicejs-cli/tests/types-breakage.test.js +0 -491
- package/slicejs-cli/tests/types-generator-errors.test.js +0 -361
- package/slicejs-cli/tests/types-generator.test.js +0 -348
- /package/api/{utils → framework}/publicEnvResolver.js +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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="
|
|
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-
|
|
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; }
|