diff --git a/.changeset/7470-designer-objectname-not-required.md b/.changeset/7470-designer-objectname-not-required.md new file mode 100644 index 0000000000..1b23a1f266 --- /dev/null +++ b/.changeset/7470-designer-objectname-not-required.md @@ -0,0 +1,21 @@ +--- +'@object-ui/plugin-map': patch +'@object-ui/plugin-gantt': patch +--- + +fix(plugin-map, plugin-gantt): the registrations no longer declare `objectName` a required input + +Both renderers read their records from one of three keys (`data`, then +`staticData`, then `objectName`), and the `object-map` / `object-gantt` zod schemas +enforce "one of the three" through `requireRecordSource` (objectui#6939). The +registrations' designer-facing `inputs` still said `objectName` was required, on +`object-map`, `map` and `object-gantt`, so the html tier's validator reported a +`missing-required-prop` error on a block authored on `staticData` or `data` that +the schema accepts and the renderer draws. + +`objectName` is now declared without `required`, and its description states the +rule: the record source is one of `data`, `staticData` and `objectName`, and the +schema refuses a block that declares none of them. No one-of form was added to the +input declaration; the zod refinement stays the place that enforces it. The +renderers' read order is unchanged. The `@object-ui/plugin-gantt` README sentence +that called `objectName` required is corrected (objectui#7470). diff --git a/packages/plugin-gantt/README.md b/packages/plugin-gantt/README.md index 293e6a9d7a..12e9804616 100644 --- a/packages/plugin-gantt/README.md +++ b/packages/plugin-gantt/README.md @@ -138,8 +138,10 @@ claim these schema types: | `object-gantt` | `plugin-gantt:object-gantt` | `ObjectGanttRenderer` | Both spellings of the surviving key resolve — `register` stores the namespaced key -*and* a bare-`type` fallback. It declares two inputs: `objectName` (required) and -the `gantt` configuration object. +*and* a bare-`type` fallback. It declares two inputs: `objectName` and the `gantt` +configuration object. `objectName` is not a required input: the record source is +one of `data`, `staticData` and `objectName` (read in that order), and the +`object-gantt` schema refuses a block that declares none of them. > **The bare `gantt` key is retired** (objectui#8008, ruled 2026-09-09). This > table used to carry a second row, `gantt` / `view:gantt`, on the same renderer. diff --git a/packages/plugin-gantt/src/__tests__/recordSourceInput-7470.test.ts b/packages/plugin-gantt/src/__tests__/recordSourceInput-7470.test.ts new file mode 100644 index 0000000000..0b2196208d --- /dev/null +++ b/packages/plugin-gantt/src/__tests__/recordSourceInput-7470.test.ts @@ -0,0 +1,77 @@ +/** + * 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. + */ + +/** + * objectui#7470 — the designer face stops claiming `objectName` is required. + * + * `ObjectGantt`'s `getDataConfig` reads `data`, then `staticData`, then + * `objectName`, and the `object-gantt` zod schema carries `requireRecordSource` + * (objectui#6939): one of the three must be present, none is required alone. + * The registration still declared `objectName` with `required: true`, so the + * registry said the opposite of the schema — and `sdui-parser`'s `validateTree` + * turns a required input into a `missing-required-prop` ERROR, which a + * `staticData`-only gantt drew while the schema accepted it. + * + * Ruled (letter align): drop `required`, state the one-of rule in the + * description, add no one-of vocabulary to the input declaration. + * + * `object-gantt` is the one registration key: the bare `gantt` key is retired + * (objectui#8008, pinned by `bare-gantt-node-key-retired-8008.test.ts`). + * + * Rows: + * 1. `objectName` is declared (non-vacuity) and is not required. + * 2. Its description names all three record sources. + * 3. The description's claim about the schema is TRUE: `ObjectGanttSchema` + * refuses a block with none of the three with the refinement's own code, + * and accepts a block authored on `staticData` alone. + */ + +import { describe, it, expect } from 'vitest'; +import { ComponentRegistry } from '@object-ui/core'; +import { ObjectGanttSchema } from '@object-ui/types/zod'; +// Module scope, not a hook: this import IS the registration (AGENTS.md +// test-discipline section). +import '../index'; + +const objectNameInput = () => + ((ComponentRegistry.getConfig('object-gantt', 'plugin-gantt') as any)?.inputs ?? []).find( + (i: any) => i.name === 'objectName', + ); + +describe('objectui#7470 — the object-gantt registration does not declare objectName required', () => { + it('objectName is declared and not required', () => { + const input = objectNameInput(); + expect(input, 'object-gantt declares objectName').toBeDefined(); + expect(input.required).not.toBe(true); + }); + + it('the description names the three record sources', () => { + const description: string = objectNameInput()?.description ?? ''; + for (const key of ['`data`', '`staticData`', '`objectName`']) { + expect(description).toContain(key); + } + expect(description).toContain('`object-gantt` schema refuses'); + }); + + it('the schema the description names refuses a block with no record source', () => { + const parsed = ObjectGanttSchema.safeParse({ type: 'object-gantt' }); + expect(parsed.success).toBe(false); + const codes = parsed.success + ? [] + : parsed.error.issues.map((issue: any) => issue.params?.code); + expect(codes).toContain('RECORD_SOURCE_REQUIRED'); + }); + + it('control: the same schema accepts a block authored on staticData alone', () => { + const parsed = ObjectGanttSchema.safeParse({ + type: 'object-gantt', + staticData: [{ id: '1', title: 'Task', start: '2026-01-01', end: '2026-01-02' }], + }); + expect(parsed.success).toBe(true); + }); +}); diff --git a/packages/plugin-gantt/src/index.tsx b/packages/plugin-gantt/src/index.tsx index cf28d634e1..e42da87907 100644 --- a/packages/plugin-gantt/src/index.tsx +++ b/packages/plugin-gantt/src/index.tsx @@ -112,12 +112,17 @@ export const ObjectGanttRenderer: React.FC<{ schema: any }> = elementDataSourceB ); }); +// `objectName` is NOT a required input (objectui#7470): `getDataConfig` reads +// `data`, then `staticData`, then `objectName`, and the `object-gantt` zod +// schema (`requireRecordSource`, objectui#6939) is where "one of the three" is +// enforced. The input list is a flat declaration with a boolean `required`, so +// it states the rule in the description rather than growing a one-of form. ComponentRegistry.register('object-gantt', ObjectGanttRenderer, { namespace: 'plugin-gantt', label: 'Object Gantt', category: 'view', inputs: [ - { name: 'objectName', type: 'string', required: true }, + { name: 'objectName', type: 'string', description: 'ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-gantt` schema refuses a block that declares none of them.' }, { name: 'gantt', type: 'object', description: 'startDateField, endDateField, titleField, progressField, percentageField, colorField, dependenciesField' }, ], }); diff --git a/packages/plugin-map/src/index.recordSourceInput-7470.test.tsx b/packages/plugin-map/src/index.recordSourceInput-7470.test.tsx new file mode 100644 index 0000000000..399b21c01c --- /dev/null +++ b/packages/plugin-map/src/index.recordSourceInput-7470.test.tsx @@ -0,0 +1,91 @@ +/** + * 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. + */ + +/** + * objectui#7470 — the designer face stops claiming `objectName` is required. + * + * `ObjectMap`'s `getDataConfig` reads `data`, then `staticData`, then + * `objectName`, and the `object-map` zod schema carries `requireRecordSource` + * (objectui#6939): one of the three must be present, none is required alone. + * Both registration keys still declared `objectName` with `required: true`, so + * the registry said the opposite of the schema — and `sdui-parser`'s + * `validateTree` turns a required input into a `missing-required-prop` ERROR, + * which a `staticData`-only map drew while the schema accepted it. + * + * Ruled (letter align): drop `required`, state the one-of rule in the + * description, add no one-of vocabulary to the input declaration. + * + * Rows, per registration key: + * 1. `objectName` is declared (non-vacuity: a wrong type/namespace read fails + * here) and is not required. + * 2. Its description names all three record sources. + * 3. The description's claim about the schema is TRUE: `ObjectMapSchema` + * refuses a block with none of the three with the refinement's own code, + * and accepts a block authored on `staticData` alone. + */ + +import { describe, it, expect, vi } from 'vitest'; + +// `src/index.tsx` pulls in the real map bindings; stub them so a plain module +// import needs no WebGL canvas (same stub as `index.registration.test.tsx`). +vi.mock('react-map-gl/maplibre', () => ({ + default: () => null, + Map: () => null, + NavigationControl: () => null, + Marker: () => null, + Popup: () => null, +})); + +import { ComponentRegistry } from '@object-ui/core'; +import { ObjectMapSchema } from '@object-ui/types/zod'; +// Module scope, not a hook: this import IS the registration (AGENTS.md +// test-discipline section). +import './index'; + +const MAP_KEYS = [ + { label: 'object-map', type: 'object-map', namespace: 'plugin-map' }, + { label: 'view:map', type: 'map', namespace: 'view' }, +] as const; + +const objectNameInput = (type: string, namespace: string) => + ((ComponentRegistry.getConfig(type, namespace) as any)?.inputs ?? []).find( + (i: any) => i.name === 'objectName', + ); + +describe('objectui#7470 — map registrations do not declare objectName required', () => { + it.each(MAP_KEYS)('$label — objectName is declared and not required', ({ type, namespace }) => { + const input = objectNameInput(type, namespace); + expect(input, `${type} declares objectName`).toBeDefined(); + expect(input.required).not.toBe(true); + }); + + it.each(MAP_KEYS)('$label — the description names the three record sources', ({ type, namespace }) => { + const description: string = objectNameInput(type, namespace)?.description ?? ''; + for (const key of ['`data`', '`staticData`', '`objectName`']) { + expect(description).toContain(key); + } + expect(description).toContain('`object-map` schema refuses'); + }); + + it('the schema the description names refuses a block with no record source', () => { + const parsed = ObjectMapSchema.safeParse({ type: 'object-map' }); + expect(parsed.success).toBe(false); + const codes = parsed.success + ? [] + : parsed.error.issues.map((issue: any) => issue.params?.code); + expect(codes).toContain('RECORD_SOURCE_REQUIRED'); + }); + + it('control: the same schema accepts a block authored on staticData alone', () => { + const parsed = ObjectMapSchema.safeParse({ + type: 'object-map', + staticData: [{ id: '1', lat: 1, lng: 2 }], + }); + expect(parsed.success).toBe(true); + }); +}); diff --git a/packages/plugin-map/src/index.tsx b/packages/plugin-map/src/index.tsx index a673652637..524bb2d135 100644 --- a/packages/plugin-map/src/index.tsx +++ b/packages/plugin-map/src/index.tsx @@ -59,12 +59,17 @@ export const ObjectMapRenderer: React.FC = elementDataSourceBlock(({ schema ); }); +// `objectName` is NOT a required input (objectui#7470): `getDataConfig` reads +// `data`, then `staticData`, then `objectName`, and the `object-map` zod schema +// (`requireRecordSource`, objectui#6939) is where "one of the three" is +// enforced. The input list is a flat declaration with a boolean `required`, so +// it states the rule in the description rather than growing a one-of form. ComponentRegistry.register('object-map', ObjectMapRenderer, { namespace: 'plugin-map', label: 'Object Map', category: 'view', inputs: [ - { name: 'objectName', type: 'string', required: true }, + { name: 'objectName', type: 'string', description: 'ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-map` schema refuses a block that declares none of them.' }, { name: 'map', type: 'object', description: 'latitudeField, longitudeField, titleField' }, ], }); @@ -74,7 +79,7 @@ ComponentRegistry.register('map', ObjectMapRenderer, { label: 'Map View', category: 'view', inputs: [ - { name: 'objectName', type: 'string', required: true }, + { name: 'objectName', type: 'string', description: 'ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-map` schema refuses a block that declares none of them.' }, { name: 'map', type: 'object', description: 'latitudeField, longitudeField, titleField' }, ], });