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

vercelBot commented Jul 18, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 18, 2026 1:52am

Request Review

@github-actionsgithub-actionsBot 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 mainJul 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/mteststooling

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

@os-zhuang@claude