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
3 changes: 3 additions & 0 deletions .changeset/10719-retire-phase35-validation-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ object-level rule union `ObjectValidationRule` and its variants, and
the zero-reader reading behind the ruling covers this repository and the
ObjectStack framework only; a host application that imports any of the eight
names stops compiling on upgrade.

⚠️ **Dated note, 2026-10-02 — `DesignerValidationRule` is retired in this release too — objectui#11434.**
Later in this same release objectui#11434 retired `DesignerFieldDefinition.validationRules` on both faces and removed its element type `DesignerValidationRule` from the exports, so the sentence above that lists it among the names that stay exported no longer holds for it. `FieldValidationRules`, `FieldValidationFunction` and `ObjectValidationRule` still stay exported. The rest of this entry is kept as the reading of this change.
3 changes: 3 additions & 0 deletions .changeset/10859-designer-zod-arms.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ Migration: delete `variables` from your `process-designer` nodes and `parameters
- `@object-ui/types/zod` exports `PageDesignerSchema`, `DataModelDesignerSchema`, `ProcessDesignerSchema`, `ReportDesignerSchema`, `ObjectManagerSchema` and `FieldDesignerSchema`, each a mirror of its TypeScript declaration in `designer.ts`, and the mirrors of the record types those nodes hold (`DesignerComponentSchema`, `DesignerCanvasConfigSchema`, `BPMNNodeSchema`, `ReportDesignerSectionSchema`, `ObjectDefinitionSchema`, `DesignerFieldDefinitionSchema` and the rest). They reach `AnyComponentSchema` through `DesignerUnionSchema`, so `safeValidateSchema`, `validateSchema`, the strict authoring face and `objectui validate` now judge a designer node by its own members instead of refusing it with `invalid_union` at `type`.
- Each arm requires what its declaration requires (for example `processName`, `nodes` and `edges` on `process-designer`), and refuses `body` and `children` by name, because none of the six components reads a content channel (objectui#9256).
- Four declared members that no designer reads are NOT on the arms: `autoLayout` on `data-model-designer`, `lanes` and `version` on `process-designer`, and `previewMode` on `report-designer`. They stay declared on the TypeScript face. The strict face refuses them as unrecognized keys, and the tolerant face passes them through unjudged.

⚠️ **Dated note, 2026-10-02 — two of the four unmirrored members, and seven members in all, are retired in this release — objectui#11434.**
Later in this same release objectui#11434 retired `autoLayout` (`data-model-designer`) and `previewMode` (`report-designer`) on both faces: each is now a `?: never` tombstone on the TypeScript face and refused BY NAME on the arms, not as an unrecognized key. The same change retired the record members `DesignerComponent.parentId`, `DataModelRelationship.onUpdate`, `BPMNNode.serviceEndpoint`, `ObjectDefinition.relationships` and `DesignerFieldDefinition.validationRules`, and removed the record mirrors `ObjectDefinitionRelationshipSchema` and `DesignerValidationRuleSchema` with their types. `lanes` and `version` on `process-designer` are still unmirrored. The rest of this entry is kept as the reading of this change.
5 changes: 5 additions & 0 deletions .changeset/11434-app-shell-relationships-note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@object-ui/app-shell': patch
---

`MetadataService` changes in comments only: its notes on the object payload now say that `ObjectDefinition.relationships` is retired from the UI model as well as kept off the wire (objectui#11434). The payload `saveObject` writes is unchanged.
44 changes: 44 additions & 0 deletions .changeset/11434-designer-members-retired.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
'@object-ui/types': minor
---

**BREAKING (authoring)** — seven members of the `@object-ui/plugin-designer` node declarations and their record types are retired on both faces, and two element types leave the package (objectui#11434, ADR-0049).

**Clause-②: yes** — authorable members of published `@object-ui/types` declarations narrow to `never`. Scored `minor`, not `major`: this repository scores its own breaking changes `minor` and spells the breaking semantics out in the body (`check:changeset-no-major`).

**Why.** objectui#11434 settled every declared member of this family that no designer reads, by the maintainer's criterion: "does the mainstream have it? Yes ⇒ give it a reader. No ⇒ retire it on both faces." Each member below was measured unread — by source and by a runtime probe through the real `SchemaRenderer` and registry, which drew the same markup with the member set and unset — and nothing in this repository or in ObjectStack authored it outside test fixtures. The seat ruled each one RETIRE. The members ruled READ keep their declarations; their readers ship in later changes of the same card.

**What changes.** Each member is now a `?: never` tombstone on the TypeScript face, so `tsc` refuses it by name. On the zod face it is a retirement tombstone, so `safeValidateSchema`, the strict authoring face and `objectui validate` refuse it by name with the reason and what to write instead. Before this change the zod face refused `autoLayout` and `previewMode` only as unrecognized keys on the strict face, and accepted the other five. The renderers are untouched: every member was ignored at render before, and still is.

**Migration — one line per member.**

- `DataModelDesignerSchema.autoLayout`: delete it. Auto-layout is an action; use the data-model designer's toolbar Auto Layout button.
- `ReportDesignerSchema.previewMode`: delete it. Preview is a mode of the tool, not report state; for a chrome-free, non-editable layout author `readOnly: true`, `showToolbar: false` and `showPropertyPanel: false`.
- `DesignerComponent.parentId`: delete it, and nest the child in its parent's `children` array, the tree's one spelling.
- `DataModelRelationship.onUpdate`: delete it. The platform's relationship contract has no update behaviour, so there is nothing to configure in its place.
- `BPMNNode.serviceEndpoint`: delete it; there is nothing to configure in its place. A service task references an implementation, not a URL.
- `ObjectDefinition.relationships`: delete it, and declare each relationship on the referencing field instead — in the designer, a `lookup` field whose `referenceTo` names the related object (in `@objectstack/spec` metadata, a `lookup` / `master_detail` field whose `reference` names it). `@objectstack/spec`'s `ObjectSchema` already refused an object-level `relationships` array as an unrecognized key, and `MetadataService` never put it on the wire.
- `DesignerFieldDefinition.validationRules`: delete it. Put bounds on the field as `min` / `max` / `minLength` / `maxLength` in the field metadata, and any other rule in the object's `validations`. `@objectstack/spec`'s `FieldSchema` refuses `validationRules` as an unrecognized key, and no converter carried it.

**Removed exports.** `ObjectDefinitionRelationship` and `DesignerValidationRule` typed only the two retired arrays above, so they leave the root barrel, and their zod mirrors `ObjectDefinitionRelationshipSchema` and `DesignerValidationRuleSchema` leave `@object-ui/types/zod`. A consumer that imported one gets a compile error naming it; drop the import along with the retired key.

```ts
// before: compiled, and the relationship list reached nothing
const account: ObjectDefinition = {
id: 'account',
name: 'account',
label: 'Account',
relationships: [{ relatedObject: 'contact', type: 'one-to-many' }],
};

// after: the relationship is a field on the referencing object
const contactAccount: DesignerFieldDefinition = {
id: 'account',
name: 'account',
label: 'Account',
type: 'lookup',
referenceTo: 'account',
};
```

⚠️ **The census behind "read by nothing" is the in-repo half.** Every package, app, example, doc and skill in this repository, and the ObjectStack framework, was searched: none reads these members, and only test fixtures authored them. Customer applications and documents outside both were not enumerated. A TypeScript consumer that authored one of these keys gets a compile error naming it.
7 changes: 7 additions & 0 deletions .changeset/11434-i18n-validation-rule-labels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@object-ui/i18n': patch
---

The ten locale packs drop `appDesigner.fieldDesigner.validationRules` ("Validation Rules") and `appDesigner.fieldDesigner.addRule` ("Add Rule") (objectui#11434).

Both labelled an editor for `DesignerFieldDefinition.validationRules` that was never built: no component asked for either key, and `@object-ui/types` retires that member on both faces in this release. A host that looked either key up itself now gets the key back instead of a label; author its own label if it needs one.
14 changes: 14 additions & 0 deletions .changeset/11434-plugin-designer-retired-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@object-ui/plugin-designer': minor
---

**BREAKING (TypeScript props)** — `DataModelDesignerProps.autoLayout` and `ReportDesignerProps.previewMode` are removed, and the `data-model-designer` registration no longer publishes `autoLayout` as an input (objectui#11434).

Neither prop was ever read: `DataModelDesigner` arranges entities only when the toolbar's Auto Layout button is pressed, and `ReportDesigner` has no preview mode. `@object-ui/types` retires the matching node members in the same release, so the component face, the registration and both node faces now agree.

**Migration.**

- `<DataModelDesigner autoLayout />`: drop the prop; use the toolbar's Auto Layout button. The package README's example no longer passes it.
- `<ReportDesigner previewMode />`: drop the prop; for a chrome-free, non-editable layout pass `readOnly`, `showToolbar={false}` and `showPropertyPanel={false}`.

The Field Designer's defaults map also drops the labels `appDesigner.fieldDesigner.validationRules` and `appDesigner.fieldDesigner.addRule`, which named an editor that was never built (see the `@object-ui/i18n` note of this release). Nothing renders differently.
7 changes: 4 additions & 3 deletions content/docs/plugins/plugin-form.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,10 @@ is bound to react-hook-form, whose initial values come from the form.

#### `validation` is an object keyed by rule name

There is no `ValidationRule` type in this repository. Near-spellings do exist —
`ObjectValidationRule` and `DesignerValidationRule` — and neither of them types
this key. The type of the key
There is no `ValidationRule` type in this repository. A near-spelling does exist —
`ObjectValidationRule`, the object-level rule union — and it does not type this
key. (`DesignerValidationRule`, a second one, left the package with
objectui#11434.) The type of the key
is `FieldValidationRules` (`packages/types/src/form.ts`), and it is **not** an
array of `{ type, value, message }` entries:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,11 @@ const unrecognizedKeys = (result: ReturnType<typeof ObjectSchema.safeParse>): st
const BASE = { name: 'account', label: 'Account', fields: { n: { type: 'text', label: 'N' } } };

/**
* An object as the Object Manager holds it, with all three UI-only keys
* populated — the state a designer save actually starts from.
* An object as the Object Manager holds it, with both UI-only keys populated —
* the state a designer save actually starts from. The third key objectui#6223
* kept off the wire, `relationships`, is no longer an `ObjectDefinition` member
* at all (objectui#11434 retired it, a `?: never` tombstone); {@link LEGACY}
* below hands one in from outside the type system.
*/
const MANAGED: ObjectDefinition = {
id: 'account',
Expand All @@ -100,11 +103,20 @@ const MANAGED: ObjectDefinition = {
sortOrder: 3,
isSystem: false,
fieldCount: 1,
relationships: [
{ relatedObject: 'contact', type: 'one-to-many', label: 'Contacts', foreignKey: 'account_id' },
],
};

/**
* {@link MANAGED} plus an object-level `relationships` array, the way a caller
* outside the type system (a JavaScript host, state saved before objectui#11434
* retired the member) can still hand one to `saveObject`. The cast is the point:
* `tsc` now refuses the key on `ObjectDefinition`, and this value is what proves
* the converter's explicit field list, not the type, keeps it off the wire.
*/
const LEGACY = {
...MANAGED,
relationships: [{ relatedObject: 'contact', type: 'one-to-many', label: 'Contacts', foreignKey: 'account_id' }],
} as unknown as ObjectDefinition;

async function putFor(obj: ObjectDefinition = MANAGED): Promise<Record<string, unknown>> {
const { adapter, puts } = makeCapturingAdapter();
await new MetadataService(adapter).saveObject(obj, [{ name: 'name', type: 'text', label: 'Name' }]);
Expand Down Expand Up @@ -207,21 +219,24 @@ describe('objectui#6223 · `sortOrder` — list order, not object metadata', ()

describe('objectui#6223 · `relationships` — the spec models these on the FIELD', () => {
it('does not put `relationships` on the wire even when the object carries them', async () => {
const put = await putFor();
// Non-vacuity: the input really carries the key, through the cast LEGACY documents.
expect('relationships' in LEGACY).toBe(true);
const put = await putFor(LEGACY);
expect('relationships' in put).toBe(false);
expect(put.name).toBe('account');
});

it('and `ObjectSchema` reports no `relationships` among the refused keys of that body', async () => {
expect(unrecognizedKeys(ObjectSchema.safeParse(await putFor()))).not.toContain('relationships');
expect(unrecognizedKeys(ObjectSchema.safeParse(await putFor(LEGACY)))).not.toContain('relationships');
});
});

describe('objectui#6223 · the whole body, and the honest limit of this fix', () => {
it('carries NO key `ObjectSchema` refuses by name', async () => {
// The claim of this card, stated once over the whole document rather than
// key by key. Before the fix this was ["group", "sortOrder", "relationships"].
expect(unrecognizedKeys(ObjectSchema.safeParse(await putFor()))).toEqual([]);
// key by key. Before the fix this was ["group", "sortOrder", "relationships"];
// LEGACY carries all three.
expect(unrecognizedKeys(ObjectSchema.safeParse(await putFor(LEGACY)))).toEqual([]);
});

it('still carries everything the spec DOES accept — the fix removed keys, it did not empty the payload', async () => {
Expand Down
22 changes: 13 additions & 9 deletions packages/app-shell/src/services/MetadataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ export interface ObjectMetadataPayload {
fields?: Record<string, FieldMetadataPayload>;
// No `relationships` (objectui#6223): the spec models relationships on the
// FIELD — `reference` / `master_detail` plus object-level `indexes` — and
// `ObjectSchema` refuses an object-level `relationships` array by name. What
// the designer should author for a relationship is a data-model question
// that this card does not settle; what it settles is that this shape must
// stop putting the key on the wire.
// `ObjectSchema` refuses an object-level `relationships` array as an
// unrecognized key. This shape stopped putting the key on the wire there;
// objectui#11434 then retired `ObjectDefinition.relationships` from the UI
// model too (a `?: never` tombstone), so a relationship is authored as a
// reference field.
}

/** Shape written to the metadata API for a field definition. */
Expand Down Expand Up @@ -159,12 +160,15 @@ export interface FieldMetadataPayload {
/**
* Convert an `ObjectDefinition` (UI) to the API payload shape.
*
* `ObjectDefinition` carries three keys that deliberately do NOT cross into the
* `ObjectDefinition` carries two keys that deliberately do NOT cross into the
* payload (objectui#6223): `group` and `sortOrder` are the Object Manager's own
* display category and display order, and `relationships` has no object-level
* home in the spec. `ObjectSchema` refuses all three BY NAME, so copying them
* across is what turned a designer save into a 422. The UI model keeps them;
* the wire shape does not.
* display category and display order. `ObjectSchema` refuses both as
* unrecognized keys, so copying them across is what turned a designer save
* into a 422. The UI model keeps them; the wire shape does not. A third, `relationships`, has no
* object-level home in the spec either: objectui#6223 kept it off the wire, and
* objectui#11434 retired it from the UI model as well. The explicit field list
* below is what keeps a value handed in from outside the type system off the
* wire.
*/
function toObjectPayload(obj: ObjectDefinition, fields?: FieldMetadataPayload[]): ObjectMetadataPayload {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
* "these survive" against keys that may themselves be dead would bake an
* unverified claim into the guard. Every key in {@link SURVIVING} was confirmed
* live by call site in `packages/plugin-designer/src/FieldDesigner.tsx`.
* objectui#11434 later settled two of the eight: `validationRules` and
* `addRule` labelled an editor for `DesignerFieldDefinition.validationRules`
* that was never built, and left the ten packs and the designer defaults map
* when that member was retired.
*/
import { describe, it, expect } from 'vitest';
import { builtInLocales } from '../locales/index';
Expand Down
Loading
Loading