nodality 1.3.4 → 1.3.6

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 (207) hide show
  1. package/dist/animator.cjs.js.LICENSE.txt +1 -1
  2. package/dist/animator.esm.js.LICENSE.txt +1 -1
  3. package/dist/audionew.cjs.js.LICENSE.txt +1 -1
  4. package/dist/audionew.esm.js.LICENSE.txt +1 -1
  5. package/dist/base.cjs.js.LICENSE.txt +1 -1
  6. package/dist/base.esm.js.LICENSE.txt +1 -1
  7. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  8. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  9. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  10. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  11. package/dist/bundle.umd.js +1 -1
  12. package/dist/bundle.umd.js.LICENSE.txt +1 -1
  13. package/dist/button.cjs.js.LICENSE.txt +1 -1
  14. package/dist/button.esm.js.LICENSE.txt +1 -1
  15. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  16. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  17. package/dist/center.cjs.js.LICENSE.txt +1 -1
  18. package/dist/center.esm.js.LICENSE.txt +1 -1
  19. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  20. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  21. package/dist/code.cjs.js.LICENSE.txt +1 -1
  22. package/dist/code.esm.js.LICENSE.txt +1 -1
  23. package/dist/container.cjs.js.LICENSE.txt +1 -1
  24. package/dist/container.esm.js.LICENSE.txt +1 -1
  25. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  26. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  27. package/dist/designer.cjs.js +1 -1
  28. package/dist/designer.cjs.js.LICENSE.txt +1 -1
  29. package/dist/designer.esm.js +1 -1
  30. package/dist/designer.esm.js.LICENSE.txt +1 -1
  31. package/dist/element-mapper.cjs.js +1 -1
  32. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  33. package/dist/element-mapper.esm.js +1 -1
  34. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  35. package/dist/finalresult.esm.js +1 -1
  36. package/dist/finalresult.esm.js.LICENSE.txt +1 -1
  37. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  38. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  39. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  40. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  41. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  42. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  43. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  44. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  45. package/dist/free.cjs.js.LICENSE.txt +1 -1
  46. package/dist/free.esm.js.LICENSE.txt +1 -1
  47. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  48. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  49. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  50. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  51. package/dist/image.cjs.js.LICENSE.txt +1 -1
  52. package/dist/image.esm.js.LICENSE.txt +1 -1
  53. package/dist/index.cjs.js +1 -1
  54. package/dist/index.cjs.js.LICENSE.txt +1 -1
  55. package/dist/index.d.ts +1 -1
  56. package/dist/index.esm.js +1 -1
  57. package/dist/index.esm.js.LICENSE.txt +1 -1
  58. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  59. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  60. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  61. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  62. package/dist/link.cjs.js.LICENSE.txt +1 -1
  63. package/dist/link.esm.js.LICENSE.txt +1 -1
  64. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  65. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  66. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  67. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  68. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  69. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  70. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  71. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  72. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  73. package/dist/picker.esm.js.LICENSE.txt +1 -1
  74. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  75. package/dist/progress.esm.js.LICENSE.txt +1 -1
  76. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  77. package/dist/radio.esm.js.LICENSE.txt +1 -1
  78. package/dist/range.cjs.js.LICENSE.txt +1 -1
  79. package/dist/range.esm.js.LICENSE.txt +1 -1
  80. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  81. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  82. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  83. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  84. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  85. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  86. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  87. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  88. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  89. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  90. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  91. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  92. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  93. package/dist/stack.esm.js.LICENSE.txt +1 -1
  94. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  95. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  96. package/dist/table.cjs.js.LICENSE.txt +1 -1
  97. package/dist/table.esm.js.LICENSE.txt +1 -1
  98. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  99. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  100. package/dist/text.cjs.js.LICENSE.txt +1 -1
  101. package/dist/text.esm.js.LICENSE.txt +1 -1
  102. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  103. package/dist/theme.esm.js.LICENSE.txt +1 -1
  104. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  105. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  106. package/dist/ulist.cjs.js +1 -1
  107. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  108. package/dist/ulist.esm.js +1 -1
  109. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  110. package/dist/video.cjs.js.LICENSE.txt +1 -1
  111. package/dist/video.esm.js.LICENSE.txt +1 -1
  112. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  113. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  114. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  115. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  116. package/layout/animator.js +1 -1
  117. package/layout/audio.js +1 -1
  118. package/layout/audionew.js +1 -1
  119. package/layout/base.js +1 -1
  120. package/layout/beta-desktop-bar.js +1 -1
  121. package/layout/beta-mobile-bar.js +1 -1
  122. package/layout/button.js +1 -1
  123. package/layout/center.js +1 -1
  124. package/layout/checkbox.js +1 -1
  125. package/layout/circle.js +1 -1
  126. package/layout/code.js +1 -1
  127. package/layout/container.js +1 -1
  128. package/layout/dropdown-2025.js +1 -1
  129. package/layout/flex-card.js +1 -1
  130. package/layout/flex-grid.js +1 -1
  131. package/layout/flex-row.js +1 -1
  132. package/layout/form-components/custom.js +1 -1
  133. package/layout/form-components/data-list.js +1 -1
  134. package/layout/form-components/floating-input.js +1 -1
  135. package/layout/form-components/form-all.js +1 -1
  136. package/layout/form-components/form.js +1 -1
  137. package/layout/form-components/image-picker.js +1 -1
  138. package/layout/form-components/picker.js +1 -1
  139. package/layout/form-components/radio.js +1 -1
  140. package/layout/form-components/radiogroup.js +1 -1
  141. package/layout/form-components/range.js +1 -1
  142. package/layout/free.js +1 -1
  143. package/layout/grid-switcher.js +1 -1
  144. package/layout/grid.js +1 -1
  145. package/layout/horizontal-scroller.js +1 -1
  146. package/layout/image.js +1 -1
  147. package/layout/index.js +1 -1
  148. package/layout/link.js +1 -1
  149. package/layout/list.js +1 -1
  150. package/layout/meta-adder.js +1 -1
  151. package/layout/modal-2025.js +1 -1
  152. package/layout/morph.js +1 -1
  153. package/layout/multiswitcher.js +1 -1
  154. package/layout/nav-bar.js +1 -1
  155. package/layout/nav-factor/custom-div.js +1 -1
  156. package/layout/new-nav-bar.js +1 -1
  157. package/layout/polygon.js +1 -1
  158. package/layout/prerender-site.js +1 -1
  159. package/layout/prerender.js +1 -1
  160. package/layout/progress.js +1 -1
  161. package/layout/row.js +1 -1
  162. package/layout/scroll-video.js +1 -1
  163. package/layout/side-bar.js +1 -1
  164. package/layout/side-nav-bar.js +1 -1
  165. package/layout/simple-bar.js +1 -1
  166. package/layout/slider-2025.js +1 -1
  167. package/layout/spacer.js +1 -1
  168. package/layout/stack.js +1 -1
  169. package/layout/svg.js +1 -1
  170. package/layout/switcher.js +1 -1
  171. package/layout/table.js +1 -1
  172. package/layout/text-field.js +1 -1
  173. package/layout/text.js +1 -1
  174. package/layout/ulist.js +16 -1
  175. package/layout/video.js +1 -1
  176. package/layout/wrap.js +1 -1
  177. package/layout/zoom-card.js +1 -1
  178. package/lib/agent-surface.js +1 -1
  179. package/lib/card-getter.js +1 -1
  180. package/lib/check-page.js +1 -1
  181. package/lib/codegen.js +1 -1
  182. package/lib/data.js +1 -1
  183. package/lib/designer.js +1 -1
  184. package/lib/element-mapper.js +98 -5
  185. package/lib/element-params.generated.js +1 -1
  186. package/lib/keyframe-animation.js +1 -1
  187. package/lib/link-getter.js +1 -1
  188. package/lib/morph-node.js +1 -1
  189. package/lib/normalize-spec.js +1 -1
  190. package/lib/parse-html.js +1 -1
  191. package/lib/raster-inspect.js +1 -1
  192. package/lib/raster-ops.js +1 -1
  193. package/lib/raster-presets.js +1 -1
  194. package/lib/resolve-refs.js +1 -1
  195. package/lib/scroll-video.js +1 -1
  196. package/lib/seo.js +1 -1
  197. package/lib/stacker.js +1 -1
  198. package/lib/suggest.js +1 -1
  199. package/lib/theme.js +1 -1
  200. package/lib/transform-anim.js +1 -1
  201. package/lib/transition.js +1 -1
  202. package/lib/validate-nodes.js +83 -2
  203. package/lib/webmcp-adapter.js +1 -1
  204. package/package.json +1 -1
  205. package/schema.json +609 -3264
  206. package/scripts/generate-schema.mjs +102 -12
  207. package/skills/nodality/SKILL.md +20 -2
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/row.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/spacer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/stack.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/svg.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/table.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/text.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/ulist.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -176,6 +176,21 @@ toCode(indent = 0) {
176
176
  const pad = " ".repeat(indent);
177
177
  let code = `${pad}new UList()`;
178
178
 
179
+ // The options, so the emitted source reproduces this instance. Without
180
+ // this the generator wrote `new UList()` and dropped every option that
181
+ // had been set on it -- and since Des.set() EXECUTES the emitted
182
+ // source, the page rendered from code that no longer described the
183
+ // list. The mapper was forwarding correctly the whole time; the loss
184
+ // was here, at the boundary the artefact is supposed to cross.
185
+ const opts = Object.fromEntries(
186
+ Object.entries(this.options || {}).filter(([, v]) =>
187
+ v !== undefined && v !== null && v !== ""),
188
+ );
189
+ if (Object.keys(opts).length) {
190
+ code += `\n${pad} .set(${JSON.stringify(opts, null, 2)
191
+ .split("\n").join(`\n${pad} `)})`;
192
+ }
193
+
179
194
  if (this.items && this.items.length) {
180
195
  code += `\n${pad} .setItems([\n` +
181
196
  this.items.map(c =>
package/layout/video.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/wrap.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/check-page.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/codegen.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/data.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/designer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -125,9 +125,35 @@ const navEntry = (i) =>
125
125
  // The failure this guards is silent: a composite handed content in the slot it
126
126
  // does not read emits its placeholder template, so the page renders, nothing
127
127
  // throws, and the content is simply gone.
128
+ // Kept byte-equal to `CONTENT_SLOT` in lib/validate-nodes.js, which cannot be
129
+ // imported here: this file pulls in every layout component and therefore a DOM,
130
+ // and the validator has to run in a bare Node process. Two copies is the price
131
+ // of that, and they had already drifted -- `stack` was declared here and not
132
+ // there -- so a unit test now pins them equal rather than trusting the comment.
133
+ //
134
+ // The per-type descriptions below are read by scripts/generate-schema.mjs. They
135
+ // live here because the mapper is what decides how a slot is read; before they
136
+ // existed the generator keyed descriptions by parameter NAME across every file
137
+ // it scanned, first writer winning, so the picker's `items` documentation was
138
+ // served for `cards`, `nav`, `sideNav`, `table` and `ulist` as well.
139
+ // Names whose meaning is decided by the element type, not by whichever file
140
+ // happened to document them first. See the SCOPED note in generate-schema.mjs.
141
+ //@scoped items
142
+ //@scoped type
143
+ //@scoped id
144
+ //@ type: The element type, which selects the mapper that builds it -- "h1", "cards", "form", and so on.
145
+ //@ id: This element's DOM id. Also what a design node's `target` names, and what `parse` recovers an element by.
146
+ //@ cards.items: The cards. Each entry is either a data object -- { title, link, img } -- or an ARRAY of element specs, which renders as one card containing them.
147
+ //@ nav.items: The links, each `{ title, link }` -- or a plain string.
148
+ //@ sideNav.items: The links, each `{ title, link }` -- or a plain string.
149
+ //@ table.items: The rows. Each entry is a `{ title, link }` or `{ text, url }` data object, a `[value, text]` pair, or an element spec.
150
+ //@ ulist.items: The list entries, each an element spec -- `{ type, ... }` -- or a plain string.
151
+ //@ dropdown.items: The choices, each a `[value, text]` pair -- or a plain string used as both.
152
+ //@ radio.items: The choices, each a plain string.
128
153
  const CONTENT_SLOT = Object.freeze({
129
154
  cards: "items", table: "items", ulist: "items",
130
155
  nav: "items", sideNav: "items",
156
+ dropdown: "items", picker: "items", radio: "items",
131
157
  row: "children", form: "children", stack: "children", wrap: "children",
132
158
  });
133
159
 
@@ -295,6 +321,17 @@ class ElementMapper { // 22:09:58 04/11/2024
295
321
  // return new Text("Hello").set({size: "S3"});
296
322
 
297
323
  return new FlexRow().set({
324
+ // Everything the author declared. Without it a row's
325
+ // options were the node lookups alone, every one of
326
+ // them `undefined` when no node targets this element,
327
+ // so FlexRow.toCode() -- which correctly strips
328
+ // undefined -- emitted `.set({})` and the emitted
329
+ // source carried nothing. The empty object was the
330
+ // symptom; this is the cause.
331
+ ...this.elOpts(el, [
332
+ "stroke", "gradient", "animation", "shadow", "span",
333
+ "backgroundOp", "marginOp", "transform", "filtera", "raster",
334
+ ]),
298
335
  stroke: this.filtero("blast", el.id, obj.customOptions),
299
336
  gradient: this.filtero("gradient", el.id, obj.customOptions),
300
337
  animation: this.filtero("animation", el.id, obj.customOptions),
@@ -1624,6 +1661,27 @@ let elo = new Polygon({ id: "hex" })
1624
1661
 
1625
1662
  return new Text(el.text || el.value)
1626
1663
  .set({
1664
+ // Everything the author declared, first, so that every option
1665
+ // Text reads is reachable from a spec. Hand-listing the
1666
+ // forwards left 36 of this type's 56 documented options
1667
+ // settable only on the emitted component -- `background`,
1668
+ // `align`, `width`, `opacity`, `radius` among them -- so a
1669
+ // page could declare one, validate clean, and render without
1670
+ // it. The note on `tag` below was one instance of that gap,
1671
+ // closed one option at a time; this closes the rest.
1672
+ //
1673
+ // The spread comes FIRST and every assignment below overrides
1674
+ // it, so nothing the mapper computes can be displaced by a
1675
+ // field of the same name. The drop list is the keys this
1676
+ // method derives from the NODE array: they belong to N, they
1677
+ // are resolved by `filtero` below, and letting E supply them
1678
+ // would both cross the two halves of the format and let an
1679
+ // empty lookup overwrite an author's value with `undefined`.
1680
+ ...this.elOpts(el, [
1681
+ "value", // constructor argument, above
1682
+ "stroke", "gradient", "animation", "shadow", "span",
1683
+ "backgroundOp", "marginOp", "transform", "filtera", "raster",
1684
+ ]),
1627
1685
  id: el.id,
1628
1686
  class: el.class,
1629
1687
  color: el.color,
@@ -1662,7 +1720,14 @@ let elo = new Polygon({ id: "hex" })
1662
1720
 
1663
1721
  return new Image(el.url)
1664
1722
  .set({
1665
- ...el,
1723
+ // elOpts rather than a raw `...el`: the raw spread put `type`,
1724
+ // `children` and `items` into the component's OPTIONS, where
1725
+ // they mean nothing, and -- now that toCode() serialises
1726
+ // options -- writes them into the emitted source as well. The
1727
+ // artefact is meant to be code a developer reads and owns, so
1728
+ // it should not carry the specification's own bookkeeping.
1729
+ ...this.elOpts(el),
1730
+ url: el.url, // skipped by elOpts; the raw spread carried it
1666
1731
  isFull:el.isFull,
1667
1732
  // index: obj.i + "",
1668
1733
  animation: this.filtero("animation", el.id, obj.customOptions),
@@ -1857,12 +1922,28 @@ const blast = this.filtero("blast", el.id, obj.customOptions);
1857
1922
 
1858
1923
 
1859
1924
 
1925
+ // The author's own options, serialised into the emitted source. This
1926
+ // mapper returns SOURCE rather than an instance, so there is nothing to
1927
+ // spread onto -- the equivalent of `...elOpts(el)` here is to write the
1928
+ // entries into the `.set({...})` literal being generated.
1929
+ //
1930
+ // They come AFTER the four template defaults and BEFORE the node ops, and
1931
+ // that order is the contract: a default is a starting point an author may
1932
+ // overrule, so `{type:"cards", gap:"2rem"}` wins over `gap:"1rem"`; a node
1933
+ // is the N half of the format addressing this element by id, so it wins
1934
+ // over E. Before this, every one of these keys was accepted by the
1935
+ // validator, advertised by the schema, and silently dropped on the floor.
1936
+ const authored = Object.entries(this.elOpts(obj.el, [
1937
+ "backgroundCard", // consumed above, to pick the template
1938
+ "gradient", "animation", "shadow", "marginOp", "filtera", "blast", "raster",
1939
+ ])).map(([k, v]) => `,\n ${JSON.stringify(k)}: ${JSON.stringify(v)}`).join("");
1940
+
1860
1941
  return ` new FlexGrid()
1861
1942
  .set({
1862
1943
  colat: "700px",
1863
1944
  wrap: true,
1864
1945
  align: "center",
1865
- gap: "1rem"
1946
+ gap: "1rem"${authored}
1866
1947
  ${
1867
1948
  gradient !== undefined ? `,\n gradient: ${JSON.stringify(gradient)}` : ""
1868
1949
  }${
@@ -2118,7 +2199,13 @@ static form(obj){
2118
2199
  // slice on a word. A caller-supplied `size` still passes through.
2119
2200
  const wrapper = new Wrapper(kind)
2120
2201
  .set({
2121
- ...el,
2202
+ // elOpts rather than a raw `...el`: the raw spread put `type`,
2203
+ // `children` and `items` into the component's OPTIONS, where
2204
+ // they mean nothing, and -- now that toCode() serialises
2205
+ // options -- writes them into the emitted source as well. The
2206
+ // artefact is meant to be code a developer reads and owns, so
2207
+ // it should not carry the specification's own bookkeeping.
2208
+ ...this.elOpts(el),
2122
2209
  font: el.font ?? "Arial",
2123
2210
  stroke: this.filtero("blast", el.id, obj.customOptions),
2124
2211
  gradient: this.filtero("gradient", el.id, obj.customOptions),
@@ -2158,7 +2245,13 @@ static form(obj){
2158
2245
  // output of getElType on a non-heading type.
2159
2246
  const list = new UList(kind)
2160
2247
  .set({
2161
- ...el,
2248
+ // elOpts rather than a raw `...el`: the raw spread put `type`,
2249
+ // `children` and `items` into the component's OPTIONS, where
2250
+ // they mean nothing, and -- now that toCode() serialises
2251
+ // options -- writes them into the emitted source as well. The
2252
+ // artefact is meant to be code a developer reads and owns, so
2253
+ // it should not carry the specification's own bookkeeping.
2254
+ ...this.elOpts(el),
2162
2255
  font: el.font ?? "Arial",
2163
2256
  stroke: this.filtero("blast", el.id, obj.customOptions),
2164
2257
  gradient: this.filtero("gradient", el.id, obj.customOptions),
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/morph-node.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/parse-html.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/raster-ops.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/seo.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/stacker.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/suggest.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/theme.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/transition.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.4
2
+ * nodality v1.3.6
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -158,9 +158,18 @@ const familyOf = (node) => {
158
158
  // be demonstrated is absent, and absent means silent: reporting a slot that
159
159
  // might work would stop `preview` rendering a page that renders, which 1.2.7
160
160
  // established is the costlier direction to be wrong in.
161
+ //
162
+ // Four types had that evidence and were missing anyway. `stack` was declared in
163
+ // the mapper's copy of this table and not in this one, so the two had already
164
+ // drifted -- which is what the comment on SPEC_ITEMS below warns about, one
165
+ // table over. `dropdown`, `picker` and `radio` read `items` as choices and were
166
+ // in neither. The omission was invisible to the drift test because the test
167
+ // iterated THIS map, so a type absent from it was a type it never checked; the
168
+ // test now iterates the element registry instead.
161
169
  export const CONTENT_SLOT = {
162
170
  cards: "items", nav: "items", sideNav: "items", table: "items", ulist: "items",
163
- row: "children", form: "children", wrap: "children",
171
+ dropdown: "items", picker: "items", radio: "items",
172
+ row: "children", form: "children", stack: "children", wrap: "children",
164
173
  };
165
174
 
166
175
  // Of the types that read `items`, which read the entries as ELEMENT SPECS
@@ -172,6 +181,41 @@ export const CONTENT_SLOT = {
172
181
  // drifted before 1.2.8.
173
182
  export const SPEC_ITEMS = new Set(["ulist"]);
174
183
 
184
+ // Of the types that read `items` but NOT as flat element specs, what an entry
185
+ // may be. Measured by rendering every shape against every type rather than read
186
+ // off the mappers, because the mappers disagree: `table` takes an element spec,
187
+ // `cards` takes one only inside a nested array, and `nav` takes neither.
188
+ //
189
+ // This guards the expensive kind of failure rather than the cheap one. An entry
190
+ // carrying `type` IS a well-formed element spec, so nothing here is a typo and
191
+ // near-miss detection cannot reach it: `cards` renders an empty card, `nav`
192
+ // drops the entry, both pages render, and the content is simply gone. That is
193
+ // the "validates and is wrong" case, and it is the one shape a generator
194
+ // reaches for first, because every other slot in the format takes element
195
+ // specs.
196
+ //
197
+ // `keys` are the fields that prove an entry was MEANT as data. A report fires
198
+ // only when none of them is present -- i.e. only when the entry cannot render
199
+ // anything at all -- because a false report does not annotate the page, it
200
+ // prevents it.
201
+ export const ITEM_SHAPE = {
202
+ cards: {
203
+ nest: true, keys: ["title", "link", "img"],
204
+ valid: 'a data object -- { title, link, img } -- or an ARRAY of element specs, which renders as one card containing them',
205
+ },
206
+ nav: {
207
+ nest: false, keys: ["title", "link"],
208
+ valid: '{ title, link }, or a plain string',
209
+ },
210
+ sideNav: {
211
+ nest: false, keys: ["title", "link"],
212
+ valid: '{ title, link }, or a plain string',
213
+ },
214
+ dropdown: { nest: false, keys: [], valid: 'a plain string, or a [value, text] pair' },
215
+ picker: { nest: false, keys: [], valid: 'a plain string, or a [value, text] pair' },
216
+ radio: { nest: false, keys: [], valid: 'a plain string' },
217
+ };
218
+
175
219
  export function validateNodes(nodes, elements, defs) {
176
220
  const errors = [];
177
221
  const push = (code, path, got, suggestions, valid) => {
@@ -433,6 +477,43 @@ export function validateNodes(nodes, elements, defs) {
433
477
  }
434
478
  });
435
479
  }
480
+
481
+ // Phase S8: an entry in the wrong SHAPE for a type that reads
482
+ // `items` as something other than element specs. The sibling
483
+ // check above walks entries that ARE specs; this one reports
484
+ // entries that are specs where a spec renders nothing.
485
+ //
486
+ // Fires only when the entry carries none of the fields the type
487
+ // reads, so an entry meant as data is never reported, and a
488
+ // `$ref` -- resolved later -- is left alone. The suggestion
489
+ // hands back the replacement rather than describing it, which
490
+ // 8.7.4 measured as the difference between a report a model can
491
+ // act on and one it cannot.
492
+ const shape = ITEM_SHAPE[el.type];
493
+ if (shape && Array.isArray(el.items)) {
494
+ el.items.forEach((entry, k) => {
495
+ const at = `${p}.items[${k}]`;
496
+ if (entry === null || typeof entry !== "object") return;
497
+ if (Array.isArray(entry)) return; // nested spec, or a [value, text] pair
498
+ if (isRef(entry)) return;
499
+ if (shape.keys.some((key) => entry[key] !== undefined)) return;
500
+
501
+ const text = ["text", "title", "label"]
502
+ .map((key) => entry[key]).find((v) => typeof v === "string");
503
+ const suggestions = [];
504
+ if (shape.nest && typeof entry.type === "string") {
505
+ suggestions.push(JSON.stringify([entry]));
506
+ } else if (shape.keys.length) {
507
+ suggestions.push(JSON.stringify({
508
+ title: text === undefined ? "..." : text,
509
+ link: entry.url || entry.link || "#",
510
+ }));
511
+ } else if (text !== undefined) {
512
+ suggestions.push(JSON.stringify(text));
513
+ }
514
+ push("WRONG_ITEM_SHAPE", at, entry, suggestions, [shape.valid]);
515
+ });
516
+ }
436
517
  });
437
518
  };
438
519
  walkEls(toWalk, "elements");