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
feat(spec): unify conditional-visibility predicate under visibleWhen (ADR-0089)
Make `visibleWhen` the single canonical conditional-visibility key across all
layers (data field, view form section/field, page component), aligning with the
existing `readonlyWhen` / `requiredWhen` family and the resolved
`conditionalRequired → requiredWhen` precedent.
D1 — canonical key: accept `visibleWhen` on `FormFieldSchema` /
`FormSectionSchema` (view.zod.ts) and `PageComponentSchema` (page.zod.ts),
documenting the per-layer binding root (runtime surfaces bind
`record` + `current_user` + `page.<var>`; metadata-editing forms bind `data`).
D2 — aliases + normalization: mark the view `visibleOn` and page `visibility`
keys `@deprecated`; fold them into `visibleWhen` once at the schema boundary via
a shared `normalizeVisibleWhen` zod `.transform()` (canonical wins when both are
present), so no consumer re-implements the fallback.
Codemod first-party sources to canonical `visibleWhen`: metadata-editing forms
(`*.form.ts`), the showcase view/pages, and the affected tests + docs + the
objectstack-formula skill + the expression-conformance ledger (both spellings
kept as live CEL surfaces during the deprecation window).
Out of scope, unchanged: boolean `visible` (Tab on/off), field `hidden`,
gallery `visibleFields`, and unrelated `visibility` enums (feed / package /
environment / agent). The `.strict()` flip (D3a), the lint rule (D3b), and the
ObjectUI renderer reads (sibling repo) are staged follow-ups per the ADR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fU25GeYSPKq9eEURsQffF
ADR-0089: unify the conditional-visibility predicate under one canonical key, `visibleWhen`, across every layer (data field, view form section/field, page component). This aligns visibility with the existing `readonlyWhen` / `requiredWhen` family and the `conditionalRequired → requiredWhen` precedent.
6
+
7
+
**Canonical key:**`visibleWhen` — a CEL predicate; the element is shown only when it is TRUE. The binding *root* is still set by the layer: runtime record forms and pages bind `record` + `current_user` (pages also expose `page.<var>`); metadata-editing forms (`*.form.ts`) bind `data`.
8
+
9
+
**Deprecated aliases (still accepted):** the view key `visibleOn` and the page key `visibility` are now `@deprecated`. Both are folded into `visibleWhen`**once, at the schema boundary** (a zod `.transform()`), so consumers only ever read `visibleWhen`. When both a canonical and an alias key are present, the canonical wins.
- Data field / field option: already `visibleWhen` — unchanged.
16
+
17
+
Out of scope (unchanged): the boolean `visible` (Tab on/off), field `hidden`, gallery `visibleFields`, and unrelated `visibility`*enums* (feed / package / environment / agent). Aliases remain for the standard deprecation window and are removed in a future major.
Copy file name to clipboardExpand all lines: docs/adr/0089-unify-visibility-predicate-naming.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# ADR-0089: Unify the conditional-visibility predicate under one name (`visibleWhen`), alias the rest
2
2
3
-
**Status**: Proposed (2026-07-05)
3
+
**Status**: Accepted (2026-07-14) — D1 + D2 + codemod + docs implemented in #2642. D3a (`.strict()` flip) and D3b (lint rule) deferred to follow-up per the ADR's staged rollout (sweep first, then flip).
4
4
**Deciders**: ObjectStack Protocol Architects
5
5
**Builds on**: [ADR-0049](./0049-no-unenforced-security-properties.md) (enforce-or-remove — a declared-but-unchecked visibility key is exactly this class), [ADR-0078](./0078-no-silently-inert-metadata.md) (no silently-inert metadata — a mis-layered visibility key that zod strips is inert by accident), [ADR-0085](./0085-object-semantic-roles-over-surface-hint-blocks.md) (the enforce-or-remove pass that already deleted a consumer-less `visibleOn` from `fieldGroups`), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (the AI-authoring population this ADR optimizes for), [ADR-0058](./0058-expression-and-predicate-surface.md) (the CEL predicate surface these keys all belong to), [ADR-0087](./0087-metadata-protocol-upgrade-contract.md) (conversion-over-notification; this rename ships as an L1 invisible break via the alias mechanism)
6
6
**Consumers**: `@objectstack/spec` (the field / view / page zod schemas + the alias normalization), `@objectstack/objectql` (`rule-validator` — the server-side enforcer), `@objectstack/lint` (the new mis-layer / wrong-root rule), ObjectUI form + page renderers, and every AI author of `*.object.ts` / `*.view.ts` / `*.page.ts`
'This panel is gated on `page.selectedProjectId != ""`. It became visible the instant the picker wrote the variable — the same page-local state any other component (or its data filter) can read.',
0 commit comments