nodality 1.3.10 → 1.3.12

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 (285) hide show
  1. package/dist/animator.cjs.js +1 -1
  2. package/dist/animator.cjs.js.LICENSE.txt +1 -1
  3. package/dist/animator.esm.js +1 -1
  4. package/dist/animator.esm.js.LICENSE.txt +1 -1
  5. package/dist/audionew.cjs.js +1 -1
  6. package/dist/audionew.cjs.js.LICENSE.txt +1 -1
  7. package/dist/audionew.esm.js +1 -1
  8. package/dist/audionew.esm.js.LICENSE.txt +1 -1
  9. package/dist/base.cjs.js +1 -1
  10. package/dist/base.cjs.js.LICENSE.txt +1 -1
  11. package/dist/base.esm.js +1 -1
  12. package/dist/base.esm.js.LICENSE.txt +1 -1
  13. package/dist/beta-desktop-bar.cjs.js +1 -1
  14. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  15. package/dist/beta-desktop-bar.esm.js +1 -1
  16. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  17. package/dist/beta-mobile-bar.cjs.js +1 -1
  18. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  19. package/dist/beta-mobile-bar.esm.js +1 -1
  20. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  21. package/dist/bundle.umd.js +1 -1
  22. package/dist/bundle.umd.js.LICENSE.txt +1 -1
  23. package/dist/button.cjs.js +1 -1
  24. package/dist/button.cjs.js.LICENSE.txt +1 -1
  25. package/dist/button.esm.js +1 -1
  26. package/dist/button.esm.js.LICENSE.txt +1 -1
  27. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  28. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  29. package/dist/center.cjs.js +1 -1
  30. package/dist/center.cjs.js.LICENSE.txt +1 -1
  31. package/dist/center.esm.js +1 -1
  32. package/dist/center.esm.js.LICENSE.txt +1 -1
  33. package/dist/checkbox.cjs.js +1 -1
  34. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  35. package/dist/checkbox.esm.js +1 -1
  36. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  37. package/dist/code.cjs.js +1 -1
  38. package/dist/code.cjs.js.LICENSE.txt +1 -1
  39. package/dist/code.esm.js +1 -1
  40. package/dist/code.esm.js.LICENSE.txt +1 -1
  41. package/dist/container.cjs.js +1 -1
  42. package/dist/container.cjs.js.LICENSE.txt +1 -1
  43. package/dist/container.esm.js +1 -1
  44. package/dist/container.esm.js.LICENSE.txt +1 -1
  45. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  46. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  47. package/dist/designer.cjs.js +1 -1
  48. package/dist/designer.cjs.js.LICENSE.txt +1 -1
  49. package/dist/designer.esm.js +1 -1
  50. package/dist/designer.esm.js.LICENSE.txt +1 -1
  51. package/dist/element-mapper.cjs.js +1 -1
  52. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  53. package/dist/element-mapper.esm.js +1 -1
  54. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  55. package/dist/finalresult.esm.js +1 -1
  56. package/dist/finalresult.esm.js.LICENSE.txt +1 -1
  57. package/dist/flex-card.cjs.js +1 -1
  58. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  59. package/dist/flex-card.esm.js +1 -1
  60. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  61. package/dist/flex-grid.cjs.js +1 -1
  62. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  63. package/dist/flex-grid.esm.js +1 -1
  64. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  65. package/dist/flex-row.cjs.js +1 -1
  66. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  67. package/dist/flex-row.esm.js +1 -1
  68. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  69. package/dist/floating-input.cjs.js +1 -1
  70. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  71. package/dist/floating-input.esm.js +1 -1
  72. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  73. package/dist/free.cjs.js +1 -1
  74. package/dist/free.cjs.js.LICENSE.txt +1 -1
  75. package/dist/free.esm.js +1 -1
  76. package/dist/free.esm.js.LICENSE.txt +1 -1
  77. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  78. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  79. package/dist/image-picker.cjs.js +1 -1
  80. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  81. package/dist/image-picker.esm.js +1 -1
  82. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  83. package/dist/image.cjs.js +1 -1
  84. package/dist/image.cjs.js.LICENSE.txt +1 -1
  85. package/dist/image.esm.js +1 -1
  86. package/dist/image.esm.js.LICENSE.txt +1 -1
  87. package/dist/index.cjs.js +1 -1
  88. package/dist/index.cjs.js.LICENSE.txt +1 -1
  89. package/dist/index.d.ts +1 -1
  90. package/dist/index.esm.js +1 -1
  91. package/dist/index.esm.js.LICENSE.txt +1 -1
  92. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  93. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  94. package/dist/link-getter.cjs.js +1 -1
  95. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  96. package/dist/link-getter.esm.js +1 -1
  97. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  98. package/dist/link.cjs.js +1 -1
  99. package/dist/link.cjs.js.LICENSE.txt +1 -1
  100. package/dist/link.esm.js +1 -1
  101. package/dist/link.esm.js.LICENSE.txt +1 -1
  102. package/dist/meta-adder.cjs.js +1 -1
  103. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  104. package/dist/meta-adder.esm.js +1 -1
  105. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  106. package/dist/modal-2025.cjs.js +1 -1
  107. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  108. package/dist/modal-2025.esm.js +1 -1
  109. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  110. package/dist/multiswitcher.cjs.js +1 -1
  111. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  112. package/dist/multiswitcher.esm.js +1 -1
  113. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  114. package/dist/new-nav-bar.cjs.js +1 -1
  115. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  116. package/dist/new-nav-bar.esm.js +1 -1
  117. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  118. package/dist/picker.cjs.js +1 -1
  119. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  120. package/dist/picker.esm.js +1 -1
  121. package/dist/picker.esm.js.LICENSE.txt +1 -1
  122. package/dist/progress.cjs.js +1 -1
  123. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  124. package/dist/progress.esm.js +1 -1
  125. package/dist/progress.esm.js.LICENSE.txt +1 -1
  126. package/dist/radio.cjs.js +1 -1
  127. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  128. package/dist/radio.esm.js +1 -1
  129. package/dist/radio.esm.js.LICENSE.txt +1 -1
  130. package/dist/range.cjs.js.LICENSE.txt +1 -1
  131. package/dist/range.esm.js.LICENSE.txt +1 -1
  132. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  133. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  134. package/dist/side-bar.cjs.js +1 -1
  135. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  136. package/dist/side-bar.esm.js +1 -1
  137. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  138. package/dist/side-nav-bar.cjs.js +1 -1
  139. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  140. package/dist/side-nav-bar.esm.js +1 -1
  141. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  142. package/dist/simple-bar.cjs.js +1 -1
  143. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  144. package/dist/simple-bar.esm.js +1 -1
  145. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  146. package/dist/slider-2025.cjs.js +1 -1
  147. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  148. package/dist/slider-2025.esm.js +1 -1
  149. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  150. package/dist/spacer.cjs.js +1 -1
  151. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  152. package/dist/spacer.esm.js +1 -1
  153. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  154. package/dist/stack.cjs.js +1 -1
  155. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  156. package/dist/stack.esm.js +1 -1
  157. package/dist/stack.esm.js.LICENSE.txt +1 -1
  158. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  159. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  160. package/dist/table.cjs.js +1 -1
  161. package/dist/table.cjs.js.LICENSE.txt +1 -1
  162. package/dist/table.esm.js +1 -1
  163. package/dist/table.esm.js.LICENSE.txt +1 -1
  164. package/dist/text-field.cjs.js +1 -1
  165. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  166. package/dist/text-field.esm.js +1 -1
  167. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  168. package/dist/text.cjs.js +1 -1
  169. package/dist/text.cjs.js.LICENSE.txt +1 -1
  170. package/dist/text.esm.js +1 -1
  171. package/dist/text.esm.js.LICENSE.txt +1 -1
  172. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  173. package/dist/theme.esm.js.LICENSE.txt +1 -1
  174. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  175. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  176. package/dist/ulist.cjs.js +1 -1
  177. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  178. package/dist/ulist.esm.js +1 -1
  179. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  180. package/dist/video.cjs.js +1 -1
  181. package/dist/video.cjs.js.LICENSE.txt +1 -1
  182. package/dist/video.esm.js +1 -1
  183. package/dist/video.esm.js.LICENSE.txt +1 -1
  184. package/dist/wrap.cjs.js +1 -1
  185. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  186. package/dist/wrap.esm.js +1 -1
  187. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  188. package/dist/zoom-card.cjs.js +1 -1
  189. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  190. package/dist/zoom-card.esm.js +1 -1
  191. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  192. package/layout/animator.js +85 -23
  193. package/layout/audio.js +21 -1
  194. package/layout/audionew.js +1 -1
  195. package/layout/base.js +1 -1
  196. package/layout/beta-desktop-bar.js +1 -1
  197. package/layout/beta-mobile-bar.js +1 -1
  198. package/layout/button.js +4 -2
  199. package/layout/center.js +1 -1
  200. package/layout/checkbox.js +1 -1
  201. package/layout/circle.js +1 -1
  202. package/layout/code.js +2 -2
  203. package/layout/container.js +2 -2
  204. package/layout/dropdown-2025.js +1 -1
  205. package/layout/flex-card.js +1 -1
  206. package/layout/flex-grid.js +1 -1
  207. package/layout/flex-row.js +1 -1
  208. package/layout/form-components/custom.js +1 -1
  209. package/layout/form-components/data-list.js +1 -1
  210. package/layout/form-components/floating-input.js +1 -1
  211. package/layout/form-components/form-all.js +1 -1
  212. package/layout/form-components/form.js +1 -1
  213. package/layout/form-components/image-picker.js +1 -1
  214. package/layout/form-components/picker.js +1 -1
  215. package/layout/form-components/radio.js +6 -1
  216. package/layout/form-components/radiogroup.js +1 -1
  217. package/layout/form-components/range.js +1 -1
  218. package/layout/free.js +1 -1
  219. package/layout/grid-overlay.js +19 -19
  220. package/layout/grid-switcher.js +3 -1
  221. package/layout/grid.js +1 -1
  222. package/layout/horizontal-scroller.js +1 -1
  223. package/layout/image.js +2 -2
  224. package/layout/index.js +1 -1
  225. package/layout/link.js +1 -1
  226. package/layout/list.js +1 -1
  227. package/layout/meta-adder.js +1 -1
  228. package/layout/modal-2025.js +1 -1
  229. package/layout/morph.js +1 -1
  230. package/layout/multiswitcher.js +6 -2
  231. package/layout/nav-bar.js +1 -1
  232. package/layout/nav-factor/custom-div.js +1 -1
  233. package/layout/new-nav-bar.js +1 -1
  234. package/layout/polygon.js +1 -1
  235. package/layout/prerender-site.js +1 -1
  236. package/layout/prerender.js +1 -1
  237. package/layout/progress.js +1 -1
  238. package/layout/row.js +1 -1
  239. package/layout/scroll-video.js +1 -1
  240. package/layout/side-bar.js +1 -1
  241. package/layout/side-nav-bar.js +6 -1
  242. package/layout/simple-bar.js +1 -1
  243. package/layout/slider-2025.js +1 -1
  244. package/layout/spacer.js +1 -1
  245. package/layout/stack.js +5 -2
  246. package/layout/svg.js +1 -1
  247. package/layout/switcher.js +1 -1
  248. package/layout/table.js +1 -1
  249. package/layout/text-field.js +1 -1
  250. package/layout/text.js +1 -1
  251. package/layout/ulist.js +1 -1
  252. package/layout/video.js +22 -22
  253. package/layout/wrap.js +1 -1
  254. package/layout/zoom-card.js +1 -1
  255. package/lib/agent-surface.js +1 -1
  256. package/lib/card-getter.js +1 -1
  257. package/lib/check-page.js +1 -1
  258. package/lib/codegen.js +1 -1
  259. package/lib/data.js +1 -1
  260. package/lib/designer.js +1 -1
  261. package/lib/element-mapper.js +103 -15
  262. package/lib/element-params.generated.js +13 -2
  263. package/lib/grid-geometry.js +1 -1
  264. package/lib/keyframe-animation.js +1 -1
  265. package/lib/link-getter.js +1 -1
  266. package/lib/morph-node.js +1 -1
  267. package/lib/normalize-spec.js +1 -1
  268. package/lib/parse-html.js +1 -1
  269. package/lib/raster-inspect.js +1 -1
  270. package/lib/raster-ops.js +1 -1
  271. package/lib/raster-presets.js +1 -1
  272. package/lib/resolve-refs.js +1 -1
  273. package/lib/scroll-video.js +1 -1
  274. package/lib/seo.js +1 -1
  275. package/lib/stacker.js +1 -1
  276. package/lib/suggest.js +1 -1
  277. package/lib/theme.js +1 -1
  278. package/lib/transform-anim.js +1 -1
  279. package/lib/transition.js +1 -1
  280. package/lib/validate-nodes.js +146 -4
  281. package/lib/webmcp-adapter.js +1 -1
  282. package/package.json +1 -1
  283. package/schema.json +1391 -2263
  284. package/scripts/generate-schema.mjs +135 -15
  285. package/skills/nodality/SKILL.md +15 -0
@@ -84,19 +84,69 @@ const DISPATCH = {};
84
84
  }
85
85
 
86
86
  // ── 3. mapper method -> component classes it constructs ──────────────
87
+ // Braces are counted over CODE only: a `}` inside a string or a comment is
88
+ // text, not structure.
89
+ //
90
+ // The previous version counted them in `mapperLive`, which strips comments by
91
+ // deleting everything after `//` on a line — and that also truncates any line
92
+ // holding a URL, taking its closing brace with it. Four mapper bodies
93
+ // (`dropdown`, `protoNav`, `sideNav`, `gridItemsSource`) therefore never
94
+ // balanced and ran on to the end of the class, so every method BELOW them was
95
+ // read as part of them. The visible cost was a new mapper's parameters being
96
+ // credited to `cards`, `nav`, `sideNav` and `dropdown` as well as to its own
97
+ // type; the invisible cost was that reachability (below) could not be
98
+ // determined at all, because an overrunning body picks up someone else's
99
+ // `elOpts()` call.
87
100
  function methodBody(name) {
88
- const at = mapperLive.indexOf(`static ${name}(`);
101
+ const at = src.indexOf(`static ${name}(`);
89
102
  if (at < 0) return "";
90
- // Balance braces from the method's opening brace.
91
- let i = mapperLive.indexOf("{", at), depth = 0;
92
- for (let j = i; j < mapperLive.length; j++) {
93
- if (mapperLive[j] === "{") depth++;
94
- else if (mapperLive[j] === "}") { depth--; if (!depth) return mapperLive.slice(i, j); }
103
+ const start = src.indexOf("{", at);
104
+ if (start < 0) return "";
105
+
106
+ let depth = 0;
107
+ // "code" | "line" | "block" | "'" | '"' | "`"
108
+ let state = "code";
109
+ // Template literals nest: `${ ... `inner` ... }` returns to code inside ${}.
110
+ const stack = [];
111
+
112
+ for (let j = start; j < src.length; j++) {
113
+ const c = src[j], next = src[j + 1];
114
+ if (state === "line") { if (c === "\n") state = "code"; continue; }
115
+ if (state === "block") { if (c === "*" && next === "/") { state = "code"; j++; } continue; }
116
+ if (state === "'" || state === '"') {
117
+ if (c === "\\") { j++; continue; }
118
+ if (c === state) state = "code";
119
+ continue;
120
+ }
121
+ if (state === "`") {
122
+ if (c === "\\") { j++; continue; }
123
+ if (c === "`") { state = "code"; continue; }
124
+ if (c === "$" && next === "{") { stack.push("`"); state = "code"; depth++; j++; }
125
+ continue;
126
+ }
127
+ // code
128
+ if (c === "/" && next === "/") { state = "line"; j++; continue; }
129
+ if (c === "/" && next === "*") { state = "block"; j++; continue; }
130
+ if (c === "'" || c === '"' || c === "`") { state = c; continue; }
131
+ if (c === "{") { depth++; continue; }
132
+ if (c === "}") {
133
+ depth--;
134
+ if (stack.length && depth === stack.length) { stack.pop(); state = "`"; continue; }
135
+ if (!depth) return src.slice(start, j);
136
+ }
95
137
  }
96
- return mapperLive.slice(i);
138
+ return src.slice(start);
97
139
  }
140
+ // `new X(` in a mapper is not always a component: the bodies legitimately
141
+ // build an Error to reject bad input and a Set to de-duplicate. Reporting
142
+ // those as the type's components told an agent that a page uses a component
143
+ // called `Error`.
144
+ const BUILTINS = new Set(["Error", "TypeError", "RangeError", "Set", "Map",
145
+ "WeakMap", "WeakSet", "Date", "RegExp", "Promise", "Array", "Object",
146
+ "Function", "Number", "String", "Boolean", "Intl", "URL", "URLSearchParams"]);
98
147
  const classesIn = (body) =>
99
- [...new Set([...body.matchAll(/new\s+([A-Z][A-Za-z0-9]*)\s*\(/g)].map((m) => m[1]))];
148
+ [...new Set([...body.matchAll(/new\s+([A-Z][A-Za-z0-9]*)\s*\(/g)].map((m) => m[1]))]
149
+ .filter((name) => !BUILTINS.has(name));
100
150
 
101
151
  // ── 4. class -> file, from element-mapper's own imports ──────────────
102
152
  const IMPORTS = {};
@@ -155,6 +205,12 @@ function paramsOf(file) {
155
205
  // component, decides what the slot means.
156
206
  const DESC_BY_FILE = new Map(), DEPR_BY_FILE = new Map();
157
207
  const DESC_BY_TYPE = {};
208
+ // Declared value SHAPES, written as `//@ name {unit}: text`. Without them the
209
+ // validator can only check that a parameter name exists — never that `mar: 7`
210
+ // or `columns: "four"` is a value the component can use. The raster op
211
+ // registry has carried units since it was written; element parameters had
212
+ // none, which is why a spec full of unusable values validated clean.
213
+ const UNIT_BY_TYPE = {}, UNIT_BY_FILE = new Map(), UNIT_GLOBAL = {};
158
214
  // Names whose meaning depends on the element type, declared with `//@scoped
159
215
  // <name>`. For these a description is never BORROWED from a file that does not
160
216
  // contribute the parameter: `items` is documented once, in picker.js, and is
@@ -174,19 +230,27 @@ const DESC_GLOBAL = {}, DEPR_GLOBAL = {};
174
230
  .map((rel) => join(ROOT, "lib", rel)).filter(existsSync));
175
231
  files.add(MAPPER);
176
232
  for (const f of files) {
177
- const desc = {}, depr = {};
233
+ const desc = {}, depr = {}, units = {};
178
234
  for (const line of readFileSync(f, "utf8").split("\n")) {
179
235
  let m = line.match(/\/\/@scoped\s+([a-zA-Z][a-zA-Z0-9]*)\s*$/);
180
236
  if (m) { SCOPED.add(m[1]); continue; }
181
237
  m = line.match(/\/\/@deprecated\s+([a-zA-Z][a-zA-Z0-9]*)\s*:\s*(.+)$/);
182
238
  if (m) { depr[m[1]] ??= m[2].trim(); DEPR_GLOBAL[m[1]] ??= m[2].trim(); continue; }
183
239
  // Qualified first: `//@ cards.items: …` binds to one type only.
184
- m = line.match(/\/\/@\s+([a-zA-Z][a-zA-Z0-9]*)\.([a-zA-Z][a-zA-Z0-9]*)\s*:\s*(.+)$/);
185
- if (m) { (DESC_BY_TYPE[m[1]] ??= {})[m[2]] ??= m[3].trim(); continue; }
186
- m = line.match(/\/\/@\s+([a-zA-Z][a-zA-Z0-9]*)\s*:\s*(.+)$/);
187
- if (m) { desc[m[1]] ??= m[2].trim(); DESC_GLOBAL[m[1]] ??= m[2].trim(); }
240
+ // An optional `{unit}` before the colon declares the value's shape.
241
+ m = line.match(/\/\/@\s+([a-zA-Z][a-zA-Z0-9]*)\.([a-zA-Z][a-zA-Z0-9]*)\s*(?:\{([^}]+)\})?\s*:\s*(.+)$/);
242
+ if (m) {
243
+ (DESC_BY_TYPE[m[1]] ??= {})[m[2]] ??= m[4].trim();
244
+ if (m[3]) (UNIT_BY_TYPE[m[1]] ??= {})[m[2]] ??= m[3].trim();
245
+ continue;
246
+ }
247
+ m = line.match(/\/\/@\s+([a-zA-Z][a-zA-Z0-9]*)\s*(?:\{([^}]+)\})?\s*:\s*(.+)$/);
248
+ if (m) {
249
+ desc[m[1]] ??= m[3].trim(); DESC_GLOBAL[m[1]] ??= m[3].trim();
250
+ if (m[2]) { units[m[1]] ??= m[2].trim(); UNIT_GLOBAL[m[1]] ??= m[2].trim(); }
251
+ }
188
252
  }
189
- DESC_BY_FILE.set(f, desc); DEPR_BY_FILE.set(f, depr);
253
+ DESC_BY_FILE.set(f, desc); DEPR_BY_FILE.set(f, depr); UNIT_BY_FILE.set(f, units);
190
254
  }
191
255
  }
192
256
 
@@ -246,6 +310,38 @@ for (const type of TYPES) {
246
310
  // image.js, and an annotation living in any other file would have been
247
311
  // dropped -- which cost `img.alt` its description on the first attempt at
248
312
  // this fix, the one parameter the comment in paramsOf() exists to protect.
313
+ // Can a spec actually REACH the parameters its components read?
314
+ //
315
+ // Only if the mapper hands the element's options over — `elOpts(el)` or a
316
+ // spread. A mapper that builds its own options object instead (mapTable is
317
+ // the plain case: it hardcodes cellPadding, cellAlign, style.font and
318
+ // headStyle and never looks at the element) leaves every one of those names
319
+ // unreachable, however faithfully the component reads them. Listing them
320
+ // anyway is the schema's worst failure mode, because the validator then
321
+ // vouches for a spec that cannot work: 43% of all declared parameters were
322
+ // in that state when this was written.
323
+ //
324
+ // Where nothing is forwarded, the type is credited only with what the mapper
325
+ // itself reads off the element, plus the two every mapper handles.
326
+ // Three ways a mapper hands the element over: `elOpts(el)`, a spread, or
327
+ // taking the element object ITSELF as the options object and passing it on
328
+ // (`let re = obj.el; re["url"] = …; new Link().set(re)` — how mapLink
329
+ // works). The third forwards just as completely and looks nothing like the
330
+ // other two.
331
+ //
332
+ // Aliasing alone is not forwarding: almost every mapper opens with
333
+ // `let el = obj.el` to read from it, and mapTable does exactly that while
334
+ // building its own options object from literals. What distinguishes the two
335
+ // is whether the alias is handed to a component.
336
+ const aliases = [...body.matchAll(/(?:let|const|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:obj\.)?el\s*[;,]/g)]
337
+ .map((m) => m[1]);
338
+ const passesAlias = aliases.some((a) =>
339
+ new RegExp(`\\.set\\(\\s*${a}\\s*[,)]|new\\s+[A-Z]\\w*\\(\\s*${a}\\s*[,)]`).test(body));
340
+ const forwardsElement = /\belOpts\s*\(|\.\.\.el\b/.test(body) || passesAlias;
341
+ const mapperReads = new Set([...body.matchAll(/\bel\.([a-zA-Z][a-zA-Z0-9]*)/g)].map((m) => m[1]));
342
+ mapperReads.add("type"); mapperReads.add("id");
343
+ const reachable = (name) => forwardsElement || mapperReads.has(name);
344
+
249
345
  const params = new Set();
250
346
  const from = new Map();
251
347
  const contributed = (name, file) => {
@@ -253,7 +349,7 @@ for (const type of TYPES) {
253
349
  if (!from.has(name)) from.set(name, []);
254
350
  if (!from.get(name).includes(file)) from.get(name).push(file);
255
351
  };
256
- for (const f of files) for (const p of paramsOf(f)) contributed(p, f);
352
+ for (const f of files) for (const p of paramsOf(f)) if (reachable(p)) contributed(p, f);
257
353
 
258
354
  // The mapper's OWN `el.<name>` reads. Scanning components alone missed
259
355
  // these, because a mapper often passes an element field as a constructor
@@ -278,13 +374,20 @@ for (const type of TYPES) {
278
374
  schema.types[type] = {
279
375
  resolved: ok,
280
376
  mapper: method,
377
+ // Whether the mapper hands the element's options to its components. When
378
+ // false, this type's vocabulary is only what the mapper reads by name.
379
+ forwardsElement,
281
380
  components: classes,
282
381
  params: [...params].sort().map((name) => {
283
382
  const where = from.get(name) || [];
284
383
  const description = annotationFor(DESC_BY_FILE, name, where, type, true);
285
384
  const deprecated = annotationFor(DEPR_BY_FILE, name, where, type, false);
385
+ const unit = (UNIT_BY_TYPE[type] && UNIT_BY_TYPE[type][name])
386
+ || where.map((f) => (UNIT_BY_FILE.get(f) || {})[name]).find(Boolean)
387
+ || UNIT_GLOBAL[name];
286
388
  return {
287
389
  name,
390
+ ...(unit ? { unit } : {}),
288
391
  ...(description ? { description } : {}),
289
392
  ...(deprecated ? { deprecated } : {}),
290
393
  };
@@ -335,11 +438,28 @@ if (args.includes("--stdout")) { process.stdout.write(json); process.exit(0); }
335
438
  const union = new Set();
336
439
  for (const t of Object.values(schema.types)) for (const p of t.params) union.add(p.name);
337
440
  const names = [...union].sort();
441
+ const byType = {};
442
+ const units = {};
443
+ for (const [type, t] of Object.entries(schema.types)) {
444
+ byType[type] = t.params.map((p) => p.name);
445
+ for (const p of t.params) if (p.unit) units[`${type}.${p.name}`] = p.unit;
446
+ }
338
447
  const mod = `// GENERATED by scripts/generate-schema.mjs — do not edit.
339
448
  // Every parameter name any element type reads, recovered from source.
340
449
  // Used for near-miss typo detection in validate-nodes.js. A drift test
341
450
  // regenerates this and fails if it differs, so it cannot rot silently.
342
451
  export const ELEMENT_PARAM_NAMES = ${JSON.stringify(names)};
452
+
453
+ // Per-type vocabulary. The union above answers "is this a real parameter
454
+ // anywhere"; this answers "does THIS type accept it", which is what a page
455
+ // actually needs to know — writing \`keySet\` on a \`table\` is a name the
456
+ // union knows and the type ignores.
457
+ export const ELEMENT_PARAMS_BY_TYPE = ${JSON.stringify(byType)};
458
+
459
+ // Declared value shapes, keyed "type.param". Only parameters whose source
460
+ // carries a \`//@ name {unit}:\` annotation appear, so this grows as the
461
+ // library documents itself and never claims a shape it was not told.
462
+ export const ELEMENT_PARAM_UNITS = ${JSON.stringify(units)};
343
463
  `;
344
464
  writeFileSync(join(ROOT, "lib", "element-params.generated.js"), mod);
345
465
  }
@@ -68,6 +68,21 @@ Skipping step 3 is the classic failure: a misspelled op renders
68
68
  *nothing* rather than erroring, so the page looks plausible and is
69
69
  silently missing its effects.
70
70
 
71
+ **Read `warnings`, not just `errors`.** The report has both. `errors`
72
+ mean the page will not render as a page; `warnings` mean it renders and
73
+ does not do what you wrote:
74
+
75
+ | code | what it means |
76
+ |---|---|
77
+ | `BAD_PARAM_VALUE` | the value is not the shape the parameter takes — `width: 7` is not a CSS length, `mar: 7` is not `[{a: 7}]`. As an *error* where the value is provably inert; as a warning otherwise. |
78
+ | `PARAM_NOT_ON_TYPE` | a real parameter name that THIS type does not read, so it is accepted and dropped — `keySet` on a `table`. |
79
+
80
+ `get_schema <type>` carries a `unit` on every parameter whose shape the
81
+ source declares, which is where those checks come from, and a
82
+ `forwardsElement` flag: when it is false the type's vocabulary is only
83
+ what its mapper reads by name, because the mapper builds its own options
84
+ rather than passing yours on.
85
+
71
86
  ## HTML's names are accepted
72
87
 
73
88
  `src` and `href` both mean `url`; `options` means `items`. Write either — the