Skip to content

A per-option visibleWhen written in a *.form.ts is silently inert — the metadata-admin renderer never reads it #6247

Description

@claude

Rebuilt from objectstack-ai/objectstack#11793 (fix lands in this repo's packages/app-shell/src/views/metadata-admin/SchemaForm.tsx; REST issue-transfer unavailable to the triage seat — recipe: provenance header + qualified refs + source closed as moved). Premise re-verified on this repo's origin/main: SchemaForm's predicate reads are node-level only (sections/fields/repeater rows); the select-option mapping never consults opt.visibleWhen.

Filing unassigned — recording, not claiming. Measured while implementing objectstack-ai/objectstack#11410, where per-option visibility was the first shape considered and had to be ruled out.

The observation

FormFieldBaseSchema.options is z.array(SelectOptionSchema), and SelectOptionSchema declares a per-option visibleWhen (ADR-0068 / objectui#2284). So a metadata form (packages/spec/src/data/*.form.ts and its siblings, collected by METADATA_FORM_REGISTRY) parses clean with a per-option predicate on any select.

It has no effect there. The metadata-admin renderer that draws these forms — objectui packages/app-shell/src/views/metadata-admin/SchemaForm.tsx — maps fieldSpec.options straight to select items and never consults opt.visibleWhen. Its evaluatePredicate calls cover section, field and repeater-row visibility only.

So on this surface the key is accepted, stored, shipped, and ignored — the ADR-0049 declared-but-unenforced shape, scoped to one of the key's two consuming surfaces.

Why it is easy to get wrong in exactly the costly direction

An author reaching for it wants to withdraw an option. Both failure modes point the same way — the option stays offered:

  1. Metadata-admin surface: the predicate is never evaluated, so every option renders.
  2. Runtime form surface (resolveCascadingOptions to evalFieldPredicate, where the key is honored): the scope binds record / previous / extra and neverdata. Metadata forms spell their predicates data.*, so a data.-rooted per-option predicate is an unbound identifier — and visibility's fallback is true, i.e. the option is kept.

A narrowing written this way therefore reads as done and does nothing. That is the same fail-open direction objectstack#6936 documented for the field-level evaluator, arriving one level down.

Not a claim that the docblock is wrong

SelectOptionSchema.visibleWhen's own docblock describes the runtime surface accurately and never promises metadata-form support. The gap is that nothing tells an author reading FormFieldBaseSchema.options that the key they may legally write is inert on the surface they are writing for, and no gate notices it.

Possible directions (not a recommendation — this wants triage)

  • Teach the metadata-admin SchemaForm to filter options by visibleWhen (fix in objectui; makes declared = enforced on both surfaces, and would need the data binding decided since that is the root metadata forms use).
  • Or state the boundary where it is reachable — on FormFieldBaseSchema.options — and consider a gate that fails a *.form.ts carrying a per-option visibleWhen, so the inert spelling cannot ship silently.

Zero *.form.ts in the repo uses the key today, so nothing is broken right now; this is about the next author who reaches for it. objectstack-ai/objectstack#11410 worked around it by using two field declarations with disjoint field-level visibleWhen, which is the shape the renderer does honor.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain: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