Skip to content

fix(spec): keep lazySchema proxies identity-compatible with z.toJSONSchema (objectui#2561)#3021

Merged
os-zhuang merged 1 commit into
mainfrom
fix/lazy-schema-tojsonschema-identity
Jul 16, 2026
Merged

fix(spec): keep lazySchema proxies identity-compatible with z.toJSONSchema (objectui#2561)#3021
os-zhuang merged 1 commit into
mainfrom
fix/lazy-schema-tojsonschema-identity

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Problem

z.toJSONSchema crashes with TypeError: Cannot set properties of undefined (setting 'ref') on spec 15 PageSchema / ViewSchema / PageComponentSchema / FormFieldSchema (default lazy mode; OS_EAGER_SCHEMAS=1 converts fine). This breaks ObjectUI Studio's spec-derived Page/View inspector JSONSchema derivation (page-schema.ts / view-schema.ts in objectui app-shell) and currently blocks objectui's spec ^15 adoption (objectstack-ai/objectui#2561).

Root cause

zod's toJSONSchema keys its seen map on the node object it traverses — the lazySchema Proxy, wherever a schema is referenced lazily (z.lazy(() => X) recursion getters, direct conversion roots). But zod's per-type hooks close over the real instance at construction time (inst._zod.processJSONSchema = (ctx, …) => pipeProcessor(inst, ctx, …)), and the wrapper-type processors (pipe/lazy/optional/default/…) then resolve ctx.seen.get(inst) — a miss when the entry was keyed on the Proxy.

Latent until now: plain-object schemas never look themselves up (objectProcessor doesn't touch seen.get(self)), so the proxies were harmless while every lazy-referenced schema was a bare z.object. ADR-0089 D3a turned PageComponentSchema / FormFieldSchema into .strict().transform(normalizeVisibleWhen) pipes — a wrapper type — detonating the identity mismatch. (zod 4.4.3 is current latest; no zod-side fix available.)

Fix

lazySchema's proxy now serves a memoised _zod facade: prototype-delegates every read to the real internals, wrapping only processJSONSchema to alias the proxy's seen entry onto the real instance (alias, never clobber) before delegating. Parse hot path unchanged (facade built once per proxy); OS_EAGER_SCHEMAS=1 bypass unchanged.

Tests

  • lazy-schema.test.ts: D3a pipe shape, recursion through z.lazy(() => proxy) (FormFieldSchema shape), mixed proxy+real traversal in both orders, facade memoisation. Verified the 3 new conversion tests fail without the fix.
  • page.test.ts / view.test.ts: pin the exact Studio derivation calls (z.toJSONSchema(PageSchema/ViewSchema, { io: 'input', unrepresentable: 'any' })).
  • Full @objectstack/spec suite: 253 files / 6863 tests green.

Changeset: patch @objectstack/spec. Once released, objectui rides it in the #2561 types-package PR (spec ^15 pin bump).

Refs objectstack-ai/objectui#2561, ADR-0089 D3a.

🤖 Generated with Claude Code

…chema (objectui#2561)

zod's toJSONSchema keys its seen-map on the traversed node (the lazySchema
Proxy at z.lazy recursion getters and conversion roots), while its
wrapper-type processors (pipe/lazy/optional/default/...) resolve
ctx.seen.get(inst) via the REAL instance captured at construction — the
identity mismatch crashes with "Cannot set properties of undefined
(setting 'ref')". Latent while lazy-referenced schemas were plain objects;
ADR-0089 D3a made PageComponentSchema/FormFieldSchema .strict().transform()
pipes, breaking ObjectUI Studio's spec-derived Page/View inspector
JSONSchema derivation under spec 15.

The proxy now serves a memoised _zod facade that prototype-delegates to the
real internals and wraps only processJSONSchema to alias the proxy's seen
entry onto the real instance before delegating. Parse behavior unchanged;
OS_EAGER_SCHEMAS=1 remains the bypass. Regression tests: the D3a pipe shape,
recursion through z.lazy(() => proxy), mixed proxy+real traversal, and the
PageSchema/ViewSchema Studio derivation paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 16, 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 16, 2026 6:09am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ui tooling size/m labels Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

97 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 @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @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/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/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/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/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/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via 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/authorization.mdx (via @objectstack/spec)
  • 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/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.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 @objectstack/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/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.

@os-zhuang
os-zhuang merged commit d918c9f into main Jul 16, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the fix/lazy-schema-tojsonschema-identity branch July 16, 2026 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant