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
@@ -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$1 = ['intent', 'model', 'initialState'];
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$1.filter(s => control[s] != null);
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
- if (typeof o.submit != 'string' || !model.includes(o.submit) || clash) {
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
- : `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' }) } }`,
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 = (entry.count || 1) + 1;
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: null,
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
- const ev = (type) => events[type] || (events[type] = { emitters: [], selectors: [] });
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
- add(ev(t).emitters, c.name);
3626
- for (const t of c.eventsSelected)
3627
- add(ev(t).selectors, c.name);
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