nodality 1.1.14 → 1.2.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 (201) hide show
  1. package/bin/mcp-server.mjs +41 -5
  2. package/bin/nodality.js +61 -0
  3. package/dist/animator.cjs.js.LICENSE.txt +1 -1
  4. package/dist/animator.esm.js.LICENSE.txt +1 -1
  5. package/dist/audionew.cjs.js.LICENSE.txt +1 -1
  6. package/dist/audionew.esm.js.LICENSE.txt +1 -1
  7. package/dist/base.cjs.js.LICENSE.txt +1 -1
  8. package/dist/base.esm.js.LICENSE.txt +1 -1
  9. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  10. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  11. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  12. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  13. package/dist/bundle.umd.js +1 -1
  14. package/dist/bundle.umd.js.LICENSE.txt +60 -1
  15. package/dist/button.cjs.js.LICENSE.txt +1 -1
  16. package/dist/button.esm.js.LICENSE.txt +1 -1
  17. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  18. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  19. package/dist/center.cjs.js.LICENSE.txt +1 -1
  20. package/dist/center.esm.js.LICENSE.txt +1 -1
  21. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  22. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  23. package/dist/code.cjs.js.LICENSE.txt +1 -1
  24. package/dist/code.esm.js.LICENSE.txt +1 -1
  25. package/dist/container.cjs.js.LICENSE.txt +1 -1
  26. package/dist/container.esm.js.LICENSE.txt +1 -1
  27. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  28. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  29. package/dist/designer.cjs.js +1 -1
  30. package/dist/designer.cjs.js.LICENSE.txt +60 -1
  31. package/dist/designer.esm.js +1 -1
  32. package/dist/designer.esm.js.LICENSE.txt +60 -1
  33. package/dist/element-mapper.cjs.js +1 -1
  34. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  35. package/dist/element-mapper.esm.js +1 -1
  36. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  37. package/dist/finalresult.esm.js +1 -1
  38. package/dist/finalresult.esm.js.LICENSE.txt +60 -1
  39. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  40. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  41. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  42. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  43. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  44. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  45. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  46. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  47. package/dist/free.cjs.js.LICENSE.txt +1 -1
  48. package/dist/free.esm.js.LICENSE.txt +1 -1
  49. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  50. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  51. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  52. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  53. package/dist/image.cjs.js.LICENSE.txt +1 -1
  54. package/dist/image.esm.js.LICENSE.txt +1 -1
  55. package/dist/index.cjs.js +1 -1
  56. package/dist/index.cjs.js.LICENSE.txt +60 -1
  57. package/dist/index.d.ts +1 -1
  58. package/dist/index.esm.js +1 -1
  59. package/dist/index.esm.js.LICENSE.txt +60 -1
  60. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  61. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  62. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  63. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  64. package/dist/link.cjs.js.LICENSE.txt +1 -1
  65. package/dist/link.esm.js.LICENSE.txt +1 -1
  66. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  67. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  68. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  69. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  70. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  71. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  72. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  73. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  74. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  75. package/dist/picker.esm.js.LICENSE.txt +1 -1
  76. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  77. package/dist/progress.esm.js.LICENSE.txt +1 -1
  78. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  79. package/dist/radio.esm.js.LICENSE.txt +1 -1
  80. package/dist/range.cjs.js.LICENSE.txt +1 -1
  81. package/dist/range.esm.js.LICENSE.txt +1 -1
  82. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  83. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  84. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  85. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  86. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  87. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  88. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  89. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  90. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  91. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  92. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  93. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  94. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  95. package/dist/stack.esm.js.LICENSE.txt +1 -1
  96. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  97. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  98. package/dist/table.cjs.js.LICENSE.txt +1 -1
  99. package/dist/table.esm.js.LICENSE.txt +1 -1
  100. package/dist/text-field.cjs.js +1 -1
  101. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  102. package/dist/text-field.esm.js +1 -1
  103. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  104. package/dist/text.cjs.js.LICENSE.txt +1 -1
  105. package/dist/text.esm.js.LICENSE.txt +1 -1
  106. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  107. package/dist/theme.esm.js.LICENSE.txt +1 -1
  108. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  109. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  110. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  111. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  112. package/dist/video.cjs.js.LICENSE.txt +1 -1
  113. package/dist/video.esm.js.LICENSE.txt +1 -1
  114. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  115. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  116. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  117. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  118. package/layout/animator.js +1 -1
  119. package/layout/audio.js +1 -1
  120. package/layout/audionew.js +1 -1
  121. package/layout/base.js +1 -1
  122. package/layout/beta-desktop-bar.js +1 -1
  123. package/layout/beta-mobile-bar.js +1 -1
  124. package/layout/button.js +1 -1
  125. package/layout/center.js +1 -1
  126. package/layout/checkbox.js +1 -1
  127. package/layout/circle.js +1 -1
  128. package/layout/code.js +1 -1
  129. package/layout/container.js +1 -1
  130. package/layout/dropdown-2025.js +1 -1
  131. package/layout/flex-card.js +1 -1
  132. package/layout/flex-grid.js +1 -1
  133. package/layout/flex-row.js +1 -1
  134. package/layout/form-components/custom.js +1 -1
  135. package/layout/form-components/data-list.js +1 -1
  136. package/layout/form-components/floating-input.js +1 -1
  137. package/layout/form-components/form-all.js +1 -1
  138. package/layout/form-components/form.js +6 -1
  139. package/layout/form-components/image-picker.js +1 -1
  140. package/layout/form-components/picker.js +1 -1
  141. package/layout/form-components/radio.js +1 -1
  142. package/layout/form-components/radiogroup.js +1 -1
  143. package/layout/form-components/range.js +1 -1
  144. package/layout/free.js +1 -1
  145. package/layout/grid-switcher.js +1 -1
  146. package/layout/grid.js +1 -1
  147. package/layout/horizontal-scroller.js +1 -1
  148. package/layout/image.js +1 -1
  149. package/layout/index.js +1 -1
  150. package/layout/link.js +1 -1
  151. package/layout/list.js +1 -1
  152. package/layout/meta-adder.js +1 -1
  153. package/layout/modal-2025.js +1 -1
  154. package/layout/morph.js +1 -1
  155. package/layout/multiswitcher.js +1 -1
  156. package/layout/nav-bar.js +1 -1
  157. package/layout/nav-factor/custom-div.js +1 -1
  158. package/layout/new-nav-bar.js +1 -1
  159. package/layout/polygon.js +1 -1
  160. package/layout/prerender-site.js +1 -1
  161. package/layout/prerender.js +1 -1
  162. package/layout/progress.js +1 -1
  163. package/layout/row.js +1 -1
  164. package/layout/scroll-video.js +1 -1
  165. package/layout/side-bar.js +1 -1
  166. package/layout/side-nav-bar.js +1 -1
  167. package/layout/simple-bar.js +1 -1
  168. package/layout/slider-2025.js +1 -1
  169. package/layout/spacer.js +1 -1
  170. package/layout/stack.js +1 -1
  171. package/layout/svg.js +1 -1
  172. package/layout/switcher.js +1 -1
  173. package/layout/table.js +1 -1
  174. package/layout/text-field.js +13 -1
  175. package/layout/text.js +1 -1
  176. package/layout/ulist.js +1 -1
  177. package/layout/video.js +1 -1
  178. package/layout/wrap.js +1 -1
  179. package/layout/zoom-card.js +1 -1
  180. package/lib/agent-surface.js +347 -0
  181. package/lib/card-getter.js +1 -1
  182. package/lib/codegen.js +1 -1
  183. package/lib/data.js +1 -1
  184. package/lib/designer.js +22 -1
  185. package/lib/element-mapper.js +2 -1
  186. package/lib/keyframe-animation.js +1 -1
  187. package/lib/link-getter.js +1 -1
  188. package/lib/morph-node.js +47 -8
  189. package/lib/raster-inspect.js +1 -1
  190. package/lib/raster-ops.js +1 -1
  191. package/lib/raster-presets.js +1 -1
  192. package/lib/scroll-video.js +1 -1
  193. package/lib/seo.js +1 -1
  194. package/lib/stacker.js +1 -1
  195. package/lib/suggest.js +1 -1
  196. package/lib/theme.js +1 -1
  197. package/lib/transform-anim.js +1 -1
  198. package/lib/transition.js +1 -1
  199. package/lib/validate-nodes.js +196 -32
  200. package/lib/webmcp-adapter.js +307 -0
  201. package/package.json +1 -1
@@ -0,0 +1,307 @@
1
+ /*!
2
+ * nodality v1.2.0
3
+ * (c) 2026 Filip Vabrousek
4
+ * License: MIT
5
+ */
6
+
7
+ /*!
8
+ * webmcp-adapter.js — the derived surface, made callable.
9
+ *
10
+ * Everything protocol-shaped in this library lives HERE, in one file,
11
+ * on purpose. WebMCP is six months old, is a Draft Community Group
12
+ * Report rather than a standard, is available only in an origin trial,
13
+ * and has ALREADY moved its entry point once (`navigator.modelContext`
14
+ * → `document.modelContext`). A library that scattered those facts
15
+ * through the designer and the prerenderer would need archaeology every
16
+ * time the draft moved; here the next move is one file and a failing
17
+ * test.
18
+ *
19
+ * The registration itself is a REQUEST, not a guarantee — exactly the
20
+ * posture the live raster backend takes toward HTML-in-Canvas. Where
21
+ * the API is absent (every stock browser today, and jsdom at build
22
+ * time) nothing registers, nothing warns, and the page behaves
23
+ * identically. What still happens everywhere is the DECLARATION: the
24
+ * manifest is written into the document as data, so a crawler, an
25
+ * indexer, or an agent deciding whether to visit at all can read the
26
+ * site's capabilities without executing anything and without the API
27
+ * existing. That half needs no browser support and no standard.
28
+ */
29
+
30
+ import { deriveSurface } from "./agent-surface.js";
31
+
32
+ /**
33
+ * The draft this adapter is written against. Asserted in ONE place so
34
+ * that a spec move is a single-file change with a failing test, and so
35
+ * that every emitted manifest records what it was derived against
36
+ * rather than implying a stability the spec does not yet have.
37
+ */
38
+ export const SPEC_DRAFT = "2026-07-21";
39
+
40
+ /** Where the declaration lives in the document, JSON-LD style. */
41
+ export const MANIFEST_ID = "nodality-agent-manifest";
42
+
43
+ /**
44
+ * The registry, wherever this browser currently keeps it.
45
+ *
46
+ * Both spellings are accepted because the trial spans the rename: the
47
+ * draft moved to `document.modelContext`, Chrome 150 deprecated the
48
+ * older location, and pages served to both are the normal case for the
49
+ * length of an origin trial.
50
+ */
51
+ function registry() {
52
+ if (typeof document !== "undefined" && document.modelContext) return document.modelContext;
53
+ if (typeof navigator !== "undefined" && navigator.modelContext) return navigator.modelContext;
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * `CSS.escape`, or a serviceable stand-in.
59
+ *
60
+ * jsdom has no `CSS` global, and the build-time pass runs there — the
61
+ * unguarded call threw, was swallowed by the designer's try/catch, and
62
+ * every prerendered page silently shipped without its declaration. The
63
+ * fallback escapes what an id may legally contain and a selector may
64
+ * not; ids here come from descriptors the author wrote, not from user
65
+ * input, so this is about correctness on ordinary hyphens and dots
66
+ * rather than about hostile strings.
67
+ */
68
+ const esc = (s) => {
69
+ const str = String(s);
70
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") return CSS.escape(str);
71
+ return str.replace(/([^a-zA-Z0-9_-])/g, "\\$1");
72
+ };
73
+
74
+ /** Text an agent can read, from the subtree that is actually on screen. */
75
+ function readSubtree(root) {
76
+ if (!root) return { text: "", actions: [] };
77
+ const visible = (el) => {
78
+ const r = el.getBoundingClientRect();
79
+ if (!(r.width > 0 && r.height > 0)) return false;
80
+ const cs = getComputedStyle(el);
81
+ return cs.visibility !== "hidden" && cs.display !== "none" && cs.opacity !== "0";
82
+ };
83
+ const lines = [];
84
+ for (const el of root.querySelectorAll("h1,h2,h3,h4,h5,h6,p,li")) {
85
+ if (!visible(el)) continue;
86
+ const t = (el.textContent || "").replace(/\s+/g, " ").trim();
87
+ if (!t) continue;
88
+ const tag = el.tagName.toLowerCase();
89
+ lines.push(/^h[1-6]$/.test(tag) ? `${"#".repeat(+tag[1])} ${t}` : t);
90
+ }
91
+ const actions = [];
92
+ for (const el of root.querySelectorAll("a,button,[role=button]")) {
93
+ if (!visible(el)) continue;
94
+ const t = (el.textContent || "").replace(/\s+/g, " ").trim();
95
+ if (t && !actions.includes(t)) actions.push(t);
96
+ }
97
+ return { text: lines.join("\n"), actions };
98
+ }
99
+
100
+ /** A structured refusal, in the shape every other report in this library uses. */
101
+ const refuse = (code, got, valid) => ({ ok: false, code, got, valid });
102
+
103
+ /**
104
+ * Give a derived surface its handlers and offer it to the browser.
105
+ *
106
+ * @param {object} args
107
+ * @param {Element} args.mount the rendered mount
108
+ * @param {Array} args.elements E
109
+ * @param {Array} args.nodes N
110
+ * @param {Array} args.morphs handles returned by applyMorphNodes
111
+ * @returns {{manifest: object, tools: object[], registered: boolean,
112
+ * destroy: function}|null}
113
+ */
114
+ export function installAgentSurface({ mount, elements, nodes, morphs }) {
115
+ const derived = deriveSurface(elements, nodes, { specDraft: SPEC_DRAFT });
116
+ if (!derived) return null; // opt-in: no node, no surface
117
+
118
+ const { tools, manifest } = derived;
119
+ const handles = Array.isArray(morphs) ? morphs.filter(Boolean) : [];
120
+ const graphFor = (id) => handles.find((h) => h.states && h.states.has(id)) || null;
121
+
122
+ /**
123
+ * The rendered form a descriptor id names, wherever it currently is.
124
+ *
125
+ * A morph state that has not been visited is DETACHED — held by the
126
+ * controller, absent from the document — so a form inside one cannot
127
+ * be found by querying the mount. It is reachable through the state
128
+ * elements the controller holds, and stamping or filling it there
129
+ * works exactly as well: the attributes and values travel with the
130
+ * element when the transition inserts it.
131
+ */
132
+ const findForm = (formId) => {
133
+ const dig = (root) => {
134
+ if (!root || typeof root.querySelector !== "function") return null;
135
+ if (root.id === formId) {
136
+ return root.tagName === "FORM" ? root : root.querySelector("form");
137
+ }
138
+ const host = root.querySelector(`#${esc(formId)}`);
139
+ if (!host) return null;
140
+ return host.tagName === "FORM" ? host : host.querySelector("form");
141
+ };
142
+ const here = dig(mount);
143
+ if (here) return here;
144
+ for (const h of handles) {
145
+ if (!h.states) continue;
146
+ for (const el of h.states.values()) {
147
+ const found = dig(el);
148
+ if (found) return found;
149
+ }
150
+ }
151
+ return null;
152
+ };
153
+
154
+ // ── the declaration, written wherever we are ─────────────────────
155
+ //
156
+ // In jsdom this ends up inside the prerendered HTML, which is the
157
+ // whole point: the static declaration exists because the same code
158
+ // ran at build time, not because a second emitter was written for
159
+ // it. In a browser it is simply inert data in the page.
160
+ if (typeof document !== "undefined") {
161
+ const prev = document.getElementById(MANIFEST_ID);
162
+ if (prev) prev.remove();
163
+ const tag = document.createElement("script");
164
+ tag.type = "application/json";
165
+ tag.id = MANIFEST_ID;
166
+ tag.textContent = JSON.stringify(manifest, null, 2);
167
+ (document.body || document.documentElement).appendChild(tag);
168
+ }
169
+
170
+ // ── the spec's own form annotations ──────────────────────────────
171
+ //
172
+ // Emitted rather than resisted. The draft's declarative path derives
173
+ // a tool from `toolname`/`tooldescription` on a form, and Lighthouse
174
+ // already audits for them; stamping the SAME forms we derive from
175
+ // means the annotated surface and the derived surface describe one
176
+ // thing by construction, and a browser that only implements the
177
+ // declarative path still gets the form.
178
+ for (const t of tools) {
179
+ if (t.kind !== "submit" || !mount) continue;
180
+ const el = findForm(t.formId);
181
+ if (!el) continue;
182
+ el.setAttribute("toolname", t.name);
183
+ el.setAttribute("tooldescription", t.description);
184
+ }
185
+
186
+ // ── handlers ─────────────────────────────────────────────────────
187
+ //
188
+ // Traversals are SERIALISED, and this is an agent-specific hazard
189
+ // rather than a controller bug: a human cannot click twice inside
190
+ // one tick, so the controller's interrupt handling has never had to
191
+ // survive two transitions starting before either has built its
192
+ // pipeline. An agent does it trivially — two executeTool calls, no
193
+ // await — and the observed result was an interface with NOTHING on
194
+ // screen. Queuing here means the second call runs from the state the
195
+ // first actually landed in, which may make it unreachable; that
196
+ // returns a constructive refusal, which is a coherent answer. The
197
+ // controller is left exactly as it is, because for its own callers
198
+ // it is exactly right.
199
+ let queue = Promise.resolve();
200
+ const serial = (fn) => {
201
+ const run = queue.then(fn, fn);
202
+ queue = run.then(() => {}, () => {});
203
+ return run;
204
+ };
205
+
206
+ const execute = {
207
+ navigate: ({ destination } = {}) => serial(async () => {
208
+ const h = graphFor(destination);
209
+ if (!h) {
210
+ return refuse("UNKNOWN_VIEW", destination,
211
+ handles.flatMap((x) => [...x.states.keys()]));
212
+ }
213
+ // Reachability is a property of WHERE THE GRAPH IS, not of
214
+ // the schema, so it is answered here and not by the enum: a
215
+ // destination valid from `work` is invalid from `home`, and
216
+ // the honest answer names what is reachable right now.
217
+ const from = h.current;
218
+ await h.goToState(destination);
219
+ if (h.current !== destination) {
220
+ const edge = [...h.states.keys()];
221
+ return refuse("UNREACHABLE_FROM_HERE", destination,
222
+ edge.filter((id) => id !== from));
223
+ }
224
+ return { ok: true, view: h.current, ...readSubtree(h.stage || mount) };
225
+ }),
226
+
227
+ back: () => serial(async () => {
228
+ const h = handles.find((x) => x.history && x.history.length);
229
+ if (!h) return refuse("NO_HISTORY", null, []);
230
+ await h.goBack();
231
+ return { ok: true, view: h.current, ...readSubtree(h.stage || mount) };
232
+ }),
233
+
234
+ read: async () => ({ ok: true, ...readSubtree(mount) }),
235
+
236
+ submit: async (args = {}, tool) => {
237
+ const form = findForm(tool.formId);
238
+ if (!form) return refuse("NO_FORM", tool.formId, []);
239
+
240
+ const props = (tool.inputSchema && tool.inputSchema.properties) || {};
241
+ const required = (tool.inputSchema && tool.inputSchema.required) || [];
242
+ const missing = required.filter(
243
+ (k) => args[k] === undefined || args[k] === "" || args[k] === null);
244
+ if (missing.length) {
245
+ // The constraint, not a boolean. A generator repairs from
246
+ // this in one turn; "false" costs it a guess.
247
+ return refuse("MISSING_REQUIRED", missing, Object.keys(props));
248
+ }
249
+
250
+ const filled = [];
251
+ for (const key of Object.keys(props)) {
252
+ if (args[key] === undefined) continue;
253
+ const field = form.querySelector(
254
+ `[name="${key}"], #${CSS.escape(key)}`);
255
+ if (!field) continue;
256
+ if (field.type === "checkbox") field.checked = !!args[key];
257
+ else field.value = String(args[key]);
258
+ // Both, because component state may listen for either.
259
+ field.dispatchEvent(new Event("input", { bubbles: true }));
260
+ field.dispatchEvent(new Event("change", { bubbles: true }));
261
+ filled.push(key);
262
+ }
263
+
264
+ // Submitted through the form's OWN path, so its listeners,
265
+ // its validation attributes and the browser's constraint
266
+ // checking all apply. An agent submit is a user submit.
267
+ if (typeof form.requestSubmit === "function") form.requestSubmit();
268
+ else form.submit();
269
+ return { ok: true, submitted: tool.formId, filled };
270
+ },
271
+ };
272
+
273
+ // ── registration, if this browser has anywhere to register ───────
274
+ const reg = registry();
275
+ let registered = false;
276
+ if (reg && typeof reg.registerTool === "function") {
277
+ for (const t of tools) {
278
+ try {
279
+ reg.registerTool({
280
+ name: t.name,
281
+ description: t.description,
282
+ inputSchema: t.inputSchema,
283
+ execute: (args) => execute[t.kind](args, t),
284
+ });
285
+ registered = true;
286
+ } catch (e) {
287
+ // A failed registration must never take the page down.
288
+ console.warn("[nodality] agent surface: tool rejected", t.name, e);
289
+ }
290
+ }
291
+ }
292
+
293
+ return {
294
+ manifest, tools, registered,
295
+ destroy() {
296
+ if (reg && typeof reg.unregisterTool === "function") {
297
+ for (const t of tools) {
298
+ try { reg.unregisterTool(t.name); } catch (e) { /* already gone */ }
299
+ }
300
+ }
301
+ if (typeof document !== "undefined") {
302
+ const tag = document.getElementById(MANIFEST_ID);
303
+ if (tag) tag.remove();
304
+ }
305
+ },
306
+ };
307
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodality",
3
- "version": "1.1.14",
3
+ "version": "1.2.0",
4
4
  "description": "A lightweight library for declarative UI elements.",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",