Skip to content

Implement ADR-0089: unify conditional-visibility predicate under visibleWhen #2642

Description

@os-zhuang

Follow-up to implement the decision in ADR-0089 (docs/adr/0089-unify-visibility-predicate-naming.md), merged in #2641. The ADR is the design decision only — no code has changed yet. This issue tracks the implementation.

Problem (recap)

The same "show only when the CEL predicate is TRUE" concept is spelled three ways by layer, and zod's default strip mode makes a mis-layered key vanish silently:

Layer Key today Location
Data field / field option visibleWhen packages/spec/src/data/field.zod.ts:592, 108
View form section / field visibleOn packages/spec/src/ui/view.zod.ts:762, 781
Page component visibility packages/spec/src/ui/page.zod.ts:109

Canonical target is visibleWhen (aligns with the existing readonlyWhen/requiredWhen family and the conditionalRequired → requiredWhen precedent).

Tasks

  • D1 — canonical visibleWhen. Accept visibleWhen on view form sections/fields (view.zod.ts) and page components (page.zod.ts), matching the data-field semantics. Document the per-layer binding root (runtime surfaces bind record + current_user; metadata-editing forms bind data).
  • D2 — alias + parse-time normalization. Mark visibleOn / visibility @deprecated; normalize to visibleWhen in parse() (fold visibleOn ?? visibility into visibleWhen when the canonical key is absent), mirroring the conditionalRequired/requiredWhen pattern in field.zod.ts:594-598. Register the rename in the deprecation / spec-changes.json registry per ADR-0087.
  • D3a — .strict(). Tighten the relevant object schemas so a mis-layered / stale-alias visibility key is a parse error, not a silent strip (ADR-0049 enforce-or-remove, ADR-0078 no-silently-inert). Sweep the monorepo + example apps before flipping strict on.
  • D3b — lint rule. Add a @objectstack/lint rule flagging (a) a deprecated alias in freshly authored source (autofix → visibleWhen), and (b) a predicate whose binding root mismatches its layer (data. in a runtime form predicate, or record. in a metadata form predicate).
  • Codemod first-party sources. packages/ + examples/: visibleOn (~13 files) / visibility (~34 files) → visibleWhen. Update content/docs/** and the objectstack-ui / objectstack-data skills (including the layer→binding-root table).
  • Renderer reads. Confirm ObjectUI form + page renderers read the normalized visibleWhen.
  • Deprecation window then removal. Keep aliases for the standard window; remove in a future major.

Out of scope

  • Boolean visible (Tab on/off, view.zod.ts:276) — a static flag, not a predicate; keeps its name and type per ADR-0089 D4.
  • hidden (field boolean) and visibleFields (gallery card array) — unrelated.

References

  • ADR-0089 (this repo, docs/adr/0089-unify-visibility-predicate-naming.md) · merged in docs(adr): ADR-0089 — unify conditional-visibility predicate under visibleWhen #2641
  • Precedent: conditionalRequired → requiredWhen resolution in field.zod.ts + coalesce in packages/objectql/src/validation/rule-validator.ts:378
  • Related ADRs: 0087 (conversion-over-notification), 0049 (enforce-or-remove), 0078 (no-silently-inert), 0085 (already removed a consumer-less visibleOn)

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    Fields

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions