bitboss-ui 3.0.0-beta.30 → 3.0.0-beta.32

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 (229) hide show
  1. package/README.md +29 -29
  2. package/bin/bitboss-ui-mcp.mjs +2 -1
  3. package/bin/bitboss-ui.mjs +50 -29
  4. package/dist/ai/BbBadge.md +8 -3
  5. package/dist/ai/BbBaseCheckboxIcon.md +3 -3
  6. package/dist/ai/BbBaseRadioIcon.md +3 -3
  7. package/dist/ai/BbBaseSwitchIcon.md +3 -3
  8. package/dist/ai/BbBreadcrumbs.md +4 -1
  9. package/dist/ai/BbCalendar.md +91 -18
  10. package/dist/ai/BbCheckbox.md +2 -2
  11. package/dist/ai/BbCheckboxGroup.md +20 -13
  12. package/dist/ai/BbColorInput.md +6 -6
  13. package/dist/ai/BbColorPalette.md +10 -9
  14. package/dist/ai/BbDatePicker.md +69 -30
  15. package/dist/ai/BbDatePickerInput.md +30 -22
  16. package/dist/ai/BbDialog.md +3 -3
  17. package/dist/ai/BbDropdown.md +348 -56
  18. package/dist/ai/BbDropdownButton.md +37 -18
  19. package/dist/ai/BbIcon.md +1 -1
  20. package/dist/ai/BbNumberInput.md +3 -3
  21. package/dist/ai/BbOffCanvas.md +2 -2
  22. package/dist/ai/BbPopover.md +9 -21
  23. package/dist/ai/BbRadio.md +2 -2
  24. package/dist/ai/BbRadioGroup.md +21 -22
  25. package/dist/ai/BbRating.md +3 -3
  26. package/dist/ai/BbSelect.md +19 -19
  27. package/dist/ai/BbSelectPopover.md +26 -26
  28. package/dist/ai/BbSwitch.md +2 -2
  29. package/dist/ai/BbSwitchGroup.md +20 -13
  30. package/dist/ai/BbTable.md +15 -16
  31. package/dist/ai/BbTabs.md +20 -15
  32. package/dist/ai/BbTabsList.md +2 -1
  33. package/dist/ai/BbTabsPanes.md +1 -1
  34. package/dist/ai/BbTag.md +3 -3
  35. package/dist/ai/BbTextInput.md +3 -3
  36. package/dist/ai/BbTextarea.md +3 -3
  37. package/dist/ai/BbTimePicker.md +9 -8
  38. package/dist/ai/BbTimePickerInput.md +7 -6
  39. package/dist/ai/BbTree.md +11 -1
  40. package/dist/ai/FlatListBox.md +4 -4
  41. package/dist/ai/GroupedListBox.md +4 -4
  42. package/dist/ai/ListBox.md +7 -7
  43. package/dist/ai/OptionsContainer.md +1 -1
  44. package/dist/ai/changelog.json +47 -3
  45. package/dist/ai/components.json +1317 -887
  46. package/dist/ai/guides/agent-contract.md +1 -1
  47. package/dist/ai/guides/ai-router.md +3 -5
  48. package/dist/ai/guides/component-picker.md +26 -25
  49. package/dist/ai/guides/design-language.md +1 -1
  50. package/dist/ai/guides/design-tokens.md +8 -8
  51. package/dist/ai/guides/icons-policy.md +5 -4
  52. package/dist/ai/guides/installation-and-plugin-setup.md +4 -4
  53. package/dist/ai/guides/migration/components/bb-dropdown.md +11 -10
  54. package/dist/ai/guides/migration/v2-to-v3.md +26 -26
  55. package/dist/ai/guides/options-items-playbook.md +1 -1
  56. package/dist/ai/guides/passthrough.md +130 -94
  57. package/dist/ai/source/BbBadge.md +4 -2
  58. package/dist/ai/source/BbBadgeButton.md +43 -3
  59. package/dist/ai/source/BbBaseCheckboxIcon.md +1 -1
  60. package/dist/ai/source/BbBaseRadioIcon.md +1 -1
  61. package/dist/ai/source/BbBaseSwitchIcon.md +1 -1
  62. package/dist/ai/source/BbBreadcrumbs.md +5 -0
  63. package/dist/ai/source/BbCalendar.md +39 -37
  64. package/dist/ai/source/BbCheckbox.md +1 -1
  65. package/dist/ai/source/BbCheckboxGroup.md +8 -8
  66. package/dist/ai/source/BbColorInput.md +5 -5
  67. package/dist/ai/source/BbColorPalette.md +24 -15
  68. package/dist/ai/source/BbDatePicker.md +51 -29
  69. package/dist/ai/source/BbDatePickerInput.md +26 -23
  70. package/dist/ai/source/BbDialog.md +3 -3
  71. package/dist/ai/source/BbDropdown.md +109 -28
  72. package/dist/ai/source/BbDropdownButton.md +40 -27
  73. package/dist/ai/source/BbDropdownGroup.md +93 -25
  74. package/dist/ai/source/BbForm.md +2 -2
  75. package/dist/ai/source/BbNumberInput.md +1 -1
  76. package/dist/ai/source/BbOffCanvas.md +3 -3
  77. package/dist/ai/source/BbPopover.md +12 -13
  78. package/dist/ai/source/BbRadio.md +1 -1
  79. package/dist/ai/source/BbRadioGroup.md +8 -8
  80. package/dist/ai/source/BbRating.md +6 -6
  81. package/dist/ai/source/BbSelect.md +16 -16
  82. package/dist/ai/source/BbSelectPopover.md +34 -30
  83. package/dist/ai/source/BbSwitch.md +1 -1
  84. package/dist/ai/source/BbSwitchGroup.md +6 -6
  85. package/dist/ai/source/BbTable.md +38 -11
  86. package/dist/ai/source/BbTabs.md +14 -4
  87. package/dist/ai/source/BbTabsList.md +13 -3
  88. package/dist/ai/source/BbTabsPanes.md +13 -3
  89. package/dist/ai/source/BbTabsRoot.md +13 -3
  90. package/dist/ai/source/BbTextInput.md +1 -1
  91. package/dist/ai/source/BbTextarea.md +1 -1
  92. package/dist/ai/source/BbTimePicker.md +12 -9
  93. package/dist/ai/source/BbTimePickerInput.md +10 -8
  94. package/dist/ai/source/BbTree.md +6 -0
  95. package/dist/ai/source/CommonPopover.md +1 -4
  96. package/dist/ai/source/FlatListBox.md +6 -6
  97. package/dist/ai/source/GroupedListBox.md +6 -6
  98. package/dist/ai/source/ListBox.md +1 -1
  99. package/dist/ai/source/OptionsContainer.md +5 -5
  100. package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +1 -1
  101. package/dist/components/BbBadge/BbBadgeButton.vue_vue_type_script_setup_true_lang.js +24 -8
  102. package/dist/components/BbBadge/types.d.ts +3 -1
  103. package/dist/components/BbBaseCheckboxIcon/BbBaseCheckboxIcon.vue_vue_type_script_setup_true_lang.js +2 -2
  104. package/dist/components/BbBaseCheckboxIcon/types.d.ts +1 -1
  105. package/dist/components/BbBaseRadioIcon/BbBaseRadioIcon.vue_vue_type_script_setup_true_lang.js +2 -2
  106. package/dist/components/BbBaseRadioIcon/types.d.ts +1 -1
  107. package/dist/components/BbBaseSwitchIcon/BbBaseSwitchIcon.vue_vue_type_script_setup_true_lang.js +2 -2
  108. package/dist/components/BbBaseSwitchIcon/types.d.ts +1 -1
  109. package/dist/components/BbBreadcrumbs/types.d.ts +5 -0
  110. package/dist/components/BbCalendar/BbCalendar.vue_vue_type_script_setup_true_lang.js +63 -63
  111. package/dist/components/BbCalendar/CalendarMonthPanel.vue_vue_type_script_setup_true_lang.js +4 -4
  112. package/dist/components/BbCalendar/CalendarYearPanel.vue_vue_type_script_setup_true_lang.js +4 -4
  113. package/dist/components/BbCalendar/types.d.ts +20 -16
  114. package/dist/components/BbCalendar/types.js +7 -7
  115. package/dist/components/BbCalendar/useCalendarContext.d.ts +1 -1
  116. package/dist/components/BbCalendar/useCalendarGrid.d.ts +1 -1
  117. package/dist/components/BbCheckbox/BbCheckbox.vue_vue_type_script_setup_true_lang.js +7 -7
  118. package/dist/components/BbCheckbox/types.d.ts +1 -1
  119. package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +23 -23
  120. package/dist/components/BbCheckboxGroup/types.d.ts +6 -6
  121. package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +16 -16
  122. package/dist/components/BbColorInput/types.d.ts +3 -3
  123. package/dist/components/BbColorInput/types.js +1 -1
  124. package/dist/components/BbColorPalette/BbColorPalette.vue_vue_type_script_setup_true_lang.js +101 -101
  125. package/dist/components/BbColorPalette/types.d.ts +11 -8
  126. package/dist/components/BbDatePicker/BbDatePicker.vue_vue_type_script_setup_true_lang.js +74 -71
  127. package/dist/components/BbDatePicker/types.d.ts +13 -10
  128. package/dist/components/BbDatePicker/types.js +7 -7
  129. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +148 -148
  130. package/dist/components/BbDatePickerInput/types.d.ts +9 -6
  131. package/dist/components/BbDatePickerInput/types.js +8 -8
  132. package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +79 -74
  133. package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +69 -70
  134. package/dist/components/BbDropdown/BbDropdownList.vue.d.ts +6 -0
  135. package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +283 -262
  136. package/dist/components/BbDropdown/normalizeGroups.js +1 -0
  137. package/dist/components/BbDropdown/types.d.ts +86 -19
  138. package/dist/components/BbDropdown/types.js +3 -3
  139. package/dist/components/BbDropdown/useDropdownContext.d.ts +1 -1
  140. package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +52 -53
  141. package/dist/components/BbDropdownButton/types.d.ts +19 -10
  142. package/dist/components/BbDropdownButton/types.js +6 -6
  143. package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +12 -12
  144. package/dist/components/BbNumberInput/types.d.ts +1 -1
  145. package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +169 -169
  146. package/dist/components/BbPopover/types.d.ts +6 -7
  147. package/dist/components/BbRadio/BbRadio.vue_vue_type_script_setup_true_lang.js +7 -7
  148. package/dist/components/BbRadio/types.d.ts +1 -1
  149. package/dist/components/BbRadioGroup/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +23 -23
  150. package/dist/components/BbRadioGroup/types.d.ts +6 -6
  151. package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +91 -92
  152. package/dist/components/BbRating/types.d.ts +3 -3
  153. package/dist/components/BbRating/types.js +1 -1
  154. package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +46 -46
  155. package/dist/components/BbSelect/types.d.ts +7 -7
  156. package/dist/components/BbSelect/types.js +4 -4
  157. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +274 -273
  158. package/dist/components/BbSelectPopover/types.d.ts +16 -15
  159. package/dist/components/BbSelectPopover/types.js +3 -3
  160. package/dist/components/BbSwitch/BbSwitch.vue_vue_type_script_setup_true_lang.js +7 -7
  161. package/dist/components/BbSwitch/types.d.ts +1 -1
  162. package/dist/components/BbSwitchGroup/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +23 -23
  163. package/dist/components/BbSwitchGroup/types.d.ts +4 -4
  164. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +800 -795
  165. package/dist/components/BbTable/types.d.ts +7 -1
  166. package/dist/components/BbTable/utils.d.ts +1 -1
  167. package/dist/components/BbTable/utils.js +3 -3
  168. package/dist/components/BbTabs/types.d.ts +13 -3
  169. package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +13 -13
  170. package/dist/components/BbTag/types.d.ts +1 -1
  171. package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +12 -12
  172. package/dist/components/BbTextInput/types.d.ts +1 -1
  173. package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +12 -12
  174. package/dist/components/BbTextarea/types.d.ts +1 -1
  175. package/dist/components/BbTimePicker/BbTimePicker.vue_vue_type_script_setup_true_lang.js +116 -113
  176. package/dist/components/BbTimePicker/types.d.ts +6 -5
  177. package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +235 -232
  178. package/dist/components/BbTimePickerInput/types.d.ts +3 -3
  179. package/dist/components/BbTimePickerInput/types.js +1 -1
  180. package/dist/components/BbTree/types.d.ts +6 -0
  181. package/dist/components/CommonFieldInput/CommonFieldInput.vue.d.ts +1 -1
  182. package/dist/components/CommonPopover/CommonPopover.vue_vue_type_script_setup_true_lang.js +23 -22
  183. package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +5 -5
  184. package/dist/components/FlatListBox/types.d.ts +1 -1
  185. package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +5 -5
  186. package/dist/components/GroupedListBox/types.d.ts +1 -1
  187. package/dist/components/ListBox/types.d.ts +1 -1
  188. package/dist/components/OptionsContainer/OptionsContainer.vue_vue_type_script_setup_true_lang.js +2 -2
  189. package/dist/components/OptionsContainer/types.d.ts +2 -2
  190. package/dist/composables/usePassthrough.d.ts +2 -2
  191. package/dist/composables/usePassthrough.js +7 -10
  192. package/dist/index.d.ts +1 -1
  193. package/dist/llms-full.txt +1051 -556
  194. package/dist/llms-medium.txt +35 -36
  195. package/dist/llms.txt +1 -1
  196. package/dist/styles.css +1 -1
  197. package/dist/types/BadgeVariant.d.ts +6 -4
  198. package/dist/types/passthrough.d.ts +43 -38
  199. package/dist/types/passthrough.js +8 -8
  200. package/dist/types/ptComponentMap.d.ts +23 -23
  201. package/dist/utils/passthrough.d.ts +8 -4
  202. package/dist/utils/passthrough.js +21 -17
  203. package/dist/utils/passthroughTestKit.d.ts +3 -3
  204. package/dist/validated/BbCheckbox.vue_vue_type_script_setup_true_lang.js +6 -6
  205. package/dist/validated/BbCheckboxGroup.vue.d.ts +22 -22
  206. package/dist/validated/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +22 -22
  207. package/dist/validated/BbColorInput.vue_vue_type_script_setup_true_lang.js +16 -16
  208. package/dist/validated/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +144 -144
  209. package/dist/validated/BbForm.vue_vue_type_script_setup_true_lang.js +2 -3
  210. package/dist/validated/BbNumberInput.vue_vue_type_script_setup_true_lang.js +12 -12
  211. package/dist/validated/BbRadioGroup.vue.d.ts +22 -22
  212. package/dist/validated/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +22 -22
  213. package/dist/validated/BbRating.vue_vue_type_script_setup_true_lang.js +6 -6
  214. package/dist/validated/BbSelect.vue.d.ts +43 -43
  215. package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +43 -43
  216. package/dist/validated/BbSwitch.vue_vue_type_script_setup_true_lang.js +6 -6
  217. package/dist/validated/BbSwitchGroup.vue.d.ts +22 -22
  218. package/dist/validated/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +22 -22
  219. package/dist/validated/BbTag.vue_vue_type_script_setup_true_lang.js +13 -13
  220. package/dist/validated/BbTextInput.vue_vue_type_script_setup_true_lang.js +12 -12
  221. package/dist/validated/BbTextarea.vue_vue_type_script_setup_true_lang.js +12 -12
  222. package/dist/validated/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +16 -16
  223. package/dist/vite-plugin.d.ts +3 -4
  224. package/dist/vite.js +3 -3
  225. package/llms.txt +1 -1
  226. package/package.json +1 -1
  227. package/scripts/lib/component-tree.ts +1 -1
  228. package/scripts/lib/mcp-config.mjs +49 -24
  229. package/scripts/lib/validate-bb-markup.mjs +14 -2
package/README.md CHANGED
@@ -244,7 +244,8 @@ npx bitboss-ui ai-init
244
244
  # same as: npx bitboss-ui ai-init --update
245
245
  ```
246
246
 
247
- Idempotently writes:
247
+ Idempotently writes the pointer files below **and** sets up the MCP server
248
+ (next section); `--no-mcp` skips the server:
248
249
 
249
250
  | File | Harness |
250
251
  | --------------------------------------- | ------------------------ |
@@ -256,49 +257,48 @@ Idempotently writes:
256
257
 
257
258
  Re-run after upgrading `bitboss-ui` so versioned pointers stay fresh.
258
259
 
259
- ### MCP server (optional)
260
+ ### MCP server
260
261
 
261
- The kickstart above writes static pointer files. Add `--mcp` to also register a
262
- live [Model Context Protocol](https://modelcontextprotocol.io) server so agents
263
- can **query** the knowledge base (search components, read a contract, validate
264
- markup) instead of reading files blind:
262
+ Besides the static pointer files, `ai-init` registers a live
263
+ [Model Context Protocol](https://modelcontextprotocol.io) server so agents can
264
+ **query** the knowledge base (search components, read a contract, validate
265
+ markup) instead of reading files blind. It is on by default; opt out with:
265
266
 
266
267
  ```bash
267
- npm i -D @modelcontextprotocol/sdk zod
268
- npx bitboss-ui ai-init --mcp
268
+ npx bitboss-ui ai-init --no-mcp
269
269
  ```
270
270
 
271
- Those two are **optional peer dependencies**, not dependencies: together they
272
- weigh ~12 MB — more than every runtime dependency of the component library
273
- combined — and nothing but the MCP server loads them, so installing
274
- `bitboss-ui` never drags them in. `ai-init --mcp` warns if they are missing,
275
- and `bitboss-ui mcp` tells you what to install rather than dying on a module
276
- resolution error.
271
+ The server needs `@modelcontextprotocol/sdk` and `zod`. They are **optional
272
+ peer dependencies**, not dependencies: together they weigh ~12 MB — more than
273
+ every runtime dependency of the component library combined — and nothing but
274
+ the MCP server loads them, so installing `bitboss-ui` never drags them in.
275
+ `ai-init` installs them as dev dependencies with your package manager (npm,
276
+ pnpm, yarn or bun, from your lockfile); if that fails it prints the command to
277
+ run, and `bitboss-ui mcp` tells you what to install rather than dying on a
278
+ module resolution error. `--no-mcp` skips the install too.
277
279
 
278
280
  The agent harness launches the server itself (`node node_modules/bitboss-ui/bin/bitboss-ui.mjs mcp`,
279
281
  the installed binary rather than `npx`, so it can never fetch from the registry, its optional
280
282
  peers resolve in your project, and it cannot drift behind an upgrade) on demand —
281
- you never run it by hand. `--mcp` merges the server entry into each harness's
283
+ you never run it by hand. `ai-init` merges the server entry into each harness's
282
284
  own config (never overwriting other servers you've registered), and each write
283
285
  is idempotent:
284
286
 
285
- | File | Harness | Scope |
286
- | ------------------------------------- | ----------------- | -------------------------- |
287
- | `.mcp.json` | Claude Code | project |
288
- | `.cursor/mcp.json` | Cursor | project |
289
- | `.vscode/mcp.json` | VS Code / Copilot | project |
290
- | `~/.codeium/windsurf/mcp_config.json` | Windsurf | **global (whole machine)** |
287
+ | File | Harness |
288
+ | ------------------ | ----------------- |
289
+ | `.mcp.json` | Claude Code |
290
+ | `.cursor/mcp.json` | Cursor |
291
+ | `.vscode/mcp.json` | VS Code / Copilot |
291
292
 
292
- > **Windsurf is global.** Cascade only reads one config in your home directory —
293
- > it has no per-project scope — so registering it affects **every** project on
294
- > your machine, not just this one. That is why the dev-server auto-registration
295
- > below deliberately skips Windsurf; only the explicit `ai-init --mcp` command
296
- > writes it.
293
+ Windsurf only reads a global config in your home directory, so the server is
294
+ not registered for it — add the same entry to
295
+ `~/.codeium/windsurf/mcp_config.json` yourself if you use it. Its
296
+ `.windsurf/rules` pointer is written as above.
297
297
 
298
- **Zero-command option (dev server):** instead of running `ai-init --mcp`, pass
298
+ **Zero-command option (dev server):** instead of running `ai-init`, pass
299
299
  `mcp: true` to the Vite/Nuxt plugin and the dev server registers the
300
- project-scoped harnesses (Claude Code, Cursor, VS Code) automatically on boot —
301
- idempotent, and it leaves Windsurf's global config alone:
300
+ same harnesses automatically on boot — idempotent, and it never installs
301
+ anything (run `ai-init` once for the peers):
302
302
 
303
303
  ```ts
304
304
  bitbossUi({ iconDir: './assets/icons', mcp: true });
@@ -25,7 +25,8 @@
25
25
  * is stable across zod 3 and 4, so the lower bound costs nothing.
26
26
  *
27
27
  * Run standalone: `npx bitboss-ui mcp` (delegated from bin/bitboss-ui.mjs).
28
- * 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';
@@ -11,8 +11,10 @@
11
11
  * - .github/copilot-instructions.md fragment (GitHub Copilot)
12
12
  * - .claude/skills/bitboss-ui/SKILL.md (Claude Code)
13
13
  * - .windsurf/rules/bitboss-ui.md (Windsurf)
14
- * Accepts an optional --mcp flag (combinable with --update) to also
15
- * register the `bitboss-ui mcp` server in .mcp.json.
14
+ * Also registers the `bitboss-ui mcp` server for Claude Code,
15
+ * Cursor and VS Code and installs its optional peers
16
+ * (`@modelcontextprotocol/sdk`, `zod`) with the project's package
17
+ * manager; pass --no-mcp to skip both.
16
18
  * check Validate Bb* markup in .vue/.md files against the installed
17
19
  * components.json manifest (unknown props, removed props, bad
18
20
  * v-models, unknown `<template #slot>` names, `href`/`to`/
@@ -43,6 +45,7 @@
43
45
  * Plain ESM JavaScript on purpose: shipped as-is in the package, no build.
44
46
  */
45
47
 
48
+ import { spawnSync } from 'node:child_process';
46
49
  import {
47
50
  existsSync,
48
51
  globSync,
@@ -60,6 +63,8 @@ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
60
63
  import { fileURLToPath } from 'node:url';
61
64
  import { parseArgs } from 'node:util';
62
65
  import {
66
+ detectPackageManager,
67
+ devInstallCommand,
63
68
  ensureMcpConfigs,
64
69
  missingMcpPeers,
65
70
  MCP_BIN_PATH,
@@ -209,7 +214,7 @@ function tryWrite(label, fn) {
209
214
  }
210
215
  }
211
216
 
212
- function aiInit({ mcp = false } = {}) {
217
+ function aiInit({ mcp = true } = {}) {
213
218
  const projectRoot = process.cwd();
214
219
  const version = packageVersion();
215
220
 
@@ -258,11 +263,7 @@ function aiInit({ mcp = false } = {}) {
258
263
  ];
259
264
 
260
265
  if (mcp) {
261
- // Explicit command → register every harness, including Windsurf's global
262
- // (home-dir) config.
263
- for (const result of ensureMcpConfigs(projectRoot, {
264
- includeWindsurf: true,
265
- })) {
266
+ for (const result of ensureMcpConfigs(projectRoot)) {
266
267
  const suffix = result.reason ? ` (${result.reason})` : '';
267
268
  results.push({
268
269
  path: `${result.path} [${result.label}]${suffix}`,
@@ -271,18 +272,34 @@ function aiInit({ mcp = false } = {}) {
271
272
  }
272
273
 
273
274
  // The harness launches the server on demand, long after this command
274
- // ran — so a missing peer would surface inside an agent as a server
275
- // that never starts. Say it here, where the person is still watching.
275
+ // ran — a missing peer would surface inside an agent as a server that
276
+ // never starts. They are optional peers (~12 MB only the server loads),
277
+ // so installing the library never adds them; registering the server is
278
+ // the moment the project opts in, so install them here.
276
279
  const missing = missingMcpPeers(projectRoot);
277
280
  if (missing.length > 0) {
278
- console.warn(
279
- `[bitboss-ui] the MCP server needs ${missing.join(' and ')}, which ${
280
- missing.length > 1 ? 'are' : 'is'
281
- } not installed:\n` +
282
- `\n npm i -D ${missing.join(' ')}\n\n` +
283
- ' Optional peers, not dependencies — ~12 MB that only the MCP\n' +
284
- ' server uses. The harness entries above are written either way.'
281
+ const [cmd, ...args] = devInstallCommand(
282
+ detectPackageManager(projectRoot),
283
+ missing
285
284
  );
285
+ const printed = [cmd, ...args].join(' ');
286
+ console.log(`[bitboss-ui] installing the MCP server's peers: ${printed}`);
287
+ const installed = spawnSync(cmd, args, {
288
+ cwd: projectRoot,
289
+ stdio: 'inherit',
290
+ // npm/pnpm/yarn are .cmd shims on Windows, which only a shell runs.
291
+ shell: process.platform === 'win32',
292
+ });
293
+ if (installed.status === 0) {
294
+ results.push({ path: missing.join(', '), action: 'installed' });
295
+ } else {
296
+ console.warn(
297
+ `[bitboss-ui] could not install ${missing.join(' and ')} — run it yourself:\n` +
298
+ `\n ${printed}\n\n` +
299
+ ' The harness entries are written either way; the server starts\n' +
300
+ ' once the peers are installed.'
301
+ );
302
+ }
286
303
  }
287
304
  }
288
305
 
@@ -295,11 +312,11 @@ function aiInit({ mcp = false } = {}) {
295
312
  );
296
313
  if (mcp) {
297
314
  console.log(
298
- 'MCP: registered the bitboss-ui server for Claude Code (.mcp.json), Cursor (.cursor/mcp.json), VS Code/Copilot (.vscode/mcp.json), and Windsurf (~/.codeium global) — restart your harness to pick it up.'
315
+ 'MCP: registered the bitboss-ui server for Claude Code (.mcp.json), Cursor (.cursor/mcp.json) and VS Code/Copilot (.vscode/mcp.json) — restart your harness to pick it up. `--no-mcp` skips this.'
299
316
  );
300
317
  } else {
301
318
  console.log(
302
- 'Tip: `npx bitboss-ui ai-init --mcp` also registers the MCP server across Claude Code / Cursor / VS Code / Windsurf so agents can query the knowledge base instead of reading files.'
319
+ 'MCP: skipped (--no-mcp). Re-run without it to let agents query the knowledge base through the bitboss-ui MCP server instead of reading files.'
303
320
  );
304
321
  }
305
322
  console.log('Re-run after upgrading bitboss-ui to refresh the pointers.');
@@ -904,14 +921,16 @@ Usage:
904
921
  npx bitboss-ui <command>
905
922
 
906
923
  Commands:
907
- ai-init [--update] [--mcp]
924
+ ai-init [--update] [--no-mcp]
908
925
  Inject AI-agent knowledge-base pointers into the current
909
926
  project (AGENTS.md, Cursor rules, Copilot instructions,
910
- Claude skill, Windsurf rules). Idempotent — safe to re-run
911
- after upgrades. \`--mcp\` also registers the bitboss-ui MCP
912
- server for Claude Code (.mcp.json), Cursor (.cursor/mcp.json),
913
- VS Code/Copilot (.vscode/mcp.json), and Windsurf (~/.codeium
914
- global) — merges, never overwrites other servers.
927
+ Claude skill, Windsurf rules), register the bitboss-ui MCP
928
+ server for Claude Code (.mcp.json), Cursor
929
+ (.cursor/mcp.json) and VS Code/Copilot (.vscode/mcp.json) —
930
+ merges, never overwrites other servers — and install its
931
+ optional peers with the project's package manager.
932
+ \`--no-mcp\` skips the server and the install. Idempotent —
933
+ safe to re-run after upgrades.
915
934
  check [glob…] [--json] [--allow-empty] [--allow-component <Name>]
916
935
  [--no-hints] [--manifest <path>]
917
936
  Validate \`Bb*\` markup in .vue/.md files against the
@@ -1000,9 +1019,11 @@ switch (command) {
1000
1019
  case 'ai-init': {
1001
1020
  const { values } = parseCommandArgs(command, commandArgs, {
1002
1021
  update: { type: 'boolean' },
1003
- mcp: { type: 'boolean' },
1022
+ // The MCP server is registered by default: people ran ai-init, got
1023
+ // the pointers, and never learned the server existed.
1024
+ 'no-mcp': { type: 'boolean' },
1004
1025
  });
1005
- aiInit({ mcp: values.mcp ?? false });
1026
+ aiInit({ mcp: !values['no-mcp'] });
1006
1027
  break;
1007
1028
  }
1008
1029
  case 'check': {
@@ -1059,7 +1080,7 @@ switch (command) {
1059
1080
  // from, so a cached copy can never see the project's node_modules no
1060
1081
  // matter what is installed there. Telling that user to install peers
1061
1082
  // they already have is what sent a consumer down a dead end
1062
- // (2026-09-09). `ai-init --mcp` now writes the local binary instead
1083
+ // (2026-09-09). `ai-init` now writes the local binary instead
1063
1084
  // of `npx`, so the fix is to rewrite the config.
1064
1085
  const fromNpxCache = /[/\\]_npx[/\\]/.test(
1065
1086
  fileURLToPath(import.meta.url)
@@ -1071,7 +1092,7 @@ switch (command) {
1071
1092
  'its optional peers resolve there, not in your project, so the server cannot start\n' +
1072
1093
  `even though this project has them installed (${MCP_PEERS.join(', ')}).\n\n` +
1073
1094
  'Fix the harness config to invoke the installed binary:\n\n' +
1074
- ' npx bitboss-ui ai-init --mcp\n\n' +
1095
+ ' npx bitboss-ui ai-init\n\n' +
1075
1096
  `which writes \`node ${MCP_BIN_PATH} mcp\`. To run it right now:\n\n` +
1076
1097
  ` node ${MCP_BIN_PATH} mcp\n\n` +
1077
1098
  `Resolution failed with: ${error.message}`
@@ -73,6 +73,11 @@ the soft palette from
73
73
  `.bb-badge--<name>` in your project stylesheet. Never invent `variant="success"`
74
74
  — that name is not built-in and ships no CSS.
75
75
 
76
+ `variant="none"` renders the badge with no variant class at all — the base
77
+ tokens stay until you set `--bg` / `--fg` on it. It is the same escape hatch
78
+ `BbButton` has; prefer a registered variant or a one-off `--bg` / `--fg`
79
+ override on a normal variant.
80
+
76
81
  **Invoice status pills + plan tiers**
77
82
 
78
83
  ```vue
@@ -559,7 +564,7 @@ anchor an overlay directly — no wrapper element, no `defineExpose`:
559
564
  variant="secondary"
560
565
  @click:clear="status = 'Open'"
561
566
  >
562
- <BbBadgeButton ref="statusChip">Status: {{ status }}</BbBadgeButton>
567
+ <BbBadgeButton ref="status-chip">Status: {{ status }}</BbBadgeButton>
563
568
  </BbBadge>
564
569
 
565
570
  <!-- The ref resolves to the rendered button element, so it anchors the
@@ -588,7 +593,7 @@ type Status = (typeof options)[number];
588
593
 
589
594
  const status = ref<Status>('Open');
590
595
  const open = ref(false);
591
- const statusChip = useTemplateRef('statusChip');
596
+ const statusChip = useTemplateRef('status-chip');
592
597
 
593
598
  const onPick = (option: Status) => {
594
599
  status.value = option;
@@ -847,7 +852,7 @@ The full grammar, the merge rules and the global map are in the
847
852
  | `loading` | `boolean \| undefined` | `false` | | Shows a spinner in place of the leftmost icon, mirroring `BbButton`: it replaces the `icon` glyph or the `prepend:icon` when one is set; when the `append:icon` is the sole icon it replaces that instead (e.g. a select-activator chevron while… |
848
853
  | `prepend:icon` | `string \| undefined` | | | Icon rendered before the label. |
849
854
  | `size` | `keyof Sizes \| undefined` | `"md"` | | Preset size of the badge. |
850
- | `variant` | `keyof BadgeVariantRegistry \| undefined` | `"primary"` | | Visual variant: `primary`, `secondary`, `destructive`, or `outline`. Register more via the vite plugin `badgeVariants` option and style `.bb-badge--<variant>` (set `--bg` / `--fg`). |
855
+ | `variant` | `BadgeVariantType \| undefined` | `"primary"` | | Visual variant: `primary`, `secondary`, `destructive`, or `outline`. Register more via the vite plugin `badgeVariants` option and style `.bb-badge--<variant>` (set `--bg` / `--fg`). `'none'` renders no variant class — an escape hatch for a … |
851
856
 
852
857
  ## Passthrough parts and states
853
858
 
@@ -157,7 +157,7 @@ from an ancestor — so `--size` set on a wrapper `div` is simply ignored
157
157
  ### Restyling with passthrough
158
158
 
159
159
  `pt` reaches the one part with a class list — or, in the object form, a
160
- style and attributes. Part: `root` (the drawn glyph). States: `checked`, `indeterminate`, `readonly`, `disabled`, `warnings`, `errors`, `focused`, `focus-visible` — each
160
+ style and attributes. Part: `root` (the drawn glyph). States: `checked`, `indeterminate`, `readonly`, `disabled`, `warnings`, `errors`, `focused`, `focusVisible` — each
161
161
  true exactly while the glyph paints it, so they follow its own rules:
162
162
  `warnings` is off while `errors` is on.
163
163
 
@@ -202,7 +202,7 @@ The full grammar, the merge rules and the global map are in the
202
202
 
203
203
  | Prop | Type | Default | Required | Description |
204
204
  | --- | --- | --- | --- | --- |
205
- | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `root`. States: `checked`, `disabled`, `errors`, `focus-visible`, `focused`, `indeterminate`, `readonly`, `warnings`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
205
+ | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `root`. States: `checked`, `disabled`, `errors`, `focused`, `focusVisible`, `indeterminate`, `readonly`, `warnings`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
206
206
  | `checked` | `boolean \| undefined` | `false` | | Renders the checked state (fills the box and draws the checkmark). |
207
207
  | `disabled` | `boolean \| undefined` | `false` | | Disables the glyph (muted fill, not-allowed cursor). Purely visual. |
208
208
  | `focused` | `boolean \| undefined` | `false` | | Whether the paired native input has focus — any focus, mouse included. Paints `--focused`, the hook the `focused` passthrough state aliases (T19); draws nothing by itself — the ring stays on `focusVisible`. |
@@ -231,7 +231,7 @@ States are listed in precedence order: when two are on at once and their entries
231
231
  | `warnings` | Component-wide: the toggle shows warnings and no errors. |
232
232
  | `errors` | Component-wide: the toggle shows errors — the glyph and the message line turn red. |
233
233
  | `focused` | Component-wide: the native input has focus — any focus, mouse included, like the `focused` slot prop. |
234
- | `focus-visible` | Component-wide: that focus is keyboard-visible — where a focus ring belongs. |
234
+ | `focusVisible` | Component-wide: that focus is keyboard-visible — where a focus ring belongs. |
235
235
 
236
236
  ## Events
237
237
 
@@ -171,7 +171,7 @@ from an ancestor — so `--size` set on a wrapper `div` is simply ignored
171
171
  ### Restyling with passthrough
172
172
 
173
173
  `pt` reaches the one part with a class list — or, in the object form, a
174
- style and attributes. Part: `root` (the drawn glyph). States: `checked`, `readonly`, `disabled`, `warnings`, `errors`, `focused`, `focus-visible` — each
174
+ style and attributes. Part: `root` (the drawn glyph). States: `checked`, `readonly`, `disabled`, `warnings`, `errors`, `focused`, `focusVisible` — each
175
175
  true exactly while the glyph paints it, so they follow its own rules:
176
176
  `warnings` is off while `errors` is on.
177
177
 
@@ -217,7 +217,7 @@ The full grammar, the merge rules and the global map are in the
217
217
 
218
218
  | Prop | Type | Default | Required | Description |
219
219
  | --- | --- | --- | --- | --- |
220
- | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `root`. States: `checked`, `disabled`, `errors`, `focus-visible`, `focused`, `readonly`, `warnings`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
220
+ | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `root`. States: `checked`, `disabled`, `errors`, `focused`, `focusVisible`, `readonly`, `warnings`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
221
221
  | `checked` | `boolean \| undefined` | `false` | | Renders the checked state (grows the inner dot). |
222
222
  | `disabled` | `boolean \| undefined` | `false` | | Disables the glyph (muted fill, not-allowed cursor). Purely visual. |
223
223
  | `focused` | `boolean \| undefined` | `false` | | Whether the paired native input has focus — any focus, mouse included. Paints `--focused`, the hook the `focused` passthrough state aliases (T19); draws nothing by itself — the ring stays on `focusVisible`. |
@@ -244,7 +244,7 @@ States are listed in precedence order: when two are on at once and their entries
244
244
  | `warnings` | Component-wide: the toggle shows warnings and no errors. |
245
245
  | `errors` | Component-wide: the toggle shows errors — the glyph and the message line turn red. |
246
246
  | `focused` | Component-wide: the native input has focus — any focus, mouse included, like the `focused` slot prop. |
247
- | `focus-visible` | Component-wide: that focus is keyboard-visible — where a focus ring belongs. |
247
+ | `focusVisible` | Component-wide: that focus is keyboard-visible — where a focus ring belongs. |
248
248
 
249
249
  ## Events
250
250
 
@@ -167,7 +167,7 @@ from an ancestor — so `--w` set on a wrapper `div` is simply ignored
167
167
  ### Restyling with passthrough
168
168
 
169
169
  `pt` reaches the one part with a class list — or, in the object form, a
170
- style and attributes. Part: `root` (the drawn glyph). States: `checked`, `indeterminate`, `readonly`, `disabled`, `warnings`, `errors`, `focused`, `focus-visible` — each
170
+ style and attributes. Part: `root` (the drawn glyph). States: `checked`, `indeterminate`, `readonly`, `disabled`, `warnings`, `errors`, `focused`, `focusVisible` — each
171
171
  true exactly while the glyph paints it, so they follow its own rules:
172
172
  `warnings` is off while `errors` is on.
173
173
 
@@ -213,7 +213,7 @@ The full grammar, the merge rules and the global map are in the
213
213
 
214
214
  | Prop | Type | Default | Required | Description |
215
215
  | --- | --- | --- | --- | --- |
216
- | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `root`. States: `checked`, `disabled`, `errors`, `focus-visible`, `focused`, `indeterminate`, `readonly`, `warnings`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
216
+ | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `root`. States: `checked`, `disabled`, `errors`, `focused`, `focusVisible`, `indeterminate`, `readonly`, `warnings`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
217
217
  | `checked` | `boolean \| undefined` | `false` | | Renders the checked state (fills the track and slides the thumb to the end). |
218
218
  | `disabled` | `boolean \| undefined` | `false` | | Disables the glyph (muted track, not-allowed cursor). Purely visual. |
219
219
  | `focused` | `boolean \| undefined` | `false` | | Whether the paired native input has focus — any focus, mouse included. Paints `--focused`, the hook the `focused` passthrough state aliases (T19); draws nothing by itself — the ring stays on `focusVisible`. |
@@ -242,7 +242,7 @@ States are listed in precedence order: when two are on at once and their entries
242
242
  | `warnings` | Component-wide: the toggle shows warnings and no errors. |
243
243
  | `errors` | Component-wide: the toggle shows errors — the glyph and the message line turn red. |
244
244
  | `focused` | Component-wide: the native input has focus — any focus, mouse included, like the `focused` slot prop. |
245
- | `focus-visible` | Component-wide: that focus is keyboard-visible — where a focus ring belongs. |
245
+ | `focusVisible` | Component-wide: that focus is keyboard-visible — where a focus ring belongs. |
246
246
 
247
247
  ## Events
248
248
 
@@ -707,6 +707,9 @@ States are listed in precedence order: when two are on at once and their entries
707
707
  - `item:append` — scope: `BbBreadcrumbsItemSlotProps` — Content rendered after **every** breadcrumb item's link/button. Generic fallback below a per-item `<key>:append` slot and above the item's `append:icon`.
708
708
  - `item:prepend` — scope: `BbBreadcrumbsItemSlotProps` — Content rendered before **every** breadcrumb item's link/button, in the leading edge region. Generic fallback: a per-item `<key>:prepend` slot takes precedence, and the item's `prepend:icon` is used when neither slot is present.
709
709
  - `prepend` — scope: `BbBreadcrumbsEdgeSlotProps` — Content rendered before the breadcrumb list, in a dedicated prepend region.
710
+ - `#<key>` (named from data) — scope: `BbBreadcrumbsItemSlotProps` — One crumb's label, by its `key` through `slotKey`; falls back to `item.text`. A crumb folded into the overflow menu follows BbDropdown's slot rules instead.
711
+ - `#<key>:prepend` (named from data) — scope: `BbBreadcrumbsItemSlotProps` — The glyph before one crumb's label; beats `item:prepend`, which beats its `prepend:icon`.
712
+ - `#<key>:append` (named from data) — scope: `BbBreadcrumbsItemSlotProps` — The glyph after one crumb's label; beats `item:append`, which beats its `append:icon`.
710
713
 
711
714
  ## Component tree
712
715
 
@@ -722,7 +725,7 @@ States are listed in precedence order: when two are on at once and their entries
722
725
  - `BbDropdown` _(public — [contract](./BbDropdown.md))_ — its own pt parts (`BbDropdown`): `panel` → `CommonPopover`, `root` → `CommonPopover`, `header` → `div.bb-dropdown__header`, `list` → `span.bb-dropdown__items-container`, `footer` → `div.bb-dropdown__footer`; documented CSS variables: `--menu-inset`; also mounts `CommonPopover`, `DropdownPipelineResolver`
723
726
  - `BbDropdownList` _(internal — not importable, reach it through `BbDropdown`)_ — also mounts `BbBaseButton`, `BbIcon`, `CommonPopover`, `BbDropdownList`
724
727
  - `BbBadge` _(public — [contract](./BbBadge.md))_ — its own pt parts (`BbBadge`): `spinner` → `BbSpinner`, `icon` → `BbIcon`, `clear` → `button.bb-badge__clear-button`; documented CSS variables: `--bg`, `--fg`, `--border-width`, `--ring`, `--min-size-md`, `--min-size`, `--elev`, `--r`, `--font-size`, `--icon-size`, `--gap`, `--padding-inline`, `--clear-size`, `--pad-left`; also mounts `BbIcon`, `BbSpinner`, `BadgeBodyContent`
725
- - `AdaptiveDropdown` _(internal — not importable, reach it through `BbDropdown`)_ — hands `BbOffCanvas` the pt map `{ header: 'header', footer: 'footer', sheet: 'root' }` (ours → theirs); also mounts `BbSmoothHeight`, `BbDropdownList`
728
+ - `AdaptiveDropdown` _(internal — not importable, reach it through `BbDropdown`)_ — hands `BbOffCanvas` the pt map `{ header: 'header', footer: 'footer', panel: 'root', sheet: 'root' }` (ours → theirs); also mounts `BbSmoothHeight`, `BbDropdownList`
726
729
  - `BbOffCanvas` _(public — [contract](./BbOffCanvas.md))_ — its own pt parts (`BbOffCanvas`): `header` → `div.bb-offcanvas__header`, `title` → `span.bb-offcanvas__title`, `description` → `p.bb-offcanvas__description`, `close` → `CloseButton`, `content` → `div.bb-offcanvas__body`, `footer` → `div.bb-offcanvas__footer`; documented CSS variables: `--stack-x`, `--stack-fill`, `--stack-transition-duration`, `--px`, `--handle-w`, `--handle-h`, `--handle-bg`, `--close-size`
727
730
  - `CloseButton` _(internal — not importable, reach it through `BbOffCanvas`)_ — documented CSS variables: `--size`, `--p`
728
731
  - `BbButton` _(public — [contract](./BbButton.md))_ — its own pt parts (`BbButton`): `root` → `BbBaseButton`, `spinner` → `BbSpinner`, `icon` → `BbIcon`, `text` → `span.bb-button__content`; documented CSS variables: `--h-xs`, `--h`, `--icon-size`, `--px`, `--fs`, `--r`, `--gap`, `--bg`, `--bg-hover`, `--bg-pressed`, `--fg`, `--border-color`, `--ring`, `--bw`, `--fg-hover`, `--border-hover`, `--fg-pressed`, `--border-pressed`, `--bg-disabled`, `--fg-disabled`, `--border-disabled`, `--opacity-disabled`, `--elev-tier`, `--elev`; also mounts `BbIcon`, `BbBaseButton`, `BbSpinner`
@@ -376,6 +376,77 @@ const summary = computed(() => {
376
376
  </script>
377
377
  ```
378
378
 
379
+ ### Decorating days
380
+
381
+ Four slots decorate the day grid. Each receives the day's state: `label` (the
382
+ day number), `selected`, `outside` (a day of the previous or next month),
383
+ `first` / `middle` / `last` (range edges) and `item` (the day as a dayjs
384
+ object).
385
+
386
+ | Slot | Renders |
387
+ | ---------------------- | ------------------------------------------- |
388
+ | `#day` | every day's label, **replacing** the number |
389
+ | `#append:day` | under every day (dots, prices, badges) |
390
+ | `#<YYYY_MM_DD>` | one date's label, e.g. `#2026_12_25` |
391
+ | `#append:<YYYY_MM_DD>` | under one date, e.g. `#append:2026_12_25` |
392
+
393
+ A date's slot name is its local `YYYY-MM-DD` with `_` for `-`, and it wins over
394
+ the matching every-day slot for that date. Per-date slots suit a few fixed
395
+ dates (holidays, a launch day); for data-driven marks — events from an API —
396
+ use `#append:day` and look the day up from `item`. The per-date slots cover the
397
+ day grid only, not the month and year panels. Keep decorations small: they
398
+ render in every visible cell.
399
+
400
+ **Holiday hours on specific dates**
401
+
402
+ ```vue
403
+ <template>
404
+ <div class="grid w-fit gap-3 rounded-(--bb-radius) border p-4">
405
+ <div>
406
+ <p class="m-0 text-sm font-medium">Pickup day</p>
407
+ <p class="m-0 text-xs text-(--bb-text-muted)">
408
+ The shop keeps holiday hours over Christmas.
409
+ </p>
410
+ </div>
411
+ <BbCalendar
412
+ v-model="pickup"
413
+ aria-label="Pickup day"
414
+ floating
415
+ :selectable="isOpen"
416
+ >
417
+ <!-- One slot per date: its `YYYY-MM-DD` with `_` for `-`. -->
418
+ <template #append:2026_12_24>
419
+ <span class="holiday-note">Half day</span>
420
+ </template>
421
+ <template #append:2026_12_25>
422
+ <span class="holiday-note">Closed</span>
423
+ </template>
424
+ <template #append:2026_12_26>
425
+ <span class="holiday-note">Closed</span>
426
+ </template>
427
+ </BbCalendar>
428
+ </div>
429
+ </template>
430
+
431
+ <script setup lang="ts">
432
+ import { ref } from 'vue';
433
+ import { BbCalendar } from 'bitboss-ui';
434
+
435
+ const pickup = ref<string | null>('2026-12-22');
436
+
437
+ const closed = new Set(['2026-12-25', '2026-12-26']);
438
+ const isOpen = (date: string) => !closed.has(date);
439
+ </script>
440
+
441
+ <style scoped>
442
+ .holiday-note {
443
+ color: var(--bb-text-muted);
444
+ font-size: 0.625rem;
445
+ line-height: 1;
446
+ }
447
+ </style>
448
+ ```
449
+
379
450
  ### Keyboard and accessibility
380
451
 
381
452
  The calendar is an ordinary control in the tab order: **one tab stop** lands
@@ -399,20 +470,20 @@ and attributes — optionally only while a state is on. Parts: `root` (the
399
470
  calendar's box, where a consumer `class` lands too; it carries the sizing
400
471
  tokens, so `pt:root="[--pad-x:2px]"` tightens the padding and the grid width
401
472
  follows), `header` (the navigation bar), `arrow` (the previous / next
402
- buttons), `month` and `year` (the heading buttons), `column-header` (each
403
- weekday letter), `day` (one whole day cell) and `day-button` (the button
404
- inside it), `month-item` and `year-item` (one button of the month / year
473
+ buttons), `month` and `year` (the heading buttons), `columnHeader` (each
474
+ weekday letter), `day` (one whole day cell) and `dayButton` (the button
475
+ inside it), `monthItem` and `yearItem` (one button of the month / year
405
476
  panel).
406
477
 
407
478
  States: `root` is `disabled` while the calendar is; `arrow` is `disabled` at a
408
479
  `min` / `max` bound; `month` and `year` are `active` while their panel is
409
- open. The cells resolve per node — on `day` and `day-button`: `selected`,
480
+ open. The cells resolve per node — on `day` and `dayButton`: `selected`,
410
481
  `disabled`, `today`, `outside` (a day of the previous or next month),
411
- `range-start` / `range-end` / `in-range` (a committed range) and `highlighted`
482
+ `rangeStart` / `rangeEnd` / `inRange` (a committed range) and `highlighted`
412
483
  (the keyboard cursor). The panel items carry the same set without `today` and
413
484
  `outside`.
414
485
 
415
- `day` is the whole cell and `day-button` the rounded mark inside it: a
486
+ `day` is the whole cell and `dayButton` the rounded mark inside it: a
416
487
  background on `pt:day` paints the full cell square, so restyle the mark on
417
488
  `pt:day-button`. The time rail is not a part.
418
489
 
@@ -451,7 +522,7 @@ the page is `BbDatePicker`.
451
522
 
452
523
  | Prop | Type | Default | Required | Description |
453
524
  | --- | --- | --- | --- | --- |
454
- | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `arrow`, `column-header`, `day`, `day-button`, `header`, `month`, `month-item`, `root`, `year`, `year-item`. States: `active`, `disabled`, `highlighted`, `in-range`, `outside`, `range-end`, `range-start`, `selected`, `today`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
525
+ | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `arrow`, `columnHeader`, `day`, `dayButton`, `header`, `month`, `monthItem`, `root`, `year`, `yearItem`. States: `active`, `disabled`, `highlighted`, `inRange`, `outside`, `rangeEnd`, `rangeStart`, `selected`, `today`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
455
526
  | `activeSegment` | `BbCalendarSegment \| undefined` | | | Which end the time rail edits in range + `type="datetime"` (`v-model:active-segment`). Standalone use manages this internally; an embedding host (the date input) drives it from its focused field. Ignored outside range + datetime. |
456
527
  | `ampm` | `boolean \| undefined` | `false` | | 12-hour display with an AM/PM column (requires `type="datetime"`); emits stay 24h. |
457
528
  | `disabled` | `boolean \| undefined` | `false` | | Disables every cell, the navigation and the time rail. |
@@ -481,22 +552,22 @@ the page is `BbDatePicker`.
481
552
  | `arrow` | The previous / next buttons — a broadcast. |
482
553
  | `month` | The month heading button; it opens the month panel. |
483
554
  | `year` | The year heading button; it opens the year panel. |
484
- | `column-header` | Each weekday letter above the grid — a broadcast. |
485
- | `day` | One whole day cell — a broadcast resolved per day. A background here paints the full cell square; restyle the round mark on `day-button`. |
486
- | `day-button` | The button inside a day cell — the mark that shows selection. A broadcast resolved per day. |
487
- | `month-item` | One month button of the month panel (the home grid of `type="month"`) — a broadcast resolved per month. |
488
- | `year-item` | One year button of the year list — a broadcast resolved per year. |
555
+ | `columnHeader` | Each weekday letter above the grid — a broadcast. |
556
+ | `day` | One whole day cell — a broadcast resolved per day. A background here paints the full cell square; restyle the round mark on `dayButton`. |
557
+ | `dayButton` | The button inside a day cell — the mark that shows selection. A broadcast resolved per day. |
558
+ | `monthItem` | One month button of the month panel (the home grid of `type="month"`) — a broadcast resolved per month. |
559
+ | `yearItem` | One year button of the year list — a broadcast resolved per year. |
489
560
 
490
561
  States are listed in precedence order: when two are on at once and their entries conflict, the later one wins.
491
562
 
492
563
  | State | When it is on |
493
564
  | --- | --- |
494
- | `outside` | Per node, on `day` / `day-button`: a day of the previous or next month, shown to fill the grid. |
495
- | `today` | Per node, on `day` / `day-button`: today. |
496
- | `in-range` | Per node, on the cells (`day`, `day-button`, `month-item`, `year-item`): inside a committed range, between its ends. |
565
+ | `outside` | Per node, on `day` / `dayButton`: a day of the previous or next month, shown to fill the grid. |
566
+ | `today` | Per node, on `day` / `dayButton`: today. |
567
+ | `inRange` | Per node, on the cells (`day`, `dayButton`, `monthItem`, `yearItem`): inside a committed range, between its ends. |
497
568
  | `selected` | Per node, on the cells: a chosen day, month or year. |
498
- | `range-start` | Per node, on the cells: a committed range's first day, month or year. |
499
- | `range-end` | Per node, on the cells: a committed range's last day, month or year. |
569
+ | `rangeStart` | Per node, on the cells: a committed range's first day, month or year. |
570
+ | `rangeEnd` | Per node, on the cells: a committed range's last day, month or year. |
500
571
  | `disabled` | Component-wide while the calendar is `disabled`. Also per node: on the cells, one outside `min` / `max` or refused by `selectable`; on `arrow`, at a bound or while the month / year panel is open. |
501
572
  | `active` | Per node, on `month` / `year`: its panel is open. |
502
573
  | `highlighted` | Per node, on the cells: the keyboard cursor. |
@@ -513,6 +584,8 @@ States are listed in precedence order: when two are on at once and their entries
513
584
 
514
585
  - `append:day` — scope: `BbCalendarDaySlotProps` — Appends content below each calendar day.
515
586
  - `day` — scope: `BbCalendarDaySlotProps` — Replaces the day button label inside each calendar day.
587
+ - `#<YYYY_MM_DD>` (named from data) — scope: `BbCalendarDaySlotProps` — One date's button label (e.g. `#2024_03_15`: the local `YYYY-MM-DD` through `slotKey`); beats `day`. Day grid only.
588
+ - `#append:<YYYY_MM_DD>` (named from data) — scope: `BbCalendarDaySlotProps` — Content under one date's cell; beats `append:day`.
516
589
 
517
590
  ## CSS custom properties
518
591
 
@@ -537,7 +610,7 @@ Set these on the element, or on a class you put on it, to retune this component
537
610
  - **CSS custom properties** listed on a node are declared on THAT node's root and read by elements inside it. Set one on that node's root or on the element inside it that reads it — the pt part that lands there is how you reach it (`pt:box="[--border-color:…]"` on a text input) — never on `BbCalendar`'s root, where the node's own declaration masks it (design-tokens guide, _Overriding one from a consumer app_, rule 0).
538
611
  - Listed nodes are the ones `BbCalendar`'s API reaches or that declare a documented CSS variable; the rest fold into "also mounts". The complete tree, repeats and undocumented locals included, is the `tree` field of this component in `components.json`.
539
612
 
540
- - `BbCalendar` _(this component)_ — its own template binds: `header` → `div.bb-calendar__controls`, `arrow` → `BbBaseButton`, `month` → `BbBaseButton`, `year` → `BbBaseButton`, `day` → `div.bb-calendar__date`, `day-button` → `button.bb-calendar__date-button`
613
+ - `BbCalendar` _(this component)_ — its own template binds: `header` → `div.bb-calendar__controls`, `arrow` → `BbBaseButton`, `month` → `BbBaseButton`, `year` → `BbBaseButton`, `day` → `div.bb-calendar__date`, `dayButton` → `button.bb-calendar__date-button`
541
614
  - `BbBaseButton` _(public — [contract](./BbBaseButton.md))_ — its own pt parts (`BbBaseButton`): `root` → `RouterComponent`, `root` → `a`, `root` → `component`; also mounts `RouterComponent`
542
615
  - `CommonTimeSelector` _(internal — not importable, reach it through `BbCalendar`)_ — documented CSS variables: `--time-mark`; also mounts `BbBaseButton`
543
616
  - Also mounts `ScaleFade`, `Slide`, `CalendarMonthPanel`, `CalendarYearPanel`
@@ -670,7 +670,7 @@ map are in the [passthrough guide](./guides/passthrough.md).
670
670
 
671
671
  | Prop | Type | Default | Required | Description |
672
672
  | --- | --- | --- | --- | --- |
673
- | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `description`, `hint`, `icon`, `label`, `message`, `root`. States: `checked`, `disabled`, `errors`, `focus-visible`, `focused`, `indeterminate`, `readonly`, `warnings`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
673
+ | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `description`, `hint`, `icon`, `label`, `message`, `root`. States: `checked`, `disabled`, `errors`, `focused`, `focusVisible`, `indeterminate`, `readonly`, `warnings`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. |
674
674
  | `autofocus` | `Booleanish \| undefined` | | | Sets autofocus on page load. |
675
675
  | `checked` | `boolean \| undefined` | `undefined` | | Defines the input as checked. |
676
676
  | `description` | `string \| undefined` | | | Descriptive text displayed below the label and above the input. Unlike the hint it is always visible, and it is linked to the input via `aria-describedby` (after any `errors` / `warnings`, before the `hint`). |
@@ -721,7 +721,7 @@ States are listed in precedence order: when two are on at once and their entries
721
721
  | `warnings` | Component-wide: the toggle shows warnings and no errors. |
722
722
  | `errors` | Component-wide: the toggle shows errors — the glyph and the message line turn red. |
723
723
  | `focused` | Component-wide: the native input has focus — any focus, mouse included, like the `focused` slot prop. |
724
- | `focus-visible` | Component-wide: that focus is keyboard-visible — where a focus ring belongs. |
724
+ | `focusVisible` | Component-wide: that focus is keyboard-visible — where a focus ring belongs. |
725
725
 
726
726
  ## Events
727
727