bitboss-ui 3.0.0-alpha.5 → 3.0.0-alpha.9

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 (181) hide show
  1. package/README.md +44 -14
  2. package/bin/bitboss-ui-mcp.mjs +16 -2
  3. package/bin/bitboss-ui.mjs +93 -126
  4. package/dist/ai/BbAlert.md +9 -7
  5. package/dist/ai/BbAvatar.md +14 -7
  6. package/dist/ai/BbBadgeButton.md +13 -0
  7. package/dist/ai/BbBaseButton.md +19 -3
  8. package/dist/ai/BbBaseRating.md +1 -0
  9. package/dist/ai/BbButton.md +48 -4
  10. package/dist/ai/BbDatePickerInput.md +7 -3
  11. package/dist/ai/BbDropdownButton.md +0 -1
  12. package/dist/ai/BbDropzone.md +136 -31
  13. package/dist/ai/BbNumberInput.md +5 -2
  14. package/dist/ai/BbRadio.md +7 -5
  15. package/dist/ai/BbRadioGroup.md +3 -2
  16. package/dist/ai/BbSelect.md +1 -1
  17. package/dist/ai/BbSelectPopover.md +7 -1
  18. package/dist/ai/BbSwitchGroup.md +0 -3
  19. package/dist/ai/BbTable.md +17 -19
  20. package/dist/ai/BbTabs.md +26 -1
  21. package/dist/ai/BbTextInput.md +5 -3
  22. package/dist/ai/BbTextarea.md +5 -3
  23. package/dist/ai/BbToast.md +3 -1
  24. package/dist/ai/changelog.json +284 -0
  25. package/dist/ai/components.json +360 -11
  26. package/dist/ai/composables/useBbTableContext.md +12 -12
  27. package/dist/ai/guides/agent-contract.md +32 -1
  28. package/dist/ai/guides/ai-router.md +5 -2
  29. package/dist/ai/guides/app-layout.md +43 -3
  30. package/dist/ai/guides/coherence-playbook.md +10 -10
  31. package/dist/ai/guides/component-picker.md +44 -8
  32. package/dist/ai/guides/design-tokens.md +19 -6
  33. package/dist/ai/guides/fetch-items-playbook.md +3 -1
  34. package/dist/ai/guides/inertia-helpers.md +91 -16
  35. package/dist/ai/guides/inline-edit-playbook.md +41 -0
  36. package/dist/ai/guides/installation-and-plugin-setup.md +74 -63
  37. package/dist/ai/guides/migration/components/bb-date-picker-input.md +38 -0
  38. package/dist/ai/guides/migration/components/bb-intersection.md +70 -0
  39. package/dist/ai/guides/migration/components/bb-select-popover.md +2 -0
  40. package/dist/ai/guides/migration/components/bb-select.md +26 -0
  41. package/dist/ai/guides/migration/components/bb-spinner.md +18 -0
  42. package/dist/ai/guides/migration/components/bb-text-input.md +9 -0
  43. package/dist/ai/guides/migration/components/bb-tooltip.md +32 -7
  44. package/dist/ai/guides/migration/components/use-broadcast-channel-instance.md +56 -0
  45. package/dist/ai/guides/migration/components/use-confirm.md +18 -10
  46. package/dist/ai/guides/migration/v2-to-v3.md +15 -2
  47. package/dist/ai/guides/options-items-playbook.md +1 -1
  48. package/dist/ai/guides/page-shell.md +57 -9
  49. package/dist/ai/guides/validated-forms.md +21 -5
  50. package/dist/ai/index.md +3 -2
  51. package/dist/ai/recipes/inertia/approvals-inbox.md +9 -3
  52. package/dist/ai/recipes/inertia/layout-scaffold.md +98 -2
  53. package/dist/ai/recipes/inertia/records-workspace.md +1 -1
  54. package/dist/ai/recipes/inertia/upload-center.md +1 -1
  55. package/dist/ai/recipes/nuxt/approvals-inbox.md +16 -10
  56. package/dist/ai/recipes/nuxt/layout-scaffold.md +98 -2
  57. package/dist/ai/recipes/vue/approvals-inbox.md +16 -10
  58. package/dist/ai/recipes/vue/layout-scaffold.md +98 -2
  59. package/dist/ai/source/BbAccordion.md +12 -0
  60. package/dist/ai/source/BbAlert.md +33 -3
  61. package/dist/ai/source/BbBadge.md +31 -4
  62. package/dist/ai/source/BbBadgeButton.md +3 -3
  63. package/dist/ai/source/BbBaseButton.md +132 -5
  64. package/dist/ai/source/BbBaseColorPalette.md +9 -1
  65. package/dist/ai/source/BbBaseDatePicker.md +19 -4
  66. package/dist/ai/source/BbBaseDatePickerInput.md +28 -17
  67. package/dist/ai/source/BbBaseInputContainer.md +6 -7
  68. package/dist/ai/source/BbBaseRadio.md +6 -2
  69. package/dist/ai/source/BbBaseRadioGroup.md +6 -0
  70. package/dist/ai/source/BbBaseRadioIcon.md +6 -6
  71. package/dist/ai/source/BbBaseRating.md +36 -2
  72. package/dist/ai/source/BbBaseSelect.md +48 -6
  73. package/dist/ai/source/BbBaseSlider.md +11 -2
  74. package/dist/ai/source/BbBaseSwitchIcon.md +6 -2
  75. package/dist/ai/source/BbBaseTimePickerInput.md +32 -26
  76. package/dist/ai/source/BbButton.md +83 -0
  77. package/dist/ai/source/BbCollapsible.md +1 -1
  78. package/dist/ai/source/BbDatePickerInput.md +1 -1
  79. package/dist/ai/source/BbDialog.md +6 -2
  80. package/dist/ai/source/BbDropdown.md +47 -2
  81. package/dist/ai/source/BbDropdownButton.md +1 -1
  82. package/dist/ai/source/BbDropdownGroup.md +47 -2
  83. package/dist/ai/source/BbDropzone.md +5 -4
  84. package/dist/ai/source/BbIndicator.md +15 -1
  85. package/dist/ai/source/BbOffCanvas.md +5 -3
  86. package/dist/ai/source/BbPagination.md +4 -4
  87. package/dist/ai/source/BbPopover.md +8 -0
  88. package/dist/ai/source/BbRating.md +14 -1
  89. package/dist/ai/source/BbSelectPopover.md +43 -18
  90. package/dist/ai/source/BbSmoothHeight.md +1 -1
  91. package/dist/ai/source/BbSpinner.md +26 -11
  92. package/dist/ai/source/BbTable.md +14 -43
  93. package/dist/ai/source/BbTabs.md +11 -2
  94. package/dist/ai/source/BbTabsList.md +11 -2
  95. package/dist/ai/source/BbTabsPanes.md +11 -2
  96. package/dist/ai/source/BbTabsRoot.md +11 -2
  97. package/dist/ai/source/BbTimePickerInput.md +1 -1
  98. package/dist/ai/source/BbToast.md +17 -12
  99. package/dist/ai/source/BbToastPortal.md +17 -12
  100. package/dist/ai/source/BbTooltip.md +47 -14
  101. package/dist/ai/source/BbTree.md +10 -0
  102. package/dist/ai/source/ChipsBox.md +24 -0
  103. package/dist/ai/source/CommaBox.md +16 -0
  104. package/dist/ai/source/FlatListBox.md +20 -7
  105. package/dist/ai/source/GroupedListBox.md +20 -7
  106. package/dist/assets/svgs/spinner.svg_raw.js +1 -1
  107. package/dist/badge-variants.d.ts +0 -6
  108. package/dist/button-variants.d.ts +0 -1
  109. package/dist/components/BbAlert/BbAlert.vue_vue_type_script_setup_true_lang.js +1 -1
  110. package/dist/components/BbAlert/types.d.ts +10 -1
  111. package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +56 -48
  112. package/dist/components/BbBadge/BbBadgeButton.vue_vue_type_script_setup_true_lang.js +22 -1
  113. package/dist/components/BbBaseButton/BbBaseButton.vue_vue_type_script_setup_true_lang.js +31 -1
  114. package/dist/components/BbBaseButton/types.d.ts +77 -0
  115. package/dist/components/BbBaseButton/types.js +13 -0
  116. package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue_vue_type_script_setup_true_lang.js +194 -194
  117. package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue_vue_type_script_setup_true_lang.js +2 -1
  118. package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue_vue_type_script_setup_true_lang.js +6 -3
  119. package/dist/components/BbBaseDatePicker/BbBaseDatePickerYearSelector.vue_vue_type_script_setup_true_lang.js +45 -44
  120. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +11 -11
  121. package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue_vue_type_script_setup_true_lang.js +0 -1
  122. package/dist/components/BbBaseRadio/BbBaseRadio.vue_vue_type_script_setup_true_lang.js +0 -1
  123. package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue_vue_type_script_setup_true_lang.js +2 -0
  124. package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +33 -25
  125. package/dist/components/BbBaseRating/types.d.ts +8 -0
  126. package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +64 -61
  127. package/dist/components/BbBaseSlider/BbBaseSlider.vue_vue_type_script_setup_true_lang.js +5 -1
  128. package/dist/components/BbBaseTag/BbBaseTag.vue.d.ts +1 -1
  129. package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +41 -44
  130. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +18 -1
  131. package/dist/components/BbButton/types.d.ts +77 -0
  132. package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +1 -1
  133. package/dist/components/BbDropdown/types.d.ts +33 -2
  134. package/dist/components/BbDropzone/BbDropzone.vue_vue_type_script_setup_true_lang.js +1 -2
  135. package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +55 -55
  136. package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +9 -8
  137. package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +28 -26
  138. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +106 -103
  139. package/dist/components/BbTable/BbTable.vue.d.ts +1 -1
  140. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +364 -371
  141. package/dist/components/BbTable/types.d.ts +3 -8
  142. package/dist/components/BbTabs/types.d.ts +11 -2
  143. package/dist/components/BbTooltip/BbTooltip.vue.d.ts +1 -1
  144. package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +81 -78
  145. package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +64 -61
  146. package/dist/components/CommaBox/CommaBox.vue_vue_type_script_setup_true_lang.js +62 -59
  147. package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +18 -15
  148. package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +90 -87
  149. package/dist/composables/useBbTableContext.d.ts +2 -2
  150. package/dist/composables/useBbTabsContext.js +6 -4
  151. package/dist/composables/useConfirm.js +6 -4
  152. package/dist/composables/useListboxFocus.d.ts +2 -0
  153. package/dist/composables/useListboxFocus.js +30 -19
  154. package/dist/composables/useLocale.js +2 -2
  155. package/dist/composables/useToast.js +37 -33
  156. package/dist/deprecation/ai-deprecations.json.d.ts +44 -0
  157. package/dist/deprecation/ai-deprecations.json.js +1 -1
  158. package/dist/index.d.ts +3 -3
  159. package/dist/index.js +50 -51
  160. package/dist/llms-full.txt +1403 -381
  161. package/dist/llms-medium.txt +155 -74
  162. package/dist/llms.txt +3 -2
  163. package/dist/locale-registry.d.ts +21 -0
  164. package/dist/nuxt-module.d.ts +14 -0
  165. package/dist/styles.css +1 -1
  166. package/dist/types/Config.d.ts +8 -17
  167. package/dist/validated/index.d.ts +14 -0
  168. package/dist/validated/useValidatedField.js +19 -19
  169. package/dist/vite-plugin.d.ts +30 -25
  170. package/dist/vite.js +169 -183
  171. package/llms.txt +3 -2
  172. package/package.json +24 -13
  173. package/scripts/README.md +66 -0
  174. package/scripts/lib/eslint-plugin.d.ts +65 -0
  175. package/scripts/lib/eslint-plugin.mjs +3 -3
  176. package/scripts/lib/hand-roll-hints.mjs +178 -0
  177. package/scripts/lib/mcp-config.mjs +39 -1
  178. package/scripts/lib/validate-bb-markup.mjs +224 -1
  179. package/dist/ai/composables/useBroadcastChannelInstance.md +0 -97
  180. package/dist/composables/useBroadcastChannelInstance.d.ts +0 -14
  181. package/dist/composables/useBroadcastChannelInstance.js +0 -39
package/README.md CHANGED
@@ -6,11 +6,29 @@ Vue 3 component library used across BitBoss products. It ships typed building bl
6
6
 
7
7
  ## What’s in the package
8
8
 
9
- Published artifacts are **`dist/`** (ESM JavaScript, `.d.ts`, and **`index.css`**) plus this **README**. The sections below match how the library is organised in code and in the [documentation](https://ui-components-docs.vercel.app/).
9
+ Published artifacts are **`dist/`** (ESM JavaScript, `.d.ts`, the two stylesheets **`styles.css`** and **`reset.css`**, and the `dist/ai/` knowledge base), plus the **`bin/`** CLI, **`scripts/lib/`** (ESLint plugin and markup validator), the root **`llms.txt`**, and this **README**. The sections below match how the library is organised in code and in the [documentation](https://ui-components-docs.vercel.app/).
10
10
 
11
11
  ### Styles
12
12
 
13
13
  - **Global stylesheet** — import `bitboss-ui/styles.css` once. It bundles design tokens, base rules, and styles for all exported components (built from the library’s PostCSS/Tailwind pipeline).
14
+ - **Optional reset** — `bitboss-ui/reset.css`, only if your app has no reset of its own. Import it _before_ `styles.css` (or let the build plugin inject it with `resetCss: true`).
15
+
16
+ ### TypeScript: use `vue-tsc` 3
17
+
18
+ Use **`vue-tsc` 3 or newer**. Under `vue-tsc` 2 the template type-checker
19
+ silently checks **nothing** about the props of the nine components declared with
20
+ `generic="T"` — `BbSelect`, `BbSelectPopover`, `BbTable`, `BbTabs`,
21
+ `BbTabsRoot`, `BbTree`, `BbRadioGroup`, `BbCheckboxGroup`, `BbSwitchGroup` —
22
+ because they emit as generic functions rather than `DefineComponent<…>`, a shape
23
+ Vue Language Tools 2 cannot read props out of.
24
+
25
+ The trap is that the failure is partial and invisible: unknown-**component**
26
+ checking still fires and non-generic components still reject bad props, so the
27
+ gate looks like it is working. A real consumer shipped
28
+ `<BbRadioGroup label="…">` (the prop is `legend`) through six release slices on
29
+ `vue-tsc@^2.2.12` with a green `--noEmit`.
30
+
31
+ Full detail: `ai/guides/installation-and-plugin-setup.md`.
14
32
 
15
33
  ### TypeScript: icons
16
34
 
@@ -71,13 +89,14 @@ For most **Base\*** and **Bb\*** components, the package exports matching **prop
71
89
 
72
90
  The same knowledge base the npm package ships is served publicly over a CDN, so
73
91
  an agent can read it before anything is installed. `@alpha` tracks the latest
74
- v3 prerelease; pin an exact version (`bitboss-ui@3.0.0-alpha.4`) for stable
75
- links.
92
+ v3 prerelease; pin an exact version (`bitboss-ui@3.0.0-alpha.5`) for stable
93
+ links. Sizes grow with the catalogue — treat them as the current order of
94
+ magnitude, not a contract.
76
95
 
77
- - [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.
78
- - [Index](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/llms.txt) (~17 KB) — link index into every document.
96
+ - [Core knowledge base](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/dist/llms-medium.txt) (~110 KB) — **start here if you can only fetch one file.** Hard rules, setup, component picker, design language, and the full component catalogue.
97
+ - [Index](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/llms.txt) (~19 KB) — link index into every document.
79
98
  - [components.json](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/dist/ai/components.json) — machine-readable API surface, for programmatic validation.
80
- - [Complete knowledge base](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/dist/llms-full.txt) (~2.9 MB) — everything concatenated. Bulk ingestion only; too large to prompt with.
99
+ - [Complete knowledge base](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/dist/llms-full.txt) (~3.2 MB) — everything concatenated. Bulk ingestion only; too large to prompt with.
81
100
 
82
101
  Already installed? Prefer the local copy under `node_modules/bitboss-ui/dist/ai/`,
83
102
  run `npx bitboss-ui ai-init` to write agent pointers, and
@@ -204,9 +223,17 @@ can **query** the knowledge base (search components, read a contract, validate
204
223
  markup) instead of reading files blind:
205
224
 
206
225
  ```bash
226
+ npm i -D @modelcontextprotocol/sdk zod
207
227
  npx bitboss-ui ai-init --mcp
208
228
  ```
209
229
 
230
+ Those two are **optional peer dependencies**, not dependencies: together they
231
+ weigh ~12 MB — more than every runtime dependency of the component library
232
+ combined — and nothing but the MCP server loads them, so installing
233
+ `bitboss-ui` never drags them in. `ai-init --mcp` warns if they are missing,
234
+ and `bitboss-ui mcp` tells you what to install rather than dying on a module
235
+ resolution error.
236
+
210
237
  The agent harness launches the server itself (`npx bitboss-ui@<installed version> mcp`,
211
238
  pinned so npx can never fall back to fetching a different version from the registry) on demand —
212
239
  you never run it by hand. `--mcp` merges the server entry into each harness's
@@ -333,13 +360,16 @@ A standard `llms.txt` discovery file is also published at:
333
360
 
334
361
  ### Package exports
335
362
 
336
- ```ts
337
- // catalogue entry
338
- import 'bitboss-ui/ai';
339
- // or resolve concrete files:
340
- // bitboss-ui/ai/components.json
341
- // bitboss-ui/ai/guides/ai-router.md
342
- ```
363
+ These are **resolution paths, not importable modules** — they resolve to
364
+ Markdown and JSON, so a side-effect `import` of them neither type-checks nor
365
+ bundles. Use them with `import.meta.resolve` / `require.resolve`, or read the
366
+ files directly:
367
+
368
+ | Subpath | Resolves to |
369
+ | ----------------------------------- | ------------------------------ |
370
+ | `bitboss-ui/ai` | `dist/ai/index.md` (catalogue) |
371
+ | `bitboss-ui/ai/components.json` | `dist/ai/components.json` |
372
+ | `bitboss-ui/ai/guides/ai-router.md` | `dist/ai/guides/ai-router.md` |
343
373
 
344
374
  Agents can also read paths under `node_modules/bitboss-ui/dist/ai/` directly.
345
375
 
@@ -376,7 +406,7 @@ MIT — see [`LICENSE`](LICENSE).
376
406
 
377
407
  The source repository is private while v3 is in development, so there is no
378
408
  public issue tracker yet. **Report bugs by email to
379
- [dev@bitboss.it](mailto:dev@bitboss.it)** — that address is the `bugs` contact
409
+ [hey@bitboss.it](mailto:hey@bitboss.it)** — that address is the `bugs` contact
380
410
  in `package.json`, so `npm bugs bitboss-ui` opens it too.
381
411
 
382
412
  To make a report actionable, include:
@@ -7,8 +7,22 @@
7
7
  *
8
8
  * Built on `@modelcontextprotocol/sdk` (McpServer over the stdio transport);
9
9
  * the SDK owns JSON-RPC framing, capability handshake, and protocol-version
10
- * negotiation. Tool input schemas are declared as zod shapes (zod ships with
11
- * the SDK); the SDK renders them to the JSON Schema advertised by tools/list.
10
+ * negotiation. Tool input schemas are declared as zod shapes; the SDK renders
11
+ * them to the JSON Schema advertised by tools/list.
12
+ *
13
+ * BOTH imports below are OPTIONAL PEERS, not dependencies — together ~12 MB,
14
+ * more than every runtime dependency of the component library combined, and
15
+ * loaded by nothing else. `bin/bitboss-ui.mjs` catches the resolution failure
16
+ * and names them; `missingMcpPeers()` warns at registration time.
17
+ *
18
+ * `zod`'s declared range is `^3.25 || ^4.0` — copied verbatim from the SDK's
19
+ * own dependency range, NOT invented. Do not "simplify" it to `>=` or to a
20
+ * single major: the schemas built here are validated by the SDK's OWN copy of
21
+ * zod, so a consumer hoisting a different MAJOR fails the SDK's brand checks
22
+ * while npm stays silent (both ranges are independently satisfied). Mirroring
23
+ * the range means it moves when the SDK's does. Everything used here
24
+ * (`z.string`, `z.enum`, `z.number`, `.describe`, `.int`, `.min`, `.optional`)
25
+ * is stable across zod 3 and 4, so the lower bound costs nothing.
12
26
  *
13
27
  * Run standalone: `npx bitboss-ui mcp` (delegated from bin/bitboss-ui.mjs).
14
28
  * Register with an MCP-capable harness via `npx bitboss-ui ai-init --mcp`.
@@ -13,7 +13,6 @@
13
13
  * - .windsurf/rules/bitboss-ui.md (Windsurf)
14
14
  * Accepts an optional --mcp flag (combinable with --update) to also
15
15
  * register the `bitboss-ui mcp` server in .mcp.json.
16
- * locale Generate a custom locale blueprint (.locale.ts)
17
16
  * check Validate Bb* markup in .vue/.md files against the installed
18
17
  * components.json manifest (unknown props, removed props, bad
19
18
  * v-models, unknown `<template #slot>` names, `href`/`to`/
@@ -35,11 +34,14 @@ import {
35
34
  statSync,
36
35
  writeFileSync,
37
36
  } from 'node:fs';
38
- import { createRequire } from 'node:module';
37
+ import { findHandRollHints } from '../scripts/lib/hand-roll-hints.mjs';
39
38
  import { dirname, isAbsolute, join, relative } from 'node:path';
40
39
  import { fileURLToPath } from 'node:url';
41
40
  import { parseArgs } from 'node:util';
42
- import { ensureMcpConfigs } from '../scripts/lib/mcp-config.mjs';
41
+ import {
42
+ ensureMcpConfigs,
43
+ missingMcpPeers,
44
+ } from '../scripts/lib/mcp-config.mjs';
43
45
  import {
44
46
  loadManifest,
45
47
  validateMarkdown,
@@ -238,6 +240,21 @@ function aiInit({ mcp = false } = {}) {
238
240
  action: result.action,
239
241
  });
240
242
  }
243
+
244
+ // The harness launches the server on demand, long after this command
245
+ // ran — so a missing peer would surface inside an agent as a server
246
+ // that never starts. Say it here, where the person is still watching.
247
+ const missing = missingMcpPeers(projectRoot);
248
+ if (missing.length > 0) {
249
+ console.warn(
250
+ `[bitboss-ui] the MCP server needs ${missing.join(' and ')}, which ${
251
+ missing.length > 1 ? 'are' : 'is'
252
+ } not installed:\n` +
253
+ `\n npm i -D ${missing.join(' ')}\n\n` +
254
+ ' Optional peers, not dependencies — ~12 MB that only the MCP\n' +
255
+ ' server uses. The harness entries above are written either way.'
256
+ );
257
+ }
241
258
  }
242
259
 
243
260
  console.log(`[bitboss-ui] ai-init complete (library v${version})`);
@@ -266,112 +283,6 @@ function aiInit({ mcp = false } = {}) {
266
283
  }
267
284
  }
268
285
 
269
- /**
270
- * Serialize the blueprint dictionary as a readable TS object literal
271
- * (single-quoted, tab-indented, matching the library's own locale files).
272
- */
273
- function localeObjectLiteral(dictionary) {
274
- const quote = (s) =>
275
- `'${String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
276
- const sections = Object.entries(dictionary)
277
- .map(([section, entries]) => {
278
- const lines = Object.entries(entries)
279
- .map(([key, value]) => `\t\t${key}: ${quote(value)},`)
280
- .join('\n');
281
- return `\t${section}: {\n${lines}\n\t},`;
282
- })
283
- .join('\n');
284
- return `{\n${sections}\n}`;
285
- }
286
-
287
- function localeBlueprint(key) {
288
- const projectRoot = process.cwd();
289
-
290
- if (!key) {
291
- console.error(
292
- '[bitboss-ui] Usage: npx bitboss-ui locale <key> (e.g. `npx bitboss-ui locale nl`)'
293
- );
294
- process.exit(1);
295
- }
296
- // Keys double as dayjs module names (`dayjs/locale/<key>`) — keep them tame.
297
- if (!/^[a-z0-9-]+$/.test(key)) {
298
- console.error(
299
- `[bitboss-ui] Invalid locale key '${key}' — use lowercase letters, digits and dashes (it doubles as the dayjs locale name, e.g. 'nl', 'ja', 'zh-cn').`
300
- );
301
- process.exit(1);
302
- }
303
-
304
- const blueprintPath = join(PACKAGE_ROOT, 'dist', 'locale-blueprint.json');
305
- if (!existsSync(blueprintPath)) {
306
- console.error(
307
- '[bitboss-ui] dist/locale-blueprint.json not found in the installed package — reinstall bitboss-ui (or run `npm run build` when working in the library repo).'
308
- );
309
- process.exit(1);
310
- }
311
- const { builtins, template } = JSON.parse(
312
- readFileSync(blueprintPath, 'utf-8')
313
- );
314
-
315
- if (builtins.includes(key)) {
316
- console.error(
317
- `[bitboss-ui] '${key}' is a built-in locale — built-ins cannot be overridden; the \`additionalLocales\` option only adds new ones. Just set \`locale: '${key}'\`.`
318
- );
319
- process.exit(1);
320
- }
321
-
322
- const outPath = join(projectRoot, `${key}.locale.ts`);
323
- if (existsSync(outPath)) {
324
- console.error(
325
- `[bitboss-ui] ${key}.locale.ts already exists — delete it first if you want a fresh blueprint.`
326
- );
327
- process.exit(1);
328
- }
329
-
330
- // Same check the vite plugin performs at build time — surface it here too,
331
- // while the consumer is still choosing the key.
332
- let dayjsHint = `Calendar (dayjs) locale '${key}' found — calendars will follow automatically.`;
333
- try {
334
- // Consumer's own resolution first; fall back to the installed package's
335
- // (dayjs is a bitboss-ui dependency, so it's always reachable from here).
336
- try {
337
- createRequire(join(projectRoot, 'package.json')).resolve(
338
- `dayjs/locale/${key}.js`
339
- );
340
- } catch {
341
- createRequire(join(PACKAGE_ROOT, 'package.json')).resolve(
342
- `dayjs/locale/${key}.js`
343
- );
344
- }
345
- } catch {
346
- dayjsHint = `⚠ dayjs ships no locale named '${key}' — calendars will fall back to English (the vite plugin repeats this warning at build time). If a nearby dayjs name exists (e.g. 'zh-cn', 'pt-br'), prefer it as your key: https://github.com/iamkun/dayjs/tree/dev/src/locale`;
347
- }
348
-
349
- writeFileSync(
350
- outPath,
351
- `import type { LocaleOverrides } from 'bitboss-ui';
352
-
353
- /**
354
- * bitboss-ui locale blueprint for '${key}' — generated by \`npx bitboss-ui locale ${key}\`.
355
- *
356
- * Translate the English strings below ({0}/{1} placeholders are interpolated
357
- * arguments — keep them). Keys you delete fall back per-key to the library's
358
- * default locale. Register it in your vite config:
359
- *
360
- * import ${key.replace(/-/g, '_')} from './${key}.locale';
361
- * bitbossUi({ iconDir: '...', locale: '${key}', additionalLocales: { '${key}': ${key.replace(/-/g, '_')} } })
362
- *
363
- * The plugin regenerates \`locale-registry.d.ts\` on build start, making
364
- * '${key}' a valid \`locale\` value everywhere (run dev once after adding it).
365
- */
366
- export default ${localeObjectLiteral(template)} satisfies LocaleOverrides;
367
- `,
368
- 'utf-8'
369
- );
370
-
371
- console.log(`[bitboss-ui] Wrote ${key}.locale.ts (English blueprint).`);
372
- console.log(` ${dayjsHint}`);
373
- }
374
-
375
286
  // SNT-007: a bare-segment skip list of `node_modules`/`dist`/`.git` let a
376
287
  // default (no-glob) `check` sweep straight through a Laravel-style consumer's
377
288
  // `vendor/`, `storage/`, and `public/` trees — hundreds of vendored
@@ -550,9 +461,34 @@ function checkCommand(globs, jsonMode, allowEmpty = false) {
550
461
  process.exit(1);
551
462
  }
552
463
  const manifest = loadManifest(manifestPath);
464
+
465
+ /*
466
+ * Every `--bb-*` the INSTALLED package declares, read from the stylesheet
467
+ * the consumer actually ships. Read from disk rather than hardcoded so this
468
+ * can never claim a token is invented when their version does declare it.
469
+ * Absent stylesheet → an empty set → the unknown-token hint stays silent,
470
+ * which is the right failure: no data, no accusation.
471
+ */
472
+ const stylesPath = join(PACKAGE_ROOT, 'dist', 'styles.css');
473
+ const knownTokens = existsSync(stylesPath)
474
+ ? new Set(
475
+ [
476
+ ...readFileSync(stylesPath, 'utf-8').matchAll(
477
+ /(--bb-[a-z0-9-]+)\s*:/gi
478
+ ),
479
+ ].map((m) => m[1].toLowerCase())
480
+ )
481
+ : new Set();
553
482
  const files = resolveCheckFiles(projectRoot, globs);
554
483
 
555
484
  const findings = [];
485
+ /*
486
+ * Advisory only — see scripts/lib/hand-roll-hints.mjs. Hints never touch the
487
+ * exit code, so a guess can never fail a consumer's build. `--no-hints`
488
+ * silences them for anyone who finds them noisy.
489
+ */
490
+ const hints = [];
491
+ const wantHints = !process.argv.includes('--no-hints');
556
492
  for (const file of files) {
557
493
  const relPath = relative(projectRoot, file);
558
494
  const content = readFileSync(file, 'utf-8');
@@ -562,10 +498,34 @@ function checkCommand(globs, jsonMode, allowEmpty = false) {
562
498
  for (const finding of fileFindings) {
563
499
  findings.push({ file: relPath, ...finding });
564
500
  }
501
+ // .md files are documentation ABOUT components; a round <img> in a doc
502
+ // example is illustrative, not a hand-roll.
503
+ if (wantHints && !file.endsWith('.md')) {
504
+ for (const hint of findHandRollHints(content, knownTokens)) {
505
+ hints.push({ file: relPath, ...hint });
506
+ }
507
+ }
565
508
  }
566
509
 
510
+ /** Advisory block, printed on success and on failure alike. Never fatal. */
511
+ const printHints = () => {
512
+ if (hints.length === 0) return;
513
+ console.log(
514
+ `\n[bitboss-ui] ${hints.length} hint(s) — advisory, not errors:`
515
+ );
516
+ for (const hint of hints) {
517
+ console.log(` ${hint.file}:${hint.line}`);
518
+ console.log(` → ${hint.message}`);
519
+ }
520
+ console.log(
521
+ ' Deliberate? Ignore these, record them in a HANDROLLED note, or pass --no-hints.'
522
+ );
523
+ };
524
+
567
525
  if (jsonMode) {
568
- console.log(JSON.stringify({ findings, files: files.length }, null, 2));
526
+ console.log(
527
+ JSON.stringify({ findings, hints, files: files.length }, null, 2)
528
+ );
569
529
  if (findings.length > 0) process.exit(1);
570
530
  if (files.length === 0 && globs.length > 0 && !allowEmpty) process.exit(1);
571
531
  return;
@@ -595,6 +555,7 @@ function checkCommand(globs, jsonMode, allowEmpty = false) {
595
555
  console.log(
596
556
  `[bitboss-ui] check OK — ${summarizeFileCounts(files)} file(s), zero unknown Bb* props/v-models/slots/nav-attrs.`
597
557
  );
558
+ printHints();
598
559
  return;
599
560
  }
600
561
 
@@ -620,6 +581,7 @@ function checkCommand(globs, jsonMode, allowEmpty = false) {
620
581
  console.error(
621
582
  '\nFix the markup (or the component API) so it stays copy-safe for agents.'
622
583
  );
584
+ printHints();
623
585
  process.exit(1);
624
586
  }
625
587
 
@@ -654,9 +616,6 @@ Commands:
654
616
  mcp Start a stdio MCP server exposing the dist/ai knowledge
655
617
  base (search_components, get_component, list_recipes,
656
618
  get_recipe, validate) to MCP-capable agent harnesses.
657
- locale <key> Generate <key>.locale.ts at the project root — an English
658
- blueprint dictionary to translate and pass to the plugin's
659
- \`additionalLocales\` option (additions only; built-ins are refused).
660
619
  help Show this help.
661
620
  `);
662
621
  }
@@ -710,22 +669,30 @@ switch (command) {
710
669
  break;
711
670
  }
712
671
  case 'mcp': {
713
- const { main } = await import('./bitboss-ui-mcp.mjs');
672
+ // `@modelcontextprotocol/sdk` + `zod` are OPTIONAL peers, not
673
+ // dependencies: together they weigh ~12 MB — more than every runtime
674
+ // dependency of the component library combined, and the SDK drags in a
675
+ // whole HTTP stack (express, hono, cors, jose). Only this subcommand
676
+ // needs them, so consumers who never run the MCP server do not pay for
677
+ // it. That makes the import failure a SUPPORTED path, and a bare
678
+ // ERR_MODULE_NOT_FOUND stack is not an answer — say what to install.
679
+ let main;
680
+ try {
681
+ ({ main } = await import('./bitboss-ui-mcp.mjs'));
682
+ } catch (error) {
683
+ if (error?.code !== 'ERR_MODULE_NOT_FOUND') throw error;
684
+ console.error(
685
+ 'bitboss-ui mcp needs two optional peer dependencies that are not installed:\n' +
686
+ '\n npm i -D @modelcontextprotocol/sdk zod\n\n' +
687
+ 'They are optional because they are ~12 MB and only this subcommand uses them.\n' +
688
+ `Resolution failed with: ${error.message}`
689
+ );
690
+ process.exitCode = 1;
691
+ break;
692
+ }
714
693
  await main();
715
694
  break;
716
695
  }
717
- case 'locale': {
718
- const { positionals } = parseCommandArgs(
719
- command,
720
- commandArgs,
721
- {},
722
- {
723
- allowPositionals: true,
724
- }
725
- );
726
- localeBlueprint(positionals[0]);
727
- break;
728
- }
729
696
  case 'help':
730
697
  case '--help':
731
698
  case '-h':
@@ -207,9 +207,13 @@ click handler for something a link can do.
207
207
  </div>
208
208
  ```
209
209
 
210
- These are the only two slots — there is no default or actions slot. A short
211
- inline link in `#text` (as above) is fine; anything more interactive belongs
212
- outside the alert, or in a `BbDialog`.
210
+ **The default slot is the text slot.** `<BbAlert>Something went wrong</BbAlert>`
211
+ is the shortest form and the one to reach for; `text` as a prop is for when a
212
+ string is all you have (an `:items` loop, a server message). When more than one
213
+ is present the most specific wins: `#text` > children > `text`.
214
+
215
+ There is no `actions` slot. A short inline link in the text (as above) is fine;
216
+ anything more interactive belongs outside the alert, or in a `BbDialog`.
213
217
 
214
218
  #### Brand art via a raw SVG
215
219
 
@@ -353,9 +357,6 @@ const publish = () => {
353
357
 
354
358
  ### Gotchas & anti-patterns
355
359
 
356
- - **There is no default slot.** `<BbAlert>some text</BbAlert>` renders
357
- nothing — Vue drops an unconsumed default slot silently, with no warning.
358
- Use `title`/`text` (or `#title`/`#text`, see above).
359
360
  - **Omitting `v-model` does not make an alert permanent** — the close button
360
361
  still dismisses it locally. Use `hide-close` for that.
361
362
  - Don't rebuild dismissal with your own button + `v-if`; the built-in close
@@ -407,7 +408,8 @@ const publish = () => {
407
408
 
408
409
  ## Slots
409
410
 
410
- - `text` — scope: `BbAlertTextSlotProps` — Replaces the default alert body text.
411
+ - `default` — scope: `any` — The alert body — the shortest form, and the one to reach for: `<BbAlert>Something went wrong</BbAlert>`. Same idiom as the rest of the `text`-prop family (BbButton, BbBaseButton, BbTooltip, BbIndicator). When more than one source is present…
412
+ - `text` — scope: `BbAlertTextSlotProps` — Replaces the default alert body text. Wins over children and `text`.
411
413
  - `title` — scope: `BbAlertTitleSlotProps` — Replaces the default alert title text.
412
414
 
413
415
  ## See Also
@@ -439,10 +439,12 @@ const feed: FeedEntry[] = [
439
439
  Load failures are normally caught by the image's `error` event. As a safety
440
440
  net — mainly for SSR/hydrated pages where the events fired before hydration —
441
441
  the component also checks the image `timeout` ms after mount (default `400`)
442
- and switches to the fallback if it hasn't loaded correctly by then. This makes
443
- `timeout` an effective **deadline**: an image slower than `timeout` falls back
444
- and stays on the fallback until `src` changes. If your avatars come from a
445
- slow origin, raise `timeout` rather than accepting spurious fallbacks.
442
+ and switches to the fallback if it hasn't loaded correctly by then. The
443
+ `<img>` stays mounted (visually hidden) through that switch, so the deadline
444
+ is **not** terminal: a slow image shows the fallback at `timeout` and swaps
445
+ itself back in the moment it finishes loading. Only a real `error` is
446
+ permanent until `src` changes. If your avatars come from a slow origin, raise
447
+ `timeout` rather than accepting a visible fallback-then-photo flip.
446
448
 
447
449
  Platform notes: the component is SSR-safe (Nuxt/Inertia) with no client-only
448
450
  markup; the only global config it reads is `iconDefaultSizes` for the size
@@ -461,15 +463,20 @@ keys.
461
463
 
462
464
  ### Gotchas & anti-patterns
463
465
 
464
- - **There is no `color` prop.** The fallback is always primary-token colored;
465
- don't reach for per-avatar inline backgrounds. Use variants and target the generated `bb-avatar--{{variant}}` or retheme `--bb-primary` /
466
- `--bb-primary-fg` if the palette is wrong.
466
+ - **There is no `color` prop, and no `variant` either.** The fallback is always
467
+ primary-token colored; don't reach for per-avatar inline backgrounds. If the
468
+ palette is wrong, retheme `--bb-primary` / `--bb-primary-fg` theming here
469
+ happens through the tokens, not a prop.
467
470
  - **Default `timeout` can false-fallback slow images** (see above) — tune it,
468
471
  don't reimplement image loading around the component.
469
472
  - Don't hand-position presence dots with absolute CSS — `BbIndicator dot`
470
473
  exists for that.
471
474
  - Don't wrap the avatar in a raw `<span @click>` for menus — put it inside
472
475
  `BbBaseButton`/`BbDropdown` so focus and keyboard behavior come for free.
476
+ - **The shape is a circle, and it is not configurable.** `border-radius: 50%`
477
+ is baked in. A rounded-SQUARE avatar — common in dense tables, where a square
478
+ reads as "entity" and a circle as "person" — has no answer here; build that
479
+ one yourself rather than fighting the radius.
473
480
  - Don't pre-crop sources to circles — the component center-crops; ship square
474
481
  or larger originals.
475
482
  - Don't leave `alt` empty for meaningful avatars: fallbacks lose their
@@ -14,24 +14,36 @@
14
14
  | --- | --- | --- | --- | --- |
15
15
  | `activeClass` | `string \| undefined` | | | CSS class applied when the component renders as a link and the target of the link is the current route or the url matches partially. Ported for Inertia compatibility. |
16
16
  | `ariaCurrentValue` | `"page" \| "step" \| "location" \| "date" \| "time" \| "true" \| "false" \| undefined` | | | Value forwarded to the `aria-current` attribute when the component renders as a router link and the target route is an exact match. Use to communicate the current location to assistive technologies. |
17
+ | `async` | `boolean \| undefined` | | | Inertia: runs the visit without blocking — the page stays interactive and several async visits can be in flight at once. |
18
+ | `cacheFor` | `string \| number \| (string \| number)[] \| undefined` | | | Inertia: how long a prefetched response stays fresh before it is re-fetched. A single duration, or `[staleAfter, expiresAfter]`. Typed platform-agnostically (matching BbButton) so the library's types never require `@inertiajs/vue3` to be in… |
19
+ | `cacheTags` | `string \| string[] \| undefined` | | | Inertia: tags to file this visit's prefetch cache under, so a later request can invalidate the whole tagged group. |
20
+ | `component` | `string \| undefined` | | | Inertia: the page component this visit resolves to. Rarely set by hand — the server normally decides it. |
17
21
  | `data` | `object \| undefined` | | | Request payload forwarded to Inertia when navigating via `href` in an Inertia-enabled app. Ignored when not using Inertia. |
18
22
  | `disabled` | `boolean \| undefined` | | | Disables user interaction. - When rendering as a native button, sets the `disabled` attribute. - When rendering as a link (anchor/Inertia), removes `href`, adds `aria-disabled="true"`, and prevents navigation while keeping focusable semanti… |
19
23
  | `download` | `string \| boolean \| undefined` | | | Marks an `href` link as a download. Renders a plain `<a download>` doing a native navigation — never an Inertia/router visit — so file downloads (including same-origin, `blob:` and `data:` URLs) work. Pass a string to set the suggested file… |
20
24
  | `exactActiveClass` | `string \| undefined` | | | CSS class applied when the component renders as a link and the target of the link and the url matches exactly. Ported for Inertia compatibility. |
25
+ | `except` | `string[] \| undefined` | | | Inertia: the inverse of `only` — properties to EXCLUDE from a partial reload. Pass one or the other, not both. |
21
26
  | `external` | `boolean \| undefined` | | | Forces an `href` link to render as a plain `<a>` (native navigation), bypassing Inertia/router interception — the same intent as Nuxt's `NuxtLink` `external`. Use for links outside the SPA. One of the native-anchor signals alongside `target… |
22
27
  | `headers` | `object \| undefined` | | | Additional HTTP headers forwarded to Inertia when navigating via `href` in an Inertia-enabled app. |
23
28
  | `href` | `string \| undefined` | | | Hyperlink reference used when rendering as an anchor (or as an Inertia link in Inertia-enabled apps). If provided and not disabled, the component renders as an anchor/Inertia link. |
29
+ | `instant` | `boolean \| undefined` | | | Inertia: navigate optimistically on click and reconcile when the response lands, instead of waiting for the round trip. |
24
30
  | `method` | `"get" \| "post" \| "put" \| "patch" \| "delete" \| undefined` | | | HTTP method used for Inertia navigation when `href` is provided in an Inertia-enabled app. Ignored otherwise. |
25
31
  | `onBefore` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia right before the request is sent. |
26
32
  | `onCancel` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a request is cancelled. |
27
33
  | `onCancelToken` | `((cancelToken: unknown) => void) \| undefined` | | | Receives the Inertia cancel token source when a request is initiated. Can be used to cancel the request. Typed platform-agnostically (matching BbButton) so the library's types never require `@inertiajs/vue3` to be installed. |
34
+ | `onError` | `((errors: Record<string, string>) => void) \| undefined` | | | Lifecycle hook invoked by Inertia when the server responds with VALIDATION errors — the ordinary 422 path. This is the only failure callback Inertia's `Link` accepts. `onHttpException` (server answered 5xx) and `onNetworkError` (the request… |
28
35
  | `onFinish` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia after the request has finished (regardless of success or error). |
29
36
  | `only` | `string[] \| undefined` | | | Limits the properties that are preserved in Inertia partial reloads. |
37
+ | `onPrefetched` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a prefetch for this link has completed and is cached. |
38
+ | `onPrefetching` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a prefetch for this link starts. |
30
39
  | `onProgress` | `((progress: { percentage: number \| undefined; }) => void) \| undefined` | | | Progress callback invoked by Inertia with the upload/download percentage when available. |
31
40
  | `onStart` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a request starts. |
32
41
  | `onSuccess` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a request succeeds. |
42
+ | `pageProps` | `Record<string, unknown> \| ((currentProps: Record<string, unknown>, sharedProps: Record<string, unknown>) => Record<string, unknown>) \| null \| undefined` | | | Inertia: props to merge into the next page optimistically, before the server responds. Either an object or a function of the current props. Typed platform-agnostically (matching BbButton) so the library's types never require `@inertiajs/vue… |
43
+ | `prefetch` | `string \| boolean \| string[] \| undefined` | | | Inertia: fetch and cache this link's page ahead of the click. `true` uses the default trigger; a string or list of strings picks them (`'mount'`, `'hover'`, `'click'`). |
33
44
  | `preserveScroll` | `boolean \| ((props: Record<string, unknown>) => boolean) \| undefined` | | | Controls whether Inertia should preserve the current scroll position after navigation. Can be a boolean or a predicate receiving the visit props. |
34
45
  | `preserveState` | `boolean \| ((props: Record<string, unknown>) => boolean) \| null \| undefined` | | | Controls whether Inertia should preserve the current state after navigation. Can be a boolean or a predicate receiving the visit props. |
46
+ | `preserveUrl` | `boolean \| undefined` | | | Inertia: keep the current URL in the address bar even though the page content changes. |
35
47
  | `queryStringArrayFormat` | `"brackets" \| "indices" \| undefined` | | | Format to use when serializing array values into the query string for Inertia requests. |
36
48
  | `rel` | `string \| undefined` | | | Relationship between the current document and the linked resource. Useful for security when opening new tabs (e.g. `noopener noreferrer`). |
37
49
  | `replace` | `boolean \| undefined` | | | Uses history replacement instead of push navigation. - With Vue Router (`to`), calls `router.replace`. - With Inertia (`href`), performs a replace visit. |
@@ -39,6 +51,7 @@
39
51
  | `target` | `string \| undefined` | | | Target browsing context for anchor/Inertia links (e.g. `_self`, `_blank`). Ignored when rendering as a native button. |
40
52
  | `to` | `string \| wt \| bt \| undefined` | | | Route location to navigate to. When provided (and not disabled), the component renders as a Vue Router link. |
41
53
  | `type` | `"button" \| "submit" \| "reset" \| undefined` | | | Native `type` attribute used when rendering as a button (e.g. `button`, `submit`, `reset`). |
54
+ | `viewTransition` | `boolean \| undefined` | | | Inertia: run the page swap inside a View Transition, where the browser supports one. |
42
55
 
43
56
  ## Events
44
57
 
@@ -102,9 +102,12 @@ const pinged = ref(false);
102
102
  ### It ships no chrome — you style it
103
103
 
104
104
  The only classes it applies are structural: `bb-base-button` (a focus-ring
105
- scaffold, `cursor`, inherited text color and transitions), `bb-base-button--block`
106
- and `bb-base-button--disabled`. There is **no padding, background, border, or
107
- variant appearance** — that is the whole point. Give it a class (your own, or
105
+ scaffold, `cursor` and transitions), `bb-base-button--block` and
106
+ `bb-base-button--disabled`. There is **no padding, background, border, or
107
+ variant appearance** — that is the whole point. It sets **no text color**, so a
108
+ `text-*` utility on the element just works with no specificity fight; the text
109
+ inherits from its container (in anchor mode, the browser's blue-and-underlined
110
+ link chrome is neutralized so it inherits too). Give it a class (your own, or
108
111
  Tailwind utilities) and it looks like whatever you draw. This is what makes it
109
112
  the right base for a clickable card or a bespoke nav item:
110
113
 
@@ -454,25 +457,37 @@ const sent = ref(false);
454
457
  | --- | --- | --- | --- | --- |
455
458
  | `activeClass` | `string \| undefined` | | | CSS class applied when the component renders as a link and the target of the link is the current route or the url matches partially. Ported for Inertia compatibility. |
456
459
  | `ariaCurrentValue` | `"page" \| "step" \| "location" \| "date" \| "time" \| "true" \| "false" \| undefined` | | | Value forwarded to the `aria-current` attribute when the component renders as a router link and the target route is an exact match. Use to communicate the current location to assistive technologies. |
460
+ | `async` | `boolean \| undefined` | | | Inertia: runs the visit without blocking — the page stays interactive and several async visits can be in flight at once. |
457
461
  | `block` | `boolean \| undefined` | | | Makes the component take the full available width (block-level layout). Adds the `bb-base-button--block` modifier class. |
462
+ | `cacheFor` | `string \| number \| (string \| number)[] \| undefined` | | | Inertia: how long a prefetched response stays fresh before it is re-fetched. A single duration, or `[staleAfter, expiresAfter]`. Typed platform-agnostically (matching BbButton) so the library's types never require `@inertiajs/vue3` to be in… |
463
+ | `cacheTags` | `string \| string[] \| undefined` | | | Inertia: tags to file this visit's prefetch cache under, so a later request can invalidate the whole tagged group. |
464
+ | `component` | `string \| undefined` | | | Inertia: the page component this visit resolves to. Rarely set by hand — the server normally decides it. |
458
465
  | `data` | `object \| undefined` | | | Request payload forwarded to Inertia when navigating via `href` in an Inertia-enabled app. Ignored when not using Inertia. |
459
466
  | `disabled` | `boolean \| undefined` | | | Disables user interaction. - When rendering as a native button, sets the `disabled` attribute. - When rendering as a link (anchor/Inertia), removes `href`, adds `aria-disabled="true"`, and prevents navigation while keeping focusable semanti… |
460
467
  | `download` | `string \| boolean \| undefined` | | | Marks an `href` link as a download. Renders a plain `<a download>` doing a native navigation — never an Inertia/router visit — so file downloads (including same-origin, `blob:` and `data:` URLs) work. Pass a string to set the suggested file… |
461
468
  | `exactActiveClass` | `string \| undefined` | | | CSS class applied when the component renders as a link and the target of the link and the url matches exactly. Ported for Inertia compatibility. |
469
+ | `except` | `string[] \| undefined` | | | Inertia: the inverse of `only` — properties to EXCLUDE from a partial reload. Pass one or the other, not both. |
462
470
  | `external` | `boolean \| undefined` | | | Forces an `href` link to render as a plain `<a>` (native navigation), bypassing Inertia/router interception — the same intent as Nuxt's `NuxtLink` `external`. Use for links outside the SPA. One of the native-anchor signals alongside `target… |
463
471
  | `headers` | `object \| undefined` | | | Additional HTTP headers forwarded to Inertia when navigating via `href` in an Inertia-enabled app. |
464
472
  | `href` | `string \| undefined` | | | Hyperlink reference used when rendering as an anchor (or as an Inertia link in Inertia-enabled apps). If provided and not disabled, the component renders as an anchor/Inertia link. |
473
+ | `instant` | `boolean \| undefined` | | | Inertia: navigate optimistically on click and reconcile when the response lands, instead of waiting for the round trip. |
465
474
  | `method` | `"get" \| "post" \| "put" \| "patch" \| "delete" \| undefined` | | | HTTP method used for Inertia navigation when `href` is provided in an Inertia-enabled app. Ignored otherwise. |
466
475
  | `onBefore` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia right before the request is sent. |
467
476
  | `onCancel` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a request is cancelled. |
468
477
  | `onCancelToken` | `((cancelToken: unknown) => void) \| undefined` | | | Receives the Inertia cancel token source when a request is initiated. Can be used to cancel the request. Typed platform-agnostically (matching BbButton) so the library's types never require `@inertiajs/vue3` to be installed. |
478
+ | `onError` | `((errors: Record<string, string>) => void) \| undefined` | | | Lifecycle hook invoked by Inertia when the server responds with VALIDATION errors — the ordinary 422 path. This is the only failure callback Inertia's `Link` accepts. `onHttpException` (server answered 5xx) and `onNetworkError` (the request… |
469
479
  | `onFinish` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia after the request has finished (regardless of success or error). |
470
480
  | `only` | `string[] \| undefined` | | | Limits the properties that are preserved in Inertia partial reloads. |
481
+ | `onPrefetched` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a prefetch for this link has completed and is cached. |
482
+ | `onPrefetching` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a prefetch for this link starts. |
471
483
  | `onProgress` | `((progress: { percentage: number \| undefined; }) => void) \| undefined` | | | Progress callback invoked by Inertia with the upload/download percentage when available. |
472
484
  | `onStart` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a request starts. |
473
485
  | `onSuccess` | `(() => void) \| undefined` | | | Lifecycle hook invoked by Inertia when a request succeeds. |
486
+ | `pageProps` | `Record<string, unknown> \| ((currentProps: Record<string, unknown>, sharedProps: Record<string, unknown>) => Record<string, unknown>) \| null \| undefined` | | | Inertia: props to merge into the next page optimistically, before the server responds. Either an object or a function of the current props. Typed platform-agnostically (matching BbButton) so the library's types never require `@inertiajs/vue… |
487
+ | `prefetch` | `string \| boolean \| string[] \| undefined` | | | Inertia: fetch and cache this link's page ahead of the click. `true` uses the default trigger; a string or list of strings picks them (`'mount'`, `'hover'`, `'click'`). |
474
488
  | `preserveScroll` | `boolean \| ((props: Record<string, unknown>) => boolean) \| undefined` | | | Controls whether Inertia should preserve the current scroll position after navigation. Can be a boolean or a predicate receiving the visit props. |
475
489
  | `preserveState` | `boolean \| ((props: Record<string, unknown>) => boolean) \| null \| undefined` | | | Controls whether Inertia should preserve the current state after navigation. Can be a boolean or a predicate receiving the visit props. |
490
+ | `preserveUrl` | `boolean \| undefined` | | | Inertia: keep the current URL in the address bar even though the page content changes. |
476
491
  | `queryStringArrayFormat` | `"brackets" \| "indices" \| undefined` | | | Format to use when serializing array values into the query string for Inertia requests. |
477
492
  | `rel` | `string \| undefined` | | | Relationship between the current document and the linked resource. Useful for security when opening new tabs (e.g. `noopener noreferrer`). |
478
493
  | `replace` | `boolean \| undefined` | | | Uses history replacement instead of push navigation. - With Vue Router (`to`), calls `router.replace`. - With Inertia (`href`), performs a replace visit. |
@@ -481,6 +496,7 @@ const sent = ref(false);
481
496
  | `text` | `string \| undefined` | | | Fallback text content rendered when no default slot is provided. |
482
497
  | `to` | `string \| wt \| bt \| undefined` | | | Route location to navigate to. When provided (and not disabled), the component renders as a Vue Router link. |
483
498
  | `type` | `"button" \| "submit" \| "reset" \| undefined` | `"button"` | | Native `type` attribute used when rendering as a button (e.g. `button`, `submit`, `reset`). |
499
+ | `viewTransition` | `boolean \| undefined` | | | Inertia: run the page swap inside a View Transition, where the browser supports one. |
484
500
 
485
501
  ## Events
486
502
 
@@ -17,6 +17,7 @@
17
17
  | Prop | Type | Default | Required | Description |
18
18
  | --- | --- | --- | --- | --- |
19
19
  | `ariaDescribedby` | `string \| undefined` | | | Id(s) of elements describing this rating input for assistive technologies. |
20
+ | `ariaLabelledby` | `string \| undefined` | | | Id(s) of the element(s) that NAME this rating group. Supplying one is what promotes the inner container to `role="radiogroup"` (Q4.5): an unnamed `radiogroup` announces as a bare "radio group" and is no better than the role-less `<span>` it… |
20
21
  | `autofocus` | `Booleanish \| undefined` | | | Sets autofocus on page load. |
21
22
  | `clearable` | `boolean \| undefined` | `false` | | Allows clearing the selected rating by clicking the current value. |
22
23
  | `disabled` | `boolean \| undefined` | | | Disable all interactions with the rating. |