Summary
Found while implementing #5142 (the object-list toolbar's Import button consuming importPredicates).
createPredicates has exactly one consumer in objectui: RelatedRecordActionsBridge (PR for #4646). That closed the related-list toolbar. The standalone object-list page has two more create entry points, and neither reads the key:
packages/app-shell/src/views/ObjectView.tsx:2129 header "New" button (b4089beca)
packages/app-shell/src/views/ObjectView.tsx:2194 phone-only floating "+" (b4089beca)
Both gate on affordances.create && can(objectDef.name, 'create') — the object-level verdict only. affordances.createPredicates is right there on the same resolved object and is dropped.
Why the standalone list is in scope for a toolbar predicate
The spec does not treat "standalone list" as a surface where toolbar predicates are inapplicable — it states the binding for it explicitly (RowCrudActionOverrideSchema docblock, @objectstack/spec@17.0.0):
> Toolbar affordances (create, import) have no row to bind … They evaluate once per toolbar against the record in scope where the toolbar renders: on a record page's related list that is the host (parent) record. On a standalone object list there is no record in scope, so a predicate reading record.* has nothing to bind and — per the fail-closed rule above — hides the button.
So the surface is in scope; what is absent there is only the record. Predicates over the host scope (os.user.*, features.*) bind normally and are the meaningful shape, and a record.* one is supposed to fail closed. Today the object-list page does neither: it ignores the envelope entirely.
Impact
An author writing one userActions.create object form gets two different verdicts for the same declaration, depending on which surface renders the button: honoured on a record page's related list, ignored on the object list page. visibleWhen: false — the objectui#3492 shape — does not hide the object list's "New".
Not reported from the field. Severity judged at filing time is unreliable in both directions (objectui#4646 came from a downstream project, #5142's twin did not), so this is filed plainly for triage rather than graded here.
Deliberately NOT done in the PR for #5142
That PR is scoped to the import key: it adds the consumer for importPredicates at ObjectView's single Import entry and widens UserActionsOverride.import to match. Extending it to create would have changed a second affordance on the same surface under an issue that names only one, so the create half is recorded here instead. The consumption shape it would mirror is already in the same file after that PR (importVisible / importDisabled, useRowPredicate with fallback: false, ?? true for visibleWhen, != null for disabledWhen), and UserActionsOverride.create is already the widened union — so this is the consumer half only, no type change needed.
Note the mobile FAB is a second render point of the same affordance, so both need the gate or they disagree with each other. (Import has no mobile counterpart by design — CSV import is desktop-only there — which is why #5142 had exactly one entry to close.)
Related
Summary
Found while implementing #5142 (the object-list toolbar's Import button consuming
importPredicates).createPredicateshas exactly one consumer in objectui:RelatedRecordActionsBridge(PR for #4646). That closed the related-list toolbar. The standalone object-list page has two more create entry points, and neither reads the key:Both gate on
affordances.create && can(objectDef.name, 'create')— the object-level verdict only.affordances.createPredicatesis right there on the same resolved object and is dropped.Why the standalone list is in scope for a toolbar predicate
The spec does not treat "standalone list" as a surface where toolbar predicates are inapplicable — it states the binding for it explicitly (
RowCrudActionOverrideSchemadocblock,@objectstack/spec@17.0.0):> Toolbar affordances (
create,import) have no row to bind … They evaluate once per toolbar against the record in scope where the toolbar renders: on a record page's related list that is the host (parent) record. On a standalone object list there is no record in scope, so a predicate readingrecord.*has nothing to bind and — per the fail-closed rule above — hides the button.So the surface is in scope; what is absent there is only the record. Predicates over the host scope (
os.user.*,features.*) bind normally and are the meaningful shape, and arecord.*one is supposed to fail closed. Today the object-list page does neither: it ignores the envelope entirely.Impact
An author writing one
userActions.createobject form gets two different verdicts for the same declaration, depending on which surface renders the button: honoured on a record page's related list, ignored on the object list page.visibleWhen: false— the objectui#3492 shape — does not hide the object list's "New".Not reported from the field. Severity judged at filing time is unreliable in both directions (objectui#4646 came from a downstream project, #5142's twin did not), so this is filed plainly for triage rather than graded here.
Deliberately NOT done in the PR for #5142
That PR is scoped to the
importkey: it adds the consumer forimportPredicatesatObjectView's single Import entry and widensUserActionsOverride.importto match. Extending it tocreatewould have changed a second affordance on the same surface under an issue that names only one, so the create half is recorded here instead. The consumption shape it would mirror is already in the same file after that PR (importVisible/importDisabled,useRowPredicatewithfallback: false,?? trueforvisibleWhen,!= nullfordisabledWhen), andUserActionsOverride.createis already the widened union — so this is the consumer half only, no type change needed.Note the mobile FAB is a second render point of the same affordance, so both need the gate or they disagree with each other. (Import has no mobile counterpart by design — CSV import is desktop-only there — which is why #5142 had exactly one entry to close.)
Related
userActions.createpredicates — spec 17.0.0 shipped the declaration half, the renderer half is missing (createPredicatesproduced but never consumed) #4646 — the create half on related lists, fixed (PR fix(app-shell,plugin-list): 关联列表「+ New」消费 create 谓词,行内编辑开关补 update 权限门 (#4646, #4647) #5145).importPredicatesis produced by the spec resolver and consumed nowhere — the same inverse-symmetry gap as #4646, one key over #5142 — the import half on the object-list toolbar.userActions.delete.visibleWhen— the BOOLEAN form of the same key reaches all three delete surfaces, the predicate form only reaches the row kebab #4419 / [console] 记录详情页头不消费 userActions.<action>.visibleWhen:同一谓词列表行已生效、记录页头照旧渲染按钮 #4213 — same family on the detail header.