## 背景 这是 #2579(详情页相关子表 tab)里明确 defer 的 follow-up —— 见该 issue 的「相关 follow-up」一节。 `PageComponentSchema` 已有 `visibility: ExpressionInputSchema`(CEL 谓词,`packages/spec/src/ui/page.zod.ts` L109),所以**页面组件**可按记录状态条件显隐。但 `page:tabs` 的 **item 本身**没有 `visibility` —— 形状只有 `{ label, icon?, children }`(`packages/spec/src/ui/component.zod.ts` `PageTabsProps`)。 后果:你能用组件级 `visibility` 隐藏一个 tab 的**内容**,但 **tab 头(标签)还在**,点进去是空的。无法真正「整条 tab 按记录状态显隐」。 典型企业需求: - "Contracts" tab 仅当 `record.status == 'customer'` 显示; - 按记录阶段 / 角色显隐的 tab。 ## 提案 给 `PageTabsProps.items[]` 加一个可选 `visibility`,复用与组件级 `visibility` / action `visible` 相同的 `ExpressionInputSchema`(CEL): ```ts items: z.array(z.object({ label: I18nLabelSchema, icon: z.string().optional(), visibility: ExpressionInputSchema.optional() .describe('Visibility predicate (CEL). When false, the whole tab (header + panel) is omitted.'), children: z.array(z.unknown()).describe('Child components'), })), ``` **语义**:`visibility` 求值为 false → 整条 tab(头 + 面板)从 tab 条移除,而不是留一个空头。求值上下文与组件级 `visibility` 一致(record + page 变量;page 变量变化时响应式重算)。 **渲染器(objectui)**:`page:tabs` 渲染时过滤掉 `visibility` 为 false 的 item;若**当前激活的 tab 被隐藏** → 自动回落到第一个可见 tab(避免空白)。 ## 设计边界(与 #2579 一致) - 这是 **Tier 2(page 层)** 能力 —— 条件 / 排布归 page,**不进对象层**(ADR-0085 准入测试:对象层只放跨表面业务意图,不放「隐藏 / 重排」)。所以只加在 `PageTabsProps`,不碰 `ObjectSchema`。 - 复用现有 CEL(`ExpressionInputSchema`),不引入新表达式机制;与 `PageComponentSchema.visibility`、action `visible` / `disabled`、nav 可见条件同源。 - 加性、向后兼容(可选字段)→ **minor**。 ## 改动范围 - **framework(spec)**:`packages/spec/src/ui/component.zod.ts` 的 `PageTabsProps.items` 加 `visibility`;补测试。 - **objectui(渲染)**:`page:tabs` 渲染器按 `visibility` 过滤 item + 激活 tab 回落;补测试。 ## 验收 - page:tabs 某 item 带 `visibility: 'record.status == "customer"'`:非 customer 记录看不到该 tab 头,customer 记录看得到。 - 激活的 tab 被隐藏时自动落到第一个可见 tab,无空白面板。 - 不带 `visibility` 的 item 行为完全不变。 Refs #2579.
Activity
os-zhuang commentedon Jul 15, 2026
基于 ADR-0089 的重新评估结论
本 issue(2026-07-05)早于 ADR-0089(2026-07-14 接受),重新评估如下:
提案本身依然成立,不需要重新设计。 Tier 2(page 层)定位、复用
ExpressionInputSchema(CEL)、不进对象层、整条 tab(头 + 面板)移除的语义、激活 tab 回落 —— 这些都不受 ADR-0089 影响,照原样落地。唯一修正:字段名从
visibility改为visibleWhen。 ADR-0089 D1 已把条件显隐谓词统一为规范名visibleWhen(与readonlyWhen/requiredWhen家族对齐),visibility/visibleOn降级为@deprecatedalias。给一个全新表面按提案原文加visibility等于在 alias 废弃窗口内新增该 alias 的使用面,方向相反。新表面不接受 alias。 ADR-0089 D2 的 alias 机制服务于已有键的存量元数据;tab item 此前没有任何 visibility 键,不存在存量 → 只声明
visibleWhen,visibility/visibleOn在 tab item 上不被接受(spec 测试已固化;渲染器也不做防御性 alias 读取,Prime Directive #12)。注意:PageTabsProps目前是 strip 模式(非.strict()),写错键会被静默丢弃 —— ADR-0089 D3b 的 lint(标记新写源码中的 deprecated alias)可以兜住visibility这个具体拼法。已按此落地(均为 draft):
PageTabsProps.items[]+visibleWhen,含测试与 changeset(minor)顺带发现(不在本 issue 范围内):渲染器实际消费的 item 级
value(?tab=URL 同步的稳定标识,objectui#2257)和count(徽标数)在PageTabsProps.items[]里未声明 —— 若未来对 items 翻.strict(),这两个键会先被误伤;需要时可另开 issue 补声明。Generated by Claude Code
test(dogfood): classify page:tabs item visibleWhen in the expression-…
feat(spec): conditional tabs — page:tabs items accept a visibleWhen C…