sygnal 6.5.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +156 -0
- package/README.md +2 -0
- package/dist/astro/index.cjs.js +423 -27
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +423 -27
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs.map +1 -1
- package/dist/devtools.cjs.js +141 -3
- package/dist/devtools.cjs.js.map +1 -1
- package/dist/devtools.esm.js +141 -4
- package/dist/devtools.esm.js.map +1 -1
- package/dist/diagnostics.cjs.js +169 -14
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +168 -15
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/guide/accessibility.md +2 -0
- package/dist/guide/ai-chat.md +6 -6
- package/dist/guide/behaviors.md +1 -1
- package/dist/guide/element-commands.md +4 -2
- package/dist/guide/forms-reference.md +89 -3
- package/dist/guide/forms.md +8 -8
- package/dist/guide/http.md +77 -7
- package/dist/guide/migrating-to-7.md +314 -0
- package/dist/guide/recipes/ai-summarize.md +2 -1
- package/dist/guide/recipes/i18n.md +1 -1
- package/dist/guide/ui/tabs.md +5 -4
- package/dist/guide/ui/tooltip.md +24 -0
- package/dist/index.cjs.js +781 -320
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +319 -43
- package/dist/index.esm.js +781 -320
- package/dist/index.esm.js.map +1 -1
- package/dist/sygnal.min.js +1 -1
- package/dist/sygnal.min.js.map +1 -1
- package/dist/ui.cjs.js +96 -11
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.esm.js +96 -12
- package/dist/ui.esm.js.map +1 -1
- package/dist/vike/config/package.json +1 -1
- package/dist/vike/onRenderClient.cjs.js +63 -43
- package/dist/vike/onRenderClient.cjs.js.map +1 -1
- package/dist/vike/onRenderClient.mjs +63 -43
- package/dist/vike/onRenderClient.mjs.map +1 -1
- package/dist/vike/onRenderHtml.cjs.js +21 -25
- package/dist/vike/onRenderHtml.cjs.js.map +1 -1
- package/dist/vike/onRenderHtml.mjs +21 -25
- package/dist/vike/onRenderHtml.mjs.map +1 -1
- package/dist/vite/plugin.cjs.js +410 -27
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +410 -27
- package/dist/vite/plugin.mjs.map +1 -1
- package/llms.txt +17 -16
- package/package.json +1 -1
- package/src/astro/index.d.ts +8 -0
- package/src/astro/index.ts +18 -0
- package/src/core/actions.ts +28 -21
- package/src/core/define.ts +2 -0
- package/src/core/hooks.ts +8 -2
- package/src/core/hosts/collection.ts +4 -3
- package/src/core/instance.ts +60 -16
- package/src/core/runtime.ts +5 -1
- package/src/core/view.ts +2 -0
- package/src/cycle/dom/utils.ts +4 -2
- package/src/devtools.d.ts +13 -0
- package/src/devtools.ts +3 -0
- package/src/extra/ai/chat/behavior.ts +3 -3
- package/src/extra/ai/commandBar.ts +3 -3
- package/src/extra/ai/compose/behavior.ts +3 -3
- package/src/extra/ai/transports/shared.ts +8 -2
- package/src/extra/behaviors.ts +21 -0
- package/src/extra/devGraph.ts +111 -0
- package/src/extra/devMcp.ts +1 -1
- package/src/extra/devtools.ts +18 -0
- package/src/extra/diagnostics/checks/fetch.ts +21 -1
- package/src/extra/diagnostics/checks/forms.ts +4 -3
- package/src/extra/diagnostics/checks/index.ts +2 -1
- package/src/extra/diagnostics/checks/inspect.ts +68 -6
- package/src/extra/diagnostics/checks/next.ts +3 -0
- package/src/extra/diagnostics/checks/public.d.ts +133 -12
- package/src/extra/diagnostics/checks/timingAudit.ts +47 -0
- package/src/extra/diagnostics/codes.ts +19 -0
- package/src/extra/fetchDriver.ts +112 -7
- package/src/extra/fetchEventSource.ts +113 -0
- package/src/extra/form.ts +68 -26
- package/src/extra/formHelpers.ts +3 -1
- package/src/extra/lens.ts +3 -2
- package/src/extra/queryCache.ts +4 -3
- package/src/extra/socketDriver.ts +10 -4
- package/src/extra/testing.ts +68 -11
- package/src/index.d.ts +199 -30
- package/src/index.ts +1 -0
- package/src/shared.ts +3 -0
- package/src/ui/dialog.ts +3 -2
- package/src/ui/tooltips.ts +92 -0
- package/src/ui.d.ts +35 -0
- package/src/ui.ts +1 -0
- package/src/vike/onRenderClient.ts +44 -46
- package/src/vike/onRenderHtml.ts +5 -26
- package/src/vike/shell.ts +33 -0
- package/src/vite/graph.ts +255 -0
- package/src/vite/mcp.ts +3 -3
- package/src/vite/plugin.d.ts +19 -0
- package/src/vite/plugin.ts +144 -25
package/dist/diagnostics.esm.js
CHANGED
|
@@ -123,6 +123,9 @@ const CODE_TITLES = {
|
|
|
123
123
|
SYG127: 'Behavior collision or unresolvable uses entry',
|
|
124
124
|
SYG128: 'Duplicate control key',
|
|
125
125
|
SYG129: "CHILD.select() of a component this one doesn't render",
|
|
126
|
+
// PLAN-10 X-1 (D512): graph-wide checks, static only (sygnal-check)
|
|
127
|
+
SYG170: 'Context field read with no provider',
|
|
128
|
+
SYG171: 'PARENT sink no parent reads',
|
|
126
129
|
SYG112: 'Reply action has no model entry',
|
|
127
130
|
SYG130: 'href() names no route or leaves out a param',
|
|
128
131
|
SYG131: 'Route params the pattern does not use',
|
|
@@ -278,6 +281,8 @@ const CODE_TITLES = {
|
|
|
278
281
|
SYG634: 'latest: true with a computed key',
|
|
279
282
|
// PLAN-3 5-5 (D88)
|
|
280
283
|
SYG635: 'Caching asked for without a queryCache',
|
|
284
|
+
// PLAN-11 U-1: progress requests (dev entry, fetch.ts)
|
|
285
|
+
SYG636: "progress that can't be reported as asked",
|
|
281
286
|
// PLAN-4 GS-2: element commands (reported by the dev entry, checks/elementCommands.ts)
|
|
282
287
|
SYG640: 'Element command target not found',
|
|
283
288
|
SYG641: 'Unknown element command',
|
|
@@ -337,6 +342,9 @@ const CODE_TITLES = {
|
|
|
337
342
|
// PLAN-9 X-2: static only (sygnal-check rules/syg253-catalog.js)
|
|
338
343
|
SYG253: 'Misspelled catalog declaration key',
|
|
339
344
|
SYG254: 'Catalog descriptions or options overlap',
|
|
345
|
+
SYG256: 'Output guard rejects the updated state',
|
|
346
|
+
SYG257: 'Output reads a field its action changed (7.0 timing audit)',
|
|
347
|
+
SYG454: 'State binding changed without a new component identity',
|
|
340
348
|
// PLAN-9 H: chat client tools and ask (src/extra/ai/chat/behavior.ts, compose/ask.ts, compose/render.ts)
|
|
341
349
|
SYG160: 'Result part rendered outside a card',
|
|
342
350
|
SYG684: 'Client tool shadows an agent tool',
|
|
@@ -396,6 +404,9 @@ const DEV_CODE_SEVERITY = {
|
|
|
396
404
|
SYG128: 'error',
|
|
397
405
|
// PLAN-4 4-G2: static only (sygnal-check); listed so the entry knows its title
|
|
398
406
|
SYG129: 'warn',
|
|
407
|
+
// PLAN-10 X-1: static only (sygnal-check)
|
|
408
|
+
SYG170: 'warn',
|
|
409
|
+
SYG171: 'warn',
|
|
399
410
|
SYG221: 'error',
|
|
400
411
|
// PLAN-4 GS-4 (state.ts)
|
|
401
412
|
SYG222: 'warn',
|
|
@@ -444,6 +455,8 @@ const DEV_CODE_SEVERITY = {
|
|
|
444
455
|
SYG634: 'info',
|
|
445
456
|
// PLAN-3 5-5 (fetch.ts)
|
|
446
457
|
SYG635: 'warn',
|
|
458
|
+
// PLAN-11 U-1 (fetch.ts)
|
|
459
|
+
SYG636: 'warn',
|
|
447
460
|
// PLAN-4 GS-2 (checks/elementCommands.ts, through the core bridge; renderComponent too)
|
|
448
461
|
SYG640: 'warn',
|
|
449
462
|
SYG641: 'error',
|
|
@@ -560,6 +573,12 @@ const DEV_CODE_SEVERITY = {
|
|
|
560
573
|
// PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
|
|
561
574
|
SYG253: 'warn',
|
|
562
575
|
SYG254: 'info',
|
|
576
|
+
// PLAN-12: static only; proven after-state output guard contradiction.
|
|
577
|
+
SYG256: 'warn',
|
|
578
|
+
// 7.0: the opt-in timing audit (checks/next.ts; configureTimingAudit)
|
|
579
|
+
SYG257: 'info',
|
|
580
|
+
// PLAN-12: static only for legacy (6.x) binding contracts. Sygnal 7 remounts known targets.
|
|
581
|
+
SYG454: 'warn',
|
|
563
582
|
};
|
|
564
583
|
function getCodeInfo(code) {
|
|
565
584
|
const severity = CODE_SEVERITY[code];
|
|
@@ -1247,7 +1266,7 @@ onReset(() => {
|
|
|
1247
1266
|
});
|
|
1248
1267
|
/** the control that rendered a vnode (only while the dev entry is installed) */
|
|
1249
1268
|
const controlOfVnode = (vnode) => vnode && typeof vnode == 'object' ? vnodeControls.get(vnode) : undefined;
|
|
1250
|
-
const STATICS$
|
|
1269
|
+
const STATICS$2 = ['intent', 'model', 'initialState'];
|
|
1251
1270
|
const describe = (v) => v === undefined || v === null ? String(v)
|
|
1252
1271
|
: Array.isArray(v) ? `an array of ${v.length}`
|
|
1253
1272
|
: typeof v != 'object' ? `a ${typeof v} (${JSON.stringify(v)})`
|
|
@@ -1261,7 +1280,7 @@ function onControl(control, vnode) {
|
|
|
1261
1280
|
vnodeControls.set(vnode, control);
|
|
1262
1281
|
if (!staticsSeen.has(control)) {
|
|
1263
1282
|
staticsSeen.add(control);
|
|
1264
|
-
const given = STATICS$
|
|
1283
|
+
const given = STATICS$2.filter(s => control[s] != null);
|
|
1265
1284
|
if (given.length) {
|
|
1266
1285
|
devReport('SYG125', {
|
|
1267
1286
|
message: `The control ${key} has ${given.map(s => '.' + s).join(', ')}, but controls are elements, not components: ${given.length > 1 ? 'these statics are' : 'it is'} never used`,
|
|
@@ -2185,14 +2204,15 @@ const formsCheck = {
|
|
|
2185
2204
|
});
|
|
2186
2205
|
const model = Object.keys(view.model || {}).map(a => a.split('|')[0].trim()), own = ownActions(b);
|
|
2187
2206
|
const clash = own.includes(o.submit);
|
|
2188
|
-
|
|
2207
|
+
// PLAN-11 F-3: with `request`, the form sends the submit itself and `submit` is optional
|
|
2208
|
+
if (o.request ? o.submit !== undefined && (typeof o.submit != 'string' || !model.includes(o.submit) || clash) : typeof o.submit != 'string' || !model.includes(o.submit) || clash) {
|
|
2189
2209
|
devReport('SYG234', {
|
|
2190
2210
|
component,
|
|
2191
|
-
message: typeof o.submit != 'string' ? `${name}'s form '${key}' has no submit action, so a valid submit dispatches nothing`
|
|
2211
|
+
message: typeof o.submit != 'string' ? `${name}'s form '${key}' has no submit action and no request, so a valid submit dispatches nothing`
|
|
2192
2212
|
: clash ? `${name}'s form '${key}' names submit: '${o.submit}', one of the form's own actions; a valid submit dispatches the form's '${key}.${o.submit}'${model.includes(o.submit) ? `, never ${name}'s '${o.submit}' entry` : ''}`
|
|
2193
2213
|
: `${name}'s form '${key}' dispatches '${o.submit}' on a valid submit, but ${name} has no model entry '${o.submit}'`,
|
|
2194
2214
|
fix: clash ? SUBMIT_FIX(key, o.submit, own)
|
|
2195
|
-
: `
|
|
2215
|
+
: `Give the form a request, form(schema, { values, request: { url: '/api/signup' } }) (it sends the valid submit itself), or name a host action and add it to the model: submit: 'SIGN_UP', model: { SIGN_UP: { HTTP: (state, values) => ({ url, json: values, ok: '${key}.DONE', error: '${key}.ERRORS' }) } }`,
|
|
2196
2216
|
data: { key, submit: o.submit },
|
|
2197
2217
|
});
|
|
2198
2218
|
}
|
|
@@ -2617,6 +2637,13 @@ const routerCheck = {
|
|
|
2617
2637
|
const isStandardSchema = (schema) => !!schema && (typeof schema == 'object' || typeof schema == 'function') && typeof schema['~standard']?.validate == 'function';
|
|
2618
2638
|
|
|
2619
2639
|
const IDEMPOTENT = /^(GET|HEAD)$/i;
|
|
2640
|
+
// PLAN-11 U-1: fetch() init options a progress request (XMLHttpRequest) can't honour
|
|
2641
|
+
const XHR_IGNORES = ['keepalive', 'priority', 'cache', 'redirect', 'integrity', 'mode', 'referrer', 'referrerPolicy', 'window', 'duplex'];
|
|
2642
|
+
const reportProgress = (component, sink, url, why, fix) => {
|
|
2643
|
+
if (!once(`SYG636:${nameOf(component)}:${url}:${why}`))
|
|
2644
|
+
return;
|
|
2645
|
+
devReport('SYG636', { component, message: `${sink} ${url ? url + ' ' : ''}has progress, but ${why}`, fix, data: { sink, url } });
|
|
2646
|
+
};
|
|
2620
2647
|
/** instance → reply key lanes it has sent, and ok name → the other key it was sent under */
|
|
2621
2648
|
const lanes = new WeakMap();
|
|
2622
2649
|
const fetchSink = (component, name) => component?.sources?.[name]?.__sygnalStatic === 'resources';
|
|
@@ -2640,7 +2667,7 @@ function checkOne(component, sink, req, where) {
|
|
|
2640
2667
|
return;
|
|
2641
2668
|
const name = nameOf(component);
|
|
2642
2669
|
const url = typeof req.url === 'string' ? req.url : '';
|
|
2643
|
-
const method = String(req.method || (req.json !== undefined || req.body !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
2670
|
+
const method = String(req.method || (req.json !== undefined || req.body !== undefined || req.multipart !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
2644
2671
|
if ((req.cache === true || req.staleTime != null) && !IDEMPOTENT.test(method) && once(`SYG630:${name}:${method}:${url}`)) {
|
|
2645
2672
|
devReport('SYG630', {
|
|
2646
2673
|
component,
|
|
@@ -2651,6 +2678,17 @@ function checkOne(component, sink, req, where) {
|
|
|
2651
2678
|
}
|
|
2652
2679
|
if (req.cache === true || req.staleTime != null)
|
|
2653
2680
|
reportNoCache(component, sink, req.cache === true ? 'cache: true' : 'staleTime', url);
|
|
2681
|
+
if (req.progress) {
|
|
2682
|
+
if (where.includes('.resources.'))
|
|
2683
|
+
reportProgress(component, sink, url, 'resources report no progress (it is ignored)', 'Send the download as a request with progress and reply actions instead of a resource, or remove progress');
|
|
2684
|
+
else if (req.batch)
|
|
2685
|
+
reportProgress(component, sink, url, 'a batch request reports no progress (it is ignored)', 'Send the parts as separate requests with progress, or remove progress');
|
|
2686
|
+
else {
|
|
2687
|
+
const bad = XHR_IGNORES.filter(k => req.init && req.init[k] !== undefined);
|
|
2688
|
+
if (bad.length)
|
|
2689
|
+
reportProgress(component, sink, url, `it goes over XMLHttpRequest, which ignores init.${bad.join(', init.')}`, `Remove ${bad.join(', ')} from init for this request, or drop progress if the request needs ${bad.length > 1 ? 'them' : 'it'}`);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2654
2692
|
if ('validate' in req && !isStandardSchema(req.validate) && once(`SYG631:${name}:${url}`)) {
|
|
2655
2693
|
devReport('SYG631', {
|
|
2656
2694
|
component,
|
|
@@ -3458,6 +3496,9 @@ const slim = (d) => {
|
|
|
3458
3496
|
out.fix = d.fix;
|
|
3459
3497
|
if (d.docsUrl)
|
|
3460
3498
|
out.docsUrl = d.docsUrl;
|
|
3499
|
+
// PLAN-10 P-1: when it was reported (runtime diagnostics are reported once per process)
|
|
3500
|
+
if (typeof d.timestamp === 'number')
|
|
3501
|
+
out.timestamp = d.timestamp;
|
|
3461
3502
|
if (d.data !== undefined) {
|
|
3462
3503
|
// keep it JSON-safe (data can hold errors, vnodes, ...)
|
|
3463
3504
|
try {
|
|
@@ -3561,12 +3602,61 @@ function childrenOf(r, all) {
|
|
|
3561
3602
|
const key = `${c.name}\u0000${via[c.kind]}`;
|
|
3562
3603
|
const entry = out.get(key);
|
|
3563
3604
|
if (entry)
|
|
3564
|
-
entry.count =
|
|
3605
|
+
entry.count = entry.ids.push(c.id);
|
|
3565
3606
|
else
|
|
3566
|
-
out.set(key, { name: c.name, via: via[c.kind], count: 1 });
|
|
3607
|
+
out.set(key, { name: c.name, via: via[c.kind], count: 1, ids: [c.id] });
|
|
3567
3608
|
}
|
|
3568
3609
|
return [...out.values()];
|
|
3569
3610
|
}
|
|
3611
|
+
/** the statics sygnal-check reads (its STATIC_PROPS): a component function with none is view-only */
|
|
3612
|
+
const STATICS$1 = ['intent', 'model', 'initialState', 'isolatedState', 'context', 'calculated', 'connections', 'resources', 'route', 'head', 'uses', 'timers', 'persist', 'viewTransitions', 'browser', 'agent'];
|
|
3613
|
+
/** PLAN-10 D522: no statics and nothing the core added (renderComponent's test intent, a behavior's model) */
|
|
3614
|
+
function isViewOnly(c) {
|
|
3615
|
+
const def = c && c.__next && c.__next.def;
|
|
3616
|
+
if (!def || def.intent || (def.handlers && def.handlers.size) || def.initialState !== undefined)
|
|
3617
|
+
return false;
|
|
3618
|
+
const fn = def.view;
|
|
3619
|
+
return !!fn && STATICS$1.every(k => fn[k] === undefined);
|
|
3620
|
+
}
|
|
3621
|
+
/** PLAN-10 P-2 (D544): the static key sygnal/vite's dev transform stamped on the component function */
|
|
3622
|
+
function stampedKey(c) {
|
|
3623
|
+
const fn = c && c.__next && c.__next.def && c.__next.def.view;
|
|
3624
|
+
const k = fn && fn.__sygnalKey;
|
|
3625
|
+
return typeof k === 'string' ? k : undefined;
|
|
3626
|
+
}
|
|
3627
|
+
/** PLAN-10 D530: the context keys the instance's latest render read (InstanceView.reads); every key when it read them all */
|
|
3628
|
+
function contextReadsOf(c) {
|
|
3629
|
+
const v = c && c.__next;
|
|
3630
|
+
const reads = v && v.reads;
|
|
3631
|
+
if (!reads)
|
|
3632
|
+
return [];
|
|
3633
|
+
const out = [...reads].filter(k => k !== '\u0000all');
|
|
3634
|
+
if (reads.has('\u0000all')) {
|
|
3635
|
+
try {
|
|
3636
|
+
for (const k of Object.keys(v.context || {}))
|
|
3637
|
+
if (!out.includes(k))
|
|
3638
|
+
out.push(k);
|
|
3639
|
+
}
|
|
3640
|
+
catch (_) { /* no context */ }
|
|
3641
|
+
}
|
|
3642
|
+
return out;
|
|
3643
|
+
}
|
|
3644
|
+
/** PLAN-10 D522: the app (run()) of each instance: the index of its root among the live roots, oldest first */
|
|
3645
|
+
function appsOf(all) {
|
|
3646
|
+
const byId = new Map(all.map(r => [r.id, r]));
|
|
3647
|
+
const roots = all.filter(r => r.parentId === null).sort((a, b) => Number(a.id) - Number(b.id));
|
|
3648
|
+
const index = new Map(roots.map((r, i) => [r, i]));
|
|
3649
|
+
const out = new Map();
|
|
3650
|
+
for (const r of all) {
|
|
3651
|
+
let top = r;
|
|
3652
|
+
for (let n = 0; top && top.parentId !== null && n < 10000; n++)
|
|
3653
|
+
top = byId.get(top.parentId);
|
|
3654
|
+
const i = top && index.get(top);
|
|
3655
|
+
if (i !== undefined)
|
|
3656
|
+
out.set(r, i);
|
|
3657
|
+
}
|
|
3658
|
+
return out;
|
|
3659
|
+
}
|
|
3570
3660
|
/** The app graph of the live component instances (see ./public.d.ts). */
|
|
3571
3661
|
function inspect(options = {}) {
|
|
3572
3662
|
// report SYG105 (info) for EVENTS types selected but never emitted so far
|
|
@@ -3591,21 +3681,28 @@ function inspect(options = {}) {
|
|
|
3591
3681
|
const ids = options.ids && new Set(options.ids.map(String));
|
|
3592
3682
|
const recs = ids ? all.filter(r => ids.has(r.id)) : all;
|
|
3593
3683
|
const names = new Set(recs.map(r => r.name));
|
|
3684
|
+
const apps = appsOf(all);
|
|
3594
3685
|
const components = recs.map(r => {
|
|
3595
3686
|
const c = r.instance;
|
|
3596
3687
|
const calculated = c && c.calculated && typeof c.calculated === 'object' ? Object.keys(c.calculated) : [];
|
|
3597
3688
|
const selectors = selectorsOf(r, options, diags);
|
|
3598
3689
|
const controls = controlsOf(r, selectors);
|
|
3690
|
+
const key = stampedKey(c);
|
|
3599
3691
|
return {
|
|
3600
3692
|
name: r.name,
|
|
3601
3693
|
id: r.id,
|
|
3694
|
+
...(key ? { key } : {}),
|
|
3602
3695
|
parentId: r.parentId,
|
|
3603
3696
|
kind: r.kind,
|
|
3697
|
+
...(isViewOnly(c) ? { viewOnly: true } : {}),
|
|
3698
|
+
...(r.parentId === null ? { entry: true } : {}),
|
|
3699
|
+
...(apps.has(r) ? { app: apps.get(r) } : {}),
|
|
3700
|
+
...(c && c.__next && c.__next.shown === false ? { shown: false } : {}),
|
|
3604
3701
|
actions: actionsOf(r),
|
|
3605
3702
|
stateKeys: stateKeysOf(c, calculated),
|
|
3606
3703
|
calculated,
|
|
3607
3704
|
contextProvides: c && isPlainObject(c.context) ? Object.keys(c.context) : [],
|
|
3608
|
-
contextConsumes:
|
|
3705
|
+
contextConsumes: contextReadsOf(c),
|
|
3609
3706
|
eventsEmitted: [...r.eventsEmitted],
|
|
3610
3707
|
eventsSelected: [...r.eventsSelected],
|
|
3611
3708
|
children: childrenOf(r, all),
|
|
@@ -3617,14 +3714,21 @@ function inspect(options = {}) {
|
|
|
3617
3714
|
});
|
|
3618
3715
|
const cache = cacheOf(recs) || {};
|
|
3619
3716
|
const events = {};
|
|
3620
|
-
|
|
3717
|
+
// names once each (as before); PLAN-10 D522: every instance's id in emitterIds / selectorIds
|
|
3718
|
+
const ev = (type) => events[type] || (events[type] = { emitters: [], selectors: [], emitterIds: [], selectorIds: [] });
|
|
3621
3719
|
const add = (list, name) => { if (!list.includes(name))
|
|
3622
3720
|
list.push(name); };
|
|
3623
3721
|
for (const c of components) {
|
|
3624
|
-
for (const t of c.eventsEmitted)
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3722
|
+
for (const t of c.eventsEmitted) {
|
|
3723
|
+
const e = ev(t);
|
|
3724
|
+
add(e.emitters, c.name);
|
|
3725
|
+
e.emitterIds.push(c.id);
|
|
3726
|
+
}
|
|
3727
|
+
for (const t of c.eventsSelected) {
|
|
3728
|
+
const e = ev(t);
|
|
3729
|
+
add(e.selectors, c.name);
|
|
3730
|
+
e.selectorIds.push(c.id);
|
|
3731
|
+
}
|
|
3628
3732
|
}
|
|
3629
3733
|
// emitted on the bus by something that isn't a listed component (e.g. a driver, a pruned component)
|
|
3630
3734
|
if (!ids)
|
|
@@ -3685,6 +3789,52 @@ function installInspect() {
|
|
|
3685
3789
|
};
|
|
3686
3790
|
}
|
|
3687
3791
|
|
|
3792
|
+
/**
|
|
3793
|
+
* The 7.0 timing audit (SYG257, info): since 7.0 a model entry's STATE runs first and every other
|
|
3794
|
+
* sink gets the state it committed (6.x gave them the state from before the action). For code
|
|
3795
|
+
* written for 6.x, this reports each output sink that reads a top-level state field its action's
|
|
3796
|
+
* STATE changed: it gets the new value now. Fix: read `props.previousState.<field>` where the old
|
|
3797
|
+
* value is meant; a read meant to see the new value needs nothing. Opt-in, because in 7.0-native
|
|
3798
|
+
* code reading the new value is the intent. Turn it on with any of:
|
|
3799
|
+
* - configureTimingAudit(true) (this entry)
|
|
3800
|
+
* - renderComponent(C, { timingAudit: true }) (tests)
|
|
3801
|
+
* - sygnal({ diagnostics: { timingAudit: true } }) (sygnal/vite, dev)
|
|
3802
|
+
* - globalThis.__SYGNAL_TIMING_AUDIT__ = true
|
|
3803
|
+
* The flag lives on the core bridge (shared by every copy of this entry and renderComponent).
|
|
3804
|
+
* Mechanism: the dev hook layer's wrapHandler (checks/next.ts) wraps each non-STATE handler and
|
|
3805
|
+
* gives it the state through a Proxy that notes reads of changed top-level fields. Top-level
|
|
3806
|
+
* fields only (a nested change shows as its top-level field changing); once per component name,
|
|
3807
|
+
* action, sink and field. Nothing in production builds.
|
|
3808
|
+
*/
|
|
3809
|
+
/** Turn the timing audit on (true), off (false), or back to the default (globalThis.__SYGNAL_TIMING_AUDIT__). */
|
|
3810
|
+
function configureTimingAudit(on) {
|
|
3811
|
+
const core = bridge();
|
|
3812
|
+
if (core)
|
|
3813
|
+
core.timingAudit = on;
|
|
3814
|
+
}
|
|
3815
|
+
/** Whether the timing audit is on. */
|
|
3816
|
+
function isTimingAuditEnabled() {
|
|
3817
|
+
const flag = bridge()?.timingAudit;
|
|
3818
|
+
return flag === undefined ? globalThis.__SYGNAL_TIMING_AUDIT__ === true : flag === true;
|
|
3819
|
+
}
|
|
3820
|
+
/** handler `h` of `f`'s action `type`, sink `sink`, audited (f: the checks' instance facade) */
|
|
3821
|
+
const audited = (f, type, sink, h) => (s, d, nx, p, ...r) => {
|
|
3822
|
+
const was = p?.previousState;
|
|
3823
|
+
if (!isTimingAuditEnabled() || !was || was === s || !s || typeof s != 'object' || typeof was != 'object' || Array.isArray(s))
|
|
3824
|
+
return h(s, d, nx, p, ...r);
|
|
3825
|
+
const view = new Proxy(s, { get(t, k, rcv) {
|
|
3826
|
+
if (typeof k == 'string' && k in was && was[k] !== t[k] && once(`SYG257:${f.name}:${type}:${sink}:${k}`))
|
|
3827
|
+
devReport('SYG257', {
|
|
3828
|
+
component: f,
|
|
3829
|
+
message: `${f.name}'s '${type}' ${sink} read state.${k}, which the same action's STATE changed: since 7.0 it gets the new value (6.x gave it the old one)`,
|
|
3830
|
+
fix: `If it meant the value from before the action, read props.previousState.${k} (the 4th argument); if it meant the new value, nothing to change`,
|
|
3831
|
+
data: { action: type, sink, field: k },
|
|
3832
|
+
});
|
|
3833
|
+
return Reflect.get(t, k, rcv);
|
|
3834
|
+
} });
|
|
3835
|
+
return h(view, d, nx, p, ...r);
|
|
3836
|
+
};
|
|
3837
|
+
|
|
3688
3838
|
/**
|
|
3689
3839
|
* PLAN-4.6 R4: the dev entry's checks on the next component core (src/core/).
|
|
3690
3840
|
*
|
|
@@ -4197,6 +4347,9 @@ function nextHooks(_api) {
|
|
|
4197
4347
|
onAction(iv, a) { log.onAction(iv, a); },
|
|
4198
4348
|
wrapHandler(iv, type, sink, fn) {
|
|
4199
4349
|
let h = log.wrapHandler(iv, type, sink, fn) || fn;
|
|
4350
|
+
// 7.0 timing audit (SYG257, opt-in; checks/timingAudit.ts): checked per call, so turning it on later counts
|
|
4351
|
+
if (on() && sink != 'STATE' && typeof h == 'function')
|
|
4352
|
+
h = audited(fac(iv), type, sink, h);
|
|
4200
4353
|
// SYG222: the STATE reducers, wrapped (today the check rewrote the instance's model)
|
|
4201
4354
|
if (on() && sink == 'STATE' && typeof fn == 'function')
|
|
4202
4355
|
h = watchMutation(fac(iv), type, h);
|
|
@@ -4411,5 +4564,5 @@ function resetChecks() {
|
|
|
4411
4564
|
}
|
|
4412
4565
|
installChecks();
|
|
4413
4566
|
|
|
4414
|
-
export { RXJS_HINTS, checkEventBus, checks, configureChecks, configureStrict, getCodeInfo, inspect, installChecks, isStrictEnabled, listCodes, resetChecks };
|
|
4567
|
+
export { RXJS_HINTS, checkEventBus, checks, configureChecks, configureStrict, configureTimingAudit, getCodeInfo, inspect, installChecks, isStrictEnabled, isTimingAuditEnabled, listCodes, resetChecks };
|
|
4415
4568
|
//# sourceMappingURL=diagnostics.esm.js.map
|