@sableui/react 0.6.2 → 0.7.1
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/AGENTS.md +2 -2
- package/CHANGELOG.md +315 -0
- package/CONTRIBUTING.md +16 -10
- package/DESIGN.md +3 -3
- package/README.md +3 -3
- package/bin/sable-lint.mjs +18 -0
- package/bin/sable.mjs +470 -62
- package/dist/blocks/api-keys-block.js +44 -0
- package/dist/blocks/api-keys-block.meta.js +21 -0
- package/dist/blocks/chat-thread-block.js +17 -0
- package/dist/blocks/chat-thread-block.meta.js +20 -0
- package/dist/blocks/command-palette-block.js +24 -0
- package/dist/blocks/command-palette-block.meta.js +21 -0
- package/dist/blocks/cta-band-block.js +27 -0
- package/dist/blocks/cta-band-block.meta.js +20 -0
- package/dist/blocks/danger-zone-block.js +39 -0
- package/dist/blocks/danger-zone-block.meta.js +21 -0
- package/dist/blocks/device-comparison-block.js +69 -0
- package/dist/blocks/device-comparison-block.meta.js +21 -0
- package/dist/blocks/event-card-block.js +40 -0
- package/dist/blocks/event-card-block.meta.js +21 -0
- package/dist/blocks/faq-block.js +38 -0
- package/dist/blocks/faq-block.meta.js +20 -0
- package/dist/blocks/feature-grid-block.js +38 -0
- package/dist/blocks/feature-grid-block.meta.js +21 -0
- package/dist/blocks/feedback-form-block.js +30 -0
- package/dist/blocks/feedback-form-block.meta.js +21 -0
- package/dist/blocks/goal-progress-block.js +24 -0
- package/dist/blocks/goal-progress-block.meta.js +21 -0
- package/dist/blocks/hero-block.js +33 -0
- package/dist/blocks/hero-block.meta.js +21 -0
- package/dist/blocks/index.js +162 -1964
- package/dist/blocks/invite-team-block.js +36 -0
- package/dist/blocks/invite-team-block.meta.js +20 -0
- package/dist/blocks/invoice-history-block.js +41 -0
- package/dist/blocks/invoice-history-block.meta.js +21 -0
- package/dist/blocks/kpi-sparkline-block.js +29 -0
- package/dist/blocks/kpi-sparkline-block.meta.js +20 -0
- package/dist/blocks/media-player-block.js +68 -0
- package/dist/blocks/media-player-block.meta.js +21 -0
- package/dist/blocks/newsletter-block.js +35 -0
- package/dist/blocks/newsletter-block.meta.js +21 -0
- package/dist/blocks/not-found-block.js +27 -0
- package/dist/blocks/not-found-block.meta.js +20 -0
- package/dist/blocks/notification-list-block.js +32 -0
- package/dist/blocks/notification-list-block.meta.js +21 -0
- package/dist/blocks/notification-settings-block.js +25 -0
- package/dist/blocks/notification-settings-block.meta.js +20 -0
- package/dist/blocks/order-tracking-block.js +26 -0
- package/dist/blocks/order-tracking-block.meta.js +20 -0
- package/dist/blocks/pricing-card-block.js +46 -0
- package/dist/blocks/pricing-card-block.meta.js +21 -0
- package/dist/blocks/pricing-table-block.js +88 -0
- package/dist/blocks/pricing-table-block.meta.js +20 -0
- package/dist/blocks/product-card-block.js +26 -0
- package/dist/blocks/product-card-block.meta.js +21 -0
- package/dist/blocks/profile-card-block.js +58 -0
- package/dist/blocks/profile-card-block.meta.js +21 -0
- package/dist/blocks/profile-form-block.js +70 -0
- package/dist/blocks/profile-form-block.meta.js +21 -0
- package/dist/blocks/revenue-breakdown-block.js +38 -0
- package/dist/blocks/revenue-breakdown-block.meta.js +21 -0
- package/dist/blocks/sign-in-block.js +38 -0
- package/dist/blocks/sign-in-block.meta.js +21 -0
- package/dist/blocks/sign-up-block.js +48 -0
- package/dist/blocks/sign-up-block.meta.js +21 -0
- package/dist/blocks/site-footer-block.js +54 -0
- package/dist/blocks/site-footer-block.meta.js +20 -0
- package/dist/blocks/stat-row-block.js +28 -0
- package/dist/blocks/stat-row-block.meta.js +21 -0
- package/dist/blocks/steps-block.js +37 -0
- package/dist/blocks/steps-block.meta.js +20 -0
- package/dist/blocks/task-list-block.js +52 -0
- package/dist/blocks/task-list-block.meta.js +21 -0
- package/dist/blocks/team-members-block.js +49 -0
- package/dist/blocks/team-members-block.meta.js +21 -0
- package/dist/blocks/testimonial-block.js +49 -0
- package/dist/blocks/testimonial-block.meta.js +20 -0
- package/dist/blocks/traffic-sources-block.js +39 -0
- package/dist/blocks/traffic-sources-block.meta.js +21 -0
- package/dist/blocks/traffic-trend-block.js +61 -0
- package/dist/blocks/traffic-trend-block.meta.js +21 -0
- package/dist/blocks/upload-list-block.js +39 -0
- package/dist/blocks/upload-list-block.meta.js +21 -0
- package/dist/blocks/verify-code-block.js +28 -0
- package/dist/blocks/verify-code-block.meta.js +21 -0
- package/dist/blocks/wallet-block.js +48 -0
- package/dist/blocks/wallet-block.meta.js +21 -0
- package/dist/components/accordion/accordion.js +63 -0
- package/dist/components/accordion/accordion.meta.js +155 -0
- package/dist/components/accordion/index.js +15 -0
- package/dist/components/app-header/app-header.js +30 -0
- package/dist/components/app-header/app-header.meta.js +88 -0
- package/dist/components/app-header/index.js +7 -0
- package/dist/components/app-shell/app-shell-dock.js +37 -0
- package/dist/components/app-shell/app-shell.js +67 -0
- package/dist/components/app-shell/app-shell.meta.js +176 -0
- package/dist/components/app-shell/index.js +7 -0
- package/dist/components/area-chart/area-chart.js +10 -0
- package/dist/components/area-chart/area-chart.meta.js +218 -0
- package/dist/components/area-chart/index.js +7 -0
- package/dist/components/attachment/attachment.js +176 -0
- package/dist/components/attachment/attachment.meta.js +179 -0
- package/dist/components/attachment/attachment.variants.js +47 -0
- package/dist/components/attachment/index.js +7 -0
- package/dist/components/avatar/avatar.js +192 -0
- package/dist/components/avatar/avatar.meta.js +233 -0
- package/dist/components/avatar/avatar.variants.js +44 -0
- package/dist/components/avatar/index.js +22 -0
- package/dist/components/badge/badge.js +26 -0
- package/dist/components/badge/badge.meta.js +171 -0
- package/dist/components/badge/badge.variants.js +103 -0
- package/dist/components/badge/index.js +11 -0
- package/dist/components/banner/banner.js +76 -0
- package/dist/components/banner/banner.meta.js +172 -0
- package/dist/components/banner/banner.variants.js +44 -0
- package/dist/components/banner/index.js +9 -0
- package/dist/components/bar-chart/bar-chart.js +10 -0
- package/dist/components/bar-chart/bar-chart.meta.js +245 -0
- package/dist/components/bar-chart/index.js +7 -0
- package/dist/components/bottom-nav/bottom-nav.js +63 -0
- package/dist/components/bottom-nav/bottom-nav.meta.js +95 -0
- package/dist/components/bottom-nav/index.js +11 -0
- package/dist/components/breadcrumbs/breadcrumbs.js +90 -0
- package/dist/components/breadcrumbs/breadcrumbs.meta.js +111 -0
- package/dist/components/breadcrumbs/index.js +17 -0
- package/dist/components/browser/browser.js +39 -0
- package/dist/components/browser/browser.meta.js +80 -0
- package/dist/components/browser/index.js +7 -0
- package/dist/components/bubble/bubble.js +110 -0
- package/dist/components/bubble/bubble.meta.js +158 -0
- package/dist/components/bubble/bubble.variants.js +90 -0
- package/dist/components/bubble/index.js +13 -0
- package/dist/components/button/button.js +87 -0
- package/dist/components/button/button.meta.js +302 -0
- package/dist/components/button/button.variants.js +137 -0
- package/dist/components/button/index.js +9 -0
- package/dist/components/calendar/calendar.js +89 -0
- package/dist/components/calendar/calendar.meta.js +136 -0
- package/dist/components/calendar/index.js +7 -0
- package/dist/components/card/card.js +203 -0
- package/dist/components/card/card.meta.js +212 -0
- package/dist/components/card/index.js +23 -0
- package/dist/components/carousel/carousel.js +428 -0
- package/dist/components/carousel/carousel.meta.js +100 -0
- package/dist/components/carousel/index.js +7 -0
- package/dist/components/chat-composer/chat-composer.js +174 -0
- package/dist/components/chat-composer/chat-composer.meta.js +241 -0
- package/dist/components/chat-composer/chat-composer.variants.js +17 -0
- package/dist/components/chat-composer/index.js +7 -0
- package/dist/components/checkbox/checkbox-mark.js +28 -0
- package/dist/components/checkbox/checkbox.js +30 -0
- package/dist/components/checkbox/checkbox.meta.js +127 -0
- package/dist/components/checkbox/index.js +7 -0
- package/dist/components/checkbox-card/checkbox-card.js +94 -0
- package/dist/components/checkbox-card/checkbox-card.meta.js +154 -0
- package/dist/components/checkbox-card/checkbox-card.variants.js +43 -0
- package/dist/components/checkbox-card/index.js +11 -0
- package/dist/components/code/code.js +19 -0
- package/dist/components/code/code.meta.js +71 -0
- package/dist/components/code/index.js +7 -0
- package/dist/components/code-block/code-block.js +84 -0
- package/dist/components/code-block/code-block.meta.js +103 -0
- package/dist/components/code-block/index.js +8 -0
- package/dist/components/collapsible/collapsible.js +38 -0
- package/dist/components/collapsible/collapsible.meta.js +117 -0
- package/dist/components/collapsible/index.js +15 -0
- package/dist/components/color-field/color-field.js +293 -0
- package/dist/components/color-field/color-field.meta.js +142 -0
- package/dist/components/color-field/color.js +69 -0
- package/dist/components/color-field/index.js +15 -0
- package/dist/components/combobox/combobox.js +486 -0
- package/dist/components/combobox/combobox.meta.js +217 -0
- package/dist/components/combobox/index.js +37 -0
- package/dist/components/command-menu/command-menu-trigger.js +122 -0
- package/dist/components/command-menu/command-menu.js +433 -0
- package/dist/components/command-menu/command-menu.meta.js +279 -0
- package/dist/components/command-menu/index.js +21 -0
- package/dist/components/context-card/context-card.js +62 -0
- package/dist/components/context-card/context-card.meta.js +106 -0
- package/dist/components/context-card/index.js +17 -0
- package/dist/components/context-menu/context-menu.js +92 -0
- package/dist/components/context-menu/context-menu.meta.js +138 -0
- package/dist/components/context-menu/index.js +23 -0
- package/dist/components/data-table/data-table.js +142 -0
- package/dist/components/data-table/data-table.meta.js +146 -0
- package/dist/components/data-table/index.js +9 -0
- package/dist/components/date-picker/date-picker.js +196 -0
- package/dist/components/date-picker/date-picker.meta.js +164 -0
- package/dist/components/date-picker/index.js +7 -0
- package/dist/components/date-tile/date-tile.js +29 -0
- package/dist/components/date-tile/date-tile.meta.js +72 -0
- package/dist/components/date-tile/index.js +7 -0
- package/dist/components/destructive-action-modal/destructive-action-modal.js +130 -0
- package/dist/components/destructive-action-modal/destructive-action-modal.meta.js +136 -0
- package/dist/components/destructive-action-modal/index.js +9 -0
- package/dist/components/dialog/dialog.js +207 -0
- package/dist/components/dialog/dialog.meta.js +239 -0
- package/dist/components/dialog/index.js +45 -0
- package/dist/components/disclosure-picker/disclosure-picker.js +417 -0
- package/dist/components/disclosure-picker/disclosure-picker.meta.js +213 -0
- package/dist/components/disclosure-picker/index.js +21 -0
- package/dist/components/empty-state/empty-state.js +52 -0
- package/dist/components/empty-state/empty-state.meta.js +158 -0
- package/dist/components/empty-state/index.js +7 -0
- package/dist/components/entity/entity.js +70 -0
- package/dist/components/entity/entity.meta.js +108 -0
- package/dist/components/entity/entity.variants.js +16 -0
- package/dist/components/entity/index.js +7 -0
- package/dist/components/feedback/feedback.js +93 -0
- package/dist/components/feedback/feedback.meta.js +109 -0
- package/dist/components/feedback/index.js +7 -0
- package/dist/components/fieldset/fieldset.js +80 -0
- package/dist/components/fieldset/fieldset.meta.js +160 -0
- package/dist/components/fieldset/index.js +17 -0
- package/dist/components/file-dropzone/file-dropzone.js +193 -0
- package/dist/components/file-dropzone/file-dropzone.meta.js +210 -0
- package/dist/components/file-dropzone/file-dropzone.variants.js +26 -0
- package/dist/components/file-dropzone/index.js +13 -0
- package/dist/components/font-picker/font-picker.js +77 -0
- package/dist/components/font-picker/font-picker.meta.js +119 -0
- package/dist/components/font-picker/index.js +7 -0
- package/dist/components/form/form.js +145 -0
- package/dist/components/form/form.meta.js +142 -0
- package/dist/components/form/index.js +11 -0
- package/dist/components/gliding-highlight/gliding-highlight.js +50 -0
- package/dist/components/gliding-highlight/gliding-highlight.meta.js +135 -0
- package/dist/components/gliding-highlight/index.js +9 -0
- package/dist/components/gradient-art/gradient-art.js +168 -0
- package/dist/components/gradient-art/gradient-art.meta.js +81 -0
- package/dist/components/gradient-art/index.js +7 -0
- package/dist/components/gradient-art/renderer.js +336 -0
- package/dist/components/inline-status/index.js +7 -0
- package/dist/components/inline-status/inline-status.js +33 -0
- package/dist/components/inline-status/inline-status.meta.js +83 -0
- package/dist/components/inline-status/inline-status.variants.js +18 -0
- package/dist/components/input/index.js +7 -0
- package/dist/components/input/input.js +33 -0
- package/dist/components/input/input.meta.js +155 -0
- package/dist/components/kbd/index.js +8 -0
- package/dist/components/kbd/kbd.js +54 -0
- package/dist/components/kbd/kbd.meta.js +95 -0
- package/dist/components/label/index.js +7 -0
- package/dist/components/label/label.js +51 -0
- package/dist/components/label/label.meta.js +98 -0
- package/dist/components/line-chart/index.js +7 -0
- package/dist/components/line-chart/line-chart.js +10 -0
- package/dist/components/line-chart/line-chart.meta.js +222 -0
- package/dist/components/marker/index.js +7 -0
- package/dist/components/marker/marker.js +39 -0
- package/dist/components/marker/marker.meta.js +132 -0
- package/dist/components/marker/marker.variants.js +25 -0
- package/dist/components/marquee/index.js +7 -0
- package/dist/components/marquee/marquee.js +146 -0
- package/dist/components/marquee/marquee.meta.js +106 -0
- package/dist/components/media-image/index.js +7 -0
- package/dist/components/media-image/media-image.js +56 -0
- package/dist/components/media-image/media-image.meta.js +92 -0
- package/dist/components/media-tile/index.js +7 -0
- package/dist/components/media-tile/media-tile.js +135 -0
- package/dist/components/media-tile/media-tile.meta.js +133 -0
- package/dist/components/media-tile/media-tile.variants.js +48 -0
- package/dist/components/menu/index.js +33 -0
- package/dist/components/menu/menu.js +168 -0
- package/dist/components/menu/menu.meta.js +176 -0
- package/dist/components/message/index.js +7 -0
- package/dist/components/message/message.js +83 -0
- package/dist/components/message/message.meta.js +150 -0
- package/dist/components/message/message.variants.js +41 -0
- package/dist/components/message-scroller/index.js +9 -0
- package/dist/components/message-scroller/message-scroller.js +300 -0
- package/dist/components/message-scroller/message-scroller.meta.js +172 -0
- package/dist/components/meter/index.js +20 -0
- package/dist/components/meter/meter.js +112 -0
- package/dist/components/meter/meter.meta.js +227 -0
- package/dist/components/meter/meter.variants.js +61 -0
- package/dist/components/middle-truncate/index.js +7 -0
- package/dist/components/middle-truncate/middle-truncate.js +28 -0
- package/dist/components/middle-truncate/middle-truncate.meta.js +77 -0
- package/dist/components/note/index.js +9 -0
- package/dist/components/note/note.js +73 -0
- package/dist/components/note/note.meta.js +112 -0
- package/dist/components/note/note.variants.js +22 -0
- package/dist/components/notification-badge/index.js +15 -0
- package/dist/components/notification-badge/notification-badge.js +48 -0
- package/dist/components/notification-badge/notification-badge.meta.js +107 -0
- package/dist/components/notification-badge/notification-badge.variants.js +57 -0
- package/dist/components/number-field/index.js +7 -0
- package/dist/components/number-field/number-field.js +70 -0
- package/dist/components/number-field/number-field.meta.js +109 -0
- package/dist/components/otp-field/index.js +7 -0
- package/dist/components/otp-field/otp-field.js +70 -0
- package/dist/components/otp-field/otp-field.meta.js +197 -0
- package/dist/components/overflow-menu/index.js +7 -0
- package/dist/components/overflow-menu/overflow-menu.js +26 -0
- package/dist/components/overflow-menu/overflow-menu.meta.js +100 -0
- package/dist/components/page-header/index.js +7 -0
- package/dist/components/page-header/page-header.js +34 -0
- package/dist/components/page-header/page-header.meta.js +104 -0
- package/dist/components/page-loader/index.js +7 -0
- package/dist/components/page-loader/page-loader.js +37 -0
- package/dist/components/page-loader/page-loader.meta.js +85 -0
- package/dist/components/pagination/index.js +7 -0
- package/dist/components/pagination/pagination.js +105 -0
- package/dist/components/pagination/pagination.meta.js +141 -0
- package/dist/components/panel-group/index.js +10 -0
- package/dist/components/panel-group/panel-group.js +168 -0
- package/dist/components/panel-group/panel-group.meta.js +153 -0
- package/dist/components/panel-section/index.js +9 -0
- package/dist/components/panel-section/panel-section.js +115 -0
- package/dist/components/panel-section/panel-section.meta.js +156 -0
- package/dist/components/phone/index.js +7 -0
- package/dist/components/phone/phone.js +50 -0
- package/dist/{chunk-KFZ43RKU.js → components/phone/phone.meta.js} +3 -50
- package/dist/{chunk-NEQNXI2G.js → components/phone/status-icons.js} +4 -4
- package/dist/components/pie-chart/index.js +7 -0
- package/dist/components/pie-chart/pie-chart.js +191 -0
- package/dist/components/pie-chart/pie-chart.meta.js +210 -0
- package/dist/components/pill/index.js +9 -0
- package/dist/components/pill/pill.js +24 -0
- package/dist/components/pill/pill.meta.js +108 -0
- package/dist/components/pill/pill.variants.js +65 -0
- package/dist/components/popover/index.js +23 -0
- package/dist/components/popover/popover.js +100 -0
- package/dist/components/popover/popover.meta.js +136 -0
- package/dist/components/progress/index.js +19 -0
- package/dist/components/progress/progress.js +70 -0
- package/dist/components/progress/progress.meta.js +171 -0
- package/dist/components/progress/progress.variants.js +17 -0
- package/dist/components/radar-chart/index.js +7 -0
- package/dist/components/radar-chart/radar-chart.js +161 -0
- package/dist/components/radar-chart/radar-chart.meta.js +204 -0
- package/dist/components/radial-chart/index.js +7 -0
- package/dist/components/radial-chart/radial-chart.js +175 -0
- package/dist/components/radial-chart/radial-chart.meta.js +216 -0
- package/dist/components/radio/index.js +8 -0
- package/dist/components/radio/radio.js +39 -0
- package/dist/components/radio/radio.meta.js +104 -0
- package/dist/components/radio-card/index.js +7 -0
- package/dist/components/radio-card/radio-card.js +81 -0
- package/dist/components/radio-card/radio-card.meta.js +144 -0
- package/dist/components/radio-card/radio-card.variants.js +43 -0
- package/dist/components/relative-time/index.js +7 -0
- package/dist/components/relative-time/relative-time.js +47 -0
- package/dist/components/relative-time/relative-time.meta.js +77 -0
- package/dist/components/scroller/index.js +7 -0
- package/dist/components/scroller/scroller.js +111 -0
- package/dist/components/scroller/scroller.meta.js +118 -0
- package/dist/components/scrub-field/index.js +7 -0
- package/dist/components/scrub-field/scrub-field.js +124 -0
- package/dist/components/scrub-field/scrub-field.meta.js +139 -0
- package/dist/components/search-input/index.js +7 -0
- package/dist/components/search-input/search-input.js +91 -0
- package/dist/components/search-input/search-input.meta.js +129 -0
- package/dist/components/section-header/index.js +7 -0
- package/dist/components/section-header/section-header.js +39 -0
- package/dist/components/section-header/section-header.meta.js +174 -0
- package/dist/components/segmented-bar/index.js +14 -0
- package/dist/components/segmented-bar/segmented-bar.js +26 -0
- package/dist/components/segmented-bar/segmented-bar.meta.js +108 -0
- package/dist/components/segmented-bar/segmented-bar.variants.js +39 -0
- package/dist/components/select/index.js +25 -0
- package/dist/components/select/select.js +191 -0
- package/dist/components/select/select.meta.js +189 -0
- package/dist/components/separator/index.js +7 -0
- package/dist/components/separator/separator.js +42 -0
- package/dist/components/separator/separator.meta.js +84 -0
- package/dist/components/settings-menu/index.js +11 -0
- package/dist/components/settings-menu/settings-menu.js +101 -0
- package/dist/components/settings-menu/settings-menu.meta.js +177 -0
- package/dist/components/settings-menu/settings-menu.variants.js +15 -0
- package/dist/components/sheet/index.js +29 -0
- package/dist/components/sheet/sheet.js +121 -0
- package/dist/components/sheet/sheet.meta.js +167 -0
- package/dist/components/sheet/sheet.variants.js +21 -0
- package/dist/components/show-more/index.js +7 -0
- package/dist/components/show-more/show-more.js +87 -0
- package/dist/components/show-more/show-more.meta.js +79 -0
- package/dist/components/side-nav/index.js +13 -0
- package/dist/components/side-nav/side-nav.js +75 -0
- package/dist/components/side-nav/side-nav.meta.js +139 -0
- package/dist/components/side-nav/side-nav.variants.js +34 -0
- package/dist/components/sidebar/index.js +21 -0
- package/dist/components/sidebar/sidebar.js +196 -0
- package/dist/components/sidebar/sidebar.meta.js +118 -0
- package/dist/components/skeleton/index.js +7 -0
- package/dist/components/skeleton/skeleton.js +16 -0
- package/dist/components/skeleton/skeleton.meta.js +90 -0
- package/dist/components/slider/index.js +21 -0
- package/dist/components/slider/slider.js +115 -0
- package/dist/components/slider/slider.meta.js +214 -0
- package/dist/components/sparkline/index.js +7 -0
- package/dist/components/sparkline/sparkline.js +84 -0
- package/dist/components/sparkline/sparkline.meta.js +147 -0
- package/dist/components/spinner/index.js +7 -0
- package/dist/components/spinner/spinner.js +59 -0
- package/dist/components/spinner/spinner.meta.js +118 -0
- package/dist/components/status-bar/index.js +9 -0
- package/dist/components/status-bar/status-bar.js +40 -0
- package/dist/components/status-bar/status-bar.meta.js +117 -0
- package/dist/components/status-dot/index.js +9 -0
- package/dist/components/status-dot/status-dot.js +40 -0
- package/dist/components/status-dot/status-dot.meta.js +113 -0
- package/dist/components/status-dot/status-dot.variants.js +28 -0
- package/dist/components/stepper/index.js +17 -0
- package/dist/components/stepper/stepper.js +161 -0
- package/dist/components/stepper/stepper.meta.js +190 -0
- package/dist/components/stepper/stepper.variants.js +72 -0
- package/dist/components/streaming-text/index.js +7 -0
- package/dist/components/streaming-text/streaming-text.js +55 -0
- package/dist/components/streaming-text/streaming-text.meta.js +147 -0
- package/dist/components/switch/index.js +7 -0
- package/dist/components/switch/switch.js +26 -0
- package/dist/components/switch/switch.meta.js +133 -0
- package/dist/components/table/index.js +23 -0
- package/dist/components/table/table.js +117 -0
- package/dist/components/table/table.meta.js +143 -0
- package/dist/components/tabs/index.js +17 -0
- package/dist/components/tabs/tabs.js +58 -0
- package/dist/components/tabs/tabs.meta.js +120 -0
- package/dist/components/textarea/index.js +7 -0
- package/dist/components/textarea/textarea.js +38 -0
- package/dist/components/textarea/textarea.meta.js +117 -0
- package/dist/components/theme-selector/index.js +11 -0
- package/dist/components/theme-selector/theme-selector.js +145 -0
- package/dist/components/theme-selector/theme-selector.meta.js +181 -0
- package/dist/components/theme-switcher/index.js +7 -0
- package/dist/components/theme-switcher/theme-switcher.js +106 -0
- package/dist/components/theme-switcher/theme-switcher.meta.js +99 -0
- package/dist/components/thinking-orb/index.js +9 -0
- package/dist/components/thinking-orb/thinking-orb.js +126 -0
- package/dist/components/thinking-orb/thinking-orb.meta.js +142 -0
- package/dist/components/thinking-orb/thinking-orb.render.js +252 -0
- package/dist/components/toast/index.js +29 -0
- package/dist/components/toast/toast.js +254 -0
- package/dist/components/toast/toast.meta.js +261 -0
- package/dist/components/toggle/index.js +13 -0
- package/dist/components/toggle/toggle.js +51 -0
- package/dist/components/toggle/toggle.meta.js +129 -0
- package/dist/components/toolbar/index.js +27 -0
- package/dist/components/toolbar/toolbar.js +80 -0
- package/dist/components/toolbar/toolbar.meta.js +272 -0
- package/dist/components/toolbar/toolbar.variants.js +16 -0
- package/dist/components/tooltip/index.js +21 -0
- package/dist/components/tooltip/tooltip.js +74 -0
- package/dist/components/tooltip/tooltip.meta.js +156 -0
- package/dist/components/tree/index.js +12 -0
- package/dist/components/tree/tree.js +364 -0
- package/dist/components/tree/tree.meta.js +334 -0
- package/dist/components/video/index.js +7 -0
- package/dist/components/video/video.js +59 -0
- package/dist/components/video/video.meta.js +81 -0
- package/dist/components/workspace/index.js +9 -0
- package/dist/components/workspace/workspace.js +79 -0
- package/dist/components/workspace/workspace.meta.js +183 -0
- package/dist/foundations/accessibility.js +107 -0
- package/dist/foundations/index.js +25 -792
- package/dist/foundations/motion.js +135 -0
- package/dist/foundations/scrolling.js +62 -0
- package/dist/foundations/shape.js +113 -0
- package/dist/foundations/spacing.js +121 -0
- package/dist/foundations/surfaces.js +107 -0
- package/dist/foundations/types.js +0 -0
- package/dist/foundations/typography.js +152 -0
- package/dist/icon/Icon.js +30 -0
- package/dist/icon/IconProvider.js +27 -0
- package/dist/icon/compose.js +13 -0
- package/dist/icon/index.d.ts +13 -5
- package/dist/icon/index.js +13 -2
- package/dist/icon/registry.js +0 -0
- package/dist/icon-sets/brands.generated.js +123 -0
- package/dist/icon-sets/brands.js +10 -123
- package/dist/icon-sets/reicon.d.ts +3 -1
- package/dist/icon-sets/reicon.js +209 -1
- package/dist/index.d.ts +618 -34
- package/dist/index.js +211 -11351
- package/dist/ios/chrome.js +36 -0
- package/dist/ios/index.js +19 -315
- package/dist/ios/liquid-glass.js +103 -0
- package/dist/ios/native-app.js +46 -0
- package/dist/ios/navigation.js +63 -0
- package/dist/ios/phone-frame.js +64 -0
- package/dist/ios/preview-theme.js +83 -0
- package/dist/ios/preview.js +10 -137
- package/dist/ios/sheet.js +85 -0
- package/dist/ios/theme.js +19 -0
- package/dist/layout/AspectBox.js +27 -0
- package/dist/layout/Bleed.js +15 -0
- package/dist/layout/Center.js +21 -0
- package/dist/layout/Columns.js +70 -0
- package/dist/layout/Grid.js +50 -0
- package/dist/layout/GridOverlay.js +41 -0
- package/dist/layout/Page.js +11 -0
- package/dist/layout/Region.js +39 -0
- package/dist/layout/Row.js +45 -0
- package/dist/layout/SafeArea.js +26 -0
- package/dist/layout/Spacer.js +9 -0
- package/dist/layout/SplitPane.js +55 -0
- package/dist/layout/Stack.js +42 -0
- package/dist/layout/_responsive.js +16 -0
- package/dist/layout/_safeArea.js +18 -0
- package/dist/layout/index.d.ts +30 -8
- package/dist/layout/index.js +14 -2
- package/dist/lib/anchored-scroll.js +7 -0
- package/dist/lib/bleed.js +7 -0
- package/dist/lib/chart/cartesian.js +365 -0
- package/dist/lib/chart/chart-frame.js +163 -0
- package/dist/lib/chart/chart-legend.js +13 -0
- package/dist/lib/chart/chart-tooltip.js +38 -0
- package/dist/lib/chart/format.js +24 -0
- package/dist/lib/chart/geometry.js +114 -0
- package/dist/lib/chart/motion.js +34 -0
- package/dist/lib/chart/scale.js +72 -0
- package/dist/lib/chart/types.js +45 -0
- package/dist/lib/chart/use-arrival.js +41 -0
- package/dist/{chunk-SRQWQUTW.js → lib/cn.js} +5 -5
- package/dist/lib/component-guidance.js +215 -0
- package/dist/lib/density.js +54 -0
- package/dist/lib/field-shell.js +11 -0
- package/dist/lib/glyphs.js +9 -0
- package/dist/lib/heading-row.js +36 -0
- package/dist/lib/highlight-glide.js +84 -0
- package/dist/lib/hit-target.js +5 -0
- package/dist/lib/meta.js +0 -0
- package/dist/lib/portal-theme-scope.js +74 -0
- package/dist/{chunk-FKZXAFIY.js → lib/presentation.js} +8 -3
- package/dist/lib/refs.js +14 -0
- package/dist/lib/scrub-gesture.js +50 -0
- package/dist/lib/segmented.js +13 -0
- package/dist/lib/selection-card.js +41 -0
- package/dist/lib/status-tone.js +13 -0
- package/dist/lib/taxonomy.js +214 -0
- package/dist/lib/trim-label.js +20 -0
- package/dist/lib/use-copy.js +27 -0
- package/dist/lib/use-edge-fade.js +32 -0
- package/dist/lib/use-isomorphic-layout-effect.js +8 -0
- package/dist/lib/use-resize-observer.js +18 -0
- package/dist/lib/use-scroll-activity.js +52 -0
- package/dist/lib/use-wheel-to-horizontal.js +65 -0
- package/dist/motion/MotionProvider.js +75 -0
- package/dist/motion/Presence.js +112 -0
- package/dist/motion/Reveal.js +42 -0
- package/dist/motion/TextReveal.js +61 -0
- package/dist/motion/index.js +24 -0
- package/dist/motion/useInView.js +36 -0
- package/dist/motion/useReducedMotion.js +25 -0
- package/dist/motion/viewTransition.js +47 -0
- package/dist/patterns/auth-page.js +90 -0
- package/dist/patterns/auth-page.meta.js +27 -0
- package/dist/patterns/brand-landing-page.js +341 -0
- package/dist/patterns/brand-landing-page.meta.js +28 -0
- package/dist/patterns/chat-panel.js +185 -0
- package/dist/patterns/chat-panel.meta.js +31 -0
- package/dist/patterns/dashboard-page.js +332 -0
- package/dist/patterns/dashboard-page.meta.js +29 -0
- package/dist/patterns/feed-page.js +205 -0
- package/dist/patterns/feed-page.meta.js +25 -0
- package/dist/patterns/index.js +34 -2140
- package/dist/patterns/landing-page.js +356 -0
- package/dist/patterns/landing-page.meta.js +26 -0
- package/dist/patterns/list-detail-page.js +373 -0
- package/dist/patterns/list-detail-page.meta.js +24 -0
- package/dist/patterns/settings-page.js +268 -0
- package/dist/patterns/settings-page.meta.js +27 -0
- package/dist/presets/index.d.ts +7 -1
- package/dist/presets/index.js +53 -185
- package/dist/styles.css +118 -3
- package/dist/{theme-provider-kQszHYji.d.ts → theme-provider-S0NVJs2Y.d.ts} +6 -1
- package/dist/{chunk-NETUFI34.js → themes/_contract.js} +14 -2
- package/dist/themes/atelier/index.js +17 -1
- package/dist/themes/contrast.js +288 -0
- package/dist/themes/ember/ember.css +6 -6
- package/dist/themes/ember/index.js +16 -1
- package/dist/themes/index.d.ts +1 -1
- package/dist/themes/index.js +3 -8
- package/dist/themes/meridian/index.js +16 -1
- package/dist/themes/poppy/index.js +17 -1
- package/dist/themes/prism/index.js +17 -1
- package/dist/themes/prism/prism.css +2 -2
- package/dist/themes/registry.js +59 -0
- package/dist/themes/sable/index.js +16 -1
- package/dist/{chunk-EFLC4NA3.js → themes/theme-provider.js} +38 -54
- package/dist/themes/verdant/index.js +17 -1
- package/llms/blocks/media-player-block.md +20 -7
- package/llms/blocks/product-card-block.md +6 -4
- package/llms/blocks/task-list-block.md +1 -1
- package/llms/blocks/traffic-sources-block.md +1 -1
- package/llms/blocks/traffic-trend-block.md +5 -3
- package/llms/blocks/verify-code-block.md +1 -1
- package/llms/components/app-shell.md +5 -2
- package/llms/components/avatar.md +25 -3
- package/llms/components/button.md +20 -1
- package/llms/components/card.md +1 -1
- package/llms/components/disclosure-picker.md +168 -0
- package/llms/components/font-picker.md +104 -0
- package/llms/components/media-image.md +98 -0
- package/llms/components/media-tile.md +143 -0
- package/llms/components/page-header.md +6 -5
- package/llms/components/pie-chart.md +2 -2
- package/llms/components/radial-chart.md +1 -1
- package/llms/components/section-header.md +144 -0
- package/llms/components/sheet.md +10 -0
- package/llms/components/sidebar.md +4 -3
- package/llms/components/slider.md +14 -2
- package/llms/components/theme-selector.md +44 -4
- package/llms/components/tree.md +3 -3
- package/llms/layout/grid.md +24 -3
- package/llms/layout/region.md +16 -3
- package/llms/patterns/dashboard-page.md +3 -6
- package/llms/patterns/feed-page.md +4 -4
- package/llms/patterns/list-detail-page.md +1 -0
- package/llms/patterns/settings-page.md +2 -5
- package/llms/reference/utilities.md +84 -4
- package/llms/tasks/building-a-page.md +34 -3
- package/llms/tasks/converting-an-app.md +14 -4
- package/llms/tasks/creating-a-preset.md +4 -1
- package/llms/tasks/reviewing-a-design.md +19 -0
- package/llms/tasks/using-presets.md +19 -0
- package/llms-full.txt +1066 -78
- package/llms.txt +26 -9
- package/package.json +1 -1
- package/registry.json +2342 -164
- package/scripts/agent-setup.mjs +90 -0
- package/scripts/design-presets.mjs +59 -6
- package/scripts/grade.mjs +195 -29
- package/scripts/lint-rules.mjs +486 -10
- package/scripts/review-evidence.mjs +28 -1
- package/skills/sable/SKILL.md +67 -24
- package/targets/ios.json +45 -0
- package/dist/chunk-3IWZYBGH.js +0 -398
- package/dist/chunk-7MG4SHYX.js +0 -15
- package/dist/chunk-7QSDYBXO.js +0 -7421
- package/dist/chunk-ADLLDWLW.js +0 -15
- package/dist/chunk-C4Z3CF54.js +0 -15
- package/dist/chunk-EDXLM7YL.js +0 -5078
- package/dist/chunk-EHH4MHO6.js +0 -14
- package/dist/chunk-K7RUHUGC.js +0 -14
- package/dist/chunk-KO3NAEBF.js +0 -15
- package/dist/chunk-L4IEIQQA.js +0 -14
- package/dist/chunk-QJ6FWTUG.js +0 -40
- package/dist/chunk-VVW4UCDI.js +0 -4908
- package/dist/chunk-XAZGMY73.js +0 -187
package/scripts/agent-setup.mjs
CHANGED
|
@@ -20,6 +20,96 @@ function entryContent(source, projectPath) {
|
|
|
20
20
|
.replace(RESOLVE, `node -p "require.resolve('@sableui/react/registry.json', { paths: ['${projectPath}'] })"`)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* How the project runs its own installed `sable`, read from its lockfile. Never a
|
|
25
|
+
* bare `npx sable`, which would fetch an unrelated public package when this one
|
|
26
|
+
* is missing.
|
|
27
|
+
*/
|
|
28
|
+
export function packageManager(root, project) {
|
|
29
|
+
for (let dir = resolve(project); ; dir = dirname(dir)) {
|
|
30
|
+
if (existsSync(join(dir, "pnpm-lock.yaml"))) return "pnpm"
|
|
31
|
+
if (existsSync(join(dir, "yarn.lock"))) return "yarn"
|
|
32
|
+
if (existsSync(join(dir, "package-lock.json"))) return "npm"
|
|
33
|
+
if (dir === resolve(root) || dirname(dir) === dir) break
|
|
34
|
+
}
|
|
35
|
+
return "npm"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const STOP_HOOK_MARKER = "sable check --stop-hook"
|
|
39
|
+
// Words both Git Bash and PowerShell 5.1 pass through literally once double-quoted.
|
|
40
|
+
const PORTABLE = /^[\w@./ -]+$/
|
|
41
|
+
const quoted = (s) => (/^[\w./-]+$/.test(s) ? s : `"${s}"`)
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The Claude Code Stop hook that runs `sable check` before a task can end:
|
|
45
|
+
* settings.json → hooks.Stop[] → { hooks: [{ type: "command", command }] }.
|
|
46
|
+
* Stop takes no matcher. The command exits 2 with the failures on stderr, which
|
|
47
|
+
* keeps Claude working and shows it what to fix (code.claude.com/docs/en/hooks).
|
|
48
|
+
*
|
|
49
|
+
* The command is one plain invocation with no shell syntax — no `cd`, no `&&`,
|
|
50
|
+
* no "${CLAUDE_PROJECT_DIR}" — because Claude Code runs hooks under Git Bash or,
|
|
51
|
+
* without it on Windows, PowerShell 5.1, which supports neither. `sable check
|
|
52
|
+
* --stop-hook` reads CLAUDE_PROJECT_DIR from its environment instead, and
|
|
53
|
+
* `--root` names the Sable package when the entries live at a repository root.
|
|
54
|
+
* A workspace package is reached through the package manager's own workspace
|
|
55
|
+
* flag, since its `sable` bin is not on the root's path.
|
|
56
|
+
*/
|
|
57
|
+
export function claudeStopHook({ root, project = root }) {
|
|
58
|
+
const projectPath = relative(resolve(root), resolve(project)).replaceAll("\\", "/")
|
|
59
|
+
if (projectPath.startsWith("..") || isAbsolute(projectPath)) throw new Error("--root must be the project directory or one of its ancestors, such as the repository root.")
|
|
60
|
+
if (projectPath && !PORTABLE.test(projectPath)) throw new Error(`Unsupported characters for a portable hook in the project path: ${projectPath}. Add the hook by hand.`)
|
|
61
|
+
const pm = packageManager(root, project)
|
|
62
|
+
let runner = { pnpm: "pnpm exec sable", yarn: "yarn sable", npm: "npm exec --no -- sable" }[pm]
|
|
63
|
+
if (projectPath) {
|
|
64
|
+
let name
|
|
65
|
+
try { name = JSON.parse(readFileSync(join(project, "package.json"), "utf8")).name } catch { /* unnamed */ }
|
|
66
|
+
if (typeof name === "string" && PORTABLE.test(name)) {
|
|
67
|
+
runner = {
|
|
68
|
+
pnpm: `pnpm --filter ${quoted(name)} exec sable`,
|
|
69
|
+
yarn: `yarn workspace ${quoted(name)} sable`,
|
|
70
|
+
npm: `npm exec --workspace ${quoted(name)} --no -- sable`,
|
|
71
|
+
}[pm]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const command = `${runner} check --stop-hook${projectPath ? ` --root ${quoted(projectPath)}` : ""}`
|
|
75
|
+
const entry = { hooks: [{ type: "command", command, timeout: 300 }] }
|
|
76
|
+
return { settingsFile: ".claude/settings.json", command, entry, settings: { hooks: { Stop: [entry] } }, installed: false }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Write the Stop hook into `.claude/settings.json`, only when asked (`--hook`).
|
|
81
|
+
* Merges into existing settings, keeps every other key and hook, refuses
|
|
82
|
+
* settings it cannot parse rather than overwrite them, and adds nothing when a
|
|
83
|
+
* Sable check hook is already there.
|
|
84
|
+
*/
|
|
85
|
+
export function installClaudeStopHook({ root, project = root, dryRun = false }) {
|
|
86
|
+
const hook = claudeStopHook({ root, project })
|
|
87
|
+
const path = join(root, hook.settingsFile)
|
|
88
|
+
let current = root
|
|
89
|
+
for (const part of hook.settingsFile.split("/")) {
|
|
90
|
+
current = join(current, part)
|
|
91
|
+
try { if (lstatSync(current).isSymbolicLink()) throw new Error(`Refusing a linked destination: ${hook.settingsFile}`) }
|
|
92
|
+
catch (error) { if (error.code !== "ENOENT") throw error }
|
|
93
|
+
}
|
|
94
|
+
let settings = {}
|
|
95
|
+
if (existsSync(path)) {
|
|
96
|
+
try { settings = JSON.parse(readFileSync(path, "utf8")) }
|
|
97
|
+
catch { throw new Error(`${hook.settingsFile} is not valid JSON; fix it or add the hook by hand.`) }
|
|
98
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error(`${hook.settingsFile} is not a JSON object.`)
|
|
99
|
+
}
|
|
100
|
+
const hooks = settings.hooks ?? {}
|
|
101
|
+
if (typeof hooks !== "object" || Array.isArray(hooks)) throw new Error(`${hook.settingsFile} has a "hooks" value that is not an object.`)
|
|
102
|
+
const stop = hooks.Stop ?? []
|
|
103
|
+
if (!Array.isArray(stop)) throw new Error(`${hook.settingsFile} has a "hooks.Stop" value that is not an array.`)
|
|
104
|
+
const alreadyPresent = stop.some((group) => (group?.hooks ?? []).some((h) => String(h?.command ?? "").includes(STOP_HOOK_MARKER)))
|
|
105
|
+
if (!alreadyPresent && !dryRun) {
|
|
106
|
+
const next = { ...settings, hooks: { ...hooks, Stop: [...stop, hook.entry] } }
|
|
107
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
108
|
+
writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`)
|
|
109
|
+
}
|
|
110
|
+
return { ...hook, installed: true, alreadyPresent }
|
|
111
|
+
}
|
|
112
|
+
|
|
23
113
|
/**
|
|
24
114
|
* Install discoverable loaders; the installed package owns the actual workflow.
|
|
25
115
|
*
|
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs"
|
|
2
|
-
import { resolve } from "node:path"
|
|
2
|
+
import { parse, resolve } from "node:path"
|
|
3
3
|
import { fileURLToPath } from "node:url"
|
|
4
4
|
import { LABEL_CONTRAST_RULE, parsePreset, presetCss, presetLabelContrast } from "../dist/presets/index.js"
|
|
5
5
|
import { parseFlags } from "./design-preferences.mjs"
|
|
6
|
+
import { packageManager } from "./agent-setup.mjs"
|
|
6
7
|
|
|
7
8
|
const packageRoot = fileURLToPath(new URL("../", import.meta.url))
|
|
8
9
|
const args = process.argv.slice(2)
|
|
9
10
|
const json = args.includes("--json")
|
|
10
11
|
function emit(type, data, output) { process.stdout.write(json ? JSON.stringify({ type, data }) + "\n" : output + "\n") }
|
|
12
|
+
|
|
13
|
+
/** -1, 0 or 1 for two semver strings; a prerelease sorts before its release. */
|
|
14
|
+
function compareVersions(a, b) {
|
|
15
|
+
const parse = (v) => {
|
|
16
|
+
const [core, pre] = String(v).split("-", 2)
|
|
17
|
+
return { nums: core.split(".").map((n) => Number(n) || 0), pre: pre ?? null }
|
|
18
|
+
}
|
|
19
|
+
const x = parse(a), y = parse(b)
|
|
20
|
+
for (let i = 0; i < 3; i++) if ((x.nums[i] ?? 0) !== (y.nums[i] ?? 0)) return (x.nums[i] ?? 0) < (y.nums[i] ?? 0) ? -1 : 1
|
|
21
|
+
if (x.pre === y.pre) return 0
|
|
22
|
+
if (x.pre === null) return 1
|
|
23
|
+
if (y.pre === null) return -1
|
|
24
|
+
return x.pre < y.pre ? -1 : 1
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A preset records the Sable it was authored with. Authored with a NEWER one
|
|
29
|
+
* than is installed, it can name tokens and defaults this package does not have
|
|
30
|
+
* yet; it still validates against the installed contract, so this warns rather
|
|
31
|
+
* than fails — and says how to upgrade, with the project's own package manager.
|
|
32
|
+
*/
|
|
33
|
+
function versionWarnings(preset, installed) {
|
|
34
|
+
if (compareVersions(preset.sableVersion, installed) <= 0) return []
|
|
35
|
+
const pm = packageManager(parse(process.cwd()).root, process.cwd())
|
|
36
|
+
const upgrade = { pnpm: "pnpm add @sableui/react@latest", yarn: "yarn add @sableui/react@latest", npm: "npm install @sableui/react@latest" }[pm]
|
|
37
|
+
return [{
|
|
38
|
+
code: "WARN_PRESET_NEWER_SABLE",
|
|
39
|
+
message: `Preset ${preset.id} was authored with @sableui/react ${preset.sableVersion}, newer than the installed ${installed}. It validated against ${installed}; tokens or behaviour added after ${installed} will not apply until you upgrade.`,
|
|
40
|
+
authoredWith: preset.sableVersion,
|
|
41
|
+
installed,
|
|
42
|
+
suggestion: upgrade,
|
|
43
|
+
}]
|
|
44
|
+
}
|
|
11
45
|
try {
|
|
12
46
|
const flags = parseFlags(args, ["--check", "--css"], ["--template", "--json"])
|
|
13
47
|
const modes = ["--template", "--check", "--css"].filter(arg => flags.has(arg))
|
|
@@ -21,7 +55,7 @@ try {
|
|
|
21
55
|
const target = match[1].includes(".dark") ? groups.dark : groups.light
|
|
22
56
|
for (const declaration of match[2].matchAll(/--([\w-]+)\s*:\s*([^;]+);/g)) target[declaration[1]] = declaration[2].trim()
|
|
23
57
|
}
|
|
24
|
-
const { PRESET_TOKEN_NAMES } = await import("../dist/presets/index.js")
|
|
58
|
+
const { PAGE_WIDTH_TOKENS, PRESET_TOKEN_NAMES } = await import("../dist/presets/index.js")
|
|
25
59
|
function tokens(mode) {
|
|
26
60
|
const values = { ...groups.light, ...(mode === "dark" ? groups.dark : {}) }
|
|
27
61
|
function resolveValue(value, chain = []) {
|
|
@@ -31,7 +65,9 @@ try {
|
|
|
31
65
|
return resolveValue(values[name], [...chain, name])
|
|
32
66
|
})
|
|
33
67
|
}
|
|
34
|
-
|
|
68
|
+
// Page widths are opt-in: copying the defaults would override any widths
|
|
69
|
+
// an app already sets in its own CSS, since preset CSS loads after it.
|
|
70
|
+
return Object.fromEntries(PRESET_TOKEN_NAMES.filter(name => values[name] && !PAGE_WIDTH_TOKENS.includes(name)).map(name => [name, resolveValue(values[name])]))
|
|
35
71
|
}
|
|
36
72
|
const preset = parsePreset({ schemaVersion: 1, id: "my-preset", label: "My preset", revision: 1, sableVersion: manifest.version, defaultMode: "dark", fonts: { sans: groups.light["font-sans"], mono: groups.light["font-mono"] }, direction: "Content-first composition with a clear primary action. Replace this guidance with the direction for your product.", tokens: { shared: {}, light: tokens("light"), dark: tokens("dark") } })
|
|
37
73
|
emit("preset.template", preset, JSON.stringify(preset, null, 2))
|
|
@@ -39,6 +75,9 @@ try {
|
|
|
39
75
|
const file = flags.get(action)
|
|
40
76
|
if (!file || file.startsWith("--")) throw new Error(`${action} requires a preset JSON path.`)
|
|
41
77
|
const preset = parsePreset(JSON.parse(readFileSync(resolve(file), "utf8")))
|
|
78
|
+
const installed = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8")).version
|
|
79
|
+
const warnings = versionWarnings(preset, installed)
|
|
80
|
+
const warningText = warnings.map((w) => `\nWarning: ${w.message}\n → ${w.suggestion}`).join("")
|
|
42
81
|
if (action === "--check") {
|
|
43
82
|
// Labels on brand and status fills: Sable's opinion, enforced unless the preset
|
|
44
83
|
// records that it declines it (`exceptions`), which is then reported.
|
|
@@ -58,12 +97,26 @@ try {
|
|
|
58
97
|
}
|
|
59
98
|
const accepted = labels.accepted.map(f => `\n accepted: ${pair(f)} — ${f.reason}`).join("")
|
|
60
99
|
const unmeasured = labels.unmeasured.length ? `, ${labels.unmeasured.length} unmeasured (${labels.unmeasured.join(", ")})` : ""
|
|
100
|
+
// A passing check is the start of applying a preset, not the end: say what
|
|
101
|
+
// makes the app use it, so a check does not read as "done".
|
|
102
|
+
const fonts = [...new Set(Object.values(preset.fonts).filter(Boolean).map(stack => stack.split(",")[0].trim().replace(/^["']|["']$/g, "")))]
|
|
103
|
+
const next = [
|
|
104
|
+
`sable preset --css ${file} > <stylesheet>, imported after Sable's`,
|
|
105
|
+
`load fonts: ${fonts.join(", ")}`,
|
|
106
|
+
`SableThemeProvider defaultThemeId="${preset.id}" defaultPreference="${preset.defaultMode}" with the preset in customThemes`,
|
|
107
|
+
"build the app, run sable check, review both modes (sable docs using-presets)",
|
|
108
|
+
]
|
|
61
109
|
emit(
|
|
62
110
|
"preset.check",
|
|
63
|
-
{ valid: true, id: preset.id, revision: preset.revision, authoredWith: preset.sableVersion, labelContrast: { measured: labels.measured, accepted: labels.accepted, unmeasured: labels.unmeasured } },
|
|
64
|
-
`Valid preset ${preset.id}, revision ${preset.revision}. Labels on filled roles: ${labels.measured} pairs measured${unmeasured}.${accepted}\nReview rendered contrast and brief fit separately
|
|
111
|
+
{ valid: true, id: preset.id, revision: preset.revision, authoredWith: preset.sableVersion, installed, warnings, labelContrast: { measured: labels.measured, accepted: labels.accepted, unmeasured: labels.unmeasured }, fonts, next },
|
|
112
|
+
`Valid preset ${preset.id}, revision ${preset.revision}. Labels on filled roles: ${labels.measured} pairs measured${unmeasured}.${accepted}${warningText}\nReview rendered contrast and brief fit separately.\nTo apply it to an app:\n${next.map(step => ` - ${step}`).join("\n")}`,
|
|
65
113
|
)
|
|
66
|
-
} else {
|
|
114
|
+
} else {
|
|
115
|
+
const css = presetCss(preset)
|
|
116
|
+
// The CSS is the output, so in text mode the warning goes to stderr.
|
|
117
|
+
if (!json && warningText) process.stderr.write(`${warningText.trim()}\n`)
|
|
118
|
+
emit("preset.css", { id: preset.id, revision: preset.revision, css, warnings }, css)
|
|
119
|
+
}
|
|
67
120
|
}
|
|
68
121
|
} catch (error) {
|
|
69
122
|
process.stderr.write(JSON.stringify({ type: "error", code: error.code ?? "ERR_PRESET_INVALID", message: error.message, ...(error.data ? { data: error.data } : {}), suggestions: error.suggestions ?? ["sable docs using-presets", "sable preset --template"] }) + "\n")
|
package/scripts/grade.mjs
CHANGED
|
@@ -33,7 +33,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"
|
|
|
33
33
|
import { createHash } from "node:crypto"
|
|
34
34
|
import { dirname, extname, join, relative, resolve } from "node:path"
|
|
35
35
|
import { fileURLToPath } from "node:url"
|
|
36
|
-
import { ignoreGlob, lintText, makeRules } from "./lint-rules.mjs"
|
|
36
|
+
import { ignoreGlob, lintText, makeRules, surfaceInventory } from "./lint-rules.mjs"
|
|
37
37
|
import { labelContrastScanner, statusTextDrift } from "./label-contrast.mjs"
|
|
38
38
|
import { skeletonize } from "./skeleton.mjs"
|
|
39
39
|
import { assessReview, reviewTemplate } from "./review-evidence.mjs"
|
|
@@ -150,23 +150,150 @@ function exportSurface() {
|
|
|
150
150
|
for (const n of REGISTRY.motionLayer?.policy ?? []) names.add(n)
|
|
151
151
|
for (const n of REGISTRY.motionLayer?.primitives ?? []) names.add(n)
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
const root = SUBPATHS.get("")
|
|
154
|
+
if (root) for (const n of root.names) names.add(n)
|
|
155
|
+
return names
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The export surface of every published subpath, read from the package's own
|
|
160
|
+
* `exports` map (src in the workspace, dist .d.ts once published) by following
|
|
161
|
+
* each entry's `export … from` / `export *` chain.
|
|
162
|
+
*
|
|
163
|
+
* It has to be per subpath. Judging every `@sableui/react/<anything>` import
|
|
164
|
+
* against the root barrel reported `SignInBlock` from `/blocks`,
|
|
165
|
+
* `presetManifest` from `/presets` and `reiconIconSet` from `/icons/reicon` as
|
|
166
|
+
* hallucinations — 25 of them in Sable's own docs app — and a check that cries
|
|
167
|
+
* wolf on correct code is a check people learn to skip.
|
|
168
|
+
*
|
|
169
|
+
* A surface is `complete: false` when a chain leaves the package (`export * from
|
|
170
|
+
* "some-dep"`); names imported from it are then not judged rather than guessed.
|
|
171
|
+
*/
|
|
172
|
+
function subpathSurfaces() {
|
|
173
|
+
const pkg = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"))
|
|
174
|
+
const exportsMap = pkg.exports ?? {}
|
|
175
|
+
const out = new Map()
|
|
176
|
+
const typesFile = (entry) => {
|
|
177
|
+
const v = typeof entry === "string" ? entry : (entry?.types ?? entry?.import?.types ?? entry?.default ?? entry?.import)
|
|
178
|
+
if (typeof v !== "string" || !/\.(?:d\.)?[cm]?[jt]sx?$/.test(v)) return null
|
|
179
|
+
return join(PKG_ROOT, v)
|
|
180
|
+
}
|
|
181
|
+
const candidates = (base) => {
|
|
182
|
+
const stem = base.replace(/\.(?:d\.)?[cm]?[jt]sx?$/, "")
|
|
183
|
+
return [base, `${stem}.d.ts`, `${stem}.ts`, `${stem}.tsx`, `${stem}.d.mts`, join(base, "index.d.ts"), join(base, "index.ts"), join(base, "index.tsx")]
|
|
184
|
+
}
|
|
185
|
+
const collect = (file, names, seen, state) => {
|
|
186
|
+
if (seen.has(file)) return
|
|
187
|
+
seen.add(file)
|
|
188
|
+
const src = readFileSync(file, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1")
|
|
189
|
+
for (const m of src.matchAll(/export\s+(?:type\s+)?\{([^}]*)\}/g)) {
|
|
158
190
|
for (const raw of m[1].split(",")) {
|
|
159
|
-
const name = raw.trim().split(/\s+as\s+/).pop()?.trim()
|
|
160
|
-
if (name && /^[A-Za-z_$][\w$]*$/.test(name)) names.add(name)
|
|
191
|
+
const name = raw.trim().replace(/^type\s+/, "").split(/\s+as\s+/).pop()?.trim()
|
|
192
|
+
if (name && name !== "default" && /^[A-Za-z_$][\w$]*$/.test(name)) names.add(name)
|
|
161
193
|
}
|
|
162
194
|
}
|
|
163
|
-
for (const m of src.matchAll(/export\s+(?:declare\s+)?(?:const|function|
|
|
164
|
-
|
|
195
|
+
for (const m of src.matchAll(/export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:const|let|var|function\*?|class|type|interface|enum|namespace)\s+([\w$]+)/g)) names.add(m[1])
|
|
196
|
+
for (const m of src.matchAll(/export\s+\*\s+as\s+([\w$]+)\s+from/g)) names.add(m[1])
|
|
197
|
+
for (const m of src.matchAll(/export\s+\*\s+from\s+["']([^"']+)["']/g)) {
|
|
198
|
+
if (!m[1].startsWith(".")) { state.complete = false; continue }
|
|
199
|
+
const next = candidates(resolve(dirname(file), m[1])).find((c) => existsSync(c) && statSync(c).isFile())
|
|
200
|
+
if (next) collect(next, names, seen, state)
|
|
201
|
+
else state.complete = false
|
|
202
|
+
}
|
|
165
203
|
}
|
|
166
|
-
|
|
204
|
+
for (const [key, entry] of Object.entries(exportsMap)) {
|
|
205
|
+
const sub = key === "." ? "" : key.replace(/^\.\//, "")
|
|
206
|
+
const file = typesFile(entry)
|
|
207
|
+
if (!file) {
|
|
208
|
+
out.set(sub, { names: new Set(), complete: true, asset: true })
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
const names = new Set()
|
|
212
|
+
const state = { complete: true }
|
|
213
|
+
const start = candidates(file).find((c) => existsSync(c) && statSync(c).isFile())
|
|
214
|
+
if (start) collect(start, names, new Set(), state)
|
|
215
|
+
else state.complete = false
|
|
216
|
+
out.set(sub, { names, complete: state.complete })
|
|
217
|
+
}
|
|
218
|
+
return out
|
|
167
219
|
}
|
|
220
|
+
const SUBPATHS = subpathSurfaces()
|
|
168
221
|
const EXPORTS = exportSurface()
|
|
169
222
|
|
|
223
|
+
/**
|
|
224
|
+
* What one file imports from Sable: local name → { name, from }. Aliases
|
|
225
|
+
* (`Button as SableButton`) map back to the Sable name, and a component the app
|
|
226
|
+
* defines itself under a Sable name (its own `PageHeader`) is simply absent, so
|
|
227
|
+
* the prop and icon checks below never judge it against Sable's contract.
|
|
228
|
+
*/
|
|
229
|
+
/**
|
|
230
|
+
* The text with every template literal's literal part blanked (newlines kept,
|
|
231
|
+
* so line numbers hold). A docs page or a code-preview builder carries JSX as
|
|
232
|
+
* DATA — `<RadioCard value="post" …>` inside a backtick string, `<Icon
|
|
233
|
+
* name="${glyph}" />` assembled for display — and judging that as rendered JSX
|
|
234
|
+
* reported attributes like `n` (from a `\n` escape) and an icon named
|
|
235
|
+
* `${glyph}`. `${…}` expressions are kept, since they are code.
|
|
236
|
+
*/
|
|
237
|
+
function maskTemplates(text) {
|
|
238
|
+
let out = ""
|
|
239
|
+
const stack = [] // "t" = inside a template literal, number = brace depth of a ${ } expression
|
|
240
|
+
let quote = null
|
|
241
|
+
for (let i = 0; i < text.length; i++) {
|
|
242
|
+
const c = text[i]
|
|
243
|
+
const top = stack[stack.length - 1]
|
|
244
|
+
if (top === "t") {
|
|
245
|
+
if (c === "\\") { out += text[i + 1] === "\n" ? " \n" : " "; i++; continue }
|
|
246
|
+
if (c === "`") { stack.pop(); out += c; continue }
|
|
247
|
+
if (c === "$" && text[i + 1] === "{") { stack.push(0); out += "${"; i++; continue }
|
|
248
|
+
out += c === "\n" ? "\n" : " "
|
|
249
|
+
continue
|
|
250
|
+
}
|
|
251
|
+
if (quote) {
|
|
252
|
+
if (c === "\\") { out += c + (text[i + 1] ?? ""); i++; continue }
|
|
253
|
+
if (c === quote || c === "\n") quote = null
|
|
254
|
+
out += c
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
257
|
+
if (c === "/" && text[i + 1] === "/") {
|
|
258
|
+
const nl = text.indexOf("\n", i)
|
|
259
|
+
const end = nl === -1 ? text.length : nl
|
|
260
|
+
out += text.slice(i, end)
|
|
261
|
+
i = end - 1
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
if (c === "/" && text[i + 1] === "*") {
|
|
265
|
+
const end = text.indexOf("*/", i + 2)
|
|
266
|
+
const stop = end === -1 ? text.length : end + 2
|
|
267
|
+
out += text.slice(i, stop)
|
|
268
|
+
i = stop - 1
|
|
269
|
+
continue
|
|
270
|
+
}
|
|
271
|
+
if (c === "'" || c === '"') { quote = c; out += c; continue }
|
|
272
|
+
if (c === "`") { stack.push("t"); out += c; continue }
|
|
273
|
+
if (typeof top === "number") {
|
|
274
|
+
if (c === "{") stack[stack.length - 1]++
|
|
275
|
+
else if (c === "}") {
|
|
276
|
+
if (top === 0) { stack.pop(); out += c; continue }
|
|
277
|
+
stack[stack.length - 1]--
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
out += c
|
|
281
|
+
}
|
|
282
|
+
return out
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const SABLE_IMPORT =/import\s+(?:type\s+)?(?:[\w$]+\s*,\s*)?\{([^}]*)\}\s*from\s*["'](@sableui\/react(?:\/[^"']*)?)["']/g
|
|
286
|
+
function sableImports(text) {
|
|
287
|
+
const out = new Map()
|
|
288
|
+
for (const m of text.matchAll(SABLE_IMPORT)) {
|
|
289
|
+
for (const raw of m[1].split(",")) {
|
|
290
|
+
const [name, local] = raw.trim().replace(/^type\s+/, "").split(/\s+as\s+/).map((s) => s?.trim())
|
|
291
|
+
if (name) out.set(local || name, { name, from: m[2], index: m.index })
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return out
|
|
295
|
+
}
|
|
296
|
+
|
|
170
297
|
/** Props any element legitimately takes, so they never read as hallucinated. */
|
|
171
298
|
const UNIVERSAL_PROPS = new Set([
|
|
172
299
|
"key", "ref", "className", "style", "id", "role", "tabIndex", "title", "hidden", "slot", "children",
|
|
@@ -175,6 +302,8 @@ const UNIVERSAL_PROPS = new Set([
|
|
|
175
302
|
"disabled", "required", "readOnly", "placeholder", "type", "href", "target", "rel", "src", "alt",
|
|
176
303
|
"width", "height", "colSpan", "rowSpan", "htmlFor", "autoFocus", "autoComplete", "maxLength",
|
|
177
304
|
"minLength", "min", "max", "step", "rows", "cols", "open", "selected", "multiple", "loading",
|
|
305
|
+
// Media attributes, for components that spread onto <video>/<audio>/<img> (Video, Image).
|
|
306
|
+
"autoPlay", "muted", "loop", "playsInline", "preload", "crossOrigin", "decoding", "fetchPriority", "srcSet", "sizes",
|
|
178
307
|
])
|
|
179
308
|
const isEventProp = (p) => /^on[A-Z]/.test(p)
|
|
180
309
|
const isPassthrough = (p) => p.startsWith("data-") || p.startsWith("aria-") || p.startsWith("--")
|
|
@@ -237,6 +366,12 @@ const sourceHash = fingerprint.digest("hex")
|
|
|
237
366
|
const findings = { contract: [], hallucinatedComponent: [], hallucinatedProp: [], radixIdiom: [], hardcodedControl: [], offContractIcon: [] }
|
|
238
367
|
let loc = 0
|
|
239
368
|
const routeSkeletons = []
|
|
369
|
+
/* App-owned surfaces: intrinsic elements wearing a surface's or a shape's
|
|
370
|
+
classes. Never scored — a review list for the reuse inventory, so the agent
|
|
371
|
+
accounts for each one (compose the named component, or say why not) instead
|
|
372
|
+
of listing from memory. The lookalike-* lint rules fail the unambiguous
|
|
373
|
+
cases; this lists the rest. */
|
|
374
|
+
const inventory = []
|
|
240
375
|
|
|
241
376
|
/**
|
|
242
377
|
* Attribute names on one JSX open tag, stopping at the tag's own `>`.
|
|
@@ -298,31 +433,39 @@ for (const file of files) {
|
|
|
298
433
|
|
|
299
434
|
if (!/\.(tsx|jsx|ts|js)$/.test(extname(file) ? file : "")) continue
|
|
300
435
|
|
|
301
|
-
/* 2. Imports that name something the
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
436
|
+
/* 2. Imports that name something the subpath they come from does not export.
|
|
437
|
+
Each import is judged against ITS subpath's surface; an unpublished
|
|
438
|
+
subpath is itself the finding. */
|
|
439
|
+
const imported = sableImports(text)
|
|
440
|
+
for (const { name, from, index } of imported.values()) {
|
|
441
|
+
const sub = from === "@sableui/react" ? "" : from.slice("@sableui/react/".length)
|
|
442
|
+
const surface = SUBPATHS.get(sub)
|
|
443
|
+
if (!surface) {
|
|
444
|
+
findings.hallucinatedComponent.push({ file: rel, name, from, reason: "not a published subpath", line: lineOf(text, index) })
|
|
445
|
+
continue
|
|
309
446
|
}
|
|
447
|
+
const known = surface.names.has(name) || (sub === "" && EXPORTS.has(name))
|
|
448
|
+
if (!known && surface.complete) findings.hallucinatedComponent.push({ file: rel, name, from, line: lineOf(text, index) })
|
|
310
449
|
}
|
|
311
450
|
|
|
312
451
|
/* 3. Props set on a Sable component that its contract does not declare.
|
|
313
452
|
The contract is the component's real type (registry → propSurface),
|
|
314
453
|
which includes what it forwards to Base UI — Toggle's `pressed`,
|
|
315
|
-
Combobox's `filter` — plus its documented table.
|
|
316
|
-
|
|
317
|
-
|
|
454
|
+
Combobox's `filter` — plus its documented table. Only tags the file
|
|
455
|
+
imports from Sable are judged: an app's own `PageHeader` is not ours. */
|
|
456
|
+
const code = maskTemplates(text)
|
|
457
|
+
for (const m of code.matchAll(/<([A-Z][\w]*)[\s/>]/g)) {
|
|
458
|
+
const sableName = imported.get(m[1])?.name
|
|
459
|
+
if (!sableName) continue
|
|
460
|
+
const comp = REGISTRY.components?.find((c) => c.name === sableName)
|
|
318
461
|
if (!comp?.props?.length) continue
|
|
319
|
-
const declared = new Set([...comp.props.map((p) => p.name), ...(PROP_SURFACE[
|
|
462
|
+
const declared = new Set([...comp.props.map((p) => p.name), ...(PROP_SURFACE[sableName] ?? [])])
|
|
320
463
|
for (const sub of comp.subcomponents ?? []) {
|
|
321
464
|
if (typeof sub !== "string") for (const p of sub.props ?? []) declared.add(p.name)
|
|
322
465
|
}
|
|
323
|
-
for (const attr of attrsOf(
|
|
466
|
+
for (const attr of attrsOf(code, m.index + m[0].length - 1)) {
|
|
324
467
|
if (declared.has(attr) || UNIVERSAL_PROPS.has(attr) || isEventProp(attr) || isPassthrough(attr)) continue
|
|
325
|
-
findings.hallucinatedProp.push({ file: rel, component:
|
|
468
|
+
findings.hallucinatedProp.push({ file: rel, component: sableName, prop: attr, line: lineOf(text, m.index) })
|
|
326
469
|
}
|
|
327
470
|
}
|
|
328
471
|
|
|
@@ -345,12 +488,21 @@ for (const file of files) {
|
|
|
345
488
|
}
|
|
346
489
|
|
|
347
490
|
/* 6. Icon names outside the contract render nothing at all — unless the app
|
|
348
|
-
supplies them itself and has declared them (APP_ICONS).
|
|
349
|
-
|
|
350
|
-
|
|
491
|
+
supplies them itself and has declared them (APP_ICONS). Only Sable's
|
|
492
|
+
<Icon> (under whatever local name it was imported as) is judged. */
|
|
493
|
+
for (const [local, { name }] of imported) {
|
|
494
|
+
if (name !== "Icon") continue
|
|
495
|
+
for (const m of code.matchAll(new RegExp(`<${local}\\s+[^>]*name=["']([^"']+)["']`, "g"))) {
|
|
496
|
+
if (!ICONS.has(m[1]) && !APP_ICONS?.names.has(m[1])) findings.offContractIcon.push({ file: rel, name: m[1], line: lineOf(text, m.index) })
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/* 7. Surfaces the app built itself (informational). */
|
|
501
|
+
if (/\.(tsx|jsx)$/.test(file)) {
|
|
502
|
+
for (const { index, ...s } of surfaceInventory(text)) inventory.push({ file: rel, line: lineOf(text, index), ...s })
|
|
351
503
|
}
|
|
352
504
|
|
|
353
|
-
/*
|
|
505
|
+
/* 8. Composition. Anything exporting a component that returns a big tree is
|
|
354
506
|
treated as a route worth comparing against the canonical shells. */
|
|
355
507
|
if (/\.(tsx|jsx)$/.test(file) && /export\s+(default\s+)?function\s+[A-Z]/.test(text)) {
|
|
356
508
|
const { tree } = skeletonize(text)
|
|
@@ -485,6 +637,13 @@ const result = {
|
|
|
485
637
|
counts,
|
|
486
638
|
per100loc: Object.fromEntries(Object.entries(counts).map(([k, v]) => [k, per100(v)])),
|
|
487
639
|
conformance: { informationalOnly: true, mean: meanConformance, routes: conformance },
|
|
640
|
+
inventory: {
|
|
641
|
+
informationalOnly: true,
|
|
642
|
+
meaning: "App-owned surfaces to account for in the reuse inventory: compose the named Sable component, or record why the app owns it. Not scored.",
|
|
643
|
+
total: inventory.length,
|
|
644
|
+
byKind: Object.fromEntries(["surface", "media frame", "round"].map((k) => [k, inventory.filter((s) => s.kind === k).length])),
|
|
645
|
+
surfaces: inventory,
|
|
646
|
+
},
|
|
488
647
|
cliUsage,
|
|
489
648
|
appIcons: APP_ICONS ? { file: APP_ICONS.file, names: APP_ICONS.names.size } : null,
|
|
490
649
|
findings,
|
|
@@ -519,6 +678,13 @@ if (conformance.length) {
|
|
|
519
678
|
} else {
|
|
520
679
|
L(" composition no route-like files found")
|
|
521
680
|
}
|
|
681
|
+
if (inventory.length) {
|
|
682
|
+
const kinds = ["surface", "media frame", "round"].map((k) => [k, inventory.filter((s) => s.kind === k).length]).filter(([, n]) => n)
|
|
683
|
+
L()
|
|
684
|
+
L(` app-owned surfaces (review, not scored) — ${inventory.length}: ${kinds.map(([k, n]) => `${n} ${k}`).join(" · ")}`)
|
|
685
|
+
for (const s of inventory.slice(0, 12)) L(` ${s.file}:${s.line} <${s.tag}> ${s.kind} (${s.classes}) → ${s.consider}`)
|
|
686
|
+
if (inventory.length > 12) L(` … ${inventory.length - 12} more (--json lists every one)`)
|
|
687
|
+
}
|
|
522
688
|
if (cliUsage) {
|
|
523
689
|
L()
|
|
524
690
|
L(` sable CLI ${cliUsage.invocations} invocation(s)`)
|