@adia-ai/web-components 0.8.40 → 0.8.41

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 (378) hide show
  1. package/CHANGELOG.md +50 -3
  2. package/MIGRATION.md +1042 -16
  3. package/README.md +25 -30
  4. package/USAGE.md +26 -0
  5. package/bin/audit-template-children.mjs +556 -0
  6. package/components/accordion/accordion-item.a2ui.json +4 -0
  7. package/components/accordion/accordion-item.yaml +2 -0
  8. package/components/accordion/accordion.a2ui.json +4 -0
  9. package/components/accordion/accordion.yaml +2 -0
  10. package/components/action-list/action-item.a2ui.json +24 -2
  11. package/components/action-list/action-item.yaml +28 -3
  12. package/components/action-list/action-list.a2ui.json +6 -1
  13. package/components/action-list/action-list.class.js +52 -11
  14. package/components/action-list/action-list.css +43 -3
  15. package/components/action-list/action-list.d.ts +4 -2
  16. package/components/action-list/action-list.yaml +5 -0
  17. package/components/adia-mark/adia-mark.a2ui.json +1 -0
  18. package/components/adia-wordmark/adia-wordmark.a2ui.json +1 -0
  19. package/components/agent-artifact/agent-artifact.a2ui.json +1 -0
  20. package/components/agent-feedback-bar/agent-feedback-bar.a2ui.json +6 -5
  21. package/components/agent-feedback-bar/agent-feedback-bar.yaml +6 -2
  22. package/components/agent-questions/agent-questions.a2ui.json +1 -0
  23. package/components/agent-reasoning/agent-reasoning.a2ui.json +6 -5
  24. package/components/agent-reasoning/agent-reasoning.yaml +6 -2
  25. package/components/agent-suggestions/agent-suggestions.a2ui.json +11 -2
  26. package/components/agent-suggestions/agent-suggestions.class.js +2 -1
  27. package/components/agent-suggestions/agent-suggestions.d.ts +4 -4
  28. package/components/agent-suggestions/agent-suggestions.yaml +4 -2
  29. package/components/agent-trace/agent-trace.a2ui.json +6 -5
  30. package/components/agent-trace/agent-trace.yaml +6 -2
  31. package/components/alert/alert.a2ui.json +1 -0
  32. package/components/alert/alert.css +1 -1
  33. package/components/anchor-bar/anchor-bar.a2ui.json +1 -0
  34. package/components/aside/aside.a2ui.json +1 -0
  35. package/components/avatar/avatar-group.a2ui.json +7 -1
  36. package/components/avatar/avatar-group.yaml +1 -0
  37. package/components/avatar/avatar.a2ui.json +12 -11
  38. package/components/avatar/avatar.d.ts +1 -1
  39. package/components/avatar/avatar.yaml +4 -4
  40. package/components/badge/badge.a2ui.json +1 -0
  41. package/components/block/block.a2ui.json +1 -0
  42. package/components/blockquote/blockquote.a2ui.json +1 -0
  43. package/components/breadcrumb/breadcrumb.a2ui.json +1 -0
  44. package/components/button/button.a2ui.json +1 -3
  45. package/components/button/button.yaml +1 -3
  46. package/components/calendar-grid/calendar-grid.a2ui.json +1 -0
  47. package/components/calendar-grid/calendar-grid.class.js +2 -1
  48. package/components/calendar-picker/calendar-picker.a2ui.json +143 -24
  49. package/components/calendar-picker/calendar-picker.class.js +490 -197
  50. package/components/calendar-picker/calendar-picker.css +120 -171
  51. package/components/calendar-picker/calendar-picker.yaml +225 -47
  52. package/components/canvas/canvas.a2ui.json +6 -5
  53. package/components/canvas/canvas.d.ts +37 -0
  54. package/components/canvas/canvas.js +72 -3
  55. package/components/canvas/canvas.yaml +6 -2
  56. package/components/card/card.a2ui.json +25 -2
  57. package/components/card/card.class.js +3 -0
  58. package/components/card/card.css +31 -1
  59. package/components/card/card.d.ts +2 -2
  60. package/components/card/card.examples.md +3 -3
  61. package/components/card/card.yaml +28 -4
  62. package/components/chart/chart.a2ui.json +9 -4
  63. package/components/chart/chart.class.js +6 -6
  64. package/components/chart/chart.css +6 -4
  65. package/components/chart/chart.d.ts +1 -1
  66. package/components/chart/chart.yaml +5 -3
  67. package/components/chart-legend/chart-legend.a2ui.json +1 -0
  68. package/components/chat-thread/chat-input.a2ui.json +1 -0
  69. package/components/chat-thread/chat-thread.a2ui.json +10 -9
  70. package/components/chat-thread/chat-thread.yaml +13 -20
  71. package/components/check/check.a2ui.json +10 -9
  72. package/components/check/check.yaml +4 -4
  73. package/components/code/code.a2ui.json +1 -0
  74. package/components/col/col.a2ui.json +1 -0
  75. package/components/color-input/color-input.a2ui.json +1 -0
  76. package/components/color-picker/color-picker.a2ui.json +1 -0
  77. package/components/combobox/combobox.a2ui.json +3 -2
  78. package/components/combobox/combobox.yaml +2 -2
  79. package/components/command/command.a2ui.json +12 -10
  80. package/components/command/command.class.js +3 -2
  81. package/components/command/command.yaml +17 -4
  82. package/components/context-menu/context-menu.a2ui.json +1 -0
  83. package/components/date-range-picker/date-range-picker.a2ui.json +2 -1
  84. package/components/date-range-picker/date-range-picker.yaml +1 -1
  85. package/components/datetime-picker/datetime-picker.a2ui.json +15 -108
  86. package/components/datetime-picker/datetime-picker.class.js +37 -690
  87. package/components/datetime-picker/datetime-picker.css +11 -183
  88. package/components/datetime-picker/datetime-picker.d.ts +4 -5
  89. package/components/datetime-picker/datetime-picker.yaml +44 -135
  90. package/components/demo-toggle/demo-toggle.a2ui.json +1 -0
  91. package/components/demo-toggle/demo-toggle.css +1 -1
  92. package/components/description-list/description-list.a2ui.json +1 -0
  93. package/components/display-field/display-field.a2ui.json +1 -0
  94. package/components/divider/divider.a2ui.json +1 -0
  95. package/components/drawer/drawer.a2ui.json +1 -3
  96. package/components/drawer/drawer.css +1 -1
  97. package/components/drawer/drawer.yaml +0 -2
  98. package/components/drilldown/drilldown.a2ui.json +22 -20
  99. package/components/drilldown/drilldown.class.js +4 -3
  100. package/components/drilldown/drilldown.css +6 -6
  101. package/components/drilldown/drilldown.yaml +40 -9
  102. package/components/embed/embed.a2ui.json +6 -5
  103. package/components/embed/embed.yaml +2 -2
  104. package/components/empty-state/empty-state.a2ui.json +1 -0
  105. package/components/feed/feed-item.a2ui.json +25 -5
  106. package/components/feed/feed-item.yaml +28 -7
  107. package/components/feed/feed.a2ui.json +9 -2
  108. package/components/feed/feed.class.js +19 -12
  109. package/components/feed/feed.css +8 -8
  110. package/components/feed/feed.yaml +14 -1
  111. package/components/field/field.a2ui.json +1 -0
  112. package/components/field/field.examples.md +1 -1
  113. package/components/fields/fields.a2ui.json +1 -0
  114. package/components/fields/fields.examples.md +1 -1
  115. package/components/footer/footer.a2ui.json +1 -0
  116. package/components/frame/frame.a2ui.json +1 -0
  117. package/components/grid/grid.a2ui.json +1 -0
  118. package/components/header/header.a2ui.json +3 -2
  119. package/components/header/header.yaml +3 -3
  120. package/components/heatmap/heatmap.a2ui.json +2 -1
  121. package/components/heatmap/heatmap.class.js +1 -1
  122. package/components/heatmap/heatmap.css +4 -4
  123. package/components/heatmap/heatmap.yaml +1 -1
  124. package/components/icon/icon.a2ui.json +1 -0
  125. package/components/image/image.a2ui.json +28 -14
  126. package/components/image/image.class.js +38 -23
  127. package/components/image/image.css +18 -8
  128. package/components/image/image.d.ts +2 -2
  129. package/components/image/image.examples.md +3 -3
  130. package/components/image/image.yaml +23 -7
  131. package/components/inline-edit/inline-edit.a2ui.json +1 -0
  132. package/components/inline-message/inline-message.a2ui.json +3 -1
  133. package/components/inline-message/inline-message.class.js +3 -1
  134. package/components/inline-message/inline-message.d.ts +2 -2
  135. package/components/inline-message/inline-message.yaml +2 -1
  136. package/components/input/input.a2ui.json +66 -16
  137. package/components/input/input.class.js +64 -7
  138. package/components/input/input.css +14 -14
  139. package/components/input/input.d.ts +9 -4
  140. package/components/input/input.yaml +83 -10
  141. package/components/inspector/inspector.a2ui.json +1 -0
  142. package/components/integration-card/integration-card.a2ui.json +1 -0
  143. package/components/kbd/kbd.a2ui.json +1 -0
  144. package/components/link/link.a2ui.json +2 -1
  145. package/components/link/link.yaml +1 -1
  146. package/components/list/list-item.a2ui.json +9 -0
  147. package/components/list/list-item.yaml +12 -0
  148. package/components/list/list.a2ui.json +9 -3
  149. package/components/list/list.class.js +14 -1
  150. package/components/list/list.css +12 -1
  151. package/components/list/list.d.ts +7 -5
  152. package/components/list/list.yaml +16 -6
  153. package/components/list-window/list-window.a2ui.json +1 -0
  154. package/components/list-window/list-window.class.js +2 -1
  155. package/components/loading-overlay/loading-overlay.a2ui.json +1 -0
  156. package/components/mark/mark.a2ui.json +2 -1
  157. package/components/mark/mark.yaml +1 -1
  158. package/components/menu/menu-divider.a2ui.json +1 -0
  159. package/components/menu/menu-item.a2ui.json +3 -1
  160. package/components/menu/menu-item.yaml +6 -0
  161. package/components/menu/menu-label.a2ui.json +1 -0
  162. package/components/menu/menu.a2ui.json +3 -2
  163. package/components/menu/menu.class.js +2 -2
  164. package/components/menu/menu.d.ts +2 -2
  165. package/components/menu/menu.yaml +3 -3
  166. package/components/modal/modal.a2ui.json +1 -0
  167. package/components/nav/nav.a2ui.json +1 -0
  168. package/components/nav/nav.class.js +14 -3
  169. package/components/nav-group/nav-group.a2ui.json +1 -0
  170. package/components/nav-group/nav-group.class.js +3 -1
  171. package/components/nav-item/nav-item.a2ui.json +1 -0
  172. package/components/noodles/noodles.a2ui.json +1 -0
  173. package/components/noodles/noodles.class.js +5 -1
  174. package/components/number-format/number-format.a2ui.json +1 -0
  175. package/components/option-card/option-card.a2ui.json +1 -0
  176. package/components/otp-input/otp-input.a2ui.json +3 -2
  177. package/components/otp-input/otp-input.yaml +9 -2
  178. package/components/page/page.a2ui.json +13 -0
  179. package/components/page/page.d.ts +1 -1
  180. package/components/page/page.yaml +1 -0
  181. package/components/pagination/pagination.a2ui.json +1 -0
  182. package/components/pagination/pagination.class.js +3 -2
  183. package/components/pane/pane.a2ui.json +6 -0
  184. package/components/pane/pane.class.js +3 -3
  185. package/components/pane/pane.css +2 -2
  186. package/components/pane/pane.yaml +3 -0
  187. package/components/password-strength/password-strength.a2ui.json +1 -0
  188. package/components/pipeline-status/pipeline-status.a2ui.json +1 -0
  189. package/components/popover/popover.a2ui.json +7 -6
  190. package/components/popover/popover.class.js +2 -2
  191. package/components/popover/popover.d.ts +2 -2
  192. package/components/popover/popover.yaml +4 -4
  193. package/components/preview/preview.a2ui.json +1 -0
  194. package/components/preview/preview.class.js +5 -1
  195. package/components/progress/progress.a2ui.json +47 -24
  196. package/components/progress/progress.class.js +66 -11
  197. package/components/progress/progress.css +44 -26
  198. package/components/progress/progress.d.ts +7 -5
  199. package/components/progress/progress.examples.md +2 -2
  200. package/components/progress/progress.yaml +235 -53
  201. package/components/progress-row/progress-row.a2ui.json +16 -12
  202. package/components/progress-row/progress-row.d.ts +2 -2
  203. package/components/progress-row/progress-row.examples.md +2 -14
  204. package/components/progress-row/progress-row.yaml +28 -36
  205. package/components/qr-code/qr-code.a2ui.json +3 -2
  206. package/components/qr-code/qr-code.class.js +4 -4
  207. package/components/qr-code/qr-code.css +1 -1
  208. package/components/qr-code/qr-code.yaml +5 -3
  209. package/components/radio/radio.a2ui.json +8 -7
  210. package/components/radio/radio.class.js +8 -0
  211. package/components/radio/radio.yaml +3 -3
  212. package/components/radio-group/radio-group.a2ui.json +5 -3
  213. package/components/radio-group/radio-group.class.js +11 -0
  214. package/components/radio-group/radio-group.yaml +7 -1
  215. package/components/range/range.a2ui.json +6 -0
  216. package/components/range/range.class.js +2 -2
  217. package/components/range/range.css +2 -2
  218. package/components/range/range.yaml +3 -0
  219. package/components/rating/rating.a2ui.json +1 -0
  220. package/components/relative-time/relative-time.a2ui.json +1 -0
  221. package/components/richtext/richtext.a2ui.json +2 -1
  222. package/components/richtext/richtext.yaml +1 -1
  223. package/components/row/row.a2ui.json +1 -0
  224. package/components/search/search.a2ui.json +1 -0
  225. package/components/search/search.class.js +4 -1
  226. package/components/section/section.a2ui.json +1 -0
  227. package/components/segment/segment.a2ui.json +21 -10
  228. package/components/segment/segment.css +15 -0
  229. package/components/segment/segment.d.ts +2 -1
  230. package/components/segment/segment.yaml +20 -13
  231. package/components/segmented/segmented.a2ui.json +36 -7
  232. package/components/segmented/segmented.class.js +113 -22
  233. package/components/segmented/segmented.css +3 -0
  234. package/components/segmented/segmented.d.ts +3 -1
  235. package/components/segmented/segmented.yaml +80 -8
  236. package/components/select/select.a2ui.json +18 -13
  237. package/components/select/select.class.js +61 -37
  238. package/components/select/select.css +15 -13
  239. package/components/select/select.yaml +18 -7
  240. package/components/skeleton/skeleton.a2ui.json +8 -7
  241. package/components/skeleton/skeleton.class.js +8 -7
  242. package/components/skeleton/skeleton.css +21 -6
  243. package/components/skeleton/skeleton.d.ts +2 -2
  244. package/components/skeleton/skeleton.examples.md +2 -2
  245. package/components/skeleton/skeleton.yaml +3 -2
  246. package/components/skip-nav/skip-nav.a2ui.json +1 -0
  247. package/components/slider/slider.a2ui.json +6 -0
  248. package/components/slider/slider.class.js +20 -3
  249. package/components/slider/slider.css +2 -2
  250. package/components/slider/slider.yaml +3 -0
  251. package/components/spinner/spinner.a2ui.json +1 -0
  252. package/components/stack/stack.a2ui.json +1 -0
  253. package/components/stat/stat.a2ui.json +1 -0
  254. package/components/stat/stat.js +2 -2
  255. package/components/step-progress/step-progress.a2ui.json +1 -0
  256. package/components/step-progress/step-progress.yaml +1 -1
  257. package/components/stepper/stepper-item.a2ui.json +7 -3
  258. package/components/stepper/stepper-item.yaml +15 -5
  259. package/components/stepper/stepper.a2ui.json +5 -1
  260. package/components/stepper/stepper.class.js +31 -16
  261. package/components/stepper/stepper.d.ts +1 -1
  262. package/components/stepper/stepper.yaml +4 -2
  263. package/components/stream/stream.a2ui.json +1 -0
  264. package/components/swatch/swatch.a2ui.json +1 -0
  265. package/components/swiper/swiper.a2ui.json +1 -0
  266. package/components/swiper/swiper.class.js +5 -1
  267. package/components/switch/switch.a2ui.json +12 -11
  268. package/components/switch/switch.css +1 -1
  269. package/components/switch/switch.yaml +6 -6
  270. package/components/table/table.a2ui.json +1 -0
  271. package/components/table/table.class.js +14 -12
  272. package/components/table/table.css +11 -9
  273. package/components/table-toolbar/table-toolbar.a2ui.json +1 -0
  274. package/components/table-toolbar/table-toolbar.class.js +38 -19
  275. package/components/table-toolbar/table-toolbar.css +1 -1
  276. package/components/tabs/tab.a2ui.json +9 -0
  277. package/components/tabs/tab.js +4 -0
  278. package/components/tabs/tab.yaml +15 -0
  279. package/components/tabs/tabs.a2ui.json +9 -4
  280. package/components/tabs/tabs.class.js +13 -1
  281. package/components/tabs/tabs.css +8 -0
  282. package/components/tabs/tabs.d.ts +2 -0
  283. package/components/tabs/tabs.yaml +9 -2
  284. package/components/tag/tag.a2ui.json +1 -0
  285. package/components/tags-input/tags-input.a2ui.json +22 -17
  286. package/components/tags-input/tags-input.class.js +13 -11
  287. package/components/tags-input/tags-input.css +10 -6
  288. package/components/tags-input/tags-input.d.ts +16 -7
  289. package/components/tags-input/tags-input.examples.md +2 -2
  290. package/components/tags-input/tags-input.yaml +25 -8
  291. package/components/text/text.a2ui.json +6 -5
  292. package/components/text/text.css +10 -5
  293. package/components/text/text.d.ts +2 -2
  294. package/components/text/text.yaml +9 -4
  295. package/components/textarea/textarea.a2ui.json +2 -1
  296. package/components/textarea/textarea.yaml +1 -1
  297. package/components/theme-provider/theme-provider.a2ui.json +2 -1
  298. package/components/theme-provider/theme-provider.d.ts +5 -3
  299. package/components/theme-provider/theme-provider.yaml +5 -3
  300. package/components/time-picker/time-picker.a2ui.json +1 -0
  301. package/components/time-picker/time-picker.class.js +4 -2
  302. package/components/timeline/timeline-item.a2ui.json +26 -3
  303. package/components/timeline/timeline-item.yaml +22 -7
  304. package/components/timeline/timeline.a2ui.json +4 -0
  305. package/components/timeline/timeline.class.js +29 -18
  306. package/components/timeline/timeline.d.ts +3 -3
  307. package/components/timeline/timeline.yaml +2 -0
  308. package/components/toast/toast.a2ui.json +10 -3
  309. package/components/toast/toast.yaml +20 -13
  310. package/components/toc/toc.a2ui.json +1 -0
  311. package/components/toggle-group/toggle-group.a2ui.json +7 -3
  312. package/components/toggle-group/toggle-group.class.js +40 -1
  313. package/components/toggle-group/toggle-group.css +6 -1
  314. package/components/toggle-group/toggle-group.d.ts +2 -2
  315. package/components/toggle-group/toggle-group.yaml +21 -12
  316. package/components/toggle-group/toggle-option.a2ui.json +7 -3
  317. package/components/toggle-group/toggle-option.yaml +13 -7
  318. package/components/toggle-scheme/toggle-scheme.a2ui.json +2 -1
  319. package/components/toggle-scheme/toggle-scheme.yaml +1 -1
  320. package/components/toolbar/toolbar-group.a2ui.json +1 -0
  321. package/components/toolbar/toolbar.a2ui.json +1 -0
  322. package/components/toolbar/toolbar.class.js +3 -0
  323. package/components/tooltip/tooltip.a2ui.json +1 -0
  324. package/components/tour/tour-step.a2ui.json +4 -0
  325. package/components/tour/tour-step.yaml +2 -0
  326. package/components/tour/tour.a2ui.json +4 -0
  327. package/components/tour/tour.yaml +2 -0
  328. package/components/tree/tree-item.a2ui.json +5 -0
  329. package/components/tree/tree-item.yaml +3 -0
  330. package/components/tree/tree.a2ui.json +4 -0
  331. package/components/tree/tree.class.js +2 -1
  332. package/components/tree/tree.yaml +2 -0
  333. package/components/upload/upload.a2ui.json +8 -1
  334. package/components/upload/upload.class.js +35 -5
  335. package/components/upload/upload.css +28 -0
  336. package/components/upload/upload.d.ts +16 -0
  337. package/components/upload/upload.yaml +27 -7
  338. package/components/visually-hidden/visually-hidden.a2ui.json +1 -0
  339. package/core/dom.d.ts +11 -0
  340. package/core/dom.js +34 -0
  341. package/core/form.js +7 -0
  342. package/core/index.d.ts +1 -0
  343. package/core/index.js +1 -0
  344. package/core/responsive.js +16 -4
  345. package/core/template.js +2 -1
  346. package/custom-elements.json +437 -489
  347. package/dist/host.min.css +1 -1
  348. package/dist/host.sheet.js +1 -1
  349. package/dist/theme-provider.min.js +5 -5
  350. package/dist/web-components.min.css +1 -1
  351. package/dist/web-components.min.js +91 -104
  352. package/dist/web-components.sheet.js +1 -1
  353. package/package.json +4 -2
  354. package/patterns/conversion-funnel/conversion-funnel.examples.html +15 -15
  355. package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
  356. package/patterns/inline-dialog/inline-dialog.html +4 -0
  357. package/patterns/timezone-selector/timezone-selector.examples.html +1 -1
  358. package/patterns/timezone-selector/timezone-selector.html +2 -2
  359. package/styles/api/text.css +7 -0
  360. package/traits/_catalog.json +129 -41
  361. package/traits/anchor-positioning/anchor-positioning.js +1 -0
  362. package/traits/announcer/announcer.js +1 -0
  363. package/traits/arrow-grid-nav/arrow-grid-nav.js +1 -0
  364. package/traits/attention-pulse/attention-pulse.js +1 -0
  365. package/traits/count-up/count-up.js +1 -0
  366. package/traits/define.js +78 -0
  367. package/traits/focus-restore/focus-restore.js +2 -1
  368. package/traits/haptic-feedback/haptic-feedback.js +1 -0
  369. package/traits/input-mask/input-mask.js +1 -0
  370. package/traits/intersection-observer/intersection-observer.js +1 -0
  371. package/traits/layout-animation/layout-animation.js +1 -0
  372. package/traits/noise-texture/noise-texture.js +1 -0
  373. package/traits/resize-observer/resize-observer.js +1 -0
  374. package/traits/scroll-progress/scroll-progress.js +1 -0
  375. package/traits/snap-to-grid/snap-to-grid.js +1 -0
  376. package/traits/sound-feedback/sound-feedback.js +1 -0
  377. package/traits/spring-animate/spring-animate.js +1 -0
  378. package/traits/validation/validation.js +1 -0
@@ -0,0 +1,556 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * audit-template-children.mjs — `adia-ui-audit-template-children` bin.
4
+ *
5
+ * The CONSUMABLE, installed-package twin of this repo's own
6
+ * `scripts/dev/audit-template-child-conflict.mjs` (gh#1446) — one shared
7
+ * implementation, resolved here against the SHIPPED tarball layout
8
+ * (`components/<name>/<name>.class.js` + `*.yaml`) rather than repo-relative paths,
9
+ * so a downstream consumer running `npx @adia-ai/web-components
10
+ * audit-template-children` (or the installed `adia-ui-audit-template-children`
11
+ * bin directly) gets the real gate instead of a reimplementation that can
12
+ * drift from it. Raised by the adiav2 SSR trial (gh#1430, gh#1435): their
13
+ * kit spec names this script as an admission gate, but until now it only
14
+ * existed as a repo-internal dev script — unreachable from an installed
15
+ * package — so they reimplemented it against `components/<name>/<name>.class.js` +
16
+ * yaml `slots:`, and that reimplementation had already drifted on the
17
+ * definition of "accepts consumer children" (see gh#1435: it read every
18
+ * `slots:` key as fillable; this repo's convention is `slots.default` +
19
+ * non-null template only). This file is the fix: the repo script
20
+ * (`scripts/dev/audit-template-child-conflict.mjs`) is now a thin wrapper
21
+ * around THIS implementation, pointed at repo-relative include dirs instead
22
+ * of the installed-package defaults below.
23
+ *
24
+ * ── The failure shape (gh#284) ──────────────────────────────────────────
25
+ * `UIElement.connectedCallback` runs `stamp(template(this), this)`, and
26
+ * `stamp()`'s first mount for a container calls `container.replaceChildren(f)`
27
+ * — replacing EVERY existing child with the template's own output. In a
28
+ * real browser this is invisible (a freshly-parsed, undefined custom
29
+ * element has no children yet). Under SSR (`connectedCallback` running
30
+ * against server-parsed DOM that already has real children — nested custom
31
+ * elements, or projected body text), the same call silently destroys that
32
+ * pre-existing content.
33
+ *
34
+ * **The definition of "accepts consumer children" this script enforces —
35
+ * and the one gh#1446's issue asks any reimplementation to match exactly:**
36
+ * a component whose `static template` is NOT the literal `() => null`
37
+ * no-op, AND whose yaml declares a `slots` entry named `default` (this
38
+ * repo's established convention for "real consumer children compose
39
+ * here" — see `nav.yaml` / `admin-shell.yaml`). That pairing is flagged
40
+ * **critical** (the CONTAINER shape — nested CE children silently wiped).
41
+ *
42
+ * A component whose `static template` is non-null AND whose own class.js
43
+ * top-of-file JSDoc usage example shows non-empty text content between the
44
+ * component's own opening/closing tags (e.g.
45
+ * `<text-ui variant="heading">Hello World</text-ui>`) is flagged
46
+ * **advisory** — a heuristic for the PROJECTED-TEXT shape, since implicit
47
+ * body text has no yaml `slots` entry to check. Skipped for a component
48
+ * already flagged critical (avoid double-reporting the same root cause).
49
+ *
50
+ * **What's NOT flagged**: a non-null template whose only children are ones
51
+ * the component's OWN `render()` manages via `slot=`-attributed elements it
52
+ * appends/removes itself (avatar-ui's `#imgEl`/`#initialsEl` pattern) —
53
+ * self-managed content, not consumer-provided. These components have
54
+ * `static template = () => null` anyway, so the non-null-template gate
55
+ * already excludes them.
56
+ *
57
+ * ── The `slots:` read, isolated on purpose (gh#1447) ────────────────────
58
+ * `declaredSlotNames()` below is the ONLY place this script reads a yaml's
59
+ * `slots:` block. gh#1447 (a sibling ticket, NOT this one — this script does
60
+ * not touch the yaml `slots:` schema) may replace the header-documented
61
+ * "`slots.default` = consumer children" convention with a mechanical
62
+ * `owned:`/`parts:` field on each slot entry. When that lands, only this one
63
+ * function's body needs to change — every caller keeps asking
64
+ * `acceptsConsumerChildrenSlot(yamlText)` and gets the right answer.
65
+ *
66
+ * ── Resolving against the INSTALLED package ──────────────────────────────
67
+ * With no `--include=` flag, this script scans its own installed location:
68
+ * `<this-package>/components/` plus, if `@adia-ai/web-modules` resolves
69
+ * from here too (it's an optionalDependency of `@adia-ai/web-components`),
70
+ * that package's whole tree — the same two roots the repo-internal wrapper
71
+ * passes explicitly as repo-relative paths. No `yaml` npm dependency is
72
+ * used or required — `declaredSlotNames()` is a minimal, self-contained
73
+ * scalar/mapping reader scoped to exactly the `slots:` block, the same
74
+ * "no external yaml dependency" convention `bin/doc.mjs` already
75
+ * established for this package (its `parseYaml()`); `check:packed-imports`
76
+ * would fail a bin script that imported an undeclared bare specifier, so
77
+ * staying dependency-free here is load-bearing, not just tidy.
78
+ *
79
+ * Usage:
80
+ * npx adia-ui-audit-template-children
81
+ * npx adia-ui-audit-template-children --json
82
+ * npx adia-ui-audit-template-children --strict
83
+ * npx adia-ui-audit-template-children --include=<dir> [--include=<dir> ...]
84
+ * node bin/audit-template-children.mjs selftest
85
+ *
86
+ * Exit codes:
87
+ * 0 — no critical findings (or `selftest` passed)
88
+ * 1 — at least one critical finding (--strict raises to any finding),
89
+ * or a `selftest` assertion failed
90
+ * 2 — usage error, or (selftest only) a required tool was unavailable
91
+ */
92
+
93
+ import { readFileSync, readdirSync, statSync, existsSync, mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
94
+ import { join, relative, dirname, basename, resolve } from 'node:path';
95
+ import { fileURLToPath } from 'node:url';
96
+ import { createRequire } from 'node:module';
97
+ import { spawnSync } from 'node:child_process';
98
+ import { tmpdir } from 'node:os';
99
+
100
+ const __filename = fileURLToPath(import.meta.url);
101
+ const __dirname = dirname(__filename);
102
+ const PKG_ROOT = resolve(__dirname, '..'); // bin/ -> package root
103
+
104
+ // ── Minimal yaml scalar reader (no external `yaml` dependency) ─────────────
105
+ //
106
+ // Strips a trailing YAML comment from a single raw line. A `#` only starts
107
+ // a comment when it's at column 0 or preceded by whitespace (the YAML
108
+ // plain-scalar rule) — a bare mid-word `#` (e.g. `gh#1253` inside a folded
109
+ // description block) is content, not a comment marker. Quote state is
110
+ // tracked (paired ' / ") so a `#` inside an actual quoted string is never
111
+ // treated as a comment start either. Same fix as `bin/doc.mjs`'s
112
+ // `stripYamlComment` (PR #1284 — the naive `/^([^#"']*)(#.*)?$/` regex cut
113
+ // any line at its first bare `#`, silently truncating `gh#NNNN` mentions).
114
+ function stripYamlComment(line) {
115
+ let inSingle = false;
116
+ let inDouble = false;
117
+ for (let i = 0; i < line.length; i++) {
118
+ const ch = line[i];
119
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
120
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
121
+ else if (ch === '#' && !inSingle && !inDouble && (i === 0 || /\s/.test(line[i - 1]))) {
122
+ return line.slice(0, i).replace(/\s+$/, '');
123
+ }
124
+ }
125
+ return line.replace(/\s+$/, '');
126
+ }
127
+
128
+ /**
129
+ * Reads a yaml source's top-level `slots:` block and returns the set of
130
+ * declared slot names — nothing more. THE one helper gh#1447's own change
131
+ * (a mechanical `owned:`/`parts:` field) swaps out from the inside; every
132
+ * caller keeps asking `acceptsConsumerChildrenSlot(yamlText)`.
133
+ */
134
+ export function declaredSlotNames(yamlText) {
135
+ const lines = yamlText.split(/\r?\n/).map(stripYamlComment);
136
+ const names = new Set();
137
+ let inSlots = false;
138
+ for (const line of lines) {
139
+ if (!inSlots) {
140
+ if (/^slots\s*:\s*$/.test(line)) inSlots = true;
141
+ continue;
142
+ }
143
+ if (line === '') continue;
144
+ if (/^\S/.test(line)) break; // next top-level key — block over
145
+ const m = line.match(/^ {2}([^:\s]+)\s*:/);
146
+ if (m) names.add(m[1]);
147
+ }
148
+ return names;
149
+ }
150
+
151
+ /** The header-documented definition of "accepts consumer children" (slot half). */
152
+ export function acceptsConsumerChildrenSlot(yamlText) {
153
+ return declaredSlotNames(yamlText).has('default');
154
+ }
155
+
156
+ // ── `static template` extraction (verbatim from the gh#284 audit) ─────────
157
+ //
158
+ // Matches the three declaration shapes seen in the codebase:
159
+ // static template = () => null;
160
+ // static template = () => html`...`;
161
+ // static template = (el) => { ... };
162
+ // We only need to know (a) whether the declaration exists, and (b) whether
163
+ // its RHS is the literal no-op `() => null` — not evaluate it.
164
+ export function extractTemplateDecl(jsText) {
165
+ const m = jsText.match(/static\s+template\s*=\s*/);
166
+ if (!m) return null;
167
+ const start = m.index + m[0].length;
168
+ let depthParen = 0, depthBrace = 0, inBacktick = false;
169
+ let i = start;
170
+ while (i < jsText.length) {
171
+ const c = jsText[i];
172
+ if (inBacktick) {
173
+ if (c === '\\') { i += 2; continue; }
174
+ if (c === '`') inBacktick = false;
175
+ i++;
176
+ continue;
177
+ }
178
+ if (c === '`') { inBacktick = true; i++; continue; }
179
+ if (c === '(') depthParen++;
180
+ else if (c === ')') depthParen--;
181
+ else if (c === '{') depthBrace++;
182
+ else if (c === '}') depthBrace--;
183
+ else if (c === ';' && depthParen <= 0 && depthBrace <= 0) break;
184
+ i++;
185
+ }
186
+ return jsText.slice(start, i).trim();
187
+ }
188
+
189
+ export function isNullTemplate(decl) {
190
+ if (!decl) return true; // no declaration at all — inherits base `() => null`
191
+ const normalized = decl.replace(/\s+/g, '');
192
+ return /^\(\)=>null$/.test(normalized) || /^\(\)=>\{return null;?\}$/.test(normalized);
193
+ }
194
+
195
+ // ── Projected-text heuristic (advisory) ─────────────────────────────────
196
+ //
197
+ // Looks for the component's own tag used in its top-of-file JSDoc usage
198
+ // example with non-empty text content between open/close tags — the shape
199
+ // of `<text-ui variant="heading">Hello World</text-ui>`. Attribute-only
200
+ // examples (`<button-ui text="Save">`) and self-closing/empty-body examples
201
+ // don't match.
202
+ export function hasProjectedTextExample(jsText, tag) {
203
+ const re = new RegExp(`<${tag}\\b[^>]*>([^<]*[a-zA-Z][^<]*)</${tag}>`, 'i');
204
+ const m = jsText.match(re);
205
+ return !!(m && m[1] && m[1].trim().length > 0);
206
+ }
207
+
208
+ // ── Component enumeration ────────────────────────────────────────────────
209
+
210
+ export function walkYamls(dir, out = []) {
211
+ let entries;
212
+ try {
213
+ entries = readdirSync(dir);
214
+ } catch {
215
+ return out;
216
+ }
217
+ for (const name of entries) {
218
+ if (name === 'node_modules' || name === 'dist' || name.startsWith('.')) continue;
219
+ const full = join(dir, name);
220
+ let s;
221
+ try {
222
+ s = statSync(full);
223
+ } catch {
224
+ continue;
225
+ }
226
+ if (s.isDirectory()) {
227
+ walkYamls(full, out);
228
+ } else if (name.endsWith('.yaml') && !name.endsWith('.a2ui.json.yaml')) {
229
+ const parentName = basename(dir);
230
+ if (name === `${parentName}.yaml`) out.push(full);
231
+ }
232
+ }
233
+ return out;
234
+ }
235
+
236
+ export function findClassJs(componentDir, name) {
237
+ const v2 = join(componentDir, `${name}.class.js`);
238
+ if (existsSync(v2)) return v2;
239
+ const v1 = join(componentDir, 'class.js');
240
+ if (existsSync(v1)) return v1;
241
+ return null;
242
+ }
243
+
244
+ /**
245
+ * Default scan roots when no `--include=` is given: this package's own
246
+ * `components/`, plus `@adia-ai/web-modules`'s package root IF that
247
+ * optionalDependency resolves from here (declared, per
248
+ * `check:packed-imports`'s undeclared-specifier gate, in
249
+ * `package.json#optionalDependencies`) — a consumer who installed it gets
250
+ * the same two-root scan the repo-internal wrapper passes explicitly.
251
+ */
252
+ export function defaultIncludes() {
253
+ const includes = [resolve(PKG_ROOT, 'components')];
254
+ const req = createRequire(import.meta.url);
255
+ try {
256
+ const wmPkgJsonPath = req.resolve('@adia-ai/web-modules/package.json');
257
+ includes.push(dirname(wmPkgJsonPath));
258
+ } catch {
259
+ // Optional — not installed in this consumer. web-components-only scan.
260
+ }
261
+ return includes;
262
+ }
263
+
264
+ // ── Core scan ────────────────────────────────────────────────────────────
265
+
266
+ export function runAudit(includes) {
267
+ const yamlPaths = [];
268
+ for (const inc of includes) walkYamls(inc, yamlPaths);
269
+
270
+ const findings = [];
271
+
272
+ for (const yamlPath of yamlPaths) {
273
+ const yamlText = readFileSync(yamlPath, 'utf8');
274
+ const componentDir = dirname(yamlPath);
275
+ const compName = basename(yamlPath, '.yaml');
276
+ const classPath = findClassJs(componentDir, compName);
277
+ if (!classPath) continue; // CSS-only component — no stamp() involved
278
+
279
+ const jsText = readFileSync(classPath, 'utf8');
280
+ const decl = extractTemplateDecl(jsText);
281
+ if (isNullTemplate(decl)) continue; // never calls stamp() — no exposure
282
+
283
+ // yaml `tag:` scalar — top-level, single-line only (matches every
284
+ // component in this repo; a folded/quoted tag would be unusual enough
285
+ // to fall back on the compName-derived default below).
286
+ const tagMatch = yamlText.match(/^tag\s*:\s*(.+)$/m);
287
+ const tag = (tagMatch ? tagMatch[1].trim() : '') || `${compName}-ui`;
288
+
289
+ if (acceptsConsumerChildrenSlot(yamlText)) {
290
+ findings.push({
291
+ component: compName,
292
+ tag,
293
+ yaml: yamlPath,
294
+ classJs: classPath,
295
+ shape: 'container',
296
+ severity: 'critical',
297
+ templateDecl: decl.length > 80 ? decl.slice(0, 80) + '…' : decl,
298
+ });
299
+ continue; // avoid double-reporting the same component below
300
+ }
301
+
302
+ if (hasProjectedTextExample(jsText, tag)) {
303
+ findings.push({
304
+ component: compName,
305
+ tag,
306
+ yaml: yamlPath,
307
+ classJs: classPath,
308
+ shape: 'projected-text',
309
+ severity: 'advisory',
310
+ templateDecl: decl.length > 80 ? decl.slice(0, 80) + '…' : decl,
311
+ });
312
+ }
313
+ }
314
+
315
+ const critical = findings.filter((f) => f.severity === 'critical');
316
+ const advisory = findings.filter((f) => f.severity === 'advisory');
317
+ return { scanned: yamlPaths.length, findings, critical, advisory };
318
+ }
319
+
320
+ // ── Output ───────────────────────────────────────────────────────────────
321
+
322
+ function relPath(p) {
323
+ return relative(process.cwd(), p);
324
+ }
325
+
326
+ export function printReport(result, { jsonOut = false } = {}) {
327
+ const { scanned, findings, critical, advisory } = result;
328
+
329
+ if (jsonOut) {
330
+ process.stdout.write(JSON.stringify({
331
+ scanned,
332
+ findings: findings.map((f) => ({ ...f, yaml: relPath(f.yaml), classJs: relPath(f.classJs) })),
333
+ summary: { critical: critical.length, advisory: advisory.length },
334
+ }, null, 2) + '\n');
335
+ return;
336
+ }
337
+
338
+ if (critical.length) {
339
+ console.log('\n✗ CRITICAL — non-null template + a documented `slots.default` (gh#284 container shape):\n');
340
+ for (const f of critical) {
341
+ console.log(` ✗ <${f.tag}> static template = ${f.templateDecl}`);
342
+ console.log(` yaml: ${relPath(f.yaml)}`);
343
+ console.log(` class: ${relPath(f.classJs)}`);
344
+ }
345
+ console.log('\n connectedCallback\'s stamp() replaces ALL children on first mount. A');
346
+ console.log(' documented default slot means real consumer children can be present there');
347
+ console.log(' before connect (SSR, or a consumer pre-populating markup) — they will be');
348
+ console.log(' silently wiped. Either make the template null (compose via CSS + render()');
349
+ console.log(' DOM surgery), or treat this as a real regression against gh#284 / gh#1446\'s');
350
+ console.log(' documented contract before shipping the component.\n');
351
+ }
352
+ if (advisory.length) {
353
+ console.log('\n⚠ ADVISORY — non-null template + a body-text usage example (gh#284 projected-text shape):\n');
354
+ for (const f of advisory) {
355
+ console.log(` ⚠ <${f.tag}> static template = ${f.templateDecl}`);
356
+ console.log(` yaml: ${relPath(f.yaml)}`);
357
+ console.log(` class: ${relPath(f.classJs)}`);
358
+ }
359
+ console.log('\n No yaml `slots.default` to confirm this mechanically — the component\'s own');
360
+ console.log(' usage example shows plain text content between its tags, which stamp()\'s');
361
+ console.log(' first-mount replaceChildren would destroy under SSR. Verify manually.\n');
362
+ }
363
+ console.log(`[audit-template-children] scanned ${scanned} component yaml(s):`);
364
+ console.log(` critical: ${critical.length}`);
365
+ console.log(` advisory: ${advisory.length}\n`);
366
+ }
367
+
368
+ // ── CLI ──────────────────────────────────────────────────────────────────
369
+
370
+ function usage(code) {
371
+ console.error('Usage: adia-ui-audit-template-children [--json] [--strict] [--include=<dir> ...]');
372
+ console.error(' adia-ui-audit-template-children selftest');
373
+ return code;
374
+ }
375
+
376
+ export function main(argv) {
377
+ if (argv[0] === 'selftest') return selftest();
378
+
379
+ for (const a of argv) {
380
+ if (a === '--json' || a === '--strict' || a.startsWith('--include=')) continue;
381
+ return usage(2);
382
+ }
383
+
384
+ const jsonOut = argv.includes('--json');
385
+ const strict = argv.includes('--strict');
386
+ const includeArgs = argv.filter((a) => a.startsWith('--include='));
387
+ const includes = includeArgs.length
388
+ ? includeArgs.map((a) => resolve(process.cwd(), a.slice('--include='.length)))
389
+ : defaultIncludes();
390
+
391
+ const result = runAudit(includes);
392
+ printReport(result, { jsonOut });
393
+
394
+ if (result.critical.length > 0) return 1;
395
+ if (strict && result.advisory.length > 0) return 1;
396
+ return 0;
397
+ }
398
+
399
+ // ── Selftest ─────────────────────────────────────────────────────────────
400
+ //
401
+ // Proves the shared implementation end to end against a scratch fixture
402
+ // tree shaped like a real (or installed) `components/` directory —
403
+ // deliberately spawns THIS file as a child process rather than only
404
+ // calling `main()` in-process, so it also proves the CLI wiring (argv
405
+ // parsing, exit codes, --json shape), not just the pure functions.
406
+
407
+ function writeFixtureComponent(dir, name, { tag, template, slots, usageExample }) {
408
+ const compDir = join(dir, name);
409
+ mkdirSync(compDir, { recursive: true });
410
+ const yamlLines = [`name: UI${name}`, `tag: ${tag}`, `component: ${name}`];
411
+ if (slots && slots.length) {
412
+ yamlLines.push('slots:');
413
+ for (const s of slots) yamlLines.push(` ${s}:\n description: "test slot"`);
414
+ }
415
+ writeFileSync(join(compDir, `${name}.yaml`), yamlLines.join('\n') + '\n');
416
+ const jsLines = [
417
+ '/**',
418
+ ` * Usage: ${usageExample ?? `<${tag}></${tag}>`}`,
419
+ ' */',
420
+ `export class UI${name} {`,
421
+ ` static template = ${template};`,
422
+ '}',
423
+ ];
424
+ writeFileSync(join(compDir, `${name}.class.js`), jsLines.join('\n') + '\n');
425
+ }
426
+
427
+ function selftest() {
428
+ const scratch = mkdtempSync(join(tmpdir(), 'audit-template-children-selftest-'));
429
+ const failures = [];
430
+ const assert = (cond, msg) => { if (!cond) failures.push(msg); console.log(` ${cond ? 'ok ' : 'FAIL'} ${msg}`); };
431
+
432
+ try {
433
+ // ── Unit: declaredSlotNames / acceptsConsumerChildrenSlot ──
434
+ assert(
435
+ declaredSlotNames('name: Foo\nslots:\n default:\n description: "x"\n header:\n description: "y"\nprops:\n bar: {}\n').size === 2,
436
+ 'declaredSlotNames: reads a 2-entry slots block'
437
+ );
438
+ assert(
439
+ acceptsConsumerChildrenSlot('name: Foo\nslots:\n default:\n description: "x"\n'),
440
+ 'acceptsConsumerChildrenSlot: true when slots.default present'
441
+ );
442
+ assert(
443
+ !acceptsConsumerChildrenSlot('name: Foo\nslots:\n header:\n description: "x"\n'),
444
+ 'acceptsConsumerChildrenSlot: false when default is absent (non-default slot only)'
445
+ );
446
+ assert(
447
+ !acceptsConsumerChildrenSlot('name: Foo\ndescription: "no slots block at all"\n'),
448
+ 'acceptsConsumerChildrenSlot: false with no slots: block'
449
+ );
450
+ // PR #1284 regression: a bare `gh#NNNN` inside a description must not
451
+ // truncate the line as if `#` started a comment.
452
+ assert(
453
+ declaredSlotNames('slots:\n default:\n description: "see gh#1446 for context"\n').has('default'),
454
+ 'declaredSlotNames: a mid-word gh#NNNN is not treated as a comment'
455
+ );
456
+
457
+ // ── Unit: template extraction ──
458
+ assert(isNullTemplate(extractTemplateDecl('static template = () => null;')), 'isNullTemplate: () => null');
459
+ assert(!isNullTemplate(extractTemplateDecl('static template = () => html`<span></span>`;')), 'isNullTemplate: false for a real template');
460
+ assert(isNullTemplate(extractTemplateDecl('no template here')), 'isNullTemplate: true when no declaration exists');
461
+
462
+ // ── End-to-end: a scratch `components/` tree + CLI invocation ──
463
+ const componentsDir = join(scratch, 'components');
464
+ // container shape — MUST be critical
465
+ writeFixtureComponent(componentsDir, 'leaky-container', {
466
+ tag: 'leaky-container-ui',
467
+ template: '() => html`<div></div>`',
468
+ slots: ['default'],
469
+ });
470
+ // projected-text shape — MUST be advisory
471
+ writeFixtureComponent(componentsDir, 'leaky-text', {
472
+ tag: 'leaky-text-ui',
473
+ template: '() => html`<span></span>`',
474
+ slots: [],
475
+ usageExample: '<leaky-text-ui>Hello World</leaky-text-ui>',
476
+ });
477
+ // safe: null template + slots.default — NEGATIVE CONTROL, no finding
478
+ writeFixtureComponent(componentsDir, 'safe-container', {
479
+ tag: 'safe-container-ui',
480
+ template: '() => null',
481
+ slots: ['default'],
482
+ });
483
+ // safe: non-null template, non-default slot only, attribute-only usage
484
+ writeFixtureComponent(componentsDir, 'safe-attrs', {
485
+ tag: 'safe-attrs-ui',
486
+ template: '() => html`<span></span>`',
487
+ slots: ['icon'],
488
+ usageExample: '<safe-attrs-ui label="Save"></safe-attrs-ui>',
489
+ });
490
+
491
+ const invoke = (...args) => spawnSync(process.execPath, [__filename, ...args], { cwd: scratch, encoding: 'utf8' });
492
+
493
+ const jsonRun = invoke(`--include=${componentsDir}`, '--json');
494
+ assert(jsonRun.status === 1, `critical finding present -> exit 1 (got ${jsonRun.status}, stderr: ${jsonRun.stderr})`);
495
+ let parsed = null;
496
+ try { parsed = JSON.parse(jsonRun.stdout); } catch (e) { failures.push(`--json produced invalid JSON: ${e.message}`); }
497
+ if (parsed) {
498
+ assert(parsed.summary.critical === 1, `exactly 1 critical finding (got ${parsed.summary?.critical})`);
499
+ assert(parsed.summary.advisory === 1, `exactly 1 advisory finding (got ${parsed.summary?.advisory})`);
500
+ assert(parsed.scanned === 4, `scanned all 4 fixture components (got ${parsed.scanned})`);
501
+ const criticalNames = parsed.findings.filter(f => f.severity === 'critical').map(f => f.component);
502
+ assert(criticalNames.includes('leaky-container') && !criticalNames.includes('safe-container'),
503
+ 'critical finding is leaky-container only (negative control safe-container excluded)');
504
+ }
505
+
506
+ // --strict raises an advisory-only-would-be-0 run to 1 — but here critical
507
+ // is already present, so just confirm --strict doesn't LOWER the exit code.
508
+ const strictRun = invoke(`--include=${componentsDir}`, '--strict');
509
+ assert(strictRun.status === 1, `--strict with a critical present -> exit 1 (got ${strictRun.status})`);
510
+
511
+ // Advisory-only tree (drop the critical fixture) — plain run exits 0,
512
+ // --strict raises it to 1.
513
+ const advisoryOnlyDir = join(scratch, 'components-advisory-only');
514
+ writeFixtureComponent(advisoryOnlyDir, 'leaky-text', {
515
+ tag: 'leaky-text-ui',
516
+ template: '() => html`<span></span>`',
517
+ slots: [],
518
+ usageExample: '<leaky-text-ui>Hello World</leaky-text-ui>',
519
+ });
520
+ const plainAdvisory = invoke(`--include=${advisoryOnlyDir}`);
521
+ assert(plainAdvisory.status === 0, `advisory-only, no --strict -> exit 0 (got ${plainAdvisory.status})`);
522
+ const strictAdvisory = invoke(`--include=${advisoryOnlyDir}`, '--strict');
523
+ assert(strictAdvisory.status === 1, `advisory-only, --strict -> exit 1 (got ${strictAdvisory.status})`);
524
+
525
+ // Empty tree — clean exit 0, scanned 0.
526
+ const emptyDir = join(scratch, 'empty');
527
+ mkdirSync(emptyDir, { recursive: true });
528
+ const emptyRun = invoke(`--include=${emptyDir}`, '--json');
529
+ assert(emptyRun.status === 0, `empty include dir -> exit 0 (got ${emptyRun.status})`);
530
+
531
+ // Usage error.
532
+ const bogus = invoke('--not-a-real-flag');
533
+ assert(bogus.status === 2, `unknown flag -> exit 2 (got ${bogus.status})`);
534
+
535
+ // defaultIncludes() resolves without throwing even when web-modules
536
+ // isn't resolvable (this scratch dir has no node_modules at all).
537
+ let defaultsOk = true;
538
+ try { defaultIncludes(); } catch { defaultsOk = false; }
539
+ assert(defaultsOk, 'defaultIncludes() does not throw when @adia-ai/web-modules is unresolvable');
540
+ } finally {
541
+ rmSync(scratch, { recursive: true, force: true });
542
+ }
543
+
544
+ const pass = failures.length === 0;
545
+ console.log(`\n${pass ? 'ok' : 'FAIL'} — audit-template-children selftest (${failures.length} failure(s))`);
546
+ return pass ? 0 : 1;
547
+ }
548
+
549
+ // ── Entrypoint guard — only run the CLI when this file is the process's
550
+ // own entry script, so importing these exports (the package's own test
551
+ // suite) never has the side effect of running main() against the test
552
+ // runner's own argv. ──
553
+ const isMain = process.argv[1] && resolve(process.argv[1]) === __filename;
554
+ if (isMain) {
555
+ process.exitCode = main(process.argv.slice(2));
556
+ }
@@ -40,6 +40,9 @@
40
40
  ],
41
41
  "unevaluatedProperties": false,
42
42
  "x-adiaui": {
43
+ "allowedParents": [
44
+ "Accordion"
45
+ ],
43
46
  "anti_patterns": [],
44
47
  "category": "layout",
45
48
  "composes": [],
@@ -64,6 +67,7 @@
64
67
  "section-toggle"
65
68
  ],
66
69
  "name": "UIAccordionItem",
70
+ "parts": {},
67
71
  "related": [
68
72
  "Accordion",
69
73
  "Card"
@@ -105,6 +105,8 @@ related:
105
105
  - Card
106
106
 
107
107
  a2ui:
108
+ allowedParents:
109
+ - Accordion
108
110
  rules:
109
111
  - rule: 'Child of <accordion-ui> — places one collapsible section with header + body.'
110
112
  reason: 'Cluster contract.'
@@ -36,6 +36,9 @@
36
36
  ],
37
37
  "unevaluatedProperties": false,
38
38
  "x-adiaui": {
39
+ "allowedChildren": [
40
+ "AccordionItem"
41
+ ],
39
42
  "anti_patterns": [],
40
43
  "category": "container",
41
44
  "composes": [
@@ -76,6 +79,7 @@
76
79
  "expand"
77
80
  ],
78
81
  "name": "UIAccordion",
82
+ "parts": {},
79
83
  "related": [
80
84
  "accordion-item",
81
85
  "input",
@@ -57,6 +57,8 @@ tokens: {}
57
57
  requiredIcons:
58
58
  - caret-down
59
59
  a2ui:
60
+ allowedChildren:
61
+ - AccordionItem
60
62
  rules:
61
63
  - rule: 'Hosts <accordion-item-ui> children. One or more sections may be open simultaneously (set single= to enforce only-one-open).'
62
64
  reason: 'Multi-vs-single open mode.'
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/ActionItem.json",
4
4
  "title": "ActionItem",
5
- "description": "Child of <action-list-ui>. One actionable row — icon + label, forwards activation to the parent action-list.",
5
+ "description": "Child of <action-list-ui>. One actionable row — icon + label + optional\nsubtitle, forwards activation to the parent action-list. Row contract\nconverges on <menu-item-ui>: icon/text/subtitle/value/variant/disabled\nprops and icon/text/subtitle/trailing slot overrides (#1363 C5).",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -25,6 +25,11 @@
25
25
  "description": "Leading icon name (Phosphor).",
26
26
  "type": "string"
27
27
  },
28
+ "subtitle": {
29
+ "description": "Optional secondary description line rendered under the label.",
30
+ "type": "string",
31
+ "default": ""
32
+ },
28
33
  "text": {
29
34
  "description": "Action label.",
30
35
  "type": "string"
@@ -34,8 +39,12 @@
34
39
  "type": "string"
35
40
  },
36
41
  "variant": {
37
- "description": "Visual variant (default | accent | danger).",
42
+ "description": "Visual variant (default | danger).",
38
43
  "type": "string",
44
+ "enum": [
45
+ "default",
46
+ "danger"
47
+ ],
39
48
  "default": "default"
40
49
  }
41
50
  },
@@ -44,6 +53,9 @@
44
53
  ],
45
54
  "unevaluatedProperties": false,
46
55
  "x-adiaui": {
56
+ "allowedParents": [
57
+ "ActionList"
58
+ ],
47
59
  "anti_patterns": [],
48
60
  "category": "navigation",
49
61
  "composes": [],
@@ -57,6 +69,7 @@
57
69
  "action-list-row"
58
70
  ],
59
71
  "name": "UIActionItem",
72
+ "parts": {},
60
73
  "related": [
61
74
  "ActionList",
62
75
  "MenuItem",
@@ -65,6 +78,15 @@
65
78
  "slots": {
66
79
  "icon": {
67
80
  "description": "Override the [icon] glyph with a custom slotted element (custom icon-ui, image, avatar). Mutually exclusive with the [icon] attribute — slot child wins."
81
+ },
82
+ "subtitle": {
83
+ "description": "Custom secondary-line content override; falls through to [subtitle] prop if not slotted."
84
+ },
85
+ "text": {
86
+ "description": "Custom label content; falls through to [text] prop if not slotted."
87
+ },
88
+ "trailing": {
89
+ "description": "Trailing element pinned to the far edge (shortcut hint, badge, caret). Supersedes the unnamed default slot this row previously used for trailing content — see MIGRATION GUIDE."
68
90
  }
69
91
  },
70
92
  "states": [],