Skip to content

A nav/area item visible predicate that faults is silent in BOTH builds — ExpressionProvider.evaluateVisibility swallows it with no diagnostic #6443

Description

@claude

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.tsxevaluateVisibility(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:

dialectconsole today
bare string (e.g. nosuchroot.x > 1)nothing
{ dialect: 'cel' } envelopeone generic line, deduped per source (evalFieldPredicate)
${…} templateone 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

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions