bitboss-ui 3.0.0-beta.35 → 3.0.0-beta.37
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/README.md +31 -12
- package/bin/bitboss-ui-mcp.mjs +799 -74
- package/bin/bitboss-ui.mjs +6 -3
- package/dist/ai/BbAccordion.md +8 -8
- package/dist/ai/BbAlert.md +12 -7
- package/dist/ai/BbAvatar.md +9 -6
- package/dist/ai/BbBadge.md +21 -21
- package/dist/ai/BbBadgeButton.md +10 -10
- package/dist/ai/BbBaseButton.md +7 -7
- package/dist/ai/BbBaseCheckboxIcon.md +1 -1
- package/dist/ai/BbBaseRadioIcon.md +1 -1
- package/dist/ai/BbBaseSwitchIcon.md +2 -2
- package/dist/ai/BbBreadcrumbs.md +17 -10
- package/dist/ai/BbButton.md +31 -28
- package/dist/ai/BbCalendar.md +29 -21
- package/dist/ai/BbCheckbox.md +21 -22
- package/dist/ai/BbCheckboxGroup.md +36 -29
- package/dist/ai/BbCollapsible.md +2 -2
- package/dist/ai/BbColorInput.md +39 -40
- package/dist/ai/BbColorPalette.md +10 -10
- package/dist/ai/BbConfirm.md +6 -6
- package/dist/ai/BbConfirmPortal.md +1 -1
- package/dist/ai/BbDatePicker.md +36 -33
- package/dist/ai/BbDatePickerInput.md +52 -64
- package/dist/ai/BbDialog.md +22 -22
- package/dist/ai/BbDropdown.md +32 -28
- package/dist/ai/BbDropdownButton.md +39 -28
- package/dist/ai/BbDropdownGroup.md +4 -13
- package/dist/ai/BbDropzone.md +12 -12
- package/dist/ai/BbForm.md +6 -4
- package/dist/ai/BbIcon.md +2 -2
- package/dist/ai/BbIndicator.md +2 -2
- package/dist/ai/BbNumberInput.md +39 -40
- package/dist/ai/BbOffCanvas.md +19 -19
- package/dist/ai/BbPagination.md +8 -8
- package/dist/ai/BbPopover.md +15 -15
- package/dist/ai/BbProgress.md +2 -2
- package/dist/ai/BbPtScope.md +1 -1
- package/dist/ai/BbRadio.md +21 -22
- package/dist/ai/BbRadioGroup.md +36 -29
- package/dist/ai/BbRating.md +27 -30
- package/dist/ai/BbSelect.md +80 -58
- package/dist/ai/BbSelectPopover.md +44 -44
- package/dist/ai/BbSlider.md +29 -30
- package/dist/ai/BbSmoothHeight.md +1 -1
- package/dist/ai/BbSpinner.md +6 -6
- package/dist/ai/BbSwitch.md +23 -24
- package/dist/ai/BbSwitchGroup.md +37 -30
- package/dist/ai/BbTable.md +247 -87
- package/dist/ai/BbTabs.md +171 -91
- package/dist/ai/BbTabsList.md +30 -11
- package/dist/ai/{BbTabsPanes.md → BbTabsPanels.md} +28 -10
- package/dist/ai/BbTabsRoot.md +8 -58
- package/dist/ai/BbTag.md +66 -65
- package/dist/ai/BbTextInput.md +35 -34
- package/dist/ai/BbTextarea.md +36 -35
- package/dist/ai/BbTimePicker.md +22 -17
- package/dist/ai/BbTimePickerInput.md +39 -36
- package/dist/ai/BbToast.md +128 -31
- package/dist/ai/BbToastPortal.md +2 -29
- package/dist/ai/BbTooltip.md +9 -9
- package/dist/ai/BbTree.md +7 -7
- package/dist/ai/ChipsBox.md +20 -20
- package/dist/ai/CommaBox.md +2 -2
- package/dist/ai/CommonField.md +11 -11
- package/dist/ai/CommonFieldInput.md +8 -8
- package/dist/ai/CommonPopover.md +12 -12
- package/dist/ai/CommonTimeSelector.md +8 -6
- package/dist/ai/FlatListBox.md +8 -6
- package/dist/ai/GroupedListBox.md +9 -7
- package/dist/ai/ListBox.md +13 -11
- package/dist/ai/OptionsContainer.md +5 -5
- package/dist/ai/changelog.json +151 -19
- package/dist/ai/components.json +4734 -1479
- package/dist/ai/composables/useBbTableContext.md +8 -3
- package/dist/ai/composables/useConfirm.md +2 -0
- package/dist/ai/composables/useMobile.md +4 -4
- package/dist/ai/composables/useToast.md +18 -17
- package/dist/ai/guides/ai-router.md +2 -2
- package/dist/ai/guides/app-layout.md +5 -3
- package/dist/ai/guides/coherence-playbook.md +8 -8
- package/dist/ai/guides/component-picker.md +1 -1
- package/dist/ai/guides/design-language.md +33 -33
- package/dist/ai/guides/design-tokens.md +34 -19
- package/dist/ai/guides/fetch-items-playbook.md +21 -15
- package/dist/ai/guides/icons-policy.md +10 -12
- package/dist/ai/guides/inline-edit-playbook.md +6 -4
- package/dist/ai/guides/installation-and-plugin-setup.md +35 -2
- package/dist/ai/guides/migration/components/bb-badge.md +5 -5
- package/dist/ai/guides/migration/components/bb-checkbox-group.md +18 -18
- package/dist/ai/guides/migration/components/bb-chip.md +6 -6
- package/dist/ai/guides/migration/components/bb-date-picker-input.md +9 -10
- package/dist/ai/guides/migration/components/bb-dialog.md +4 -4
- package/dist/ai/guides/migration/components/bb-dropdown.md +2 -1
- package/dist/ai/guides/migration/components/bb-rating.md +12 -11
- package/dist/ai/guides/migration/components/bb-select.md +1 -1
- package/dist/ai/guides/migration/components/bb-slider.md +7 -0
- package/dist/ai/guides/migration/components/bb-table.md +28 -28
- package/dist/ai/guides/migration/components/bb-tabs.md +27 -21
- package/dist/ai/guides/migration/components/bb-tag.md +9 -8
- package/dist/ai/guides/migration/components/bb-toast.md +1 -1
- package/dist/ai/guides/migration/components/bb-tree.md +1 -1
- package/dist/ai/guides/migration/components/use-confirm.md +1 -1
- package/dist/ai/guides/migration/v2-to-v3.md +143 -98
- package/dist/ai/guides/migration/v3-beta-css-classes.md +723 -0
- package/dist/ai/guides/options-items-playbook.md +11 -6
- package/dist/ai/guides/passthrough.md +97 -67
- package/dist/ai/guides/ssr-prefill.md +3 -1
- package/dist/ai/index.md +5 -4
- package/dist/ai/recipes/inertia/inline-edit-workspace.md +10 -10
- package/dist/ai/recipes/inertia/record-form.md +5 -5
- package/dist/ai/recipes/inertia/records-workspace.md +4 -4
- package/dist/ai/recipes/inertia/upload-center.md +28 -28
- package/dist/ai/recipes/nuxt/inline-edit-workspace.md +32 -32
- package/dist/ai/recipes/nuxt/record-form.md +5 -5
- package/dist/ai/recipes/nuxt/records-workspace.md +4 -4
- package/dist/ai/recipes/nuxt/upload-center.md +27 -27
- package/dist/ai/recipes/vue/approvals-inbox.md +2 -2
- package/dist/ai/recipes/vue/inline-edit-workspace.md +33 -33
- package/dist/ai/recipes/vue/record-form.md +21 -21
- package/dist/ai/recipes/vue/records-workspace.md +4 -4
- package/dist/ai/recipes/vue/upload-center.md +27 -27
- package/dist/ai/source/BbAccordion.md +3 -2
- package/dist/ai/source/BbAvatar.md +6 -0
- package/dist/ai/source/BbBadge.md +16 -16
- package/dist/ai/source/BbBadgeButton.md +14 -14
- package/dist/ai/source/BbBreadcrumbs.md +51 -5
- package/dist/ai/source/BbButton.md +81 -7
- package/dist/ai/source/BbCalendar.md +98 -45
- package/dist/ai/source/BbCheckbox.md +2 -1
- package/dist/ai/source/BbCheckboxGroup.md +122 -94
- package/dist/ai/source/BbCollapsible.md +6 -0
- package/dist/ai/source/BbColorInput.md +14 -2
- package/dist/ai/source/BbColorPalette.md +18 -12
- package/dist/ai/source/BbConfirm.md +5 -2
- package/dist/ai/source/BbConfirmPortal.md +6 -3
- package/dist/ai/source/BbDatePicker.md +41 -14
- package/dist/ai/source/BbDatePickerInput.md +23 -27
- package/dist/ai/source/BbDialog.md +21 -4
- package/dist/ai/source/BbDropdown.md +20 -11
- package/dist/ai/source/BbDropdownButton.md +32 -5
- package/dist/ai/source/BbDropdownGroup.md +15 -9
- package/dist/ai/source/BbDropzone.md +7 -6
- package/dist/ai/source/BbIcon.md +8 -4
- package/dist/ai/source/BbIndicator.md +5 -5
- package/dist/ai/source/BbNumberInput.md +15 -2
- package/dist/ai/source/BbOffCanvas.md +35 -18
- package/dist/ai/source/BbPagination.md +16 -8
- package/dist/ai/source/BbPopover.md +37 -7
- package/dist/ai/source/BbRadio.md +2 -1
- package/dist/ai/source/BbRadioGroup.md +105 -92
- package/dist/ai/source/BbRating.md +17 -12
- package/dist/ai/source/BbSelect.md +97 -23
- package/dist/ai/source/BbSelectPopover.md +48 -37
- package/dist/ai/source/BbSlider.md +17 -2
- package/dist/ai/source/BbSwitchGroup.md +104 -92
- package/dist/ai/source/BbTable.md +350 -170
- package/dist/ai/source/BbTabs.md +193 -41
- package/dist/ai/source/BbTabsList.md +212 -54
- package/dist/ai/source/{BbTabsPanes.md → BbTabsPanels.md} +189 -50
- package/dist/ai/source/BbTabsRoot.md +148 -31
- package/dist/ai/source/BbTag.md +125 -44
- package/dist/ai/source/BbTextInput.md +13 -1
- package/dist/ai/source/BbTextarea.md +10 -2
- package/dist/ai/source/BbTimePicker.md +60 -13
- package/dist/ai/source/BbTimePickerInput.md +53 -10
- package/dist/ai/source/BbToast.md +113 -48
- package/dist/ai/source/BbToastPortal.md +117 -49
- package/dist/ai/source/BbTooltip.md +2 -2
- package/dist/ai/source/BbTree.md +6 -4
- package/dist/ai/source/ChipsBox.md +17 -7
- package/dist/ai/source/CommaBox.md +16 -9
- package/dist/ai/source/CommonField.md +7 -4
- package/dist/ai/source/CommonFieldInput.md +2 -0
- package/dist/ai/source/CommonTimeSelector.md +107 -25
- package/dist/ai/source/FlatListBox.md +15 -5
- package/dist/ai/source/GroupedListBox.md +15 -5
- package/dist/ai/source/ListBox.md +11 -0
- package/dist/ai/source/OptionsContainer.md +12 -10
- package/dist/components/BbAccordion/types.d.ts +2 -1
- package/dist/components/BbBadge/BadgeBodyContent.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/BbBaseButton/BbBaseButton.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +107 -98
- package/dist/components/BbButton/BbButton.vue.d.ts +5 -0
- package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbButton/types.d.ts +64 -0
- package/dist/components/BbCalendar/BbCalendar.vue_vue_type_script_setup_true_lang.js +203 -172
- package/dist/components/BbCalendar/CalendarMonthPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbCalendar/CalendarYearPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbCalendar/types.d.ts +18 -8
- package/dist/components/BbCalendar/types.js +3 -2
- package/dist/components/BbCalendar/useCalendarGrid.js +14 -14
- package/dist/components/BbCalendar/useDayGrid.js +1 -1
- package/dist/components/BbCheckbox/types.d.ts +2 -1
- package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +103 -103
- package/dist/components/BbCheckboxGroup/types.d.ts +2 -1
- package/dist/components/BbColorInput/BbColorInput.vue.d.ts +2 -2
- package/dist/components/BbColorInput/types.d.ts +14 -2
- package/dist/components/BbColorPalette/BbColorPalette.vue.d.ts +2 -2
- package/dist/components/BbColorPalette/BbColorPalette.vue_vue_type_script_setup_true_lang.js +166 -165
- package/dist/components/BbColorPalette/types.d.ts +2 -2
- package/dist/components/BbConfirm/BbConfirm.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/components/BbConfirm/BbConfirmPortal.vue.d.ts +1 -0
- package/dist/components/BbDatePicker/BbDatePicker.vue.d.ts +2 -2
- package/dist/components/BbDatePicker/BbDatePicker.vue_vue_type_script_setup_true_lang.js +89 -72
- package/dist/components/BbDatePicker/types.d.ts +13 -5
- package/dist/components/BbDatePicker/types.js +3 -2
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue.d.ts +2 -2
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +348 -340
- package/dist/components/BbDatePickerInput/types.d.ts +10 -3
- package/dist/components/BbDatePickerInput/types.js +3 -2
- package/dist/components/BbDialog/BbDialog.vue.d.ts +1 -1
- package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +131 -122
- package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/BbDropdown/BbDropdown.vue.d.ts +4 -4
- package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +177 -176
- package/dist/components/BbDropdown/BbDropdownGroup.vue.d.ts +4 -0
- package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/BbDropdown/DropdownPipelineResolver.vue_vue_type_script_setup_true_lang.js +102 -102
- package/dist/components/BbDropdown/types.d.ts +3 -1
- package/dist/components/BbDropdownButton/BbDropdownButton.vue.d.ts +4 -4
- package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +34 -23
- package/dist/components/BbDropdownButton/types.d.ts +13 -2
- package/dist/components/BbDropdownButton/types.js +2 -0
- package/dist/components/BbDropzone/BbDropzone.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/BbDropzone/types.d.ts +2 -1
- package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/BbIndicator/BbIndicator.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/components/BbNumberInput/types.d.ts +8 -1
- package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +1 -1
- package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +186 -178
- package/dist/components/BbPagination/BbPagination.vue_vue_type_script_setup_true_lang.js +65 -57
- package/dist/components/BbPopover/BbPopover.vue.d.ts +1 -1
- package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +155 -145
- package/dist/components/BbPopover/types.d.ts +2 -1
- package/dist/components/BbRadio/types.d.ts +2 -1
- package/dist/components/BbRadioGroup/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +68 -68
- package/dist/components/BbRadioGroup/types.d.ts +2 -1
- package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/components/BbRating/types.d.ts +5 -2
- package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +41 -22
- package/dist/components/BbSelect/types.d.ts +42 -11
- package/dist/components/BbSelect/types.js +17 -5
- package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +310 -306
- package/dist/components/BbSlider/BbSlider.vue_vue_type_script_setup_true_lang.js +183 -181
- package/dist/components/BbSlider/types.d.ts +2 -1
- package/dist/components/BbSwitchGroup/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +102 -102
- package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +970 -847
- package/dist/components/BbTable/BbTableDataRow.d.ts +5 -4
- package/dist/components/BbTable/BbTableDataRow.js +18 -8
- package/dist/components/BbTable/BbTableExpandRow.js +1 -1
- package/dist/components/BbTable/BbTableSelectToggle.d.ts +17 -0
- package/dist/components/BbTable/BbTableSelectToggle.js +8 -0
- package/dist/components/BbTable/types.d.ts +56 -14
- package/dist/components/BbTable/types.js +14 -1
- package/dist/components/BbTabs/BbTabs.vue_vue_type_script_setup_true_lang.js +44 -23
- package/dist/components/BbTabs/BbTabsList.vue_vue_type_script_setup_true_lang.js +101 -64
- package/dist/components/BbTabs/BbTabsPanels.vue.d.ts +12 -0
- package/dist/components/BbTabs/BbTabsPanels.vue.js +6 -0
- package/dist/components/BbTabs/BbTabsPanels.vue_vue_type_script_setup_true_lang.js +59 -0
- package/dist/components/BbTabs/BbTabsRoot.vue_vue_type_script_setup_true_lang.js +0 -10
- package/dist/components/BbTabs/types.d.ts +115 -28
- package/dist/components/BbTabs/types.js +24 -1
- package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +168 -151
- package/dist/components/BbTag/types.d.ts +47 -14
- package/dist/components/BbTag/types.js +19 -5
- package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/BbTextInput/types.d.ts +8 -1
- package/dist/components/BbTextarea/types.d.ts +10 -2
- package/dist/components/BbTimePicker/BbTimePicker.vue.d.ts +2 -2
- package/dist/components/BbTimePicker/BbTimePicker.vue_vue_type_script_setup_true_lang.js +154 -129
- package/dist/components/BbTimePicker/types.d.ts +28 -7
- package/dist/components/BbTimePicker/types.js +7 -1
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue.d.ts +2 -2
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +217 -194
- package/dist/components/BbTimePickerInput/types.d.ts +28 -5
- package/dist/components/BbTimePickerInput/types.js +2 -0
- package/dist/components/BbToast/BbToast.vue_vue_type_script_setup_true_lang.js +12 -0
- package/dist/components/BbToast/BbToastMessage.vue.d.ts +3 -3
- package/dist/components/BbToast/BbToastMessage.vue_vue_type_script_setup_true_lang.js +72 -70
- package/dist/components/BbToast/BbToastPortal.vue.d.ts +1 -0
- package/dist/components/BbToast/BbToastRegion.vue.d.ts +2 -2
- package/dist/components/BbToast/types.d.ts +35 -10
- package/dist/components/BbToast/types.js +16 -1
- package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbTree/BbTree.vue_vue_type_script_setup_true_lang.js +35 -33
- package/dist/components/BbTree/BbTreeLevel.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/BbTree/context.d.ts +5 -0
- package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +41 -41
- package/dist/components/ChipsBox/types.d.ts +4 -3
- package/dist/components/CommaBox/CommaBox.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/CommaBox/types.d.ts +4 -3
- package/dist/components/CommonField/CommonField.vue_vue_type_script_setup_true_lang.js +4 -3
- package/dist/components/CommonFieldInput/CommonFieldInput.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/components/CommonPopover/CommonPopover.vue.d.ts +1 -1
- package/dist/components/CommonTimeSelector/CommonTimeSelector.vue.d.ts +19 -3
- package/dist/components/CommonTimeSelector/CommonTimeSelector.vue_vue_type_script_setup_true_lang.js +114 -89
- package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +111 -104
- package/dist/components/FlatListBox/types.d.ts +7 -0
- package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +12 -5
- package/dist/components/GroupedListBox/types.d.ts +7 -0
- package/dist/components/ListBox/ListBox.vue_vue_type_script_setup_true_lang.js +10 -0
- package/dist/components/ListBox/types.d.ts +7 -0
- package/dist/components/OptionsContainer/OptionsContainer.vue_vue_type_script_setup_true_lang.js +5 -3
- package/dist/components/OptionsContainer/types.d.ts +2 -2
- package/dist/composables/useBbTableContext.d.ts +33 -1
- package/dist/composables/useBbTableContext.js +83 -81
- package/dist/composables/useBbTabsContext.d.ts +10 -18
- package/dist/composables/useBbTabsContext.js +123 -133
- package/dist/composables/useCoarsePointer.js +7 -10
- package/dist/composables/useEscapeStackEntry.d.ts +13 -0
- package/dist/composables/useEscapeStackEntry.js +16 -0
- package/dist/composables/useHydrated.d.ts +18 -0
- package/dist/composables/useHydrated.js +13 -7
- package/dist/composables/useMobile.js +8 -11
- package/dist/composables/usePassthrough.d.ts +22 -1
- package/dist/composables/usePassthrough.js +76 -63
- package/dist/composables/useViewportWidth.d.ts +5 -39
- package/dist/composables/useViewportWidth.js +13 -7
- package/dist/deprecation/ai-deprecations.json.d.ts +23 -1
- package/dist/deprecation/ai-deprecations.json.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +29 -29
- package/dist/llms-full.txt +4532 -3271
- package/dist/llms-medium.txt +214 -181
- package/dist/llms.txt +4 -3
- package/dist/nuxt-auto-imports.js +1 -1
- package/dist/nuxt-module.d.ts +7 -0
- package/dist/nuxt.js +26 -17
- package/dist/plugin.js +24 -17
- package/dist/runtime/nuxt-plugin.js +26 -22
- package/dist/styles.css +2 -2
- package/dist/types/ToastVariant.d.ts +1 -1
- package/dist/types/passthrough.d.ts +1 -1
- package/dist/types/ptComponentMap.d.ts +13 -11
- package/dist/utils/versionCheck.js +1 -1
- package/dist/validated/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +32 -16
- package/dist/validated/BbSelect.vue.d.ts +36 -18
- package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +27 -9
- package/dist/validated/BbTag.vue_vue_type_script_setup_true_lang.js +16 -7
- package/dist/validated/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +18 -0
- package/dist/vite-plugin.d.ts +1 -1
- package/llms.txt +4 -3
- package/package.json +17 -10
- package/scripts/lib/component-attr-targets.ts +6 -0
- package/scripts/lib/doc-sections.mjs +181 -0
- package/scripts/lib/eslint-plugin.d.ts +1 -0
- package/scripts/lib/eslint-plugin.mjs +7 -1
- package/scripts/lib/validate-bb-markup.mjs +335 -27
- package/dist/components/BbTabs/BbTabsPanes.vue.d.ts +0 -16
- package/dist/components/BbTabs/BbTabsPanes.vue.js +0 -6
- package/dist/components/BbTabs/BbTabsPanes.vue_vue_type_script_setup_true_lang.js +0 -36
package/bin/bitboss-ui-mcp.mjs
CHANGED
|
@@ -35,6 +35,14 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
|
35
35
|
import { dirname, join, sep } from 'node:path';
|
|
36
36
|
import { fileURLToPath } from 'node:url';
|
|
37
37
|
import { z } from 'zod';
|
|
38
|
+
import {
|
|
39
|
+
frontmatter,
|
|
40
|
+
outline,
|
|
41
|
+
pickSections,
|
|
42
|
+
searchSections,
|
|
43
|
+
splitSections,
|
|
44
|
+
stripFrontmatter,
|
|
45
|
+
} from '../scripts/lib/doc-sections.mjs';
|
|
38
46
|
import {
|
|
39
47
|
loadManifest,
|
|
40
48
|
possessive,
|
|
@@ -126,13 +134,31 @@ const SEARCH_STOPWORDS = new Set([
|
|
|
126
134
|
'element',
|
|
127
135
|
]);
|
|
128
136
|
|
|
129
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* `'data table view'` → `['data', 'table']`. Order is not significant.
|
|
139
|
+
*
|
|
140
|
+
* Names arrive as tags and identifiers too (review D6-6): `bb-select` used to
|
|
141
|
+
* return all 60 components on the token `bb`, and `BbCheckboxIcon` nothing.
|
|
142
|
+
* So a CamelCase word is also split into its words, the joined word loses a
|
|
143
|
+
* leading `bb`, and a bare `bb` is dropped.
|
|
144
|
+
*/
|
|
130
145
|
function tokenize(query) {
|
|
131
146
|
const raw = String(query ?? '')
|
|
132
|
-
.
|
|
133
|
-
.
|
|
134
|
-
.
|
|
135
|
-
|
|
147
|
+
.split(/[^A-Za-z0-9]+/)
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
.flatMap((word) => {
|
|
150
|
+
const lower = word.toLowerCase();
|
|
151
|
+
const parts = word
|
|
152
|
+
.replace(/^Bb(?=[A-Z])/, '')
|
|
153
|
+
.split(/(?<=[a-z0-9])(?=[A-Z])/)
|
|
154
|
+
.map((part) => part.toLowerCase());
|
|
155
|
+
return [
|
|
156
|
+
lower.replace(/^bb(?=.)/, ''),
|
|
157
|
+
...(parts.length > 1 ? parts : []),
|
|
158
|
+
];
|
|
159
|
+
})
|
|
160
|
+
.filter((token) => token && token !== 'bb');
|
|
161
|
+
const meaningful = [...new Set(raw)].filter((t) => !SEARCH_STOPWORDS.has(t));
|
|
136
162
|
// An all-stopword query ('the view') still deserves its literal reading
|
|
137
163
|
// rather than an empty token list that matches everything.
|
|
138
164
|
return meaningful.length > 0 ? meaningful : raw;
|
|
@@ -228,22 +254,41 @@ function loadNotInLibrary() {
|
|
|
228
254
|
assumed,
|
|
229
255
|
reality,
|
|
230
256
|
tokens: aliasTokens(assumed),
|
|
257
|
+
// The names the row is ABOUT: `BbSkeleton` → 'skeleton'.
|
|
258
|
+
names: [...assumed.matchAll(/`(\w+)`/g)].map(([, name]) =>
|
|
259
|
+
name.toLowerCase().replace(/^(bb|use)(?=.)/, '')
|
|
260
|
+
),
|
|
231
261
|
});
|
|
232
262
|
}
|
|
233
263
|
return cachedNotInLibrary;
|
|
234
264
|
}
|
|
235
265
|
|
|
236
|
-
/**
|
|
266
|
+
/**
|
|
267
|
+
* Picker rows the query is about. A row matches when the query names the
|
|
268
|
+
* row's own component (`skeleton loading` → BbSkeleton, `drawer` → BbDrawer),
|
|
269
|
+
* or when every word of a multi-word query is in the row (`data table`),
|
|
270
|
+
* counting a shared 6-letter stem (`scheduler` / `scheduling`). One shared
|
|
271
|
+
* generic word is not enough any more: 'text input' answered with the
|
|
272
|
+
* rich-text-editor verdict as "the authoritative answer", 'tab' with the
|
|
273
|
+
* cross-tab live-feed row (review D6-7).
|
|
274
|
+
*/
|
|
237
275
|
function matchNotInLibrary(tokens) {
|
|
238
|
-
|
|
276
|
+
if (!tokens.length) return [];
|
|
277
|
+
const covers = (row, token) =>
|
|
278
|
+
row.tokens.some(
|
|
279
|
+
(t) =>
|
|
280
|
+
t === token ||
|
|
281
|
+
(t.length >= 6 &&
|
|
282
|
+
token.length >= 6 &&
|
|
283
|
+
t.slice(0, 6) === token.slice(0, 6))
|
|
284
|
+
);
|
|
239
285
|
return loadNotInLibrary()
|
|
240
|
-
.
|
|
241
|
-
row
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
.
|
|
246
|
-
.map(({ row }) => ({ assumed: row.assumed, reality: row.reality }));
|
|
286
|
+
.filter(
|
|
287
|
+
(row) =>
|
|
288
|
+
tokens.some((t) => row.names.includes(t)) ||
|
|
289
|
+
(tokens.length > 1 && tokens.every((t) => covers(row, t)))
|
|
290
|
+
)
|
|
291
|
+
.map((row) => ({ assumed: row.assumed, reality: row.reality }));
|
|
247
292
|
}
|
|
248
293
|
|
|
249
294
|
function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
@@ -260,6 +305,17 @@ function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
|
260
305
|
? manifest.components
|
|
261
306
|
: manifest.components.filter((c) => !c.referenceOnly);
|
|
262
307
|
|
|
308
|
+
// No query: the catalog, one line per component (review D6-19) — the
|
|
309
|
+
// answer to "which components exist?", which no tool gave before.
|
|
310
|
+
if (!needle || needle === '*') {
|
|
311
|
+
return {
|
|
312
|
+
query: query ?? '',
|
|
313
|
+
count: pool.length,
|
|
314
|
+
catalog: pool.map((c) => `${c.name} (${c.group}): ${c.description}`),
|
|
315
|
+
hint: 'The whole consumer catalog. get_component({ name }) for one; search_components({ query }) to rank by a need.',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
263
319
|
const tokens = tokenize(needle);
|
|
264
320
|
const notInLibrary = matchNotInLibrary(tokens);
|
|
265
321
|
|
|
@@ -291,6 +347,16 @@ function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
|
291
347
|
|
|
292
348
|
const shown = scored.slice(0, max);
|
|
293
349
|
|
|
350
|
+
// Internal components an exact search would have found (review D6-15):
|
|
351
|
+
// 'CommonPopover' answered "No manifest match" although it is in the
|
|
352
|
+
// manifest, only filtered out as reference-only.
|
|
353
|
+
const hiddenReferenceOnly = includeReferenceOnly
|
|
354
|
+
? []
|
|
355
|
+
: manifest.components
|
|
356
|
+
.filter((c) => c.referenceOnly)
|
|
357
|
+
.filter((c) => scoreComponent(c, tokens).score >= 80)
|
|
358
|
+
.map((c) => c.name);
|
|
359
|
+
|
|
294
360
|
/**
|
|
295
361
|
* Public type exports whose name matches the query. Scored the same way as
|
|
296
362
|
* a component name (exact, prefix, substring) but never mixed into the
|
|
@@ -316,6 +382,8 @@ function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
|
316
382
|
...(type.deprecated ? { deprecated: true } : {}),
|
|
317
383
|
...(type.replacedBy ? { replacedBy: type.replacedBy } : {}),
|
|
318
384
|
usedBy: type.usedBy ?? [],
|
|
385
|
+
// The short answer only; get_component(name) has the definition.
|
|
386
|
+
...(type.values ? { values: type.values } : {}),
|
|
319
387
|
import: `import type { ${type.replacedBy ?? type.name} } from 'bitboss-ui';`,
|
|
320
388
|
}));
|
|
321
389
|
|
|
@@ -345,6 +413,13 @@ function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
|
345
413
|
// consumer who hit this concluded the export was undocumented and
|
|
346
414
|
// allowlisted the tag.
|
|
347
415
|
...(matchedTypes.length > 0 ? { types: matchedTypes } : {}),
|
|
416
|
+
...(hiddenReferenceOnly.length
|
|
417
|
+
? {
|
|
418
|
+
hiddenReferenceOnly,
|
|
419
|
+
referenceOnlyHint:
|
|
420
|
+
'Internal building blocks, not consumer API: never write them in a template. Use the ranked components; pass includeReferenceOnly: true only to read how one works.',
|
|
421
|
+
}
|
|
422
|
+
: {}),
|
|
348
423
|
guidance:
|
|
349
424
|
notInLibrary.length > 0
|
|
350
425
|
? 'The `notInLibrary` entries are the authoritative answer for this query — prefer them over the ranked components, and do not hand-roll what they redirect you to.'
|
|
@@ -401,9 +476,154 @@ function truncateMarkdown(text, maxChars, readHint) {
|
|
|
401
476
|
};
|
|
402
477
|
}
|
|
403
478
|
|
|
404
|
-
|
|
479
|
+
/**
|
|
480
|
+
* Names an agent brings from v2, from an older beta or from habit, mapped to
|
|
481
|
+
* what replaced them. Parsed from the shipped docs so there is one source of
|
|
482
|
+
* truth: the v2 → v3 guide's § 5.1 component table, and changelog rows that
|
|
483
|
+
* say "BbX is renamed BbY". Without it `get_component('BbChip')` answered
|
|
484
|
+
* "unknown component" although the guide had the exact answer (review D6-5).
|
|
485
|
+
*/
|
|
486
|
+
let cachedRetired = null;
|
|
487
|
+
function loadRetired() {
|
|
488
|
+
if (cachedRetired) return cachedRetired;
|
|
489
|
+
cachedRetired = new Map();
|
|
490
|
+
const guidePath = join(AI_ROOT, 'guides', 'migration', 'v2-to-v3.md');
|
|
491
|
+
if (existsSync(guidePath)) {
|
|
492
|
+
const md = readFileSync(guidePath, 'utf-8');
|
|
493
|
+
const table = md.split(/^### 5\.1 .*$/m)[1]?.split(/^### /m)[0] ?? '';
|
|
494
|
+
for (const line of table.split('\n')) {
|
|
495
|
+
if (!line.startsWith('|')) continue;
|
|
496
|
+
const [was, now, todo] = line
|
|
497
|
+
.replace(/^\|/, '')
|
|
498
|
+
.replace(/\|\s*$/, '')
|
|
499
|
+
.split('|')
|
|
500
|
+
.map((cell) => cell.trim());
|
|
501
|
+
if (!was || !now || /^-+$/.test(was) || was === 'v2') continue;
|
|
502
|
+
for (const [, retired] of was.matchAll(/`(\w+)`/g)) {
|
|
503
|
+
cachedRetired.set(retired, {
|
|
504
|
+
now: now.replace(/\*\*/g, ''),
|
|
505
|
+
todo: (todo ?? '').replace(/\*\*/g, '').split(/(?<=\.)\s/)[0],
|
|
506
|
+
guide: 'migration/v2-to-v3.md',
|
|
507
|
+
section: '5.1 Components renamed, split, or removed',
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (existsSync(CHANGELOG_PATH)) {
|
|
513
|
+
const doc = JSON.parse(readFileSync(CHANGELOG_PATH, 'utf-8'));
|
|
514
|
+
for (const row of doc.breaking ?? []) {
|
|
515
|
+
const match = String(row.description ?? '').match(
|
|
516
|
+
/\b(Bb[A-Z]\w+) is renamed (Bb[A-Z]\w+)/
|
|
517
|
+
);
|
|
518
|
+
if (match && !cachedRetired.has(match[1])) {
|
|
519
|
+
cachedRetired.set(match[1], {
|
|
520
|
+
now: `\`${match[2]}\``,
|
|
521
|
+
todo: row.description.split(/(?<=\.)\s|—/)[0].trim(),
|
|
522
|
+
guide: row.guide ?? null,
|
|
523
|
+
firstReleasedIn: row.firstReleasedIn ?? null,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return cachedRetired;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** `bb-date-picker-input` → `BbDatePickerInput`; `button` → `Button`. */
|
|
532
|
+
const pascal = (value) =>
|
|
533
|
+
String(value ?? '')
|
|
534
|
+
.split(/[^A-Za-z0-9]+/)
|
|
535
|
+
.filter(Boolean)
|
|
536
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
537
|
+
.join('');
|
|
538
|
+
|
|
539
|
+
/** Levenshtein distance, small inputs only. */
|
|
540
|
+
function editDistance(a, b) {
|
|
541
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
542
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
543
|
+
let previous = row[0];
|
|
544
|
+
row[0] = i;
|
|
545
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
546
|
+
const current = row[j];
|
|
547
|
+
row[j] = Math.min(
|
|
548
|
+
row[j] + 1,
|
|
549
|
+
row[j - 1] + 1,
|
|
550
|
+
previous + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
551
|
+
);
|
|
552
|
+
previous = current;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return row[b.length];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Find the component an agent meant. Exact first; then the spellings agents
|
|
560
|
+
* really send — `bb-button`, `bbbutton`, `Button` (review D6-2) — before
|
|
561
|
+
* giving up with the nearest names.
|
|
562
|
+
*/
|
|
563
|
+
function resolveComponent(manifest, name) {
|
|
564
|
+
const raw = String(name ?? '').trim();
|
|
565
|
+
const exact = manifest.components.find((c) => c.name === raw);
|
|
566
|
+
if (exact) return { entry: exact };
|
|
567
|
+
const byLower = new Map(
|
|
568
|
+
manifest.components.map((c) => [c.name.toLowerCase(), c])
|
|
569
|
+
);
|
|
570
|
+
const candidates = [raw, pascal(raw)].flatMap((value) => [
|
|
571
|
+
value.toLowerCase(),
|
|
572
|
+
`bb${value.toLowerCase().replace(/^bb/, '')}`,
|
|
573
|
+
]);
|
|
574
|
+
for (const candidate of candidates) {
|
|
575
|
+
const hit = byLower.get(candidate);
|
|
576
|
+
if (hit) return { entry: hit, resolvedFrom: raw };
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function nearestComponents(manifest, name, count = 3) {
|
|
582
|
+
const needle = pascal(name).toLowerCase().replace(/^bb/, '');
|
|
583
|
+
return manifest.components
|
|
584
|
+
.filter((c) => !c.referenceOnly)
|
|
585
|
+
.map((c) => ({
|
|
586
|
+
name: c.name,
|
|
587
|
+
distance: editDistance(needle, c.name.toLowerCase().replace(/^bb/, '')),
|
|
588
|
+
}))
|
|
589
|
+
.sort((a, b) => a.distance - b.distance)
|
|
590
|
+
.slice(0, count)
|
|
591
|
+
.filter(({ distance }) => distance <= Math.max(3, needle.length / 2))
|
|
592
|
+
.map(({ name: found }) => found);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** Guide sections of a contract: everything under `## Usage & Guidelines`. */
|
|
596
|
+
function contractGuideSections(markdown) {
|
|
597
|
+
const all = splitSections(markdown);
|
|
598
|
+
const start = all.findIndex(
|
|
599
|
+
(s) => s.level === 2 && /^Usage & Guidelines/i.test(s.title)
|
|
600
|
+
);
|
|
601
|
+
if (start === -1) return [];
|
|
602
|
+
const end = all.findIndex((s, i) => i > start && s.level === 2);
|
|
603
|
+
return all.slice(start + 1, end === -1 ? undefined : end);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** Sections every agent needs, served whole in the default answer. */
|
|
607
|
+
const ALWAYS_SECTIONS = [/^gotchas/i, /^accessibility/i];
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* `get_component({ name, sections?, include?, maxChars? })`.
|
|
611
|
+
*
|
|
612
|
+
* COMPACT BY DEFAULT (release review 2026-09-23, D6-4). The old answer was
|
|
613
|
+
* the manifest entry plus the whole contract cut at 40 000 chars: 76–101 KB,
|
|
614
|
+
* and the cut dropped `Gotchas` on six components while the tail it kept
|
|
615
|
+
* repeated the structured entry. Blind Haiku answerers could not read it and
|
|
616
|
+
* guessed. Now the default is the structured entry (props never cut — a
|
|
617
|
+
* partial prop list is what makes agents invent API) minus the parts that
|
|
618
|
+
* repeat it (`pt:*` prop rows: the `pt` section lists every part and state),
|
|
619
|
+
* the guide's outline, and its Gotchas + Accessibility sections. Guide
|
|
620
|
+
* sections, the component tree, the CSS variables with their descriptions
|
|
621
|
+
* and the whole contract come on request.
|
|
622
|
+
*/
|
|
623
|
+
function getComponent({ name, sections, include, maxChars } = {}) {
|
|
405
624
|
const manifest = loadComponentsManifest();
|
|
406
|
-
const
|
|
625
|
+
const resolved = resolveComponent(manifest, name);
|
|
626
|
+
const entry = resolved?.entry;
|
|
407
627
|
if (!entry) {
|
|
408
628
|
/*
|
|
409
629
|
* A public TYPE asked for as a component. `BbTableColumn`,
|
|
@@ -425,18 +645,67 @@ function getComponent({ name, maxChars } = {}) {
|
|
|
425
645
|
...(type.replacedBy ? { replacedBy: type.replacedBy } : {}),
|
|
426
646
|
component: type.component,
|
|
427
647
|
usedBy: type.usedBy ?? [],
|
|
648
|
+
// What it is (Q50.2): the declaration, the accepted values when
|
|
649
|
+
// finite (open when `extensibleVia` names a registry), and the
|
|
650
|
+
// resolved fields when the declaration leans on a private type.
|
|
651
|
+
...(type.definition ? { definition: type.definition } : {}),
|
|
652
|
+
...(type.values ? { values: type.values } : {}),
|
|
653
|
+
...(type.extensibleVia ? { extensibleVia: type.extensibleVia } : {}),
|
|
654
|
+
...(type.fields ? { fields: type.fields } : {}),
|
|
428
655
|
import: `import type { ${type.replacedBy ?? name} } from 'bitboss-ui';`,
|
|
429
656
|
hint: use
|
|
430
|
-
? `A type, not a component: it types ${possessive(use.component)} \`${use.name}\` ${use.surface}.
|
|
657
|
+
? `A type, not a component: it types ${possessive(use.component)} \`${use.name}\` ${use.surface}. \`definition\` above is the type itself; call get_component('${use.component}') to see it in use.`
|
|
431
658
|
: 'A type, not a component — import it for the value you pass, never write it as a tag.',
|
|
432
659
|
};
|
|
433
660
|
}
|
|
661
|
+
// A directive (`v-bb-tooltip`, `vBbTooltip`) — review D3-13.
|
|
662
|
+
const directiveName = String(name ?? '')
|
|
663
|
+
.trim()
|
|
664
|
+
.replace(/^vBb([A-Z]\w*)$/, (_, word) => `v-bb-${word.toLowerCase()}`)
|
|
665
|
+
.toLowerCase();
|
|
666
|
+
const directive = (manifest.directives ?? []).find(
|
|
667
|
+
(d) => d.name === directiveName
|
|
668
|
+
);
|
|
669
|
+
if (directive) {
|
|
670
|
+
const value = (manifest.types ?? []).find(
|
|
671
|
+
(t) => t.name === directive.value
|
|
672
|
+
);
|
|
673
|
+
return {
|
|
674
|
+
found: true,
|
|
675
|
+
kind: 'directive',
|
|
676
|
+
...directive,
|
|
677
|
+
...(value?.definition ? { valueDefinition: value.definition } : {}),
|
|
678
|
+
...(value?.fields ? { valueFields: value.fields } : {}),
|
|
679
|
+
hint: `A directive, the floating twin of ${directive.mirrors}: read get_component({ name: '${directive.mirrors}', sections: ['directive'] }) for its guide section.`,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
const retired = loadRetired().get(String(name ?? '').trim());
|
|
683
|
+
if (retired) {
|
|
684
|
+
return {
|
|
685
|
+
found: false,
|
|
686
|
+
name,
|
|
687
|
+
retired: true,
|
|
688
|
+
now: retired.now,
|
|
689
|
+
todo: retired.todo,
|
|
690
|
+
guide: retired.guide,
|
|
691
|
+
...(retired.section ? { section: retired.section } : {}),
|
|
692
|
+
hint: `\`${name}\` is not in this version. \`now\` says what replaced it; read get_guide({ name: '${retired.guide}'${retired.section ? `, sections: ['${retired.section}']` : ''} }) for the full row.`,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
const picker = matchNotInLibrary(tokenize(name));
|
|
696
|
+
const didYouMean = nearestComponents(manifest, name);
|
|
434
697
|
return {
|
|
435
698
|
found: false,
|
|
436
699
|
name,
|
|
437
|
-
|
|
700
|
+
...(didYouMean.length ? { didYouMean } : {}),
|
|
701
|
+
...(picker.length ? { notInLibrary: picker.slice(0, 3) } : {}),
|
|
702
|
+
hint: picker.length
|
|
703
|
+
? "Not a component here — `notInLibrary` is the picker's verdict for this name."
|
|
704
|
+
: 'Unknown component name. Try one of `didYouMean`, or search_components with a plain noun.',
|
|
438
705
|
};
|
|
439
706
|
}
|
|
707
|
+
|
|
708
|
+
const wanted = new Set(include ?? []);
|
|
440
709
|
const contractPath = entry.contract ?? null;
|
|
441
710
|
const contractAbsPath = contractPath
|
|
442
711
|
? join(PACKAGE_ROOT, 'dist', contractPath)
|
|
@@ -444,12 +713,9 @@ function getComponent({ name, maxChars } = {}) {
|
|
|
444
713
|
const raw =
|
|
445
714
|
contractAbsPath && existsSync(contractAbsPath)
|
|
446
715
|
? readFileSync(contractAbsPath, 'utf-8')
|
|
447
|
-
:
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
maxChars,
|
|
451
|
-
`The structured \`component\` field above is COMPLETE (all props/events/slots) — the cut text is guidance prose only.`
|
|
452
|
-
);
|
|
716
|
+
: '';
|
|
717
|
+
const guide = contractGuideSections(raw);
|
|
718
|
+
|
|
453
719
|
/*
|
|
454
720
|
* The types this component's own API names, so "what do I import to build
|
|
455
721
|
* the value for `columns`?" is answered where the question is asked. Without
|
|
@@ -465,19 +731,98 @@ function getComponent({ name, maxChars } = {}) {
|
|
|
465
731
|
.filter((type) => !type.deprecated)
|
|
466
732
|
.map((type) => ({
|
|
467
733
|
name: type.name,
|
|
468
|
-
usage: type.usage,
|
|
469
734
|
usedBy: (type.usedBy ?? []).filter((use) => use.component === entry.name),
|
|
470
735
|
import: `import type { ${type.name} } from 'bitboss-ui';`,
|
|
736
|
+
...(type.values ? { values: type.values } : {}),
|
|
737
|
+
...(type.extensibleVia ? { extensibleVia: type.extensibleVia } : {}),
|
|
738
|
+
...(type.fields ? { fields: type.fields } : {}),
|
|
739
|
+
...(!type.values && !type.fields && type.definition
|
|
740
|
+
? { definition: type.definition }
|
|
741
|
+
: {}),
|
|
471
742
|
}));
|
|
472
743
|
|
|
744
|
+
const ptRows = entry.props.filter(
|
|
745
|
+
(p) => p.name === 'pt' || p.name.startsWith('pt:')
|
|
746
|
+
);
|
|
747
|
+
const component = {
|
|
748
|
+
...entry,
|
|
749
|
+
props: (wanted.has('ptProps')
|
|
750
|
+
? entry.props
|
|
751
|
+
: entry.props.filter((p) => !ptRows.includes(p))
|
|
752
|
+
).map(({ required, ...prop }) => (required ? { ...prop, required } : prop)),
|
|
753
|
+
...(entry.cssProps && !wanted.has('cssProps')
|
|
754
|
+
? { cssProps: entry.cssProps.map((c) => c.name ?? c) }
|
|
755
|
+
: {}),
|
|
756
|
+
};
|
|
757
|
+
if (!wanted.has('tree')) delete component.tree;
|
|
758
|
+
// `removed` and `renamed` repeat each other with migration metadata; one
|
|
759
|
+
// line per old name answers "what happened to X" (include 'history' for
|
|
760
|
+
// the records the fixers use).
|
|
761
|
+
if (
|
|
762
|
+
!wanted.has('history') &&
|
|
763
|
+
(entry.removed?.length || entry.renamed?.length)
|
|
764
|
+
) {
|
|
765
|
+
const renamedFrom = new Set((entry.renamed ?? []).map((r) => r.from));
|
|
766
|
+
component.history = [
|
|
767
|
+
...(entry.renamed ?? []).map(
|
|
768
|
+
(r) =>
|
|
769
|
+
`${r.from} → ${r.to}${r.fix?.kind && r.fix.kind !== 'rename' ? ` (${r.fix.kind})` : ''}`
|
|
770
|
+
),
|
|
771
|
+
...(entry.removed ?? [])
|
|
772
|
+
.filter((r) => !renamedFrom.has(r.name))
|
|
773
|
+
.map((r) => `${r.name} removed${r.new ? ` → ${r.new}` : ''}`),
|
|
774
|
+
];
|
|
775
|
+
delete component.removed;
|
|
776
|
+
delete component.renamed;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const { picked, missing } = pickSections(guide, sections);
|
|
780
|
+
const always = guide.filter(
|
|
781
|
+
(s) => ALWAYS_SECTIONS.some((re) => re.test(s.title)) && !picked.includes(s)
|
|
782
|
+
);
|
|
783
|
+
const contract = wanted.has('contract')
|
|
784
|
+
? truncateMarkdown(
|
|
785
|
+
raw,
|
|
786
|
+
maxChars,
|
|
787
|
+
'The structured `component` field above is complete; the cut text is guide prose.'
|
|
788
|
+
)
|
|
789
|
+
: null;
|
|
790
|
+
|
|
473
791
|
return {
|
|
474
792
|
found: true,
|
|
475
|
-
|
|
476
|
-
component
|
|
793
|
+
...(resolved.resolvedFrom ? { resolvedFrom: resolved.resolvedFrom } : {}),
|
|
794
|
+
component,
|
|
477
795
|
...(types.length > 0 ? { types } : {}),
|
|
796
|
+
...(ptRows.length && !wanted.has('ptProps')
|
|
797
|
+
? {
|
|
798
|
+
ptProps: `${ptRows.length} pt:* props not listed: every \`pt:<part>\` and \`pt:<part>:<state>\` in \`component.pt\` is a prop taking a class string (or object / array), and \`pt\` takes the same keys as one object.`,
|
|
799
|
+
}
|
|
800
|
+
: {}),
|
|
801
|
+
guide: {
|
|
802
|
+
sections: guide
|
|
803
|
+
.filter((s) => s.level === 3)
|
|
804
|
+
.map((s) => ({ title: s.title, chars: s.text.length })),
|
|
805
|
+
...(picked.length
|
|
806
|
+
? { requested: picked.map((s) => ({ title: s.title, text: s.text })) }
|
|
807
|
+
: {}),
|
|
808
|
+
...(missing.length ? { notFound: missing } : {}),
|
|
809
|
+
...(always.length
|
|
810
|
+
? { always: always.map((s) => ({ title: s.title, text: s.text })) }
|
|
811
|
+
: {}),
|
|
812
|
+
},
|
|
478
813
|
contractPath,
|
|
479
|
-
|
|
480
|
-
|
|
814
|
+
...(contract
|
|
815
|
+
? {
|
|
816
|
+
contract: contract.content,
|
|
817
|
+
...(contract.truncated
|
|
818
|
+
? {
|
|
819
|
+
contractTruncated: true,
|
|
820
|
+
contractHint: contract.hint,
|
|
821
|
+
}
|
|
822
|
+
: {}),
|
|
823
|
+
}
|
|
824
|
+
: {}),
|
|
825
|
+
hint: `Structured API above is complete. Read guide sections with get_component({ name: '${entry.name}', sections: ['<title from guide.sections>'] }); add include: ['tree' | 'cssProps' | 'ptProps' | 'history' | 'contract'] for the component tree, CSS variables with descriptions, every pt:* row, the full v2/beta rename records, or the whole contract. search_docs finds a topic across all docs.`,
|
|
481
826
|
};
|
|
482
827
|
}
|
|
483
828
|
|
|
@@ -546,6 +891,11 @@ const tokenKind = (inKnobBlock, inDerivedBlock) =>
|
|
|
546
891
|
* tolerant of the missing final semicolon that minified CSS emits) is the
|
|
547
892
|
* cheapest way to keep the scope — and this file ships in the package, so it
|
|
548
893
|
* may not reach for postcss the way the dev-only `check:tokens` script does.
|
|
894
|
+
*
|
|
895
|
+
* `when` carries the enclosing `@supports` condition(s), if any: a value there
|
|
896
|
+
* is not the theme value but one browser path — `--bb-primary-fg` ships an
|
|
897
|
+
* auto-contrast formula with literal fallbacks, and folding the fallbacks into
|
|
898
|
+
* the theme values tells an agent to pin a literal that defeats the formula.
|
|
549
899
|
*/
|
|
550
900
|
function* eachTokenDeclaration(css) {
|
|
551
901
|
const stack = [];
|
|
@@ -566,11 +916,23 @@ function* eachTokenDeclaration(css) {
|
|
|
566
916
|
quote = char;
|
|
567
917
|
buffer += char;
|
|
568
918
|
} else if (char === '{') {
|
|
569
|
-
|
|
919
|
+
// A comment before a rule would otherwise prefix its selector or
|
|
920
|
+
// hide the `@supports` it opens.
|
|
921
|
+
stack.push(buffer.replace(/\/\*[\s\S]*?\*\//g, '').trim());
|
|
570
922
|
buffer = '';
|
|
571
923
|
} else if (char === '}' || char === ';') {
|
|
572
924
|
const decl = declaration();
|
|
573
|
-
if (decl)
|
|
925
|
+
if (decl) {
|
|
926
|
+
const supports = stack
|
|
927
|
+
.slice(0, -1)
|
|
928
|
+
.filter((scope) => scope.startsWith('@supports'))
|
|
929
|
+
.map((scope) => scope.replace(/^@supports\s*/, ''));
|
|
930
|
+
yield {
|
|
931
|
+
...decl,
|
|
932
|
+
selector: stack[stack.length - 1] ?? '',
|
|
933
|
+
...(supports.length ? { when: supports.join(' and ') } : {}),
|
|
934
|
+
};
|
|
935
|
+
}
|
|
574
936
|
if (char === '}') stack.pop();
|
|
575
937
|
} else {
|
|
576
938
|
buffer += char;
|
|
@@ -611,16 +973,27 @@ function listTokens({ filter } = {}) {
|
|
|
611
973
|
}
|
|
612
974
|
const css = readFileSync(STYLES_CSS, 'utf-8');
|
|
613
975
|
const byName = new Map();
|
|
614
|
-
for (const { name, value, selector } of eachTokenDeclaration(css)) {
|
|
976
|
+
for (const { name, value, selector, when } of eachTokenDeclaration(css)) {
|
|
615
977
|
const entry = byName.get(name) ?? {
|
|
616
978
|
theme: [],
|
|
617
979
|
local: [],
|
|
980
|
+
conditional: [],
|
|
618
981
|
knobBlock: false,
|
|
619
982
|
derivedBlock: false,
|
|
620
983
|
};
|
|
621
984
|
if (isThemeSelector(selector)) {
|
|
622
985
|
if (isDerivedBlock(selector)) entry.derivedBlock = true;
|
|
623
986
|
else entry.knobBlock = true;
|
|
987
|
+
}
|
|
988
|
+
if (when) {
|
|
989
|
+
// One browser path, not the theme value nor a dark override.
|
|
990
|
+
if (
|
|
991
|
+
!entry.conditional.some(
|
|
992
|
+
(c) => c.when === when && c.selector === selector
|
|
993
|
+
)
|
|
994
|
+
)
|
|
995
|
+
entry.conditional.push({ when, selector, value });
|
|
996
|
+
} else if (isThemeSelector(selector)) {
|
|
624
997
|
// Light first, dark after: report both rather than let one win.
|
|
625
998
|
if (!entry.theme.includes(value)) entry.theme.push(value);
|
|
626
999
|
} else if (!entry.local.some((r) => r.selector === selector)) {
|
|
@@ -637,12 +1010,13 @@ function listTokens({ filter } = {}) {
|
|
|
637
1010
|
const tokens = [...byName.entries()]
|
|
638
1011
|
.filter(([name]) => !needle || name.toLowerCase().includes(needle))
|
|
639
1012
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
640
|
-
.map(([name, { theme, local, knobBlock, derivedBlock }]) => ({
|
|
1013
|
+
.map(([name, { theme, local, conditional, knobBlock, derivedBlock }]) => ({
|
|
641
1014
|
name,
|
|
642
1015
|
kind: tokenKind(knobBlock, derivedBlock),
|
|
643
1016
|
value: theme[0],
|
|
644
1017
|
...(theme.length > 1 ? { themeOverrides: theme.slice(1) } : {}),
|
|
645
1018
|
...(local.length > 0 ? { localRebinds: local } : {}),
|
|
1019
|
+
...(conditional.length > 0 ? { conditional } : {}),
|
|
646
1020
|
}));
|
|
647
1021
|
return {
|
|
648
1022
|
count: tokens.length,
|
|
@@ -651,7 +1025,7 @@ function listTokens({ filter } = {}) {
|
|
|
651
1025
|
hint:
|
|
652
1026
|
needle && tokens.length === 0
|
|
653
1027
|
? `No token name contains '${needle}'. Call list_tokens with no filter for all ${byName.size} — the names are semantic (primary/danger/panel/text/border/radius/…), not literal colours.`
|
|
654
|
-
: 'Theme tokens are set by the consumer app (e.g. on :root / .dark); `themeOverrides` is the dark-theme value. `localRebinds` is a component rebinding the token inside its own subtree — not something to copy. `kind` says what you may set: "knob" is yours; "derived" is a formula recomputed at every theme boundary, so setting it stops it tracking the knob it came from — set that knob instead; "knob-tracking" is a knob that follows another until you pin it, and the pin must be repeated at every boundary you use, .dark included. Component-level unprefixed tokens are documented per component; see the design-tokens guide via get_guide.',
|
|
1028
|
+
: 'Theme tokens are set by the consumer app (e.g. on :root / .dark); `themeOverrides` is the dark-theme value. `localRebinds` is a component rebinding the token inside its own subtree — not something to copy. `conditional` is a value that applies only under an @supports condition (`when`) — e.g. an auto-contrast formula with literal fallbacks for older browsers; pinning a fallback literal defeats the formula. `kind` says what you may set: "knob" is yours; "derived" is a formula recomputed at every theme boundary, so setting it stops it tracking the knob it came from — set that knob instead; "knob-tracking" is a knob that follows another until you pin it, and the pin must be repeated at every boundary you use, .dark included. Component-level unprefixed tokens are documented per component; see the design-tokens guide via get_guide.',
|
|
655
1029
|
};
|
|
656
1030
|
}
|
|
657
1031
|
|
|
@@ -670,41 +1044,276 @@ function collectGuidePaths(dir, prefix = '') {
|
|
|
670
1044
|
|
|
671
1045
|
/** `list_guides()` — the shipped prose guides an agent can read. */
|
|
672
1046
|
function listGuides() {
|
|
673
|
-
const
|
|
1047
|
+
const guidesDir = join(AI_ROOT, 'guides');
|
|
1048
|
+
const all = collectGuidePaths(guidesDir).sort();
|
|
1049
|
+
/*
|
|
1050
|
+
* Title + summary per guide (review D6-13): 57 bare paths could not tell an
|
|
1051
|
+
* agent which one answers its question. The 40 per-component migration
|
|
1052
|
+
* pages collapse to one row — get_guide('migration/components/<kebab>').
|
|
1053
|
+
*/
|
|
1054
|
+
const perComponent = all.filter((p) => p.startsWith('migration/components/'));
|
|
1055
|
+
const guides = all
|
|
1056
|
+
.filter((p) => !perComponent.includes(p))
|
|
1057
|
+
.map((path) => {
|
|
1058
|
+
const markdown = readFileSync(join(guidesDir, path), 'utf-8');
|
|
1059
|
+
const meta = frontmatter(markdown);
|
|
1060
|
+
const title =
|
|
1061
|
+
meta.title ??
|
|
1062
|
+
stripFrontmatter(markdown)
|
|
1063
|
+
.match(/^# (.+)$/m)?.[1]
|
|
1064
|
+
?.trim();
|
|
1065
|
+
return {
|
|
1066
|
+
name: path,
|
|
1067
|
+
...(title ? { title } : {}),
|
|
1068
|
+
...(meta.summary ? { summary: meta.summary } : {}),
|
|
1069
|
+
};
|
|
1070
|
+
});
|
|
674
1071
|
return {
|
|
675
|
-
count:
|
|
1072
|
+
count: all.length,
|
|
676
1073
|
guides,
|
|
677
|
-
|
|
1074
|
+
...(perComponent.length
|
|
1075
|
+
? {
|
|
1076
|
+
perComponentMigration: `${perComponent.length} pages: get_guide({ name: 'migration/components/<kebab-name>' }), e.g. 'migration/components/bb-select'.`,
|
|
1077
|
+
}
|
|
1078
|
+
: {}),
|
|
1079
|
+
hint: 'Read one with get_guide({ name }); a long guide answers with its outline — then pass sections. search_docs finds the section for a question across every guide. Start at `ai-router.md` — do not read them all.',
|
|
678
1080
|
};
|
|
679
1081
|
}
|
|
680
1082
|
|
|
681
|
-
/**
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1083
|
+
/** Guides this size or larger answer with an outline unless asked for more. */
|
|
1084
|
+
// The router (13 KB, the documented entry point) still comes whole.
|
|
1085
|
+
const GUIDE_OUTLINE_OVER = 16000;
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* The shipped guide a pointer means. Pointers come from everywhere — the
|
|
1089
|
+
* changelog (`components/bb-table.md (CSS table)`), contracts
|
|
1090
|
+
* (`guides/passthrough.md`), the repo layout (`ai/guides/…`) — and every form
|
|
1091
|
+
* used to answer "Guide not found" (review D6-11).
|
|
1092
|
+
*/
|
|
1093
|
+
function resolveGuidePath(name) {
|
|
1094
|
+
const guidesDir = join(AI_ROOT, 'guides');
|
|
1095
|
+
const all = collectGuidePaths(guidesDir);
|
|
1096
|
+
let rel = String(name ?? '')
|
|
1097
|
+
.trim()
|
|
1098
|
+
.replace(/\s*\(.*\)\s*$/, '')
|
|
1099
|
+
.replace(/^\/+/, '')
|
|
1100
|
+
.replace(/^(dist\/)?(ai\/)?(guides\/)?/, '');
|
|
1101
|
+
if (!rel || rel.includes('..')) return null;
|
|
1102
|
+
if (!rel.endsWith('.md')) rel = `${rel}.md`;
|
|
1103
|
+
const tries = [rel, `migration/${rel}`, `migration/components/${rel}`];
|
|
1104
|
+
for (const attempt of tries) if (all.includes(attempt)) return attempt;
|
|
1105
|
+
const base = rel.split('/').pop();
|
|
1106
|
+
const byBase = all.filter((p) => p.split('/').pop() === base);
|
|
1107
|
+
return byBase.length === 1 ? byBase[0] : null;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/** `get_guide({ name, sections?, maxChars? })`. */
|
|
1111
|
+
function getGuide({ name, sections, maxChars } = {}) {
|
|
1112
|
+
const resolved = resolveGuidePath(name);
|
|
1113
|
+
if (!resolved) {
|
|
1114
|
+
const all = collectGuidePaths(join(AI_ROOT, 'guides'));
|
|
1115
|
+
const needle = String(name ?? '').toLowerCase();
|
|
1116
|
+
const near = all
|
|
1117
|
+
.filter((p) =>
|
|
1118
|
+
needle
|
|
1119
|
+
.split(/[^a-z0-9]+/)
|
|
1120
|
+
.some((w) => w.length > 3 && p.toLowerCase().includes(w))
|
|
1121
|
+
)
|
|
1122
|
+
.slice(0, 5);
|
|
686
1123
|
return {
|
|
687
1124
|
found: false,
|
|
688
|
-
error: `
|
|
689
|
-
|
|
1125
|
+
error: `Guide not found: ${name}`,
|
|
1126
|
+
...(near.length ? { didYouMean: near } : {}),
|
|
1127
|
+
hint: 'Call list_guides for the names, or search_docs to find the section by topic.',
|
|
690
1128
|
};
|
|
691
1129
|
}
|
|
692
|
-
const
|
|
693
|
-
const
|
|
694
|
-
const
|
|
695
|
-
|
|
1130
|
+
const markdown = readFileSync(join(AI_ROOT, 'guides', resolved), 'utf-8');
|
|
1131
|
+
const meta = frontmatter(markdown);
|
|
1132
|
+
const all = splitSections(markdown);
|
|
1133
|
+
const base = {
|
|
1134
|
+
found: true,
|
|
1135
|
+
name: resolved,
|
|
1136
|
+
...(resolved !== name ? { resolvedFrom: name } : {}),
|
|
1137
|
+
...(meta.title ? { title: meta.title } : {}),
|
|
1138
|
+
};
|
|
1139
|
+
if (sections?.length) {
|
|
1140
|
+
const { picked, missing } = pickSections(all, sections);
|
|
696
1141
|
return {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
1142
|
+
...base,
|
|
1143
|
+
sections: picked.map((s) => ({ title: s.title, text: s.text })),
|
|
1144
|
+
...(missing.length ? { notFound: missing, outline: outline(all) } : {}),
|
|
700
1145
|
};
|
|
701
1146
|
}
|
|
702
|
-
const
|
|
703
|
-
|
|
1147
|
+
const body = stripFrontmatter(markdown);
|
|
1148
|
+
if (maxChars === undefined && body.length >= GUIDE_OUTLINE_OVER) {
|
|
1149
|
+
const intro = body.split(/^## /m)[0].trim();
|
|
1150
|
+
return {
|
|
1151
|
+
...base,
|
|
1152
|
+
...(meta.summary ? { summary: meta.summary } : {}),
|
|
1153
|
+
chars: body.length,
|
|
1154
|
+
intro: intro.slice(0, 2000),
|
|
1155
|
+
outline: outline(all),
|
|
1156
|
+
hint: `This guide is ${body.length} chars. Read what you need: get_guide({ name: '${resolved}', sections: ['<title from outline>'] }) — a ## section includes its ### children. maxChars: 0 returns the whole guide.`,
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
const { content, ...cut } = truncateMarkdown(
|
|
1160
|
+
markdown,
|
|
704
1161
|
maxChars,
|
|
705
|
-
|
|
1162
|
+
'Guides are ordered narrative — the cut tail is later sections.'
|
|
1163
|
+
);
|
|
1164
|
+
return { ...base, content, ...cut };
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* `search_docs({ query, limit? })` — the section that answers a question,
|
|
1169
|
+
* across guides, composables, component guides, component props and recipes.
|
|
1170
|
+
*
|
|
1171
|
+
* WHY (review 2026-09-23). The facts Haiku missed were all shipped — the
|
|
1172
|
+
* `.dark` class, `cssLayers`, `passthroughDeduper`, `ssrViewportWidth`, the
|
|
1173
|
+
* `ai-init` and `changelog` commands, `prefill: false` — but each sat in one
|
|
1174
|
+
* section of a long document, and no tool could say which. Each result is a
|
|
1175
|
+
* snippet plus the exact call that reads the whole section.
|
|
1176
|
+
*/
|
|
1177
|
+
let cachedDocIndex = null;
|
|
1178
|
+
function docIndex() {
|
|
1179
|
+
if (cachedDocIndex) return cachedDocIndex;
|
|
1180
|
+
const entries = [];
|
|
1181
|
+
const addSections = (source, markdown, read) => {
|
|
1182
|
+
for (const section of splitSections(markdown)) {
|
|
1183
|
+
if (section.own.length < 40) continue;
|
|
1184
|
+
entries.push({
|
|
1185
|
+
source,
|
|
1186
|
+
section: section.title,
|
|
1187
|
+
text: section.own,
|
|
1188
|
+
read: read(section.title),
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
const guidesDir = join(AI_ROOT, 'guides');
|
|
1193
|
+
for (const path of collectGuidePaths(guidesDir)) {
|
|
1194
|
+
addSections(
|
|
1195
|
+
`guide ${path}`,
|
|
1196
|
+
readFileSync(join(guidesDir, path), 'utf-8'),
|
|
1197
|
+
(title) => ({
|
|
1198
|
+
tool: 'get_guide',
|
|
1199
|
+
args: { name: path, sections: [title] },
|
|
1200
|
+
})
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
const composablesDir = join(AI_ROOT, 'composables');
|
|
1204
|
+
if (existsSync(composablesDir)) {
|
|
1205
|
+
for (const file of readdirSync(composablesDir).filter((f) =>
|
|
1206
|
+
f.endsWith('.md')
|
|
1207
|
+
)) {
|
|
1208
|
+
const markdown = readFileSync(join(composablesDir, file), 'utf-8');
|
|
1209
|
+
const name = file.slice(0, -3);
|
|
1210
|
+
entries.push({
|
|
1211
|
+
source: `composable ${name}`,
|
|
1212
|
+
section: frontmatter(markdown).summary ?? name,
|
|
1213
|
+
text: stripFrontmatter(markdown).slice(0, 4000),
|
|
1214
|
+
read: { tool: 'get_composable', args: { name } },
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
const manifest = loadComponentsManifest();
|
|
1219
|
+
for (const component of manifest.components.filter((c) => !c.referenceOnly)) {
|
|
1220
|
+
const path = join(PACKAGE_ROOT, 'dist', component.contract ?? '');
|
|
1221
|
+
if (component.contract && existsSync(path)) {
|
|
1222
|
+
for (const section of contractGuideSections(
|
|
1223
|
+
readFileSync(path, 'utf-8')
|
|
1224
|
+
)) {
|
|
1225
|
+
if (section.own.length < 40) continue;
|
|
1226
|
+
entries.push({
|
|
1227
|
+
source: `component ${component.name}`,
|
|
1228
|
+
section: section.title,
|
|
1229
|
+
text: section.own,
|
|
1230
|
+
read: {
|
|
1231
|
+
tool: 'get_component',
|
|
1232
|
+
args: { name: component.name, sections: [section.title] },
|
|
1233
|
+
},
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
for (const prop of component.props) {
|
|
1238
|
+
if (prop.name.startsWith('pt')) continue;
|
|
1239
|
+
entries.push({
|
|
1240
|
+
source: `component ${component.name}`,
|
|
1241
|
+
section: `prop ${prop.name}`,
|
|
1242
|
+
text: `${prop.name}: ${prop.type}${prop.default ? ` = ${prop.default}` : ''}. ${prop.description ?? ''}`,
|
|
1243
|
+
read: { tool: 'get_component', args: { name: component.name } },
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
for (const event of component.events ?? []) {
|
|
1247
|
+
entries.push({
|
|
1248
|
+
source: `component ${component.name}`,
|
|
1249
|
+
section: `event ${event.name}`,
|
|
1250
|
+
text: `@${event.name} ${event.signature}. ${event.description ?? ''}`,
|
|
1251
|
+
read: { tool: 'get_component', args: { name: component.name } },
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
for (const platform of RECIPE_PLATFORMS) {
|
|
1256
|
+
const dir = join(AI_ROOT, 'recipes', platform);
|
|
1257
|
+
if (!existsSync(dir)) continue;
|
|
1258
|
+
for (const file of readdirSync(dir).filter((f) => f.endsWith('.md'))) {
|
|
1259
|
+
const name = file.slice(0, -3);
|
|
1260
|
+
const markdown = readFileSync(join(dir, file), 'utf-8');
|
|
1261
|
+
entries.push({
|
|
1262
|
+
source: `recipe ${platform}/${name}`,
|
|
1263
|
+
section: frontmatter(markdown).title ?? name,
|
|
1264
|
+
text: frontmatter(markdown).summary ?? name,
|
|
1265
|
+
read: { tool: 'get_recipe', args: { platform, name } },
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const readme = join(PACKAGE_ROOT, 'README.md');
|
|
1270
|
+
if (existsSync(readme)) {
|
|
1271
|
+
addSections('README', readFileSync(readme, 'utf-8'), () => ({
|
|
1272
|
+
tool: 'read file',
|
|
1273
|
+
args: { path: 'node_modules/bitboss-ui/README.md' },
|
|
1274
|
+
}));
|
|
1275
|
+
}
|
|
1276
|
+
cachedDocIndex = entries;
|
|
1277
|
+
return entries;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function searchDocs({ query, limit } = {}) {
|
|
1281
|
+
const max = Number.isFinite(limit) && limit > 0 ? Math.min(limit, 20) : 8;
|
|
1282
|
+
const { tokens, total, results } = searchSections(docIndex(), query, max);
|
|
1283
|
+
return {
|
|
1284
|
+
query: query ?? '',
|
|
1285
|
+
tokens,
|
|
1286
|
+
count: results.length,
|
|
1287
|
+
...(total > results.length ? { totalMatches: total } : {}),
|
|
1288
|
+
results,
|
|
1289
|
+
hint: results.length
|
|
1290
|
+
? 'Each result is one section; `read` is the call that returns it whole. Prefer the top results: they match the most query words.'
|
|
1291
|
+
: 'Nothing matched. Use fewer, more specific words (a prop, option or component name), or search_components for components.',
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/** `get_composable({ name })` — one composable's doc (useToast, useConfirm…). */
|
|
1296
|
+
function getComposable({ name } = {}) {
|
|
1297
|
+
const dir = join(AI_ROOT, 'composables');
|
|
1298
|
+
const files = existsSync(dir)
|
|
1299
|
+
? readdirSync(dir).filter((f) => f.endsWith('.md'))
|
|
1300
|
+
: [];
|
|
1301
|
+
const wanted = String(name ?? '').replace(/\.md$/, '');
|
|
1302
|
+
const file = files.find(
|
|
1303
|
+
(f) => f.slice(0, -3).toLowerCase() === wanted.toLowerCase()
|
|
706
1304
|
);
|
|
707
|
-
|
|
1305
|
+
if (!file) {
|
|
1306
|
+
return {
|
|
1307
|
+
found: false,
|
|
1308
|
+
error: `Composable not found: ${name}`,
|
|
1309
|
+
available: files.map((f) => f.slice(0, -3)),
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
return {
|
|
1313
|
+
found: true,
|
|
1314
|
+
name: file.slice(0, -3),
|
|
1315
|
+
content: stripFrontmatter(readFileSync(join(dir, file), 'utf-8')),
|
|
1316
|
+
};
|
|
708
1317
|
}
|
|
709
1318
|
|
|
710
1319
|
function listRecipes({ platform } = {}) {
|
|
@@ -716,21 +1325,40 @@ function listRecipes({ platform } = {}) {
|
|
|
716
1325
|
error: `Unknown platform(s): ${invalid.join(', ')} — expected one of ${RECIPE_PLATFORMS.join(', ')}`,
|
|
717
1326
|
};
|
|
718
1327
|
}
|
|
719
|
-
|
|
1328
|
+
/*
|
|
1329
|
+
* One row per recipe with its platforms, title, summary and size (review
|
|
1330
|
+
* D6-13): records-workspace / inline-edit-workspace / approvals-inbox could
|
|
1331
|
+
* not be told apart by name, and each costs 11–61 KB to open.
|
|
1332
|
+
*/
|
|
1333
|
+
const byName = new Map();
|
|
720
1334
|
for (const p of platforms) {
|
|
721
1335
|
const dir = join(AI_ROOT, 'recipes', p);
|
|
722
1336
|
if (!existsSync(dir)) continue;
|
|
723
1337
|
for (const file of readdirSync(dir)) {
|
|
724
|
-
if (file.endsWith('.md'))
|
|
725
|
-
|
|
726
|
-
|
|
1338
|
+
if (!file.endsWith('.md')) continue;
|
|
1339
|
+
const name = file.slice(0, -3);
|
|
1340
|
+
const markdown = readFileSync(join(dir, file), 'utf-8');
|
|
1341
|
+
const meta = frontmatter(markdown);
|
|
1342
|
+
const row = byName.get(name) ?? {
|
|
1343
|
+
name,
|
|
1344
|
+
...(meta.title ? { title: meta.title } : {}),
|
|
1345
|
+
...(meta.summary ? { summary: meta.summary } : {}),
|
|
1346
|
+
platforms: [],
|
|
1347
|
+
chars: {},
|
|
1348
|
+
};
|
|
1349
|
+
row.platforms.push(p);
|
|
1350
|
+
row.chars[p] = markdown.length;
|
|
1351
|
+
byName.set(name, row);
|
|
727
1352
|
}
|
|
728
1353
|
}
|
|
729
|
-
return {
|
|
1354
|
+
return {
|
|
1355
|
+
recipes: [...byName.values()],
|
|
1356
|
+
hint: 'get_recipe({ platform, name }) returns the whole recipe (they are copied whole); pass sections to read only part of a long one.',
|
|
1357
|
+
};
|
|
730
1358
|
}
|
|
731
1359
|
|
|
732
1360
|
/** `get_recipe({ platform, name })` — read one recipe's markdown content. */
|
|
733
|
-
function getRecipe({ platform, name, maxChars } = {}) {
|
|
1361
|
+
function getRecipe({ platform, name, sections, maxChars } = {}) {
|
|
734
1362
|
if (!RECIPE_PLATFORMS.includes(platform)) {
|
|
735
1363
|
return {
|
|
736
1364
|
found: false,
|
|
@@ -760,9 +1388,26 @@ function getRecipe({ platform, name, maxChars } = {}) {
|
|
|
760
1388
|
hint: 'Call list_recipes for the available names.',
|
|
761
1389
|
};
|
|
762
1390
|
}
|
|
1391
|
+
const markdown = readFileSync(filePath, 'utf-8');
|
|
1392
|
+
if (sections?.length) {
|
|
1393
|
+
const all = splitSections(markdown);
|
|
1394
|
+
const { picked, missing } = pickSections(all, sections);
|
|
1395
|
+
return {
|
|
1396
|
+
found: true,
|
|
1397
|
+
platform,
|
|
1398
|
+
name: fileName.slice(0, -3),
|
|
1399
|
+
sections: picked.map((section) => ({
|
|
1400
|
+
title: section.title,
|
|
1401
|
+
text: section.text,
|
|
1402
|
+
})),
|
|
1403
|
+
...(missing.length ? { notFound: missing, outline: outline(all) } : {}),
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
// Whole by default (review D6-14): a recipe is copied whole, and the old
|
|
1407
|
+
// 40 000-char default cut 7 of 9 vue recipes inside their page code.
|
|
763
1408
|
const { content, ...meta } = truncateMarkdown(
|
|
764
|
-
|
|
765
|
-
maxChars,
|
|
1409
|
+
markdown,
|
|
1410
|
+
maxChars ?? 0,
|
|
766
1411
|
`Recipes are ordered narrative — the cut tail is later sections.`
|
|
767
1412
|
);
|
|
768
1413
|
return {
|
|
@@ -825,7 +1470,12 @@ function compareVersions(a, b) {
|
|
|
825
1470
|
* halves are fixed: rows are stamped with `firstReleasedIn` at build time, and
|
|
826
1471
|
* this serves the slice.
|
|
827
1472
|
*/
|
|
828
|
-
function listChanges({ since, until } = {}) {
|
|
1473
|
+
function listChanges({ since: rawSince, until: rawUntil } = {}) {
|
|
1474
|
+
// The repo's own tags are spelled `v3.0.0-beta.33` (review D6-12).
|
|
1475
|
+
const since =
|
|
1476
|
+
rawSince == null ? rawSince : String(rawSince).replace(/^v/, '');
|
|
1477
|
+
const until =
|
|
1478
|
+
rawUntil == null ? rawUntil : String(rawUntil).replace(/^v/, '');
|
|
829
1479
|
if (!existsSync(CHANGELOG_PATH)) {
|
|
830
1480
|
return {
|
|
831
1481
|
error: `changelog.json not found at ${CHANGELOG_PATH} — reinstall bitboss-ui (or run \`npm run build\` when working in the library repo).`,
|
|
@@ -895,7 +1545,8 @@ function toolCallback(fn) {
|
|
|
895
1545
|
try {
|
|
896
1546
|
const result = fn(args ?? {});
|
|
897
1547
|
return {
|
|
898
|
-
|
|
1548
|
+
// Compact JSON: indentation was a quarter of every answer (review D6).
|
|
1549
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
899
1550
|
isError: false,
|
|
900
1551
|
};
|
|
901
1552
|
} catch (error) {
|
|
@@ -910,11 +1561,25 @@ function toolCallback(fn) {
|
|
|
910
1561
|
|
|
911
1562
|
const platformSchema = z.enum(RECIPE_PLATFORMS);
|
|
912
1563
|
|
|
1564
|
+
/**
|
|
1565
|
+
* Sent once at initialize (review D6-1): an MCP-only agent otherwise had no
|
|
1566
|
+
* way to know which tool answers which question, and read whole guides.
|
|
1567
|
+
*/
|
|
1568
|
+
const SERVER_INSTRUCTIONS = [
|
|
1569
|
+
'bitboss-ui: a Vue 3 component library. Answer from these tools, never from memory — agents that guess invent props.',
|
|
1570
|
+
'- A topic or "how do I…" question (dark mode, cascade layers, SSR, a plugin option, a CLI command): search_docs, then open the section it points to.',
|
|
1571
|
+
'- Which component: search_components (empty query = the whole catalog). One component: get_component({ name }) — compact; pass sections for guide prose, include for more.',
|
|
1572
|
+
'- A type in a props table (BbTableColumn, ButtonVariantType): get_component({ name }) returns its definition and values.',
|
|
1573
|
+
'- After writing markup: validate. Before custom CSS: list_tokens. Before bumping the version: list_changes({ since }).',
|
|
1574
|
+
'- Composables (useToast, useConfirm…): get_composable. Whole pages: list_recipes, get_recipe.',
|
|
1575
|
+
'- Long guides answer with an outline: pass sections.',
|
|
1576
|
+
].join('\n');
|
|
1577
|
+
|
|
913
1578
|
/** Register the knowledge-base tools on a fresh McpServer instance. */
|
|
914
1579
|
function buildServer() {
|
|
915
1580
|
const server = new McpServer(
|
|
916
1581
|
{ name: 'bitboss-ui', version: packageVersion() },
|
|
917
|
-
{ capabilities: { tools: {} } }
|
|
1582
|
+
{ capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS }
|
|
918
1583
|
);
|
|
919
1584
|
|
|
920
1585
|
server.registerTool(
|
|
@@ -949,16 +1614,30 @@ function buildServer() {
|
|
|
949
1614
|
'get_component',
|
|
950
1615
|
{
|
|
951
1616
|
description:
|
|
952
|
-
|
|
1617
|
+
"One bitboss-ui component: every prop, event, slot, v-model and pt part (never cut), the types its API uses with their values, the guide's outline and its Gotchas + Accessibility sections. Compact by default; pass `sections` for guide prose and `include` for more. Also answers for a public TYPE name (its definition and values), a v2/beta name that was removed or renamed (what replaced it), and near-miss spellings ('bb-button', 'Button').",
|
|
953
1618
|
inputSchema: {
|
|
954
|
-
name: z
|
|
1619
|
+
name: z
|
|
1620
|
+
.string()
|
|
1621
|
+
.describe("Component name, e.g. 'BbButton' ('bb-button' works too)."),
|
|
1622
|
+
sections: z
|
|
1623
|
+
.array(z.string())
|
|
1624
|
+
.optional()
|
|
1625
|
+
.describe(
|
|
1626
|
+
"Guide sections to return whole, by title from `guide.sections`, e.g. ['Toggle buttons']. Case-insensitive; a partial title matches."
|
|
1627
|
+
),
|
|
1628
|
+
include: z
|
|
1629
|
+
.array(z.enum(['tree', 'cssProps', 'ptProps', 'history', 'contract']))
|
|
1630
|
+
.optional()
|
|
1631
|
+
.describe(
|
|
1632
|
+
'Extra parts: tree = the component tree, cssProps = CSS variables with descriptions, ptProps = every pt:* prop row, history = full v2/beta rename records, contract = the whole contract markdown.'
|
|
1633
|
+
),
|
|
955
1634
|
maxChars: z
|
|
956
1635
|
.number()
|
|
957
1636
|
.int()
|
|
958
1637
|
.min(0)
|
|
959
1638
|
.optional()
|
|
960
1639
|
.describe(
|
|
961
|
-
`
|
|
1640
|
+
`With include: ['contract'] only — cap on the contract markdown (default ${MARKDOWN_DEFAULT_MAX_CHARS}; 0 = no cap).`
|
|
962
1641
|
),
|
|
963
1642
|
},
|
|
964
1643
|
},
|
|
@@ -1019,13 +1698,19 @@ function buildServer() {
|
|
|
1019
1698
|
'get_guide',
|
|
1020
1699
|
{
|
|
1021
1700
|
description:
|
|
1022
|
-
"Read one bitboss-ui guide
|
|
1701
|
+
"Read one bitboss-ui guide (names from list_guides, e.g. 'design-tokens' or 'migration/v2-to-v3'). A long guide answers with its outline: then pass `sections`. Pointers such as 'ai/guides/passthrough.md' or 'components/bb-table.md' resolve too.",
|
|
1023
1702
|
inputSchema: {
|
|
1024
1703
|
name: z
|
|
1025
1704
|
.string()
|
|
1026
1705
|
.describe(
|
|
1027
1706
|
"Guide path relative to the guides dir, e.g. 'design-tokens.md'. The '.md' suffix is optional."
|
|
1028
1707
|
),
|
|
1708
|
+
sections: z
|
|
1709
|
+
.array(z.string())
|
|
1710
|
+
.optional()
|
|
1711
|
+
.describe(
|
|
1712
|
+
"Section titles from the outline, e.g. ['Dark mode']. A ## section includes its ### children."
|
|
1713
|
+
),
|
|
1029
1714
|
maxChars: z
|
|
1030
1715
|
.number()
|
|
1031
1716
|
.int()
|
|
@@ -1043,7 +1728,7 @@ function buildServer() {
|
|
|
1043
1728
|
'list_recipes',
|
|
1044
1729
|
{
|
|
1045
1730
|
description:
|
|
1046
|
-
'List
|
|
1731
|
+
'List the page-level recipes (whole-page compositions) with title, summary, platforms and size, optionally filtered by platform.',
|
|
1047
1732
|
inputSchema: {
|
|
1048
1733
|
platform: platformSchema
|
|
1049
1734
|
.optional()
|
|
@@ -1057,8 +1742,12 @@ function buildServer() {
|
|
|
1057
1742
|
'get_recipe',
|
|
1058
1743
|
{
|
|
1059
1744
|
description:
|
|
1060
|
-
'Read
|
|
1745
|
+
'Read one recipe by platform and name — whole by default (recipes are copied whole); pass `sections` to read part of a long one.',
|
|
1061
1746
|
inputSchema: {
|
|
1747
|
+
sections: z
|
|
1748
|
+
.array(z.string())
|
|
1749
|
+
.optional()
|
|
1750
|
+
.describe('Section titles to return instead of the whole recipe.'),
|
|
1062
1751
|
platform: platformSchema.describe(
|
|
1063
1752
|
'Which platform variant of the recipe to read.'
|
|
1064
1753
|
),
|
|
@@ -1070,12 +1759,44 @@ function buildServer() {
|
|
|
1070
1759
|
.int()
|
|
1071
1760
|
.min(0)
|
|
1072
1761
|
.optional()
|
|
1762
|
+
.describe('Optional cap on the returned markdown (default: no cap).'),
|
|
1763
|
+
},
|
|
1764
|
+
},
|
|
1765
|
+
toolCallback(getRecipe)
|
|
1766
|
+
);
|
|
1767
|
+
|
|
1768
|
+
server.registerTool(
|
|
1769
|
+
'search_docs',
|
|
1770
|
+
{
|
|
1771
|
+
description:
|
|
1772
|
+
'Find the section that answers a question, across every guide, component guide, component prop and event, composable, recipe and the README. Use it for "how do I…" and "is there…" questions (dark mode, cascade layers, SSR, a plugin option, a CLI command, a migration). Each result is a snippet plus the exact call that reads the whole section.',
|
|
1773
|
+
inputSchema: {
|
|
1774
|
+
query: z
|
|
1775
|
+
.string()
|
|
1073
1776
|
.describe(
|
|
1074
|
-
|
|
1777
|
+
"A few specific words, e.g. 'dark mode class' or 'tailwind cascade layers'."
|
|
1075
1778
|
),
|
|
1779
|
+
limit: z
|
|
1780
|
+
.number()
|
|
1781
|
+
.int()
|
|
1782
|
+
.positive()
|
|
1783
|
+
.optional()
|
|
1784
|
+
.describe('Max results (default 8, max 20).'),
|
|
1076
1785
|
},
|
|
1077
1786
|
},
|
|
1078
|
-
toolCallback(
|
|
1787
|
+
toolCallback(searchDocs)
|
|
1788
|
+
);
|
|
1789
|
+
|
|
1790
|
+
server.registerTool(
|
|
1791
|
+
'get_composable',
|
|
1792
|
+
{
|
|
1793
|
+
description:
|
|
1794
|
+
"Read one composable's doc (useToast, useConfirm, useBbTableContext, useMobile…): what it returns, what host it needs, examples.",
|
|
1795
|
+
inputSchema: {
|
|
1796
|
+
name: z.string().describe("Composable name, e.g. 'useToast'."),
|
|
1797
|
+
},
|
|
1798
|
+
},
|
|
1799
|
+
toolCallback(getComposable)
|
|
1079
1800
|
);
|
|
1080
1801
|
|
|
1081
1802
|
server.registerTool(
|
|
@@ -1132,7 +1853,11 @@ export const start = main;
|
|
|
1132
1853
|
* concurrently. Importing this module does not start a server.
|
|
1133
1854
|
*/
|
|
1134
1855
|
export {
|
|
1856
|
+
buildServer,
|
|
1857
|
+
SERVER_INSTRUCTIONS,
|
|
1135
1858
|
searchComponents,
|
|
1859
|
+
searchDocs,
|
|
1860
|
+
getComposable,
|
|
1136
1861
|
getComponent,
|
|
1137
1862
|
listTokens,
|
|
1138
1863
|
listGuides,
|