Skip to content

One intent, two spellings: visible (actions) vs visibleWhen (fields / sections / userActions) — and the alias guard only covers one direction #7816

Description

@baozhoutao

Summary

One intent — "show this thing only when the predicate holds" — is spelled two different ways depending on which schema you are authoring, and the guard that catches the wrong spelling only runs in one direction.

The action schemas already recognise the other spelling and rename it: actionObject() registers visibleWhen → visible, showWhen → visible, disabledWhen → disabled (action.zod.ts:776), and ACTION_PARAM_KEY_ALIASES maps visiblewhen/visibleon/visibility → visible (L74). The comment there states the motive exactly:

ADR-0089 made visibleWhen the canonical predicate on view/page schemas. An author who learned it there would silently lose a param's capability gate here.

The reverse alias does not exist. An author who learns visible on an action and writes it on a field or on userActions.delete gets a bare unknown-key error that never names the key they should have used.

Measured (spec dist on main, ObjectSchema.create)

ACCEPTED  field.visibleWhen                          (canonical)
REJECTED  field.visible
          Unrecognized key(s) on this field: `visible`. Until #4001 closed this shape
          these were dropped silently — …
REJECTED  userActions.delete.visible
          Unrecognized key: "visible"
REJECTED  userActions.delete.disabled
          Unrecognized key: "disabled"

Neither message mentions visibleWhen / disabledWhen. RowCrudActionOverrideSchema is a plain z.object({…}).strict() — it has no aliases / guidance map at all, so it cannot say anything beyond zod's default.

The arms accepted also differ, which matters for any unification:

surface key false literal CEL string {dialect, source}
action / action param visible ✅ (#5970)
field / section / component / userActions.* visibleWhen

On userActions.edit/delete the boolean lives on a sibling key (enabled: false), so the same "settled at authoring time" case is spelled differently again.

Why raise it

The asymmetry argument the repo already accepts, applied to itself. From action.zod.ts (#5970, on visible vs disabled before they were unified):

An asymmetry between two keys that mean the same kind of thing is a dialect nursery: it teaches each consumer to keep its own widening … and every one of those is a second de-facto contract (Prime Directive #12).

Two spellings for one intent across neighbouring schemas is the same nursery one level up. Today it costs authors a failed parse and a search; the aliases already carry the admission that authors DO move between these surfaces.

Asks (in order of cost)

  1. Symmetry, no behaviour change — give RowCrudActionOverrideSchema (and the field / section / component shapes) the same aliases treatment the action shapes have: visible → visibleWhen, showWhen → visibleWhen, disabled → disabledWhen. Purely a better error; nothing that parses today changes. Note the boolean case on userActions.* should point at enabled, not at visibleWhen, or the hint will just move the confusion.
  2. A platform decision on ONE canonical, recorded rather than left implicit. visibleWhen is the majority surface and already ADR-0089's canonical, so converging there (with visible demoted to a registered alias on the action shapes) is the smaller move — but it needs a call on:

Item 1 stands on its own even if item 2 lands as "keep both" — right now the guard protects one direction of a two-way street.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions