@bridge-ui/vue 0.0.7 → 0.0.8

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 (136) hide show
  1. package/ai/AGENTS.md +31 -0
  2. package/ai/README.md +35 -0
  3. package/ai/guidelines/core.md +44 -0
  4. package/ai/llms.txt +38 -0
  5. package/ai/skills/bridge-ui-components/SKILL.md +37 -0
  6. package/ai/skills/bridge-ui-forms/SKILL.md +38 -0
  7. package/ai/skills/bridge-ui-overlays/SKILL.md +37 -0
  8. package/ai/skills/bridge-ui-setup/SKILL.md +38 -0
  9. package/bin/__tests__/ai.test.ts +319 -0
  10. package/bin/ai.mjs +469 -0
  11. package/dist/Adapters/I18n/useI18nAdapter.d.ts +1 -1
  12. package/dist/Adapters/Icon/useIconAdapter.d.ts +1 -1
  13. package/dist/Components/Autocomplete/Autocomplete.vue.d.ts +5 -5
  14. package/dist/Components/Autocomplete/composables/useAutocomplete.d.ts +3 -2
  15. package/dist/Components/Autocomplete/composables/useAutocomplete.js +6 -2
  16. package/dist/Components/Avatar/composables/useAvatar.d.ts +1 -1
  17. package/dist/Components/BaseField/BaseField.js +5 -0
  18. package/dist/Components/BaseField/BaseField.vue.d.ts +15 -0
  19. package/dist/Components/BaseField/BaseField.vue_vue_type_script_setup_true_lang.js +41 -0
  20. package/dist/Components/BaseField/BaseFieldLabel.js +5 -0
  21. package/dist/Components/{OtpField/OtpFieldLabel.vue.d.ts → BaseField/BaseFieldLabel.vue.d.ts} +2 -2
  22. package/dist/Components/{OtpField/OtpFieldLabel.vue_vue_type_script_setup_true_lang.js → BaseField/BaseFieldLabel.vue_vue_type_script_setup_true_lang.js} +2 -2
  23. package/dist/Components/BaseField/baseField.types.d.ts +222 -0
  24. package/dist/Components/BaseField/composables/useBaseField.d.ts +55 -0
  25. package/dist/Components/BaseField/composables/useBaseField.js +131 -0
  26. package/dist/Components/BaseField/index.d.ts +4 -0
  27. package/dist/Components/BaseField/index.js +3 -0
  28. package/dist/Components/Button/composables/useButton.d.ts +1 -1
  29. package/dist/Components/Checkbox/composables/useCheckbox.d.ts +1 -1
  30. package/dist/Components/Checkbox/composables/useCheckbox.js +1 -1
  31. package/dist/Components/Chip/Chip.vue.d.ts +1 -1
  32. package/dist/Components/Chip/composables/useChip.d.ts +1 -1
  33. package/dist/Components/Drawer/Drawer.vue.d.ts +2 -2
  34. package/dist/Components/Drawer/composables/useDrawer.d.ts +1 -1
  35. package/dist/Components/FormControl/composables/useFormControl.d.ts +11 -2
  36. package/dist/Components/FormControl/composables/useFormControl.js +57 -53
  37. package/dist/Components/FormControl/index.d.ts +1 -1
  38. package/dist/Components/FormField/composables/useFormField.d.ts +5 -1
  39. package/dist/Components/FormField/composables/useFormField.js +1 -1
  40. package/dist/Components/ListItem/ListItem.vue.d.ts +1 -1
  41. package/dist/Components/ListItem/composables/useListItem.d.ts +1 -1
  42. package/dist/Components/Listbox/Listbox.vue.d.ts +3 -3
  43. package/dist/Components/Listbox/Listbox.vue_vue_type_script_setup_true_lang.js +2 -1
  44. package/dist/Components/Listbox/composables/useListbox.d.ts +10 -1
  45. package/dist/Components/Listbox/composables/useListbox.js +20 -20
  46. package/dist/Components/Listbox/index.d.ts +1 -0
  47. package/dist/Components/Listbox/listbox.types.d.ts +6 -0
  48. package/dist/Components/Menu/Menu.vue.d.ts +2 -2
  49. package/dist/Components/Modal/Modal.vue.d.ts +2 -2
  50. package/dist/Components/Modal/composables/useModal.d.ts +1 -1
  51. package/dist/Components/NumberField/NumberField.vue.d.ts +2 -2
  52. package/dist/Components/NumberField/composables/useNumberField.d.ts +2 -2
  53. package/dist/Components/NumberField/composables/useNumberField.js +4 -1
  54. package/dist/Components/OtpField/OtpField.vue.d.ts +3 -3
  55. package/dist/Components/OtpField/OtpField.vue_vue_type_script_setup_true_lang.js +34 -52
  56. package/dist/Components/OtpField/composables/useOtpField.d.ts +40 -213
  57. package/dist/Components/OtpField/composables/useOtpField.js +92 -150
  58. package/dist/Components/OtpField/otpField.types.d.ts +10 -180
  59. package/dist/Components/PasswordField/composables/usePasswordField.d.ts +2 -2
  60. package/dist/Components/PasswordField/composables/usePasswordField.js +4 -1
  61. package/dist/Components/Radio/composables/useRadio.d.ts +1 -1
  62. package/dist/Components/Radio/composables/useRadio.js +1 -1
  63. package/dist/Components/Select/Select.vue.d.ts +4 -4
  64. package/dist/Components/Select/composables/useSelect.d.ts +3 -2
  65. package/dist/Components/Select/composables/useSelect.js +6 -2
  66. package/dist/Components/Slider/Slider.js +5 -0
  67. package/dist/Components/Slider/Slider.vue.d.ts +25 -0
  68. package/dist/Components/Slider/Slider.vue_vue_type_script_setup_true_lang.js +113 -0
  69. package/dist/Components/Slider/composables/useSlider.d.ts +482 -0
  70. package/dist/Components/Slider/composables/useSlider.js +287 -0
  71. package/dist/Components/Slider/index.d.ts +4 -0
  72. package/dist/Components/Slider/index.js +3 -0
  73. package/dist/Components/Slider/slider.types.d.ts +169 -0
  74. package/dist/Components/Snackbar/Snackbar.vue.d.ts +2 -2
  75. package/dist/Components/Switch/composables/useSwitch.d.ts +1 -1
  76. package/dist/Components/Switch/composables/useSwitch.js +1 -1
  77. package/dist/Components/Tabs/Tabs.vue.d.ts +2 -2
  78. package/dist/Components/TextField/composables/useTextField.d.ts +2 -2
  79. package/dist/Components/TextField/composables/useTextField.js +1 -1
  80. package/dist/Components/Textarea/composables/useTextarea.d.ts +2 -2
  81. package/dist/Components/Textarea/composables/useTextarea.js +1 -0
  82. package/dist/Utils/index.d.ts +3 -3
  83. package/dist/Utils/index.js +5 -2
  84. package/dist/augments.d.ts +29 -17
  85. package/dist/index.d.ts +5 -1
  86. package/dist/index.js +59 -55
  87. package/docs/README.md +56 -0
  88. package/docs/components/Alert.md +96 -0
  89. package/docs/components/Autocomplete.md +198 -0
  90. package/docs/components/Avatar.md +60 -0
  91. package/docs/components/Badge.md +47 -0
  92. package/docs/components/BaseField.md +126 -0
  93. package/docs/components/BridgeUIProvider.md +151 -0
  94. package/docs/components/Button.md +93 -0
  95. package/docs/components/Card.md +287 -0
  96. package/docs/components/Checkbox.md +87 -0
  97. package/docs/components/Divider.md +56 -0
  98. package/docs/components/Drawer.md +137 -0
  99. package/docs/components/FormControl.md +66 -0
  100. package/docs/components/FormField.md +71 -0
  101. package/docs/components/I18n.md +125 -0
  102. package/docs/components/Icon.md +88 -0
  103. package/docs/components/Label.md +31 -0
  104. package/docs/components/Link.md +71 -0
  105. package/docs/components/List.md +167 -0
  106. package/docs/components/Menu.md +97 -0
  107. package/docs/components/Modal.md +154 -0
  108. package/docs/components/NumberField.md +80 -0
  109. package/docs/components/OtpField.md +133 -0
  110. package/docs/components/PasswordField.md +82 -0
  111. package/docs/components/Progress.md +68 -0
  112. package/docs/components/Radio.md +91 -0
  113. package/docs/components/Select.md +181 -0
  114. package/docs/components/Skeleton.md +38 -0
  115. package/docs/components/Slider.md +140 -0
  116. package/docs/components/Snackbar.md +99 -0
  117. package/docs/components/Spinner.md +61 -0
  118. package/docs/components/Switch.md +72 -0
  119. package/docs/components/Tabs.md +207 -0
  120. package/docs/components/TextField.md +72 -0
  121. package/docs/components/Textarea.md +80 -0
  122. package/docs/components/Tooltip.md +125 -0
  123. package/docs/components/useBreakpoint.md +100 -0
  124. package/docs/components/useDialogAction.md +97 -0
  125. package/docs/components/useDrawerAction.md +75 -0
  126. package/docs/components/useModalAction.md +75 -0
  127. package/docs/components/useSnackbarAction.md +85 -0
  128. package/docs/examples/i18n-dictionary.ts +48 -0
  129. package/docs/examples/i18n-vue-i18n.ts +34 -0
  130. package/docs/examples/icon-fontawesome.ts +112 -0
  131. package/docs/examples/icon-heroicons.ts +57 -0
  132. package/docs/examples/icon-lucide.ts +57 -0
  133. package/docs/examples/icon-phosphor.ts +57 -0
  134. package/docs/examples/icon-tabler.ts +57 -0
  135. package/package.json +12 -5
  136. package/dist/Components/OtpField/OtpFieldLabel.js +0 -5
package/ai/AGENTS.md ADDED
@@ -0,0 +1,31 @@
1
+ # Bridge UI (Vue) — agent instructions
2
+
3
+ Use **`@bridge-ui/vue`**. Prefer Bridge components over ad-hoc markup.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ npm install @bridge-ui/vue
9
+ npx bridge-ui-vue-ai install
10
+ ```
11
+
12
+ ## Always follow
13
+
14
+ - Guidelines: `.ai/guidelines/core.md`
15
+ - Component docs: `.ai/docs/components/` (index: `.ai/docs/README.md`)
16
+ - Adapter samples: `.ai/docs/examples/`
17
+ - Skills (on demand) under `.cursor/skills/`:
18
+ - `bridge-ui-setup`
19
+ - `bridge-ui-components`
20
+ - `bridge-ui-forms`
21
+ - `bridge-ui-overlays`
22
+
23
+ Skills point at the docs — **read `.ai/docs/components/{Component}.md` for copy-paste examples**.
24
+
25
+ ## Hard rules
26
+
27
+ 1. Import from `@bridge-ui/vue/Components/{Name}`.
28
+ 2. Tailwind v4 + `@bridge-ui/vue/theme.css`.
29
+ 3. `createBridgeUI` / `BridgeUIProvider` + `BridgeUIHosts` when using action hooks.
30
+ 4. Fields: `v-model`.
31
+ 5. Modal body: `Card` (no `ModalCard`).
package/ai/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # AI resources (Vue)
2
+
3
+ Consumer guidelines and Agent Skills for `@bridge-ui/vue`.
4
+
5
+ Component **examples live in** [`../docs/components/`](../docs/components/). Adapter samples: [`../docs/examples/`](../docs/examples/). Skills only route agents to those pages.
6
+
7
+ ```bash
8
+ npx bridge-ui-vue-ai install
9
+ ```
10
+
11
+ ## What gets installed
12
+
13
+ | Path | Role |
14
+ | ------------------------------ | -------------------------------------------------------------- |
15
+ | `AGENTS.md` (root) | Short stub / inject block — auto-loaded by Cursor, Codex, etc. |
16
+ | `CLAUDE.md` | Symlink (or copy) to root `AGENTS.md` for Claude Code |
17
+ | `llms.txt` | Short package index some tools/LLMs look for |
18
+ | `.ai/AGENTS.md` | Full agent guide |
19
+ | `.ai/guidelines/`, `.ai/docs/` | Guidelines and component/example docs |
20
+ | `.cursor/skills/bridge-ui-*` | On-demand Agent Skills |
21
+
22
+ ## `npx` / bin link
23
+
24
+ `npx bridge-ui-vue-ai` resolves the bin from `node_modules/.bin`. After upgrading Bridge (especially with local `file:` installs), run `npm install` again so the bin link exists.
25
+
26
+ Fallbacks if `npx` hits the registry (404):
27
+
28
+ ```bash
29
+ npx --package=@bridge-ui/vue bridge-ui-vue-ai install
30
+ node node_modules/@bridge-ui/vue/bin/ai.mjs install
31
+ ```
32
+
33
+ ## Dual React + Vue
34
+
35
+ Install destinations collide (`.ai/`, `llms.txt`, skills, root stub). In apps that use both packages, run the AI install for **one** framework only.
@@ -0,0 +1,44 @@
1
+ # Bridge UI (Vue) — consumer guidelines
2
+
3
+ Use these rules when writing application code with `@bridge-ui/vue`. Do **not** invent APIs; prefer examples from package docs (`.ai/docs/components/` after install, or `docs/components/` in this package).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @bridge-ui/vue
9
+ npx bridge-ui-vue-ai install
10
+ ```
11
+
12
+ `@bridge-ui/core` is transitive. Peer: Vue ^3.4.
13
+
14
+ ## Setup (required)
15
+
16
+ 1. Tailwind CSS **v4**.
17
+ 2. `@import "@bridge-ui/vue/theme.css"`.
18
+ 3. `createBridgeUI` and/or `BridgeUIProvider` — see `.ai/docs/components/BridgeUIProvider.md`.
19
+ 4. Mount `BridgeUIHosts` when using action hooks.
20
+
21
+ ## Imports
22
+
23
+ ```ts
24
+ import { Button } from "@bridge-ui/vue/Components/Button";
25
+ ```
26
+
27
+ ## Vue patterns
28
+
29
+ | Concern | Pattern |
30
+ | ---------- | -------------------------------------------------- |
31
+ | Fields | `v-model` / `modelValue` |
32
+ | Modal open | `v-model` |
33
+ | Events | emits / `v-on:close` |
34
+ | Slots | `<template #start>` |
35
+ | Templates | kebab-case attrs (`error-message`, `custom-props`) |
36
+
37
+ ## Hard rules
38
+
39
+ - Prefer Bridge tokens (`color`, `size`, `variant`, `density`) when they exist.
40
+ - `custom-props` is for **inner** parts; root HTML attrs stay on the component.
41
+ - Modal/Drawer content: **`Card`** — no `ModalCard`.
42
+ - Do not destructure `useBreakpoint()` helpers — keep the reactive object.
43
+ - Do not compare Bridge to other UI libraries in generated docs or comments.
44
+ - Load skills under `.cursor/skills/` and read the linked `.ai/docs/` pages for examples.
package/ai/llms.txt ADDED
@@ -0,0 +1,38 @@
1
+ # @bridge-ui/vue
2
+
3
+ > Vue 3 component library. Design tokens and utils come from transitive `@bridge-ui/core`.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ npm install @bridge-ui/vue
9
+ npx bridge-ui-vue-ai install
10
+ ```
11
+
12
+ ## AI + docs (this package)
13
+
14
+ - ai/AGENTS.md
15
+ - ai/llms.txt
16
+ - ai/guidelines/core.md
17
+ - ai/skills/*/SKILL.md
18
+ - docs/README.md
19
+ - docs/components/*.md
20
+ - docs/examples/*
21
+
22
+ After install:
23
+
24
+ - AGENTS.md (root stub) + CLAUDE.md → AGENTS.md
25
+ - .ai/AGENTS.md (full guide)
26
+ - llms.txt
27
+ - .ai/guidelines/
28
+ - .ai/docs/ (components + examples)
29
+ - .cursor/skills/bridge-ui-*
30
+
31
+ ## Imports
32
+
33
+ - `@bridge-ui/vue`
34
+ - `@bridge-ui/vue/Components/{Name}`
35
+ - `@bridge-ui/vue/Provider`
36
+ - `@bridge-ui/vue/Actions`
37
+ - `@bridge-ui/vue/theme.css`
38
+ - `@bridge-ui/vue/docs/components/{Name}.md`
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: bridge-ui-components
3
+ description: >-
4
+ Use Bridge UI Vue presentational components — Button, Avatar, Card, Alert,
5
+ Badge, Icon, Link, List, Tabs, Spinner, Skeleton, Progress, classes,
6
+ customProps, slots. Use when building UI outside forms and overlays.
7
+ ---
8
+
9
+ # Bridge UI (Vue) — components
10
+
11
+ Do **not** invent APIs. Copy examples from `.ai/docs/components/{Component}.md` (package: `docs/components/{Component}.md`).
12
+
13
+ In templates, use kebab-case attrs (`start-icon`, `custom-props`, `error-message`).
14
+
15
+ ## Start here
16
+
17
+ | Need | Doc |
18
+ | ----------------------------- | -------------------------------------------------------------- |
19
+ | Actions | `.ai/docs/components/Button.md` |
20
+ | Surfaces / modal body | `.ai/docs/components/Card.md` |
21
+ | Avatar | `.ai/docs/components/Avatar.md` |
22
+ | Badge | `.ai/docs/components/Badge.md` |
23
+ | Alert | `.ai/docs/components/Alert.md` |
24
+ | Icon | `.ai/docs/components/Icon.md` |
25
+ | Lists | `.ai/docs/components/List.md` |
26
+ | Tabs | `.ai/docs/components/Tabs.md` |
27
+ | Spinner / skeleton / progress | `.ai/docs/components/Spinner.md`, `Skeleton.md`, `Progress.md` |
28
+ | Index | `.ai/docs/README.md` |
29
+
30
+ ## Hard rules
31
+
32
+ 1. Import from `@bridge-ui/vue/Components/{Name}`.
33
+ 2. Prefer Bridge tokens (`color`, `size`, `variant`, `density`) when they exist.
34
+ 3. `:classes` / `:custom-props` / named slots follow each component doc.
35
+ 4. Root HTML attributes stay on the component; use `custom-props` for **inner** parts.
36
+ 5. Modal/Drawer content uses **`Card`** — there is no `ModalCard` export.
37
+ 6. Do not compare Bridge to other UI libraries in generated docs or comments.
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: bridge-ui-forms
3
+ description: >-
4
+ Build forms with Bridge UI Vue — TextField, Textarea, PasswordField,
5
+ NumberField, OtpField, Select, Autocomplete, Slider, Checkbox, Radio,
6
+ Switch, FormField, FormControl, BaseField. Use when wiring inputs,
7
+ validation chrome, or select.
8
+ ---
9
+
10
+ # Bridge UI (Vue) — forms
11
+
12
+ Do **not** invent APIs. Copy examples from `.ai/docs/components/{Component}.md`.
13
+
14
+ ## Binding
15
+
16
+ Controlled fields: **`v-model` / `modelValue`**. Never React `value`/`onChange`.
17
+
18
+ ## Start here
19
+
20
+ | Need | Doc |
21
+ | ------------------------- | ------------------------------------------------------------------ |
22
+ | Text input | `.ai/docs/components/TextField.md` |
23
+ | Password | `.ai/docs/components/PasswordField.md` |
24
+ | Number / OTP / textarea | `.ai/docs/components/NumberField.md`, `OtpField.md`, `Textarea.md` |
25
+ | Slider | `.ai/docs/components/Slider.md` |
26
+ | Select | `.ai/docs/components/Select.md` |
27
+ | Autocomplete | `.ai/docs/components/Autocomplete.md` |
28
+ | Checkbox / radio / switch | `.ai/docs/components/Checkbox.md`, `Radio.md`, `Switch.md` |
29
+ | Input chrome | `.ai/docs/components/FormField.md` |
30
+ | Toggle row chrome | `.ai/docs/components/FormControl.md` |
31
+ | Custom control chrome | `.ai/docs/components/BaseField.md` |
32
+
33
+ ## Hard rules
34
+
35
+ 1. Map validation to `error`, `error-message`, `description`, `disabled`, `readOnly`.
36
+ 2. Native input attrs often go through `:custom-props` → `input` — see TextField docs.
37
+ 3. Use only documented option shapes for Select/Autocomplete.
38
+ 4. Prefer public fields/controls in apps. `FormField`, `FormControl`, `BaseField`, and `Listbox` are exported building blocks for advanced composition — they are **not** registry keys. Theme via the public parent (`components.TextField`, `tokens.baseField`, `tokens.formControl`, `tokens.listbox` on Select/Autocomplete). Shared form density: `global.formDefaults: { size, rounded }` (Radio/Switch ignore `rounded`).
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: bridge-ui-overlays
3
+ description: >-
4
+ Bridge UI Vue overlays and imperative actions — Modal, Drawer, Menu, Tooltip,
5
+ Snackbar, useModalAction, useDialogAction, useDrawerAction, useSnackbarAction,
6
+ BridgeUIHosts. Use when opening layers or toast/dialog flows.
7
+ ---
8
+
9
+ # Bridge UI (Vue) — overlays & actions
10
+
11
+ Do **not** invent APIs. Copy examples from `.ai/docs/components/{Component}.md`.
12
+
13
+ ## Binding
14
+
15
+ Overlays: **`v-model`**. Modal/Drawer body: **`Card`** (no `ModalCard`).
16
+
17
+ ## Start here
18
+
19
+ | Need | Doc |
20
+ | ----------------- | ------------------------------------------ |
21
+ | Modal | `.ai/docs/components/Modal.md` |
22
+ | Drawer | `.ai/docs/components/Drawer.md` |
23
+ | Menu | `.ai/docs/components/Menu.md` |
24
+ | Tooltip | `.ai/docs/components/Tooltip.md` |
25
+ | Snackbar | `.ai/docs/components/Snackbar.md` |
26
+ | useModalAction | `.ai/docs/components/useModalAction.md` |
27
+ | useDialogAction | `.ai/docs/components/useDialogAction.md` |
28
+ | useDrawerAction | `.ai/docs/components/useDrawerAction.md` |
29
+ | useSnackbarAction | `.ai/docs/components/useSnackbarAction.md` |
30
+ | Breakpoints | `.ai/docs/components/useBreakpoint.md` |
31
+
32
+ ## Hard rules
33
+
34
+ 1. Mount `BridgeUIHosts` before using action hooks.
35
+ 2. `align` on Modal applies on all breakpoints — use `useBreakpoint` for mobile bottom sheets (keep the reactive object; do not destructure helpers).
36
+ 3. Menu panel children are real components (`List` / `ListItem`, etc.). Docs may show a local `MenuContent` helper — that is **not** a Bridge export.
37
+ 4. Match action `open` option shapes to the `use*Action` docs. Escape closes the top nested layer only.
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: bridge-ui-setup
3
+ description: >-
4
+ Install and configure Bridge UI Vue — Tailwind v4 theme CSS, createBridgeUI /
5
+ BridgeUIProvider, component defaults, icon and i18n adapters, BridgeUIHosts,
6
+ useBridgeUI. Use when scaffolding Bridge, wiring the provider, or fixing
7
+ missing theme / hosts / adapters.
8
+ ---
9
+
10
+ # Bridge UI (Vue) — setup
11
+
12
+ Do **not** invent APIs. Read the package docs (after install: `.ai/docs/`; in this package: `docs/`).
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install @bridge-ui/vue
18
+ npx bridge-ui-vue-ai install
19
+ ```
20
+
21
+ ## Required reading
22
+
23
+ | Topic | Doc |
24
+ | ------------------------------------------------------ | ----------------------------------------- |
25
+ | Provider / plugin, `global`, `components`, icons, i18n | `.ai/docs/components/BridgeUIProvider.md` |
26
+ | Icon adapter usage | `.ai/docs/components/Icon.md` |
27
+ | i18n adapter | `.ai/docs/components/I18n.md` |
28
+ | Breakpoints | `.ai/docs/components/useBreakpoint.md` |
29
+ | Adapter sample code | `.ai/docs/examples/` |
30
+
31
+ ## Hard rules
32
+
33
+ 1. Tailwind CSS **v4** + `@import "@bridge-ui/vue/theme.css"`.
34
+ 2. Use `app.use(createBridgeUI({ ... }))` and/or `<BridgeUIProvider>`.
35
+ 3. Mount `BridgeUIHosts` when using action hooks (`useDialogAction`, `useModalAction`, `useDrawerAction`, `useSnackbarAction`).
36
+ 4. Semantic icon names and chrome strings need `global.icons` / `global.i18n` — copy samples from `.ai/docs/examples/`.
37
+ 5. Prefer deep imports: `@bridge-ui/vue/Components/{Name}`.
38
+ 6. Do not destructure `useBreakpoint()` helpers — keep the reactive object.
@@ -0,0 +1,319 @@
1
+ // @vitest-environment node
2
+
3
+ // ** External Imports
4
+ import { spawnSync } from "node:child_process";
5
+ import {
6
+ existsSync,
7
+ lstatSync,
8
+ mkdirSync,
9
+ mkdtempSync,
10
+ readFileSync,
11
+ realpathSync,
12
+ rmSync,
13
+ symlinkSync,
14
+ writeFileSync,
15
+ } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import { dirname, join, resolve } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ import { afterEach, describe, expect, test } from "vitest";
20
+
21
+ const __dirname = dirname(fileURLToPath(import.meta.url));
22
+
23
+ const packageRoot = resolve(__dirname, "../..");
24
+ const pkg = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
25
+
26
+ const aiRoot = join(packageRoot, "ai");
27
+ const packageName = pkg.name as string;
28
+ const binPath = join(packageRoot, "bin", "ai.mjs");
29
+ const binName = Object.keys(pkg.bin ?? {})[0] as string;
30
+
31
+ const MARK_END = "<!-- bridge-ui-ai:end -->";
32
+ const MARK_START = "<!-- bridge-ui-ai:start -->";
33
+
34
+ const skillNames = [
35
+ "bridge-ui-forms",
36
+ "bridge-ui-setup",
37
+ "bridge-ui-overlays",
38
+ "bridge-ui-components",
39
+ ] as const;
40
+
41
+ const installedRels = [
42
+ ".ai/docs",
43
+ "llms.txt",
44
+ ".ai/AGENTS.md",
45
+ ".ai/guidelines",
46
+ ...skillNames.map((name) => join(".cursor", "skills", name)),
47
+ ] as const;
48
+
49
+ let appRoot: string;
50
+
51
+ function createAppRoot() {
52
+ appRoot = mkdtempSync(join(tmpdir(), "bridge-ui-ai-"));
53
+ return appRoot;
54
+ }
55
+
56
+ function run(args: string[]) {
57
+ return spawnSync(process.execPath, [binPath, ...args], {
58
+ encoding: "utf8",
59
+ env: process.env,
60
+ });
61
+ }
62
+
63
+ function isSymlink(path: string) {
64
+ return lstatSync(path).isSymbolicLink();
65
+ }
66
+
67
+ afterEach(() => {
68
+ if (appRoot && existsSync(appRoot)) {
69
+ rmSync(appRoot, { recursive: true, force: true });
70
+ }
71
+ });
72
+
73
+ describe("bridge-ui-ai CLI", () => {
74
+ test("it should print help for help and --help", () => {
75
+ for (const args of [["help"], ["--help"], ["-h"], []]) {
76
+ const result = run(args);
77
+
78
+ expect(result.status).toBe(0);
79
+ expect(result.stdout).toContain("install");
80
+ expect(result.stdout).toContain("remove");
81
+ expect(result.stdout).toContain(`Usage: ${binName}`);
82
+ }
83
+ });
84
+
85
+ test("it should exit with code 1 for unknown commands", () => {
86
+ const result = run(["nope"]);
87
+
88
+ expect(result.status).toBe(1);
89
+ expect(result.stdout).toContain(`Usage: ${binName}`);
90
+ });
91
+
92
+ test("it should symlink AI resources and write a root AGENTS.md stub", () => {
93
+ const cwd = createAppRoot();
94
+ const result = run(["install", "--cwd", cwd]);
95
+
96
+ expect(result.status).toBe(0);
97
+ expect(result.stdout).toContain(
98
+ `Installed Bridge UI AI resources from ${packageName}`,
99
+ );
100
+
101
+ for (const rel of installedRels) {
102
+ const dest = join(cwd, rel);
103
+
104
+ expect(isSymlink(dest), rel).toBe(true);
105
+ expect(existsSync(dest), rel).toBe(true);
106
+ }
107
+
108
+ const agents = join(cwd, "AGENTS.md");
109
+ const content = readFileSync(agents, "utf8");
110
+
111
+ expect(isSymlink(agents)).toBe(false);
112
+ expect(content).toContain(MARK_START);
113
+ expect(content).toContain(MARK_END);
114
+ expect(content).toContain(packageName);
115
+ expect(content).toContain(".ai/AGENTS.md");
116
+ expect(content).not.toBe(readFileSync(join(aiRoot, "AGENTS.md"), "utf8"));
117
+ expect(realpathSync(join(cwd, ".ai", "AGENTS.md"))).toBe(
118
+ realpathSync(join(aiRoot, "AGENTS.md")),
119
+ );
120
+ });
121
+
122
+ test("it should copy AI resources when --copy is set", () => {
123
+ const cwd = createAppRoot();
124
+
125
+ const result = run(["install", "--copy", "--cwd", cwd]);
126
+
127
+ expect(result.status).toBe(0);
128
+ expect(result.stdout).toMatch(/copied\s+\.ai\/AGENTS\.md/);
129
+
130
+ for (const rel of installedRels) {
131
+ const dest = join(cwd, rel);
132
+
133
+ expect(isSymlink(dest), rel).toBe(false);
134
+ expect(existsSync(dest), rel).toBe(true);
135
+ }
136
+
137
+ const agents = join(cwd, "AGENTS.md");
138
+
139
+ expect(isSymlink(agents)).toBe(false);
140
+ expect(readFileSync(agents, "utf8")).toContain(MARK_START);
141
+ expect(readFileSync(agents, "utf8")).not.toBe(
142
+ readFileSync(join(aiRoot, "AGENTS.md"), "utf8"),
143
+ );
144
+ });
145
+
146
+ test("it should inject a Bridge block when AGENTS.md already exists", () => {
147
+ const cwd = createAppRoot();
148
+
149
+ const agentsPath = join(cwd, "AGENTS.md");
150
+
151
+ writeFileSync(agentsPath, "# My app agents\n\nKeep me.\n");
152
+
153
+ const result = run(["install", "--cwd", cwd]);
154
+
155
+ expect(result.status).toBe(0);
156
+ expect(result.stdout).toMatch(/updated\s+AGENTS\.md/);
157
+
158
+ const content = readFileSync(agentsPath, "utf8");
159
+
160
+ expect(content).toContain(MARK_END);
161
+ expect(content).toContain("Keep me.");
162
+ expect(content).toContain(MARK_START);
163
+ expect(content).toContain(packageName);
164
+ expect(isSymlink(agentsPath)).toBe(false);
165
+ expect(content).toContain("# My app agents");
166
+ expect(content).toContain(`npx ${binName} install`);
167
+ });
168
+
169
+ test("it should replace an existing Bridge block on reinstall", () => {
170
+ const cwd = createAppRoot();
171
+
172
+ const agentsPath = join(cwd, "AGENTS.md");
173
+
174
+ writeFileSync(
175
+ agentsPath,
176
+ [
177
+ "# My app agents",
178
+ "",
179
+ MARK_START,
180
+ "stale block",
181
+ MARK_END,
182
+ "",
183
+ "After block.",
184
+ "",
185
+ ].join("\n"),
186
+ );
187
+
188
+ const result = run(["install", "--cwd", cwd]);
189
+
190
+ expect(result.status).toBe(0);
191
+
192
+ const content = readFileSync(agentsPath, "utf8");
193
+
194
+ expect(content).toContain("After block.");
195
+ expect(content).toContain("## Bridge UI");
196
+ expect(content).toContain("# My app agents");
197
+ expect(content).not.toContain("stale block");
198
+ });
199
+
200
+ test("it should remove linked resources, root stub, and empty dirs", () => {
201
+ const cwd = createAppRoot();
202
+
203
+ expect(run(["install", "--cwd", cwd]).status).toBe(0);
204
+
205
+ const result = run(["remove", "--cwd", cwd]);
206
+
207
+ expect(result.status).toBe(0);
208
+ expect(result.stdout).toContain(
209
+ `Removed Bridge UI AI resources for ${packageName}`,
210
+ );
211
+
212
+ for (const rel of installedRels) {
213
+ expect(existsSync(join(cwd, rel)), rel).toBe(false);
214
+ }
215
+
216
+ expect(existsSync(join(cwd, "AGENTS.md"))).toBe(false);
217
+ expect(existsSync(join(cwd, "CLAUDE.md"))).toBe(false);
218
+ expect(existsSync(join(cwd, ".ai"))).toBe(false);
219
+ expect(existsSync(join(cwd, ".cursor"))).toBe(false);
220
+ });
221
+
222
+ test("it should strip the Bridge block and keep custom AGENTS.md content", () => {
223
+ const cwd = createAppRoot();
224
+
225
+ const agentsPath = join(cwd, "AGENTS.md");
226
+
227
+ writeFileSync(agentsPath, "# My app agents\n\nKeep me.\n");
228
+
229
+ expect(run(["install", "--cwd", cwd]).status).toBe(0);
230
+ expect(run(["remove", "--cwd", cwd]).status).toBe(0);
231
+
232
+ expect(existsSync(agentsPath)).toBe(true);
233
+
234
+ const content = readFileSync(agentsPath, "utf8");
235
+
236
+ expect(content).toContain("Keep me.");
237
+ expect(content).toContain("# My app agents");
238
+ expect(content).not.toContain(MARK_END);
239
+ expect(content).not.toContain(MARK_START);
240
+ });
241
+
242
+ test("it should recreate parent dirs when installing over a previous install", () => {
243
+ const cwd = createAppRoot();
244
+
245
+ expect(run(["install", "--cwd", cwd]).status).toBe(0);
246
+ expect(run(["remove", "--cwd", cwd]).status).toBe(0);
247
+
248
+ mkdirSync(join(cwd, ".ai"), { recursive: true });
249
+ mkdirSync(join(cwd, ".cursor", "skills"), { recursive: true });
250
+
251
+ const result = run(["install", "--copy", "--cwd", cwd]);
252
+
253
+ expect(result.status).toBe(0);
254
+ expect(existsSync(join(cwd, ".ai", "guidelines", "core.md"))).toBe(true);
255
+ expect(
256
+ existsSync(join(cwd, ".cursor", "skills", "bridge-ui-setup", "SKILL.md")),
257
+ ).toBe(true);
258
+ });
259
+
260
+ test("it should remove AGENTS.md after install --copy", () => {
261
+ const cwd = createAppRoot();
262
+
263
+ expect(run(["install", "--copy", "--cwd", cwd]).status).toBe(0);
264
+ expect(existsSync(join(cwd, "AGENTS.md"))).toBe(true);
265
+
266
+ expect(run(["remove", "--cwd", cwd]).status).toBe(0);
267
+ expect(existsSync(join(cwd, "AGENTS.md"))).toBe(false);
268
+ });
269
+
270
+ test("it should link CLAUDE.md to the root stub and remove it on uninstall", () => {
271
+ const cwd = createAppRoot();
272
+
273
+ expect(run(["install", "--cwd", cwd]).status).toBe(0);
274
+
275
+ const claude = join(cwd, "CLAUDE.md");
276
+ const agents = join(cwd, "AGENTS.md");
277
+
278
+ expect(isSymlink(claude)).toBe(true);
279
+ expect(realpathSync(claude)).toBe(realpathSync(agents));
280
+
281
+ expect(run(["remove", "--cwd", cwd]).status).toBe(0);
282
+ expect(existsSync(claude)).toBe(false);
283
+ });
284
+
285
+ test("it should not overwrite a custom CLAUDE.md", () => {
286
+ const cwd = createAppRoot();
287
+ const claude = join(cwd, "CLAUDE.md");
288
+
289
+ writeFileSync(claude, "# My Claude rules\n");
290
+
291
+ expect(run(["install", "--cwd", cwd]).status).toBe(0);
292
+ expect(isSymlink(claude)).toBe(false);
293
+ expect(readFileSync(claude, "utf8")).toBe("# My Claude rules\n");
294
+
295
+ expect(run(["remove", "--cwd", cwd]).status).toBe(0);
296
+ expect(existsSync(claude)).toBe(true);
297
+ expect(readFileSync(claude, "utf8")).toBe("# My Claude rules\n");
298
+ });
299
+
300
+ test("it should not mutate another package AGENTS.md when replacing a foreign symlink", () => {
301
+ const cwd = createAppRoot();
302
+
303
+ const otherRoot = resolve(packageRoot, "../react");
304
+ const otherAgents = join(otherRoot, "ai", "AGENTS.md");
305
+ const before = readFileSync(otherAgents, "utf8");
306
+
307
+ symlinkSync(otherAgents, join(cwd, "AGENTS.md"));
308
+
309
+ const result = run(["install", "--cwd", cwd]);
310
+
311
+ expect(result.status).toBe(0);
312
+ expect(isSymlink(join(cwd, "AGENTS.md"))).toBe(false);
313
+ expect(readFileSync(join(cwd, "AGENTS.md"), "utf8")).toContain(MARK_START);
314
+
315
+ expect(readFileSync(otherAgents, "utf8")).toBe(before);
316
+ expect(readFileSync(otherAgents, "utf8")).not.toContain(MARK_START);
317
+ expect(readFileSync(otherAgents, "utf8")).not.toContain(packageName);
318
+ });
319
+ });