bitboss-ui 3.0.0-alpha.1 → 3.0.0-alpha.4

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 (310) hide show
  1. package/README.md +31 -4
  2. package/bin/bitboss-ui-mcp.mjs +123 -18
  3. package/bin/bitboss-ui.mjs +101 -17
  4. package/bin/bitboss-ui.node.test.mjs +113 -0
  5. package/dist/ai/BbAccordion.md +14 -8
  6. package/dist/ai/BbAlert.md +7 -4
  7. package/dist/ai/BbAvatar.md +13 -9
  8. package/dist/ai/BbBadge.md +8 -6
  9. package/dist/ai/BbBaseButton.md +6 -4
  10. package/dist/ai/BbBaseCheckbox.md +1 -1
  11. package/dist/ai/BbBaseCheckboxIcon.md +8 -6
  12. package/dist/ai/BbBaseColorInput.md +1 -1
  13. package/dist/ai/BbBaseInputContainer.md +4 -4
  14. package/dist/ai/BbBaseRadioGroup.md +1 -0
  15. package/dist/ai/BbBaseRadioIcon.md +11 -7
  16. package/dist/ai/BbBaseSelect.md +1 -1
  17. package/dist/ai/BbBaseSwitch.md +1 -1
  18. package/dist/ai/BbBaseSwitchIcon.md +9 -7
  19. package/dist/ai/BbBreadcrumbs.md +22 -11
  20. package/dist/ai/BbButton.md +94 -20
  21. package/dist/ai/BbCheckbox.md +36 -23
  22. package/dist/ai/BbCheckboxGroup.md +28 -16
  23. package/dist/ai/BbCollapsible.md +15 -9
  24. package/dist/ai/BbColorInput.md +19 -12
  25. package/dist/ai/BbColorPalette.md +11 -7
  26. package/dist/ai/BbConfirm.md +69 -22
  27. package/dist/ai/BbConfirmPortal.md +1 -1
  28. package/dist/ai/BbDatePickerInput.md +30 -16
  29. package/dist/ai/BbDialog.md +21 -13
  30. package/dist/ai/BbDropdown.md +39 -21
  31. package/dist/ai/BbDropdownButton.md +31 -16
  32. package/dist/ai/BbDropzone.md +117 -59
  33. package/dist/ai/BbIcon.md +12 -9
  34. package/dist/ai/BbIndicator.md +1 -1
  35. package/dist/ai/BbNumberInput.md +19 -15
  36. package/dist/ai/BbOffCanvas.md +36 -14
  37. package/dist/ai/BbPagination.md +19 -13
  38. package/dist/ai/BbPopover.md +18 -10
  39. package/dist/ai/BbProgress.md +22 -16
  40. package/dist/ai/BbRadio.md +19 -10
  41. package/dist/ai/BbRadioGroup.md +17 -6
  42. package/dist/ai/BbRating.md +24 -12
  43. package/dist/ai/BbSelect.md +44 -25
  44. package/dist/ai/BbSelectPopover.md +27 -11
  45. package/dist/ai/BbSlider.md +38 -15
  46. package/dist/ai/BbSmoothHeight.md +24 -11
  47. package/dist/ai/BbSpinner.md +3 -3
  48. package/dist/ai/BbSwitch.md +55 -31
  49. package/dist/ai/BbSwitchGroup.md +37 -19
  50. package/dist/ai/BbTable.md +55 -22
  51. package/dist/ai/BbTabs.md +69 -35
  52. package/dist/ai/BbTag.md +40 -17
  53. package/dist/ai/BbTextInput.md +47 -21
  54. package/dist/ai/BbTextarea.md +38 -24
  55. package/dist/ai/BbTimePickerInput.md +10 -6
  56. package/dist/ai/BbToast.md +62 -17
  57. package/dist/ai/BbToastPortal.md +1 -1
  58. package/dist/ai/BbTooltip.md +16 -6
  59. package/dist/ai/BbTree.md +21 -11
  60. package/dist/ai/CommonPopover.md +1 -1
  61. package/dist/ai/components.json +108 -94
  62. package/dist/ai/composables/useBbConfig.md +1 -1
  63. package/dist/ai/composables/useBbTableContext.md +5 -2
  64. package/dist/ai/composables/useBbTabsContext.md +1 -1
  65. package/dist/ai/composables/useBroadcastChannelInstance.md +1 -1
  66. package/dist/ai/composables/useConfirm.md +14 -7
  67. package/dist/ai/composables/useCountdown.md +8 -6
  68. package/dist/ai/composables/useToast.md +17 -9
  69. package/dist/ai/guides/agent-contract.md +58 -1
  70. package/dist/ai/guides/ai-router.md +13 -11
  71. package/dist/ai/guides/app-layout.md +34 -11
  72. package/dist/ai/guides/component-picker.md +47 -6
  73. package/dist/ai/guides/design-language.md +20 -3
  74. package/dist/ai/guides/design-tokens.md +27 -1
  75. package/dist/ai/guides/floating-bottom-panel.md +44 -105
  76. package/dist/ai/guides/icons-policy.md +1 -1
  77. package/dist/ai/guides/inertia-helpers.md +57 -8
  78. package/dist/ai/guides/inline-edit-playbook.md +9 -0
  79. package/dist/ai/guides/installation-and-plugin-setup.md +68 -5
  80. package/dist/ai/guides/migration/components/bb-icon.md +1 -1
  81. package/dist/ai/guides/migration/components/bb-offcanvas.md +28 -19
  82. package/dist/ai/guides/migration/components/bb-select-popover.md +1 -0
  83. package/dist/ai/guides/migration/components/bb-select.md +1 -0
  84. package/dist/ai/guides/migration/v2-to-v3.md +1 -1
  85. package/dist/ai/guides/page-shell.md +31 -232
  86. package/dist/ai/guides/validated-forms.md +1 -1
  87. package/dist/ai/index.md +10 -8
  88. package/dist/ai/recipes/inertia/approvals-inbox.md +65 -28
  89. package/dist/ai/recipes/inertia/inline-edit-workspace.md +55 -23
  90. package/dist/ai/recipes/inertia/layout-scaffold.md +385 -0
  91. package/dist/ai/recipes/inertia/onboarding.md +60 -34
  92. package/dist/ai/recipes/inertia/ownership-atlas.md +83 -37
  93. package/dist/ai/recipes/inertia/record-form.md +78 -31
  94. package/dist/ai/recipes/inertia/records-workspace.md +118 -30
  95. package/dist/ai/recipes/inertia/upload-center.md +134 -57
  96. package/dist/ai/recipes/inertia/wizard-form.md +56 -33
  97. package/dist/ai/recipes/nuxt/approvals-inbox.md +38 -21
  98. package/dist/ai/recipes/nuxt/inline-edit-workspace.md +30 -17
  99. package/dist/ai/recipes/nuxt/layout-scaffold.md +385 -0
  100. package/dist/ai/recipes/nuxt/onboarding.md +60 -34
  101. package/dist/ai/recipes/nuxt/record-form.md +76 -30
  102. package/dist/ai/recipes/nuxt/records-workspace.md +34 -19
  103. package/dist/ai/recipes/nuxt/upload-center.md +74 -35
  104. package/dist/ai/recipes/nuxt/wizard-form.md +56 -33
  105. package/dist/ai/recipes/vue/approvals-inbox.md +38 -21
  106. package/dist/ai/recipes/vue/inline-edit-workspace.md +29 -16
  107. package/dist/ai/recipes/vue/layout-scaffold.md +385 -0
  108. package/dist/ai/recipes/vue/onboarding.md +60 -34
  109. package/dist/ai/recipes/vue/record-form.md +80 -34
  110. package/dist/ai/recipes/vue/records-workspace.md +34 -19
  111. package/dist/ai/recipes/vue/upload-center.md +75 -36
  112. package/dist/ai/recipes/vue/wizard-form.md +56 -33
  113. package/dist/ai/source/BbAccordion.md +2 -1
  114. package/dist/ai/source/BbAlert.md +5 -0
  115. package/dist/ai/source/BbAvatar.md +2 -0
  116. package/dist/ai/source/BbBadge.md +1 -1
  117. package/dist/ai/source/BbBadgeButton.md +1 -1
  118. package/dist/ai/source/BbBaseCheckbox.md +3 -1
  119. package/dist/ai/source/BbBaseColorInput.md +6 -3
  120. package/dist/ai/source/BbBaseColorPalette.md +2 -2
  121. package/dist/ai/source/BbBaseDatePickerInput.md +4 -0
  122. package/dist/ai/source/BbBaseInputContainer.md +79 -17
  123. package/dist/ai/source/BbBaseNumberInput.md +2 -0
  124. package/dist/ai/source/BbBaseRadioGroup.md +35 -1
  125. package/dist/ai/source/BbBaseSelect.md +4 -1
  126. package/dist/ai/source/BbBaseTag.md +7 -1
  127. package/dist/ai/source/BbBaseTimePickerInput.md +19 -1
  128. package/dist/ai/source/BbBreadcrumbs.md +4 -0
  129. package/dist/ai/source/BbButton.md +17 -1
  130. package/dist/ai/source/BbCheckbox.md +8 -1
  131. package/dist/ai/source/BbCheckboxGroup.md +8 -1
  132. package/dist/ai/source/BbCollapsible.md +4 -0
  133. package/dist/ai/source/BbColorInput.md +8 -1
  134. package/dist/ai/source/BbColorPalette.md +11 -1
  135. package/dist/ai/source/BbConfirm.md +21 -1
  136. package/dist/ai/source/BbDatePicker.md +5 -1
  137. package/dist/ai/source/BbDatePickerInput.md +8 -1
  138. package/dist/ai/source/BbDialog.md +6 -0
  139. package/dist/ai/source/BbDropdown.md +21 -3
  140. package/dist/ai/source/BbDropdownButton.md +2 -0
  141. package/dist/ai/source/BbDropdownGroup.md +12 -2
  142. package/dist/ai/source/BbDropzone.md +120 -5
  143. package/dist/ai/source/BbIcon.md +4 -2
  144. package/dist/ai/source/BbIndicator.md +1 -1
  145. package/dist/ai/source/BbNumberInput.md +10 -1
  146. package/dist/ai/source/BbOffCanvas.md +6 -0
  147. package/dist/ai/source/BbPagination.md +6 -0
  148. package/dist/ai/source/BbPopover.md +15 -8
  149. package/dist/ai/source/BbRadio.md +10 -1
  150. package/dist/ai/source/BbRadioGroup.md +21 -2
  151. package/dist/ai/source/BbRating.md +8 -1
  152. package/dist/ai/source/BbSelect.md +8 -1
  153. package/dist/ai/source/BbSelectPopover.md +32 -7
  154. package/dist/ai/source/BbSlider.md +8 -1
  155. package/dist/ai/source/BbSmoothHeight.md +4 -0
  156. package/dist/ai/source/BbTable.md +86 -1
  157. package/dist/ai/source/BbTag.md +8 -1
  158. package/dist/ai/source/BbTextInput.md +8 -1
  159. package/dist/ai/source/BbTextarea.md +8 -1
  160. package/dist/ai/source/BbTimePicker.md +5 -1
  161. package/dist/ai/source/BbTimePickerInput.md +11 -2
  162. package/dist/ai/source/BbToast.md +14 -13
  163. package/dist/ai/source/BbTooltip.md +14 -0
  164. package/dist/ai/source/BbTree.md +4 -0
  165. package/dist/ai/source/CommonPopover.md +10 -0
  166. package/dist/alert-variants.d.ts +7 -0
  167. package/dist/assets/css/reset.css_inline.js +1 -1
  168. package/dist/badge-variants.d.ts +7 -6
  169. package/dist/button-variants.d.ts +7 -1
  170. package/dist/components/BbAccordion/types.d.ts +2 -1
  171. package/dist/components/BbAlert/types.d.ts +5 -0
  172. package/dist/components/BbAvatar/types.d.ts +2 -0
  173. package/dist/components/BbBadge/types.d.ts +1 -1
  174. package/dist/components/BbBaseButton/RouterComponent.vue_vue_type_script_setup_true_lang.js +13 -12
  175. package/dist/components/BbBaseCheckbox/types.d.ts +3 -1
  176. package/dist/components/BbBaseColorInput/types.d.ts +6 -3
  177. package/dist/components/BbBaseColorPalette/types.d.ts +2 -2
  178. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +123 -120
  179. package/dist/components/BbBaseDatePickerInput/types.d.ts +2 -0
  180. package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue_vue_type_script_setup_true_lang.js +55 -48
  181. package/dist/components/BbBaseInputContainer/types.d.ts +18 -6
  182. package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue_vue_type_script_setup_true_lang.js +4 -0
  183. package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue_vue_type_script_setup_true_lang.js +60 -55
  184. package/dist/components/BbBaseRadioGroup/types.d.ts +8 -0
  185. package/dist/components/BbBaseSelect/types.d.ts +4 -1
  186. package/dist/components/BbBaseTag/BbBaseTag.vue_vue_type_script_setup_true_lang.js +1 -1
  187. package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +117 -112
  188. package/dist/components/BbBreadcrumbs/types.d.ts +4 -0
  189. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +49 -45
  190. package/dist/components/BbCheckbox/types.d.ts +8 -1
  191. package/dist/components/BbCheckboxGroup/types.d.ts +8 -1
  192. package/dist/components/BbCollapsible/types.d.ts +4 -0
  193. package/dist/components/BbColorInput/types.d.ts +8 -1
  194. package/dist/components/BbColorPalette/BbColorPalette.vue_vue_type_script_setup_true_lang.js +44 -43
  195. package/dist/components/BbColorPalette/types.d.ts +6 -0
  196. package/dist/components/BbConfirm/BbConfirm.vue_vue_type_script_setup_true_lang.js +70 -62
  197. package/dist/components/BbDatePicker/types.d.ts +5 -1
  198. package/dist/components/BbDatePickerInput/types.d.ts +8 -1
  199. package/dist/components/BbDialog/BbDialog.vue.d.ts +6 -0
  200. package/dist/components/BbDropdown/AdaptiveDropdown.vue.d.ts +6 -0
  201. package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +221 -220
  202. package/dist/components/BbDropdown/BbDropdownList.vue.d.ts +5 -1
  203. package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +191 -185
  204. package/dist/components/BbDropdown/types.d.ts +12 -2
  205. package/dist/components/BbDropdownButton/types.d.ts +2 -0
  206. package/dist/components/BbDropzone/BbDropzone.vue_vue_type_script_setup_true_lang.js +140 -116
  207. package/dist/components/BbDropzone/types.d.ts +22 -1
  208. package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +1 -1
  209. package/dist/components/BbIndicator/types.d.ts +1 -1
  210. package/dist/components/BbNumberInput/types.d.ts +10 -1
  211. package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +6 -0
  212. package/dist/components/BbPagination/BbPagination.vue.d.ts +6 -0
  213. package/dist/components/BbPopover/BbPopover.vue.d.ts +6 -0
  214. package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +116 -117
  215. package/dist/components/BbPopover/types.d.ts +6 -0
  216. package/dist/components/BbRadio/types.d.ts +10 -1
  217. package/dist/components/BbRadioGroup/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +29 -27
  218. package/dist/components/BbRadioGroup/types.d.ts +8 -1
  219. package/dist/components/BbRating/types.d.ts +8 -1
  220. package/dist/components/BbSelect/types.d.ts +8 -1
  221. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +334 -332
  222. package/dist/components/BbSelectPopover/types.d.ts +8 -3
  223. package/dist/components/BbSlider/types.d.ts +8 -1
  224. package/dist/components/BbSmoothHeight/types.d.ts +4 -0
  225. package/dist/components/BbTable/BbTable.vue.d.ts +5 -0
  226. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +383 -372
  227. package/dist/components/BbTable/types.d.ts +37 -0
  228. package/dist/components/BbTag/types.d.ts +8 -1
  229. package/dist/components/BbTextInput/types.d.ts +8 -1
  230. package/dist/components/BbTextarea/types.d.ts +8 -1
  231. package/dist/components/BbTimePicker/types.d.ts +5 -1
  232. package/dist/components/BbTimePickerInput/types.d.ts +11 -2
  233. package/dist/components/BbToast/BbToast.vue_vue_type_script_setup_true_lang.js +23 -19
  234. package/dist/components/BbTooltip/BbTooltip.vue.d.ts +6 -0
  235. package/dist/components/BbTooltip/types.d.ts +8 -0
  236. package/dist/components/BbTree/BbTree.vue.d.ts +2 -0
  237. package/dist/components/BbTree/types.d.ts +2 -0
  238. package/dist/components/CommonPopover/CommonPopover.vue.d.ts +6 -0
  239. package/dist/components/CommonPopover/types.d.ts +4 -0
  240. package/dist/components/Transitions/ScaleFade.vue.d.ts +2 -2
  241. package/dist/components/Transitions/Slide.vue.d.ts +15 -0
  242. package/dist/components/Transitions/Slide.vue.js +1 -1
  243. package/dist/composables/useBbTabsContext.js +38 -32
  244. package/dist/composables/useClosingInert.d.ts +28 -0
  245. package/dist/composables/useClosingInert.js +23 -0
  246. package/dist/composables/useConfirm.d.ts +25 -5
  247. package/dist/composables/useConfirm.js +61 -53
  248. package/dist/composables/useSegmentedFields.d.ts +2 -1
  249. package/dist/composables/useSegmentedFields.js +82 -75
  250. package/dist/composables/useToast.d.ts +26 -0
  251. package/dist/composables/useToast.js +22 -13
  252. package/dist/confirm-variants.d.ts +7 -0
  253. package/dist/deprecation/ai-deprecations.json.d.ts +1 -1
  254. package/dist/deprecation/ai-deprecations.json.js +1 -1
  255. package/dist/directives/createPopoverDirective.js +2 -3
  256. package/dist/dropdown-item-variants.d.ts +7 -0
  257. package/dist/index.d.ts +5 -3
  258. package/dist/indicator-variants.d.ts +7 -0
  259. package/dist/llms-full.txt +4423 -1793
  260. package/dist/llms-medium.txt +206 -26
  261. package/dist/llms.txt +9 -7
  262. package/dist/locale-registry.d.ts +37 -0
  263. package/dist/reset.css +1 -1
  264. package/dist/styles.css +1 -1
  265. package/dist/toast-variants.d.ts +7 -0
  266. package/dist/tooltip-variants.d.ts +7 -0
  267. package/dist/types/CommonProps.d.ts +4 -0
  268. package/dist/validated/BbCheckbox.vue.d.ts +3 -3
  269. package/dist/validated/BbCheckbox.vue_vue_type_script_setup_true_lang.js +8 -8
  270. package/dist/validated/BbCheckboxGroup.vue.d.ts +2 -2
  271. package/dist/validated/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +10 -4
  272. package/dist/validated/BbColorInput.vue.d.ts +2 -2
  273. package/dist/validated/BbColorInput.vue_vue_type_script_setup_true_lang.js +2 -2
  274. package/dist/validated/BbDatePickerInput.vue.d.ts +3 -2
  275. package/dist/validated/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +6 -3
  276. package/dist/validated/BbDropzone.vue.d.ts +2 -2
  277. package/dist/validated/BbDropzone.vue_vue_type_script_setup_true_lang.js +2 -2
  278. package/dist/validated/BbNumberInput.vue.d.ts +2 -2
  279. package/dist/validated/BbNumberInput.vue_vue_type_script_setup_true_lang.js +2 -2
  280. package/dist/validated/BbRadioGroup.vue.d.ts +2 -2
  281. package/dist/validated/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +6 -3
  282. package/dist/validated/BbRating.vue.d.ts +2 -2
  283. package/dist/validated/BbRating.vue_vue_type_script_setup_true_lang.js +2 -2
  284. package/dist/validated/BbSelect.vue.d.ts +2 -2
  285. package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +14 -5
  286. package/dist/validated/BbSlider.vue.d.ts +2 -2
  287. package/dist/validated/BbSlider.vue_vue_type_script_setup_true_lang.js +2 -2
  288. package/dist/validated/BbSwitch.vue.d.ts +3 -3
  289. package/dist/validated/BbSwitch.vue_vue_type_script_setup_true_lang.js +8 -8
  290. package/dist/validated/BbSwitchGroup.vue.d.ts +2 -2
  291. package/dist/validated/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +10 -4
  292. package/dist/validated/BbTag.vue.d.ts +2 -2
  293. package/dist/validated/BbTag.vue_vue_type_script_setup_true_lang.js +2 -2
  294. package/dist/validated/BbTextInput.vue.d.ts +2 -2
  295. package/dist/validated/BbTextInput.vue_vue_type_script_setup_true_lang.js +2 -2
  296. package/dist/validated/BbTextarea.vue.d.ts +2 -2
  297. package/dist/validated/BbTextarea.vue_vue_type_script_setup_true_lang.js +2 -2
  298. package/dist/validated/BbTimePickerInput.vue.d.ts +3 -2
  299. package/dist/validated/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +6 -3
  300. package/dist/validated/useValidatedField.d.ts +43 -2
  301. package/dist/vite-plugin.d.ts +44 -0
  302. package/dist/vite.js +177 -104
  303. package/llms.txt +9 -7
  304. package/package.json +32 -13
  305. package/scripts/lib/eslint-plugin-recommended.node.test.mjs +87 -0
  306. package/scripts/lib/eslint-plugin-slots.node.test.mjs +271 -0
  307. package/scripts/lib/eslint-plugin.mjs +422 -5
  308. package/scripts/lib/eslint-plugin.node.test.mjs +32 -0
  309. package/scripts/lib/validate-bb-markup.mjs +69 -2
  310. package/dist/ai/guides/migration/components/bb-off-canvas.md +0 -25
package/README.md CHANGED
@@ -70,7 +70,7 @@ For most **Base\*** and **Bb\*** components, the package exports matching **prop
70
70
 
71
71
  The same knowledge base the npm package ships is served publicly over a CDN, so
72
72
  an agent can read it before anything is installed. `@alpha` tracks the latest
73
- v3 prerelease; pin an exact version (`bitboss-ui@3.0.0-alpha.1`) for stable
73
+ v3 prerelease; pin an exact version (`bitboss-ui@3.0.0-alpha.4`) for stable
74
74
  links.
75
75
 
76
76
  - [Core knowledge base](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/dist/llms-medium.txt) (~74 KB) — **start here if you can only fetch one file.** Hard rules, setup, component picker, design language, and the full component catalogue.
@@ -94,18 +94,25 @@ This package is developed and maintained by [BitBoss](https://www.bitboss.it/),
94
94
 
95
95
  ## Installation
96
96
 
97
+ **v3 is a prerelease: the `@alpha` tag is required.** A bare
98
+ `npm install bitboss-ui` resolves to the v2 line, which has no build plugin and
99
+ no `dist/ai` knowledge base — if you are following the v3 docs, that install
100
+ will not work.
101
+
97
102
  ```bash
98
- npm install bitboss-ui
103
+ npm install bitboss-ui@alpha
99
104
  ```
100
105
 
101
106
  ```bash
102
- pnpm add bitboss-ui
107
+ pnpm add bitboss-ui@alpha
103
108
  ```
104
109
 
105
110
  ```bash
106
- yarn add bitboss-ui
111
+ yarn add bitboss-ui@alpha
107
112
  ```
108
113
 
114
+ Verify with `npm ls bitboss-ui` — it must report `3.0.0-alpha.*`.
115
+
109
116
  ---
110
117
 
111
118
  ## Quick start
@@ -236,13 +243,33 @@ manifest checks as `bitboss-ui check`, so editor, CLI, and CI agree. It reuses
236
243
  your existing `vue-eslint-parser` setup (from `eslint-plugin-vue`) and no-ops on
237
244
  non-Vue files.
238
245
 
246
+ **Needs the TypeScript parser wired for `.ts` and `.vue`.** This library is
247
+ TS-first — recipes ship `<script setup lang="ts">`, some with `generic="T"` —
248
+ and without the two blocks below, `vue-eslint-parser` throws a `Parsing error`
249
+ on those files. A parsing error means **no rule runs on that file at all**,
250
+ `no-unknown-attributes` included, so the safety net silently goes dark on
251
+ exactly the files it matters most for.
252
+
253
+ ```bash
254
+ npm i -D @typescript-eslint/parser
255
+ ```
256
+
239
257
  ```js
240
258
  // eslint.config.js (flat config)
241
259
  import vue from 'eslint-plugin-vue';
260
+ import tsParser from '@typescript-eslint/parser';
242
261
  import bitbossUi from 'bitboss-ui/eslint-plugin';
243
262
 
244
263
  export default [
245
264
  ...vue.configs['flat/recommended'],
265
+ {
266
+ files: ['**/*.ts'],
267
+ languageOptions: { parser: tsParser },
268
+ },
269
+ {
270
+ files: ['**/*.vue'],
271
+ languageOptions: { parserOptions: { parser: tsParser } },
272
+ },
246
273
  ...bitbossUi.configs.recommended, // enables no-unknown-attributes on **/*.vue
247
274
  ];
248
275
  ```
@@ -81,7 +81,8 @@ function searchComponents({ query, limit, includeReferenceOnly } = {}) {
81
81
  const needle = String(query ?? '')
82
82
  .toLowerCase()
83
83
  .trim();
84
- const max = Number.isFinite(limit) && limit > 0 ? limit : SEARCH_DEFAULT_LIMIT;
84
+ const max =
85
+ Number.isFinite(limit) && limit > 0 ? limit : SEARCH_DEFAULT_LIMIT;
85
86
 
86
87
  // Consumer-first: reference-only internals are excluded unless explicitly
87
88
  // requested — recommending them is an agent-contract violation.
@@ -198,13 +199,83 @@ function getComponent({ name, maxChars } = {}) {
198
199
  /** Shipped stylesheet that carries every resolved `--bb-*` theme token. */
199
200
  const STYLES_CSS = join(PACKAGE_ROOT, 'dist', 'styles.css');
200
201
 
202
+ /**
203
+ * Selectors that carry a THEME value rather than a component-local rebind:
204
+ * the root scopes a consumer themes on. A selector list qualifies only when
205
+ * every comma-part is built solely from these — `:root`, `.light`, `:root.dark`
206
+ * and `html.dark` are theme roots; `.bb-tooltip .bb-common-popover__bubble` is
207
+ * not, however many `--bb-*` it redeclares.
208
+ */
209
+ const THEME_ROOT_PART = /^(?::root|html|body|\.light|\.dark)+$/;
210
+ function isThemeSelector(selector) {
211
+ return selector
212
+ .split(',')
213
+ .every((part) =>
214
+ THEME_ROOT_PART.test(part.replace(/[\s>+~]+/g, '').trim())
215
+ );
216
+ }
217
+
218
+ /**
219
+ * Walk a stylesheet and yield every `--bb-*` declaration WITH the selector that
220
+ * owns it. A flat regex cannot do this: it sees a declaration but not its
221
+ * scope, so a component that locally rebinds a theme token is indistinguishable
222
+ * from the dark theme redefining it. Brace-depth scanning (quote-aware, and
223
+ * tolerant of the missing final semicolon that minified CSS emits) is the
224
+ * cheapest way to keep the scope — and this file ships in the package, so it
225
+ * may not reach for postcss the way the dev-only `check:tokens` script does.
226
+ */
227
+ function* eachTokenDeclaration(css) {
228
+ const stack = [];
229
+ let buffer = '';
230
+ let quote = null;
231
+ const declaration = () => {
232
+ const m = buffer.match(/(--bb-[a-z0-9-]+)\s*:\s*([\s\S]+)/);
233
+ buffer = '';
234
+ return m ? { name: m[1], value: m[2].trim() } : null;
235
+ };
236
+ for (const char of css) {
237
+ if (quote) {
238
+ buffer += char;
239
+ if (char === quote) quote = null;
240
+ continue;
241
+ }
242
+ if (char === '"' || char === "'") {
243
+ quote = char;
244
+ buffer += char;
245
+ } else if (char === '{') {
246
+ stack.push(buffer.trim());
247
+ buffer = '';
248
+ } else if (char === '}' || char === ';') {
249
+ const decl = declaration();
250
+ if (decl) yield { ...decl, selector: stack[stack.length - 1] ?? '' };
251
+ if (char === '}') stack.pop();
252
+ } else {
253
+ buffer += char;
254
+ }
255
+ }
256
+ }
257
+
201
258
  /**
202
259
  * `list_tokens({ filter })` — the `--bb-*` design tokens with their values,
203
- * read from the shipped stylesheet (the same set `check:tokens` counts).
260
+ * read from the shipped stylesheet (the same 89 `check:tokens` counts).
261
+ *
262
+ * WHY ONLY `--bb-*` — and why there is no opt-in for the unprefixed ones, even
263
+ * though `search_components` has `includeReferenceOnly`. The two tiers are not
264
+ * the same kind of thing. `--bb-*` is a flat, global, consumer-settable
265
+ * contract. Unprefixed component tokens are *sealed to the file that consumes
266
+ * them* (`check:tokens` rule 3), so the same name means different things in
267
+ * different components and is only meaningful inside that component's scope.
268
+ * Serving them as one flat list would invite exactly the wrong move — an agent
269
+ * writing `:root { --gap: … }` and wondering why nothing changed. They are
270
+ * documented per component, which `get_component` already serves.
271
+ *
272
+ * WHY `dist/styles.css` AND NOT `src/assets/css/variables.css`: `src/` is not
273
+ * in the published package, so the source file does not exist for the consumer
274
+ * this server runs inside. The shipped sheet also carries *resolved* values.
204
275
  *
205
- * A token declared more than once is a theme override: the first value is the
206
- * light/base one, later values are dark-theme overrides, so both are reported
207
- * rather than one silently winning.
276
+ * There is no `limit`/`maxChars` here by design: the whole set is ~9 KB, and a
277
+ * half-list of tokens is the same failure as a half-list of props it makes an
278
+ * agent invent the missing ones.
208
279
  */
209
280
  function listTokens({ filter } = {}) {
210
281
  if (!existsSync(STYLES_CSS)) {
@@ -216,12 +287,18 @@ function listTokens({ filter } = {}) {
216
287
  }
217
288
  const css = readFileSync(STYLES_CSS, 'utf-8');
218
289
  const byName = new Map();
219
- for (const m of css.matchAll(/(--bb-[a-z0-9-]+)\s*:\s*([^;}]+)/gi)) {
220
- const name = m[1];
221
- const value = m[2].trim();
222
- const values = byName.get(name) ?? [];
223
- if (!values.includes(value)) values.push(value);
224
- byName.set(name, values);
290
+ for (const { name, value, selector } of eachTokenDeclaration(css)) {
291
+ const entry = byName.get(name) ?? { theme: [], local: [] };
292
+ if (isThemeSelector(selector)) {
293
+ // Light first, dark after: report both rather than let one win.
294
+ if (!entry.theme.includes(value)) entry.theme.push(value);
295
+ } else if (!entry.local.some((r) => r.selector === selector)) {
296
+ // A component rebinding a theme token for its own subtree. Reporting
297
+ // this as a `themeOverride` is a lie an agent acts on — it reads
298
+ // `--bb-arrow: 0` as "the dark theme hides arrows".
299
+ entry.local.push({ selector, value });
300
+ }
301
+ byName.set(name, entry);
225
302
  }
226
303
  const needle = String(filter ?? '')
227
304
  .toLowerCase()
@@ -229,16 +306,20 @@ function listTokens({ filter } = {}) {
229
306
  const tokens = [...byName.entries()]
230
307
  .filter(([name]) => !needle || name.toLowerCase().includes(needle))
231
308
  .sort(([a], [b]) => a.localeCompare(b))
232
- .map(([name, values]) => ({
309
+ .map(([name, { theme, local }]) => ({
233
310
  name,
234
- value: values[0],
235
- ...(values.length > 1 ? { themeOverrides: values.slice(1) } : {}),
311
+ value: theme[0],
312
+ ...(theme.length > 1 ? { themeOverrides: theme.slice(1) } : {}),
313
+ ...(local.length > 0 ? { localRebinds: local } : {}),
236
314
  }));
237
315
  return {
238
316
  count: tokens.length,
239
317
  ...(needle ? { filter: needle } : { total: byName.size }),
240
318
  tokens,
241
- hint: 'Theme tokens are set by the consumer app (e.g. on :root / .dark). Component-level unprefixed tokens are documented per component; see the design-tokens guide via get_guide.',
319
+ hint:
320
+ needle && tokens.length === 0
321
+ ? `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.`
322
+ : '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.',
242
323
  };
243
324
  }
244
325
 
@@ -248,7 +329,8 @@ function collectGuidePaths(dir, prefix = '') {
248
329
  if (!existsSync(dir)) return out;
249
330
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
250
331
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
251
- if (entry.isDirectory()) out.push(...collectGuidePaths(join(dir, entry.name), rel));
332
+ if (entry.isDirectory())
333
+ out.push(...collectGuidePaths(join(dir, entry.name), rel));
252
334
  else if (entry.name.endsWith('.md')) out.push(rel);
253
335
  }
254
336
  return out;
@@ -269,7 +351,11 @@ function getGuide({ name, maxChars } = {}) {
269
351
  const rel = String(name ?? '').replace(/^\/+/, '');
270
352
  // Contain reads to the guides dir — no traversal out of the knowledge base.
271
353
  if (!rel || rel.includes('..')) {
272
- return { found: false, error: `Invalid guide name '${name}'.` };
354
+ return {
355
+ found: false,
356
+ error: `Invalid guide name '${name}'.`,
357
+ hint: 'Call list_guides for the available names.',
358
+ };
273
359
  }
274
360
  const fileName = rel.endsWith('.md') ? rel : `${rel}.md`;
275
361
  const guidesDir = join(AI_ROOT, 'guides');
@@ -384,7 +470,7 @@ function buildServer() {
384
470
  'search_components',
385
471
  {
386
472
  description:
387
- "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.",
473
+ '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.',
388
474
  inputSchema: {
389
475
  query: z
390
476
  .string()
@@ -550,6 +636,25 @@ export async function main() {
550
636
 
551
637
  export const start = main;
552
638
 
639
+ /**
640
+ * The knowledge-base half, exported for in-process callers that want the same
641
+ * answers an MCP client gets without paying for a subprocess and a handshake —
642
+ * today, `scripts/run-eval-agent.mjs`, which hands these to a model as tools so
643
+ * the benchmark measures the knowledge base this server actually serves. Pure
644
+ * functions of the installed `dist/`: no transport, no state, safe to call
645
+ * concurrently. Importing this module does not start a server.
646
+ */
647
+ export {
648
+ searchComponents,
649
+ getComponent,
650
+ listTokens,
651
+ listGuides,
652
+ getGuide,
653
+ listRecipes,
654
+ getRecipe,
655
+ validateCode,
656
+ };
657
+
553
658
  const isDirectRun =
554
659
  process.argv[1] && import.meta.url === `file://${process.argv[1]}`;
555
660
  if (isDirectRun) {
@@ -370,33 +370,79 @@ export default ${localeObjectLiteral(template)} satisfies LocaleOverrides;
370
370
  console.log(` ${dayjsHint}`);
371
371
  }
372
372
 
373
- const CHECK_SKIP_DIRS = new Set(['node_modules', 'dist', '.git']);
373
+ // SNT-007: a bare-segment skip list of `node_modules`/`dist`/`.git` let a
374
+ // default (no-glob) `check` sweep straight through a Laravel-style consumer's
375
+ // `vendor/`, `storage/`, and `public/` trees — hundreds of vendored
376
+ // `README.md`s outnumbering the app's own `.vue`/`.md` files 20:1. These are
377
+ // all directories no project authors `Bb*` markup into, so they're safe
378
+ // blanket exclusions; framework build caches (`.nuxt`, `.output`) get the
379
+ // same treatment.
380
+ const CHECK_SKIP_DIRS = new Set([
381
+ 'node_modules',
382
+ 'dist',
383
+ '.git',
384
+ 'vendor',
385
+ 'storage',
386
+ 'public',
387
+ '.output',
388
+ '.nuxt',
389
+ ]);
390
+ // Composite (multi-segment) skips: too narrow to earn a bare-segment entry —
391
+ // skipping all of `bootstrap/` would also hide a project's own
392
+ // `bootstrap/*.md` — but `bootstrap/cache` specifically is Laravel's compiled
393
+ // config/routes cache, never hand-authored.
394
+ const CHECK_SKIP_SUBPATHS = ['bootstrap/cache'];
374
395
  const CHECK_EXTENSIONS = ['.vue', '.md'];
375
396
 
376
- /** True when any path segment is a directory `check` never descends into. */
397
+ /** True when a path segment (by itself) is a directory `check` never descends into. */
398
+ function isSkippedSegment(name) {
399
+ return CHECK_SKIP_DIRS.has(name);
400
+ }
401
+
402
+ /** True when the relative path IS, or falls under, a skipped multi-segment subpath. */
403
+ function isSkippedSubpath(relPath) {
404
+ const normalized = relPath.split(/[\\/]/).join('/');
405
+ return CHECK_SKIP_SUBPATHS.some(
406
+ (sub) => normalized === sub || normalized.startsWith(`${sub}/`)
407
+ );
408
+ }
409
+
410
+ /** True when any path segment (or composite subpath) is skipped. */
377
411
  function inSkippedDir(relPath) {
378
- return relPath.split(/[\\/]/).some((segment) => CHECK_SKIP_DIRS.has(segment));
412
+ return (
413
+ relPath.split(/[\\/]/).some((segment) => isSkippedSegment(segment)) ||
414
+ isSkippedSubpath(relPath)
415
+ );
379
416
  }
380
417
 
381
418
  /**
382
- * Every `.vue`/`.md` file under `dir`, excluding node_modules/dist/.git at any
383
- * depth. Recursive readdir rather than `globSync` because glob's `**` never
384
- * traverses dot-directories (and there is no `dot` option) the sweep must
385
- * keep matching files like `.github/copilot-instructions.md`.
419
+ * Every `.vue`/`.md` file under `dir`, excluding the skip list (see
420
+ * `CHECK_SKIP_DIRS`/`CHECK_SKIP_SUBPATHS`) at any depth. A hand-rolled
421
+ * recursive walk rather than `globSync` because glob's `**` never traverses
422
+ * dot-directories (and there is no `dot` option) — the sweep must keep
423
+ * matching files like `.github/copilot-instructions.md`. Pruning skipped
424
+ * directories BEFORE descending (rather than filtering after a flat
425
+ * `recursive: true` readdir) is what keeps a multi-thousand-file `vendor/`
426
+ * tree from being walked at all.
386
427
  */
387
- function collectCheckFiles(dir) {
428
+ function collectCheckFiles(dir, relBase = dir) {
388
429
  let entries;
389
430
  try {
390
- entries = readdirSync(dir, { recursive: true, withFileTypes: true });
431
+ entries = readdirSync(dir, { withFileTypes: true });
391
432
  } catch {
392
433
  return [];
393
434
  }
394
435
  const out = [];
395
436
  for (const entry of entries) {
437
+ const full = join(dir, entry.name);
438
+ if (entry.isDirectory()) {
439
+ if (isSkippedSegment(entry.name)) continue;
440
+ if (isSkippedSubpath(relative(relBase, full))) continue;
441
+ out.push(...collectCheckFiles(full, relBase));
442
+ continue;
443
+ }
396
444
  if (!entry.isFile()) continue;
397
445
  if (!CHECK_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) continue;
398
- const full = join(entry.parentPath, entry.name);
399
- if (inSkippedDir(relative(dir, full))) continue;
400
446
  out.push(full);
401
447
  }
402
448
  return out;
@@ -404,10 +450,16 @@ function collectCheckFiles(dir) {
404
450
 
405
451
  /**
406
452
  * Resolve the file list for `check`: explicit globs/paths if given, otherwise
407
- * every `.vue`/`.md` file under `cwd` (excluding node_modules/dist/.git).
408
- * Literal paths resolve directly (files of any extension as-is, directories
409
- * swept recursively); glob patterns (`*`, `**`, `?`, `{a,b}`) go through
410
- * `node:fs` globSync, restricted to `.vue`/`.md` outside the skipped dirs.
453
+ * every `.vue`/`.md` file under `cwd` (excluding `CHECK_SKIP_DIRS`/
454
+ * `CHECK_SKIP_SUBPATHS` node_modules, dist, .git, vendor, storage, public,
455
+ * .output, .nuxt, bootstrap/cache). Literal paths resolve directly: a literal
456
+ * FILE is always included (even one that lives inside a skipped dir — asking
457
+ * for it by name is the widen-the-scan escape hatch), a literal DIRECTORY is
458
+ * swept recursively and only its descendants are checked against the skip
459
+ * list (so `check vendor/some-pkg` still works even though a bare sweep would
460
+ * never enter `vendor/` on its own). Glob patterns (`*`, `**`, `?`, `{a,b}`)
461
+ * go through `node:fs` globSync, restricted to `.vue`/`.md` outside the
462
+ * skipped dirs.
411
463
  */
412
464
  function resolveCheckFiles(cwd, patterns) {
413
465
  if (patterns.length === 0) return collectCheckFiles(cwd).sort();
@@ -450,6 +502,36 @@ function resolveCheckFiles(cwd, patterns) {
450
502
  return [...matched].sort();
451
503
  }
452
504
 
505
+ /**
506
+ * `4 .vue + 8 .md` — a plain file count on its own ("715 file(s)") reads as
507
+ * "715 files carefully checked" when SNT-007 showed it can just as easily be
508
+ * "701 vendored READMEs + 14 real files"; splitting by extension makes the
509
+ * number honest about what was actually covered. `.vue`/`.md` sort first
510
+ * (the extensions `check` targets by default); any other extension a literal
511
+ * path argument pulled in — see `resolveCheckFiles` — sorts after, A-Z.
512
+ */
513
+ function summarizeFileCounts(files) {
514
+ const counts = new Map();
515
+ for (const file of files) {
516
+ const base = file.split(/[\\/]/).pop() ?? file;
517
+ const dot = base.lastIndexOf('.');
518
+ const ext = dot === -1 ? '(no extension)' : base.slice(dot);
519
+ counts.set(ext, (counts.get(ext) ?? 0) + 1);
520
+ }
521
+ const priority = ['.vue', '.md'];
522
+ const exts = [...counts.keys()].sort((a, b) => {
523
+ const pa = priority.indexOf(a);
524
+ const pb = priority.indexOf(b);
525
+ if (pa !== -1 || pb !== -1) {
526
+ return (
527
+ (pa === -1 ? priority.length : pa) - (pb === -1 ? priority.length : pb)
528
+ );
529
+ }
530
+ return a.localeCompare(b);
531
+ });
532
+ return exts.map((ext) => `${counts.get(ext)} ${ext}`).join(' + ');
533
+ }
534
+
453
535
  /**
454
536
  * `npx bitboss-ui check [glob…] [--json]` — validate `Bb*` markup in `.vue`
455
537
  * and `.md` files against the installed components.json manifest.
@@ -494,7 +576,7 @@ function checkCommand(globs, jsonMode) {
494
576
 
495
577
  if (findings.length === 0) {
496
578
  console.log(
497
- `[bitboss-ui] check OK — ${files.length} file(s), zero unknown Bb* props/v-models.`
579
+ `[bitboss-ui] check OK — ${summarizeFileCounts(files)} file(s), zero unknown Bb* props/v-models.`
498
580
  );
499
581
  return;
500
582
  }
@@ -543,7 +625,9 @@ Commands:
543
625
  Validate \`Bb*\` markup in .vue/.md files against the
544
626
  installed components.json manifest (unknown/removed props,
545
627
  bad v-models). Defaults to every .vue/.md file under the
546
- current directory (node_modules/dist/.git excluded).
628
+ current directory (node_modules, dist, .git, vendor,
629
+ storage, public, .output, .nuxt, bootstrap/cache
630
+ excluded — pass an explicit glob/path to widen the scan).
547
631
  Exits 1 on findings; \`--json\` prints \`{ findings: [...] }\`.
548
632
  mcp Start a stdio MCP server exposing the dist/ai knowledge
549
633
  base (search_components, get_component, list_recipes,
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `npx bitboss-ui check` integration tests.
3
+ *
4
+ * The CLI entrypoint (`bitboss-ui.mjs`) runs its command dispatch as a
5
+ * top-level side effect keyed off `process.argv` — importing it directly
6
+ * would execute the dispatcher against the TEST RUNNER's own argv. So this
7
+ * spawns the real binary as a subprocess against a scratch directory, which
8
+ * also happens to be the most faithful reproduction of SNT-007: a Laravel-
9
+ * shaped consumer tree where `check` used to report on `vendor/**` READMEs
10
+ * as if they were the project's own markup.
11
+ */
12
+ import { execFileSync } from 'node:child_process';
13
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
14
+ import { tmpdir } from 'node:os';
15
+ import { join } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { afterEach, describe, expect, it } from 'vitest';
18
+
19
+ const BIN = fileURLToPath(new URL('./bitboss-ui.mjs', import.meta.url));
20
+
21
+ let scratch;
22
+
23
+ afterEach(() => {
24
+ if (scratch) rmSync(scratch, { recursive: true, force: true });
25
+ scratch = undefined;
26
+ });
27
+
28
+ /** Build a temp Laravel-shaped consumer tree: vendored noise + real markup. */
29
+ function makeLaravelLikeFixture() {
30
+ const dir = mkdtempSync(join(tmpdir(), 'bitboss-ui-check-'));
31
+
32
+ // Noise a default sweep must now skip.
33
+ mkdirSync(join(dir, 'vendor', 'some', 'package'), { recursive: true });
34
+ writeFileSync(
35
+ join(dir, 'vendor', 'some', 'package', 'README.md'),
36
+ '# Vendored package\n'
37
+ );
38
+ mkdirSync(join(dir, 'storage', 'logs'), { recursive: true });
39
+ writeFileSync(join(dir, 'storage', 'logs', 'notes.md'), '# log notes\n');
40
+ mkdirSync(join(dir, 'public', 'build'), { recursive: true });
41
+ writeFileSync(join(dir, 'public', 'build', 'manifest.md'), '# build\n');
42
+ mkdirSync(join(dir, 'bootstrap', 'cache'), { recursive: true });
43
+ writeFileSync(
44
+ join(dir, 'bootstrap', 'cache', 'config.md'),
45
+ '# compiled config\n'
46
+ );
47
+
48
+ // A hand-authored doc that happens to live next to (not inside) the
49
+ // skipped `bootstrap/cache` dir — must still be counted.
50
+ writeFileSync(join(dir, 'bootstrap', 'README.md'), '# real bootstrap doc\n');
51
+
52
+ // Real app markup: one .vue, one .md.
53
+ mkdirSync(join(dir, 'resources', 'js', 'components'), { recursive: true });
54
+ writeFileSync(
55
+ join(dir, 'resources', 'js', 'components', 'Widget.vue'),
56
+ '<template><div>hi</div></template>\n'
57
+ );
58
+ writeFileSync(join(dir, 'GUIDE.md'), '# guide\n');
59
+
60
+ return dir;
61
+ }
62
+
63
+ function runCheck(cwd, args = []) {
64
+ try {
65
+ const stdout = execFileSync(process.execPath, [BIN, 'check', ...args], {
66
+ cwd,
67
+ encoding: 'utf-8',
68
+ });
69
+ return { status: 0, stdout };
70
+ } catch (error) {
71
+ return {
72
+ status: error.status,
73
+ stdout: error.stdout ?? '',
74
+ stderr: error.stderr ?? '',
75
+ };
76
+ }
77
+ }
78
+
79
+ describe('bitboss-ui check — default sweep skip list (SNT-007)', () => {
80
+ it('skips vendor/storage/public/bootstrap-cache and splits the summary by extension', () => {
81
+ scratch = makeLaravelLikeFixture();
82
+ const { status, stdout } = runCheck(scratch);
83
+
84
+ expect(status).toBe(0);
85
+ // Exactly the two real files: Widget.vue + (GUIDE.md + bootstrap/README.md).
86
+ expect(stdout).toMatch(
87
+ /\[bitboss-ui\] check OK — 1 \.vue \+ 2 \.md file\(s\), zero unknown Bb\* props\/v-models\./
88
+ );
89
+ });
90
+
91
+ it('still reports findings when a real file has an unknown prop', () => {
92
+ scratch = makeLaravelLikeFixture();
93
+ writeFileSync(
94
+ join(scratch, 'resources', 'js', 'components', 'Bad.vue'),
95
+ '<template><BbButton totallyBogusProp="x" /></template>\n'
96
+ );
97
+ const { status, stdout, stderr } = runCheck(scratch);
98
+
99
+ expect(status).toBe(1);
100
+ expect(stdout + stderr).toMatch(/check FAILED/);
101
+ expect(stdout + stderr).toMatch(/totallyBogusProp|totallybogusprop/i);
102
+ });
103
+
104
+ it('an explicit path still widens the scan into a normally-skipped dir', () => {
105
+ scratch = makeLaravelLikeFixture();
106
+ const { status, stdout } = runCheck(scratch, [
107
+ join('vendor', 'some', 'package', 'README.md'),
108
+ ]);
109
+
110
+ expect(status).toBe(0);
111
+ expect(stdout).toMatch(/check OK — 1 \.md file\(s\)/);
112
+ });
113
+ });
@@ -69,7 +69,7 @@ collapsed state and strips padding/borders you put on the panel itself.
69
69
  </div>
70
70
  </template>
71
71
 
72
- <p class="px-3 pb-2.5 text-sm text-gray-500">
72
+ <p class="px-3 pb-2.5 text-sm text-[color:var(--bb-text-muted)]">
73
73
  {{ faq.answer }}
74
74
  </p>
75
75
  </BbAccordion>
@@ -145,7 +145,9 @@ on the row, badge included, toggles the panel.
145
145
  </template>
146
146
 
147
147
  <div class="flex flex-col gap-1.5 px-3 pb-2.5">
148
- <p class="text-xs text-gray-500">{{ endpoint.summary }}</p>
148
+ <p class="text-xs text-[color:var(--bb-text-muted)]">
149
+ {{ endpoint.summary }}
150
+ </p>
149
151
  <dl class="flex flex-col gap-1 text-xs">
150
152
  <div
151
153
  v-for="param in endpoint.params"
@@ -153,7 +155,7 @@ on the row, badge included, toggles the panel.
153
155
  class="flex items-baseline gap-2"
154
156
  >
155
157
  <dt class="shrink-0 font-mono">{{ param.name }}</dt>
156
- <dd class="text-gray-500">
158
+ <dd class="text-[color:var(--bb-text-muted)]">
157
159
  {{ param.type }} · {{ param.description }}
158
160
  </dd>
159
161
  </div>
@@ -279,7 +281,7 @@ Both slots — `header` and the default (body) slot — receive the same scope,
279
281
  </template>
280
282
  <template #default="{ toggle }">
281
283
  <div class="flex flex-col items-start gap-1.5 px-3 pb-2.5">
282
- <p class="text-sm text-gray-500">
284
+ <p class="text-sm text-[color:var(--bb-text-muted)]">
283
285
  Dropdown keyboard navigation, smoother select popovers, and a new
284
286
  color picker.
285
287
  </p>
@@ -329,7 +331,9 @@ to `null`). Prefer a stable key over an index when the list can reorder.
329
331
  class="flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm"
330
332
  >
331
333
  <span class="font-medium">{{ plan.name }}</span>
332
- <span class="flex items-center gap-1.5 text-gray-500">
334
+ <span
335
+ class="flex items-center gap-1.5 text-[color:var(--bb-text-muted)]"
336
+ >
333
337
  {{ plan.price }}
334
338
  <span
335
339
  class="shrink-0 transition-transform duration-200"
@@ -341,7 +345,9 @@ to `null`). Prefer a stable key over an index when the list can reorder.
341
345
  </div>
342
346
  </template>
343
347
 
344
- <ul class="flex flex-col gap-1 px-3 pb-2.5 text-sm text-gray-500">
348
+ <ul
349
+ class="flex flex-col gap-1 px-3 pb-2.5 text-sm text-[color:var(--bb-text-muted)]"
350
+ >
345
351
  <li v-for="feature in plan.features" :key="feature">{{ feature }}</li>
346
352
  </ul>
347
353
  </BbAccordion>
@@ -410,7 +416,7 @@ padding on the nested body content.
410
416
  <BbAccordion v-model="advanced">
411
417
  <template #header="{ value }">
412
418
  <div
413
- class="flex w-full items-center justify-between gap-2 py-1 text-left text-sm text-gray-500"
419
+ class="flex w-full items-center justify-between gap-2 py-1 text-left text-sm text-[color:var(--bb-text-muted)]"
414
420
  >
415
421
  <span>Advanced</span>
416
422
  <span
@@ -525,7 +531,7 @@ is generated for you.
525
531
  | --- | --- | --- | --- | --- |
526
532
  | `eager` | `boolean \| undefined` | | | Prevents rendering content before it's made visible. |
527
533
  | `id` | `string \| undefined` | | | The identifier of the component. |
528
- | `modelValue` | `boolean \| undefined` | `false` | | Used by v-model to trigger opening / closing the collapsible. |
534
+ | `modelValue` | `boolean \| undefined` | `false` | | Used by v-model to trigger opening / closing the collapsible. An accordion with no `v-model` starts closed. |
529
535
  | `transitionDuration` | `number \| undefined` | `transitions.medium` | | How long the transition has to last in milliseconds |
530
536
 
531
537
  ## Events