From 6cfb2d6a6a37f8caa2271af9e4ccac1807e28602 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Thu, 25 Jun 2026 17:53:51 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(spec):=20RecordRelatedListProps.add=20?= =?UTF-8?q?=E2=80=94=20add-existing-via-picker=20(generic=20m2m/junction)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A related list could only '+ New' (create+navigate). Add an optional `add` config so a related list can LINK existing records via a picker: pick from `add.picker.object` (the far side) → create a link row in `objectName` as {[relationshipField]: parentId, [add.linkField]: pickedId} (junction), or omit linkField to re-parent a 1:m child. Generic over any relationship; the canonical use is 'Assigned Users' on a permission set (assign sys_user → ai_seat etc.), with server-side insert rules (e.g. the AI-seat cap) surfacing inline. Co-Authored-By: Claude Opus 4.8 --- packages/spec/src/ui/component.zod.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 92a03fedf4..a05a87f8ca 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -84,6 +84,29 @@ export const RecordRelatedListProps = z.object({ title: I18nLabelSchema.optional().describe('Custom title for the related list'), showViewAll: z.boolean().default(true).describe('Show "View All" link to see all related records'), actions: z.array(z.string()).optional().describe('Action IDs available for related records'), + /** + * Enable an "Add" affordance that links EXISTING records via a picker, rather + * than only "+ New" (create-and-navigate). Generic over m2m / junction + * relationships: pick records from `add.picker.object` (the far side) and + * create a link row in `objectName` with `{[relationshipField]: , + * [add.linkField]: }`. Omit `linkField` for a plain 1:m re-parent + * (the picked child's `relationshipField` is set to the parent id instead). + * Server-side rules still apply on insert (e.g. the AI-seat cap), and their + * errors surface in the dialog. The canonical use is "Assigned Users" on a + * permission set (objectName=`sys_user_permission_set`, + * relationshipField=`permission_set_id`, picker.object=`sys_user`, + * linkField=`user_id`). + */ + add: z.object({ + picker: z.object({ + object: z.string().describe('Object to pick records from (the far side of an m2m, or the child object for a 1:m re-parent).'), + valueField: z.string().default('id').describe('Field on the picked record used as the link value (default `id`).'), + labelField: z.string().optional().describe('Field shown in the picker rows (defaults to the object title field).'), + filter: z.array(ViewFilterRuleSchema).optional().describe('Restrict which records the picker offers.'), + }).describe('Where the Add affordance sources records from.'), + linkField: z.string().optional().describe('Field on `objectName` that stores the picked record id (junction case). Omit for a 1:m re-parent.'), + label: I18nLabelSchema.optional().describe('Label for the Add button (default "Add").'), + }).optional().describe('Add-existing-via-picker config (generic m2m/junction assignment).'), /** ARIA accessibility */ aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'), }); From 8df9f13a14b439904dad3d70db7cdf1ba015c0c0 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Thu, 25 Jun 2026 18:42:19 +0800 Subject: [PATCH 2/2] chore(changeset): related-list add-by-picker --- .changeset/sdui-related-list-add.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sdui-related-list-add.md diff --git a/.changeset/sdui-related-list-add.md b/.changeset/sdui-related-list-add.md new file mode 100644 index 0000000000..89a6962550 --- /dev/null +++ b/.changeset/sdui-related-list-add.md @@ -0,0 +1,5 @@ +--- +'@objectstack/spec': minor +--- + +feat(spec): RecordRelatedListProps.add — add-existing-via-picker (generic m2m/junction assignment). A related list can now link existing records via a picker, not just create+navigate. Powers a generic "Assigned Users" / Manage Assignments UI on permission sets.