bitboss-ui 3.0.0-alpha.6 → 3.0.0-beta.0

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/bin/bitboss-ui.mjs +0 -123
  2. package/dist/ai/BbBaseRating.md +1 -0
  3. package/dist/ai/BbDatePickerInput.md +7 -3
  4. package/dist/ai/BbIcon.md +6 -1
  5. package/dist/ai/BbNumberInput.md +5 -2
  6. package/dist/ai/BbTextInput.md +5 -3
  7. package/dist/ai/BbTextarea.md +5 -3
  8. package/dist/ai/BbToast.md +3 -1
  9. package/dist/ai/changelog.json +1 -1
  10. package/dist/ai/components.json +66 -7
  11. package/dist/ai/guides/agent-contract.md +3 -4
  12. package/dist/ai/guides/component-picker.md +7 -8
  13. package/dist/ai/guides/design-tokens.md +45 -0
  14. package/dist/ai/guides/installation-and-plugin-setup.md +15 -54
  15. package/dist/ai/guides/migration/components/bb-icon.md +26 -0
  16. package/dist/ai/guides/migration/components/bb-intersection.md +70 -0
  17. package/dist/ai/guides/migration/components/bb-tooltip.md +32 -7
  18. package/dist/ai/guides/migration/components/use-broadcast-channel-instance.md +56 -0
  19. package/dist/ai/guides/migration/components/use-confirm.md +18 -10
  20. package/dist/ai/guides/migration/v2-to-v3.md +34 -2
  21. package/dist/ai/guides/validated-forms.md +18 -4
  22. package/dist/ai/index.md +3 -2
  23. package/dist/ai/source/BbBadge.md +28 -1
  24. package/dist/ai/source/BbBaseRadioIcon.md +1 -1
  25. package/dist/ai/source/BbBaseRating.md +36 -2
  26. package/dist/ai/source/BbBaseSelect.md +33 -4
  27. package/dist/ai/source/BbBaseSwitchIcon.md +6 -2
  28. package/dist/ai/source/BbCheckboxGroup.md +2 -2
  29. package/dist/ai/source/BbIndicator.md +15 -1
  30. package/dist/ai/source/BbRadioGroup.md +2 -2
  31. package/dist/ai/source/BbRating.md +16 -3
  32. package/dist/ai/source/BbSwitchGroup.md +2 -2
  33. package/dist/ai/source/BbToast.md +10 -4
  34. package/dist/ai/source/BbToastPortal.md +10 -4
  35. package/dist/ai/source/BbTooltip.md +11 -2
  36. package/dist/ai/source/ChipsBox.md +24 -0
  37. package/dist/ai/source/CommaBox.md +16 -0
  38. package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +56 -48
  39. package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +33 -25
  40. package/dist/components/BbBaseRating/types.d.ts +8 -0
  41. package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +35 -32
  42. package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +28 -26
  43. package/dist/components/BbTooltip/BbTooltip.vue.d.ts +1 -1
  44. package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +64 -61
  45. package/dist/components/CommaBox/CommaBox.vue_vue_type_script_setup_true_lang.js +62 -59
  46. package/dist/composables/useConfirm.js +6 -4
  47. package/dist/composables/useLocale.js +2 -2
  48. package/dist/composables/useToast.js +37 -33
  49. package/dist/deprecation/ai-deprecations.json.d.ts +44 -0
  50. package/dist/deprecation/ai-deprecations.json.js +1 -1
  51. package/dist/index.d.ts +1 -2
  52. package/dist/index.js +50 -51
  53. package/dist/llms-full.txt +359 -199
  54. package/dist/llms-medium.txt +25 -66
  55. package/dist/llms.txt +3 -2
  56. package/dist/styles.css +1 -1
  57. package/dist/types/Config.d.ts +8 -17
  58. package/dist/validated/useValidatedField.js +19 -19
  59. package/dist/vite-plugin.d.ts +3 -25
  60. package/dist/vite.js +116 -158
  61. package/llms.txt +3 -2
  62. package/package.json +5 -5
  63. package/scripts/lib/hand-roll-hints.mjs +29 -24
  64. package/scripts/lib/validate-bb-markup.mjs +118 -1
  65. package/dist/ai/composables/useBroadcastChannelInstance.md +0 -97
  66. package/dist/composables/useBroadcastChannelInstance.d.ts +0 -14
  67. package/dist/composables/useBroadcastChannelInstance.js +0 -39
@@ -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`/
@@ -36,7 +35,6 @@ import {
36
35
  writeFileSync,
37
36
  } from 'node:fs';
38
37
  import { findHandRollHints } from '../scripts/lib/hand-roll-hints.mjs';
39
- import { createRequire } from 'node:module';
40
38
  import { dirname, isAbsolute, join, relative } from 'node:path';
41
39
  import { fileURLToPath } from 'node:url';
42
40
  import { parseArgs } from 'node:util';
@@ -285,112 +283,6 @@ function aiInit({ mcp = false } = {}) {
285
283
  }
286
284
  }
287
285
 
288
- /**
289
- * Serialize the blueprint dictionary as a readable TS object literal
290
- * (single-quoted, tab-indented, matching the library's own locale files).
291
- */
292
- function localeObjectLiteral(dictionary) {
293
- const quote = (s) =>
294
- `'${String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
295
- const sections = Object.entries(dictionary)
296
- .map(([section, entries]) => {
297
- const lines = Object.entries(entries)
298
- .map(([key, value]) => `\t\t${key}: ${quote(value)},`)
299
- .join('\n');
300
- return `\t${section}: {\n${lines}\n\t},`;
301
- })
302
- .join('\n');
303
- return `{\n${sections}\n}`;
304
- }
305
-
306
- function localeBlueprint(key) {
307
- const projectRoot = process.cwd();
308
-
309
- if (!key) {
310
- console.error(
311
- '[bitboss-ui] Usage: npx bitboss-ui locale <key> (e.g. `npx bitboss-ui locale nl`)'
312
- );
313
- process.exit(1);
314
- }
315
- // Keys double as dayjs module names (`dayjs/locale/<key>`) — keep them tame.
316
- if (!/^[a-z0-9-]+$/.test(key)) {
317
- console.error(
318
- `[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').`
319
- );
320
- process.exit(1);
321
- }
322
-
323
- const blueprintPath = join(PACKAGE_ROOT, 'dist', 'locale-blueprint.json');
324
- if (!existsSync(blueprintPath)) {
325
- console.error(
326
- '[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).'
327
- );
328
- process.exit(1);
329
- }
330
- const { builtins, template } = JSON.parse(
331
- readFileSync(blueprintPath, 'utf-8')
332
- );
333
-
334
- if (builtins.includes(key)) {
335
- console.error(
336
- `[bitboss-ui] '${key}' is a built-in locale — built-ins cannot be overridden; the \`additionalLocales\` option only adds new ones. Just set \`locale: '${key}'\`.`
337
- );
338
- process.exit(1);
339
- }
340
-
341
- const outPath = join(projectRoot, `${key}.locale.ts`);
342
- if (existsSync(outPath)) {
343
- console.error(
344
- `[bitboss-ui] ${key}.locale.ts already exists — delete it first if you want a fresh blueprint.`
345
- );
346
- process.exit(1);
347
- }
348
-
349
- // Same check the vite plugin performs at build time — surface it here too,
350
- // while the consumer is still choosing the key.
351
- let dayjsHint = `Calendar (dayjs) locale '${key}' found — calendars will follow automatically.`;
352
- try {
353
- // Consumer's own resolution first; fall back to the installed package's
354
- // (dayjs is a bitboss-ui dependency, so it's always reachable from here).
355
- try {
356
- createRequire(join(projectRoot, 'package.json')).resolve(
357
- `dayjs/locale/${key}.js`
358
- );
359
- } catch {
360
- createRequire(join(PACKAGE_ROOT, 'package.json')).resolve(
361
- `dayjs/locale/${key}.js`
362
- );
363
- }
364
- } catch {
365
- 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`;
366
- }
367
-
368
- writeFileSync(
369
- outPath,
370
- `import type { LocaleOverrides } from 'bitboss-ui';
371
-
372
- /**
373
- * bitboss-ui locale blueprint for '${key}' — generated by \`npx bitboss-ui locale ${key}\`.
374
- *
375
- * Translate the English strings below ({0}/{1} placeholders are interpolated
376
- * arguments — keep them). Keys you delete fall back per-key to the library's
377
- * default locale. Register it in your vite config:
378
- *
379
- * import ${key.replace(/-/g, '_')} from './${key}.locale';
380
- * bitbossUi({ iconDir: '...', locale: '${key}', additionalLocales: { '${key}': ${key.replace(/-/g, '_')} } })
381
- *
382
- * The plugin regenerates \`locale-registry.d.ts\` on build start, making
383
- * '${key}' a valid \`locale\` value everywhere (run dev once after adding it).
384
- */
385
- export default ${localeObjectLiteral(template)} satisfies LocaleOverrides;
386
- `,
387
- 'utf-8'
388
- );
389
-
390
- console.log(`[bitboss-ui] Wrote ${key}.locale.ts (English blueprint).`);
391
- console.log(` ${dayjsHint}`);
392
- }
393
-
394
286
  // SNT-007: a bare-segment skip list of `node_modules`/`dist`/`.git` let a
395
287
  // default (no-glob) `check` sweep straight through a Laravel-style consumer's
396
288
  // `vendor/`, `storage/`, and `public/` trees — hundreds of vendored
@@ -724,9 +616,6 @@ Commands:
724
616
  mcp Start a stdio MCP server exposing the dist/ai knowledge
725
617
  base (search_components, get_component, list_recipes,
726
618
  get_recipe, validate) to MCP-capable agent harnesses.
727
- locale <key> Generate <key>.locale.ts at the project root — an English
728
- blueprint dictionary to translate and pass to the plugin's
729
- \`additionalLocales\` option (additions only; built-ins are refused).
730
619
  help Show this help.
731
620
  `);
732
621
  }
@@ -804,18 +693,6 @@ switch (command) {
804
693
  await main();
805
694
  break;
806
695
  }
807
- case 'locale': {
808
- const { positionals } = parseCommandArgs(
809
- command,
810
- commandArgs,
811
- {},
812
- {
813
- allowPositionals: true,
814
- }
815
- );
816
- localeBlueprint(positionals[0]);
817
- break;
818
- }
819
696
  case 'help':
820
697
  case '--help':
821
698
  case '-h':
@@ -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. |
@@ -33,9 +33,13 @@ date (see _Time selection_). `label` is required.
33
33
 
34
34
  The shared surface applies:
35
35
 
36
- - **`label`** (required) doubles as the default `name`; **`hideLabel`** hides it
37
- visually while keeping the accessible name; **`labelPosition`** aligns it; the
38
- **`label` slot** replaces it with `{ text, hasErrors }`.
36
+ - **`label`** (required) names the field for display and accessibility only —
37
+ it has **no** effect on the native `name` attribute of the segment inputs.
38
+ Leave `name` unset and the rendered segments carry none at all, so a plain
39
+ form post silently drops the value; pass `name` explicitly whenever it's
40
+ submitted via a native form or read from `FormData`. **`hideLabel`** hides
41
+ it visually while keeping the accessible name; **`labelPosition`** aligns
42
+ it; the **`label` slot** replaces it with `{ text, hasErrors }`.
39
43
  - **`labelMode`** — `outside` / `floating` / `inside`. When unset it falls back
40
44
  to the project-wide `config.defaultInputLabelMode` (library default:
41
45
  `outside`), the same resolution as `BbTextInput` and `BbNumberInput`.
package/dist/ai/BbIcon.md CHANGED
@@ -135,7 +135,12 @@ The `icon` prop takes a name in one of three forms:
135
135
  - **`local:name`** — an SVG in the configured `iconDir` (brand marks, custom
136
136
  illustrations). Each `.svg` file becomes `local:<basename>`.
137
137
  - **bare `name`** — an unprefixed name **normalizes to `local:`**; it never
138
- searches provider sets.
138
+ searches provider sets. Prefer writing the prefix anyway: the Iconify editor
139
+ extension keys off `prefix:name`, so `local:user-circle` gets an inline
140
+ preview and name autocomplete in the template while a bare `user-circle`
141
+ reads as an ordinary string and gets neither. It also puts your own art in
142
+ the same shape as `mdi:home` / `lucide:check`, so the source of every icon is
143
+ legible without inferring it from a missing prefix.
139
144
 
140
145
  Discovery-first: **only use names you have verified exist** — a missing icon
141
146
  **throws at mount** (dev shows an actionable Vite overlay with the exact
@@ -36,8 +36,11 @@ and locale-separator quirks of native number inputs don't apply here.
36
36
 
37
37
  ### Label & label modes
38
38
 
39
- `label` is required and doubles as the default `name`. The shared label controls
40
- apply:
39
+ `label` is required, but it names the field for display and accessibility
40
+ only — it has **no** effect on the native `name` attribute. Leave `name`
41
+ unset and the rendered `<input>` carries none at all, so a plain form post
42
+ silently drops the value; pass `name` explicitly whenever it's submitted via
43
+ a native form or read from `FormData`. The shared label controls apply:
41
44
 
42
45
  - **`labelMode`** sets where the label sits: `outside` (default, above),
43
46
  `floating` (rests inside like a placeholder, lifts on focus/fill) or `inside`
@@ -29,9 +29,11 @@ Pick a sibling when the shape of the data is different:
29
29
 
30
30
  ### Label & label modes
31
31
 
32
- `label` names the field and doubles as the default `name` (lowercased,
33
- accents stripped, spaces removed) pass `name` explicitly when the value is
34
- posted to a server.
32
+ `label` names the field for display and accessibility only it has **no**
33
+ effect on the native `name` attribute. Leave `name` unset and the rendered
34
+ `<input>` carries none at all, so a plain form post silently drops the value.
35
+ Pass `name` explicitly whenever the value is submitted via a native form or
36
+ read from `FormData`.
35
37
 
36
38
  - **`labelMode`** sets where the label sits: `outside` (above the field),
37
39
  `floating` (rests inside like a placeholder at reduced opacity, lifts on
@@ -30,9 +30,11 @@ Pick a sibling when the shape of the data is different:
30
30
 
31
31
  ### Label
32
32
 
33
- `label` names the field and doubles as the default `name` (lowercased, accents
34
- stripped, spaces removed) pass `name` explicitly when the value is posted to a
35
- server.
33
+ `label` names the field for display and accessibility only it has **no**
34
+ effect on the native `name` attribute. Leave `name` unset and the rendered
35
+ `<textarea>` carries none at all, so a plain form post silently drops the
36
+ value. Pass `name` explicitly whenever the value is submitted via a native
37
+ form or read from `FormData`.
36
38
 
37
39
  - **`hideLabel`** keeps the label for screen readers but removes it visually.
38
40
  - **`labelPosition`** aligns the label text `left` / `center` / `right`.
@@ -165,7 +165,9 @@ Three things worth internalising:
165
165
  - **Variants tint the icon and set announcement priority — nothing else.** A
166
166
  variant toast without an `icon` looks identical to a default one, so pass a
167
167
  matching icon (`lucide:circle-check` for success, `lucide:circle-x` for
168
- destructive…) whenever the tone matters visually. No icon is ever implied.
168
+ destructive…) whenever the tone matters visually. No icon is ever implied
169
+ dev builds warn when a built-in variant is used without one, since the tone
170
+ is otherwise dropped with no trace.
169
171
  - `toast(...)` returns the message `id`. Keep it to `update(...)` or
170
172
  `dismiss(...)` that toast later; `dismiss()` with no argument clears the
171
173
  whole stack.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "library": "bitboss-ui",
4
- "version": "3.0.0-alpha.6",
4
+ "version": "3.0.0-beta.0",
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-alpha.6",
5
- "generatedAt": "2026-08-07T15:42:40.421Z",
4
+ "version": "3.0.0-beta.0",
5
+ "generatedAt": "2026-08-24T15:06:25.249Z",
6
6
  "composables": [
7
7
  {
8
8
  "name": "useBbConfig",
@@ -24,11 +24,6 @@
24
24
  "description": "Read and drive a BbTabs/BbTabsRoot group from anywhere by id — writable current tab, goTo/select, no template refs.",
25
25
  "doc": "ai/composables/useBbTabsContext.md"
26
26
  },
27
- {
28
- "name": "useBroadcastChannelInstance",
29
- "description": "Typed cross-tab messaging via the BroadcastChannel API with per-component subscriber lifecycle.",
30
- "doc": "ai/composables/useBroadcastChannelInstance.md"
31
- },
32
27
  {
33
28
  "name": "useConfirm",
34
29
  "description": "Promise-based confirm dialogs — await the user's yes/no, with yes/no button configs, async onClick busy states, and portal content. Requires exactly one <BbConfirm /> host, mounted at the app root.",
@@ -243,6 +238,16 @@
243
238
  "fix": {
244
239
  "kind": "invert"
245
240
  }
241
+ },
242
+ {
243
+ "name": "theme",
244
+ "new": "variant",
245
+ "since": "3.0.0-unreleased",
246
+ "migration": "ai/guides/migration/components/bb-alert.md",
247
+ "silent": true,
248
+ "fix": {
249
+ "kind": "rename"
250
+ }
246
251
  }
247
252
  ],
248
253
  "renamed": [
@@ -254,6 +259,15 @@
254
259
  "fix": {
255
260
  "kind": "invert"
256
261
  }
262
+ },
263
+ {
264
+ "from": "theme",
265
+ "to": "variant",
266
+ "since": "3.0.0-unreleased",
267
+ "migration": "ai/guides/migration/components/bb-alert.md",
268
+ "fix": {
269
+ "kind": "rename"
270
+ }
257
271
  }
258
272
  ]
259
273
  },
@@ -3020,6 +3034,12 @@
3020
3034
  "required": false,
3021
3035
  "description": "Id(s) of elements describing this rating input for assistive technologies."
3022
3036
  },
3037
+ {
3038
+ "name": "ariaLabelledby",
3039
+ "type": "string | undefined",
3040
+ "required": false,
3041
+ "description": "Id(s) of the element(s) that NAME this rating group. Supplying one is what\npromotes the inner container to `role=\"radiogroup\"` (Q4.5): an unnamed\n`radiogroup` announces as a bare \"radio group\" and is no better than the\nrole-less `<span>` it replaces, so the role is withheld until a name\nsource exists. `BbRating` wires this to its `<legend>`."
3042
+ },
3023
3043
  {
3024
3044
  "name": "autofocus",
3025
3045
  "type": "Booleanish | undefined",
@@ -15246,6 +15266,36 @@
15246
15266
  }
15247
15267
  ],
15248
15268
  "removed": [
15269
+ {
15270
+ "name": "block",
15271
+ "new": null,
15272
+ "since": "3.0.0-unreleased",
15273
+ "migration": "ai/guides/migration/components/bb-tooltip.md",
15274
+ "silent": true,
15275
+ "fix": {
15276
+ "kind": "remove"
15277
+ }
15278
+ },
15279
+ {
15280
+ "name": "showClose",
15281
+ "new": null,
15282
+ "since": "3.0.0-unreleased",
15283
+ "migration": "ai/guides/migration/components/bb-tooltip.md",
15284
+ "silent": true,
15285
+ "fix": {
15286
+ "kind": "remove"
15287
+ }
15288
+ },
15289
+ {
15290
+ "name": "theme",
15291
+ "new": "variant",
15292
+ "since": "3.0.0-unreleased",
15293
+ "migration": "ai/guides/migration/components/bb-tooltip.md",
15294
+ "silent": true,
15295
+ "fix": {
15296
+ "kind": "rename"
15297
+ }
15298
+ },
15249
15299
  {
15250
15300
  "name": "timeout",
15251
15301
  "new": "delay",
@@ -15258,6 +15308,15 @@
15258
15308
  }
15259
15309
  ],
15260
15310
  "renamed": [
15311
+ {
15312
+ "from": "theme",
15313
+ "to": "variant",
15314
+ "since": "3.0.0-unreleased",
15315
+ "migration": "ai/guides/migration/components/bb-tooltip.md",
15316
+ "fix": {
15317
+ "kind": "rename"
15318
+ }
15319
+ },
15261
15320
  {
15262
15321
  "from": "timeout",
15263
15322
  "to": "delay",
@@ -234,11 +234,10 @@ twice.
234
234
  Symptoms that mean stop: an `<img>` with an initials/icon fallback →
235
235
  `BbAvatar` (**circle-only** — a rounded-square avatar really is yours to build);
236
236
  a hand-written spin animation → `BbSpinner`; a tinted `<span>` pill standing for
237
- a status → `BbBadge` with a registered `soft-*` variant; a raw
238
- `new BroadcastChannel(…)` → `useBroadcastChannelInstance`. Full symptom index:
237
+ a status → `BbBadge` with a registered `soft-*` variant. Full symptom index:
239
238
  [Component Picker](./component-picker.md) § Am I about to rebuild one of ours?
240
239
 
241
- `npx bitboss-ui check` prints the avatar, spinner and `BroadcastChannel` shapes
240
+ `npx bitboss-ui check` prints the avatar and spinner shapes
242
241
  as **advisory hints** — never a build failure, because a round `<img>` is
243
242
  sometimes a logo — so that much backstops itself on files it scans. The badge
244
243
  case is not decidable from markup and has no hint.
@@ -255,4 +254,4 @@ case is not decidable from markup and has no hint.
255
254
  - [ ] Uses declarative coherence/dependencies before manual watchers.
256
255
  - [ ] Mounts exactly one `<BbToast />` and one `<BbConfirm />` host, at the app root — never inside a page or component.
257
256
  - [ ] Confirmed the plugin's `locale` is set explicitly if the app isn't Italian (default is `'it'`) — see Setup-First Rule.
258
- - [ ] Ran the Rebuild Check over the hand-written markup — nothing reimplements `BbAvatar`, `BbSpinner`, `BbBadge` or `useBroadcastChannelInstance`.
257
+ - [ ] Ran the Rebuild Check over the hand-written markup — nothing reimplements `BbAvatar`, `BbSpinner` or `BbBadge`.
@@ -22,12 +22,11 @@ this picker four times and hand-rolled an avatar twice while `BbAvatar` sat in
22
22
  the Data Display table below. So this section is indexed by **symptom**: match it
23
23
  against the markup you are about to write, not against a need.
24
24
 
25
- | You are about to write | Stop — use |
26
- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
- | An `<img>` with an initials/icon fallback branch, `border-radius: 50%`, `object-fit: cover` | [BbAvatar](../BbAvatar.md) — `src`/`alt`/size, initials slot, icon fallback, load-error handling |
28
- | `@keyframes spin` / `animation: … spin` on an element that means "waiting" | [BbSpinner](../BbSpinner.md) — sized off the control scale, honours `prefers-reduced-motion` |
29
- | A `<span>` pill with a tinted background standing for a status | [BbBadge](../BbBadge.md) with a registered `soft-*` variant (design-language § Getting soft semantic badges) |
30
- | `new BroadcastChannel('…')` for cross-tab sync | [useBroadcastChannelInstance](../composables/useBroadcastChannelInstance.md) — a typed wrapper: each call opens its own channel and removes its listeners on unmount |
25
+ | You are about to write | Stop — use |
26
+ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
27
+ | An `<img>` with an initials/icon fallback branch, `border-radius: 50%`, `object-fit: cover` | [BbAvatar](../BbAvatar.md) — `src`/`alt`/size, initials slot, icon fallback, load-error handling |
28
+ | `@keyframes spin` / `animation: … spin` on an element that means "waiting" | [BbSpinner](../BbSpinner.md) — sized off the control scale, honours `prefers-reduced-motion` |
29
+ | A `<span>` pill with a tinted background standing for a status | [BbBadge](../BbBadge.md) with a registered `soft-*` variant (design-language § Getting soft semantic badges) |
31
30
 
32
31
  **`BbAvatar` is circle-only.** Its `border-radius: 50%` is in the component's own
33
32
  CSS and no prop changes it, so a rounded-**square** avatar genuinely has no answer
@@ -35,7 +34,7 @@ here — build that one yourself. Everything else about it (fallbacks, load erro
35
34
  sizing) still applies, so the honest options are "use it and accept a circle" or
36
35
  "hand-roll the square one knowingly", not "hand-roll it because you never saw it".
37
36
 
38
- `npx bitboss-ui check` prints the avatar, spinner and `BroadcastChannel` rows as
37
+ `npx bitboss-ui check` prints the avatar and spinner rows as
39
38
  **advisory hints** — never a build failure, since a round `<img>` is sometimes a
40
39
  logo — on every `.vue` file it scans. That is the backstop: it fires without
41
40
  anyone thinking to ask. The badge case is not decidable from markup (a tinted
@@ -244,7 +243,7 @@ compose it or write it yourself.
244
243
  | `BbTab` (singular) | The consumer API is [BbTabs](../BbTabs.md) / `BbTabsRoot`. `BbTab` appears only as the **v2 "before" side** of the migration diffs. |
245
244
  | `BbCard` | Does not exist — a card is a `<div>` with tokens. See [design-language](./design-language.md). |
246
245
  | `BbList` / `BbListItem` / `BbDescriptionList` | Do not exist — a list row is an `<li>` with tokens, and [BbBaseButton](../BbBaseButton.md) makes the whole row navigable. See the note under Data Display. |
247
- | `useLiveFeed` / any polling or cross-tab feed helper | Does not exist. The cross-tab half is [useBroadcastChannelInstance](../composables/useBroadcastChannelInstance.md) (one shared channel per name); the polling and the merge are yours. |
246
+ | `useLiveFeed` / any polling or cross-tab feed helper | Does not exist, and neither does a cross-tab helper `useBroadcastChannelInstance` was removed (cross-tab messaging is not a UI concern). Use `useBroadcastChannel` from `@vueuse/core`; the polling and the merge are yours. |
248
247
  | `BbStepper` / `BbWizard` | Does not exist as a component; the wizard-form recipe composes one. |
249
248
  | `BbSkeleton` (standalone) | Only `BbTable` ships skeletons, via the column `skeleton` field. |
250
249
  | `BbCommand` / command palette | Does not exist. Compose `BbDialog` + `BbSelectPopover`. |
@@ -295,6 +295,51 @@ Component tokens are still real affordances: a consumer can retune one
295
295
  instance (`<BbTabs style="--list-h: 40px">`) or a whole page scope. They are
296
296
  just not global namespace.
297
297
 
298
+ ### Overriding one from a consumer app
299
+
300
+ Three rules, in order of how often they are broken.
301
+
302
+ **1. Override only what you actually render.** Several components expose a
303
+ per-size lookup table (`--icon-size-xs … --icon-size-2xl`, `--h-xs … --h-2xl`)
304
+ plus an _active_ token the size class re-points. Setting the whole family
305
+ because you want one value is not "thorough" — it silently opts you out of the
306
+ size scale for variants you may start using later, and it buries the one line
307
+ that mattered. Retune the sizes you use:
308
+
309
+ ```css
310
+ /* we render sm and md buttons; leave the rest on the library's scale */
311
+ .bb-button {
312
+ --icon-size-sm: 12px;
313
+ --icon-size-md: 12px;
314
+ }
315
+ ```
316
+
317
+ **2. Override the per-size entry, not the active token.** The active token is
318
+ re-resolved by the size class, which is _more specific_ than the root class:
319
+
320
+ ```css
321
+ .bb-button {
322
+ --icon-size: var(--icon-size-md);
323
+ } /* 0,1,0 */
324
+ .bb-button--sm {
325
+ --icon-size: var(--icon-size-sm);
326
+ } /* 0,2,0 — nested */
327
+ ```
328
+
329
+ So `.bb-button { --icon-size: 12px }` appears to work on default buttons and
330
+ silently does nothing on every `size="sm"` one — the confusing half-failure that
331
+ sends people to the shotgun in rule 1. The per-size entry has no competing
332
+ declaration, so it always wins. (A scope selector of equal-or-greater
333
+ specificity also works, but you have to keep winning that race; overriding the
334
+ entry does not.)
335
+
336
+ **3. Scope it to the component, never `:root`.** Short names are deliberately
337
+ reused across components — `--icon-size` is read by `BbButton`, `BbBadge` and
338
+ `CloseIcon`; `--gap`, `--px`, `--h` are everywhere. Hoisting one to `:root`
339
+ retunes every component that happens to share the name, which is exactly what
340
+ tier-2's sealing rule exists to prevent. Put it on the component's class, a page
341
+ scope, or the instance's `style=`.
342
+
298
343
  ## The emphasis ladder
299
344
 
300
345
  All translucency/tinting goes through **one ladder** of `color-mix()`
@@ -319,7 +319,7 @@ Default label rendering mode for input-like components; set globally instead of
319
319
 
320
320
  ### `requiredAsterisk?: boolean` (default: `false`)
321
321
 
322
- Marks required fields with a red asterisk beside the label/legend. Presentational only — it keys off the standard `required` prop and changes neither validation nor native `required` semantics. The marker is applied by the [`bitboss-ui/validated`](./validated-forms.md) controls (any field that is `required` or carries `rules`); core controls ignore the flag, so on a hand-wired form render the exported `BbAsterisk` in your own label content.
322
+ Marks required fields with a red asterisk beside the label/legend. Presentational only — it keys off the standard `required` prop and changes neither validation nor native `required` semantics. The marker is applied by the [`bitboss-ui/validated`](./validated-forms.md) controls a field that is `required`, or whose `rules` contain a `required` rule; other rules never raise it, because they only apply once a value exists. Core controls ignore the flag, so on a hand-wired form render the exported `BbAsterisk` in your own label content.
323
323
 
324
324
  ### `locale?: LocaleKey` (default: `'it'`)
325
325
 
@@ -350,70 +350,31 @@ bitbossUi({
350
350
  ```
351
351
 
352
352
  Your `locale` and `'en'` are always auto-included, so the list never needs to
353
- repeat them; custom `additionalLocales` keys are always enabled and don't
354
- belong here.
353
+ repeat them.
355
354
  There is deliberately **no catch-all** ("all") value — it would hide the
356
355
  bundle cost. Wanting every built-in means writing the list, so the cost is
357
356
  visible in your config where reviewers can see it.
358
357
 
359
358
  **Selecting a non-enabled locale is a compile error**, not a silent runtime
360
- fallback: the generated `locale-registry.d.ts` registers only enabled
361
- built-ins + custom keys, so `setConfig({ locale: 'ja' })` fails to type-check
362
- until `'ja'` is enabled. Run dev once after changing the list.
359
+ fallback: the generated `locale-registry.d.ts` registers only the enabled
360
+ built-ins, so `setConfig({ locale: 'ja' })` fails to type-check until `'ja'` is
361
+ enabled. Run dev once after changing the list.
363
362
 
364
- ### `additionalLocales?: CustomLocales` (default: `{}`)
363
+ ### Custom locales are not a supported surface
365
364
 
366
- **Additional** locale dictionaries, keyed by locale. Strictly additive:
367
- built-in locales cannot be overridden — a built-in key here is a type error,
368
- and one smuggled past the compiler is ignored with a build-time warning.
365
+ The library ships **22 dictionaries** and that is the whole set:
369
366
 
370
- The full flow for adding a locale (using Georgian, which the library doesn't
371
- ship):
372
-
373
- ```bash
374
- # 1. Generate a translation blueprint (English strings) at the project root
375
- npx bitboss-ui locale ka
376
367
  ```
377
-
378
- ```ts
379
- // 2. Translate ka.locale.ts, then register it
380
- import ka from './ka.locale';
381
-
382
- bitbossUi({
383
- iconDir: './src/assets/icons',
384
- locale: 'ka',
385
- additionalLocales: { ka },
386
- });
368
+ cs da de el en es fi fr hu it ja ko nb nl pl pt ro ru sv tr uk zh-cn
387
369
  ```
388
370
 
389
- That's the whole setup on build start the plugin:
390
-
391
- - **verifies the dictionary against the library's English template** and warns
392
- about `missing` keys (strings the library requires that your translation
393
- doesn't cover — this is how a library upgrade that adds new strings alerts
394
- you instead of silently falling back to the default locale forever) and
395
- `unknown` keys (typos, or strings an upgrade removed).
396
-
397
- - **regenerates `locale-registry.d.ts`**, augmenting `LocaleRegistry` with
398
- your keys (exactly like the variant registries), so `'ka'` is a valid
399
- `locale` value everywhere — the plugin option and `setConfig({ locale })`
400
- alike. Run dev once after adding a key so the file exists.
401
- - **wires the matching dayjs locale** (`dayjs/locale/ka`) so calendars follow
402
- your locale's month/day names and week start. Any dayjs-shipped name works.
403
- If dayjs has no locale under your key, the plugin **warns at build time**
404
- and calendars fall back to English (there is no runtime warning) — when a
405
- nearby dayjs name exists (`zh-cn`, `pt-br`, …), prefer it as your key.
406
-
407
- Keys you delete from the blueprint fall back per-key to the default locale
408
- (your `locale` when it's a built-in, otherwise `en` — always bundled, always
409
- complete) — and are reported by the completeness warning above, which is the
410
- point: partial is allowed, silent-partial is not.
411
-
412
- Locale dictionaries are **build-time only**: `setConfig` accepts `locale` but
413
- deliberately not `additionalLocales` — a runtime path would bypass every check
414
- the plugin performs (built-in collision, completeness verification, dayjs
415
- wiring, registry typing). Only the active `locale` switches at runtime, among
416
- what the build enabled.
371
+ There is no `additionalLocales` option and no `npx bitboss-ui locale` blueprint
372
+ generator — both were removed deliberately (owner ruling, 2026-08-08). If your
373
+ language is not on that list, the supported answer is to ask for it to be
374
+ added upstream rather than to author a dictionary in your app.
375
+
376
+ Locale selection is **build-time**: `enabledLocales` decides what ships, and
377
+ only the active `locale` switches at runtime, among what the build enabled.
417
378
 
418
379
  ### `toastPosition?: BbToastPosition` (default: `'bottom-right'`)
419
380
 
@@ -25,3 +25,29 @@ summary: The required prop is renamed type→icon, color is removed, and the xxl
25
25
  Provider prefixes now require the matching `@iconify-json/<prefix>` dev
26
26
  dependency, and names must be **literal strings** (the build scans statically;
27
27
  dynamically assembled names won't be bundled).
28
+
29
+ ## Prefix your own icons with `local:` while you are in there
30
+
31
+ A v2 `type` naming an icon from your own `iconDir` was bare — `type="user-circle"`.
32
+ Carry it over **with the prefix**:
33
+
34
+ ```diff
35
+ - <BbIcon type="user-circle" />
36
+ + <BbIcon icon="local:user-circle" />
37
+ ```
38
+
39
+ This is not a functional change: `BbIcon` already resolves a name with no `:`
40
+ as `local:<name>`, so both spellings render the same icon. It is a **tooling**
41
+ change, and worth doing across the whole app in one pass:
42
+
43
+ - The Iconify editor extension keys off `prefix:name`. Spelled `local:user-circle`
44
+ it renders an inline preview and autocompletes the name in the template; spelled
45
+ `user-circle` it sees an ordinary string and shows nothing.
46
+ - It reads unambiguously next to `mdi:home` / `lucide:check` — same shape, and the
47
+ source of every icon is legible at a glance instead of inferred from absence.
48
+
49
+ Apply it wherever the name is written **literally in a template**. Leave names that
50
+ arrive through a variable, a map, or props alone — the prefix buys nothing there
51
+ (no extension preview to gain), and rewriting data is out of scope for a rename.
52
+ The same goes for any component prop that takes an icon (`prepend:icon`,
53
+ `append:icon`, `icon` on buttons, dropdown items, toasts…), not just `<BbIcon>`.