From 2083a24f849f2c13cc5e3242cc710f63380c2e4c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 21:23:34 +0000 Subject: [PATCH] fix(plugin-map, plugin-gantt): objectName is not a required registration input The registrations of object-map, map and object-gantt declared objectName required while the renderers read data, staticData or objectName and the zod schemas enforce one of the three via requireRecordSource. Drop required, state the rule in the input description, and correct the plugin-gantt README. Co-Authored-By: Claude --- .../7470-designer-objectname-not-required.md | 21 +++++ packages/plugin-gantt/README.md | 6 +- .../__tests__/recordSourceInput-7470.test.ts | 77 ++++++++++++++++ packages/plugin-gantt/src/index.tsx | 7 +- .../src/index.recordSourceInput-7470.test.tsx | 91 +++++++++++++++++++ packages/plugin-map/src/index.tsx | 9 +- 6 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 .changeset/7470-designer-objectname-not-required.md create mode 100644 packages/plugin-gantt/src/__tests__/recordSourceInput-7470.test.ts create mode 100644 packages/plugin-map/src/index.recordSourceInput-7470.test.tsx 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' }, ], });