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

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 container — ViewSchema refined to require at least one of list/form/listViews/formViews (an empty container is rejected, mirroring defineView).
  2. ViewItem record — ViewItemSchema; 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.ts — computeMetadataDiagnostics('view', …) for bad ViewItem, good ViewItem, personalization overlay, and container.
  • packages/objectql/src/protocol-view-identity-overlay.test.ts — saveMetaItem 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

This branch was successfully deployed

1 active deployment
Preview — 62fbaed7 Deployed Jul 18, 2026 by vercel[bot]
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 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