Anatomy
Anatomy 规范
Anatomy 是什么?
Section titled “Anatomy 是什么?”Anatomy 是 Proto UI 用来描述复合原型结构语义的系统。它回答的问题是:当一个原型被拆成 root、trigger、content、item、indicator 等多个稳定 part 后,这些 part 如何被识别为同一个 family,分别承担什么 role,以及它们之间有什么结构关系。
Anatomy 不是信息通路。组件间共享数据仍然属于 Context;组件向 App Maker 承诺可访问能力属于 Expose;User 交互进入组件属于 Event;User 感知组件结果属于 Feedback。Anatomy 只解决复合原型的结构协作问题,尤其是单靠 Context 难以表达的 role、domain、order 与 relation。
它也不是装配器。Anatomy 不创建 part,不移动 part,不自动补全缺失结构,不改写 template,也不替原型作者组合其他原型。
Family Spec
Section titled “Family Spec”Anatomy family 是一套结构语义坐标系。稳定 family 应在模块静态位置创建:
import { createAnatomyFamily } from '@proto.ui/core';
export const SELECT_FAMILY = createAnatomyFamily('base-select', { roles: { root: { cardinality: { min: 1, max: 1 } }, trigger: { cardinality: { min: 0, max: 1 } }, content: { cardinality: { min: 0, max: 1 } }, item: { cardinality: { min: 0, max: 100 } }, }, relations: [ { kind: 'contains', parent: 'root', child: 'trigger' }, { kind: 'contains', parent: 'root', child: 'content' }, { kind: 'contains', parent: 'content', child: 'item' }, ],});debugName 只用于诊断、调试和工具展示。family identity 由 token 的引用身份决定,而不是由字符串名称决定。
family spec 必须声明 root role。root claim 是 anatomy domain 的 scope anchor;没有 root,runtime query 就没有稳定的作用域。
def.anatomy.family(family, spec) 不属于原型作者 API。稳定库级 family 不应要求每个 part 重复调用 register*Family(def) helper;part 只需要在 setup 期间 claim 已经携带 canonical spec 的 family token。
Claim 与 Domain
Section titled “Claim 与 Domain”part 在 setup 期间通过 claim 声明自己承担某个 role:
def.anatomy.claim(SELECT_FAMILY, { role: 'trigger' });claim 只声明结构身份。它不会创建 part,不会注入行为,也不会替代 asHook 调用。同一个 prototype instance 在同一个 family 中只能 claim 一个 role,且 role 必须存在于 family spec 中。
root claim 划定一个 anatomy domain:
def.anatomy.claim(SELECT_FAMILY, { role: 'root' });runtime query 只在当前 instance 所属的最近 root domain 中解析。同一个 family 可以在不同位置形成多个独立 domain;查询不得跨 domain 混合 part。
Profile
Section titled “Profile”profile 是同一个 family 内的具名 refinement。它适合表达“这个 family 在某个使用形态下需要更严格的结构”,但它不是继承、派生或 override。
profile 可以:
- 收紧 cardinality
- 增加 asHook requirement
- 增加更严格 relation
profile 不可以:
- 放宽 family cardinality
- 删除 family requirement
- 移除 family relation
- 把一个 family 变成另一个 family
如果差异无法用“收紧”表达,就应该定义新的 anatomy family,而不是在 family 上建立一套继承链。
Requirement 与 Diagnostics
Section titled “Requirement 与 Diagnostics”v0 稳定的 Anatomy requirement 只有 asHook 检查:
roles: { trigger: { cardinality: { min: 0, max: 1 }, requires: [{ kind: 'hook', name: 'asTrigger' }], },}claim 表示“我是这个 role”;requirement 表示“承担这个 role 的 prototype 应具备某种行为协议能力”。requirement 不会自动调用 asHook,也不会注入行为。
diagnostics 区分两层:
| 层级 | 默认等级 | 含义 |
|---|---|---|
| family | error | family 的核心结构语义不成立 |
| profile | warning | 某个具名 refinement 的推荐或收紧结构未满足 |
非法 claim、缺失合法 domain、family cardinality/relation/requirement 失败都属于 family 级问题。profile 额外 cardinality、relation 或 requirement 未满足,默认是 warning。
Runtime Query 与 PartView
Section titled “Runtime Query 与 PartView”runtime 期间可以查询当前 domain 中实际存在的 part:
const trigger = run.anatomy.partsOf(SELECT_FAMILY, 'trigger')[0] ?? null;const hasContent = run.anatomy.has(SELECT_FAMILY, 'content');query 返回的是受限 PartView,不是 prototype instance,也不是 host node。PartView 不得暴露 root target、adapter target 或任何 raw reference。
PartView 可以读取目标 part 显式 expose 的能力:
const close = trigger?.getExpose('close');如果目标 part 没有 expose 某个 key,Anatomy 不会推断、补造或访问内部替代能力。Anatomy 因此在实现上依赖 Expose,但这种依赖是“使用 Expose”,不是继承 Expose 的信息通路身份。
Order View
Section titled “Order View”run.anatomy.order 是 Anatomy 的宿主有序结构视图。它回答的是:当前 domain 中这些 part 按宿主可观察顺序排列时是什么样。
const items = run.anatomy.order.partsOf(SELECT_FAMILY, 'item');const index = run.anatomy.order.indexOfSelf(SELECT_FAMILY, 'item');const prev = run.anatomy.order.prevOfSelf(SELECT_FAMILY, 'item');const next = run.anatomy.order.nextOfSelf(SELECT_FAMILY, 'item');order view 不等于 collection。它不负责 item metadata、selection、active item、roving focus 或键盘策略。上层 asHook 可以基于 anatomy.order 组织 collection 语义,但 anatomy.order 自身只提供结构顺序投影。
version() 表示 order signature 变化,不表示业务数据、Expose 值、State 值、Feedback 结果或 Template 内容变化。
Query Policy
Section titled “Query Policy”普通作者侧 runtime query 默认是 strict:当前 instance 如果无法解析到合法 domain,query 应该失败。
实现内部保留 missing-domain query policy,用于少数结构投影 helper,例如 useCollection 在临时结构窗口中返回 null 或 []。这不是 context.try* 那种语义可选性,也不表示 anatomy 关系本身可选。
契约实体预览
Section titled “契约实体预览”与测试的关系
Section titled “与测试的关系”Anatomy 覆盖映射到以下测试实体:
| 测试实体 | 主要覆盖 |
|---|---|
T-ANATOMY-0001 | family spec、setup registration、claim 基础规则 |
T-ANATOMY-0002 | domain resolution、runtime query、PartView safety、Expose 读取 |
T-ANATOMY-0003 | profile refinement、asHook requirement、diagnostics |
T-ANATOMY-ORDER-0001 | order view、self index、prev/next、version、privileged missing policy |
这些测试把 Anatomy 从“复合组件内部能互相找一下”收敛为可验证边界:family 如何定义,part 如何 claim,domain 如何划分,runtime 能看见什么,PartView 不能泄漏什么,以及 order view 到底承担哪一层语义。
与其他规范的关系
Section titled “与其他规范的关系”Core提供createAnatomyFamily、setup/runtime phase、render read view 与共享类型边界。Context是官方组件间信息通路;Anatomy 是结构语义补全,不替代 Context。Expose是 Component → App Maker 信息通路;Anatomy 只能读取 part 主动 expose 的能力。Template决定 render output;Anatomy 不创建、不移动、不补全 template 结构。asHook可以提供 role 所需的行为协议能力;Anatomy requirement 只诊断是否具备这些能力。Lifecycle定义 family/claim、runtime query、order query 与 cleanup 的可用时机。