@sableui/react 0.6.1 → 0.7.0
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 +285 -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 +200 -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 +33 -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 +611 -33
- 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 +25 -6
- 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 +19 -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 +210 -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.js +43 -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/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/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/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/converting-an-app.md +14 -4
- package/llms/tasks/reviewing-a-design.md +15 -0
- package/llms-full.txt +981 -65
- package/llms.txt +27 -10
- package/package.json +1 -1
- package/registry.json +2324 -171
- package/scripts/agent-setup.mjs +90 -0
- package/scripts/design-presets.mjs +46 -4
- 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 +56 -22
- 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/bin/sable.mjs
CHANGED
|
@@ -31,16 +31,16 @@
|
|
|
31
31
|
* hand-written capability list is a capability list that lies.
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
import {
|
|
34
|
+
import { spawnSync } from "node:child_process"
|
|
35
35
|
import { appendFileSync, existsSync, readdirSync, readFileSync, statSync } from "node:fs"
|
|
36
36
|
import { dirname, join, relative, resolve } from "node:path"
|
|
37
37
|
import { fileURLToPath } from "node:url"
|
|
38
38
|
import { skeletonize } from "../scripts/skeleton.mjs"
|
|
39
|
-
import { installAgentSkill } from "../scripts/agent-setup.mjs"
|
|
39
|
+
import { claudeStopHook, installAgentSkill, installClaudeStopHook } from "../scripts/agent-setup.mjs"
|
|
40
40
|
|
|
41
41
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
42
42
|
const PKG_ROOT = join(HERE, "..")
|
|
43
|
-
|
|
43
|
+
let CWD = process.cwd()
|
|
44
44
|
|
|
45
45
|
/* ── The catalog. ───────────────────────────────────────────────────────────── */
|
|
46
46
|
|
|
@@ -99,6 +99,7 @@ const COMMANDS = [
|
|
|
99
99
|
{ name: "--root", value: "dir", describe: "Write the entries in this ancestor instead, e.g. the repository root when Sable belongs to one workspace package. Run setup in that package." },
|
|
100
100
|
{ name: "--update", value: null, describe: "Refresh unchanged managed entries after a package upgrade." },
|
|
101
101
|
{ name: "--dry-run", value: null, describe: "Check and report the destination without writing files." },
|
|
102
|
+
{ name: "--hook", value: null, describe: "Also add a Claude Code Stop hook that runs `sable check` to .claude/settings.json (Claude targets only). Without it, setup prints the hook and writes nothing there." },
|
|
102
103
|
],
|
|
103
104
|
returns: ["setup.agent"],
|
|
104
105
|
},
|
|
@@ -111,17 +112,30 @@ const COMMANDS = [
|
|
|
111
112
|
},
|
|
112
113
|
{
|
|
113
114
|
name: "search",
|
|
114
|
-
summary: "One ranked result set across components, patterns, blocks, layout primitives, utilities, tokens, and docs.",
|
|
115
|
+
summary: "One ranked result set across components, providers, patterns, blocks, layout primitives, utilities, tokens, and docs.",
|
|
115
116
|
arg: { name: "query", required: true },
|
|
116
117
|
flags: [
|
|
117
|
-
{ name: "--type", value: "component|pattern|block|layout|utility|token|doc", describe: "Restrict to one kind." },
|
|
118
|
+
{ name: "--type", value: "component|provider|pattern|block|layout|utility|token|doc", describe: "Restrict to one kind." },
|
|
118
119
|
{ name: "--limit", value: "n", describe: "Cap results. Default 20." },
|
|
119
120
|
],
|
|
120
121
|
returns: ["search"],
|
|
121
122
|
},
|
|
123
|
+
{
|
|
124
|
+
name: "check",
|
|
125
|
+
summary: "Run before declaring a build done: doctor, lint and grade in one pass, then the review steps no tool can verify. Exits 1 if any check fails.",
|
|
126
|
+
arg: { name: "path...", required: false },
|
|
127
|
+
flags: [
|
|
128
|
+
{ name: "--ignore", value: "glob", repeatable: true, describe: "Skip matching paths in lint and grade. Repeatable." },
|
|
129
|
+
{ name: "--review", value: "review.json", describe: "Also require recorded review evidence (as sable grade --review)." },
|
|
130
|
+
{ name: "--icons", value: "icons.json", describe: "Icon names the app adds, as for sable grade --icons." },
|
|
131
|
+
{ name: "--root", value: "dir", describe: "Check this project directory instead of the current one. Under --stop-hook it is relative to $CLAUDE_PROJECT_DIR." },
|
|
132
|
+
{ name: "--stop-hook", value: null, describe: "Agent Stop-hook mode: checks the project at $CLAUDE_PROJECT_DIR (plus --root); on failure prints the summary to stderr and exits 2, so the agent keeps working instead of ending its turn." },
|
|
133
|
+
],
|
|
134
|
+
returns: ["check"],
|
|
135
|
+
},
|
|
122
136
|
{
|
|
123
137
|
name: "component",
|
|
124
|
-
summary: "A component's contract: props, subcomponents, a11y, tokens, examples.",
|
|
138
|
+
summary: "A component's contract: props, subcomponents, a11y, tokens, examples. A provider name resolves to its provider entry.",
|
|
125
139
|
arg: { name: "Name", required: false },
|
|
126
140
|
flags: [
|
|
127
141
|
{ name: "--list", value: null, describe: "List the catalog instead." },
|
|
@@ -166,6 +180,17 @@ const COMMANDS = [
|
|
|
166
180
|
],
|
|
167
181
|
returns: ["layout.list", "layout.detail", "layout.doctrine"],
|
|
168
182
|
},
|
|
183
|
+
{
|
|
184
|
+
name: "provider",
|
|
185
|
+
summary: "App-level providers (SableThemeProvider, IconProvider, MotionProvider, ToastProvider, …): where to mount each, props, and examples.",
|
|
186
|
+
arg: { name: "Name", required: false },
|
|
187
|
+
flags: [
|
|
188
|
+
{ name: "--list", value: null, describe: "List the providers instead." },
|
|
189
|
+
{ name: "--props", value: null, describe: "Props table only." },
|
|
190
|
+
{ name: "--examples", value: null, describe: "Examples only, imports included." },
|
|
191
|
+
],
|
|
192
|
+
returns: ["provider.list", "provider.detail", "provider.props", "provider.examples"],
|
|
193
|
+
},
|
|
169
194
|
{
|
|
170
195
|
name: "token",
|
|
171
196
|
summary: "The token contract, with intent — what each token means and what it is NOT.",
|
|
@@ -175,7 +200,7 @@ const COMMANDS = [
|
|
|
175
200
|
},
|
|
176
201
|
{
|
|
177
202
|
name: "utility",
|
|
178
|
-
summary: "The CSS utility layer, with intent — relative surfaces, frames, state variants, scroll policies — and what each replaces.",
|
|
203
|
+
summary: "The CSS utility layer, with intent — relative surfaces, frames, state variants, scroll policies, motion recipes, type tiers — and what each replaces.",
|
|
179
204
|
arg: { name: "name", required: false },
|
|
180
205
|
flags: [
|
|
181
206
|
{ name: "--list", value: null, describe: "List the utilities, grouped by family." },
|
|
@@ -185,11 +210,11 @@ const COMMANDS = [
|
|
|
185
210
|
},
|
|
186
211
|
{
|
|
187
212
|
name: "docs",
|
|
188
|
-
summary: "The llms/ markdown mirrors: per-component pages, task walkthroughs, token intent.",
|
|
213
|
+
summary: "The package's own guides (AGENTS, README, CONTRIBUTING, DESIGN, CHANGELOG) and the llms/ markdown mirrors: per-component pages, task walkthroughs, token intent.",
|
|
189
214
|
arg: { name: "slug", required: false },
|
|
190
215
|
flags: [
|
|
191
216
|
{ name: "--list", value: null, describe: "List every doc slug." },
|
|
192
|
-
{ name: "--kind", value: "components|layout|patterns|blocks|reference|tasks|taste", describe: "With --list: one kind." },
|
|
217
|
+
{ name: "--kind", value: "package|components|layout|patterns|blocks|foundations|reference|tasks|taste", describe: "With --list: one kind." },
|
|
193
218
|
],
|
|
194
219
|
returns: ["docs.list", "docs.detail"],
|
|
195
220
|
},
|
|
@@ -210,7 +235,7 @@ const COMMANDS = [
|
|
|
210
235
|
{ name: "--strict", value: null, describe: "Exit 1 for contract findings or missing/incomplete/unresolved review evidence." },
|
|
211
236
|
{ name: "--cli-log", value: "file.jsonl", describe: "Include retrieval usage from a SABLE_CLI_LOG file." },
|
|
212
237
|
{ name: "--icons", value: "icons.json", describe: 'Icon names the app adds with composeIconSets, as { "names": [...] }. Default: .sable/icons.json when present.' },
|
|
213
|
-
{ name: "--ignore", value: "glob", describe: "Skip matching paths in the checks, with the same globs as sable-lint --ignore. Repeatable." },
|
|
238
|
+
{ name: "--ignore", value: "glob", repeatable: true, describe: "Skip matching paths in the checks, with the same globs as sable-lint --ignore. Repeatable." },
|
|
214
239
|
],
|
|
215
240
|
returns: ["grade.score", "grade.template"],
|
|
216
241
|
},
|
|
@@ -232,8 +257,12 @@ const COMMANDS = [
|
|
|
232
257
|
name: "lint",
|
|
233
258
|
summary: "Run the design contract against this project (delegates to sable-lint).",
|
|
234
259
|
arg: { name: "dir...", required: false },
|
|
235
|
-
flags: [
|
|
236
|
-
|
|
260
|
+
flags: [
|
|
261
|
+
{ name: "--rules", value: null, describe: "List the rules." },
|
|
262
|
+
{ name: "--ignore", value: "glob", repeatable: true, describe: "Skip matching paths. Repeatable." },
|
|
263
|
+
{ name: "--since", value: "version", describe: "What the contract added since that version." },
|
|
264
|
+
],
|
|
265
|
+
returns: ["lint"],
|
|
237
266
|
},
|
|
238
267
|
{
|
|
239
268
|
name: "atlas",
|
|
@@ -292,7 +321,10 @@ const ERROR_CODES = [
|
|
|
292
321
|
{ code: "ERR_UNKNOWN_LAYOUT", when: "No layout primitive by that name." },
|
|
293
322
|
{ code: "ERR_UNKNOWN_TOKEN_GROUP", when: "No token group by that name." },
|
|
294
323
|
{ code: "ERR_UNKNOWN_UTILITY", when: "No utility or variant by that name. `suggestions` carries near matches." },
|
|
295
|
-
{ code: "ERR_UNKNOWN_DOC", when: "No doc mirror by that slug." },
|
|
324
|
+
{ code: "ERR_UNKNOWN_DOC", when: "No package guide or doc mirror by that slug. `suggestions` carries near matches and the package guides." },
|
|
325
|
+
{ code: "ERR_UNKNOWN_PROVIDER", when: "No provider by that name. `suggestions` carries near matches." },
|
|
326
|
+
{ code: "ERR_LINT_INPUT", when: "sable lint --json: a path to lint does not exist." },
|
|
327
|
+
{ code: "ERR_CHECK_FAILED", when: "sable check: doctor, lint, or grade failed. `data.checks` names which." },
|
|
296
328
|
{ code: "ERR_BAD_FLAG", when: "A flag value is outside its allowed set." },
|
|
297
329
|
{ code: "ERR_NO_RESULTS", when: "A search matched nothing." },
|
|
298
330
|
{ code: "ERR_REGISTRY_MISSING", when: "The installed package has no registry.json." },
|
|
@@ -312,24 +344,56 @@ const ERROR_CODES = [
|
|
|
312
344
|
|
|
313
345
|
/* ── Args. ──────────────────────────────────────────────────────────────────── */
|
|
314
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Global flags belong to `sable`, not to the command, so every command accepts
|
|
349
|
+
* them. The parser reads the command's own flag table to know which flags are
|
|
350
|
+
* switches: a switch never takes the next word as its value, so
|
|
351
|
+
* `sable component --dense Button` names Button rather than setting
|
|
352
|
+
* `--dense=Button`. A command that delegates to a script gets argv with the
|
|
353
|
+
* flags only `sable` understands (`--dense`, `--detail`) taken out, because the
|
|
354
|
+
* scripts reject flags they do not know — which is how `sable learn --search
|
|
355
|
+
* "…" --dense` used to fail with "Unknown flag: --dense" while `--help` listed
|
|
356
|
+
* `--dense` as global.
|
|
357
|
+
*/
|
|
315
358
|
const argv = process.argv.slice(2)
|
|
359
|
+
const GLOBAL_VALUE_FLAGS = new Set(GLOBAL_FLAGS.filter((f) => f.value).map((f) => f.name.slice(2)))
|
|
360
|
+
const SABLE_ONLY_FLAGS = new Set(["dense", "detail"])
|
|
361
|
+
const command = (() => {
|
|
362
|
+
for (let i = 0; i < argv.length; i++) {
|
|
363
|
+
const a = argv[i]
|
|
364
|
+
if (!a.startsWith("--")) return a
|
|
365
|
+
if (!a.includes("=") && GLOBAL_VALUE_FLAGS.has(a.slice(2))) i++
|
|
366
|
+
}
|
|
367
|
+
return undefined
|
|
368
|
+
})()
|
|
369
|
+
const SPEC = new Map(
|
|
370
|
+
[...GLOBAL_FLAGS, ...(COMMANDS.find((c) => c.name === command)?.flags ?? [])].map((f) => [f.name.slice(2), f]),
|
|
371
|
+
)
|
|
316
372
|
const flags = new Map()
|
|
317
373
|
const positional = []
|
|
374
|
+
/** argv for a delegating command: everything after the command word, minus sable-only flags. */
|
|
375
|
+
const forwarded = []
|
|
318
376
|
for (let i = 0; i < argv.length; i++) {
|
|
319
377
|
const a = argv[i]
|
|
320
378
|
if (!a.startsWith("--")) {
|
|
321
379
|
positional.push(a)
|
|
380
|
+
if (positional.length > 1) forwarded.push(a)
|
|
322
381
|
continue
|
|
323
382
|
}
|
|
324
383
|
const eq = a.indexOf("=")
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
384
|
+
const name = eq === -1 ? a.slice(2) : a.slice(2, eq)
|
|
385
|
+
const spec = SPEC.get(name)
|
|
386
|
+
const takesValue = eq === -1 && argv[i + 1] !== undefined && !argv[i + 1].startsWith("--") && (spec ? spec.value !== null : true)
|
|
387
|
+
const value = eq !== -1 ? a.slice(eq + 1) : takesValue ? argv[++i] : true
|
|
388
|
+
// Repeatable flags (`--ignore`) keep every value; everything else keeps the last.
|
|
389
|
+
if (flags.has(name) && spec?.repeatable) flags.set(name, [flags.get(name)].flat().concat(value))
|
|
390
|
+
else flags.set(name, spec?.repeatable ? [value] : value)
|
|
391
|
+
if (!SABLE_ONLY_FLAGS.has(name)) forwarded.push(...(eq !== -1 || value === true ? [a] : [a, value]))
|
|
328
392
|
}
|
|
329
393
|
|
|
330
394
|
const JSON_OUT = flags.has("json")
|
|
331
395
|
const DENSE = flags.has("dense")
|
|
332
|
-
const
|
|
396
|
+
const rest = positional.slice(1)
|
|
333
397
|
|
|
334
398
|
/* ── Usage log. Opt-in, off unless SABLE_CLI_LOG names a file. ──────────────────
|
|
335
399
|
*
|
|
@@ -438,6 +502,9 @@ const UTILITIES = R.utilities?.items ?? []
|
|
|
438
502
|
|
|
439
503
|
const importLine = (name) => `import { ${name} } from "@sableui/react"`
|
|
440
504
|
|
|
505
|
+
const PROVIDERS = R.providers?.items ?? []
|
|
506
|
+
const findProvider = (name) => PROVIDERS.find((p) => p.name.toLowerCase() === String(name).toLowerCase())
|
|
507
|
+
|
|
441
508
|
function findComponent(name) {
|
|
442
509
|
const hit = COMPONENTS.find((c) => c.name.toLowerCase() === name.toLowerCase())
|
|
443
510
|
if (hit) return hit
|
|
@@ -445,7 +512,7 @@ function findComponent(name) {
|
|
|
445
512
|
"ERR_UNKNOWN_COMPONENT",
|
|
446
513
|
`No component named "${name}".`,
|
|
447
514
|
[
|
|
448
|
-
...nearest(name, COMPONENTS.map((c) => c.name)).map((n) => `Did you mean ${n}?`),
|
|
515
|
+
...nearest(name, [...COMPONENTS, ...PROVIDERS].map((c) => c.name)).map((n) => `Did you mean ${n}?`),
|
|
449
516
|
`sable search "${name}" — searches summaries and keywords too`,
|
|
450
517
|
"sable component --list",
|
|
451
518
|
],
|
|
@@ -518,6 +585,9 @@ function cmdSearch() {
|
|
|
518
585
|
if (!type || type === "component")
|
|
519
586
|
for (const c of COMPONENTS)
|
|
520
587
|
pool.push({ kind: "component", name: c.name, summary: c.summary, status: c.status, group: c.group, _s: score({ name: c.name, keywords: c.keywords, text: `${c.summary} ${c.usage?.description ?? ""}` }) })
|
|
588
|
+
if (!type || type === "provider")
|
|
589
|
+
for (const p of PROVIDERS)
|
|
590
|
+
pool.push({ kind: "provider", name: p.name, summary: p.summary, _s: score({ name: p.name, keywords: ["provider", ...(p.companions ?? [])], text: `${p.summary} ${p.placement ?? ""}` }) })
|
|
521
591
|
if (!type || type === "pattern")
|
|
522
592
|
for (const p of PATTERNS)
|
|
523
593
|
pool.push({ kind: "pattern", name: p.name, summary: p.summary, _s: score({ name: p.name, text: `${p.summary} ${p.intent ?? ""}` }) })
|
|
@@ -540,7 +610,7 @@ function cmdSearch() {
|
|
|
540
610
|
// Ties resolve in the order AGENTS.md tells an agent to resolve a need:
|
|
541
611
|
// a component first, then a pattern or primitive, then a utility, then a
|
|
542
612
|
// token. "segmented" should land on ToggleGroup before the well behind it.
|
|
543
|
-
const KIND_ORDER = ["component", "pattern", "block", "layout", "utility", "token", "doc"]
|
|
613
|
+
const KIND_ORDER = ["component", "provider", "pattern", "block", "layout", "utility", "token", "doc"]
|
|
544
614
|
const hits = pool
|
|
545
615
|
.filter((h) => h._s > 0)
|
|
546
616
|
.sort((a, b) => b._s - a._s || KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind))
|
|
@@ -610,6 +680,10 @@ function cmdComponent() {
|
|
|
610
680
|
)
|
|
611
681
|
}
|
|
612
682
|
|
|
683
|
+
// A provider is asked for the way a component is. Answer with its entry rather
|
|
684
|
+
// than ERR_UNKNOWN_COMPONENT, which is what `sable component SableThemeProvider`
|
|
685
|
+
// used to say.
|
|
686
|
+
if (!COMPONENTS.some((c) => c.name.toLowerCase() === rest[0].toLowerCase()) && findProvider(rest[0])) return cmdProvider()
|
|
613
687
|
const c = findComponent(rest[0])
|
|
614
688
|
|
|
615
689
|
if (flags.has("props"))
|
|
@@ -707,6 +781,74 @@ function docSlugFor(name) {
|
|
|
707
781
|
return entry.path.split("/").pop().replace(/\.tsx$/, "")
|
|
708
782
|
}
|
|
709
783
|
|
|
784
|
+
/* ── provider ───────────────────────────────────────────────────────────────── */
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* App-level providers. Two kinds share one shape: app-wide ones documented in
|
|
788
|
+
* src/providers/providers.meta.ts (props, placement, examples), and ones owned
|
|
789
|
+
* by a component (`partOf`), whose full contract stays with that component.
|
|
790
|
+
*/
|
|
791
|
+
function cmdProvider() {
|
|
792
|
+
if (flags.has("list") || !rest[0]) {
|
|
793
|
+
const data = PROVIDERS.map((p) => ({
|
|
794
|
+
name: p.name,
|
|
795
|
+
...(p.partOf ? { partOf: p.partOf } : { required: p.required.startsWith("Required") }),
|
|
796
|
+
summary: p.summary,
|
|
797
|
+
}))
|
|
798
|
+
return ok("provider.list", { count: data.length, about: R.providers?.about, providers: data }, () =>
|
|
799
|
+
data
|
|
800
|
+
.map((p) =>
|
|
801
|
+
DENSE
|
|
802
|
+
? `${p.name}${p.partOf ? ` partOf:${p.partOf}` : p.required ? " required" : ""}`
|
|
803
|
+
: ` ${p.name.padEnd(24)}${(p.partOf ? `part of ${p.partOf}` : p.required ? "required" : "optional").padEnd(18)}${firstSentence(p.summary)}`,
|
|
804
|
+
)
|
|
805
|
+
.join("\n"),
|
|
806
|
+
)
|
|
807
|
+
}
|
|
808
|
+
const p = findProvider(rest[0])
|
|
809
|
+
if (!p)
|
|
810
|
+
fail("ERR_UNKNOWN_PROVIDER", `No provider named "${rest[0]}".`, [
|
|
811
|
+
...nearest(rest[0], PROVIDERS.map((x) => x.name)).map((n) => `Did you mean ${n}?`),
|
|
812
|
+
"sable provider --list",
|
|
813
|
+
])
|
|
814
|
+
const owner = p.partOf ? COMPONENTS.find((c) => c.name === p.partOf) : null
|
|
815
|
+
const props = p.props ?? []
|
|
816
|
+
const contract = owner ? `sable component ${owner.name} --props` : null
|
|
817
|
+
if (flags.has("props"))
|
|
818
|
+
return ok("provider.props", { name: p.name, props, ...(p.signature ? { signature: p.signature } : {}), ...(contract ? { contract } : {}) }, () =>
|
|
819
|
+
p.partOf ? ` ${p.name} ${p.signature}\n\nFull contract: ${contract}` : renderProps(p),
|
|
820
|
+
)
|
|
821
|
+
if (flags.has("examples")) return ok("provider.examples", { name: p.name, examples: p.examples ?? [] }, () => renderExamples(p))
|
|
822
|
+
|
|
823
|
+
const level = detailLevel("compact")
|
|
824
|
+
const data = { name: p.name, kind: "provider", import: p.import, summary: p.summary }
|
|
825
|
+
if (p.partOf) Object.assign(data, { partOf: p.partOf, signature: p.signature, contract })
|
|
826
|
+
else Object.assign(data, { required: p.required, placement: p.placement })
|
|
827
|
+
if (level !== "brief") {
|
|
828
|
+
if (!p.partOf) data.props = props
|
|
829
|
+
data.companions = p.companions ?? []
|
|
830
|
+
data.related = p.related ?? []
|
|
831
|
+
data.examples = p.examples ?? []
|
|
832
|
+
}
|
|
833
|
+
if (level === "full") data.notes = p.notes ?? []
|
|
834
|
+
ok("provider.detail", data, () =>
|
|
835
|
+
[
|
|
836
|
+
`${p.name} [provider${p.partOf ? `, part of ${p.partOf}` : ""}]`,
|
|
837
|
+
p.summary,
|
|
838
|
+
`\n${p.import}`,
|
|
839
|
+
p.partOf ? `\n ${p.name} ${p.signature}\n\nFull contract: ${contract}` : "",
|
|
840
|
+
p.required ? `\n${p.required}` : "",
|
|
841
|
+
p.placement ? `${DENSE ? "" : "Where: "}${p.placement}` : "",
|
|
842
|
+
level === "brief" || p.partOf ? "" : renderProps(p),
|
|
843
|
+
level === "brief" || !(p.companions ?? []).length ? "" : `\nWith: ${p.companions.join(", ")}`,
|
|
844
|
+
level === "brief" ? "" : renderExamples(p),
|
|
845
|
+
level === "full" ? section("notes", (p.notes ?? []).map(bullet)) : "",
|
|
846
|
+
]
|
|
847
|
+
.filter(Boolean)
|
|
848
|
+
.join("\n"),
|
|
849
|
+
)
|
|
850
|
+
}
|
|
851
|
+
|
|
710
852
|
/* ── pattern / block ────────────────────────────────────────────────────────── */
|
|
711
853
|
|
|
712
854
|
/**
|
|
@@ -895,9 +1037,30 @@ function findUtility(input) {
|
|
|
895
1037
|
const name = input.replace(/^\./, "").replace(/:$/, "")
|
|
896
1038
|
const exact = UTILITIES.find((u) => u.name === name)
|
|
897
1039
|
if (exact) return exact
|
|
898
|
-
|
|
1040
|
+
const functional = UTILITIES.filter((u) => u.name.endsWith("-*")).find((u) => name.startsWith(u.name.slice(0, -1)))
|
|
1041
|
+
if (functional) return functional
|
|
1042
|
+
return TYPE_TIERS.find((u) => u.name === name)
|
|
899
1043
|
}
|
|
900
1044
|
|
|
1045
|
+
/**
|
|
1046
|
+
* The paired `text-*` page tiers are `@utility` declarations too, documented in
|
|
1047
|
+
* `tokens.typography` (one home, so the two descriptions cannot disagree). An
|
|
1048
|
+
* agent types the class it wants to write, so `sable utility text-heading-1`
|
|
1049
|
+
* resolves there instead of answering ERR_UNKNOWN_UTILITY.
|
|
1050
|
+
*/
|
|
1051
|
+
const TYPE_TIERS = (R.tokens?.typography ?? [])
|
|
1052
|
+
.filter((t) => typeof t === "object" && t.name)
|
|
1053
|
+
.map((t) => ({
|
|
1054
|
+
name: `text-${t.name}`,
|
|
1055
|
+
kind: "utility",
|
|
1056
|
+
family: "typography",
|
|
1057
|
+
intent: t.intent,
|
|
1058
|
+
whenToUse: t.whenToUse,
|
|
1059
|
+
insteadOf: t.insteadOf,
|
|
1060
|
+
example: `<p className="text-${t.name}">…</p>`,
|
|
1061
|
+
documentedIn: "registry.json → tokens.typography",
|
|
1062
|
+
}))
|
|
1063
|
+
|
|
901
1064
|
function cmdUtility() {
|
|
902
1065
|
const name = rest[0]
|
|
903
1066
|
if (flags.has("list") || !name) {
|
|
@@ -930,7 +1093,7 @@ function cmdUtility() {
|
|
|
930
1093
|
const u = findUtility(name)
|
|
931
1094
|
if (!u)
|
|
932
1095
|
fail("ERR_UNKNOWN_UTILITY", `No utility "${name}".`, [
|
|
933
|
-
...nearest(name, UTILITIES.map((x) => x.name)).map((n) => `sable utility ${n}`),
|
|
1096
|
+
...nearest(name, [...UTILITIES, ...TYPE_TIERS].map((x) => x.name)).map((n) => `sable utility ${n}`),
|
|
934
1097
|
`sable search "${name}"`,
|
|
935
1098
|
"sable utility --list",
|
|
936
1099
|
])
|
|
@@ -952,9 +1115,29 @@ function cmdUtility() {
|
|
|
952
1115
|
|
|
953
1116
|
/* ── docs ───────────────────────────────────────────────────────────────────── */
|
|
954
1117
|
|
|
1118
|
+
/**
|
|
1119
|
+
* The package's own guides, served by name. llms.txt tells an agent to read
|
|
1120
|
+
* AGENTS.md first, and `sable docs AGENTS` answered ERR_UNKNOWN_DOC — the one
|
|
1121
|
+
* document the CLI was pointing at was the one it could not serve. These ship
|
|
1122
|
+
* in the package's `files`, so they are always next to this CLI.
|
|
1123
|
+
*/
|
|
1124
|
+
const PACKAGE_DOCS = [
|
|
1125
|
+
{ slug: "AGENTS", file: "AGENTS.md", aliases: ["agents.md", "agent"] },
|
|
1126
|
+
{ slug: "README", file: "README.md", aliases: ["readme.md", "install"] },
|
|
1127
|
+
{ slug: "CONTRIBUTING", file: "CONTRIBUTING.md", aliases: ["contributing.md"] },
|
|
1128
|
+
{ slug: "DESIGN", file: "DESIGN.md", aliases: ["design.md"] },
|
|
1129
|
+
{ slug: "CHANGELOG", file: "CHANGELOG.md", aliases: ["changelog.md", "changes"] },
|
|
1130
|
+
{ slug: "llms", file: "llms.txt", aliases: ["llms.txt"] },
|
|
1131
|
+
]
|
|
1132
|
+
|
|
1133
|
+
function packageDoc(name) {
|
|
1134
|
+
const q = String(name).toLowerCase()
|
|
1135
|
+
return PACKAGE_DOCS.find((d) => d.slug.toLowerCase() === q || d.aliases.includes(q))
|
|
1136
|
+
}
|
|
1137
|
+
|
|
955
1138
|
function docSlugs() {
|
|
956
|
-
|
|
957
|
-
|
|
1139
|
+
const out = PACKAGE_DOCS.filter((d) => existsSync(join(PKG_ROOT, d.file))).map((d) => ({ kind: "package", slug: d.slug, path: join(PKG_ROOT, d.file) }))
|
|
1140
|
+
if (!existsSync(LLMS_DIR)) return out
|
|
958
1141
|
for (const kind of readdirSync(LLMS_DIR)) {
|
|
959
1142
|
const dir = join(LLMS_DIR, kind)
|
|
960
1143
|
if (!statSync(dir).isDirectory()) continue
|
|
@@ -968,6 +1151,9 @@ function docSlugs() {
|
|
|
968
1151
|
function cmdDocs() {
|
|
969
1152
|
const all = docSlugs()
|
|
970
1153
|
const kind = flags.get("kind")
|
|
1154
|
+
const kinds = [...new Set(all.map((d) => d.kind))]
|
|
1155
|
+
if (kind !== undefined && !kinds.includes(kind))
|
|
1156
|
+
fail("ERR_BAD_FLAG", `No doc kind "${kind}".`, kinds.map((k) => `sable docs --list --kind ${k}`))
|
|
971
1157
|
if (flags.has("list") || !rest[0]) {
|
|
972
1158
|
const list = kind ? all.filter((d) => d.kind === kind) : all
|
|
973
1159
|
return ok("docs.list", { count: list.length, docs: list.map(({ kind, slug }) => ({ kind, slug })) }, () => {
|
|
@@ -980,14 +1166,18 @@ function cmdDocs() {
|
|
|
980
1166
|
}
|
|
981
1167
|
|
|
982
1168
|
const want = docSlugFor(rest[0])
|
|
1169
|
+
const pkg = packageDoc(rest[0])
|
|
983
1170
|
const hit =
|
|
1171
|
+
(pkg && all.find((d) => d.kind === "package" && d.slug === pkg.slug)) ??
|
|
984
1172
|
all.find((d) => d.slug === rest[0]) ??
|
|
985
1173
|
all.find((d) => d.slug === want) ??
|
|
986
1174
|
all.find((d) => d.slug === slugify(rest[0]))
|
|
987
1175
|
if (!hit)
|
|
988
|
-
fail("ERR_UNKNOWN_DOC", `No doc mirror for "${rest[0]}".`, [
|
|
989
|
-
...nearest(want, all.map((d) => d.slug)).map((s) => `Did you mean ${s}?`),
|
|
990
|
-
"
|
|
1176
|
+
fail("ERR_UNKNOWN_DOC", `No package guide or doc mirror for "${rest[0]}".`, [
|
|
1177
|
+
...nearest(want, all.map((d) => d.slug), 5).map((s) => `Did you mean sable docs ${s}?`),
|
|
1178
|
+
`Package guides: ${all.filter((d) => d.kind === "package").map((d) => d.slug).join(", ")}`,
|
|
1179
|
+
`Task guides: ${all.filter((d) => d.kind === "tasks").map((d) => d.slug).join(", ")}`,
|
|
1180
|
+
"sable docs --list — every slug, by kind",
|
|
991
1181
|
])
|
|
992
1182
|
const content = readFileSync(hit.path, "utf8")
|
|
993
1183
|
ok("docs.detail", { kind: hit.kind, slug: hit.slug, content }, content)
|
|
@@ -1002,7 +1192,7 @@ function cmdDocs() {
|
|
|
1002
1192
|
* package's classes, or `data-sable-theme` set with no theme CSS imported, which
|
|
1003
1193
|
* renders the default theme and looks like the switcher is broken.
|
|
1004
1194
|
*/
|
|
1005
|
-
function
|
|
1195
|
+
function doctorReport() {
|
|
1006
1196
|
const checks = []
|
|
1007
1197
|
const add = (status, name, detail, fix) => checks.push({ status, name, detail, ...(fix ? { fix } : {}) })
|
|
1008
1198
|
|
|
@@ -1095,28 +1285,51 @@ function cmdDoctor() {
|
|
|
1095
1285
|
)
|
|
1096
1286
|
}
|
|
1097
1287
|
|
|
1288
|
+
/* The icon trap: icons are opt-in, so an app that renders Sable components with
|
|
1289
|
+
* no IconProvider gets every icon — its own and the ones inside Sable's
|
|
1290
|
+
* components — as nothing, with no error. */
|
|
1291
|
+
const usesSable = /from\s+["']@sableui\/react(?:\/(?:patterns|blocks|layout))?["']/.test(srcText)
|
|
1292
|
+
if (usesSable) {
|
|
1293
|
+
if (/<IconProvider\b/.test(srcText)) add("pass", "icons", "<IconProvider> is mounted.")
|
|
1294
|
+
else
|
|
1295
|
+
add(
|
|
1296
|
+
"fail",
|
|
1297
|
+
"icons",
|
|
1298
|
+
"No <IconProvider> — every <Icon> renders nothing.",
|
|
1299
|
+
'Wrap your app in <IconProvider set={reiconIconSet}> from "@sableui/react/icons/reicon".',
|
|
1300
|
+
)
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1098
1303
|
if (pkg && !/resolve\s*:\s*\{[^}]*dedupe|dedupe/.test(readConfigs()))
|
|
1099
1304
|
add("info", "dedupe", "Could not confirm React dedupe in a bundler config.", 'resolve.dedupe: ["react","react-dom"] — a second React is an invalid-hook blank page.')
|
|
1100
1305
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1306
|
+
return { version: R.version, checks, failed: checks.filter((c) => c.status === "fail").length }
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function cmdDoctor() {
|
|
1310
|
+
const data = doctorReport()
|
|
1311
|
+
const { checks, failed } = data
|
|
1103
1312
|
if (JSON_OUT) {
|
|
1104
|
-
process.stdout.write(`${JSON.stringify({ type: "doctor", data }, null, 1)}\n`)
|
|
1313
|
+
process.stdout.write(`${JSON.stringify({ type: "doctor", data }, null, DENSE ? 0 : 1)}\n`)
|
|
1105
1314
|
} else {
|
|
1106
|
-
|
|
1107
|
-
for (const c of checks) {
|
|
1108
|
-
process.stdout.write(` ${mark[c.status]} ${c.name.padEnd(14)}${c.detail}\n`)
|
|
1109
|
-
if (c.fix) process.stdout.write(` → ${c.fix}\n`)
|
|
1110
|
-
}
|
|
1315
|
+
process.stdout.write(doctorLines(checks))
|
|
1111
1316
|
process.stdout.write(
|
|
1112
1317
|
failed
|
|
1113
1318
|
? `\n[sable doctor] ✗ ${failed} failure(s). Each one renders wrong without erroring.\n`
|
|
1114
1319
|
: "\n[sable doctor] ✓ wiring holds.\n",
|
|
1115
1320
|
)
|
|
1116
1321
|
}
|
|
1322
|
+
logInvocation(failed ? 1 : 0)
|
|
1117
1323
|
process.exit(failed ? 1 : 0)
|
|
1118
1324
|
}
|
|
1119
1325
|
|
|
1326
|
+
function doctorLines(checks, indent = " ") {
|
|
1327
|
+
const mark = { pass: "✓", warn: "!", fail: "✗", info: "·" }
|
|
1328
|
+
return checks
|
|
1329
|
+
.map((c) => `${indent}${mark[c.status]} ${c.name.padEnd(14)}${c.detail}\n${c.fix ? `${indent} → ${c.fix}\n` : ""}`)
|
|
1330
|
+
.join("")
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1120
1333
|
const SKIP_DIRS = new Set(["node_modules", ".next", ".nuxt", "dist", "build", "out", "coverage", ".git", ".turbo"])
|
|
1121
1334
|
function collectSources(match, limit = 4000) {
|
|
1122
1335
|
const out = []
|
|
@@ -1154,26 +1367,201 @@ function readConfigs() {
|
|
|
1154
1367
|
|
|
1155
1368
|
/* ── lint (passthrough) ─────────────────────────────────────────────────────── */
|
|
1156
1369
|
|
|
1370
|
+
/**
|
|
1371
|
+
* Run one of the package's scripts with the argv a delegating command was given,
|
|
1372
|
+
* minus the flags only `sable` understands. With `--json --dense`, a JSON
|
|
1373
|
+
* document on stdout is re-emitted on one line, so `--dense` means the same
|
|
1374
|
+
* thing here as on the commands `sable` answers itself.
|
|
1375
|
+
*/
|
|
1376
|
+
function runScript(file, args = forwarded) {
|
|
1377
|
+
const r = spawnSync(process.execPath, [file, ...args], { encoding: "utf8", cwd: CWD, maxBuffer: 256 * 1024 * 1024 })
|
|
1378
|
+
return { code: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" }
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
function passThrough({ code, stdout, stderr }) {
|
|
1382
|
+
let out = stdout
|
|
1383
|
+
if (JSON_OUT && DENSE && out.trim()) {
|
|
1384
|
+
try {
|
|
1385
|
+
out = `${JSON.stringify(JSON.parse(out))}\n`
|
|
1386
|
+
} catch {
|
|
1387
|
+
/* Not one JSON document; print it as it came. */
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
process.stdout.write(out)
|
|
1391
|
+
process.stderr.write(stderr)
|
|
1392
|
+
logInvocation(code)
|
|
1393
|
+
process.exit(code)
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1157
1396
|
function cmdLint() {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1397
|
+
passThrough(runScript(join(HERE, "sable-lint.mjs")))
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/* ── check ──────────────────────────────────────────────────────────────────── */
|
|
1401
|
+
|
|
1402
|
+
/**
|
|
1403
|
+
* The review steps from the skill's finish checklist that no tool can verify.
|
|
1404
|
+
* They are printed, numbered, after the automated checks pass or fail, because a
|
|
1405
|
+
* green `sable check` is a contract result, not a design approval — and an agent
|
|
1406
|
+
* that reads "✓" as "done" skips exactly these.
|
|
1407
|
+
*/
|
|
1408
|
+
const MANUAL_REVIEW = [
|
|
1409
|
+
{ step: "Review the rendered design", how: "sable docs reviewing-a-design — capture desktop (≥1024px) and mobile (≤480px), every promised colour mode; judge brief fit, hierarchy, rhythm, imagery." },
|
|
1410
|
+
{ step: "Walk the journey", how: "sable docs reviewing-a-journey — operate the primary task and its recovery path end to end; loading, empty, error and success states; keyboard and touch." },
|
|
1411
|
+
{ step: "Review the motion", how: "sable docs reviewing-motion — observe playback and interruption for anything that moves." },
|
|
1412
|
+
{ step: "Check reduced motion", how: "Turn on prefers-reduced-motion (or MotionProvider \"reduced\"): state still changes, nothing essential depends on animation, nothing keeps moving." },
|
|
1413
|
+
{ step: "Write the reuse inventory", how: "List the Sable components, parts, utilities and token groups used, and every app-owned UI or CSS addition with the catalog search that justified it. Start from the app-owned surfaces sable check lists (sable grade --json → inventory): each composes the component named beside it, or the inventory says why the app owns it." },
|
|
1414
|
+
{ step: "Record what you learned", how: "sable docs learning-from-builds — keep conditional lessons with evidence (sable learn --record); never record human approval you did not get." },
|
|
1415
|
+
]
|
|
1416
|
+
|
|
1417
|
+
function cmdCheck() {
|
|
1418
|
+
/* Where to check. A Stop hook's command must carry no shell syntax — Claude
|
|
1419
|
+
* Code runs it under Git Bash, or PowerShell 5.1 where neither `&&` nor
|
|
1420
|
+
* "${VAR}" works — so it cannot `cd` first. Instead the hook runs from
|
|
1421
|
+
* anywhere and `check` finds the project itself: Claude Code's
|
|
1422
|
+
* CLAUDE_PROJECT_DIR, plus `--root` for a workspace package inside it. */
|
|
1423
|
+
const rootFlag = flags.get("root")
|
|
1424
|
+
if (rootFlag === true) fail("ERR_BAD_ARGUMENT", "--root needs a directory.", ["sable check --root apps/web"])
|
|
1425
|
+
const base = flags.has("stop-hook") && process.env.CLAUDE_PROJECT_DIR ? process.env.CLAUDE_PROJECT_DIR : CWD
|
|
1426
|
+
const target = rootFlag ? resolve(base, rootFlag) : base
|
|
1427
|
+
if (!existsSync(target) || !statSync(target).isDirectory()) fail("ERR_BAD_ARGUMENT", `Not a directory: ${target}`, ["sable check --root <project dir>"])
|
|
1428
|
+
if (target !== CWD) {
|
|
1429
|
+
process.chdir(target)
|
|
1430
|
+
CWD = target
|
|
1431
|
+
}
|
|
1432
|
+
const paths = rest.length ? rest : ["src"]
|
|
1433
|
+
const ignore = [flags.get("ignore") ?? []].flat().filter((g) => typeof g === "string").flatMap((g) => ["--ignore", g])
|
|
1434
|
+
const checks = []
|
|
1435
|
+
|
|
1436
|
+
const doctor = doctorReport()
|
|
1437
|
+
const doctorFailures = doctor.checks.filter((c) => c.status === "fail")
|
|
1438
|
+
checks.push({
|
|
1439
|
+
name: "doctor",
|
|
1440
|
+
status: doctorFailures.length ? "fail" : "pass",
|
|
1441
|
+
summary: doctorFailures.length ? `${doctorFailures.length} wiring failure(s): ${doctorFailures.map((c) => c.name).join(", ")}` : "wiring holds",
|
|
1442
|
+
details: doctorFailures.map((c) => `${c.name}: ${c.detail}${c.fix ? ` → ${c.fix}` : ""}`),
|
|
1443
|
+
data: doctor,
|
|
1444
|
+
})
|
|
1445
|
+
|
|
1446
|
+
const parse = (r) => {
|
|
1447
|
+
try {
|
|
1448
|
+
return JSON.parse(r.stdout)
|
|
1449
|
+
} catch {
|
|
1450
|
+
return null
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
const firstLine = (r) => (r.stderr || r.stdout).trim().split("\n")[0] ?? "no output"
|
|
1454
|
+
|
|
1455
|
+
const lintRun = runScript(join(HERE, "sable-lint.mjs"), [...paths, ...ignore, "--json"])
|
|
1456
|
+
const lint = parse(lintRun)
|
|
1457
|
+
if (!lint || lint.type === "error") checks.push({ name: "lint", status: "error", summary: lint?.message ?? firstLine(lintRun), details: [] })
|
|
1458
|
+
else {
|
|
1459
|
+
const v = lint.data.violations
|
|
1460
|
+
checks.push({
|
|
1461
|
+
name: "lint",
|
|
1462
|
+
status: v.length ? "fail" : "pass",
|
|
1463
|
+
summary: v.length ? `${v.length} violation(s) in ${lint.data.filesScanned} files` : `${lint.data.filesScanned} files clean`,
|
|
1464
|
+
details: v.map((x) => `${x.ruleId} — ${x.file}:${x.line} → ${x.match}`),
|
|
1465
|
+
data: lint.data,
|
|
1466
|
+
})
|
|
1163
1467
|
}
|
|
1468
|
+
|
|
1469
|
+
const review = typeof flags.get("review") === "string" ? ["--review", flags.get("review")] : []
|
|
1470
|
+
const icons = typeof flags.get("icons") === "string" ? ["--icons", flags.get("icons")] : []
|
|
1471
|
+
const gradeRun = runScript(join(PKG_ROOT, "scripts", "grade.mjs"), [...paths, ...ignore, ...review, ...icons, "--json"])
|
|
1472
|
+
const grade = parse(gradeRun)
|
|
1473
|
+
if (!grade || grade.type === "error") checks.push({ name: "grade", status: "error", summary: grade?.message ?? firstLine(gradeRun), details: [] })
|
|
1474
|
+
else {
|
|
1475
|
+
// Contract violations are lint's to report; grade adds what lint cannot see.
|
|
1476
|
+
const LABELS = { hallucinatedComponent: "hallucinated components", hallucinatedProp: "hallucinated props", radixIdiom: "Radix idioms", hardcodedControl: "hardcoded control heights", offContractIcon: "off-contract icons" }
|
|
1477
|
+
const found = Object.entries(LABELS).filter(([k]) => grade.counts[k])
|
|
1478
|
+
const reviewMissing = review.length > 0 && grade.review?.status !== "review-recorded"
|
|
1479
|
+
const details = found.flatMap(([k]) => grade.findings[k].map((f) => `${LABELS[k]} — ${f.file}:${f.line} → ${f.name ?? f.prop ?? f.what ?? f.match}${f.component ? ` on ${f.component}` : ""}${f.from ? ` from ${f.from}${f.reason ? ` (${f.reason})` : ""}` : ""}`))
|
|
1480
|
+
if (reviewMissing) details.push(`review: ${grade.review?.status ?? "missing"}${grade.review?.problems?.length ? ` — ${grade.review.problems.join("; ")}` : ""}`)
|
|
1481
|
+
checks.push({
|
|
1482
|
+
name: "grade",
|
|
1483
|
+
status: found.length || reviewMissing ? "fail" : "pass",
|
|
1484
|
+
summary: [
|
|
1485
|
+
`score ${grade.score}`,
|
|
1486
|
+
found.length ? found.map(([k]) => `${grade.counts[k]} ${LABELS[k]}`).join(", ") : "no hallucinated components or props, Radix idioms, hardcoded heights or off-contract icons",
|
|
1487
|
+
reviewMissing ? `review ${grade.review?.status ?? "missing"}` : "",
|
|
1488
|
+
]
|
|
1489
|
+
.filter(Boolean)
|
|
1490
|
+
.join("; "),
|
|
1491
|
+
details,
|
|
1492
|
+
data: { score: grade.score, contract: grade.contract, counts: grade.counts, review: grade.review, findings: grade.findings },
|
|
1493
|
+
})
|
|
1494
|
+
}
|
|
1495
|
+
/* App-owned surfaces, from grade. Never a failure: the input to the reuse
|
|
1496
|
+
* inventory (finish step 6), which otherwise gets written from memory. */
|
|
1497
|
+
const inventory = grade && grade.type !== "error" ? (grade.inventory ?? null) : null
|
|
1498
|
+
|
|
1499
|
+
const failed = checks.filter((c) => c.status !== "pass").map((c) => c.name)
|
|
1500
|
+
const data = { paths, passed: !failed.length, failed, checks, inventory, manualReview: MANUAL_REVIEW }
|
|
1501
|
+
const MARK = { pass: "✓", fail: "✗", error: "✗" }
|
|
1502
|
+
const MAX_DETAILS = 12
|
|
1503
|
+
const summaryText = () =>
|
|
1504
|
+
[
|
|
1505
|
+
`sable check — ${paths.join(" ")}`,
|
|
1506
|
+
...checks.flatMap((c) => [
|
|
1507
|
+
` ${MARK[c.status]} ${c.name.padEnd(7)}${c.summary}`,
|
|
1508
|
+
...c.details.slice(0, MAX_DETAILS).map((d) => ` ${d}`),
|
|
1509
|
+
...(c.details.length > MAX_DETAILS ? [` … ${c.details.length - MAX_DETAILS} more: sable ${c.name}${c.name === "doctor" ? "" : ` ${paths.join(" ")}`}`] : []),
|
|
1510
|
+
]),
|
|
1511
|
+
"",
|
|
1512
|
+
failed.length ? `[sable check] ✗ ${failed.join(", ")} failed. Fix these and run sable check again.` : "[sable check] ✓ doctor, lint and grade pass.",
|
|
1513
|
+
].join("\n")
|
|
1514
|
+
const inventoryText = () => {
|
|
1515
|
+
const list = inventory?.surfaces ?? []
|
|
1516
|
+
if (!list.length) return ""
|
|
1517
|
+
return [
|
|
1518
|
+
"",
|
|
1519
|
+
`App-owned surfaces for the reuse inventory (review, not failures) — ${list.length}:`,
|
|
1520
|
+
...list.slice(0, MAX_DETAILS).map((s) => ` ${s.file}:${s.line} <${s.tag}> ${s.kind} (${s.classes}) → ${s.consider}`),
|
|
1521
|
+
...(list.length > MAX_DETAILS ? [` … ${list.length - MAX_DETAILS} more: sable grade ${paths.join(" ")} --json → inventory.surfaces`] : []),
|
|
1522
|
+
"",
|
|
1523
|
+
].join("\n")
|
|
1524
|
+
}
|
|
1525
|
+
const manualText = () =>
|
|
1526
|
+
[
|
|
1527
|
+
"",
|
|
1528
|
+
"Automated checks cannot judge the design. Before calling it done:",
|
|
1529
|
+
...MANUAL_REVIEW.map((m, i) => ` ${i + 1}. ${m.step}: ${m.how}`),
|
|
1530
|
+
].join("\n")
|
|
1531
|
+
|
|
1532
|
+
/* Stop-hook mode (Claude Code): exit 2 blocks the agent from ending its turn
|
|
1533
|
+
* and feeds stderr back to it. A Stop hook that always blocks can loop, so
|
|
1534
|
+
* when the agent is already continuing because of a stop hook
|
|
1535
|
+
* (`stop_hook_active`), report and let it stop rather than block again. */
|
|
1536
|
+
if (flags.has("stop-hook")) {
|
|
1537
|
+
let input = {}
|
|
1538
|
+
try {
|
|
1539
|
+
input = JSON.parse(readFileSync(0, "utf8") || "{}")
|
|
1540
|
+
} catch {
|
|
1541
|
+
/* No or malformed hook input: treat it as a first stop. */
|
|
1542
|
+
}
|
|
1543
|
+
logInvocation(failed.length ? 2 : 0)
|
|
1544
|
+
if (!failed.length) process.exit(0)
|
|
1545
|
+
if (input.stop_hook_active) {
|
|
1546
|
+
process.stdout.write(`${summaryText()}\n(sable check still fails; not blocking a second time.)\n`)
|
|
1547
|
+
process.exit(0)
|
|
1548
|
+
}
|
|
1549
|
+
process.stderr.write(`${summaryText()}\n${manualText()}\n`)
|
|
1550
|
+
process.exit(2)
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
if (JSON_OUT) process.stdout.write(`${JSON.stringify({ type: "check", data }, null, DENSE ? 0 : 1)}\n`)
|
|
1554
|
+
else process.stdout.write(`${summaryText()}\n${inventoryText()}${manualText()}\n`)
|
|
1555
|
+
logInvocation(failed.length ? 1 : 0, failed.length ? "ERR_CHECK_FAILED" : undefined)
|
|
1556
|
+
process.exit(failed.length ? 1 : 0)
|
|
1164
1557
|
}
|
|
1165
1558
|
|
|
1166
1559
|
/* ── Dispatch. ──────────────────────────────────────────────────────────────── */
|
|
1167
1560
|
|
|
1168
1561
|
function cmdGrade() {
|
|
1169
|
-
|
|
1170
|
-
let
|
|
1171
|
-
|
|
1172
|
-
output = execFileSync(process.execPath, [join(PKG_ROOT, "scripts/grade.mjs"), ...argv.slice(1)], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
|
|
1173
|
-
} catch (error) {
|
|
1174
|
-
output = `${error.stdout ?? ""}${error.stderr ?? ""}`
|
|
1175
|
-
code = error.status ?? 1
|
|
1176
|
-
}
|
|
1562
|
+
const run = runScript(join(PKG_ROOT, "scripts/grade.mjs"))
|
|
1563
|
+
let output = run.code === 0 ? run.stdout : `${run.stdout}${run.stderr}`
|
|
1564
|
+
const code = run.code
|
|
1177
1565
|
if (JSON_OUT) {
|
|
1178
1566
|
try {
|
|
1179
1567
|
const data = JSON.parse(output)
|
|
@@ -1190,13 +1578,35 @@ function cmdLearn() {
|
|
|
1190
1578
|
}
|
|
1191
1579
|
|
|
1192
1580
|
function cmdDesignTool(script) {
|
|
1581
|
+
passThrough(runScript(join(PKG_ROOT, "scripts", script)))
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
function cmdSetup() {
|
|
1585
|
+
if (!flags.has("agent") || rest.length) fail("ERR_BAD_ARGUMENT", "Use sable setup --agent from your project root.", ["sable setup --agent --dry-run"])
|
|
1586
|
+
const root = flags.get("root")
|
|
1587
|
+
if (root === true) fail("ERR_BAD_ARGUMENT", "--root needs a directory.", ["pnpm --filter <package> exec sable setup --agent --root ../.."])
|
|
1588
|
+
const target = flags.get("target") ?? "all"
|
|
1589
|
+
const forClaude = target === "all" || target === "claude"
|
|
1590
|
+
if (flags.has("hook") && !forClaude)
|
|
1591
|
+
fail("ERR_BAD_ARGUMENT", "--hook installs a Claude Code Stop hook; use it with --target claude or all.", ["sable setup --agent --target claude --hook"])
|
|
1193
1592
|
try {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1593
|
+
const options = { root: root ? resolve(CWD, root) : CWD, project: CWD, packageRoot: PKG_ROOT, target, update: flags.has("update"), dryRun: flags.has("dry-run") }
|
|
1594
|
+
// Preflight the settings file too, so a refusal there writes nothing anywhere.
|
|
1595
|
+
if (flags.has("hook")) installClaudeStopHook({ ...options, dryRun: true })
|
|
1596
|
+
const data = installAgentSkill(options)
|
|
1597
|
+
/* The Stop hook changes how the user's agent behaves, so setup never adds it
|
|
1598
|
+
* on its own: it prints the exact entry, and writes it only behind --hook —
|
|
1599
|
+
* the same rule setup keeps for local edits. */
|
|
1600
|
+
if (forClaude) data.stopHook = flags.has("hook") ? installClaudeStopHook(options) : claudeStopHook(options)
|
|
1601
|
+
const hook = data.stopHook
|
|
1602
|
+
const hookText = !hook
|
|
1603
|
+
? ""
|
|
1604
|
+
: hook.installed
|
|
1605
|
+
? `\n${data.dryRun ? "Would add" : hook.alreadyPresent ? "Already present:" : "Added"} a Claude Code Stop hook to ${hook.settingsFile}: Claude cannot end a task while \`sable check\` fails.`
|
|
1606
|
+
: `\nOptional: block Claude Code from ending a task while \`sable check\` fails. Add this to ${hook.settingsFile} (merge into an existing "hooks" key), or rerun with --hook to add it:\n\n${JSON.stringify(hook.settings, null, 2)}\n\nThe hook runs: ${hook.command}`
|
|
1607
|
+
ok("setup.agent", data, `${data.dryRun ? "Would install" : "Installed"} Sable skill entries at ${data.destinations.map((d) => relative(CWD, join(data.root, d)).replaceAll("\\", "/")).join(", ")}.\nNext: ${data.next}${hookText}`)
|
|
1197
1608
|
} catch (error) {
|
|
1198
|
-
|
|
1199
|
-
process.exit(error.status ?? 1)
|
|
1609
|
+
fail("ERR_AGENT_SETUP", error.message, ["sable setup --agent --dry-run"])
|
|
1200
1610
|
}
|
|
1201
1611
|
}
|
|
1202
1612
|
|
|
@@ -1217,15 +1627,9 @@ const HANDLERS = {
|
|
|
1217
1627
|
ok("target", target, `iOS ${target.platformVersion}${target.status ? ` (${target.status})` : ""}: ${target.designPolicy.principle}\n${target.statusNote ? `${target.statusNote}\n` : ""}${target.designPolicy.rule}\n${(target.designPolicy.layout ?? []).map((l) => `Layout: ${l}\n`).join("")}` + Object.values(target.components).map(c => `${c.source}: ${c.strategy} → ${c.strategy === "contextual" ? "choose by role" : c.components.join(" + ") || "redesign for phone"}`).join("\n"))
|
|
1218
1628
|
},
|
|
1219
1629
|
preset: () => cmdDesignTool("design-presets.mjs"),
|
|
1220
|
-
setup:
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
if (root === true) fail("ERR_BAD_ARGUMENT", "--root needs a directory.", ["pnpm --filter <package> exec sable setup --agent --root ../.."])
|
|
1224
|
-
try {
|
|
1225
|
-
const data = installAgentSkill({ root: root ? resolve(CWD, root) : CWD, project: CWD, packageRoot: PKG_ROOT, target: flags.get("target") ?? "all", update: flags.has("update"), dryRun: flags.has("dry-run") })
|
|
1226
|
-
ok("setup.agent", data, `${data.dryRun ? "Would install" : "Installed"} Sable skill entries at ${data.destinations.map((d) => relative(CWD, join(data.root, d)).replaceAll("\\", "/")).join(", ")}.\nNext: ${data.next}`)
|
|
1227
|
-
} catch (error) { fail("ERR_AGENT_SETUP", error.message, ["sable setup --agent --dry-run"]) }
|
|
1228
|
-
},
|
|
1630
|
+
setup: cmdSetup,
|
|
1631
|
+
check: cmdCheck,
|
|
1632
|
+
provider: cmdProvider,
|
|
1229
1633
|
manifest: cmdManifest,
|
|
1230
1634
|
search: cmdSearch,
|
|
1231
1635
|
component: cmdComponent,
|
|
@@ -1261,6 +1665,10 @@ if (!command || flags.has("help") || command === "help") {
|
|
|
1261
1665
|
cmdManifest()
|
|
1262
1666
|
}
|
|
1263
1667
|
|
|
1668
|
+
// Global flags are validated once, here, so a bad value is an error on every
|
|
1669
|
+
// command — not only on the ones that happen to read it.
|
|
1670
|
+
if (flags.has("detail")) detailLevel()
|
|
1671
|
+
|
|
1264
1672
|
const handler = HANDLERS[command]
|
|
1265
1673
|
if (!handler)
|
|
1266
1674
|
fail("ERR_UNKNOWN_COMMAND", `No command "${command}".`, [
|