fix(app-shell,core): 对象列表工具栏 Import 消费 importPredicates (#5142) - #5154
Merged
Conversation
`@objectstack/spec@17.0.0` 同时放宽了两个 toolbar-scope 键:`userActions.create` 与 `userActions.import` 类型完全相同(`z.union([z.boolean(), RowCrudActionOverrideSchema])`),`resolveCrudAffordances` 对两键各发一个谓词信封, docblock 一句话把两者绑在一起(`importPredicates` — same binding as `createPredicates`)。#4646 补上了 create 半,import 半仍是「已声明、零消费」: `importPredicates` 在 objectui src 中没有任何读取者。作者可以写 `userActions.import.visibleWhen`,spec 接受、resolver 解析,而对象列表工具栏照旧 无条件提供 CSV 导入入口。 本次让工具栏消费它,逐一镜像关联列表的 create 半:`visibleWhen` fail-CLOSED、 `disabledWhen` fail-SOFT、声明判定用 `?? true` / `!= null`(而非真值),谓词层叠 在对象级判定之上——谓词只能收窄 bucket / 有效 API 操作集 / 主体授权已允许的, 不能重新打开它们已关闭的。 按 spec 的绑定语义,toolbar 谓词对「工具栏所在 scope 的记录」求值一次,而独立对象 列表没有 scope 记录:`os.user.*` / `features.*` 这类作用域谓词才是这里有意义的形态, 读 `record.*` 的谓词无处绑定并按 fail-closed 隐藏按钮——这正是 spec 为该面明写的 规则,不是本实现的取舍。 `UserActionsOverride.import` 由 `boolean` 放宽为与 `create` 同一 union,刻意与消费者 同车:#4646 有意保留窄型并写明理由——类型先于消费者放宽,等于把「已声明、零消费」 的缺陷在隔壁键上重造一遍。 Fixes #5142 Co-authored-by: Claude <noreply@anthropic.com>
类型放宽在运行时被擦除,vitest 看不见它;能观察这一性质的只有 tsc。 packages/core/tsconfig.test.json 会编译该测试文件,并由包的 type-check 脚本串起 (objectui#3181),正是 CI 跑的那条。把 `import` 改回 `boolean`,带类型注解的常量 即编译失败。 同时补运行时钉:importPredicates 穿过 objectui 侧的 #3391 交集层、boolean 分支不带 谓词、`enabled: false` 时 resolver 仍报告谓词(是否呈现由消费者分层规则决定)。 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收:ACCEPT(session_01GTRjn8xBqp75dk7kFupVRt,objectui 分片,批次 23) 实物核验:merge-base 验收要点:
三件套照常:本评论 → undraft → auto-merge(SQUASH)。 Generated by Claude Code |
yinlianghui
marked this pull request as ready for review
August 18, 2026 07:12
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 #5142
背景:与 #4646 / PR #5145 的镜像关系
@objectstack/spec@17.0.0同时放宽了两个 toolbar-scope 键,不只create。二者在 spec 中类型完全相同,resolver 对两键各发一个谓词信封,docblock 一句话把绑定绑在一起(importPredicates— same binding ascreatePredicates)。#4646 补上了 create 半(PR #5145,关联列表工具栏),import 半仍是「已声明、零消费」。本 PR 是 import 半,逐一镜像 PR #5145 的
evalCreatePredicate形。前提三条逐条复测(基线
b4089beca,含 PR #5145 与 #5147):@objectstack/spec@17.0.0实测:userActions.import的对象形经resolveCrudAffordances返回importPredicates: { visibleWhen, disabledWhen },与create半逐字对称;.d.ts中create:/import:都是z.ZodUnionof[ZodBoolean, RowCrudActionOverrideSchema](1208 / 1253 行)。成立。packages/core/src/utils/managedBy.ts:76那条「刻意不放宽」的注释本身,不是读取点。成立。managedBy.tsUserActionsOverride.create的 docblock 尾部,原话是「widening the type ahead of a consumer would re-declare the inert-metadata defect this change removes, one key over」。本单正是它指名的那一单。成立。消费落点清点
对象列表工具栏的 Import 入口只有一处:
ObjectView.tsx头部按钮(data-testid="object-view-import-button")。与 create 半不同 —— create 在同文件有两个渲染点(头部按钮 + 手机端浮动+),而 Import 按设计没有移动端对应物(源注释:CSV 导入本质是桌面工作流,移动端不占位)。因此本 PR 关掉的就是全部入口。实施
ObjectView.tsx:objectCanImport && importVisible作可见门,disabled={importDisabled}作置灰门。visibleWhenfail-CLOSED、disabledWhenfail-SOFT;声明判定沿用家族规则 ——?? true(而非真值)使visibleWhen: false真的隐藏而不是读成「未设门」(fix(plugin-grid): 批量操作按钮忽略 requiredPermissions,且布尔 visible 被判为故障导致按钮全员隐藏 #3492 不变量),!= null且在求值之外,使空谓词读成「无条件」而不是「禁用」。求值走useRowPredicate(..., { fallback: false, warnOnError: true }),即记录页头(PR fix(plugin-detail): the record detail header honors userActions predicates (#4419) #4515)/ 行 kebab / DetailView 在同一位置的既有形。affordances.import && can(object,'create')),谓词不能重新打开 bucket / 有效 API 操作集([console] 记录详情页头的 api 动作:target URL 的 {field} 占位符从不插值——record_header 执行器不提供 params._rowRecord,literal{id}被原样发出 #3391)/ 主体授权(列表行内建【编辑】【删除】没接权限门:只与 apiOperations 求交,而它与用户无关 ⇒ 无写权账号恒可见 #4096、Grid toolbar inline-edit toggle is not gated oncan(object, 'update');userActions.editInlineis declared in spec but has no consumer #4647)已关闭的东西。identity-import 旁路不读affordances.import,是另一个 affordance,刻意留在该层之外。record.*has nothing to bind and — per the fail-closed rule above — hides the button」。对象列表顶层工具栏正是该面,故此处故意传null:os.user.*/features.*这类作用域谓词正常绑定(这里有意义的形态),读record.*的谓词无处绑定 → fail-closed 隐藏。这是 spec 为该面明写的规则,不是本实现的取舍;测试里两半都钉住了。core/managedBy.ts:UserActionsOverride.import由boolean放宽为与create同一 union,与消费者同车;把那条「刻意窄型」注释改写为指向本单的落地说明。⛔ 未动 spec、未动 ObjectGrid。
测试
新增
ObjectView.importPredicates.test.tsx(14 例,真渲染 +data-testid断言,不是纯函数替身):visibleWhen 假 ⇒ 隐藏 / 真 ⇒ 保留;disabledWhen 真 ⇒ 置灰、假 ⇒ 可用、空串 ⇒ 无条件;纯 boolean 旧形与无userActions不回归;enabled: false对象级关断;权限门(#4647)与谓词层叠加互不短路(谓词为真也无法重开被主体关掉的);configbucket 未 opt-in 时谓词不能复活按钮;record.*不可绑定的 fail-CLOSED / fail-SOFT 两向。managedBy.test.ts增 4 例,其中一例是编译期钉:类型放宽在运行时被擦除,vitest 看不见,只有tsc能看见 ——packages/core/tsconfig.test.json编译该文件且由包type-check串起(#3181),即 CI 跑的那条。证据:
反向验证(先书面预判,再跑;结果与预判一致,含一处「反直觉但预判正确」的方向)
(a) 摘消费 —— 还原改前门(
affordances.import && can(...),去掉disabled)。预判:恰 3 例红 —— visibleWhen 假、disabledWhen 真、record.*fail-CLOSED;其余 11 例是控制组,压根不进谓词层,两个世界都绿。实测:Tests 3 failed | 11 passed (14),红的正是预判那三条。(b) 塞假谓词键 —— 把隐藏用例的
visibleWhen改拼成visible_when。预判:该例翻红(按钮照出),证明门是钉在规范键上,而不是「只要有个像谓词的键就算」。实测:1 failed | 13 passed,AssertionError: expected button… to be null。(c) 自选非显然方向:只回滚类型、保留消费者 —— 预判:全部运行时用例仍绿(放宽在运行时被擦除,resolver 是 spec 的,从不查 objectui 本地类型),只有
tsc会红。实测正如预判:vitest … 44 passed (44),而tsc -p packages/core/tsconfig.test.json报这条正是加编译期钉的理由:类型那一半对整个运行时套件不可见,窄型在仓内也从不承重 —— 它承的是下游作者 tsc 的判词。同一次变异下
@ts-expect-error(42那行)没有报「未使用」,说明放宽到的是 union 而非any。三次变异均已还原,commit 后重跑复绿。
半径外发现
createPredicates— the create half is honoured on related lists only #5153(unassigned,PM 分诊):对象列表页自己的「New」按钮与手机端浮动+同样不消费createPredicates—— create 半只在关联列表落地。按 spec 同一段 docblock,独立列表这个面在 toolbar 谓词的适用范围内(缺的只是记录),所以同一份userActions.create声明会因渲染面不同得到两种判词。立卡前已按关键字 + 文件路径查重,只回到 Related-list toolbar “+ New” does not consumeuserActions.createpredicates — spec 17.0.0 shipped the declaration half, the renderer half is missing (createPredicatesproduced but never consumed) #4646(已关)与importPredicatesis produced by the spec resolver and consumed nowhere — the same inverse-symmetry gap as #4646, one key over #5142,非重复件。刻意未在本 PR 内修:本单只点名import键。Generated by Claude Code