Skip to content

feat(showcase): 动作显隐矩阵——四个动作面 × 三种 visible 写法 × requiredPermissions × 每种字段类型的谓词 - #6157

Merged
baozhoutao merged 5 commits into
mainfrom
claude/showcase-action-visible-matrix
Aug 7, 2026
Merged

feat(showcase): 动作显隐矩阵——四个动作面 × 三种 visible 写法 × requiredPermissions × 每种字段类型的谓词#6157
baozhoutao merged 5 commits into
mainfrom
claude/showcase-action-visible-matrix

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

showcase_field_zoo 加一整套可证伪的动作门控夹具,覆盖决定「按钮出不出现」的两件事:visible(记录上的 CEL 谓词)与 requiredPermissions(ADR-0066 D4 能力门)。

配套的 objectui 修复:objectstack-ai/objectui#3548(closes objectui#3492 / #3501)。本 PR 是那两个修复的实测夹具,也是它们的验收现场。

为什么需要它

一个门由四个各自过滤自己列表的面求值:列表工具栏 / 行内 kebab / 记录页头 / 批量选择栏。它们已经在两个方向上跑偏过:批量栏整个忽略 requiredPermissions(行 kebab 藏、勾选后又冒出来,objectui#3492);关系字段在一个面上绑成展开记录、另一个面上绑成外键,同一条谓词一真一假(objectui#3501)。任何只测单个面的用例都看不见这类 bug。

两条种子记录是可证伪性的来源:Specimen — Full(字段全填)必须出现,Specimen — Minimal(多数为 null)必须消失 —— 写错的表现是按钮出现在错误的地方,而不是一个悄悄变绿的测试。

内容

src/ui/actions/predicate-matrix.action.ts

  • 四面一致性 —— showcase_zoo_relation_gate 是最利的一条:record.f_lookup == record.f_lookups[0],两个操作数都是 lookup,谁被展开谁就变成对象、比较就静默为假。gated_columns 视图把 f_lookup 设为列(于是被 populate=)、默认列表两个都不展开 —— 同样的记录、同样的谓词,两个视图必须给出同一结论。两个字段都不是默认列表的列,同时也把 $select 投影压上测试台。
  • 三种 visible 写法 —— 裸 CEL 字符串 / P`…` 标签模板 / { dialect, source } 信封,同一条谓词并排钉住;三者若在同一条记录上分歧,就是某个渲染器把某一种当特例了。布尔不在此列ExpressionInput 不接受它,objectstack build 发不出这种形状,由 objectui 自己的单测钉(不在这里假装覆盖)。
  • requiredPermissions —— 持有 / 不持有 / 两个取 AND / 空声明四种,四个面同一结论。
  • 字段类型谓词园 —— 每种字段类型一条 visible:lookup(单/多/空)、master_detail、tree、user、文本族、数值族、时间族、布尔/选项族、json / location / address / composite / repeater / vector,以及 AND / OR / NOT / 三元。全部对着真实 runtime 的 CEL 引擎实测过,不是照着规范猜的。

src/security/capabilities.ts —— 新增 showcase.restricted_ops定义了但不授予任何权限集。这是 ADR-0066 三方分离里通常被省掉的、可证伪的那一半 —— 没有一个「谁都不持有」的能力,就没有东西能在某个面停止应用门控时报警,而那正是批量栏当初整个忽略 requiredPermissions 却无人察觉的原因。

src/ui/views/field-zoo.view.ts —— 默认列表(列窄,被门控的字段都不是列)、gated_columns(把关系字段提为列,于是被展开)、inline_bulk_defsbulkActionDefs + execution: 'aggregate',弹窗报告合格/跳过拆分)。默认列表另带一条关系字段的条件格式规则。

顺带钉住三条实测得到的作者规则

  1. 必须 record. 前缀(裸字段在页头路径是未声明变量,抛错→fail-closed 隐藏);
  2. 遍历/调方法前必须判空 —— null 没有成员也没有方法,不判空在 Minimal 上直接 fault,而 fault 在行/批量面 fail-closed、在宽松路径 fail-open;
  3. contains() / matches()不要 startsWith() / endsWith() —— 后者不是 CEL,objectui 会降级到遗留 JS 引擎并打 deprecation 警告,服务端引擎则根本不认。

一个故意留下的红色夹具

showcase_zoo_dialect_split:同一条 .contains() 谓词,在行内 kebab 里正常、在记录页头被判抛错并隐藏 —— 页头不走 CEL 引擎(objectstack-ai/objectui#3521,实测控制台原话已附在该单里)。按仓库「绝不粉饰未交付的能力」的规矩,不改写成能过的写法,而是留成一屏可对比的现场;#3521 落地后这个按钮应当出现在页头,那就是它的验收条件。

同样明说的取舍:归属谓词的自然写法 record.owner_id == os.user.id 编译不过 —— 平台注入列不在发布的对象 metadata 里,作者时校验判 unknown field。已报 #6113,showcase 里退到自己声明的 user 字段并在注释里写清原因,不绕过。

测试

pnpm validate exit 0(无新增 error)、pnpm typecheck 通过、pnpm test 146 passed。

真机实测(showcase :3100 + objectui console dev :5181):$select 实际带上谓词读的 34 个非列字段;三个记录面结论一致;四种 requiredPermissions 表现符合预期;gated_columnspopulate=f_lookup,f_master_detail 时关系比关系仍为真;聚合批量弹窗正确报「1 条所选记录不符合条件,将被跳过」。

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 6:05am

Request Review

@github-actions github-actions Bot added the size/l label Aug 7, 2026
… 每种字段类型的谓词

给 `showcase_field_zoo` 加一整套可证伪的动作门控夹具,覆盖决定「按钮出不出现」的两件事:
`visible`(记录上的 CEL 谓词)与 `requiredPermissions`(ADR-0066 D4 能力门)。

两条种子记录是可证伪性的来源:Specimen — Full(字段全填)必须出现,
Specimen — Minimal(多数为 null)必须消失——写错的表现是按钮出现在错误的地方,
而不是一个悄悄变绿的测试。

- **四面一致性**:同一个动作声明在行内 kebab / 记录页头 / ⋯ 菜单,并被视图的
  `bulkActions` 提升进批量栏。关系比关系(`f_lookup == f_lookups[0]`)是最利的
  一条:两个操作数都是 lookup,谁被展开谁就变成对象,比较就静默为假。
  `gated_columns` 视图把 `f_lookup` 设为列(于是被 `populate=`),默认列表两个都不展开
  —— 同样的记录、同样的谓词,两个视图必须给出同一结论(objectui#3501)。
- **三种 visible 写法**:裸 CEL 字符串 / `P` 标签模板 / `{ dialect, source }` 信封,
  同一条谓词并排钉住;三者若在同一条记录上分歧,就是某个渲染器把某一种当特例了。
  布尔 visible 不在此列——`ExpressionInput` 不接受它,`objectstack build` 发不出这种
  形状,由 objectui 自己的单测钉(objectui#3492)。
- **requiredPermissions**:持有 / 不持有 / 两个取 AND / 空声明四种,四个面同一结论。
  新增 `showcase.restricted_ops` 能力:**定义了但不授予任何权限集**——门的可证伪一半,
  正是批量栏当初整个忽略 `requiredPermissions` 时无人察觉的原因。
- **字段类型谓词园**:每种字段类型一条 `visible`,含 lookup(单/多/空)、master_detail、
  tree、user、文本族、数值族、时间族、布尔/选项族、json/location/address/composite/
  repeater/vector,以及 AND/OR/NOT/三元。全部对着真实 runtime 的 CEL 引擎实测过,
  不是照着规范猜的。

顺带钉住三条实测得到的作者规则:必须 `record.` 前缀;遍历/调方法前必须判空
(`null` 没有成员也没有方法,不判空在 Minimal 上直接 fault);用 `contains()` / `matches()`
而不是 `startsWith()` / `endsWith()`(后者不是 CEL,objectui 会降级到遗留 JS 引擎并告警)。

`showcase_zoo_dialect_split` 是故意留下的**红色夹具**:同一个 `.contains()` 谓词在行内
kebab 里正常、在记录页头被判抛错并隐藏——页头不走 CEL 引擎(objectui#3521)。
按仓库规矩不粉饰,改成一屏可对比的现场。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@baozhoutao baozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 7, 2026
@baozhoutao

Copy link
Copy Markdown
Contributor Author

CI 两条红的都处理了:

1. Check Changeset —— 本 PR 的改动全部落在 examples/git diff --name-only origin/main...HEAD 无一例外),按门自己给的路线 2(且它标注为 PREFERRED)打上 skip-changeset 标签。没有走已被关闭的空 changeset 路线。

2. TypeScript Type Check → check:i18n-coverage —— 真账,不是环境问题:examples/app-showcase 的未翻译计数被冻结在 451,我新声明了 ~56 个动作标签 + 3 个视图标签却一条没译,把它顶到 572。

翻译而不是抬基线。 脚本抬头写明「the debt is FROZEN, and the build fails the moment it grows」;translations/index.ts 里既有的两条注释也各自写着「Translated at birth … this example is ratcheted at its current untranslated count」。抬基线正是这道门要防的事。

补齐 zh-CN.objects.showcase_field_zoo 下的 _actions(12 条门控动作 + 44 条字段类型谓词,含 3 条 successMessage)与 _views(3 个视图标签)。本地复跑:

check-i18n-coverage: OK (12 config(s), 660 baselined untranslated string(s), none new).

基线仍是 451,一字未动。顺带修了上一笔批量替换在 field-zoo.view.ts 留下的一处缩进。

pnpm validate exit 0 / pnpm typecheck 通过 / pnpm test 146 passed。

🤖 Generated with Claude Code

@baozhoutao
baozhoutao added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit ede5a8e Aug 7, 2026
23 checks passed
@baozhoutao
baozhoutao deleted the claude/showcase-action-visible-matrix branch August 7, 2026 06:44
baozhoutao and others added 4 commits August 7, 2026 01:44
`check-i18n-coverage` 把本示例的未翻译计数冻结在 451,新声明的标签一旦跳过
zh-CN 就把计数顶上去、门变红 —— 我加了 ~56 个动作标签和 3 个视图标签却一条没译,
CI 报 451 → 572。

翻译而不是抬基线:那个脚本的抬头写明「debt is FROZEN, and the build fails the
moment it grows」,本文件里既有的两条注释也各自写着「Translated at birth ...
this example is ratcheted at its current untranslated count」。抬基线正是它防的
那件事。

补齐 `zh-CN.objects.showcase_field_zoo` 下的 `_actions`(12 条门控动作 + 44 条
字段类型谓词,含 3 条 successMessage)与 `_views`(3 个视图标签)。标签本身是覆盖
夹具的说明文字,中文照写同一句技术陈述,不做意译。

顺带修 `field-zoo.view.ts` 里一处缩进(上一笔批量替换留下的)。

现在 `check-i18n-coverage: OK (none new)`,基线仍是 451。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
合入 main 后 CI 仍红 451 → 452,本地却绿 —— 差别在于 CI 跑的是**分支与 main 的
合并结果**,而分支落后 main 136 个提交,其中 b3c1f3c#5164)改了 `_views` 的
键法:不带 `name` 声明的容器默认列表被 composer 注册为 `<object>.default`,
bundle 的键就是这个裸运行时键。我写的 `_views.list` 是没有任何查找能到达的拼法,
正是 #5164 修掉的那个。

`ContactViews` 的注释里已经把这条定案写清楚了,我照它对齐。

顺带记下这次的教训并写进注释:**落后 main 的分支上「本地绿」不作数** —— 这条
只有在合并结果上才暴露。

合并后复跑:`check-i18n-coverage: OK (none new)`、`pnpm validate` exit 0、
`typecheck` 通过、146 tests passed。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
合 main 之后重新实测:这条谓词现在**校验通过**。#5378 已经把答案放进
`packages/spec/src/data/injected-system-columns.ts`,让作者时校验能推导出平台注入
列 —— 我原先注释里写的「校验器拒绝它,已上报」在合并后的树上是**假话**,不能就
这么随 PR 发出去。

于是不只是改注释,而是把这条谓词作为正式标本加回来(`showcase_zoo_owner_gate`
+ 类型园里的 `showcase_zoo_t_owner`,三个记录面 + 两个视图的 `bulkActions`)。
它值得独立一条,因为 `owner_id` 是注入列而非声明字段,同时压两个面:

- 作者时:它就是 #5378 落地的活证据;
- 运行时:列表 `$select` 按对象声明字段构造,消费方若不认识平台列就会把它当拼错
  丢掉、让谓词在缺席键上 fault —— 即 objectui#3501 的 `PLATFORM_RECORD_COLUMNS`。

与既有的 `showcase_zoo_user_gate`(声明的 `user` 字段)成对:注入列与声明字段必须
门控一致。

标签当场补 zh-CN,棘轮仍是 451、`check-i18n-coverage: OK (none new)`;
`pnpm validate` exit 0、`typecheck` 通过、146 tests passed。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant