nodality 1.2.11 → 1.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 (281) hide show
  1. package/bin/mcp-server.mjs +45 -0
  2. package/dist/animator.cjs.js +1 -1
  3. package/dist/animator.cjs.js.LICENSE.txt +1 -1
  4. package/dist/animator.esm.js +1 -1
  5. package/dist/animator.esm.js.LICENSE.txt +1 -1
  6. package/dist/audionew.cjs.js +1 -1
  7. package/dist/audionew.cjs.js.LICENSE.txt +1 -1
  8. package/dist/audionew.esm.js +1 -1
  9. package/dist/audionew.esm.js.LICENSE.txt +1 -1
  10. package/dist/base.cjs.js +1 -1
  11. package/dist/base.cjs.js.LICENSE.txt +1 -1
  12. package/dist/base.esm.js +1 -1
  13. package/dist/base.esm.js.LICENSE.txt +1 -1
  14. package/dist/beta-desktop-bar.cjs.js +1 -1
  15. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  16. package/dist/beta-desktop-bar.esm.js +1 -1
  17. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  18. package/dist/beta-mobile-bar.cjs.js +1 -1
  19. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  20. package/dist/beta-mobile-bar.esm.js +1 -1
  21. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  22. package/dist/bundle.umd.js +1 -1
  23. package/dist/bundle.umd.js.LICENSE.txt +1 -1
  24. package/dist/button.cjs.js +1 -1
  25. package/dist/button.cjs.js.LICENSE.txt +1 -1
  26. package/dist/button.esm.js +1 -1
  27. package/dist/button.esm.js.LICENSE.txt +1 -1
  28. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  29. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  30. package/dist/center.cjs.js +1 -1
  31. package/dist/center.cjs.js.LICENSE.txt +1 -1
  32. package/dist/center.esm.js +1 -1
  33. package/dist/center.esm.js.LICENSE.txt +1 -1
  34. package/dist/checkbox.cjs.js +1 -1
  35. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  36. package/dist/checkbox.esm.js +1 -1
  37. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  38. package/dist/code.cjs.js +1 -1
  39. package/dist/code.cjs.js.LICENSE.txt +1 -1
  40. package/dist/code.esm.js +1 -1
  41. package/dist/code.esm.js.LICENSE.txt +1 -1
  42. package/dist/container.cjs.js +1 -1
  43. package/dist/container.cjs.js.LICENSE.txt +1 -1
  44. package/dist/container.esm.js +1 -1
  45. package/dist/container.esm.js.LICENSE.txt +1 -1
  46. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  47. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  48. package/dist/designer.cjs.js +1 -1
  49. package/dist/designer.cjs.js.LICENSE.txt +1 -1
  50. package/dist/designer.esm.js +1 -1
  51. package/dist/designer.esm.js.LICENSE.txt +1 -1
  52. package/dist/element-mapper.cjs.js +1 -1
  53. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  54. package/dist/element-mapper.esm.js +1 -1
  55. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  56. package/dist/finalresult.esm.js +1 -1
  57. package/dist/finalresult.esm.js.LICENSE.txt +1 -1
  58. package/dist/flex-card.cjs.js +1 -1
  59. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  60. package/dist/flex-card.esm.js +1 -1
  61. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  62. package/dist/flex-grid.cjs.js +1 -1
  63. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  64. package/dist/flex-grid.esm.js +1 -1
  65. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  66. package/dist/flex-row.cjs.js +1 -1
  67. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  68. package/dist/flex-row.esm.js +1 -1
  69. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  70. package/dist/floating-input.cjs.js +1 -1
  71. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  72. package/dist/floating-input.esm.js +1 -1
  73. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  74. package/dist/free.cjs.js +1 -1
  75. package/dist/free.cjs.js.LICENSE.txt +1 -1
  76. package/dist/free.esm.js +1 -1
  77. package/dist/free.esm.js.LICENSE.txt +1 -1
  78. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  79. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  80. package/dist/image-picker.cjs.js +1 -1
  81. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  82. package/dist/image-picker.esm.js +1 -1
  83. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  84. package/dist/image.cjs.js +1 -1
  85. package/dist/image.cjs.js.LICENSE.txt +1 -1
  86. package/dist/image.esm.js +1 -1
  87. package/dist/image.esm.js.LICENSE.txt +1 -1
  88. package/dist/index.cjs.js +1 -1
  89. package/dist/index.cjs.js.LICENSE.txt +1 -1
  90. package/dist/index.d.ts +3 -2
  91. package/dist/index.esm.js +1 -1
  92. package/dist/index.esm.js.LICENSE.txt +1 -1
  93. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  94. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  95. package/dist/link-getter.cjs.js +1 -1
  96. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  97. package/dist/link-getter.esm.js +1 -1
  98. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  99. package/dist/link.cjs.js +1 -1
  100. package/dist/link.cjs.js.LICENSE.txt +1 -1
  101. package/dist/link.esm.js +1 -1
  102. package/dist/link.esm.js.LICENSE.txt +1 -1
  103. package/dist/meta-adder.cjs.js +1 -1
  104. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  105. package/dist/meta-adder.esm.js +1 -1
  106. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  107. package/dist/modal-2025.cjs.js +1 -1
  108. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  109. package/dist/modal-2025.esm.js +1 -1
  110. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  111. package/dist/multiswitcher.cjs.js +1 -1
  112. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  113. package/dist/multiswitcher.esm.js +1 -1
  114. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  115. package/dist/new-nav-bar.cjs.js +1 -1
  116. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  117. package/dist/new-nav-bar.esm.js +1 -1
  118. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  119. package/dist/picker.cjs.js +1 -1
  120. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  121. package/dist/picker.esm.js +1 -1
  122. package/dist/picker.esm.js.LICENSE.txt +1 -1
  123. package/dist/progress.cjs.js +1 -1
  124. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  125. package/dist/progress.esm.js +1 -1
  126. package/dist/progress.esm.js.LICENSE.txt +1 -1
  127. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  128. package/dist/radio.esm.js.LICENSE.txt +1 -1
  129. package/dist/range.cjs.js.LICENSE.txt +1 -1
  130. package/dist/range.esm.js.LICENSE.txt +1 -1
  131. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  132. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  133. package/dist/side-bar.cjs.js +1 -1
  134. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  135. package/dist/side-bar.esm.js +1 -1
  136. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  137. package/dist/side-nav-bar.cjs.js +1 -1
  138. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  139. package/dist/side-nav-bar.esm.js +1 -1
  140. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  141. package/dist/simple-bar.cjs.js +1 -1
  142. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  143. package/dist/simple-bar.esm.js +1 -1
  144. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  145. package/dist/slider-2025.cjs.js +1 -1
  146. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  147. package/dist/slider-2025.esm.js +1 -1
  148. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  149. package/dist/spacer.cjs.js +1 -1
  150. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  151. package/dist/spacer.esm.js +1 -1
  152. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  153. package/dist/stack.cjs.js +1 -1
  154. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  155. package/dist/stack.esm.js +1 -1
  156. package/dist/stack.esm.js.LICENSE.txt +1 -1
  157. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  158. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  159. package/dist/table.cjs.js +1 -1
  160. package/dist/table.cjs.js.LICENSE.txt +1 -1
  161. package/dist/table.esm.js +1 -1
  162. package/dist/table.esm.js.LICENSE.txt +1 -1
  163. package/dist/text-field.cjs.js +1 -1
  164. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  165. package/dist/text-field.esm.js +1 -1
  166. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  167. package/dist/text.cjs.js +1 -1
  168. package/dist/text.cjs.js.LICENSE.txt +1 -1
  169. package/dist/text.esm.js +1 -1
  170. package/dist/text.esm.js.LICENSE.txt +1 -1
  171. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  172. package/dist/theme.esm.js.LICENSE.txt +1 -1
  173. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  174. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  175. package/dist/ulist.cjs.js +1 -1
  176. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  177. package/dist/ulist.esm.js +1 -1
  178. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  179. package/dist/video.cjs.js +1 -1
  180. package/dist/video.cjs.js.LICENSE.txt +1 -1
  181. package/dist/video.esm.js +1 -1
  182. package/dist/video.esm.js.LICENSE.txt +1 -1
  183. package/dist/wrap.cjs.js +1 -1
  184. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  185. package/dist/wrap.esm.js +1 -1
  186. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  187. package/dist/zoom-card.cjs.js +1 -1
  188. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  189. package/dist/zoom-card.esm.js +1 -1
  190. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  191. package/layout/animator.js +15 -2
  192. package/layout/audio.js +1 -1
  193. package/layout/audionew.js +1 -1
  194. package/layout/base.js +1 -1
  195. package/layout/beta-desktop-bar.js +1 -1
  196. package/layout/beta-mobile-bar.js +1 -1
  197. package/layout/button.js +1 -1
  198. package/layout/center.js +1 -1
  199. package/layout/checkbox.js +1 -1
  200. package/layout/circle.js +1 -1
  201. package/layout/code.js +1 -1
  202. package/layout/container.js +1 -1
  203. package/layout/dropdown-2025.js +10 -3
  204. package/layout/flex-card.js +1 -1
  205. package/layout/flex-grid.js +1 -1
  206. package/layout/flex-row.js +1 -1
  207. package/layout/form-components/custom.js +1 -1
  208. package/layout/form-components/data-list.js +1 -1
  209. package/layout/form-components/floating-input.js +14 -1
  210. package/layout/form-components/form-all.js +1 -1
  211. package/layout/form-components/form.js +1 -1
  212. package/layout/form-components/image-picker.js +5 -3
  213. package/layout/form-components/picker.js +1 -1
  214. package/layout/form-components/radio.js +1 -1
  215. package/layout/form-components/radiogroup.js +1 -1
  216. package/layout/form-components/range.js +1 -1
  217. package/layout/free.js +1 -1
  218. package/layout/grid-switcher.js +1 -1
  219. package/layout/grid.js +1 -1
  220. package/layout/horizontal-scroller.js +1 -1
  221. package/layout/image.js +15 -2
  222. package/layout/index.js +1 -1
  223. package/layout/link.js +1 -1
  224. package/layout/list.js +1 -1
  225. package/layout/meta-adder.js +1 -1
  226. package/layout/modal-2025.js +1 -1
  227. package/layout/morph.js +1 -1
  228. package/layout/multiswitcher.js +1 -1
  229. package/layout/nav-bar.js +1 -1
  230. package/layout/nav-factor/custom-div.js +1 -1
  231. package/layout/new-nav-bar.js +1 -1
  232. package/layout/polygon.js +1 -1
  233. package/layout/prerender-site.js +1 -1
  234. package/layout/prerender.js +1 -1
  235. package/layout/progress.js +1 -1
  236. package/layout/row.js +1 -1
  237. package/layout/scroll-video.js +1 -1
  238. package/layout/side-bar.js +11 -3
  239. package/layout/side-nav-bar.js +16 -3
  240. package/layout/simple-bar.js +1 -1
  241. package/layout/slider-2025.js +1 -1
  242. package/layout/spacer.js +1 -1
  243. package/layout/stack.js +1 -1
  244. package/layout/svg.js +1 -1
  245. package/layout/switcher.js +1 -1
  246. package/layout/table.js +1 -1
  247. package/layout/text-field.js +16 -1
  248. package/layout/text.js +28 -1
  249. package/layout/ulist.js +1 -1
  250. package/layout/video.js +1 -1
  251. package/layout/wrap.js +1 -1
  252. package/layout/zoom-card.js +1 -1
  253. package/lib/agent-surface.js +1 -1
  254. package/lib/card-getter.js +1 -1
  255. package/lib/check-page.js +239 -0
  256. package/lib/codegen.js +1 -1
  257. package/lib/data.js +1 -1
  258. package/lib/designer.js +64 -2
  259. package/lib/element-mapper.js +113 -23
  260. package/lib/element-params.generated.js +2 -2
  261. package/lib/keyframe-animation.js +1 -1
  262. package/lib/link-getter.js +1 -1
  263. package/lib/morph-node.js +8 -1
  264. package/lib/normalize-spec.js +134 -0
  265. package/lib/parse-html.js +1 -1
  266. package/lib/raster-inspect.js +1 -1
  267. package/lib/raster-ops.js +1 -1
  268. package/lib/raster-presets.js +1 -1
  269. package/lib/resolve-refs.js +121 -0
  270. package/lib/scroll-video.js +1 -1
  271. package/lib/seo.js +1 -1
  272. package/lib/stacker.js +1 -1
  273. package/lib/suggest.js +1 -1
  274. package/lib/theme.js +1 -1
  275. package/lib/transform-anim.js +1 -1
  276. package/lib/transition.js +1 -1
  277. package/lib/validate-nodes.js +60 -3
  278. package/lib/webmcp-adapter.js +1 -1
  279. package/package.json +3 -1
  280. package/schema.json +211 -12
  281. package/skills/nodality/SKILL.md +50 -0
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/nav-bar.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/polygon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
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.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -52,8 +52,16 @@ class SideBar extends Animator{
52
52
  btn.style.background = "white";
53
53
  btn.style.opacity = 0.80;
54
54
  btn.style.border = "none";
55
- btn.width = "1.5em";
56
- btn.height = "1.5em";
55
+ // `btn.width = ...` sets a PROPERTY that <button> does not have, so
56
+ // this element had no size at all: 0x0 in the browser, impossible to
57
+ // hit and invisible to the checker's tap-target rule until it was
58
+ // measured. Style, and at least the 24px WCAG 2.2 minimum.
59
+ btn.style.width = "2rem";
60
+ btn.style.height = "2rem";
61
+ btn.style.minWidth = "24px";
62
+ btn.style.minHeight = "24px";
63
+ // A control with no text is a control with nothing to announce.
64
+ if (!btn.getAttribute("aria-label")) btn.setAttribute("aria-label", "Toggle navigation");
57
65
  btn.style.borderRadius = "50%";
58
66
  btn.style.zIndex = 2;
59
67
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -140,8 +140,16 @@ class SideNav extends Animator {
140
140
 
141
141
  createToggleButton(color) {
142
142
  const button = document.createElement("button");
143
- button.innerText = "☰";
143
+ // textContent, not innerText: jsdom has no innerText setter and the SSG
144
+ // prerenders through jsdom, so this glyph never reached a
145
+ // prerendered page — the toggle shipped as an empty button.
146
+ button.textContent = "☰";
144
147
  button.style.padding = "10px 20px";
148
+ // WCAG 2.2 AA asks for 24x24. These measured 40x20: wide enough,
149
+ // and two pixels short in the direction that matters on a phone.
150
+ button.style.minWidth = "24px";
151
+ button.style.minHeight = "24px";
152
+ button.style.lineHeight = "1";
145
153
  button.style.backgroundColor = "transparent";
146
154
  button.style.color = color ?? "#3498db";
147
155
  button.style.border = "none";
@@ -158,9 +166,14 @@ class SideNav extends Animator {
158
166
 
159
167
  createCloseButton(color) {
160
168
  const button = document.createElement("button");
161
- button.innerText = "×";
169
+ button.textContent = "×";
162
170
  button.style.fontSize = "1.6rem";
163
171
  button.style.padding = "10px 20px";
172
+ // WCAG 2.2 AA asks for 24x24. These measured 40x20: wide enough,
173
+ // and two pixels short in the direction that matters on a phone.
174
+ button.style.minWidth = "24px";
175
+ button.style.minHeight = "24px";
176
+ button.style.lineHeight = "1";
164
177
  button.style.backgroundColor = "transparent";
165
178
  button.style.color = color ?? "black";
166
179
  button.style.border = "none";
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
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.2.11
2
+ * nodality v1.3.0
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.2.11
2
+ * nodality v1.3.0
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.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
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.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -44,6 +44,21 @@ class TextField extends Animator {
44
44
  this.res.setAttribute("type", obj.type ?? "text");
45
45
  //@ placeholder: Placeholder text shown while the field is empty.
46
46
  obj.placeholder && (this.res.placeholder = obj.placeholder);
47
+
48
+ // width:100% plus horizontal padding is 100% PLUS the padding under
49
+ // the default content-box, so a full-width field pushed the page 20px
50
+ // wider than the viewport — the overflow measured exactly the padding.
51
+ this.res.style.boxSizing = "border-box";
52
+
53
+ //@ label: Accessible name for the field.
54
+ // A placeholder is NOT an accessible name: it disappears the moment
55
+ // anyone types, and assistive technology is not required to announce
56
+ // it. Take a real label where there is one and fall back to the
57
+ // placeholder, which is better than nothing at all.
58
+ if (!this.res.getAttribute("aria-label")) {
59
+ const name = obj.label ?? obj.title ?? obj.placeholder;
60
+ name && this.res.setAttribute("aria-label", String(name));
61
+ }
47
62
  //@ name: Form field name. What a submitted form actually SENDS.
48
63
  //@ id: DOM id on the input.
49
64
  //@ required: Marks the field required for native validation.
package/layout/text.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -228,6 +228,27 @@ this.res.style.position = "absolute";
228
228
  this.fluid(obj.fluid);
229
229
  }
230
230
 
231
+ //@ tag: Heading level to render, independent of `size`/`fluid`.
232
+ // Size and semantics used to be the same decision: `size: "S5"`
233
+ // produced an <h5> and there was no way to have one without the
234
+ // other. That is fine for a person choosing both at once and wrong
235
+ // for a generated page, where the document outline is structure and
236
+ // the type scale is style. A card title wants to LOOK like a card
237
+ // title and SIT at h3, and before this it could not.
238
+ //
239
+ // Additive: absent `tag`, the element is exactly what size chose.
240
+ if (obj.tag && /^(h[1-6]|p)$/.test(String(obj.tag))) {
241
+ const want = String(obj.tag).toLowerCase();
242
+ if (this.res && this.res.tagName.toLowerCase() !== want) {
243
+ const swapped = document.createElement(want);
244
+ for (const a of Array.from(this.res.attributes)) {
245
+ swapped.setAttribute(a.name, a.value);
246
+ }
247
+ while (this.res.firstChild) swapped.appendChild(this.res.firstChild);
248
+ this.res = swapped;
249
+ }
250
+ }
251
+
231
252
  obj.fluidc && (this.elCSS.push(`font-family: ${obj.font}; \n`));
232
253
  obj.align && (this.res.style.textAlign = obj.align);
233
254
 
@@ -348,6 +369,12 @@ addIcon(obj){
348
369
  img.style.marginLeft = "10px";
349
370
  img.style.height = "auto";
350
371
  img.setAttribute("src", obj.url);
372
+ //@ alt: Description for an icon that carries meaning; omit for decoration.
373
+ // An <img> with no alt at all is an unlabelled image to a screen reader,
374
+ // which is what the nav's dropdown chevron was. Empty alt is the correct
375
+ // answer for decoration: it says "skip this", rather than leaving the
376
+ // reader to announce a URL.
377
+ img.setAttribute("alt", obj.alt === undefined ? "" : String(obj.alt));
351
378
  this.res.appendChild(img);
352
379
  return this;
353
380
  }
package/layout/ulist.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/video.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
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.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -0,0 +1,239 @@
1
+ /*!
2
+ * nodality v1.3.0
3
+ * (c) 2026 Filip Vabrousek
4
+ * License: MIT
5
+ */
6
+
7
+ /**
8
+ * Tier 1 of AGENTIC-FIRST-PLAN.md §10 — what a page actually does.
9
+ *
10
+ * Everything else in this library checks the INPUT. `validate_nodes` checks
11
+ * vocabulary, the schema says what a type accepts, the parser says what a page
12
+ * was made from. None of them can tell an agent that the page it just produced
13
+ * overflows its viewport, clips its own text, or is unreadable. The loop is
14
+ * write-only: a spec can be perfect and the page still wrong.
15
+ *
16
+ * This closes it. The checks need REAL LAYOUT — geometry, computed styles,
17
+ * stacking — which jsdom does not have, so this drives a browser through
18
+ * Playwright. Playwright is not a dependency of this package, for the same
19
+ * reason jsdom is not: it is a build-time concern and bundling it would put a
20
+ * large download into every install that never renders anything. Its absence
21
+ * arrives as a report, never as a stack trace.
22
+ *
23
+ * The report shape is deliberately the one `validate_nodes` returns —
24
+ * `{ ok, errors: [{ code, path, got, suggestions, valid }] }` — because an
25
+ * agent should parse one thing. `path` is a CSS selector rather than a spec
26
+ * path: these are facts about the rendered page, and the selector is what
27
+ * locates them in it.
28
+ *
29
+ * WHAT IT DOES NOT DO. It does not judge whether a design is good. Every check
30
+ * here is a fact that can be measured and that a reasonable page does not
31
+ * exhibit — text outside its box, a target too small to hit, a heading level
32
+ * skipped. Taste is not in scope and should not be faked.
33
+ */
34
+
35
+ /** Viewports every page is checked at unless the caller says otherwise. */
36
+ export const DEFAULT_VIEWPORTS = [
37
+ { name: "mobile", width: 390, height: 844 },
38
+ { name: "desktop", width: 1280, height: 900 },
39
+ ];
40
+
41
+ /**
42
+ * The checks, as a function evaluated inside the page. Written as one string
43
+ * so it crosses the browser boundary intact; everything it needs is in scope
44
+ * there and nothing from this module is.
45
+ */
46
+ const IN_PAGE = () => {
47
+ const out = [];
48
+ const add = (code, path, got, detail) => out.push({ code, path, got, detail });
49
+
50
+ /** A short, stable selector for a node, so a report can be acted on. */
51
+ const sel = (el) => {
52
+ if (!el || !el.tagName) return "?";
53
+ if (el.id) return `#${CSS.escape(el.id)}`;
54
+ const tag = el.tagName.toLowerCase();
55
+ const cls = (el.getAttribute("class") || "").trim().split(/\s+/).filter(Boolean)[0];
56
+ const base = cls ? `${tag}.${CSS.escape(cls)}` : tag;
57
+ const sibs = el.parentElement
58
+ ? [...el.parentElement.children].filter((n) => n.tagName === el.tagName) : [];
59
+ return sibs.length > 1 ? `${base}:nth-of-type(${sibs.indexOf(el) + 1})` : base;
60
+ };
61
+
62
+ const all = [...document.body.querySelectorAll("*")];
63
+ const vw = window.innerWidth;
64
+
65
+ // 1. The page scrolls sideways. Almost never intended, and the single most
66
+ // common way a generated layout is visibly broken on a phone.
67
+ if (document.documentElement.scrollWidth > vw + 1) {
68
+ add("HORIZONTAL_OVERFLOW", "html", document.documentElement.scrollWidth,
69
+ `the document is ${document.documentElement.scrollWidth}px wide in a ${vw}px viewport`);
70
+ }
71
+
72
+ for (const el of all) {
73
+ const r = el.getBoundingClientRect();
74
+ if (!r.width && !r.height) continue;
75
+ const cs = getComputedStyle(el);
76
+ if (cs.display === "none" || cs.visibility === "hidden") continue;
77
+
78
+ // 2. An element sticking out past the right edge. Left/top are not
79
+ // checked: off-canvas menus and slide-in panels park there legitimately.
80
+ if (r.right > vw + 1 && cs.position !== "fixed") {
81
+ add("ELEMENT_OVERFLOWS_VIEWPORT", sel(el), Math.round(r.right),
82
+ `extends ${Math.round(r.right - vw)}px past the ${vw}px viewport`);
83
+ }
84
+
85
+ // 3. Content taller than its own box, with the overflow hidden — the
86
+ // signature of clipped text, which reads as missing content.
87
+ if ((cs.overflow === "hidden" || cs.overflowY === "hidden") &&
88
+ el.scrollHeight > el.clientHeight + 2 && el.clientHeight > 0 &&
89
+ (el.textContent || "").trim().length > 0) {
90
+ add("CONTENT_CLIPPED", sel(el), el.scrollHeight,
91
+ `content is ${el.scrollHeight}px in a ${el.clientHeight}px box with overflow hidden`);
92
+ }
93
+
94
+ // 4. An image nobody can describe. Covers the background-image form
95
+ // too, which is what this library's cards emit.
96
+ const tag = el.tagName.toLowerCase();
97
+ if (tag === "img" && !el.hasAttribute("alt")) {
98
+ add("IMAGE_WITHOUT_ALT", sel(el), el.getAttribute("src") || "", "no alt attribute");
99
+ }
100
+ if (el.getAttribute("role") === "img" && !el.getAttribute("aria-label")) {
101
+ add("IMAGE_WITHOUT_ALT", sel(el), "role=img", "role=img with no aria-label");
102
+ }
103
+
104
+ // 5. A target too small to hit. 24px is the WCAG 2.2 AA minimum.
105
+ const interactive = tag === "a" || tag === "button" ||
106
+ (tag === "input" && el.type !== "hidden") || el.getAttribute("role") === "button";
107
+ if (interactive && r.width > 0 && (r.width < 24 || r.height < 24)) {
108
+ add("TAP_TARGET_TOO_SMALL", sel(el), `${Math.round(r.width)}x${Math.round(r.height)}`,
109
+ "below the 24x24 minimum");
110
+ }
111
+
112
+ // 6. A control with nothing to announce.
113
+ if (interactive && !(el.textContent || "").trim() &&
114
+ !el.getAttribute("aria-label") && !el.querySelector("img[alt]:not([alt=''])")) {
115
+ add("CONTROL_WITHOUT_LABEL", sel(el), tag, "no text and no accessible name");
116
+ }
117
+ }
118
+
119
+ // 7. Heading levels that skip. A generated page is often read by machines
120
+ // before people, and the outline is the structure they read.
121
+ const heads = all.filter((e) => /^H[1-6]$/.test(e.tagName));
122
+ let prev = 0;
123
+ for (const h of heads) {
124
+ const lvl = Number(h.tagName[1]);
125
+ if (prev && lvl > prev + 1) {
126
+ add("HEADING_LEVEL_SKIPPED", sel(h), h.tagName,
127
+ `h${prev} is followed by ${h.tagName.toLowerCase()}`);
128
+ }
129
+ prev = lvl;
130
+ }
131
+
132
+ // 8. Text nobody can read. Only where a background colour is actually
133
+ // resolvable — guessing through an image would produce false alarms,
134
+ // and a false alarm here costs more than a miss.
135
+ const lum = (c) => {
136
+ const m = c.match(/[\d.]+/g);
137
+ if (!m || m.length < 3) return null;
138
+ if (m.length > 3 && Number(m[3]) === 0) return null;
139
+ const [r, g, b] = m.slice(0, 3).map((v) => {
140
+ const s = Number(v) / 255;
141
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
142
+ });
143
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
144
+ };
145
+ const bgOf = (el) => {
146
+ for (let n = el; n && n !== document.documentElement; n = n.parentElement) {
147
+ const cs = getComputedStyle(n);
148
+ if (cs.backgroundImage && cs.backgroundImage !== "none") return null;
149
+ const l = lum(cs.backgroundColor);
150
+ if (l !== null) return l;
151
+ }
152
+ return lum(getComputedStyle(document.body).backgroundColor);
153
+ };
154
+ for (const el of all) {
155
+ const own = [...el.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim());
156
+ if (!own) continue;
157
+ const cs = getComputedStyle(el);
158
+ if (cs.display === "none" || cs.visibility === "hidden") continue;
159
+ const fg = lum(cs.color), bg = bgOf(el);
160
+ if (fg === null || bg === null) continue;
161
+ const ratio = (Math.max(fg, bg) + 0.05) / (Math.min(fg, bg) + 0.05);
162
+ const size = parseFloat(cs.fontSize) || 16;
163
+ const large = size >= 24 || (size >= 18.66 && Number(cs.fontWeight) >= 700);
164
+ const need = large ? 3 : 4.5;
165
+ if (ratio < need) {
166
+ add("LOW_CONTRAST", sel(el), `${ratio.toFixed(2)}:1`,
167
+ `needs ${need}:1 at ${Math.round(size)}px`);
168
+ }
169
+ }
170
+ return out;
171
+ };
172
+
173
+ /**
174
+ * Check rendered HTML and return a report in the shape everything else here
175
+ * returns. `html` is a full document or a fragment; a fragment is wrapped.
176
+ */
177
+ export async function checkPage(html, opts = {}) {
178
+ const viewports = opts.viewports || DEFAULT_VIEWPORTS;
179
+ let chromium;
180
+ try {
181
+ ({ chromium } = await import("playwright"));
182
+ } catch (e) {
183
+ // The same arrangement `preview` uses for jsdom: a missing build-time
184
+ // package is a report, not a crash, and it names what to install.
185
+ return {
186
+ ok: false,
187
+ errors: [{
188
+ code: "MISSING_PEER_DEPENDENCY",
189
+ path: "check_page",
190
+ got: "playwright",
191
+ suggestions: ["npm install --save-dev playwright", "npx playwright install chromium"],
192
+ valid: [],
193
+ detail: "checking a page needs real layout — geometry and computed " +
194
+ "styles — which only a browser has. The library does not bundle " +
195
+ "one: it is needed for checking, not for rendering, and bundling " +
196
+ "it would put a large download into every install.",
197
+ }],
198
+ };
199
+ }
200
+
201
+ const doc = /<html[\s>]/i.test(html)
202
+ ? html
203
+ : `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`;
204
+
205
+ const browser = await chromium.launch();
206
+ const errors = [];
207
+ try {
208
+ for (const vp of viewports) {
209
+ const page = await browser.newPage({ viewport: { width: vp.width, height: vp.height } });
210
+ try {
211
+ await page.setContent(doc, { waitUntil: "load" });
212
+ const found = await page.evaluate(IN_PAGE);
213
+ for (const f of found) {
214
+ errors.push({
215
+ code: f.code,
216
+ path: f.path,
217
+ got: f.got,
218
+ suggestions: [],
219
+ valid: [],
220
+ detail: f.detail,
221
+ viewport: vp.name,
222
+ });
223
+ }
224
+ } finally { await page.close(); }
225
+ }
226
+ } finally { await browser.close(); }
227
+
228
+ // The same finding at two viewports is one problem, reported once, with
229
+ // the widths it was seen at. An agent repairing a page should see a list
230
+ // of problems, not a list of measurements.
231
+ const merged = new Map();
232
+ for (const e of errors) {
233
+ const key = `${e.code}|${e.path}|${e.detail}`;
234
+ if (merged.has(key)) merged.get(key).viewport += `, ${e.viewport}`;
235
+ else merged.set(key, { ...e });
236
+ }
237
+ const list = [...merged.values()];
238
+ return { ok: list.length === 0, errors: list, viewports: viewports.map((v) => v.name) };
239
+ }
package/lib/codegen.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.2.11
2
+ * nodality v1.3.0
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.2.11
2
+ * nodality v1.3.0
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */