Summary
The selection bar's built-in Delete ignores userActions.delete.visibleWhen. Tick only a row the predicate excludes and the bar still offers Delete.
Unlike the detail-header half (filed separately as a straight defect), this one needs a semantics decision before it can be implemented — a bulk action spans a set of records, and a per-record predicate has more than one defensible meaning over a set.
Repro (framework examples/app-showcase, console pinned 6314e87, ADMIN — permissions not a variable)
showcase_invoice declares (src/data/objects/invoice.object.ts:58):
delete: {visibleWhen: P`record.status != 'paid'`}Tick only the paid invoice INV-1011 (predicate false) → the selection bar renders the red Delete. The row kebab for the same record correctly hides it.
Boolean control — temporarily delete: false, artifact rebuilt: the bar's Delete disappears (bulkDelete: 0). So the bar honours the key's boolean form and only drops the predicate.
Where
plugin-list/src/ListView.tsx:887 (permittedBulkActions) intersects the declared bulk actions with
resolveEffectiveCrudAffordances(objectDef,effectiveApiOps).delete&&canDo(objectName,'delete')
— bucket ∧ userActions.deleteas a boolean ∧ apiOperations ∧ the principal's allowDelete. No per-record layer exists here, and the grid path (ObjectGrid's own bar) is the same. The bar does have the selected rows in hand, so the input is available.
The decision needed
Three defensible behaviours when the selection contains predicate-excluded rows:
- Filter the operation — run Delete over the allowed subset only, and report the skipped ones (
BulkActionDialog already has a bulk-skipped-notice slot for exactly this shape). - Gate the button — hide/disable Delete while any excluded row is selected. Simplest to reason about, but one stray tick disables the whole bar.
- Leave it — accept that
visibleWhen is a per-row affordance and out of scope for set operations, and say so in the spec text so authors stop expecting it.
Option 1 matches what the dialog is already built for and keeps the predicate meaningful without punishing a mixed selection; option 3 is legitimate but should then be documented, because today the same key behaves differently on two surfaces with no note anywhere.
Whichever is chosen, the pairing worth avoiding is the current one: honoured on the row, silently ignored on the bar.
Related
Summary
The selection bar's built-in Delete ignores
userActions.delete.visibleWhen. Tick only a row the predicate excludes and the bar still offers Delete.Unlike the detail-header half (filed separately as a straight defect), this one needs a semantics decision before it can be implemented — a bulk action spans a set of records, and a per-record predicate has more than one defensible meaning over a set.
Repro (framework
examples/app-showcase, console pinned6314e87, ADMIN — permissions not a variable)showcase_invoicedeclares (src/data/objects/invoice.object.ts:58):Tick only the paid invoice
INV-1011(predicate false) → the selection bar renders the red Delete. The row kebab for the same record correctly hides it.Boolean control — temporarily
delete: false, artifact rebuilt: the bar's Delete disappears (bulkDelete: 0). So the bar honours the key's boolean form and only drops the predicate.Where
plugin-list/src/ListView.tsx:887(permittedBulkActions) intersects the declared bulk actions with— bucket ∧
userActions.deleteas a boolean ∧ apiOperations ∧ the principal'sallowDelete. No per-record layer exists here, and the grid path (ObjectGrid's own bar) is the same. The bar does have the selected rows in hand, so the input is available.The decision needed
Three defensible behaviours when the selection contains predicate-excluded rows:
BulkActionDialogalready has abulk-skipped-noticeslot for exactly this shape).visibleWhenis a per-row affordance and out of scope for set operations, and say so in the spec text so authors stop expecting it.Option 1 matches what the dialog is already built for and keeps the predicate meaningful without punishing a mixed selection; option 3 is legitimate but should then be documented, because today the same key behaves differently on two surfaces with no note anywhere.
Whichever is chosen, the pairing worth avoiding is the current one: honoured on the row, silently ignored on the bar.
Related
userActions.delete.visibleWhen— the BOOLEAN form of the same key reaches all three delete surfaces, the predicate form only reaches the row kebab #4419