Skip to content

feat(spec): unify conditional-visibility predicate under visibleWhen (ADR-0089) - #2900

Merged
os-zhuang merged 4 commits into
mainfrom
claude/adr-0089-visible-when-p0gfkz
Jul 14, 2026
Merged

feat(spec): unify conditional-visibility predicate under visibleWhen (ADR-0089)#2900
os-zhuang merged 4 commits into
mainfrom
claude/adr-0089-visible-when-p0gfkz

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Implements ADR-0089 (docs/adr/0089-unify-visibility-predicate-naming.md), closing the core of #2642.

One concept — "show this only when the CEL predicate is TRUE" — was spelled three ways by layer (visibleWhen on data fields, visibleOn on view forms, visibility on page components), and zod's default strip made a mis-layered key vanish silently. This PR makes visibleWhen the single canonical key everywhere, aligning with the existing readonlyWhen / requiredWhen family and the resolved conditionalRequired → requiredWhen precedent.

What changed

D1 — canonical visibleWhen is now accepted on view form sections/fields (FormFieldSchema, FormSectionSchema in view.zod.ts) and page components (PageComponentSchema in page.zod.ts), matching the data-field semantics. The per-layer binding root is documented in TSDoc:

LayerPredicate binds
Runtime record forms & pages (*.view.ts, *.page.ts)record + current_user (pages also expose page.<var>)
Metadata-editing forms (*.form.ts)data — the row under edit

D2 — aliases + parse-time normalization.visibleOn (view) and visibility (page) are marked @deprecated and folded into visibleWhenonce, at the schema boundary, via a shared normalizeVisibleWhen zod .transform() (packages/spec/src/shared/visibility.ts). Canonical wins when both are present; the alias is dropped from the parsed output. No renderer or validator re-implements the fallback.

Codemod (first-party). Renamed in-scope usages to visibleWhen: metadata-editing forms (field.form.ts, object.form.ts, page.form.ts, action.form.ts, report.form.ts, view.form.ts), the showcase view + pages, affected tests, docs (layout-dsl.mdx + the layer→binding-root table, views.mdx, formulas.mdx), the objectstack-formula skill, and the expression-conformance ledger (both spellings kept as live CEL surfaces during the deprecation window).

Note on scope: the ADR's "~34 visibility sites" over-counts — most visibility occurrences are unrelated enums (feed / package / environment / agent). Only page-component predicate usages are in scope, so the codemod is surgical rather than a blind rename. sdui-parser's visibleOn is a separate amis-style DSL convention and is intentionally untouched.

Tests

New ADR-0089 describe blocks in view.test.ts and page.test.ts prove: deprecated alias → visibleWhen normalization, alias dropped from output, and canonical-wins-when-both-present. Verified green in isolation: @objectstack/spec (6721), example-showcase (55), objectql (842), metadata (260), rest (255), and the dogfood expression-conformance ledger. No first-party consumer reads the raw aliases.

Out of scope / follow-ups (per the ADR's staged rollout)

  • D3a — .strict() flip and D3b — the @objectstack/lint rule: deferred; the ADR gates the strict flip on a full monorepo + example sweep and a deprecation window, and a broad strict flip has a large blast radius under parallel-agent development.
  • ObjectUI renderer reads (visibleWhen): lives in the sibling objectui repo (not in this backend repo); companion change tracked with Implement ADR-0089: unify conditional-visibility predicate under visibleWhen #2642.
  • Boolean visible (Tab on/off), field hidden, gallery visibleFields — unchanged per ADR-0089 D4.

Includes a minor changeset with the FROM → TO migration mapping. ADR status flipped to Accepted.

🤖 Generated with Claude Code

https://claude.ai/code/session_017fU25GeYSPKq9eEURsQffF


Generated by Claude Code

…(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
@vercel

vercelBot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 14, 2026 11:30am

Request Review

@github-actions

github-actionsBot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/cli, @objectstack/dogfood, @objectstack/lint, @objectstack/spec.

104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx(via @objectstack/cli)
  • content/docs/api/environment-routing.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/cli, packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/backup-restore.mdx(via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx(via @objectstack/cli)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/cli, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/permissions/authentication.mdx(via @objectstack/cli)
  • content/docs/permissions/authorization.mdx(via packages/dogfood, @objectstack/lint, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx(via packages/dogfood)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/objectos/realtime-protocol.mdx(via @objectstack/cli)
  • content/docs/protocol/objectos/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Regenerate the public API-surface snapshot to include the two new
`@objectstack/spec` exports added by ADR-0089 — `normalizeVisibleWhen`
and `VISIBILITY_ALIAS_KEYS` (shared visibility-normalization helper).
Non-breaking (2 added, 0 removed/narrowed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fU25GeYSPKq9eEURsQffF
Add `validateVisibilityPredicates` (`@objectstack/lint`) and wire it into
`os validate` + `os compile` as advisory warnings:
- `visibility-alias-deprecated` — a deprecated `visibleOn` (view form) or
`visibility` (page component) key in authored source → steer to `visibleWhen`.
- `visibility-root-mislayered` — a runtime view/page visibility predicate rooted
at `data.` (the metadata-editing-form root) → runtime surfaces bind
`record`/`current_user`/`page.<var>`, so a `data.` root never matches.
Runs on the PRE-parse (normalized) stack — like `validate-list-view-mode` —
because the schema folds `visibleOn`/`visibility` into `visibleWhen` at parse,
so the parsed stack no longer carries the alias the author wrote. Both rules are
warnings; the build never fails on them.
Addresses #2903 (ADR-0089 D3b).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fU25GeYSPKq9eEURsQffF
…le-when-p0gfkz
# Conflicts:
#	packages/cli/src/commands/validate.ts
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:dataprotocol:uisize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude