nodality 1.1.8 → 1.1.10

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 (189) hide show
  1. package/bin/mcp-server.mjs +334 -0
  2. package/bin/nodality.js +16 -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.LICENSE.txt +1 -1
  14. package/dist/button.cjs.js.LICENSE.txt +1 -1
  15. package/dist/button.esm.js.LICENSE.txt +1 -1
  16. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  17. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  18. package/dist/center.cjs.js.LICENSE.txt +1 -1
  19. package/dist/center.esm.js.LICENSE.txt +1 -1
  20. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  21. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  22. package/dist/code.cjs.js.LICENSE.txt +1 -1
  23. package/dist/code.esm.js.LICENSE.txt +1 -1
  24. package/dist/container.cjs.js.LICENSE.txt +1 -1
  25. package/dist/container.esm.js.LICENSE.txt +1 -1
  26. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  27. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  28. package/dist/designer.cjs.js.LICENSE.txt +1 -1
  29. package/dist/designer.esm.js.LICENSE.txt +1 -1
  30. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  31. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  32. package/dist/finalresult.esm.js.LICENSE.txt +1 -1
  33. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  34. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  35. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  36. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  37. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  38. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  39. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  40. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  41. package/dist/free.cjs.js.LICENSE.txt +1 -1
  42. package/dist/free.esm.js.LICENSE.txt +1 -1
  43. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  44. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  45. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  46. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  47. package/dist/image.cjs.js.LICENSE.txt +1 -1
  48. package/dist/image.esm.js.LICENSE.txt +1 -1
  49. package/dist/index.cjs.js.LICENSE.txt +1 -1
  50. package/dist/index.d.ts +1 -1
  51. package/dist/index.esm.js.LICENSE.txt +1 -1
  52. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  53. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  54. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  55. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  56. package/dist/link.cjs.js.LICENSE.txt +1 -1
  57. package/dist/link.esm.js.LICENSE.txt +1 -1
  58. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  59. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  60. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  61. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  62. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  63. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  64. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  65. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  66. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  67. package/dist/picker.esm.js.LICENSE.txt +1 -1
  68. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  69. package/dist/progress.esm.js.LICENSE.txt +1 -1
  70. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  71. package/dist/radio.esm.js.LICENSE.txt +1 -1
  72. package/dist/range.cjs.js.LICENSE.txt +1 -1
  73. package/dist/range.esm.js.LICENSE.txt +1 -1
  74. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  75. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  76. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  77. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  78. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  79. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  80. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  81. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  82. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  83. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  84. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  85. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  86. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  87. package/dist/stack.esm.js.LICENSE.txt +1 -1
  88. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  89. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  90. package/dist/table.cjs.js.LICENSE.txt +1 -1
  91. package/dist/table.esm.js.LICENSE.txt +1 -1
  92. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  93. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  94. package/dist/text.cjs.js.LICENSE.txt +1 -1
  95. package/dist/text.esm.js.LICENSE.txt +1 -1
  96. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  97. package/dist/theme.esm.js.LICENSE.txt +1 -1
  98. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  99. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  100. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  101. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  102. package/dist/video.cjs.js.LICENSE.txt +1 -1
  103. package/dist/video.esm.js.LICENSE.txt +1 -1
  104. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  105. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  106. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  107. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  108. package/layout/animator.js +1 -1
  109. package/layout/audio.js +1 -1
  110. package/layout/audionew.js +1 -1
  111. package/layout/base.js +1 -1
  112. package/layout/beta-desktop-bar.js +1 -1
  113. package/layout/beta-mobile-bar.js +1 -1
  114. package/layout/button.js +1 -1
  115. package/layout/center.js +1 -1
  116. package/layout/checkbox.js +1 -1
  117. package/layout/circle.js +1 -1
  118. package/layout/code.js +1 -1
  119. package/layout/container.js +1 -1
  120. package/layout/dropdown-2025.js +1 -1
  121. package/layout/flex-card.js +1 -1
  122. package/layout/flex-grid.js +1 -1
  123. package/layout/flex-row.js +1 -1
  124. package/layout/form-components/custom.js +1 -1
  125. package/layout/form-components/data-list.js +1 -1
  126. package/layout/form-components/floating-input.js +1 -1
  127. package/layout/form-components/form-all.js +1 -1
  128. package/layout/form-components/form.js +1 -1
  129. package/layout/form-components/image-picker.js +1 -1
  130. package/layout/form-components/picker.js +1 -1
  131. package/layout/form-components/radio.js +1 -1
  132. package/layout/form-components/radiogroup.js +1 -1
  133. package/layout/form-components/range.js +1 -1
  134. package/layout/free.js +1 -1
  135. package/layout/grid-switcher.js +1 -1
  136. package/layout/grid.js +1 -1
  137. package/layout/horizontal-scroller.js +1 -1
  138. package/layout/image.js +1 -1
  139. package/layout/index.js +1 -1
  140. package/layout/link.js +1 -1
  141. package/layout/list.js +1 -1
  142. package/layout/meta-adder.js +1 -1
  143. package/layout/modal-2025.js +1 -1
  144. package/layout/morph.js +1 -1
  145. package/layout/multiswitcher.js +1 -1
  146. package/layout/nav-bar.js +1 -1
  147. package/layout/nav-factor/custom-div.js +1 -1
  148. package/layout/new-nav-bar.js +1 -1
  149. package/layout/polygon.js +1 -1
  150. package/layout/prerender-site.js +1 -1
  151. package/layout/prerender.js +1 -1
  152. package/layout/progress.js +1 -1
  153. package/layout/row.js +1 -1
  154. package/layout/scroll-video.js +1 -1
  155. package/layout/side-bar.js +1 -1
  156. package/layout/side-nav-bar.js +1 -1
  157. package/layout/simple-bar.js +1 -1
  158. package/layout/slider-2025.js +1 -1
  159. package/layout/spacer.js +1 -1
  160. package/layout/stack.js +1 -1
  161. package/layout/svg.js +1 -1
  162. package/layout/switcher.js +1 -1
  163. package/layout/table.js +1 -1
  164. package/layout/text-field.js +1 -1
  165. package/layout/text.js +1 -1
  166. package/layout/ulist.js +1 -1
  167. package/layout/video.js +1 -1
  168. package/layout/wrap.js +1 -1
  169. package/layout/zoom-card.js +1 -1
  170. package/lib/card-getter.js +1 -1
  171. package/lib/codegen.js +1 -1
  172. package/lib/data.js +1 -1
  173. package/lib/designer.js +1 -1
  174. package/lib/element-mapper.js +1 -1
  175. package/lib/keyframe-animation.js +1 -1
  176. package/lib/link-getter.js +1 -1
  177. package/lib/morph-node.js +1 -1
  178. package/lib/raster-inspect.js +1 -1
  179. package/lib/raster-ops.js +1 -1
  180. package/lib/raster-presets.js +1 -1
  181. package/lib/scroll-video.js +1 -1
  182. package/lib/seo.js +1 -1
  183. package/lib/stacker.js +1 -1
  184. package/lib/suggest.js +1 -1
  185. package/lib/theme.js +1 -1
  186. package/lib/transform-anim.js +1 -1
  187. package/lib/transition.js +1 -1
  188. package/lib/validate-nodes.js +247 -0
  189. package/package.json +2 -1
@@ -0,0 +1,247 @@
1
+ /*!
2
+ * nodality v1.1.10
3
+ * (c) 2026 Filip Vabrousek
4
+ * License: MIT
5
+ */
6
+
7
+ /*!
8
+ * validate-nodes.js — check an `N` array before it is applied.
9
+ *
10
+ * The library already validates an op DEFINITION when it is registered
11
+ * (`validateRasterOp`), which catches a bad op author. Nothing checked a
12
+ * node INSTANCE — the thing a page, or an agent, actually writes. A
13
+ * misspelled op name, a parameter that belongs to a different op, a
14
+ * driver that does not exist: all of these were silent, and a silent
15
+ * no-op is the worst possible failure for a generator that cannot see
16
+ * the screen.
17
+ *
18
+ * The report shape is deliberately identical to the one `layout/morph.js`
19
+ * `validate()` returns — `{ ok, errors: [{ code, path, got, suggestions,
20
+ * valid }] }` — so that a consumer, human or agent, learns one shape and
21
+ * reads every diagnostic this library produces with it.
22
+ *
23
+ * Suggestions are the point. An agent that writes "dithr" should be told
24
+ * "dither" and repair itself in one turn, rather than receiving nothing
25
+ * and concluding the effect simply does not work.
26
+ */
27
+
28
+ import {
29
+ REGISTRY, RASTER_OP_NAMES, DRIVER_NAMES, RASTER_UNITS,
30
+ FRAMEWORK_DOC, EASING_NAMES,
31
+ } from "./raster-ops.js";
32
+ import { didYouMean, suggest } from "./suggest.js";
33
+
34
+ /**
35
+ * Every key a morph node reads. Anything else on the node is ignored by
36
+ * the runtime, which is why it has to be reported here.
37
+ */
38
+ const MORPH_FIELDS = [
39
+ "op", "target", "from", "to",
40
+ "effect", "duration", "back", "live", "fade",
41
+ ];
42
+
43
+ /** Node families, told apart exactly the way the runtime tells them apart. */
44
+ const familyOf = (node) => {
45
+ if (!node || typeof node !== "object") return "invalid";
46
+ const op = node.op;
47
+ if (typeof op === "string") return op === "morph" ? "morph" : "raster";
48
+ if (op && typeof op === "object" && typeof op.name === "string") return "design";
49
+ return "invalid";
50
+ };
51
+
52
+ /**
53
+ * Check an array of nodes against the registry.
54
+ *
55
+ * @param {Array} nodes the `N` array
56
+ * @param {Array} [elements] the `E` array; when given, `target` and a
57
+ * morph's `from`/`to` are checked to name ids that actually exist,
58
+ * which is the other half of the "silent no-op" problem: a correctly
59
+ * spelled op aimed at an element that is not there does nothing at all.
60
+ */
61
+ export function validateNodes(nodes, elements) {
62
+ const errors = [];
63
+ const push = (code, path, got, suggestions, valid) => {
64
+ errors.push({
65
+ code, path, got,
66
+ suggestions: suggestions || [],
67
+ valid: valid || [],
68
+ });
69
+ };
70
+
71
+ if (!Array.isArray(nodes)) {
72
+ push("BAD_NODES", "nodes", nodes, [], ["an array of node objects"]);
73
+ return { ok: false, errors };
74
+ }
75
+
76
+ // Ids declared by E, including children, since `target` may name any
77
+ // of them. Absent E, id checking is skipped rather than guessed at.
78
+ const ids = [];
79
+ const walk = (list) => {
80
+ if (!Array.isArray(list)) return;
81
+ for (const el of list) {
82
+ if (!el || typeof el !== "object") continue;
83
+ if (typeof el.id === "string") ids.push(el.id);
84
+ walk(el.children);
85
+ }
86
+ };
87
+ if (elements !== undefined) walk(elements);
88
+ const idsKnown = elements !== undefined;
89
+
90
+ const checkTargets = (value, path) => {
91
+ if (value === undefined) return;
92
+ const list = Array.isArray(value) ? value : [value];
93
+ list.forEach((t, i) => {
94
+ const at = Array.isArray(value) ? `${path}[${i}]` : path;
95
+ if (typeof t !== "string") {
96
+ push("BAD_TARGET", at, t, [], ["an element id"]);
97
+ return;
98
+ }
99
+ // Ids are written bare in E and may be written with a leading
100
+ // "#" in a node; accept both rather than inventing a rule.
101
+ const bare = t.startsWith("#") ? t.slice(1) : t;
102
+ if (idsKnown && !ids.includes(bare)) {
103
+ push("UNKNOWN_TARGET", at, t, suggest(bare, ids), ids);
104
+ }
105
+ });
106
+ };
107
+
108
+ nodes.forEach((node, i) => {
109
+ const path = `nodes[${i}]`;
110
+ const family = familyOf(node);
111
+
112
+ if (family === "invalid") {
113
+ push("BAD_NODE", path, node, [],
114
+ ["{ op: \"<raster op>\" }", "{ op: { name: \"<design op>\" } }",
115
+ "{ op: \"morph\", from, to }"]);
116
+ return;
117
+ }
118
+
119
+ checkTargets(node.target, `${path}.target`);
120
+
121
+ if (family === "design") return; // design nodes carry their own vocabulary
122
+
123
+ if (family === "morph") {
124
+ // The morph node's own contract. `from` and `to` are what make
125
+ // it a morph; without them the node is inert.
126
+ if (typeof node.from !== "string") {
127
+ push("MISSING_FIELD", `${path}.from`, node.from, [],
128
+ ["the id of the element that morphs"]);
129
+ } else {
130
+ checkTargets(node.from, `${path}.from`);
131
+ }
132
+ if (node.to === undefined) {
133
+ push("MISSING_FIELD", `${path}.to`, undefined, [],
134
+ ["[\"id\", ...]", "{ \"Link label\": \"id\" }"]);
135
+ } else if (Array.isArray(node.to)) {
136
+ checkTargets(node.to, `${path}.to`);
137
+ } else if (node.to && typeof node.to === "object") {
138
+ for (const label in node.to) {
139
+ checkTargets(node.to[label], `${path}.to.${label}`);
140
+ }
141
+ } else {
142
+ push("BAD_FIELD", `${path}.to`, node.to, [],
143
+ ["an array of ids, or a map of link label to id"]);
144
+ }
145
+ if (node.duration !== undefined &&
146
+ (typeof node.duration !== "number" || !(node.duration > 0))) {
147
+ push("BAD_VALUE", `${path}.duration`, node.duration, [],
148
+ ["a positive number of milliseconds"]);
149
+ }
150
+ if (node.effect !== undefined &&
151
+ typeof node.effect !== "string" && !Array.isArray(node.effect)) {
152
+ push("BAD_FIELD", `${path}.effect`, node.effect, [],
153
+ ["a preset name, or an inline array of raster nodes"]);
154
+ }
155
+ // Unknown keys, checked here for the same reason they are
156
+ // checked on a raster node: a morph silently ignores what it
157
+ // does not recognise, so `duraton: 900` produces a transition
158
+ // that runs at the default speed and reports nothing. Found by
159
+ // driving the published server end to end, which is precisely
160
+ // the mistake an agent makes.
161
+ for (const key in node) {
162
+ if (MORPH_FIELDS.includes(key)) continue;
163
+ push("UNKNOWN_PARAM", `${path}.${key}`, key,
164
+ suggest(key, MORPH_FIELDS), MORPH_FIELDS);
165
+ }
166
+ return;
167
+ }
168
+
169
+ // ── raster nodes ────────────────────────────────────────────
170
+ const def = REGISTRY[node.op];
171
+ if (!def) {
172
+ push("UNKNOWN_OP", `${path}.op`, node.op,
173
+ suggest(node.op, RASTER_OP_NAMES), RASTER_OP_NAMES);
174
+ return; // parameters cannot be judged without a definition
175
+ }
176
+
177
+ const params = (def.doc && def.doc.params) || {};
178
+ const known = Object.keys(params);
179
+ const shared = Object.keys(FRAMEWORK_DOC);
180
+
181
+ for (const key in node) {
182
+ if (key === "op") continue;
183
+ if (shared.includes(key) || known.includes(key)) continue;
184
+ // A parameter that belongs to no op is a typo; one that belongs
185
+ // to a DIFFERENT op is the more interesting mistake, and the
186
+ // suggestion list covers both because it is drawn from this
187
+ // op's vocabulary plus the shared one.
188
+ push("UNKNOWN_PARAM", `${path}.${key}`, key,
189
+ suggest(key, known.concat(shared)), known.concat(shared));
190
+ }
191
+
192
+ if (node.by !== undefined && !DRIVER_NAMES.includes(node.by)) {
193
+ push("UNKNOWN_DRIVER", `${path}.by`, node.by,
194
+ suggest(node.by, DRIVER_NAMES), DRIVER_NAMES);
195
+ }
196
+ if (node.ease !== undefined && typeof node.ease === "string" &&
197
+ !EASING_NAMES.includes(node.ease)) {
198
+ push("UNKNOWN_EASING", `${path}.ease`, node.ease,
199
+ suggest(node.ease, EASING_NAMES), EASING_NAMES);
200
+ }
201
+ if (node.side !== undefined && !["old", "new"].includes(node.side)) {
202
+ push("BAD_VALUE", `${path}.side`, node.side,
203
+ suggest(String(node.side), ["old", "new"]), ["old", "new"]);
204
+ }
205
+ });
206
+
207
+ return { ok: errors.length === 0, errors };
208
+ }
209
+
210
+ /**
211
+ * The vocabulary, as data — what an agent needs before writing anything.
212
+ * Assembled from the registry rather than maintained separately, so it
213
+ * cannot drift from what the pipeline actually accepts.
214
+ */
215
+ export function describeOps() {
216
+ const ops = RASTER_OP_NAMES.map((name) => {
217
+ const def = REGISTRY[name] || {};
218
+ const doc = def.doc || {};
219
+ const params = doc.params || {};
220
+ return {
221
+ op: name,
222
+ stage: Array.isArray(def.stage) ? def.stage : [def.stage].filter(Boolean),
223
+ summary: doc.summary || "",
224
+ params: Object.keys(params).map((p) => ({
225
+ name: p,
226
+ default: params[p].default,
227
+ unit: params[p].unit,
228
+ structural: !!params[p].structural,
229
+ summary: params[p].summary || "",
230
+ })),
231
+ };
232
+ });
233
+ return {
234
+ ops,
235
+ shared: Object.keys(FRAMEWORK_DOC).map((k) => ({
236
+ name: k,
237
+ default: FRAMEWORK_DOC[k].default,
238
+ unit: FRAMEWORK_DOC[k].unit,
239
+ summary: FRAMEWORK_DOC[k].summary || "",
240
+ })),
241
+ drivers: DRIVER_NAMES,
242
+ easings: EASING_NAMES,
243
+ units: RASTER_UNITS,
244
+ };
245
+ }
246
+
247
+ export { didYouMean };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodality",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "A lightweight library for declarative UI elements.",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -28,6 +28,7 @@
28
28
  "./morph": "./layout/morph.js",
29
29
  "./presets": "./lib/raster-presets.js",
30
30
  "./raster": "./lib/raster-ops.js",
31
+ "./validate": "./lib/validate-nodes.js",
31
32
  "./inspect": "./lib/raster-inspect.js",
32
33
  "./examples/*": "./examples/*.js",
33
34
  "./transition": "./lib/transition.js",