nodality 1.3.14 → 1.3.16

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 (218) hide show
  1. package/bin/install-skill.mjs +3 -3
  2. package/bin/mcp-server.mjs +46 -1
  3. package/bin/nodality.mjs +104 -1
  4. package/dist/animator.cjs.js.LICENSE.txt +1 -1
  5. package/dist/animator.esm.js.LICENSE.txt +1 -1
  6. package/dist/audionew.cjs.js.LICENSE.txt +1 -1
  7. package/dist/audionew.esm.js.LICENSE.txt +1 -1
  8. package/dist/base.cjs.js.LICENSE.txt +1 -1
  9. package/dist/base.esm.js.LICENSE.txt +1 -1
  10. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  11. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  12. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  13. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  14. package/dist/bundle.umd.js +1 -1
  15. package/dist/bundle.umd.js.LICENSE.txt +1 -1
  16. package/dist/button.cjs.js.LICENSE.txt +1 -1
  17. package/dist/button.esm.js.LICENSE.txt +1 -1
  18. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  19. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  20. package/dist/center.cjs.js.LICENSE.txt +1 -1
  21. package/dist/center.esm.js.LICENSE.txt +1 -1
  22. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  23. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  24. package/dist/code.cjs.js.LICENSE.txt +1 -1
  25. package/dist/code.esm.js.LICENSE.txt +1 -1
  26. package/dist/container.cjs.js.LICENSE.txt +1 -1
  27. package/dist/container.esm.js.LICENSE.txt +1 -1
  28. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  29. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  30. package/dist/designer.cjs.js +1 -1
  31. package/dist/designer.cjs.js.LICENSE.txt +1 -1
  32. package/dist/designer.esm.js +1 -1
  33. package/dist/designer.esm.js.LICENSE.txt +1 -1
  34. package/dist/element-mapper.cjs.js +1 -1
  35. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  36. package/dist/element-mapper.esm.js +1 -1
  37. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  38. package/dist/finalresult.esm.js +2 -2
  39. package/dist/finalresult.esm.js.LICENSE.txt +1 -1
  40. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  41. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  42. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  43. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  44. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  45. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  46. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  47. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  48. package/dist/free.cjs.js.LICENSE.txt +1 -1
  49. package/dist/free.esm.js.LICENSE.txt +1 -1
  50. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  51. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  52. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  53. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  54. package/dist/image.cjs.js.LICENSE.txt +1 -1
  55. package/dist/image.esm.js.LICENSE.txt +1 -1
  56. package/dist/index.cjs.js +1 -1
  57. package/dist/index.cjs.js.LICENSE.txt +1 -1
  58. package/dist/index.d.ts +20 -1
  59. package/dist/index.esm.js +2 -2
  60. package/dist/index.esm.js.LICENSE.txt +1 -1
  61. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  62. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  63. package/dist/link-getter.cjs.js +1 -1
  64. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  65. package/dist/link-getter.esm.js +1 -1
  66. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  67. package/dist/link.cjs.js +1 -1
  68. package/dist/link.cjs.js.LICENSE.txt +1 -1
  69. package/dist/link.esm.js +1 -1
  70. package/dist/link.esm.js.LICENSE.txt +1 -1
  71. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  72. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  73. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  74. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  75. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  76. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  77. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  78. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  79. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  80. package/dist/picker.esm.js.LICENSE.txt +1 -1
  81. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  82. package/dist/progress.esm.js.LICENSE.txt +1 -1
  83. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  84. package/dist/radio.esm.js.LICENSE.txt +1 -1
  85. package/dist/range.cjs.js.LICENSE.txt +1 -1
  86. package/dist/range.esm.js.LICENSE.txt +1 -1
  87. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  88. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  89. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  90. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  91. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  92. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  93. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  94. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  95. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  96. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  97. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  98. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  99. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  100. package/dist/stack.esm.js.LICENSE.txt +1 -1
  101. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  102. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  103. package/dist/table.cjs.js.LICENSE.txt +1 -1
  104. package/dist/table.esm.js.LICENSE.txt +1 -1
  105. package/dist/text-field.cjs.js +1 -1
  106. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  107. package/dist/text-field.esm.js +1 -1
  108. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  109. package/dist/text.cjs.js.LICENSE.txt +1 -1
  110. package/dist/text.esm.js.LICENSE.txt +1 -1
  111. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  112. package/dist/theme.esm.js.LICENSE.txt +1 -1
  113. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  114. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  115. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  116. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  117. package/dist/video.cjs.js.LICENSE.txt +1 -1
  118. package/dist/video.esm.js.LICENSE.txt +1 -1
  119. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  120. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  121. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  122. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  123. package/layout/animator.js +1 -1
  124. package/layout/audio.js +1 -1
  125. package/layout/audionew.js +1 -1
  126. package/layout/base.js +1 -1
  127. package/layout/beta-desktop-bar.js +1 -1
  128. package/layout/beta-mobile-bar.js +1 -1
  129. package/layout/button.js +1 -1
  130. package/layout/center.js +1 -1
  131. package/layout/checkbox.js +1 -1
  132. package/layout/circle.js +1 -1
  133. package/layout/code.js +1 -1
  134. package/layout/container.js +1 -1
  135. package/layout/dropdown-2025.js +1 -1
  136. package/layout/flex-card.js +1 -1
  137. package/layout/flex-grid.js +1 -1
  138. package/layout/flex-row.js +1 -1
  139. package/layout/form-components/custom.js +1 -1
  140. package/layout/form-components/data-list.js +1 -1
  141. package/layout/form-components/floating-input.js +1 -1
  142. package/layout/form-components/form-all.js +1 -1
  143. package/layout/form-components/form.js +9 -2
  144. package/layout/form-components/image-picker.js +1 -1
  145. package/layout/form-components/picker.js +1 -1
  146. package/layout/form-components/radio.js +1 -1
  147. package/layout/form-components/radiogroup.js +1 -1
  148. package/layout/form-components/range.js +1 -1
  149. package/layout/free.js +1 -1
  150. package/layout/grid-overlay.js +1 -1
  151. package/layout/grid-switcher.js +1 -1
  152. package/layout/grid.js +1 -1
  153. package/layout/horizontal-scroller.js +1 -1
  154. package/layout/image.js +1 -1
  155. package/layout/index.js +5 -1
  156. package/layout/link.js +6 -3
  157. package/layout/list.js +1 -1
  158. package/layout/meta-adder.js +1 -1
  159. package/layout/modal-2025.js +1 -1
  160. package/layout/morph.js +1 -1
  161. package/layout/multiswitcher.js +1 -1
  162. package/layout/nav-bar.js +1 -1
  163. package/layout/nav-factor/custom-div.js +1 -1
  164. package/layout/new-nav-bar.js +1 -1
  165. package/layout/polygon.js +1 -1
  166. package/layout/prerender-site.js +1 -1
  167. package/layout/prerender.js +1 -1
  168. package/layout/progress.js +1 -1
  169. package/layout/row.js +1 -1
  170. package/layout/scroll-video.js +1 -1
  171. package/layout/shop.js +261 -0
  172. package/layout/side-bar.js +1 -1
  173. package/layout/side-nav-bar.js +1 -1
  174. package/layout/simple-bar.js +1 -1
  175. package/layout/slider-2025.js +1 -1
  176. package/layout/spacer.js +1 -1
  177. package/layout/stack.js +1 -1
  178. package/layout/svg.js +1 -1
  179. package/layout/switcher.js +1 -1
  180. package/layout/table.js +1 -1
  181. package/layout/text-field.js +4 -2
  182. package/layout/text.js +1 -1
  183. package/layout/ulist.js +1 -1
  184. package/layout/video.js +1 -1
  185. package/layout/wrap.js +1 -1
  186. package/layout/zoom-card.js +1 -1
  187. package/lib/agent-surface.js +24 -2
  188. package/lib/card-getter.js +1 -1
  189. package/lib/check-page.js +37 -5
  190. package/lib/codegen.js +1 -1
  191. package/lib/data.js +1 -1
  192. package/lib/designer.js +3 -2
  193. package/lib/element-mapper.js +118 -1
  194. package/lib/element-params.generated.js +4 -4
  195. package/lib/grid-geometry.js +1 -1
  196. package/lib/keyframe-animation.js +1 -1
  197. package/lib/known-inert.js +85 -0
  198. package/lib/link-getter.js +1 -1
  199. package/lib/morph-node.js +1 -1
  200. package/lib/normalize-spec.js +1 -1
  201. package/lib/parse-html.js +1 -1
  202. package/lib/raster-inspect.js +1 -1
  203. package/lib/raster-ops.js +1 -1
  204. package/lib/raster-presets.js +1 -1
  205. package/lib/resolve-refs.js +1 -1
  206. package/lib/scroll-video.js +1 -1
  207. package/lib/seo.js +1 -1
  208. package/lib/stacker.js +1 -1
  209. package/lib/suggest.js +1 -1
  210. package/lib/theme.js +1 -1
  211. package/lib/transform-anim.js +1 -1
  212. package/lib/transition.js +1 -1
  213. package/lib/validate-nodes.js +55 -4
  214. package/lib/webmcp-adapter.js +36 -1
  215. package/package.json +1 -1
  216. package/schema.json +3477 -816
  217. package/scripts/generate-schema.mjs +7 -0
  218. package/skills/nodality/SKILL.md +11 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.14
2
+ * nodality v1.3.16
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.14
2
+ * nodality v1.3.16
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.14
2
+ * nodality v1.3.16
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.14
2
+ * nodality v1.3.16
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.14
2
+ * nodality v1.3.16
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.14
2
+ * nodality v1.3.16
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.14
2
+ * nodality v1.3.16
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.14
2
+ * nodality v1.3.16
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.14
2
+ * nodality v1.3.16
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.14
2
+ * nodality v1.3.16
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.14
2
+ * nodality v1.3.16
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.14
2
+ * nodality v1.3.16
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.14
2
+ * nodality v1.3.16
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.14
2
+ * nodality v1.3.16
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.14
2
+ * nodality v1.3.16
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.14
2
+ * nodality v1.3.16
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -142,6 +142,7 @@ function shapeFor(unit) {
142
142
  return SHAPES[unit] || null;
143
143
  }
144
144
  import { didYouMean, suggest, levenshtein } from "./suggest.js";
145
+ import { KNOWN_INERT } from "./known-inert.js";
145
146
 
146
147
  /**
147
148
  * Every key a morph node reads. Anything else on the node is ignored by
@@ -167,7 +168,7 @@ const EDGE_FIELDS = [
167
168
  * what it exposes is DERIVED, so there is nothing here to describe the
168
169
  * tools with — only which parts of the page may be reached.
169
170
  */
170
- const AGENT_SURFACE_FIELDS = ["op", "name", "forms", "exclude"];
171
+ const AGENT_SURFACE_FIELDS = ["op", "name", "forms", "exclude", "products"];
171
172
 
172
173
  /**
173
174
  * Design ops that may be written as a BARE STRING, `{ op: "gradient" }`,
@@ -277,6 +278,7 @@ export const CONTENT_SLOT = {
277
278
  cards: "items", nav: "items", sideNav: "items", table: "items", ulist: "items",
278
279
  dropdown: "items", picker: "items", radio: "items",
279
280
  row: "children", form: "children", stack: "children", wrap: "children",
281
+ product: "children",
280
282
  };
281
283
 
282
284
  // Of the types that read `items`, which read the entries as ELEMENT SPECS
@@ -417,6 +419,36 @@ export function validateNodes(nodes, elements, defs) {
417
419
  ? resolveRefs(elements, defs)
418
420
  : elements;
419
421
 
422
+ // Commerce order. A `product` looks for the page's `store` the moment
423
+ // it is attached; Des attaches in array order, so a product rendered
424
+ // before the store stays empty, and the only report is a console error
425
+ // in the browser. Found by building a product page with the store at the
426
+ // end, where it looked tidy.
427
+ if (Array.isArray(toWalk)) {
428
+ let storeAt = null;
429
+ const products = [];
430
+ const visit = (list, base) => {
431
+ if (!Array.isArray(list)) return;
432
+ list.forEach((el, i) => {
433
+ if (!el || typeof el !== "object") return;
434
+ const p = `${base}[${i}]`;
435
+ if (el.type === "store" && storeAt === null) storeAt = p;
436
+ if (el.type === "product") products.push({ p, before: storeAt === null });
437
+ visit(el.children, `${p}.children`);
438
+ });
439
+ };
440
+ visit(toWalk, "elements");
441
+ if (products.length && storeAt === null) {
442
+ warn("NO_STORE", products[0].p, "product", ["{ type: \"store\", domain: \"https://your-shop.example\" }"],
443
+ ["a store element placed before the first product"]);
444
+ } else {
445
+ for (const { p } of products.filter((x) => x.before)) {
446
+ warn("STORE_AFTER_PRODUCT", p, "product", [`move ${storeAt} before this product`],
447
+ ["the store element first: a product finds its store when it is attached"]);
448
+ }
449
+ }
450
+ }
451
+
420
452
  // An alias and its canonical name both present, disagreeing: one of them
421
453
  // is going to be ignored. The author hears it here rather than finding it
422
454
  // in the page.
@@ -524,6 +556,18 @@ export function validateNodes(nodes, elements, defs) {
524
556
  `"${el.type}" does not read "${key}" — it is accepted and ignored`);
525
557
  }
526
558
 
559
+ // Advertised by the schema for this type, and known to
560
+ // do nothing (the conformance baseline). Same failure
561
+ // as above — accepted, ignored — but the name IS in
562
+ // the type's vocabulary, so only this list knows.
563
+ if (KNOWN_INERT.has(`${el.type}.${key}`)) {
564
+ warn("PARAM_INERT", `${p}.${key}`, key, [],
565
+ [key === "size" && /^(h[1-6]|p)$/.test(el.type)
566
+ ? "the element type sets the scale step; use `tag` for the heading level and keySet fontSize for the size"
567
+ : `see \`npx nodality schema ${el.type}\`; wrap the element in a \`wrap\` to style or place it`],
568
+ `"${el.type}" lists "${key}" but ignores it — a known gap, recorded in lib/known-inert.js`);
569
+ }
570
+
527
571
  // A value the parameter cannot use. Checked only where
528
572
  // the source declares a shape, so this grows with the
529
573
  // annotations and never guesses.
@@ -728,11 +772,13 @@ export function validateNodes(nodes, elements, defs) {
728
772
  // form of the silent no-op, and an agent has no way to see
729
773
  // that the tool it expected was never registered.
730
774
  const formIds = [];
775
+ const productIds = [];
731
776
  const walkForms = (list) => {
732
777
  if (!Array.isArray(list)) return;
733
778
  for (const el of list) {
734
779
  if (!el || typeof el !== "object") continue;
735
780
  if (el.type === "form" && typeof el.id === "string") formIds.push(el.id);
781
+ if (el.type === "product" && typeof el.id === "string") productIds.push(el.id.replace(/^#/, ""));
736
782
  walkForms(el.children);
737
783
  }
738
784
  };
@@ -743,7 +789,7 @@ export function validateNodes(nodes, elements, defs) {
743
789
  ["a string used to prefix every derived tool name"]);
744
790
  }
745
791
 
746
- for (const key of ["forms", "exclude"]) {
792
+ for (const key of ["forms", "exclude", "products"]) {
747
793
  if (node[key] === undefined) continue;
748
794
  if (!Array.isArray(node[key])) {
749
795
  push("BAD_FIELD", `${path}.${key}`, node[key], [], ["an array of ids"]);
@@ -763,6 +809,10 @@ export function validateNodes(nodes, elements, defs) {
763
809
  if (idsKnown && !formIds.includes(bare)) {
764
810
  push("UNKNOWN_FORM", at, v, suggest(bare, formIds), formIds);
765
811
  }
812
+ } else if (key === "products") {
813
+ if (idsKnown && !productIds.includes(bare)) {
814
+ push("UNKNOWN_PRODUCT", at, v, suggest(bare, productIds), productIds);
815
+ }
766
816
  } else if (idsKnown && !ids.includes(bare)) {
767
817
  push("UNKNOWN_STATE", at, v, suggest(bare, ids), ids);
768
818
  }
@@ -775,7 +825,8 @@ export function validateNodes(nodes, elements, defs) {
775
825
  // in the expectation of more says so.
776
826
  const hasChain = nodes.some((n) => n && n.op === "morph");
777
827
  const hasForms = Array.isArray(node.forms) && node.forms.length > 0;
778
- if (!hasChain && !hasForms) {
828
+ const hasProducts = Array.isArray(node.products) && node.products.length > 0;
829
+ if (!hasChain && !hasForms && !hasProducts) {
779
830
  push("EMPTY_SURFACE", path, node, [],
780
831
  ["add a { op: \"morph\" } node, or name a form in `forms`"]);
781
832
  }
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.14
2
+ * nodality v1.3.16
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -265,6 +265,41 @@ export function installAgentSurface({ mount, elements, nodes, morphs }) {
265
265
 
266
266
  read: async () => ({ ok: true, view: currentView(), ...readSubtree(currentSubtree()) }),
267
267
 
268
+ // What the product box shows now: the live rendering once Shopify has
269
+ // answered (its placeholder is hidden then), the static copy before.
270
+ product: async (args, tool) => {
271
+ const box = mount && mount.querySelector(`[id="${tool.productId}"]`);
272
+ if (!box) return refuse("NO_PRODUCT", tool.productId, []);
273
+ const ph = box.querySelector("[shopify-loading-placeholder]");
274
+ const live = ph && typeof window !== "undefined" && window.getComputedStyle(ph).display === "none";
275
+ // Its own reader, not readSubtree: a price and its options sit in
276
+ // inline elements and in Shopify's (open) shadow DOM, which a
277
+ // reader of headings and paragraphs never reaches. Visible text in
278
+ // document order; the template, scripts, styles and anything hidden
279
+ // — the placeholder once the live copy is up — are skipped.
280
+ const parts = [];
281
+ const actions = [];
282
+ const walk = (n) => {
283
+ for (const c of n.childNodes) {
284
+ if (c.nodeType === 3) {
285
+ const t = c.textContent.replace(/\s+/g, " ").trim();
286
+ if (t) parts.push(t);
287
+ } else if (c.nodeType === 1) {
288
+ if (/^(TEMPLATE|SCRIPT|STYLE)$/.test(c.tagName)) continue;
289
+ if (window.getComputedStyle(c).display === "none") continue;
290
+ if (/^(A|BUTTON)$/.test(c.tagName)) {
291
+ const t = c.textContent.replace(/\s+/g, " ").trim();
292
+ if (t && !actions.includes(t)) actions.push(t);
293
+ }
294
+ if (c.shadowRoot) walk(c.shadowRoot);
295
+ walk(c);
296
+ }
297
+ }
298
+ };
299
+ walk(box);
300
+ return { ok: true, product: tool.productId, handle: box.getAttribute("handle"), live: !!live, text: parts.join("\n"), actions };
301
+ },
302
+
268
303
  submit: async (args = {}, tool) => {
269
304
  const form = findForm(tool.formId);
270
305
  if (!form) return refuse("NO_FORM", tool.formId, []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodality",
3
- "version": "1.3.14",
3
+ "version": "1.3.16",
4
4
  "description": "A lightweight library for declarative UI elements.",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",