Skip to content

feat(web_core): write the v0.9 basic catalog in Zod 4 - #37

Closed
louis-bompart wants to merge 1 commit into
claude/zod-scope/protocol-zod4from
claude/zod-scope/basic-v09-zod4
Closed

louis-bompart wants to merge 1 commit into
claude/zod-scope/protocol-zod4from
claude/zod-scope/basic-v09-zod4

Conversation

@louis-bompart

Copy link
Copy Markdown
Collaborator

Description

This PR is the first step of porting the Zod 3 authoring support, stacked on #36. The v0.9 basic catalog moves to Zod 4, as #34 did for v1.0. Its document, validationSchema, binding and validation messages are unchanged.

The catalog.

  • src/v0_9/scripts/generate-catalog.mjs passes zod: 'v4'. It imports the Zod 4 v0.9 common types from feat(web_core): compile loaded catalogs to Zod 4 #35.
  • The catalog's two definition models, CatalogComponentCommonSchema and ThemeDefSchema, are now Zod 4 objects named with .meta(specMeta({defName})) instead of specDef().
  • The catalog's themeSchema (the shared BasicCatalogThemeSchema) and the exported v0.9 common types stay Zod 3. They move later in the authoring port.

Catalog mixins with specMeta. v0.9 components mix in CatalogComponentCommon, a definition from the catalog itself. #34 left that gap: specMeta could only name common types.

  • The problem: a mixin given only by name, and resolved through the catalog's defModels, broke reuse. A v0.9 basic component used in another catalog wrote a $ref to a definition that catalog didn't have. The composite-catalog tests caught it.
  • The fix: specMeta({mixins: [...]}) now takes the model itself, as spec() does.
    • It writes the model's defName into the JSON, and keeps the model, keyed by the annotation object.
    • SpecShaper finds the model through the schema's meta(), so the component brings its mixin along, as a Zod 3 component did.
    • A mixin name can also name one of the catalog's defModels. This is for libraries whose schemas don't report their metadata.
  • CatalogDocumentOptions.defModels now takes any EntrySchema.
  • New type: SpecMetaAnnotation.

Zod 4 and cycles. Zod 4's toJSONSchema fails on the v0.9 common types: "Error converting schema to JSON".

  • Cause: DynamicValue and FunctionCall refer to each other, and both name their definition with .meta({$ref}). Zod 4 doesn't extract a schema that already has a $ref into $defs, so the output object stays cyclic.
  • Fix: A2UI already reads such a schema as its reference, so normalizeSchema passes Zod an override, through Standard JSON Schema libraryOptions. It writes a schema named by its metadata $ref as that reference and its metadata alone.
  • This leaves the documents and views of every catalog unchanged, byte for byte.

Named types. The Zod 4 v0.9 common types now reuse the exported FunctionCall and DynamicValue types instead of declaring their own copies. Otherwise, Angular's declaration emit can't name the props types it infers from the catalog (TS2742).

Tests.

  • New tests:
    • a mixin model carried into any catalog;
    • a mixin named through defModels;
    • an unnamed mixin model rejected;
    • a cyclic Zod 4 schema read as its reference.
  • The fromZod3 catalog test now builds its own small Zod 3 catalog with a catalog mixin, since the v0.9 basic catalog is no longer Zod 3.

Breaking, per the changelog.

  • The v0.9 basic catalog's schemas are Zod 4.
  • A component's own schema outputs its defaults, such as Divider.axis.

Next: the v0.8 basic catalog, then the exported common types and helpers, spec(), exclusiveUnion and fromZod3, and then removing Zod 3.

Validation

  • yarn build from clean, with the generators rerun (stable output)
  • node --test "dist/**/*.test.js": 1453 passing, 0 failing, 42 todo (the known cases)
  • node tests/conformance/conformance_test.mjs: all vectors passing
  • yarn lint: 0 errors, same warning count
  • yarn format:check
  • yarn test in typescript/catalogs/{mcp,vegachart}, typescript/renderers/{lit,react,markdown-it} and typescript/agents/a2ui_agent, plus the agent SDK conformance suite (478). Angular builds and passes test:unit (328) and test:integration (222); test:closure-compiler needs Java 21.
  • Compared before and after:
    • the toJson() and validationSchema of every code catalog (basic v0.8, v0.9, v1, vegachart v0.9 and v1): byte for byte identical;
    • the binding views of every loaded catalog document (5,625 results): identical;
    • validation of 10,497 payloads against every catalog document and the code basic catalogs: no outcome or message changed. One accepted value gained a default (Divider {} now parses to {axis: "horizontal"} through its own schema), which the core doesn't use.

Part of a2ui-project#2160

Pre-launch Checklist

One time:

For this PR:

  • I have updated the relevant CHANGELOG.md file.
  • I updated/added relevant documentation.
  • My code changes (if any) have tests.
  • If my branch is on a fork, I have verified that scripts/e2e_test.sh passes.

🤖 Generated with Claude Code

The v0.9 basic catalog's components, functions and definition models are
generated in Zod 4, on the Zod 4 v0.9 common types, like the v1.0 catalog.

specMeta() takes a mixin model, named with specMeta({defName}), and A2UI
finds it through the schema's meta(), so a component reused in another
catalog still writes its CatalogComponentCommon mixin. A mixin name can also
name one of the catalog's defModels. Zod 4 converts a schema named with
.meta({$ref}) to its reference and metadata, which also lets it convert the
v0.9 DynamicValue/FunctionCall cycle. The Zod 4 v0.9 common types reuse the
exported recursive types so inferred types can be named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@louis-bompart

Copy link
Copy Markdown
Collaborator Author

Superseded by the regrouped stack on upstream main: this change is now part of #53 (series #45 to #54). The branch is kept for reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant