bitboss-ui 3.0.0-beta.5 → 3.0.0-beta.50

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