You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
finding: PageComponentSchema.visibleWhen's describe names three roots; the renderer binds seven — user / ctx.user / os.user / app / features are undocumented on that surface #11256
Filed unassigned by the domain:engine seat while implementing #9167 round 2 (session session_01RfyXxZ2WPjcjhuXpiQQc3y, PR #11254). Observation, not a defect claim — nothing is broken; the describe is narrower than the binding. Not claimed, not queued.
What the spec says
packages/spec/src/ui/page.zod.ts:189:
Visibility predicate (CEL) — component rendered only when TRUE. Binds record, current_user, page.<var>.
What the renderer actually binds
SchemaRenderer (objectui packages/react/src/SchemaRenderer.tsx, pin 190fbd01d) builds the evaluator scope as the ambient predicate scope plus current_user, the bound record, data (the data-source adapter) and page. The ambient scope comes from app-shell/src/providers/ExpressionProvider.tsx:
constscope={current_user: user, user,ctx: { user },os: { user }, app, data, features };
with the comment that these are the deliberate ADR-0068 aliases, "so a predicate authored against any one form evaluates identically on client, server-formula, and server-RLS".
So a page-component visibleWhen binds record, current_user, user, ctx.user, os.user, app, features, page.* and data — nine roots where the describe names three.
Why it is worth recording rather than shrugging at
The platform's own metadata uses an undocumented one. The sys_user alert gate ruled on finding: two authored record:alert page-block visible predicates read record.* unguarded — same sparse-face fault class as #8990, but outside its action-only census #9167 is has(record.id) && has(record.email_verified) && record.id == ctx.user.id && …, matching the sibling resend_verification_email action predicate on the same object. Measured working in a real console (verified user → hidden, unverified self → VISIBLE, another user → hidden), so ctx.user is genuinely bound here — but an author reading the describe would conclude it is not, and an author reading the platform page would conclude the describe is stale. Both readings are half right.
data means two different things on two surfaces that share the key name. On a component node the evaluator binds data to the data-source adapter; page:tabs' item-level visibleWhen (objectui containers.tsx:449) builds its own evaluator and binds data to the record row. Neither is documented at page.zod.ts:189.
⚠️Not a request to widen anything. Contract-first says the fix could equally be narrowing the renderer to the declared three rather than documenting the other six — the aliases are ADR-0068's, and whether ADR-0068 was ever meant to reach the page-component surface is the question this card cannot answer from here. Whoever takes it should settle that first; the wrong move is to paste the renderer's list into the describe because it happens to be what the code does.
Filed unassigned by the
domain:engineseat while implementing #9167 round 2 (sessionsession_01RfyXxZ2WPjcjhuXpiQQc3y, PR #11254). Observation, not a defect claim — nothing is broken; the describe is narrower than the binding. Not claimed, not queued.What the spec says
packages/spec/src/ui/page.zod.ts:189:What the renderer actually binds
SchemaRenderer(objectuipackages/react/src/SchemaRenderer.tsx, pin190fbd01d) builds the evaluator scope as the ambient predicate scope pluscurrent_user, the boundrecord,data(the data-source adapter) andpage. The ambient scope comes fromapp-shell/src/providers/ExpressionProvider.tsx:with the comment that these are the deliberate ADR-0068 aliases, "so a predicate authored against any one form evaluates identically on client, server-formula, and server-RLS".
So a page-component
visibleWhenbindsrecord,current_user,user,ctx.user,os.user,app,features,page.*anddata— nine roots where the describe names three.Why it is worth recording rather than shrugging at
sys_useralert gate ruled on finding: two authoredrecord:alertpage-blockvisiblepredicates readrecord.*unguarded — same sparse-face fault class as #8990, but outside its action-only census #9167 ishas(record.id) && has(record.email_verified) && record.id == ctx.user.id && …, matching the siblingresend_verification_emailaction predicate on the same object. Measured working in a real console (verified user → hidden, unverified self → VISIBLE, another user → hidden), soctx.useris genuinely bound here — but an author reading the describe would conclude it is not, and an author reading the platform page would conclude the describe is stale. Both readings are half right.datameans two different things on two surfaces that share the key name. On a component node the evaluator bindsdatato the data-source adapter;page:tabs' item-levelvisibleWhen(objectuicontainers.tsx:449) builds its own evaluator and bindsdatato the record row. Neither is documented atpage.zod.ts:189.record:alertpage-blockvisiblepredicates readrecord.*unguarded — same sparse-face fault class as #8990, but outside its action-only census #9167 was bought on: the declared surface should be the one that works.Adjacent, filed separately: #11255 (raw-literal pages serve
ExpressionInputSchemakeys un-normalized).Generated by Claude Code