From 8ff07b235de9478a8b19f6f2610f41156bd6050f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 02:38:19 +0000 Subject: [PATCH] feat(types)!: retire the block schema family (#4895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02 (director seat, summon #8, verbatim 「同意」) — option C1, retire the family in one change, no transition window. Gone from `@object-ui/types`, both subpaths that carried them: `.` BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema, BlockInstanceSchema, BlockVariable, BlockMetadata, BlockLibraryItem, ComponentSchema `./zod` the nine mirrors above plus BlockComponentSchema — which was also AnyComponentSchema's block arm The zod half is the one that mattered. On 17.6.0 the shipped `AnyComponentSchema.safeParse({ type: 'block-library' })` returned success for a node no page can render, so an author who copied the documented shape was told green by the validator and then got OBJUI-001. All five discriminants are now refused, pinned in `phase2-schemas.test.ts` beside the theme refusals retired the same way. `blocks.ts` and `zod/blocks.zod.ts` are kept as ADR-0049 tombstones exporting nothing; `block-family-retired-4895.test.ts` pins every retired name out of them. `content/docs/blocks/block-schema.mdx` is deleted with the family and its DOC_TYPE_EXEMPTIONS entries go with it — `check:doc-types` reports `stale-exemption` otherwise. objectui#7023 dissolves into this retirement. The live slotted record-page vocabulary (`kind: 'slotted'`, `page.slots`, `usePageAssignment`, `PageBlockCanvas`, `PageBlockInspector`) shares the words "block" and "slot" with the retired family and shares no declaration, type or file with it. It is not touched. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC --- .changeset/4895-retire-block-schema-family.md | 52 ++ AGENTS.md | 2 +- content/docs/blocks/block-schema.mdx | 553 ------------------ content/docs/blocks/meta.json | 1 - content/docs/components/feedback/empty.mdx | 2 +- .../docs/components/overlay/alert-dialog.mdx | 2 +- content/docs/guide/architecture.md | 4 +- content/docs/guide/ci-cd-pipeline.md | 3 +- content/docs/guide/layout.md | 8 +- content/docs/guide/schema-overview.md | 41 +- content/docs/guide/troubleshooting.md | 6 +- .../src/renderers/feedback/empty.tsx | 10 +- packages/data-objectstack/README.md | 4 +- .../block-family-retired-4895.test.ts | 96 +++ .../overlay-node-slot-doc-types-7082.test.ts | 52 +- .../src/__tests__/phase2-schemas.test.ts | 114 ++-- .../src/__tests__/zod-mirror-parity.test.ts | 22 - packages/types/src/blocks.ts | 451 ++------------ packages/types/src/index.ts | 19 +- packages/types/src/zod/blocks.zod.ts | 190 ++---- packages/types/src/zod/index.zod.ts | 24 +- scripts/check-doc-component-types.mjs | 55 +- scripts/check-doc-snippet-types.mjs | 7 +- 23 files changed, 395 insertions(+), 1323 deletions(-) create mode 100644 .changeset/4895-retire-block-schema-family.md delete mode 100644 content/docs/blocks/block-schema.mdx create mode 100644 packages/types/src/__tests__/block-family-retired-4895.test.ts diff --git a/.changeset/4895-retire-block-schema-family.md b/.changeset/4895-retire-block-schema-family.md new file mode 100644 index 0000000000..67af25b994 --- /dev/null +++ b/.changeset/4895-retire-block-schema-family.md @@ -0,0 +1,52 @@ +--- +'@object-ui/types': minor +'@object-ui/components': patch +--- + +Removed the block schema family (objectui#4895, ADR-0049 enforce-or-remove, maintainer +ruling 2026-09-02 — option C1, retire the family in one change, no transition window). + +**Breaking on a published surface, deliberately.** These names are gone from +`@object-ui/types`, from both subpaths that carried them: + +- `.` (types): `BlockSchema`, `BlockSlot`, `BlockLibrarySchema`, `BlockEditorSchema`, + `BlockInstanceSchema`, plus the support types with no other reader — `BlockVariable`, + `BlockMetadata`, `BlockLibraryItem` — and `ComponentSchema`. +- `./zod` (runtime validators): `BlockVariableSchema`, `BlockSlotSchema`, + `BlockMetadataSchema`, `BlockSchema`, `BlockLibraryItemSchema`, `BlockLibrarySchema`, + `BlockEditorSchema`, `BlockInstanceSchema`, `ComponentSchema`, and the + `BlockComponentSchema` union over them — which was also `AnyComponentSchema`'s block arm. + +The zod half is the one that mattered. `AnyComponentSchema.safeParse({ type: +'block-library' })` returned **success** on 17.6.0 for a node no page can render, so an +author who copied the documented shape was told green by the shipped validator and then got +the registry's `OBJUI-001` "Unknown component type" panel. Validated-then-broken is worse +than never-validated, because the green light is what the author trusted. All five +discriminants — `block`, `block-library`, `block-editor`, `block-instance`, `component` — +are now **refused**, pinned in `phase2-schemas.test.ts` alongside the theme refusals +retired the same way. + +Evidence the family was declared-but-unenforced: zero `ComponentRegistry.register(...)` +sites claimed any of the five keys (positive control `'table'` resolves to two), zero +renderers, and zero readers outside `packages/types/src`. The liveness pass this card's +earlier deferral was keyed to (objectui#6935) established that external consumption of this +package is structurally unmeasurable — the certainly-live control `TableSchema` returns the +same zero external consumers — so the ruling was taken on the evidence in hand rather than +on a deferral whose exit cannot fire. + +⚠️ **Not this family, and not touched.** The live slotted record-page vocabulary — +`PageNodeSchema.kind === 'slotted'` with `slots?: PageSlotMap` (`packages/types/src/layout.ts`), +rendered by `usePageAssignment` / `PageBlockCanvas` / `PageBlockInspector` in +`@object-ui/app-shell` — shares the words "block" and "slot" with the retired family and +shares no declaration, type or file with it. Neither is the `type: 'component'` NAVIGATION +item kind (`{ type: 'component', componentRef }`, `NavigationItemSchema` in +`zod/app.zod.ts`), a different declaration in a different module. + +`@object-ui/components` carries one forced consequence: `renderers/feedback/empty.tsx` +annotated its `action` child as the retired `ComponentSchema` and now says `SchemaNode`, +the node type `SchemaRenderer` actually takes. + +`packages/types/src/blocks.ts` and `packages/types/src/zod/blocks.zod.ts` are kept as +ADR-0049 tombstones exporting nothing, and `block-family-retired-4895.test.ts` pins every +retired name out of them. `content/docs/blocks/block-schema.mdx` is deleted with the family, +and objectui#7023 — the narrower validator-only fix — dissolves into this retirement. diff --git a/AGENTS.md b/AGENTS.md index dfd766ac99..36c71417af 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,7 +48,7 @@ You don't just build components — you build a **Renderer** that interprets JSO | Package | Role | Responsibility | 🔴 Constraints | |---|---|---|---| -| `@object-ui/types` | The Protocol | Pure JSON interfaces (`ComponentSchema`, `ActionSchema`) | **Zero deps. No React.** | +| `@object-ui/types` | The Protocol | Pure JSON interfaces (`BaseSchema`, `ActionSchema`) | **Zero deps. No React.** | | `@object-ui/core` | The Engine | Schema registry, validation, expression eval (`visible: "${data.age > 18}"`) | No UI-lib deps. Logic only. | | `@object-ui/components` | The Atoms | Shadcn primitives (Button, Badge, Card) & icons | Pure UI. No business logic. | | `@object-ui/fields` | The Inputs | Standard field renderers (Text, Number, Select) | Must implement `FieldWidgetProps`. | diff --git a/content/docs/blocks/block-schema.mdx b/content/docs/blocks/block-schema.mdx deleted file mode 100644 index c6ee107104..0000000000 --- a/content/docs/blocks/block-schema.mdx +++ /dev/null @@ -1,553 +0,0 @@ ---- -title: "Block Schema (BlockSchema)" -description: "Type-level declarations for reusable component blocks: variables, slots, templates and marketplace metadata. No renderer consumes this family." ---- - -import { SchemaExample } from '@/app/components/ComponentDemo'; - -# Block Schema - -`BlockSchema` is a **definition**, not a rendered node. It declares the shape of a -reusable component block: the variables it accepts, the slots it advertises, the -template it would expand to, and the metadata a marketplace listing would carry. - - -Every schema on this page is a **declaration** in `@object-ui/types`, mirrored by a Zod -validator in `@object-ui/types/zod`. Those declarations and their validators are the -whole implementation: - -- **No component is registered** for `block`, `block-library`, `block-editor` or - `block-instance`, and none of them appears in `AnySchema`, the runtime node union in - `packages/types/src/index.ts`. They are discriminants of the interfaces below, **not - renderable node types**. Putting one into a `children` array gives you the renderer's - `OBJUI-001` "Unknown component type" panel. -- **Nothing expands a block: `BlockSchema.slots`, `BlockInstanceSchema.slotContent` and - `BlockSchema.template` have no reader.** The population that would hold one is named and - empty — there is no block expander, no block library UI and no block editor. These - interfaces and their Zod validators are the whole implementation. Bounded to the block - family's **own** keys deliberately: the bare words `slots` and `template` are live - vocabulary elsewhere in this repository and *are* read at runtime there — - `PageSchema.slots` drives slotted record pages (`buildDefaultPageSchema` in - `@object-ui/plugin-detail`), and `PageNodeSchema.template` selects a page layout from - `TEMPLATE_REGISTRY` in `@object-ui/components`. Neither goes near `BlockSchema`. - -So use these types to **describe** a block (author tooling, a marketplace payload, a -validation step) and do not expect one to render. Looking for slots that work today? -That is an unrelated vocabulary: record pages support `kind: "slotted"` with a `slots` -map, wired end to end. See [Slotted Pages](/docs/guide/slotted-pages). - - -## Overview - -`BlockSchema` declares: -- **Variables** - typed, defaultable parameters a block accepts -- **Slots** - named content-injection points a block advertises -- **Template** - the component tree a block would expand to -- **Marketplace metadata** - author, version, license, preview and rating fields -- **Version** - a `version` string carried on the block's metadata - -## Interactive Examples - -These demos are ordinary **registered** components (`card`, `flex`, `stack`, `text`, -`icon`, `button`, `badge`) hand-assembled to show what a block *would* look like once -expanded. None of them goes through `BlockSchema`: there is nothing that expands one. - -### Feature Card Block - - - -### Block Variations - - - - - -### Block Marketplace Card - - - -## Basic Usage - -```ts -import type { BlockSchema } from '@object-ui/types'; - -const heroBlock: BlockSchema = { - type: 'block', - - meta: { - name: 'hero-section', - label: 'Hero Section', - description: 'A customizable hero banner with image and CTA', - category: 'Marketing' - }, - - variables: [ - { - name: 'title', - type: 'string', - defaultValue: 'Welcome', - required: true - }, - { - name: 'showButton', - type: 'boolean', - defaultValue: true - } - ], - - slots: [ - { - name: 'content', - label: 'Content Area' - } - ], - - template: { - type: 'div', - className: 'hero-section', - children: [] - } -}; -``` - -## Properties - -### Block Metadata - -```ts -interface BlockMetadata { - name: string; // Block identifier - label?: string; // Display name - description?: string; // Block description - category?: string; // Category for organization - icon?: string; // Icon name - tags?: string[]; // Search tags - author?: string; // Creator - version?: string; // Version number - license?: string; // License type - repository?: string; // Source code URL - preview?: string; // Preview image URL - premium?: boolean; // Premium/paid block -} -``` - -### Block Variables - -Variables define configurable properties: - -```ts -interface BlockVariable { - name: string; // Variable name - label?: string; // Display label - type?: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'component'; - defaultValue?: any; // Default value - description?: string; // Help text - required?: boolean; // Is required? - validation?: any; // Validation rules - enum?: any[]; // Allowed values -} -``` - -### Block Slots - -Slots define content injection points: - -```ts -import type { SchemaNode } from '@object-ui/types'; - -interface BlockSlot { - name: string; // Slot name - label?: string; // Display label - description?: string; // Description - defaultContent?: SchemaNode | SchemaNode[]; - allowedTypes?: string[]; // Allowed component types - maxChildren?: number; // Maximum children - required?: boolean; // Is required? -} -``` - -#### Filling a slot: `slotContent` - -A block **declares** its slots in `slots[]`; a caller **fills** them through -`slotContent`, a map keyed by slot name whose values are `SchemaNode | SchemaNode[]`. -The key is declared on both `BlockSchema` and `BlockInstanceSchema` -(`packages/types/src/blocks.ts`): - -```plaintext -// The block declares a slot named 'content'... -slots: [ - { name: 'content', label: 'Content Area' } -], - -// ...and a caller fills it by name. -slotContent: { - content: [ - { type: 'text', value: 'Injected into the "content" slot' } - ] -} -``` - -**There is no `slot` node type.** Slot content is never addressed by placing a -`{ type: 'slot' }` placeholder inside `template`: nothing registers `slot`, so such a -node renders as `OBJUI-001`. `slotContent` is the declared path, and like the rest of -this family it is **declared but not yet consumed** - no renderer reads it today. - -## Complete Example - -```ts -import type { BlockSchema } from '@object-ui/types'; - -const cardBlock: BlockSchema = { - type: 'block', - - // Metadata - meta: { - name: 'feature-card', - label: 'Feature Card', - description: 'A card component for highlighting product features', - category: 'Marketing', - icon: 'box', - tags: ['card', 'feature', 'marketing', 'product'], - author: 'ObjectUI Team', - version: '1.0.0', - license: 'MIT', - repository: 'https://github.com/objectui/blocks', - preview: '/blocks/feature-card-preview.png' - }, - - // Variables (configurable props) - variables: [ - { - name: 'title', - label: 'Title', - type: 'string', - defaultValue: 'Feature Title', - description: 'The card title text', - required: true - }, - { - name: 'description', - label: 'Description', - type: 'string', - defaultValue: 'Feature description goes here', - description: 'The card description text' - }, - { - name: 'icon', - label: 'Icon', - type: 'string', - defaultValue: 'star', - description: 'Lucide icon name', - enum: ['star', 'heart', 'check', 'zap', 'shield'] - }, - { - name: 'variant', - label: 'Variant', - type: 'string', - defaultValue: 'default', - enum: ['default', 'outline', 'filled'] - }, - { - name: 'showButton', - label: 'Show Button', - type: 'boolean', - defaultValue: true, - description: 'Show the action button' - }, - { - name: 'buttonText', - label: 'Button Text', - type: 'string', - defaultValue: 'Learn More' - } - ], - - // Slots (content injection points) - slots: [ - { - name: 'header', - label: 'Header Content', - description: 'Optional header above the card', - allowedTypes: ['text', 'image', 'icon'], - maxChildren: 1 - }, - { - name: 'content', - label: 'Additional Content', - description: 'Extra content below description', - allowedTypes: ['text', 'list', 'div'] - } - ], - - // Template (component tree) - template: { - type: 'card', - className: 'feature-card ${variant}', - children: [ - { - type: 'div', - className: 'card-icon', - children: [ - { - type: 'icon', - icon: '${icon}', - size: 48, - className: 'text-primary' - } - ] - }, - { - type: 'div', - className: 'card-content', - children: [ - { - type: 'text', - variant: 'h3', - content: '${title}', - className: 'card-title' - }, - { - type: 'text', - content: '${description}', - className: 'card-description' - }, - { - type: 'button', - label: '${buttonText}', - variant: 'outline', - visible: '${showButton}', - className: 'card-button' - } - ] - } - ] - }, - - editable: true -}; -``` - -## Using Blocks - -### Block Instance - -`BlockInstanceSchema` declares the shape of a **reference to a block**: which block, what -variable values, what slot content. It is a definition, not a component - nothing -resolves `blockId` or expands the block it names. - -```ts -import type { BlockInstanceSchema } from '@object-ui/types'; - -const instance: BlockInstanceSchema = { - type: 'block-instance', - blockId: 'feature-card', - - // Override variable values - values: { - title: 'Fast Performance', - description: 'Lightning-fast response times', - icon: 'zap', - variant: 'filled', - showButton: true, - buttonText: 'See Benchmarks' - }, - - // Provide slot content - slotContent: { - content: [ - { - type: 'list', - items: [ - 'Sub-second page loads', - 'Optimized bundle size', - 'Edge caching' - ] - } - ] - } -}; -``` - -## Block Library - -`BlockLibrarySchema` declares the shape of a **block-library payload**: the query that -selected it and the listings it returned. It is a data shape, not a browser component - -nothing renders a library, and `onInstall` / `onPreview` name handlers that no dispatcher -looks up. - -```ts -import type { BlockLibrarySchema, BlockSchema } from '@object-ui/types'; - -// The block defined under "Basic Usage" above. -declare const heroBlock: BlockSchema; - -const library: BlockLibrarySchema = { - type: 'block-library', - apiEndpoint: '/api/blocks', - category: 'Marketing', - searchQuery: 'hero', - showPremium: true, - loading: false, - - blocks: [ - { - id: 'hero-section-1', - meta: { - name: 'hero-section', - label: 'Hero Section', - category: 'Marketing', - // ... other metadata - }, - schema: heroBlock, - installs: 1234, - rating: 4.8, - ratingCount: 156 - } - ], - - onInstall: 'handleInstallBlock', - onPreview: 'handlePreviewBlock' -}; -``` - -## Block Editor - -`BlockEditorSchema` declares the shape of a **block-editor configuration**: which block is -open and which panels a host would show. It is a configuration shape, not an editor -component - no block editor exists to consume it. - -```ts -import type { BlockEditorSchema, BlockSchema } from '@object-ui/types'; - -// The block defined under "Complete Example" above. -declare const cardBlock: BlockSchema; - -const editor: BlockEditorSchema = { - type: 'block-editor', - - block: cardBlock, - - showVariables: true, - showSlots: true, - showTemplate: true, - showPreview: true, - - onSave: 'handleSaveBlock', - onCancel: 'handleCancel' -}; -``` - -## Marketplace Example - -```ts -import type { BlockLibrarySchema } from '@object-ui/types'; - -const marketplace: BlockLibrarySchema = { - type: 'block-library', - apiEndpoint: 'https://blocks.objectui.com/api', - - blocks: [ - { - id: 'pricing-table-pro', - meta: { - name: 'pricing-table-pro', - label: 'Pricing Table Pro', - description: 'Professional pricing comparison table', - category: 'Marketing', - icon: 'dollar-sign', - tags: ['pricing', 'saas', 'comparison'], - author: 'ObjectUI Team', - version: '2.1.0', - license: 'MIT', - premium: false, - preview: 'https://blocks.objectui.com/previews/pricing-table.png' - }, - schema: { type: 'block', meta: { name: 'pricing-table-pro' } }, - installs: 5420, - rating: 4.9, - ratingCount: 234, - updatedAt: '2024-01-15T10:00:00Z' - }, - { - id: 'testimonial-slider', - meta: { - name: 'testimonial-slider', - label: 'Testimonial Slider', - description: 'Animated testimonial carousel', - category: 'Marketing', - icon: 'Quote', - tags: ['testimonial', 'slider', 'social-proof'], - author: 'Community', - version: '1.3.0', - premium: true - }, - schema: { type: 'block', meta: { name: 'testimonial-slider' } }, - installs: 2150, - rating: 4.7, - ratingCount: 89 - } - ] -}; -``` - -## Runtime Validation - -```ts -import { BlockSchema } from '@object-ui/types/zod'; - -// The block configuration to validate. -declare const myBlock: unknown; - -const result = BlockSchema.safeParse(myBlock); - -if (result.success) { - console.log('Valid block configuration'); -} else { - console.error('Validation errors:', result.error); -} -``` - -## Best Practices - -1. **Clear naming** - Use descriptive names for blocks, variables, and slots -2. **Provide defaults** - Set sensible default values for all variables -3. **Document thoroughly** - Add descriptions for variables and slots -4. **Use enums for choices** - Limit options with enum arrays -5. **Version carefully** - Use semantic versioning (1.0.0) -6. **Test variations** - Try different variable combinations -7. **Add previews** - Include preview images for marketplace listings -8. **Tag appropriately** - Use relevant tags for discoverability - -## Use Cases - -### Landing Page Components -- Hero sections -- Feature grids -- Pricing tables -- Testimonial carousels -- Call-to-action blocks - -### Dashboard Widgets -- Metric cards -- Chart containers -- Data tables -- Status indicators - -### Form Sections -- Multi-step wizards -- Address forms -- Payment forms -- Survey sections - -### Content Blocks -- Blog post templates -- Documentation sections -- FAQ accordions -- Timeline components - -## Related - -- [Slotted Pages](/docs/guide/slotted-pages) - the slot system that **is** wired end to end -- [Component Registry](/docs/guide/component-registry) - what registering a renderable type takes -- [Schema Rendering](/docs/guide/schema-rendering) - how registered node types are rendered -- [Plugins](/docs/guide/plugins) - Extending ObjectUI diff --git a/content/docs/blocks/meta.json b/content/docs/blocks/meta.json index 8958516393..6d9ecd7f28 100644 --- a/content/docs/blocks/meta.json +++ b/content/docs/blocks/meta.json @@ -1,7 +1,6 @@ { "title": "Blocks", "pages": [ - "block-schema", "authentication", "dashboard", "forms", diff --git a/content/docs/components/feedback/empty.mdx b/content/docs/components/feedback/empty.mdx index 8856170caf..a577a18981 100644 --- a/content/docs/components/feedback/empty.mdx +++ b/content/docs/components/feedback/empty.mdx @@ -25,7 +25,7 @@ interface EmptySchema { icon?: string; // Lucide icon name title?: string; // Main message description?: string; // Supporting text - action?: ComponentSchema; // Call to action button + action?: BaseSchema; // Call to action button className?: string; } ``` diff --git a/content/docs/components/overlay/alert-dialog.mdx b/content/docs/components/overlay/alert-dialog.mdx index c8c888230d..bf37523c82 100644 --- a/content/docs/components/overlay/alert-dialog.mdx +++ b/content/docs/components/overlay/alert-dialog.mdx @@ -30,7 +30,7 @@ interface AlertDialogSchema { trigger: SchemaNode; // Component that triggers the dialog // Actions - actions?: ComponentSchema[]; // Action buttons + actions?: BaseSchema[]; // Action buttons // Styling className?: string; diff --git a/content/docs/guide/architecture.md b/content/docs/guide/architecture.md index 5eaabc118b..d5cf1f4a60 100644 --- a/content/docs/guide/architecture.md +++ b/content/docs/guide/architecture.md @@ -45,7 +45,7 @@ ObjectUI is organized as a PNPM monorepo with clear separation of concerns: - **Role**: The Protocol - **Contains**: Pure TypeScript interfaces for JSON schemas - **Constraint**: ZERO dependencies, no React code -- **Example**: `ComponentSchema`, `ActionSchema`, `FieldSchema` +- **Example**: `BaseSchema`, `ActionSchema`, `FieldSchema` #### `@object-ui/core` - **Role**: The Engine @@ -334,7 +334,7 @@ import { cn } from '@/lib/utils' ObjectUI is built with **TypeScript** in strict mode: ```typescript -import type { ComponentSchema, ButtonSchema } from '@object-ui/types' +import type { BaseSchema, ButtonSchema } from '@object-ui/types' function handleClick() { // ... diff --git a/content/docs/guide/ci-cd-pipeline.md b/content/docs/guide/ci-cd-pipeline.md index 8657ab57ab..7afb1a6962 100644 --- a/content/docs/guide/ci-cd-pipeline.md +++ b/content/docs/guide/ci-cd-pipeline.md @@ -635,7 +635,8 @@ renderable children on another, so any global rule is a silent false green somew flat: every literal is a candidate component key, and a value outside the derived universe must be **declared** in the script's `DOC_TYPE_EXEMPTIONS` — keyed by (file, value), with a written reason naming the vocabulary it really belongs to. A whole-file exemption is deliberately not offered: -`blocks/block-schema.mdx` carries `type: 'block'` and `type: 'div'` in the same document. +`api/schema-reference.md` carries `"type": "action"` (an ActionSchema discriminant) and +`"type": "card"` (a registered component key) in the same document. Entries are re-derived per run, so one whose page stopped spelling that type fails as a stale exemption rather than quietly widening the hole. diff --git a/content/docs/guide/layout.md b/content/docs/guide/layout.md index 573d411c19..fb2b71c788 100644 --- a/content/docs/guide/layout.md +++ b/content/docs/guide/layout.md @@ -185,10 +185,10 @@ The `Page` component provides a consistent wrapper for individual pages with opt label: string, href?: string }>, - actions?: ComponentSchema[], // Action buttons + actions?: SchemaNode[], // Action buttons // Content - body: ComponentSchema, // Main page content + body: SchemaNode, // Main page content // Layout options maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full', @@ -270,7 +270,7 @@ Unresolvable tokens collapse to an empty string rather than leaking the raw temp icon?: string, // Lucide icon name, rendered in a chip left of the title actions?: Array, // action ids, or inline ActionDef objects showBack?: boolean, // back arrow; inferred from record context when omitted - children?: ComponentSchema[], // rendered into the right-aligned slot; `actions` takes precedence + children?: SchemaNode[], // rendered into the right-aligned slot; `actions` takes precedence className?: string, } ``` @@ -282,7 +282,7 @@ their own Close control), and `false` otherwise. Pass it explicitly to override. `actions` is handed to the `record:quick_actions` widget with `location: 'record_header'`. Its entries are **action ids** — resolved from the object's own `actions` metadata, which keeps the definitions in one place — or inline `ActionDef` -objects. They are **not** `ComponentSchema` nodes: a `{ "type": "button", … }` entry +objects. They are **not** `SchemaNode` nodes: a `{ "type": "button", … }` entry renders nothing here. > **Write `subtitle`. `description` is retired.** `@objectstack/spec/ui`'s diff --git a/content/docs/guide/schema-overview.md b/content/docs/guide/schema-overview.md index 622e3f2152..288d00fbc7 100644 --- a/content/docs/guide/schema-overview.md +++ b/content/docs/guide/schema-overview.md @@ -5,7 +5,7 @@ description: "Comprehensive overview of ObjectUI schemas for building enterprise # Schema Overview -ObjectUI provides powerful schemas that enable you to build sophisticated enterprise applications with advanced features like theming, reporting, reusable components, and complex workflows. This guide provides an overview of all available schemas and helps you get started quickly. +ObjectUI provides powerful schemas that enable you to build sophisticated enterprise applications with advanced features like theming, reporting, and complex workflows. This guide provides an overview of all available schemas and helps you get started quickly. ## Key Capabilities @@ -15,7 +15,6 @@ ObjectUI includes enterprise-grade capabilities to build production-ready applic - **Dynamic Theming** - Brand your applications with custom themes and light/dark modes - **Advanced Actions** - Build complex workflows with API calls, chaining, and conditions - **Enterprise Reporting** - Generate, schedule, and export comprehensive reports -- **Reusable Components** - Create and share component blocks across projects ## Core Schemas @@ -132,36 +131,6 @@ const report: ReportComponentSchema = { --- -### Reusable Components - -#### [Block Schema](/docs/blocks/block-schema) -Reusable component blocks with variables, slots, and marketplace support. - - - -```typescript -const block: BlockSchema = { - type: 'block', - meta: { name: 'hero-section', category: 'Marketing' }, - variables: [ - { name: 'title', type: 'string', defaultValue: 'Welcome' } - ], - slots: [ - { name: 'content', label: 'Content Area' } - ], - template: { type: 'div', children: [...] } -}; -``` - -**Features:** -- Typed variables (props) -- Content slots -- Block templates -- Marketplace support -- Version control - ---- - ## Quick Comparison | Schema | Purpose | Best For | @@ -169,7 +138,6 @@ const block: BlockSchema = { | **AppComponentSchema** | Application structure | Multi-page apps, dashboards | | **Enhanced Actions** | Complex workflows | API integration, multi-step processes | | **ReportComponentSchema** | Data reporting | Analytics, business intelligence | -| **BlockSchema** | Reusable components | Marketing pages, component libraries | ## View Components @@ -209,8 +177,7 @@ Import the type definitions you need: import type { AppComponentSchema, ActionSchema, - ReportComponentSchema, - BlockSchema + ReportComponentSchema } from '@object-ui/types'; ``` @@ -222,8 +189,7 @@ For runtime validation, use the included Zod schemas: import { AppComponentSchema, ActionSchema, - ReportComponentSchema, - BlockSchema + ReportComponentSchema } from '@object-ui/types/zod'; const myConfig = { type: 'app', title: 'My Application', layout: 'sidebar' }; @@ -300,7 +266,6 @@ ObjectUI includes these top-level schemas: - **`AppComponentSchema`** - Define your entire application structure - **`ReportComponentSchema`** - Create data reports with aggregation -- **`BlockSchema`** - Build reusable component blocks ### Enhanced ActionSchema diff --git a/content/docs/guide/troubleshooting.md b/content/docs/guide/troubleshooting.md index ff84bc2790..fe98cec31f 100644 --- a/content/docs/guide/troubleshooting.md +++ b/content/docs/guide/troubleshooting.md @@ -144,7 +144,7 @@ The validation engine lives in `packages/core/src/validation/` and uses Zod sche ## 6. TypeScript Errors with Schema Types -**Symptom:** TypeScript cannot find types like `ComponentSchema`, `ActionSchema`, or `FieldWidgetProps`. +**Symptom:** TypeScript cannot find types like `BaseSchema`, `ActionSchema`, or `FieldWidgetProps`. **Cause:** `@object-ui/types` is not installed, or the version is mismatched. @@ -158,7 +158,7 @@ Then import types from the correct entry points: ```typescript // Base types -import type { ComponentSchema } from '@object-ui/types'; +import type { BaseSchema } from '@object-ui/types'; // Category-specific types import type { FormSchema } from '@object-ui/types/form'; @@ -167,7 +167,7 @@ import type { DataDisplaySchema } from '@object-ui/types/data-display'; // Zod validation schemas — same PascalCase names as the types, so alias one of // the two when a module imports both. -import { ComponentSchema as ComponentSchemaValidator } from '@object-ui/types/zod'; +import { BaseSchema as BaseSchemaValidator } from '@object-ui/types/zod'; ``` The `@object-ui/types` package exports multiple entry points (`base`, `layout`, `form`, `data-display`, `feedback`, `overlay`, `navigation`, `complex`, `data`, `zod`). Check `packages/types/package.json` for the full list. diff --git a/packages/components/src/renderers/feedback/empty.tsx b/packages/components/src/renderers/feedback/empty.tsx index d1e05600a9..cb5ff4532a 100644 --- a/packages/components/src/renderers/feedback/empty.tsx +++ b/packages/components/src/renderers/feedback/empty.tsx @@ -8,7 +8,7 @@ import { ComponentRegistry } from '@object-ui/core'; import type { CSSProperties } from 'react'; -import type { EmptySchema, ComponentSchema } from '@object-ui/types'; +import type { EmptySchema, BaseSchema } from '@object-ui/types'; import { SchemaRenderer } from '@object-ui/react'; import { DataEmptyState } from '../../custom/view-states'; @@ -27,7 +27,13 @@ ComponentRegistry.register('empty', ...emptyProps } = props as Record; - const actionSchema = (schema as any).action as ComponentSchema | undefined; + // `BaseSchema`, not the retired block-family `ComponentSchema` (objectui#4895): + // `action` is any renderable node handed to `SchemaRenderer`, never the + // `type: 'component'` kind the old annotation named. NOT `SchemaNode` — + // objectui#7082 recorded that this cast needs the OBJECT half, since the + // guard below rejects the `string | number | boolean` members `SchemaNode` + // also admits. + const actionSchema = (schema as any).action as BaseSchema | undefined; const actionNode = actionSchema && typeof actionSchema === 'object' ? : undefined; diff --git a/packages/data-objectstack/README.md b/packages/data-objectstack/README.md index dffebf8293..dff473e0a3 100644 --- a/packages/data-objectstack/README.md +++ b/packages/data-objectstack/README.md @@ -23,9 +23,9 @@ npm install @object-ui/data-objectstack ```typescript import { createObjectStackAdapter } from '@object-ui/data-objectstack'; import { SchemaRenderer } from '@object-ui/react'; -import type { ComponentSchema } from '@object-ui/types'; +import type { BaseSchema } from '@object-ui/types'; -declare const mySchema: ComponentSchema; +declare const mySchema: BaseSchema; // 1. Create the adapter const dataSource = createObjectStackAdapter({ diff --git a/packages/types/src/__tests__/block-family-retired-4895.test.ts b/packages/types/src/__tests__/block-family-retired-4895.test.ts new file mode 100644 index 0000000000..c80dcf97bd --- /dev/null +++ b/packages/types/src/__tests__/block-family-retired-4895.test.ts @@ -0,0 +1,96 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * The block schema family stays retired (objectui#4895, ADR-0049). + * + * Maintainer ruling of 2026-09-02 (director seat, summon #8, verbatim 「同意」), + * option C1: retire the family in one change, no transition window. The two + * modules that held it are kept as tombstones exporting nothing, and this file + * is what makes those tombstones executable — it dynamic-imports both and pins + * every retired name OUT of them, the same shape + * `spec-subschema-parity.test.ts` uses for the retired theme validators. + * + * Why a pin and not just a deletion. The names were on a PUBLISHED surface + * (`@object-ui/types` at 17.x, subpaths `.` and `./zod`), so re-adding one is a + * published-contract decision, not a convenience. Without this file the only + * thing standing between a re-added `BlockSchema` and the published surface is + * someone remembering the ruling. + * + * The refusal half — that `AnyComponentSchema` no longer green-lights any of + * the five discriminants — is pinned in `phase2-schemas.test.ts`, next to the + * theme refusals. This file pins the SYMBOLS; that one pins the BEHAVIOUR. + */ +import { describe, it, expect } from 'vitest'; + +/** Names that lived in `../blocks.ts`. */ +const RETIRED_TYPES = [ + 'BlockSchema', + 'BlockSlot', + 'BlockLibrarySchema', + 'BlockEditorSchema', + 'BlockInstanceSchema', + 'BlockVariable', + 'BlockMetadata', + 'BlockLibraryItem', + 'ComponentSchema', +] as const; + +/** Names that lived in `../zod/blocks.zod.ts` — the ten parity-ledger entries. */ +const RETIRED_VALIDATORS = [ + 'BlockVariableSchema', + 'BlockSlotSchema', + 'BlockMetadataSchema', + 'BlockSchema', + 'BlockLibraryItemSchema', + 'BlockLibrarySchema', + 'BlockEditorSchema', + 'BlockInstanceSchema', + 'ComponentSchema', + 'BlockComponentSchema', +] as const; + +describe('block schema family stays retired (objectui#4895, ADR-0049)', () => { + it('the zod tombstone re-exports none of the ten retired validators', async () => { + const blocksZod = await import('../zod/blocks.zod.js'); + for (const name of RETIRED_VALIDATORS) { + expect( + name in blocksZod, + `'${name}' was retired with the block family (objectui#4895, maintainer ruling ` + + `2026-09-02, option C1) — do not reintroduce it without a new ruling`, + ).toBe(false); + } + // Non-vacuity control: the module must still BE a module. A failed import + // would make every assertion above pass for the wrong reason. + expect(typeof blocksZod).toBe('object'); + }); + + it('the zod barrel no longer republishes them on the ./zod subpath', async () => { + const zodBarrel = await import('../zod/index.zod.js'); + for (const name of RETIRED_VALIDATORS) { + expect(name in zodBarrel, `'${name}' is back on the published ./zod surface`).toBe(false); + } + // Positive control on the same barrel: a live validator IS still exported, + // so the refusals above measure the retirement, not a broken import. + expect('TableSchema' in zodBarrel).toBe(true); + expect('AnyComponentSchema' in zodBarrel).toBe(true); + }); + + it('the type tombstone is a module with nothing left to export', async () => { + const blocks = await import('../blocks.js'); + // Types erase, so the runtime namespace of `../blocks.ts` is the only thing + // observable here — and after the retirement it must be empty. The TYPE + // half of this pin is the compile itself: `../index.ts` no longer re-exports + // any of RETIRED_TYPES, and `type-check` would fail if it did while the + // declarations are gone. + expect(Object.keys(blocks)).toEqual([]); + for (const name of RETIRED_TYPES) { + expect(name in blocks, `'${name}' is back in the blocks tombstone`).toBe(false); + } + }); +}); diff --git a/packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts b/packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts index fffadbcdd2..f6c31b0b48 100644 --- a/packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts +++ b/packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts @@ -44,13 +44,19 @@ * * ## What the pages taught before, measured on `2c3cd1b` * - * Nine rows across six pages spelled a node slot `ComponentSchema`. That is a - * real shipped export (`blocks.ts`) and it is NOT a node slot -- it is the + * Nine rows across six pages spelled a node slot `ComponentSchema`. That was a + * real shipped export (`blocks.ts`) and it was NOT a node slot -- it was the * concrete `type: 'component'` block. A reader who looked the name up found a * narrow, unrelated type. Seven of the nine are corrected; the other two are * NOT type-name defects at all and are recorded as divergences below, because * no honest docs-only edit resolves them. * + * ⚠️ Since objectui#4895 that export is GONE -- the whole block schema family + * was retired under ADR-0049 (maintainer ruling 2026-09-02, option C1). The + * correction stands unchanged; only its two type-level premise assertions left, + * because the type they compared against no longer exists. See the note where + * they stood. + * * ## Two rows this file records instead of asserting green * * `AlertDialogSchema.actions` and `EmptySchema.action` are documented but @@ -79,7 +85,6 @@ import type { HoverCardSchema, SheetSchema, } from '../overlay'; -import type { ComponentSchema } from '../blocks'; const HERE = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = join(HERE, '..', '..', '..', '..'); @@ -110,11 +115,20 @@ export type _DropdownAdmitsArray = Expect, false>>; export type _SheetAdmitsArray = Expect, false>>; -// `ComponentSchema` is a real export and NOT a node slot -- the premise the -// whole correction rests on. A `type: 'component'` block is one SchemaNode -// among many, never the slot type. -export type _ComponentSchemaIsANode = Expect>; -export type _NodeSlotIsNotAComponent = Expect>; +// The premise the whole correction rests on used to be pinned here as two +// type-level assertions: `ComponentSchema` is a real export, and it is NOT a +// node slot -- a `type: 'component'` block is one SchemaNode among many, never +// the slot type. Both are gone because their SUBJECT is: `ComponentSchema` was +// retired with the whole block schema family in objectui#4895 (ADR-0049 +// enforce-or-remove, maintainer ruling 2026-09-02, option C1), so the +// comparison is no longer expressible -- the same shape as the theme +// retirement's note in `phase2-schemas.test.ts`. +// +// The correction this file pins is UNAFFECTED, and in fact strengthened: the +// nine rows that spelled a node slot `ComponentSchema` were wrong because the +// name meant something narrow and unrelated, and now the name means nothing at +// all. `block-family-retired-4895.test.ts` pins it out of the published +// surface; the seven corrected rows below still assert against `SchemaNode`. /* -- Reading a member row, on both sides -- */ @@ -222,11 +236,18 @@ describe('six overlay/feedback pages name node slots at the declared type (objec expect(docRow(owner, key)?.typeText).not.toContain('ComponentSchema'); }); - it('`ComponentSchema` is a distinct shipped export, which is why the old rows were wrong', () => { + it('`ComponentSchema` is no longer a shipped export at all, which is why the old rows were wrong', () => { + // The original assertion here read `blocks.ts` and proved `ComponentSchema` + // was a DISTINCT export -- the concrete `type: 'component'` block, not a + // slot type -- which is what made the nine old rows wrong. objectui#4895 + // retired the whole block schema family, so the same premise is now proved + // the other way: the name is gone, and `blocks.ts` is a tombstone. const blocks = read('packages/types/src/blocks.ts'); - expect(blocks).toContain('export interface ComponentSchema extends BaseSchema {'); - // It is the concrete `type: 'component'` block, not a slot type. - expect(members(interfaceBody(blocks, 'export interface ComponentSchema extends BaseSchema {', 'blocks.ts')).get('type')?.typeText).toBe("'component'"); + expect(blocks).not.toContain('export interface ComponentSchema'); + expect(blocks).toContain('RETIRED (objectui#4895'); + // Control: the file is still there and still readable, so the absence above + // is a reading about its CONTENT and not about a failed read. + expect(blocks).toContain('@module blocks'); // And `SchemaNode` is the slot type these keys actually carry. expect(read('packages/types/src/base.ts')).toContain( 'export type SchemaNode = BaseSchema | string | number | boolean | null | undefined;', @@ -280,9 +301,12 @@ describe('rows a docs-only edit cannot honestly resolve, recorded rather than re // Undeclared-but-consumed, the objectui#6150 class. It is also why the row // was not renamed to `SchemaNode`: the renderer requires an OBJECT, so // `SchemaNode` -- which admits `string | number | boolean` -- would have - // been a new false claim rather than a correction. + // been a new false claim rather than a correction. The cast named + // `ComponentSchema` until objectui#4895 retired it; it now names + // `BaseSchema`, which IS that object half, so the reasoning above is + // preserved rather than worked around. const renderer = read('packages/components/src/renderers/feedback/empty.tsx'); - expect(renderer).toContain("(schema as any).action as ComponentSchema | undefined"); + expect(renderer).toContain("(schema as any).action as BaseSchema | undefined"); expect(renderer).toContain("typeof actionSchema === 'object'"); }); diff --git a/packages/types/src/__tests__/phase2-schemas.test.ts b/packages/types/src/__tests__/phase2-schemas.test.ts index a0ad06e696..53e78760e2 100644 --- a/packages/types/src/__tests__/phase2-schemas.test.ts +++ b/packages/types/src/__tests__/phase2-schemas.test.ts @@ -1,14 +1,13 @@ /** * Tests for Phase 2 Schema Definitions - * Testing AppSchema, ReportComponentSchema, BlockSchema, and Enhanced ActionSchema + * Testing AppSchema, ReportComponentSchema and Enhanced ActionSchema, plus the + * retirement pins for the theme and block component kinds. */ import { describe, it, expect } from 'vitest'; import { AppComponentSchema, ReportComponentSchema, ReportBuilderSchema, - BlockSchema, - BlockLibrarySchema, ActionSchema, DetailViewSchema, ViewSwitcherSchema, @@ -270,82 +269,68 @@ describe('Phase 2: ReportComponentSchema Zod Validation', () => { }); }); -describe('Phase 2: BlockSchema Zod Validation', () => { - it('should validate a complete BlockSchema', () => { - const block = { +describe('Phase 2: Block component kinds — retirement pins', () => { + // The WHOLE block schema family is retired: `BlockSchema` (`type: 'block'`), + // `BlockLibrarySchema` (`'block-library'`), `BlockEditorSchema` + // (`'block-editor'`), `BlockInstanceSchema` (`'block-instance'`) and the + // `ComponentSchema` (`'component'`) arm that rode the same union — objectui#4895, + // ADR-0049 enforce-or-remove, under the maintainer ruling of 2026-09-02 + // (option C1, no transition window). Zero `ComponentRegistry.register(...)` + // sites claimed any of the five; the positive control `'table'` resolves to + // two, which is what makes that zero a reading rather than a broken grep. + // + // This is the pin that matters most on this retirement. The TypeScript half + // was merely published; `blocks.zod.ts` shipped as RUNTIME values under + // `@object-ui/types/zod`, and `AnyComponentSchema` accepted every one of the + // five discriminants — so an author who copied the documented + // `{ type: 'block-library' }` was told GREEN by the shipped validator and + // then got the registry's "Unknown component type" panel (OBJUI-001). + // Validated-then-broken is worse than never-validated. The shapes below are + // the exact fixtures the old acceptance tests proved VALID; they are now + // proven REFUSED, so a re-added kind fails here rather than reappearing + // silently on the published surface. + // + // ⚠️ NOT this family, and deliberately not pinned out: the live slotted + // record-page vocabulary (`PageNodeSchema.kind === 'slotted'` with + // `slots?: PageSlotMap` in `../layout.ts`, rendered by `PageBlockCanvas` / + // `PageBlockInspector` in `@object-ui/app-shell`), and the `type: 'component'` + // NAVIGATION item kind declared by `NavigationItemSchema` in + // `../zod/app.zod.ts` (objectui#2918) — a different declaration in a + // different module, pinned live by `navigation-model.test.ts`. + it('refuses the retired block component kinds, while a live kind still parses', () => { + // The fixture the old `should validate a complete BlockSchema` case proved VALID. + const retiredBlock = { type: 'block', meta: { name: 'hero-section', label: 'Hero Section', - description: 'A customizable hero section with image and text', category: 'Marketing', - icon: 'Layout', - tags: ['hero', 'landing', 'marketing'], - author: 'ObjectUI Team', - version: '1.0.0', - }, - variables: [ - { - name: 'title', - label: 'Title', - type: 'string', - defaultValue: 'Welcome', - required: true, - }, - { - name: 'subtitle', - label: 'Subtitle', - type: 'string', - defaultValue: 'Get started today', - }, - { - name: 'showButton', - label: 'Show Button', - type: 'boolean', - defaultValue: true, - }, - ], - slots: [ - { - name: 'content', - label: 'Content', - description: 'Main content area', - required: false, - }, - ], - template: { - type: 'div', - className: 'hero', - children: [ - { - type: 'text', - value: '${title}', - }, - ], }, + variables: [{ name: 'title', label: 'Title', type: 'string', defaultValue: 'Welcome', required: true }], + slots: [{ name: 'content', label: 'Content', required: false }], + template: { type: 'div', className: 'hero', children: [{ type: 'text', value: '${title}' }] }, editable: true, }; + expect(AnyComponentSchema.safeParse(retiredBlock).success).toBe(false); - const result = BlockSchema.safeParse(block); - expect(result.success).toBe(true); - if (result.success) { - expect(result.data.meta?.name).toBe('hero-section'); - expect(result.data.variables).toHaveLength(3); - expect(result.data.slots).toHaveLength(1); - } - }); - - it('should validate BlockLibrarySchema', () => { - const library = { + // The fixture the old `should validate BlockLibrarySchema` case proved VALID — + // and the exact shape the ruling names as the measured green light. + const retiredLibrary = { type: 'block-library', category: 'Marketing', searchQuery: 'hero', showPremium: true, loading: false, }; + expect(AnyComponentSchema.safeParse(retiredLibrary).success).toBe(false); - const result = BlockLibrarySchema.safeParse(library); - expect(result.success).toBe(true); + expect(AnyComponentSchema.safeParse({ type: 'block-editor', showVariables: true }).success).toBe(false); + expect(AnyComponentSchema.safeParse({ type: 'block-instance', blockId: 'hero-section' }).success).toBe(false); + expect(AnyComponentSchema.safeParse({ type: 'component', componentName: 'Hero' }).success).toBe(false); + + // Positive control on the same pipeline: a still-declared kind parses GREEN, + // so the five refusals measure the retirement, not a broken union. + expect(AnyComponentSchema.safeParse({ type: 'action', label: 'Control Action' }).success).toBe(true); }); }); @@ -739,7 +724,8 @@ describe('Phase 2: AnyComponentSchema Union Type', () => { // `{ type: 'theme' }` removed with the kind itself (objectui#5489); its // refusal is pinned in the theme describe block above. { type: 'report', title: 'Test Report' }, - { type: 'block', meta: { name: 'test-block' } }, + // `{ type: 'block' }` removed with the whole family (objectui#4895); the + // refusals are pinned in the block describe block above. { type: 'action', label: 'Test Action' }, { type: 'detail-view', title: 'Test Detail' }, { type: 'view-switcher', views: [] }, diff --git a/packages/types/src/__tests__/zod-mirror-parity.test.ts b/packages/types/src/__tests__/zod-mirror-parity.test.ts index 3df8cb85d6..37561e7130 100644 --- a/packages/types/src/__tests__/zod-mirror-parity.test.ts +++ b/packages/types/src/__tests__/zod-mirror-parity.test.ts @@ -131,7 +131,6 @@ import type { z } from 'zod'; import { AppActionSchema, AppComponentSchema, NavigationAreaSchema } from '../zod/app.zod.js'; import { BaseSchema, ComponentConfigSchema, ComponentInputSchema, ComponentMetaSchema, KeyedI18nLabelSchema } from '../zod/base.zod.js'; -import { BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema } from '../zod/blocks.zod.js'; import { CalendarEventSchema, CalendarViewSchema, CarouselItemSchema, CarouselSchema, ChatbotSchema, ChatMessageSchema, ChatMessageSourceSchema, ChatToolInvocationSchema, DashboardComponentSchema, DashboardConfigSchema, DashboardWidgetConfigSchema, DashboardWidgetLayoutSchema, DashboardWidgetSchema, FilterBuilderSchema, FilterFieldSchema, KanbanCardSchema, KanbanColumnSchema, KanbanSchema } from '../zod/complex.zod.js'; import { ActionCallbackSchema, CRUDDialogSchema, DetailSchema } from '../zod/crud.zod.js'; import { AlertSchema, AvatarSchema, BadgeSchema, BarChartSchema, ChartDataSeriesSchema, ChartSchema, DataTableSchema, HtmlSchema, KbdSchema, ListItemSchema, ListSchema, MarkdownSchema, StaticTableColumnSchema, StatisticSchema, TableColumnSchema, TableSchema, TimelineEventSchema, TimelineSchema, TreeViewSchema } from '../zod/data-display.zod.js'; @@ -147,7 +146,6 @@ import { DetailViewFieldSchema, DetailViewSchema, DetailViewSectionSchema, Detai import type { AppAction as Ts_AppAction, AppComponentSchema as Ts_AppComponentSchema, NavigationArea as Ts_NavigationArea } from '../app'; import type { BaseSchema as Ts_BaseSchema, ComponentConfig as Ts_ComponentConfig, ComponentInput as Ts_ComponentInput, ComponentMeta as Ts_ComponentMeta, KeyedI18nLabel as Ts_KeyedI18nLabel } from '../base'; -import type { BlockEditorSchema as Ts_BlockEditorSchema, BlockInstanceSchema as Ts_BlockInstanceSchema, BlockLibraryItem as Ts_BlockLibraryItem, BlockLibrarySchema as Ts_BlockLibrarySchema, BlockMetadata as Ts_BlockMetadata, BlockSchema as Ts_BlockSchema, BlockSlot as Ts_BlockSlot, BlockVariable as Ts_BlockVariable, ComponentSchema as Ts_ComponentSchema } from '../blocks'; import type { CalendarEvent as Ts_CalendarEvent, CalendarViewSchema as Ts_CalendarViewSchema, CarouselItem as Ts_CarouselItem, CarouselSchema as Ts_CarouselSchema, ChatbotSchema as Ts_ChatbotSchema, ChatMessage as Ts_ChatMessage, ChatMessageSource as Ts_ChatMessageSource, ChatToolInvocation as Ts_ChatToolInvocation, DashboardComponentSchema as Ts_DashboardComponentSchema, DashboardWidgetLayout as Ts_DashboardWidgetLayout, DashboardWidgetSchema as Ts_DashboardWidgetSchema, FilterBuilderSchema as Ts_FilterBuilderSchema, FilterField as Ts_FilterField, KanbanCard as Ts_KanbanCard, KanbanColumn as Ts_KanbanColumn, KanbanSchema as Ts_KanbanSchema } from '../complex'; import type { DashboardConfig as Ts_DashboardConfig, DashboardWidgetConfig as Ts_DashboardWidgetConfig } from '../designer'; import type { ActionCallback as Ts_ActionCallback, CRUDDialogSchema as Ts_CRUDDialogSchema, DetailSchema as Ts_DetailSchema } from '../crud'; @@ -354,15 +352,6 @@ const MIRRORS = { 'base.zod.ts#ComponentInputSchema': ComponentInputSchema, 'base.zod.ts#ComponentMetaSchema': ComponentMetaSchema, 'base.zod.ts#KeyedI18nLabelSchema': KeyedI18nLabelSchema, - 'blocks.zod.ts#BlockEditorSchema': BlockEditorSchema, - 'blocks.zod.ts#BlockInstanceSchema': BlockInstanceSchema, - 'blocks.zod.ts#BlockLibraryItemSchema': BlockLibraryItemSchema, - 'blocks.zod.ts#BlockLibrarySchema': BlockLibrarySchema, - 'blocks.zod.ts#BlockMetadataSchema': BlockMetadataSchema, - 'blocks.zod.ts#BlockSchema': BlockSchema, - 'blocks.zod.ts#BlockSlotSchema': BlockSlotSchema, - 'blocks.zod.ts#BlockVariableSchema': BlockVariableSchema, - 'blocks.zod.ts#ComponentSchema': ComponentSchema, 'complex.zod.ts#CalendarEventSchema': CalendarEventSchema, 'complex.zod.ts#CalendarViewSchema': CalendarViewSchema, 'complex.zod.ts#CarouselItemSchema': CarouselItemSchema, @@ -521,15 +510,6 @@ interface Declared { 'base.zod.ts#ComponentInputSchema': Ts_ComponentInput; 'base.zod.ts#ComponentMetaSchema': Ts_ComponentMeta; 'base.zod.ts#KeyedI18nLabelSchema': Ts_KeyedI18nLabel; - 'blocks.zod.ts#BlockEditorSchema': Ts_BlockEditorSchema; - 'blocks.zod.ts#BlockInstanceSchema': Ts_BlockInstanceSchema; - 'blocks.zod.ts#BlockLibraryItemSchema': Ts_BlockLibraryItem; - 'blocks.zod.ts#BlockLibrarySchema': Ts_BlockLibrarySchema; - 'blocks.zod.ts#BlockMetadataSchema': Ts_BlockMetadata; - 'blocks.zod.ts#BlockSchema': Ts_BlockSchema; - 'blocks.zod.ts#BlockSlotSchema': Ts_BlockSlot; - 'blocks.zod.ts#BlockVariableSchema': Ts_BlockVariable; - 'blocks.zod.ts#ComponentSchema': Ts_ComponentSchema; 'complex.zod.ts#CalendarEventSchema': Ts_CalendarEvent; 'complex.zod.ts#CalendarViewSchema': Ts_CalendarViewSchema; 'complex.zod.ts#CarouselItemSchema': Ts_CarouselItem; @@ -1483,8 +1463,6 @@ const EXCLUSIONS: Readonly> = { "an index signature, not a declared key set — there are no keys to compare", 'base.zod.ts#EventHandlersSchema': "an index signature, not a declared key set — there are no keys to compare", - 'blocks.zod.ts#BlockComponentSchema': - "a union OVER the mirrors, not an object of its own — its members are checked individually above", 'complex.zod.ts#CalendarViewModeSchema': "a bare vocabulary with no `.shape`; it is checked where a mirrored KEY declares it", 'complex.zod.ts#DashboardWidgetTypeSchema': diff --git a/packages/types/src/blocks.ts b/packages/types/src/blocks.ts index 5f237873ac..d11b539d42 100644 --- a/packages/types/src/blocks.ts +++ b/packages/types/src/blocks.ts @@ -7,399 +7,62 @@ */ /** - * @object-ui/types - Block Schema - * - * Defines reusable component blocks for composition and templating. - */ - -import type { BaseSchema, SchemaNode } from './base.js'; - -/** - * Block Variable Definition - */ -export interface BlockVariable { - /** - * Variable name - */ - name: string; - - /** - * Display label - */ - label?: string; - - /** - * Variable type - */ - type?: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'component'; - - /** - * Default value - */ - defaultValue?: any; - - /** - * Description/help text - */ - description?: string; - - /** - * Whether this variable is required - */ - required?: boolean; - - /** - * Validation rules - */ - validation?: any; - - /** - * Enum options (for string/number types) - */ - enum?: any[]; -} - -/** - * Block Slot Definition - */ -export interface BlockSlot { - /** - * Slot name - */ - name: string; - - /** - * Display label - */ - label?: string; - - /** - * Description - */ - description?: string; - - /** - * Default content - */ - defaultContent?: SchemaNode | SchemaNode[]; - - /** - * Allowed component types - */ - allowedTypes?: string[]; - - /** - * Maximum number of children - */ - maxChildren?: number; - - /** - * Whether this slot is required - */ - required?: boolean; -} - -/** - * Block Metadata - */ -export interface BlockMetadata { - /** - * Block name/identifier - */ - name: string; - - /** - * Display label - */ - label?: string; - - /** - * Block description - */ - description?: string; - - /** - * Block category - */ - category?: string; - - /** - * Block icon - */ - icon?: string; - - /** - * Block tags for search - */ - tags?: string[]; - - /** - * Author/creator - */ - author?: string; - - /** - * Version - */ - version?: string; - - /** - * License - */ - license?: string; - - /** - * Repository URL - */ - repository?: string; - - /** - * Preview image URL - */ - preview?: string; - - /** - * Is this a premium/paid block - */ - premium?: boolean; -} - -/** - * Block Schema - Reusable component block - */ -export interface BlockSchema extends BaseSchema { - type: 'block'; - - /** - * Block metadata - */ - meta?: BlockMetadata; - - /** - * Block variables/props - */ - variables?: BlockVariable[]; - - /** - * Block slots for content injection - */ - slots?: BlockSlot[]; - - /** - * Block template (component tree) - */ - template?: SchemaNode | SchemaNode[]; - - /** - * Variable values (when using a block) - */ - values?: Record; - - /** - * Slot content (when using a block) - */ - slotContent?: Record; - - /** - * Block reference (for reusing saved blocks) - */ - blockRef?: string; - - /** - * Enable edit mode - */ - editable?: boolean; -} - -/** - * Block Library Item - */ -export interface BlockLibraryItem { - /** - * Unique identifier - */ - id: string; - - /** - * Block metadata - */ - meta: BlockMetadata; - - /** - * Block schema - */ - schema: BlockSchema; - - /** - * Installation count - */ - installs?: number; - - /** - * Rating (1-5) - */ - rating?: number; - - /** - * Number of ratings - */ - ratingCount?: number; - - /** - * Last updated timestamp - */ - updatedAt?: string; - - /** - * Created timestamp - */ - createdAt?: string; -} - -/** - * Block Library Schema - Browse and install blocks - */ -export interface BlockLibrarySchema extends BaseSchema { - type: 'block-library'; - - /** - * Library API endpoint - */ - apiEndpoint?: string; - - /** - * Filter by category - */ - category?: string; - - /** - * Search query - */ - searchQuery?: string; - - /** - * Filter by tags - */ - tags?: string[]; - - /** - * Show premium blocks - */ - showPremium?: boolean; - - /** - * Blocks to display - */ - blocks?: BlockLibraryItem[]; - - /** - * Loading state - */ - loading?: boolean; - - /** - * Install callback - */ - onInstall?: string; - - /** - * Preview callback - */ - onPreview?: string; -} - -/** - * Block Editor Schema - Edit/create blocks - */ -export interface BlockEditorSchema extends BaseSchema { - type: 'block-editor'; - - /** - * Block being edited - */ - block?: BlockSchema; - - /** - * Show variable editor - */ - showVariables?: boolean; - - /** - * Show slot editor - */ - showSlots?: boolean; - - /** - * Show template editor - */ - showTemplate?: boolean; - - /** - * Show preview - */ - showPreview?: boolean; - - /** - * Save callback - */ - onSave?: string; - - /** - * Cancel callback - */ - onCancel?: string; -} - -/** - * Block Instance Schema - Use a saved block - */ -export interface BlockInstanceSchema extends BaseSchema { - type: 'block-instance'; - - /** - * Block reference ID - */ - blockId: string; - - /** - * Block name (for local blocks) - */ - blockName?: string; - - /** - * Variable values - */ - values?: Record; - - /** - * Slot content - */ - slotContent?: Record; - - /** - * Override styles - */ - overrideStyles?: boolean; -} - -/** - * Component Schema Extension - For base component metadata + * @object-ui/types - Block Schema (the whole family, RETIRED) + * + * This module exports NOTHING any more. It is kept as the ADR-0049 tombstone + * for the block schema family, and `__tests__/block-family-retired-4895.test.ts` + * dynamic-imports it to pin the retired names OUT of it. + * + * RETIRED (objectui#4895, ADR-0049 enforce-or-remove) under the maintainer + * ruling recorded on that card on 2026-09-02 (director seat, summon #8, + * maintainer verbatim 「同意」), option C1 — retire the family in one change, + * no transition window. The names that lived here: + * + * - `BlockSchema` (`type: 'block'`) + * - `BlockSlot` (`BlockSchema.slots[]` element) + * - `BlockLibrarySchema` (`type: 'block-library'`) + * - `BlockEditorSchema` (`type: 'block-editor'`) + * - `BlockInstanceSchema` (`type: 'block-instance'`) + * - `BlockVariable` / `BlockMetadata` / `BlockLibraryItem` — support types + * with no declaration site and no reader outside the five above + * - `ComponentSchema` (`type: 'component'`) — the fifth arm of the zod + * `BlockComponentSchema` union, retired with it; see the note below + * + * Why. Declared-but-unenforced, the ADR-0049 shape to retire: zero + * `ComponentRegistry.register()` sites claimed any of the four discriminants + * (positive control `'table'` resolves to two), zero renderers, and zero + * readers anywhere outside this package. The zod mirrors, however, were NOT + * inert — they shipped as runtime values under the `./zod` subpath and their + * discriminants were accepted by the published `AnyComponentSchema`, so an + * author who copied the documented `{ type: 'block-library' }` got a GREEN + * validator and then the registry's "Unknown component type" panel + * (OBJUI-001). Validated-then-broken is worse than never-validated, because + * the green light is what the author trusted. The liveness pass that this + * card's earlier deferral was keyed to (objectui#6935) established that + * external consumption of this package is structurally unmeasurable — the + * certainly-live control `TableSchema` returns the same zero — so the ruling + * was taken on the evidence in hand rather than on an exit that cannot fire. + * + * ⚠️ NOT the same family, and deliberately untouched: the live slotted + * record-page vocabulary — `PageNodeSchema.kind === 'slotted'` with + * `PageNodeSchema.slots?: PageSlotMap` (`./layout.ts`), rendered by + * `usePageAssignment` / `PageBlockCanvas` / `PageBlockInspector` in + * `@object-ui/app-shell`. It shares the words "block" and "slot" with the + * family above and shares no declaration, no type and no file with it; + * objectui#5937 drew that line first. + * + * The `ComponentSchema` note. The `type: 'component'` NAVIGATION item kind + * (`{ type: 'component', componentRef: 'ns:name' }`, declared by + * `NavigationItemSchema` in `./zod/app.zod.ts`, objectui#2918) is a DIFFERENT + * declaration in a different module and is untouched by this retirement. What + * went is the block family's own `ComponentSchema` node kind, which carried + * `componentName` / `props` / `children` and, like its four siblings, was + * registered nowhere. + * + * @module blocks + * @packageDocumentation */ -export interface ComponentSchema extends BaseSchema { - type: 'component'; - - /** - * Component name/identifier - */ - componentName?: string; - - /** - * Component props - */ - props?: Record; - /** - * Component children - */ - children?: SchemaNode | SchemaNode[]; -} +// Kept a module on purpose — the retirement pin above dynamic-imports this +// file — with nothing left to export. +export {}; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 5ef3478d2a..d089ea5235 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -836,18 +836,13 @@ export type { AIInsightsSchema, } from './ai.js'; -export type { - // Block System - BlockSchema, - BlockMetadata, - BlockVariable, - BlockSlot, - BlockLibraryItem, - BlockLibrarySchema, - BlockEditorSchema, - BlockInstanceSchema, - ComponentSchema, -} from './blocks.js'; +// The Block System re-export block is GONE, not emptied: `BlockSchema`, +// `BlockMetadata`, `BlockVariable`, `BlockSlot`, `BlockLibraryItem`, +// `BlockLibrarySchema`, `BlockEditorSchema`, `BlockInstanceSchema` and +// `ComponentSchema` were RETIRED whole in objectui#4895 (ADR-0049 +// enforce-or-remove, maintainer ruling 2026-09-02, option C1). `./blocks.ts` +// is kept as the tombstone and carries the reasoning, the evidence and the +// boundary against the live slotted record-page vocabulary in `./layout.ts`. export type { // View System Enhancements diff --git a/packages/types/src/zod/blocks.zod.ts b/packages/types/src/zod/blocks.zod.ts index 25f00660dd..11a2ac73ad 100644 --- a/packages/types/src/zod/blocks.zod.ts +++ b/packages/types/src/zod/blocks.zod.ts @@ -7,164 +7,40 @@ */ /** - * @object-ui/types/zod - Block Schema Zod Validators - * - * Zod validation schemas for reusable component blocks. - * Following @objectstack/spec UI specification format. - * + * @object-ui/types/zod - Block Schema Zod Validators (all RETIRED) + * + * This module exports NOTHING any more. It is kept as the ADR-0049 tombstone + * for the block schema family's runtime validators, and + * `__tests__/block-family-retired-4895.test.ts` dynamic-imports it to pin the + * retired names OUT of it. + * + * RETIRED (objectui#4895, ADR-0049 enforce-or-remove) with the TypeScript half + * in `../blocks.ts` — see that file for the ruling, the evidence and the + * boundary against the live slotted record-page vocabulary. Ten values lived + * here, and all ten were carried by `__tests__/zod-mirror-parity.test.ts`: + * + * - `BlockVariableSchema`, `BlockSlotSchema`, `BlockMetadataSchema`, + * `BlockSchema`, `BlockLibraryItemSchema`, `BlockLibrarySchema`, + * `BlockEditorSchema`, `BlockInstanceSchema`, `ComponentSchema` + * - `BlockComponentSchema` — the discriminated union over the five node + * kinds above, and the arm through which `AnyComponentSchema` + * (`./index.zod.ts`) accepted every one of them + * + * This half is the one that mattered. The types were merely published; these + * were published AND executable, so `AnyComponentSchema.safeParse({ type: + * 'block-library' })` returned success for a node no page can render. + * `./index.zod.ts` no longer carries a block arm and now refuses all five + * kinds; `__tests__/phase2-schemas.test.ts` pins those refusals next to the + * theme refusals retired the same way (objectui#5489, objectui#5647). + * + * Do NOT hand-write local mirrors of any retired schema here: re-declaring one + * is a published-contract decision no ruling has taken, and the retirement pin + * above fails if a retired name reappears. + * * @module zod/blocks * @packageDocumentation */ -import { z } from 'zod'; -import { BaseSchema, SchemaNodeSchema } from './base.zod.js'; - -/** - * Block Variable Schema - */ -export const BlockVariableSchema = z.object({ - name: z.string().describe('Variable name'), - label: z.string().optional().describe('Display label'), - type: z.enum(['string', 'number', 'boolean', 'object', 'array', 'component']).optional().describe('Variable type'), - defaultValue: z.any().optional().describe('Default value'), - description: z.string().optional().describe('Description/help text'), - required: z.boolean().optional().describe('Whether this variable is required'), - validation: z.any().optional().describe('Validation rules'), - enum: z.array(z.any()).optional().describe('Enum options (for string/number types)'), -}); - -/** - * Block Slot Schema - */ -export const BlockSlotSchema = z.object({ - name: z.string().describe('Slot name'), - label: z.string().optional().describe('Display label'), - description: z.string().optional().describe('Description'), - defaultContent: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional().describe('Default content'), - allowedTypes: z.array(z.string()).optional().describe('Allowed component types'), - maxChildren: z.number().optional().describe('Maximum number of children'), - required: z.boolean().optional().describe('Whether this slot is required'), -}); - -/** - * Block Metadata Schema - */ -export const BlockMetadataSchema = z.object({ - name: z.string().describe('Block name/identifier'), - label: z.string().optional().describe('Display label'), - description: z.string().optional().describe('Block description'), - category: z.string().optional().describe('Block category'), - icon: z.string().optional().describe('Block icon'), - tags: z.array(z.string()).optional().describe('Block tags for search'), - author: z.string().optional().describe('Author/creator'), - version: z.string().optional().describe('Version'), - license: z.string().optional().describe('License'), - repository: z.string().optional().describe('Repository URL'), - preview: z.string().optional().describe('Preview image URL'), - premium: z.boolean().optional().describe('Is this a premium/paid block'), -}); - -/** - * Block Schema - */ -export const BlockSchema = BaseSchema.extend({ - type: z.literal('block'), - meta: BlockMetadataSchema.optional().describe('Block metadata'), - variables: z.array(BlockVariableSchema).optional().describe('Block variables/props'), - slots: z.array(BlockSlotSchema).optional().describe('Block slots for content injection'), - template: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional().describe('Block template (component tree)'), - values: z.record(z.string(), z.any()).optional().describe('Variable values (when using a block)'), - slotContent: z.record(z.string(), z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)])).optional().describe('Slot content (when using a block)'), - blockRef: z.string().optional().describe('Block reference (for reusing saved blocks)'), - editable: z.boolean().optional().describe('Enable edit mode'), -}); - -/** - * Block Library Item Schema - */ -export const BlockLibraryItemSchema = z.object({ - id: z.string().describe('Unique identifier'), - meta: BlockMetadataSchema.describe('Block metadata'), - schema: BlockSchema.describe('Block schema'), - installs: z.number().optional().describe('Installation count'), - rating: z.number().optional().describe('Rating (1-5)'), - ratingCount: z.number().optional().describe('Number of ratings'), - updatedAt: z.string().optional().describe('Last updated timestamp'), - createdAt: z.string().optional().describe('Created timestamp'), -}); - -/** - * Block Library Schema - */ -export const BlockLibrarySchema = BaseSchema.extend({ - type: z.literal('block-library'), - apiEndpoint: z.string().optional().describe('Library API endpoint'), - category: z.string().optional().describe('Filter by category'), - searchQuery: z.string().optional().describe('Search query'), - tags: z.array(z.string()).optional().describe('Filter by tags'), - showPremium: z.boolean().optional().describe('Show premium blocks'), - blocks: z.array(BlockLibraryItemSchema).optional().describe('Blocks to display'), - loading: z.boolean().optional().describe('Loading state'), - onInstall: z.string().optional().describe('Install callback'), - onPreview: z.string().optional().describe('Preview callback'), -}); - -/** - * Block Editor Schema - */ -export const BlockEditorSchema = BaseSchema.extend({ - type: z.literal('block-editor'), - block: BlockSchema.optional().describe('Block being edited'), - showVariables: z.boolean().optional().describe('Show variable editor'), - showSlots: z.boolean().optional().describe('Show slot editor'), - showTemplate: z.boolean().optional().describe('Show template editor'), - showPreview: z.boolean().optional().describe('Show preview'), - onSave: z.string().optional().describe('Save callback'), - onCancel: z.string().optional().describe('Cancel callback'), -}); - -/** - * Block Instance Schema - */ -export const BlockInstanceSchema = BaseSchema.extend({ - type: z.literal('block-instance'), - blockId: z.string().describe('Block reference ID'), - blockName: z.string().optional().describe('Block name (for local blocks)'), - values: z.record(z.string(), z.any()).optional().describe('Variable values'), - slotContent: z.record(z.string(), z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)])).optional().describe('Slot content'), - overrideStyles: z.boolean().optional().describe('Override styles'), -}); - -/** - * Component Schema - */ -export const ComponentSchema = BaseSchema.extend({ - type: z.literal('component'), - componentName: z.string().optional().describe('Component name/identifier'), - props: z.record(z.string(), z.any()).optional().describe('Component props'), - children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional().describe('Component children'), -}); - -/** - * Union of all block schemas - */ -export const BlockComponentSchema = z.discriminatedUnion('type', [ - BlockSchema, - BlockLibrarySchema, - BlockEditorSchema, - BlockInstanceSchema, - ComponentSchema, -]); - -/** - * Export type inference helpers - */ -export type BlockVariableSchemaType = z.infer; -export type BlockSlotSchemaType = z.infer; -export type BlockMetadataSchemaType = z.infer; -export type BlockSchemaType = z.infer; -export type BlockLibraryItemSchemaType = z.infer; -export type BlockLibrarySchemaType = z.infer; -export type BlockEditorSchemaType = z.infer; -export type BlockInstanceSchemaType = z.infer; -export type ComponentSchemaType = z.infer; +// Kept a module on purpose — the retirement pin above dynamic-imports this +// file — with nothing left to export. +export {}; diff --git a/packages/types/src/zod/index.zod.ts b/packages/types/src/zod/index.zod.ts index 8df67d6bce..1cfdaefb62 100644 --- a/packages/types/src/zod/index.zod.ts +++ b/packages/types/src/zod/index.zod.ts @@ -313,18 +313,16 @@ export { ReportUnionSchema, } from './reports.zod.js'; -export { - BlockVariableSchema, - BlockSlotSchema, - BlockMetadataSchema, - BlockSchema, - BlockLibraryItemSchema, - BlockLibrarySchema, - BlockEditorSchema, - BlockInstanceSchema, - ComponentSchema, - BlockComponentSchema, -} from './blocks.zod.js'; +// `./blocks.zod` exports NOTHING any more — the module is kept only as the +// tombstone for the retired block schema family. `BlockVariableSchema`, +// `BlockSlotSchema`, `BlockMetadataSchema`, `BlockSchema`, +// `BlockLibraryItemSchema`, `BlockLibrarySchema`, `BlockEditorSchema`, +// `BlockInstanceSchema`, `ComponentSchema` and the `BlockComponentSchema` +// union over them RETIRED in objectui#4895 (ADR-0049 enforce-or-remove, +// maintainer ruling 2026-09-02, option C1). `AnyComponentSchema` below no +// longer carries a block arm, so `{ type: 'block' | 'block-library' | +// 'block-editor' | 'block-instance' | 'component' }` is now refused rather +// than green-lit for a node no page can render. export { ViewTypeSchema, @@ -355,7 +353,6 @@ import { ComplexSchema } from './complex.zod.js'; import { ObjectQLComponentSchema } from './objectql.zod.js'; import { CRUDComponentSchema } from './crud.zod.js'; import { ReportUnionSchema } from './reports.zod.js'; -import { BlockComponentSchema } from './blocks.zod.js'; import { ViewComponentSchema } from './views.zod.js'; /** @@ -375,7 +372,6 @@ export const AnyComponentSchema = z.union([ ObjectQLComponentSchema, CRUDComponentSchema, ReportUnionSchema, - BlockComponentSchema, ViewComponentSchema, ]); diff --git a/scripts/check-doc-component-types.mjs b/scripts/check-doc-component-types.mjs index afee8fe4fa..aa3fcf7edb 100644 --- a/scripts/check-doc-component-types.mjs +++ b/scripts/check-doc-component-types.mjs @@ -120,8 +120,10 @@ * * Exemptions are keyed by (file, value), never by file alone and never by value * alone. A whole-file exemption would silence real defects on pages that mix - * vocabularies — `blocks/block-schema.mdx` carries `type: 'block'` AND - * `type: 'div'` in the same document — and a value-only exemption would let a + * vocabularies — `api/schema-reference.md` carries `"type": "action"` (an + * ActionSchema discriminant, exempted below) AND `"type": "card"` / + * `"type": "table"` (registered component keys) in the same document, measured + * on this tree — and a value-only exemption would let a * page anywhere in the tree teach `submit` as a component. (file, value) also * keeps the entry honest: it says which page speaks which dialect, which is the * fact a reader of that page needs. @@ -372,39 +374,23 @@ const DOC_TYPE_EXEMPTIONS = { string: 'PageNodeSchema variable declaration\'s data type inside `variables[]`, next to `name` / ' + '`defaultValue` — `PageVariable` (packages/types/src/layout.ts:566, re-exported from ' + - '@objectstack/spec\'s `PageVariableSchema`). Same vocabulary as blocks/block-schema.mdx\'s ' + - '`string`.', + '@objectstack/spec\'s `PageVariableSchema`). (This reason used to add "same vocabulary as ' + + 'blocks/block-schema.mdx\'s `string`"; that page was DELETED with the whole block schema ' + + 'family in objectui#4895, so this entry now stands on its own declaration site.)', }, 'content/docs/blocks/authentication.mdx': { submit: 'ActionSchema discriminant under a button\'s `action` key, not a node type. ' + '`@object-ui/types` ActionSchema.', }, - 'content/docs/blocks/block-schema.mdx': { - block: - 'BlockSchema discriminant — `packages/types/src/blocks.ts` declares `type: \'block\'`, and ' + - '`packages/types/src/zod/blocks.zod.ts` validates it. A block definition is not a rendered node.', - 'block-instance': - 'BlockInstanceSchema discriminant — packages/types/src/blocks.ts:357, zod/blocks.zod.ts:130. A ' + - 'reference to a block is a definition, not a rendered node: it is absent from `AnySchema` ' + - '(types/src/index.ts) and nothing resolves its `blockId`.', - 'block-library': - 'BlockLibrarySchema discriminant — packages/types/src/blocks.ts:263, zod/blocks.zod.ts:100. The ' + - 'shape of a block-library PAYLOAD, not a browser component: absent from `AnySchema`, and no ' + - 'renderer reads it.', - 'block-editor': - 'BlockEditorSchema discriminant — packages/types/src/blocks.ts:315, zod/blocks.zod.ts:116. The ' + - 'shape of an editor CONFIGURATION, not an editor component: absent from `AnySchema`, and no ' + - 'block editor exists to consume it.', - // `slot` was here, exempted pending objectui#4895. That card ruled (maintainer, - // 2026-08-19, recorded on the issue): Option B, docs-truth fix — the family stays - // type-level, the phantom `type: 'slot'` node is DELETED from the page, and the - // page teaches the declared `slotContent` key instead. `slot` is now spelled - // nowhere in blocks/block-schema.mdx, so an exemption for it would itself fail as - // `stale-exemption`. Nothing to exempt; the entry is gone rather than re-pointed. - string: - 'BlockVariable.type — a variable declaration\'s data type, next to `defaultValue` / `required`.', - }, + // `content/docs/blocks/block-schema.mdx` had five entries here — `block`, + // `block-instance`, `block-library`, `block-editor` (the four block-family + // discriminants) and `string` (a `BlockVariable.type` data type) — plus a note + // recording that `slot` had already left in the objectui#5937 docs-truth fix. + // The PAGE is gone: the block schema family was retired whole in objectui#4895 + // (ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02, option C1), so + // every one of those entries would now fail as `stale-exemption`. Deleted with + // their site rather than re-pointed — there is no page left to point at. 'content/docs/blocks/dashboard.mdx': { navigate: 'ActionSchema discriminant under a node\'s `action` key.', }, @@ -538,17 +524,14 @@ const DOC_TYPE_EXEMPTIONS = { 'ActionSchema discriminant in a `const action: ActionSchema = { … }` declaration — this page ' + 'tours each schema family by declaring one of each, so the literal is the document\'s own ' + 'discriminant. Same vocabulary as core/enhanced-actions.mdx.', - block: - 'BlockSchema discriminant in a `const block: BlockSchema = { … }` declaration — ' + - 'packages/types/src/blocks.ts, validated by zod/blocks.zod.ts. A block definition is not a ' + - 'rendered node.', group: 'AppSchema menu entry kind — a navigation group holding `children` items, same ' + 'vocabulary as core/app-schema.mdx.', item: 'AppSchema menu entry kind — a navigation item, sibling of `group`. Same vocabulary as ' + 'core/app-schema.mdx. Not a rendered node.', - string: - 'BlockVariable.type in the BlockSchema tour\'s `variables[]` — a variable declaration\'s data ' + - 'type, next to `name` / `defaultValue`.', + // `block` and `string` also stood here — the `const block: BlockSchema` tour and + // the `BlockVariable.type` inside its `variables[]`. Both left with the block + // schema family (objectui#4895): the tour is deleted from that page, so each + // entry would now fail as `stale-exemption`. }, 'content/docs/guide/schema-playground.md': { reset: diff --git a/scripts/check-doc-snippet-types.mjs b/scripts/check-doc-snippet-types.mjs index 1945ac294d..bcc2c23230 100644 --- a/scripts/check-doc-snippet-types.mjs +++ b/scripts/check-doc-snippet-types.mjs @@ -857,7 +857,12 @@ function importedSpecifiers(body) { const SENTINEL_EXPORT = 'ThisNameIsDefinitelyNotExported'; const CONTROL_PACKAGE = '@object-ui/types'; -const CONTROL_REAL_EXPORT = 'ComponentSchema'; +// `BaseSchema` since objectui#4895: the control needs a name `@object-ui/types` +// really exports, and the previous choice, `ComponentSchema`, was retired with +// the whole block schema family. A control that names a deleted export fails the +// harness rather than the documents — which is exactly what it did, loudly, and +// is how this line was found. +const CONTROL_REAL_EXPORT = 'BaseSchema'; /** * The UNDECLARED control's specifier (see the header). Three properties make it