Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .changeset/block-schema-docs-truth-4895.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
---

Docs and a gate ledger only — this publishes nothing, declared explicitly with an empty
frontmatter rather than left undeclared. No package `src/` is touched: the two files
changed are `content/docs/blocks/block-schema.mdx` and
`scripts/check-doc-component-types.mjs`.

Corrects `content/docs/blocks/block-schema.mdx` to the vocabulary the repository actually
declares, per the maintainer ruling recorded on objectui#4895 (Option B: docs-truth fix,
the block family re-scoped as type-level).

The page taught a `{ type: 'slot', name: 'content' }` node inside
`BlockSchema.template`. `template` is typed `SchemaNode | SchemaNode[]`, so that snippet
sat on the render path — and `slot` is registered nowhere. Measured against the
`check-doc-component-types.mjs` derivation of the registered universe (659 keys), the only
keys matching `/block|slot/` are `blockquote` and `ui:blockquote`. A reader who copied the
snippet got the renderer's `OBJUI-001` "Unknown component type" panel. That node is
deleted, and the page now teaches `slotContent`, the key `BlockSchema` and
`BlockInstanceSchema` actually declare.

**`slotContent` is documented as declared-but-not-yet-consumed, not as the working path**,
because that is what it measures as. Outside `packages/types` — the interface, its Zod
mirror, and that package's own parse test — nothing in the repository reads `slotContent`,
`slots` or `template`. Rewriting one phantom into a second phantom is the failure this card
exists to close, so the page states the status plainly instead of implying a runtime that
does not exist. For the same reason the component-schema framing is dropped from
`block-library` / `block-editor` / `block-instance`: none of them appears in `AnySchema`,
the runtime node union in `packages/types/src/index.ts`.

Two further measurements are written into the page because they are what a confused reader
needs. The four `<SchemaExample>` demos it embeds are ordinary registered component trees
(`card`, `flex`, `stack`, `text`, `icon`, `button`, `badge`) — none carries `type: 'block'`,
`slots` or `slotContent`, so nothing on the page was ever exercising the block vocabulary.
And the slot system that *is* wired end to end is a different family entirely: slotted
record pages (`kind: "slotted"`, `page.slots`), consumed by `usePageAssignment`,
`PageBlockCanvas` and `PageBlockInspector`. The page now links there rather than leaving the
two `slots` spellings to be conflated.

The `slot` entry in `DOC_TYPE_EXEMPTIONS` pointed at this card and is dropped, as the ruling
requires. With the phantom node gone the entry would itself fail as `stale-exemption`, so it
is deleted rather than re-pointed; the three terse family reasons are re-pointed at the
ruling's framing in the same pass.

Option A (build renderers for the family) is rejected on zero pull and Option C (retire the
family) is deferred; neither is implemented here.
91 changes: 73 additions & 18 deletions content/docs/blocks/block-schema.mdx
Original file line number Diff line number Diff line change
@@ -1,26 +1,51 @@
---
title: "Block Schema (BlockSchema)"
description: "Reusable component blocks with variables, slots, and marketplace support"
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

The `BlockSchema` defines reusable component blocks that can be configured with variables, support content injection through slots, and shared via a marketplace.
`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.

<Callout type="warn" title="Status: type-level only, nothing renders this family">
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 reads `slots`, `slotContent` or `template` at runtime.** There is no block
expander, no block library UI and no block editor in this repository. The keys are
declared and validated, and that is all.

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).
</Callout>

## Overview

BlockSchema enables:
- **Reusable components** - Create once, use everywhere
- **Variable system** - Typed props for customization
- **Slot system** - Content injection points
- **Block templates** - Predefined component trees
- **Marketplace support** - Share and discover blocks
- **Version control** - Track block versions
`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

<SchemaExample id="block-schema/feature-card-block" />
Expand Down Expand Up @@ -133,6 +158,32 @@ interface BlockSlot {
}
```

#### 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

```plaintext
Expand Down Expand Up @@ -250,10 +301,6 @@ const cardBlock: BlockSchema = {
value: '${description}',
className: 'card-description'
},
{
type: 'slot',
name: 'content'
},
{
type: 'button',
label: '${buttonText}',
Expand All @@ -274,7 +321,9 @@ const cardBlock: BlockSchema = {

### Block Instance

Use `BlockInstanceSchema` to instantiate a block:
`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.

```plaintext
import type { BlockInstanceSchema } from '@object-ui/types';
Expand Down Expand Up @@ -311,7 +360,10 @@ const instance: BlockInstanceSchema = {

## Block Library

Use `BlockLibrarySchema` to browse available blocks:
`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.

```plaintext
import type { BlockLibrarySchema } from '@object-ui/types';
Expand Down Expand Up @@ -347,7 +399,9 @@ const library: BlockLibrarySchema = {

## Block Editor

Use `BlockEditorSchema` to create/edit blocks:
`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.

```plaintext
import type { BlockEditorSchema } from '@object-ui/types';
Expand Down Expand Up @@ -472,6 +526,7 @@ if (result.success) {

## Related

- [Component Registry](/docs/guide/component-registry) - Registering components
- [Schema Rendering](/docs/guide/schema-rendering) - Rendering blocks
- [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
25 changes: 15 additions & 10 deletions scripts/check-doc-component-types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -271,18 +271,23 @@ const DOC_TYPE_EXEMPTIONS = {
'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.',
'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.',
'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.',
slot:
'Block slot placeholder inside `BlockSchema.template`, which IS a `SchemaNode` — so unlike its ' +
'siblings above this one sits on the render path and nothing registers `slot`. Filed as ' +
'objectui#4895: the correct spelling is not one thing (register a slot node, or route the ' +
'snippet through the declared `slotContent` key), and objectui#4823 does not pre-decide it. ' +
'DELETE this entry when #4895 lands — the gate reports a stale exemption, so it cannot be ' +
'forgotten.',
'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`.',
},
Expand Down
Loading