Found while censusing predicate-fault catch sites for #6038 (production diagnostic for a faulting node gate). Filed unassigned; not fixed in that PR — see "Why it was left out" below.
The site
packages/app-shell/src/providers/ExpressionProvider.tsx — evaluateVisibility(expression, evaluator):
try{returnevaluator.evaluateCondition(expression);}catch{returntrue;// Default to visible on error}This is the gate for nav and area item visible predicates — the ones an app author writes to hide a menu entry by role. Its own docblock records why it matters: before it routed envelopes and bare strings to evaluateCondition, "a constant-false nav visible predicate (e.g. P`'org_admin' in current_user.positions`) still rendered for everyone — the app author had no working way to hide a menu item by role."
What is silent, measured per dialect
Measured on the built @object-ui/core evaluator (packages/core/dist/evaluator/ExpressionEvaluator.js), a faulting predicate with no options:
| dialect | console today |
|---|
bare string (e.g. nosuchroot.x > 1) | nothing |
{ dialect: 'cel' } envelope | one generic line, deduped per source (evalFieldPredicate) |
${…} template | one generic line per evaluation, never deduped |
The bare-string dialect is the one objectstack#11254 measured a live gate breaking on. So a nav item whose visible predicate faults renders for everyone — including the role it was written to exclude — and says nothing. Unlike the node gate #6038 fixed, this one was silent in a development build too.
The fix is now a one-liner
#6038 added EvaluationOptions.onFault to @object-ui/core's ExpressionEvaluator — a passback that reports the fault the evaluator has already caught, at the same number of engine calls (no throwOnError double-evaluation), and reportUnresolvableVisibilityPredicate is exported from @object-ui/react so every surface shares ONE reporter and ONE rate limit. @object-ui/app-shell already depends on @object-ui/react, so this site needs the same shape SchemaRenderer and page:tabs now use:
returnevaluator.evaluateCondition(expression,{onFault: (reason)=>reportUnresolvableVisibilityPredicate(<nodetype>,<id>,'visible',expression,reason),});What needs deciding rather than copying: what to pass as the node type/id, since a nav item is not a schema node — the dedupe key is (type, key, source), so whatever is chosen decides the rate limit's granularity here.
Why it was left out of #6038
The 2026-08-25 ruling is scoped to "a visibleWhen / node-gate predicate" and to "replacing the current __DEV__-only silence". This site is neither: a different key (visible), a different tier (app-shell chrome rather than the schema node tree), and its silence is unconditional rather than __DEV__-gated. Stating the boundary rather than quietly widening the PR's file surface into a third package.
Related: #6038 (the ruling and the seam), #5926 (the parent visibleWhen census), objectstack#11254 (where a silent bare-string breakage was measured).
Generated by Claude Code
Found while censusing predicate-fault catch sites for #6038 (production diagnostic for a faulting node gate). Filed unassigned; not fixed in that PR — see "Why it was left out" below.
The site
packages/app-shell/src/providers/ExpressionProvider.tsx—evaluateVisibility(expression, evaluator):This is the gate for nav and area item
visiblepredicates — the ones an app author writes to hide a menu entry by role. Its own docblock records why it matters: before it routed envelopes and bare strings toevaluateCondition, "a constant-false navvisiblepredicate (e.g.P`'org_admin' in current_user.positions`) still rendered for everyone — the app author had no working way to hide a menu item by role."What is silent, measured per dialect
Measured on the built
@object-ui/coreevaluator (packages/core/dist/evaluator/ExpressionEvaluator.js), a faulting predicate with no options:nosuchroot.x > 1){ dialect: 'cel' }envelopeevalFieldPredicate)${…}templateThe bare-string dialect is the one objectstack#11254 measured a live gate breaking on. So a nav item whose
visiblepredicate faults renders for everyone — including the role it was written to exclude — and says nothing. Unlike the node gate #6038 fixed, this one was silent in a development build too.The fix is now a one-liner
#6038 added
EvaluationOptions.onFaultto@object-ui/core'sExpressionEvaluator— a passback that reports the fault the evaluator has already caught, at the same number of engine calls (nothrowOnErrordouble-evaluation), andreportUnresolvableVisibilityPredicateis exported from@object-ui/reactso every surface shares ONE reporter and ONE rate limit.@object-ui/app-shellalready depends on@object-ui/react, so this site needs the same shapeSchemaRendererandpage:tabsnow use:What needs deciding rather than copying: what to pass as the node type/id, since a nav item is not a schema node — the dedupe key is (type, key, source), so whatever is chosen decides the rate limit's granularity here.
Why it was left out of #6038
The 2026-08-25 ruling is scoped to "a
visibleWhen/ node-gate predicate" and to "replacing the current__DEV__-only silence". This site is neither: a different key (visible), a different tier (app-shell chrome rather than the schema node tree), and its silence is unconditional rather than__DEV__-gated. Stating the boundary rather than quietly widening the PR's file surface into a third package.Related: #6038 (the ruling and the seam), #5926 (the parent
visibleWhencensus), objectstack#11254 (where a silent bare-string breakage was measured).Generated by Claude Code