diff --git a/.changeset/7493-componentinput-retire-label-defaultvalue-advanced.md b/.changeset/7493-componentinput-retire-label-defaultvalue-advanced.md new file mode 100644 index 0000000000..4074b8a982 --- /dev/null +++ b/.changeset/7493-componentinput-retire-label-defaultvalue-advanced.md @@ -0,0 +1,85 @@ +--- +'@object-ui/types': minor +'@object-ui/core': patch +'@object-ui/components': patch +'@object-ui/layout': patch +'@object-ui/app-shell': patch +'@object-ui/plugin-ai': patch +'@object-ui/plugin-calendar': patch +'@object-ui/plugin-charts': patch +'@object-ui/plugin-chatbot': patch +'@object-ui/plugin-dashboard': patch +'@object-ui/plugin-designer': patch +'@object-ui/plugin-detail': patch +'@object-ui/plugin-editor': patch +'@object-ui/plugin-form': patch +'@object-ui/plugin-gantt': patch +'@object-ui/plugin-grid': patch +'@object-ui/plugin-kanban': patch +'@object-ui/plugin-list': patch +'@object-ui/plugin-map': patch +'@object-ui/plugin-markdown': patch +'@object-ui/plugin-report': patch +'@object-ui/plugin-timeline': patch +'@object-ui/plugin-tree': patch +'@object-ui/plugin-view': patch +--- + +**Breaking for authored metadata:** `ComponentInput.label`, `ComponentInput.defaultValue` and +`ComponentInput.advanced` are RETIRED on both faces (objectui#7493 item ① and objectui#7781; +maintainer ruling A of 2026-09-06, immediate, no deprecation window; ADR-0049 enforce-or-remove). +They are the three keys the manifest serializer does not forward, and nothing read them on any +publication or consumption path. + +No manifest ever published them, so no consumer could ever have read them. `sdui-parser`'s +serializer (`packages/sdui-parser/src/index.ts`) forwards exactly six keys per input — `name`, +`type`, `required`, `enum`, `binding`, `description` — so a value authored under any of the three +never reached `sdui.manifest.json`, the generated JSX `.d.ts`, or a diagnostic; its boundary type +has no slot for them; the registry's data-source seam reads `name` only; and neither the designer +nor the app-shell inspectors consult registry `inputs` at all. A structural census over every +`inputs:` array in the repository (re-measured on this change's merge-base, `name` 951 and `type` +951 as the controls) counted the writes: `label` 908, `defaultValue` 245, `advanced` 9 — written on +nearly every registration, read by nothing. + +FROM → TO, per key — all three **TOMBSTONED, not removed**, because the route was measured on +the built face before it was chosen: `ComponentInputSchema` is a non-strict `z.object`, and an +undeclared key parses GREEN and is silently STRIPPED, so a deletion would have swallowed 1,162 +authored values in silence. The tombstone is what makes the refusal loud and by name. + +- `label?: string` → `label?: never` on the interface, `retirementTombstone()` on the Zod mirror. + Migration: delete the key. An input is identified by its `name` on every path that reaches it; + nothing ever rendered a label for it. +- `defaultValue?: any` → `defaultValue?: never` / `retirementTombstone()`. Migration: delete the + key. The renderer's own fallback read IS the default; tell the author about it in `description`, + which IS published. (Tightening the type to `unknown` was ruled out: it closes no error class, + since nothing reads the value.) +- `advanced?: boolean` → `advanced?: never` / `retirementTombstone()`. Migration: delete the key. + No designer surface ever hid an "advanced" input; there is nothing to write instead. + +The retirement kit: `?: never` on `ComponentInput` (`packages/types/src/base.ts`), so authoring one +is a `tsc` error at the registration site; `retirementTombstone()` on `ComponentInputSchema` +(`packages/types/src/zod/base.zod.ts`), so an authored value is REFUSED at parse time with +`code: 'invalid_type'`, the key named in the issue `path`, and the migration note as the message +(one string, both channels). Pinned in +`packages/types/src/__tests__/component-input-retired-keys-7493.test.ts`, which also holds a +tree-scoped absence census over every `inputs:` array under `packages/**` and `apps/**`. + +Accept-set change, stated plainly for reviewers: a document that sets any of the three keys on a +`ComponentInput` used to parse GREEN (the value was then dropped by the serializer) and now parses +RED. Every in-repo authoring site — 1,199 keys across 110 registration files, the three standalone +`ComponentInput[]` arrays and the two named input arrays `tsc` found included — is deleted in the same change, as the ruling's split rule +requires; the `WidgetRegistry` seam no longer copies the widget-manifest values onto the synthesized +`ComponentInput` (they fed nothing), and the data-source declaration `ELEMENT_DATA_SOURCE_INPUT` +drops its `label`. The patch entries on the other packages record exactly that: their registrations +stop authoring inert keys, with no runtime or published-manifest change. + +The nine test files that read `defaultValue` off a registration were re-pinned against the +renderer's ACTUAL default (its own fallback read, or the `defaultProps` it ships) instead of the +declaration that went away; two assertions that only restated the shadow default were dropped with +the reason on the line. + +The in-repo zero is what was measured. Whether anything OUTSIDE this repository writes these keys +is not measurable from here (the objectui#5674 limit); converting such a write from a silent drop +into a named refusal is exactly what the tombstones buy. `WidgetInput`'s own `label` / +`defaultValue` / `advanced` (the widget-manifest face) stay declared and writable — nothing has +ruled on that face; that it now has no reader either is recorded as objectui#7911. diff --git a/apps/console/src/__tests__/public-block-binding-reach.test.tsx b/apps/console/src/__tests__/public-block-binding-reach.test.tsx index b7ea137a9f..40f59e8bd5 100644 --- a/apps/console/src/__tests__/public-block-binding-reach.test.tsx +++ b/apps/console/src/__tests__/public-block-binding-reach.test.tsx @@ -324,7 +324,9 @@ const sampleFor = (input: any): unknown => { if (input.name === 'details') { return [{ childObject: PROBE_OBJECT, title: 'Probe Detail' }]; } - if (input.defaultValue !== undefined) return input.defaultValue; + // (`ComponentInput.defaultValue` is an ADR-0049 tombstone since objectui#7493 — + // no registration declares a default any more, so the sample is decided by the + // declared TYPE below, as it already was for every input without one.) switch (input.type) { case 'number': return 1; diff --git a/apps/console/src/__tests__/record-block-record-reach.test.tsx b/apps/console/src/__tests__/record-block-record-reach.test.tsx index a4ccc9ab29..d1ba7b68cc 100644 --- a/apps/console/src/__tests__/record-block-record-reach.test.tsx +++ b/apps/console/src/__tests__/record-block-record-reach.test.tsx @@ -245,12 +245,38 @@ const SAMPLE_BY_INPUT: Readonly> = { // affordance is configured against something that exists rather than at a // dangling name. add: { picker: { object: PROBE_CHILD_OBJECT } }, + // The SIXTH instance arrived with objectui#7493, which retired + // `ComponentInput.defaultValue`: until then `sampleFor` seeded these six from + // the registration's declared default, which happened to be the renderer's + // own fallback and the one value under which the probe is askable. The + // declared default is gone (nothing but this sampler ever read it), so each + // renderer default is restated HERE, next to the read it mirrors: + // - `record:related_list` reads `relationshipValueField || 'id'`; the + // generic `'x'` names a parent field the record does not carry, so the + // list holds its fetch (by design) and the record cannot reach the output. + // - `record:details` passes `showHeader ?? false`; with the header chrome ON + // the same record renders differently twice and the stability control + // (rightly) refuses to grade the probe. The header is not what this probe + // asks about. + // - `record:quick_actions` reads `location || 'record_header'`, + // `align || 'end'`, `variant || 'default'`, `size || 'sm'`; the + // probe action is declared at the header location, and the generic + // samples (`'x'` for the two strings, the enum's first arm for the + // other two) point the bar at a location with no actions. + relationshipValueField: 'id', + showHeader: false, + location: 'record_header', + align: 'end', + variant: 'default', + size: 'sm', }; /** Fill one declared input. */ const sampleFor = (input: any): unknown => { if (input.name in SAMPLE_BY_INPUT) return SAMPLE_BY_INPUT[input.name]; - if (input.defaultValue !== undefined) return input.defaultValue; + // (`ComponentInput.defaultValue` is an ADR-0049 tombstone since objectui#7493 — + // no registration declares a default any more, so the sample is decided by the + // declared TYPE below, as it already was for every input without one.) switch (input.type) { case 'number': return 1; case 'boolean': return true; diff --git a/content/docs/guide/architecture.md b/content/docs/guide/architecture.md index 0d1192fbba..b596cb74e7 100644 --- a/content/docs/guide/architecture.md +++ b/content/docs/guide/architecture.md @@ -202,7 +202,7 @@ ComponentRegistry.register('my-widget', MyWidgetComponent, { category: 'Custom', icon: 'box', inputs: [ - { name: 'title', type: 'string', label: 'Title' } + { name: 'title', type: 'string' } ] }) ``` diff --git a/content/docs/guide/component-registry.md b/content/docs/guide/component-registry.md index 7cbb651bcd..b173f55489 100644 --- a/content/docs/guide/component-registry.md +++ b/content/docs/guide/component-registry.md @@ -127,8 +127,8 @@ ComponentRegistry.register('my-component', MyComponent, { category: 'Custom', icon: 'component-icon', inputs: [ - { name: 'title', type: 'string', label: 'Title' }, - { name: 'content', type: 'string', label: 'Content' } + { name: 'title', type: 'string' }, + { name: 'content', type: 'string' } ] }) ``` @@ -434,11 +434,11 @@ ComponentRegistry.register('rating', RatingComponent, { category: 'Form', labelling: 'group', inputs: [ - { name: 'name', type: 'string', label: 'Name', required: true }, - { name: 'label', type: 'string', label: 'Label' }, - { name: 'maxStars', type: 'number', label: 'Max stars', defaultValue: 5 }, - { name: 'required', type: 'boolean', label: 'Required' }, - { name: 'disabled', type: 'boolean', label: 'Disabled' } + { name: 'name', type: 'string', required: true }, + { name: 'label', type: 'string' }, + { name: 'maxStars', type: 'number', description: 'Defaults to 5 — the renderer\'s own fallback' }, + { name: 'required', type: 'boolean' }, + { name: 'disabled', type: 'boolean' } ] }) diff --git a/content/docs/guide/plugin-development.md b/content/docs/guide/plugin-development.md index cb43c4a7bb..b7825ba0c3 100644 --- a/content/docs/guide/plugin-development.md +++ b/content/docs/guide/plugin-development.md @@ -158,8 +158,8 @@ ComponentRegistry.register('board', BoardRenderer, { label: 'Board View', category: 'plugin', inputs: [ - { name: 'columns', type: 'array', label: 'Columns', required: true }, - { name: 'items', type: 'array', label: 'Items', required: true }, + { name: 'columns', type: 'array', required: true }, + { name: 'items', type: 'array', required: true }, ], defaultProps: { columns: [ @@ -366,20 +366,19 @@ export interface BoardSchema extends BaseSchema { } ``` -Declare `ComponentInput` entries when registering so the visual designer can offer a property panel: +Declare `ComponentInput` entries when registering: they are what the published manifest (`sdui.manifest.json`) and the JSX-page compiler's diagnostics read. Each entry carries the six keys the manifest forwards — `name`, `type`, `required`, `enum`, `binding`, `description`; a default belongs in the renderer's own fallback read and, for the author, in `description` (`label`, `defaultValue` and `advanced` are retired keys — nothing ever read them): ```tsx ComponentRegistry.register('board', BoardRenderer, { inputs: [ - { name: 'columns', type: 'array', label: 'Columns', required: true }, - { name: 'items', type: 'array', label: 'Items', required: true }, + { name: 'columns', type: 'array', required: true }, + { name: 'items', type: 'array', required: true }, { name: 'layout', type: 'enum', - label: 'Layout', enum: ['horizontal', 'vertical'], - defaultValue: 'horizontal', + description: 'Defaults to "horizontal" — the renderer\'s own fallback', }, ], }); diff --git a/packages/app-shell/src/views/record-approvals-renderer.tsx b/packages/app-shell/src/views/record-approvals-renderer.tsx index 82b6128001..d0f1097cf0 100644 --- a/packages/app-shell/src/views/record-approvals-renderer.tsx +++ b/packages/app-shell/src/views/record-approvals-renderer.tsx @@ -83,7 +83,7 @@ ComponentRegistry.register('approvals', RecordApprovalsRenderer, { category: 'record', label: 'Approvals', icon: 'Stamp', - inputs: [{ name: 'className', type: 'string', label: 'CSS Class' }], + inputs: [{ name: 'className', type: 'string' }], }); export default RecordApprovalsRenderer; diff --git a/packages/app-shell/src/views/record-attachments-renderer.tsx b/packages/app-shell/src/views/record-attachments-renderer.tsx index b96e447c19..1b1c1349b2 100644 --- a/packages/app-shell/src/views/record-attachments-renderer.tsx +++ b/packages/app-shell/src/views/record-attachments-renderer.tsx @@ -72,7 +72,7 @@ ComponentRegistry.register('attachments', RecordAttachmentsRenderer, { category: 'record', label: 'Attachments', icon: 'Paperclip', - inputs: [{ name: 'className', type: 'string', label: 'CSS Class' }], + inputs: [{ name: 'className', type: 'string' }], }); export default RecordAttachmentsRenderer; diff --git a/packages/components/src/__tests__/icon-renderer-declared-default.test.ts b/packages/components/src/__tests__/icon-renderer-declared-default.test.ts index 30a264c55b..06c8b7a7e8 100644 --- a/packages/components/src/__tests__/icon-renderer-declared-default.test.ts +++ b/packages/components/src/__tests__/icon-renderer-declared-default.test.ts @@ -68,43 +68,51 @@ import '../renderers/basic/icon'; // extend the gate to the whole population — ⛔ not to restore a pin over one // registration. // -// ── What this file still asserts, and why that half survives the same argument -// The coupling claim below is NOT a claim about lucide's vocabulary. It needs no -// external record, no copied tokeniser and no consumer to be well-formed: it -// says two declarations in ONE registration agree with each other. It therefore -// cannot drift the way the membership half did, costs nothing to keep, and -// becomes correct the instant any consumer appears. +// ── The COUPLING half was retired at objectui#7493 ────────────────────────── +// It asserted that the registration's `icon` meta and the glyph input's +// `defaultValue` named the same glyph. `ComponentInput.defaultValue` is an +// ADR-0049 retirement tombstone since objectui#7493 (with `label` and +// `advanced`, objectui#7781): the manifest serializer never forwarded it and +// no consumer of `ComponentMeta.inputs` — no designer, no palette, no renderer +// — ever read it, so the "second spelling" was a shadow value only this pin +// compared. There is no renderer default to re-pin it against either: an +// `icon` node with no `icon` name renders the PLACEHOLDER branch and warns +// (objectui#5631), by design — the renderer has no fallback glyph. +// +// ── What this file still asserts +// The registration's `icon` meta — the ONE declared spelling left — is a real +// icon name, and the `icon` input is still declared (it is what the manifest +// publishes for the key). Both are preconditions the old coupling test carried; +// they survive because the ledger in +// `scripts/__tests__/check-lucide-icon-record-names.test.ts` keeps this file as +// the record of the objectui#5936 retirement, and because a registration whose +// palette glyph silently went blank is the objectui#5622 defect's other half. // --------------------------------------------------------------------------- const meta = ComponentRegistry.getMeta('icon', 'ui'); const glyphInput = meta?.inputs?.find(input => input.name === 'icon'); -/** Both declared spellings, each labelled by the surface it drives. */ -const DECLARED_DEFAULTS: Array<[string, string | undefined]> = [ - ['registration `icon` (the palette entry glyph)', meta?.icon], - ['`icon` input `defaultValue` (what a dropped `icon` renders)', glyphInput?.defaultValue as string | undefined], -]; - -describe('the `ui:icon` renderer\'s declared spellings agree (objectui#5622)', () => { - it('both declared spellings were actually found — the precondition', () => { - // Load-bearing, not ceremony: the assertion below compares two values that - // are BOTH `undefined` if the registry read comes back empty, and - // `undefined === undefined` passes. Without this the coupling check goes - // vacuously green on a registration that no longer exists. +describe('the `ui:icon` registration\'s one declared glyph spelling (objectui#5622, objectui#7493)', () => { + it('the registration and its `icon` input were actually found — the precondition', () => { + // Load-bearing, not ceremony: `typeof undefined` is a string too, so the + // glyph assertion below needs the registration to exist first. expect(meta, '`ui:icon` is not registered — the import above no longer registers it.').toBeDefined(); expect( glyphInput, 'the `icon` input is gone from the `ui:icon` registration — fix the reader or the registration.', ).toBeDefined(); - for (const [surface, spelling] of DECLARED_DEFAULTS) { - expect(typeof spelling, `${surface} declares no icon name at all`).toBe('string'); - } }); - it('keeps the palette glyph and the dropped default the same name', () => { - // The defect was one name in two places; the repair is only correct if they - // stay one name. Split them and the palette advertises a glyph the dropped - // component does not render. - expect(meta?.icon).toBe(glyphInput?.defaultValue); + it('declares the palette glyph as a non-empty icon name', () => { + expect(typeof meta?.icon, 'registration `icon` (the palette entry glyph) declares no icon name at all').toBe('string'); + expect(meta?.icon).not.toBe(''); + }); + + it('the `icon` input carries no declared default — the tombstone, read off the registry', () => { + // Not `toBeUndefined()` on the property: that would be vacuously green on a + // missing input too (the precondition above guards it), and the point is + // that the key is ABSENT from what the registration publishes, not that its + // value happens to be undefined. + expect(glyphInput).not.toHaveProperty('defaultValue'); }); }); diff --git a/packages/components/src/__tests__/record-picker-inputs-spec-parity.test.ts b/packages/components/src/__tests__/record-picker-inputs-spec-parity.test.ts index 154ae7ebb1..913bc41210 100644 --- a/packages/components/src/__tests__/record-picker-inputs-spec-parity.test.ts +++ b/packages/components/src/__tests__/record-picker-inputs-spec-parity.test.ts @@ -209,17 +209,13 @@ describe('element:record_picker — registry inputs vs @objectstack/spec', () => expect(description).toMatch(/\$filter/); }); - it('carries no `defaultValue` on the filter entry', () => { - // A default here would pre-fill every picker in the designer with a filter - // the renderer has no opinion about — and a filter's default is not "empty - // object", it is "no filter at all", which `undefined` already is. The spec - // declares no default either. - // - // Existence asserted first: `input('filter')?.defaultValue` is also - // `undefined` when the input is GONE, so without this line the check would - // pass most loudly in the one case it is supposed to notice. + it('declares no default for `filter` — the spec parses none in', () => { + // A filter's default is not "empty object", it is "no filter at all", which + // `undefined` already is, and the spec declares no default. (This used to + // also assert the registration carried no `ComponentInput.defaultValue`; + // that key is an ADR-0049 tombstone since objectui#7493, so the spec's + // parse is the one channel a default could reach an author through.) expect(input('filter')).toBeDefined(); - expect(input('filter')?.defaultValue).toBeUndefined(); expect( ElementRecordPickerPropsSchema.safeParse({ object: 'account' }).data, ).not.toHaveProperty('filter'); diff --git a/packages/components/src/__tests__/text-input-inputs-spec-parity.test.ts b/packages/components/src/__tests__/text-input-inputs-spec-parity.test.ts index 44c9fbce6d..de540d49fc 100644 --- a/packages/components/src/__tests__/text-input-inputs-spec-parity.test.ts +++ b/packages/components/src/__tests__/text-input-inputs-spec-parity.test.ts @@ -188,17 +188,13 @@ describe('element:text_input — registry inputs vs @objectstack/spec', () => { expect(description).toMatch(/empty/i); }); - it('carries no `defaultValue` OF ITS OWN on the defaultValue entry', () => { - // A `ComponentInput.defaultValue` on this input would publish a default for - // the default — the designer would pre-fill a seed value the renderer has no - // opinion about, and every text input in the gallery would come up carrying - // it. The spec declares no default here either. - // - // Existence asserted first: `input('defaultValue')?.defaultValue` is also - // `undefined` when the input is GONE, so without this line the check would - // pass most loudly in the one case it is supposed to notice. + it('declares no default OF ITS OWN for the defaultValue entry — the spec parses none in', () => { + // A default for the default would seed every text input in the gallery with + // a value the renderer has no opinion about; the spec declares none. (This + // used to also assert the registration carried no `ComponentInput.defaultValue`; + // that key is an ADR-0049 tombstone since objectui#7493, so the spec's parse + // is the one channel a default could reach an author through.) expect(input('defaultValue')).toBeDefined(); - expect(input('defaultValue')?.defaultValue).toBeUndefined(); expect(ElementTextInputPropsSchema.safeParse({}).data).not.toHaveProperty('defaultValue'); }); }); diff --git a/packages/components/src/renderers/action/action-bar.tsx b/packages/components/src/renderers/action/action-bar.tsx index db59e4642f..e986327a76 100644 --- a/packages/components/src/renderers/action/action-bar.tsx +++ b/packages/components/src/renderers/action/action-bar.tsx @@ -328,42 +328,33 @@ ComponentRegistry.register('bar', ActionBarRenderer, { skipFallback: true, label: 'Action Bar', inputs: [ - { name: 'actions', type: 'object', label: 'Actions' }, - { name: 'systemActions', type: 'object', label: 'System Actions (always in overflow)' }, + { name: 'actions', type: 'object' }, + { name: 'systemActions', type: 'object' }, { name: 'location', type: 'enum', - label: 'Location', enum: [...ACTION_LOCATIONS], }, { name: 'maxVisible', type: 'number', - label: 'Max Visible Actions', - defaultValue: 3, }, { name: 'direction', type: 'enum', - label: 'Direction', enum: ['horizontal', 'vertical'], - defaultValue: 'horizontal', }, { name: 'variant', type: 'enum', - label: 'Default Variant', enum: ['default', 'secondary', 'outline', 'ghost'], - defaultValue: 'outline', }, { name: 'size', type: 'enum', - label: 'Default Size', enum: ['sm', 'md', 'lg'], - defaultValue: 'sm', }, - { name: 'className', type: 'string', label: 'CSS Class', advanced: true }, + { name: 'className', type: 'string' }, ], defaultProps: { maxVisible: 3, diff --git a/packages/components/src/renderers/action/action-button.tsx b/packages/components/src/renderers/action/action-button.tsx index 0bcdb9aad8..d40d40c15c 100644 --- a/packages/components/src/renderers/action/action-button.tsx +++ b/packages/components/src/renderers/action/action-button.tsx @@ -295,32 +295,26 @@ ComponentRegistry.register('button', ActionButtonRenderer, { skipFallback: true, label: 'Action Button', inputs: [ - { name: 'name', type: 'string', label: 'Action Name' }, - { name: 'label', type: 'string', label: 'Label', defaultValue: 'Action' }, - { name: 'icon', type: 'string', label: 'Icon' }, + { name: 'name', type: 'string' }, + { name: 'label', type: 'string' }, + { name: 'icon', type: 'string' }, { name: 'actionType', type: 'enum', - label: 'Action Type', enum: ['script', 'url', 'modal', 'flow', 'api'], - defaultValue: 'script', }, - { name: 'target', type: 'string', label: 'Target' }, + { name: 'target', type: 'string' }, { name: 'variant', type: 'enum', - label: 'Variant', enum: ['default', 'primary', 'secondary', 'destructive', 'outline', 'ghost'], - defaultValue: 'default', }, { name: 'size', type: 'enum', - label: 'Size', enum: ['sm', 'md', 'lg'], - defaultValue: 'md', }, - { name: 'className', type: 'string', label: 'CSS Class', advanced: true }, + { name: 'className', type: 'string' }, ], defaultProps: { label: 'Action', diff --git a/packages/components/src/renderers/action/action-group.tsx b/packages/components/src/renderers/action/action-group.tsx index e74ff599e5..938e6f2323 100644 --- a/packages/components/src/renderers/action/action-group.tsx +++ b/packages/components/src/renderers/action/action-group.tsx @@ -369,32 +369,26 @@ ComponentRegistry.register('group', ActionGroupRenderer, { skipFallback: true, label: 'Action Group', inputs: [ - { name: 'name', type: 'string', label: 'Group Name' }, - { name: 'label', type: 'string', label: 'Label' }, - { name: 'icon', type: 'string', label: 'Icon' }, - { name: 'actions', type: 'object', label: 'Actions' }, + { name: 'name', type: 'string' }, + { name: 'label', type: 'string' }, + { name: 'icon', type: 'string' }, + { name: 'actions', type: 'object' }, { name: 'display', type: 'enum', - label: 'Display Mode', enum: ['inline', 'dropdown'], - defaultValue: 'inline', }, { name: 'variant', type: 'enum', - label: 'Variant', enum: ['default', 'secondary', 'outline', 'ghost'], - defaultValue: 'outline', }, { name: 'size', type: 'enum', - label: 'Size', enum: ['sm', 'md', 'lg'], - defaultValue: 'sm', }, - { name: 'className', type: 'string', label: 'CSS Class', advanced: true }, + { name: 'className', type: 'string' }, ], defaultProps: { display: 'inline', diff --git a/packages/components/src/renderers/action/action-icon.tsx b/packages/components/src/renderers/action/action-icon.tsx index a39427e6b5..fe7cad63de 100644 --- a/packages/components/src/renderers/action/action-icon.tsx +++ b/packages/components/src/renderers/action/action-icon.tsx @@ -226,25 +226,21 @@ ComponentRegistry.register('icon', ActionIconRenderer, { skipFallback: true, label: 'Action Icon', inputs: [ - { name: 'name', type: 'string', label: 'Action Name' }, - { name: 'label', type: 'string', label: 'Tooltip Label' }, - { name: 'icon', type: 'string', label: 'Icon' }, + { name: 'name', type: 'string' }, + { name: 'label', type: 'string' }, + { name: 'icon', type: 'string' }, { name: 'actionType', type: 'enum', - label: 'Action Type', enum: ['script', 'url', 'modal', 'flow', 'api'], - defaultValue: 'script', }, - { name: 'target', type: 'string', label: 'Target' }, + { name: 'target', type: 'string' }, { name: 'variant', type: 'enum', - label: 'Variant', enum: ['default', 'secondary', 'destructive', 'outline', 'ghost'], - defaultValue: 'ghost', }, - { name: 'className', type: 'string', label: 'CSS Class', advanced: true }, + { name: 'className', type: 'string' }, ], defaultProps: { icon: 'play', diff --git a/packages/components/src/renderers/action/action-menu.tsx b/packages/components/src/renderers/action/action-menu.tsx index 2a1ddcf150..043fca9947 100644 --- a/packages/components/src/renderers/action/action-menu.tsx +++ b/packages/components/src/renderers/action/action-menu.tsx @@ -353,17 +353,15 @@ ComponentRegistry.register('menu', ActionMenuRenderer, { skipFallback: true, label: 'Action Menu', inputs: [ - { name: 'label', type: 'string', label: 'Trigger Label' }, - { name: 'icon', type: 'string', label: 'Trigger Icon' }, - { name: 'actions', type: 'object', label: 'Actions' }, + { name: 'label', type: 'string' }, + { name: 'icon', type: 'string' }, + { name: 'actions', type: 'object' }, { name: 'variant', type: 'enum', - label: 'Trigger Variant', enum: ['default', 'secondary', 'outline', 'ghost'], - defaultValue: 'ghost', }, - { name: 'className', type: 'string', label: 'CSS Class', advanced: true }, + { name: 'className', type: 'string' }, ], defaultProps: { variant: 'ghost', diff --git a/packages/components/src/renderers/basic/button-group.tsx b/packages/components/src/renderers/basic/button-group.tsx index 67d1ef545f..e285ed0153 100644 --- a/packages/components/src/renderers/basic/button-group.tsx +++ b/packages/components/src/renderers/basic/button-group.tsx @@ -54,18 +54,12 @@ ComponentRegistry.register('button-group', { name: 'variant', type: 'enum', - enum: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'], - defaultValue: 'default', - label: 'Variant' - }, + enum: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'] }, { name: 'size', type: 'enum', - enum: ['default', 'sm', 'lg', 'icon'], - defaultValue: 'default', - label: 'Size' - }, - { name: 'className', type: 'string', label: 'CSS Class' } + enum: ['default', 'sm', 'lg', 'icon'] }, + { name: 'className', type: 'string' } ], defaultProps: { variant: 'default', diff --git a/packages/components/src/renderers/basic/data-list.tsx b/packages/components/src/renderers/basic/data-list.tsx index 08b8a66f61..ccb6cd2cd6 100644 --- a/packages/components/src/renderers/basic/data-list.tsx +++ b/packages/components/src/renderers/basic/data-list.tsx @@ -78,9 +78,9 @@ ComponentRegistry.register('definition-list', DefinitionListRenderer, { label: 'Definition List', category: 'content', inputs: [ - { name: 'items', type: 'array', label: 'Items', required: true, description: 'Term/description pairs [{ term, description }]' }, - { name: 'columns', type: 'enum', label: 'Columns', enum: ['1', '2'], defaultValue: '1' }, - { name: 'inline', type: 'boolean', label: 'Inline', description: 'Term and description on one baseline-aligned row' }, + { name: 'items', type: 'array', required: true, description: 'Term/description pairs [{ term, description }]' }, + { name: 'columns', type: 'enum', enum: ['1', '2'] }, + { name: 'inline', type: 'boolean', description: 'Term and description on one baseline-aligned row' }, ], }); @@ -187,13 +187,13 @@ ComponentRegistry.register('repeater', RepeaterRenderer, { label: 'Repeater', category: 'content', inputs: [ - { name: 'object', type: 'string', label: 'Object', required: true, description: 'Object whose records the list repeats over' }, - { name: 'titleField', type: 'string', label: 'Title Field' }, - { name: 'fields', type: 'array', label: 'Fields', description: 'Columns per row — bare names or { field, label? }' }, - { name: 'filter', type: 'array', label: 'Filter' }, - { name: 'sort', type: 'array', label: 'Sort' }, - { name: 'limit', type: 'number', label: 'Limit' }, - { name: 'emptyText', type: 'string', label: 'Empty Text' }, - { name: 'divided', type: 'boolean', label: 'Divided', description: 'Separator between rows' }, + { name: 'object', type: 'string', required: true, description: 'Object whose records the list repeats over' }, + { name: 'titleField', type: 'string' }, + { name: 'fields', type: 'array', description: 'Columns per row — bare names or { field, label? }' }, + { name: 'filter', type: 'array' }, + { name: 'sort', type: 'array' }, + { name: 'limit', type: 'number' }, + { name: 'emptyText', type: 'string' }, + { name: 'divided', type: 'boolean', description: 'Separator between rows' }, ], }); diff --git a/packages/components/src/renderers/basic/div.tsx b/packages/components/src/renderers/basic/div.tsx index 5c0bdb67d6..5fbfb0c221 100644 --- a/packages/components/src/renderers/basic/div.tsx +++ b/packages/components/src/renderers/basic/div.tsx @@ -135,7 +135,7 @@ ComponentRegistry.register('div', 'use "card", "flex", or layout components like "container", "stack", or "grid"', }, inputs: [ - { name: 'className', type: 'string', label: 'CSS Class' } + { name: 'className', type: 'string' } ], defaultProps: { className: 'p-2 sm:p-4 border border-dashed border-gray-300 rounded min-h-[100px]' diff --git a/packages/components/src/renderers/basic/elements.tsx b/packages/components/src/renderers/basic/elements.tsx index 1921b6957f..498cbdef4e 100644 --- a/packages/components/src/renderers/basic/elements.tsx +++ b/packages/components/src/renderers/basic/elements.tsx @@ -107,9 +107,9 @@ ComponentRegistry.register('text', ElementTextRenderer, { // site above. While this said `type: 'string'` the manifest gate reported // `type-mismatch` on the map form, which is the shape this input's own // description teaches the author to write. - { name: 'content', type: ['string', 'object'], label: 'Content', required: true, description: 'Accepts an inline translation map ({ en, "zh-CN", … })' }, - { name: 'variant', type: 'enum', label: 'Variant', enum: ['heading', 'subheading', 'body', 'caption'], defaultValue: 'body' }, - { name: 'align', type: 'enum', label: 'Align', enum: ['left', 'center', 'right'], defaultValue: 'left' }, + { name: 'content', type: ['string', 'object'], required: true, description: 'Accepts an inline translation map ({ en, "zh-CN", … })' }, + { name: 'variant', type: 'enum', enum: ['heading', 'subheading', 'body', 'caption'] }, + { name: 'align', type: 'enum', enum: ['left', 'center', 'right'] }, ], }); @@ -321,13 +321,13 @@ ComponentRegistry.register('button', ElementButtonRenderer, { // (objectui#4970): `ComponentPropsMap['element:button'].label` is // `string | Record< string, string >` on the 17.0.0 GA pin, and the rendered // label goes through `pickLocalized`. - { name: 'label', type: ['string', 'object'], label: 'Label', required: true, description: 'Accepts an inline translation map ({ en, "zh-CN", … })' }, - { name: 'action', type: 'object', label: 'Action', description: 'Inline ActionDef executed on click (url / navigation / api / script / modal / flow); omitted → renders inert' }, - { name: 'variant', type: 'enum', label: 'Variant', enum: ['primary', 'secondary', 'danger', 'ghost', 'link'], defaultValue: 'primary' }, - { name: 'size', type: 'enum', label: 'Size', enum: ['small', 'medium', 'large'], defaultValue: 'medium' }, - { name: 'icon', type: 'string', label: 'Icon', description: 'Lucide icon name' }, - { name: 'iconPosition', type: 'enum', label: 'Icon Position', enum: ['left', 'right'], defaultValue: 'left' }, - { name: 'disabled', type: 'boolean', label: 'Disabled' }, + { name: 'label', type: ['string', 'object'], required: true, description: 'Accepts an inline translation map ({ en, "zh-CN", … })' }, + { name: 'action', type: 'object', description: 'Inline ActionDef executed on click (url / navigation / api / script / modal / flow); omitted → renders inert' }, + { name: 'variant', type: 'enum', enum: ['primary', 'secondary', 'danger', 'ghost', 'link'] }, + { name: 'size', type: 'enum', enum: ['small', 'medium', 'large'] }, + { name: 'icon', type: 'string', description: 'Lucide icon name' }, + { name: 'iconPosition', type: 'enum', enum: ['left', 'right'] }, + { name: 'disabled', type: 'boolean' }, ], }); @@ -467,12 +467,12 @@ ComponentRegistry.register('number', ElementNumberRenderer, { label: 'Number', category: 'content', inputs: [ - { name: 'object', type: 'string', label: 'Object', required: true, description: 'Object the aggregate runs over' }, - { name: 'aggregate', type: 'enum', label: 'Aggregate', enum: ['count', 'sum', 'avg', 'min', 'max'], required: true }, - { name: 'field', type: 'string', label: 'Field', description: 'Measure field (required for every aggregate except count)' }, - { name: 'filter', type: 'array', label: 'Filter' }, - { name: 'format', type: 'enum', label: 'Format', enum: ['number', 'currency', 'percent'], defaultValue: 'number' }, - { name: 'prefix', type: 'string', label: 'Prefix' }, - { name: 'suffix', type: 'string', label: 'Suffix' }, + { name: 'object', type: 'string', required: true, description: 'Object the aggregate runs over' }, + { name: 'aggregate', type: 'enum', enum: ['count', 'sum', 'avg', 'min', 'max'], required: true }, + { name: 'field', type: 'string', description: 'Measure field (required for every aggregate except count)' }, + { name: 'filter', type: 'array' }, + { name: 'format', type: 'enum', enum: ['number', 'currency', 'percent'] }, + { name: 'prefix', type: 'string' }, + { name: 'suffix', type: 'string' }, ], }); diff --git a/packages/components/src/renderers/basic/html-elements.tsx b/packages/components/src/renderers/basic/html-elements.tsx index 5149d3dc4d..20e13438eb 100644 --- a/packages/components/src/renderers/basic/html-elements.tsx +++ b/packages/components/src/renderers/basic/html-elements.tsx @@ -50,24 +50,24 @@ const TAGS = [ 'figure', 'figcaption', 'img', 'hr', 'br', 'time', 'address', 'cite', 'q', ] as const; -const PER_TAG_INPUTS: Record> = { +const PER_TAG_INPUTS: Record> = { a: [ - { name: 'href', type: 'string', label: 'Link URL' }, - { name: 'target', type: 'string', label: 'Target' }, - { name: 'rel', type: 'string', label: 'Rel' }, - { name: 'title', type: 'string', label: 'Title' }, + { name: 'href', type: 'string' }, + { name: 'target', type: 'string' }, + { name: 'rel', type: 'string' }, + { name: 'title', type: 'string' }, ], img: [ - { name: 'src', type: 'string', label: 'Image URL' }, - { name: 'alt', type: 'string', label: 'Alt text' }, - { name: 'width', type: 'number', label: 'Width' }, - { name: 'height', type: 'number', label: 'Height' }, - { name: 'title', type: 'string', label: 'Title' }, + { name: 'src', type: 'string' }, + { name: 'alt', type: 'string' }, + { name: 'width', type: 'number' }, + { name: 'height', type: 'number' }, + { name: 'title', type: 'string' }, ], - time: [{ name: 'dateTime', type: 'string', label: 'Datetime' }], - abbr: [{ name: 'title', type: 'string', label: 'Title' }], - q: [{ name: 'cite', type: 'string', label: 'Cite' }], - blockquote: [{ name: 'cite', type: 'string', label: 'Cite' }], + time: [{ name: 'dateTime', type: 'string' }], + abbr: [{ name: 'title', type: 'string' }], + q: [{ name: 'cite', type: 'string' }], + blockquote: [{ name: 'cite', type: 'string' }], }; function sanitizeHref(value: unknown): string | undefined { @@ -182,7 +182,7 @@ for (const tag of TAGS) { label: tag.toUpperCase(), category: 'basic', inputs: [ - { name: 'className', type: 'string', label: 'CSS Class' }, + { name: 'className', type: 'string' }, ...(PER_TAG_INPUTS[tag] ?? []), ], }); diff --git a/packages/components/src/renderers/basic/html.tsx b/packages/components/src/renderers/basic/html.tsx index 4b06470f59..b2a9adc072 100644 --- a/packages/components/src/renderers/basic/html.tsx +++ b/packages/components/src/renderers/basic/html.tsx @@ -37,7 +37,7 @@ ComponentRegistry.register('html', namespace: 'ui', label: 'HTML Content', inputs: [ - { name: 'html', type: 'string', label: 'HTML', description: 'Raw HTML content' } + { name: 'html', type: 'string', description: 'Raw HTML content' } ] } ); diff --git a/packages/components/src/renderers/basic/icon.tsx b/packages/components/src/renderers/basic/icon.tsx index d9b8ad55f4..730c7fdff2 100644 --- a/packages/components/src/renderers/basic/icon.tsx +++ b/packages/components/src/renderers/basic/icon.tsx @@ -264,10 +264,10 @@ ComponentRegistry.register('icon', // // The `name:` on the left is the INPUT DESCRIPTOR's own key — which // schema property this input edits. Its value is what changed. - { name: 'icon', type: 'string', label: 'Icon Name', defaultValue: 'face-slightly-smiling' }, - { name: 'size', type: 'number', label: 'Size (px)' }, - { name: 'color', type: 'string', label: 'Color Class' }, - { name: 'className', type: 'string', label: 'CSS Class' } + { name: 'icon', type: 'string' }, + { name: 'size', type: 'number' }, + { name: 'color', type: 'string' }, + { name: 'className', type: 'string' } ] } ); diff --git a/packages/components/src/renderers/basic/image.tsx b/packages/components/src/renderers/basic/image.tsx index 95d1695ee4..73342ac147 100644 --- a/packages/components/src/renderers/basic/image.tsx +++ b/packages/components/src/renderers/basic/image.tsx @@ -38,9 +38,9 @@ ComponentRegistry.register('image', icon: 'image', category: 'basic', inputs: [ - { name: 'src', type: 'string', label: 'Source URL' }, - { name: 'alt', type: 'string', label: 'Alt Text' }, - { name: 'className', type: 'string', label: 'Classes' } + { name: 'src', type: 'string' }, + { name: 'alt', type: 'string' }, + { name: 'className', type: 'string' } ], defaultProps: { className: 'max-w-full h-auto' diff --git a/packages/components/src/renderers/basic/navigation-menu.tsx b/packages/components/src/renderers/basic/navigation-menu.tsx index 80ea582b75..132cf3b542 100644 --- a/packages/components/src/renderers/basic/navigation-menu.tsx +++ b/packages/components/src/renderers/basic/navigation-menu.tsx @@ -69,7 +69,7 @@ ComponentRegistry.register('navigation-menu', namespace: 'ui', label: 'Navigation Menu', inputs: [ - { name: 'className', type: 'string', label: 'CSS Class' } + { name: 'className', type: 'string' } ], defaultProps: { items: [ diff --git a/packages/components/src/renderers/basic/pagination.tsx b/packages/components/src/renderers/basic/pagination.tsx index 5c874cdaeb..0b668438df 100644 --- a/packages/components/src/renderers/basic/pagination.tsx +++ b/packages/components/src/renderers/basic/pagination.tsx @@ -97,9 +97,9 @@ ComponentRegistry.register('pagination', namespace: 'ui', label: 'Pagination', inputs: [ - { name: 'currentPage', type: 'number', label: 'Current Page', defaultValue: 1 }, - { name: 'totalPages', type: 'number', label: 'Total Pages', defaultValue: 10 }, - { name: 'className', type: 'string', label: 'CSS Class' } + { name: 'currentPage', type: 'number' }, + { name: 'totalPages', type: 'number' }, + { name: 'className', type: 'string' } ], defaultProps: { currentPage: 1, diff --git a/packages/components/src/renderers/basic/record-picker.tsx b/packages/components/src/renderers/basic/record-picker.tsx index 4b7f69118d..ca16cc4bff 100644 --- a/packages/components/src/renderers/basic/record-picker.tsx +++ b/packages/components/src/renderers/basic/record-picker.tsx @@ -342,7 +342,7 @@ ComponentRegistry.register('record_picker', elementDataSourceBlock(ElementRecord // `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`, whose // explicit exemption for this key is deleted by the same change. inputs: [ - { name: 'object', type: 'string', label: 'Object' }, + { name: 'object', type: 'string' }, { name: 'filter', // `'object'` is the spec's shape, not a chosen arm. `filter` is @@ -361,15 +361,14 @@ ComponentRegistry.register('record_picker', elementDataSourceBlock(ElementRecord // same reason once its render site learned to resolve both // (objectui#5590). type: 'object', - label: 'Filter', // Taken from what the renderer DOES with the key, because the one thing // an author cannot read off the spec is which of the two places they may // write a filter actually wins. description: 'Filter criteria narrowing which records the picker offers, as a spec FilterCondition object — `{ status: "open" }`, or `{ $and: [ … ] }` for a group. It becomes the `$filter` of the picker\'s own query, so it decides which records exist for the user, not merely how they are shown. PRECEDENCE: a node-level `dataSource` binding wins outright. The renderer reads `dataSource.filter ?? filter`, so when the binding — or the saved view its `view` names, which AND-combine with each other because the spec calls the binding\'s filter *additional* — supplies a filter, THIS key is dropped entirely rather than merged into it; it applies only when the node carries no `dataSource`, or that `dataSource` and its view both leave `filter` unset. A rule ARRAY (an ObjectQL AST, or a view\'s rule list) is not a FilterCondition and the spec rejects it here.', }, - { name: 'labelField', type: 'string', label: 'Label Field' }, - { name: 'valueField', type: 'string', label: 'Value Field' }, + { name: 'labelField', type: 'string' }, + { name: 'valueField', type: 'string' }, { name: 'placeholder', // TWO arms, declared in the change that makes the second one render — the @@ -383,7 +382,6 @@ ComponentRegistry.register('record_picker', elementDataSourceBlock(ElementRecord // object arm would be the opposite defect — `type-mismatch` reported on a // legal write this input's own description teaches (objectui#5637). type: ['string', 'object'], - label: 'Placeholder', description: 'Prompt shown in the closed control while no record is selected (renderer default "Select a record…"). Display-only — it never reaches the query. Accepts either a plain string or an inline per-locale map (`{ en: "Owner", "zh-CN": "负责人" }`), the `I18nLabel` union rc.6 widened this key to; the renderer resolves the map against the active language at the read site, falling back through base language, a region-qualified sibling, `default`, then `en`. It is REPLACED while the picker is busy: "Loading…" during the fetch and "Failed to load" after an error both win over this key. An authored empty string stays empty; the default applies only when the key is absent.', }, @@ -397,7 +395,6 @@ ComponentRegistry.register('record_picker', elementDataSourceBlock(ElementRecord // would have advertised a shape that reached the screen wrong or not at // all; the read site resolves it now (objectui#5637). type: ['string', 'object'], - label: 'Label', description: 'Caption rendered above the picker, in a `