sygnal 6.6.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 (82) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/README.md +2 -0
  3. package/dist/astro/index.cjs.js +32 -9
  4. package/dist/astro/index.cjs.js.map +1 -1
  5. package/dist/astro/index.mjs +32 -9
  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.map +1 -1
  10. package/dist/devtools.esm.js.map +1 -1
  11. package/dist/diagnostics.cjs.js +87 -4
  12. package/dist/diagnostics.cjs.js.map +1 -1
  13. package/dist/diagnostics.esm.js +86 -5
  14. package/dist/diagnostics.esm.js.map +1 -1
  15. package/dist/guide/accessibility.md +2 -0
  16. package/dist/guide/ai-chat.md +6 -6
  17. package/dist/guide/behaviors.md +1 -1
  18. package/dist/guide/element-commands.md +4 -2
  19. package/dist/guide/forms-reference.md +89 -3
  20. package/dist/guide/forms.md +8 -8
  21. package/dist/guide/http.md +77 -7
  22. package/dist/guide/migrating-to-7.md +314 -0
  23. package/dist/guide/recipes/ai-summarize.md +2 -1
  24. package/dist/guide/recipes/i18n.md +1 -1
  25. package/dist/guide/ui/tabs.md +5 -4
  26. package/dist/guide/ui/tooltip.md +24 -0
  27. package/dist/index.cjs.js +772 -318
  28. package/dist/index.cjs.js.map +1 -1
  29. package/dist/index.d.ts +198 -31
  30. package/dist/index.esm.js +772 -318
  31. package/dist/index.esm.js.map +1 -1
  32. package/dist/sygnal.min.js +1 -1
  33. package/dist/sygnal.min.js.map +1 -1
  34. package/dist/ui.cjs.js +96 -11
  35. package/dist/ui.cjs.js.map +1 -1
  36. package/dist/ui.esm.js +96 -12
  37. package/dist/ui.esm.js.map +1 -1
  38. package/dist/vike/config/package.json +1 -1
  39. package/dist/vite/plugin.cjs.js +19 -9
  40. package/dist/vite/plugin.cjs.js.map +1 -1
  41. package/dist/vite/plugin.mjs +19 -9
  42. package/dist/vite/plugin.mjs.map +1 -1
  43. package/llms.txt +17 -16
  44. package/package.json +1 -1
  45. package/src/astro/index.d.ts +8 -0
  46. package/src/astro/index.ts +18 -0
  47. package/src/core/actions.ts +28 -21
  48. package/src/core/define.ts +2 -0
  49. package/src/core/hooks.ts +2 -2
  50. package/src/core/hosts/collection.ts +4 -3
  51. package/src/core/instance.ts +60 -16
  52. package/src/core/runtime.ts +5 -1
  53. package/src/extra/ai/chat/behavior.ts +3 -3
  54. package/src/extra/ai/commandBar.ts +3 -3
  55. package/src/extra/ai/compose/behavior.ts +3 -3
  56. package/src/extra/ai/transports/shared.ts +8 -2
  57. package/src/extra/behaviors.ts +21 -0
  58. package/src/extra/diagnostics/checks/fetch.ts +21 -1
  59. package/src/extra/diagnostics/checks/forms.ts +4 -3
  60. package/src/extra/diagnostics/checks/index.ts +1 -0
  61. package/src/extra/diagnostics/checks/next.ts +3 -0
  62. package/src/extra/diagnostics/checks/public.d.ts +12 -0
  63. package/src/extra/diagnostics/checks/timingAudit.ts +47 -0
  64. package/src/extra/diagnostics/codes.ts +13 -0
  65. package/src/extra/fetchDriver.ts +112 -7
  66. package/src/extra/fetchEventSource.ts +113 -0
  67. package/src/extra/form.ts +68 -26
  68. package/src/extra/formHelpers.ts +3 -1
  69. package/src/extra/lens.ts +3 -2
  70. package/src/extra/queryCache.ts +4 -3
  71. package/src/extra/socketDriver.ts +10 -4
  72. package/src/extra/testing.ts +68 -11
  73. package/src/index.d.ts +197 -30
  74. package/src/index.ts +1 -0
  75. package/src/shared.ts +3 -0
  76. package/src/ui/dialog.ts +3 -2
  77. package/src/ui/tooltips.ts +92 -0
  78. package/src/ui.d.ts +35 -0
  79. package/src/ui.ts +1 -0
  80. package/src/vite/graph.ts +11 -3
  81. package/src/vite/plugin.d.ts +7 -0
  82. package/src/vite/plugin.ts +10 -5
@@ -281,6 +281,8 @@ const CODE_TITLES = {
281
281
  SYG634: 'latest: true with a computed key',
282
282
  // PLAN-3 5-5 (D88)
283
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",
284
286
  // PLAN-4 GS-2: element commands (reported by the dev entry, checks/elementCommands.ts)
285
287
  SYG640: 'Element command target not found',
286
288
  SYG641: 'Unknown element command',
@@ -340,6 +342,9 @@ const CODE_TITLES = {
340
342
  // PLAN-9 X-2: static only (sygnal-check rules/syg253-catalog.js)
341
343
  SYG253: 'Misspelled catalog declaration key',
342
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',
343
348
  // PLAN-9 H: chat client tools and ask (src/extra/ai/chat/behavior.ts, compose/ask.ts, compose/render.ts)
344
349
  SYG160: 'Result part rendered outside a card',
345
350
  SYG684: 'Client tool shadows an agent tool',
@@ -450,6 +455,8 @@ const DEV_CODE_SEVERITY = {
450
455
  SYG634: 'info',
451
456
  // PLAN-3 5-5 (fetch.ts)
452
457
  SYG635: 'warn',
458
+ // PLAN-11 U-1 (fetch.ts)
459
+ SYG636: 'warn',
453
460
  // PLAN-4 GS-2 (checks/elementCommands.ts, through the core bridge; renderComponent too)
454
461
  SYG640: 'warn',
455
462
  SYG641: 'error',
@@ -566,6 +573,12 @@ const DEV_CODE_SEVERITY = {
566
573
  // PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
567
574
  SYG253: 'warn',
568
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',
569
582
  };
570
583
  function getCodeInfo(code) {
571
584
  const severity = CODE_SEVERITY[code];
@@ -2191,14 +2204,15 @@ const formsCheck = {
2191
2204
  });
2192
2205
  const model = Object.keys(view.model || {}).map(a => a.split('|')[0].trim()), own = ownActions(b);
2193
2206
  const clash = own.includes(o.submit);
2194
- 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) {
2195
2209
  devReport('SYG234', {
2196
2210
  component,
2197
- 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`
2198
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` : ''}`
2199
2213
  : `${name}'s form '${key}' dispatches '${o.submit}' on a valid submit, but ${name} has no model entry '${o.submit}'`,
2200
2214
  fix: clash ? SUBMIT_FIX(key, o.submit, own)
2201
- : `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' }) } }`,
2202
2216
  data: { key, submit: o.submit },
2203
2217
  });
2204
2218
  }
@@ -2623,6 +2637,13 @@ const routerCheck = {
2623
2637
  const isStandardSchema = (schema) => !!schema && (typeof schema == 'object' || typeof schema == 'function') && typeof schema['~standard']?.validate == 'function';
2624
2638
 
2625
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
+ };
2626
2647
  /** instance → reply key lanes it has sent, and ok name → the other key it was sent under */
2627
2648
  const lanes = new WeakMap();
2628
2649
  const fetchSink = (component, name) => component?.sources?.[name]?.__sygnalStatic === 'resources';
@@ -2646,7 +2667,7 @@ function checkOne(component, sink, req, where) {
2646
2667
  return;
2647
2668
  const name = nameOf(component);
2648
2669
  const url = typeof req.url === 'string' ? req.url : '';
2649
- 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();
2650
2671
  if ((req.cache === true || req.staleTime != null) && !IDEMPOTENT.test(method) && once(`SYG630:${name}:${method}:${url}`)) {
2651
2672
  devReport('SYG630', {
2652
2673
  component,
@@ -2657,6 +2678,17 @@ function checkOne(component, sink, req, where) {
2657
2678
  }
2658
2679
  if (req.cache === true || req.staleTime != null)
2659
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
+ }
2660
2692
  if ('validate' in req && !isStandardSchema(req.validate) && once(`SYG631:${name}:${url}`)) {
2661
2693
  devReport('SYG631', {
2662
2694
  component,
@@ -3757,6 +3789,52 @@ function installInspect() {
3757
3789
  };
3758
3790
  }
3759
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
+
3760
3838
  /**
3761
3839
  * PLAN-4.6 R4: the dev entry's checks on the next component core (src/core/).
3762
3840
  *
@@ -4269,6 +4347,9 @@ function nextHooks(_api) {
4269
4347
  onAction(iv, a) { log.onAction(iv, a); },
4270
4348
  wrapHandler(iv, type, sink, fn) {
4271
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);
4272
4353
  // SYG222: the STATE reducers, wrapped (today the check rewrote the instance's model)
4273
4354
  if (on() && sink == 'STATE' && typeof fn == 'function')
4274
4355
  h = watchMutation(fac(iv), type, h);
@@ -4483,5 +4564,5 @@ function resetChecks() {
4483
4564
  }
4484
4565
  installChecks();
4485
4566
 
4486
- 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 };
4487
4568
  //# sourceMappingURL=diagnostics.esm.js.map