bitboss-ui 3.0.0-beta.18 → 3.0.0-beta.19

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 (67) hide show
  1. package/README.md +39 -7
  2. package/dist/ai/BbBaseCheckboxGroup.md +1 -1
  3. package/dist/ai/BbBaseSwitchGroup.md +1 -1
  4. package/dist/ai/BbDialog.md +63 -0
  5. package/dist/ai/BbOffCanvas.md +58 -0
  6. package/dist/ai/BbSelect.md +11 -5
  7. package/dist/ai/BbSelectPopover.md +10 -5
  8. package/dist/ai/BbSwitch.md +13 -0
  9. package/dist/ai/BbTable.md +7 -4
  10. package/dist/ai/changelog.json +1 -1
  11. package/dist/ai/components.json +26 -24
  12. package/dist/ai/composables/useBbConfig.md +47 -6
  13. package/dist/ai/guides/ai-router.md +9 -4
  14. package/dist/ai/guides/fetch-items-playbook.md +1 -1
  15. package/dist/ai/guides/installation-and-plugin-setup.md +19 -5
  16. package/dist/ai/guides/migration/components/bb-checkbox.md +35 -2
  17. package/dist/ai/guides/migration/components/bb-dialog.md +27 -7
  18. package/dist/ai/guides/migration/components/bb-offcanvas.md +11 -10
  19. package/dist/ai/guides/migration/v2-to-v3.md +10 -10
  20. package/dist/ai/guides/ssr-prefill.md +120 -0
  21. package/dist/ai/index.md +3 -2
  22. package/dist/ai/source/BbBaseCheckboxGroup.md +5 -1
  23. package/dist/ai/source/BbBaseRadioGroup.md +4 -0
  24. package/dist/ai/source/BbBaseSwitchGroup.md +4 -0
  25. package/dist/ai/source/BbCheckbox.md +15 -1
  26. package/dist/ai/source/BbDialog.md +85 -3
  27. package/dist/ai/source/BbOffCanvas.md +72 -3
  28. package/dist/ai/source/BbRadio.md +15 -1
  29. package/dist/ai/source/BbSelectPopover.md +4 -0
  30. package/dist/ai/source/BbSwitch.md +15 -1
  31. package/dist/ai/source/BbTable.md +4 -0
  32. package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue_vue_type_script_setup_true_lang.js +28 -26
  33. package/dist/components/BbBaseCheckboxGroup/types.d.ts +1 -1
  34. package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue_vue_type_script_setup_true_lang.js +22 -20
  35. package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue_vue_type_script_setup_true_lang.js +28 -26
  36. package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +115 -94
  37. package/dist/components/BbDialog/types.d.ts +23 -0
  38. package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +149 -134
  39. package/dist/components/BbOffCanvas/types.d.ts +23 -0
  40. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +82 -80
  41. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +204 -202
  42. package/dist/composables/useBbConfig.d.ts +11 -5
  43. package/dist/composables/useBbConfig.js +5 -6
  44. package/dist/composables/useConfig.d.ts +13 -4
  45. package/dist/composables/useConfig.js +1 -1
  46. package/dist/composables/useItemsGetter.d.ts +11 -0
  47. package/dist/composables/useItemsGetter.js +44 -18
  48. package/dist/composables/usePrefill.d.ts +21 -2
  49. package/dist/composables/usePrefill.js +25 -19
  50. package/dist/composables/useSsrState.d.ts +20 -0
  51. package/dist/composables/useSsrState.js +13 -0
  52. package/dist/deprecation/ai-deprecations.json.d.ts +0 -22
  53. package/dist/deprecation/ai-deprecations.json.js +1 -1
  54. package/dist/index.d.ts +1 -0
  55. package/dist/index.js +63 -62
  56. package/dist/llms-full.txt +634 -245
  57. package/dist/llms-medium.txt +37 -18
  58. package/dist/llms.txt +5 -4
  59. package/dist/runtime/nuxt-plugin.js +28 -21
  60. package/dist/styles.css +1 -1
  61. package/dist/utilities/misc/ssrStateInjectionKey.d.ts +42 -0
  62. package/dist/utilities/misc/ssrStateInjectionKey.js +4 -0
  63. package/dist/vite.js +6 -2
  64. package/llms.txt +6 -5
  65. package/package.json +2 -2
  66. package/scripts/lib/eslint-plugin.d.ts +19 -1
  67. package/scripts/lib/eslint-plugin.mjs +146 -0
package/README.md CHANGED
@@ -6,7 +6,7 @@ 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`, 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/).
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.bitboss.it/).
10
10
 
11
11
  ### Styles
12
12
 
@@ -90,7 +90,7 @@ For the public **Bb\*** components, the package exports matching **props**, **ev
90
90
 
91
91
  **Primary docs (components, props, guides):**
92
92
 
93
- - [BitBoss UI documentation](https://ui-components-docs.vercel.app/) — site with guides and API reference.
93
+ - [BitBoss UI documentation](https://ui-components.bitboss.it/) — site with guides and API reference.
94
94
 
95
95
  **For AI coding agents:**
96
96
 
@@ -100,10 +100,10 @@ v3 prerelease; pin an exact version (`bitboss-ui@3.0.0-alpha.5`) for stable
100
100
  links. Sizes grow with the catalogue — treat them as the current order of
101
101
  magnitude, not a contract.
102
102
 
103
- - [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.
104
- - [Index](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/llms.txt) (~19 KB) — link index into every document.
105
- - [components.json](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/dist/ai/components.json) — machine-readable API surface, for programmatic validation.
106
- - [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.
103
+ - [Core knowledge base](https://llms-bitboss-ui.bitboss.it/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.
104
+ - [Index](https://llms-bitboss-ui.bitboss.it/llms.txt) (~19 KB) — link index into every document.
105
+ - [components.json](https://llms-bitboss-ui.bitboss.it/dist/ai/components.json) — machine-readable API surface, for programmatic validation.
106
+ - [Complete knowledge base](https://llms-bitboss-ui.bitboss.it/dist/llms-full.txt) (~3.2 MB) — everything concatenated. Bulk ingestion only; too large to prompt with.
107
107
 
108
108
  Already installed? Prefer the local copy under `node_modules/bitboss-ui/dist/ai/`,
109
109
  run `npx bitboss-ui ai-init` to write agent pointers, and
@@ -328,6 +328,38 @@ migrations — a value that must move to a slot, directive, or CSS — are repor
328
328
  with a migration-guide link but left for you. Run `eslint --fix`, then the
329
329
  remaining reports are exactly the by-hand cases.
330
330
 
331
+ #### The one v2 break a codemod cannot own: `reverse`
332
+
333
+ `reverse` on `BbCheckbox` / `BbRadio` / `BbSwitch` kept its **name** and
334
+ inverted its **meaning** in v3. Both versions render the same default layout,
335
+ so only explicit values moved:
336
+
337
+ | v2 | v3 |
338
+ | ----------------------------- | --------- |
339
+ | `:reverse="false"` | `reverse` |
340
+ | `reverse` / `:reverse="true"` | delete it |
341
+
342
+ That is invisible to every other check — `:reverse="false"` is a declared prop,
343
+ correctly typed, holding its own default value — and it cannot go in the
344
+ rename table above, because the table is keyed by prop _name_: an entry for
345
+ `reverse` would rewrite the legitimate v3 spellings too.
346
+
347
+ So it has its own rule, `bitboss-ui/no-reverse-false` (in `recommended`). It
348
+ flags only a **literal** `:reverse="false"` and offers both corrections as
349
+ editor suggestions — a dynamic `:reverse="isRtl"` that merely evaluates false
350
+ is left alone. For a bulk sweep of a v2 codebase, opt into the fix:
351
+
352
+ ```js
353
+ {
354
+ rules: {
355
+ 'bitboss-ui/no-reverse-false': ['error', { assumeV2: true }],
356
+ },
357
+ }
358
+ ```
359
+
360
+ `assumeV2` is off by default because the two readings produce opposite
361
+ layouts — turn it on only for files you know came from v2, then turn it off.
362
+
331
363
  Every real HTML attribute (plus `aria-*` / `data-*`) falls through untouched, so
332
364
  attribute inheritance is never flagged. Escape hatches when you do need one:
333
365
 
@@ -406,7 +438,7 @@ MIT — see [`LICENSE`](LICENSE).
406
438
 
407
439
  ## Support
408
440
 
409
- - **Usage and APIs:** see the [documentation](https://ui-components-docs.vercel.app/).
441
+ - **Usage and APIs:** see the [documentation](https://ui-components.bitboss.it/).
410
442
  - **Company / licensing / commercial:** [BitBoss](https://www.bitboss.it/).
411
443
 
412
444
  ### Reporting a bug
@@ -22,7 +22,7 @@
22
22
  | `depsDebounceTime` | `number \| undefined` | `0` | | Debounce delay (ms) applied when reacting to `dependencies` changes. |
23
23
  | `direction` | `"horizontal" \| "vertical" \| undefined` | `"horizontal"` | | Direction of the group layout. - `horizontal`: options flow in rows - `vertical`: options stack in a column |
24
24
  | `disabled` | `boolean \| undefined` | `false` | | Disables interaction for the whole group. |
25
- | `enforceCoherence` | `boolean \| undefined` | `false` | | Enforces that `modelValue` contains only values present in `items`. Incoherent values are removed and `update:modelValue` is emitted with a coherent array. Please check out {@link https://ui-components-docs.vercel.app/it/guides/coherence th… |
25
+ | `enforceCoherence` | `boolean \| undefined` | `false` | | Enforces that `modelValue` contains only values present in `items`. Incoherent values are removed and `update:modelValue` is emitted with a coherent array. Please check out {@link https://ui-components.bitboss.it/it/guides/coherence the doc… |
26
26
  | `hasErrors` | `boolean \| undefined` | `false` | | Puts the component into an error state adds the error styling class. |
27
27
  | `hasWarning` | `boolean \| undefined` | `false` | | Puts the component into a warning state. Errors take priority when both are set. |
28
28
  | `hideLabel` | `boolean \| undefined` | `false` | | Visually hides each option's label text (the text rendered next to every input) while keeping it accessible to screen readers. |
@@ -22,7 +22,7 @@
22
22
  | `depsDebounceTime` | `number \| undefined` | `0` | | Debounce delay (ms) applied when reacting to `dependencies` changes. |
23
23
  | `direction` | `"horizontal" \| "vertical" \| undefined` | `"horizontal"` | | Direction of the group layout. - `horizontal`: options flow in rows - `vertical`: options stack in a column |
24
24
  | `disabled` | `boolean \| undefined` | `false` | | Disables interaction for the whole group. |
25
- | `enforceCoherence` | `boolean \| undefined` | `false` | | Enforces that `modelValue` contains only values present in `items`. Incoherent values are removed and `update:modelValue` is emitted with a coherent array. Please check out {@link https://ui-components-docs.vercel.app/it/guides/coherence th… |
25
+ | `enforceCoherence` | `boolean \| undefined` | `false` | | Enforces that `modelValue` contains only values present in `items`. Incoherent values are removed and `update:modelValue` is emitted with a coherent array. Please check out {@link https://ui-components.bitboss.it/it/guides/coherence the doc… |
26
26
  | `hasErrors` | `boolean \| undefined` | `false` | | Puts the component into an error state adds the error styling class. |
27
27
  | `hasWarning` | `boolean \| undefined` | `false` | | Puts the component into a warning state. Errors take priority when both are set. |
28
28
  | `hideLabel` | `boolean \| undefined` | `false` | | Visually hides each option's label text (the text rendered next to every input) while keeping it accessible to screen readers. |
@@ -186,6 +186,65 @@ Use the `title` slot to restyle just the title text — it stays wired to
186
186
  removes the whole header (the close button then floats over the body corner
187
187
  unless `hide-close`), and you own the accessible name.
188
188
 
189
+ Add `description` for one muted line under the title — the dialog's subtitle,
190
+ and the only thing `aria-describedby` points at:
191
+
192
+ **A described dialog**
193
+
194
+ ```vue
195
+ <template>
196
+ <div
197
+ class="flex w-full max-w-sm flex-col gap-3 rounded-[var(--bb-radius)] border p-4"
198
+ >
199
+ <div>
200
+ <p class="m-0 text-sm font-medium">INV-2044</p>
201
+ <p class="m-0 text-xs text-[color:var(--bb-text-muted)]">
202
+ Northwind Traders · €1,280.00 · paid
203
+ </p>
204
+ </div>
205
+ <div>
206
+ <BbButton
207
+ prepend:icon="lucide:trash-2"
208
+ variant="outline"
209
+ @click="open = true"
210
+ >
211
+ Delete invoice
212
+ </BbButton>
213
+ </div>
214
+ </div>
215
+ <BbDialog
216
+ v-model="open"
217
+ description="This permanently removes the invoice and its payment history."
218
+ title="Delete invoice INV-2044"
219
+ >
220
+ <p class="m-0">
221
+ Anything that does not fit in one line belongs here, in the body — which
222
+ is deliberately not what <code>aria-describedby</code> points at.
223
+ </p>
224
+ <template #footer>
225
+ <div class="flex justify-end gap-2">
226
+ <BbButton variant="ghost" @click="open = false">Cancel</BbButton>
227
+ <BbButton variant="destructive" @click="open = false">Delete</BbButton>
228
+ </div>
229
+ </template>
230
+ </BbDialog>
231
+ </template>
232
+
233
+ <script setup lang="ts">
234
+ import { ref } from 'vue';
235
+ import { BbButton, BbDialog } from 'bitboss-ui';
236
+
237
+ const open = ref(false);
238
+ </script>
239
+ ```
240
+
241
+ Keep it to a sentence; the detail belongs in the body, which is deliberately
242
+ **not** described (pointing `aria-describedby` at arbitrary markup makes a
243
+ screen reader read a whole form as the dialog's description). Use the
244
+ `description` slot when the line needs markup — it receives the prop as `text`.
245
+ A dialog with no `description` keeps its original single-row header, so nothing
246
+ shifts unless you opt in.
247
+
189
248
  Prefer `title` (or the `title` slot) for the standard header. The `header` slot
190
249
  replaces the entire header on both surfaces — you own the close control and must
191
250
  wire `titleId` for `aria-labelledby`. Give your root element `flex-1` so it fills
@@ -762,6 +821,8 @@ default falls back to `config.adaptive`.
762
821
  - The dialog is a true modal (`showModal()`): the browser contains focus within
763
822
  it, the page behind is inert, and focus returns to the trigger when it
764
823
  closes. `focus-target` only chooses where focus **starts**.
824
+ - `description` is what `aria-describedby` points at, so the dialog announces
825
+ its purpose and not just its name. Nothing else is described — see above.
765
826
  - The header close button carries a localized `aria-label` out of the box
766
827
  (`config.locale`).
767
828
  - Don't lean on `persistent` to trap users. Keep a visible way out (a Cancel or
@@ -776,6 +837,7 @@ default falls back to `config.adaptive`.
776
837
  | Prop | Type | Default | Required | Description |
777
838
  | --- | --- | --- | --- | --- |
778
839
  | `adaptive` | `boolean \| undefined` | ``config.adaptive` (`true`)` | | Adapt the surface to the viewport: render as a `BbOffCanvas` drawer on mobile instead of a centered dialog. The surface is resolved when the dialog opens and stays fixed for that session: resizing across the mobile breakpoint while open nev… |
840
+ | `description` | `string \| undefined` | | | One short line under the title, in the header — the shadcn `DialogDescription` shape: same block as the title, lower hierarchy, muted. It is also what `aria-describedby` points at. That wiring was dropped in v3 along with the old `descripti… |
779
841
  | `disabled` | `boolean \| undefined` | `false` | | Disable EVERY user-initiated dismissal: backdrop click, Escape AND the header close (×) button (denied attempts play the deny nudge). Use it while an operation is in flight (e.g. a save) so the dialog cannot be dismissed mid-work. Compare `… |
780
842
  | `eager` | `boolean \| undefined` | `false` | | Render dialog content immediately instead of waiting for the first open. |
781
843
  | `focusTarget` | `string \| undefined` | | | CSS selector of the element to focus after opening. Defaults to the first tabbable element. |
@@ -800,6 +862,7 @@ default falls back to `config.adaptive`.
800
862
  ## Slots
801
863
 
802
864
  - `default` — scope: `BbDialogDefaultSlotProps` — Primary body content of the dialog, rendered in the scrollable body area.
865
+ - `description` — scope: `BbDialogDescriptionSlotProps` — Replaces the description text under the title. Renders the header even when `title` and `description` are unset — a slotted description is a description, the same rule `#title` follows.
803
866
  - `footer` — scope: `object` — Content rendered in the dialog footer, below the body area. Typically used for action buttons.
804
867
  - `header` — scope: `BbDialogHeaderSlotProps` — Replaces the entire dialog header section (title + close button) on both surfaces — the desktop `<dialog>` and the mobile off-canvas. Because it replaces the default close (✕), the slot owns closing: render your own control wired to the `cl…
805
868
  - `title` — scope: `BbDialogTitleSlotProps` — Replaces the default title text inside the header. Remains linked to the dialog through `aria-labelledby`. Providing this slot renders the header even when `title` is unset — a slotted title is a title.
@@ -161,6 +161,11 @@ header and footer stay pinned.
161
161
 
162
162
  - Use the `title` slot to restyle just the title text — it stays wired to
163
163
  `aria-labelledby` and receives the `title` prop as `text`.
164
+ - **`description`** adds one muted line under the title — the panel's subtitle,
165
+ and the only thing `aria-describedby` points at. Keep it to a sentence; the
166
+ body is deliberately not described. The `description` slot takes markup and
167
+ receives the prop as `text`. A panel without one keeps its original
168
+ single-row header.
164
169
  - The **`header` slot** replaces the whole header section; it receives
165
170
  `titleId`, `title` and `close`, so you can build a custom header and still
166
171
  keep the accessible name wired through `titleId`. `BbDialog` supports the
@@ -169,6 +174,55 @@ header and footer stay pinned.
169
174
  - Omitting `title` (and the `header` slot) removes the header entirely — you
170
175
  then own the accessible name.
171
176
 
177
+ **A described panel (via the #description slot)**
178
+
179
+ ```vue
180
+ <template>
181
+ <BbButton
182
+ prepend:icon="lucide:user-plus"
183
+ variant="outline"
184
+ @click="open = true"
185
+ >
186
+ Invite teammate
187
+ </BbButton>
188
+ <BbOffCanvas v-model="open" side="right" title="Invite teammate">
189
+ <!-- The slot takes markup where the prop would only take a string. -->
190
+ <template #description>
191
+ They join the <strong>Northwind</strong> workspace as a viewer.
192
+ </template>
193
+ <div class="grid gap-3">
194
+ <BbTextInput
195
+ v-model="email"
196
+ compact
197
+ label="Email"
198
+ placeholder="ada@northwind.co"
199
+ type="email"
200
+ />
201
+ </div>
202
+ <template #footer>
203
+ <div class="flex justify-end gap-2">
204
+ <BbButton variant="ghost" @click="open = false">Cancel</BbButton>
205
+ <BbButton
206
+ :disabled="!email.trim()"
207
+ variant="primary"
208
+ @click="open = false"
209
+ >
210
+ Send invite
211
+ </BbButton>
212
+ </div>
213
+ </template>
214
+ </BbOffCanvas>
215
+ </template>
216
+
217
+ <script setup lang="ts">
218
+ import { ref } from 'vue';
219
+ import { BbButton, BbOffCanvas, BbTextInput } from 'bitboss-ui';
220
+
221
+ const open = ref(false);
222
+ const email = ref('');
223
+ </script>
224
+ ```
225
+
172
226
  ### A filters drawer
173
227
 
174
228
  The archetypal off-canvas: search + toggles in the `default` slot, Apply/Reset
@@ -913,6 +967,8 @@ The panel is a native `<dialog>` opened with `showModal()` — the same mechanic
913
967
  - The panel is a true modal (`showModal()`): focus is contained within it, the
914
968
  page behind is inert, and focus returns to the trigger when it closes.
915
969
  `focus-target` only chooses where focus **starts**.
970
+ - `description` is what `aria-describedby` points at, so the panel announces
971
+ its purpose and not just its name.
916
972
  - The header close button carries a localized `aria-label` out of the box
917
973
  (`config.locale`).
918
974
  - Draggable dismissal is a convenience, not the only way out — the close button,
@@ -929,6 +985,7 @@ The panel is a native `<dialog>` opened with `showModal()` — the same mechanic
929
985
 
930
986
  | Prop | Type | Default | Required | Description |
931
987
  | --- | --- | --- | --- | --- |
988
+ | `description` | `string \| undefined` | | | One short line under the title, in the header — the shadcn `DialogDescription` shape: same block as the title, lower hierarchy, muted. It is also what `aria-describedby` points at. That wiring was dropped in v3 along with the old `descripti… |
932
989
  | `disabled` | `boolean \| undefined` | `false` | | Disable EVERY user-initiated dismissal: backdrop click, Escape, drag-to-dismiss AND the header close (×) button (denied attempts play the deny nudge). Use it while an operation is in flight (e.g. a save) so the drawer cannot be dismissed mi… |
933
990
  | `draggable` | `boolean \| undefined` | `false` | | Enable drag-to-dismiss. The panel can be dragged from anywhere toward its edge to close, and a grab handle is shown on the inner edge as the affordance. When `false`, the panel is static (dismiss via the close button, backdrop, or Escape on… |
934
991
  | `eager` | `boolean \| undefined` | `false` | | Render offcanvas content immediately instead of waiting for the first open. |
@@ -955,6 +1012,7 @@ The panel is a native `<dialog>` opened with `showModal()` — the same mechanic
955
1012
  ## Slots
956
1013
 
957
1014
  - `default` — scope: `{ close: () => void; }` — Primary body content of the off-canvas panel, rendered in the scrollable body area.
1015
+ - `description` — scope: `BbOffCanvasDescriptionSlotProps` — Replaces the description text under the title. Renders the header even when `title` and `description` are unset — a slotted description is a description, the same rule `#title` follows.
958
1016
  - `footer` — scope: `object` — Content rendered in the panel footer, below the body area. Typically used for action buttons.
959
1017
  - `header` — scope: `BbOffCanvasHeaderSlotProps` — Replaces the entire off-canvas header section (title + close button). Maintains accessibility wiring via the provided props.
960
1018
  - `title` — scope: `BbOffCanvasTitleSlotProps` — Replaces the default title text inside the header. Remains linked to the panel through `aria-labelledby`. Providing this slot renders the header even when `title` is unset — a slotted title is a title.
@@ -613,8 +613,10 @@ later responses don't include them — no need to merge them into every response
613
613
  `prefill` controls **when the first load happens**:
614
614
 
615
615
  - `'interaction'` (default) — on the first open.
616
- - `true` — immediately on mount, and during SSR (`onServerPrefetch`), so the
617
- panel is warm on hydration.
616
+ - `true` — immediately on mount, and during SSR **on hosts that can carry the
617
+ result to the client** (Nuxt does; see
618
+ [SSR prefill](./guides/ssr-prefill.md)), so the panel is warm on hydration.
619
+ Everywhere else the load happens on the client, right after mount.
618
620
  - `false` — **search-first**: nothing loads until the user types. Opening is not
619
621
  a search, so the panel opens empty and shows a _"search to begin"_ hint until
620
622
  the first query. Use it when the result set is too big or too costly to browse
@@ -1337,9 +1339,13 @@ From the vite/nuxt plugin options:
1337
1339
 
1338
1340
  Platform notes:
1339
1341
 
1340
- - **SSR / Nuxt**: `prefill: true` providers also run in `onServerPrefetch`, so
1341
- server-rendered pages hydrate with options loaded. Keep providers isomorphic
1342
- (no `window` access).
1342
+ - **SSR / Nuxt**: under `bitboss-ui/nuxt`, `prefill: true` providers run in
1343
+ `onServerPrefetch` AND their result travels in the Nuxt payload, so the client
1344
+ hydrates with the options already in hand and never re-requests them. Under any
1345
+ other SSR host the provider does **not** run on the server at all — the server
1346
+ emits the placeholder and the client loads on mount, which keeps hydration
1347
+ clean. Keep providers isomorphic (no `window` access) and have them return
1348
+ plain, serializable rows. See [SSR prefill](./guides/ssr-prefill.md).
1343
1349
  - **Plain HTML forms / Inertia form posts**: give the select a `name` — hidden
1344
1350
  inputs carry the values (JSON-stringified when not strings), and `required`
1345
1351
  participates in native validation.
@@ -398,8 +398,9 @@ const destinationLabel = computed(
398
398
  ### Laziness, prefill and the trigger label
399
399
 
400
400
  `prefill` controls **when the first load happens**: `'interaction'` (default) on
401
- first open, `true` immediately on mount (and during SSR via `onServerPrefetch`),
402
- `false` **search-first** nothing is fetched until the user types a query.
401
+ first open, `true` immediately on mount (and during SSR via `onServerPrefetch`,
402
+ but only on a host that can transfer the result see
403
+ [SSR prefill](./guides/ssr-prefill.md)), `false` **search-first** — nothing is fetched until the user types a query.
403
404
  Opening the panel is not a search, so it loads nothing; until the first query the
404
405
  list shows a _"search to begin"_ hint instead of "no data".
405
406
 
@@ -1199,9 +1200,13 @@ floating popover everywhere.
1199
1200
  - **Global config** — `config.adaptive` (default for `adaptive`),
1200
1201
  `config.mobileMaxWidth` (the mobile threshold), `config.locale` (the search
1201
1202
  field's default accessible name and panel string defaults).
1202
- - **SSR / Nuxt** — `prefill: true` providers also run in `onServerPrefetch`, so
1203
- server-rendered pages hydrate with options loaded; keep providers isomorphic
1204
- (no `window` access).
1203
+ - **SSR / Nuxt** — under `bitboss-ui/nuxt`, `prefill: true` providers run in
1204
+ `onServerPrefetch` and their result rides the Nuxt payload, so the client
1205
+ hydrates with options already loaded and does not fetch again. On other SSR
1206
+ hosts the provider stays client-side and the server renders the placeholder —
1207
+ deliberately, since server markup the client cannot rebuild is a hydration
1208
+ mismatch. Keep providers isomorphic (no `window` access) and returning plain
1209
+ rows. See [SSR prefill](./guides/ssr-prefill.md).
1205
1210
 
1206
1211
  ### Works well with
1207
1212
 
@@ -386,6 +386,18 @@ const usageAnalytics = ref(true);
386
386
  name="captions"
387
387
  reverse
388
388
  />
389
+ <!-- A described row keeps the same trailing edge: `input-position`
390
+ applies on the description path too, and the switch pins to the
391
+ label's FIRST line rather than the centre of the two-line block. -->
392
+ <BbSwitch
393
+ v-model="downloads"
394
+ description="Uses storage on this device, and only over Wi-Fi."
395
+ direction="xxxxxx xx"
396
+ input-position="right"
397
+ label="Download for offline"
398
+ name="downloads"
399
+ reverse
400
+ />
389
401
  </div>
390
402
  </template>
391
403
  <script setup lang="ts">
@@ -394,6 +406,7 @@ import { BbSwitch } from 'bitboss-ui';
394
406
 
395
407
  const autoplay = ref(true);
396
408
  const captions = ref(false);
409
+ const downloads = ref(true);
397
410
  </script>
398
411
  ```
399
412
 
@@ -2754,10 +2754,13 @@ reader.
2754
2754
  plugin is still on the default locale, which is `it` — set the plugin's
2755
2755
  `locale` option, or override per table with `actions-text` (it is visually
2756
2756
  hidden in the header but read by screen readers).
2757
- - **SSR / Nuxt.** A provider `items` function participates in
2758
- `onServerPrefetch`, so the first page can render server-side. The table
2759
- context (and thus `table-id` pagination wiring) is client-side only
2760
- hydration-time onward.
2757
+ - **SSR / Nuxt.** Under `bitboss-ui/nuxt` a provider `items` function
2758
+ participates in `onServerPrefetch` and its rows travel in the Nuxt payload, so
2759
+ the first page renders server-side and hydrates without re-fetching. On other
2760
+ SSR hosts the provider runs on the client only and the server renders the
2761
+ empty table — see [SSR prefill](./guides/ssr-prefill.md). The table context
2762
+ (and thus `table-id` pagination wiring) is client-side only — hydration-time
2763
+ onward.
2761
2764
  - **Forms.** With `name`, the selection submits as hidden inputs — no JS
2762
2765
  needed between the table and a plain `<form>` POST (Inertia included).
2763
2766
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "library": "bitboss-ui",
4
- "version": "3.0.0-beta.18",
4
+ "version": "3.0.0-beta.19",
5
5
  "upgrade": "v2-to-v3",
6
6
  "guide": "ai/guides/migration/v2-to-v3.md",
7
7
  "summary": {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "library": "bitboss-ui",
4
- "version": "3.0.0-beta.18",
5
- "generatedAt": "2026-08-31T15:46:07.123Z",
4
+ "version": "3.0.0-beta.19",
5
+ "generatedAt": "2026-09-01T10:39:24.755Z",
6
6
  "composables": [
7
7
  {
8
8
  "name": "useBbConfig",
@@ -1233,7 +1233,7 @@
1233
1233
  "type": "boolean | undefined",
1234
1234
  "required": false,
1235
1235
  "default": "false",
1236
- "description": "Enforces that `modelValue` contains only values present in `items`.\nIncoherent values are removed and `update:modelValue` is emitted with a\ncoherent array. Please check out {@link https://ui-components-docs.vercel.app/it/guides/coherence the docs} for more information."
1236
+ "description": "Enforces that `modelValue` contains only values present in `items`.\nIncoherent values are removed and `update:modelValue` is emitted with a\ncoherent array. Please check out {@link https://ui-components.bitboss.it/it/guides/coherence the docs} for more information."
1237
1237
  },
1238
1238
  {
1239
1239
  "name": "hasErrors",
@@ -4140,7 +4140,7 @@
4140
4140
  "type": "boolean | undefined",
4141
4141
  "required": false,
4142
4142
  "default": "false",
4143
- "description": "Enforces that `modelValue` contains only values present in `items`.\nIncoherent values are removed and `update:modelValue` is emitted with a\ncoherent array. Please check out {@link https://ui-components-docs.vercel.app/it/guides/coherence the docs} for more information."
4143
+ "description": "Enforces that `modelValue` contains only values present in `items`.\nIncoherent values are removed and `update:modelValue` is emitted with a\ncoherent array. Please check out {@link https://ui-components.bitboss.it/it/guides/coherence the docs} for more information."
4144
4144
  },
4145
4145
  {
4146
4146
  "name": "hasErrors",
@@ -8043,6 +8043,12 @@
8043
8043
  "default": "`config.adaptive` (`true`)",
8044
8044
  "description": "Adapt the surface to the viewport: render as a `BbOffCanvas` drawer on\nmobile instead of a centered dialog. The surface is resolved when the\ndialog opens and stays fixed for that session: resizing across the mobile\nbreakpoint while open never swaps it.\n\nWhen unset, falls back to the global `config.adaptive`."
8045
8045
  },
8046
+ {
8047
+ "name": "description",
8048
+ "type": "string | undefined",
8049
+ "required": false,
8050
+ "description": "One short line under the title, in the header — the shadcn\n`DialogDescription` shape: same block as the title, lower hierarchy,\nmuted.\n\nIt is also what `aria-describedby` points at. That wiring was dropped in\nv3 along with the old `description` prop, on the reasoning that pointing\nit at arbitrary body content would read whole forms aloud — true, and the\nreason it aims at THIS element only. Keep it to a sentence; anything\nlonger belongs in the body."
8051
+ },
8046
8052
  {
8047
8053
  "name": "disabled",
8048
8054
  "type": "boolean | undefined",
@@ -8151,6 +8157,11 @@
8151
8157
  "type": "BbDialogDefaultSlotProps",
8152
8158
  "description": "Primary body content of the dialog, rendered in the scrollable body area."
8153
8159
  },
8160
+ {
8161
+ "name": "description",
8162
+ "type": "BbDialogDescriptionSlotProps",
8163
+ "description": "Replaces the description text under the title. Renders the header even\nwhen `title` and `description` are unset — a slotted description is a\ndescription, the same rule `#title` follows."
8164
+ },
8154
8165
  {
8155
8166
  "name": "footer",
8156
8167
  "type": "object",
@@ -8195,16 +8206,6 @@
8195
8206
  "kind": "manual"
8196
8207
  }
8197
8208
  },
8198
- {
8199
- "name": "description",
8200
- "new": null,
8201
- "since": "3.0.0-unreleased",
8202
- "migration": "ai/guides/migration/components/bb-dialog.md",
8203
- "silent": true,
8204
- "fix": {
8205
- "kind": "manual"
8206
- }
8207
- },
8208
8209
  {
8209
8210
  "name": "hideHeader",
8210
8211
  "new": null,
@@ -9437,6 +9438,12 @@
9437
9438
  "contract": "ai/BbOffCanvas.md",
9438
9439
  "source": "ai/source/BbOffCanvas.md",
9439
9440
  "props": [
9441
+ {
9442
+ "name": "description",
9443
+ "type": "string | undefined",
9444
+ "required": false,
9445
+ "description": "One short line under the title, in the header — the shadcn\n`DialogDescription` shape: same block as the title, lower hierarchy,\nmuted.\n\nIt is also what `aria-describedby` points at. That wiring was dropped in\nv3 along with the old `description` prop, on the reasoning that pointing\nit at arbitrary body content would read whole forms aloud — true, and the\nreason it aims at THIS element only. Keep it to a sentence; anything\nlonger belongs in the body."
9446
+ },
9440
9447
  {
9441
9448
  "name": "disabled",
9442
9449
  "type": "boolean | undefined",
@@ -9560,6 +9567,11 @@
9560
9567
  "type": "{ close: () => void; }",
9561
9568
  "description": "Primary body content of the off-canvas panel, rendered in the scrollable body area."
9562
9569
  },
9570
+ {
9571
+ "name": "description",
9572
+ "type": "BbOffCanvasDescriptionSlotProps",
9573
+ "description": "Replaces the description text under the title. Renders the header even\nwhen `title` and `description` are unset — a slotted description is a\ndescription, the same rule `#title` follows."
9574
+ },
9563
9575
  {
9564
9576
  "name": "footer",
9565
9577
  "type": "object",
@@ -9594,16 +9606,6 @@
9594
9606
  "kind": "manual"
9595
9607
  }
9596
9608
  },
9597
- {
9598
- "name": "description",
9599
- "new": null,
9600
- "since": "3.0.0-unreleased",
9601
- "migration": "ai/guides/migration/components/bb-offcanvas.md",
9602
- "silent": true,
9603
- "fix": {
9604
- "kind": "manual"
9605
- }
9606
- },
9607
9609
  {
9608
9610
  "name": "direction",
9609
9611
  "new": "side",
@@ -53,12 +53,53 @@ That is the route to prefer. It is right before hydration, so it is what
53
53
  crawlers index, what a prerendered search index reads, and what a reader with
54
54
  JS disabled keeps.
55
55
 
56
- **`setConfig` during a server render is the fallback, and it has one rule:**
57
- call it from a component's `setup` (or a composable a component calls), where
58
- it can resolve the current app. Called from module scope there is no app to
59
- address, so the write lands on the process-global build-time defaults and
60
- leaks into other visitors being served concurrently it warns in dev when it
61
- detects this.
56
+ #### Nuxt
57
+
58
+ **Do not call `app.use(bitbossUiPlugin)` in Nuxt** the `bitboss-ui/nuxt`
59
+ module registers the runtime plugin for you, and that plugin provides the
60
+ per-app config itself. `defineNuxtPlugin` runs per request on the server and
61
+ Nuxt builds one Vue app per request, so your own plugin is the per-request
62
+ seam:
63
+
64
+ ```ts
65
+ // plugins/bitboss-locale.ts
66
+ import { useBbConfig } from 'bitboss-ui';
67
+
68
+ export default defineNuxtPlugin((nuxtApp) => {
69
+ const locale = localeFor(useRequestEvent());
70
+ // A plugin has an APP but no component instance, so hand it the app's
71
+ // injection context — otherwise the write cannot find this app's config
72
+ // and lands on the process-global defaults.
73
+ nuxtApp.vueApp.runWithContext(() => {
74
+ useBbConfig().setConfig({ locale });
75
+ });
76
+ });
77
+ ```
78
+
79
+ Calling `setConfig` from a component's `setup` works in Nuxt too, and is
80
+ per-request for the same reason. `runWithContext` is only needed where no
81
+ component exists yet.
82
+
83
+ > Fixed in 3.0.0-beta.19 (Q20.4). Before that the Nuxt runtime plugin never
84
+ > provided the config, so **every** `setConfig` under Nuxt — including from a
85
+ > component's `setup`, the path this page prescribed — wrote the process-global
86
+ > and leaked across concurrent visitors, silently. If you worked around it,
87
+ > you can stop.
88
+
89
+ #### The rule, and what the dev warning means
90
+
91
+ **`setConfig` during a server render needs an app to address.** From a
92
+ component's `setup` (or a composable a component calls), or inside
93
+ `app.runWithContext`, it resolves the current app and the write is
94
+ per-visitor. With neither — module scope, say — there is no app, the write
95
+ lands on the process-global build-time defaults, and it leaks into other
96
+ visitors being served concurrently.
97
+
98
+ The dev warning fires on exactly that: it compares where the write actually
99
+ landed against the build-time object, rather than guessing from the call site.
100
+ So it stays silent for a correct per-app write, and it still fires for a
101
+ global one made from inside a component — which is what a missing plugin (or,
102
+ before beta.19, Nuxt) produced.
62
103
 
63
104
  On the client none of this matters: one process is one user, and `setConfig`
64
105
  from anywhere is fine.
@@ -14,13 +14,18 @@ Knowledge base root (installed package):
14
14
  Package export entry: `bitboss-ui/ai` → `dist/ai/index.md`.
15
15
  Subpaths: `bitboss-ui/ai/components.json`, `bitboss-ui/ai/guides/…`, etc.
16
16
 
17
- **Not installed, or can only fetch URLs?** The same files are served from the
18
- published package at `https://cdn.jsdelivr.net/npm/bitboss-ui@beta/dist/ai/…`.
19
- If you can make only one request, fetch
20
- [`…@beta/dist/llms-medium.txt`](https://cdn.jsdelivr.net/npm/bitboss-ui@beta/dist/llms-medium.txt)
17
+ **Not installed, or can only fetch URLs?** The same files are served at
18
+ `https://llms-bitboss-ui.bitboss.it/dist/ai/…`. If you can make only one
19
+ request, fetch
20
+ [`/dist/llms-medium.txt`](https://llms-bitboss-ui.bitboss.it/dist/llms-medium.txt)
21
21
  — this router plus the agent contract, setup, component picker, design language
22
22
  and the full component catalogue, in ~110 KB.
23
23
 
24
+ That host is a thin proxy in front of the published npm tarball. If it is ever
25
+ unreachable, the same paths work directly against
26
+ `https://cdn.jsdelivr.net/npm/bitboss-ui@beta/…` — one hop further from us, and
27
+ pinned to the prerelease channel rather than following it.
28
+
24
29
  ---
25
30
 
26
31
  ## Mandatory order (every UI task)
@@ -58,7 +58,7 @@ The corollary: **`prefill` cannot tell you an initial load from a dependency ref
58
58
 
59
59
  ### The `prefill` prop (`BbSelect` / `BbSelectPopover`)
60
60
 
61
- `prefill` controls **when** the initial load happens: `'interaction'` (default) waits for the first user interaction, `true` loads on mount (SSR-friendly — it also runs in `onServerPrefetch`), `false` is **search-first** — nothing loads until the user types, so opening the panel fetches nothing and the list shows a "search to begin" hint until the first query. Groups and tables always load on mount.
61
+ `prefill` controls **when** the initial load happens: `'interaction'` (default) waits for the first user interaction, `true` loads on mount (SSR-friendly — under `bitboss-ui/nuxt` it also runs in `onServerPrefetch` and the result is transferred to the client; see [SSR prefill](./ssr-prefill.md)), `false` is **search-first** — nothing loads until the user types, so opening the panel fetches nothing and the list shows a "search to begin" hint until the first query. Groups and tables always load on mount.
62
62
 
63
63
  Reach for `false` when the result set is too large or expensive to browse and the query is the only sensible entry point. Note that `false` and `'interaction'` differ **only** on this point: both skip the mount load, but `'interaction'` fetches on open and `false` waits for a query.
64
64