sygnal 6.1.1 → 6.3.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 (92) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +3 -1
  3. package/dist/ai.cjs.js +36 -0
  4. package/dist/ai.cjs.js.map +1 -1
  5. package/dist/ai.esm.js +1 -1
  6. package/dist/astro/server.cjs.js +3 -3
  7. package/dist/astro/server.cjs.js.map +1 -1
  8. package/dist/astro/server.mjs +3 -3
  9. package/dist/astro/server.mjs.map +1 -1
  10. package/dist/diagnostics.cjs.js +174 -6
  11. package/dist/diagnostics.cjs.js.map +1 -1
  12. package/dist/diagnostics.esm.js +174 -6
  13. package/dist/diagnostics.esm.js.map +1 -1
  14. package/dist/guide/adapters.md +2 -1
  15. package/dist/guide/agent.md +119 -7
  16. package/dist/guide/ai-cards.md +319 -0
  17. package/dist/guide/ai-chat.md +64 -16
  18. package/dist/guide/ai-compose.md +881 -0
  19. package/dist/guide/ai-decisions.md +17 -1
  20. package/dist/guide/behaviors.md +1 -1
  21. package/dist/guide/error-boundaries.md +2 -1
  22. package/dist/guide/forms.md +1 -1
  23. package/dist/guide/http.md +5 -4
  24. package/dist/guide/mcp-apps.md +4 -2
  25. package/dist/guide/persistence.md +1 -1
  26. package/dist/guide/recipes/ai-form-fill.md +1 -1
  27. package/dist/guide/recipes/ai-summarize.md +1 -1
  28. package/dist/guide/recipes/ai-support-inbox.md +1 -1
  29. package/dist/guide/resources.md +11 -10
  30. package/dist/guide/ui/combobox.md +1 -1
  31. package/dist/guide/undo.md +1 -1
  32. package/dist/guide/web-components.md +1 -0
  33. package/dist/guide/webmcp.md +1 -2
  34. package/dist/index.cjs.js +5842 -370
  35. package/dist/index.cjs.js.map +1 -1
  36. package/dist/index.d.ts +679 -13
  37. package/dist/index.esm.js +5832 -370
  38. package/dist/index.esm.js.map +1 -1
  39. package/dist/sygnal.min.js +1 -1
  40. package/dist/sygnal.min.js.map +1 -1
  41. package/dist/vike/config/package.json +1 -1
  42. package/dist/vike/onRenderClient.cjs.js +3 -2
  43. package/dist/vike/onRenderClient.cjs.js.map +1 -1
  44. package/dist/vike/onRenderClient.mjs +3 -2
  45. package/dist/vike/onRenderClient.mjs.map +1 -1
  46. package/llms.txt +8 -6
  47. package/package.json +2 -1
  48. package/src/ai.d.ts +683 -12
  49. package/src/ai.ts +6 -0
  50. package/src/collection.ts +2 -1
  51. package/src/core/instance.ts +6 -3
  52. package/src/core/markers/lazy.ts +7 -3
  53. package/src/core/markers/portal.ts +5 -2
  54. package/src/core/markers/suspense.ts +3 -2
  55. package/src/core/markers/transition.ts +6 -3
  56. package/src/core/registry.ts +9 -5
  57. package/src/cycle/dom/IsolateModule.ts +25 -22
  58. package/src/extra/ai/agent/index.ts +111 -26
  59. package/src/extra/ai/chat/behavior.ts +368 -87
  60. package/src/extra/ai/chat/driver.ts +7 -4
  61. package/src/extra/ai/compose/agent.ts +55 -0
  62. package/src/extra/ai/compose/ask.ts +147 -0
  63. package/src/extra/ai/compose/behavior.ts +183 -0
  64. package/src/extra/ai/compose/catalog.ts +211 -0
  65. package/src/extra/ai/compose/composed.ts +61 -0
  66. package/src/extra/ai/compose/decisions.ts +1649 -0
  67. package/src/extra/ai/compose/diag.ts +33 -0
  68. package/src/extra/ai/compose/index.ts +23 -0
  69. package/src/extra/ai/compose/layout.ts +441 -0
  70. package/src/extra/ai/compose/llm.ts +1120 -0
  71. package/src/extra/ai/compose/render.ts +159 -0
  72. package/src/extra/ai/compose/testing.ts +71 -0
  73. package/src/extra/ai/compose/types.ts +159 -0
  74. package/src/extra/ai/decide.ts +97 -1
  75. package/src/extra/ai/index.ts +6 -1
  76. package/src/extra/ai/webmcp.ts +5 -3
  77. package/src/extra/diagnostics/checks/next.ts +86 -2
  78. package/src/extra/diagnostics/codes.ts +64 -3
  79. package/src/extra/fetchDriver.ts +58 -27
  80. package/src/extra/lens.ts +15 -0
  81. package/src/extra/ssr.ts +3 -3
  82. package/src/extra/testing.ts +2 -0
  83. package/src/extra/widget.ts +3 -3
  84. package/src/index.d.ts +42 -1
  85. package/src/index.ts +8 -0
  86. package/src/lazy.ts +3 -2
  87. package/src/portal.ts +5 -2
  88. package/src/pragma/index.ts +2 -1
  89. package/src/suspense.ts +5 -2
  90. package/src/switchable.ts +2 -1
  91. package/src/transition.ts +5 -2
  92. package/src/vike/onRenderClient.ts +3 -2
@@ -230,9 +230,13 @@ const CODE_TITLES = {
230
230
  // PLAN-5 2-R (G-393; checks/state.ts; dev entry)
231
231
  SYG238: 'Root component with a model but no initial state',
232
232
  // PLAN-6 A-1 (sygnal/ai agent layer, src/extra/ai/agent; runtime, while an agent layer runs)
233
- SYG240: 'Agent action input has no JSON Schema form',
234
- SYG241: 'Agent read, when or label threw',
235
- SYG243: 'Agent action input schema converted with losses',
233
+ SYG240: 'Agent action or query input has no JSON Schema form',
234
+ SYG241: 'Agent read, query, when, until or label threw',
235
+ SYG243: 'Agent action or query input schema converted with losses',
236
+ // agent queries (src/extra/ai/agent; runtime and sygnal-check)
237
+ SYG246: 'Agent query is not a synchronous function',
238
+ SYG247: 'Agent query has the tool name of an action',
239
+ SYG248: 'Agent action until names no resource',
236
240
  // PLAN-6 A-2 (src/extra/ai/webmcp.ts; reported with the severity passed, dev)
237
241
  SYG242: 'WebMCP tool over a size budget',
238
242
  SYG244: 'Agent read returns strings, untrusted not declared',
@@ -263,6 +267,10 @@ const CODE_TITLES = {
263
267
  SYG423: 'Context change skipped a view whose output it changes',
264
268
  SYG424: 'Duplicate Collection item id',
265
269
  SYG425: 'isolatedState slice lacks initialState keys',
270
+ // PLAN-9 (D471): checks/next.ts; dev entry and sygnal-check
271
+ SYG426: 'Deprecated lowercase marker tag',
272
+ // PLAN-9 (D470): checks/next.ts; dev entry
273
+ SYG453: 'Two children with the same identity share one instance',
266
274
  SYG601: "Retired in 6.0: Missing or invalid sources",
267
275
  SYG602: 'Intent is not a function',
268
276
  SYG603: 'Intent returned an invalid value',
@@ -326,6 +334,29 @@ const CODE_TITLES = {
326
334
  // PLAN-6 A-2: experimentalExposeWebMcp (src/extra/ai/webmcp.ts; dev)
327
335
  SYG674: 'WebMCP not available',
328
336
  SYG676: 'WebMCP registerTool rejected',
337
+ // ---- PLAN-9 compose (A: src/extra/ai/compose/; dev, reported with the severity passed) ----
338
+ SYG161: 'Catalog container render is a component with a model',
339
+ SYG162: 'Catalog part with neither component nor render',
340
+ SYG250: 'Layout names an unknown part or slot',
341
+ SYG251: 'Invalid param value in a layout',
342
+ SYG252: 'Record param from() does not return an array of { value }',
343
+ SYG450: 'Catalog in names an unknown container or slot',
344
+ SYG451: 'Composed has no catalog or from is not a composition',
345
+ SYG452: 'Layout lists a node key twice',
346
+ SYG681: 'Compose backend option problem',
347
+ // PLAN-9 B-1/B-2: compose.decisions() (src/extra/ai/compose/decisions.ts; explicit severities)
348
+ SYG680: 'Part is LLM-only under a decision backend',
349
+ SYG682: 'Decision model answered outside the criteria',
350
+ // PLAN-9 B-3: compose.llm() (src/extra/ai/compose/llm.ts; the compose status becomes 'error')
351
+ SYG683: 'LLM layout invalid after repair',
352
+ // PLAN-9 X-2: static only (sygnal-check rules/syg253-catalog.js)
353
+ SYG253: 'Misspelled catalog declaration key',
354
+ SYG254: 'Catalog descriptions or options overlap',
355
+ // PLAN-9 H: chat client tools and ask (src/extra/ai/chat/behavior.ts, compose/ask.ts, compose/render.ts)
356
+ SYG160: 'Result part rendered outside a card',
357
+ SYG684: 'Client tool shadows an agent tool',
358
+ SYG685: 'Answer for a tool call that is not open',
359
+ SYG686: 'Ask catalog without a result part',
329
360
  // PLAN-4 GS-3: a11y lane (static only, sygnal-check)
330
361
  SYG701: 'Click listener on a non-interactive element',
331
362
  SYG702: 'Form field without an accessible label',
@@ -403,6 +434,9 @@ const DEV_CODE_SEVERITY = {
403
434
  SYG240: 'error',
404
435
  SYG241: 'error',
405
436
  SYG243: 'warn',
437
+ SYG246: 'error',
438
+ SYG247: 'warn',
439
+ SYG248: 'warn',
406
440
  // PLAN-6 A-2 (WebMCP, reported with the severity passed)
407
441
  SYG242: 'warn',
408
442
  SYG244: 'warn',
@@ -439,6 +473,10 @@ const DEV_CODE_SEVERITY = {
439
473
  SYG423: 'warn',
440
474
  SYG424: 'warn',
441
475
  SYG425: 'warn',
476
+ // PLAN-9 (checks/next.ts): D471 a lowercase <collection> / <switchable> tag (deprecated in 6.3.0)
477
+ SYG426: 'warn',
478
+ // PLAN-9 (checks/next.ts): D470 key identity, two children with one identity
479
+ SYG453: 'warn',
442
480
  SYG612: 'error',
443
481
  // PLAN-4 D131 (element.ts, through the core bridge; a console warning in dev with diagnostics off)
444
482
  SYG644: 'warn',
@@ -479,6 +517,16 @@ const DEV_CODE_SEVERITY = {
479
517
  // PLAN-6 A-2 (experimentalExposeWebMcp reports them with an explicit severity)
480
518
  SYG674: 'info',
481
519
  SYG676: 'warn',
520
+ // PLAN-9 B-1/B-2 (compose.decisions() reports them with an explicit severity)
521
+ SYG680: 'info',
522
+ SYG682: 'warn',
523
+ // PLAN-9 B-3 (compose.llm)
524
+ SYG683: 'error',
525
+ // PLAN-9 H (chat client tools and ask; reported with the severity passed)
526
+ SYG160: 'warn',
527
+ SYG684: 'warn',
528
+ SYG685: 'warn',
529
+ SYG686: 'warn',
482
530
  // PLAN-5 V-1: VirtualCollection (checks/virtual.ts, through the core bridge)
483
531
  SYG430: 'warn',
484
532
  SYG431: 'warn',
@@ -510,6 +558,19 @@ const DEV_CODE_SEVERITY = {
510
558
  SYG153: 'warn',
511
559
  SYG730: 'warn',
512
560
  SYG731: 'warn',
561
+ // ---- PLAN-9 compose (A: src/extra/ai/compose/, reported with the severity passed, dev) ----
562
+ SYG161: 'error',
563
+ SYG162: 'error',
564
+ SYG250: 'warn',
565
+ SYG251: 'warn',
566
+ SYG252: 'warn',
567
+ SYG450: 'warn',
568
+ SYG451: 'warn',
569
+ SYG452: 'warn',
570
+ SYG681: 'error',
571
+ // PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
572
+ SYG253: 'warn',
573
+ SYG254: 'info',
513
574
  };
514
575
  function getCodeInfo(code) {
515
576
  const severity = CODE_SEVERITY[code];
@@ -3618,7 +3679,8 @@ function installInspect() {
3618
3679
  * a Collection's viewTransitionName that isn't an identifier, the same name twice on the
3619
3680
  * page after a patch, G-460: on rendered elements), SYG612 (D173: a form
3620
3681
  * 6.0 removed, met at runtime; once per form and component, with a link to the migration
3621
- * guide).
3682
+ * guide), SYG453 (PLAN-9, D470: two children with one identity share one instance), SYG426
3683
+ * (PLAN-9, D471: a lowercase <collection> / <switchable> tag, deprecated in 6.3.0).
3622
3684
  *
3623
3685
  * Only while diagnostics are on (the checks' own rule); production builds never load this.
3624
3686
  */
@@ -3691,6 +3753,101 @@ function removedTags(f, v, depth = 0) {
3691
3753
  for (const k of kids)
3692
3754
  removedTags(f, k, depth + 1);
3693
3755
  }
3756
+ /**
3757
+ * PLAN-9 SYG453 (D470): two children of one view with the same identity (the same component and
3758
+ * `key`, else `id`; `sel::key` in the core) are one instance, rendered in both places. The core
3759
+ * doesn't look (0 production bytes): in the owner's injected vnode that instance's output, a
3760
+ * scoped root (`data.isolate` in the real DOM driver, a `___<scope>` class in the mock DOM), is
3761
+ * the same object twice. Its name and key come from the child whose DOM source has that scope.
3762
+ */
3763
+ const scopeOfVnode = (n) => {
3764
+ const iso = n.data?.isolate;
3765
+ if (Array.isArray(iso))
3766
+ return iso.length ? iso[iso.length - 1]?.scope : undefined;
3767
+ const m = typeof n.sel == 'string' && /\.___([^.]+)/.exec(n.sel);
3768
+ return m ? m[1] : undefined;
3769
+ };
3770
+ const scopeOfSource = (dom) => {
3771
+ const ns = dom?.namespace;
3772
+ if (Array.isArray(ns))
3773
+ return ns[ns.length - 1]?.scope;
3774
+ const p = dom?._path;
3775
+ const m = Array.isArray(p) && /^\.___(.+)$/.exec(String(p[p.length - 1]));
3776
+ return m ? m[1] : undefined;
3777
+ };
3778
+ function sharedIdentity(iv, v) {
3779
+ const seen = new Set(), dup = new Set();
3780
+ const walk = (n, d) => {
3781
+ if (!n || typeof n != 'object' || d > 300)
3782
+ return;
3783
+ const sc = scopeOfVnode(n);
3784
+ if (sc !== undefined) {
3785
+ if (seen.has(n)) {
3786
+ dup.add(sc);
3787
+ return;
3788
+ }
3789
+ seen.add(n);
3790
+ }
3791
+ const kids = n.children;
3792
+ if (Array.isArray(kids))
3793
+ for (const k of kids)
3794
+ walk(k, d + 1);
3795
+ };
3796
+ walk(v, 0);
3797
+ if (!dup.size)
3798
+ return;
3799
+ const owner = iv.name;
3800
+ for (const sc of dup) {
3801
+ let child;
3802
+ try {
3803
+ child = iv.children().find((c) => scopeOfSource(c.sources?.DOM) === sc);
3804
+ }
3805
+ catch (_) { /* ignore */ }
3806
+ const name = child ? child.name : 'a child component';
3807
+ // the identity's own part of the child's uid (the owner's uid + '-' + its key, id or path)
3808
+ const key = child && typeof child.uid == 'string' && child.uid.startsWith(iv.uid + '-') ? child.uid.slice(iv.uid.length + 1) : undefined;
3809
+ if (!once(`SYG453:${owner}:${name}:${key ?? sc}`))
3810
+ continue;
3811
+ devReport('SYG453', {
3812
+ component: owner,
3813
+ message: `${owner} renders ${child ? `<${name}>` : name} in two places with the same identity${key !== undefined ? ` (key or id ${JSON.stringify(key)})` : ''}, so both places show one instance: it has one state, and the second tag's props replace the first's`,
3814
+ fix: `Give each <${child ? name : 'Child'}> a different key (or id) among its parent's children, e.g. key={item.id}; a key on a component is its identity`,
3815
+ data: { child: child ? name : undefined, key },
3816
+ });
3817
+ }
3818
+ }
3819
+ /**
3820
+ * PLAN-9 SYG426 (D471): the lowercase host tags, `<collection>` / `<switchable>` (JSX string tags,
3821
+ * or `h('collection')`), deprecated in 6.3.0; the next major removes them (and registers the hosts
3822
+ * on first use, as Portal). The core doesn't look (0 production bytes): the instance's template
3823
+ * keeps a host marker after reconcile (only `pres` markers are rewritten), and the imported
3824
+ * components' markers carry their function in `data.m`, a string tag's don't. The template is
3825
+ * the raw instance's (`iv.i`: the InstanceView's own field, a dev entry of the same build reads it;
3826
+ * the vnode onRender gets has the hosts' output injected in their place). Once per tag.
3827
+ */
3828
+ const HOST_TAGS = { collection: 'Collection', switchable: 'Switchable' };
3829
+ function lowercaseTags(iv) {
3830
+ const raw = iv.i;
3831
+ if (!raw?.kids?.size || !raw.tmpl)
3832
+ return;
3833
+ const walk = (n, d) => {
3834
+ if (!n || typeof n != 'object' || n.$p || d > 300)
3835
+ return;
3836
+ const name = typeof n.sel == 'string' && !n.data?.m && HOST_TAGS[n.sel];
3837
+ if (name && once(`SYG426:${n.sel}`))
3838
+ devReport('SYG426', {
3839
+ component: iv.name,
3840
+ message: `${iv.name} renders the lowercase <${n.sel}> tag, which is deprecated since Sygnal 6.3.0 and will be removed in the next major (it will then render an unknown <${n.sel}> element)`,
3841
+ fix: `import { ${name} } from 'sygnal' and write <${name} …> (sygnal-check --fix does it)`,
3842
+ data: { tag: n.sel, component: name },
3843
+ });
3844
+ const kids = n.children;
3845
+ if (Array.isArray(kids))
3846
+ for (const k of kids)
3847
+ walk(k, d + 1);
3848
+ };
3849
+ walk(raw.tmpl, 0);
3850
+ }
3694
3851
  /** the instance fields the checks read from today's Component instances */
3695
3852
  class Facade {
3696
3853
  constructor(__next) {
@@ -3951,8 +4108,19 @@ function nextHooks(_api) {
3951
4108
  onRender(iv, vnode) {
3952
4109
  const f = fac(iv);
3953
4110
  H.onRender(f, vnode);
3954
- if (on())
3955
- removedTags(f, vnode);
4111
+ if (!on())
4112
+ return;
4113
+ removedTags(f, vnode);
4114
+ try {
4115
+ lowercaseTags(iv);
4116
+ }
4117
+ catch (_) { /* a check never breaks the app */ }
4118
+ // SYG453: only a view with children can render one twice
4119
+ if (iv.children().length)
4120
+ try {
4121
+ sharedIdentity(iv, vnode);
4122
+ }
4123
+ catch (_) { /* a check never breaks the app */ }
3956
4124
  },
3957
4125
  onReducer(iv, type, prev, next) {
3958
4126
  H.onReducer(fac(iv), type, prev, next, 'STATE');