sygnal 6.2.0 → 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 (76) hide show
  1. package/CHANGELOG.md +61 -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 +164 -3
  11. package/dist/diagnostics.cjs.js.map +1 -1
  12. package/dist/diagnostics.esm.js +164 -3
  13. package/dist/diagnostics.esm.js.map +1 -1
  14. package/dist/guide/agent.md +7 -1
  15. package/dist/guide/ai-cards.md +319 -0
  16. package/dist/guide/ai-chat.md +4 -0
  17. package/dist/guide/ai-compose.md +881 -0
  18. package/dist/guide/ai-decisions.md +16 -0
  19. package/dist/guide/http.md +1 -0
  20. package/dist/index.cjs.js +5695 -347
  21. package/dist/index.cjs.js.map +1 -1
  22. package/dist/index.d.ts +638 -8
  23. package/dist/index.esm.js +5685 -347
  24. package/dist/index.esm.js.map +1 -1
  25. package/dist/sygnal.min.js +1 -1
  26. package/dist/sygnal.min.js.map +1 -1
  27. package/dist/vike/config/package.json +1 -1
  28. package/dist/vike/onRenderClient.cjs.js +3 -2
  29. package/dist/vike/onRenderClient.cjs.js.map +1 -1
  30. package/dist/vike/onRenderClient.mjs +3 -2
  31. package/dist/vike/onRenderClient.mjs.map +1 -1
  32. package/llms.txt +6 -4
  33. package/package.json +2 -1
  34. package/src/ai.d.ts +642 -7
  35. package/src/ai.ts +6 -0
  36. package/src/collection.ts +2 -1
  37. package/src/core/instance.ts +6 -3
  38. package/src/core/markers/lazy.ts +7 -3
  39. package/src/core/markers/portal.ts +5 -2
  40. package/src/core/markers/suspense.ts +3 -2
  41. package/src/core/markers/transition.ts +6 -3
  42. package/src/core/registry.ts +9 -5
  43. package/src/cycle/dom/IsolateModule.ts +25 -22
  44. package/src/extra/ai/chat/behavior.ts +354 -81
  45. package/src/extra/ai/chat/driver.ts +7 -4
  46. package/src/extra/ai/compose/agent.ts +55 -0
  47. package/src/extra/ai/compose/ask.ts +147 -0
  48. package/src/extra/ai/compose/behavior.ts +183 -0
  49. package/src/extra/ai/compose/catalog.ts +211 -0
  50. package/src/extra/ai/compose/composed.ts +61 -0
  51. package/src/extra/ai/compose/decisions.ts +1649 -0
  52. package/src/extra/ai/compose/diag.ts +33 -0
  53. package/src/extra/ai/compose/index.ts +23 -0
  54. package/src/extra/ai/compose/layout.ts +441 -0
  55. package/src/extra/ai/compose/llm.ts +1120 -0
  56. package/src/extra/ai/compose/render.ts +159 -0
  57. package/src/extra/ai/compose/testing.ts +71 -0
  58. package/src/extra/ai/compose/types.ts +159 -0
  59. package/src/extra/ai/decide.ts +97 -1
  60. package/src/extra/ai/index.ts +6 -1
  61. package/src/extra/diagnostics/checks/next.ts +86 -2
  62. package/src/extra/diagnostics/codes.ts +54 -0
  63. package/src/extra/fetchDriver.ts +58 -27
  64. package/src/extra/lens.ts +15 -0
  65. package/src/extra/ssr.ts +3 -3
  66. package/src/extra/testing.ts +2 -0
  67. package/src/extra/widget.ts +3 -3
  68. package/src/index.d.ts +41 -0
  69. package/src/index.ts +8 -0
  70. package/src/lazy.ts +3 -2
  71. package/src/portal.ts +5 -2
  72. package/src/pragma/index.ts +2 -1
  73. package/src/suspense.ts +5 -2
  74. package/src/switchable.ts +2 -1
  75. package/src/transition.ts +5 -2
  76. package/src/vike/onRenderClient.ts +3 -2
package/src/ai.ts CHANGED
@@ -21,3 +21,9 @@ export {anthropicMessages, agui, fromAISDK, strictSchemas} from './index'
21
21
  export {makeMcpAppDriver} from './index'
22
22
  // A-3 (3-A3, experimental, D291)
23
23
  export {formTool} from './index'
24
+ // PLAN-9 (A)
25
+ export {defineCatalog, compose, composeWith, fromTree, toTree, removeNode} from './index'
26
+ // PLAN-9 H: chat client tools and cards
27
+ export {openToolCalls, askCard} from './index'
28
+ // PLAN-9 M-1
29
+ export {Composed} from './index'
package/src/collection.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {h} from './cycle/dom/snabbdom';
2
- // the core's handler for this marker, registered on import (D157)
2
+ // the core's host for this marker, registered on import (D157): unlike Portal, Transition and
3
+ // Suspense (G-751), not on first use, because the lowercase tag works without an import (docs)
3
4
  import './core/hosts/collection';
4
5
 
5
6
  /** `<Collection of={Item} from="items" />`: a marker vnode the core's Collection host renders */
@@ -52,12 +52,15 @@ function extractSlots(children: any[]): [any[], Record<string, any[]>] {
52
52
  return [slots.default || [], slots]
53
53
  }
54
54
 
55
- /** an instance's id among its parent's children: its `id` prop, else its path in the view */
55
+ /**
56
+ * an instance's id among its parent's children (D456): its vnode `key`, else its `id` prop, else
57
+ * its path in the view
58
+ */
56
59
  const idOf = (n: any, path: string) => {
57
- const id = n.data?.props?.id
60
+ const id = n.key ?? n.data?.props?.id
58
61
  let s: any
59
62
  // (G-298: an id JSON can't encode, a BigInt, is used as its string)
60
- if (id) try { s = JSON.stringify(id) } catch (_) { s = String(id) }
63
+ if (id != null) try { s = JSON.stringify(id) } catch (_) { s = String(id) }
61
64
  return `${n.sel}::${(s && s.replace(/"/g, '')) || path}`
62
65
  }
63
66
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * PLAN-4.6 core: lazy() components (R2), registered on import by the public `lazy` module.
2
+ * PLAN-4.6 core: lazy() components (R2), registered by the first `lazy()` call (G-751).
3
3
  * A view resolver (registry `resolvers`): until the import resolves, the LazyWrapper itself is
4
4
  * the component (it renders the data-sygnal-lazy="loading" placeholder Suspense waits for, or
5
5
  * the error placeholder); once it has, the loaded component replaces it under the same id (a
@@ -15,7 +15,7 @@ import {resolvers} from '../registry'
15
15
  /** wrapper -> the owners waiting for it (`done`: settled, nobody waits any more) */
16
16
  export const waiting = new WeakMap<any, Set<any> & {done?: boolean}>()
17
17
 
18
- resolvers.push((view, owner) => {
18
+ const resolve = (view: any, owner: any) => {
19
19
  if (!view.__sygnalLazy) return
20
20
  if (view.__sygnalLazyLoaded()) return view.__sygnalLazyLoadedComponent || undefined
21
21
  let w = waiting.get(view)
@@ -37,4 +37,8 @@ resolvers.push((view, owner) => {
37
37
  set.add(owner)
38
38
  owner.dispose$?.().addListener({next: () => set.delete(owner)})
39
39
  }
40
- })
40
+ }
41
+
42
+ let on: any
43
+ /** G-751: registers the resolver once; `lazy()` calls it, not the module's import */
44
+ export const enableLazy = () => { on ||= resolvers.push(resolve) }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * PLAN-4.6 core: the Portal marker (R2), registered on import by the public `Portal` module.
2
+ * PLAN-4.6 core: the Portal marker (R2), registered by the public `Portal` when used (G-751).
3
3
  * A template rewrite (registry `pres`): the `portal` marker becomes a hidden placeholder whose
4
4
  * hooks patch its children into the target with a separate snabbdom patch function. As today
5
5
  * (the 5.x core's createPortalPlaceholder):
@@ -93,4 +93,7 @@ export function portalPlaceholder(target: string, children: any[]): any {
93
93
  }
94
94
  }
95
95
 
96
- pres.portal = (n) => portalPlaceholder(n.data?.props?.target, n.children || [])
96
+ // (widgets among its children: `pres.widget.$k` rewrites them, extra/widget.ts; the core doesn't walk them)
97
+ const pre = (n: any, o: any, x: string) => portalPlaceholder(n.data?.props?.target, (pres.widget as any)?.$k(n.children || [], o, x) || n.children || [])
98
+ /** G-751: registers the rewrite; the public `Portal` calls it when used (its `$reg`), not on import */
99
+ export const enablePortal = () => { pres.portal = pre }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * PLAN-4.6 core: Suspense (R2), registered on import by the public `Suspense` module. A
2
+ * PLAN-4.6 core: Suspense (R2), registered by the public `Suspense` when used (G-751). A
3
3
  * post-processor (registry `posts`) of the injected vnode of an instance whose template has a
4
4
  * `suspense` marker (the 5.x core's processSuspensePost):
5
5
  * - the core injects a child whose READY is false with data-sygnal-ready="false", and a lazy
@@ -38,4 +38,5 @@ export function suspensePost(v: any): any {
38
38
  return out ? {...v, children: out} : v
39
39
  }
40
40
 
41
- posts.suspense = suspensePost
41
+ /** G-751: registers the post-processor; the public `Suspense` calls it when used (its `$reg`) */
42
+ export const enableSuspense = () => { posts.suspense = suspensePost }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * PLAN-4.6 core: the Transition marker (R2), registered on import by the public `Transition`
3
- * module. A template rewrite (registry `pres`): the `transition` marker is replaced by its first
2
+ * PLAN-4.6 core: the Transition marker (R2), registered by the public `Transition`
3
+ * when used (G-751). A template rewrite (registry `pres`): the `transition` marker is replaced by its first
4
4
  * child (walked on, at the marker's path), with snabbdom hooks that add the classes as today
5
5
  * (the 5.x core's applyTransitionHooks):
6
6
  * `${name}-enter|leave-from` + `-active`, two frames later `-from` becomes `-to`, and at the end
@@ -57,7 +57,7 @@ export function transitionHooks(vnode: any, p: {name?: string, duration?: number
57
57
  // (no global symbol registry entry per name)
58
58
  const T = Symbol()
59
59
 
60
- pres.transition = (n) => {
60
+ const pre = (n: any) => {
61
61
  const child = n.children?.[0], p = n.data?.props || {}, k = p.name + ' ' + p.duration
62
62
  // no element child: the text child (or the marker itself, left as it is: today's behaviour)
63
63
  if (!child?.sel) return child || n
@@ -68,3 +68,6 @@ pres.transition = (n) => {
68
68
  // fragment or text has no element to animate: nothing happens
69
69
  return child.sel == 'collection' ? {...child, data: {...child.data, tr: (v: any) => (v[T] ||= {})[k] ||= transitionHooks(v, p)}} : transitionHooks(child, p)
70
70
  }
71
+
72
+ /** G-751: registers the rewrite; the public `Transition` calls it when used (its `$reg`) */
73
+ export const enableTransition = () => { pres.transition = pre }
@@ -1,9 +1,13 @@
1
1
  /**
2
- * PLAN-4.6 core: the module registries (04-hooks-contract §2.1). A feature module fills them
3
- * when it is imported; the core only looks them up, so an app that never imports the module pays
4
- * nothing (D157). R2 fills hosts (core/hosts: Collection, Switchable) and the markers
5
- * (core/markers: Portal, Transition, ClientOnly, Suspense, lazy), each imported by its public
6
- * module; R3 adds the definition hooks (behaviors, undo, selection, pager, persist, resources).
2
+ * PLAN-4.6 core: the module registries (04-hooks-contract §2.1). A feature fills them when it is
3
+ * used; the core only looks them up, so an app that never uses the feature pays nothing (D157).
4
+ * R2 fills hosts (core/hosts: Collection, Switchable) and the markers (core/markers: Portal,
5
+ * Transition, ClientOnly, Suspense, lazy). G-751: Portal, Transition and Suspense register their
6
+ * handler when a tag uses them (the pragma calls the marker's `$reg`) and `lazy()` its resolver,
7
+ * not on import: a module-level registration is kept by every bundle of the single-file build.
8
+ * Collection and Switchable register on import (the lowercase `<collection>` / `<switchable>`
9
+ * work without one); ClientOnly's is in runtime.ts (sygnal/vike can't reach the registry). R3
10
+ * adds the definition hooks (behaviors, undo, selection, pager, persist, resources).
7
11
  *
8
12
  * The entries are core modules: they get the owner's raw instance (hooks get InstanceViews).
9
13
  */
@@ -81,25 +81,25 @@ export class IsolateModule {
81
81
 
82
82
  public createModule() {
83
83
  const self = this;
84
- // G-564: a vnode can be in the old tree and the new one (a component's cached vnode, a
85
- // Collection item's copy, a hoisted vnode). When an ancestor is recreated (`alt ? <ol>{c}</ol>
86
- // : <ul>{c}</ul>`, a new key), snabbdom creates it again and gives it the new element before it
87
- // destroys the old subtree, so destroy hooks saw the new element (a widget inside wasn't
88
- // unmounted, its scope lost the new element). A created element notes its children's elements
89
- // (M: a child with one is mounted, or was), before they are created; a destroyed vnode gives
90
- // its noted children their old elements back for their destroy hooks (and theirs, down the
91
- // subtree), and they get the new ones again when the removal starts (S), before the insert
92
- // hooks. (A vnode that moves to another parent still gives its own destroy hook the new one)
93
- const M = new Map<any, any>(), S: any[] = [];
94
- // G-573: a vnode in the new tree twice: its first note (its element before the patch) wins.
95
- // A destroy hook that throws ends the patch before remove / post: the next patch first gives
96
- // the vnodes their new elements back and forgets the notes (pre)
97
- const back = (c: any) => M.has(c) && (S.push(c, c.elm), c.elm = M.get(c));
98
- const ret = () => { for (let e; S.length; ) e = S.pop(), S.pop().elm = e; };
84
+ // G-564, G-750, G-753: a vnode can be in the old tree and the new one (a component's cached
85
+ // vnode, a Collection item's copy, a hoisted vnode). Under another parent element (a recreated
86
+ // ancestor: `alt ? <ol>{c}</ol> : <ul>{c}</ul>`; a keyed instance moved to another column, or
87
+ // into a new row that takes its old place among its siblings) snabbdom created it again and
88
+ // gave it the new element while the old tree still had it: its destroy hooks saw the new
89
+ // element (a widget inside wasn't unmounted, its scope lost the new element), its old parent
90
+ // removed the new element and left the old one (G-750), or inserted a new sibling before it,
91
+ // inside that sibling (NotFoundError, the patch stopped: G-753). Now an element that is
92
+ // created or patched takes a copy of each child that has an element and isn't one of its old
93
+ // children (only a mounted vnode has one), before it is created: the old tree keeps the vnode
94
+ // and its element. The copy's children array is its own, so its children are copied when it
95
+ // is created. After the patch (post) each vnode takes its copy's fields (its element) and its
96
+ // place again, so the next patch sees the same vnode (snabbdom's shortcut). A patch that
97
+ // throws keeps the copies (pre forgets them): they still work, as copies
98
+ const R: any[] = [];
99
+ const copy = (a: any[], i: number) => { R.push(a, i, a[i]); a[i] = {...a[i], children: a[i].children?.slice()} };
99
100
  return {
100
101
  pre() {
101
- ret();
102
- M.clear();
102
+ R.length = 0;
103
103
  },
104
104
 
105
105
  create(emptyVNode: VNode, vNode: VNode) {
@@ -109,7 +109,7 @@ export class IsolateModule {
109
109
  if (Array.isArray(namespace)) {
110
110
  self.insertElement(namespace, elm as Element);
111
111
  }
112
- children?.forEach((c: any) => c?.elm && M.set(c, M.get(c) || c.elm));
112
+ children?.forEach((c: any, i: number) => c?.elm && copy(children, i));
113
113
  },
114
114
 
115
115
  update(oldVNode: VNode, vNode: VNode) {
@@ -126,21 +126,25 @@ export class IsolateModule {
126
126
  if (Array.isArray(namespace)) {
127
127
  self.insertElement(namespace, elm as Element);
128
128
  }
129
+ // (oldVNode !== vNode here: snabbdom returns early for the same vnode; the Set only when a
130
+ // child isn't at its old index)
131
+ const oc = oldVNode.children, nc: any = vNode.children
132
+ let o: any
133
+ nc?.forEach((c: any, i: number) => c?.elm && c !== oc?.[i] && !(o ||= new Set(oc)).has(c) && copy(nc, i));
129
134
  },
130
135
 
131
- // (the elements as they are now: post runs after the vnodes got their new ones back)
132
136
  destroy(vNode: VNode) {
133
- vNode.children?.forEach(back);
134
137
  self.vnodesBeingRemoved.push(vNode.elm as Element);
135
138
  },
136
139
 
137
140
  remove(vNode: VNode, cb: Function) {
138
141
  self.vnodesBeingRemoved.push(vNode.elm as Element);
139
- ret();
140
142
  cb();
141
143
  },
142
144
 
143
145
  post() {
146
+ for (let i = 0; i < R.length; i += 3) R[i][R[i + 1]] = Object.assign(R[i + 2], R[i][R[i + 1]]);
147
+ R.length = 0;
144
148
  // G-144: a removed root element is no longer one of its scope's roots (an element no
145
149
  // scope has as a root isn't in any set: removing it does nothing)
146
150
  for (const elm of self.vnodesBeingRemoved) {
@@ -148,7 +152,6 @@ export class IsolateModule {
148
152
  (self.eventDelegator as EventDelegator).removeElement(elm);
149
153
  }
150
154
  self.vnodesBeingRemoved = [];
151
- M.clear();
152
155
  },
153
156
  };
154
157
  }