bitboss-ui 3.0.0-beta.41 → 3.0.0-beta.43
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 +57 -7
- package/bin/bitboss-ui-mcp.mjs +1034 -85
- package/bin/bitboss-ui.mjs +16 -3
- package/dist/ai/BbAccordion.md +4 -4
- package/dist/ai/BbAlert.md +4 -4
- package/dist/ai/BbAsterisk.md +11 -7
- package/dist/ai/BbAvatar.md +2 -2
- package/dist/ai/BbBadge.md +7 -4
- package/dist/ai/BbBadgeButton.md +4 -1
- package/dist/ai/BbBaseButton.md +2 -2
- package/dist/ai/BbBreadcrumbs.md +22 -16
- package/dist/ai/BbButton.md +32 -4
- package/dist/ai/BbCalendar.md +12 -4
- package/dist/ai/BbCheckbox.md +13 -4
- package/dist/ai/BbCheckboxGroup.md +4 -3
- package/dist/ai/BbColorInput.md +6 -4
- package/dist/ai/BbColorPalette.md +13 -7
- package/dist/ai/BbConfirm.md +2 -2
- package/dist/ai/BbDatePicker.md +4 -4
- package/dist/ai/BbDatePickerInput.md +15 -8
- package/dist/ai/BbDialog.md +2 -2
- package/dist/ai/BbDropdown.md +12 -8
- package/dist/ai/BbDropdownButton.md +9 -8
- package/dist/ai/BbDropzone.md +1 -1
- package/dist/ai/BbForm.md +124 -39
- package/dist/ai/BbIcon.md +3 -1
- package/dist/ai/BbNumberInput.md +12 -8
- package/dist/ai/BbOffCanvas.md +3 -3
- package/dist/ai/BbPagination.md +5 -2
- package/dist/ai/BbPopover.md +4 -3
- package/dist/ai/BbRadio.md +5 -4
- package/dist/ai/BbRadioGroup.md +4 -3
- package/dist/ai/BbRating.md +19 -10
- package/dist/ai/BbSelect.md +32 -18
- package/dist/ai/BbSelectPopover.md +12 -6
- package/dist/ai/BbSlider.md +11 -5
- package/dist/ai/BbSmoothHeight.md +3 -2
- package/dist/ai/BbSpinner.md +1 -1
- package/dist/ai/BbSwitch.md +12 -5
- package/dist/ai/BbSwitchGroup.md +4 -3
- package/dist/ai/BbTable.md +144 -58
- package/dist/ai/BbTabs.md +9 -7
- package/dist/ai/BbTabsList.md +3 -3
- package/dist/ai/BbTabsPanels.md +1 -1
- package/dist/ai/BbTabsRoot.md +1 -1
- package/dist/ai/BbTag.md +16 -9
- package/dist/ai/BbTextInput.md +17 -4
- package/dist/ai/BbTextarea.md +21 -14
- package/dist/ai/BbTimePicker.md +7 -4
- package/dist/ai/BbTimePickerInput.md +10 -5
- package/dist/ai/BbToast.md +2 -2
- package/dist/ai/BbTooltip.md +2 -2
- package/dist/ai/BbTree.md +1 -1
- package/dist/ai/ChipsBox.md +6 -3
- package/dist/ai/ClearableButton.md +1 -1
- package/dist/ai/CommonField.md +10 -1
- package/dist/ai/CommonFieldInput.md +3 -2
- package/dist/ai/CommonTimeSelector.md +2 -2
- package/dist/ai/ErrorIcon.md +1 -1
- package/dist/ai/FlatListBox.md +3 -3
- package/dist/ai/GroupedListBox.md +3 -3
- package/dist/ai/ListBox.md +3 -3
- package/dist/ai/WarningIcon.md +1 -1
- package/dist/ai/changelog.json +1109 -8
- package/dist/ai/components.json +298 -57860
- package/dist/ai/composables/useBbFormContext.md +19 -19
- package/dist/ai/composables/useBbFormField.md +14 -3
- package/dist/ai/design-tokens.json +46 -19
- package/dist/ai/design-tokens.manifest.yaml +5 -5
- package/dist/ai/guides/agent-contract.md +15 -4
- package/dist/ai/guides/ai-router.md +26 -24
- package/dist/ai/guides/design-language.md +13 -10
- package/dist/ai/guides/design-tokens.md +130 -43
- package/dist/ai/guides/icons-policy.md +3 -5
- package/dist/ai/guides/installation-and-plugin-setup.md +9 -5
- package/dist/ai/guides/migration/components/bb-breadcrumbs.md +1 -0
- package/dist/ai/guides/migration/components/bb-slider.md +5 -0
- package/dist/ai/guides/migration/v2-to-v3.md +56 -17
- package/dist/ai/guides/migration/v3-beta-tooling.md +79 -5
- package/dist/ai/guides/passthrough.md +19 -9
- package/dist/ai/guides/preset-components.md +6 -3
- package/dist/ai/guides/ssr-prefill.md +27 -4
- package/dist/ai/guides/validated-forms.md +21 -16
- package/dist/ai/guides/validation-providers.md +36 -23
- package/dist/ai/index.md +6 -6
- package/dist/ai/manifest/components/BbAccordion.json +41 -0
- package/dist/ai/manifest/components/BbAccordion.tree.json +25 -0
- package/dist/ai/manifest/components/BbAlert.json +56 -0
- package/dist/ai/manifest/components/BbAlert.tree.json +80 -0
- package/dist/ai/manifest/components/BbAsterisk.json +26 -0
- package/dist/ai/manifest/components/BbAsterisk.tree.json +13 -0
- package/dist/ai/manifest/components/BbAvatar.json +38 -0
- package/dist/ai/manifest/components/BbAvatar.tree.json +39 -0
- package/dist/ai/manifest/components/BbBadge.json +50 -0
- package/dist/ai/manifest/components/BbBadge.tree.json +86 -0
- package/dist/ai/manifest/components/BbBadgeButton.json +67 -0
- package/dist/ai/manifest/components/BbBadgeButton.tree.json +32 -0
- package/dist/ai/manifest/components/BbBaseButton.json +69 -0
- package/dist/ai/manifest/components/BbBaseButton.tree.json +25 -0
- package/dist/ai/manifest/components/BbBaseCheckboxIcon.json +46 -0
- package/dist/ai/manifest/components/BbBaseCheckboxIcon.tree.json +41 -0
- package/dist/ai/manifest/components/BbBaseRadioIcon.json +44 -0
- package/dist/ai/manifest/components/BbBaseRadioIcon.tree.json +36 -0
- package/dist/ai/manifest/components/BbBaseSwitchIcon.json +46 -0
- package/dist/ai/manifest/components/BbBaseSwitchIcon.tree.json +44 -0
- package/dist/ai/manifest/components/BbBreadcrumbs.json +52 -0
- package/dist/ai/manifest/components/BbBreadcrumbs.tree.json +45 -0
- package/dist/ai/manifest/components/BbButton.json +101 -0
- package/dist/ai/manifest/components/BbButton.tree.json +206 -0
- package/dist/ai/manifest/components/BbCalendar.json +84 -0
- package/dist/ai/manifest/components/BbCalendar.tree.json +99 -0
- package/dist/ai/manifest/components/BbCheckbox.json +97 -0
- package/dist/ai/manifest/components/BbCheckbox.tree.json +22 -0
- package/dist/ai/manifest/components/BbCheckboxGroup.json +127 -0
- package/dist/ai/manifest/components/BbCheckboxGroup.tree.json +52 -0
- package/dist/ai/manifest/components/BbCollapsible.json +32 -0
- package/dist/ai/manifest/components/BbCollapsible.tree.json +17 -0
- package/dist/ai/manifest/components/BbColorInput.json +131 -0
- package/dist/ai/manifest/components/BbColorInput.tree.json +37 -0
- package/dist/ai/manifest/components/BbColorPalette.json +73 -0
- package/dist/ai/manifest/components/BbColorPalette.tree.json +55 -0
- package/dist/ai/manifest/components/BbConfirm.json +34 -0
- package/dist/ai/manifest/components/BbConfirm.tree.json +30 -0
- package/dist/ai/manifest/components/BbConfirmPortal.json +18 -0
- package/dist/ai/manifest/components/BbDatePicker.json +106 -0
- package/dist/ai/manifest/components/BbDatePicker.tree.json +54 -0
- package/dist/ai/manifest/components/BbDatePickerInput.json +163 -0
- package/dist/ai/manifest/components/BbDatePickerInput.tree.json +50 -0
- package/dist/ai/manifest/components/BbDialog.json +76 -0
- package/dist/ai/manifest/components/BbDialog.tree.json +74 -0
- package/dist/ai/manifest/components/BbDropdown.json +98 -0
- package/dist/ai/manifest/components/BbDropdown.tree.json +120 -0
- package/dist/ai/manifest/components/BbDropdownButton.json +104 -0
- package/dist/ai/manifest/components/BbDropdownButton.tree.json +28 -0
- package/dist/ai/manifest/components/BbDropdownGroup.json +25 -0
- package/dist/ai/manifest/components/BbDropzone.json +66 -0
- package/dist/ai/manifest/components/BbDropzone.tree.json +35 -0
- package/dist/ai/manifest/components/BbForm.json +41 -0
- package/dist/ai/manifest/components/BbIcon.json +33 -0
- package/dist/ai/manifest/components/BbIcon.tree.json +19 -0
- package/dist/ai/manifest/components/BbIndicator.json +39 -0
- package/dist/ai/manifest/components/BbIndicator.tree.json +67 -0
- package/dist/ai/manifest/components/BbNumberInput.json +121 -0
- package/dist/ai/manifest/components/BbNumberInput.tree.json +27 -0
- package/dist/ai/manifest/components/BbOffCanvas.json +72 -0
- package/dist/ai/manifest/components/BbOffCanvas.tree.json +115 -0
- package/dist/ai/manifest/components/BbPagination.json +61 -0
- package/dist/ai/manifest/components/BbPagination.tree.json +69 -0
- package/dist/ai/manifest/components/BbPopover.json +81 -0
- package/dist/ai/manifest/components/BbPopover.tree.json +36 -0
- package/dist/ai/manifest/components/BbProgress.json +31 -0
- package/dist/ai/manifest/components/BbProgress.tree.json +25 -0
- package/dist/ai/manifest/components/BbPtScope.json +21 -0
- package/dist/ai/manifest/components/BbRadio.json +86 -0
- package/dist/ai/manifest/components/BbRadio.tree.json +22 -0
- package/dist/ai/manifest/components/BbRadioGroup.json +126 -0
- package/dist/ai/manifest/components/BbRadioGroup.tree.json +52 -0
- package/dist/ai/manifest/components/BbRating.json +98 -0
- package/dist/ai/manifest/components/BbRating.tree.json +55 -0
- package/dist/ai/manifest/components/BbSelect.json +170 -0
- package/dist/ai/manifest/components/BbSelect.tree.json +53 -0
- package/dist/ai/manifest/components/BbSelectPopover.json +122 -0
- package/dist/ai/manifest/components/BbSelectPopover.tree.json +108 -0
- package/dist/ai/manifest/components/BbSlider.json +107 -0
- package/dist/ai/manifest/components/BbSlider.tree.json +89 -0
- package/dist/ai/manifest/components/BbSmoothHeight.json +31 -0
- package/dist/ai/manifest/components/BbSmoothHeight.tree.json +16 -0
- package/dist/ai/manifest/components/BbSpinner.json +31 -0
- package/dist/ai/manifest/components/BbSpinner.tree.json +31 -0
- package/dist/ai/manifest/components/BbSwitch.json +97 -0
- package/dist/ai/manifest/components/BbSwitch.tree.json +22 -0
- package/dist/ai/manifest/components/BbSwitchGroup.json +127 -0
- package/dist/ai/manifest/components/BbSwitchGroup.tree.json +52 -0
- package/dist/ai/manifest/components/BbTable.json +150 -0
- package/dist/ai/manifest/components/BbTable.tree.json +199 -0
- package/dist/ai/manifest/components/BbTabs.json +83 -0
- package/dist/ai/manifest/components/BbTabs.tree.json +88 -0
- package/dist/ai/manifest/components/BbTabsList.json +47 -0
- package/dist/ai/manifest/components/BbTabsList.tree.json +94 -0
- package/dist/ai/manifest/components/BbTabsPanels.json +32 -0
- package/dist/ai/manifest/components/BbTabsPanels.tree.json +69 -0
- package/dist/ai/manifest/components/BbTabsRoot.json +61 -0
- package/dist/ai/manifest/components/BbTag.json +128 -0
- package/dist/ai/manifest/components/BbTag.tree.json +40 -0
- package/dist/ai/manifest/components/BbTextInput.json +122 -0
- package/dist/ai/manifest/components/BbTextInput.tree.json +27 -0
- package/dist/ai/manifest/components/BbTextarea.json +117 -0
- package/dist/ai/manifest/components/BbTextarea.tree.json +37 -0
- package/dist/ai/manifest/components/BbTimePicker.json +76 -0
- package/dist/ai/manifest/components/BbTimePicker.tree.json +37 -0
- package/dist/ai/manifest/components/BbTimePickerInput.json +131 -0
- package/dist/ai/manifest/components/BbTimePickerInput.tree.json +44 -0
- package/dist/ai/manifest/components/BbToast.json +43 -0
- package/dist/ai/manifest/components/BbToast.tree.json +91 -0
- package/dist/ai/manifest/components/BbToastPortal.json +18 -0
- package/dist/ai/manifest/components/BbTooltip.json +60 -0
- package/dist/ai/manifest/components/BbTooltip.tree.json +41 -0
- package/dist/ai/manifest/components/BbTree.json +51 -0
- package/dist/ai/manifest/components/BbTree.tree.json +31 -0
- package/dist/ai/manifest/components/ChipsBox.json +32 -0
- package/dist/ai/manifest/components/ChipsBox.tree.json +11 -0
- package/dist/ai/manifest/components/ClearableButton.json +19 -0
- package/dist/ai/manifest/components/ClearableButton.tree.json +19 -0
- package/dist/ai/manifest/components/CommaBox.json +30 -0
- package/dist/ai/manifest/components/CommonField.json +53 -0
- package/dist/ai/manifest/components/CommonField.tree.json +92 -0
- package/dist/ai/manifest/components/CommonFieldInput.json +47 -0
- package/dist/ai/manifest/components/CommonFieldInput.tree.json +95 -0
- package/dist/ai/manifest/components/CommonPopover.json +44 -0
- package/dist/ai/manifest/components/CommonPopover.tree.json +26 -0
- package/dist/ai/manifest/components/CommonTimeSelector.json +31 -0
- package/dist/ai/manifest/components/CommonTimeSelector.tree.json +43 -0
- package/dist/ai/manifest/components/ErrorIcon.json +17 -0
- package/dist/ai/manifest/components/ErrorIcon.tree.json +22 -0
- package/dist/ai/manifest/components/FlatListBox.json +42 -0
- package/dist/ai/manifest/components/FlatListBox.tree.json +15 -0
- package/dist/ai/manifest/components/GroupedListBox.json +44 -0
- package/dist/ai/manifest/components/GroupedListBox.tree.json +15 -0
- package/dist/ai/manifest/components/GuidesComponent.json +17 -0
- package/dist/ai/manifest/components/ListBox.json +42 -0
- package/dist/ai/manifest/components/ListBox.tree.json +9 -0
- package/dist/ai/manifest/components/OptionsContainer.json +41 -0
- package/dist/ai/manifest/components/WarningIcon.json +17 -0
- package/dist/ai/manifest/components/WarningIcon.tree.json +22 -0
- package/dist/ai/manifest/index.json +118 -0
- package/dist/ai/manifest/meta.json +430 -0
- package/dist/ai/manifest/types.advanced.json +55 -0
- package/dist/ai/manifest/types.api.json +43 -0
- package/dist/ai/manifest/types.wrapper.json +115 -0
- package/dist/ai/recipes/inertia/inline-edit-workspace.md +1 -1
- package/dist/ai/recipes/inertia/record-form.md +1 -1
- package/dist/ai/recipes/inertia/settings-workspace.md +109 -79
- package/dist/ai/recipes/nuxt/inline-edit-workspace.md +1 -1
- package/dist/ai/recipes/nuxt/record-form.md +1 -1
- package/dist/ai/recipes/nuxt/settings-workspace.md +108 -76
- package/dist/ai/recipes/vue/inline-edit-workspace.md +1 -1
- package/dist/ai/recipes/vue/record-form.md +1 -1
- package/dist/ai/recipes/vue/settings-workspace.md +108 -76
- package/dist/ai/source/BbBadge.md +34 -0
- package/dist/ai/source/BbBadgeButton.md +34 -0
- package/dist/ai/source/BbBaseCheckboxIcon.md +3 -1
- package/dist/ai/source/BbBaseRadioIcon.md +2 -1
- package/dist/ai/source/BbBaseSwitchIcon.md +7 -5
- package/dist/ai/source/BbBreadcrumbs.md +44 -17
- package/dist/ai/source/BbButton.md +66 -6
- package/dist/ai/source/BbCalendar.md +117 -6
- package/dist/ai/source/BbCheckbox.md +24 -5
- package/dist/ai/source/BbCheckboxGroup.md +9 -1
- package/dist/ai/source/BbColorInput.md +30 -0
- package/dist/ai/source/BbColorPalette.md +54 -7
- package/dist/ai/source/BbDatePickerInput.md +91 -29
- package/dist/ai/source/BbDropdownButton.md +29 -1
- package/dist/ai/source/BbForm.md +130 -27
- package/dist/ai/source/BbIcon.md +5 -2
- package/dist/ai/source/BbNumberInput.md +25 -8
- package/dist/ai/source/BbPagination.md +18 -0
- package/dist/ai/source/BbPopover.md +12 -0
- package/dist/ai/source/BbRadio.md +2 -2
- package/dist/ai/source/BbRadioGroup.md +11 -1
- package/dist/ai/source/BbRating.md +21 -5
- package/dist/ai/source/BbSelect.md +17 -5
- package/dist/ai/source/BbSelectPopover.md +72 -2
- package/dist/ai/source/BbSlider.md +10 -9
- package/dist/ai/source/BbSmoothHeight.md +6 -2
- package/dist/ai/source/BbSwitch.md +22 -3
- package/dist/ai/source/BbSwitchGroup.md +9 -1
- package/dist/ai/source/BbTable.md +181 -2
- package/dist/ai/source/BbTag.md +73 -29
- package/dist/ai/source/BbTextInput.md +32 -1
- package/dist/ai/source/BbTextarea.md +39 -45
- package/dist/ai/source/BbTimePickerInput.md +40 -2
- package/dist/ai/source/BbTree.md +15 -3
- package/dist/ai/source/CommonField.md +103 -0
- package/dist/ai/source/CommonFieldInput.md +26 -5
- package/dist/ai/source/CommonTimeSelector.md +10 -0
- package/dist/ai/source/FlatListBox.md +4 -0
- package/dist/ai/source/GroupedListBox.md +4 -0
- package/dist/ai/source/ListBox.md +4 -0
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +109 -107
- package/dist/components/BbBreadcrumbs/types.d.ts +1 -1
- package/dist/components/BbCalendar/BbCalendar.vue_vue_type_script_setup_true_lang.js +151 -149
- package/dist/components/BbCalendar/CalendarYearPanel.vue_vue_type_script_setup_true_lang.js +51 -48
- package/dist/components/BbCalendar/types.d.ts +5 -1
- package/dist/components/BbCheckbox/BbCheckbox.vue_vue_type_script_setup_true_lang.js +61 -57
- package/dist/components/BbCheckbox/types.d.ts +2 -2
- package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +43 -38
- package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +53 -51
- package/dist/components/BbColorPalette/BbColorPalette.vue_vue_type_script_setup_true_lang.js +257 -242
- package/dist/components/BbColorPalette/types.d.ts +10 -3
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +320 -305
- package/dist/components/BbDatePickerInput/types.d.ts +3 -3
- package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +64 -57
- package/dist/components/BbForm/BbForm.vue.d.ts +30 -12
- package/dist/components/BbForm/BbForm.vue_vue_type_script_setup_true_lang.js +170 -139
- package/dist/components/BbForm/BbFormCustomField.js +34 -28
- package/dist/components/BbForm/bindFormField.d.ts +27 -0
- package/dist/components/BbForm/bindFormField.js +9 -7
- package/dist/components/BbForm/formFields.d.ts +10 -2
- package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +24 -23
- package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +72 -67
- package/dist/components/BbNumberInput/types.d.ts +3 -2
- package/dist/components/BbRadio/types.d.ts +2 -2
- package/dist/components/BbRadioGroup/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +36 -29
- package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +26 -22
- package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +108 -104
- package/dist/components/BbSelect/types.d.ts +9 -3
- package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +376 -357
- package/dist/components/BbSelectPopover/sheetChipAttrs.d.ts +18 -0
- package/dist/components/BbSelectPopover/sheetChipAttrs.js +5 -0
- package/dist/components/BbSelectPopover/types.d.ts +3 -1
- package/dist/components/BbSlider/BbSlider.vue_vue_type_script_setup_true_lang.js +99 -102
- package/dist/components/BbSlider/types.d.ts +0 -4
- package/dist/components/BbSwitch/BbSwitch.vue_vue_type_script_setup_true_lang.js +71 -67
- package/dist/components/BbSwitchGroup/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +75 -70
- package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +803 -765
- package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +138 -124
- package/dist/components/BbTag/types.d.ts +2 -1
- package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +88 -80
- package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +71 -65
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +216 -203
- package/dist/components/BbTree/BbTree.vue_vue_type_script_setup_true_lang.js +2 -4
- package/dist/components/CommonField/CommonField.vue_vue_type_script_setup_true_lang.js +92 -86
- package/dist/components/CommonField/types.d.ts +7 -0
- package/dist/components/CommonTimeSelector/CommonTimeSelector.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/components/FlatListBox/types.d.ts +4 -0
- package/dist/components/GroupedListBox/types.d.ts +4 -0
- package/dist/components/ListBox/types.d.ts +4 -0
- package/dist/composables/ssrMarker.d.ts +15 -0
- package/dist/composables/ssrMarker.js +19 -0
- package/dist/composables/useBaseOptions.js +7 -1
- package/dist/composables/useBbFormContext.d.ts +15 -4
- package/dist/composables/useBbFormContext.js +18 -5
- package/dist/composables/useBbFormField.d.ts +13 -2
- package/dist/composables/useBbFormField.js +40 -24
- package/dist/composables/useFormBinding.d.ts +11 -3
- package/dist/composables/useFormBinding.js +31 -21
- package/dist/composables/useItemsGetter.d.ts +28 -0
- package/dist/composables/useItemsGetter.js +45 -33
- package/dist/composables/usePrefill.js +9 -8
- package/dist/composables/useServerPrefetch.d.ts +28 -0
- package/dist/composables/useServerPrefetch.js +9 -0
- package/dist/composables/useSsrState.d.ts +9 -1
- package/dist/deprecation/ai-deprecations.json.d.ts +35 -0
- package/dist/deprecation/ai-deprecations.json.js +1 -1
- package/dist/deprecation/removedProps.js +2 -2
- package/dist/llms-full.txt +1514 -772
- package/dist/llms-medium.txt +65 -45
- package/dist/llms.txt +2 -2
- package/dist/styles.css +2 -2
- package/dist/utilities/functions/toggleInput.d.ts +16 -0
- package/dist/utilities/functions/toggleInput.js +7 -2
- package/dist/utilities/functions/warnRemovedAttrs.d.ts +1 -1
- package/dist/utilities/functions/warnRemovedAttrs.js +11 -1
- package/dist/utilities/misc/ssrStateInjectionKey.d.ts +8 -3
- package/dist/utilities/misc/ssrValue.d.ts +20 -0
- package/dist/utilities/misc/ssrValue.js +7 -0
- package/dist/utils/versionCheck.js +1 -1
- package/dist/vite.js +175 -168
- package/llms.txt +2 -2
- package/package.json +3 -2
- package/scripts/lib/agent-manifest.mjs +509 -0
- package/scripts/lib/css-color-resolve.mjs +128 -4
- package/scripts/lib/design-token-manifest.mjs +11 -4
- package/scripts/lib/doc-sections.mjs +5 -3
- package/scripts/lib/eslint-disable.mjs +5 -0
- package/scripts/lib/eslint-plugin.d.ts +5 -1
- package/scripts/lib/eslint-plugin.mjs +240 -0
- package/scripts/lib/form-v-model.mjs +190 -0
- package/scripts/lib/manifest-pointers.mjs +71 -0
- package/scripts/lib/project-variant-dts.mjs +25 -3
- package/scripts/lib/reactive-v-model.mjs +221 -0
- package/scripts/lib/validate-bb-markup.mjs +116 -8
package/bin/bitboss-ui-mcp.mjs
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
33
33
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
34
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
34
|
+
import { existsSync, readFileSync, readdirSync, statSync } 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';
|
|
@@ -48,6 +48,10 @@ import {
|
|
|
48
48
|
possessive,
|
|
49
49
|
validateVueSnippet,
|
|
50
50
|
} from '../scripts/lib/validate-bb-markup.mjs';
|
|
51
|
+
import {
|
|
52
|
+
agentComponent,
|
|
53
|
+
firstSentence,
|
|
54
|
+
} from '../scripts/lib/agent-manifest.mjs';
|
|
51
55
|
|
|
52
56
|
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
53
57
|
const AI_ROOT = join(PACKAGE_ROOT, 'dist', 'ai');
|
|
@@ -65,19 +69,38 @@ function packageVersion() {
|
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
/*
|
|
73
|
+
* Parsed once per build of components.json (Q56.46): the 1.2 MB file was
|
|
74
|
+
* re-read and re-parsed on every tool call. Both caches below (the raw
|
|
75
|
+
* manifest for the lookup tools, the validator's map for `validate`) are
|
|
76
|
+
* keyed on the file's mtime, so a rebuild under a running server (library
|
|
77
|
+
* repo, `npm run build`) is picked up by every tool.
|
|
78
|
+
*/
|
|
79
|
+
let cachedManifest = null;
|
|
68
80
|
function loadComponentsManifest() {
|
|
69
81
|
if (!existsSync(MANIFEST_PATH)) {
|
|
70
82
|
throw new Error(
|
|
71
83
|
`components.json not found at ${MANIFEST_PATH} — reinstall bitboss-ui (or run \`npm run build\` when working in the library repo).`
|
|
72
84
|
);
|
|
73
85
|
}
|
|
74
|
-
|
|
86
|
+
const mtime = statSync(MANIFEST_PATH).mtimeMs;
|
|
87
|
+
if (cachedManifest?.mtime !== mtime)
|
|
88
|
+
cachedManifest = {
|
|
89
|
+
mtime,
|
|
90
|
+
value: JSON.parse(readFileSync(MANIFEST_PATH, 'utf-8')),
|
|
91
|
+
};
|
|
92
|
+
return cachedManifest.value;
|
|
75
93
|
}
|
|
76
94
|
|
|
77
95
|
let cachedManifestMap = null;
|
|
78
96
|
function getManifestMap() {
|
|
79
|
-
|
|
80
|
-
|
|
97
|
+
// Missing file: loadManifest throws the install hint.
|
|
98
|
+
const mtime = existsSync(MANIFEST_PATH)
|
|
99
|
+
? statSync(MANIFEST_PATH).mtimeMs
|
|
100
|
+
: null;
|
|
101
|
+
if (cachedManifestMap?.mtime !== mtime || mtime === null)
|
|
102
|
+
cachedManifestMap = { mtime, value: loadManifest(MANIFEST_PATH) };
|
|
103
|
+
return cachedManifestMap.value;
|
|
81
104
|
}
|
|
82
105
|
|
|
83
106
|
/** `search_components({ query })` — token match over name/description. */
|
|
@@ -441,7 +464,7 @@ function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
|
441
464
|
* is what makes an agent invent API, which is precisely what this server
|
|
442
465
|
* exists to prevent.
|
|
443
466
|
*/
|
|
444
|
-
const MARKDOWN_DEFAULT_MAX_CHARS =
|
|
467
|
+
const MARKDOWN_DEFAULT_MAX_CHARS = 16000; // was 40 000 (MCP panel 2026-09-26)
|
|
445
468
|
|
|
446
469
|
/**
|
|
447
470
|
* Cut `text` to `maxChars` on a line boundary, reporting what was dropped.
|
|
@@ -580,6 +603,13 @@ function resolveComponent(manifest, name) {
|
|
|
580
603
|
|
|
581
604
|
function nearestComponents(manifest, name, count = 3) {
|
|
582
605
|
const needle = pascal(name).toLowerCase().replace(/^bb/, '');
|
|
606
|
+
// A partial name (`BbInput`) matches the components that contain it
|
|
607
|
+
// (BbTextInput, BbNumberInput…) before any edit-distance guess.
|
|
608
|
+
const containing = manifest.components
|
|
609
|
+
.filter((c) => !c.referenceOnly)
|
|
610
|
+
.map((c) => c.name)
|
|
611
|
+
.filter((n) => needle.length >= 3 && n.toLowerCase().includes(needle));
|
|
612
|
+
if (containing.length) return containing.slice(0, Math.max(count, 5));
|
|
583
613
|
return manifest.components
|
|
584
614
|
.filter((c) => !c.referenceOnly)
|
|
585
615
|
.map((c) => ({
|
|
@@ -662,8 +692,346 @@ function contractGuideSections(markdown) {
|
|
|
662
692
|
return all.slice(start + 1, end === -1 ? undefined : end);
|
|
663
693
|
}
|
|
664
694
|
|
|
665
|
-
|
|
695
|
+
/*
|
|
696
|
+
* A section is served whole up to SECTION_CAP chars. Past that (the review
|
|
697
|
+
* found a 142.9K `migration/v2-to-v3.md` section behind one search hit) it
|
|
698
|
+
* answers its own outline: ask again for a sub-section, or pass maxChars: 0.
|
|
699
|
+
*/
|
|
700
|
+
const SECTION_CAP = 16000;
|
|
701
|
+
function sectionAnswer(sec, all, maxChars) {
|
|
702
|
+
if (maxChars === 0) return { title: sec.title, text: sec.text };
|
|
703
|
+
const cap = maxChars > 0 ? maxChars : SECTION_CAP;
|
|
704
|
+
if (sec.text.length <= cap) return { title: sec.title, text: sec.text };
|
|
705
|
+
const inner = all.filter(
|
|
706
|
+
(other) =>
|
|
707
|
+
other !== sec && other.level > sec.level && sec.text.includes(other.text)
|
|
708
|
+
);
|
|
709
|
+
// No sub-sections to point at (v2-to-v3 § 5.6, 68K), or an explicit cap:
|
|
710
|
+
// the text itself, cut — never an empty outline (adversarial review 2).
|
|
711
|
+
if (!inner.length || maxChars > 0) {
|
|
712
|
+
const cut = sec.text.slice(0, cap);
|
|
713
|
+
const end = cut.lastIndexOf('\n');
|
|
714
|
+
return {
|
|
715
|
+
title: sec.title,
|
|
716
|
+
text: end > cap / 2 ? cut.slice(0, end) : cut,
|
|
717
|
+
truncated: true,
|
|
718
|
+
chars: sec.text.length,
|
|
719
|
+
hint: `Cut at ${cap} of ${sec.text.length} chars. maxChars: 0 returns all of it${inner.length ? ', or ask for a sub-section by title' : ''}.`,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
// Sub-sections: the section's own lead-in (up to 4K) and its outline.
|
|
723
|
+
const own = sec.own.length > 4000 ? `${sec.own.slice(0, 4000)} …` : sec.own;
|
|
724
|
+
return {
|
|
725
|
+
title: sec.title,
|
|
726
|
+
chars: sec.text.length,
|
|
727
|
+
text: own,
|
|
728
|
+
outline: outline(inner),
|
|
729
|
+
hint: `This section is ${sec.text.length} chars: its lead-in is above; ask for a sub-section by title, or pass maxChars: 0 for all of it.`,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/*
|
|
734
|
+
* A multi-part answer (several sections, members or recipe files) stops at
|
|
735
|
+
* ANSWER_CAP chars and names the rest: one call per part never came to 149K
|
|
736
|
+
* (BbTable, every section at once) or 52K (every member) by accident.
|
|
737
|
+
*/
|
|
738
|
+
const ANSWER_CAP = 24000;
|
|
739
|
+
function capParts(parts, label) {
|
|
740
|
+
const kept = [];
|
|
741
|
+
let size = 0;
|
|
742
|
+
for (const part of parts) {
|
|
743
|
+
const n = JSON.stringify(part).length;
|
|
744
|
+
if (kept.length && size + n > ANSWER_CAP) break;
|
|
745
|
+
kept.push(part);
|
|
746
|
+
size += n;
|
|
747
|
+
}
|
|
748
|
+
const rest = parts.slice(kept.length);
|
|
749
|
+
return {
|
|
750
|
+
kept,
|
|
751
|
+
...(rest.length
|
|
752
|
+
? {
|
|
753
|
+
[`remaining${label}`]: rest.map((part) => part.title ?? part.name),
|
|
754
|
+
}
|
|
755
|
+
: {}),
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** Sections every agent needs, served whole in the full answer. */
|
|
666
760
|
const ALWAYS_SECTIONS = [/^gotchas/i, /^accessibility/i];
|
|
761
|
+
/** The summary keeps only these whole; the rest are in the outline (MCP panel 2026-09-26). */
|
|
762
|
+
const SUMMARY_SECTIONS = [/^gotchas/i];
|
|
763
|
+
|
|
764
|
+
/*
|
|
765
|
+
* SUMMARY ROWS (MCP panel 2026-09-26, owner: compact by default, expand on
|
|
766
|
+
* request). Blind Haiku runs saved `get_component BbTable` (38–47 KB) to a file
|
|
767
|
+
* to read it; Claude Code warns above ~10K tokens of tool output. The summary
|
|
768
|
+
* keeps EVERY name (a partial prop list is what makes agents invent API) as one
|
|
769
|
+
* line — name, type, default, the description's first sentence — and marks a
|
|
770
|
+
* cut sentence with " …": `members: ['name']` returns the full row.
|
|
771
|
+
* `firstSentence` lives in scripts/lib/agent-manifest.mjs: the manifest
|
|
772
|
+
* index (Q56.46) cuts descriptions the same way.
|
|
773
|
+
*/
|
|
774
|
+
|
|
775
|
+
/** A type as the summary prints it: no ` | undefined` tail, long ones cut. */
|
|
776
|
+
function shortType(type, cap = 180) {
|
|
777
|
+
const t = String(type ?? '')
|
|
778
|
+
.replace(/\s*\|\s*undefined$/, '')
|
|
779
|
+
.replace(/\s+/g, ' ');
|
|
780
|
+
return t.length > cap ? `${t.slice(0, cap)}…` : t;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
let sharedPropKeys = null;
|
|
784
|
+
/**
|
|
785
|
+
* A prop folds only when EVERYTHING an agent reads off it is the same on
|
|
786
|
+
* every component: type, default, values and description (the adversarial
|
|
787
|
+
* review found `items` / `multiple` / `padding` folded with different types
|
|
788
|
+
* and defaults). A required prop never folds: its own line carries the fact.
|
|
789
|
+
*/
|
|
790
|
+
const foldKey = (p) =>
|
|
791
|
+
[
|
|
792
|
+
p.name,
|
|
793
|
+
p.type,
|
|
794
|
+
p.default ?? '',
|
|
795
|
+
(p.values ?? []).join('|'),
|
|
796
|
+
p.description ?? '',
|
|
797
|
+
].join('\u0000');
|
|
798
|
+
/**
|
|
799
|
+
* Prop rows that are identical (name AND description) on 3+ public components:
|
|
800
|
+
* the form-field set on 16 controls, the Inertia link props on the buttons.
|
|
801
|
+
* The summary lists them by name once (`common`) instead of repeating ~4–8 KB
|
|
802
|
+
* of the same prose per component; `members` returns any of them whole.
|
|
803
|
+
*/
|
|
804
|
+
function sharedProps(manifest) {
|
|
805
|
+
if (sharedPropKeys) return sharedPropKeys;
|
|
806
|
+
const count = new Map();
|
|
807
|
+
for (const component of manifest.components ?? []) {
|
|
808
|
+
if (component.referenceOnly) continue;
|
|
809
|
+
for (const prop of component.props ?? []) {
|
|
810
|
+
if (!prop.description || prop.name.startsWith('pt')) continue;
|
|
811
|
+
const key = foldKey(prop);
|
|
812
|
+
if (prop.required) continue;
|
|
813
|
+
count.set(key, (count.get(key) ?? 0) + 1);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
sharedPropKeys = new Set(
|
|
817
|
+
[...count].filter(([, n]) => n >= 3).map(([key]) => key)
|
|
818
|
+
);
|
|
819
|
+
return sharedPropKeys;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const propLine = (p) => {
|
|
823
|
+
const values =
|
|
824
|
+
p.values?.length &&
|
|
825
|
+
!/Registry|VariantType|BbPtScopeName|LocaleKey/.test(String(p.type)) &&
|
|
826
|
+
!p.values.some((v) => /\s/.test(String(v))) &&
|
|
827
|
+
!p.values.every((v) => String(p.type).includes(String(v)))
|
|
828
|
+
? ` ∈ ${p.values.map((v) => String(v)).join('|')}`.slice(0, 120)
|
|
829
|
+
: '';
|
|
830
|
+
return `${p.name}${p.required ? '' : '?'}: ${shortType(p.type)}${values}${p.default != null && p.default !== '' ? ` = ${p.default}` : ''}${p.description ? ` — ${firstSentence(p.description)}` : ''}`;
|
|
831
|
+
};
|
|
832
|
+
const eventLine = (e) =>
|
|
833
|
+
`${e.name}${String(e.signature ?? '').replace(/: void$/, '')}${e.description ? ` — ${firstSentence(e.description)}` : ''}`;
|
|
834
|
+
/** The top-level keys of a slot scope type `{ a?: …; b: … }`. */
|
|
835
|
+
function scopeKeys(type) {
|
|
836
|
+
const t = String(type ?? '').trim();
|
|
837
|
+
if (!t.startsWith('{')) return [];
|
|
838
|
+
const keys = [];
|
|
839
|
+
let depth = 0;
|
|
840
|
+
let token = '';
|
|
841
|
+
const inner = t.slice(1, -1);
|
|
842
|
+
for (let i = 0; i < inner.length; i++) {
|
|
843
|
+
const ch = inner[i];
|
|
844
|
+
if ('{([<'.includes(ch)) depth++;
|
|
845
|
+
// The `>` of an arrow `=>` closes nothing.
|
|
846
|
+
else if ('})]'.includes(ch) || (ch === '>' && inner[i - 1] !== '='))
|
|
847
|
+
depth--;
|
|
848
|
+
if (depth === 0 && (ch === ';' || ch === ',')) {
|
|
849
|
+
const key = token.trim().match(/^([\w$]+)\??\s*:/)?.[1];
|
|
850
|
+
if (key) keys.push(key);
|
|
851
|
+
token = '';
|
|
852
|
+
} else token += ch;
|
|
853
|
+
}
|
|
854
|
+
const last = token.trim().match(/^([\w$]+)\??\s*:/)?.[1];
|
|
855
|
+
if (last) keys.push(last);
|
|
856
|
+
return keys;
|
|
857
|
+
}
|
|
858
|
+
const slotLine = (sl) => {
|
|
859
|
+
// A named scope type (BbSelectOptionSlotProps…) carries resolved `fields`.
|
|
860
|
+
const keys = sl.fields?.length
|
|
861
|
+
? sl.fields.map((f) => String(f).match(/^([\w$]+)/)?.[1]).filter(Boolean)
|
|
862
|
+
: scopeKeys(sl.type);
|
|
863
|
+
return `#${sl.name}${keys.length ? ` { ${keys.join(', ')} }` : ''}${sl.description ? ` — ${firstSentence(sl.description)}` : ''}`;
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
/** One line per v2/beta rename or removal (include 'history' for the records). */
|
|
867
|
+
function historyLines(entry) {
|
|
868
|
+
const renamedFrom = new Set((entry.renamed ?? []).map((r) => r.from));
|
|
869
|
+
// A slot row reads as a slot (`#no-data → #empty`), a removal with no
|
|
870
|
+
// single successor carries its hint (Q51.16).
|
|
871
|
+
const spell = (r, name) => (r.surface === 'slot' ? `#${name}` : name);
|
|
872
|
+
// A rename that also maps VALUES says which: `inputDirection →
|
|
873
|
+
// listLayout` alone sent 2 of 3 answerers to `list-layout="vertical"`
|
|
874
|
+
// when the map is vertical → vertical-stretch (2026-09-24).
|
|
875
|
+
const values = (r) =>
|
|
876
|
+
Object.entries(r.fix?.valueMap ?? {})
|
|
877
|
+
.filter(([from, to]) => from !== to)
|
|
878
|
+
.map(([from, to]) => `"${from}" → "${to}"`)
|
|
879
|
+
.join(', ');
|
|
880
|
+
return [
|
|
881
|
+
...(entry.renamed ?? []).map(
|
|
882
|
+
(r) =>
|
|
883
|
+
`${spell(r, r.from)} → ${spell(r, r.to)}${r.fix?.kind && r.fix.kind !== 'rename' ? ` (${r.fix.kind})` : ''}${values(r) ? ` (values: ${values(r)})` : ''}`
|
|
884
|
+
),
|
|
885
|
+
...(entry.removed ?? [])
|
|
886
|
+
.filter((r) => !renamedFrom.has(r.name))
|
|
887
|
+
.map(
|
|
888
|
+
(r) =>
|
|
889
|
+
`${spell(r, r.name)} removed${r.new ? ` → ${spell(r, r.new)}` : ''}${r.hint ? ` — ${r.hint}` : ''}`
|
|
890
|
+
),
|
|
891
|
+
];
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/** The compact `component` of the summary answer. */
|
|
895
|
+
function summarizeComponent(entry, manifest) {
|
|
896
|
+
const shared = sharedProps(manifest);
|
|
897
|
+
const own = [];
|
|
898
|
+
const common = [];
|
|
899
|
+
for (const p of entry.props ?? []) {
|
|
900
|
+
if (p.name === 'pt' || p.name.startsWith('pt:')) continue;
|
|
901
|
+
if (!p.required && shared.has(foldKey(p))) common.push(p.name);
|
|
902
|
+
else own.push(propLine(p));
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
name: entry.name,
|
|
906
|
+
...(entry.group ? { group: entry.group } : {}),
|
|
907
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
908
|
+
props: own,
|
|
909
|
+
...(common.length
|
|
910
|
+
? {
|
|
911
|
+
commonProps: `${common.join(', ')} — optional props identical (type, default, description) on 3+ components (form fields, link props); members: ['${common[0]}'] returns one whole`,
|
|
912
|
+
}
|
|
913
|
+
: {}),
|
|
914
|
+
...(entry.models?.length
|
|
915
|
+
? {
|
|
916
|
+
models: entry.models.map(
|
|
917
|
+
(m) =>
|
|
918
|
+
`v-model${m.name === 'modelValue' ? '' : `:${m.name}`}: ${shortType(m.type)}`
|
|
919
|
+
),
|
|
920
|
+
}
|
|
921
|
+
: {}),
|
|
922
|
+
...(entry.events?.length ? { events: entry.events.map(eventLine) } : {}),
|
|
923
|
+
...(entry.slots?.length ? { slots: entry.slots.map(slotLine) } : {}),
|
|
924
|
+
...(entry.exposed?.length
|
|
925
|
+
? { exposed: entry.exposed.map((x) => `${x.name}: ${shortType(x.type)}`) }
|
|
926
|
+
: {}),
|
|
927
|
+
...(entry.pt?.parts?.length
|
|
928
|
+
? {
|
|
929
|
+
pt: `parts: ${entry.pt.parts.map((x) => x.name).join(', ')}${entry.pt.states?.length ? `; states: ${entry.pt.states.map((x) => x.name).join(', ')}` : ''}. Each is a \`pt:<part>\` / \`pt:<part>:<state>\` prop; the \`pt\` object takes the same keys WITHOUT the \`pt:\` prefix: { ${ptExample(entry)} }. What a part or state is: members: ['<part>'].`,
|
|
930
|
+
}
|
|
931
|
+
: {}),
|
|
932
|
+
...(entry.cssProps?.length
|
|
933
|
+
? { cssProps: entry.cssProps.map((c) => c.name ?? c) }
|
|
934
|
+
: {}),
|
|
935
|
+
...(entry.removed?.length || entry.renamed?.length
|
|
936
|
+
? { history: historyLines(entry) }
|
|
937
|
+
: {}),
|
|
938
|
+
...(entry.attrTargets ? { attrTargets: entry.attrTargets } : {}),
|
|
939
|
+
...(entry.referenceOnly ? { referenceOnly: true } : {}),
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* `members`: the whole rows for the names asked — props, events, slots
|
|
945
|
+
* (`#name` or `name`), v-models (`v-model:sort`) and pt parts / states
|
|
946
|
+
* (`pt:row`, `row`), whose descriptions the summary line leaves out.
|
|
947
|
+
*/
|
|
948
|
+
function pickMembers(entry, names) {
|
|
949
|
+
const norm = (n) =>
|
|
950
|
+
String(n)
|
|
951
|
+
.trim()
|
|
952
|
+
.replace(/^#/, '')
|
|
953
|
+
.replace(/^v-model:/, '')
|
|
954
|
+
.toLowerCase();
|
|
955
|
+
const wanted = names.map(norm);
|
|
956
|
+
const hit = new Set();
|
|
957
|
+
const take = (list, key = (x) => x.name) =>
|
|
958
|
+
(list ?? []).filter((x) => {
|
|
959
|
+
const k = String(key(x)).toLowerCase();
|
|
960
|
+
const ok = wanted.includes(k);
|
|
961
|
+
if (ok) wanted.forEach((w, i) => w === k && hit.add(i));
|
|
962
|
+
return ok;
|
|
963
|
+
});
|
|
964
|
+
const props = take(entry.props);
|
|
965
|
+
const events = take(entry.events);
|
|
966
|
+
const slots = take(entry.slots);
|
|
967
|
+
const models = (entry.models ?? []).filter((m) =>
|
|
968
|
+
names.some(
|
|
969
|
+
(n) =>
|
|
970
|
+
String(n).trim() ===
|
|
971
|
+
(m.name === 'modelValue' ? 'v-model' : `v-model:${m.name}`)
|
|
972
|
+
)
|
|
973
|
+
);
|
|
974
|
+
names.forEach((n, i) => {
|
|
975
|
+
if (/^v-model$/.test(String(n).trim()) && models.length) hit.add(i);
|
|
976
|
+
});
|
|
977
|
+
// pt: `row`, `pt:row`, `row:selected` / `pt:row:selected` (the part AND
|
|
978
|
+
// the state, both with their descriptions).
|
|
979
|
+
const ptWanted = names.map((n) =>
|
|
980
|
+
String(n).trim().replace(/^pt:/, '').toLowerCase().split(':')
|
|
981
|
+
);
|
|
982
|
+
const parts = (entry.pt?.parts ?? []).filter((x) =>
|
|
983
|
+
ptWanted.some(([part], i) => {
|
|
984
|
+
const ok = part === x.name.toLowerCase();
|
|
985
|
+
if (ok) hit.add(i);
|
|
986
|
+
return ok;
|
|
987
|
+
})
|
|
988
|
+
);
|
|
989
|
+
const states = (entry.pt?.states ?? []).filter((x) =>
|
|
990
|
+
ptWanted.some((segments, i) => {
|
|
991
|
+
const ok = segments.at(-1) === x.name.toLowerCase();
|
|
992
|
+
if (ok) hit.add(i);
|
|
993
|
+
return ok;
|
|
994
|
+
})
|
|
995
|
+
);
|
|
996
|
+
const notFound = names.filter((_, i) => !hit.has(i));
|
|
997
|
+
// Names built from a requested WORD (`loading` → `interactiveWhileLoading`,
|
|
998
|
+
// `loadingText`): a members-only call skips the summary, and a blind run
|
|
999
|
+
// concluded v2's enabled-while-loading had no v3 equivalent. Whole camel
|
|
1000
|
+
// words only (`key` does not match `keyboardNavigation`).
|
|
1001
|
+
const words = (n) =>
|
|
1002
|
+
n
|
|
1003
|
+
.replace(/^#/, '')
|
|
1004
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
1005
|
+
.toLowerCase()
|
|
1006
|
+
.split(/[\s:_-]+/);
|
|
1007
|
+
const asked = names.map(norm).filter((n) => n.length >= 3);
|
|
1008
|
+
const related = [
|
|
1009
|
+
...(entry.props ?? []).map((p) => p.name),
|
|
1010
|
+
...(entry.events ?? []).map((e) => e.name),
|
|
1011
|
+
...(entry.slots ?? []).map((sl) => `#${sl.name}`),
|
|
1012
|
+
].filter(
|
|
1013
|
+
(n) =>
|
|
1014
|
+
!n.startsWith('pt:') &&
|
|
1015
|
+
!asked.includes(n.replace(/^#/, '').toLowerCase()) &&
|
|
1016
|
+
asked.some((a) => words(n).includes(a))
|
|
1017
|
+
);
|
|
1018
|
+
return {
|
|
1019
|
+
...(props.length ? { props } : {}),
|
|
1020
|
+
...(events.length ? { events } : {}),
|
|
1021
|
+
...(slots.length ? { slots } : {}),
|
|
1022
|
+
...(models.length ? { models } : {}),
|
|
1023
|
+
...(parts.length || states.length
|
|
1024
|
+
? {
|
|
1025
|
+
pt: {
|
|
1026
|
+
...(parts.length ? { parts } : {}),
|
|
1027
|
+
...(states.length ? { states } : {}),
|
|
1028
|
+
},
|
|
1029
|
+
}
|
|
1030
|
+
: {}),
|
|
1031
|
+
...(related.length ? { related } : {}),
|
|
1032
|
+
...(notFound.length ? { notFound } : {}),
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
667
1035
|
|
|
668
1036
|
/**
|
|
669
1037
|
* `get_component({ name, sections?, include?, maxChars? })`.
|
|
@@ -679,7 +1047,14 @@ const ALWAYS_SECTIONS = [/^gotchas/i, /^accessibility/i];
|
|
|
679
1047
|
* sections, the component tree, the CSS variables with their descriptions
|
|
680
1048
|
* and the whole contract come on request.
|
|
681
1049
|
*/
|
|
682
|
-
function getComponent({
|
|
1050
|
+
function getComponent({
|
|
1051
|
+
name,
|
|
1052
|
+
sections,
|
|
1053
|
+
include,
|
|
1054
|
+
maxChars,
|
|
1055
|
+
detail,
|
|
1056
|
+
members,
|
|
1057
|
+
} = {}) {
|
|
683
1058
|
const manifest = loadComponentsManifest();
|
|
684
1059
|
const resolved = resolveComponent(manifest, name);
|
|
685
1060
|
const entry = resolved?.entry;
|
|
@@ -807,56 +1182,144 @@ function getComponent({ name, sections, include, maxChars } = {}) {
|
|
|
807
1182
|
const ptRows = entry.props.filter(
|
|
808
1183
|
(p) => p.name === 'pt' || p.name.startsWith('pt:')
|
|
809
1184
|
);
|
|
1185
|
+
const found = {
|
|
1186
|
+
found: true,
|
|
1187
|
+
...(resolved.resolvedFrom ? { resolvedFrom: resolved.resolvedFrom } : {}),
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
// `members`: exactly the rows asked for, nothing else.
|
|
1191
|
+
if (members?.length) {
|
|
1192
|
+
const picked = pickMembers(entry, members);
|
|
1193
|
+
// Rows past the cap are named, not dropped.
|
|
1194
|
+
for (const kind of ['props', 'events', 'slots']) {
|
|
1195
|
+
if (!picked[kind]) continue;
|
|
1196
|
+
const { kept, ...rest } = capParts(picked[kind], 'Members');
|
|
1197
|
+
picked[kind] = kept;
|
|
1198
|
+
if (rest.remainingMembers)
|
|
1199
|
+
picked.remainingMembers = [
|
|
1200
|
+
...(picked.remainingMembers ?? []),
|
|
1201
|
+
...rest.remainingMembers,
|
|
1202
|
+
];
|
|
1203
|
+
}
|
|
1204
|
+
return { ...found, name: entry.name, ...picked };
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const full = detail === 'full';
|
|
1208
|
+
const asked = (sections?.length ?? 0) + (include?.length ?? 0) > 0;
|
|
1209
|
+
const { picked, missing } = pickSections(guide, sections);
|
|
1210
|
+
const guideOutline = guide
|
|
1211
|
+
.filter((s) => s.level === 3)
|
|
1212
|
+
.map((s) => ({ title: s.title, chars: s.text.length }));
|
|
1213
|
+
|
|
1214
|
+
// A follow-up for sections / include parts returns ONLY those parts: the
|
|
1215
|
+
// summary was already read (it used to be resent whole: BbTable + one
|
|
1216
|
+
// section was 49K chars for a ~9K section).
|
|
1217
|
+
if (asked && !full) {
|
|
1218
|
+
return {
|
|
1219
|
+
...found,
|
|
1220
|
+
name: entry.name,
|
|
1221
|
+
...(picked.length
|
|
1222
|
+
? (() => {
|
|
1223
|
+
const { kept, ...rest } = capParts(
|
|
1224
|
+
picked.map((sec) => sectionAnswer(sec, guide, maxChars)),
|
|
1225
|
+
'Sections'
|
|
1226
|
+
);
|
|
1227
|
+
return { sections: kept, ...rest };
|
|
1228
|
+
})()
|
|
1229
|
+
: {}),
|
|
1230
|
+
// A missed title gets the outline, never the whole answer.
|
|
1231
|
+
...(missing.length ? { notFound: missing, outline: guideOutline } : {}),
|
|
1232
|
+
...(wanted.has('tree') && entry.tree ? { tree: entry.tree } : {}),
|
|
1233
|
+
...(wanted.has('cssProps') && entry.cssProps
|
|
1234
|
+
? { cssProps: entry.cssProps }
|
|
1235
|
+
: {}),
|
|
1236
|
+
...(wanted.has('css') && entry.css ? { css: entry.css } : {}),
|
|
1237
|
+
...(wanted.has('ptProps')
|
|
1238
|
+
? (() => {
|
|
1239
|
+
const { kept, ...rest } = capParts(ptRows, 'PtProps');
|
|
1240
|
+
return { ptProps: kept, ...rest, pt: entry.pt };
|
|
1241
|
+
})()
|
|
1242
|
+
: {}),
|
|
1243
|
+
...(wanted.has('history')
|
|
1244
|
+
? { removed: entry.removed ?? [], renamed: entry.renamed ?? [] }
|
|
1245
|
+
: {}),
|
|
1246
|
+
...(wanted.has('contract')
|
|
1247
|
+
? (() => {
|
|
1248
|
+
const c = truncateMarkdown(
|
|
1249
|
+
raw,
|
|
1250
|
+
maxChars,
|
|
1251
|
+
'Pass maxChars: 0 for the whole contract.'
|
|
1252
|
+
);
|
|
1253
|
+
return {
|
|
1254
|
+
contract: c.content,
|
|
1255
|
+
...(c.truncated
|
|
1256
|
+
? { contractTruncated: true, contractHint: c.hint }
|
|
1257
|
+
: {}),
|
|
1258
|
+
};
|
|
1259
|
+
})()
|
|
1260
|
+
: {}),
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
if (!full) {
|
|
1265
|
+
const kept = guide.filter((s) =>
|
|
1266
|
+
SUMMARY_SECTIONS.some((re) => re.test(s.title))
|
|
1267
|
+
);
|
|
1268
|
+
return {
|
|
1269
|
+
...found,
|
|
1270
|
+
component: summarizeComponent(entry, manifest),
|
|
1271
|
+
...(types.length > 0 ? { types } : {}),
|
|
1272
|
+
...(entry.pt?.parts?.length ? { ptTyping: ptTyping(entry) } : {}),
|
|
1273
|
+
...(seeAlso(entry, raw).length ? { seeAlso: seeAlso(entry, raw) } : {}),
|
|
1274
|
+
contractPath,
|
|
1275
|
+
guide: {
|
|
1276
|
+
sections: guideOutline,
|
|
1277
|
+
...(kept.length
|
|
1278
|
+
? { always: kept.map((s) => ({ title: s.title, text: s.text })) }
|
|
1279
|
+
: {}),
|
|
1280
|
+
},
|
|
1281
|
+
hint: `Summary: every name is listed; a line ending " …" is cut. Whole rows: members: ['<name>']; guide prose: sections: ['<title>']; include: ['tree' | 'cssProps' | 'css' | 'ptProps' | 'history' | 'contract']; detail: 'full' for the whole entry.`,
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/*
|
|
1286
|
+
* The whole entry is the shipped agent file, dist/ai/manifest/components/
|
|
1287
|
+
* <Name>.json (Q56.46: one builder), with the MCP's own compaction on top:
|
|
1288
|
+
* the types go in the answer's `types` (with import lines), the cssProps
|
|
1289
|
+
* as names, the register as one history line per old name.
|
|
1290
|
+
*/
|
|
1291
|
+
const { types: _agentTypes, ...agent } = agentComponent(entry, manifest);
|
|
810
1292
|
const component = {
|
|
811
|
-
...
|
|
812
|
-
|
|
813
|
-
?
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1293
|
+
...agent,
|
|
1294
|
+
...(wanted.has('ptProps')
|
|
1295
|
+
? {
|
|
1296
|
+
props: entry.props.map(({ required, ...prop }) =>
|
|
1297
|
+
required ? { ...prop, required } : prop
|
|
1298
|
+
),
|
|
1299
|
+
}
|
|
818
1300
|
: {}),
|
|
1301
|
+
...(entry.cssProps
|
|
1302
|
+
? {
|
|
1303
|
+
cssProps: wanted.has('cssProps')
|
|
1304
|
+
? entry.cssProps
|
|
1305
|
+
: entry.cssProps.map((c) => c.name ?? c),
|
|
1306
|
+
}
|
|
1307
|
+
: {}),
|
|
1308
|
+
...(wanted.has('tree') && entry.tree ? { tree: entry.tree } : {}),
|
|
1309
|
+
// The rendered `.bb-*` classes (Q46.5) are for tools (`check`,
|
|
1310
|
+
// `validate`); an agent restyles through pt and the locals.
|
|
1311
|
+
...(wanted.has('css') && entry.css ? { css: entry.css } : {}),
|
|
819
1312
|
};
|
|
820
|
-
if (!wanted.has('tree')) delete component.tree;
|
|
821
|
-
// The rendered `.bb-*` classes (Q46.5) are for tools (`check`, `validate`);
|
|
822
|
-
// an agent restyles through pt and the locals. include: ['css'] lists them.
|
|
823
|
-
if (!wanted.has('css')) delete component.css;
|
|
824
1313
|
// `removed` and `renamed` repeat each other with migration metadata; one
|
|
825
1314
|
// line per old name answers "what happened to X" (include 'history' for
|
|
826
1315
|
// the records the fixers use).
|
|
827
|
-
if (
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
const renamedFrom = new Set((entry.renamed ?? []).map((r) => r.from));
|
|
832
|
-
// A slot row reads as a slot (`#no-data → #empty`), a removal with no
|
|
833
|
-
// single successor carries its hint (Q51.16).
|
|
834
|
-
const spell = (r, name) => (r.surface === 'slot' ? `#${name}` : name);
|
|
835
|
-
// A rename that also maps VALUES says which: `inputDirection →
|
|
836
|
-
// listLayout` alone sent 2 of 3 answerers to `list-layout="vertical"`
|
|
837
|
-
// when the map is vertical → vertical-stretch (2026-09-24).
|
|
838
|
-
const values = (r) =>
|
|
839
|
-
Object.entries(r.fix?.valueMap ?? {})
|
|
840
|
-
.filter(([from, to]) => from !== to)
|
|
841
|
-
.map(([from, to]) => `"${from}" → "${to}"`)
|
|
842
|
-
.join(', ');
|
|
843
|
-
component.history = [
|
|
844
|
-
...(entry.renamed ?? []).map(
|
|
845
|
-
(r) =>
|
|
846
|
-
`${spell(r, r.from)} → ${spell(r, r.to)}${r.fix?.kind && r.fix.kind !== 'rename' ? ` (${r.fix.kind})` : ''}${values(r) ? ` (values: ${values(r)})` : ''}`
|
|
847
|
-
),
|
|
848
|
-
...(entry.removed ?? [])
|
|
849
|
-
.filter((r) => !renamedFrom.has(r.name))
|
|
850
|
-
.map(
|
|
851
|
-
(r) =>
|
|
852
|
-
`${spell(r, r.name)} removed${r.new ? ` → ${spell(r, r.new)}` : ''}${r.hint ? ` — ${r.hint}` : ''}`
|
|
853
|
-
),
|
|
854
|
-
];
|
|
1316
|
+
if (wanted.has('history')) {
|
|
1317
|
+
if (entry.renamed) component.renamed = entry.renamed;
|
|
1318
|
+
} else if (entry.removed?.length || entry.renamed?.length) {
|
|
1319
|
+
component.history = historyLines(entry);
|
|
855
1320
|
delete component.removed;
|
|
856
|
-
delete component.renamed;
|
|
857
1321
|
}
|
|
858
1322
|
|
|
859
|
-
const { picked, missing } = pickSections(guide, sections);
|
|
860
1323
|
const always = guide.filter(
|
|
861
1324
|
(s) => ALWAYS_SECTIONS.some((re) => re.test(s.title)) && !picked.includes(s)
|
|
862
1325
|
);
|
|
@@ -989,8 +1452,16 @@ function* eachTokenDeclaration(css) {
|
|
|
989
1452
|
const stack = [];
|
|
990
1453
|
let buffer = '';
|
|
991
1454
|
let quote = null;
|
|
1455
|
+
// Parenthesis depth: CSS `if()` separates its branches with `;`, and a
|
|
1456
|
+
// branch may read `style(--bb-x: …)`. Only a top-level `;` ends a
|
|
1457
|
+
// declaration, and only a name at its START is the property it sets
|
|
1458
|
+
// (Q56.48: `--fg-hover: if(style(--bb-primary-fg: …)…)` was served as a
|
|
1459
|
+
// local rebind of --bb-primary-fg).
|
|
1460
|
+
let depth = 0;
|
|
992
1461
|
const declaration = () => {
|
|
993
|
-
const m = buffer
|
|
1462
|
+
const m = buffer
|
|
1463
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
1464
|
+
.match(/^\s*(--bb-[a-z0-9-]+)\s*:\s*([\s\S]+)/);
|
|
994
1465
|
buffer = '';
|
|
995
1466
|
return m ? { name: m[1], value: m[2].trim() } : null;
|
|
996
1467
|
};
|
|
@@ -1003,6 +1474,11 @@ function* eachTokenDeclaration(css) {
|
|
|
1003
1474
|
if (char === '"' || char === "'") {
|
|
1004
1475
|
quote = char;
|
|
1005
1476
|
buffer += char;
|
|
1477
|
+
} else if (char === '(' || char === ')') {
|
|
1478
|
+
depth = Math.max(0, depth + (char === '(' ? 1 : -1));
|
|
1479
|
+
buffer += char;
|
|
1480
|
+
} else if (depth > 0) {
|
|
1481
|
+
buffer += char;
|
|
1006
1482
|
} else if (char === '{') {
|
|
1007
1483
|
// A comment before a rule would otherwise prefix its selector or
|
|
1008
1484
|
// hide the `@supports` it opens.
|
|
@@ -1178,7 +1654,7 @@ function listGuides() {
|
|
|
1178
1654
|
|
|
1179
1655
|
/** Guides this size or larger answer with an outline unless asked for more. */
|
|
1180
1656
|
// The router (13 KB, the documented entry point) still comes whole.
|
|
1181
|
-
const GUIDE_OUTLINE_OVER =
|
|
1657
|
+
const GUIDE_OUTLINE_OVER = 8000; // was 16 000: ai-router.md (16K, "a tiny map") came back whole (MCP panel 2026-09-26)
|
|
1182
1658
|
|
|
1183
1659
|
/**
|
|
1184
1660
|
* The shipped guide a pointer means. Pointers come from everywhere — the
|
|
@@ -1205,6 +1681,38 @@ function resolveGuidePath(name) {
|
|
|
1205
1681
|
|
|
1206
1682
|
/** `get_guide({ name, sections?, maxChars? })`. */
|
|
1207
1683
|
function getGuide({ name, sections, maxChars } = {}) {
|
|
1684
|
+
// The package README, which search_docs indexes (MCP panel 2026-09-26).
|
|
1685
|
+
if (/^readme(\.md)?$/i.test(String(name ?? '').trim())) {
|
|
1686
|
+
const readme = join(PACKAGE_ROOT, 'README.md');
|
|
1687
|
+
if (existsSync(readme)) {
|
|
1688
|
+
const markdown = readFileSync(readme, 'utf-8');
|
|
1689
|
+
const all = splitSections(markdown);
|
|
1690
|
+
if (sections?.length) {
|
|
1691
|
+
const { picked, missing } = pickSections(all, sections);
|
|
1692
|
+
return {
|
|
1693
|
+
found: true,
|
|
1694
|
+
name: 'README',
|
|
1695
|
+
...(() => {
|
|
1696
|
+
const { kept, ...rest } = capParts(
|
|
1697
|
+
picked.map((sec) => sectionAnswer(sec, all, maxChars)),
|
|
1698
|
+
'Sections'
|
|
1699
|
+
);
|
|
1700
|
+
return { sections: kept, ...rest };
|
|
1701
|
+
})(),
|
|
1702
|
+
...(missing.length
|
|
1703
|
+
? { notFound: missing, outline: outline(all) }
|
|
1704
|
+
: {}),
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
return {
|
|
1708
|
+
found: true,
|
|
1709
|
+
name: 'README',
|
|
1710
|
+
chars: markdown.length,
|
|
1711
|
+
outline: outline(all),
|
|
1712
|
+
hint: "Read what you need: get_guide({ name: 'README', sections: ['<title>'] }).",
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1208
1716
|
const resolved = resolveGuidePath(name);
|
|
1209
1717
|
if (!resolved) {
|
|
1210
1718
|
const all = collectGuidePaths(join(AI_ROOT, 'guides'));
|
|
@@ -1216,10 +1724,25 @@ function getGuide({ name, sections, maxChars } = {}) {
|
|
|
1216
1724
|
.some((w) => w.length > 3 && p.toLowerCase().includes(w))
|
|
1217
1725
|
)
|
|
1218
1726
|
.slice(0, 5);
|
|
1727
|
+
// A dead end cost the agent a turn (`get_guide('theming')`): answer with
|
|
1728
|
+
// the sections that match the name as a query.
|
|
1729
|
+
let found = [];
|
|
1730
|
+
try {
|
|
1731
|
+
found = searchSections(docIndex(), String(name ?? ''), 3).results;
|
|
1732
|
+
} catch {
|
|
1733
|
+
/* an unbuilt package has no index: the plain miss still answers */
|
|
1734
|
+
}
|
|
1735
|
+
const hits = found.map((r) => ({
|
|
1736
|
+
section: `${r.source} › ${r.section}`,
|
|
1737
|
+
read: r.read?.tool
|
|
1738
|
+
? `${r.read.tool}(${JSON.stringify(r.read.args ?? {})})`
|
|
1739
|
+
: r.read,
|
|
1740
|
+
}));
|
|
1219
1741
|
return {
|
|
1220
1742
|
found: false,
|
|
1221
1743
|
error: `Guide not found: ${name}`,
|
|
1222
1744
|
...(near.length ? { didYouMean: near } : {}),
|
|
1745
|
+
...(hits.length ? { related: hits } : {}),
|
|
1223
1746
|
hint: 'Call list_guides for the names, or search_docs to find the section by topic.',
|
|
1224
1747
|
};
|
|
1225
1748
|
}
|
|
@@ -1236,7 +1759,13 @@ function getGuide({ name, sections, maxChars } = {}) {
|
|
|
1236
1759
|
const { picked, missing } = pickSections(all, sections);
|
|
1237
1760
|
return {
|
|
1238
1761
|
...base,
|
|
1239
|
-
|
|
1762
|
+
...(() => {
|
|
1763
|
+
const { kept, ...rest } = capParts(
|
|
1764
|
+
picked.map((sec) => sectionAnswer(sec, all, maxChars)),
|
|
1765
|
+
'Sections'
|
|
1766
|
+
);
|
|
1767
|
+
return { sections: kept, ...rest };
|
|
1768
|
+
})(),
|
|
1240
1769
|
...(missing.length ? { notFound: missing, outline: outline(all) } : {}),
|
|
1241
1770
|
};
|
|
1242
1771
|
}
|
|
@@ -1336,7 +1865,11 @@ function docIndex() {
|
|
|
1336
1865
|
source: `component ${component.name}`,
|
|
1337
1866
|
section: `prop ${prop.name}`,
|
|
1338
1867
|
text: `${prop.name}: ${prop.type}${prop.default ? ` = ${prop.default}` : ''}. ${prop.description ?? ''}`,
|
|
1339
|
-
|
|
1868
|
+
// The one row, not the component (~0.5K instead of up to 39K).
|
|
1869
|
+
read: {
|
|
1870
|
+
tool: 'get_component',
|
|
1871
|
+
args: { name: component.name, members: [prop.name] },
|
|
1872
|
+
},
|
|
1340
1873
|
});
|
|
1341
1874
|
}
|
|
1342
1875
|
for (const event of component.events ?? []) {
|
|
@@ -1344,7 +1877,10 @@ function docIndex() {
|
|
|
1344
1877
|
source: `component ${component.name}`,
|
|
1345
1878
|
section: `event ${event.name}`,
|
|
1346
1879
|
text: `@${event.name} ${event.signature}. ${event.description ?? ''}`,
|
|
1347
|
-
read: {
|
|
1880
|
+
read: {
|
|
1881
|
+
tool: 'get_component',
|
|
1882
|
+
args: { name: component.name, members: [event.name] },
|
|
1883
|
+
},
|
|
1348
1884
|
});
|
|
1349
1885
|
}
|
|
1350
1886
|
/*
|
|
@@ -1489,9 +2025,11 @@ function docIndex() {
|
|
|
1489
2025
|
}
|
|
1490
2026
|
const readme = join(PACKAGE_ROOT, 'README.md');
|
|
1491
2027
|
if (existsSync(readme)) {
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
2028
|
+
// Served by get_guide under the name 'README' (it used to point at a
|
|
2029
|
+
// "read file" call, which is not a tool).
|
|
2030
|
+
addSections('README', readFileSync(readme, 'utf-8'), (title) => ({
|
|
2031
|
+
tool: 'get_guide',
|
|
2032
|
+
args: { name: 'README', sections: [title] },
|
|
1495
2033
|
}));
|
|
1496
2034
|
}
|
|
1497
2035
|
cachedDocIndex = entries;
|
|
@@ -1500,13 +2038,16 @@ function docIndex() {
|
|
|
1500
2038
|
|
|
1501
2039
|
function searchDocs({ query, limit } = {}) {
|
|
1502
2040
|
const max = Number.isFinite(limit) && limit > 0 ? Math.min(limit, 20) : 8;
|
|
1503
|
-
const {
|
|
2041
|
+
const { total, results } = searchSections(docIndex(), query, max);
|
|
2042
|
+
// `read` as the call itself, one string: the object form cost ~100 chars a
|
|
2043
|
+
// result and agents copy the call anyway.
|
|
2044
|
+
const call = (read) =>
|
|
2045
|
+
read?.tool ? `${read.tool}(${JSON.stringify(read.args ?? {})})` : read;
|
|
1504
2046
|
return {
|
|
1505
2047
|
query: query ?? '',
|
|
1506
|
-
tokens,
|
|
1507
2048
|
count: results.length,
|
|
1508
2049
|
...(total > results.length ? { totalMatches: total } : {}),
|
|
1509
|
-
results,
|
|
2050
|
+
results: results.map((r) => ({ ...r, read: call(r.read) })),
|
|
1510
2051
|
hint: results.length
|
|
1511
2052
|
? 'Each result is one section; `read` is the call that returns it whole. Prefer the top results: they match the most query words.'
|
|
1512
2053
|
: 'Nothing matched. Use fewer, more specific words (a prop, option or component name), or search_components for components.',
|
|
@@ -1514,7 +2055,7 @@ function searchDocs({ query, limit } = {}) {
|
|
|
1514
2055
|
}
|
|
1515
2056
|
|
|
1516
2057
|
/** `get_composable({ name })` — one composable's doc (useToast, useConfirm…). */
|
|
1517
|
-
function getComposable({ name } = {}) {
|
|
2058
|
+
function getComposable({ name, sections, maxChars } = {}) {
|
|
1518
2059
|
const dir = join(AI_ROOT, 'composables');
|
|
1519
2060
|
const files = existsSync(dir)
|
|
1520
2061
|
? readdirSync(dir).filter((f) => f.endsWith('.md'))
|
|
@@ -1540,12 +2081,44 @@ function getComposable({ name } = {}) {
|
|
|
1540
2081
|
const host = frontmatter(markdown).summary?.match(
|
|
1541
2082
|
/Requires (exactly one <Bb\w+ \/> host[^.]*)\./
|
|
1542
2083
|
)?.[1];
|
|
1543
|
-
|
|
2084
|
+
const base = {
|
|
1544
2085
|
found: true,
|
|
1545
2086
|
name: file.slice(0, -3),
|
|
1546
2087
|
...(host ? { requires: host } : {}),
|
|
1547
|
-
content: stripFrontmatter(markdown),
|
|
1548
2088
|
};
|
|
2089
|
+
const body = stripFrontmatter(markdown);
|
|
2090
|
+
const all = splitSections(markdown);
|
|
2091
|
+
if (sections?.length) {
|
|
2092
|
+
const { picked, missing } = pickSections(all, sections);
|
|
2093
|
+
return {
|
|
2094
|
+
...base,
|
|
2095
|
+
...(() => {
|
|
2096
|
+
const { kept, ...rest } = capParts(
|
|
2097
|
+
picked.map((sec) => sectionAnswer(sec, all, maxChars)),
|
|
2098
|
+
'Sections'
|
|
2099
|
+
);
|
|
2100
|
+
return { sections: kept, ...rest };
|
|
2101
|
+
})(),
|
|
2102
|
+
...(missing.length ? { notFound: missing, outline: outline(all) } : {}),
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
// Same rule as get_guide (MCP panel 2026-09-26): past 8K, the intro and the
|
|
2106
|
+
// outline; useConfirm's doc was 18K in one answer.
|
|
2107
|
+
if (maxChars === undefined && body.length >= GUIDE_OUTLINE_OVER) {
|
|
2108
|
+
return {
|
|
2109
|
+
...base,
|
|
2110
|
+
chars: body.length,
|
|
2111
|
+
intro: body.split(/^## /m)[0].trim().slice(0, 3000),
|
|
2112
|
+
outline: outline(all),
|
|
2113
|
+
hint: `Read what you need: get_composable({ name: '${base.name}', sections: ['<title from outline>'] }); maxChars: 0 returns it whole.`,
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
const { content, ...cut } = truncateMarkdown(
|
|
2117
|
+
markdown,
|
|
2118
|
+
maxChars,
|
|
2119
|
+
'The cut tail is later sections.'
|
|
2120
|
+
);
|
|
2121
|
+
return { ...base, content: stripFrontmatter(content), ...cut };
|
|
1549
2122
|
}
|
|
1550
2123
|
|
|
1551
2124
|
function listRecipes({ platform } = {}) {
|
|
@@ -1590,7 +2163,7 @@ function listRecipes({ platform } = {}) {
|
|
|
1590
2163
|
}
|
|
1591
2164
|
|
|
1592
2165
|
/** `get_recipe({ platform, name })` — read one recipe's markdown content. */
|
|
1593
|
-
function getRecipe({ platform, name, sections, maxChars } = {}) {
|
|
2166
|
+
function getRecipe({ platform, name, sections, files, maxChars } = {}) {
|
|
1594
2167
|
if (!RECIPE_PLATFORMS.includes(platform)) {
|
|
1595
2168
|
return {
|
|
1596
2169
|
found: false,
|
|
@@ -1628,15 +2201,102 @@ function getRecipe({ platform, name, sections, maxChars } = {}) {
|
|
|
1628
2201
|
found: true,
|
|
1629
2202
|
platform,
|
|
1630
2203
|
name: fileName.slice(0, -3),
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
2204
|
+
...(() => {
|
|
2205
|
+
const { kept, ...rest } = capParts(
|
|
2206
|
+
picked.map((sec) => sectionAnswer(sec, all, maxChars)),
|
|
2207
|
+
'Sections'
|
|
2208
|
+
);
|
|
2209
|
+
return { sections: kept, ...rest };
|
|
2210
|
+
})(),
|
|
1635
2211
|
...(missing.length ? { notFound: missing, outline: outline(all) } : {}),
|
|
1636
2212
|
};
|
|
1637
2213
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
2214
|
+
/*
|
|
2215
|
+
* OUTLINE FIRST (MCP panel 2026-09-26, owner ruling M3). Whole recipes were
|
|
2216
|
+
* median ~59K chars, max 129K (~32K tokens): past Claude Code's 25K cut, so
|
|
2217
|
+
* "whole by default" did not reach agents whole anyway. The default is the
|
|
2218
|
+
* prose that explains the page (every section outside "The code"), the code
|
|
2219
|
+
* files with their sizes, and the shipped file's path — an agent that can
|
|
2220
|
+
* read files copies it from there; one that can't asks for `files`.
|
|
2221
|
+
* `maxChars: 0` still returns the markdown whole.
|
|
2222
|
+
*/
|
|
2223
|
+
const all = splitSections(markdown);
|
|
2224
|
+
const code = all.find(
|
|
2225
|
+
(sec) => sec.level === 2 && /^the (code|files)\b/i.test(sec.title)
|
|
2226
|
+
);
|
|
2227
|
+
const codeFiles = code
|
|
2228
|
+
? all.filter(
|
|
2229
|
+
(sec) => sec.level === 3 && code.text.includes(sec.text) && sec !== code
|
|
2230
|
+
)
|
|
2231
|
+
: [];
|
|
2232
|
+
if (files?.length) {
|
|
2233
|
+
// An exact title first, else a substring: `RejectDialog.vue` must not
|
|
2234
|
+
// also return BulkRejectDialog.vue.
|
|
2235
|
+
const hits = [];
|
|
2236
|
+
for (const f of files) {
|
|
2237
|
+
const w = String(f).toLowerCase();
|
|
2238
|
+
const exact = codeFiles.filter((sec) => sec.title.toLowerCase() === w);
|
|
2239
|
+
for (const sec of exact.length
|
|
2240
|
+
? exact
|
|
2241
|
+
: codeFiles.filter((sec) => sec.title.toLowerCase().includes(w)))
|
|
2242
|
+
if (!hits.includes(sec)) hits.push(sec);
|
|
2243
|
+
}
|
|
2244
|
+
const missingFiles = files.filter(
|
|
2245
|
+
(f) =>
|
|
2246
|
+
!codeFiles.some((sec) =>
|
|
2247
|
+
sec.title.toLowerCase().includes(String(f).toLowerCase())
|
|
2248
|
+
)
|
|
2249
|
+
);
|
|
2250
|
+
return {
|
|
2251
|
+
found: true,
|
|
2252
|
+
platform,
|
|
2253
|
+
name: fileName.slice(0, -3),
|
|
2254
|
+
...(() => {
|
|
2255
|
+
const { kept, ...rest } = capParts(
|
|
2256
|
+
hits.map((sec) => ({ title: sec.title, text: sec.text })),
|
|
2257
|
+
'Files'
|
|
2258
|
+
);
|
|
2259
|
+
return { files: kept, ...rest };
|
|
2260
|
+
})(),
|
|
2261
|
+
...(missingFiles.length
|
|
2262
|
+
? {
|
|
2263
|
+
notFound: missingFiles,
|
|
2264
|
+
available: codeFiles.map((sec) => sec.title),
|
|
2265
|
+
}
|
|
2266
|
+
: {}),
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
if (maxChars == null && code) {
|
|
2270
|
+
const prose = all.filter((sec) => sec.level === 2 && sec !== code);
|
|
2271
|
+
const meta = frontmatter(markdown);
|
|
2272
|
+
// The words between the title and the first ## (the adversarial review:
|
|
2273
|
+
// "read the XSRF-TOKEN cookie … or every upload 419s" lived there), and
|
|
2274
|
+
// the lead-in of the code section before its first file.
|
|
2275
|
+
const body = stripFrontmatter(markdown);
|
|
2276
|
+
const intro = body
|
|
2277
|
+
.split(/^## /m)[0]
|
|
2278
|
+
.replace(/^# .*$/m, '')
|
|
2279
|
+
.trim();
|
|
2280
|
+
const codeIntro = code.own.replace(/^## .*$/m, '').trim();
|
|
2281
|
+
return {
|
|
2282
|
+
found: true,
|
|
2283
|
+
platform,
|
|
2284
|
+
name: fileName.slice(0, -3),
|
|
2285
|
+
...(meta.title ? { title: meta.title } : {}),
|
|
2286
|
+
path: filePath,
|
|
2287
|
+
chars: markdown.length,
|
|
2288
|
+
...(intro ? { intro } : {}),
|
|
2289
|
+
prose: prose.map((sec) => ({ title: sec.title, text: sec.text })),
|
|
2290
|
+
...(codeIntro ? { codeIntro } : {}),
|
|
2291
|
+
files: codeFiles.map((sec) => ({
|
|
2292
|
+
title: sec.title,
|
|
2293
|
+
chars: sec.text.length,
|
|
2294
|
+
})),
|
|
2295
|
+
hint: `Copy the page whole: read the file at \`path\`, or fetch the code with files: ['<title>'] (a few per call). maxChars: 0 returns the whole markdown in one answer.`,
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
// Whole on request (review D6-14): the old 40 000-char default cut 7 of 9
|
|
2299
|
+
// vue recipes inside their page code.
|
|
1640
2300
|
const { content, ...meta } = truncateMarkdown(
|
|
1641
2301
|
markdown,
|
|
1642
2302
|
maxChars ?? 0,
|
|
@@ -1758,10 +2418,96 @@ function compactChanges(rows) {
|
|
|
1758
2418
|
const ROW_FIELDS =
|
|
1759
2419
|
'`surface` says what a rename names (prop | slot | pt | cssVar | class | option); `kind` is only rename vs behaviour; `firstReleasedIn` is the first npm release with the change.';
|
|
1760
2420
|
|
|
1761
|
-
|
|
1762
|
-
|
|
2421
|
+
/*
|
|
2422
|
+
* STAGED ANSWERS (MCP panel 2026-09-26). The whole v2 → v3 list was 93K chars
|
|
2423
|
+
* (~23K tokens, over Claude Code's 25K cap with the envelope) and one upgrade
|
|
2424
|
+
* hop 20–33K, 59% of it behaviour prose. Now: no range → counts per release;
|
|
2425
|
+
* a range or filter → ONE LINE per change, grouped by release, behaviour rows
|
|
2426
|
+
* as their hand-written `summary`; `detail: 'rows'` → the full rows as before.
|
|
2427
|
+
*/
|
|
2428
|
+
const CHANGES_PAGE = 60;
|
|
2429
|
+
|
|
2430
|
+
function changeLine(row, focus) {
|
|
2431
|
+
// 16 form controls in full is ~250 chars of the line; the component filter
|
|
2432
|
+
// and `detail: 'rows'` name them all.
|
|
2433
|
+
const list = row.components ?? [];
|
|
2434
|
+
// The component asked about always shows, never hidden in "+N more".
|
|
2435
|
+
const shown =
|
|
2436
|
+
focus && list.includes(focus)
|
|
2437
|
+
? [focus, ...list.filter((c) => c !== focus)]
|
|
2438
|
+
: list;
|
|
2439
|
+
const who =
|
|
2440
|
+
shown.length > 3
|
|
2441
|
+
? `${shown.slice(0, 2).join(' / ')} +${shown.length - 2} more`
|
|
2442
|
+
: shown.join(' / ');
|
|
2443
|
+
if (row.kind === 'rename') {
|
|
2444
|
+
const to = row.to == null ? 'removed' : row.to;
|
|
2445
|
+
return `${row.id} ${who ? `${who} ` : ''}${row.surface ?? 'prop'} ${row.from} → ${to}${row.note ? ` — ${firstSentence(row.note, 140)}` : ''}`;
|
|
2446
|
+
}
|
|
2447
|
+
return `${row.id} ${row.summary ?? firstSentence(row.description, 160)}`;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
function changeLines(rows, cursor, withClassMap = false, focus) {
|
|
2451
|
+
const lines = [];
|
|
2452
|
+
const classRenames = {};
|
|
2453
|
+
for (const row of rows) {
|
|
2454
|
+
if (row.surface === 'class' && String(row.id).startsWith('css:')) {
|
|
2455
|
+
classRenames[row.from] = row.to;
|
|
2456
|
+
continue;
|
|
2457
|
+
}
|
|
2458
|
+
lines.push([row.firstReleasedIn ?? 'unreleased', changeLine(row, focus)]);
|
|
2459
|
+
}
|
|
2460
|
+
// Release order, unreleased last, so pages read oldest → newest.
|
|
2461
|
+
lines.sort(([a], [b]) =>
|
|
2462
|
+
a === b
|
|
2463
|
+
? 0
|
|
2464
|
+
: a === 'unreleased'
|
|
2465
|
+
? 1
|
|
2466
|
+
: b === 'unreleased'
|
|
2467
|
+
? -1
|
|
2468
|
+
: compareVersions(a, b)
|
|
2469
|
+
);
|
|
2470
|
+
const start = Math.max(0, Number(cursor ?? 0) || 0);
|
|
2471
|
+
const page = lines.slice(start, start + CHANGES_PAGE);
|
|
2472
|
+
const byRelease = {};
|
|
2473
|
+
for (const [release, line] of page) (byRelease[release] ??= []).push(line);
|
|
2474
|
+
return {
|
|
2475
|
+
changes: byRelease,
|
|
2476
|
+
...(start + CHANGES_PAGE < lines.length
|
|
2477
|
+
? {
|
|
2478
|
+
nextCursor: String(start + CHANGES_PAGE),
|
|
2479
|
+
remaining: lines.length - start - CHANGES_PAGE,
|
|
2480
|
+
}
|
|
2481
|
+
: {}),
|
|
2482
|
+
...(Object.keys(classRenames).length && start === 0 && !withClassMap
|
|
2483
|
+
? {
|
|
2484
|
+
classRenames: `${Object.keys(classRenames).length} .bb-* class renames (the beta.36 sweep): the map with surface: 'class'`,
|
|
2485
|
+
}
|
|
2486
|
+
: {}),
|
|
2487
|
+
...(Object.keys(classRenames).length && start === 0 && withClassMap
|
|
2488
|
+
? {
|
|
2489
|
+
classRenames,
|
|
2490
|
+
classRenamesNote:
|
|
2491
|
+
'class renames from the beta.36 sweep map: modifiers follow (.old--x → .new--x); ai/guides/migration/v3-beta-css-classes.md',
|
|
2492
|
+
}
|
|
2493
|
+
: {}),
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
function listChanges({
|
|
2498
|
+
since: rawSince,
|
|
2499
|
+
until: rawUntil,
|
|
2500
|
+
component,
|
|
2501
|
+
surface,
|
|
2502
|
+
kind,
|
|
2503
|
+
detail,
|
|
2504
|
+
cursor,
|
|
2505
|
+
} = {}) {
|
|
2506
|
+
// The repo's own tags are spelled `v3.0.0-beta.33` (review D6-12). A v2
|
|
2507
|
+
// version means "the whole v2 → v3 upgrade": every row, one line each.
|
|
2508
|
+
const fromV2 = rawSince != null && /^v?2(\.|$)/.test(String(rawSince));
|
|
1763
2509
|
const since =
|
|
1764
|
-
rawSince == null ?
|
|
2510
|
+
rawSince == null || fromV2 ? undefined : String(rawSince).replace(/^v/, '');
|
|
1765
2511
|
const until =
|
|
1766
2512
|
rawUntil == null ? rawUntil : String(rawUntil).replace(/^v/, '');
|
|
1767
2513
|
if (!existsSync(CHANGELOG_PATH)) {
|
|
@@ -1793,12 +2539,114 @@ function listChanges({ since: rawSince, until: rawUntil } = {}) {
|
|
|
1793
2539
|
return {
|
|
1794
2540
|
error: `Unknown ${label} version '${value}'.`,
|
|
1795
2541
|
knownReleases: releases,
|
|
1796
|
-
hint: 'Pass a version exactly as listed in knownReleases,
|
|
2542
|
+
hint: 'Pass a version exactly as listed in knownReleases; coming from v2, pass since: "2" for every break of the v2 → v3 upgrade.',
|
|
1797
2543
|
};
|
|
1798
2544
|
}
|
|
1799
2545
|
}
|
|
1800
2546
|
|
|
1801
|
-
|
|
2547
|
+
if (
|
|
2548
|
+
since != null &&
|
|
2549
|
+
until != null &&
|
|
2550
|
+
compareVersions(String(since), String(until)) >= 0
|
|
2551
|
+
) {
|
|
2552
|
+
return {
|
|
2553
|
+
error: `since (${since}) must be older than until (${until}).`,
|
|
2554
|
+
knownReleases: releases,
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
// Resolve the component the way get_component does ('bb-select',
|
|
2558
|
+
// 'Select'), and say so when it is no component at all — an empty list
|
|
2559
|
+
// would read as "nothing breaks".
|
|
2560
|
+
if (component) {
|
|
2561
|
+
const resolved = resolveComponent(loadComponentsManifest(), component);
|
|
2562
|
+
// A retired name (BbTabsPanes) is still a key in the rows it renamed.
|
|
2563
|
+
const retiredName =
|
|
2564
|
+
!resolved?.entry && loadRetired().has(String(component).trim());
|
|
2565
|
+
if (!resolved?.entry && !retiredName) {
|
|
2566
|
+
return {
|
|
2567
|
+
error: `Unknown component '${component}'.`,
|
|
2568
|
+
didYouMean: nearestComponents(loadComponentsManifest(), component),
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
if (resolved?.entry) component = resolved.entry.name;
|
|
2572
|
+
}
|
|
2573
|
+
const rows = detail === 'rows';
|
|
2574
|
+
const filtered = (doc.breaking ?? []).filter(
|
|
2575
|
+
(row) =>
|
|
2576
|
+
(!component ||
|
|
2577
|
+
(row.components ?? []).some(
|
|
2578
|
+
(c) => c.toLowerCase() === String(component).toLowerCase()
|
|
2579
|
+
)) &&
|
|
2580
|
+
(!surface ||
|
|
2581
|
+
(row.surface ?? (row.kind === 'rename' ? 'prop' : null)) === surface) &&
|
|
2582
|
+
(!kind || row.kind === kind)
|
|
2583
|
+
);
|
|
2584
|
+
const all = filtered;
|
|
2585
|
+
const filteredBy = {
|
|
2586
|
+
...(component ? { component } : {}),
|
|
2587
|
+
...(surface ? { surface } : {}),
|
|
2588
|
+
...(kind ? { kind } : {}),
|
|
2589
|
+
};
|
|
2590
|
+
|
|
2591
|
+
// No range and no filter: counts per release — the map, not the territory.
|
|
2592
|
+
if (
|
|
2593
|
+
since == null &&
|
|
2594
|
+
until == null &&
|
|
2595
|
+
!rows &&
|
|
2596
|
+
!fromV2 &&
|
|
2597
|
+
!Object.keys(filteredBy).length
|
|
2598
|
+
) {
|
|
2599
|
+
const perRelease = {};
|
|
2600
|
+
for (const row of all) {
|
|
2601
|
+
const at = row.firstReleasedIn ?? 'unreleased';
|
|
2602
|
+
const bucket = (perRelease[at] ??= {
|
|
2603
|
+
renames: 0,
|
|
2604
|
+
behaviour: 0,
|
|
2605
|
+
classRenames: 0,
|
|
2606
|
+
});
|
|
2607
|
+
if (row.surface === 'class' && String(row.id).startsWith('css:'))
|
|
2608
|
+
bucket.classRenames++;
|
|
2609
|
+
else if (row.kind === 'rename') bucket.renames++;
|
|
2610
|
+
else bucket.behaviour++;
|
|
2611
|
+
}
|
|
2612
|
+
return {
|
|
2613
|
+
version: doc.version,
|
|
2614
|
+
upgrade: doc.upgrade,
|
|
2615
|
+
guide: doc.guide,
|
|
2616
|
+
count: all.length,
|
|
2617
|
+
releases,
|
|
2618
|
+
perRelease: Object.fromEntries(
|
|
2619
|
+
Object.entries(perRelease).sort(([a], [b]) =>
|
|
2620
|
+
a === 'unreleased'
|
|
2621
|
+
? 1
|
|
2622
|
+
: b === 'unreleased'
|
|
2623
|
+
? -1
|
|
2624
|
+
: compareVersions(a, b)
|
|
2625
|
+
)
|
|
2626
|
+
),
|
|
2627
|
+
hint: 'Counts only. What breaks when you bump: list_changes({ since: "<the version you have>" }) — one line per change; coming from v2: since: "2". One component: { component: "BbSelect" }. Full rows: detail: "rows".',
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
// A filter with no range, or `since` a v2 version: one line per change
|
|
2632
|
+
// (the v2 → v3 upgrade, or that slice of it).
|
|
2633
|
+
if (since == null && until == null && !rows) {
|
|
2634
|
+
return {
|
|
2635
|
+
version: doc.version,
|
|
2636
|
+
filteredBy,
|
|
2637
|
+
count: all.length,
|
|
2638
|
+
...changeLines(all, cursor, surface === 'class', component),
|
|
2639
|
+
guide: doc.guide,
|
|
2640
|
+
...(component
|
|
2641
|
+
? {
|
|
2642
|
+
libraryWide: (doc.breaking ?? []).filter((row) => row.libraryWide)
|
|
2643
|
+
.length,
|
|
2644
|
+
}
|
|
2645
|
+
: {}),
|
|
2646
|
+
hint: `One line per change (id, components, old → new, or the break in one sentence).${component ? ' `libraryWide` more changes touch every app (CLI, plugin, machine-readable files): list_changes({ kind: "behaviour" }).' : ''} Whole rows: detail: "rows" with the same filter. Next page: cursor: nextCursor.`,
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
|
|
1802
2650
|
// No range asked for: the whole v2 → v3 set, which is what the file has
|
|
1803
2651
|
// always meant.
|
|
1804
2652
|
if (since == null && until == null) {
|
|
@@ -1824,6 +2672,22 @@ function listChanges({ since: rawSince, until: rawUntil } = {}) {
|
|
|
1824
2672
|
return true;
|
|
1825
2673
|
});
|
|
1826
2674
|
|
|
2675
|
+
if (!rows) {
|
|
2676
|
+
return {
|
|
2677
|
+
version: doc.version,
|
|
2678
|
+
since: since ?? null,
|
|
2679
|
+
until: until ?? doc.version,
|
|
2680
|
+
...(Object.keys(filteredBy).length ? { filteredBy } : {}),
|
|
2681
|
+
count: inRange.length,
|
|
2682
|
+
...changeLines(inRange, cursor, surface === 'class', component),
|
|
2683
|
+
guide: doc.guide,
|
|
2684
|
+
hint:
|
|
2685
|
+
inRange.length === 0
|
|
2686
|
+
? 'No breaking changes recorded in that range — features and fixes are deliberately not tracked here, so an empty result means nothing BREAKS, not that nothing changed.'
|
|
2687
|
+
: 'One line per change, grouped by the release that shipped it (id, components, old → new, or the break in one sentence). Whole rows: detail: "rows". The `guide` page explains each. Next page: cursor: nextCursor.',
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
|
|
1827
2691
|
return {
|
|
1828
2692
|
version: doc.version,
|
|
1829
2693
|
since: since ?? null,
|
|
@@ -1845,7 +2709,17 @@ function toolCallback(fn) {
|
|
|
1845
2709
|
return {
|
|
1846
2710
|
// Compact JSON: indentation was a quarter of every answer (review D6).
|
|
1847
2711
|
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
1848
|
-
|
|
2712
|
+
// A miss is an error the model should act on (MCP spec: tool
|
|
2713
|
+
// execution errors go in the result with isError). Every miss
|
|
2714
|
+
// still carries its next step: didYouMean, related, hint.
|
|
2715
|
+
// A redirect (a retired v2 name, a picker verdict like BbDrawer →
|
|
2716
|
+
// BbOffCanvas) is an ANSWER, not an error: some clients collapse or
|
|
2717
|
+
// raise on isError (adversarial review 2026-09-26).
|
|
2718
|
+
isError:
|
|
2719
|
+
(result?.found === false &&
|
|
2720
|
+
!result?.retired &&
|
|
2721
|
+
!result?.notInLibrary?.length) ||
|
|
2722
|
+
typeof result?.error === 'string',
|
|
1849
2723
|
};
|
|
1850
2724
|
} catch (error) {
|
|
1851
2725
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1867,12 +2741,12 @@ const SERVER_INSTRUCTIONS = [
|
|
|
1867
2741
|
'bitboss-ui: a Vue 3 component library. Answer from these tools, never from memory — agents that guess invent props.',
|
|
1868
2742
|
"- Tailwind v4 app: set cssLayers: TAILWIND_V4_LAYERS ('bitboss-ui/vite') at setup, or utilities lose.",
|
|
1869
2743
|
'- 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.',
|
|
1870
|
-
'- Which component: search_components (empty query = the whole catalog). One component: get_component({ name })
|
|
2744
|
+
'- Which component: search_components (empty query = the whole catalog). One component: get_component({ name }), one line per member; members: [names] for whole rows, sections for prose.',
|
|
1871
2745
|
'- A type in a props table (BbTableColumn, ButtonVariantType): get_component({ name }) returns its definition and values.',
|
|
1872
2746
|
'- After writing markup: validate. Before custom CSS: list_tokens.',
|
|
1873
|
-
'- An old name (v2 or earlier-beta prop, slot, pt key, class) that stopped working: search_docs(the old name).
|
|
2747
|
+
'- An old name (v2 or earlier-beta prop, slot, pt key, class) that stopped working: search_docs(the old name). What a bump breaks: list_changes({ since: <your version> }), one line per change.',
|
|
1874
2748
|
"- pt in <script> (a const, a wrapper's prop): type it <Name>Props['pt']; Pt* types are not exported.",
|
|
1875
|
-
'- Composables
|
|
2749
|
+
'- Composables: get_composable. Whole pages: list_recipes, get_recipe (prose + files). Long docs answer with an outline: pass sections.',
|
|
1876
2750
|
].join('\n');
|
|
1877
2751
|
|
|
1878
2752
|
/** Register the knowledge-base tools on a fresh McpServer instance. */
|
|
@@ -1881,12 +2755,33 @@ function buildServer() {
|
|
|
1881
2755
|
{ name: 'bitboss-ui', version: packageVersion() },
|
|
1882
2756
|
{ capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS }
|
|
1883
2757
|
);
|
|
2758
|
+
// Every tool reads the installed package and nothing else: say so, so a
|
|
2759
|
+
// client can call them without a confirmation (MCP tool annotations).
|
|
2760
|
+
const registerTool = server.registerTool.bind(server);
|
|
2761
|
+
server.registerTool = (name, config, callback) =>
|
|
2762
|
+
registerTool(
|
|
2763
|
+
name,
|
|
2764
|
+
{
|
|
2765
|
+
title: name
|
|
2766
|
+
.split('_')
|
|
2767
|
+
.map((w, i) => (i ? w : w[0].toUpperCase() + w.slice(1)))
|
|
2768
|
+
.join(' '),
|
|
2769
|
+
...config,
|
|
2770
|
+
annotations: {
|
|
2771
|
+
readOnlyHint: true,
|
|
2772
|
+
destructiveHint: false,
|
|
2773
|
+
idempotentHint: true,
|
|
2774
|
+
openWorldHint: false,
|
|
2775
|
+
},
|
|
2776
|
+
},
|
|
2777
|
+
callback
|
|
2778
|
+
);
|
|
1884
2779
|
|
|
1885
2780
|
server.registerTool(
|
|
1886
2781
|
'search_components',
|
|
1887
2782
|
{
|
|
1888
2783
|
description:
|
|
1889
|
-
'
|
|
2784
|
+
'Find the right Bb* component by name or need ("data table" → BbTable). Also returns `notInLibrary`: verdicts for names agents invent ("BbDrawer" is BbOffCanvas). An empty result does not mean the need is uncovered: read the component-picker guide before hand-rolling anything.',
|
|
1890
2785
|
inputSchema: {
|
|
1891
2786
|
query: z
|
|
1892
2787
|
.string()
|
|
@@ -1914,7 +2809,7 @@ function buildServer() {
|
|
|
1914
2809
|
'get_component',
|
|
1915
2810
|
{
|
|
1916
2811
|
description:
|
|
1917
|
-
"One bitboss-ui component: every prop, event
|
|
2812
|
+
"One bitboss-ui component, compact: every prop, event and slot as one line (props: name, type, allowed values, default, first sentence; optional props identical across components folded into `commonProps`), v-models, pt parts and states, the types its API uses, the guide's outline and its Gotchas. Then ask for exactly what you need: `members` for whole rows, `sections` for guide prose, `include` for extra parts (each returns only that), `detail: 'full'` for everything. Also answers for a public TYPE name, a removed or renamed v2/beta name, and near-miss spellings ('bb-button', 'Button').",
|
|
1918
2813
|
inputSchema: {
|
|
1919
2814
|
name: z
|
|
1920
2815
|
.string()
|
|
@@ -1948,6 +2843,18 @@ function buildServer() {
|
|
|
1948
2843
|
.describe(
|
|
1949
2844
|
`With include: ['contract'] only — cap on the contract markdown (default ${MARKDOWN_DEFAULT_MAX_CHARS}; 0 = no cap).`
|
|
1950
2845
|
),
|
|
2846
|
+
members: z
|
|
2847
|
+
.array(z.string())
|
|
2848
|
+
.optional()
|
|
2849
|
+
.describe(
|
|
2850
|
+
"Props, events or slots to return whole, by name, e.g. ['columns', 'click:row', '#actions']. Returns only those rows."
|
|
2851
|
+
),
|
|
2852
|
+
detail: z
|
|
2853
|
+
.enum(['summary', 'full'])
|
|
2854
|
+
.optional()
|
|
2855
|
+
.describe(
|
|
2856
|
+
"'summary' (default): one line per member. 'full': the whole manifest entry, every description, the Gotchas and Accessibility sections."
|
|
2857
|
+
),
|
|
1951
2858
|
},
|
|
1952
2859
|
},
|
|
1953
2860
|
toolCallback(getComponent)
|
|
@@ -1984,7 +2891,7 @@ function buildServer() {
|
|
|
1984
2891
|
'list_changes',
|
|
1985
2892
|
{
|
|
1986
2893
|
description:
|
|
1987
|
-
'Breaking changes between
|
|
2894
|
+
'Breaking changes between bitboss-ui releases — what breaks if you bump. Call with since=<the version you are on>: one line per change, grouped by release. No arguments: counts per release. Narrow with component / surface / kind; detail: "rows" for the full records. Only BREAKING changes are tracked; features and fixes are not.',
|
|
1988
2895
|
inputSchema: {
|
|
1989
2896
|
since: z
|
|
1990
2897
|
.string()
|
|
@@ -1998,6 +2905,30 @@ function buildServer() {
|
|
|
1998
2905
|
.describe(
|
|
1999
2906
|
'Inclusive upper bound (default: the installed version). Unreleased breaks are included only when this is omitted.'
|
|
2000
2907
|
),
|
|
2908
|
+
component: z
|
|
2909
|
+
.string()
|
|
2910
|
+
.optional()
|
|
2911
|
+
.describe("Only this component's breaks, e.g. 'BbSelect'."),
|
|
2912
|
+
surface: z
|
|
2913
|
+
.enum(['prop', 'slot', 'pt', 'cssVar', 'class', 'option', 'method'])
|
|
2914
|
+
.optional()
|
|
2915
|
+
.describe('Only renames of this surface.'),
|
|
2916
|
+
kind: z
|
|
2917
|
+
.enum(['rename', 'behaviour'])
|
|
2918
|
+
.optional()
|
|
2919
|
+
.describe('Only renames, or only behaviour changes.'),
|
|
2920
|
+
detail: z
|
|
2921
|
+
.enum(['lines', 'rows'])
|
|
2922
|
+
.optional()
|
|
2923
|
+
.describe(
|
|
2924
|
+
"'lines' (default): one line per change. 'rows': the full records (description, guide, default, fix)."
|
|
2925
|
+
),
|
|
2926
|
+
cursor: z
|
|
2927
|
+
.string()
|
|
2928
|
+
.optional()
|
|
2929
|
+
.describe(
|
|
2930
|
+
'The `nextCursor` of the previous answer, for the next page.'
|
|
2931
|
+
),
|
|
2001
2932
|
},
|
|
2002
2933
|
},
|
|
2003
2934
|
toolCallback(listChanges)
|
|
@@ -2051,12 +2982,18 @@ function buildServer() {
|
|
|
2051
2982
|
'get_recipe',
|
|
2052
2983
|
{
|
|
2053
2984
|
description:
|
|
2054
|
-
'Read one recipe by platform and name
|
|
2985
|
+
'Read one page recipe by platform and name: its prose (anatomy, key ideas, why), the list of code files with sizes, and the path of the shipped markdown to copy from. Fetch code with `files`, other parts with `sections`; `maxChars: 0` returns the whole markdown.',
|
|
2055
2986
|
inputSchema: {
|
|
2056
2987
|
sections: z
|
|
2057
2988
|
.array(z.string())
|
|
2058
2989
|
.optional()
|
|
2059
|
-
.describe('Section titles to return instead of the
|
|
2990
|
+
.describe('Section titles to return instead of the default.'),
|
|
2991
|
+
files: z
|
|
2992
|
+
.array(z.string())
|
|
2993
|
+
.optional()
|
|
2994
|
+
.describe(
|
|
2995
|
+
"Code files to return whole, by title from `files`, e.g. ['Index.vue', 'context.ts']."
|
|
2996
|
+
),
|
|
2060
2997
|
platform: platformSchema.describe(
|
|
2061
2998
|
'Which platform variant of the recipe to read.'
|
|
2062
2999
|
),
|
|
@@ -2068,7 +3005,9 @@ function buildServer() {
|
|
|
2068
3005
|
.int()
|
|
2069
3006
|
.min(0)
|
|
2070
3007
|
.optional()
|
|
2071
|
-
.describe(
|
|
3008
|
+
.describe(
|
|
3009
|
+
'Return the markdown itself instead of the outline: 0 = whole, N = cut at N chars.'
|
|
3010
|
+
),
|
|
2072
3011
|
},
|
|
2073
3012
|
},
|
|
2074
3013
|
toolCallback(getRecipe)
|
|
@@ -2100,9 +3039,19 @@ function buildServer() {
|
|
|
2100
3039
|
'get_composable',
|
|
2101
3040
|
{
|
|
2102
3041
|
description:
|
|
2103
|
-
"Read one composable's doc (useToast, useConfirm, useBbTableContext, useMobile…): what it returns, what host it needs, examples.",
|
|
3042
|
+
"Read one composable's doc (useToast, useConfirm, useBbTableContext, useMobile…): what it returns, what host it needs, examples. A long doc answers its intro and outline; pass `sections`.",
|
|
2104
3043
|
inputSchema: {
|
|
2105
3044
|
name: z.string().describe("Composable name, e.g. 'useToast'."),
|
|
3045
|
+
sections: z
|
|
3046
|
+
.array(z.string())
|
|
3047
|
+
.optional()
|
|
3048
|
+
.describe('Section titles to return, from `outline`.'),
|
|
3049
|
+
maxChars: z
|
|
3050
|
+
.number()
|
|
3051
|
+
.int()
|
|
3052
|
+
.min(0)
|
|
3053
|
+
.optional()
|
|
3054
|
+
.describe('Return the doc itself: 0 = whole, N = cut at N chars.'),
|
|
2106
3055
|
},
|
|
2107
3056
|
},
|
|
2108
3057
|
toolCallback(getComposable)
|