Skip to content

Commit 0de68bb

Browse files
committed
feat(plugin-detail): record:related_list reads its declared actions, host-bridge actions stay the default (objectui#11163)
The maintainer ruled ENFORCE on objectstack-ai/objectstack#20665: `record:related_list.actions` ("Action IDs available for related records") was declared, published and parsed, and no renderer read it. The renderer now reads it. Absent, the host bridge's actions render exactly as before and no lookup is made. Authored, the authored list is what renders, in authored order: each id resolves against the related object's registered actions (useMetadataItem + resolveDeclaredActionIds, the lookup record:quick_actions.actionNames uses) and is placed by its own locations (list_toolbar -> header button, list_item -> row menu), run by the host's executor. `[]` draws no actions. An id that does not resolve, or whose action declares no list location, is named in a role="status" notice on the list instead of being dropped. The parity ledger's NO_READ_SITE_TO_PIN exemption becomes a member pin; MEMBER_PIN_EXEMPTION_CEILING 5 -> 4 (the four remaining units are the objectui#11111 bookings owed to objectui#11168). Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ Co-authored-by: Claude <noreply@anthropic.com>
1 parent ae0b9d3 commit 0de68bb

4 files changed

Lines changed: 630 additions & 47 deletions

File tree

‎apps/console/src/__tests__/registry-inputs-spec-parity.test.ts‎

Lines changed: 37 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2413,6 +2413,11 @@ const MULTI_KIND_MEMBER_CONTRACTS: Record<string, string> = {
24132413
// member shape, and fixing it is an ADR-0049 enforce-or-remove decision the spec
24142414
// owns. See `NO_READ_SITE_TO_PIN`.
24152415
//
2416+
// ⭐ objectui#11163 CLOSED IT, in the direction the spec's owner chose: the
2417+
// maintainer ruled ENFORCE on objectstack-ai/objectstack#20665, the renderer
2418+
// reads `schema.actions`, and the key's member pin is registered above.
2419+
// `NO_READ_SITE_TO_PIN` was deleted with its one entry.
2420+
//
24162421
// ⭐ THIRD SLICE, AND THE FIRST BLOCK LEFT WITH NO EXEMPTIONS AT ALL.
24172422
// objectui#8071's third slice converted the four remaining keys of
24182423
// `element:record_picker` — `dataSource`, `label`, `placeholder` and `sort` —
@@ -3012,6 +3017,10 @@ const MEMBER_PINS: Record<string, MemberPin> = {
30123017
file: 'packages/plugin-detail/src/renderers/__tests__/record-quick-actions.requiredPermissions-gate.test.tsx',
30133018
pins: 'The BLOCK-LEVEL gate (`required.every((p) => perms.can(objectName, p))`) that hides the whole bar before any action is drawn — distinct from an `ActionDef`\'s own per-action `requiredPermissions`, which `record-quick-actions.declared-action-ids-7182.test.tsx`\'s `gated` fixture already covers. No key at all: the bar renders on the (empty) grant set, so the gate is provably driven by the key\'s PRESENCE. A single held permission gates as expected, and the discriminating row is a PARTIAL grant on a two-entry array — gated only when read as `.every` over the WHOLE array rather than its first element — with the all-granted case as that row\'s own positive control. New file: no existing test drove `schema.requiredPermissions` itself, only the unrelated per-action field of the same name (objectui#8071).',
30143019
},
3020+
'record:related_list.actions': {
3021+
file: 'packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.authoredActions-11163.test.tsx',
3022+
pins: 'Members are action-id STRINGS resolved against the RELATED object\'s own `actions` — the `useMetadataItem` + `resolveDeclaredActionIds` lookup `record:quick_actions.actionNames` resolves through — and placed by each action\'s own `locations`, read through the real `SchemaRenderer`, this block\'s registration, the real `RelatedList` and the real data table. An authored list renders IN AUTHORED ORDER (`list_toolbar` members as header buttons, `list_item` members in each row\'s menu, one list feeding both) and REPLACES the host bridge\'s default rather than adding to it; an ABSENT key leaves the bridge\'s actions exactly as they were and makes no lookup at all (the control); `[]` draws none while the built-in New stays, because New is not an action id; and an authored action runs through the host\'s executor with its resolved definition. The refusals are member facts too: an id the registry cannot resolve and an id whose action declares neither list location are each named in a `role="status"` notice while the resolvable members still render, an array of inline action objects is refused whole, nothing is refused while the lookup is in flight, and with no host the list stays read-only while a bad id is still refused, so the refusal is the registry\'s and not the host\'s. New file (objectui#11163, the ENFORCE ruling on objectstack#20665), replacing the `NO_READ_SITE_TO_PIN` exemption this key carried since objectui#8071 slice 2.',
3023+
},
30153024
'record:related_list.add': {
30163025
file: 'packages/plugin-detail/src/__tests__/recordRelatedListInputs.spec-parity.test.ts',
30173026
pins: 'Every spec member key of `add` must be discoverable from its description, the published defaults must be the RENDERER\'s rather than the spec\'s prose, and `picker.filter` must be documented as a real restriction (objectui#3808).',
@@ -3079,47 +3088,16 @@ const AWAITING_A_PIN_NEWLY_JUDGED =
30793088
'so this is a correction to the measured population and not a newly added array key. ' +
30803089
'objectui#8071 owns writing the pin; delete this entry in the same change that registers it.';
30813090

3082-
/**
3083-
* The reason a key carries when there is no read site for a pin to constrain.
3084-
*
3085-
* The `Record<string, string>` shape exists for exactly this: a key that needs a
3086-
* DIFFERENT reason gets its own string rather than a second mechanism.
3087-
* `AWAITING_A_PIN` says "objectui#8071 owns writing the pin", and for this key
3088-
* that sentence would be FALSE in a way nothing else here could report — a
3089-
* member pin constrains the shape the RENDERER READS (objectui#8068's
3090-
* criterion), and this key is not read at all.
3091-
*
3092-
* Measured on `record:related_list.actions` while writing slice 2's four pins:
3093-
* `renderers/record-related-list.tsx` never touches `schema.actions`, and the
3094-
* row actions it does render come from `useRelatedRecordActions()` — the HOST's
3095-
* bridge, keyed on the child object, not on this array. The declaration
3096-
* (`type: 'array', of: 'string'`, "Action IDs available for related records")
3097-
* and the spec key `RecordRelatedListProps.actions` therefore both publish an
3098-
* authoring surface that changes nothing: no diagnostic, no `unknown-prop`, and
3099-
* a page that behaves identically with the key and without it.
3100-
*
3101-
* ⛔ NOT convertible by writing a better test. The honest pin available today is
3102-
* "this key is dead", which is a pin on CURRENT behaviour whose whole content is
3103-
* that the contract is wrong — and whether that is fixed by giving the key a
3104-
* read site or by retiring it is an ADR-0049 enforce-or-remove question the SPEC
3105-
* owns, not one a renderer-side member pin should settle.
3106-
*
3107-
* NOT filed as a card of its own, deliberately: objectui#7300 is open on the
3108-
* same mechanism from the other side — a page cannot declare its related lists
3109-
* read-only BECAUSE the affordances are host-resolved from the child object
3110-
* rather than from the node. This key is what that mechanism looks like on the
3111-
* authoring surface, so it belongs to that decision rather than beside it. This
3112-
* entry moves when it lands, in whichever direction it lands.
3113-
*/
3114-
const NO_READ_SITE_TO_PIN =
3115-
'No member pin is possible: nothing reads this key. A member pin constrains the shape the ' +
3116-
'RENDERER reads (objectui#8068), and `renderers/record-related-list.tsx` never touches ' +
3117-
'`schema.actions` — the row/toolbar actions it renders come from the host bridge ' +
3118-
'`useRelatedRecordActions()`, keyed on the child object. Declared by the registration AND by ' +
3119-
'the spec (`RecordRelatedListProps.actions`) while changing nothing that renders. Measured by ' +
3120-
'objectui#8071 slice 2; the fix is an ADR-0049 enforce-or-remove decision the spec owns ' +
3121-
'(give the key a read site, or retire it), not a pin. Same mechanism as objectui#7300, which ' +
3122-
'is open on the affordance side of it.';
3091+
// `NO_READ_SITE_TO_PIN` — the reason `record:related_list.actions` carried from
3092+
// objectui#8071 slice 2 ("No member pin is possible: nothing reads this key")
3093+
// — was DELETED by objectui#11163 together with its one entry. The maintainer
3094+
// ruled ENFORCE on objectstack-ai/objectstack#20665 (batch #247, 「业务上需要支持,
3095+
// ENFORCE」): `renderers/record-related-list.tsx` now reads `schema.actions`, so
3096+
// the key has the read site the constant said it lacked, and its member pin is
3097+
// registered in `MEMBER_PINS`. A reason no entry carries is a claim nothing
3098+
// checks, the same ground `AWAITING_A_PIN` went on. The docblocks further down
3099+
// still name it in their per-slice history; read those mentions as the retired
3100+
// constant.
31233101

31243102
/**
31253103
* Array/object-armed inputs accepted WITHOUT a member pin for now, each with the
@@ -3201,9 +3179,11 @@ const MEMBER_PIN_EXEMPTIONS: Record<string, string> = {
32013179
// this header stays only as a note for the next reader who greps for it.
32023180

32033181
// record:related_list — objectui#8071 slice 2 pinned `columns`, `dataSource`,
3204-
// `filter` and `sort`. `actions` is the one left, and it is left for a
3205-
// DIFFERENT reason: see the constant.
3206-
'record:related_list.actions': NO_READ_SITE_TO_PIN,
3182+
// `filter` and `sort`; objectui#11163 pinned `actions`, the key that was left
3183+
// for a reason of its own (no read site) until the ENFORCE ruling on
3184+
// objectstack-ai/objectstack#20665 gave it one. The block is now fully
3185+
// pinned, and this header stays only as a note for the next reader who
3186+
// greps for it.
32073187

32083188
// object-calendar — objectui#8176 brought both `calendar` and `dataSource`
32093189
// into this population (see `NEWLY_JUDGED_UNPINNED_MEMBERS`'s docblock);
@@ -4027,8 +4007,20 @@ const NEWLY_JUDGED_UNPINNED_MEMBERS: string[] = [];
40274007
* and deleted their two booked entries, so the ceiling follows the list down
40284008
* in the same change. The four left are the `element:*` bookings, owed by the
40294009
* card's later slices.
4010+
*
4011+
* ## 5 -> 4 (objectui#11163) — `record:related_list.actions` gets its read site
4012+
*
4013+
* The one unit that was not a booking — the `NO_READ_SITE_TO_PIN` key slice 18
4014+
* left as this constant's whole remainder — moved the way its reason said it
4015+
* would: with the enforce-or-remove decision the spec owns. The maintainer ruled
4016+
* ENFORCE on objectstack-ai/objectstack#20665, `renderers/record-related-list.tsx`
4017+
* reads the key, and the pin is registered. -1 exemption, -1 ceiling, +1 pin;
4018+
* the constant is deleted with its entry. ⚠️ Not 1 -> 0 as the card was written:
4019+
* the objectui#11111 bookings above landed between the card and this change, so
4020+
* the four `element:*` units owed to objectui#11168 are what remain, and they
4021+
* move only with that card.
40304022
*/
4031-
const MEMBER_PIN_EXEMPTION_CEILING = 5;
4023+
const MEMBER_PIN_EXEMPTION_CEILING = 4;
40324024

40334025
/**
40344026
* Every test file a member pin can live in, as LAZY `?raw` loaders.

0 commit comments

Comments
 (0)