bitboss-ui 3.0.0-beta.4 → 3.0.0-beta.42
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 +365 -98
- package/bin/bitboss-ui-mcp.mjs +2500 -74
- package/bin/bitboss-ui.mjs +729 -72
- package/dist/ai/BbAccordion.md +178 -56
- package/dist/ai/BbAlert.md +142 -30
- package/dist/ai/BbAsterisk.md +65 -4
- package/dist/ai/BbAvatar.md +194 -53
- package/dist/ai/BbBadge.md +253 -50
- package/dist/ai/BbBadgeButton.md +77 -21
- package/dist/ai/BbBaseButton.md +132 -51
- package/dist/ai/BbBaseCheckboxIcon.md +118 -26
- package/dist/ai/BbBaseRadioIcon.md +115 -26
- package/dist/ai/BbBaseSwitchIcon.md +123 -28
- package/dist/ai/BbBreadcrumbs.md +234 -58
- package/dist/ai/BbButton.md +389 -107
- package/dist/ai/BbCalendar.md +675 -0
- package/dist/ai/BbCheckbox.md +250 -80
- package/dist/ai/BbCheckboxGroup.md +575 -66
- package/dist/ai/BbCollapsible.md +158 -39
- package/dist/ai/BbColorInput.md +323 -82
- package/dist/ai/BbColorPalette.md +272 -66
- package/dist/ai/BbConfirm.md +220 -51
- package/dist/ai/BbConfirmPortal.md +1 -1
- package/dist/ai/BbDatePicker.md +274 -39
- package/dist/ai/BbDatePickerInput.md +331 -96
- package/dist/ai/BbDialog.md +492 -87
- package/dist/ai/BbDropdown.md +1187 -105
- package/dist/ai/BbDropdownButton.md +245 -66
- package/dist/ai/BbDropdownGroup.md +5 -5
- package/dist/ai/BbDropzone.md +140 -36
- package/dist/ai/BbForm.md +1187 -40
- package/dist/ai/BbIcon.md +102 -33
- package/dist/ai/BbIndicator.md +79 -12
- package/dist/ai/BbNumberInput.md +260 -87
- package/dist/ai/BbOffCanvas.md +396 -91
- package/dist/ai/BbPagination.md +202 -32
- package/dist/ai/BbPopover.md +221 -55
- package/dist/ai/BbProgress.md +137 -71
- package/dist/ai/BbPtScope.md +136 -0
- package/dist/ai/BbRadio.md +234 -43
- package/dist/ai/BbRadioGroup.md +529 -91
- package/dist/ai/BbRating.md +246 -61
- package/dist/ai/BbSelect.md +706 -123
- package/dist/ai/BbSelectPopover.md +441 -123
- package/dist/ai/BbSlider.md +276 -80
- package/dist/ai/BbSmoothHeight.md +145 -36
- package/dist/ai/BbSpinner.md +179 -81
- package/dist/ai/BbSwitch.md +255 -63
- package/dist/ai/BbSwitchGroup.md +394 -65
- package/dist/ai/BbTable.md +1767 -237
- package/dist/ai/BbTabs.md +901 -179
- package/dist/ai/BbTabsList.md +81 -7
- package/dist/ai/BbTabsPanels.md +77 -0
- package/dist/ai/BbTabsRoot.md +16 -15
- package/dist/ai/BbTag.md +265 -79
- package/dist/ai/BbTextInput.md +484 -102
- package/dist/ai/BbTextarea.md +318 -100
- package/dist/ai/BbTimePicker.md +164 -26
- package/dist/ai/BbTimePickerInput.md +282 -47
- package/dist/ai/BbToast.md +286 -40
- package/dist/ai/BbToastPortal.md +3 -3
- package/dist/ai/BbTooltip.md +187 -34
- package/dist/ai/BbTree.md +260 -85
- package/dist/ai/ChipsBox.md +46 -2
- package/dist/ai/ClearableButton.md +12 -0
- package/dist/ai/CommaBox.md +2 -1
- package/dist/ai/CommonField.md +64 -0
- package/dist/ai/CommonFieldInput.md +91 -0
- package/dist/ai/CommonPopover.md +17 -12
- package/dist/ai/CommonTimeSelector.md +63 -0
- package/dist/ai/ErrorIcon.md +20 -0
- package/dist/ai/FlatListBox.md +41 -11
- package/dist/ai/GroupedListBox.md +42 -12
- package/dist/ai/ListBox.md +46 -11
- package/dist/ai/OptionsContainer.md +13 -10
- package/dist/ai/WarningIcon.md +20 -0
- package/dist/ai/changelog.json +5076 -141
- package/dist/ai/components.json +52226 -10010
- package/dist/ai/composables/useBbConfig.md +72 -11
- package/dist/ai/composables/useBbDropdownContext.md +168 -0
- package/dist/ai/composables/useBbFormContext.md +51 -24
- package/dist/ai/composables/useBbFormField.md +145 -0
- package/dist/ai/composables/useBbTableContext.md +41 -19
- package/dist/ai/composables/useConfirm.md +16 -8
- package/dist/ai/composables/useCountdown.md +1 -1
- package/dist/ai/composables/useId.md +10 -10
- package/dist/ai/composables/useMobile.md +17 -10
- package/dist/ai/composables/useToast.md +25 -19
- package/dist/ai/design-tokens.json +947 -0
- package/dist/ai/design-tokens.manifest.yaml +175 -0
- package/dist/ai/guides/agent-contract.md +178 -22
- package/dist/ai/guides/ai-router.md +79 -40
- package/dist/ai/guides/app-layout.md +8 -4
- package/dist/ai/guides/coherence-playbook.md +8 -8
- package/dist/ai/guides/component-picker.md +42 -34
- package/dist/ai/guides/design-language.md +279 -99
- package/dist/ai/guides/design-tokens.md +512 -114
- package/dist/ai/guides/fetch-items-playbook.md +22 -16
- package/dist/ai/guides/icons-policy.md +70 -40
- package/dist/ai/guides/inertia-helpers.md +4 -1
- package/dist/ai/guides/inline-edit-playbook.md +13 -6
- package/dist/ai/guides/installation-and-plugin-setup.md +821 -73
- package/dist/ai/guides/migration/components/bb-accordion.md +37 -0
- package/dist/ai/guides/migration/components/bb-alert.md +2 -2
- package/dist/ai/guides/migration/components/bb-avatar.md +13 -14
- package/dist/ai/guides/migration/components/bb-badge.md +5 -5
- package/dist/ai/guides/migration/components/bb-breadcrumbs.md +10 -8
- package/dist/ai/guides/migration/components/bb-button.md +80 -20
- package/dist/ai/guides/migration/components/bb-checkbox-group.md +116 -29
- package/dist/ai/guides/migration/components/bb-checkbox.md +53 -10
- package/dist/ai/guides/migration/components/bb-chip.md +6 -6
- package/dist/ai/guides/migration/components/bb-collapsible.md +5 -5
- package/dist/ai/guides/migration/components/bb-color-input.md +5 -5
- package/dist/ai/guides/migration/components/bb-color-palette.md +16 -9
- package/dist/ai/guides/migration/components/bb-date-picker-input.md +45 -35
- package/dist/ai/guides/migration/components/bb-dialog.md +69 -36
- package/dist/ai/guides/migration/components/bb-dropdown-button.md +7 -1
- package/dist/ai/guides/migration/components/bb-dropdown.md +31 -8
- package/dist/ai/guides/migration/components/bb-dropzone.md +8 -0
- package/dist/ai/guides/migration/components/bb-icon.md +7 -7
- package/dist/ai/guides/migration/components/bb-intersection.md +1 -1
- package/dist/ai/guides/migration/components/bb-offcanvas.md +29 -31
- package/dist/ai/guides/migration/components/bb-pagination.md +10 -0
- package/dist/ai/guides/migration/components/bb-popover.md +19 -11
- package/dist/ai/guides/migration/components/bb-radio-group.md +14 -0
- package/dist/ai/guides/migration/components/bb-rating.md +61 -24
- package/dist/ai/guides/migration/components/bb-select-popover.md +9 -1
- package/dist/ai/guides/migration/components/bb-select.md +21 -4
- package/dist/ai/guides/migration/components/bb-slider.md +19 -6
- package/dist/ai/guides/migration/components/bb-switch-group.md +14 -0
- package/dist/ai/guides/migration/components/bb-table.md +574 -33
- package/dist/ai/guides/migration/components/bb-tabs.md +91 -41
- package/dist/ai/guides/migration/components/bb-tag.md +9 -7
- package/dist/ai/guides/migration/components/bb-text-input.md +12 -12
- package/dist/ai/guides/migration/components/bb-toast.md +2 -2
- package/dist/ai/guides/migration/components/bb-tooltip.md +18 -0
- package/dist/ai/guides/migration/components/bb-tree.md +11 -9
- package/dist/ai/guides/migration/components/use-confirm.md +9 -1
- package/dist/ai/guides/migration/v2-to-v3.md +852 -241
- package/dist/ai/guides/migration/v3-beta-css-classes.md +751 -0
- package/dist/ai/guides/migration/v3-beta-tooling.md +149 -0
- package/dist/ai/guides/options-items-playbook.md +161 -12
- package/dist/ai/guides/page-shell.md +1 -1
- package/dist/ai/guides/passthrough.md +968 -0
- package/dist/ai/guides/preset-components.md +364 -0
- package/dist/ai/guides/ssr-prefill.md +150 -0
- package/dist/ai/guides/validated-forms.md +471 -55
- package/dist/ai/guides/validation-libraries.md +171 -0
- package/dist/ai/guides/validation-providers.md +113 -0
- package/dist/ai/index.md +45 -44
- package/dist/ai/recipes/inertia/approvals-inbox.md +26 -10
- package/dist/ai/recipes/inertia/command-palette.md +2 -2
- package/dist/ai/recipes/inertia/inline-edit-workspace.md +46 -30
- package/dist/ai/recipes/inertia/layout-scaffold.md +6 -2
- package/dist/ai/recipes/inertia/onboarding.md +30 -8
- package/dist/ai/recipes/inertia/ownership-atlas.md +72 -49
- package/dist/ai/recipes/inertia/record-form.md +53 -19
- package/dist/ai/recipes/inertia/records-workspace.md +201 -49
- package/dist/ai/recipes/inertia/settings-workspace.md +3815 -0
- package/dist/ai/recipes/inertia/upload-center.md +54 -43
- package/dist/ai/recipes/inertia/wizard-form.md +98 -14
- package/dist/ai/recipes/nuxt/approvals-inbox.md +26 -10
- package/dist/ai/recipes/nuxt/command-palette.md +2 -2
- package/dist/ai/recipes/nuxt/inline-edit-workspace.md +68 -52
- package/dist/ai/recipes/nuxt/layout-scaffold.md +6 -2
- package/dist/ai/recipes/nuxt/onboarding.md +30 -8
- package/dist/ai/recipes/nuxt/record-form.md +66 -22
- package/dist/ai/recipes/nuxt/records-workspace.md +199 -48
- package/dist/ai/recipes/nuxt/settings-workspace.md +3904 -0
- package/dist/ai/recipes/nuxt/upload-center.md +52 -41
- package/dist/ai/recipes/nuxt/wizard-form.md +98 -14
- package/dist/ai/recipes/vue/approvals-inbox.md +28 -12
- package/dist/ai/recipes/vue/command-palette.md +2 -2
- package/dist/ai/recipes/vue/inline-edit-workspace.md +69 -53
- package/dist/ai/recipes/vue/layout-scaffold.md +6 -2
- package/dist/ai/recipes/vue/onboarding.md +32 -8
- package/dist/ai/recipes/vue/record-form.md +81 -54
- package/dist/ai/recipes/vue/records-workspace.md +198 -49
- package/dist/ai/recipes/vue/settings-workspace.md +3842 -0
- package/dist/ai/recipes/vue/upload-center.md +53 -42
- package/dist/ai/recipes/vue/wizard-form.md +97 -14
- package/dist/ai/source/BbAccordion.md +90 -24
- package/dist/ai/source/BbAlert.md +209 -62
- package/dist/ai/source/BbAsterisk.md +54 -4
- package/dist/ai/source/BbAvatar.md +73 -16
- package/dist/ai/source/BbBadge.md +299 -97
- package/dist/ai/source/BbBadgeButton.md +252 -79
- package/dist/ai/source/BbBaseButton.md +108 -12
- package/dist/ai/source/BbBaseCheckboxIcon.md +153 -36
- package/dist/ai/source/BbBaseRadioIcon.md +123 -18
- package/dist/ai/source/BbBaseSwitchIcon.md +145 -33
- package/dist/ai/source/BbBreadcrumbs.md +392 -71
- package/dist/ai/source/BbButton.md +576 -172
- package/dist/ai/source/BbCalendar.md +1855 -0
- package/dist/ai/source/BbCheckbox.md +489 -68
- package/dist/ai/source/BbCheckboxGroup.md +984 -95
- package/dist/ai/source/BbCollapsible.md +96 -8
- package/dist/ai/source/BbColorInput.md +627 -67
- package/dist/ai/source/BbColorPalette.md +1335 -69
- package/dist/ai/source/BbConfirm.md +149 -7
- package/dist/ai/source/BbConfirmPortal.md +89 -3
- package/dist/ai/source/BbDatePicker.md +527 -381
- package/dist/ai/source/BbDatePickerInput.md +2077 -118
- package/dist/ai/source/BbDialog.md +531 -64
- package/dist/ai/source/BbDropdown.md +885 -175
- package/dist/ai/source/BbDropdownButton.md +405 -79
- package/dist/ai/source/BbDropdownGroup.md +687 -96
- package/dist/ai/source/BbDropzone.md +140 -31
- package/dist/ai/source/BbForm.md +878 -18
- package/dist/ai/source/BbIcon.md +134 -53
- package/dist/ai/source/BbIndicator.md +117 -60
- package/dist/ai/source/BbNumberInput.md +684 -84
- package/dist/ai/source/BbOffCanvas.md +1007 -261
- package/dist/ai/source/BbPagination.md +221 -31
- package/dist/ai/source/BbPopover.md +278 -89
- package/dist/ai/source/BbProgress.md +88 -26
- package/dist/ai/source/BbPtScope.md +54 -0
- package/dist/ai/source/BbRadio.md +388 -58
- package/dist/ai/source/BbRadioGroup.md +950 -109
- package/dist/ai/source/BbRating.md +742 -84
- package/dist/ai/source/BbSelect.md +1108 -96
- package/dist/ai/source/BbSelectPopover.md +879 -116
- package/dist/ai/source/BbSlider.md +1396 -103
- package/dist/ai/source/BbSmoothHeight.md +54 -4
- package/dist/ai/source/BbSpinner.md +62 -6
- package/dist/ai/source/BbSwitch.md +316 -60
- package/dist/ai/source/BbSwitchGroup.md +769 -83
- package/dist/ai/source/BbTable.md +7135 -1870
- package/dist/ai/source/BbTabs.md +368 -111
- package/dist/ai/source/BbTabsList.md +503 -146
- package/dist/ai/source/BbTabsPanels.md +625 -0
- package/dist/ai/source/BbTabsRoot.md +319 -100
- package/dist/ai/source/BbTag.md +841 -88
- package/dist/ai/source/BbTextInput.md +528 -68
- package/dist/ai/source/BbTextarea.md +536 -65
- package/dist/ai/source/BbTimePicker.md +395 -69
- package/dist/ai/source/BbTimePickerInput.md +1271 -106
- package/dist/ai/source/BbToast.md +250 -74
- package/dist/ai/source/BbToastPortal.md +210 -66
- package/dist/ai/source/BbTooltip.md +181 -30
- package/dist/ai/source/BbTree.md +88 -11
- package/dist/ai/source/ChipsBox.md +25 -5
- package/dist/ai/source/CommaBox.md +21 -6
- package/dist/ai/source/{BbBaseInputContainer.md → CommonField.md} +496 -250
- package/dist/ai/source/CommonFieldInput.md +869 -0
- package/dist/ai/source/CommonPopover.md +116 -30
- package/dist/ai/source/CommonTimeSelector.md +736 -0
- package/dist/ai/source/ErrorIcon.md +2 -2
- package/dist/ai/source/FlatListBox.md +192 -17
- package/dist/ai/source/GroupedListBox.md +240 -44
- package/dist/ai/source/GuidesComponent.md +14 -7
- package/dist/ai/source/ListBox.md +50 -8
- package/dist/ai/source/OptionsContainer.md +288 -60
- package/dist/ai/source/WarningIcon.md +2 -2
- package/dist/assets/css/reset.css_inline.js +1 -1
- package/dist/assets/svgs/star.svg_raw.js +4 -0
- package/dist/components/BbAccordion/BbAccordion.vue_vue_type_script_setup_true_lang.js +49 -32
- package/dist/components/BbAccordion/types.d.ts +35 -3
- package/dist/components/BbAccordion/types.js +11 -0
- package/dist/components/BbAlert/BbAlert.vue_vue_type_script_setup_true_lang.js +46 -30
- package/dist/components/BbAlert/types.d.ts +37 -3
- package/dist/components/BbAlert/types.js +13 -0
- package/dist/components/BbAsterisk/BbAsterisk.vue.d.ts +3 -1
- package/dist/components/BbAsterisk/BbAsterisk.vue_vue_type_script_setup_true_lang.js +23 -8
- package/dist/components/BbAsterisk/types.d.ts +18 -0
- package/dist/components/BbAsterisk/types.js +7 -0
- package/dist/components/BbAvatar/BbAvatar.vue.d.ts +1 -1
- package/dist/components/BbAvatar/BbAvatar.vue_vue_type_script_setup_true_lang.js +57 -44
- package/dist/components/BbAvatar/types.d.ts +23 -0
- package/dist/components/BbAvatar/types.js +7 -0
- package/dist/components/BbBadge/BadgeBodyContent.vue_vue_type_script_setup_true_lang.js +24 -18
- package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +96 -53
- package/dist/components/BbBadge/BbBadgeButton.vue_vue_type_script_setup_true_lang.js +27 -8
- package/dist/components/BbBadge/badgeButtonContext.d.ts +25 -8
- package/dist/components/BbBadge/types.d.ts +46 -3
- package/dist/components/BbBadge/types.js +13 -0
- package/dist/components/BbBaseButton/BbBaseButton.vue_vue_type_script_setup_true_lang.js +109 -96
- package/dist/components/BbBaseButton/RouterComponent.vue_vue_type_script_setup_true_lang.js +11 -11
- package/dist/components/BbBaseButton/types.d.ts +33 -3
- package/dist/components/BbBaseButton/types.js +9 -2
- package/dist/components/BbBaseCheckboxIcon/BbBaseCheckboxIcon.vue_vue_type_script_setup_true_lang.js +50 -18
- package/dist/components/BbBaseCheckboxIcon/types.d.ts +31 -2
- package/dist/components/BbBaseCheckboxIcon/types.js +8 -0
- package/dist/components/BbBaseRadioIcon/BbBaseRadioIcon.vue_vue_type_script_setup_true_lang.js +45 -15
- package/dist/components/BbBaseRadioIcon/types.d.ts +31 -2
- package/dist/components/BbBaseRadioIcon/types.js +8 -0
- package/dist/components/BbBaseSwitchIcon/BbBaseSwitchIcon.vue_vue_type_script_setup_true_lang.js +48 -16
- package/dist/components/BbBaseSwitchIcon/types.d.ts +31 -2
- package/dist/components/BbBaseSwitchIcon/types.js +8 -0
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +191 -134
- package/dist/components/BbBreadcrumbs/types.d.ts +45 -3
- package/dist/components/BbBreadcrumbs/types.js +12 -0
- package/dist/components/BbButton/BbButton.vue.d.ts +5 -0
- package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +107 -81
- package/dist/components/BbButton/types.d.ts +113 -2
- package/dist/components/BbButton/types.js +12 -0
- package/dist/components/BbCalendar/BbCalendar.vue.d.ts +34 -0
- package/dist/components/BbCalendar/BbCalendar.vue.js +6 -0
- package/dist/components/BbCalendar/BbCalendar.vue_vue_type_script_setup_true_lang.js +610 -0
- package/dist/components/BbCalendar/CalendarMonthPanel.vue.js +5 -0
- package/dist/components/BbCalendar/CalendarMonthPanel.vue_vue_type_script_setup_true_lang.js +124 -0
- package/dist/components/BbCalendar/CalendarYearPanel.vue.js +5 -0
- package/dist/components/BbCalendar/CalendarYearPanel.vue_vue_type_script_setup_true_lang.js +129 -0
- package/dist/components/BbCalendar/calendarHost.d.ts +29 -0
- package/dist/components/BbCalendar/calendarHost.js +10 -0
- package/dist/components/BbCalendar/types.d.ts +229 -0
- package/dist/components/BbCalendar/types.js +29 -0
- package/dist/components/{BbBaseDatePicker/useDatePickerContext.d.ts → BbCalendar/useCalendarContext.d.ts} +10 -7
- package/dist/components/BbCalendar/useCalendarContext.js +11 -0
- package/dist/components/BbCalendar/useCalendarGrid.d.ts +208 -0
- package/dist/components/BbCalendar/useCalendarGrid.js +274 -0
- package/dist/components/BbCalendar/useDayGrid.d.ts +79 -0
- package/dist/components/BbCalendar/useDayGrid.js +204 -0
- package/dist/components/BbCalendar/useToday.d.ts +23 -0
- package/dist/components/BbCalendar/useToday.js +21 -0
- package/dist/components/BbCheckbox/BbCheckbox.vue.d.ts +7 -7
- package/dist/components/BbCheckbox/BbCheckbox.vue_vue_type_script_setup_true_lang.js +218 -72
- package/dist/components/BbCheckbox/types.d.ts +180 -13
- package/dist/components/BbCheckbox/types.js +8 -0
- package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue.d.ts +15 -3
- package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +343 -90
- package/dist/components/BbCheckboxGroup/types.d.ts +195 -20
- package/dist/components/BbCheckboxGroup/types.js +10 -0
- package/dist/components/BbCollapsible/BbCollapsible.vue.d.ts +2 -1
- package/dist/components/BbCollapsible/BbCollapsible.vue_vue_type_script_setup_true_lang.js +41 -19
- package/dist/components/BbCollapsible/types.d.ts +44 -1
- package/dist/components/BbCollapsible/types.js +7 -0
- package/dist/components/BbColorInput/BbColorInput.vue.d.ts +17 -7
- package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +332 -86
- package/dist/components/BbColorInput/types.d.ts +230 -12
- package/dist/components/BbColorInput/types.js +24 -0
- package/dist/components/BbColorPalette/BbColorPalette.vue.d.ts +10 -2
- package/dist/components/BbColorPalette/BbColorPalette.vue_vue_type_script_setup_true_lang.js +530 -108
- package/dist/components/BbColorPalette/types.d.ts +86 -10
- package/dist/components/BbColorPalette/types.js +12 -0
- package/dist/components/BbConfirm/BbConfirm.vue.d.ts +2 -1
- package/dist/components/BbConfirm/BbConfirm.vue_vue_type_script_setup_true_lang.js +125 -76
- package/dist/components/BbConfirm/BbConfirmPortal.vue.d.ts +1 -0
- package/dist/components/BbConfirm/types.d.ts +49 -0
- package/dist/components/BbConfirm/types.js +17 -0
- package/dist/components/BbDatePicker/BbDatePicker.vue.d.ts +16 -10
- package/dist/components/BbDatePicker/BbDatePicker.vue_vue_type_script_setup_true_lang.js +320 -223
- package/dist/components/BbDatePicker/types.d.ts +111 -17
- package/dist/components/BbDatePicker/types.js +33 -0
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue.d.ts +16 -4
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +1187 -87
- package/dist/components/BbDatePickerInput/types.d.ts +224 -19
- package/dist/components/BbDatePickerInput/types.js +39 -0
- package/dist/components/BbDialog/BbDialog.vue.d.ts +3 -3
- package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +227 -136
- package/dist/components/BbDialog/types.d.ts +92 -3
- package/dist/components/BbDialog/types.js +16 -0
- package/dist/components/BbDialog/useDialogStack.d.ts +12 -0
- package/dist/components/BbDialog/useDialogStack.js +9 -2
- package/dist/components/BbDropdown/AdaptiveDropdown.vue.d.ts +2 -0
- package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +108 -96
- package/dist/components/BbDropdown/BbDropdown.vue.d.ts +10 -2
- package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +337 -268
- package/dist/components/BbDropdown/BbDropdownGroup.vue.d.ts +5 -1
- package/dist/components/BbDropdown/BbDropdownList.vue.d.ts +8 -2
- package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +377 -293
- package/dist/components/BbDropdown/DropdownPipelineResolver.vue_vue_type_script_setup_true_lang.js +129 -118
- package/dist/components/BbDropdown/normalizeGroups.js +55 -31
- package/dist/components/BbDropdown/types.d.ts +288 -30
- package/dist/components/BbDropdown/types.js +21 -2
- package/dist/components/BbDropdown/useDropdownContext.d.ts +10 -4
- package/dist/components/BbDropdown/useDropdownGroupRegistry.d.ts +1 -0
- package/dist/components/BbDropdownButton/BbDropdownButton.vue.d.ts +18 -3
- package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +179 -40
- package/dist/components/BbDropdownButton/types.d.ts +137 -8
- package/dist/components/BbDropdownButton/types.js +35 -0
- package/dist/components/BbDropzone/BbDropzone.vue.d.ts +2 -2
- package/dist/components/BbDropzone/BbDropzone.vue_vue_type_script_setup_true_lang.js +186 -144
- package/dist/components/BbDropzone/types.d.ts +38 -4
- package/dist/components/BbDropzone/types.js +16 -0
- package/dist/components/BbForm/BbForm.vue.d.ts +96 -0
- package/dist/{validated → components/BbForm}/BbForm.vue.js +1 -1
- package/dist/components/BbForm/BbForm.vue_vue_type_script_setup_true_lang.js +314 -0
- package/dist/components/BbForm/BbFormCustomField.d.ts +14 -0
- package/dist/components/BbForm/BbFormCustomField.js +43 -0
- package/dist/components/BbForm/bindFormField.d.ts +43 -0
- package/dist/components/BbForm/bindFormField.js +86 -0
- package/dist/components/BbForm/formFields.d.ts +64 -0
- package/dist/components/BbForm/formFields.js +11 -0
- package/dist/components/BbForm/formSchema.d.ts +56 -0
- package/dist/components/BbForm/formSchema.js +196 -0
- package/dist/components/BbForm/standardSchema.d.ts +59 -0
- package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +59 -48
- package/dist/components/BbIcon/iconCache.d.ts +63 -0
- package/dist/components/BbIcon/iconCache.js +35 -0
- package/dist/components/BbIcon/types.d.ts +22 -2
- package/dist/components/BbIcon/types.js +7 -0
- package/dist/components/BbIndicator/BbIndicator.vue_vue_type_script_setup_true_lang.js +34 -21
- package/dist/components/BbIndicator/types.d.ts +24 -0
- package/dist/components/BbIndicator/types.js +7 -0
- package/dist/components/BbNumberInput/BbNumberInput.vue.d.ts +12 -8
- package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +313 -85
- package/dist/components/BbNumberInput/types.d.ts +202 -18
- package/dist/components/BbNumberInput/types.js +8 -0
- package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +3 -3
- package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +311 -210
- package/dist/components/BbOffCanvas/types.d.ts +79 -6
- package/dist/components/BbOffCanvas/types.js +15 -0
- package/dist/components/BbPagination/BbPagination.vue.d.ts +2 -2
- package/dist/components/BbPagination/BbPagination.vue_vue_type_script_setup_true_lang.js +125 -90
- package/dist/components/BbPagination/types.d.ts +38 -0
- package/dist/components/BbPagination/types.js +13 -0
- package/dist/components/BbPopover/BbPopover.vue.d.ts +4 -4
- package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +219 -179
- package/dist/components/BbPopover/types.d.ts +51 -5
- package/dist/components/BbPopover/types.js +14 -0
- package/dist/components/BbProgress/BbProgress.vue_vue_type_script_setup_true_lang.js +29 -16
- package/dist/components/BbProgress/types.d.ts +25 -0
- package/dist/components/BbProgress/types.js +7 -0
- package/dist/components/BbPtScope/BbPtScope.vue.d.ts +12 -0
- package/dist/components/BbPtScope/BbPtScope.vue.js +5 -0
- package/dist/components/BbPtScope/BbPtScope.vue_vue_type_script_setup_true_lang.js +13 -0
- package/dist/components/BbPtScope/types.d.ts +15 -0
- package/dist/components/BbRadio/BbRadio.vue.d.ts +7 -7
- package/dist/components/BbRadio/BbRadio.vue_vue_type_script_setup_true_lang.js +172 -59
- package/dist/components/BbRadio/types.d.ts +152 -13
- package/dist/components/BbRadio/types.js +8 -0
- package/dist/components/BbRadioGroup/BbRadioGroup.vue.d.ts +14 -2
- package/dist/components/BbRadioGroup/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +326 -92
- package/dist/components/BbRadioGroup/types.d.ts +198 -22
- package/dist/components/BbRadioGroup/types.js +10 -0
- package/dist/components/BbRating/BbRating.vue.d.ts +10 -8
- package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +261 -78
- package/dist/components/BbRating/types.d.ts +174 -13
- package/dist/components/BbRating/types.js +21 -0
- package/dist/components/BbSelect/BbSelect.vue.d.ts +17 -3
- package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +558 -84
- package/dist/components/BbSelect/types.d.ts +241 -20
- package/dist/components/BbSelect/types.js +41 -0
- package/dist/components/BbSelectPopover/BbSelectPopover.vue.d.ts +15 -1
- package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +529 -360
- package/dist/components/BbSelectPopover/sheetChipAttrs.d.ts +18 -0
- package/dist/components/BbSelectPopover/sheetChipAttrs.js +5 -0
- package/dist/components/BbSelectPopover/types.d.ts +127 -9
- package/dist/components/BbSelectPopover/types.js +24 -0
- package/dist/components/BbSlider/BbSlider.vue.d.ts +13 -8
- package/dist/components/BbSlider/BbSlider.vue_vue_type_script_setup_true_lang.js +470 -97
- package/dist/components/BbSlider/types.d.ts +202 -17
- package/dist/components/BbSlider/types.js +26 -0
- package/dist/components/BbSmoothHeight/BbSmoothHeight.vue.d.ts +1 -1
- package/dist/components/BbSmoothHeight/BbSmoothHeight.vue_vue_type_script_setup_true_lang.js +60 -48
- package/dist/components/BbSmoothHeight/types.d.ts +19 -0
- package/dist/components/BbSmoothHeight/types.js +7 -0
- package/dist/components/BbSpinner/BbSpinner.vue_vue_type_script_setup_true_lang.js +27 -14
- package/dist/components/BbSpinner/types.d.ts +22 -1
- package/dist/components/BbSpinner/types.js +7 -0
- package/dist/components/BbSwitch/BbSwitch.vue.d.ts +7 -7
- package/dist/components/BbSwitch/BbSwitch.vue_vue_type_script_setup_true_lang.js +224 -78
- package/dist/components/BbSwitch/types.d.ts +25 -10
- package/dist/components/BbSwitch/types.js +5 -0
- package/dist/components/BbSwitchGroup/BbSwitchGroup.vue.d.ts +14 -2
- package/dist/components/BbSwitchGroup/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +343 -90
- package/dist/components/BbSwitchGroup/types.d.ts +30 -7
- package/dist/components/BbSwitchGroup/types.js +5 -0
- package/dist/components/BbTable/BbTable.vue.d.ts +27 -6
- package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +2054 -688
- package/dist/components/BbTable/BbTableDataRow.d.ts +196 -0
- package/dist/components/BbTable/BbTableDataRow.js +151 -0
- package/dist/components/BbTable/BbTableExpandRow.d.ts +84 -0
- package/dist/components/BbTable/BbTableExpandRow.js +62 -0
- package/dist/components/BbTable/BbTableSelectToggle.d.ts +120 -0
- package/dist/components/BbTable/BbTableSelectToggle.js +122 -0
- package/dist/components/BbTable/gridWidgets.d.ts +30 -0
- package/dist/components/BbTable/gridWidgets.js +14 -0
- package/dist/components/BbTable/types.d.ts +473 -123
- package/dist/components/BbTable/types.js +31 -0
- package/dist/components/BbTable/utils.d.ts +145 -4
- package/dist/components/BbTable/utils.js +154 -37
- package/dist/components/BbTabs/BbTabs.vue.d.ts +3 -1
- package/dist/components/BbTabs/BbTabs.vue_vue_type_script_setup_true_lang.js +62 -26
- package/dist/components/BbTabs/BbTabsList.vue.d.ts +3 -10
- package/dist/components/BbTabs/BbTabsList.vue_vue_type_script_setup_true_lang.js +143 -81
- package/dist/components/BbTabs/BbTabsPanels.vue.d.ts +12 -0
- package/dist/components/{BbBaseSwitch/BbBaseSwitch.vue.js → BbTabs/BbTabsPanels.vue.js} +2 -2
- package/dist/components/BbTabs/BbTabsPanels.vue_vue_type_script_setup_true_lang.js +59 -0
- package/dist/components/BbTabs/BbTabsRoot.vue.d.ts +3 -1
- package/dist/components/BbTabs/BbTabsRoot.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/BbTabs/types.d.ts +183 -23
- package/dist/components/BbTabs/types.js +34 -0
- package/dist/components/BbTag/BbTag.vue.d.ts +13 -10
- package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +397 -92
- package/dist/components/BbTag/types.d.ts +141 -15
- package/dist/components/BbTag/types.js +22 -0
- package/dist/components/BbTextInput/BbTextInput.vue.d.ts +10 -8
- package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +265 -84
- package/dist/components/BbTextInput/types.d.ts +168 -13
- package/dist/components/BbTextInput/types.js +8 -0
- package/dist/components/BbTextarea/BbTextarea.vue.d.ts +7 -7
- package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +297 -94
- package/dist/components/BbTextarea/types.d.ts +158 -14
- package/dist/components/BbTextarea/types.js +8 -0
- package/dist/components/BbTimePicker/BbTimePicker.vue.d.ts +12 -4
- package/dist/components/BbTimePicker/BbTimePicker.vue_vue_type_script_setup_true_lang.js +199 -122
- package/dist/components/BbTimePicker/types.d.ts +94 -7
- package/dist/components/BbTimePicker/types.js +18 -0
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue.d.ts +14 -4
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +762 -86
- package/dist/components/BbTimePickerInput/types.d.ts +171 -22
- package/dist/components/BbTimePickerInput/types.js +25 -0
- package/dist/components/BbToast/BbToast.vue_vue_type_script_setup_true_lang.js +63 -25
- package/dist/components/BbToast/BbToastMessage.vue.d.ts +4 -1
- package/dist/components/BbToast/BbToastMessage.vue_vue_type_script_setup_true_lang.js +79 -76
- package/dist/components/BbToast/BbToastPortal.vue.d.ts +1 -0
- package/dist/components/BbToast/BbToastRegion.vue.d.ts +4 -1
- package/dist/components/BbToast/BbToastRegion.vue_vue_type_script_setup_true_lang.js +72 -64
- package/dist/components/BbToast/types.d.ts +54 -1
- package/dist/components/BbToast/types.js +28 -0
- package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +104 -87
- package/dist/components/BbTooltip/types.d.ts +40 -2
- package/dist/components/BbTooltip/types.js +7 -0
- package/dist/components/BbTree/BbTree.vue.d.ts +15 -3
- package/dist/components/BbTree/BbTree.vue_vue_type_script_setup_true_lang.js +49 -29
- package/dist/components/BbTree/BbTreeLevel.vue.d.ts +1 -1
- package/dist/components/BbTree/BbTreeLevel.vue_vue_type_script_setup_true_lang.js +17 -16
- package/dist/components/BbTree/context.d.ts +9 -1
- package/dist/components/BbTree/types.d.ts +40 -5
- package/dist/components/BbTree/types.js +7 -0
- package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +46 -44
- package/dist/components/ChipsBox/types.d.ts +7 -0
- package/dist/components/ClearableButton/ClearableButton.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/CommaBox/CommaBox.vue_vue_type_script_setup_true_lang.js +11 -7
- package/dist/components/CommaBox/types.d.ts +7 -0
- package/dist/components/{BbBaseInputContainer/BbBaseInputContainer.vue.d.ts → CommonField/CommonField.vue.d.ts} +5 -5
- package/dist/components/{BbBaseRadio/BbBaseRadio.vue.js → CommonField/CommonField.vue.js} +2 -2
- package/dist/components/CommonField/CommonField.vue_vue_type_script_setup_true_lang.js +195 -0
- package/dist/components/CommonField/messageHeightMatrix.d.ts +2 -0
- package/dist/components/{BbBaseInputContainer → CommonField}/types.d.ts +59 -16
- package/dist/components/{CommonInputWrapper/CommonInputWrapper.vue.d.ts → CommonFieldInput/CommonFieldInput.vue.d.ts} +5 -4
- package/dist/components/{BbBaseRadioGroup/BbBaseRadioGroup.vue.js → CommonFieldInput/CommonFieldInput.vue.js} +2 -2
- package/dist/components/CommonFieldInput/CommonFieldInput.vue_vue_type_script_setup_true_lang.js +120 -0
- package/dist/components/CommonFieldInput/boxBorderMatrix.d.ts +10 -0
- package/dist/components/{CommonInputWrapper → CommonFieldInput}/types.d.ts +27 -8
- package/dist/components/CommonPopover/CommonPopover.vue.d.ts +11 -2
- package/dist/components/CommonPopover/CommonPopover.vue_vue_type_script_setup_true_lang.js +76 -63
- package/dist/components/CommonPopover/types.d.ts +27 -8
- package/dist/components/{BbBaseDatePickerInput/BbBaseDatePickerTimeSelector.vue.d.ts → CommonTimeSelector/CommonTimeSelector.vue.d.ts} +21 -2
- package/dist/components/{BbBaseColorPalette/BbBaseColorPalette.vue.js → CommonTimeSelector/CommonTimeSelector.vue.js} +2 -2
- package/dist/components/CommonTimeSelector/CommonTimeSelector.vue_vue_type_script_setup_true_lang.js +236 -0
- package/dist/components/CommonTimeSelector/types.d.ts +6 -0
- package/dist/components/FlatListBox/FlatListBox.vue.d.ts +5 -1
- package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +165 -95
- package/dist/components/FlatListBox/types.d.ts +28 -2
- package/dist/components/GroupedListBox/GroupedListBox.vue.d.ts +5 -1
- package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +217 -134
- package/dist/components/GroupedListBox/types.d.ts +28 -2
- package/dist/components/ListBox/ListBox.vue.d.ts +5 -1
- package/dist/components/ListBox/ListBox.vue_vue_type_script_setup_true_lang.js +95 -69
- package/dist/components/ListBox/types.d.ts +28 -2
- package/dist/components/OptionsContainer/OptionsContainer.vue.d.ts +1 -1
- package/dist/components/OptionsContainer/OptionsContainer.vue_vue_type_script_setup_true_lang.js +109 -37
- package/dist/components/OptionsContainer/types.d.ts +53 -9
- package/dist/components/Transitions/ScaleFade.vue_vue_type_script_setup_true_lang.js +8 -8
- package/dist/components/Transitions/Slide.vue.js +1 -1
- package/dist/composables/ssrMarker.d.ts +15 -0
- package/dist/composables/ssrMarker.js +19 -0
- package/dist/composables/useBaseOptions.d.ts +12 -1
- package/dist/composables/useBaseOptions.js +83 -37
- package/dist/composables/useBbConfig.d.ts +27 -6
- package/dist/composables/useBbConfig.js +8 -2
- package/dist/composables/useBbDropdownContext.d.ts +5 -0
- package/dist/composables/useBbFormContext.d.ts +131 -8
- package/dist/composables/useBbFormContext.js +49 -30
- package/dist/composables/useBbFormField.d.ts +51 -0
- package/dist/composables/useBbFormField.js +55 -0
- package/dist/composables/useBbTableContext.d.ts +45 -1
- package/dist/composables/useBbTableContext.js +93 -87
- package/dist/composables/useBbTabsContext.d.ts +15 -9
- package/dist/composables/useBbTabsContext.js +121 -90
- package/dist/composables/useClosingInert.d.ts +4 -4
- package/dist/composables/useCoarsePointer.js +7 -10
- package/dist/composables/useCoherence.js +1 -1
- package/dist/composables/useConfig.d.ts +59 -0
- package/dist/composables/useConfig.js +15 -0
- package/dist/composables/useConfirm.d.ts +39 -7
- package/dist/composables/useConfirm.js +50 -41
- package/dist/composables/useEscapeStackEntry.d.ts +13 -0
- package/dist/composables/useEscapeStackEntry.js +16 -0
- package/dist/composables/useFormBinding.d.ts +124 -0
- package/dist/composables/useFormBinding.js +44 -0
- package/dist/composables/useHydrated.d.ts +18 -0
- package/dist/composables/useHydrated.js +13 -7
- package/dist/composables/useId.d.ts +4 -3
- package/dist/composables/useId.js +1 -7
- package/dist/composables/useItemsGetter.d.ts +39 -0
- package/dist/composables/useItemsGetter.js +56 -18
- package/dist/composables/useKeyboardInset.d.ts +28 -0
- package/dist/composables/useKeyboardInset.js +32 -0
- package/dist/composables/useLocale.js +10 -7
- package/dist/composables/useMobile.js +7 -10
- package/dist/composables/usePassthrough.d.ts +228 -0
- package/dist/composables/usePassthrough.js +247 -0
- package/dist/composables/usePrefill.d.ts +22 -2
- package/dist/composables/usePrefill.js +36 -19
- package/dist/composables/useSafeAreaInternal.js +1 -1
- package/dist/composables/useSegmentedFields.d.ts +2 -2
- package/dist/composables/useServerPrefetch.d.ts +28 -0
- package/dist/composables/useServerPrefetch.js +9 -0
- package/dist/composables/useSsrState.d.ts +28 -0
- package/dist/composables/useSsrState.js +13 -0
- package/dist/composables/useTableWidthContext.d.ts +24 -4
- package/dist/composables/useTableWidthContext.js +31 -28
- package/dist/composables/useToast.d.ts +18 -1
- package/dist/composables/useToast.js +31 -18
- package/dist/composables/useToggleInput.d.ts +44 -0
- package/dist/composables/useToggleInput.js +39 -0
- package/dist/composables/useViewportWidth.d.ts +5 -25
- package/dist/composables/useViewportWidth.js +14 -7
- package/dist/deprecation/ai-deprecations.json.d.ts +2008 -285
- package/dist/deprecation/ai-deprecations.json.js +1 -1
- package/dist/deprecation/removedProps.js +9 -4
- package/dist/directives/bbDropdown.d.ts +15 -4
- package/dist/directives/bbDropdown.js +31 -9
- package/dist/directives/bbTooltip.d.ts +6 -0
- package/dist/directives/createPopoverDirective.d.ts +62 -0
- package/dist/directives/createPopoverDirective.js +92 -37
- package/dist/i18n/index.d.ts +10 -16
- package/dist/i18n/locales/cs.json +9 -5
- package/dist/i18n/locales/da.json +9 -5
- package/dist/i18n/locales/de.json +9 -5
- package/dist/i18n/locales/el.json +9 -5
- package/dist/i18n/locales/en.json +9 -5
- package/dist/i18n/locales/es.json +9 -5
- package/dist/i18n/locales/fi.json +9 -5
- package/dist/i18n/locales/fr.json +9 -5
- package/dist/i18n/locales/hu.json +9 -5
- package/dist/i18n/locales/it.json +9 -5
- package/dist/i18n/locales/ja.json +9 -5
- package/dist/i18n/locales/ko.json +9 -5
- package/dist/i18n/locales/nb.json +9 -5
- package/dist/i18n/locales/nl.json +9 -5
- package/dist/i18n/locales/pl.json +9 -5
- package/dist/i18n/locales/pt.json +9 -5
- package/dist/i18n/locales/ro.json +9 -5
- package/dist/i18n/locales/ru.json +9 -5
- package/dist/i18n/locales/sv.json +9 -5
- package/dist/i18n/locales/tr.json +9 -5
- package/dist/i18n/locales/uk.json +9 -5
- package/dist/i18n/locales/zh-cn.json +9 -5
- package/dist/icon-scan.d.ts +276 -0
- package/dist/icon-scan.js +485 -0
- package/dist/icons-whitelist.d.ts +5 -4
- package/dist/index.d.ts +40 -21
- package/dist/index.js +78 -71
- package/dist/llms-full.txt +61619 -28049
- package/dist/llms-medium.txt +1501 -368
- package/dist/llms.txt +32 -15
- package/dist/locale-blueprint.json +9 -5
- package/dist/nuxt-auto-imports.d.ts +76 -0
- package/dist/nuxt-auto-imports.js +32 -0
- package/dist/nuxt-module.d.ts +30 -15
- package/dist/nuxt.js +35 -9
- package/dist/passthrough-deduper.js +5 -0
- package/dist/plugin.d.ts +10 -1
- package/dist/plugin.js +29 -19
- package/dist/project-dts.d.ts +84 -0
- package/dist/project-dts.js +170 -0
- package/dist/reset.css +1 -1
- package/dist/runtime/nuxt-plugin.js +34 -21
- package/dist/styles.css +3 -2
- package/dist/types/AlertVariant.d.ts +10 -1
- package/dist/types/BadgeVariant.d.ts +16 -5
- package/dist/types/ButtonVariant.d.ts +12 -1
- package/dist/types/ColumnName.d.ts +30 -3
- package/dist/types/CommonProps.d.ts +2 -2
- package/dist/types/Config.d.ts +94 -1
- package/dist/types/ConfirmVariant.d.ts +8 -1
- package/dist/types/DropdownItemVariant.d.ts +8 -1
- package/dist/types/FieldLayout.d.ts +32 -0
- package/dist/types/IndicatorVariant.d.ts +11 -1
- package/dist/types/InputVariant.d.ts +30 -0
- package/dist/types/ItemProps.d.ts +50 -0
- package/dist/types/Option.d.ts +6 -0
- package/dist/types/PtScope.d.ts +15 -0
- package/dist/types/RegistryKey.d.ts +10 -0
- package/dist/types/TabsVariant.d.ts +25 -0
- package/dist/types/ToastVariant.d.ts +12 -2
- package/dist/types/TooltipVariant.d.ts +8 -1
- package/dist/types/passthrough.d.ts +362 -0
- package/dist/types/passthrough.js +78 -0
- package/dist/types/ptComponentMap.d.ts +62 -0
- package/dist/utilities/functions/hasVnodeListener.d.ts +16 -0
- package/dist/utilities/functions/hasVnodeListener.js +12 -0
- package/dist/utilities/functions/isImeComposing.d.ts +7 -0
- package/dist/utilities/functions/isImeComposing.js +4 -0
- package/dist/utilities/functions/itemProps.d.ts +46 -0
- package/dist/utilities/functions/itemProps.js +47 -0
- package/dist/utilities/functions/slotKey.d.ts +13 -8
- package/dist/utilities/functions/slotKey.js +78 -2
- package/dist/utilities/functions/snapToStep.js +2 -1
- package/dist/utilities/functions/toggleInput.d.ts +114 -0
- package/dist/utilities/functions/toggleInput.js +61 -0
- package/dist/utilities/functions/warnRemovedAttrs.d.ts +27 -1
- package/dist/utilities/functions/warnRemovedAttrs.js +85 -14
- package/dist/utilities/misc/listboxMetrics.d.ts +9 -3
- package/dist/utilities/misc/listboxMetrics.js +5 -2
- package/dist/utilities/misc/ssrStateInjectionKey.d.ts +47 -0
- package/dist/utilities/misc/ssrStateInjectionKey.js +4 -0
- package/dist/utilities/misc/ssrValue.d.ts +20 -0
- package/dist/utilities/misc/ssrValue.js +7 -0
- package/dist/utils/cssLayers.d.ts +91 -0
- package/dist/utils/cssLayers.js +43 -0
- package/dist/utils/injectLibraryStyles.d.ts +3 -1
- package/dist/utils/injectLibraryStyles.js +24 -23
- package/dist/utils/overlayEscapeStack.d.ts +10 -0
- package/dist/utils/overlayEscapeStack.js +5 -3
- package/dist/utils/passthrough.d.ts +67 -0
- package/dist/utils/passthrough.js +100 -0
- package/dist/utils/passthroughDeduper.d.ts +2 -0
- package/dist/utils/ptScope.d.ts +8 -0
- package/dist/utils/ptScope.js +4 -0
- package/dist/utils/versionCheck.d.ts +19 -0
- package/dist/utils/versionCheck.js +18 -0
- package/dist/vite-plugin.d.ts +301 -53
- package/dist/vite.js +554 -375
- package/llms.txt +33 -16
- package/package.json +50 -27
- package/scripts/lib/check-fix.mjs +129 -0
- package/scripts/lib/component-attr-targets.ts +123 -0
- package/scripts/lib/component-tree.ts +648 -0
- package/scripts/lib/css-class-check.mjs +326 -0
- package/scripts/lib/css-class-surface.mjs +292 -0
- package/scripts/lib/css-color-resolve.mjs +172 -0
- package/scripts/lib/css-local-check.mjs +267 -0
- package/scripts/lib/design-token-manifest.mjs +499 -0
- package/scripts/lib/doc-sections.mjs +250 -0
- package/scripts/lib/ensure-manifest.mjs +28 -5
- package/scripts/lib/eslint-disable.mjs +146 -0
- package/scripts/lib/eslint-plugin.d.ts +68 -1
- package/scripts/lib/eslint-plugin.mjs +1525 -30
- package/scripts/lib/form-v-model.mjs +190 -0
- package/scripts/lib/hand-imported-styles.mjs +326 -0
- package/scripts/lib/hand-roll-hints.mjs +50 -6
- package/scripts/lib/html-attributes.mjs +353 -0
- package/scripts/lib/local-components.mjs +177 -0
- package/scripts/lib/mcp-config.mjs +87 -58
- package/scripts/lib/migrate-fix.mjs +62 -3
- package/scripts/lib/project-variant-dts.mjs +144 -0
- package/scripts/lib/public-type-exports.mjs +273 -0
- package/scripts/lib/reactive-v-model.mjs +221 -0
- package/scripts/lib/slot-key.mjs +147 -0
- package/scripts/lib/slot-renames.mjs +46 -0
- package/scripts/lib/squashed-slot-words.mjs +133 -0
- package/scripts/lib/validate-bb-markup.mjs +2350 -105
- package/scripts/lib/wrapper-props.mjs +188 -0
- package/dist/ai/BbBaseCheckbox.md +0 -49
- package/dist/ai/BbBaseCheckboxGroup.md +0 -64
- package/dist/ai/BbBaseColorInput.md +0 -65
- package/dist/ai/BbBaseColorPalette.md +0 -39
- package/dist/ai/BbBaseDatePicker.md +0 -43
- package/dist/ai/BbBaseDatePickerInput.md +0 -73
- package/dist/ai/BbBaseInputContainer.md +0 -48
- package/dist/ai/BbBaseNumberInput.md +0 -71
- package/dist/ai/BbBaseRadio.md +0 -46
- package/dist/ai/BbBaseRadioGroup.md +0 -64
- package/dist/ai/BbBaseRating.md +0 -52
- package/dist/ai/BbBaseSelect.md +0 -88
- package/dist/ai/BbBaseSlider.md +0 -56
- package/dist/ai/BbBaseSwitch.md +0 -49
- package/dist/ai/BbBaseSwitchGroup.md +0 -64
- package/dist/ai/BbBaseTag.md +0 -68
- package/dist/ai/BbBaseTextInput.md +0 -75
- package/dist/ai/BbBaseTextarea.md +0 -62
- package/dist/ai/BbBaseTimePickerInput.md +0 -62
- package/dist/ai/BbTabsPanes.md +0 -27
- package/dist/ai/CommonInputWrapper.md +0 -42
- package/dist/ai/source/BbBaseCheckbox.md +0 -461
- package/dist/ai/source/BbBaseCheckboxGroup.md +0 -554
- package/dist/ai/source/BbBaseColorInput.md +0 -431
- package/dist/ai/source/BbBaseColorPalette.md +0 -942
- package/dist/ai/source/BbBaseDatePicker.md +0 -1448
- package/dist/ai/source/BbBaseDatePickerInput.md +0 -1940
- package/dist/ai/source/BbBaseNumberInput.md +0 -617
- package/dist/ai/source/BbBaseRadio.md +0 -381
- package/dist/ai/source/BbBaseRadioGroup.md +0 -539
- package/dist/ai/source/BbBaseRating.md +0 -607
- package/dist/ai/source/BbBaseSelect.md +0 -945
- package/dist/ai/source/BbBaseSlider.md +0 -1144
- package/dist/ai/source/BbBaseSwitch.md +0 -246
- package/dist/ai/source/BbBaseSwitchGroup.md +0 -302
- package/dist/ai/source/BbBaseTag.md +0 -694
- package/dist/ai/source/BbBaseTextInput.md +0 -511
- package/dist/ai/source/BbBaseTextarea.md +0 -504
- package/dist/ai/source/BbBaseTimePickerInput.md +0 -1037
- package/dist/ai/source/BbTabsPanes.md +0 -377
- package/dist/ai/source/CommonInputWrapper.md +0 -584
- package/dist/alert-variants.d.ts +0 -19
- package/dist/assets/svgs/sort.svg_raw.js +0 -4
- package/dist/badge-variants.d.ts +0 -19
- package/dist/button-variants.d.ts +0 -21
- package/dist/components/BbBadge/BadgeAppendIcon.vue.d.ts +0 -3
- package/dist/components/BbBadge/BadgeAppendIcon.vue.js +0 -5
- package/dist/components/BbBadge/BadgeAppendIcon.vue_vue_type_script_setup_true_lang.js +0 -22
- package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue.d.ts +0 -40
- package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue.js +0 -6
- package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue_vue_type_script_setup_true_lang.js +0 -145
- package/dist/components/BbBaseCheckbox/types.d.ts +0 -224
- package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue.d.ts +0 -19
- package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue.js +0 -6
- package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue_vue_type_script_setup_true_lang.js +0 -190
- package/dist/components/BbBaseCheckboxGroup/types.d.ts +0 -257
- package/dist/components/BbBaseColorInput/BbBaseColorInput.vue.d.ts +0 -43
- package/dist/components/BbBaseColorInput/BbBaseColorInput.vue.js +0 -6
- package/dist/components/BbBaseColorInput/BbBaseColorInput.vue_vue_type_script_setup_true_lang.js +0 -184
- package/dist/components/BbBaseColorInput/types.d.ts +0 -219
- package/dist/components/BbBaseColorPalette/BbBaseColorPalette.vue.d.ts +0 -13
- package/dist/components/BbBaseColorPalette/BbBaseColorPalette.vue_vue_type_script_setup_true_lang.js +0 -395
- package/dist/components/BbBaseColorPalette/types.d.ts +0 -40
- package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue.d.ts +0 -20
- package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue.js +0 -6
- package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue_vue_type_script_setup_true_lang.js +0 -407
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue.d.ts +0 -47
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue.js +0 -5
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue_vue_type_script_setup_true_lang.js +0 -318
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue.js +0 -5
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue_vue_type_script_setup_true_lang.js +0 -115
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerYearSelector.vue.js +0 -5
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerYearSelector.vue_vue_type_script_setup_true_lang.js +0 -116
- package/dist/components/BbBaseDatePicker/types.d.ts +0 -108
- package/dist/components/BbBaseDatePicker/useDatePickerContext.js +0 -11
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue.d.ts +0 -31
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue.js +0 -6
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +0 -737
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerTimeSelector.vue.js +0 -6
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerTimeSelector.vue_vue_type_script_setup_true_lang.js +0 -204
- package/dist/components/BbBaseDatePickerInput/types.d.ts +0 -325
- package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue.js +0 -6
- package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue_vue_type_script_setup_true_lang.js +0 -190
- package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue.d.ts +0 -52
- package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue.js +0 -6
- package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue_vue_type_script_setup_true_lang.js +0 -215
- package/dist/components/BbBaseNumberInput/types.d.ts +0 -211
- package/dist/components/BbBaseRadio/BbBaseRadio.vue.d.ts +0 -33
- package/dist/components/BbBaseRadio/BbBaseRadio.vue_vue_type_script_setup_true_lang.js +0 -113
- package/dist/components/BbBaseRadio/types.d.ts +0 -179
- package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue.d.ts +0 -19
- package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue_vue_type_script_setup_true_lang.js +0 -179
- package/dist/components/BbBaseRadioGroup/types.d.ts +0 -223
- package/dist/components/BbBaseRating/BbBaseRating.vue.d.ts +0 -38
- package/dist/components/BbBaseRating/BbBaseRating.vue.js +0 -6
- package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +0 -192
- package/dist/components/BbBaseRating/types.d.ts +0 -176
- package/dist/components/BbBaseSelect/BbBaseSelect.vue.d.ts +0 -19
- package/dist/components/BbBaseSelect/BbBaseSelect.vue.js +0 -6
- package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +0 -311
- package/dist/components/BbBaseSelect/types.d.ts +0 -297
- package/dist/components/BbBaseSlider/BbBaseSlider.vue.d.ts +0 -35
- package/dist/components/BbBaseSlider/BbBaseSlider.vue.js +0 -6
- package/dist/components/BbBaseSlider/BbBaseSlider.vue_vue_type_script_setup_true_lang.js +0 -340
- package/dist/components/BbBaseSlider/types.d.ts +0 -200
- package/dist/components/BbBaseSwitch/BbBaseSwitch.vue.d.ts +0 -35
- package/dist/components/BbBaseSwitch/BbBaseSwitch.vue_vue_type_script_setup_true_lang.js +0 -146
- package/dist/components/BbBaseSwitch/types.d.ts +0 -10
- package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue.d.ts +0 -19
- package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue.js +0 -6
- package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue_vue_type_script_setup_true_lang.js +0 -190
- package/dist/components/BbBaseSwitchGroup/types.d.ts +0 -8
- package/dist/components/BbBaseTag/BbBaseTag.vue.d.ts +0 -50
- package/dist/components/BbBaseTag/BbBaseTag.vue.js +0 -6
- package/dist/components/BbBaseTag/BbBaseTag.vue_vue_type_script_setup_true_lang.js +0 -266
- package/dist/components/BbBaseTag/types.d.ts +0 -160
- package/dist/components/BbBaseTextInput/BbBaseTextInput.vue.d.ts +0 -45
- package/dist/components/BbBaseTextInput/BbBaseTextInput.vue.js +0 -6
- package/dist/components/BbBaseTextInput/BbBaseTextInput.vue_vue_type_script_setup_true_lang.js +0 -200
- package/dist/components/BbBaseTextInput/types.d.ts +0 -209
- package/dist/components/BbBaseTextarea/BbBaseTextarea.vue.d.ts +0 -39
- package/dist/components/BbBaseTextarea/BbBaseTextarea.vue.js +0 -6
- package/dist/components/BbBaseTextarea/BbBaseTextarea.vue_vue_type_script_setup_true_lang.js +0 -187
- package/dist/components/BbBaseTextarea/types.d.ts +0 -170
- package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue.d.ts +0 -27
- package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue.js +0 -6
- package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +0 -519
- package/dist/components/BbBaseTimePickerInput/types.d.ts +0 -151
- 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 -35
- package/dist/components/CommonInputWrapper/CommonInputWrapper.vue.js +0 -6
- package/dist/components/CommonInputWrapper/CommonInputWrapper.vue_vue_type_script_setup_true_lang.js +0 -107
- package/dist/confirm-variants.d.ts +0 -17
- package/dist/dropdown-item-variants.d.ts +0 -17
- package/dist/indicator-variants.d.ts +0 -20
- package/dist/locale-registry.d.ts +0 -37
- package/dist/toast-variants.d.ts +0 -20
- package/dist/tooltip-variants.d.ts +0 -17
- package/dist/types/InputDirection.d.ts +0 -15
- package/dist/validated/BbCheckbox.vue.d.ts +0 -29
- package/dist/validated/BbCheckbox.vue.js +0 -5
- package/dist/validated/BbCheckbox.vue_vue_type_script_setup_true_lang.js +0 -75
- package/dist/validated/BbCheckboxGroup.vue.d.ts +0 -34
- package/dist/validated/BbCheckboxGroup.vue.js +0 -5
- package/dist/validated/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +0 -82
- package/dist/validated/BbColorInput.vue.d.ts +0 -37
- package/dist/validated/BbColorInput.vue.js +0 -5
- package/dist/validated/BbColorInput.vue_vue_type_script_setup_true_lang.js +0 -75
- package/dist/validated/BbDatePickerInput.vue.d.ts +0 -41
- package/dist/validated/BbDatePickerInput.vue.js +0 -5
- package/dist/validated/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +0 -90
- package/dist/validated/BbDropzone.vue.d.ts +0 -24
- package/dist/validated/BbDropzone.vue.js +0 -5
- package/dist/validated/BbDropzone.vue_vue_type_script_setup_true_lang.js +0 -45
- package/dist/validated/BbForm.vue.d.ts +0 -28
- package/dist/validated/BbForm.vue_vue_type_script_setup_true_lang.js +0 -42
- package/dist/validated/BbNumberInput.vue.d.ts +0 -36
- package/dist/validated/BbNumberInput.vue.js +0 -5
- package/dist/validated/BbNumberInput.vue_vue_type_script_setup_true_lang.js +0 -72
- package/dist/validated/BbRadioGroup.vue.d.ts +0 -34
- package/dist/validated/BbRadioGroup.vue.js +0 -5
- package/dist/validated/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +0 -78
- package/dist/validated/BbRating.vue.d.ts +0 -30
- package/dist/validated/BbRating.vue.js +0 -5
- package/dist/validated/BbRating.vue_vue_type_script_setup_true_lang.js +0 -63
- package/dist/validated/BbSelect.vue.d.ts +0 -46
- package/dist/validated/BbSelect.vue.js +0 -5
- package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +0 -109
- package/dist/validated/BbSlider.vue.d.ts +0 -36
- package/dist/validated/BbSlider.vue.js +0 -5
- package/dist/validated/BbSlider.vue_vue_type_script_setup_true_lang.js +0 -69
- package/dist/validated/BbSwitch.vue.d.ts +0 -29
- package/dist/validated/BbSwitch.vue.js +0 -5
- package/dist/validated/BbSwitch.vue_vue_type_script_setup_true_lang.js +0 -75
- package/dist/validated/BbSwitchGroup.vue.d.ts +0 -34
- package/dist/validated/BbSwitchGroup.vue.js +0 -5
- package/dist/validated/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +0 -82
- package/dist/validated/BbTag.vue.d.ts +0 -36
- package/dist/validated/BbTag.vue.js +0 -5
- package/dist/validated/BbTag.vue_vue_type_script_setup_true_lang.js +0 -72
- package/dist/validated/BbTextInput.vue.d.ts +0 -36
- package/dist/validated/BbTextInput.vue.js +0 -5
- package/dist/validated/BbTextInput.vue_vue_type_script_setup_true_lang.js +0 -72
- package/dist/validated/BbTextarea.vue.d.ts +0 -36
- package/dist/validated/BbTextarea.vue.js +0 -5
- package/dist/validated/BbTextarea.vue_vue_type_script_setup_true_lang.js +0 -70
- package/dist/validated/BbTimePickerInput.vue.d.ts +0 -37
- package/dist/validated/BbTimePickerInput.vue.js +0 -5
- package/dist/validated/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +0 -81
- package/dist/validated/index.d.ts +0 -43
- package/dist/validated/useValidatedField.d.ts +0 -93
- package/dist/validated/useValidatedField.js +0 -51
- package/dist/validated.js +0 -21
- /package/dist/components/{BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue.d.ts → BbCalendar/CalendarMonthPanel.vue.d.ts} +0 -0
- /package/dist/components/{BbBaseDatePicker/BbBaseDatePickerYearSelector.vue.d.ts → BbCalendar/CalendarYearPanel.vue.d.ts} +0 -0
- /package/dist/{components/BbBaseDatePicker/typeahead.d.ts → composables/useTypeahead.d.ts} +0 -0
- /package/dist/{components/BbBaseDatePicker/typeahead.js → composables/useTypeahead.js} +0 -0
package/bin/bitboss-ui-mcp.mjs
CHANGED
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
* is stable across zod 3 and 4, so the lower bound costs nothing.
|
|
26
26
|
*
|
|
27
27
|
* Run standalone: `npx bitboss-ui mcp` (delegated from bin/bitboss-ui.mjs).
|
|
28
|
-
*
|
|
28
|
+
* Registered with every MCP-capable harness by `npx bitboss-ui ai-init`
|
|
29
|
+
* (on by default; `--no-mcp` skips it).
|
|
29
30
|
*/
|
|
30
31
|
|
|
31
32
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
@@ -34,8 +35,17 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
|
34
35
|
import { dirname, join, sep } from 'node:path';
|
|
35
36
|
import { fileURLToPath } from 'node:url';
|
|
36
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';
|
|
37
46
|
import {
|
|
38
47
|
loadManifest,
|
|
48
|
+
possessive,
|
|
39
49
|
validateVueSnippet,
|
|
40
50
|
} from '../scripts/lib/validate-bb-markup.mjs';
|
|
41
51
|
|
|
@@ -70,26 +80,217 @@ function getManifestMap() {
|
|
|
70
80
|
return cachedManifestMap;
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
/** `search_components({ query })` —
|
|
83
|
+
/** `search_components({ query })` — token match over name/description. */
|
|
74
84
|
const SEARCH_DEFAULT_LIMIT = 20;
|
|
75
85
|
|
|
76
86
|
/**
|
|
77
|
-
*
|
|
87
|
+
* Ranking for the manifest's type tiers. `api` types are the ones an app is
|
|
88
|
+
* expected to reach for — they appear in a component's own props or events —
|
|
89
|
+
* so they lead. `wrapper` (`<Component>Props`) matters only when wrapping one
|
|
90
|
+
* of our components, and `advanced` is registries, contexts and adapters.
|
|
91
|
+
* Publishing 166 exports as a flat list made every one look equally important.
|
|
92
|
+
*/
|
|
93
|
+
const TYPE_USAGE_RANK = { api: 0, wrapper: 1, advanced: 2 };
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* WHY THIS IS NOT A PLAIN SUBSTRING MATCH ANY MORE.
|
|
97
|
+
*
|
|
98
|
+
* It was, and a consumer pilot (2026-09-09) hand-rolled three components the
|
|
99
|
+
* library ships because the whole query string had to appear verbatim in one
|
|
100
|
+
* field. `table` found BbTable; `data table` — the term most of the industry
|
|
101
|
+
* uses — found NOTHING, because no name or description contains that exact
|
|
102
|
+
* pair. Same for `command palette`, `stepper wizard`, `skeleton loading`.
|
|
103
|
+
*
|
|
104
|
+
* An empty result is the worst possible answer for a tool whose stated job is
|
|
105
|
+
* "find the right Bb* component before writing usage code": it is
|
|
106
|
+
* indistinguishable from "nothing here, build it yourself". So two things
|
|
107
|
+
* changed. Multi-word queries are TOKENISED, and every search — hit or miss —
|
|
108
|
+
* consults the component picker's "Not in this library" table, which is where
|
|
109
|
+
* the authoritative answers for the invented names already lived.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/** English filler that would otherwise match half the corpus on its own. */
|
|
113
|
+
const SEARCH_STOPWORDS = new Set([
|
|
114
|
+
'a',
|
|
115
|
+
'an',
|
|
116
|
+
'the',
|
|
117
|
+
'and',
|
|
118
|
+
'or',
|
|
119
|
+
'not',
|
|
120
|
+
'no',
|
|
121
|
+
'is',
|
|
122
|
+
'it',
|
|
123
|
+
'in',
|
|
124
|
+
'on',
|
|
125
|
+
'for',
|
|
126
|
+
'of',
|
|
127
|
+
'to',
|
|
128
|
+
'with',
|
|
129
|
+
'component',
|
|
130
|
+
'components',
|
|
131
|
+
'ui',
|
|
132
|
+
'view',
|
|
133
|
+
'widget',
|
|
134
|
+
'element',
|
|
135
|
+
]);
|
|
136
|
+
|
|
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
|
+
*/
|
|
145
|
+
function tokenize(query) {
|
|
146
|
+
const raw = String(query ?? '')
|
|
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));
|
|
162
|
+
// An all-stopword query ('the view') still deserves its literal reading
|
|
163
|
+
// rather than an empty token list that matches everything.
|
|
164
|
+
return meaningful.length > 0 ? meaningful : raw;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Relevance score for one component against ONE lowercased token. Higher wins;
|
|
78
169
|
* 0 means "no match". Name hits always outrank description hits so
|
|
79
170
|
* `search_components({ query: 'table' })` leads with BbTable, not with every
|
|
80
171
|
* component whose prose mentions a table.
|
|
81
172
|
*/
|
|
82
|
-
function
|
|
173
|
+
function scoreToken(component, needle) {
|
|
83
174
|
const name = component.name.toLowerCase();
|
|
84
175
|
const description = (component.description ?? '').toLowerCase();
|
|
85
176
|
if (name === needle) return 100;
|
|
86
177
|
if (name === `bb${needle}`) return 95; // 'button' → BbButton
|
|
87
178
|
if (name.startsWith(needle) || name.startsWith(`bb${needle}`)) return 80;
|
|
88
179
|
if (name.includes(needle)) return 60;
|
|
89
|
-
|
|
180
|
+
// Descriptions match at a WORD START, never mid-word: a plain substring made
|
|
181
|
+
// 'not' hit "notification" and returned four components for a nonsense
|
|
182
|
+
// query. Trailing suffixes still count, so 'card' finds "cards".
|
|
183
|
+
if (new RegExp(`\\b${needle}`).test(description)) return 30;
|
|
90
184
|
return 0;
|
|
91
185
|
}
|
|
92
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Score against every token. A component that matches MORE of the query ranks
|
|
189
|
+
* above one that matches a single token harder, so `data table` puts BbTable
|
|
190
|
+
* (both tokens) above a component whose description merely says "data".
|
|
191
|
+
*/
|
|
192
|
+
function scoreComponent(component, tokens) {
|
|
193
|
+
let total = 0;
|
|
194
|
+
let matched = 0;
|
|
195
|
+
for (const token of tokens) {
|
|
196
|
+
const score = scoreToken(component, token);
|
|
197
|
+
if (score > 0) {
|
|
198
|
+
matched += 1;
|
|
199
|
+
total += score;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return matched === 0 ? { matched: 0, score: 0 } : { matched, score: total };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The picker's "Not in this library" table, parsed from the shipped guide so
|
|
207
|
+
* there is exactly one source of truth. Rows carry both kinds of answer:
|
|
208
|
+
* redirects (`BbDrawer` → "It is BbOffCanvas") and verdicts (`BbCard` → "Does
|
|
209
|
+
* not exist — a card is a <div> with tokens"). Both are better answers to a
|
|
210
|
+
* miss than an empty array.
|
|
211
|
+
*/
|
|
212
|
+
const PICKER_PATH = join(AI_ROOT, 'guides', 'component-picker.md');
|
|
213
|
+
let cachedNotInLibrary = null;
|
|
214
|
+
|
|
215
|
+
/** `'BbDataTable'` → `['bbdatatable', 'data', 'table']`. */
|
|
216
|
+
function aliasTokens(cell) {
|
|
217
|
+
const words = cell
|
|
218
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') // links → their text
|
|
219
|
+
.replace(/[`*_]/g, '')
|
|
220
|
+
.split(/[^A-Za-z0-9]+/)
|
|
221
|
+
.filter(Boolean);
|
|
222
|
+
const tokens = new Set();
|
|
223
|
+
for (const word of words) {
|
|
224
|
+
tokens.add(word.toLowerCase());
|
|
225
|
+
// Split CamelCase so `BbDataTable` is reachable by 'data' and 'table'.
|
|
226
|
+
for (const part of word.replace(/^Bb/, '').split(/(?=[A-Z])/))
|
|
227
|
+
if (part.length > 1) tokens.add(part.toLowerCase());
|
|
228
|
+
}
|
|
229
|
+
return [...tokens].filter((t) => !SEARCH_STOPWORDS.has(t) && t !== 'bb');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function loadNotInLibrary() {
|
|
233
|
+
if (cachedNotInLibrary) return cachedNotInLibrary;
|
|
234
|
+
cachedNotInLibrary = [];
|
|
235
|
+
if (!existsSync(PICKER_PATH)) return cachedNotInLibrary;
|
|
236
|
+
const md = readFileSync(PICKER_PATH, 'utf-8');
|
|
237
|
+
const section = md.split(/^## Not in this library\s*$/m)[1];
|
|
238
|
+
if (!section) return cachedNotInLibrary;
|
|
239
|
+
for (const line of section.split('\n')) {
|
|
240
|
+
if (!line.startsWith('|')) continue;
|
|
241
|
+
// Tolerate a missing trailing `|`: markdown renders such a row fine, so
|
|
242
|
+
// dropping it here would silently lose an answer (it already did once).
|
|
243
|
+
const cells = line
|
|
244
|
+
.replace(/^\|/, '')
|
|
245
|
+
.replace(/\|\s*$/, '')
|
|
246
|
+
.split('|');
|
|
247
|
+
if (cells.length < 2) continue;
|
|
248
|
+
const assumed = cells[0].trim();
|
|
249
|
+
const reality = cells[1].trim();
|
|
250
|
+
// Skip the header and its `---` separator.
|
|
251
|
+
if (!assumed || /^-+$/.test(assumed) || /^Assumed\b/i.test(assumed))
|
|
252
|
+
continue;
|
|
253
|
+
cachedNotInLibrary.push({
|
|
254
|
+
assumed,
|
|
255
|
+
reality,
|
|
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
|
+
),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return cachedNotInLibrary;
|
|
264
|
+
}
|
|
265
|
+
|
|
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
|
+
*/
|
|
275
|
+
function matchNotInLibrary(tokens) {
|
|
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
|
+
);
|
|
285
|
+
return loadNotInLibrary()
|
|
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 }));
|
|
292
|
+
}
|
|
293
|
+
|
|
93
294
|
function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
94
295
|
const manifest = loadComponentsManifest();
|
|
95
296
|
const needle = String(query ?? '')
|
|
@@ -104,13 +305,88 @@ function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
|
104
305
|
? manifest.components
|
|
105
306
|
: manifest.components.filter((c) => !c.referenceOnly);
|
|
106
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
|
+
|
|
319
|
+
const tokens = tokenize(needle);
|
|
320
|
+
const notInLibrary = matchNotInLibrary(tokens);
|
|
321
|
+
|
|
322
|
+
// A matched picker row NAMES the real component ("`BbDrawer` / `BbSheet` →
|
|
323
|
+
// It is BbOffCanvas"). Rank those first: the picker knows the answer, and
|
|
324
|
+
// returning nothing for `drawer` while BbOffCanvas ships is the whole bug.
|
|
325
|
+
const redirected = new Set();
|
|
326
|
+
for (const { reality } of notInLibrary)
|
|
327
|
+
for (const name of reality.match(/\bBb[A-Z][A-Za-z]+/g) ?? [])
|
|
328
|
+
redirected.add(name);
|
|
329
|
+
|
|
107
330
|
const scored = pool
|
|
108
|
-
.map((c) =>
|
|
331
|
+
.map((c) => {
|
|
332
|
+
const base = scoreComponent(c, tokens);
|
|
333
|
+
const bonus = redirected.has(c.name) ? 90 : 0;
|
|
334
|
+
return bonus > 0
|
|
335
|
+
? { c, matched: base.matched + 1, score: base.score + bonus }
|
|
336
|
+
: { c, ...base };
|
|
337
|
+
})
|
|
109
338
|
.filter(({ score }) => score > 0)
|
|
110
|
-
//
|
|
111
|
-
|
|
339
|
+
// More of the query matched wins first; then strength; then alphabetical
|
|
340
|
+
// so results are stable between calls.
|
|
341
|
+
.sort(
|
|
342
|
+
(a, b) =>
|
|
343
|
+
b.matched - a.matched ||
|
|
344
|
+
b.score - a.score ||
|
|
345
|
+
a.c.name.localeCompare(b.c.name)
|
|
346
|
+
);
|
|
112
347
|
|
|
113
348
|
const shown = scored.slice(0, max);
|
|
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
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Public type exports whose name matches the query. Scored the same way as
|
|
362
|
+
* a component name (exact, prefix, substring) but never mixed into the
|
|
363
|
+
* component list — a type is not something you render, and ranking it
|
|
364
|
+
* beside components is how it gets written as a tag.
|
|
365
|
+
*/
|
|
366
|
+
const matchedTypes = (manifest.types ?? [])
|
|
367
|
+
.filter((type) => {
|
|
368
|
+
const lower = type.name.toLowerCase();
|
|
369
|
+
return tokens.some(
|
|
370
|
+
(token) => lower === token || lower.includes(token.toLowerCase())
|
|
371
|
+
);
|
|
372
|
+
})
|
|
373
|
+
// `api` first: those are the types you meet writing markup. The rest are
|
|
374
|
+
// wrapper props and internals, which rank below anything you would use.
|
|
375
|
+
.sort((a, b) => TYPE_USAGE_RANK[a.usage] - TYPE_USAGE_RANK[b.usage])
|
|
376
|
+
.slice(0, max)
|
|
377
|
+
.map((type) => ({
|
|
378
|
+
name: type.name,
|
|
379
|
+
kind: 'type',
|
|
380
|
+
usage: type.usage,
|
|
381
|
+
component: type.component,
|
|
382
|
+
...(type.deprecated ? { deprecated: true } : {}),
|
|
383
|
+
...(type.replacedBy ? { replacedBy: type.replacedBy } : {}),
|
|
384
|
+
usedBy: type.usedBy ?? [],
|
|
385
|
+
// The short answer only; get_component(name) has the definition.
|
|
386
|
+
...(type.values ? { values: type.values } : {}),
|
|
387
|
+
import: `import type { ${type.replacedBy ?? type.name} } from 'bitboss-ui';`,
|
|
388
|
+
}));
|
|
389
|
+
|
|
114
390
|
return {
|
|
115
391
|
query: query ?? '',
|
|
116
392
|
count: shown.length,
|
|
@@ -128,6 +404,30 @@ function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
|
128
404
|
referenceOnly: Boolean(c.referenceOnly),
|
|
129
405
|
contract: c.contract,
|
|
130
406
|
})),
|
|
407
|
+
// Authoritative answers for names agents invent. Present on hits too: a
|
|
408
|
+
// `card` query that ranks BbBaseButton (its description mentions cards)
|
|
409
|
+
// still needs the picker's verdict that there is no BbCard.
|
|
410
|
+
...(notInLibrary.length > 0 ? { notInLibrary } : {}),
|
|
411
|
+
// A query that names a public TYPE gets it back as a type, so the answer
|
|
412
|
+
// to "is BbTableColumn a thing" is never an empty component list. The
|
|
413
|
+
// consumer who hit this concluded the export was undocumented and
|
|
414
|
+
// allowlisted the tag.
|
|
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
|
+
: {}),
|
|
423
|
+
guidance:
|
|
424
|
+
notInLibrary.length > 0
|
|
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.'
|
|
426
|
+
: shown.length > 0
|
|
427
|
+
? 'Ranked by relevance. Confirm props with `get_component` before writing usage code.'
|
|
428
|
+
: matchedTypes.length > 0
|
|
429
|
+
? 'No component matches, but `types` does: these are TYPE exports. Import one for the value you pass to the prop named in its `usedBy` — never write it as a tag, and never allowlist it.'
|
|
430
|
+
: 'No manifest match and no picker entry. Zero results does NOT mean "not covered" — search a single plain noun (`table`, not `data table`), then read the guide `component-picker` (its "Not in this library" table is the full list of names that do not exist and what to use instead) before building anything by hand.',
|
|
131
431
|
};
|
|
132
432
|
}
|
|
133
433
|
|
|
@@ -141,7 +441,7 @@ function searchComponents({ query, limit, includeReferenceOnly } = {}) {
|
|
|
141
441
|
* is what makes an agent invent API, which is precisely what this server
|
|
142
442
|
* exists to prevent.
|
|
143
443
|
*/
|
|
144
|
-
const MARKDOWN_DEFAULT_MAX_CHARS =
|
|
444
|
+
const MARKDOWN_DEFAULT_MAX_CHARS = 16000; // was 40 000 (MCP panel 2026-09-26)
|
|
145
445
|
|
|
146
446
|
/**
|
|
147
447
|
* Cut `text` to `maxChars` on a line boundary, reporting what was dropped.
|
|
@@ -176,16 +476,683 @@ function truncateMarkdown(text, maxChars, readHint) {
|
|
|
176
476
|
};
|
|
177
477
|
}
|
|
178
478
|
|
|
179
|
-
|
|
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
|
+
// A partial name (`BbInput`) matches the components that contain it
|
|
584
|
+
// (BbTextInput, BbNumberInput…) before any edit-distance guess.
|
|
585
|
+
const containing = manifest.components
|
|
586
|
+
.filter((c) => !c.referenceOnly)
|
|
587
|
+
.map((c) => c.name)
|
|
588
|
+
.filter((n) => needle.length >= 3 && n.toLowerCase().includes(needle));
|
|
589
|
+
if (containing.length) return containing.slice(0, Math.max(count, 5));
|
|
590
|
+
return manifest.components
|
|
591
|
+
.filter((c) => !c.referenceOnly)
|
|
592
|
+
.map((c) => ({
|
|
593
|
+
name: c.name,
|
|
594
|
+
distance: editDistance(needle, c.name.toLowerCase().replace(/^bb/, '')),
|
|
595
|
+
}))
|
|
596
|
+
.sort((a, b) => a.distance - b.distance)
|
|
597
|
+
.slice(0, count)
|
|
598
|
+
.filter(({ distance }) => distance <= Math.max(3, needle.length / 2))
|
|
599
|
+
.map(({ name: found }) => found);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* A component whose props are generic over its ROW (`BbTableProps<Row>`,
|
|
604
|
+
* `BbSelectProps<Item>`): a prop type names the parameter `T`.
|
|
605
|
+
*/
|
|
606
|
+
const isGeneric = (entry) =>
|
|
607
|
+
entry.props.some((p) =>
|
|
608
|
+
/(^|[^A-Za-z0-9_])T([^A-Za-z0-9_]|$)/.test(p.type ?? '')
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
const propsType = (entry) =>
|
|
612
|
+
`${entry.name}Props${isGeneric(entry) ? '<Row>' : ''}['pt']`;
|
|
613
|
+
|
|
614
|
+
/** `row: '…', 'row:selected': '…'` — the object form, from the real parts. */
|
|
615
|
+
function ptExample(entry) {
|
|
616
|
+
const names = entry.pt.parts.map((p) => p.name);
|
|
617
|
+
const part =
|
|
618
|
+
['row', 'option', 'item', 'icon', 'label'].find((n) => names.includes(n)) ??
|
|
619
|
+
names[0];
|
|
620
|
+
// A state that is on for this part: per-node states name their parts.
|
|
621
|
+
const state = (
|
|
622
|
+
entry.pt.states?.find((s) =>
|
|
623
|
+
new RegExp(`\`${part}\``).test(s.description ?? '')
|
|
624
|
+
) ??
|
|
625
|
+
entry.pt.states?.find((s) => /^Component-wide/.test(s.description ?? ''))
|
|
626
|
+
)?.name;
|
|
627
|
+
return `${part}: '…'${state ? `, '${part}:${state}': '…'` : ''}`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const PT_TYPING_READ =
|
|
631
|
+
"get_guide({ name: 'passthrough.md', sections: ['Typing `pt` in `<script>`'] })";
|
|
632
|
+
|
|
633
|
+
function ptTyping(entry) {
|
|
634
|
+
// Both after-bench misses (2026-09-24) typed a table-agnostic preset
|
|
635
|
+
// `BbTableProps<any>['pt']`: say what to write when there is no row type.
|
|
636
|
+
const anyRow = isGeneric(entry)
|
|
637
|
+
? ` (\`${entry.name}Props['pt']\` for a preset that fits any row type, never \`<any>\`)`
|
|
638
|
+
: '';
|
|
639
|
+
return `In <script> (a const, a preset, a wrapper's \`pt\` prop) type it as \`${propsType(entry)}\`${anyRow} with \`satisfies\` for a const; a split global map uses \`definePt\` from 'bitboss-ui/vite'. PtValue / PtMap are not exported. ${PT_TYPING_READ}`;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* The next reads a component answer points at, each one call. Built from what
|
|
644
|
+
* the contract itself links, so it cannot name a guide the package lacks.
|
|
645
|
+
*/
|
|
646
|
+
function seeAlso(entry, contract) {
|
|
647
|
+
const out = [];
|
|
648
|
+
// The option-list family links the items playbook; its wrapper section is
|
|
649
|
+
// where "write a MemberSelect around BbSelect" is answered (Q52.6.1).
|
|
650
|
+
if (/options-items-playbook\.md/.test(contract) && isGeneric(entry))
|
|
651
|
+
out.push(
|
|
652
|
+
`Your own ${entry.name} with fixed items (a CustomerSelect): a preset, one call to definePreset (a helper you copy from the guide, not a bitboss-ui export): get_guide({ name: 'preset-components.md' }). Wrapping BbSelect in your own component with its own logic: get_guide({ name: 'options-items-playbook.md', sections: ['Wrapping BbSelect in your own component'] })`
|
|
653
|
+
);
|
|
654
|
+
if (entry.validated)
|
|
655
|
+
out.push(
|
|
656
|
+
`Validation (rules or a Zod / Valibot / Yup schema): get_guide({ name: 'validated-forms.md' })`
|
|
657
|
+
);
|
|
658
|
+
return out;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/** Guide sections of a contract: everything under `## Usage & Guidelines`. */
|
|
662
|
+
function contractGuideSections(markdown) {
|
|
663
|
+
const all = splitSections(markdown);
|
|
664
|
+
const start = all.findIndex(
|
|
665
|
+
(s) => s.level === 2 && /^Usage & Guidelines/i.test(s.title)
|
|
666
|
+
);
|
|
667
|
+
if (start === -1) return [];
|
|
668
|
+
const end = all.findIndex((s, i) => i > start && s.level === 2);
|
|
669
|
+
return all.slice(start + 1, end === -1 ? undefined : end);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/*
|
|
673
|
+
* A section is served whole up to SECTION_CAP chars. Past that (the review
|
|
674
|
+
* found a 142.9K `migration/v2-to-v3.md` section behind one search hit) it
|
|
675
|
+
* answers its own outline: ask again for a sub-section, or pass maxChars: 0.
|
|
676
|
+
*/
|
|
677
|
+
const SECTION_CAP = 16000;
|
|
678
|
+
function sectionAnswer(sec, all, maxChars) {
|
|
679
|
+
if (maxChars === 0) return { title: sec.title, text: sec.text };
|
|
680
|
+
const cap = maxChars > 0 ? maxChars : SECTION_CAP;
|
|
681
|
+
if (sec.text.length <= cap) return { title: sec.title, text: sec.text };
|
|
682
|
+
const inner = all.filter(
|
|
683
|
+
(other) =>
|
|
684
|
+
other !== sec && other.level > sec.level && sec.text.includes(other.text)
|
|
685
|
+
);
|
|
686
|
+
// No sub-sections to point at (v2-to-v3 § 5.6, 68K), or an explicit cap:
|
|
687
|
+
// the text itself, cut — never an empty outline (adversarial review 2).
|
|
688
|
+
if (!inner.length || maxChars > 0) {
|
|
689
|
+
const cut = sec.text.slice(0, cap);
|
|
690
|
+
const end = cut.lastIndexOf('\n');
|
|
691
|
+
return {
|
|
692
|
+
title: sec.title,
|
|
693
|
+
text: end > cap / 2 ? cut.slice(0, end) : cut,
|
|
694
|
+
truncated: true,
|
|
695
|
+
chars: sec.text.length,
|
|
696
|
+
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' : ''}.`,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
// Sub-sections: the section's own lead-in (up to 4K) and its outline.
|
|
700
|
+
const own = sec.own.length > 4000 ? `${sec.own.slice(0, 4000)} …` : sec.own;
|
|
701
|
+
return {
|
|
702
|
+
title: sec.title,
|
|
703
|
+
chars: sec.text.length,
|
|
704
|
+
text: own,
|
|
705
|
+
outline: outline(inner),
|
|
706
|
+
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.`,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/*
|
|
711
|
+
* A multi-part answer (several sections, members or recipe files) stops at
|
|
712
|
+
* ANSWER_CAP chars and names the rest: one call per part never came to 149K
|
|
713
|
+
* (BbTable, every section at once) or 52K (every member) by accident.
|
|
714
|
+
*/
|
|
715
|
+
const ANSWER_CAP = 24000;
|
|
716
|
+
function capParts(parts, label) {
|
|
717
|
+
const kept = [];
|
|
718
|
+
let size = 0;
|
|
719
|
+
for (const part of parts) {
|
|
720
|
+
const n = JSON.stringify(part).length;
|
|
721
|
+
if (kept.length && size + n > ANSWER_CAP) break;
|
|
722
|
+
kept.push(part);
|
|
723
|
+
size += n;
|
|
724
|
+
}
|
|
725
|
+
const rest = parts.slice(kept.length);
|
|
726
|
+
return {
|
|
727
|
+
kept,
|
|
728
|
+
...(rest.length
|
|
729
|
+
? {
|
|
730
|
+
[`remaining${label}`]: rest.map((part) => part.title ?? part.name),
|
|
731
|
+
}
|
|
732
|
+
: {}),
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** Sections every agent needs, served whole in the full answer. */
|
|
737
|
+
const ALWAYS_SECTIONS = [/^gotchas/i, /^accessibility/i];
|
|
738
|
+
/** The summary keeps only these whole; the rest are in the outline (MCP panel 2026-09-26). */
|
|
739
|
+
const SUMMARY_SECTIONS = [/^gotchas/i];
|
|
740
|
+
|
|
741
|
+
/*
|
|
742
|
+
* SUMMARY ROWS (MCP panel 2026-09-26, owner: compact by default, expand on
|
|
743
|
+
* request). Blind Haiku runs saved `get_component BbTable` (38–47 KB) to a file
|
|
744
|
+
* to read it; Claude Code warns above ~10K tokens of tool output. The summary
|
|
745
|
+
* keeps EVERY name (a partial prop list is what makes agents invent API) as one
|
|
746
|
+
* line — name, type, default, the description's first sentence — and marks a
|
|
747
|
+
* cut sentence with " …": `members: ['name']` returns the full row.
|
|
748
|
+
*/
|
|
749
|
+
function firstSentence(text, cap = 160) {
|
|
750
|
+
const flat = String(text ?? '')
|
|
751
|
+
.replace(/\s+/g, ' ')
|
|
752
|
+
.trim();
|
|
753
|
+
if (!flat) return '';
|
|
754
|
+
// A sentence ends at ". " before a capital, a backtick or a bracket — not
|
|
755
|
+
// inside "1.234,56", "v3.0.0" or after "e.g." / "i.e." / "etc.".
|
|
756
|
+
const ends = [...flat.matchAll(/[.!?](?=\s+[A-Z`([]|$)/g)]
|
|
757
|
+
.map((m) => m.index)
|
|
758
|
+
.filter((i) => !/\b(e\.g|i\.e|etc|vs|approx)$/i.test(flat.slice(0, i)));
|
|
759
|
+
let stop = ends[0] ?? flat.length - 1;
|
|
760
|
+
// A second sentence that limits the first ("Only …", "Ignored with …",
|
|
761
|
+
// "Not called in …") changes what the first one means: keep it.
|
|
762
|
+
const next = flat.slice(stop + 1).trim();
|
|
763
|
+
if (
|
|
764
|
+
ends[1] != null &&
|
|
765
|
+
/^(Only|Not|Never|Ignored|Mutually|Takes precedence|Requires|Must|Has no effect|Without|Unless|Deprecated|Wins over|Overrides)\b/.test(
|
|
766
|
+
next
|
|
767
|
+
)
|
|
768
|
+
)
|
|
769
|
+
stop = ends[1];
|
|
770
|
+
// A short, generic first sentence ("The id.") says little: add the next.
|
|
771
|
+
if (stop + 1 < 60 && ends[ends.indexOf(stop) + 1] != null)
|
|
772
|
+
stop = ends[ends.indexOf(stop) + 1];
|
|
773
|
+
let out = flat.slice(0, stop + 1);
|
|
774
|
+
if (out.length > cap) {
|
|
775
|
+
let cut = out.lastIndexOf(' ', cap);
|
|
776
|
+
if (cut < 40) cut = cap;
|
|
777
|
+
let head = out.slice(0, cut);
|
|
778
|
+
// Never end inside a code span: an unpaired backtick reads as code.
|
|
779
|
+
if ((head.match(/`/g) ?? []).length % 2)
|
|
780
|
+
head = head.slice(0, head.lastIndexOf('`')).trimEnd();
|
|
781
|
+
out = `${head} …`;
|
|
782
|
+
} else if (out.length < flat.length) out += ' …';
|
|
783
|
+
return out;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/** A type as the summary prints it: no ` | undefined` tail, long ones cut. */
|
|
787
|
+
function shortType(type, cap = 180) {
|
|
788
|
+
const t = String(type ?? '')
|
|
789
|
+
.replace(/\s*\|\s*undefined$/, '')
|
|
790
|
+
.replace(/\s+/g, ' ');
|
|
791
|
+
return t.length > cap ? `${t.slice(0, cap)}…` : t;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
let sharedPropKeys = null;
|
|
795
|
+
/**
|
|
796
|
+
* A prop folds only when EVERYTHING an agent reads off it is the same on
|
|
797
|
+
* every component: type, default, values and description (the adversarial
|
|
798
|
+
* review found `items` / `multiple` / `padding` folded with different types
|
|
799
|
+
* and defaults). A required prop never folds: its own line carries the fact.
|
|
800
|
+
*/
|
|
801
|
+
const foldKey = (p) =>
|
|
802
|
+
[
|
|
803
|
+
p.name,
|
|
804
|
+
p.type,
|
|
805
|
+
p.default ?? '',
|
|
806
|
+
(p.values ?? []).join('|'),
|
|
807
|
+
p.description ?? '',
|
|
808
|
+
].join('\u0000');
|
|
809
|
+
/**
|
|
810
|
+
* Prop rows that are identical (name AND description) on 3+ public components:
|
|
811
|
+
* the form-field set on 16 controls, the Inertia link props on the buttons.
|
|
812
|
+
* The summary lists them by name once (`common`) instead of repeating ~4–8 KB
|
|
813
|
+
* of the same prose per component; `members` returns any of them whole.
|
|
814
|
+
*/
|
|
815
|
+
function sharedProps(manifest) {
|
|
816
|
+
if (sharedPropKeys) return sharedPropKeys;
|
|
817
|
+
const count = new Map();
|
|
818
|
+
for (const component of manifest.components ?? []) {
|
|
819
|
+
if (component.referenceOnly) continue;
|
|
820
|
+
for (const prop of component.props ?? []) {
|
|
821
|
+
if (!prop.description || prop.name.startsWith('pt')) continue;
|
|
822
|
+
const key = foldKey(prop);
|
|
823
|
+
if (prop.required) continue;
|
|
824
|
+
count.set(key, (count.get(key) ?? 0) + 1);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
sharedPropKeys = new Set(
|
|
828
|
+
[...count].filter(([, n]) => n >= 3).map(([key]) => key)
|
|
829
|
+
);
|
|
830
|
+
return sharedPropKeys;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const propLine = (p) => {
|
|
834
|
+
const values =
|
|
835
|
+
p.values?.length &&
|
|
836
|
+
!/Registry|VariantType|BbPtScopeName|LocaleKey/.test(String(p.type)) &&
|
|
837
|
+
!p.values.some((v) => /\s/.test(String(v))) &&
|
|
838
|
+
!p.values.every((v) => String(p.type).includes(String(v)))
|
|
839
|
+
? ` ∈ ${p.values.map((v) => String(v)).join('|')}`.slice(0, 120)
|
|
840
|
+
: '';
|
|
841
|
+
return `${p.name}${p.required ? '' : '?'}: ${shortType(p.type)}${values}${p.default != null && p.default !== '' ? ` = ${p.default}` : ''}${p.description ? ` — ${firstSentence(p.description)}` : ''}`;
|
|
842
|
+
};
|
|
843
|
+
const eventLine = (e) =>
|
|
844
|
+
`${e.name}${String(e.signature ?? '').replace(/: void$/, '')}${e.description ? ` — ${firstSentence(e.description)}` : ''}`;
|
|
845
|
+
/** The top-level keys of a slot scope type `{ a?: …; b: … }`. */
|
|
846
|
+
function scopeKeys(type) {
|
|
847
|
+
const t = String(type ?? '').trim();
|
|
848
|
+
if (!t.startsWith('{')) return [];
|
|
849
|
+
const keys = [];
|
|
850
|
+
let depth = 0;
|
|
851
|
+
let token = '';
|
|
852
|
+
const inner = t.slice(1, -1);
|
|
853
|
+
for (let i = 0; i < inner.length; i++) {
|
|
854
|
+
const ch = inner[i];
|
|
855
|
+
if ('{([<'.includes(ch)) depth++;
|
|
856
|
+
// The `>` of an arrow `=>` closes nothing.
|
|
857
|
+
else if ('})]'.includes(ch) || (ch === '>' && inner[i - 1] !== '='))
|
|
858
|
+
depth--;
|
|
859
|
+
if (depth === 0 && (ch === ';' || ch === ',')) {
|
|
860
|
+
const key = token.trim().match(/^([\w$]+)\??\s*:/)?.[1];
|
|
861
|
+
if (key) keys.push(key);
|
|
862
|
+
token = '';
|
|
863
|
+
} else token += ch;
|
|
864
|
+
}
|
|
865
|
+
const last = token.trim().match(/^([\w$]+)\??\s*:/)?.[1];
|
|
866
|
+
if (last) keys.push(last);
|
|
867
|
+
return keys;
|
|
868
|
+
}
|
|
869
|
+
const slotLine = (sl) => {
|
|
870
|
+
// A named scope type (BbSelectOptionSlotProps…) carries resolved `fields`.
|
|
871
|
+
const keys = sl.fields?.length
|
|
872
|
+
? sl.fields.map((f) => String(f).match(/^([\w$]+)/)?.[1]).filter(Boolean)
|
|
873
|
+
: scopeKeys(sl.type);
|
|
874
|
+
return `#${sl.name}${keys.length ? ` { ${keys.join(', ')} }` : ''}${sl.description ? ` — ${firstSentence(sl.description)}` : ''}`;
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
/** One line per v2/beta rename or removal (include 'history' for the records). */
|
|
878
|
+
function historyLines(entry) {
|
|
879
|
+
const renamedFrom = new Set((entry.renamed ?? []).map((r) => r.from));
|
|
880
|
+
// A slot row reads as a slot (`#no-data → #empty`), a removal with no
|
|
881
|
+
// single successor carries its hint (Q51.16).
|
|
882
|
+
const spell = (r, name) => (r.surface === 'slot' ? `#${name}` : name);
|
|
883
|
+
// A rename that also maps VALUES says which: `inputDirection →
|
|
884
|
+
// listLayout` alone sent 2 of 3 answerers to `list-layout="vertical"`
|
|
885
|
+
// when the map is vertical → vertical-stretch (2026-09-24).
|
|
886
|
+
const values = (r) =>
|
|
887
|
+
Object.entries(r.fix?.valueMap ?? {})
|
|
888
|
+
.filter(([from, to]) => from !== to)
|
|
889
|
+
.map(([from, to]) => `"${from}" → "${to}"`)
|
|
890
|
+
.join(', ');
|
|
891
|
+
return [
|
|
892
|
+
...(entry.renamed ?? []).map(
|
|
893
|
+
(r) =>
|
|
894
|
+
`${spell(r, r.from)} → ${spell(r, r.to)}${r.fix?.kind && r.fix.kind !== 'rename' ? ` (${r.fix.kind})` : ''}${values(r) ? ` (values: ${values(r)})` : ''}`
|
|
895
|
+
),
|
|
896
|
+
...(entry.removed ?? [])
|
|
897
|
+
.filter((r) => !renamedFrom.has(r.name))
|
|
898
|
+
.map(
|
|
899
|
+
(r) =>
|
|
900
|
+
`${spell(r, r.name)} removed${r.new ? ` → ${spell(r, r.new)}` : ''}${r.hint ? ` — ${r.hint}` : ''}`
|
|
901
|
+
),
|
|
902
|
+
];
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/** The compact `component` of the summary answer. */
|
|
906
|
+
function summarizeComponent(entry, manifest) {
|
|
907
|
+
const shared = sharedProps(manifest);
|
|
908
|
+
const own = [];
|
|
909
|
+
const common = [];
|
|
910
|
+
for (const p of entry.props ?? []) {
|
|
911
|
+
if (p.name === 'pt' || p.name.startsWith('pt:')) continue;
|
|
912
|
+
if (!p.required && shared.has(foldKey(p))) common.push(p.name);
|
|
913
|
+
else own.push(propLine(p));
|
|
914
|
+
}
|
|
915
|
+
return {
|
|
916
|
+
name: entry.name,
|
|
917
|
+
...(entry.group ? { group: entry.group } : {}),
|
|
918
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
919
|
+
props: own,
|
|
920
|
+
...(common.length
|
|
921
|
+
? {
|
|
922
|
+
commonProps: `${common.join(', ')} — optional props identical (type, default, description) on 3+ components (form fields, link props); members: ['${common[0]}'] returns one whole`,
|
|
923
|
+
}
|
|
924
|
+
: {}),
|
|
925
|
+
...(entry.models?.length
|
|
926
|
+
? {
|
|
927
|
+
models: entry.models.map(
|
|
928
|
+
(m) =>
|
|
929
|
+
`v-model${m.name === 'modelValue' ? '' : `:${m.name}`}: ${shortType(m.type)}`
|
|
930
|
+
),
|
|
931
|
+
}
|
|
932
|
+
: {}),
|
|
933
|
+
...(entry.events?.length ? { events: entry.events.map(eventLine) } : {}),
|
|
934
|
+
...(entry.slots?.length ? { slots: entry.slots.map(slotLine) } : {}),
|
|
935
|
+
...(entry.exposed?.length
|
|
936
|
+
? { exposed: entry.exposed.map((x) => `${x.name}: ${shortType(x.type)}`) }
|
|
937
|
+
: {}),
|
|
938
|
+
...(entry.pt?.parts?.length
|
|
939
|
+
? {
|
|
940
|
+
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>'].`,
|
|
941
|
+
}
|
|
942
|
+
: {}),
|
|
943
|
+
...(entry.cssProps?.length
|
|
944
|
+
? { cssProps: entry.cssProps.map((c) => c.name ?? c) }
|
|
945
|
+
: {}),
|
|
946
|
+
...(entry.removed?.length || entry.renamed?.length
|
|
947
|
+
? { history: historyLines(entry) }
|
|
948
|
+
: {}),
|
|
949
|
+
...(entry.attrTargets ? { attrTargets: entry.attrTargets } : {}),
|
|
950
|
+
...(entry.referenceOnly ? { referenceOnly: true } : {}),
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* `members`: the whole rows for the names asked — props, events, slots
|
|
956
|
+
* (`#name` or `name`), v-models (`v-model:sort`) and pt parts / states
|
|
957
|
+
* (`pt:row`, `row`), whose descriptions the summary line leaves out.
|
|
958
|
+
*/
|
|
959
|
+
function pickMembers(entry, names) {
|
|
960
|
+
const norm = (n) =>
|
|
961
|
+
String(n)
|
|
962
|
+
.trim()
|
|
963
|
+
.replace(/^#/, '')
|
|
964
|
+
.replace(/^v-model:/, '')
|
|
965
|
+
.toLowerCase();
|
|
966
|
+
const wanted = names.map(norm);
|
|
967
|
+
const hit = new Set();
|
|
968
|
+
const take = (list, key = (x) => x.name) =>
|
|
969
|
+
(list ?? []).filter((x) => {
|
|
970
|
+
const k = String(key(x)).toLowerCase();
|
|
971
|
+
const ok = wanted.includes(k);
|
|
972
|
+
if (ok) wanted.forEach((w, i) => w === k && hit.add(i));
|
|
973
|
+
return ok;
|
|
974
|
+
});
|
|
975
|
+
const props = take(entry.props);
|
|
976
|
+
const events = take(entry.events);
|
|
977
|
+
const slots = take(entry.slots);
|
|
978
|
+
const models = (entry.models ?? []).filter((m) =>
|
|
979
|
+
names.some(
|
|
980
|
+
(n) =>
|
|
981
|
+
String(n).trim() ===
|
|
982
|
+
(m.name === 'modelValue' ? 'v-model' : `v-model:${m.name}`)
|
|
983
|
+
)
|
|
984
|
+
);
|
|
985
|
+
names.forEach((n, i) => {
|
|
986
|
+
if (/^v-model$/.test(String(n).trim()) && models.length) hit.add(i);
|
|
987
|
+
});
|
|
988
|
+
// pt: `row`, `pt:row`, `row:selected` / `pt:row:selected` (the part AND
|
|
989
|
+
// the state, both with their descriptions).
|
|
990
|
+
const ptWanted = names.map((n) =>
|
|
991
|
+
String(n).trim().replace(/^pt:/, '').toLowerCase().split(':')
|
|
992
|
+
);
|
|
993
|
+
const parts = (entry.pt?.parts ?? []).filter((x) =>
|
|
994
|
+
ptWanted.some(([part], i) => {
|
|
995
|
+
const ok = part === x.name.toLowerCase();
|
|
996
|
+
if (ok) hit.add(i);
|
|
997
|
+
return ok;
|
|
998
|
+
})
|
|
999
|
+
);
|
|
1000
|
+
const states = (entry.pt?.states ?? []).filter((x) =>
|
|
1001
|
+
ptWanted.some((segments, i) => {
|
|
1002
|
+
const ok = segments.at(-1) === x.name.toLowerCase();
|
|
1003
|
+
if (ok) hit.add(i);
|
|
1004
|
+
return ok;
|
|
1005
|
+
})
|
|
1006
|
+
);
|
|
1007
|
+
const notFound = names.filter((_, i) => !hit.has(i));
|
|
1008
|
+
// Names built from a requested WORD (`loading` → `interactiveWhileLoading`,
|
|
1009
|
+
// `loadingText`): a members-only call skips the summary, and a blind run
|
|
1010
|
+
// concluded v2's enabled-while-loading had no v3 equivalent. Whole camel
|
|
1011
|
+
// words only (`key` does not match `keyboardNavigation`).
|
|
1012
|
+
const words = (n) =>
|
|
1013
|
+
n
|
|
1014
|
+
.replace(/^#/, '')
|
|
1015
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
1016
|
+
.toLowerCase()
|
|
1017
|
+
.split(/[\s:_-]+/);
|
|
1018
|
+
const asked = names.map(norm).filter((n) => n.length >= 3);
|
|
1019
|
+
const related = [
|
|
1020
|
+
...(entry.props ?? []).map((p) => p.name),
|
|
1021
|
+
...(entry.events ?? []).map((e) => e.name),
|
|
1022
|
+
...(entry.slots ?? []).map((sl) => `#${sl.name}`),
|
|
1023
|
+
].filter(
|
|
1024
|
+
(n) =>
|
|
1025
|
+
!n.startsWith('pt:') &&
|
|
1026
|
+
!asked.includes(n.replace(/^#/, '').toLowerCase()) &&
|
|
1027
|
+
asked.some((a) => words(n).includes(a))
|
|
1028
|
+
);
|
|
1029
|
+
return {
|
|
1030
|
+
...(props.length ? { props } : {}),
|
|
1031
|
+
...(events.length ? { events } : {}),
|
|
1032
|
+
...(slots.length ? { slots } : {}),
|
|
1033
|
+
...(models.length ? { models } : {}),
|
|
1034
|
+
...(parts.length || states.length
|
|
1035
|
+
? {
|
|
1036
|
+
pt: {
|
|
1037
|
+
...(parts.length ? { parts } : {}),
|
|
1038
|
+
...(states.length ? { states } : {}),
|
|
1039
|
+
},
|
|
1040
|
+
}
|
|
1041
|
+
: {}),
|
|
1042
|
+
...(related.length ? { related } : {}),
|
|
1043
|
+
...(notFound.length ? { notFound } : {}),
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* `get_component({ name, sections?, include?, maxChars? })`.
|
|
1049
|
+
*
|
|
1050
|
+
* COMPACT BY DEFAULT (release review 2026-09-23, D6-4). The old answer was
|
|
1051
|
+
* the manifest entry plus the whole contract cut at 40 000 chars: 76–101 KB,
|
|
1052
|
+
* and the cut dropped `Gotchas` on six components while the tail it kept
|
|
1053
|
+
* repeated the structured entry. Blind Haiku answerers could not read it and
|
|
1054
|
+
* guessed. Now the default is the structured entry (props never cut — a
|
|
1055
|
+
* partial prop list is what makes agents invent API) minus the parts that
|
|
1056
|
+
* repeat it (`pt:*` prop rows: the `pt` section lists every part and state),
|
|
1057
|
+
* the guide's outline, and its Gotchas + Accessibility sections. Guide
|
|
1058
|
+
* sections, the component tree, the CSS variables with their descriptions
|
|
1059
|
+
* and the whole contract come on request.
|
|
1060
|
+
*/
|
|
1061
|
+
function getComponent({
|
|
1062
|
+
name,
|
|
1063
|
+
sections,
|
|
1064
|
+
include,
|
|
1065
|
+
maxChars,
|
|
1066
|
+
detail,
|
|
1067
|
+
members,
|
|
1068
|
+
} = {}) {
|
|
180
1069
|
const manifest = loadComponentsManifest();
|
|
181
|
-
const
|
|
1070
|
+
const resolved = resolveComponent(manifest, name);
|
|
1071
|
+
const entry = resolved?.entry;
|
|
182
1072
|
if (!entry) {
|
|
1073
|
+
/*
|
|
1074
|
+
* A public TYPE asked for as a component. `BbTableColumn`,
|
|
1075
|
+
* `BbDropdownItem`, `BbTabsItem` and `BbBreadcrumbsItem` are exports an
|
|
1076
|
+
* agent meets in a props table and reasonably looks up here; answering
|
|
1077
|
+
* "unknown component — try search_components" sent one consumer to the
|
|
1078
|
+
* `allowComponents` allowlist instead, which silences every check on the
|
|
1079
|
+
* tag. Answer the real question: what it is, and where it is used.
|
|
1080
|
+
*/
|
|
1081
|
+
const type = (manifest.types ?? []).find((t) => t.name === name);
|
|
1082
|
+
if (type) {
|
|
1083
|
+
const use = type.usedBy?.[0];
|
|
1084
|
+
return {
|
|
1085
|
+
found: true,
|
|
1086
|
+
kind: 'type',
|
|
1087
|
+
name,
|
|
1088
|
+
usage: type.usage,
|
|
1089
|
+
...(type.deprecated ? { deprecated: true } : {}),
|
|
1090
|
+
...(type.replacedBy ? { replacedBy: type.replacedBy } : {}),
|
|
1091
|
+
component: type.component,
|
|
1092
|
+
usedBy: type.usedBy ?? [],
|
|
1093
|
+
// What it is (Q50.2): the declaration, the accepted values when
|
|
1094
|
+
// finite (open when `extensibleVia` names a registry), and the
|
|
1095
|
+
// resolved fields when the declaration leans on a private type.
|
|
1096
|
+
...(type.definition ? { definition: type.definition } : {}),
|
|
1097
|
+
...(type.values ? { values: type.values } : {}),
|
|
1098
|
+
...(type.extensibleVia ? { extensibleVia: type.extensibleVia } : {}),
|
|
1099
|
+
...(type.fields ? { fields: type.fields } : {}),
|
|
1100
|
+
// A union of private members: each member's fields (Q51.21).
|
|
1101
|
+
...(type.members ? { members: type.members } : {}),
|
|
1102
|
+
import: `import type { ${type.replacedBy ?? name} } from 'bitboss-ui';`,
|
|
1103
|
+
hint: use
|
|
1104
|
+
? `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.`
|
|
1105
|
+
: 'A type, not a component — import it for the value you pass, never write it as a tag.',
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
// A directive (`v-bb-tooltip`, `vBbTooltip`) — review D3-13.
|
|
1109
|
+
const directiveName = String(name ?? '')
|
|
1110
|
+
.trim()
|
|
1111
|
+
.replace(/^vBb([A-Z]\w*)$/, (_, word) => `v-bb-${word.toLowerCase()}`)
|
|
1112
|
+
.toLowerCase();
|
|
1113
|
+
const directive = (manifest.directives ?? []).find(
|
|
1114
|
+
(d) => d.name === directiveName
|
|
1115
|
+
);
|
|
1116
|
+
if (directive) {
|
|
1117
|
+
const value = (manifest.types ?? []).find(
|
|
1118
|
+
(t) => t.name === directive.value
|
|
1119
|
+
);
|
|
1120
|
+
return {
|
|
1121
|
+
found: true,
|
|
1122
|
+
kind: 'directive',
|
|
1123
|
+
...directive,
|
|
1124
|
+
...(value?.definition ? { valueDefinition: value.definition } : {}),
|
|
1125
|
+
...(value?.fields ? { valueFields: value.fields } : {}),
|
|
1126
|
+
hint: `A directive, the floating twin of ${directive.mirrors}: read get_component({ name: '${directive.mirrors}', sections: ['directive'] }) for its guide section.`,
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
const retired = loadRetired().get(String(name ?? '').trim());
|
|
1130
|
+
if (retired) {
|
|
1131
|
+
return {
|
|
1132
|
+
found: false,
|
|
1133
|
+
name,
|
|
1134
|
+
retired: true,
|
|
1135
|
+
now: retired.now,
|
|
1136
|
+
todo: retired.todo,
|
|
1137
|
+
guide: retired.guide,
|
|
1138
|
+
...(retired.section ? { section: retired.section } : {}),
|
|
1139
|
+
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.`,
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
const picker = matchNotInLibrary(tokenize(name));
|
|
1143
|
+
const didYouMean = nearestComponents(manifest, name);
|
|
183
1144
|
return {
|
|
184
1145
|
found: false,
|
|
185
1146
|
name,
|
|
186
|
-
|
|
1147
|
+
...(didYouMean.length ? { didYouMean } : {}),
|
|
1148
|
+
...(picker.length ? { notInLibrary: picker.slice(0, 3) } : {}),
|
|
1149
|
+
hint: picker.length
|
|
1150
|
+
? "Not a component here — `notInLibrary` is the picker's verdict for this name."
|
|
1151
|
+
: 'Unknown component name. Try one of `didYouMean`, or search_components with a plain noun.',
|
|
187
1152
|
};
|
|
188
1153
|
}
|
|
1154
|
+
|
|
1155
|
+
const wanted = new Set(include ?? []);
|
|
189
1156
|
const contractPath = entry.contract ?? null;
|
|
190
1157
|
const contractAbsPath = contractPath
|
|
191
1158
|
? join(PACKAGE_ROOT, 'dist', contractPath)
|
|
@@ -193,19 +1160,219 @@ function getComponent({ name, maxChars } = {}) {
|
|
|
193
1160
|
const raw =
|
|
194
1161
|
contractAbsPath && existsSync(contractAbsPath)
|
|
195
1162
|
? readFileSync(contractAbsPath, 'utf-8')
|
|
196
|
-
:
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
1163
|
+
: '';
|
|
1164
|
+
const guide = contractGuideSections(raw);
|
|
1165
|
+
|
|
1166
|
+
/*
|
|
1167
|
+
* The types this component's own API names, so "what do I import to build
|
|
1168
|
+
* the value for `columns`?" is answered where the question is asked. Without
|
|
1169
|
+
* it an agent reads `BbTableColumn<T>[]` in the props table and has nowhere
|
|
1170
|
+
* to go — one consumer concluded the export was undocumented and allowlisted
|
|
1171
|
+
* the tag. Only types the component actually uses, never the whole export
|
|
1172
|
+
* list: a menu is not an answer.
|
|
1173
|
+
*/
|
|
1174
|
+
const types = (manifest.types ?? [])
|
|
1175
|
+
.filter((type) =>
|
|
1176
|
+
(type.usedBy ?? []).some((use) => use.component === entry.name)
|
|
1177
|
+
)
|
|
1178
|
+
.filter((type) => !type.deprecated)
|
|
1179
|
+
.map((type) => ({
|
|
1180
|
+
name: type.name,
|
|
1181
|
+
usedBy: (type.usedBy ?? []).filter((use) => use.component === entry.name),
|
|
1182
|
+
import: `import type { ${type.name} } from 'bitboss-ui';`,
|
|
1183
|
+
...(type.values ? { values: type.values } : {}),
|
|
1184
|
+
...(type.extensibleVia ? { extensibleVia: type.extensibleVia } : {}),
|
|
1185
|
+
...(type.fields ? { fields: type.fields } : {}),
|
|
1186
|
+
// The union's shape, then what each private member takes (Q51.21).
|
|
1187
|
+
...(!type.values && !type.fields && type.definition
|
|
1188
|
+
? { definition: type.definition }
|
|
1189
|
+
: {}),
|
|
1190
|
+
...(type.members ? { members: type.members } : {}),
|
|
1191
|
+
}));
|
|
1192
|
+
|
|
1193
|
+
const ptRows = entry.props.filter(
|
|
1194
|
+
(p) => p.name === 'pt' || p.name.startsWith('pt:')
|
|
201
1195
|
);
|
|
1196
|
+
const found = {
|
|
1197
|
+
found: true,
|
|
1198
|
+
...(resolved.resolvedFrom ? { resolvedFrom: resolved.resolvedFrom } : {}),
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
// `members`: exactly the rows asked for, nothing else.
|
|
1202
|
+
if (members?.length) {
|
|
1203
|
+
const picked = pickMembers(entry, members);
|
|
1204
|
+
// Rows past the cap are named, not dropped.
|
|
1205
|
+
for (const kind of ['props', 'events', 'slots']) {
|
|
1206
|
+
if (!picked[kind]) continue;
|
|
1207
|
+
const { kept, ...rest } = capParts(picked[kind], 'Members');
|
|
1208
|
+
picked[kind] = kept;
|
|
1209
|
+
if (rest.remainingMembers)
|
|
1210
|
+
picked.remainingMembers = [
|
|
1211
|
+
...(picked.remainingMembers ?? []),
|
|
1212
|
+
...rest.remainingMembers,
|
|
1213
|
+
];
|
|
1214
|
+
}
|
|
1215
|
+
return { ...found, name: entry.name, ...picked };
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
const full = detail === 'full';
|
|
1219
|
+
const asked = (sections?.length ?? 0) + (include?.length ?? 0) > 0;
|
|
1220
|
+
const { picked, missing } = pickSections(guide, sections);
|
|
1221
|
+
const guideOutline = guide
|
|
1222
|
+
.filter((s) => s.level === 3)
|
|
1223
|
+
.map((s) => ({ title: s.title, chars: s.text.length }));
|
|
1224
|
+
|
|
1225
|
+
// A follow-up for sections / include parts returns ONLY those parts: the
|
|
1226
|
+
// summary was already read (it used to be resent whole: BbTable + one
|
|
1227
|
+
// section was 49K chars for a ~9K section).
|
|
1228
|
+
if (asked && !full) {
|
|
1229
|
+
return {
|
|
1230
|
+
...found,
|
|
1231
|
+
name: entry.name,
|
|
1232
|
+
...(picked.length
|
|
1233
|
+
? (() => {
|
|
1234
|
+
const { kept, ...rest } = capParts(
|
|
1235
|
+
picked.map((sec) => sectionAnswer(sec, guide, maxChars)),
|
|
1236
|
+
'Sections'
|
|
1237
|
+
);
|
|
1238
|
+
return { sections: kept, ...rest };
|
|
1239
|
+
})()
|
|
1240
|
+
: {}),
|
|
1241
|
+
// A missed title gets the outline, never the whole answer.
|
|
1242
|
+
...(missing.length ? { notFound: missing, outline: guideOutline } : {}),
|
|
1243
|
+
...(wanted.has('tree') && entry.tree ? { tree: entry.tree } : {}),
|
|
1244
|
+
...(wanted.has('cssProps') && entry.cssProps
|
|
1245
|
+
? { cssProps: entry.cssProps }
|
|
1246
|
+
: {}),
|
|
1247
|
+
...(wanted.has('css') && entry.css ? { css: entry.css } : {}),
|
|
1248
|
+
...(wanted.has('ptProps')
|
|
1249
|
+
? (() => {
|
|
1250
|
+
const { kept, ...rest } = capParts(ptRows, 'PtProps');
|
|
1251
|
+
return { ptProps: kept, ...rest, pt: entry.pt };
|
|
1252
|
+
})()
|
|
1253
|
+
: {}),
|
|
1254
|
+
...(wanted.has('history')
|
|
1255
|
+
? { removed: entry.removed ?? [], renamed: entry.renamed ?? [] }
|
|
1256
|
+
: {}),
|
|
1257
|
+
...(wanted.has('contract')
|
|
1258
|
+
? (() => {
|
|
1259
|
+
const c = truncateMarkdown(
|
|
1260
|
+
raw,
|
|
1261
|
+
maxChars,
|
|
1262
|
+
'Pass maxChars: 0 for the whole contract.'
|
|
1263
|
+
);
|
|
1264
|
+
return {
|
|
1265
|
+
contract: c.content,
|
|
1266
|
+
...(c.truncated
|
|
1267
|
+
? { contractTruncated: true, contractHint: c.hint }
|
|
1268
|
+
: {}),
|
|
1269
|
+
};
|
|
1270
|
+
})()
|
|
1271
|
+
: {}),
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
if (!full) {
|
|
1276
|
+
const kept = guide.filter((s) =>
|
|
1277
|
+
SUMMARY_SECTIONS.some((re) => re.test(s.title))
|
|
1278
|
+
);
|
|
1279
|
+
return {
|
|
1280
|
+
...found,
|
|
1281
|
+
component: summarizeComponent(entry, manifest),
|
|
1282
|
+
...(types.length > 0 ? { types } : {}),
|
|
1283
|
+
...(entry.pt?.parts?.length ? { ptTyping: ptTyping(entry) } : {}),
|
|
1284
|
+
...(seeAlso(entry, raw).length ? { seeAlso: seeAlso(entry, raw) } : {}),
|
|
1285
|
+
contractPath,
|
|
1286
|
+
guide: {
|
|
1287
|
+
sections: guideOutline,
|
|
1288
|
+
...(kept.length
|
|
1289
|
+
? { always: kept.map((s) => ({ title: s.title, text: s.text })) }
|
|
1290
|
+
: {}),
|
|
1291
|
+
},
|
|
1292
|
+
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.`,
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const component = {
|
|
1297
|
+
...entry,
|
|
1298
|
+
props: (wanted.has('ptProps')
|
|
1299
|
+
? entry.props
|
|
1300
|
+
: entry.props.filter((p) => !ptRows.includes(p))
|
|
1301
|
+
).map(({ required, ...prop }) => (required ? { ...prop, required } : prop)),
|
|
1302
|
+
...(entry.cssProps && !wanted.has('cssProps')
|
|
1303
|
+
? { cssProps: entry.cssProps.map((c) => c.name ?? c) }
|
|
1304
|
+
: {}),
|
|
1305
|
+
};
|
|
1306
|
+
if (!wanted.has('tree')) delete component.tree;
|
|
1307
|
+
// The rendered `.bb-*` classes (Q46.5) are for tools (`check`, `validate`);
|
|
1308
|
+
// an agent restyles through pt and the locals. include: ['css'] lists them.
|
|
1309
|
+
if (!wanted.has('css')) delete component.css;
|
|
1310
|
+
// `removed` and `renamed` repeat each other with migration metadata; one
|
|
1311
|
+
// line per old name answers "what happened to X" (include 'history' for
|
|
1312
|
+
// the records the fixers use).
|
|
1313
|
+
if (
|
|
1314
|
+
!wanted.has('history') &&
|
|
1315
|
+
(entry.removed?.length || entry.renamed?.length)
|
|
1316
|
+
) {
|
|
1317
|
+
component.history = historyLines(entry);
|
|
1318
|
+
delete component.removed;
|
|
1319
|
+
delete component.renamed;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
const always = guide.filter(
|
|
1323
|
+
(s) => ALWAYS_SECTIONS.some((re) => re.test(s.title)) && !picked.includes(s)
|
|
1324
|
+
);
|
|
1325
|
+
const contract = wanted.has('contract')
|
|
1326
|
+
? truncateMarkdown(
|
|
1327
|
+
raw,
|
|
1328
|
+
maxChars,
|
|
1329
|
+
'The structured `component` field above is complete; the cut text is guide prose.'
|
|
1330
|
+
)
|
|
1331
|
+
: null;
|
|
1332
|
+
|
|
202
1333
|
return {
|
|
203
1334
|
found: true,
|
|
204
|
-
|
|
205
|
-
component
|
|
1335
|
+
...(resolved.resolvedFrom ? { resolvedFrom: resolved.resolvedFrom } : {}),
|
|
1336
|
+
component,
|
|
1337
|
+
...(types.length > 0 ? { types } : {}),
|
|
1338
|
+
...(ptRows.length && !wanted.has('ptProps')
|
|
1339
|
+
? {
|
|
1340
|
+
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 entries as one object keyed WITHOUT the \`pt:\` prefix: { ${ptExample(entry)} }.`,
|
|
1341
|
+
}
|
|
1342
|
+
: {}),
|
|
1343
|
+
/*
|
|
1344
|
+
* Where agents land when they type `pt` in <script> (Q52.7.1 receipts):
|
|
1345
|
+
* 5 of 6 blind Haiku runs opened get_component first, never saw the
|
|
1346
|
+
* passthrough guide's typing section, and hand-copied the part unions
|
|
1347
|
+
* or invented a `PtValue`. The one that found the section scored 4/4.
|
|
1348
|
+
*/
|
|
1349
|
+
...(entry.pt?.parts?.length ? { ptTyping: ptTyping(entry) } : {}),
|
|
1350
|
+
...(seeAlso(entry, raw).length ? { seeAlso: seeAlso(entry, raw) } : {}),
|
|
1351
|
+
guide: {
|
|
1352
|
+
sections: guide
|
|
1353
|
+
.filter((s) => s.level === 3)
|
|
1354
|
+
.map((s) => ({ title: s.title, chars: s.text.length })),
|
|
1355
|
+
...(picked.length
|
|
1356
|
+
? { requested: picked.map((s) => ({ title: s.title, text: s.text })) }
|
|
1357
|
+
: {}),
|
|
1358
|
+
...(missing.length ? { notFound: missing } : {}),
|
|
1359
|
+
...(always.length
|
|
1360
|
+
? { always: always.map((s) => ({ title: s.title, text: s.text })) }
|
|
1361
|
+
: {}),
|
|
1362
|
+
},
|
|
206
1363
|
contractPath,
|
|
207
|
-
|
|
208
|
-
|
|
1364
|
+
...(contract
|
|
1365
|
+
? {
|
|
1366
|
+
contract: contract.content,
|
|
1367
|
+
...(contract.truncated
|
|
1368
|
+
? {
|
|
1369
|
+
contractTruncated: true,
|
|
1370
|
+
contractHint: contract.hint,
|
|
1371
|
+
}
|
|
1372
|
+
: {}),
|
|
1373
|
+
}
|
|
1374
|
+
: {}),
|
|
1375
|
+
hint: `Structured API above is complete. Read guide sections with get_component({ name: '${entry.name}', sections: ['<title from guide.sections>'] }); add include: ['tree' | 'cssProps' | 'css' | 'ptProps' | 'history' | 'contract'] for the component tree, CSS variables with descriptions, the .bb-* classes it renders, every pt:* row, the full v2/beta rename records, or the whole contract. search_docs finds a topic across all docs.`,
|
|
209
1376
|
};
|
|
210
1377
|
}
|
|
211
1378
|
|
|
@@ -219,16 +1386,53 @@ const STYLES_CSS = join(PACKAGE_ROOT, 'dist', 'styles.css');
|
|
|
219
1386
|
* every comma-part is built solely from these — `:root`, `.light`, `:root.dark`
|
|
220
1387
|
* and `html.dark` are theme roots; `.bb-tooltip .bb-common-popover__bubble` is
|
|
221
1388
|
* not, however many `--bb-*` it redeclares.
|
|
1389
|
+
*
|
|
1390
|
+
* `.bb-theme` is a theme root too: it is the island boundary the derived block
|
|
1391
|
+
* re-specifies its formulas on (design-tokens.md § the CSS mechanic).
|
|
222
1392
|
*/
|
|
223
|
-
const THEME_ROOT_PART = /^(?::root|html|body|\.light|\.dark)+$/;
|
|
1393
|
+
const THEME_ROOT_PART = /^(?::root|html|body|\.light|\.dark|\.bb-theme)+$/;
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* Unwrap the `:where(…)` / `:is(…)` a selector list may be wrapped in. The
|
|
1397
|
+
* derived-token block ships as `:where(:root,.light,.dark,.bb-theme)`, and
|
|
1398
|
+
* splitting THAT on commas yields `:where(:root` and `.bb-theme)` — neither of
|
|
1399
|
+
* which is a theme root, so without this every derived token is misreported as
|
|
1400
|
+
* a component rebind with no value. That is not a cosmetic slip: `localRebinds`
|
|
1401
|
+
* is documented to agents as "not something to copy".
|
|
1402
|
+
*/
|
|
1403
|
+
function unwrapSelectorList(selector) {
|
|
1404
|
+
const trimmed = selector.trim();
|
|
1405
|
+
const match = /^:(?:where|is)\((.*)\)$/s.exec(trimmed);
|
|
1406
|
+
return match ? match[1] : trimmed;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
224
1409
|
function isThemeSelector(selector) {
|
|
225
|
-
return selector
|
|
1410
|
+
return unwrapSelectorList(selector)
|
|
226
1411
|
.split(',')
|
|
227
1412
|
.every((part) =>
|
|
228
1413
|
THEME_ROOT_PART.test(part.replace(/[\s>+~]+/g, '').trim())
|
|
229
1414
|
);
|
|
230
1415
|
}
|
|
231
1416
|
|
|
1417
|
+
/**
|
|
1418
|
+
* A theme token is a KNOB (the consumer sets it) or DERIVED (a formula the
|
|
1419
|
+
* library recomputes at every theme boundary). Setting a derived value is the
|
|
1420
|
+
* documented trap — it stops tracking the knob it was computed from.
|
|
1421
|
+
*
|
|
1422
|
+
* The split is structural in the shipped sheet, so it needs no curated list to
|
|
1423
|
+
* drift: knobs are declared on the plain scheme roots, formulas inside the
|
|
1424
|
+
* `:where(…)` boundary block. A name in BOTH is a knob that TRACKS another one
|
|
1425
|
+
* until pinned — and because the boundary block re-specifies it, a pin has to
|
|
1426
|
+
* be repeated at every boundary it is used on, `.dark` included.
|
|
1427
|
+
*/
|
|
1428
|
+
const isDerivedBlock = (selector) => /^:(?:where|is)\(/.test(selector.trim());
|
|
1429
|
+
const tokenKind = (inKnobBlock, inDerivedBlock) =>
|
|
1430
|
+
inKnobBlock && inDerivedBlock
|
|
1431
|
+
? 'knob-tracking'
|
|
1432
|
+
: inDerivedBlock
|
|
1433
|
+
? 'derived'
|
|
1434
|
+
: 'knob';
|
|
1435
|
+
|
|
232
1436
|
/**
|
|
233
1437
|
* Walk a stylesheet and yield every `--bb-*` declaration WITH the selector that
|
|
234
1438
|
* owns it. A flat regex cannot do this: it sees a declaration but not its
|
|
@@ -237,6 +1441,11 @@ function isThemeSelector(selector) {
|
|
|
237
1441
|
* tolerant of the missing final semicolon that minified CSS emits) is the
|
|
238
1442
|
* cheapest way to keep the scope — and this file ships in the package, so it
|
|
239
1443
|
* may not reach for postcss the way the dev-only `check:tokens` script does.
|
|
1444
|
+
*
|
|
1445
|
+
* `when` carries the enclosing `@supports` condition(s), if any: a value there
|
|
1446
|
+
* is not the theme value but one browser path — `--bb-primary-fg` ships an
|
|
1447
|
+
* auto-contrast formula with literal fallbacks, and folding the fallbacks into
|
|
1448
|
+
* the theme values tells an agent to pin a literal that defeats the formula.
|
|
240
1449
|
*/
|
|
241
1450
|
function* eachTokenDeclaration(css) {
|
|
242
1451
|
const stack = [];
|
|
@@ -257,11 +1466,23 @@ function* eachTokenDeclaration(css) {
|
|
|
257
1466
|
quote = char;
|
|
258
1467
|
buffer += char;
|
|
259
1468
|
} else if (char === '{') {
|
|
260
|
-
|
|
1469
|
+
// A comment before a rule would otherwise prefix its selector or
|
|
1470
|
+
// hide the `@supports` it opens.
|
|
1471
|
+
stack.push(buffer.replace(/\/\*[\s\S]*?\*\//g, '').trim());
|
|
261
1472
|
buffer = '';
|
|
262
1473
|
} else if (char === '}' || char === ';') {
|
|
263
1474
|
const decl = declaration();
|
|
264
|
-
if (decl)
|
|
1475
|
+
if (decl) {
|
|
1476
|
+
const supports = stack
|
|
1477
|
+
.slice(0, -1)
|
|
1478
|
+
.filter((scope) => scope.startsWith('@supports'))
|
|
1479
|
+
.map((scope) => scope.replace(/^@supports\s*/, ''));
|
|
1480
|
+
yield {
|
|
1481
|
+
...decl,
|
|
1482
|
+
selector: stack[stack.length - 1] ?? '',
|
|
1483
|
+
...(supports.length ? { when: supports.join(' and ') } : {}),
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
265
1486
|
if (char === '}') stack.pop();
|
|
266
1487
|
} else {
|
|
267
1488
|
buffer += char;
|
|
@@ -281,7 +1502,8 @@ function* eachTokenDeclaration(css) {
|
|
|
281
1502
|
* different components and is only meaningful inside that component's scope.
|
|
282
1503
|
* Serving them as one flat list would invite exactly the wrong move — an agent
|
|
283
1504
|
* writing `:root { --gap: … }` and wondering why nothing changed. They are
|
|
284
|
-
* documented per component
|
|
1505
|
+
* documented per component: `get_component` carries them as `cssProps`, each
|
|
1506
|
+
* with the root class to set it on, its default, and what it is.
|
|
285
1507
|
*
|
|
286
1508
|
* WHY `dist/styles.css` AND NOT `src/assets/css/variables.css`: `src/` is not
|
|
287
1509
|
* in the published package, so the source file does not exist for the consumer
|
|
@@ -301,9 +1523,27 @@ function listTokens({ filter } = {}) {
|
|
|
301
1523
|
}
|
|
302
1524
|
const css = readFileSync(STYLES_CSS, 'utf-8');
|
|
303
1525
|
const byName = new Map();
|
|
304
|
-
for (const { name, value, selector } of eachTokenDeclaration(css)) {
|
|
305
|
-
const entry = byName.get(name) ?? {
|
|
1526
|
+
for (const { name, value, selector, when } of eachTokenDeclaration(css)) {
|
|
1527
|
+
const entry = byName.get(name) ?? {
|
|
1528
|
+
theme: [],
|
|
1529
|
+
local: [],
|
|
1530
|
+
conditional: [],
|
|
1531
|
+
knobBlock: false,
|
|
1532
|
+
derivedBlock: false,
|
|
1533
|
+
};
|
|
306
1534
|
if (isThemeSelector(selector)) {
|
|
1535
|
+
if (isDerivedBlock(selector)) entry.derivedBlock = true;
|
|
1536
|
+
else entry.knobBlock = true;
|
|
1537
|
+
}
|
|
1538
|
+
if (when) {
|
|
1539
|
+
// One browser path, not the theme value nor a dark override.
|
|
1540
|
+
if (
|
|
1541
|
+
!entry.conditional.some(
|
|
1542
|
+
(c) => c.when === when && c.selector === selector
|
|
1543
|
+
)
|
|
1544
|
+
)
|
|
1545
|
+
entry.conditional.push({ when, selector, value });
|
|
1546
|
+
} else if (isThemeSelector(selector)) {
|
|
307
1547
|
// Light first, dark after: report both rather than let one win.
|
|
308
1548
|
if (!entry.theme.includes(value)) entry.theme.push(value);
|
|
309
1549
|
} else if (!entry.local.some((r) => r.selector === selector)) {
|
|
@@ -320,11 +1560,13 @@ function listTokens({ filter } = {}) {
|
|
|
320
1560
|
const tokens = [...byName.entries()]
|
|
321
1561
|
.filter(([name]) => !needle || name.toLowerCase().includes(needle))
|
|
322
1562
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
323
|
-
.map(([name, { theme, local }]) => ({
|
|
1563
|
+
.map(([name, { theme, local, conditional, knobBlock, derivedBlock }]) => ({
|
|
324
1564
|
name,
|
|
1565
|
+
kind: tokenKind(knobBlock, derivedBlock),
|
|
325
1566
|
value: theme[0],
|
|
326
1567
|
...(theme.length > 1 ? { themeOverrides: theme.slice(1) } : {}),
|
|
327
1568
|
...(local.length > 0 ? { localRebinds: local } : {}),
|
|
1569
|
+
...(conditional.length > 0 ? { conditional } : {}),
|
|
328
1570
|
}));
|
|
329
1571
|
return {
|
|
330
1572
|
count: tokens.length,
|
|
@@ -332,11 +1574,19 @@ function listTokens({ filter } = {}) {
|
|
|
332
1574
|
tokens,
|
|
333
1575
|
hint:
|
|
334
1576
|
needle && tokens.length === 0
|
|
335
|
-
? `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
|
|
336
|
-
:
|
|
1577
|
+
? `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. ${FONT_NOTE}`
|
|
1578
|
+
: FONT_NOTE +
|
|
1579
|
+
' 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.',
|
|
337
1580
|
};
|
|
338
1581
|
}
|
|
339
1582
|
|
|
1583
|
+
/**
|
|
1584
|
+
* Q53.11: there is no font-family token, by ruling; agents kept looking for
|
|
1585
|
+
* one. Said in every list_tokens answer.
|
|
1586
|
+
*/
|
|
1587
|
+
const FONT_NOTE =
|
|
1588
|
+
"Components have no font token; they inherit the page's `font-family`. Set it on `body` (or a `.bb-theme` island) to theme it.";
|
|
1589
|
+
|
|
340
1590
|
/** Every guide markdown path under dist/ai/guides, relative to that dir. */
|
|
341
1591
|
function collectGuidePaths(dir, prefix = '') {
|
|
342
1592
|
const out = [];
|
|
@@ -352,41 +1602,509 @@ function collectGuidePaths(dir, prefix = '') {
|
|
|
352
1602
|
|
|
353
1603
|
/** `list_guides()` — the shipped prose guides an agent can read. */
|
|
354
1604
|
function listGuides() {
|
|
355
|
-
const
|
|
1605
|
+
const guidesDir = join(AI_ROOT, 'guides');
|
|
1606
|
+
const all = collectGuidePaths(guidesDir).sort();
|
|
1607
|
+
/*
|
|
1608
|
+
* Title + summary per guide (review D6-13): 57 bare paths could not tell an
|
|
1609
|
+
* agent which one answers its question. The 40 per-component migration
|
|
1610
|
+
* pages collapse to one row — get_guide('migration/components/<kebab>').
|
|
1611
|
+
*/
|
|
1612
|
+
const perComponent = all.filter((p) => p.startsWith('migration/components/'));
|
|
1613
|
+
const guides = all
|
|
1614
|
+
.filter((p) => !perComponent.includes(p))
|
|
1615
|
+
.map((path) => {
|
|
1616
|
+
const markdown = readFileSync(join(guidesDir, path), 'utf-8');
|
|
1617
|
+
const meta = frontmatter(markdown);
|
|
1618
|
+
const title =
|
|
1619
|
+
meta.title ??
|
|
1620
|
+
stripFrontmatter(markdown)
|
|
1621
|
+
.match(/^# (.+)$/m)?.[1]
|
|
1622
|
+
?.trim();
|
|
1623
|
+
return {
|
|
1624
|
+
name: path,
|
|
1625
|
+
...(title ? { title } : {}),
|
|
1626
|
+
...(meta.summary ? { summary: meta.summary } : {}),
|
|
1627
|
+
};
|
|
1628
|
+
});
|
|
356
1629
|
return {
|
|
357
|
-
count:
|
|
1630
|
+
count: all.length,
|
|
358
1631
|
guides,
|
|
359
|
-
|
|
1632
|
+
...(perComponent.length
|
|
1633
|
+
? {
|
|
1634
|
+
perComponentMigration: `${perComponent.length} pages: get_guide({ name: 'migration/components/<kebab-name>' }), e.g. 'migration/components/bb-select'.`,
|
|
1635
|
+
}
|
|
1636
|
+
: {}),
|
|
1637
|
+
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.',
|
|
360
1638
|
};
|
|
361
1639
|
}
|
|
362
1640
|
|
|
363
|
-
/**
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
1641
|
+
/** Guides this size or larger answer with an outline unless asked for more. */
|
|
1642
|
+
// The router (13 KB, the documented entry point) still comes whole.
|
|
1643
|
+
const GUIDE_OUTLINE_OVER = 8000; // was 16 000: ai-router.md (16K, "a tiny map") came back whole (MCP panel 2026-09-26)
|
|
1644
|
+
|
|
1645
|
+
/**
|
|
1646
|
+
* The shipped guide a pointer means. Pointers come from everywhere — the
|
|
1647
|
+
* changelog (`components/bb-table.md (CSS table)`), contracts
|
|
1648
|
+
* (`guides/passthrough.md`), the repo layout (`ai/guides/…`) — and every form
|
|
1649
|
+
* used to answer "Guide not found" (review D6-11).
|
|
1650
|
+
*/
|
|
1651
|
+
function resolveGuidePath(name) {
|
|
1652
|
+
const guidesDir = join(AI_ROOT, 'guides');
|
|
1653
|
+
const all = collectGuidePaths(guidesDir);
|
|
1654
|
+
let rel = String(name ?? '')
|
|
1655
|
+
.trim()
|
|
1656
|
+
.replace(/\s*\(.*\)\s*$/, '')
|
|
1657
|
+
.replace(/^\/+/, '')
|
|
1658
|
+
.replace(/^(dist\/)?(ai\/)?(guides\/)?/, '');
|
|
1659
|
+
if (!rel || rel.includes('..')) return null;
|
|
1660
|
+
if (!rel.endsWith('.md')) rel = `${rel}.md`;
|
|
1661
|
+
const tries = [rel, `migration/${rel}`, `migration/components/${rel}`];
|
|
1662
|
+
for (const attempt of tries) if (all.includes(attempt)) return attempt;
|
|
1663
|
+
const base = rel.split('/').pop();
|
|
1664
|
+
const byBase = all.filter((p) => p.split('/').pop() === base);
|
|
1665
|
+
return byBase.length === 1 ? byBase[0] : null;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/** `get_guide({ name, sections?, maxChars? })`. */
|
|
1669
|
+
function getGuide({ name, sections, maxChars } = {}) {
|
|
1670
|
+
// The package README, which search_docs indexes (MCP panel 2026-09-26).
|
|
1671
|
+
if (/^readme(\.md)?$/i.test(String(name ?? '').trim())) {
|
|
1672
|
+
const readme = join(PACKAGE_ROOT, 'README.md');
|
|
1673
|
+
if (existsSync(readme)) {
|
|
1674
|
+
const markdown = readFileSync(readme, 'utf-8');
|
|
1675
|
+
const all = splitSections(markdown);
|
|
1676
|
+
if (sections?.length) {
|
|
1677
|
+
const { picked, missing } = pickSections(all, sections);
|
|
1678
|
+
return {
|
|
1679
|
+
found: true,
|
|
1680
|
+
name: 'README',
|
|
1681
|
+
...(() => {
|
|
1682
|
+
const { kept, ...rest } = capParts(
|
|
1683
|
+
picked.map((sec) => sectionAnswer(sec, all, maxChars)),
|
|
1684
|
+
'Sections'
|
|
1685
|
+
);
|
|
1686
|
+
return { sections: kept, ...rest };
|
|
1687
|
+
})(),
|
|
1688
|
+
...(missing.length
|
|
1689
|
+
? { notFound: missing, outline: outline(all) }
|
|
1690
|
+
: {}),
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
return {
|
|
1694
|
+
found: true,
|
|
1695
|
+
name: 'README',
|
|
1696
|
+
chars: markdown.length,
|
|
1697
|
+
outline: outline(all),
|
|
1698
|
+
hint: "Read what you need: get_guide({ name: 'README', sections: ['<title>'] }).",
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
const resolved = resolveGuidePath(name);
|
|
1703
|
+
if (!resolved) {
|
|
1704
|
+
const all = collectGuidePaths(join(AI_ROOT, 'guides'));
|
|
1705
|
+
const needle = String(name ?? '').toLowerCase();
|
|
1706
|
+
const near = all
|
|
1707
|
+
.filter((p) =>
|
|
1708
|
+
needle
|
|
1709
|
+
.split(/[^a-z0-9]+/)
|
|
1710
|
+
.some((w) => w.length > 3 && p.toLowerCase().includes(w))
|
|
1711
|
+
)
|
|
1712
|
+
.slice(0, 5);
|
|
1713
|
+
// A dead end cost the agent a turn (`get_guide('theming')`): answer with
|
|
1714
|
+
// the sections that match the name as a query.
|
|
1715
|
+
let found = [];
|
|
1716
|
+
try {
|
|
1717
|
+
found = searchSections(docIndex(), String(name ?? ''), 3).results;
|
|
1718
|
+
} catch {
|
|
1719
|
+
/* an unbuilt package has no index: the plain miss still answers */
|
|
1720
|
+
}
|
|
1721
|
+
const hits = found.map((r) => ({
|
|
1722
|
+
section: `${r.source} › ${r.section}`,
|
|
1723
|
+
read: r.read?.tool
|
|
1724
|
+
? `${r.read.tool}(${JSON.stringify(r.read.args ?? {})})`
|
|
1725
|
+
: r.read,
|
|
1726
|
+
}));
|
|
368
1727
|
return {
|
|
369
1728
|
found: false,
|
|
370
|
-
error: `
|
|
371
|
-
|
|
1729
|
+
error: `Guide not found: ${name}`,
|
|
1730
|
+
...(near.length ? { didYouMean: near } : {}),
|
|
1731
|
+
...(hits.length ? { related: hits } : {}),
|
|
1732
|
+
hint: 'Call list_guides for the names, or search_docs to find the section by topic.',
|
|
372
1733
|
};
|
|
373
1734
|
}
|
|
374
|
-
const
|
|
1735
|
+
const markdown = readFileSync(join(AI_ROOT, 'guides', resolved), 'utf-8');
|
|
1736
|
+
const meta = frontmatter(markdown);
|
|
1737
|
+
const all = splitSections(markdown);
|
|
1738
|
+
const base = {
|
|
1739
|
+
found: true,
|
|
1740
|
+
name: resolved,
|
|
1741
|
+
...(resolved !== name ? { resolvedFrom: name } : {}),
|
|
1742
|
+
...(meta.title ? { title: meta.title } : {}),
|
|
1743
|
+
};
|
|
1744
|
+
if (sections?.length) {
|
|
1745
|
+
const { picked, missing } = pickSections(all, sections);
|
|
1746
|
+
return {
|
|
1747
|
+
...base,
|
|
1748
|
+
...(() => {
|
|
1749
|
+
const { kept, ...rest } = capParts(
|
|
1750
|
+
picked.map((sec) => sectionAnswer(sec, all, maxChars)),
|
|
1751
|
+
'Sections'
|
|
1752
|
+
);
|
|
1753
|
+
return { sections: kept, ...rest };
|
|
1754
|
+
})(),
|
|
1755
|
+
...(missing.length ? { notFound: missing, outline: outline(all) } : {}),
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
const body = stripFrontmatter(markdown);
|
|
1759
|
+
if (maxChars === undefined && body.length >= GUIDE_OUTLINE_OVER) {
|
|
1760
|
+
const intro = body.split(/^## /m)[0].trim();
|
|
1761
|
+
return {
|
|
1762
|
+
...base,
|
|
1763
|
+
...(meta.summary ? { summary: meta.summary } : {}),
|
|
1764
|
+
chars: body.length,
|
|
1765
|
+
intro: intro.slice(0, 2000),
|
|
1766
|
+
outline: outline(all),
|
|
1767
|
+
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.`,
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
const { content, ...cut } = truncateMarkdown(
|
|
1771
|
+
markdown,
|
|
1772
|
+
maxChars,
|
|
1773
|
+
'Guides are ordered narrative — the cut tail is later sections.'
|
|
1774
|
+
);
|
|
1775
|
+
return { ...base, content, ...cut };
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
/**
|
|
1779
|
+
* `search_docs({ query, limit? })` — the section that answers a question,
|
|
1780
|
+
* across guides, composables, component guides, component props and recipes.
|
|
1781
|
+
*
|
|
1782
|
+
* WHY (review 2026-09-23). The facts Haiku missed were all shipped — the
|
|
1783
|
+
* `.dark` class, `cssLayers`, `passthroughDeduper`, `ssrViewportWidth`, the
|
|
1784
|
+
* `ai-init` and `changelog` commands, `prefill: false` — but each sat in one
|
|
1785
|
+
* section of a long document, and no tool could say which. Each result is a
|
|
1786
|
+
* snippet plus the exact call that reads the whole section.
|
|
1787
|
+
*/
|
|
1788
|
+
let cachedDocIndex = null;
|
|
1789
|
+
function docIndex() {
|
|
1790
|
+
if (cachedDocIndex) return cachedDocIndex;
|
|
1791
|
+
const entries = [];
|
|
1792
|
+
const addSections = (source, markdown, read) => {
|
|
1793
|
+
for (const section of splitSections(markdown)) {
|
|
1794
|
+
if (section.own.length < 40) continue;
|
|
1795
|
+
entries.push({
|
|
1796
|
+
source,
|
|
1797
|
+
section: section.title,
|
|
1798
|
+
text: section.own,
|
|
1799
|
+
read: read(section.title),
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
375
1803
|
const guidesDir = join(AI_ROOT, 'guides');
|
|
376
|
-
const
|
|
377
|
-
|
|
1804
|
+
for (const path of collectGuidePaths(guidesDir)) {
|
|
1805
|
+
addSections(
|
|
1806
|
+
`guide ${path}`,
|
|
1807
|
+
readFileSync(join(guidesDir, path), 'utf-8'),
|
|
1808
|
+
(title) => ({
|
|
1809
|
+
tool: 'get_guide',
|
|
1810
|
+
args: { name: path, sections: [title] },
|
|
1811
|
+
})
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
const composablesDir = join(AI_ROOT, 'composables');
|
|
1815
|
+
if (existsSync(composablesDir)) {
|
|
1816
|
+
for (const file of readdirSync(composablesDir).filter((f) =>
|
|
1817
|
+
f.endsWith('.md')
|
|
1818
|
+
)) {
|
|
1819
|
+
const markdown = readFileSync(join(composablesDir, file), 'utf-8');
|
|
1820
|
+
const name = file.slice(0, -3);
|
|
1821
|
+
entries.push({
|
|
1822
|
+
source: `composable ${name}`,
|
|
1823
|
+
section: frontmatter(markdown).summary ?? name,
|
|
1824
|
+
text: stripFrontmatter(markdown).slice(0, 4000),
|
|
1825
|
+
read: { tool: 'get_composable', args: { name } },
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
const manifest = loadComponentsManifest();
|
|
1830
|
+
for (const component of manifest.components.filter((c) => !c.referenceOnly)) {
|
|
1831
|
+
const path = join(PACKAGE_ROOT, 'dist', component.contract ?? '');
|
|
1832
|
+
if (component.contract && existsSync(path)) {
|
|
1833
|
+
for (const section of contractGuideSections(
|
|
1834
|
+
readFileSync(path, 'utf-8')
|
|
1835
|
+
)) {
|
|
1836
|
+
if (section.own.length < 40) continue;
|
|
1837
|
+
entries.push({
|
|
1838
|
+
source: `component ${component.name}`,
|
|
1839
|
+
section: section.title,
|
|
1840
|
+
text: section.own,
|
|
1841
|
+
read: {
|
|
1842
|
+
tool: 'get_component',
|
|
1843
|
+
args: { name: component.name, sections: [section.title] },
|
|
1844
|
+
},
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
for (const prop of component.props) {
|
|
1849
|
+
if (prop.name.startsWith('pt')) continue;
|
|
1850
|
+
entries.push({
|
|
1851
|
+
source: `component ${component.name}`,
|
|
1852
|
+
section: `prop ${prop.name}`,
|
|
1853
|
+
text: `${prop.name}: ${prop.type}${prop.default ? ` = ${prop.default}` : ''}. ${prop.description ?? ''}`,
|
|
1854
|
+
// The one row, not the component (~0.5K instead of up to 39K).
|
|
1855
|
+
read: {
|
|
1856
|
+
tool: 'get_component',
|
|
1857
|
+
args: { name: component.name, members: [prop.name] },
|
|
1858
|
+
},
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
for (const event of component.events ?? []) {
|
|
1862
|
+
entries.push({
|
|
1863
|
+
source: `component ${component.name}`,
|
|
1864
|
+
section: `event ${event.name}`,
|
|
1865
|
+
text: `@${event.name} ${event.signature}. ${event.description ?? ''}`,
|
|
1866
|
+
read: {
|
|
1867
|
+
tool: 'get_component',
|
|
1868
|
+
args: { name: component.name, members: [event.name] },
|
|
1869
|
+
},
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
/*
|
|
1873
|
+
* The rename registry: an agent upgrading an app searches the OLD name
|
|
1874
|
+
* (`enabled-while-loading`, `pt:input`, `#no-data`), which no current
|
|
1875
|
+
* section spells. One entry per row, with the name written every way a
|
|
1876
|
+
* query may (camel, kebab, split words), read = its migration guide.
|
|
1877
|
+
*/
|
|
1878
|
+
for (const row of component.removed ?? []) {
|
|
1879
|
+
const spell = (name) => (row.surface === 'slot' ? `#${name}` : name);
|
|
1880
|
+
const ways = (name) =>
|
|
1881
|
+
[
|
|
1882
|
+
...new Set([
|
|
1883
|
+
name,
|
|
1884
|
+
name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`),
|
|
1885
|
+
name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase(),
|
|
1886
|
+
]),
|
|
1887
|
+
].join(' / ');
|
|
1888
|
+
const kind = row.surface ?? 'prop';
|
|
1889
|
+
const guide = row.migration?.replace(/^ai\/guides\//, '');
|
|
1890
|
+
entries.push({
|
|
1891
|
+
source: `component ${component.name}`,
|
|
1892
|
+
section: `${row.new ? 'renamed' : 'removed'} ${kind} ${spell(row.name)}`,
|
|
1893
|
+
text:
|
|
1894
|
+
`${kind} ${ways(spell(row.name))} ` +
|
|
1895
|
+
(row.new
|
|
1896
|
+
? `was renamed to ${ways(spell(row.new))} on ${component.name}`
|
|
1897
|
+
: `was removed from ${component.name}`) +
|
|
1898
|
+
`${row.since && !/unreleased/.test(row.since) ? ` in ${row.since}` : ''}.` +
|
|
1899
|
+
Object.entries(row.fix?.valueMap ?? {})
|
|
1900
|
+
.filter(([from, to]) => from !== to)
|
|
1901
|
+
.map(([from, to]) => ` The value "${from}" becomes "${to}".`)
|
|
1902
|
+
.join('') +
|
|
1903
|
+
`${row.hint ? ` ${row.hint}.` : ''} \`bitboss-ui check --fix\` rewrites a rename.`,
|
|
1904
|
+
read: guide
|
|
1905
|
+
? { tool: 'get_guide', args: { name: guide } }
|
|
1906
|
+
: { tool: 'get_component', args: { name: component.name } },
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
for (const platform of RECIPE_PLATFORMS) {
|
|
1911
|
+
const dir = join(AI_ROOT, 'recipes', platform);
|
|
1912
|
+
if (!existsSync(dir)) continue;
|
|
1913
|
+
for (const file of readdirSync(dir).filter((f) => f.endsWith('.md'))) {
|
|
1914
|
+
const name = file.slice(0, -3);
|
|
1915
|
+
const markdown = readFileSync(join(dir, file), 'utf-8');
|
|
1916
|
+
entries.push({
|
|
1917
|
+
source: `recipe ${platform}/${name}`,
|
|
1918
|
+
section: frontmatter(markdown).title ?? name,
|
|
1919
|
+
text: frontmatter(markdown).summary ?? name,
|
|
1920
|
+
read: { tool: 'get_recipe', args: { platform, name } },
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
// Class renames (the sweep map rows of changelog.json): an app's
|
|
1925
|
+
// stylesheet names the OLD class, so the old class must find the new one.
|
|
1926
|
+
if (existsSync(CHANGELOG_PATH)) {
|
|
1927
|
+
const changelog = JSON.parse(readFileSync(CHANGELOG_PATH, 'utf-8'));
|
|
1928
|
+
for (const row of changelog.breaking ?? []) {
|
|
1929
|
+
if (row.surface !== 'class' || !String(row.id).startsWith('css:'))
|
|
1930
|
+
continue;
|
|
1931
|
+
entries.push({
|
|
1932
|
+
source: `class ${row.components?.[0] ?? ''}`.trim(),
|
|
1933
|
+
section: `renamed class ${row.from}`,
|
|
1934
|
+
text:
|
|
1935
|
+
`class ${row.from} was renamed to ${row.to}` +
|
|
1936
|
+
`${row.firstReleasedIn ? ` in ${row.firstReleasedIn}` : ''}` +
|
|
1937
|
+
(row.from.includes('--')
|
|
1938
|
+
? '.'
|
|
1939
|
+
: `; its modifiers follow (${row.from}--x → ${row.to}--x).`) +
|
|
1940
|
+
`${row.note ? ` ${row.note}.` : ''}`,
|
|
1941
|
+
read: {
|
|
1942
|
+
tool: 'get_guide',
|
|
1943
|
+
args: { name: 'migration/v3-beta-css-classes.md' },
|
|
1944
|
+
},
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
/*
|
|
1948
|
+
* Every other break, with the release that first shipped it. "In which
|
|
1949
|
+
* version did the activator scope lose `isOpen`?" had no search path: the
|
|
1950
|
+
* answer (f202, firstReleasedIn 3.0.0-beta.35) lived only in
|
|
1951
|
+
* list_changes, which 1 answerer in 12 called (ai-docs access push,
|
|
1952
|
+
* 2026-09-24). Prop / pt / slot renames are already indexed from the
|
|
1953
|
+
* components' registry rows above, so only the rest comes in here.
|
|
1954
|
+
*/
|
|
1955
|
+
const releases = changelog.releases ?? [];
|
|
1956
|
+
for (const row of changelog.breaking ?? []) {
|
|
1957
|
+
if (row.surface === 'class') continue;
|
|
1958
|
+
if (row.kind === 'rename' && ['prop', 'pt', 'slot'].includes(row.surface))
|
|
1959
|
+
continue;
|
|
1960
|
+
const what =
|
|
1961
|
+
row.kind === 'rename'
|
|
1962
|
+
? `${row.surface ?? ''} ${row.from} → ${row.to ?? '(removed)'}`.trim()
|
|
1963
|
+
: String(row.description ?? '');
|
|
1964
|
+
if (!what) continue;
|
|
1965
|
+
const at = row.firstReleasedIn;
|
|
1966
|
+
const before = at
|
|
1967
|
+
? releases.filter((r) => compareVersions(r, at) < 0).at(-1)
|
|
1968
|
+
: releases.at(-1);
|
|
1969
|
+
entries.push({
|
|
1970
|
+
source: `change ${row.id}`,
|
|
1971
|
+
section: `breaking change: ${what.length > 90 ? `${what.slice(0, 90)}…` : what}`,
|
|
1972
|
+
text:
|
|
1973
|
+
`${what}${row.note ? ` (${row.note})` : ''}. ` +
|
|
1974
|
+
(at
|
|
1975
|
+
? `First released in ${at}.`
|
|
1976
|
+
: 'Not released yet: it ships in the next release.') +
|
|
1977
|
+
`${row.guide ? ` Migration: ${row.guide}.` : ''}`,
|
|
1978
|
+
read: {
|
|
1979
|
+
tool: 'list_changes',
|
|
1980
|
+
args: before ? { since: before, ...(at ? { until: at } : {}) } : {},
|
|
1981
|
+
},
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
/*
|
|
1986
|
+
* The public types an app builds values with (`api` tier: BbTreeItem,
|
|
1987
|
+
* BbTableColumn, ButtonVariantType). "What shape must each BbTree item
|
|
1988
|
+
* have?" matched only the prop row `items: BbTreeItem<Meta>[]`, never the
|
|
1989
|
+
* type's fields, so 2 of 3 answerers guessed `{ label, children }`.
|
|
1990
|
+
*/
|
|
1991
|
+
for (const type of manifest.types ?? []) {
|
|
1992
|
+
if (type.usage !== 'api' || type.deprecated) continue;
|
|
1993
|
+
const use = (type.usedBy ?? [])
|
|
1994
|
+
.map((u) => `${u.component} ${u.name} ${u.surface}`)
|
|
1995
|
+
.join(', ');
|
|
1996
|
+
entries.push({
|
|
1997
|
+
source: `type ${type.name}`,
|
|
1998
|
+
section: `type ${type.name}${use ? ` (${use})` : ''}`,
|
|
1999
|
+
// The words questions use ("shape", "fields", "values") are spelled
|
|
2000
|
+
// out: the declaration alone never says them.
|
|
2001
|
+
text:
|
|
2002
|
+
`${type.name}${use ? `, the type of ${use}` : ''}: ` +
|
|
2003
|
+
(type.values
|
|
2004
|
+
? `accepted values ${type.values.join(' | ')}`
|
|
2005
|
+
: type.fields
|
|
2006
|
+
? `object shape, fields ${type.fields.join('; ')}`
|
|
2007
|
+
: String(type.definition ?? '')) +
|
|
2008
|
+
`. import type { ${type.name} } from 'bitboss-ui'.`,
|
|
2009
|
+
read: { tool: 'get_component', args: { name: type.name } },
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
const readme = join(PACKAGE_ROOT, 'README.md');
|
|
2013
|
+
if (existsSync(readme)) {
|
|
2014
|
+
// Served by get_guide under the name 'README' (it used to point at a
|
|
2015
|
+
// "read file" call, which is not a tool).
|
|
2016
|
+
addSections('README', readFileSync(readme, 'utf-8'), (title) => ({
|
|
2017
|
+
tool: 'get_guide',
|
|
2018
|
+
args: { name: 'README', sections: [title] },
|
|
2019
|
+
}));
|
|
2020
|
+
}
|
|
2021
|
+
cachedDocIndex = entries;
|
|
2022
|
+
return entries;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
function searchDocs({ query, limit } = {}) {
|
|
2026
|
+
const max = Number.isFinite(limit) && limit > 0 ? Math.min(limit, 20) : 8;
|
|
2027
|
+
const { total, results } = searchSections(docIndex(), query, max);
|
|
2028
|
+
// `read` as the call itself, one string: the object form cost ~100 chars a
|
|
2029
|
+
// result and agents copy the call anyway.
|
|
2030
|
+
const call = (read) =>
|
|
2031
|
+
read?.tool ? `${read.tool}(${JSON.stringify(read.args ?? {})})` : read;
|
|
2032
|
+
return {
|
|
2033
|
+
query: query ?? '',
|
|
2034
|
+
count: results.length,
|
|
2035
|
+
...(total > results.length ? { totalMatches: total } : {}),
|
|
2036
|
+
results: results.map((r) => ({ ...r, read: call(r.read) })),
|
|
2037
|
+
hint: results.length
|
|
2038
|
+
? 'Each result is one section; `read` is the call that returns it whole. Prefer the top results: they match the most query words.'
|
|
2039
|
+
: 'Nothing matched. Use fewer, more specific words (a prop, option or component name), or search_components for components.',
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
/** `get_composable({ name })` — one composable's doc (useToast, useConfirm…). */
|
|
2044
|
+
function getComposable({ name, sections, maxChars } = {}) {
|
|
2045
|
+
const dir = join(AI_ROOT, 'composables');
|
|
2046
|
+
const files = existsSync(dir)
|
|
2047
|
+
? readdirSync(dir).filter((f) => f.endsWith('.md'))
|
|
2048
|
+
: [];
|
|
2049
|
+
const wanted = String(name ?? '').replace(/\.md$/, '');
|
|
2050
|
+
const file = files.find(
|
|
2051
|
+
(f) => f.slice(0, -3).toLowerCase() === wanted.toLowerCase()
|
|
2052
|
+
);
|
|
2053
|
+
if (!file) {
|
|
378
2054
|
return {
|
|
379
2055
|
found: false,
|
|
380
|
-
error: `
|
|
381
|
-
|
|
2056
|
+
error: `Composable not found: ${name}`,
|
|
2057
|
+
available: files.map((f) => f.slice(0, -3)),
|
|
382
2058
|
};
|
|
383
2059
|
}
|
|
384
|
-
const
|
|
385
|
-
|
|
2060
|
+
const markdown = readFileSync(join(dir, file), 'utf-8');
|
|
2061
|
+
/*
|
|
2062
|
+
* A composable that needs a host component says so FIRST, as a field: with
|
|
2063
|
+
* no `<BbConfirm />`, `await confirm()` never settles. Every blind answerer
|
|
2064
|
+
* of "await an Are-you-sure dialog" (4 of 4, 2026-09-24) left the host out,
|
|
2065
|
+
* although the doc's second paragraph states it.
|
|
2066
|
+
*/
|
|
2067
|
+
const host = frontmatter(markdown).summary?.match(
|
|
2068
|
+
/Requires (exactly one <Bb\w+ \/> host[^.]*)\./
|
|
2069
|
+
)?.[1];
|
|
2070
|
+
const base = {
|
|
2071
|
+
found: true,
|
|
2072
|
+
name: file.slice(0, -3),
|
|
2073
|
+
...(host ? { requires: host } : {}),
|
|
2074
|
+
};
|
|
2075
|
+
const body = stripFrontmatter(markdown);
|
|
2076
|
+
const all = splitSections(markdown);
|
|
2077
|
+
if (sections?.length) {
|
|
2078
|
+
const { picked, missing } = pickSections(all, sections);
|
|
2079
|
+
return {
|
|
2080
|
+
...base,
|
|
2081
|
+
...(() => {
|
|
2082
|
+
const { kept, ...rest } = capParts(
|
|
2083
|
+
picked.map((sec) => sectionAnswer(sec, all, maxChars)),
|
|
2084
|
+
'Sections'
|
|
2085
|
+
);
|
|
2086
|
+
return { sections: kept, ...rest };
|
|
2087
|
+
})(),
|
|
2088
|
+
...(missing.length ? { notFound: missing, outline: outline(all) } : {}),
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
// Same rule as get_guide (MCP panel 2026-09-26): past 8K, the intro and the
|
|
2092
|
+
// outline; useConfirm's doc was 18K in one answer.
|
|
2093
|
+
if (maxChars === undefined && body.length >= GUIDE_OUTLINE_OVER) {
|
|
2094
|
+
return {
|
|
2095
|
+
...base,
|
|
2096
|
+
chars: body.length,
|
|
2097
|
+
intro: body.split(/^## /m)[0].trim().slice(0, 3000),
|
|
2098
|
+
outline: outline(all),
|
|
2099
|
+
hint: `Read what you need: get_composable({ name: '${base.name}', sections: ['<title from outline>'] }); maxChars: 0 returns it whole.`,
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
const { content, ...cut } = truncateMarkdown(
|
|
2103
|
+
markdown,
|
|
386
2104
|
maxChars,
|
|
387
|
-
|
|
2105
|
+
'The cut tail is later sections.'
|
|
388
2106
|
);
|
|
389
|
-
return {
|
|
2107
|
+
return { ...base, content: stripFrontmatter(content), ...cut };
|
|
390
2108
|
}
|
|
391
2109
|
|
|
392
2110
|
function listRecipes({ platform } = {}) {
|
|
@@ -398,21 +2116,40 @@ function listRecipes({ platform } = {}) {
|
|
|
398
2116
|
error: `Unknown platform(s): ${invalid.join(', ')} — expected one of ${RECIPE_PLATFORMS.join(', ')}`,
|
|
399
2117
|
};
|
|
400
2118
|
}
|
|
401
|
-
|
|
2119
|
+
/*
|
|
2120
|
+
* One row per recipe with its platforms, title, summary and size (review
|
|
2121
|
+
* D6-13): records-workspace / inline-edit-workspace / approvals-inbox could
|
|
2122
|
+
* not be told apart by name, and each costs 11–61 KB to open.
|
|
2123
|
+
*/
|
|
2124
|
+
const byName = new Map();
|
|
402
2125
|
for (const p of platforms) {
|
|
403
2126
|
const dir = join(AI_ROOT, 'recipes', p);
|
|
404
2127
|
if (!existsSync(dir)) continue;
|
|
405
2128
|
for (const file of readdirSync(dir)) {
|
|
406
|
-
if (file.endsWith('.md'))
|
|
407
|
-
|
|
408
|
-
|
|
2129
|
+
if (!file.endsWith('.md')) continue;
|
|
2130
|
+
const name = file.slice(0, -3);
|
|
2131
|
+
const markdown = readFileSync(join(dir, file), 'utf-8');
|
|
2132
|
+
const meta = frontmatter(markdown);
|
|
2133
|
+
const row = byName.get(name) ?? {
|
|
2134
|
+
name,
|
|
2135
|
+
...(meta.title ? { title: meta.title } : {}),
|
|
2136
|
+
...(meta.summary ? { summary: meta.summary } : {}),
|
|
2137
|
+
platforms: [],
|
|
2138
|
+
chars: {},
|
|
2139
|
+
};
|
|
2140
|
+
row.platforms.push(p);
|
|
2141
|
+
row.chars[p] = markdown.length;
|
|
2142
|
+
byName.set(name, row);
|
|
409
2143
|
}
|
|
410
2144
|
}
|
|
411
|
-
return {
|
|
2145
|
+
return {
|
|
2146
|
+
recipes: [...byName.values()],
|
|
2147
|
+
hint: 'get_recipe({ platform, name }) returns the whole recipe (they are copied whole); pass sections to read only part of a long one.',
|
|
2148
|
+
};
|
|
412
2149
|
}
|
|
413
2150
|
|
|
414
2151
|
/** `get_recipe({ platform, name })` — read one recipe's markdown content. */
|
|
415
|
-
function getRecipe({ platform, name, maxChars } = {}) {
|
|
2152
|
+
function getRecipe({ platform, name, sections, files, maxChars } = {}) {
|
|
416
2153
|
if (!RECIPE_PLATFORMS.includes(platform)) {
|
|
417
2154
|
return {
|
|
418
2155
|
found: false,
|
|
@@ -442,9 +2179,113 @@ function getRecipe({ platform, name, maxChars } = {}) {
|
|
|
442
2179
|
hint: 'Call list_recipes for the available names.',
|
|
443
2180
|
};
|
|
444
2181
|
}
|
|
2182
|
+
const markdown = readFileSync(filePath, 'utf-8');
|
|
2183
|
+
if (sections?.length) {
|
|
2184
|
+
const all = splitSections(markdown);
|
|
2185
|
+
const { picked, missing } = pickSections(all, sections);
|
|
2186
|
+
return {
|
|
2187
|
+
found: true,
|
|
2188
|
+
platform,
|
|
2189
|
+
name: fileName.slice(0, -3),
|
|
2190
|
+
...(() => {
|
|
2191
|
+
const { kept, ...rest } = capParts(
|
|
2192
|
+
picked.map((sec) => sectionAnswer(sec, all, maxChars)),
|
|
2193
|
+
'Sections'
|
|
2194
|
+
);
|
|
2195
|
+
return { sections: kept, ...rest };
|
|
2196
|
+
})(),
|
|
2197
|
+
...(missing.length ? { notFound: missing, outline: outline(all) } : {}),
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
/*
|
|
2201
|
+
* OUTLINE FIRST (MCP panel 2026-09-26, owner ruling M3). Whole recipes were
|
|
2202
|
+
* median ~59K chars, max 129K (~32K tokens): past Claude Code's 25K cut, so
|
|
2203
|
+
* "whole by default" did not reach agents whole anyway. The default is the
|
|
2204
|
+
* prose that explains the page (every section outside "The code"), the code
|
|
2205
|
+
* files with their sizes, and the shipped file's path — an agent that can
|
|
2206
|
+
* read files copies it from there; one that can't asks for `files`.
|
|
2207
|
+
* `maxChars: 0` still returns the markdown whole.
|
|
2208
|
+
*/
|
|
2209
|
+
const all = splitSections(markdown);
|
|
2210
|
+
const code = all.find(
|
|
2211
|
+
(sec) => sec.level === 2 && /^the (code|files)\b/i.test(sec.title)
|
|
2212
|
+
);
|
|
2213
|
+
const codeFiles = code
|
|
2214
|
+
? all.filter(
|
|
2215
|
+
(sec) => sec.level === 3 && code.text.includes(sec.text) && sec !== code
|
|
2216
|
+
)
|
|
2217
|
+
: [];
|
|
2218
|
+
if (files?.length) {
|
|
2219
|
+
// An exact title first, else a substring: `RejectDialog.vue` must not
|
|
2220
|
+
// also return BulkRejectDialog.vue.
|
|
2221
|
+
const hits = [];
|
|
2222
|
+
for (const f of files) {
|
|
2223
|
+
const w = String(f).toLowerCase();
|
|
2224
|
+
const exact = codeFiles.filter((sec) => sec.title.toLowerCase() === w);
|
|
2225
|
+
for (const sec of exact.length
|
|
2226
|
+
? exact
|
|
2227
|
+
: codeFiles.filter((sec) => sec.title.toLowerCase().includes(w)))
|
|
2228
|
+
if (!hits.includes(sec)) hits.push(sec);
|
|
2229
|
+
}
|
|
2230
|
+
const missingFiles = files.filter(
|
|
2231
|
+
(f) =>
|
|
2232
|
+
!codeFiles.some((sec) =>
|
|
2233
|
+
sec.title.toLowerCase().includes(String(f).toLowerCase())
|
|
2234
|
+
)
|
|
2235
|
+
);
|
|
2236
|
+
return {
|
|
2237
|
+
found: true,
|
|
2238
|
+
platform,
|
|
2239
|
+
name: fileName.slice(0, -3),
|
|
2240
|
+
...(() => {
|
|
2241
|
+
const { kept, ...rest } = capParts(
|
|
2242
|
+
hits.map((sec) => ({ title: sec.title, text: sec.text })),
|
|
2243
|
+
'Files'
|
|
2244
|
+
);
|
|
2245
|
+
return { files: kept, ...rest };
|
|
2246
|
+
})(),
|
|
2247
|
+
...(missingFiles.length
|
|
2248
|
+
? {
|
|
2249
|
+
notFound: missingFiles,
|
|
2250
|
+
available: codeFiles.map((sec) => sec.title),
|
|
2251
|
+
}
|
|
2252
|
+
: {}),
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
if (maxChars == null && code) {
|
|
2256
|
+
const prose = all.filter((sec) => sec.level === 2 && sec !== code);
|
|
2257
|
+
const meta = frontmatter(markdown);
|
|
2258
|
+
// The words between the title and the first ## (the adversarial review:
|
|
2259
|
+
// "read the XSRF-TOKEN cookie … or every upload 419s" lived there), and
|
|
2260
|
+
// the lead-in of the code section before its first file.
|
|
2261
|
+
const body = stripFrontmatter(markdown);
|
|
2262
|
+
const intro = body
|
|
2263
|
+
.split(/^## /m)[0]
|
|
2264
|
+
.replace(/^# .*$/m, '')
|
|
2265
|
+
.trim();
|
|
2266
|
+
const codeIntro = code.own.replace(/^## .*$/m, '').trim();
|
|
2267
|
+
return {
|
|
2268
|
+
found: true,
|
|
2269
|
+
platform,
|
|
2270
|
+
name: fileName.slice(0, -3),
|
|
2271
|
+
...(meta.title ? { title: meta.title } : {}),
|
|
2272
|
+
path: filePath,
|
|
2273
|
+
chars: markdown.length,
|
|
2274
|
+
...(intro ? { intro } : {}),
|
|
2275
|
+
prose: prose.map((sec) => ({ title: sec.title, text: sec.text })),
|
|
2276
|
+
...(codeIntro ? { codeIntro } : {}),
|
|
2277
|
+
files: codeFiles.map((sec) => ({
|
|
2278
|
+
title: sec.title,
|
|
2279
|
+
chars: sec.text.length,
|
|
2280
|
+
})),
|
|
2281
|
+
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.`,
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
// Whole on request (review D6-14): the old 40 000-char default cut 7 of 9
|
|
2285
|
+
// vue recipes inside their page code.
|
|
445
2286
|
const { content, ...meta } = truncateMarkdown(
|
|
446
|
-
|
|
447
|
-
maxChars,
|
|
2287
|
+
markdown,
|
|
2288
|
+
maxChars ?? 0,
|
|
448
2289
|
`Recipes are ordered narrative — the cut tail is later sections.`
|
|
449
2290
|
);
|
|
450
2291
|
return {
|
|
@@ -459,8 +2300,17 @@ function getRecipe({ platform, name, maxChars } = {}) {
|
|
|
459
2300
|
/** `validate({ code })` — Phase 2 validator core over a Vue snippet. */
|
|
460
2301
|
function validateCode({ code } = {}) {
|
|
461
2302
|
const manifest = getManifestMap();
|
|
462
|
-
const { findings } = validateVueSnippet(String(code ?? ''), manifest);
|
|
463
|
-
|
|
2303
|
+
const { findings: all } = validateVueSnippet(String(code ?? ''), manifest);
|
|
2304
|
+
// `severity: 'warning'` findings (a slot spelled the pre-reset way, Q53.15
|
|
2305
|
+
// C9b) are reported apart and never make the snippet invalid — the same
|
|
2306
|
+
// split `bitboss-ui check` prints.
|
|
2307
|
+
const findings = all.filter((f) => f.severity !== 'warning');
|
|
2308
|
+
const warnings = all.filter((f) => f.severity === 'warning');
|
|
2309
|
+
return {
|
|
2310
|
+
ok: findings.length === 0,
|
|
2311
|
+
findings,
|
|
2312
|
+
...(warnings.length ? { warnings } : {}),
|
|
2313
|
+
};
|
|
464
2314
|
}
|
|
465
2315
|
|
|
466
2316
|
/**
|
|
@@ -469,13 +2319,393 @@ function validateCode({ code } = {}) {
|
|
|
469
2319
|
* becomes an `isError` result carrying `Error: <message>` — preserving the
|
|
470
2320
|
* exact result shape of the previous hand-rolled dispatch.
|
|
471
2321
|
*/
|
|
2322
|
+
const CHANGELOG_PATH = join(AI_ROOT, 'changelog.json');
|
|
2323
|
+
|
|
2324
|
+
/**
|
|
2325
|
+
* Order two versions on this library's line: `3.0.0-beta.21` after
|
|
2326
|
+
* `3.0.0-beta.9` (numeric, not lexical — the bug every hand-rolled compare
|
|
2327
|
+
* has), and any prerelease before its own stable release.
|
|
2328
|
+
*/
|
|
2329
|
+
function compareVersions(a, b) {
|
|
2330
|
+
const parse = (v) => {
|
|
2331
|
+
const [core, pre = ''] = String(v).split('-');
|
|
2332
|
+
const nums = core.split('.').map((n) => Number.parseInt(n, 10) || 0);
|
|
2333
|
+
const [tag = '', n = ''] = pre.split('.');
|
|
2334
|
+
return { nums, tag, n: Number.parseInt(n, 10) || 0 };
|
|
2335
|
+
};
|
|
2336
|
+
const x = parse(a);
|
|
2337
|
+
const y = parse(b);
|
|
2338
|
+
for (let i = 0; i < 3; i += 1) {
|
|
2339
|
+
if ((x.nums[i] ?? 0) !== (y.nums[i] ?? 0))
|
|
2340
|
+
return (x.nums[i] ?? 0) - (y.nums[i] ?? 0);
|
|
2341
|
+
}
|
|
2342
|
+
// No prerelease tag outranks any prerelease of the same core version.
|
|
2343
|
+
if (!x.tag && y.tag) return 1;
|
|
2344
|
+
if (x.tag && !y.tag) return -1;
|
|
2345
|
+
if (x.tag !== y.tag) return x.tag < y.tag ? -1 : 1; // alpha < beta < rc
|
|
2346
|
+
return x.n - y.n;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
/**
|
|
2350
|
+
* `list_changes({ since, until })` — the breaking changes between two releases.
|
|
2351
|
+
*
|
|
2352
|
+
* WHY THIS TOOL EXISTS. A consumer agent on 2026-09-09 asked "what changed
|
|
2353
|
+
* between beta.20 and beta.21?", found nothing in the knowledge base that could
|
|
2354
|
+
* answer it, and diffed the two published tarballs by hand. The data existed
|
|
2355
|
+
* (`dist/ai/changelog.json`) but no tool served it, and its rows carried no
|
|
2356
|
+
* release, so even reading the file could not have answered the question. Both
|
|
2357
|
+
* halves are fixed: rows are stamped with `firstReleasedIn` at build time, and
|
|
2358
|
+
* this serves the slice.
|
|
2359
|
+
*/
|
|
2360
|
+
/*
|
|
2361
|
+
* The answer's rows, compact (owner rule: compact by default): empty fields,
|
|
2362
|
+
* `ruling` and `since` dropped, and the class renames (85 one-to-one pairs from the sweep map,
|
|
2363
|
+
* Q51.19) folded into ONE `{ ".old": ".new" }` map — complete, and a tenth of
|
|
2364
|
+
* the size of 85 row objects. Everything else stays a full row.
|
|
2365
|
+
*/
|
|
2366
|
+
function compactChanges(rows) {
|
|
2367
|
+
const classRenames = {};
|
|
2368
|
+
const breaking = [];
|
|
2369
|
+
for (const row of rows) {
|
|
2370
|
+
if (row.surface === 'class' && String(row.id).startsWith('css:')) {
|
|
2371
|
+
classRenames[row.from] = row.to;
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
// `ruling` is the library's internal decision id and `since` the
|
|
2375
|
+
// register's authoring-date note (firstReleasedIn is the release): both
|
|
2376
|
+
// stay in changelog.json, neither helps an upgrade.
|
|
2377
|
+
breaking.push(
|
|
2378
|
+
Object.fromEntries(
|
|
2379
|
+
Object.entries(row).filter(
|
|
2380
|
+
([key, value]) =>
|
|
2381
|
+
key !== 'ruling' &&
|
|
2382
|
+
key !== 'since' &&
|
|
2383
|
+
value !== '' &&
|
|
2384
|
+
value !== undefined &&
|
|
2385
|
+
!(Array.isArray(value) && value.length === 0)
|
|
2386
|
+
)
|
|
2387
|
+
)
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
return Object.keys(classRenames).length
|
|
2391
|
+
? {
|
|
2392
|
+
breaking,
|
|
2393
|
+
classRenames,
|
|
2394
|
+
classRenamesNote:
|
|
2395
|
+
'class renames from the beta.36 sweep map: modifiers follow (.old--x → .new--x); ai/guides/migration/v3-beta-css-classes.md',
|
|
2396
|
+
}
|
|
2397
|
+
: { breaking };
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
/*
|
|
2401
|
+
* Rows carry `kind` AND `surface`; 2 of 3 answerers told a script to switch on
|
|
2402
|
+
* `kind` to tell a class rename from a prop rename (2026-09-24).
|
|
2403
|
+
*/
|
|
2404
|
+
const ROW_FIELDS =
|
|
2405
|
+
'`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.';
|
|
2406
|
+
|
|
2407
|
+
/*
|
|
2408
|
+
* STAGED ANSWERS (MCP panel 2026-09-26). The whole v2 → v3 list was 93K chars
|
|
2409
|
+
* (~23K tokens, over Claude Code's 25K cap with the envelope) and one upgrade
|
|
2410
|
+
* hop 20–33K, 59% of it behaviour prose. Now: no range → counts per release;
|
|
2411
|
+
* a range or filter → ONE LINE per change, grouped by release, behaviour rows
|
|
2412
|
+
* as their hand-written `summary`; `detail: 'rows'` → the full rows as before.
|
|
2413
|
+
*/
|
|
2414
|
+
const CHANGES_PAGE = 60;
|
|
2415
|
+
|
|
2416
|
+
function changeLine(row, focus) {
|
|
2417
|
+
// 16 form controls in full is ~250 chars of the line; the component filter
|
|
2418
|
+
// and `detail: 'rows'` name them all.
|
|
2419
|
+
const list = row.components ?? [];
|
|
2420
|
+
// The component asked about always shows, never hidden in "+N more".
|
|
2421
|
+
const shown =
|
|
2422
|
+
focus && list.includes(focus)
|
|
2423
|
+
? [focus, ...list.filter((c) => c !== focus)]
|
|
2424
|
+
: list;
|
|
2425
|
+
const who =
|
|
2426
|
+
shown.length > 3
|
|
2427
|
+
? `${shown.slice(0, 2).join(' / ')} +${shown.length - 2} more`
|
|
2428
|
+
: shown.join(' / ');
|
|
2429
|
+
if (row.kind === 'rename') {
|
|
2430
|
+
const to = row.to == null ? 'removed' : row.to;
|
|
2431
|
+
return `${row.id} ${who ? `${who} ` : ''}${row.surface ?? 'prop'} ${row.from} → ${to}${row.note ? ` — ${firstSentence(row.note, 140)}` : ''}`;
|
|
2432
|
+
}
|
|
2433
|
+
return `${row.id} ${row.summary ?? firstSentence(row.description, 160)}`;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
function changeLines(rows, cursor, withClassMap = false, focus) {
|
|
2437
|
+
const lines = [];
|
|
2438
|
+
const classRenames = {};
|
|
2439
|
+
for (const row of rows) {
|
|
2440
|
+
if (row.surface === 'class' && String(row.id).startsWith('css:')) {
|
|
2441
|
+
classRenames[row.from] = row.to;
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
lines.push([row.firstReleasedIn ?? 'unreleased', changeLine(row, focus)]);
|
|
2445
|
+
}
|
|
2446
|
+
// Release order, unreleased last, so pages read oldest → newest.
|
|
2447
|
+
lines.sort(([a], [b]) =>
|
|
2448
|
+
a === b
|
|
2449
|
+
? 0
|
|
2450
|
+
: a === 'unreleased'
|
|
2451
|
+
? 1
|
|
2452
|
+
: b === 'unreleased'
|
|
2453
|
+
? -1
|
|
2454
|
+
: compareVersions(a, b)
|
|
2455
|
+
);
|
|
2456
|
+
const start = Math.max(0, Number(cursor ?? 0) || 0);
|
|
2457
|
+
const page = lines.slice(start, start + CHANGES_PAGE);
|
|
2458
|
+
const byRelease = {};
|
|
2459
|
+
for (const [release, line] of page) (byRelease[release] ??= []).push(line);
|
|
2460
|
+
return {
|
|
2461
|
+
changes: byRelease,
|
|
2462
|
+
...(start + CHANGES_PAGE < lines.length
|
|
2463
|
+
? {
|
|
2464
|
+
nextCursor: String(start + CHANGES_PAGE),
|
|
2465
|
+
remaining: lines.length - start - CHANGES_PAGE,
|
|
2466
|
+
}
|
|
2467
|
+
: {}),
|
|
2468
|
+
...(Object.keys(classRenames).length && start === 0 && !withClassMap
|
|
2469
|
+
? {
|
|
2470
|
+
classRenames: `${Object.keys(classRenames).length} .bb-* class renames (the beta.36 sweep): the map with surface: 'class'`,
|
|
2471
|
+
}
|
|
2472
|
+
: {}),
|
|
2473
|
+
...(Object.keys(classRenames).length && start === 0 && withClassMap
|
|
2474
|
+
? {
|
|
2475
|
+
classRenames,
|
|
2476
|
+
classRenamesNote:
|
|
2477
|
+
'class renames from the beta.36 sweep map: modifiers follow (.old--x → .new--x); ai/guides/migration/v3-beta-css-classes.md',
|
|
2478
|
+
}
|
|
2479
|
+
: {}),
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
function listChanges({
|
|
2484
|
+
since: rawSince,
|
|
2485
|
+
until: rawUntil,
|
|
2486
|
+
component,
|
|
2487
|
+
surface,
|
|
2488
|
+
kind,
|
|
2489
|
+
detail,
|
|
2490
|
+
cursor,
|
|
2491
|
+
} = {}) {
|
|
2492
|
+
// The repo's own tags are spelled `v3.0.0-beta.33` (review D6-12). A v2
|
|
2493
|
+
// version means "the whole v2 → v3 upgrade": every row, one line each.
|
|
2494
|
+
const fromV2 = rawSince != null && /^v?2(\.|$)/.test(String(rawSince));
|
|
2495
|
+
const since =
|
|
2496
|
+
rawSince == null || fromV2 ? undefined : String(rawSince).replace(/^v/, '');
|
|
2497
|
+
const until =
|
|
2498
|
+
rawUntil == null ? rawUntil : String(rawUntil).replace(/^v/, '');
|
|
2499
|
+
if (!existsSync(CHANGELOG_PATH)) {
|
|
2500
|
+
return {
|
|
2501
|
+
error: `changelog.json not found at ${CHANGELOG_PATH} — reinstall bitboss-ui (or run \`npm run build\` when working in the library repo).`,
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
const doc = JSON.parse(readFileSync(CHANGELOG_PATH, 'utf-8'));
|
|
2505
|
+
const releases = doc.releases ?? [];
|
|
2506
|
+
const known = new Set(releases);
|
|
2507
|
+
|
|
2508
|
+
// A typo'd version must not silently return "nothing changed" — that reads
|
|
2509
|
+
// as a clean upgrade and is the worst possible wrong answer here.
|
|
2510
|
+
for (const [label, value] of [
|
|
2511
|
+
['since', since],
|
|
2512
|
+
['until', until],
|
|
2513
|
+
]) {
|
|
2514
|
+
if (value != null && !known.has(String(value))) {
|
|
2515
|
+
if ((doc.unpublished ?? []).includes(String(value))) {
|
|
2516
|
+
const next = releases.find(
|
|
2517
|
+
(r) => compareVersions(r, String(value)) > 0
|
|
2518
|
+
);
|
|
2519
|
+
return {
|
|
2520
|
+
error: `${value} was never published to npm; its changes shipped in ${next ?? 'the next release'}.`,
|
|
2521
|
+
knownReleases: releases,
|
|
2522
|
+
hint: `Pass the version you installed from npm (for example the one before it, since=${releases.filter((r) => compareVersions(r, String(value)) < 0).at(-1) ?? '…'}).`,
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
return {
|
|
2526
|
+
error: `Unknown ${label} version '${value}'.`,
|
|
2527
|
+
knownReleases: releases,
|
|
2528
|
+
hint: 'Pass a version exactly as listed in knownReleases; coming from v2, pass since: "2" for every break of the v2 → v3 upgrade.',
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
if (
|
|
2534
|
+
since != null &&
|
|
2535
|
+
until != null &&
|
|
2536
|
+
compareVersions(String(since), String(until)) >= 0
|
|
2537
|
+
) {
|
|
2538
|
+
return {
|
|
2539
|
+
error: `since (${since}) must be older than until (${until}).`,
|
|
2540
|
+
knownReleases: releases,
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
// Resolve the component the way get_component does ('bb-select',
|
|
2544
|
+
// 'Select'), and say so when it is no component at all — an empty list
|
|
2545
|
+
// would read as "nothing breaks".
|
|
2546
|
+
if (component) {
|
|
2547
|
+
const resolved = resolveComponent(loadComponentsManifest(), component);
|
|
2548
|
+
// A retired name (BbTabsPanes) is still a key in the rows it renamed.
|
|
2549
|
+
const retiredName =
|
|
2550
|
+
!resolved?.entry && loadRetired().has(String(component).trim());
|
|
2551
|
+
if (!resolved?.entry && !retiredName) {
|
|
2552
|
+
return {
|
|
2553
|
+
error: `Unknown component '${component}'.`,
|
|
2554
|
+
didYouMean: nearestComponents(loadComponentsManifest(), component),
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
if (resolved?.entry) component = resolved.entry.name;
|
|
2558
|
+
}
|
|
2559
|
+
const rows = detail === 'rows';
|
|
2560
|
+
const filtered = (doc.breaking ?? []).filter(
|
|
2561
|
+
(row) =>
|
|
2562
|
+
(!component ||
|
|
2563
|
+
(row.components ?? []).some(
|
|
2564
|
+
(c) => c.toLowerCase() === String(component).toLowerCase()
|
|
2565
|
+
)) &&
|
|
2566
|
+
(!surface ||
|
|
2567
|
+
(row.surface ?? (row.kind === 'rename' ? 'prop' : null)) === surface) &&
|
|
2568
|
+
(!kind || row.kind === kind)
|
|
2569
|
+
);
|
|
2570
|
+
const all = filtered;
|
|
2571
|
+
const filteredBy = {
|
|
2572
|
+
...(component ? { component } : {}),
|
|
2573
|
+
...(surface ? { surface } : {}),
|
|
2574
|
+
...(kind ? { kind } : {}),
|
|
2575
|
+
};
|
|
2576
|
+
|
|
2577
|
+
// No range and no filter: counts per release — the map, not the territory.
|
|
2578
|
+
if (
|
|
2579
|
+
since == null &&
|
|
2580
|
+
until == null &&
|
|
2581
|
+
!rows &&
|
|
2582
|
+
!fromV2 &&
|
|
2583
|
+
!Object.keys(filteredBy).length
|
|
2584
|
+
) {
|
|
2585
|
+
const perRelease = {};
|
|
2586
|
+
for (const row of all) {
|
|
2587
|
+
const at = row.firstReleasedIn ?? 'unreleased';
|
|
2588
|
+
const bucket = (perRelease[at] ??= {
|
|
2589
|
+
renames: 0,
|
|
2590
|
+
behaviour: 0,
|
|
2591
|
+
classRenames: 0,
|
|
2592
|
+
});
|
|
2593
|
+
if (row.surface === 'class' && String(row.id).startsWith('css:'))
|
|
2594
|
+
bucket.classRenames++;
|
|
2595
|
+
else if (row.kind === 'rename') bucket.renames++;
|
|
2596
|
+
else bucket.behaviour++;
|
|
2597
|
+
}
|
|
2598
|
+
return {
|
|
2599
|
+
version: doc.version,
|
|
2600
|
+
upgrade: doc.upgrade,
|
|
2601
|
+
guide: doc.guide,
|
|
2602
|
+
count: all.length,
|
|
2603
|
+
releases,
|
|
2604
|
+
perRelease: Object.fromEntries(
|
|
2605
|
+
Object.entries(perRelease).sort(([a], [b]) =>
|
|
2606
|
+
a === 'unreleased'
|
|
2607
|
+
? 1
|
|
2608
|
+
: b === 'unreleased'
|
|
2609
|
+
? -1
|
|
2610
|
+
: compareVersions(a, b)
|
|
2611
|
+
)
|
|
2612
|
+
),
|
|
2613
|
+
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".',
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
// A filter with no range, or `since` a v2 version: one line per change
|
|
2618
|
+
// (the v2 → v3 upgrade, or that slice of it).
|
|
2619
|
+
if (since == null && until == null && !rows) {
|
|
2620
|
+
return {
|
|
2621
|
+
version: doc.version,
|
|
2622
|
+
filteredBy,
|
|
2623
|
+
count: all.length,
|
|
2624
|
+
...changeLines(all, cursor, surface === 'class', component),
|
|
2625
|
+
guide: doc.guide,
|
|
2626
|
+
...(component
|
|
2627
|
+
? {
|
|
2628
|
+
libraryWide: (doc.breaking ?? []).filter((row) => row.libraryWide)
|
|
2629
|
+
.length,
|
|
2630
|
+
}
|
|
2631
|
+
: {}),
|
|
2632
|
+
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.`,
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
// No range asked for: the whole v2 → v3 set, which is what the file has
|
|
2637
|
+
// always meant.
|
|
2638
|
+
if (since == null && until == null) {
|
|
2639
|
+
return {
|
|
2640
|
+
version: doc.version,
|
|
2641
|
+
upgrade: doc.upgrade,
|
|
2642
|
+
guide: doc.guide,
|
|
2643
|
+
releases,
|
|
2644
|
+
summary: doc.summary,
|
|
2645
|
+
count: all.length,
|
|
2646
|
+
...compactChanges(all),
|
|
2647
|
+
hint: `Narrow to one upgrade hop with list_changes({ since: "<your current version>" }) — that answers "what breaks if I bump". ${ROW_FIELDS}`,
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
const inRange = all.filter((entry) => {
|
|
2652
|
+
const at = entry.firstReleasedIn;
|
|
2653
|
+
// Unreleased rows belong to the future: they ship in whatever comes
|
|
2654
|
+
// next, so they count only when the caller left `until` open.
|
|
2655
|
+
if (at == null) return until == null;
|
|
2656
|
+
if (since != null && compareVersions(at, String(since)) <= 0) return false;
|
|
2657
|
+
if (until != null && compareVersions(at, String(until)) > 0) return false;
|
|
2658
|
+
return true;
|
|
2659
|
+
});
|
|
2660
|
+
|
|
2661
|
+
if (!rows) {
|
|
2662
|
+
return {
|
|
2663
|
+
version: doc.version,
|
|
2664
|
+
since: since ?? null,
|
|
2665
|
+
until: until ?? doc.version,
|
|
2666
|
+
...(Object.keys(filteredBy).length ? { filteredBy } : {}),
|
|
2667
|
+
count: inRange.length,
|
|
2668
|
+
...changeLines(inRange, cursor, surface === 'class', component),
|
|
2669
|
+
guide: doc.guide,
|
|
2670
|
+
hint:
|
|
2671
|
+
inRange.length === 0
|
|
2672
|
+
? '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.'
|
|
2673
|
+
: '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.',
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
return {
|
|
2678
|
+
version: doc.version,
|
|
2679
|
+
since: since ?? null,
|
|
2680
|
+
until: until ?? doc.version,
|
|
2681
|
+
count: inRange.length,
|
|
2682
|
+
...compactChanges(inRange),
|
|
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
|
+
: `Each entry names the component and the new spelling; \`guide\` is the migration page with the full explanation. ${ROW_FIELDS}`,
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
|
|
472
2691
|
function toolCallback(fn) {
|
|
473
2692
|
return (args) => {
|
|
474
2693
|
try {
|
|
475
2694
|
const result = fn(args ?? {});
|
|
476
2695
|
return {
|
|
477
|
-
|
|
478
|
-
|
|
2696
|
+
// Compact JSON: indentation was a quarter of every answer (review D6).
|
|
2697
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
2698
|
+
// A miss is an error the model should act on (MCP spec: tool
|
|
2699
|
+
// execution errors go in the result with isError). Every miss
|
|
2700
|
+
// still carries its next step: didYouMean, related, hint.
|
|
2701
|
+
// A redirect (a retired v2 name, a picker verdict like BbDrawer →
|
|
2702
|
+
// BbOffCanvas) is an ANSWER, not an error: some clients collapse or
|
|
2703
|
+
// raise on isError (adversarial review 2026-09-26).
|
|
2704
|
+
isError:
|
|
2705
|
+
(result?.found === false &&
|
|
2706
|
+
!result?.retired &&
|
|
2707
|
+
!result?.notInLibrary?.length) ||
|
|
2708
|
+
typeof result?.error === 'string',
|
|
479
2709
|
};
|
|
480
2710
|
} catch (error) {
|
|
481
2711
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -489,18 +2719,55 @@ function toolCallback(fn) {
|
|
|
489
2719
|
|
|
490
2720
|
const platformSchema = z.enum(RECIPE_PLATFORMS);
|
|
491
2721
|
|
|
2722
|
+
/**
|
|
2723
|
+
* Sent once at initialize (review D6-1): an MCP-only agent otherwise had no
|
|
2724
|
+
* way to know which tool answers which question, and read whole guides.
|
|
2725
|
+
*/
|
|
2726
|
+
const SERVER_INSTRUCTIONS = [
|
|
2727
|
+
'bitboss-ui: a Vue 3 component library. Answer from these tools, never from memory — agents that guess invent props.',
|
|
2728
|
+
"- Tailwind v4 app: set cssLayers: TAILWIND_V4_LAYERS ('bitboss-ui/vite') at setup, or utilities lose.",
|
|
2729
|
+
'- 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.',
|
|
2730
|
+
'- 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.',
|
|
2731
|
+
'- A type in a props table (BbTableColumn, ButtonVariantType): get_component({ name }) returns its definition and values.',
|
|
2732
|
+
'- After writing markup: validate. Before custom CSS: list_tokens.',
|
|
2733
|
+
'- 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.',
|
|
2734
|
+
"- pt in <script> (a const, a wrapper's prop): type it <Name>Props['pt']; Pt* types are not exported.",
|
|
2735
|
+
'- Composables: get_composable. Whole pages: list_recipes, get_recipe (prose + files). Long docs answer with an outline: pass sections.',
|
|
2736
|
+
].join('\n');
|
|
2737
|
+
|
|
492
2738
|
/** Register the knowledge-base tools on a fresh McpServer instance. */
|
|
493
2739
|
function buildServer() {
|
|
494
2740
|
const server = new McpServer(
|
|
495
2741
|
{ name: 'bitboss-ui', version: packageVersion() },
|
|
496
|
-
{ capabilities: { tools: {} } }
|
|
2742
|
+
{ capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS }
|
|
497
2743
|
);
|
|
2744
|
+
// Every tool reads the installed package and nothing else: say so, so a
|
|
2745
|
+
// client can call them without a confirmation (MCP tool annotations).
|
|
2746
|
+
const registerTool = server.registerTool.bind(server);
|
|
2747
|
+
server.registerTool = (name, config, callback) =>
|
|
2748
|
+
registerTool(
|
|
2749
|
+
name,
|
|
2750
|
+
{
|
|
2751
|
+
title: name
|
|
2752
|
+
.split('_')
|
|
2753
|
+
.map((w, i) => (i ? w : w[0].toUpperCase() + w.slice(1)))
|
|
2754
|
+
.join(' '),
|
|
2755
|
+
...config,
|
|
2756
|
+
annotations: {
|
|
2757
|
+
readOnlyHint: true,
|
|
2758
|
+
destructiveHint: false,
|
|
2759
|
+
idempotentHint: true,
|
|
2760
|
+
openWorldHint: false,
|
|
2761
|
+
},
|
|
2762
|
+
},
|
|
2763
|
+
callback
|
|
2764
|
+
);
|
|
498
2765
|
|
|
499
2766
|
server.registerTool(
|
|
500
2767
|
'search_components',
|
|
501
2768
|
{
|
|
502
2769
|
description:
|
|
503
|
-
'
|
|
2770
|
+
'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.',
|
|
504
2771
|
inputSchema: {
|
|
505
2772
|
query: z
|
|
506
2773
|
.string()
|
|
@@ -517,7 +2784,7 @@ function buildServer() {
|
|
|
517
2784
|
.boolean()
|
|
518
2785
|
.optional()
|
|
519
2786
|
.describe(
|
|
520
|
-
'Include reference-only internal components (
|
|
2787
|
+
'Include reference-only internal components (Common*, listbox internals). Default false — these are not consumer API and should not be recommended. BbBaseButton and the BbBase*Icon glyphs are consumer API and are always included.'
|
|
521
2788
|
),
|
|
522
2789
|
},
|
|
523
2790
|
},
|
|
@@ -528,16 +2795,51 @@ function buildServer() {
|
|
|
528
2795
|
'get_component',
|
|
529
2796
|
{
|
|
530
2797
|
description:
|
|
531
|
-
|
|
2798
|
+
"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').",
|
|
532
2799
|
inputSchema: {
|
|
533
|
-
name: z
|
|
2800
|
+
name: z
|
|
2801
|
+
.string()
|
|
2802
|
+
.describe("Component name, e.g. 'BbButton' ('bb-button' works too)."),
|
|
2803
|
+
sections: z
|
|
2804
|
+
.array(z.string())
|
|
2805
|
+
.optional()
|
|
2806
|
+
.describe(
|
|
2807
|
+
"Guide sections to return whole, by title from `guide.sections`, e.g. ['Toggle buttons']. Case-insensitive; a partial title matches."
|
|
2808
|
+
),
|
|
2809
|
+
include: z
|
|
2810
|
+
.array(
|
|
2811
|
+
z.enum([
|
|
2812
|
+
'tree',
|
|
2813
|
+
'cssProps',
|
|
2814
|
+
'css',
|
|
2815
|
+
'ptProps',
|
|
2816
|
+
'history',
|
|
2817
|
+
'contract',
|
|
2818
|
+
])
|
|
2819
|
+
)
|
|
2820
|
+
.optional()
|
|
2821
|
+
.describe(
|
|
2822
|
+
'Extra parts: tree = the component tree, cssProps = CSS variables with descriptions, css = the .bb-* classes it renders (by block; for a selector you must write), ptProps = every pt:* prop row, history = full v2/beta rename records, contract = the whole contract markdown.'
|
|
2823
|
+
),
|
|
534
2824
|
maxChars: z
|
|
535
2825
|
.number()
|
|
536
2826
|
.int()
|
|
537
2827
|
.min(0)
|
|
538
2828
|
.optional()
|
|
539
2829
|
.describe(
|
|
540
|
-
`
|
|
2830
|
+
`With include: ['contract'] only — cap on the contract markdown (default ${MARKDOWN_DEFAULT_MAX_CHARS}; 0 = no cap).`
|
|
2831
|
+
),
|
|
2832
|
+
members: z
|
|
2833
|
+
.array(z.string())
|
|
2834
|
+
.optional()
|
|
2835
|
+
.describe(
|
|
2836
|
+
"Props, events or slots to return whole, by name, e.g. ['columns', 'click:row', '#actions']. Returns only those rows."
|
|
2837
|
+
),
|
|
2838
|
+
detail: z
|
|
2839
|
+
.enum(['summary', 'full'])
|
|
2840
|
+
.optional()
|
|
2841
|
+
.describe(
|
|
2842
|
+
"'summary' (default): one line per member. 'full': the whole manifest entry, every description, the Gotchas and Accessibility sections."
|
|
541
2843
|
),
|
|
542
2844
|
},
|
|
543
2845
|
},
|
|
@@ -571,17 +2873,70 @@ function buildServer() {
|
|
|
571
2873
|
toolCallback(listGuides)
|
|
572
2874
|
);
|
|
573
2875
|
|
|
2876
|
+
server.registerTool(
|
|
2877
|
+
'list_changes',
|
|
2878
|
+
{
|
|
2879
|
+
description:
|
|
2880
|
+
'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.',
|
|
2881
|
+
inputSchema: {
|
|
2882
|
+
since: z
|
|
2883
|
+
.string()
|
|
2884
|
+
.optional()
|
|
2885
|
+
.describe(
|
|
2886
|
+
"Exclusive lower bound — the version you are currently on, e.g. '3.0.0-beta.20'. Its own breaks are excluded; you already have them."
|
|
2887
|
+
),
|
|
2888
|
+
until: z
|
|
2889
|
+
.string()
|
|
2890
|
+
.optional()
|
|
2891
|
+
.describe(
|
|
2892
|
+
'Inclusive upper bound (default: the installed version). Unreleased breaks are included only when this is omitted.'
|
|
2893
|
+
),
|
|
2894
|
+
component: z
|
|
2895
|
+
.string()
|
|
2896
|
+
.optional()
|
|
2897
|
+
.describe("Only this component's breaks, e.g. 'BbSelect'."),
|
|
2898
|
+
surface: z
|
|
2899
|
+
.enum(['prop', 'slot', 'pt', 'cssVar', 'class', 'option', 'method'])
|
|
2900
|
+
.optional()
|
|
2901
|
+
.describe('Only renames of this surface.'),
|
|
2902
|
+
kind: z
|
|
2903
|
+
.enum(['rename', 'behaviour'])
|
|
2904
|
+
.optional()
|
|
2905
|
+
.describe('Only renames, or only behaviour changes.'),
|
|
2906
|
+
detail: z
|
|
2907
|
+
.enum(['lines', 'rows'])
|
|
2908
|
+
.optional()
|
|
2909
|
+
.describe(
|
|
2910
|
+
"'lines' (default): one line per change. 'rows': the full records (description, guide, default, fix)."
|
|
2911
|
+
),
|
|
2912
|
+
cursor: z
|
|
2913
|
+
.string()
|
|
2914
|
+
.optional()
|
|
2915
|
+
.describe(
|
|
2916
|
+
'The `nextCursor` of the previous answer, for the next page.'
|
|
2917
|
+
),
|
|
2918
|
+
},
|
|
2919
|
+
},
|
|
2920
|
+
toolCallback(listChanges)
|
|
2921
|
+
);
|
|
2922
|
+
|
|
574
2923
|
server.registerTool(
|
|
575
2924
|
'get_guide',
|
|
576
2925
|
{
|
|
577
2926
|
description:
|
|
578
|
-
"Read one bitboss-ui guide
|
|
2927
|
+
"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.",
|
|
579
2928
|
inputSchema: {
|
|
580
2929
|
name: z
|
|
581
2930
|
.string()
|
|
582
2931
|
.describe(
|
|
583
2932
|
"Guide path relative to the guides dir, e.g. 'design-tokens.md'. The '.md' suffix is optional."
|
|
584
2933
|
),
|
|
2934
|
+
sections: z
|
|
2935
|
+
.array(z.string())
|
|
2936
|
+
.optional()
|
|
2937
|
+
.describe(
|
|
2938
|
+
"Section titles from the outline, e.g. ['Dark mode']. A ## section includes its ### children."
|
|
2939
|
+
),
|
|
585
2940
|
maxChars: z
|
|
586
2941
|
.number()
|
|
587
2942
|
.int()
|
|
@@ -599,7 +2954,7 @@ function buildServer() {
|
|
|
599
2954
|
'list_recipes',
|
|
600
2955
|
{
|
|
601
2956
|
description:
|
|
602
|
-
'List
|
|
2957
|
+
'List the page-level recipes (whole-page compositions) with title, summary, platforms and size, optionally filtered by platform.',
|
|
603
2958
|
inputSchema: {
|
|
604
2959
|
platform: platformSchema
|
|
605
2960
|
.optional()
|
|
@@ -613,8 +2968,18 @@ function buildServer() {
|
|
|
613
2968
|
'get_recipe',
|
|
614
2969
|
{
|
|
615
2970
|
description:
|
|
616
|
-
'Read
|
|
2971
|
+
'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.',
|
|
617
2972
|
inputSchema: {
|
|
2973
|
+
sections: z
|
|
2974
|
+
.array(z.string())
|
|
2975
|
+
.optional()
|
|
2976
|
+
.describe('Section titles to return instead of the default.'),
|
|
2977
|
+
files: z
|
|
2978
|
+
.array(z.string())
|
|
2979
|
+
.optional()
|
|
2980
|
+
.describe(
|
|
2981
|
+
"Code files to return whole, by title from `files`, e.g. ['Index.vue', 'context.ts']."
|
|
2982
|
+
),
|
|
618
2983
|
platform: platformSchema.describe(
|
|
619
2984
|
'Which platform variant of the recipe to read.'
|
|
620
2985
|
),
|
|
@@ -627,18 +2992,62 @@ function buildServer() {
|
|
|
627
2992
|
.min(0)
|
|
628
2993
|
.optional()
|
|
629
2994
|
.describe(
|
|
630
|
-
|
|
2995
|
+
'Return the markdown itself instead of the outline: 0 = whole, N = cut at N chars.'
|
|
631
2996
|
),
|
|
632
2997
|
},
|
|
633
2998
|
},
|
|
634
2999
|
toolCallback(getRecipe)
|
|
635
3000
|
);
|
|
636
3001
|
|
|
3002
|
+
server.registerTool(
|
|
3003
|
+
'search_docs',
|
|
3004
|
+
{
|
|
3005
|
+
description:
|
|
3006
|
+
'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.',
|
|
3007
|
+
inputSchema: {
|
|
3008
|
+
query: z
|
|
3009
|
+
.string()
|
|
3010
|
+
.describe(
|
|
3011
|
+
"A few specific words, e.g. 'dark mode class' or 'tailwind cascade layers'."
|
|
3012
|
+
),
|
|
3013
|
+
limit: z
|
|
3014
|
+
.number()
|
|
3015
|
+
.int()
|
|
3016
|
+
.positive()
|
|
3017
|
+
.optional()
|
|
3018
|
+
.describe('Max results (default 8, max 20).'),
|
|
3019
|
+
},
|
|
3020
|
+
},
|
|
3021
|
+
toolCallback(searchDocs)
|
|
3022
|
+
);
|
|
3023
|
+
|
|
3024
|
+
server.registerTool(
|
|
3025
|
+
'get_composable',
|
|
3026
|
+
{
|
|
3027
|
+
description:
|
|
3028
|
+
"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`.",
|
|
3029
|
+
inputSchema: {
|
|
3030
|
+
name: z.string().describe("Composable name, e.g. 'useToast'."),
|
|
3031
|
+
sections: z
|
|
3032
|
+
.array(z.string())
|
|
3033
|
+
.optional()
|
|
3034
|
+
.describe('Section titles to return, from `outline`.'),
|
|
3035
|
+
maxChars: z
|
|
3036
|
+
.number()
|
|
3037
|
+
.int()
|
|
3038
|
+
.min(0)
|
|
3039
|
+
.optional()
|
|
3040
|
+
.describe('Return the doc itself: 0 = whole, N = cut at N chars.'),
|
|
3041
|
+
},
|
|
3042
|
+
},
|
|
3043
|
+
toolCallback(getComposable)
|
|
3044
|
+
);
|
|
3045
|
+
|
|
637
3046
|
server.registerTool(
|
|
638
3047
|
'validate',
|
|
639
3048
|
{
|
|
640
3049
|
description:
|
|
641
|
-
'Validate a Vue template/SFC snippet against the installed components.json manifest: reports unknown Bb* props, unknown v-models,
|
|
3050
|
+
'Validate a Vue template/SFC snippet against the installed components.json manifest: reports unknown Bb* props, unknown v-models, removed/renamed props, and imports that are not public (a file inside bitboss-ui/dist, a Pt* type). Run this after writing bitboss-ui markup.',
|
|
642
3051
|
inputSchema: {
|
|
643
3052
|
code: z
|
|
644
3053
|
.string()
|
|
@@ -659,8 +3068,21 @@ export async function main() {
|
|
|
659
3068
|
const server = buildServer();
|
|
660
3069
|
const transport = new StdioServerTransport();
|
|
661
3070
|
await server.connect(transport);
|
|
3071
|
+
// Settle on stdin's end as well as the transport's close. `onclose` does
|
|
3072
|
+
// not fire when the host simply closes the pipe, so awaiting it alone left
|
|
3073
|
+
// the caller's top-level `await main()` pending at exit and Node printed
|
|
3074
|
+
// "Detected unsettled top-level await" on every clean shutdown (2026-09-09).
|
|
3075
|
+
// `once` on both, and idempotent resolve, so whichever arrives first wins.
|
|
662
3076
|
return new Promise((resolve) => {
|
|
663
|
-
|
|
3077
|
+
let done = false;
|
|
3078
|
+
const settle = () => {
|
|
3079
|
+
if (done) return;
|
|
3080
|
+
done = true;
|
|
3081
|
+
resolve();
|
|
3082
|
+
};
|
|
3083
|
+
transport.onclose = settle;
|
|
3084
|
+
process.stdin.once('end', settle);
|
|
3085
|
+
process.stdin.once('close', settle);
|
|
664
3086
|
});
|
|
665
3087
|
}
|
|
666
3088
|
|
|
@@ -675,7 +3097,11 @@ export const start = main;
|
|
|
675
3097
|
* concurrently. Importing this module does not start a server.
|
|
676
3098
|
*/
|
|
677
3099
|
export {
|
|
3100
|
+
buildServer,
|
|
3101
|
+
SERVER_INSTRUCTIONS,
|
|
678
3102
|
searchComponents,
|
|
3103
|
+
searchDocs,
|
|
3104
|
+
getComposable,
|
|
679
3105
|
getComponent,
|
|
680
3106
|
listTokens,
|
|
681
3107
|
listGuides,
|