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.cjs.js
CHANGED
|
@@ -143,6 +143,9 @@ const CODE_TITLES = {
|
|
|
143
143
|
SYG127: 'Behavior collision or unresolvable uses entry',
|
|
144
144
|
SYG128: 'Duplicate control key',
|
|
145
145
|
SYG129: "CHILD.select() of a component this one doesn't render",
|
|
146
|
+
// PLAN-10 X-1 (D512): graph-wide checks, static only (sygnal-check)
|
|
147
|
+
SYG170: 'Context field read with no provider',
|
|
148
|
+
SYG171: 'PARENT sink no parent reads',
|
|
146
149
|
SYG112: 'Reply action has no model entry',
|
|
147
150
|
SYG130: 'href() names no route or leaves out a param',
|
|
148
151
|
SYG131: 'Route params the pattern does not use',
|
|
@@ -298,6 +301,8 @@ const CODE_TITLES = {
|
|
|
298
301
|
SYG634: 'latest: true with a computed key',
|
|
299
302
|
// PLAN-3 5-5 (D88)
|
|
300
303
|
SYG635: 'Caching asked for without a queryCache',
|
|
304
|
+
// PLAN-11 U-1: progress requests (dev entry, fetch.ts)
|
|
305
|
+
SYG636: "progress that can't be reported as asked",
|
|
301
306
|
// PLAN-4 GS-2: element commands (reported by the dev entry, checks/elementCommands.ts)
|
|
302
307
|
SYG640: 'Element command target not found',
|
|
303
308
|
SYG641: 'Unknown element command',
|
|
@@ -357,6 +362,9 @@ const CODE_TITLES = {
|
|
|
357
362
|
// PLAN-9 X-2: static only (sygnal-check rules/syg253-catalog.js)
|
|
358
363
|
SYG253: 'Misspelled catalog declaration key',
|
|
359
364
|
SYG254: 'Catalog descriptions or options overlap',
|
|
365
|
+
SYG256: 'Output guard rejects the updated state',
|
|
366
|
+
SYG257: 'Output reads a field its action changed (7.0 timing audit)',
|
|
367
|
+
SYG454: 'State binding changed without a new component identity',
|
|
360
368
|
// PLAN-9 H: chat client tools and ask (src/extra/ai/chat/behavior.ts, compose/ask.ts, compose/render.ts)
|
|
361
369
|
SYG160: 'Result part rendered outside a card',
|
|
362
370
|
SYG684: 'Client tool shadows an agent tool',
|
|
@@ -416,6 +424,9 @@ const DEV_CODE_SEVERITY = {
|
|
|
416
424
|
SYG128: 'error',
|
|
417
425
|
// PLAN-4 4-G2: static only (sygnal-check); listed so the entry knows its title
|
|
418
426
|
SYG129: 'warn',
|
|
427
|
+
// PLAN-10 X-1: static only (sygnal-check)
|
|
428
|
+
SYG170: 'warn',
|
|
429
|
+
SYG171: 'warn',
|
|
419
430
|
SYG221: 'error',
|
|
420
431
|
// PLAN-4 GS-4 (state.ts)
|
|
421
432
|
SYG222: 'warn',
|
|
@@ -464,6 +475,8 @@ const DEV_CODE_SEVERITY = {
|
|
|
464
475
|
SYG634: 'info',
|
|
465
476
|
// PLAN-3 5-5 (fetch.ts)
|
|
466
477
|
SYG635: 'warn',
|
|
478
|
+
// PLAN-11 U-1 (fetch.ts)
|
|
479
|
+
SYG636: 'warn',
|
|
467
480
|
// PLAN-4 GS-2 (checks/elementCommands.ts, through the core bridge; renderComponent too)
|
|
468
481
|
SYG640: 'warn',
|
|
469
482
|
SYG641: 'error',
|
|
@@ -580,6 +593,12 @@ const DEV_CODE_SEVERITY = {
|
|
|
580
593
|
// PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
|
|
581
594
|
SYG253: 'warn',
|
|
582
595
|
SYG254: 'info',
|
|
596
|
+
// PLAN-12: static only; proven after-state output guard contradiction.
|
|
597
|
+
SYG256: 'warn',
|
|
598
|
+
// 7.0: the opt-in timing audit (checks/next.ts; configureTimingAudit)
|
|
599
|
+
SYG257: 'info',
|
|
600
|
+
// PLAN-12: static only for legacy (6.x) binding contracts. Sygnal 7 remounts known targets.
|
|
601
|
+
SYG454: 'warn',
|
|
583
602
|
};
|
|
584
603
|
function getCodeInfo(code) {
|
|
585
604
|
const severity = CODE_SEVERITY[code];
|
|
@@ -1267,7 +1286,7 @@ onReset(() => {
|
|
|
1267
1286
|
});
|
|
1268
1287
|
/** the control that rendered a vnode (only while the dev entry is installed) */
|
|
1269
1288
|
const controlOfVnode = (vnode) => vnode && typeof vnode == 'object' ? vnodeControls.get(vnode) : undefined;
|
|
1270
|
-
const STATICS$
|
|
1289
|
+
const STATICS$2 = ['intent', 'model', 'initialState'];
|
|
1271
1290
|
const describe = (v) => v === undefined || v === null ? String(v)
|
|
1272
1291
|
: Array.isArray(v) ? `an array of ${v.length}`
|
|
1273
1292
|
: typeof v != 'object' ? `a ${typeof v} (${JSON.stringify(v)})`
|
|
@@ -1281,7 +1300,7 @@ function onControl(control, vnode) {
|
|
|
1281
1300
|
vnodeControls.set(vnode, control);
|
|
1282
1301
|
if (!staticsSeen.has(control)) {
|
|
1283
1302
|
staticsSeen.add(control);
|
|
1284
|
-
const given = STATICS$
|
|
1303
|
+
const given = STATICS$2.filter(s => control[s] != null);
|
|
1285
1304
|
if (given.length) {
|
|
1286
1305
|
devReport('SYG125', {
|
|
1287
1306
|
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`,
|
|
@@ -2205,14 +2224,15 @@ const formsCheck = {
|
|
|
2205
2224
|
});
|
|
2206
2225
|
const model = Object.keys(view.model || {}).map(a => a.split('|')[0].trim()), own = ownActions(b);
|
|
2207
2226
|
const clash = own.includes(o.submit);
|
|
2208
|
-
|
|
2227
|
+
// PLAN-11 F-3: with `request`, the form sends the submit itself and `submit` is optional
|
|
2228
|
+
if (o.request ? o.submit !== undefined && (typeof o.submit != 'string' || !model.includes(o.submit) || clash) : typeof o.submit != 'string' || !model.includes(o.submit) || clash) {
|
|
2209
2229
|
devReport('SYG234', {
|
|
2210
2230
|
component,
|
|
2211
|
-
message: typeof o.submit != 'string' ? `${name}'s form '${key}' has no submit action, so a valid submit dispatches nothing`
|
|
2231
|
+
message: typeof o.submit != 'string' ? `${name}'s form '${key}' has no submit action and no request, so a valid submit dispatches nothing`
|
|
2212
2232
|
: 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` : ''}`
|
|
2213
2233
|
: `${name}'s form '${key}' dispatches '${o.submit}' on a valid submit, but ${name} has no model entry '${o.submit}'`,
|
|
2214
2234
|
fix: clash ? SUBMIT_FIX(key, o.submit, own)
|
|
2215
|
-
: `
|
|
2235
|
+
: `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' }) } }`,
|
|
2216
2236
|
data: { key, submit: o.submit },
|
|
2217
2237
|
});
|
|
2218
2238
|
}
|
|
@@ -2637,6 +2657,13 @@ const routerCheck = {
|
|
|
2637
2657
|
const isStandardSchema = (schema) => !!schema && (typeof schema == 'object' || typeof schema == 'function') && typeof schema['~standard']?.validate == 'function';
|
|
2638
2658
|
|
|
2639
2659
|
const IDEMPOTENT = /^(GET|HEAD)$/i;
|
|
2660
|
+
// PLAN-11 U-1: fetch() init options a progress request (XMLHttpRequest) can't honour
|
|
2661
|
+
const XHR_IGNORES = ['keepalive', 'priority', 'cache', 'redirect', 'integrity', 'mode', 'referrer', 'referrerPolicy', 'window', 'duplex'];
|
|
2662
|
+
const reportProgress = (component, sink, url, why, fix) => {
|
|
2663
|
+
if (!once(`SYG636:${nameOf(component)}:${url}:${why}`))
|
|
2664
|
+
return;
|
|
2665
|
+
devReport('SYG636', { component, message: `${sink} ${url ? url + ' ' : ''}has progress, but ${why}`, fix, data: { sink, url } });
|
|
2666
|
+
};
|
|
2640
2667
|
/** instance → reply key lanes it has sent, and ok name → the other key it was sent under */
|
|
2641
2668
|
const lanes = new WeakMap();
|
|
2642
2669
|
const fetchSink = (component, name) => component?.sources?.[name]?.__sygnalStatic === 'resources';
|
|
@@ -2660,7 +2687,7 @@ function checkOne(component, sink, req, where) {
|
|
|
2660
2687
|
return;
|
|
2661
2688
|
const name = nameOf(component);
|
|
2662
2689
|
const url = typeof req.url === 'string' ? req.url : '';
|
|
2663
|
-
const method = String(req.method || (req.json !== undefined || req.body !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
2690
|
+
const method = String(req.method || (req.json !== undefined || req.body !== undefined || req.multipart !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
2664
2691
|
if ((req.cache === true || req.staleTime != null) && !IDEMPOTENT.test(method) && once(`SYG630:${name}:${method}:${url}`)) {
|
|
2665
2692
|
devReport('SYG630', {
|
|
2666
2693
|
component,
|
|
@@ -2671,6 +2698,17 @@ function checkOne(component, sink, req, where) {
|
|
|
2671
2698
|
}
|
|
2672
2699
|
if (req.cache === true || req.staleTime != null)
|
|
2673
2700
|
reportNoCache(component, sink, req.cache === true ? 'cache: true' : 'staleTime', url);
|
|
2701
|
+
if (req.progress) {
|
|
2702
|
+
if (where.includes('.resources.'))
|
|
2703
|
+
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');
|
|
2704
|
+
else if (req.batch)
|
|
2705
|
+
reportProgress(component, sink, url, 'a batch request reports no progress (it is ignored)', 'Send the parts as separate requests with progress, or remove progress');
|
|
2706
|
+
else {
|
|
2707
|
+
const bad = XHR_IGNORES.filter(k => req.init && req.init[k] !== undefined);
|
|
2708
|
+
if (bad.length)
|
|
2709
|
+
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'}`);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2674
2712
|
if ('validate' in req && !isStandardSchema(req.validate) && once(`SYG631:${name}:${url}`)) {
|
|
2675
2713
|
devReport('SYG631', {
|
|
2676
2714
|
component,
|
|
@@ -3478,6 +3516,9 @@ const slim = (d) => {
|
|
|
3478
3516
|
out.fix = d.fix;
|
|
3479
3517
|
if (d.docsUrl)
|
|
3480
3518
|
out.docsUrl = d.docsUrl;
|
|
3519
|
+
// PLAN-10 P-1: when it was reported (runtime diagnostics are reported once per process)
|
|
3520
|
+
if (typeof d.timestamp === 'number')
|
|
3521
|
+
out.timestamp = d.timestamp;
|
|
3481
3522
|
if (d.data !== undefined) {
|
|
3482
3523
|
// keep it JSON-safe (data can hold errors, vnodes, ...)
|
|
3483
3524
|
try {
|
|
@@ -3581,12 +3622,61 @@ function childrenOf(r, all) {
|
|
|
3581
3622
|
const key = `${c.name}\u0000${via[c.kind]}`;
|
|
3582
3623
|
const entry = out.get(key);
|
|
3583
3624
|
if (entry)
|
|
3584
|
-
entry.count =
|
|
3625
|
+
entry.count = entry.ids.push(c.id);
|
|
3585
3626
|
else
|
|
3586
|
-
out.set(key, { name: c.name, via: via[c.kind], count: 1 });
|
|
3627
|
+
out.set(key, { name: c.name, via: via[c.kind], count: 1, ids: [c.id] });
|
|
3587
3628
|
}
|
|
3588
3629
|
return [...out.values()];
|
|
3589
3630
|
}
|
|
3631
|
+
/** the statics sygnal-check reads (its STATIC_PROPS): a component function with none is view-only */
|
|
3632
|
+
const STATICS$1 = ['intent', 'model', 'initialState', 'isolatedState', 'context', 'calculated', 'connections', 'resources', 'route', 'head', 'uses', 'timers', 'persist', 'viewTransitions', 'browser', 'agent'];
|
|
3633
|
+
/** PLAN-10 D522: no statics and nothing the core added (renderComponent's test intent, a behavior's model) */
|
|
3634
|
+
function isViewOnly(c) {
|
|
3635
|
+
const def = c && c.__next && c.__next.def;
|
|
3636
|
+
if (!def || def.intent || (def.handlers && def.handlers.size) || def.initialState !== undefined)
|
|
3637
|
+
return false;
|
|
3638
|
+
const fn = def.view;
|
|
3639
|
+
return !!fn && STATICS$1.every(k => fn[k] === undefined);
|
|
3640
|
+
}
|
|
3641
|
+
/** PLAN-10 P-2 (D544): the static key sygnal/vite's dev transform stamped on the component function */
|
|
3642
|
+
function stampedKey(c) {
|
|
3643
|
+
const fn = c && c.__next && c.__next.def && c.__next.def.view;
|
|
3644
|
+
const k = fn && fn.__sygnalKey;
|
|
3645
|
+
return typeof k === 'string' ? k : undefined;
|
|
3646
|
+
}
|
|
3647
|
+
/** PLAN-10 D530: the context keys the instance's latest render read (InstanceView.reads); every key when it read them all */
|
|
3648
|
+
function contextReadsOf(c) {
|
|
3649
|
+
const v = c && c.__next;
|
|
3650
|
+
const reads = v && v.reads;
|
|
3651
|
+
if (!reads)
|
|
3652
|
+
return [];
|
|
3653
|
+
const out = [...reads].filter(k => k !== '\u0000all');
|
|
3654
|
+
if (reads.has('\u0000all')) {
|
|
3655
|
+
try {
|
|
3656
|
+
for (const k of Object.keys(v.context || {}))
|
|
3657
|
+
if (!out.includes(k))
|
|
3658
|
+
out.push(k);
|
|
3659
|
+
}
|
|
3660
|
+
catch (_) { /* no context */ }
|
|
3661
|
+
}
|
|
3662
|
+
return out;
|
|
3663
|
+
}
|
|
3664
|
+
/** PLAN-10 D522: the app (run()) of each instance: the index of its root among the live roots, oldest first */
|
|
3665
|
+
function appsOf(all) {
|
|
3666
|
+
const byId = new Map(all.map(r => [r.id, r]));
|
|
3667
|
+
const roots = all.filter(r => r.parentId === null).sort((a, b) => Number(a.id) - Number(b.id));
|
|
3668
|
+
const index = new Map(roots.map((r, i) => [r, i]));
|
|
3669
|
+
const out = new Map();
|
|
3670
|
+
for (const r of all) {
|
|
3671
|
+
let top = r;
|
|
3672
|
+
for (let n = 0; top && top.parentId !== null && n < 10000; n++)
|
|
3673
|
+
top = byId.get(top.parentId);
|
|
3674
|
+
const i = top && index.get(top);
|
|
3675
|
+
if (i !== undefined)
|
|
3676
|
+
out.set(r, i);
|
|
3677
|
+
}
|
|
3678
|
+
return out;
|
|
3679
|
+
}
|
|
3590
3680
|
/** The app graph of the live component instances (see ./public.d.ts). */
|
|
3591
3681
|
function inspect(options = {}) {
|
|
3592
3682
|
// report SYG105 (info) for EVENTS types selected but never emitted so far
|
|
@@ -3611,21 +3701,28 @@ function inspect(options = {}) {
|
|
|
3611
3701
|
const ids = options.ids && new Set(options.ids.map(String));
|
|
3612
3702
|
const recs = ids ? all.filter(r => ids.has(r.id)) : all;
|
|
3613
3703
|
const names = new Set(recs.map(r => r.name));
|
|
3704
|
+
const apps = appsOf(all);
|
|
3614
3705
|
const components = recs.map(r => {
|
|
3615
3706
|
const c = r.instance;
|
|
3616
3707
|
const calculated = c && c.calculated && typeof c.calculated === 'object' ? Object.keys(c.calculated) : [];
|
|
3617
3708
|
const selectors = selectorsOf(r, options, diags);
|
|
3618
3709
|
const controls = controlsOf(r, selectors);
|
|
3710
|
+
const key = stampedKey(c);
|
|
3619
3711
|
return {
|
|
3620
3712
|
name: r.name,
|
|
3621
3713
|
id: r.id,
|
|
3714
|
+
...(key ? { key } : {}),
|
|
3622
3715
|
parentId: r.parentId,
|
|
3623
3716
|
kind: r.kind,
|
|
3717
|
+
...(isViewOnly(c) ? { viewOnly: true } : {}),
|
|
3718
|
+
...(r.parentId === null ? { entry: true } : {}),
|
|
3719
|
+
...(apps.has(r) ? { app: apps.get(r) } : {}),
|
|
3720
|
+
...(c && c.__next && c.__next.shown === false ? { shown: false } : {}),
|
|
3624
3721
|
actions: actionsOf(r),
|
|
3625
3722
|
stateKeys: stateKeysOf(c, calculated),
|
|
3626
3723
|
calculated,
|
|
3627
3724
|
contextProvides: c && isPlainObject(c.context) ? Object.keys(c.context) : [],
|
|
3628
|
-
contextConsumes:
|
|
3725
|
+
contextConsumes: contextReadsOf(c),
|
|
3629
3726
|
eventsEmitted: [...r.eventsEmitted],
|
|
3630
3727
|
eventsSelected: [...r.eventsSelected],
|
|
3631
3728
|
children: childrenOf(r, all),
|
|
@@ -3637,14 +3734,21 @@ function inspect(options = {}) {
|
|
|
3637
3734
|
});
|
|
3638
3735
|
const cache = cacheOf(recs) || {};
|
|
3639
3736
|
const events = {};
|
|
3640
|
-
|
|
3737
|
+
// names once each (as before); PLAN-10 D522: every instance's id in emitterIds / selectorIds
|
|
3738
|
+
const ev = (type) => events[type] || (events[type] = { emitters: [], selectors: [], emitterIds: [], selectorIds: [] });
|
|
3641
3739
|
const add = (list, name) => { if (!list.includes(name))
|
|
3642
3740
|
list.push(name); };
|
|
3643
3741
|
for (const c of components) {
|
|
3644
|
-
for (const t of c.eventsEmitted)
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3742
|
+
for (const t of c.eventsEmitted) {
|
|
3743
|
+
const e = ev(t);
|
|
3744
|
+
add(e.emitters, c.name);
|
|
3745
|
+
e.emitterIds.push(c.id);
|
|
3746
|
+
}
|
|
3747
|
+
for (const t of c.eventsSelected) {
|
|
3748
|
+
const e = ev(t);
|
|
3749
|
+
add(e.selectors, c.name);
|
|
3750
|
+
e.selectorIds.push(c.id);
|
|
3751
|
+
}
|
|
3648
3752
|
}
|
|
3649
3753
|
// emitted on the bus by something that isn't a listed component (e.g. a driver, a pruned component)
|
|
3650
3754
|
if (!ids)
|
|
@@ -3705,6 +3809,52 @@ function installInspect() {
|
|
|
3705
3809
|
};
|
|
3706
3810
|
}
|
|
3707
3811
|
|
|
3812
|
+
/**
|
|
3813
|
+
* The 7.0 timing audit (SYG257, info): since 7.0 a model entry's STATE runs first and every other
|
|
3814
|
+
* sink gets the state it committed (6.x gave them the state from before the action). For code
|
|
3815
|
+
* written for 6.x, this reports each output sink that reads a top-level state field its action's
|
|
3816
|
+
* STATE changed: it gets the new value now. Fix: read `props.previousState.<field>` where the old
|
|
3817
|
+
* value is meant; a read meant to see the new value needs nothing. Opt-in, because in 7.0-native
|
|
3818
|
+
* code reading the new value is the intent. Turn it on with any of:
|
|
3819
|
+
* - configureTimingAudit(true) (this entry)
|
|
3820
|
+
* - renderComponent(C, { timingAudit: true }) (tests)
|
|
3821
|
+
* - sygnal({ diagnostics: { timingAudit: true } }) (sygnal/vite, dev)
|
|
3822
|
+
* - globalThis.__SYGNAL_TIMING_AUDIT__ = true
|
|
3823
|
+
* The flag lives on the core bridge (shared by every copy of this entry and renderComponent).
|
|
3824
|
+
* Mechanism: the dev hook layer's wrapHandler (checks/next.ts) wraps each non-STATE handler and
|
|
3825
|
+
* gives it the state through a Proxy that notes reads of changed top-level fields. Top-level
|
|
3826
|
+
* fields only (a nested change shows as its top-level field changing); once per component name,
|
|
3827
|
+
* action, sink and field. Nothing in production builds.
|
|
3828
|
+
*/
|
|
3829
|
+
/** Turn the timing audit on (true), off (false), or back to the default (globalThis.__SYGNAL_TIMING_AUDIT__). */
|
|
3830
|
+
function configureTimingAudit(on) {
|
|
3831
|
+
const core = bridge();
|
|
3832
|
+
if (core)
|
|
3833
|
+
core.timingAudit = on;
|
|
3834
|
+
}
|
|
3835
|
+
/** Whether the timing audit is on. */
|
|
3836
|
+
function isTimingAuditEnabled() {
|
|
3837
|
+
const flag = bridge()?.timingAudit;
|
|
3838
|
+
return flag === undefined ? globalThis.__SYGNAL_TIMING_AUDIT__ === true : flag === true;
|
|
3839
|
+
}
|
|
3840
|
+
/** handler `h` of `f`'s action `type`, sink `sink`, audited (f: the checks' instance facade) */
|
|
3841
|
+
const audited = (f, type, sink, h) => (s, d, nx, p, ...r) => {
|
|
3842
|
+
const was = p?.previousState;
|
|
3843
|
+
if (!isTimingAuditEnabled() || !was || was === s || !s || typeof s != 'object' || typeof was != 'object' || Array.isArray(s))
|
|
3844
|
+
return h(s, d, nx, p, ...r);
|
|
3845
|
+
const view = new Proxy(s, { get(t, k, rcv) {
|
|
3846
|
+
if (typeof k == 'string' && k in was && was[k] !== t[k] && once(`SYG257:${f.name}:${type}:${sink}:${k}`))
|
|
3847
|
+
devReport('SYG257', {
|
|
3848
|
+
component: f,
|
|
3849
|
+
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)`,
|
|
3850
|
+
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`,
|
|
3851
|
+
data: { action: type, sink, field: k },
|
|
3852
|
+
});
|
|
3853
|
+
return Reflect.get(t, k, rcv);
|
|
3854
|
+
} });
|
|
3855
|
+
return h(view, d, nx, p, ...r);
|
|
3856
|
+
};
|
|
3857
|
+
|
|
3708
3858
|
/**
|
|
3709
3859
|
* PLAN-4.6 R4: the dev entry's checks on the next component core (src/core/).
|
|
3710
3860
|
*
|
|
@@ -4217,6 +4367,9 @@ function nextHooks(_api) {
|
|
|
4217
4367
|
onAction(iv, a) { log.onAction(iv, a); },
|
|
4218
4368
|
wrapHandler(iv, type, sink, fn) {
|
|
4219
4369
|
let h = log.wrapHandler(iv, type, sink, fn) || fn;
|
|
4370
|
+
// 7.0 timing audit (SYG257, opt-in; checks/timingAudit.ts): checked per call, so turning it on later counts
|
|
4371
|
+
if (on() && sink != 'STATE' && typeof h == 'function')
|
|
4372
|
+
h = audited(fac(iv), type, sink, h);
|
|
4220
4373
|
// SYG222: the STATE reducers, wrapped (today the check rewrote the instance's model)
|
|
4221
4374
|
if (on() && sink == 'STATE' && typeof fn == 'function')
|
|
4222
4375
|
h = watchMutation(fac(iv), type, h);
|
|
@@ -4436,10 +4589,12 @@ exports.checkEventBus = checkEventBus;
|
|
|
4436
4589
|
exports.checks = checks;
|
|
4437
4590
|
exports.configureChecks = configureChecks;
|
|
4438
4591
|
exports.configureStrict = configureStrict;
|
|
4592
|
+
exports.configureTimingAudit = configureTimingAudit;
|
|
4439
4593
|
exports.getCodeInfo = getCodeInfo;
|
|
4440
4594
|
exports.inspect = inspect;
|
|
4441
4595
|
exports.installChecks = installChecks;
|
|
4442
4596
|
exports.isStrictEnabled = isStrictEnabled;
|
|
4597
|
+
exports.isTimingAuditEnabled = isTimingAuditEnabled;
|
|
4443
4598
|
exports.listCodes = listCodes;
|
|
4444
4599
|
exports.resetChecks = resetChecks;
|
|
4445
4600
|
//# sourceMappingURL=diagnostics.cjs.js.map
|