Found while implementing objectui#6493 (binding the roots on the two ad-hoc field-visibility evaluators). Filed unassigned, recording only — not graded, no type.
Measured on origin/main @ d09e13fd7, and in the framework checkout beside it.
What was measured
RecordFormPage and AppContent both filter an object's fields with
evaluateVisibility(f.visible, expressionEvaluator) — i.e. they gate on
objectDef.fields[].visible. objectui#6493 makes that evaluator bind the roots this
tier declares; it deliberately did not ask whether the KEY it reads exists.
It does not. @objectstack/spec's FieldSchema is a strictObject, and visible
appears in its FIELD_KEY_GUIDANCE map as prose that REFUSES the spelling rather than
an alias that renames it (packages/spec/src/data/field.zod.ts):
visible is not a field key, and which key you want depends on the form: a static
boolean is hidden — INVERTED, so visible: false is hidden: true — while a
per-record CEL predicate is visibleWhen (shown only when TRUE). Its siblings are
readonlyWhen and requiredWhen.
The guidance's own comment says the prose form was chosen over a rename deliberately,
because "this surface declares BOTH forms and the two answers have opposite polarity".
The per-OPTION schema, by contrast, does alias it: SelectOptionSchema carries
aliases: { ..., visible: 'visibleWhen' }. So the field level is the one place the
spelling is answered with a refusal.
objectui's own packages/types agrees: field metadata declares visible_on and
visibleWhen; there is no visible on any field metadata interface (the one
visible?: string | { dialect, source } in objectql.ts is on BulkActionDef).
Scanned all 104 *.object.{ts,yml,json} files in the framework, tracking the enclosing
top-level section of every visible occurrence: 29 in actions, 15 in fields, and
every one of the 15 is prose, a comment, or a per-option visibleWhen — not one is a
field-level visible key. Nothing in the example apps authors one either.
Why it is worth recording
The gate is therefore unreachable through the authoring surface: a field visible
written in object metadata is rejected by the schema before it can reach these two
call sites, and nothing that ships authors one. Two consequences worth separating:
- objectui#6493's behaviour change is latent, not live — which is what it reports.
- The two call sites read a key the platform's own contract tells authors not to write.
That is the question objectui#6262 flagged for its own gap and this card inherits:
the honest repair may be to read visibleWhen (and hidden) here instead, or for the
spec to declare visible at field level. Both are decisions, not cleanups.
The awkward half, stated plainly
If the answer is "read visibleWhen", note that the spec declares field-level
visibleWhenhas no current_user — deliberately, and it names the exact failure
mode as the intended one:
field.zod.ts, on the per-option predicate: "Env: the live record plus the host
predicate scope, which binds current_user — wider than field-level visibleWhen,
which has no current_user."view.zod.ts, on FormField.visibleWhen: "No current_user at field level — it is
unbound here and the predicate would fault open (per-option visibleWhen is the
surface that binds it)."
ADR-0068 D1 pulls the other way for the key that is actually being evaluated today: one
user object under current_user / user / ctx.user / os.user, evaluating
identically "in a formula, an RLS policy, and a client visible gate". objectui#6493
conformed the evaluator to D1 because user was already bound there and three spellings
of the same object were not. Whether a field-level gate should see the user at all is the
question above, and it is one for the maintainer.
Related, and NOT a duplicate
- objectui#6493 — binds the roots on these two evaluators. Does not touch which key they read.
- objectui#6262 —
/forms/:name under-feeds the provider. Different mechanism, same family of question. - objectui#6505 — dev-mode
validateSchema types visible/disabled as boolean-only. That is
the COMPONENT schema's gate and a type claim in a validator; this is object FIELD metadata
and a strict-schema refusal.
Generated by Claude Code
Found while implementing objectui#6493 (binding the roots on the two ad-hoc field-visibility evaluators). Filed unassigned, recording only — not graded, no type.
Measured on
origin/main@d09e13fd7, and in the framework checkout beside it.What was measured
RecordFormPageandAppContentboth filter an object's fields withevaluateVisibility(f.visible, expressionEvaluator)— i.e. they gate onobjectDef.fields[].visible. objectui#6493 makes that evaluator bind the roots thistier declares; it deliberately did not ask whether the KEY it reads exists.
It does not.
@objectstack/spec'sFieldSchemais astrictObject, andvisibleappears in its
FIELD_KEY_GUIDANCEmap as prose that REFUSES the spelling rather thanan alias that renames it (
packages/spec/src/data/field.zod.ts):The guidance's own comment says the prose form was chosen over a rename deliberately,
because "this surface declares BOTH forms and the two answers have opposite polarity".
The per-OPTION schema, by contrast, does alias it:
SelectOptionSchemacarriesaliases: { ..., visible: 'visibleWhen' }. So the field level is the one place thespelling is answered with a refusal.
objectui's own
packages/typesagrees: field metadata declaresvisible_onandvisibleWhen; there is novisibleon any field metadata interface (the onevisible?: string | { dialect, source }inobjectql.tsis onBulkActionDef).Scanned all 104
*.object.{ts,yml,json}files in the framework, tracking the enclosingtop-level section of every
visibleoccurrence: 29 inactions, 15 infields, andevery one of the 15 is prose, a comment, or a per-option
visibleWhen— not one is afield-level
visiblekey. Nothing in the example apps authors one either.Why it is worth recording
The gate is therefore unreachable through the authoring surface: a field
visiblewritten in object metadata is rejected by the schema before it can reach these two
call sites, and nothing that ships authors one. Two consequences worth separating:
That is the question objectui#6262 flagged for its own gap and this card inherits:
the honest repair may be to read
visibleWhen(andhidden) here instead, or for thespec to declare
visibleat field level. Both are decisions, not cleanups.The awkward half, stated plainly
If the answer is "read
visibleWhen", note that the spec declares field-levelvisibleWhenhas nocurrent_user— deliberately, and it names the exact failuremode as the intended one:
field.zod.ts, on the per-option predicate: "Env: the liverecordplus the hostpredicate scope, which binds
current_user— wider than field-level visibleWhen,which has no
current_user."view.zod.ts, onFormField.visibleWhen: "Nocurrent_userat field level — it isunbound here and the predicate would fault open (per-option
visibleWhenis thesurface that binds it)."
ADR-0068 D1 pulls the other way for the key that is actually being evaluated today: one
user object under
current_user/user/ctx.user/os.user, evaluatingidentically "in a formula, an RLS policy, and a client
visiblegate". objectui#6493conformed the evaluator to D1 because
userwas already bound there and three spellingsof the same object were not. Whether a field-level gate should see the user at all is the
question above, and it is one for the maintainer.
Related, and NOT a duplicate
/forms/:nameunder-feeds the provider. Different mechanism, same family of question.validateSchematypesvisible/disabledas boolean-only. That isthe COMPONENT schema's gate and a type claim in a validator; this is object FIELD metadata
and a strict-schema refusal.
Generated by Claude Code