Skip to content

fix(spec): validate all three runtime view shapes in the view type-schema (#3095)#3159

Merged
os-zhuang merged 2 commits into
mainfrom
claude/viewschema-metadata-type-schema-28xpxr
Jul 18, 2026
Merged

fix(spec): validate all three runtime view shapes in the view type-schema (#3095)#3159
os-zhuang merged 2 commits into
mainfrom
claude/viewschema-metadata-type-schema-28xpxr

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #3095.

Problem

metadata-type-schemas.ts mapped the view metadata type to the aggregate container ViewSchema ({ list, form, listViews, formViews }, every slot optional). Zod strips unknown keys, so the two non-container shapes a view body actually carries at runtime both strip-parsed to {}:

Because they parsed to {} and succeeded, the schema was a no-op for them:

  • the 422 spec-validation in saveMetaItem never fired for ViewItem/personalization writes,
  • computeMetadataDiagnostics returned valid: true for enumerated ViewItems,
  • the view create-seed test validated against nothing (fake positive).

A broken config — e.g. a kanban missing groupByField — saved with a false 200 and was badged valid.

Fix

view now maps to a new ViewMetadataSchema (packages/spec/src/ui/view.zod.ts) — a z.union over the three runtime shapes, each validated genuinely:

  1. defineView containerViewSchema refined to require at least one of list/form/listViews/formViews (an empty container is rejected, mirroring defineView).
  2. ViewItem recordViewItemSchema; the nested config is validated against ListView/FormView.
  3. Flattened personalization overlay — inline ListView/FormView config plus optional identity fields. Structural guards pin config/list/form/listViews/formViews to undefined, so a malformed record or container can never be rescued through this lenient branch with its real payload silently stripped.

Design notes:

  • All members strip-parse (no .strict()), so auxiliary Studio round-trip keys (isPinned, sortOrder, …) still ride along without a false 422; saveMetaItem keeps persisting the body verbatim.
  • The list member is tried before the form member; a flattened form matches the form member because ListView requires columns and the ListView/FormView type enums are disjoint — so a bad list config cannot silently pass as a form.
  • z.toJSONSchema() emits the schema as an anyOf of the four members, which /api/v1/meta/types/view serves to Studio's SchemaForm (verified in tests).

No change to normalizeViewMetadata / persisted shapes — the #2555 personalization overlay is validated in place, so console saves and switcher badges do not regress.

Acceptance criteria

  • A bad ViewItem config (e.g. kanban missing groupByField) returns 422 on save and an error in diagnostics.
  • Personalization overlay bodies still save and their diagnostics badges don't regress.
  • The create-seed test now exercises genuine (non-no-op) schema validation for view.
  • /api/v1/meta/types/view JSON Schema output (union → anyOf) verified.

Tests

  • packages/spec/src/ui/view-metadata-schema.test.ts (new) — all three shapes accepted/rejected genuinely, member exclusivity, JSON-Schema anyOf emission.
  • packages/objectql/src/metadata-diagnostics.test.tscomputeMetadataDiagnostics('view', …) for bad ViewItem, good ViewItem, personalization overlay, and container.
  • packages/objectql/src/protocol-view-identity-overlay.test.tssaveMetaItem returns 422 for a broken ViewItem record and 200 for a well-formed one.

Full @objectstack/spec (6911), @objectstack/objectql (911), @objectstack/metadata-protocol (34), and @objectstack/metadata (276) suites pass. A changeset is included.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LFmMsWLC3CVULh3Ke7rUdd


Generated by Claude Code

…schema (#3095)

The `view` metadata type mapped to the aggregate container `ViewSchema`
(`{ list, form, listViews, formViews }`, all slots optional). Zod strips
unknown keys, so the two non-container shapes a `view` body carries at
runtime — a standalone ViewItem record (`{ name, object, viewKind, config }`)
and a console personalization overlay (raw config + inherited identity, #2555)
— both strip-parsed to `{}`. That made the 422 check in `saveMetaItem` and
read-time `computeMetadataDiagnostics` a no-op for them: a broken `config`
(e.g. a kanban missing `groupByField`) saved with a false 200 and badged valid.

Map `view` to a new `ViewMetadataSchema` — a union over the three shapes,
each validated genuinely:

  1. non-empty defineView container (empty container rejected),
  2. ViewItem record (nested config validated against ListView/FormView),
  3. flattened personalization overlay (inline config + identity), with
     structural guards pinning config/list/form/listViews/formViews to
     undefined so a malformed record/container is never rescued through the
     lenient branch with its payload stripped.

All members strip-parse (no .strict()) so aux Studio keys still ride along
without a false 422; saveMetaItem keeps persisting verbatim. toJSONSchema
emits an anyOf of the four members for /api/v1/meta/types/view.

Adds regression tests: schema-level (all three shapes + JSON Schema),
save-path 422 for a bad ViewItem, and computeMetadataDiagnostics badges.

Fixes #3095.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFmMsWLC3CVULh3Ke7rUdd
@vercel

vercel Bot commented Jul 18, 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 18, 2026 1:52am

Request Review

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/spec.

107 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 @objectstack/objectql, 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 packages/objectql, @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/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • 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/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • 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/rls.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/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @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/index.mdx (via @objectstack/objectql)
  • 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/objectql, @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/objectql, @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/objectql, @objectstack/spec)
  • content/docs/ui/actions.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/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.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.

…n schema

Two CI gates broke on the #3095 change that maps `view` to a union:

- Spec property liveness: `check-liveness.mts` walked each metadata type's
  top-level OBJECT shape; a union has none, so it threw "view is not an object
  schema". Teach `shapeOf` to resolve a union to its first object-typed member
  — the canonical authorable container (list/form/listViews/formViews) the
  ledger governs. Discriminated-union members (ViewItem) are skipped; their
  inner config is the same ListView/FormView surface already governed under the
  container's list/form children.
- API-surface snapshot: record the two intended new public exports
  (`ViewMetadata` type, `ViewMetadataSchema` const) via gen:api-surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFmMsWLC3CVULh3Ke7rUdd
@os-zhuang
os-zhuang marked this pull request as ready for review July 18, 2026 02:29
@os-zhuang
os-zhuang merged commit fc5a3a2 into main Jul 18, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/viewschema-metadata-type-schema-28xpxr branch July 18, 2026 02:29
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.

view metadata type-schema is a no-op for runtime shapes: ViewItem/personalization bodies strip to {} under ViewSchema

2 participants