Skip to content

Commit 3f49598

Browse files
committed
fix(plugin-sharing): label sys_record_share recipient and access options alike
The recipient vocabulary guard holds one rendering per option key across both sharing objects, so the record share labels its options the same way. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QUKVainSF9Fx4YDaSPyE64
1 parent 3f92bcd commit 3f49598

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

‎packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,14 @@ export const SysRecordShare = ObjectSchema.create({
199199
// gate reads such a row — since guest access flows through the guest
200200
// position and share links only (ADR-0090 D11). Held equal to
201201
// `RecordShareRecipientType` in `@objectstack/spec/contracts`.
202-
['user', 'group', 'position', 'unit_and_subordinates'],
202+
// Labelled options, rendered alike on `sys_sharing_rule`.
203203
{
204+
options: [
205+
{ value: 'user', label: 'User' },
206+
{ value: 'group', label: 'Group' },
207+
{ value: 'position', label: 'Position' },
208+
{ value: 'unit_and_subordinates', label: 'Business unit and subordinates' },
209+
],
204210
label: 'Recipient Type',
205211
required: true,
206212
defaultValue: 'user',
@@ -241,8 +247,11 @@ export const SysRecordShare = ObjectSchema.create({
241247
// `edit` while claiming more (ADR-0078 declared-but-unenforced; #3865).
242248
// Rows persisted with `full` are normalised to `edit` (grant-time + boot
243249
// backfill) and stay honoured by the gates until they are.
244-
['read', 'edit'],
245250
{
251+
options: [
252+
{ value: 'read', label: 'Read' },
253+
{ value: 'edit', label: 'Read and edit' },
254+
],
246255
label: 'Access Level',
247256
required: true,
248257
defaultValue: 'read',

‎packages/plugins/plugin-sharing/src/sharing-rule-option-labels.test.ts‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22

33
/**
4-
* `sys_sharing_rule.recipient_type` and `access_level` offer labelled options.
4+
* `sys_sharing_rule.recipient_type` and `access_level` offer labelled options,
5+
* and so does `sys_record_share`, which shares that vocabulary
6+
* (`recipient-vocabulary-consistency.test.ts` holds the two renderings equal).
57
* Declared as the bare-string shorthand, every label equalled its stored value,
68
* so the Sharing Rule form showed `unit_and_subordinates` as option text while
79
* the help text under it already named the options by their labels. The stored
@@ -11,6 +13,7 @@
1113

1214
import { describe, it, expect } from 'vitest';
1315
import { SysSharingRule } from './objects/sys-sharing-rule.object.js';
16+
import { SysRecordShare } from './objects/sys-record-share.object.js';
1417

1518
type Option = { value: string; label: string };
1619
const optionsOf = (field: 'recipient_type' | 'access_level'): Option[] =>
@@ -42,3 +45,19 @@ describe('sys_sharing_rule select options', () => {
4245
expect(help).toContain('"Field"');
4346
});
4447
});
48+
49+
describe('sys_record_share select options', () => {
50+
const optionsOf = (field: 'recipient_type' | 'access_level'): Option[] =>
51+
(SysRecordShare.fields[field] as { options: Option[] }).options;
52+
53+
it('keeps the stored values', () => {
54+
expect(optionsOf('recipient_type').map((o) => o.value)).toEqual(['user', 'group', 'position', 'unit_and_subordinates']);
55+
expect(optionsOf('access_level').map((o) => o.value)).toEqual(['read', 'edit']);
56+
});
57+
58+
it.each(['recipient_type', 'access_level'] as const)('%s labels no option with its machine value', (field) => {
59+
for (const o of optionsOf(field)) {
60+
expect(o.label, o.value).not.toBe(o.value);
61+
}
62+
});
63+
});

0 commit comments

Comments
 (0)