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
16 changes: 16 additions & 0 deletions .changeset/fields-catalog-form-hosted-3910.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
---
"@object-ui/fields": minor
---

fields: remove the docs-demo registration path (`registerFields` + `createFieldRenderer`), and host the docs field examples in a real form

**Breaking (shipped as `minor` per AGENTS.md §版本号策略 — objectui's major tracks `@objectstack`, so the repo's own breaking changes are `minor` with the break spelled out here):** two public exports are removed from `@object-ui/fields`:

- `registerFields()` — registered every widget under the same `field:<type>` keys as `registerAllFields()`, but wrapped in a demo-only renderer. Two paths writing the same registry keys meant whoever ran LAST won it for every consumer sharing the registry.
- `createFieldRenderer()` — that wrapper. It synthesized a `label`, a description, and a local `useState`/`onChange` around a widget.

**Migration:** there is nothing to migrate for an application. `registerAllFields()` runs on import of `@object-ui/fields` and is the one registration seam (`registerField(type)` for a single type); no shipped application ever called the removed pair — the docs site was the only caller. Code that rendered a **bare field node** standalone (`{ type: 'currency', label: 'Amount' }`) and relied on the wrapper for label and value state must host the field in a form instead: `{ type: 'form', fields: [{ name: 'amount', label: 'Amount', type: 'currency' }] }`. Seed values through the form's `defaultValues`, not a field-level `value`.

**Why the removal rather than a relocation** (ruling B of objectui#3798, confirmed by the maintainer; objectui#3308 is the origin, PR #3793 the safety net that first corrected the misleading `@deprecated` note): the wrapper existed only so the documentation could render a bare field node as a labelled, editable input. No application produces that rendering — on the live path a bare field node has no host for its label or its value. So the field docs, which are a first-hand transcription source for AI authors, were teaching a shape that does not work, and an author copying it got a node with neither label nor `onChange`. Relocating the wrapper into the docs site would have preserved that divergence under a new owner. Hosting the examples in a real form removes the reason for the wrapper to exist: the form renderer already owns label and value state, so the docs can only show what an application actually renders.

The 74 bare-node examples under `examples/schema-catalog/src/schemas/fields-*` are now form-hosted (the other 2 already were). A field's `value` moved to the form's `defaultValues`, because the form renderer spreads react-hook-form's state after the schema props and a field-level `value` is therefore ignored — a catalog guard now pins that so a dead `value` cannot come back.
13 changes: 10 additions & 3 deletions apps/site/app/components/ObjectUIProvider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,16 +2,23 @@

// Import components to trigger registration
import { initializeComponents } from '@object-ui/components';
import { registerFields } from '@object-ui/fields';
// Side-effect import: the module runs `registerAllFields()` at load, which is the
// ONE registration path (objectui#3308). The docs site used to also call
// `registerFields()` here — the demo adapter that re-registered every
// `field:<type>` key wrapped in synthesized label/state chrome, so the docs
// rendered bare field nodes in a way no real application does. Ruling B of
// objectui#3798 removed it: the catalog's field examples are form-hosted
// (`{ type: 'form', fields: [...] }`) and the real form renderer owns the label
// and the value state (objectui#3910).
import '@object-ui/fields';
import { ComponentRegistry } from '@object-ui/core';
import { useEffect } from 'react';

export function ObjectUIProvider({ children }: { children: React.ReactNode }) {
// Explicitly call init to ensure components are registered
useEffect(() => {
initializeComponents();
registerFields();


// Wait a bit for plugins to register, then log
setTimeout(() => {
const componentTypes = ComponentRegistry.getAllTypes();
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
{
"type": "auto_number",
"name": "order_number",
"label": "Order Number",
"readonly": true,
"value": "ORD-0001"
"type": "form",
"showSubmit": false,
"showCancel": false,
"defaultValues": {
"order_number": "ORD-0001"
},
"fields": [
{
"name": "order_number",
"label": "Order Number",
"type": "auto_number",
"readonly": true
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
{
"type": "auto_number",
"name": "ticket_id",
"label": "Ticket ID",
"format": "TKT-{YYYY}{MM}-{0000}",
"readonly": true,
"value": "TKT-202403-0567"
"type": "form",
"showSubmit": false,
"showCancel": false,
"defaultValues": {
"ticket_id": "TKT-202403-0567"
},
"fields": [
{
"name": "ticket_id",
"label": "Ticket ID",
"type": "auto_number",
"format": "TKT-{YYYY}{MM}-{0000}",
"readonly": true
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
{
"type": "auto_number",
"name": "invoice_id",
"label": "Invoice ID",
"format": "INV-{0000}",
"readonly": true,
"value": "INV-1234"
"type": "form",
"showSubmit": false,
"showCancel": false,
"defaultValues": {
"invoice_id": "INV-1234"
},
"fields": [
{
"name": "invoice_id",
"label": "Invoice ID",
"type": "auto_number",
"format": "INV-{0000}",
"readonly": true
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
{
"type": "boolean",
"name": "is_active",
"label": "Active"
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "is_active",
"label": "Active",
"type": "boolean"
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
{
"type": "boolean",
"name": "terms",
"label": "I agree to the terms and conditions",
"value": true
"type": "form",
"showSubmit": false,
"showCancel": false,
"defaultValues": {
"terms": true
},
"fields": [
{
"name": "terms",
"label": "I agree to the terms and conditions",
"type": "boolean"
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
{
"type": "boolean",
"name": "notifications",
"label": "Email Notifications",
"description": "Receive email updates about your account"
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "notifications",
"label": "Email Notifications",
"type": "boolean",
"description": "Receive email updates about your account"
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
{
"type": "currency",
"name": "price_eur",
"label": "Price (EUR)",
"placeholder": "€0.00",
"currency": "EUR"
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "price_eur",
"label": "Price (EUR)",
"type": "currency",
"placeholder": "€0.00",
"currency": "EUR"
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
{
"type": "currency",
"name": "amount",
"label": "Amount",
"placeholder": "$0.00",
"currency": "USD"
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "amount",
"label": "Amount",
"type": "currency",
"placeholder": "$0.00",
"currency": "USD"
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
{
"type": "date",
"name": "birthdate",
"label": "Birth Date",
"placeholder": "Select date..."
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "birthdate",
"label": "Birth Date",
"type": "date",
"placeholder": "Select date..."
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
{
"type": "date",
"name": "event_date",
"label": "Event Date",
"value": "2026-05-28T06:36:57.154Z"
"type": "form",
"showSubmit": false,
"showCancel": false,
"defaultValues": {
"event_date": "2026-05-28T06:36:57.154Z"
},
"fields": [
{
"name": "event_date",
"label": "Event Date",
"type": "date"
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
{
"type": "datetime",
"name": "meeting_time",
"label": "Meeting Time",
"placeholder": "Select date and time..."
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "meeting_time",
"label": "Meeting Time",
"type": "datetime",
"placeholder": "Select date and time..."
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
{
"type": "datetime",
"name": "created_at",
"label": "Created At",
"value": "2024-03-15T10:30",
"readonly": true
"type": "form",
"showSubmit": false,
"showCancel": false,
"defaultValues": {
"created_at": "2024-03-15T10:30"
},
"fields": [
{
"name": "created_at",
"label": "Created At",
"type": "datetime",
"readonly": true
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
{
"type": "datetime",
"name": "appointment",
"label": "Appointment",
"required": true
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "appointment",
"label": "Appointment",
"type": "datetime",
"required": true
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
{
"type": "datetime",
"name": "scheduled_at",
"label": "Scheduled At",
"value": "2024-03-15T14:30"
"type": "form",
"showSubmit": false,
"showCancel": false,
"defaultValues": {
"scheduled_at": "2024-03-15T14:30"
},
"fields": [
{
"name": "scheduled_at",
"label": "Scheduled At",
"type": "datetime"
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
{
"type": "email",
"name": "email",
"label": "Email Address",
"placeholder": "you@example.com"
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "email",
"label": "Email Address",
"type": "email",
"placeholder": "you@example.com"
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
{
"type": "email",
"name": "contact_email",
"label": "Contact Email",
"placeholder": "contact@company.com",
"required": true
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "contact_email",
"label": "Contact Email",
"type": "email",
"placeholder": "contact@company.com",
"required": true
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
{
"type": "file",
"name": "attachment",
"label": "Attachment"
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "attachment",
"label": "Attachment",
"type": "file"
}
]
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
{
"type": "file",
"name": "documents",
"label": "Documents",
"multiple": true
"type": "form",
"showSubmit": false,
"showCancel": false,
"fields": [
{
"name": "documents",
"label": "Documents",
"type": "file",
"multiple": true
}
]
}
Loading
Loading