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.
Files changed (104) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/README.md +2 -0
  3. package/dist/astro/index.cjs.js +423 -27
  4. package/dist/astro/index.cjs.js.map +1 -1
  5. package/dist/astro/index.mjs +423 -27
  6. package/dist/astro/index.mjs.map +1 -1
  7. package/dist/astro/server.cjs.js.map +1 -1
  8. package/dist/astro/server.mjs.map +1 -1
  9. package/dist/devtools.cjs.js +141 -3
  10. package/dist/devtools.cjs.js.map +1 -1
  11. package/dist/devtools.esm.js +141 -4
  12. package/dist/devtools.esm.js.map +1 -1
  13. package/dist/diagnostics.cjs.js +169 -14
  14. package/dist/diagnostics.cjs.js.map +1 -1
  15. package/dist/diagnostics.esm.js +168 -15
  16. package/dist/diagnostics.esm.js.map +1 -1
  17. package/dist/guide/accessibility.md +2 -0
  18. package/dist/guide/ai-chat.md +6 -6
  19. package/dist/guide/behaviors.md +1 -1
  20. package/dist/guide/element-commands.md +4 -2
  21. package/dist/guide/forms-reference.md +89 -3
  22. package/dist/guide/forms.md +8 -8
  23. package/dist/guide/http.md +77 -7
  24. package/dist/guide/migrating-to-7.md +314 -0
  25. package/dist/guide/recipes/ai-summarize.md +2 -1
  26. package/dist/guide/recipes/i18n.md +1 -1
  27. package/dist/guide/ui/tabs.md +5 -4
  28. package/dist/guide/ui/tooltip.md +24 -0
  29. package/dist/index.cjs.js +781 -320
  30. package/dist/index.cjs.js.map +1 -1
  31. package/dist/index.d.ts +319 -43
  32. package/dist/index.esm.js +781 -320
  33. package/dist/index.esm.js.map +1 -1
  34. package/dist/sygnal.min.js +1 -1
  35. package/dist/sygnal.min.js.map +1 -1
  36. package/dist/ui.cjs.js +96 -11
  37. package/dist/ui.cjs.js.map +1 -1
  38. package/dist/ui.esm.js +96 -12
  39. package/dist/ui.esm.js.map +1 -1
  40. package/dist/vike/config/package.json +1 -1
  41. package/dist/vike/onRenderClient.cjs.js +63 -43
  42. package/dist/vike/onRenderClient.cjs.js.map +1 -1
  43. package/dist/vike/onRenderClient.mjs +63 -43
  44. package/dist/vike/onRenderClient.mjs.map +1 -1
  45. package/dist/vike/onRenderHtml.cjs.js +21 -25
  46. package/dist/vike/onRenderHtml.cjs.js.map +1 -1
  47. package/dist/vike/onRenderHtml.mjs +21 -25
  48. package/dist/vike/onRenderHtml.mjs.map +1 -1
  49. package/dist/vite/plugin.cjs.js +410 -27
  50. package/dist/vite/plugin.cjs.js.map +1 -1
  51. package/dist/vite/plugin.mjs +410 -27
  52. package/dist/vite/plugin.mjs.map +1 -1
  53. package/llms.txt +17 -16
  54. package/package.json +1 -1
  55. package/src/astro/index.d.ts +8 -0
  56. package/src/astro/index.ts +18 -0
  57. package/src/core/actions.ts +28 -21
  58. package/src/core/define.ts +2 -0
  59. package/src/core/hooks.ts +8 -2
  60. package/src/core/hosts/collection.ts +4 -3
  61. package/src/core/instance.ts +60 -16
  62. package/src/core/runtime.ts +5 -1
  63. package/src/core/view.ts +2 -0
  64. package/src/cycle/dom/utils.ts +4 -2
  65. package/src/devtools.d.ts +13 -0
  66. package/src/devtools.ts +3 -0
  67. package/src/extra/ai/chat/behavior.ts +3 -3
  68. package/src/extra/ai/commandBar.ts +3 -3
  69. package/src/extra/ai/compose/behavior.ts +3 -3
  70. package/src/extra/ai/transports/shared.ts +8 -2
  71. package/src/extra/behaviors.ts +21 -0
  72. package/src/extra/devGraph.ts +111 -0
  73. package/src/extra/devMcp.ts +1 -1
  74. package/src/extra/devtools.ts +18 -0
  75. package/src/extra/diagnostics/checks/fetch.ts +21 -1
  76. package/src/extra/diagnostics/checks/forms.ts +4 -3
  77. package/src/extra/diagnostics/checks/index.ts +2 -1
  78. package/src/extra/diagnostics/checks/inspect.ts +68 -6
  79. package/src/extra/diagnostics/checks/next.ts +3 -0
  80. package/src/extra/diagnostics/checks/public.d.ts +133 -12
  81. package/src/extra/diagnostics/checks/timingAudit.ts +47 -0
  82. package/src/extra/diagnostics/codes.ts +19 -0
  83. package/src/extra/fetchDriver.ts +112 -7
  84. package/src/extra/fetchEventSource.ts +113 -0
  85. package/src/extra/form.ts +68 -26
  86. package/src/extra/formHelpers.ts +3 -1
  87. package/src/extra/lens.ts +3 -2
  88. package/src/extra/queryCache.ts +4 -3
  89. package/src/extra/socketDriver.ts +10 -4
  90. package/src/extra/testing.ts +68 -11
  91. package/src/index.d.ts +199 -30
  92. package/src/index.ts +1 -0
  93. package/src/shared.ts +3 -0
  94. package/src/ui/dialog.ts +3 -2
  95. package/src/ui/tooltips.ts +92 -0
  96. package/src/ui.d.ts +35 -0
  97. package/src/ui.ts +1 -0
  98. package/src/vike/onRenderClient.ts +44 -46
  99. package/src/vike/onRenderHtml.ts +5 -26
  100. package/src/vike/shell.ts +33 -0
  101. package/src/vite/graph.ts +255 -0
  102. package/src/vite/mcp.ts +3 -3
  103. package/src/vite/plugin.d.ts +19 -0
  104. package/src/vite/plugin.ts +144 -25
@@ -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$1 = ['intent', 'model', 'initialState'];
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$1.filter(s => control[s] != null);
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
- if (typeof o.submit != 'string' || !model.includes(o.submit) || clash) {
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
- : `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' }) } }`,
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 = (entry.count || 1) + 1;
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: null,
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
- const ev = (type) => events[type] || (events[type] = { emitters: [], selectors: [] });
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
- add(ev(t).emitters, c.name);
3646
- for (const t of c.eventsSelected)
3647
- add(ev(t).selectors, c.name);
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