Skip to content

Commit 607d3bb

Browse files
refactor(types)!: derive the object-form and object-calendar spec keys, rename the legacy zod ActionSchema (#25923) (#26751)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBAakEo5aeRDTLTgR7gcMAuq)_ Part of #25923 (work item 4 only; item 3 and the FormField follow-up stay on the card) Clause-②: yes (narrowing) Metadata-protocol: narrowed Skills-docs: none — `skills/objectstack-ui/references/react-blocks.md` already teaches `contentLayout` and `confirmOnDiscard`; object-calendar `locale` and `loading` are taught only by the spec rows' `.describe()` text, with no `skills/**` or `ui/content/docs` page; no `skills/**` page names the zod `ActionSchema`, and the two ui docs pages that imported it are updated in this diff ## 维护者速读 1. 卡面"33 个 spec-only 键"实测是同名碰撞:spec `ActionSchema` 对 objectui 无关的旧 `type: 'action'` 节点 schema。真实配对上的缺口是 11 个键。 2. 4 个有渲染器读取的键按引用派生:`ObjectFormSchema.contentLayout`/`confirmOnDiscard`、`ObjectCalendarSchema.locale`/`loading`(均为节点键,不是 #26411 移走的日历字段名键)。 3. 其余 7 个是 `FormFieldSchema` 上 spec 表单视图条目的键(两层同名词汇),逐键登记在 `zod-mirror-parity.test.ts` 的普查里:2 translated、4 carried-not-rendered、1 spec-retired。 4. `@object-ui/types/zod` 的 `ActionSchema` 改名 `CRUDActionSchema`:0 个生产导入方,改名比退役破坏最少;接受集不变。 5. 收窄:旧导出名消失,宽容面对 4 个键的坏值现在按 spec 拒绝 ⇒ `Clause-②: yes (narrowing)`、`Metadata-protocol: narrowed`,changeset 带 BREAKING Before/Now/Fix。 6. 门禁:dispatch-gates 34/34 exit 0 已对账;ui vitest(types、plugin-form、plugin-calendar)660/660 文件通过;依赖 `@object-ui/types` 的 39 个包 type-check 108/108 通过。 ## Measurement (main `43731031`) The key-drift probe pairs each `@object-ui/types/zod` mirror with the spec schema of the same export name in `@objectstack/spec/data` ∪ `@objectstack/spec/ui`, and each node mirror with its `ComponentPropsMap` row by `type` literal. A key the spec declares that the mirror neither declares nor refuses is "spec-only". | Pair | Spec-only keys on main | Note | |---|---|---| | `ActionSchema` ↔ spec `ActionSchema` | 33 | Not a pair: objectui's `ActionSchema` is the legacy `type: 'action'` node and shares no vocabulary with the spec's action metadata. This is the card's "33". | | `FormFieldSchema` ↔ spec `FormFieldSchema` | 7 | Two layers under one name (runtime field vs form-view entry) | | `ObjectFormSchema` ↔ `ComponentPropsMap['object-form']` | 2 | | | `ObjectCalendarSchema` ↔ `ComponentPropsMap['object-calendar']` | 2 | | | every other pair | 0 | | Real gap over the real pairs: **11 keys**. After this PR: **0 unrecorded**, judged by the new census in `zod-mirror-parity.test.ts`. ## Per-key disposition | Key | Disposition | Where | |---|---|---| | `ObjectFormSchema.contentLayout` | derived by reference (`SpecObjectFormProps['contentLayout']`, zod `.shape.contentLayout`) | `ModalForm` reads it | | `ObjectFormSchema.confirmOnDiscard` | derived by reference | `ModalForm`, `DrawerForm` read it | | `ObjectCalendarSchema.locale` | derived by reference | `plugin-calendar` `resolveAuthoredLocale(rest.locale)` | | `ObjectCalendarSchema.loading` | derived by reference | `plugin-calendar` `resolveExternalLoading(rest.loading)` | | `FormFieldSchema.field` | recorded: translated | `normalizeSectionField` writes it as the runtime `name` | | `FormFieldSchema.helpText` | recorded: translated | written as the runtime `description` | | `FormFieldSchema.immutable` | recorded: declared, not rendered | copied onto the runtime field; no renderer reads it | | `FormFieldSchema.keyField` | recorded: declared, not rendered | copied through for the record widget; no widget reads it yet | | `FormFieldSchema.disclosure` | recorded: declared, not rendered | copied through for composite widgets; no widget reads it yet | | `FormFieldSchema.precision` | recorded: declared, not rendered | number widgets read `scale` and the object field's metadata | | `FormFieldSchema.publicPicker` | recorded: spec-retired | the spec's own `retiredKey()` tombstone refuses it with its remedy | No key needed a new `z.never` refusal: the only retired key (`publicPicker`) is already refused by the spec with its remedy, and objectui's runtime layer never had it. Seat guard on the two calendar keys: `locale` and `loading` are members the spec authors on the `object-calendar` node and the renderer reads off the node. They are not the calendar field-name keys PR #26411 moved to the hand-off type, so nothing returns to the published face that item 1 took off it. ## ActionSchema: rename, not retire Importers of the zod `ActionSchema` on main: **0 production**, 7 test files, 2 docs pages. Retiring it would also break the `action` arm of the CRUD discriminated union and the `actions` arrays of `CRUDDialogSchema`, `DetailSchema` and the chain, which all hold it. Renaming changes only the export name, so it breaks fewest. The TypeScript twin keeps its deprecated `ActionSchema` name in `@object-ui/types`. ## What changes - `ui/packages/types/src/zod/objectql.zod.ts`, `ui/packages/types/src/objectql.ts`: the four keys, by reference on both faces; `ObjectFormSlotKey` carries the two form keys. - `ui/packages/types/src/zod/crud.zod.ts`, `index.zod.ts`: `ActionSchema` → `CRUDActionSchema`, every internal reference with it. - Tests: the seven test importers renamed; `object-view-slot-key-lists.test.ts` pins `ObjectFormSchema` at 71 members (was 69). - Docs: the two pages that import the const. - `.changeset/25923-spec-only-keys-crud-action-schema.md`: `@object-ui/types` minor, BREAKING Before/Now/Fix. ## Tests `zod-mirror-parity.test.ts` is extended (no new gate) with a census, "every spec-only key over the spec pairs is derived, refused or recorded": - no spec-only key without a row; no dead row; every reason at least 40 characters; - non-vacuity: the derived pairs include the four named above; - identity pins for the four derived keys, and `contentLayout: 'tabs'` refused at path `contentLayout`; - the zod barrel has no `ActionSchema` and has `CRUDActionSchema`, while `@objectstack/spec/ui` still has `ActionSchema`. Reverse check: removing the `contentLayout` derivation turns the census red naming `ObjectFormSchema.contentLayout`; restored afterwards. ## Acceptance notes - `immutable`, `keyField`, `disclosure` and `precision` are copied by `normalizeSectionField` but no renderer reads them. That is recorded here as "declared, not rendered"; no reach was measured, so no card is filed for it. - ADR-0087 disposition is `not-required (no-migration-prescription)`. `runtime-export-retired` would fit better, but that category reads only a package's main entry and refuses an operand declared in a `*.zod.ts`, and this export is on the `./zod` subpath. - **ADR-0087 marker (seat ruling, 2026-10-27):** `not-required (no-migration-prescription)` stands, as on PR #26411. The gate cannot see `@object-ui/types` exports yet, and #26220 (`PACKAGE_ROOTS` omits `ui/packages`) carries that gap. - **`immutable` is declared on the mirror but not rendered by objectui** (contract review 6445632813 ③.5). It folds into #25923's FormField follow-up and is recorded on the card at landing. ## Verification Head `38c2841a` (readings below were taken on `96c8d55d`, with `main` `94bab5a2` merged in; `d18ba32e` adds a reasoned `eslint-disable-next-line no-restricted-imports` on the census's `import * as SpecUi from '@objectstack/spec/ui'`, the form `spec-ui-schema-reexports.test.ts` uses, which turned Lint UI green; `38c2841a` corrects `enhanced-actions.mdx` to name `CRUDActionSchema`). The vitest, gates and type-check readings below were taken at `b5f1b90b` (before the merge); after the merge, `@objectstack/spec` was rebuilt and `cd ui && pnpm exec vitest run packages/types/` re-run under os-verify-lock: 393/393 files, 10691 passed, exit 0. #26365 is still an open draft; it edits `zod-mirror-parity.test.ts` in different regions, and whichever lands later merges `main` and re-runs that file. - `node scripts/pm/dispatch-gates.mjs --ran`: 34 derived, 34 run, 0 NOT-MEASURED; every exit 0. `pnpm --dir ui exec vitest run scripts/` first exited 1 on an unbuilt `@objectstack/core` dist in this container, and exited 0 once it was built. - Under os-verify-lock, `cd ui && pnpm exec vitest run packages/types/ packages/plugin-form/ packages/plugin-calendar/`: 660/660 files, 13570 passed, 96 skipped, exit 0. - Under os-verify-lock, `cd ui && pnpm exec turbo run type-check --filter='...@object-ui/types' --concurrency=2 --continue`: 39 packages in scope, 108/108 tasks successful, exit 0. - `node scripts/check-platform-checklist.mjs --changed-from origin/main`: exit 0 (no anchored file in the change set). - `node scripts/check-adr-0087-registration.mjs --base origin/main`: exit 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzhgTn8yFwqXxanJd4c --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d31329f commit 607d3bb

15 files changed

Lines changed: 314 additions & 52 deletions
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
refactor(types)!: `object-form` and `object-calendar` declare the four spec keys their renderers read, and `@object-ui/types/zod` renames its legacy `ActionSchema` to `CRUDActionSchema` (#25923)
6+
7+
Clause-②: yes (narrowing)
8+
Metadata-protocol: narrowed
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No `@objectstack/spec` key, object definition, stored row or stored shape moves. The four keys are members the spec's `ComponentPropsMap['object-form']` and `['object-calendar']` rows already declare, now declared by reference on the `@object-ui/types` mirror of those rows, so `objectui validate` refuses exactly the values the spec refuses and `objectstack migrate meta` has nothing to rewrite. The renamed zod const validates a legacy `type: 'action'` node that is not metadata and has no stored representation; the affected party is a JavaScript or TypeScript consumer importing it by name, reached by its own compiler or bundler. `runtime-export-retired` would be the natural category, but it reads only a package's main entry and refuses an operand declared in a `*.zod.ts`, and this export lives on the `./zod` subpath in `crud.zod.ts`. The package publishes (not unpublished); no ADR-0087 id covers this change (not registered or already-registered); and the symbols are zod-backed (not runtime-interface-only or type-surface-only). -->
11+
12+
`@object-ui/types` mirrors `@objectstack/spec` schemas so that `objectui validate` and `os validate` give one verdict. Over every schema a mirror pairs with (same-name exports, and the `ComponentPropsMap` row of each node type), eleven spec keys were neither declared nor refused on objectui's side. Four of them are read by a renderer, and those are now declared:
13+
14+
- **`ObjectFormSchema`** (TypeScript and zod) declares `contentLayout` and `confirmOnDiscard`, the `ComponentPropsMap['object-form']` members by reference. `ModalForm` reads `contentLayout`; `ModalForm` and `DrawerForm` read `confirmOnDiscard`.
15+
- **`ObjectCalendarSchema`** (TypeScript and zod) declares `locale` and `loading`, the `ComponentPropsMap['object-calendar']` members by reference. `plugin-calendar` reads both off the node. They are node keys, not the calendar field-name keys that live in the `calendar` block.
16+
17+
The other seven are the spec form-view entry's keys on `FormFieldSchema`, which is objectui's runtime form field, a different layer under the same name. `normalizeSectionField` in `@object-ui/plugin-form` translates between the two layers, and each key's handling is now recorded beside the mirrors.
18+
19+
The legacy `type: 'action'` node's zod schema was exported as `ActionSchema`, the name of `@objectstack/spec/ui`'s action metadata schema, with which it shares no vocabulary. It is now `CRUDActionSchema`, beside `CRUDDialogSchema` and `CRUDComponentSchema`; what it accepts is unchanged. Its TypeScript twin keeps the deprecated `ActionSchema` name in `@object-ui/types`.
20+
21+
**BREAKING** for code that imports the zod const by its old name, and for nodes that carry a value the spec refuses on the four keys.
22+
23+
- **Before.** `import { ActionSchema } from '@object-ui/types/zod'` resolved to the legacy action node's schema. `ObjectFormSchema.safeParse({ type: 'object-form', objectName: 'account', mode: 'create', contentLayout: 'tabs' })` succeeded and kept the value, which stacked the sections.
24+
- **Now.** The old import name is gone. The zod mirrors refuse `contentLayout: 'tabs'`, a non-boolean `confirmOnDiscard` or `loading`, and a non-string `locale`, at the key's own path, as the spec does.
25+
- **Fix.** FROM `import { ActionSchema } from '@object-ui/types/zod'` TO `import { CRUDActionSchema } from '@object-ui/types/zod'`. Write `contentLayout` as `'simple'` or `'tabbed'`.

‎ui/content/docs/core/enhanced-actions.mdx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,9 @@ for an either/or.
301301
> `else`. The runner has always read this key as the predicate above, and an
302302
> object without a `source` reads as "no gate declared", so an action written
303303
> that way ran **unconditionally**, with no error at author time and no
304-
> diagnostic at runtime. The shape is now refused by `ActionSchema`'s zod schema
305-
> instead of being accepted and ignored.
304+
> diagnostic at runtime. The shape is now refused by the zod schema
305+
> (`CRUDActionSchema` in `@object-ui/types/zod`) instead of being accepted and
306+
> ignored.
306307
307308
## Post-success behaviour
308309

@@ -483,7 +484,7 @@ const complexAction: ActionSchema = {
483484
## Runtime Validation
484485

485486
```ts
486-
import { ActionSchema } from '@object-ui/types/zod';
487+
import { CRUDActionSchema } from '@object-ui/types/zod';
487488

488489
const myAction = {
489490
type: 'action',
@@ -492,7 +493,7 @@ const myAction = {
492493
confirmText: 'Are you sure?',
493494
};
494495

495-
const result = ActionSchema.safeParse(myAction);
496+
const result = CRUDActionSchema.safeParse(myAction);
496497

497498
if (result.success) {
498499
console.log('Valid action configuration');

‎ui/content/docs/guide/schema-overview.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ For runtime validation, use the included Zod schemas:
187187
```typescript
188188
import {
189189
AppComponentSchema,
190-
ActionSchema,
190+
CRUDActionSchema,
191191
ReportComponentSchema
192192
} from '@object-ui/types/zod';
193193

‎ui/packages/types/src/__tests__/action-callback-retired-7068.test.ts‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import ts from 'typescript';
5959
import { ActionSchema as SpecActionSchema } from '@objectstack/spec/ui';
6060
import * as crudZod from '../zod/crud.zod.js';
6161
import * as zodBarrel from '../zod/index.zod.js';
62-
import { ActionSchema, DetailSchema } from '../zod/crud.zod.js';
62+
import { CRUDActionSchema, DetailSchema } from '../zod/crud.zod.js';
6363
import { BaseSchema } from '../zod/base.zod.js';
6464
import { safeValidateSchema } from '../zod/index.zod.js';
6565
import type { ActionSchema as ActionSchemaTS } from '../crud.js';
@@ -105,9 +105,9 @@ type Issue = { code: string; path: PropertyKey[]; message: string; expected?: st
105105
const flatIssues = (issues: Issue[]): Issue[] =>
106106
issues.flatMap((i) => (i.code === 'invalid_union' && i.errors ? i.errors.flat().flatMap((e) => flatIssues([e])) : [i]));
107107

108-
/** The lazy mirror's object shape — `ActionSchema` is `z.lazy(() => BaseSchema.extend(…))`. */
108+
/** The lazy mirror's object shape — `CRUDActionSchema` is `z.lazy(() => BaseSchema.extend(…))`. */
109109
const actionShape = (): Record<string, { description?: string }> =>
110-
(ActionSchema as unknown as { _def: { getter: () => { shape: Record<string, { description?: string }> } } })._def.getter().shape;
110+
(CRUDActionSchema as unknown as { _def: { getter: () => { shape: Record<string, { description?: string }> } } })._def.getter().shape;
111111

112112
/* ── the Zod half: refused BY NAME, with the guidance in the message ─────── */
113113

@@ -120,7 +120,7 @@ describe.each([
120120
// .optional()`). Asserting the ENVELOPE — code, path, expected and the exact
121121
// message — so the pin cannot be satisfied by an unrelated rejection, and a
122122
// WELL-FORMED callback so it is the KEY that is refused, not its members.
123-
const result = ActionSchema.safeParse({ ...LEGACY_ACTION, [key]: value });
123+
const result = CRUDActionSchema.safeParse({ ...LEGACY_ACTION, [key]: value });
124124
expect(result.success, `an authored \`${key}\` was ACCEPTED`).toBe(false);
125125
if (result.success) return;
126126

@@ -157,7 +157,7 @@ describe.each([
157157
// exactly this acceptance, kept the callback unvalidated, and stayed green.
158158
const doc = { ...LEGACY_ACTION, [key]: CALLBACKS[0][1] };
159159
expect(BaseSchema.safeParse(doc).success).toBe(true);
160-
expect(ActionSchema.safeParse(doc).success).toBe(false);
160+
expect(CRUDActionSchema.safeParse(doc).success).toBe(false);
161161
});
162162

163163
it('keeps the key DECLARED, with the same guidance on `.describe()` — one string, both channels', () => {
@@ -171,34 +171,34 @@ describe.each([
171171

172172
describe('the retirement narrows exactly `onSuccess` / `onFailure` (objectui#7068)', () => {
173173
it('a legacy action WITHOUT the two keys still parses, and its values survive', () => {
174-
const result = ActionSchema.safeParse(LEGACY_ACTION);
174+
const result = CRUDActionSchema.safeParse(LEGACY_ACTION);
175175
expect(result.success).toBe(true);
176176
if (!result.success) return;
177177
expect(result.data.api).toBe('/api/data');
178178
});
179179

180180
it('control: the adjacent `successMessage` / `errorMessage` — NOT retired — still parse and survive', () => {
181-
const result = ActionSchema.safeParse({ ...LEGACY_ACTION, successMessage: 'Loaded', errorMessage: 'Failed' });
181+
const result = CRUDActionSchema.safeParse({ ...LEGACY_ACTION, successMessage: 'Loaded', errorMessage: 'Failed' });
182182
expect(result.success).toBe(true);
183183
if (!result.success) return;
184184
expect(result.data.successMessage).toBe('Loaded');
185185
expect(result.data.errorMessage).toBe('Failed');
186186
});
187187

188188
it('control: the mirror did not stop validating — a wrong-typed `successMessage` is still refused', () => {
189-
const result = ActionSchema.safeParse({ ...LEGACY_ACTION, successMessage: 42 });
189+
const result = CRUDActionSchema.safeParse({ ...LEGACY_ACTION, successMessage: 42 });
190190
expect(result.success).toBe(false);
191191
if (result.success) return;
192192
expect(result.error.issues.map((i) => i.path.join('.'))).toContain('successMessage');
193193
});
194194

195195
it('control: `chain` and `redirect` — the declared follow-up and post-action keys — still parse', () => {
196-
const result = ActionSchema.safeParse({ ...LEGACY_ACTION, chain: [{ type: 'action', label: 'Next' }], redirect: '/done' });
196+
const result = CRUDActionSchema.safeParse({ ...LEGACY_ACTION, chain: [{ type: 'action', label: 'Next' }], redirect: '/done' });
197197
expect(result.success).toBe(true);
198198
});
199199

200200
it('an UNDECLARED key still rides `.passthrough()` — the contrast the tombstones exist for, measured live', () => {
201-
const result = ActionSchema.safeParse({ ...LEGACY_ACTION, notAKeyAtAll: 'anything' });
201+
const result = CRUDActionSchema.safeParse({ ...LEGACY_ACTION, notAKeyAtAll: 'anything' });
202202
expect(result.success).toBe(true);
203203
if (!result.success) return;
204204
expect((result.data as unknown as Record<string, unknown>).notAKeyAtAll).toBe('anything');
@@ -212,7 +212,7 @@ describe('`ActionCallback` / `ActionCallbackSchema` are DELETED, not tombstoned
212212
expect('ActionCallbackSchema' in crudZod).toBe(false);
213213
expect('ActionCallbackSchema' in zodBarrel).toBe(false);
214214
// Non-vacuity: the neighbours the barrel still carries.
215-
expect('ActionSchema' in crudZod).toBe(true);
215+
expect('CRUDActionSchema' in crudZod).toBe(true);
216216
expect('ActionExecutionModeSchema' in zodBarrel).toBe(true);
217217
});
218218

‎ui/packages/types/src/__tests__/disabled-twin-symmetry-7087.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ import type {
9595
ToggleSchema,
9696
} from '../form';
9797

98-
import { ActionSchema as ActionMirror } from '../zod/crud.zod';
98+
import { CRUDActionSchema as ActionMirror } from '../zod/crud.zod';
9999
import {
100100
AccordionItemSchema,
101101
CollapsibleSchema as CollapsibleMirror,
@@ -199,7 +199,7 @@ const IN_SCOPE: readonly MirrorCase[] = [
199199
{ name: 'form.zod.ts#InputOTPSchema', mirror: FormMirrors.InputOTPSchema, fixture: { type: 'input-otp' } },
200200
{ name: 'form.zod.ts#FormSchema', mirror: FormMirrors.FormSchema, fixture: { type: 'form', fields: [{ name: 'title', type: 'text' }] } },
201201
{ name: 'form.zod.ts#ComboboxSchema', mirror: FormMirrors.ComboboxSchema, fixture: { type: 'combobox', options: [{ label: 'A', value: 'a' }] } },
202-
{ name: 'crud.zod.ts#ActionSchema', mirror: ActionMirror, fixture: { type: 'action', label: 'Save' } },
202+
{ name: 'crud.zod.ts#CRUDActionSchema', mirror: ActionMirror, fixture: { type: 'action', label: 'Save' } },
203203
{
204204
name: 'disclosure.zod.ts#CollapsibleSchema',
205205
mirror: CollapsibleMirror,

‎ui/packages/types/src/__tests__/duration-unit-key-renames-25762.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import type { ToastSchema as ToastInterface } from '../feedback';
3737
import type { HoverCardSchema as HoverCardInterface, TooltipSchema as TooltipInterface } from '../overlay';
3838
import type { ReportComponentSchema as ReportInterface } from '../reports';
3939
import {
40-
ActionSchema,
40+
CRUDActionSchema,
4141
CarouselSchema,
4242
ChatbotEnhancedSchema,
4343
ChatbotFloatingSchema,
@@ -70,8 +70,8 @@ const CASES: readonly RenameCase[] = [
7070
{ schema: ChatbotSchema, base: { type: 'chatbot', messages: [] }, at: [], retired: 'autoResponseDelay', renamed: 'autoResponseDelayMs', unit: 'milliseconds' },
7171
{ schema: ChatbotEnhancedSchema, base: { type: 'chatbot-enhanced', messages: [] }, at: [], retired: 'autoResponseDelay', renamed: 'autoResponseDelayMs', unit: 'milliseconds' },
7272
{ schema: ChatbotFloatingSchema, base: { type: 'chatbot-floating', messages: [] }, at: [], retired: 'autoResponseDelay', renamed: 'autoResponseDelayMs', unit: 'milliseconds' },
73-
{ schema: ActionSchema, base: { type: 'action', label: 'Go' }, at: [], retired: 'timeout', renamed: 'timeoutMs', unit: 'milliseconds' },
74-
{ schema: ActionSchema, base: { type: 'action', label: 'Go', retry: { maxAttempts: 2 } }, at: ['retry'], retired: 'delay', renamed: 'delayMs', unit: 'milliseconds' },
73+
{ schema: CRUDActionSchema, base: { type: 'action', label: 'Go' }, at: [], retired: 'timeout', renamed: 'timeoutMs', unit: 'milliseconds' },
74+
{ schema: CRUDActionSchema, base: { type: 'action', label: 'Go', retry: { maxAttempts: 2 } }, at: ['retry'], retired: 'delay', renamed: 'delayMs', unit: 'milliseconds' },
7575
{ schema: ToastSchema, base: { type: 'toast' }, at: [], retired: 'duration', renamed: 'durationMs', unit: 'milliseconds' },
7676
{ schema: TooltipSchema, base: { type: 'tooltip', content: 'Hi' }, at: [], retired: 'delayDuration', renamed: 'delayDurationMs', unit: 'milliseconds' },
7777
{ schema: HoverCardSchema, base: { type: 'hover-card', ...node }, at: [], retired: 'openDelay', renamed: 'openDelayMs', unit: 'milliseconds' },

‎ui/packages/types/src/__tests__/handler-keys-string-any-mirrors-7344.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ import { z } from 'zod';
157157

158158
import { CalendarViewSchema as CalendarViewZod } from '../zod/complex.zod';
159159
import {
160-
ActionSchema as ActionZod,
160+
CRUDActionSchema as ActionZod,
161161
CRUDDialogSchema as CRUDDialogZod,
162162
DetailSchema as DetailZod,
163163
} from '../zod/crud.zod';
@@ -191,7 +191,7 @@ const objectOf = (mirror: z.ZodType, key: string): z.ZodObject<z.ZodRawShape> =>
191191
/** The six keys whose function value REACHES a renderer (channels above). */
192192
const RUNTIME_SLOT: readonly Site[] = [
193193
['views.zod.ts', 'DetailViewSchema', 'onBack', DetailViewZod],
194-
['crud.zod.ts', 'ActionSchema', 'onClick', ActionZod],
194+
['crud.zod.ts', 'CRUDActionSchema', 'onClick', ActionZod],
195195
['crud.zod.ts', 'DetailSchema', 'onBack', DetailZod],
196196
// objectui#7804 — the two keys `DetailView` reads off a `'detail'` document
197197
// that its arm never declared. They arrive by a DIFFERENT route from the four

‎ui/packages/types/src/__tests__/object-view-slot-key-lists.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,12 @@ describe('the source schemas still declare their full member sets', () => {
292292
expect(declaresStringIndex(grid)).toBe(false);
293293
});
294294

295-
it('ObjectFormSchema declares 69 members and no longer carries the #5155 index signature', () => {
295+
// 71 since objectstack#25923 (work item 4) declared `contentLayout` and
296+
// `confirmOnDiscard` by reference to `ComponentPropsMap['object-form']`; the
297+
// `form` slot carries both.
298+
it('ObjectFormSchema declares 71 members and no longer carries the #5155 index signature', () => {
296299
const form = exportedType('ObjectFormSchema');
297-
expect(memberNames(form)).toHaveLength(69);
300+
expect(memberNames(form)).toHaveLength(71);
298301
expect(memberNames(form)).toEqual(expect.arrayContaining(['fields', 'sections', 'submitText']));
299302
expect(declaresStringIndex(form)).toBe(false);
300303
});

‎ui/packages/types/src/__tests__/phase2-schemas.test.ts‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
AppComponentSchema,
99
ReportComponentSchema,
1010
ReportBuilderSchema,
11-
ActionSchema,
11+
CRUDActionSchema,
1212
DetailViewSchema,
1313
ViewSwitcherSchema,
1414
FilterUISchema,
@@ -364,7 +364,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
364364
errorMessage: 'Failed to load data',
365365
};
366366

367-
const result = ActionSchema.safeParse(ajaxAction);
367+
const result = CRUDActionSchema.safeParse(ajaxAction);
368368
expect(result.success).toBe(true);
369369
});
370370

@@ -378,7 +378,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
378378
method: 'DELETE',
379379
};
380380

381-
const result = ActionSchema.safeParse(confirmAction);
381+
const result = CRUDActionSchema.safeParse(confirmAction);
382382
expect(result.success).toBe(true);
383383
});
384384

@@ -394,7 +394,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
394394
// value is a LOUD parse rejection — never a silent strip, which would let
395395
// the author believe the dialog they configured exists. Absent stays
396396
// valid (accept case above).
397-
const result = ActionSchema.safeParse(structured);
397+
const result = CRUDActionSchema.safeParse(structured);
398398
expect(result.success).toBe(false);
399399
if (!result.success) {
400400
expect(result.error.issues.map((i) => i.path.join('.'))).toContain('confirm');
@@ -408,7 +408,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
408408
// nothing else. The author now reads the remedy (`confirmText`) in the
409409
// message itself, which is what `packages/cli`'s `validate` / `check`
410410
// print verbatim beside the path and code.
411-
const result = ActionSchema.safeParse({
411+
const result = CRUDActionSchema.safeParse({
412412
type: 'action',
413413
label: 'Delete Record',
414414
actionType: 'confirm',
@@ -427,7 +427,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
427427

428428
// Non-vacuity, IN THIS TEST: the canonical spelling still parses green.
429429
expect(
430-
ActionSchema.safeParse({ type: 'action', label: 'Delete Record', confirmText: 'Sure?' }).success,
430+
CRUDActionSchema.safeParse({ type: 'action', label: 'Delete Record', confirmText: 'Sure?' }).success,
431431
).toBe(true);
432432
});
433433

@@ -462,7 +462,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
462462
},
463463
};
464464

465-
const result = ActionSchema.safeParse(dialogAction);
465+
const result = CRUDActionSchema.safeParse(dialogAction);
466466
expect(result.success).toBe(true);
467467
});
468468

@@ -492,7 +492,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
492492
chainMode: 'sequential',
493493
};
494494

495-
const result = ActionSchema.safeParse(chainedAction);
495+
const result = CRUDActionSchema.safeParse(chainedAction);
496496
expect(result.success).toBe(true);
497497
});
498498

@@ -516,7 +516,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
516516
condition,
517517
};
518518

519-
const result = ActionSchema.safeParse(gatedAction);
519+
const result = CRUDActionSchema.safeParse(gatedAction);
520520
expect(result.success).toBe(true);
521521
});
522522

@@ -549,7 +549,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
549549
},
550550
};
551551

552-
const result = ActionSchema.safeParse(branchAction);
552+
const result = CRUDActionSchema.safeParse(branchAction);
553553
expect(result.success).toBe(false);
554554
if (result.success) return;
555555
const issues = result.error.issues.filter((i) => i.path[0] === 'condition');
@@ -573,7 +573,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
573573
},
574574
};
575575

576-
const result = ActionSchema.safeParse(trackedAction);
576+
const result = CRUDActionSchema.safeParse(trackedAction);
577577
expect(result.success).toBe(true);
578578
});
579579

@@ -590,7 +590,7 @@ describe('Phase 2: Enhanced ActionSchema Zod Validation', () => {
590590
},
591591
};
592592

593-
const result = ActionSchema.safeParse(retryAction);
593+
const result = CRUDActionSchema.safeParse(retryAction);
594594
expect(result.success).toBe(true);
595595
});
596596
});

‎ui/packages/types/src/__tests__/zod-lazy-getter-identity-7918.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
import { describe, it, expect } from 'vitest';
105105
import { z } from 'zod';
106106
import {
107-
ActionSchema,
107+
CRUDActionSchema,
108108
AppMenuItemSchema,
109109
FilterBuilderConditionSchema,
110110
FilterGroupSchema,
@@ -143,7 +143,7 @@ const MEMOISED: ReadonlyArray<readonly [string, unknown]> = [
143143
];
144144
/** ⛔ Do not "fix" these — each one's `z.lazy` dodges a real ReferenceError. */
145145
const TDZ_BOUND: ReadonlyArray<readonly [string, unknown]> = [
146-
['ActionSchema', ActionSchema],
146+
['CRUDActionSchema', CRUDActionSchema],
147147
['AppMenuItemSchema', AppMenuItemSchema],
148148
['MenuItemSchema', MenuItemSchema],
149149
['NavLinkSchema', NavLinkSchema],

0 commit comments

Comments
 (0)