@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.
- package/CHANGELOG.md +50 -3
- package/MIGRATION.md +1042 -16
- package/README.md +25 -30
- package/USAGE.md +26 -0
- package/bin/audit-template-children.mjs +556 -0
- package/components/accordion/accordion-item.a2ui.json +4 -0
- package/components/accordion/accordion-item.yaml +2 -0
- package/components/accordion/accordion.a2ui.json +4 -0
- package/components/accordion/accordion.yaml +2 -0
- package/components/action-list/action-item.a2ui.json +24 -2
- package/components/action-list/action-item.yaml +28 -3
- package/components/action-list/action-list.a2ui.json +6 -1
- package/components/action-list/action-list.class.js +52 -11
- package/components/action-list/action-list.css +43 -3
- package/components/action-list/action-list.d.ts +4 -2
- package/components/action-list/action-list.yaml +5 -0
- package/components/adia-mark/adia-mark.a2ui.json +1 -0
- package/components/adia-wordmark/adia-wordmark.a2ui.json +1 -0
- package/components/agent-artifact/agent-artifact.a2ui.json +1 -0
- package/components/agent-feedback-bar/agent-feedback-bar.a2ui.json +6 -5
- package/components/agent-feedback-bar/agent-feedback-bar.yaml +6 -2
- package/components/agent-questions/agent-questions.a2ui.json +1 -0
- package/components/agent-reasoning/agent-reasoning.a2ui.json +6 -5
- package/components/agent-reasoning/agent-reasoning.yaml +6 -2
- package/components/agent-suggestions/agent-suggestions.a2ui.json +11 -2
- package/components/agent-suggestions/agent-suggestions.class.js +2 -1
- package/components/agent-suggestions/agent-suggestions.d.ts +4 -4
- package/components/agent-suggestions/agent-suggestions.yaml +4 -2
- package/components/agent-trace/agent-trace.a2ui.json +6 -5
- package/components/agent-trace/agent-trace.yaml +6 -2
- package/components/alert/alert.a2ui.json +1 -0
- package/components/alert/alert.css +1 -1
- package/components/anchor-bar/anchor-bar.a2ui.json +1 -0
- package/components/aside/aside.a2ui.json +1 -0
- package/components/avatar/avatar-group.a2ui.json +7 -1
- package/components/avatar/avatar-group.yaml +1 -0
- package/components/avatar/avatar.a2ui.json +12 -11
- package/components/avatar/avatar.d.ts +1 -1
- package/components/avatar/avatar.yaml +4 -4
- package/components/badge/badge.a2ui.json +1 -0
- package/components/block/block.a2ui.json +1 -0
- package/components/blockquote/blockquote.a2ui.json +1 -0
- package/components/breadcrumb/breadcrumb.a2ui.json +1 -0
- package/components/button/button.a2ui.json +1 -3
- package/components/button/button.yaml +1 -3
- package/components/calendar-grid/calendar-grid.a2ui.json +1 -0
- package/components/calendar-grid/calendar-grid.class.js +2 -1
- package/components/calendar-picker/calendar-picker.a2ui.json +143 -24
- package/components/calendar-picker/calendar-picker.class.js +490 -197
- package/components/calendar-picker/calendar-picker.css +120 -171
- package/components/calendar-picker/calendar-picker.yaml +225 -47
- package/components/canvas/canvas.a2ui.json +6 -5
- package/components/canvas/canvas.d.ts +37 -0
- package/components/canvas/canvas.js +72 -3
- package/components/canvas/canvas.yaml +6 -2
- package/components/card/card.a2ui.json +25 -2
- package/components/card/card.class.js +3 -0
- package/components/card/card.css +31 -1
- package/components/card/card.d.ts +2 -2
- package/components/card/card.examples.md +3 -3
- package/components/card/card.yaml +28 -4
- package/components/chart/chart.a2ui.json +9 -4
- package/components/chart/chart.class.js +6 -6
- package/components/chart/chart.css +6 -4
- package/components/chart/chart.d.ts +1 -1
- package/components/chart/chart.yaml +5 -3
- package/components/chart-legend/chart-legend.a2ui.json +1 -0
- package/components/chat-thread/chat-input.a2ui.json +1 -0
- package/components/chat-thread/chat-thread.a2ui.json +10 -9
- package/components/chat-thread/chat-thread.yaml +13 -20
- package/components/check/check.a2ui.json +10 -9
- package/components/check/check.yaml +4 -4
- package/components/code/code.a2ui.json +1 -0
- package/components/col/col.a2ui.json +1 -0
- package/components/color-input/color-input.a2ui.json +1 -0
- package/components/color-picker/color-picker.a2ui.json +1 -0
- package/components/combobox/combobox.a2ui.json +3 -2
- package/components/combobox/combobox.yaml +2 -2
- package/components/command/command.a2ui.json +12 -10
- package/components/command/command.class.js +3 -2
- package/components/command/command.yaml +17 -4
- package/components/context-menu/context-menu.a2ui.json +1 -0
- package/components/date-range-picker/date-range-picker.a2ui.json +2 -1
- package/components/date-range-picker/date-range-picker.yaml +1 -1
- package/components/datetime-picker/datetime-picker.a2ui.json +15 -108
- package/components/datetime-picker/datetime-picker.class.js +37 -690
- package/components/datetime-picker/datetime-picker.css +11 -183
- package/components/datetime-picker/datetime-picker.d.ts +4 -5
- package/components/datetime-picker/datetime-picker.yaml +44 -135
- package/components/demo-toggle/demo-toggle.a2ui.json +1 -0
- package/components/demo-toggle/demo-toggle.css +1 -1
- package/components/description-list/description-list.a2ui.json +1 -0
- package/components/display-field/display-field.a2ui.json +1 -0
- package/components/divider/divider.a2ui.json +1 -0
- package/components/drawer/drawer.a2ui.json +1 -3
- package/components/drawer/drawer.css +1 -1
- package/components/drawer/drawer.yaml +0 -2
- package/components/drilldown/drilldown.a2ui.json +22 -20
- package/components/drilldown/drilldown.class.js +4 -3
- package/components/drilldown/drilldown.css +6 -6
- package/components/drilldown/drilldown.yaml +40 -9
- package/components/embed/embed.a2ui.json +6 -5
- package/components/embed/embed.yaml +2 -2
- package/components/empty-state/empty-state.a2ui.json +1 -0
- package/components/feed/feed-item.a2ui.json +25 -5
- package/components/feed/feed-item.yaml +28 -7
- package/components/feed/feed.a2ui.json +9 -2
- package/components/feed/feed.class.js +19 -12
- package/components/feed/feed.css +8 -8
- package/components/feed/feed.yaml +14 -1
- package/components/field/field.a2ui.json +1 -0
- package/components/field/field.examples.md +1 -1
- package/components/fields/fields.a2ui.json +1 -0
- package/components/fields/fields.examples.md +1 -1
- package/components/footer/footer.a2ui.json +1 -0
- package/components/frame/frame.a2ui.json +1 -0
- package/components/grid/grid.a2ui.json +1 -0
- package/components/header/header.a2ui.json +3 -2
- package/components/header/header.yaml +3 -3
- package/components/heatmap/heatmap.a2ui.json +2 -1
- package/components/heatmap/heatmap.class.js +1 -1
- package/components/heatmap/heatmap.css +4 -4
- package/components/heatmap/heatmap.yaml +1 -1
- package/components/icon/icon.a2ui.json +1 -0
- package/components/image/image.a2ui.json +28 -14
- package/components/image/image.class.js +38 -23
- package/components/image/image.css +18 -8
- package/components/image/image.d.ts +2 -2
- package/components/image/image.examples.md +3 -3
- package/components/image/image.yaml +23 -7
- package/components/inline-edit/inline-edit.a2ui.json +1 -0
- package/components/inline-message/inline-message.a2ui.json +3 -1
- package/components/inline-message/inline-message.class.js +3 -1
- package/components/inline-message/inline-message.d.ts +2 -2
- package/components/inline-message/inline-message.yaml +2 -1
- package/components/input/input.a2ui.json +66 -16
- package/components/input/input.class.js +64 -7
- package/components/input/input.css +14 -14
- package/components/input/input.d.ts +9 -4
- package/components/input/input.yaml +83 -10
- package/components/inspector/inspector.a2ui.json +1 -0
- package/components/integration-card/integration-card.a2ui.json +1 -0
- package/components/kbd/kbd.a2ui.json +1 -0
- package/components/link/link.a2ui.json +2 -1
- package/components/link/link.yaml +1 -1
- package/components/list/list-item.a2ui.json +9 -0
- package/components/list/list-item.yaml +12 -0
- package/components/list/list.a2ui.json +9 -3
- package/components/list/list.class.js +14 -1
- package/components/list/list.css +12 -1
- package/components/list/list.d.ts +7 -5
- package/components/list/list.yaml +16 -6
- package/components/list-window/list-window.a2ui.json +1 -0
- package/components/list-window/list-window.class.js +2 -1
- package/components/loading-overlay/loading-overlay.a2ui.json +1 -0
- package/components/mark/mark.a2ui.json +2 -1
- package/components/mark/mark.yaml +1 -1
- package/components/menu/menu-divider.a2ui.json +1 -0
- package/components/menu/menu-item.a2ui.json +3 -1
- package/components/menu/menu-item.yaml +6 -0
- package/components/menu/menu-label.a2ui.json +1 -0
- package/components/menu/menu.a2ui.json +3 -2
- package/components/menu/menu.class.js +2 -2
- package/components/menu/menu.d.ts +2 -2
- package/components/menu/menu.yaml +3 -3
- package/components/modal/modal.a2ui.json +1 -0
- package/components/nav/nav.a2ui.json +1 -0
- package/components/nav/nav.class.js +14 -3
- package/components/nav-group/nav-group.a2ui.json +1 -0
- package/components/nav-group/nav-group.class.js +3 -1
- package/components/nav-item/nav-item.a2ui.json +1 -0
- package/components/noodles/noodles.a2ui.json +1 -0
- package/components/noodles/noodles.class.js +5 -1
- package/components/number-format/number-format.a2ui.json +1 -0
- package/components/option-card/option-card.a2ui.json +1 -0
- package/components/otp-input/otp-input.a2ui.json +3 -2
- package/components/otp-input/otp-input.yaml +9 -2
- package/components/page/page.a2ui.json +13 -0
- package/components/page/page.d.ts +1 -1
- package/components/page/page.yaml +1 -0
- package/components/pagination/pagination.a2ui.json +1 -0
- package/components/pagination/pagination.class.js +3 -2
- package/components/pane/pane.a2ui.json +6 -0
- package/components/pane/pane.class.js +3 -3
- package/components/pane/pane.css +2 -2
- package/components/pane/pane.yaml +3 -0
- package/components/password-strength/password-strength.a2ui.json +1 -0
- package/components/pipeline-status/pipeline-status.a2ui.json +1 -0
- package/components/popover/popover.a2ui.json +7 -6
- package/components/popover/popover.class.js +2 -2
- package/components/popover/popover.d.ts +2 -2
- package/components/popover/popover.yaml +4 -4
- package/components/preview/preview.a2ui.json +1 -0
- package/components/preview/preview.class.js +5 -1
- package/components/progress/progress.a2ui.json +47 -24
- package/components/progress/progress.class.js +66 -11
- package/components/progress/progress.css +44 -26
- package/components/progress/progress.d.ts +7 -5
- package/components/progress/progress.examples.md +2 -2
- package/components/progress/progress.yaml +235 -53
- package/components/progress-row/progress-row.a2ui.json +16 -12
- package/components/progress-row/progress-row.d.ts +2 -2
- package/components/progress-row/progress-row.examples.md +2 -14
- package/components/progress-row/progress-row.yaml +28 -36
- package/components/qr-code/qr-code.a2ui.json +3 -2
- package/components/qr-code/qr-code.class.js +4 -4
- package/components/qr-code/qr-code.css +1 -1
- package/components/qr-code/qr-code.yaml +5 -3
- package/components/radio/radio.a2ui.json +8 -7
- package/components/radio/radio.class.js +8 -0
- package/components/radio/radio.yaml +3 -3
- package/components/radio-group/radio-group.a2ui.json +5 -3
- package/components/radio-group/radio-group.class.js +11 -0
- package/components/radio-group/radio-group.yaml +7 -1
- package/components/range/range.a2ui.json +6 -0
- package/components/range/range.class.js +2 -2
- package/components/range/range.css +2 -2
- package/components/range/range.yaml +3 -0
- package/components/rating/rating.a2ui.json +1 -0
- package/components/relative-time/relative-time.a2ui.json +1 -0
- package/components/richtext/richtext.a2ui.json +2 -1
- package/components/richtext/richtext.yaml +1 -1
- package/components/row/row.a2ui.json +1 -0
- package/components/search/search.a2ui.json +1 -0
- package/components/search/search.class.js +4 -1
- package/components/section/section.a2ui.json +1 -0
- package/components/segment/segment.a2ui.json +21 -10
- package/components/segment/segment.css +15 -0
- package/components/segment/segment.d.ts +2 -1
- package/components/segment/segment.yaml +20 -13
- package/components/segmented/segmented.a2ui.json +36 -7
- package/components/segmented/segmented.class.js +113 -22
- package/components/segmented/segmented.css +3 -0
- package/components/segmented/segmented.d.ts +3 -1
- package/components/segmented/segmented.yaml +80 -8
- package/components/select/select.a2ui.json +18 -13
- package/components/select/select.class.js +61 -37
- package/components/select/select.css +15 -13
- package/components/select/select.yaml +18 -7
- package/components/skeleton/skeleton.a2ui.json +8 -7
- package/components/skeleton/skeleton.class.js +8 -7
- package/components/skeleton/skeleton.css +21 -6
- package/components/skeleton/skeleton.d.ts +2 -2
- package/components/skeleton/skeleton.examples.md +2 -2
- package/components/skeleton/skeleton.yaml +3 -2
- package/components/skip-nav/skip-nav.a2ui.json +1 -0
- package/components/slider/slider.a2ui.json +6 -0
- package/components/slider/slider.class.js +20 -3
- package/components/slider/slider.css +2 -2
- package/components/slider/slider.yaml +3 -0
- package/components/spinner/spinner.a2ui.json +1 -0
- package/components/stack/stack.a2ui.json +1 -0
- package/components/stat/stat.a2ui.json +1 -0
- package/components/stat/stat.js +2 -2
- package/components/step-progress/step-progress.a2ui.json +1 -0
- package/components/step-progress/step-progress.yaml +1 -1
- package/components/stepper/stepper-item.a2ui.json +7 -3
- package/components/stepper/stepper-item.yaml +15 -5
- package/components/stepper/stepper.a2ui.json +5 -1
- package/components/stepper/stepper.class.js +31 -16
- package/components/stepper/stepper.d.ts +1 -1
- package/components/stepper/stepper.yaml +4 -2
- package/components/stream/stream.a2ui.json +1 -0
- package/components/swatch/swatch.a2ui.json +1 -0
- package/components/swiper/swiper.a2ui.json +1 -0
- package/components/swiper/swiper.class.js +5 -1
- package/components/switch/switch.a2ui.json +12 -11
- package/components/switch/switch.css +1 -1
- package/components/switch/switch.yaml +6 -6
- package/components/table/table.a2ui.json +1 -0
- package/components/table/table.class.js +14 -12
- package/components/table/table.css +11 -9
- package/components/table-toolbar/table-toolbar.a2ui.json +1 -0
- package/components/table-toolbar/table-toolbar.class.js +38 -19
- package/components/table-toolbar/table-toolbar.css +1 -1
- package/components/tabs/tab.a2ui.json +9 -0
- package/components/tabs/tab.js +4 -0
- package/components/tabs/tab.yaml +15 -0
- package/components/tabs/tabs.a2ui.json +9 -4
- package/components/tabs/tabs.class.js +13 -1
- package/components/tabs/tabs.css +8 -0
- package/components/tabs/tabs.d.ts +2 -0
- package/components/tabs/tabs.yaml +9 -2
- package/components/tag/tag.a2ui.json +1 -0
- package/components/tags-input/tags-input.a2ui.json +22 -17
- package/components/tags-input/tags-input.class.js +13 -11
- package/components/tags-input/tags-input.css +10 -6
- package/components/tags-input/tags-input.d.ts +16 -7
- package/components/tags-input/tags-input.examples.md +2 -2
- package/components/tags-input/tags-input.yaml +25 -8
- package/components/text/text.a2ui.json +6 -5
- package/components/text/text.css +10 -5
- package/components/text/text.d.ts +2 -2
- package/components/text/text.yaml +9 -4
- package/components/textarea/textarea.a2ui.json +2 -1
- package/components/textarea/textarea.yaml +1 -1
- package/components/theme-provider/theme-provider.a2ui.json +2 -1
- package/components/theme-provider/theme-provider.d.ts +5 -3
- package/components/theme-provider/theme-provider.yaml +5 -3
- package/components/time-picker/time-picker.a2ui.json +1 -0
- package/components/time-picker/time-picker.class.js +4 -2
- package/components/timeline/timeline-item.a2ui.json +26 -3
- package/components/timeline/timeline-item.yaml +22 -7
- package/components/timeline/timeline.a2ui.json +4 -0
- package/components/timeline/timeline.class.js +29 -18
- package/components/timeline/timeline.d.ts +3 -3
- package/components/timeline/timeline.yaml +2 -0
- package/components/toast/toast.a2ui.json +10 -3
- package/components/toast/toast.yaml +20 -13
- package/components/toc/toc.a2ui.json +1 -0
- package/components/toggle-group/toggle-group.a2ui.json +7 -3
- package/components/toggle-group/toggle-group.class.js +40 -1
- package/components/toggle-group/toggle-group.css +6 -1
- package/components/toggle-group/toggle-group.d.ts +2 -2
- package/components/toggle-group/toggle-group.yaml +21 -12
- package/components/toggle-group/toggle-option.a2ui.json +7 -3
- package/components/toggle-group/toggle-option.yaml +13 -7
- package/components/toggle-scheme/toggle-scheme.a2ui.json +2 -1
- package/components/toggle-scheme/toggle-scheme.yaml +1 -1
- package/components/toolbar/toolbar-group.a2ui.json +1 -0
- package/components/toolbar/toolbar.a2ui.json +1 -0
- package/components/toolbar/toolbar.class.js +3 -0
- package/components/tooltip/tooltip.a2ui.json +1 -0
- package/components/tour/tour-step.a2ui.json +4 -0
- package/components/tour/tour-step.yaml +2 -0
- package/components/tour/tour.a2ui.json +4 -0
- package/components/tour/tour.yaml +2 -0
- package/components/tree/tree-item.a2ui.json +5 -0
- package/components/tree/tree-item.yaml +3 -0
- package/components/tree/tree.a2ui.json +4 -0
- package/components/tree/tree.class.js +2 -1
- package/components/tree/tree.yaml +2 -0
- package/components/upload/upload.a2ui.json +8 -1
- package/components/upload/upload.class.js +35 -5
- package/components/upload/upload.css +28 -0
- package/components/upload/upload.d.ts +16 -0
- package/components/upload/upload.yaml +27 -7
- package/components/visually-hidden/visually-hidden.a2ui.json +1 -0
- package/core/dom.d.ts +11 -0
- package/core/dom.js +34 -0
- package/core/form.js +7 -0
- package/core/index.d.ts +1 -0
- package/core/index.js +1 -0
- package/core/responsive.js +16 -4
- package/core/template.js +2 -1
- package/custom-elements.json +437 -489
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +5 -5
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +91 -104
- package/dist/web-components.sheet.js +1 -1
- package/package.json +4 -2
- package/patterns/conversion-funnel/conversion-funnel.examples.html +15 -15
- package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
- package/patterns/inline-dialog/inline-dialog.html +4 -0
- package/patterns/timezone-selector/timezone-selector.examples.html +1 -1
- package/patterns/timezone-selector/timezone-selector.html +2 -2
- package/styles/api/text.css +7 -0
- package/traits/_catalog.json +129 -41
- package/traits/anchor-positioning/anchor-positioning.js +1 -0
- package/traits/announcer/announcer.js +1 -0
- package/traits/arrow-grid-nav/arrow-grid-nav.js +1 -0
- package/traits/attention-pulse/attention-pulse.js +1 -0
- package/traits/count-up/count-up.js +1 -0
- package/traits/define.js +78 -0
- package/traits/focus-restore/focus-restore.js +2 -1
- package/traits/haptic-feedback/haptic-feedback.js +1 -0
- package/traits/input-mask/input-mask.js +1 -0
- package/traits/intersection-observer/intersection-observer.js +1 -0
- package/traits/layout-animation/layout-animation.js +1 -0
- package/traits/noise-texture/noise-texture.js +1 -0
- package/traits/resize-observer/resize-observer.js +1 -0
- package/traits/scroll-progress/scroll-progress.js +1 -0
- package/traits/snap-to-grid/snap-to-grid.js +1 -0
- package/traits/sound-feedback/sound-feedback.js +1 -0
- package/traits/spring-animate/spring-animate.js +1 -0
- 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"
|
|
@@ -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 |
|
|
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": [],
|