diff --git a/.changeset/19258-recipient-id-dependson-object-name.md b/.changeset/19258-recipient-id-dependson-object-name.md new file mode 100644 index 00000000000..ced78555fb0 --- /dev/null +++ b/.changeset/19258-recipient-id-dependson-object-name.md @@ -0,0 +1,13 @@ +--- +"@objectstack/plugin-sharing": patch +--- + +`sys_sharing_rule.recipient_id` now declares `dependsOn: ['recipient_type', 'object_name']` — every sibling field its `recipient-picker` widget actually reads. + +The picker reads two siblings, not one: `recipient_type` picks the mode (a record picker over `sys_user` / `sys_team` / `sys_business_unit` / `sys_position`), and for the `field` recipient kind (#15072) it reads `object_name` to offer that object's user-valued columns. The declaration named only the first. The neighbouring `criteria_json` field already declares `dependsOn: ['object_name']` for its own `filter-condition` widget, so the key is live and correctly used a few lines up — the omission was an omission. + +Nothing was broken at runtime: the form renderer hands widgets the WHOLE watched record as `dependentValues` rather than a `dependsOn`-scoped slice, which masked the under-declaration. A renderer that ever scoped it — which is exactly what this key asks for — would drop the object name and degrade the `field` recipient mode to a plain text input **in silence**, with no error anywhere. This is the declaration catching up with what is read, so the scoping change can never be the one that breaks it. + +The same commit corrects the `recipient_id` docblock: the picker no longer "has no mapping for that kind and degrades to its text input" — the pinned console (`.objectui-sha` 87af769e, which includes objectui#10049 / commit 23b99585) offers the shared object's user-valued columns for the `field` kind, using a "holds users" predicate that is a clause-for-clause copy of this plugin's own `fieldHoldsUsers`. + +Authors and stored rows are unaffected: no key is added, removed or renamed, no value is newly accepted or refused, and no wire byte moves. diff --git a/packages/plugins/plugin-sharing/src/field-recipient.test.ts b/packages/plugins/plugin-sharing/src/field-recipient.test.ts index 6853192c4af..c2b561c4b46 100644 --- a/packages/plugins/plugin-sharing/src/field-recipient.test.ts +++ b/packages/plugins/plugin-sharing/src/field-recipient.test.ts @@ -816,6 +816,33 @@ describe('#15072 authoring seams', () => { const help = String((SysSharingRule as any).fields.recipient_id.description); expect(help).toMatch(/field/i); }); + + /** + * [#19258] `dependsOn` is the declaration of WHICH siblings the widget + * reads, and `recipient-picker` reads two: `recipient_type` picks the mode, + * and the `field` mode reads `object_name` to offer that object's + * user-valued columns. Nothing breaks today only because the form renderer + * hands widgets the whole watched record rather than a `dependsOn`-scoped + * slice — so a scoped renderer would degrade `field` mode to a plain text + * input with no error anywhere. These pin the declaration, which is the + * only thing that would survive that change. + */ + describe('[#19258] `recipient_id.dependsOn` names every sibling the picker reads', () => { + const dependsOn = (field: string): string[] => + ((SysSharingRule as any).fields[field].dependsOn as string[]) ?? []; + + it('names `recipient_type` — the sibling that picks the picker mode', () => { + expect(dependsOn('recipient_id')).toContain('recipient_type'); + }); + + it('names `object_name` — the sibling the `field` mode reads for its candidate columns', () => { + expect(dependsOn('recipient_id')).toContain('object_name'); + }); + + it('control: `criteria_json` declares the same `object_name` dependency for its own widget', () => { + expect(dependsOn('criteria_json')).toContain('object_name'); + }); + }); }); }); diff --git a/packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts b/packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts index 64e48d1aacd..3b86f89cc43 100644 --- a/packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts +++ b/packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts @@ -185,16 +185,26 @@ export const SysSharingRule = ObjectSchema.create({ label: 'Recipient', required: true, maxLength: 200, - // Rendered as a record picker whose target object follows recipient_type - // (dependsOn: recipient_type): sys_user / sys_team / sys_business_unit / - // sys_position. Stores the value the evaluator matches on — a record id - // for user/team/business_unit, the position NAME for `position`, and for - // `field` (#15072) the NAME of a user-typed field of the shared object - // (the picker has no mapping for that kind and degrades to its text - // input, which is the right input for a field name). Falls back to a - // text input when the widget is unavailable. + // Rendered as a record picker whose target object follows recipient_type: + // sys_user / sys_team / sys_business_unit / sys_position. Stores the + // value the evaluator matches on — a record id for user/team/ + // business_unit, the position NAME for `position`, and for `field` + // (#15072) the NAME of a user-typed field of the shared object, which the + // picker offers from the object named in the sibling `object_name`: its + // "holds users" predicate is a clause-for-clause copy of this plugin's + // own `fieldHoldsUsers`. Falls back to a text input when the widget is + // unavailable. + // + // `dependsOn` names BOTH siblings the widget reads — `recipient_type` + // picks the mode, and in `field` mode `object_name` decides whose columns + // are offered, the same dependency `criteria_json` declares a few lines + // up. Declaring only the first was masked by the form renderer handing + // widgets the WHOLE watched record instead of a `dependsOn`-scoped slice; + // a renderer that ever scoped it — which is what this key asks for — + // would drop the object name and degrade `field` mode to a plain text + // input in silence. widget: 'recipient-picker', - dependsOn: ['recipient_type'], + dependsOn: ['recipient_type', 'object_name'], description: 'The specific user, team, business unit or position that receives access — or, for the "Field" recipient type, the name of the record field that holds the user or users to share with.', group: 'Recipient', }),