fix(app-shell): 对象列表页「New」与移动端浮动 + 消费 createPredicates (#5153) - #5165
Merged
Conversation
…edicates (#5153) `createPredicates` had exactly one consumer in objectui — the related-list toolbar (`RelatedRecordActionsBridge`, objectui#4646 / PR #5145). The standalone object-list page renders the same create affordance twice, in the PageHeader and as the phone-only floating "+", and neither read the key: both gated on `affordances.create && can(objectDef.name, 'create')`, the object-level verdict alone. One `userActions.create` object form therefore produced two different answers depending on which surface drew the button, and `visibleWhen: false` (the objectui#3492 shape) did not hide this "New". Both entry points now layer the toolbar-scope predicates on top of that verdict, mirroring the `import` half landed in this same file (#5142 / PR #5154): `visibleWhen` fails CLOSED with `?? true` declared-ness, `disabledWhen` fails SOFT with its `!= null` gate outside the evaluation. The predicate is computed once and consumed by both render points, which is what keeps them from disagreeing. Scope record is `null` — the spec's binding for a standalone list, where a `record.*` predicate has nothing to bind and fails closed. The phone FAB is a bare `<button>` with none of the design system's `disabled:` treatment, so it takes the native `disabled` plus the same `disabled:opacity-50 disabled:pointer-events-none` utilities `Button` carries, rather than collapsing the greyed state onto "hidden". No type change: `UserActionsOverride.create` was already the widened union (#4646), and `RelatedRecordActionsBridge` is untouched. Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收:ACCEPT(objectui 分片 PM,session 实物核验(merge-base
CI 亲读:20 项中 18 项终态全 success/skipped,零失败;Test shard 2/3 在跑,按尾格零失败先例交 auto-merge 门(绿才合红不合)。席位本地等价面已全过(app-shell 429 文件全量、仓根 type-check 81/81)。 衍生卡 #5164(ObjectDataPage 整个 affordance 矩阵不读)已分诊晋级 处置:undraft + auto-merge(SQUASH)。 Generated by Claude Code |
yinlianghui
marked this pull request as ready for review
August 18, 2026 08:18
This was referenced Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5153
背景:这是 create 一族的最后一块
createPredicates在 objectui 里此前只有一个消费方 —— 关联列表工具栏(RelatedRecordActionsBridge,#4646 / PR #5145)。独立对象列表页把同一个 create affordance 渲染了两次(PageHeader 按钮 + 手机端顶掉 header 后顶上的浮动 +),两处都只看对象级判词affordances.create && can(objectDef.name, 'create'),谓词信封整个被丢掉。于是作者写一份
userActions.create对象形,会拿到两种不同判词,取决于是哪个界面画的按钮:关联列表认,对象列表不认。visibleWhen: false(objectui#3492 的形)关不掉这个「New」。本单补齐消费方,家族闭环:
修法:逐字镜像同文件刚落的 import 半
PR #5154 的
importVisible/importDisabled就在同一个文件里,本 PR 照抄其语义,不自创、不抽共享 helper(各调用方自答,沿用 PR #5154 的取舍先例):visibleWhen—— fail CLOSED,且用?? true而非真值性判定「是否声明」,所以visibleWhen: false是隐藏而不是读成「未设门」(fix(plugin-grid): 批量操作按钮忽略 requiredPermissions,且布尔 visible 被判为故障导致按钮全员隐藏 #3492 不变量);默认值是 boolean,求值器直接短路,没声明谓词的对象一次引擎调用都不付。disabledWhen—— fail SOFT(算不出的谓词不能把按钮永久置灰),!= null的声明性判定放在求值之外,所以disabledWhen: ''读作「无条件」而不是「禁用」。null—— spec 原文语义:工具栏谓词每工具栏求值一次,绑定该工具栏所在 scope 的记录;独立对象列表没有记录在 scope 里,所以读record.*的谓词无从绑定、按 fail-closed 隐藏按钮。os.user.*/features.*正常绑定,是这里有意义的形。{id}被原样发出 #3391 有效 API 操作 / principal 授权已经关掉的东西。既有权限门与 affordance 门一律未动。谓词只算一次,两个渲染点消费同一对值 —— 这正是让它们不会互相打架的原因。
浮动 + 的 disabled 三态如实分层
浮动 + 是个裸
button,不是设计系统的Button,自身没有任何置灰形态。这里没有把三态压成两态(那会把disabledWhen映射成「隐藏」,丢掉 spec 明确区分的 hidden vs greyed),而是:原生disabled(这是传达给辅助技术的那一半)+Button自己用的同一组disabled:opacity-50 disabled:pointer-events-none,让视觉可及性与 header 按钮对齐。无类型改动
UserActionsOverride.create早在 #4646 就已是放宽后的联合类型,core 未动;RelatedRecordActionsBridge/ RelatedList 未动。半径:ObjectView.tsx+ 测试 + changeset(patch)。前提复验(先行门)
卡面三条判断逐条复测,全部成立(行号因 PR #5154 漂移,现场定位):
ObjectView.tsx:2129/:2194→ 现为 :2189 / :2258,确认两处都只有affordances.create && can(...)合取,无谓词层。node_modules/@objectstack/spec@17.0.0的RowCrudActionOverrideSchemadocblock 与CrudAffordances.createPredicates逐字含卡面引文:「On a standalone object list there is no record in scope … hides the button」。独立列表在工具栏谓词适用范围内,缺的只是记录。affordances.create的 UI 消费方共三处 —— 关联列表(已落)+ 本文件两处。同文件无第三个 create 入口(showCreate: false已显式关掉插件自带的 create 按钮)。反向验证(先书面预判,再跑;四组预判全部逐条命中)
visibleWhenX?? true→ 按钮渲染)A1/A2 互为镜像,是「逐入口可观测」的实证:摘掉任一入口,另一入口的钉子仍然绿,所以两个渲染点是分别被钉住的,不是一条断言数了两遍。为此专门加了一组 per-entry 钉子 —— 合并断言省事,但会让任一入口的回归都失败在同一个测试名下。
变异 C 的价值在于它把卡面主张复现成了机器可判的 diff:
关联列表认这份声明(false = 已隐藏),对象列表两处不认(true = 照样显示)—— 这正是 #5153 描述的缺陷本身。
变异 B 证明「隐藏」是
visibleWhen这个键造成的,而非任何对象形userActions.create的存在;顺带确认拼错的键不会抛错。这不构成新缺陷:spec 的RowCrudActionOverrideSchema声明为z.core.$strict,未知键由生产方在发布期严格拒绝,渲染方的宽容位于严格 schema 的下游 —— 契约优先,消费端无需再设兜底。测试
新增
ObjectView.createPredicates.test.tsx,22 钉(镜像 import 半 14 钉 + 4 条 per-entry + 4 条家族判词一致性)。家族判词一致性一次渲染三面(关联列表 / 对象列表 New / 浮动 +),对同一份声明断言三者同判 —— 主语必须是 scope-only 谓词(os.user.*),因为那才是三面都能同样绑定的形;record.*谓词三面本就不该一致(关联列表绑宿主记录,本页没有),那是 spec 的绑定规则而非缺陷,单独钉在 fail-closed 用例里。基线 sha:
cdac3cc20(含 PR #5154 的 import 半9f23d2b32)。顺带发现(半径外,已单独立卡,未在本 PR 修)
入口清点时发现 #5164:
ObjectDataPage(ADR-0055 的/data界面)的「New」只看can(objectDef.name, 'create'),整个 CRUD affordance 矩阵在该文件里从未解析 —— 连managedBybucket 与userActions.create: false都关不掉它,比本单的缺陷更宽一层。已按查重后 unassigned 立卡,交 PM 分诊;本 PR 半径严守ObjectView.tsx,未动该文件。Generated by Claude Code