来源：https://ui.docs.xihanfun.com/guide/versioning

# 版本与兼容性政策

本页回答一个问题：升级到下一个 `1.x` 小版本时，已依赖的公开面是否会变化。

XiHan.UI 的公开面横跨五种介质：替换自带皮肤、手写 Light DOM 结构都是 [皮肤与样式分层](./styling) 与 [解剖与部件契约](./anatomy) 公开说明的用法：

| 介质 | 使用者写在哪里 | 例子 |
| --- | --- | --- |
| JS / TS 导出 | 自己的源码 | `import { XhSelectRoot } from '@xihan-ui/vue'` |
| 解剖属性 | CSS 选择器 | `[data-scope='select'][data-part='trigger']` |
| `data-*` 状态属性 | CSS 选择器 | `[data-state='open']`、`[data-tone='danger']` |
| CSS 自定义属性与 `@layer` 名 | 自己的样式表 | `--xh-bg-brand`、`@layer xihan.overrides` |
| 自定义元素标签与 attribute | 自己的 HTML | `<xh-dialog modal="true">`、`data-xh-part="content"` |

破坏其中任何一种都不会产生编译错误、运行时异常或降级，只会表现为样式丢失或控件不响应。因此本页对五种介质逐类给出结论。

## 三个档位

| 档位 | 含义 |
| --- | --- |
| **受约束** | 改名、删除、收窄语义 = major。新增同类成员 = minor。 |
| **只增不减** | 只保证已有成员不消失、不改名。新增字段与条目不算破坏。 |
| **排除** | 明确不在承诺内，任何版本都可能改。使用者不应依赖。 |

版本号采用语义化版本 `major.minor.patch`：受约束的成员变更走 major，新增走 minor，只修正行为不改名字走 patch。

## 18 个包必须同版本安装

18 个包锁步发布：任何一个包发新版本，全部 18 个包一起发同一个号。

```
@xihan-ui/core     @xihan-ui/position       @xihan-ui/tokens
@xihan-ui/styles   @xihan-ui/headless       @xihan-ui/icons
@xihan-ui/vue      @xihan-ui/web-components @xihan-ui/chat-stream
@xihan-ui/markdown @xihan-ui/code-highlight @xihan-ui/backgrounds
@xihan-ui/sound    @xihan-ui/motion         @xihan-ui/animations
@xihan-ui/pointer  @xihan-ui/viz
```

两条后果：

- 某个包的 major 可能不含任何针对它自身的变更。`@xihan-ui/markdown` 升到 `2.0.0` 可能只是因为 `@xihan-ui/vue` 修改了一个 prop。判断某次 major 是否影响当前项目，以 [更新日志](../changelog) 的分类条目为准，不以版本号跨度为准。
- 不混装版本。`@xihan-ui/vue` 与 `@xihan-ui/headless` 版本不一致时类型无法对齐；`@xihan-ui/web-components` 更严格：同一个 `xh-` 标签被两个版本注册会直接抛错。适配器与其兄弟包目前是普通 `dependencies`，包管理器不会阻止这种组合，需要使用方自行保证（见[尚无门禁的条款](#尚无门禁的条款)）。

---

## 一、JS / TS 导出面

18 个包中 18 个出 JS 或类型入口，共 36 个带类型的入口。

### 受约束

| 类别 | 数量 | 说明 |
| --- | --- | --- |
| 包名 | 18 | 把代码从一个包移到另一个包 = major |
| `exports` 子路径 | 36 个 JS 入口 | 如 `@xihan-ui/vue/backgrounds`、`@xihan-ui/web-components/define`、`@xihan-ui/core/metadata`。没有 `./*` 通配，深路径引用（`.../dist/xxx.js`）会被 Node 与打包器拒绝，这些路径不是 API |
| Vue 组件导出 `Xh*` | 1056（136 个家族） | `XhButton`、`XhSelectRoot`、`XhSelectItemIndicator` |
| Vue 组合式函数 `use<家族>` | 104 | `useSelect`、`useCombobox`。不使用库内部件、自行编写标记时的唯一入口 |
| Vue 指令 | 2 | `vBackground`（`@xihan-ui/vue/backgrounds`）、`vSound`（`@xihan-ui/vue/sound`），两个子入口各依赖一个可选 peer |
| 无头内核 `connect*` | 136 | `connectAccordion` 及其参数顺序、返回的 getter 名 |
| 无头内核 `*Machine` | 80 | 机器 schema 的形状 |
| 类型 `*Props` / `*Api` / `*ChangeDetails` / `*Schema` | 145 / 120 / 103 / 80 | 删字段、改字段名、把可选改必填都是 major |
| Vue prop 名与未传入语义 | 373 个不同名字 / 1335 处声明 | 见下方说明 |
| Vue emit 名集合 | 67 个不同名字 / 243 处 | 名字受约束（列在 `emits` 中的事件不经 `$attrs` 透传，删除名字会静默改变行为） |
| Vue 插槽名 | 12 | `default`、`item`、`label`、`trigger`、`panel`、`content`、`empty`、`indicator`、`mark`、`cell`、`split`、`tooltip` |

::: tip Vue prop 的未传入语义也是契约
`disabled` 声明为裸 `Boolean`（未传入恒为 `false`）还是 `{ type: Boolean, default: undefined }`（未传入交由底层状态机决定），在类型上几乎没有差别，但决定了不传该 prop 时组件的表现。仓库中 202 处裸 `Boolean`、180 处三态、12 处 `default: true`、1 处显式 `default: false`。把某个 prop 从一种改为另一种是 major，即使名字和类型都未变。
:::

### 只增不减

| 类别 | 数量 | 说明 |
| --- | --- | --- |
| Vue 上下文类型 `*Context` / `*Callbacks` | 108 | 用于给透传的 `api` 标注类型。只保证可读，不保证可构造：新增可选字段不算破坏，因此不要写 `const c: SelectContext = { … }` 这类字面量赋值 |
| `custom-elements.json`（CEM） | 1 份 / 138 个元素 | 已经进过清单的 `tagName` / `attribute` / `event` 条目不会消失；`cssProperties`（皮肤覆盖槽）与 `events` 的 `type`（detail 类型）由 `scripts/enrich-cem.mjs` 从皮肤与元素源码生成，`gate:cem` 校验同步。字段结构细节仍不承诺，补充算 minor |

### 排除

| 类别 | 数量 | 排除原因 |
| --- | --- | --- |
| `@xihan-ui/headless` 的内部算子与常量 | 625 | `clampRating`、`buildMonthGrid`、`colorHexToRgba`、`CAROUSEL_AUTOPLAY_INTERVAL` 等。它们是内核实现的一部分，实现变化时签名随之变化。所需默认值应从组件 props 的文档默认值读取，不应 import 常量再自行比对 |
| `@xihan-ui/headless` 的内部伴生类型 | 564 | `*Refs`（机器持有的 DOM 引用袋，46 个）、`ColorHsva`、`CascaderLevel` 等，是上述函数的参数与返回类型 |
| `xxxAnatomy` / `xxxMeta` / `xxxKeyboard` 三组导出对象 | 各 136 | 它们描述的 part 名单受约束（见第二节），但这三个对象本身的组织方式不受约束。部件名单以组件文档页的解剖表为准，不应 import 这些对象 |
| Vue 的 `provide*` / `use*Context` 函数 | 115 | [Vue 适配器](../adapters/vue) 已写明父子组件之间的 provide / inject 是内部实现，不对外开放。需要下探时使用 `use<家族>()` |
| Vue 的 `useTimelineItem` | 1 | 名字形似组合式函数，实际是 inject 管道，与上一行同类 |
| 适配器运行时底座 | Vue 3 个、WC 8 个 | `createVueRuntime` / `createVueIdGenerator` / `vueNormalize`；`createLitRuntime` / `createSpreader` / `defineElement` / `discoverParts` / `wcNormalize` / `MachineController` 等。这些是适配器与内核之间的接缝，签名依赖的类型未从同一个包导出，无法构造调用 |
| WC 的元素类导出 `Xh*Element` | 110 | 仅用于 `instanceof` 与手动 `customElements.define`，不支持 `extends`（基类不导出、`wire()` 是 protected abstract）。获取元素使用 `document.querySelector` |
| WC 元素上的 `static partContract` | 136 | 部件校验的输入数据，实现细节 |
| `dist/` 内部文件名 | — | `element-Bx4xCiT2.js` 这类打包 chunk 每次构建都可能变化，不得 deep import |
| `.d.ts` 的文件布局 | — | 类型从包入口获取，不引用具体 `.d.ts` 路径 |
| `ui/tooling/`、`ui/apps/playground-*` | — | 不发布 |

---

## 二、解剖属性（`data-scope` / `data-part`）

这是更换皮肤时最核心的一组名字，也是 Web Components 使用者手写 HTML 时书写的字符串。

| 类别 | 数量 | 档位 |
| --- | --- | --- |
| `data-scope` 取值（组件身份） | 136 | **受约束**（新增第 137 个组件是 minor） |
| `data-part` 取值（部件名） | 290 个不同名字 / 1103 条「组件 × 部件」配对 | **受约束** |
| `data-xh-part`（WC 作者书写的角色声明） | 属性名 1 个，取值即上面 290 个 | **受约束** |
| `meta.requiredParts`（必备部件） | 300 条 | **受约束**（加条目 = major），方向见下 |

`data-scope` 的取值与三处完全同名，不做任何转换：headless 目录名、自定义元素标签 `xh-<scope>`、皮肤文件 `<scope>.css`。改动一处即四处同时破坏。

::: warning `data-xh-part`、`data-xh-scroll`、墨色域与液态下层的声明是 `data-xh-` 前缀里仅有的例外
其余 `data-xh-*` 属性（`data-xh-scrollbar`、`data-xh-focus-guard`、`data-xh-inert-exempt` 等 53 个）是库自用标记，排除在承诺之外。例外都由作者书写：`data-xh-part` 是 Web Components 适配器唯一的作者输入 API；`data-xh-scroll` 只承诺「作者容器取得 reset 层的原生细条」（见[皮肤与样式分层](./styling#组件内滚动)），不进任何组件契约，删除或改名同样按公开面破坏处理；`data-xh-ink` 与 `data-xh-ink-margin` 写在作者自己的彩色区块上，声明底色极性与余量（见[色彩 · 彩色面与墨色域](/design/colors#彩色面与墨色域)），取值与语义按公开面承诺；`data-xh-backdrop` 与 `data-xh-backdrop-busy` 写在作者自己的图片、视频、画布区域上，声明液态面下层的明暗与杂乱（见[设计令牌与主题 · 液态材质](./theme#液态材质)），同样按公开面承诺。作者书写 `data-xh-part="trigger"` 是声明，元素接线后在同一节点写入 `data-scope` + `data-part` 是事实。皮肤匹配后者，后者不应手写。
:::

### requiredParts 的方向是反的

向 `requiredParts` 新增一条是收紧：原本可运行的 Light DOM 开始在 [诊断通道](./diagnostics) 报 `wc.missing-part`。规则：

- 删条目：任何版本都可以。
- 加条目：属于破坏性变更，只出现在 major，并且落地即按 `error` 报告。缺少必需角色节点恒发 `error`，没有先以 `warn` 过渡一个版本的档位。

---

## 三、`data-*` 状态属性

`connect` 一共产出 237 个不同的 `data-*` 属性名、1662 条「组件 × 属性」配对。分两类。

### 受约束

行为契约。这些不是样式钩子，删除会使功能静默失效：

| 属性 | 作用 |
| --- | --- |
| `data-value` | 集合型组件的条目值。行为层按它反查节点执行方向键导航，Web Components 使用者需手写在 `item` 节点上。删除后所有集合型组件的键盘导航静默失效 |
| `data-name` | 表单字段名（`form`） |
| `data-index` | 条目序号（0 基） |

样式钩子。自带皮肤消费了 183 个属性名 / 793 条「皮肤 × 属性」配对（不含解剖的 `data-scope` / `data-part`），第三方皮肤参照的就是这一组：

| 属性 | 选中它的皮肤份数 |
| --- | --- |
| `data-size` | 79 |
| `data-state` | 64 |
| `data-disabled` | 58 |
| `data-variant` | 32 |
| `data-invalid` | 27 |
| `data-orientation` / `data-readonly` | 各 26 |
| `data-placement` | 22 |
| `data-highlighted` | 20 |
| `data-hidden` / `data-motion` | 各 18 |
| `data-dragging` | 13 |
| `data-tone` | 10 |
| `data-current` / `data-placeholder` / `data-empty` / `data-clearable` … | 其余 |
| `data-cols-sm` / `-md` / `-lg` / `-xl` | 仅 `grid`，断点分档名受约束 |

份数低不等于使用少：`data-tone` 的规则集中在 `tone.css` 一份文件中，浮层的 `data-placement`
同样集中在定位块中，逐份皮肤只在需要额外微调时才单独选中它们。属性名与取值的约束不按份数区分。

取值同样受约束。属性名变更会破坏皮肤，取值变更同样破坏且更隐蔽：`[data-state='open']` 在取值改为 `expanded` 之后仍是合法 CSS，只是永远不匹配。自带皮肤当前用到的 34 个 `data-state` 取值全部受约束：

```
open  closed  checked  unchecked  indeterminate  on
active  current  incomplete  visible  hidden  ready  empty  invalid  error
completed  preparing  finishing  dismissing  picking  copying
```

取值的真源是 `tooling/scripts/state-vocabulary.json`，按族登记了 7 个族共 38 个取值：同族互斥，一个部件同一时刻只取其中一个。上面 21 个是皮肤当前选中的取值，其余只在 DOM 上出现，尚无对应的皮肤规则。`check-state-vocabulary` 双向校验：`connect` 发出词汇表外的值报错，皮肤选中词汇表中不存在的值同样报错。

三条视觉轴的取值同理。`data-tone` 的合法值是 6 个，唯一真源是 `tone.css`：

```
brand  neutral  success  warning  danger  info
```

### 排除

其余 45 个 `data-*`（`data-lane`、`data-one-way`、`data-checked-count`、`data-sticking`、`data-file-size`、`data-modules` 等）：自带皮肤未选中它们，组件文档页也未记载。`check-state-vocabulary` 每次都会列出这批发射但零引用的属性名（连同上面 `data-value` / `data-index` 两个数据载体，共 47 个）。它们在 DOM 上存在，但不承诺稳定，随时可能改名或消失。需要依赖它们编写样式时，先提交 issue。

---

## 四、CSS 自定义属性与 `@layer`

### 受约束

| 类别 | 数量 | 说明 |
| --- | --- | --- |
| `@layer` 名与声明顺序 | 5 | `xihan.reset` → `xihan.tokens` → `xihan.motion` → `xihan.components` → `xihan.overrides`。改名、调序、增删中间层均为 major。`xihan.overrides` 刻意留空，专供使用方覆盖，不会作为未使用的层被清理 |
| 全局令牌 · 原语层 | 115 | `--xh-color-brand-500`、`--xh-space-4`、`--xh-radius-md`。皮肤中不应直接使用它们，但接入品牌轴必须写 `--xh-color-brand-*`，因此它们是公开的 |
| 全局令牌 · 语义层 | 302 | `--xh-bg-brand`、`--xh-fg-on-brand`、`--xh-control-h-md`、`--xh-shape-control`。主题定制的正门，见 [设计令牌与主题](./theme) |
| 组件覆盖槽 | 4088（覆盖 135 个组件） | `--xh-button-bg`、`--xh-button-h`、`--xh-dialog-max-w`。全部写成 `var(--xh-x-y, 默认值)` 形态，在 `:root` 中设置即可修改该组件 |
| 语气轴槽 | 12 | `--xh-_tone`、`--xh-_tone-on`、`--xh-_tone-hover`、`--xh-_tone-subtle`、`--xh-_tone-border` 等。这是自定义语气的唯一机制：写入 `[data-tone='premium'] { --xh-_tone: gold; --xh-_tone-on: #000 }`，读取这批槽的 58 份皮肤随之生效。虽然带下划线前缀，但按受约束处理 |
| 关键帧名 | 40 | `xh-pop-in`、`xh-fade-out`、`xh-spin`。共享关键帧住在 `family/motion.css`（子入口 `@xihan-ui/styles/motion.css`），皮肤 `@import` 它；组件专属关键帧仍在各皮肤。在 `xihan.overrides` 层中重定义同名关键帧即可替换该段动画（关键帧名因此是公开面），所以改名与删名同样是 major |
| 跨包内联属性 | 4 | `--xh-_truncate-lines`、`--xh-_float-button-offset`、`--xh-_tour-spotlight-radius`、`--xh-_carousel-autoplay-duration`。由 headless 写入内联 `style`，皮肤必须读取。整套更换皮肤时若不读取这些值，文本截断、浮动按钮贴边、引导目标圆角或轮播进度会失效，且不报任何错误 |
| `@xihan-ui/styles` 的 CSS 子路径 | 160 | `.`、`./index.css`、`./index.unlayered.css`，家族文件 `./action-control.css`、`./chart.css`、`./collection-item.css`、`./field-chrome.css`、`./swatch.css`、`./material.css`、`./motion.css`，与 148 条 `.css`：136 份组件皮肤加 `./layers.css`、`./tone.css`、`./reset.css`、`./overlay-arrow.css`、`./visually-hidden.css`、`./undefined.css`、`./focus.css`、`./label.css`、`./description.css`、`./pointer.css`、`./forced-colors.css` |
| `@xihan-ui/tokens` 的 CSS 子路径 | 2 | `./tokens.css`、`./tokens.json` |

### 排除

| 类别 | 数量 | 排除原因 |
| --- | --- | --- |
| 其余 `--xh-_` 私有槽 | 465 | 皮肤内部的回退中转（`--xh-_bg`、`--xh-_bg-hover`、`--xh-_mention-py` 等），变体只改槽位、不重写规则依赖它。不应在外部设置它们 |
| 令牌的取值 | — | `--xh-color-brand-500` 这个名字受约束，其对应的 `oklch()` 值不受约束。调色板随视觉迭代变化，这正是令牌存在的意义 |
| `index.css` 的内部结构 | — | 它是生成的扁平文件：家族配方只内联一次、排在皮肤之前，皮肤段的顺序即源序。段标记注释（`/* styles/xxx.css */`）只作阅读定位，不是承诺 |
| `index.unlayered.css` 的内部结构 | — | 它是同一源序的扁平镜像，不带 `@layer`。使用该入口时没有 `xihan.overrides` 覆盖槽位，层名承诺不适用 |

::: warning 命名前缀不能反推归属
`--xh-field-py` 形似 `field` 组件的覆盖槽，实际是全局语义令牌，`field.css` 本身并不使用它。同理 `--xh-text-*`（13 个全局文本令牌）与 `text-field` 的 48 条组件槽同前缀，`--xh-color-*`（43 个原语调色板令牌）与 `color-picker` 的 70 条组件槽同前缀。判断一条属性属于哪一档，看它在不在上表列的那 660 个全局令牌里，不按前缀推断。
:::

---

## 五、自定义元素与 attribute

| 类别 | 数量 | 档位 |
| --- | --- | --- |
| 自定义元素标签 `xh-*` | 139（`defineXhElements()` 注册 138 + `xh-background`） | **受约束** |
| 注册函数 | 2（`defineXhElements`、`defineXhBackground`） | **受约束** |
| observed attribute | 1357 条声明 / 393 个不同名字 | **受约束**（具体元素上的具体属性名） |
| attribute 名词汇表本身 | 393 | **只增不减**（新组件复用 `size` / `tone` / `dir` 不算破坏） |
| `CustomEvent` 名 | 96 个名字 / 210 条「元素 × 事件」 | **受约束** |
| 事件传播语义 | `bubbles: true, composed: true`（195 处中 193 处） | **受约束**。取消冒泡会使祖先节点上的事件委托静默失效。例外是名为 `submit` 的事件（`xh-prompt-input` / `xh-question-flow`）：与原生表单提交同名，一律不冒泡，避免被祖先 `<form>` 视为自身的提交 |
| 事件 `detail` 形状 | 188 个 `*Details` 类型 | **受约束**，等同于 headless 的同名类型 |
| `attribute: false` 的 JS 字段 | 223 条（涉及 76 个字段名） | **受约束**。`collection`、`translations`、`validate`、`filter` 等只能通过 JS 赋值，HTML 中无法表达：不是每个 property 都有对应 attribute |
| 命令式方法 | 97（分布在 50 个元素） | **受约束**，含参数与返回类型 |

命令式方法全清单：

| 元素 | 方法 |
| --- | --- |
| `xh-form` | `setFieldValue` `setFieldError` `clearErrors` `submit` `reset` `getFieldId` `getFieldValue` `getFieldError` |
| `xh-notification` | `create` `updateItem` `dismiss` `dismissAll` `getItemsByPlacement` |
| `xh-file-upload` | `openFilePicker` `setFiles` `addFiles` `deleteFile` `clear` |
| `xh-timer` | `start` `pause` `resume` `reset` |
| `xh-virtualizer` | `scrollToIndex` `measureElement` `measure` |
| `xh-signature-pad` | `clear` `toSvg` |
| `xh-log` | `scrollToBottom` |
| `xh-tour` | `remeasure` |

::: tip 布尔 attribute 是三态的
`modal="false"` 表示关闭，不写表示交由组件决定，这与原生 HTML 布尔属性（写出即为真）不同。把三态转换器换回原生语义是不改名字的语义收窄，按 major 处理。
:::

以下与包结构有关的事实同样在承诺内：

- 元素在 `@xihan-ui/web-components/define`，不在包主入口。`defineXhElements()` 是全量注册：调用即注册全部 138 个元素，没有逐个的 `defineXhButton()`。（补充细粒度 define 是 minor；补充后 `defineXhElements` 的全量语义即成为承诺。）
- `xh-background` 单独在 `@xihan-ui/web-components/backgrounds`，因为它依赖可选 peer。把它移入 `./define` 会强制所有使用方安装 WebGL 引擎，属于破坏性变更。
- 元素全部是 Light DOM，没有 shadow root，`::part()` 不生效。CEM 中的 `cssParts` 条目在本包读作 `data-xh-part`，不是 shadow part。

---

## 六、什么算破坏性变更

正例（major）与反例（minor / patch）对照，按介质各举数条。

### JS 导出

| 算破坏 | 不算破坏 |
| --- | --- |
| 删除或改名任一 `Xh*` 组件（含 `XhTreeSelectBranchIndicator` 等低频部件） | 新增组件、新增部件 |
| `connectAccordion` 的返回值把 `getRootProps` 改成 `getProps` | 给 `connectAccordion` 加一个新的 getter |
| `SelectProps` 新增一个必填字段，或把可选字段改为必填 | `SelectProps` 新增可选字段 |
| `SelectApi` 删除一个 getter | `SelectContext` 新增一个可选字段 |
| 把 `useSelect()` 的返回从 `ComputedRef<Api>` 换成其他容器 | 新增 `useXxx` |
| 把某个 prop 从裸 `Boolean` 改成 `default: undefined` | 给某个 prop 补文档 |
| 从某个组件的 `emits` 数组中删除一个名字 | 向 `emits` 新增一个名字 |

### 解剖与 `data-*`

| 算破坏 | 不算破坏 |
| --- | --- |
| `data-part` 从 `indicator` 改成 `marker` | 给某个组件新增一个可选部件 |
| 把 `item-text` 改成 `itemText`（分词/大小写变化） | 皮肤内部改用其他选择器组合实现同样的视觉 |
| 某个组件不再输出 `data-size` / `data-tone` | 给某个组件补上原本没有的 `data-tone` |
| `data-state` 的取值从 `open` 改成 `expanded` | 新增一个 `data-state` 取值 |
| 把 `data-disabled` 从「存在即禁用」改成 `data-disabled="true"` | — |
| 删除 `data-value` | 删除 `data-lane`（在排除清单中） |

### CSS

| 算破坏 | 不算破坏 |
| --- | --- |
| `--xh-button-h` 改名成 `--xh-button-block-size` | 新增 `--xh-button-letter-spacing` |
| 删除 `--xh-radius-xl`（即使库内部未使用） | 调整 `--xh-color-brand-500` 的 `oklch()` 值 |
| 把 `var(--xh-alert-px, …)` 改为直接写语义令牌（等于取消该覆盖点） | 给某条规则补充 `@supports` 兜底 |
| 调整 `@layer` 声明顺序 | 在 `xihan.components` 里加规则 |
| 删除 `./tone.css` 子路径 | 新增 `./<新组件>.css` 子路径 |

### 自定义元素

| 算破坏 | 不算破坏 |
| --- | --- |
| `xh-empty-state` 改名成 `xh-empty` | 新增 `xh-xxx` 元素 |
| 某元素的 `observedAttributes` 减少一条 | 给某元素新增 attribute |
| 把 `read-only` 改写成 `readonly` | 把某个 `attribute: false` 的字段反向暴露成 attribute |
| 事件名 `value-change` 改成 `change` | 新增事件名 |
| 把某个事件的 `bubbles` 改成 `false` | 补 `HTMLElementEventMap` 类型增强 |
| `xh-notification.create()` 从返回 `string` 改成返回对象 | 给 `xh-form` 新增一个方法 |
| 把某个可选 part 提升为 `requiredParts` | 新增一个可选 part |

### 支持面

| 算破坏 | 不算破坏 |
| --- | --- |
| 抬高 Node 下限 | 降低 Node 下限 |
| 抬高浏览器硬底线（使用一个无兜底的新 CSS 特性） | 新增一个带退化路径的增强特性 |
| 收窄 `vue` peer 区间（加上限、抬下限） | 放宽 peer 区间 |
| 撤销 ESM 导出条件 | 增加 CJS 导出条件 |

---

## 七、移除流程

本库不设废弃期，也不保留兼容层。改名直接改，删除直接删：不为旧写法保留别名、不把旧名放入兜底链、不在源码中标注 `@deprecated` 延长存续。在一个 major 中读到的名字就是当时唯一存在的名字。

移除动作集中在 major，一律出现在更新日志顶部、单列一节，逐条写明旧名与替换写法，这是唯一的迁移材料。

四种介质需要单独说明：`data-*`、CSS 自定义属性、`@layer` 名、元素 attribute 没有 IDE 提示，改名之后对应选择器只会静默失配，不报错也不降级。它们唯一的告知渠道是更新日志，因此每次移除都在 changeset 中逐条列出旧名与替换写法，使用方可据此在自己的代码库中全文搜索。

---

## 八、支持面

收窄支持面按破坏性变更处理，放宽随时可以。

### 运行时

| 项 | 值 | 变更规则 |
| --- | --- | --- |
| Node（安装并运行本库） | **≥ 18** | 抬高 = major。产物实际用到的最高特性是 `Object.hasOwn`（Node 16.9），`>=18` 留了余量 |
| Node（开发本仓库） | ≥ 24，pnpm ≥ 11 | 与上一行无关，随时可变 |
| 模块格式 | **ESM only** | 无 CJS、无 node10 解析条件。包中 `main` / `module` 字段存在但指向 ESM 文件，是给旧式 bundler 的别名，不代表可以 `require` |
| 包管理器 | 无要求 | — |

### 浏览器

硬底线由三个无兜底的 CSS 特性决定：`@layer`（125/125 个样式文件使用，不识别它的解析器会丢弃整块）、`oklch()`（`tokens.css` 中 63 处，是唯一颜色来源）、`color-mix()`。

| 浏览器 | 硬底线 | 完整保真 |
| --- | --- | --- |
| Chrome / Edge | **111** | 111 |
| Firefox | **113** | 121（`:has()`） |
| Safari | **16.2** | 16.4 |

测试承诺：当前正式版，外加 Chromium 与 Safari 各向前两个大版本、Firefox ESR。硬底线只在 major 中抬高。

低于硬底线不会降级，而是无样式：皮肤中刻意不写兜底值，令牌缺席是缺陷而不是降级。

高于硬底线又无从兜底的特性，皮肤一律不用，由 `check-css-floor` 的拒绝名单拦截（`@scope`、`@starting-style`、嵌套选择器等）。其中容易误写的一条：

| 特性 | 最早完整支持 | 底线内的引擎不认时 | 皮肤的写法 |
| --- | --- | --- | --- |
| 媒体查询区间写法 `(width < 768px)` | Chrome 104 / Firefox 63 / Safari 16.4 | Safari 16.2 / 16.3 把整条查询判作不成立，块内规则静默丢失 | 断点只写 `(min-width: …)`，窄档专属规则写它的补集 `not all and (min-width: …)` |

可选增强层（不支持时按下表退化；新增增强特性不算破坏，前提是同时提供退化路径）：

| 特性 | Chrome | Firefox | Safari | 不支持时 |
| --- | --- | --- | --- | --- |
| `:has()` | 105 | 121 | 15.4 | 少量间距/gutter 微调失效 |
| `scrollbar-gutter` | 94 | 97 | 18.2 | 滚动条出现时轻微位移 |
| `dvh` / `svh` / `lvh` | 108 | 101 | 15.4 | 回落到 `vh` |
| `text-wrap: balance` | 114 | 121 | 17.5 | 标题按默认换行 |
| `light-dark()` | 123 | 120 | 17.5 | 代码块语法色退化成单色 |
| `field-sizing` | 123 | 152 | 26.2 | `prompt-input` 输入框退化成固定行数 |

Web Components 侧不构成额外约束：全部 Light DOM，不使用 shadow DOM、`ElementInternals`、`adoptedStyleSheets`，平台要求只到 Custom Elements v1。

### 宿主框架

| 包 | peer | 规则 |
| --- | --- | --- |
| `@xihan-ui/vue` | `vue: ^3.5.0` | 下限由 `useId` 决定（Vue 3.5 引入）。收窄为 major，放宽为 minor |
| `@xihan-ui/vue`、`@xihan-ui/web-components` | `@xihan-ui/backgrounds`（optional） | 不使用背景效果时无需安装 |
| `@xihan-ui/vue` | `@xihan-ui/sound`（optional） | 不使用音效时无需安装 |
| `@xihan-ui/web-components` | 无框架 peer | 原生自定义元素，不要求任何框架 |
| 其余 15 个包 | 无 | — |

---

## 九、包的稳定性分级

锁步发版意味着版本号不反映稳定性，因此单独列表。

下面两张表覆盖全部 18 个包，每个包必在其中一张中；门禁比对两张表的包名与 `packages/` 下的公开包，新增包未定级即构建失败。

### 稳定

破坏性变更只出现在 major，移除按上文流程执行。

| 包 | 说明 |
| --- | --- |
| `@xihan-ui/vue` | 1056 个组件、104 个组合式函数 |
| `@xihan-ui/react` | 组件与 hooks（铺开中，公开面随批次增长） |
| `@xihan-ui/web-components` | 139 个自定义元素 |
| `@xihan-ui/headless` | `connect*` / `*Machine` / 各类公开类型；内部算子在排除清单里 |
| `@xihan-ui/styles` | 136 份组件皮肤、5 个层名 |
| `@xihan-ui/tokens` | 660 个令牌名，外加 `./runtime` 的主题控制器与种子色 API |
| `@xihan-ui/icons` | 图标集 |
| `@xihan-ui/core` | 只有被适配器与 headless 公开消费的那部分（`createAnatomy`、`createNormalizer`、归一化规则、状态机公开面），含 `data-value` 这条集合导航契约；另有 `./date` 子入口的全部导出（`PlainDate` / `PlainTime` / `PlainDateTime`、时区换算、周规则、边界函数与格式化器） |
| `@xihan-ui/position` | `createPositionEngine` 与它的选项；其余 9 个导出是内部算子 |
| `@xihan-ui/motion` | 缓动名、时长常量、`animate`、补间与弹簧算子。三值与令牌层同源（`check-motion-source` 比对），`core` / `headless` 与各适配器均建立在它之上，改名会先破坏库自身 |
| `@xihan-ui/pointer` | `createPointerSession` / `createMultiPointerSession` 与四层几何纯函数。`headless` 与各适配器的拖拽、缩放、划动全部经由它，同上 |

### 实验

这七个包的破坏性变更可以出现在 minor 中，不适合用于不易升级的生产代码。

| 包 | 仍在变化的原因 |
| --- | --- |
| `@xihan-ui/viz` | 图表组件按期接入，比例尺、形状、场景与过渡的签名随组件落地仍会调整 |
| `@xihan-ui/code-highlight` | 承诺面是 `HighlighterPort` 端口，自研分词器（`tokenizeCode`、`langSpecOf`、`LangSpec`）随时可能整体替换 |
| `@xihan-ui/markdown` | 公开面是 `createStreamRenderer` 与它的三个类型，外加 `blockKind` / `fenceLang` / `isFenceClosed` / `LIVE_BLOCK_KEY` 四个切块算子；解析、切块、缓存的中间件随时可能变化 |
| `@xihan-ui/chat-stream` | AI 会话协议类型（`UIMessage`、`TextPart` 等）跟随上游生态演进 |
| `@xihan-ui/backgrounds` | 效果参数与点云 API 仍在调整；通用短名（`bool` / `num` / `str` / `rgb`）不是公开 API |
| `@xihan-ui/animations` | 公开面是 `MotionSpec` 的字段与 `BUILTIN_MOTION_NAMES` 里的预设名。预设表随组件接入继续调整，预设改名没有门禁拦截：它不是导出名，删除不会被报告 |
| `@xihan-ui/sound` | 同上：`SoundSpec` 的层与包络字段、三套主题、`BUILTIN_SOUND_NAMES` 中的预设名仍在调整 |

---

## 门禁覆盖范围

### 已由门禁保证

六种介质的改名即 major 已有门禁保证。`pnpm gate:surface` 运行的 `check-public-surface`
以入库基线（`ui/tooling/public-surface.json`，16366 个名字）比对当前状态：
基线中有而当前没有，即为删除或改名，构建失败。新增一律放行，因为新增是 minor。

覆盖：包名与 198 条子入口、8513 个导出名、136 个 `data-scope` 与 1103 条部件配对、
136 个组件的 1763 个 prop 名、239 种 `data-*`、34 个 `data-state` 取值、660 个令牌、
5 个 `@layer` 名、4088 个组件覆盖槽、138 个自定义元素及其 attribute 与事件。

prop 名一维是后补的：在它加入之前，修改一个 prop 名（实测 `transfer` 的 `items` 改
`collection`、`splitter` 的 `size` 改 `sizes`）其余门禁全程沉默。它的事实源是无头内核的
`<组件>Schema['props']`，各适配器的 props 均按它展开。

它的必要性可以复现：把 `switch` 的 `thumb` 改名 `knob`，其余门禁全部通过，
只有这一道拦截；把 `switch` 的 `checked` 改名 `isChecked`，同样只有它报出
「组件 prop switch: checked」。删除一个语义令牌、修改一个组件覆盖槽名同理。

确需破坏性变更时，运行 `pnpm surface:update` 更新基线并在 changeset 中说明：
门禁拦截的是无意删除，不是有意的 major。

::: warning 基线锁定的名字比本页承诺的范围宽
基线是所有可写出名字的机械快照，不区分档位：本页归入排除的成员同样在内，包括
headless 的 625 个内部算子与常量、564 个伴生类型、`*Anatomy` / `*Meta` / `*Keyboard` 三组导出对象，
以及六个实验包的全部导出。政策允许随时删改它们，但删除仍会触发门禁。
行使这份删除权时先运行 `pnpm surface:update` 更新基线，这不是破坏性变更，
changeset 按 patch / minor 记录即可：更新基线这一动作本身不代表 major。
:::

另有三道门禁把原本依赖自觉的条款转为机器检查。`check-css-floor` 保证浏览器硬底线：
`.browserslistrc` 记录底线，拒绝名单拦截 `@scope`、媒体查询区间写法等无兜底的抬底线特性，
`light-dark()` / `dvh` / `lh` 必须带级联兜底。`check-version-lock` 保证 18 包锁步：任何一个
package.json 的 version 与其余不同，门禁直接失败。`check-wiring` 保证检查系统自身：新增的
check 脚本未接入 `pnpm gate` 等同于未编写，死引用同样被拦截。

三条视觉轴已收敛为联合类型，`tone` / `size` / `variant` 不再是裸 `string`，
写错取值在编译期报错。Vue 事件载荷已有类型，101 个组件的 `emits` 全是对象式，
产物 `.d.ts` 中不再出现 `(...args: any[]) => any`。

### 尚无门禁的条款

以下条款目前没有门禁。

| 条款 | 现状 | 计划补充的机制 |
| --- | --- | --- |
| Vue 作用域插槽载荷 | 带载荷的插槽已声明 `slots:` 选项，由 `check-slot-types` 门禁的四条判据保证（缺声明 / 键非可选 / 值非函数 / 声明未用）。仅渲染无载荷插槽的部件仍不声明，消费方写错 slot 名不会报错 | 无载荷插槽也补充声明，或明确只有带载荷的插槽进入契约 |
| 移除提示（CSS / `data-*` / attribute / 层名） | 这四种介质没有 IDE 提示：名字移除之后，消费方的声明只是静默失配，既不报错也不降级。唯一的告知渠道是更新日志，因此每次移除都在 changeset 中逐条列出旧名与替换写法，供使用方在自己的代码库中全文搜索 | 无 |
| 浏览器硬底线 | 已落地：`.browserslistrc` 记录硬底线，`check-css-floor` 门禁拒绝抬底线的无兜底特性（`@scope`、媒体查询区间写法等），并校验 `light-dark()` / `dvh` / `lh` 的级联兜底 | 拒绝名单改动时联动本页支持面表格的提醒 |
| 「18 个包必须同版本」 | `check-version-lock` 门禁保证 18 个 package.json 同版本。运行期一侧只在 dev 有提示：三个适配器启动时调用 `checkLockstepVersion`，自身版本与 core 不一致时经诊断通道发出一条 `warn`；生产构建中跳过，且只比对适配器与 core 两个版本，不是全部 18 个 | 提升为 peer，或把运行期比对扩展到全部包 |

本页内容与实际行为不符时按缺陷处理，请提交 issue。
