Blocked-by: #3850
来源
发现自 #3850 + #3862 的结对实施(PR 分支 claude/issue-3850-3862-empty-predicate-gate)。给 SchemaRenderer 的 disabled 链写「visible 家族等价钉」时,顺手把同一个 useMemo 里 可见性链的另一极性 也量了一遍,发现 hidden / hiddenOn 两条腿有与 #3862 完全同构的缺陷,而且后果比置灰更重:节点整个消失。
不在 #3850 / #3862 的修法面:那两单裁决的落点是「已声明门」定义的下沉 + disabled / disabledOn 两条腿改读,visible 家族一侧被明确裁为保持 !== undefined 不动(它的 true 被取反,与「没有门」同结果,且改动会改变别名优先级)。hidden 腿的 true 不被取反,所以同一个「空谓词 → evaluateCondition 答 true」在这里意味着 HIDE。故独立成单,未认领。
机理
packages/react/src/SchemaRenderer.tsx(origin/main @ 65bb513dc073deb4e72950c287eef32cb56bf8ce),shouldHide 块(287 行起)的最后两条腿:
if (newSchema.hidden !== undefined) {
return evaluator.evaluateCondition(newSchema.hidden); // 308-310:不取反
}
if (newSchema.hiddenOn !== undefined) {
return evaluator.evaluateCondition(newSchema.hiddenOn); // 311-313:不取反
}
ExpressionEvaluator.evaluateCondition 对「没有条件」的唯一默认是 return true(意为 visible/enabled)。前四条腿(visible / visibleWhen / visibleOn / visibility)都写成 !evaluateCondition(...),那个 true 取反后落在「不隐藏」,与「没有门」同结果 —— 良性。hidden / hiddenOn 直接返回,于是 true = 隐藏:空谓词让节点消失,与 #3862 在 disabled 上的 true = 置灰是同一个不对称的第二个出口。
拼法同样是最宽的那一格 !== undefined,所以 hidden: null 也命中(null !== undefined 为真)。
实测(worktree @ 65bb513dc,一次性探针,未提交)
同一个 probe 组件,只换 key,rendered / HIDDEN 是节点是否出现在 DOM:
value | visible | visibleWhen | visibleOn | visibility | hidden | hiddenOn
'' | rendered | rendered | rendered | rendered | HIDDEN | HIDDEN
' '(纯空白) | rendered | rendered | rendered | rendered | HIDDEN | HIDDEN
{dialect:'cel',source:''} | rendered | rendered | rendered | rendered | HIDDEN | HIDDEN
null | rendered | rendered | rendered | rendered | HIDDEN | HIDDEN
false | HIDDEN | HIDDEN | HIDDEN | HIDDEN | rendered | rendered
true | rendered | rendered | rendered | rendered | HIDDEN | HIDDEN
false / true 两行都正确(声明了 verdict 就按 verdict 走),坏的正是四行「空」。
为什么值得管
修法(机械,已有唯一定义可读)
#3850 的裁决已经把「已声明门」下沉为 @object-ui/core 的 hasDeclaredPredicate(packages/core/src/evaluator/declaredPredicate.ts,该 PR 落地),disabled / disabledOn 两条腿已改读它。这里同样改读即可:
if (hasDeclaredPredicate(newSchema.hidden)) { ... }
if (hasDeclaredPredicate(newSchema.hiddenOn)) { ... }
⛔ 不要就地补 && !== '' —— 那是把第 N 种「空」拼法写进第 N 个消费者,#3842 / #3849 / #3850 三单合掉的正是这类分身。
需要注意的一处非等价:hidden 腿改读后,hidden: '' 会继续往下落到 hiddenOn(而不是在空谓词上短路),这与 disabled 链在 #3862 修后的行为一致,但它改变了别名优先级,应作为行为变更钉住而不是当作等价。另外前四条 visible 腿是否也统一改读,是一个独立的判断:那侧空谓词良性,改读会改变别名优先级(如 visible: '' + hidden: 'true' 的组合),收益是「一个概念一处定义」,代价是行为变更 —— 建议与本单一起裁,而不是在本单里顺手改。
Related: #3862(同一文件、同一 useMemo、disabled 极性的同族半边)、#3850(「已声明门」范围裁决与定义下沉)、#3842 / #3849 / #3848(动作面与执行入口的同族)、#3492(不变量出处)。未认领。
Generated by Claude Code
Blocked-by: #3850
来源
发现自 #3850 + #3862 的结对实施(PR 分支
claude/issue-3850-3862-empty-predicate-gate)。给SchemaRenderer的disabled链写「visible 家族等价钉」时,顺手把同一个useMemo里 可见性链的另一极性 也量了一遍,发现hidden/hiddenOn两条腿有与 #3862 完全同构的缺陷,而且后果比置灰更重:节点整个消失。不在 #3850 / #3862 的修法面:那两单裁决的落点是「已声明门」定义的下沉 +
disabled/disabledOn两条腿改读,visible家族一侧被明确裁为保持!== undefined不动(它的true被取反,与「没有门」同结果,且改动会改变别名优先级)。hidden腿的true不被取反,所以同一个「空谓词 →evaluateCondition答true」在这里意味着 HIDE。故独立成单,未认领。机理
packages/react/src/SchemaRenderer.tsx(origin/main@65bb513dc073deb4e72950c287eef32cb56bf8ce),shouldHide块(287 行起)的最后两条腿:ExpressionEvaluator.evaluateCondition对「没有条件」的唯一默认是return true(意为 visible/enabled)。前四条腿(visible/visibleWhen/visibleOn/visibility)都写成!evaluateCondition(...),那个true取反后落在「不隐藏」,与「没有门」同结果 —— 良性。hidden/hiddenOn直接返回,于是true= 隐藏:空谓词让节点消失,与 #3862 在disabled上的true= 置灰是同一个不对称的第二个出口。拼法同样是最宽的那一格
!== undefined,所以hidden: null也命中(null !== undefined为真)。实测(worktree @
65bb513dc,一次性探针,未提交)同一个 probe 组件,只换 key,
rendered/HIDDEN是节点是否出现在 DOM:false/true两行都正确(声明了 verdict 就按 verdict 走),坏的正是四行「空」。为什么值得管
{ dialect, source: '' }是@objectstack/spec的ExpressionInputSchema对任何已授权谓词归一后的产物,也是objectstack build在「作者把谓词留空」时编译出的形状 —— 这是真实元数据里最可能出现的空拼法(「空谓词」在三处有三种范围:disabled: { dialect: 'cel', source: '' } 仍被判成已声明的门 → 永久置灰(#3842 修完后的残留,需先裁) #3850 已为此裁过一次)。evaluatedSchema的useMemo里无条件执行,不带任何 type 判定,覆盖走SchemaRenderer的全部节点。hidden命中时节点根本不渲染 —— 作者看到的是「我写了个空的hidden,整块内容不见了」,界面上无法与「元数据本意」区分。修法(机械,已有唯一定义可读)
#3850 的裁决已经把「已声明门」下沉为
@object-ui/core的hasDeclaredPredicate(packages/core/src/evaluator/declaredPredicate.ts,该 PR 落地),disabled/disabledOn两条腿已改读它。这里同样改读即可:⛔ 不要就地补
&& !== ''—— 那是把第 N 种「空」拼法写进第 N 个消费者,#3842 / #3849 / #3850 三单合掉的正是这类分身。需要注意的一处非等价:
hidden腿改读后,hidden: ''会继续往下落到hiddenOn(而不是在空谓词上短路),这与disabled链在 #3862 修后的行为一致,但它改变了别名优先级,应作为行为变更钉住而不是当作等价。另外前四条visible腿是否也统一改读,是一个独立的判断:那侧空谓词良性,改读会改变别名优先级(如visible: ''+hidden: 'true'的组合),收益是「一个概念一处定义」,代价是行为变更 —— 建议与本单一起裁,而不是在本单里顺手改。Related: #3862(同一文件、同一
useMemo、disabled极性的同族半边)、#3850(「已声明门」范围裁决与定义下沉)、#3842 / #3849 / #3848(动作面与执行入口的同族)、#3492(不变量出处)。未认领。Generated by Claude Code