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
13 changes: 13 additions & 0 deletions .changeset/19258-recipient-id-dependson-object-name.md
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 27 additions & 0 deletions packages/plugins/plugin-sharing/src/field-recipient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}),
Expand Down
Loading