Skip to content

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

Open
os-zhuang wants to merge 3 commits into
mainfrom
claude/adr-0089-visible-when-p0gfkz
Open

feat(spec): unify conditional-visibility predicate under visibleWhen (ADR-0089)#2900
os-zhuang wants to merge 3 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:

Layer Predicate 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 visibleWhen once, 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

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 14, 2026 8:00am

Request Review

@github-actions

github-actions Bot 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.

103 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/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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants