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
21 changes: 21 additions & 0 deletions .changeset/7470-designer-objectname-not-required.md
Original file line number Diff line number Diff line change
@@ -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).
6 changes: 4 additions & 2 deletions packages/plugin-gantt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
77 changes: 77 additions & 0 deletions packages/plugin-gantt/src/__tests__/recordSourceInput-7470.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
7 changes: 6 additions & 1 deletion packages/plugin-gantt/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
],
});
Expand Down
91 changes: 91 additions & 0 deletions packages/plugin-map/src/index.recordSourceInput-7470.test.tsx
Original file line number Diff line number Diff line change
@@ -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);
});
});
9 changes: 7 additions & 2 deletions packages/plugin-map/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,17 @@ export const ObjectMapRenderer: React.FC<any> = 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' },
],
});
Expand All @@ -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' },
],
});
Loading