bitboss-ui 3.0.0-alpha.9 → 3.0.0-beta.1

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 (38) hide show
  1. package/bin/bitboss-ui.mjs +133 -12
  2. package/dist/ai/BbIcon.md +6 -1
  3. package/dist/ai/changelog.json +1 -1
  4. package/dist/ai/components.json +2 -2
  5. package/dist/ai/guides/ai-router.md +2 -2
  6. package/dist/ai/guides/design-tokens.md +91 -6
  7. package/dist/ai/guides/installation-and-plugin-setup.md +71 -5
  8. package/dist/ai/guides/migration/components/bb-alert.md +37 -0
  9. package/dist/ai/guides/migration/components/bb-avatar.md +47 -8
  10. package/dist/ai/guides/migration/components/bb-badge.md +23 -1
  11. package/dist/ai/guides/migration/components/bb-button.md +64 -0
  12. package/dist/ai/guides/migration/components/bb-checkbox-group.md +55 -1
  13. package/dist/ai/guides/migration/components/bb-date-picker-input.md +9 -2
  14. package/dist/ai/guides/migration/components/bb-dialog.md +121 -11
  15. package/dist/ai/guides/migration/components/bb-icon.md +68 -0
  16. package/dist/ai/guides/migration/components/bb-offcanvas.md +35 -1
  17. package/dist/ai/guides/migration/components/bb-rating.md +52 -1
  18. package/dist/ai/guides/migration/components/bb-select.md +48 -0
  19. package/dist/ai/guides/migration/components/bb-table.md +156 -10
  20. package/dist/ai/guides/migration/components/bb-tabs.md +79 -1
  21. package/dist/ai/guides/migration/components/bb-text-input.md +23 -1
  22. package/dist/ai/guides/migration/components/bb-toast.md +44 -10
  23. package/dist/ai/guides/migration/components/use-confirm.md +48 -13
  24. package/dist/ai/guides/migration/v2-to-v3.md +643 -105
  25. package/dist/ai/index.md +9 -9
  26. package/dist/ai/source/BbCheckboxGroup.md +2 -2
  27. package/dist/ai/source/BbDialog.md +0 -3
  28. package/dist/ai/source/BbRadioGroup.md +2 -2
  29. package/dist/ai/source/BbRating.md +2 -2
  30. package/dist/ai/source/BbSwitchGroup.md +2 -2
  31. package/dist/index.d.ts +2 -1
  32. package/dist/llms-full.txt +1908 -365
  33. package/dist/llms-medium.txt +82 -16
  34. package/dist/llms.txt +11 -11
  35. package/dist/styles.css +1 -1
  36. package/llms.txt +12 -12
  37. package/package.json +2 -1
  38. package/scripts/lib/validate-bb-markup.mjs +105 -17
@@ -17,7 +17,17 @@
17
17
  * components.json manifest (unknown props, removed props, bad
18
18
  * v-models, unknown `<template #slot>` names, `href`/`to`/
19
19
  * `method` on a component that doesn't declare them). Exits 1
20
- * on findings; supports --json output.
20
+ * on findings; supports --json output. Every finding carries the
21
+ * source line. `--allow-component <Name>` (or package.json's
22
+ * `bitboss-ui.allowComponents`) exempts an app-owned component
23
+ * whose name happens to start with `Bb`.
24
+ *
25
+ * SCOPE: `check` knows the INSTALLED v3 API and nothing about v2.
26
+ * "was removed from X" is a recorded v2→v3 break; "is not in X's
27
+ * API" only says the prop is not a v3 prop of that component —
28
+ * it may never have been one anywhere. `printFindingLegend` says
29
+ * so in the output, because two migrating fleets each spent real
30
+ * time discovering it the hard way.
21
31
  * mcp Start a stdio MCP server exposing the dist/ai knowledge base
22
32
  * (search_components, get_component, list_recipes, get_recipe,
23
33
  * validate) to MCP-capable agent harnesses.
@@ -445,12 +455,84 @@ function summarizeFileCounts(files) {
445
455
  return exts.map((ext) => `${counts.get(ext)} ${ext}`).join(' + ');
446
456
  }
447
457
 
458
+ /**
459
+ * Component names the PROJECT owns despite the `Bb` prefix, read from disk so
460
+ * CI, the editor and a local run agree without repeating a flag.
461
+ *
462
+ * Two sources, merged: `bitboss-ui.check.json`'s `allowComponents`, and
463
+ * package.json's `bitboss-ui.allowComponents`. The package.json key is the one
464
+ * to reach for — it sits next to the `eslint.config.js` that already carries
465
+ * the same list for `no-unknown-attributes`, so the two gates stay in sync.
466
+ * Unreadable/malformed files are ignored: a broken config must not turn into a
467
+ * check failure about markup.
468
+ * @returns {string[]}
469
+ */
470
+ function configuredAllowComponents(projectRoot) {
471
+ const out = [];
472
+ const readList = (path, pick) => {
473
+ try {
474
+ if (!existsSync(path)) return;
475
+ const value = pick(JSON.parse(readFileSync(path, 'utf-8')));
476
+ if (Array.isArray(value))
477
+ for (const name of value) if (typeof name === 'string') out.push(name);
478
+ } catch {
479
+ /* malformed config — ignore, never fail the check on it */
480
+ }
481
+ };
482
+ readList(
483
+ join(projectRoot, 'bitboss-ui.check.json'),
484
+ (cfg) => cfg?.allowComponents
485
+ );
486
+ readList(
487
+ join(projectRoot, 'package.json'),
488
+ (pkg) => pkg?.['bitboss-ui']?.allowComponents
489
+ );
490
+ return out;
491
+ }
492
+
493
+ /**
494
+ * CHK-SINCE-V2 — the two unknown-prop message shapes mean OPPOSITE things and
495
+ * nothing in the output said so. One migrating fleet filed two non-findings
496
+ * against the library, and the other told the categories apart only by
497
+ * installing v2 alongside and diffing by hand. Printed once per failing run,
498
+ * and only for the kinds actually present.
499
+ *
500
+ * `check` is honest about its limits here: it validates against the INSTALLED
501
+ * v3 manifest and holds no record of v2, so "is not in X's API" is a statement
502
+ * about v3 alone. Deciding whether such a prop ever worked is the reader's job,
503
+ * and the greps in the migration guide are how it is done.
504
+ */
505
+ function printFindingLegend(findings) {
506
+ const kinds = new Set(findings.map((f) => f.kind));
507
+ const rows = [];
508
+ if (kinds.has('removed-prop'))
509
+ rows.push(
510
+ ' "was removed from X" a v2→v3 break. X really had this prop; fix the call site.'
511
+ );
512
+ if (kinds.has('unknown-prop'))
513
+ rows.push(
514
+ ' "is not in X\'s API" X does not declare this prop in v3 — and `check` cannot\n' +
515
+ ' say whether it ever did. It may be a v3 removal that\n' +
516
+ ' predates the removal register, or a name that was never\n' +
517
+ ' a prop at all and has always fallen through to the DOM\n' +
518
+ ' as an inert attribute. Check the v2 source before\n' +
519
+ ' "restoring" anything: the second case needs deleting,\n' +
520
+ ' not porting.'
521
+ );
522
+ if (rows.length === 0) return;
523
+ console.error('\nReading these findings:');
524
+ for (const row of rows) console.error(row);
525
+ console.error(
526
+ ' `check` reads the installed v3 manifest only; the package carries no v2 API.'
527
+ );
528
+ }
529
+
448
530
  /**
449
531
  * `npx bitboss-ui check [glob…] [--json]` — validate `Bb*` markup (props,
450
532
  * v-models, and `<template #slot>` names) in `.vue` and `.md` files against
451
533
  * the installed components.json manifest.
452
534
  */
453
- function checkCommand(globs, jsonMode, allowEmpty = false) {
535
+ function checkCommand(globs, { json: jsonMode, allowEmpty, allowComponents }) {
454
536
  const projectRoot = process.cwd();
455
537
 
456
538
  const manifestPath = join(PACKAGE_ROOT, 'dist', 'ai', 'components.json');
@@ -481,6 +563,15 @@ function checkCommand(globs, jsonMode, allowEmpty = false) {
481
563
  : new Set();
482
564
  const files = resolveCheckFiles(projectRoot, globs);
483
565
 
566
+ // CHK-ALLOWCOMPONENTS: `Bb` is a naming convention, not proof of ownership.
567
+ // An app component called `BbRichEditor` is not invalid library markup, and
568
+ // before this the only way to silence it was to rename the component.
569
+ const allowedComponents = [
570
+ ...configuredAllowComponents(projectRoot),
571
+ ...allowComponents,
572
+ ];
573
+ const validateOptions = { allowComponents: allowedComponents };
574
+
484
575
  const findings = [];
485
576
  /*
486
577
  * Advisory only — see scripts/lib/hand-roll-hints.mjs. Hints never touch the
@@ -493,8 +584,8 @@ function checkCommand(globs, jsonMode, allowEmpty = false) {
493
584
  const relPath = relative(projectRoot, file);
494
585
  const content = readFileSync(file, 'utf-8');
495
586
  const { findings: fileFindings } = file.endsWith('.md')
496
- ? validateMarkdown(content, manifest)
497
- : validateVueSnippet(content, manifest);
587
+ ? validateMarkdown(content, manifest, validateOptions)
588
+ : validateVueSnippet(content, manifest, validateOptions);
498
589
  for (const finding of fileFindings) {
499
590
  findings.push({ file: relPath, ...finding });
500
591
  }
@@ -573,14 +664,19 @@ function checkCommand(globs, jsonMode, allowEmpty = false) {
573
664
  finding.fenceInfo != null
574
665
  ? `fence#${finding.fenceIndex}${finding.fenceInfo ? ` (${finding.fenceInfo})` : ''} `
575
666
  : '';
667
+ // CHK-LINE: three identical `full-screen` findings under one filename
668
+ // were indistinguishable, so every automated fix had to re-parse the
669
+ // file to place them. The line comes straight off the template AST.
670
+ const at = finding.line != null ? `line ${finding.line}: ` : '';
576
671
  console.error(
577
- ` ✗ ${where ? `[${where.trim()}] ` : ''}${finding.message}`
672
+ ` ✗ ${at}${where ? `[${where.trim()}] ` : ''}${finding.message}`
578
673
  );
579
674
  if (finding.hint) console.error(` hint: ${finding.hint}`);
580
675
  }
581
676
  console.error(
582
677
  '\nFix the markup (or the component API) so it stays copy-safe for agents.'
583
678
  );
679
+ printFindingLegend(findings);
584
680
  printHints();
585
681
  process.exit(1);
586
682
  }
@@ -600,7 +696,8 @@ Commands:
600
696
  server for Claude Code (.mcp.json), Cursor (.cursor/mcp.json),
601
697
  VS Code/Copilot (.vscode/mcp.json), and Windsurf (~/.codeium
602
698
  global) — merges, never overwrites other servers.
603
- check [glob…] [--json] [--allow-empty]
699
+ check [glob…] [--json] [--allow-empty] [--allow-component <Name>]
700
+ [--no-hints]
604
701
  Validate \`Bb*\` markup in .vue/.md files against the
605
702
  installed components.json manifest (unknown/removed props,
606
703
  bad v-models, unknown \`<template #slot>\` names,
@@ -612,7 +709,27 @@ Commands:
612
709
  Exits 1 on findings, and on an explicit glob that matched
613
710
  nothing (a typo'd CI path validates zero files silently) —
614
711
  pass \`--allow-empty\` when an empty match is expected.
615
- \`--json\` prints \`{ findings: [...], files }\`.
712
+
713
+ \`--allow-component <Name>\` (repeatable) exempts a
714
+ component your APP owns whose name starts with \`Bb\` —
715
+ the prefix is a convention, not proof of provenance.
716
+ Persist the list instead of repeating the flag:
717
+ package.json { "bitboss-ui": { "allowComponents": ["BbRichEditor"] } }
718
+ or \`bitboss-ui.check.json\` \`{ "allowComponents": [...] }\`.
719
+ Same option name as the ESLint rules' \`allowComponents\`,
720
+ so both gates configure alike.
721
+
722
+ \`--json\` prints \`{ findings: [...], hints: [...], files }\`.
723
+ Each finding: \`{ file, line, component, attr, kind,
724
+ message, hint?, to? }\` (plus \`fenceIndex\`/\`fenceInfo\`
725
+ inside a markdown fence; \`line\` is absolute in the file).
726
+ \`kind\` is one of: \`removed-prop\` (a v2→v3 break — fix
727
+ it), \`unknown-prop\` (not a v3 prop of that component;
728
+ may never have been one — see the legend the text output
729
+ prints), \`unknown-model\`, \`unknown-slot\`,
730
+ \`no-default-slot\`, \`inert-nav-attr\`,
731
+ \`missing-partner-prop\`, \`partner-prop\`,
732
+ \`validated-only-prop\`.
616
733
  mcp Start a stdio MCP server exposing the dist/ai knowledge
617
734
  base (search_components, get_component, list_recipes,
618
735
  get_recipe, validate) to MCP-capable agent harnesses.
@@ -658,14 +775,18 @@ switch (command) {
658
775
  {
659
776
  json: { type: 'boolean' },
660
777
  'allow-empty': { type: 'boolean' },
778
+ 'allow-component': { type: 'string', multiple: true },
779
+ // Accepted and ignored here — read straight off argv inside
780
+ // checkCommand, but parseArgs is strict, so it must be declared.
781
+ 'no-hints': { type: 'boolean' },
661
782
  },
662
783
  { allowPositionals: true }
663
784
  );
664
- checkCommand(
665
- positionals,
666
- values.json ?? false,
667
- values['allow-empty'] ?? false
668
- );
785
+ checkCommand(positionals, {
786
+ json: values.json ?? false,
787
+ allowEmpty: values['allow-empty'] ?? false,
788
+ allowComponents: values['allow-component'] ?? [],
789
+ });
669
790
  break;
670
791
  }
671
792
  case 'mcp': {
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "library": "bitboss-ui",
4
- "version": "3.0.0-alpha.9",
4
+ "version": "3.0.0-beta.1",
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.9",
5
- "generatedAt": "2026-08-24T11:02:05.896Z",
4
+ "version": "3.0.0-beta.1",
5
+ "generatedAt": "2026-08-24T19:52:26.526Z",
6
6
  "composables": [
7
7
  {
8
8
  "name": "useBbConfig",
@@ -15,9 +15,9 @@ 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
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@alpha/dist/ai/…`.
18
+ published package at `https://cdn.jsdelivr.net/npm/bitboss-ui@beta/dist/ai/…`.
19
19
  If you can make only one request, fetch
20
- [`…@alpha/dist/llms-medium.txt`](https://cdn.jsdelivr.net/npm/bitboss-ui@alpha/dist/llms-medium.txt)
20
+ [`…@beta/dist/llms-medium.txt`](https://cdn.jsdelivr.net/npm/bitboss-ui@beta/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
 
@@ -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()`
@@ -379,12 +424,40 @@ scope (`.brand { --bb-primary: … }`) does not re-derive them — re-apply the
379
424
  `light`/`dark` class on that scope, or override the derived tokens directly.
380
425
  Root-level theming (what the theme builder emits) is unaffected.
381
426
 
382
- ### Animated themes
427
+ ### Registered knobs (`@property`)
428
+
429
+ Nine tokens are registered in `variables.css`: the eight color knobs with
430
+ `syntax: '<color>'`, and `--bb-ring-opacity` with `syntax: '<percentage>'`.
431
+ All are `inherits: true`.
432
+
433
+ **Registration types them, and a typed property rejects.** A declaration that
434
+ does not parse as the declared syntax is invalid at computed-value time — the
435
+ value is discarded, and because these inherit, an override at `:root` (no
436
+ parent to inherit from) lands on the registered `initial-value`. That is the
437
+ fail-soft half: an unparseable `--bb-panel` falls back instead of detonating
438
+ every derivation that mixes it. It is also the trap. Write
383
439
 
384
- The color knobs (and `--bb-ring-opacity`) are registered with `@property`
385
- (typed `<color>`, `inherits: true`), which makes them **animatable** and
386
- fail-soft (an invalid `--bb-panel` falls back to the registered initial
387
- value instead of detonating every derivation). Add `.theme-animated`
440
+ ```css
441
+ :root {
442
+ --bb-ring-opacity: 0.15;
443
+ } /* wrong: not a <percentage> */
444
+ ```
445
+
446
+ and nothing errors — not the build, not the console. The computed value is
447
+ just `25%`, and every ring paints 1.67× stronger than the `15%` you asked for
448
+ (opacity is the ring's strength; its thickness is `--bb-ring-size`).
449
+ Percentages need the `%`; colors must be a `<color>` (`transparent` and
450
+ `currentColor` qualify, `none` and a mistyped hex do not). Nothing in the
451
+ toolchain catches it either: `bitboss-ui check` reads `.vue`/`.md`, never
452
+ stylesheets.
453
+
454
+ Fail-soft is only soft when the initial value is close to the real one. Every
455
+ color knob is registered at its light default, so a rejected color override
456
+ renders the library's own light theme. `--bb-ring-opacity` is registered at
457
+ `25%`, which matches neither shipped scope (light `15%`, dark `40%`), so a
458
+ rejected value there is the one that shows.
459
+
460
+ Typed knobs are also **animatable**. Add `.theme-animated`
388
461
  (main.css) to the element that carries the scheme class or theme overrides:
389
462
 
390
463
  ```html
@@ -525,7 +598,7 @@ escape the host dialog's `overflow: hidden` and centering transform.
525
598
  | `color-mix(in srgb, …)` | `color-mix(in oklab, …)` everywhere |
526
599
  | dialog/offcanvas `--radius-multiplier` ×1.8, literal `6px`/`8px`/`0.375rem` radii | `--bb-radius-surface` / `--bb-radius-sm` |
527
600
  | `--bb-contrasting` | `--bb-primary-fg` |
528
- | `--bb-easing-bezier` | `--bb-ease` |
601
+ | hard-coded `cubic-bezier()` literals (v2 exposed no easing token) | `--bb-ease` |
529
602
  | `--bb-panel-disabled`, `--bb-input-bg-secondary` | `--bb-muted` |
530
603
  | `--bb-muted-color`, `--bb-hint`, `--bb-placeholder`, `--bb-icon-color`, `--bb-prefix-color` | `--bb-text-muted` |
531
604
  | `--bb-input-color`, `--bb-input-bg` | `--bb-text`, `--bb-panel` |
@@ -542,6 +615,18 @@ escape the host dialog's `overflow: hidden` and centering transform.
542
615
  | `--bb-table-{id}-track-{key}` (parent→nested width bridge) | `--table-{id}-track-{key}` (distinctive cross-component name, rule 3) |
543
616
  | `--bb-select-option-*`, `--bb-primary-base`, all `--x-light`/`--x-dark` shadows | deleted (dead) |
544
617
 
618
+ **This table maps names, not values.** A token that is absent from it survived
619
+ the rename — which is not the same as surviving unchanged. Six kept their v2
620
+ name and their global scope and moved default anyway (input height, input
621
+ vertical padding, prefix column width, label weight, ring size, leading); the
622
+ before/after numbers are tabled in the v2→v3 migration guide, §3. The rule
623
+ generalizes past this migration: a `--bb-*` name is a slot in the derivation
624
+ graph, not a promise about the number in it. An override you carried forward
625
+ keeps working and keeps your old look; an override you deleted because "the
626
+ name still exists" hands you whatever `variables.css` declares today. Check
627
+ each survivor against `variables.css` and delete only the ones that were
628
+ restating a default.
629
+
545
630
  ## Enforcement
546
631
 
547
632
  `npm run check:tokens` (see `scripts/check-tokens.mjs`) fails the build when:
@@ -13,16 +13,21 @@ Source of truth for integrating `bitboss-ui`. Two pieces are always required:
13
13
  ## 1) Install
14
14
 
15
15
  ```bash
16
- # v3 is a prerelease — the `@alpha` tag is REQUIRED.
16
+ # v3 is a prerelease — a dist-tag is REQUIRED.
17
17
  # A bare `npm install bitboss-ui` still resolves to v2, which has no build
18
18
  # plugin and no `dist/ai` knowledge base, so everything below silently fails.
19
- npm install bitboss-ui@alpha
19
+ npm install bitboss-ui@beta
20
20
  # recommended default icon set (any @iconify-json/* works)
21
21
  npm install -D @iconify-json/lucide
22
22
  ```
23
23
 
24
24
  Confirm you got v3 before continuing — `npm ls bitboss-ui` must report a
25
- `3.0.0-alpha.*` version. If it reports `2.x`, nothing in this guide applies.
25
+ `3.0.0-*` version. If it reports `2.x`, nothing in this guide applies.
26
+
27
+ **`@beta` is the current v3 channel.** `@alpha` still resolves, but it is
28
+ frozen at the last alpha and does not receive fixes — it is not simply an older
29
+ copy of the same line. `@latest` is the **v2** line and always will be until v3
30
+ goes stable, so never reach for it here.
26
31
 
27
32
  Peer dependency: `vue ^3.5.12` — the only required one. `@inertiajs/vue3` is an **optional** peer, needed only in Inertia apps.
28
33
 
@@ -219,6 +224,48 @@ import 'bitboss-ui/styles.css';
219
224
  import 'bitboss-ui/reset.css';
220
225
  ```
221
226
 
227
+ **Exception — an app that needs its own CSS to load _before_ the library.**
228
+ Auto-injection is library-first and not configurable: the runtime plugin
229
+ inserts its `<style>` **before the first** `style`/`link[rel="stylesheet"]`
230
+ already in `<head>` (`src/utils/injectLibraryStyles.ts`), so the guaranteed
231
+ order above is exactly what you get and every app stylesheet — your Tailwind
232
+ entry included — lands after the library. There is no option to inject later.
233
+
234
+ That is the order most apps want. It is the wrong order if you need library
235
+ rules to beat Tailwind's Preflight **by source order**, which is the case on
236
+ **Tailwind v3**: `@tailwind base` compiles to ordinary unlayered CSS, so under
237
+ auto-injection Preflight sits after the library sheet and its element resets
238
+ (`button`, `input`, `h1`, …) win every equal-specificity tie. `injectStyles:
239
+ false` + `resetCss: false` (the default) disables injection entirely and hands
240
+ you the order:
241
+
242
+ ```ts
243
+ // vite.config.ts / the `bitboss` key in nuxt.config.ts
244
+ bitbossUi({ iconDir: '…', injectStyles: false, resetCss: false });
245
+ ```
246
+
247
+ ```ts
248
+ // app entry — order is yours now
249
+ import './app.css'; // your Tailwind entry
250
+ import 'bitboss-ui/styles.css'; // after Tailwind, so library rules win the ties
251
+ ```
252
+
253
+ Leave `resetCss` at `false` here — Preflight already reset everything.
254
+ Nothing else about the plugin changes; `injectStyles` controls only whether the
255
+ library injects its own `<style>`.
256
+
257
+ **Tailwind v4 apps do not need this** — source order does not decide their ties
258
+ at all. `@import 'tailwindcss'` puts Preflight in a real `@layer base` and
259
+ utilities in `@layer utilities`, and layered rules lose to unlayered rules of
260
+ equal specificity **regardless of source order**. `dist/styles.css` declares no
261
+ `@layer`, so the library wins those ties wherever it is injected: leave
262
+ auto-injection on. The same mechanic bites the other way, and silently — a
263
+ utility cannot re-size a Bb component either. `<BbButton class="h-12 px-6">`
264
+ loses to the library's own `.bb-button { height: var(--h); padding-left:
265
+ var(--px); padding-right: var(--px) }` (equal specificity, unlayered), with no
266
+ warning from anything. Size Bb components through their props and tokens, never
267
+ utilities. (Same trap as the scoped-`<style>` case above, one level up.)
268
+
222
269
  ## 6) Icons
223
270
 
224
271
  - `iconDir` (required) is scanned recursively for `.svg` files; each file becomes `local:<basename>`.
@@ -260,6 +307,15 @@ When `true`, the **dev server** adds `./node_modules/.bitboss-ui/local-icons.jso
260
307
 
261
308
  When `true`, the **dev server** registers the bitboss-ui MCP server in each project-scoped agent-harness config on boot — `.mcp.json` (Claude Code), `.cursor/mcp.json` (Cursor), and `.vscode/mcp.json` (VS Code / Copilot) — so agents can query the knowledge base with no manual setup. The write is merge-safe (leaves other servers alone) and idempotent (only logs when it actually changes a file). Windsurf is intentionally **not** written here: its config is global (`~/.codeium`), and silently editing a home-dir file that affects every project on the machine from a dev-server boot would be surprising — run `npx bitboss-ui ai-init --mcp` to register Windsurf too. This flag is the zero-command equivalent of that command's project-scoped writes. The MCP server's own runtime — `@modelcontextprotocol/sdk` and `zod` — ships as **optional peer dependencies** (~12 MB, more than every runtime dependency of the library combined, and loaded by nothing else), so install them with `npm i -D @modelcontextprotocol/sdk zod` before the harness first launches the server; both this flag and `ai-init --mcp` warn when they are missing.
262
309
 
310
+ **MCP is a convenience, not a requirement, and skipping it costs no
311
+ knowledge.** The server is a reader over files the install already put on disk
312
+ — every tool `readFileSync`s out of `node_modules/bitboss-ui/dist/ai/` (plus
313
+ `dist/styles.css` for the token lookup). An agent that opens that directory
314
+ itself has the identical knowledge base, `npx bitboss-ui check` runs without the
315
+ peers, and `ai-init` (no `--mcp`) still writes the harness pointers. If you are
316
+ migrating under a "no new dependencies" constraint, leave `mcp` at `false`,
317
+ skip `--mcp`, and point your agent at `dist/ai/guides/ai-router.md`.
318
+
263
319
  ### `inertiaLinkName?: string` (default: `'Link'`)
264
320
 
265
321
  Global component name used for Inertia links. **Optional** — when nothing is
@@ -438,7 +494,17 @@ than inventing hues.
438
494
 
439
495
  The package ships its agent docs in `node_modules/bitboss-ui/dist/ai/`. **Start at** `guides/ai-router.md`, then the catalogue `index.md`. Run `npx bitboss-ui ai-init` in the consumer project to inject harness-agnostic pointers (AGENTS.md, Cursor rules, Copilot instructions, Claude skill, Windsurf rules) so coding agents discover them without a vendor lock-in. After generating Bb\* markup, run `npx bitboss-ui check` (supports `--json`) so unknown props fail before review.
440
496
 
441
- Add `--mcp` to also register a live MCP server (`npx bitboss-ui@<installed version> mcp`, pinned so npx can never fetch a different version from the registry) the harness launches on demand — it merges the entry (never overwriting other servers) into `.mcp.json` (Claude Code), `.cursor/mcp.json` (Cursor), `.vscode/mcp.json` (VS Code / Copilot), and `~/.codeium/windsurf/mcp_config.json` (Windsurf). **Windsurf's config is global** — it has no per-project scope, so registering it affects every project on the machine; that is why only the explicit `ai-init --mcp` command writes it, while the plugin's `mcp: true` dev-server flag (§7) auto-registers the project-scoped harnesses only. Install the server's optional peers first — `npm i -D @modelcontextprotocol/sdk zod` — they are not dependencies of the library (~12 MB used only by the MCP server); the command warns if they are absent.
497
+ **Three tiers ship under `dist/ai/`**, and the third one is easy to miss:
498
+ `guides/` + `recipes/` are the authored prose; `<Name>.md` at the root is the
499
+ generated typed contract for each component (props, slots, events, examples);
500
+ `source/<Name>.md` is that same component's **complete implementation** —
501
+ `.vue` template, `types.ts` and `index.css` in one file, one per component
502
+ including the internal ones the package does not export. Read the source tier
503
+ when the contract cannot answer the question — the usual case is rebuilding
504
+ chrome around a control the library does not ship, since the class names such a
505
+ template emits are already styled by the public `bitboss-ui/styles.css`.
506
+
507
+ Add `--mcp` to also register a live MCP server (`npx bitboss-ui@<installed version> mcp`, pinned so npx can never fetch a different version from the registry) the harness launches on demand — it merges the entry (never overwriting other servers) into `.mcp.json` (Claude Code), `.cursor/mcp.json` (Cursor), `.vscode/mcp.json` (VS Code / Copilot), and `~/.codeium/windsurf/mcp_config.json` (Windsurf). **Windsurf's config is global** — it has no per-project scope, so registering it affects every project on the machine; that is why only the explicit `ai-init --mcp` command writes it, while the plugin's `mcp: true` dev-server flag (§7) auto-registers the project-scoped harnesses only. Install the server's optional peers first — `npm i -D @modelcontextprotocol/sdk zod` — they are not dependencies of the library (~12 MB used only by the MCP server); the command warns if they are absent. **`--mcp` is optional and skipping it loses you nothing but the transport** — the server only reads the `dist/ai/` files listed above, so run plain `ai-init` and let the agent read that directory if you cannot add the peers (see `mcp?: boolean` in §7).
442
508
 
443
509
  For a human-in-the-loop (and agent) safety net, add the `bitboss-ui/eslint-plugin` flat-config plugin (`...bitbossUi.configs.recommended`) so unknown/removed `Bb*` props surface as ESLint errors in the editor and CI — the same manifest checks as `bitboss-ui check`, reusing your `eslint-plugin-vue` parser. Real HTML attributes fall through untouched; escape hatches are the standard `<!-- eslint-disable-next-line bitboss-ui/no-unknown-attributes -->` and the rule's `allowAttributes` / `allowComponents` options. **`eslint --fix` also auto-migrates deprecated v2 props** (renames, boolean-polarity inversions like `allowWriting`→`disableWriting`, value remaps like BbToast `placement`→`position`, and deletion of inert removed props); structural migrations that need a slot/directive/CSS are reported but left by hand. **This library is TS-first (recipes ship `<script setup lang="ts">`, some with `generic="T"`), so wire `@typescript-eslint/parser` for both `.ts` and `.vue` files** — without it `vue-eslint-parser` throws a `Parsing error` on those files, which means no rule (including `no-unknown-attributes`) runs on them at all. See the README "ESLint plugin" section for the full config snippet (both parser blocks + the `npm i -D @typescript-eslint/parser` line) and the fix table. `recommended` also enables `bitboss-ui/no-active-class-on-root-link`: `active-class` on a link-capable `Bb*` component (`BbButton`/`BbBaseButton`/`BbBadgeButton`) matches by path PREFIX, so a link whose target is the literal root (`href="/"` / `to="/"`) reads as "active" on every URL — the rule flags that combination and points you at `exact-active-class` instead.
444
510
 
@@ -458,7 +524,7 @@ It also enables `bitboss-ui/require-partner-prop`: an opt-in prop set without it
458
524
  - [ ] build plugin configured: `bitboss-ui/vite` plugin or `bitboss-ui/nuxt` module (`bitboss` config key);
459
525
  - [ ] `iconDir` points at an existing SVG folder;
460
526
  - [ ] runtime plugin installed: `app.use(bitbossUiPlugin)` (automatic in Nuxt);
461
- - [ ] style strategy chosen: auto injection (default) or `injectStyles: false` + `import 'bitboss-ui/styles.css'`;
527
+ - [ ] style strategy chosen: auto injection (default) or `injectStyles: false` + `import 'bitboss-ui/styles.css'` — auto injection is always library-first, so a Tailwind **v3** app that needs Preflight to load before the library must take the second option (§5);
462
528
  - [ ] if a reset is required: `resetCss: true` or `import 'bitboss-ui/reset.css'`;
463
529
  - [ ] Inertia apps: `@inertiajs/vue3` installed (no link registration needed; set `inertiaLinkName` only if you register your own link wrapper).
464
530
  - [ ] building the [Layout Scaffold](../recipes/vue/layout-scaffold.md)? `npm i @vueuse/core` — `usePageShellContext.ts` imports `injectLocal`/`provideLocal` from it. This library depends on it too, so it can resolve via hoisting without being declared — don't rely on that; declare it in your own `package.json` or a strict installer (pnpm, Yarn PnP) will fail to resolve it.
@@ -35,3 +35,40 @@ prop now actually carries `text` (v2 mistakenly passed `title`).
35
35
  A hand-applied `class="bb-alert--warning"` also becomes `variant="warning"` —
36
36
  see [main guide §7](../v2-to-v3.md). Custom `theme` names: register via the
37
37
  plugin's `alertVariants` option and keep your CSS.
38
+
39
+ ## ⚠ `theme` painted nothing in v2 — and now two identical call sites diverge
40
+
41
+ v2's stylesheet shipped **zero** `.bb-alert--*` rules. The component emitted
42
+ `bb-alert--${theme}` faithfully, but unless your own CSS defined the rule, every
43
+ value rendered the same default surface. `theme="success"`, `theme="blue"` and
44
+ `theme="warning"` were the same alert.
45
+
46
+ In v3 the string is live, and what happens next depends on nothing but whether
47
+ the name collides with a built-in (`outline`, `primary`, `destructive`,
48
+ `warning`):
49
+
50
+ ```diff
51
+ <!-- these two rendered identically in v2 -->
52
+ - <BbAlert theme="success" title="Saved" />
53
+ + <BbAlert variant="success" title="Saved" />
54
+ <!-- `success` is not an alert built-in → compile error until you register it. You will notice. -->
55
+
56
+ - <BbAlert theme="warning" title="Quota almost reached" />
57
+ + <BbAlert variant="warning" title="Quota almost reached" />
58
+ <!-- `warning` IS a built-in → compiles, and silently repaints amber (#fffbea on #fee685). You will not. -->
59
+ ```
60
+
61
+ The rename diff above is a **visual change** for `warning`, not a no-op. Before
62
+ converting any `theme=` value, grep your own stylesheets for
63
+ `.bb-alert--<value>`:
64
+
65
+ - **Nothing styled it** → the value was decoration. Drop the prop and take the
66
+ v3 default, or register the name via `alertVariants` and ship no CSS for it,
67
+ which holds v2's appearance exactly.
68
+ - **Your CSS styled it** → register the name and keep your rule; `warning` is
69
+ the one case where your rule now lands on top of a library skin, so check
70
+ that you override every property it sets (background, border, `--main-color`,
71
+ `--muted-color`) and not just the ones your v2 rule needed.
72
+
73
+ The same trap applies to `BbTooltip` and `toast()` — v2 styled none of their
74
+ theme classes either.
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  title: 'Migration v2→v3: BbAvatar'
3
- summary: The color prop is removed — the initials fallback is always primary.
3
+ summary: The color prop is removed — retheme the fallback through the --bg-color / --text-color locals.
4
4
  ---
5
5
 
6
6
  # BbAvatar — v2 → v3
7
7
 
8
8
  ## Changes
9
9
 
10
- | v2 | v3 | Kind |
11
- | ---------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
12
- | `color?: string` | removed | initials fallback always uses the primary tokens; override `.bb-avatar` CSS for anything custom |
13
- | `timeout` | unchanged (SSR image grace period — **not** renamed; only `BbTooltip.timeout` became `delay`) | — |
10
+ | v2 | v3 | Kind |
11
+ | ---------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
12
+ | `color?: string` | removed | the fallback surface paints from two locals on `.bb-avatar` `--bg-color` / `--text-color` (below) |
13
+ | `timeout` | unchanged (SSR image grace period — **not** renamed; only `BbTooltip.timeout` became `delay`) | — |
14
14
 
15
15
  ## Edits
16
16
 
@@ -19,6 +19,45 @@ summary: The color prop is removed — the initials fallback is always primary.
19
19
  + <BbAvatar :src="user.photo" :alt="user.name" />
20
20
  ```
21
21
 
22
- If per-user colors mattered, set them in CSS on a wrapper
23
- (`.team-avatar--purple .bb-avatar { … }`) — the component no longer carries
24
- presentation-by-prop.
22
+ ## Where the color went
23
+
24
+ `.bb-avatar` declares three locals and reads them for the fallback (initials,
25
+ slotted content, or the built-in person glyph — an image covers all of them):
26
+
27
+ | local | default | paints |
28
+ | ---------------------- | ------------------------------------------ | --------------------------- |
29
+ | `--bg-color` | `var(--bb-primary)` | the avatar background |
30
+ | `--text-color` | `var(--bb-primary-fg)` | initials / slotted fallback |
31
+ | `--default-icon-color` | 80% mix of `--bb-primary-fg` over the fill | the built-in person glyph |
32
+
33
+ Set them on a scope, not on the component:
34
+
35
+ ```css
36
+ .team-avatar .bb-avatar {
37
+ --bg-color: #7c3aed;
38
+ --text-color: #fff;
39
+ }
40
+ ```
41
+
42
+ ⚠ Set **both**. `--bg-color` alone leaves the initials at `--bb-primary-fg`,
43
+ which is the contrast pair for the _primary_ color, not for yours — the symptom
44
+ is white-on-pale or black-on-dark initials.
45
+
46
+ Use this for a color that genuinely varies per instance (per-team, per-tenant).
47
+ If the v2 `color` was your brand color repeated at every call site, retheme
48
+ `--bb-primary` / `--bb-primary-fg` once instead and write no per-avatar rules.
49
+ That is the order the BbAvatar usage guide prescribes: retheme the tokens
50
+ first, reach for the locals only when the value is per-instance.
51
+
52
+ ## `.bb-avatar--square` was never a library modifier
53
+
54
+ If your app has a `.bb-avatar--square` rule, it is **your** CSS. v2 shipped no
55
+ such modifier — its entire avatar stylesheet was four rules, all hardcoding
56
+ `border-radius: 50%`, and the string `square` appears nowhere in the v2
57
+ package. v3 bakes in the same circle.
58
+
59
+ So nothing was removed and there is no v3 modifier to rename to. Your rule
60
+ keeps working: it has the same specificity as the library's `.bb-avatar` rule
61
+ and the library sheet is injected above your app CSS, so yours wins the tie.
62
+ Rename the class to an app-owned block (`.p-avatar--square`) anyway, so the
63
+ next reader does not take it for library API and go hunting for a `shape` prop.