From 7fbfb9a770ea3c94693c0239ad490a62f7f6c0f4 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:41:20 +0800 Subject: [PATCH] feat(spec,showcase): allowCreate flag for inline lookup quick-create Exposes the opt-in inline quick-create the objectui record picker supports: when no match exists, the user creates a record from the typed text via an optimistic dataSource.create with the display field. Best for simple objects whose only required field is the display field. - spec: add `allowCreate` to FieldSchema (flows through Field.lookup via FieldInput); classify it live in the liveness ledger (objectui consumer). - showcase: enable it on showcase_category.parent (Category requires only `name`, so quick-create succeeds) to demonstrate the flow end-to-end. - test: assert allowCreate survives FieldSchema.parse. Pairs with objectui #1863 (renderer quick-create reads allowCreate || allow_create). Co-Authored-By: Claude Opus 4.8 --- examples/app-showcase/src/objects/category.object.ts | 2 +- packages/spec/liveness/field.json | 4 ++++ packages/spec/src/data/field.test.ts | 2 ++ packages/spec/src/data/field.zod.ts | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/app-showcase/src/objects/category.object.ts b/examples/app-showcase/src/objects/category.object.ts index 58b331ce0c..d36038a0bf 100644 --- a/examples/app-showcase/src/objects/category.object.ts +++ b/examples/app-showcase/src/objects/category.object.ts @@ -15,7 +15,7 @@ export const Category = ObjectSchema.create({ fields: { name: Field.text({ label: 'Name', required: true, searchable: true, maxLength: 120 }), - parent: Field.lookup('showcase_category', { label: 'Parent Category' }), + parent: Field.lookup('showcase_category', { label: 'Parent Category', allowCreate: true }), color: Field.color({ label: 'Color' }), sort_order: Field.number({ label: 'Sort Order', min: 0, defaultValue: 0 }), }, diff --git a/packages/spec/liveness/field.json b/packages/spec/liveness/field.json index 4a36a3b919..9e98a62273 100644 --- a/packages/spec/liveness/field.json +++ b/packages/spec/liveness/field.json @@ -179,6 +179,10 @@ "status": "live", "note": "objectui LookupField — dependent/cascading lookup; restricts candidates by another field's value (reads dependsOn || depends_on)." }, + "allowCreate": { + "status": "live", + "note": "objectui LookupField — opt-in inline quick-create (dataSource.create from typed text) (reads allowCreate || allow_create)." + }, "maxRating": { "status": "dead", "evidence": "RatingField reads `max` (RatingField.tsx:13) — dead + redundant with max", diff --git a/packages/spec/src/data/field.test.ts b/packages/spec/src/data/field.test.ts index 9c63d7d6d4..68bc6e5906 100644 --- a/packages/spec/src/data/field.test.ts +++ b/packages/spec/src/data/field.test.ts @@ -309,6 +309,7 @@ describe('FieldSchema', () => { lookupPageSize: 20, lookupFilters: [{ field: 'status', operator: 'eq', value: 'active' }], dependsOn: ['region', { field: 'country', param: 'country_code' }], + allowCreate: true, }; const result = FieldSchema.parse(lookupField); @@ -318,6 +319,7 @@ describe('FieldSchema', () => { expect(result.lookupPageSize).toBe(20); expect(result.lookupFilters?.[0]).toEqual({ field: 'status', operator: 'eq', value: 'active' }); expect(result.dependsOn).toHaveLength(2); + expect(result.allowCreate).toBe(true); }); it('builds a lookup with picker config via Field.lookup', () => { diff --git a/packages/spec/src/data/field.zod.ts b/packages/spec/src/data/field.zod.ts index bd231b23e6..7e24d58ce4 100644 --- a/packages/spec/src/data/field.zod.ts +++ b/packages/spec/src/data/field.zod.ts @@ -473,6 +473,7 @@ export const FieldSchema = lazySchema(() => z.object({ field: z.string(), param: z.string().optional(), })])).optional().describe('Dependent lookup: restrict candidates by the value of other field(s) on the same record. String = same local/remote key; {field,param} when the remote filter key differs.'), + allowCreate: z.boolean().optional().describe('Allow inline quick-create from the record picker: when no match exists the user can create a record from the typed text (optimistic dataSource.create with the display field). Best for simple objects whose only required field is the display field.'), /** Calculation — CEL formula. Plain string accepted for back-compat; build emits canonical envelope. */ expression: ExpressionInputSchema.optional().describe('Formula expression (CEL). e.g. F`record.amount * 0.1`'),