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
10 changes: 10 additions & 0 deletions .changeset/27050-bulk-lookup-param-prose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@objectstack/spec': patch
---

docs(spec): a promoted bulk action's lookup param carries its field's picker settings, and the spec now says so

Clause-②: no
Metadata-protocol: none

A promoted bulk action's field-backed lookup param now brings the field's `lookupFilters`, `lookupColumns`, `lookupPageSize` and `descriptionField` to the selection bar's picker, the same as the single-record dialog. The text that still said those four are dropped on the bulk path is corrected: the `bulkActionDefs[].params[]` refusal for the widget-config keys, and the upgrade guidance of the `ui-bulk-action-param-unknown-keys-refused` migration entry. Both now name `accept`, `maxSize` and those four as the keys that reach the bulk dialog when declared on the field. `idField` and `titleFormat` still do not travel. The accepted keys do not change.
2 changes: 1 addition & 1 deletion packages/spec/liveness/view.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
"bulkActionDefs": {
"status": "live",
"evidence": "packages/spec/src/ui/bulk-action.zod.ts",
"note": "objectui: ListView.tsx:1343 forwards rich defs to ObjectGrid (BulkActionDialog). Dispatch: useBulkExecutor.ts run() — per-record fan-out by default; execution:'aggregate' defs go through the ONE-call bulkCall branch injecting params._selectedIds (ObjectGrid.runBulkActionAggregate, objectui#3139). Verified objectui@4bf612c. #4457 gave the def a SHAPE (it was z.record(z.any())): the entry schema is BulkActionDefSchema, strict, and it refuses the combinations that parse but the executor never reads — so this row's liveness now covers the keys inside a def, not just the array. A PROMOTED def's field-backed params (packages/spec/src/ui/bulk-action.zod.ts; moved out of spec source, #23434): resolved when the dialog opens by objectui main @dca25aff2 packages/plugin-grid/src/resolveBulkActions.ts#resolvePromotedBulkParams through `resolveActionParams` (objectui 29b99490), then renamed onto the bulk vocabulary by the same file's #toBulkParam, which carries the field's type, translated label, options, required, description, placeholder, defaultValue, multiple, `dependsOn`, `accept` and `maxSize` (plus a reference-bearing field's reference and display field) and drops the lookup settings. An authored param's `options[]` entries (packages/spec/src/ui/bulk-action.zod.ts, the `.passthrough()` rationale; moved out of spec source, #23434): objectui @b9814b334 packages/plugin-grid/src/components/bulkParamToField.ts#bulkParamToField spreads each entry into the field metadata (`options?.map(o => ({ ...o, value: String(o.value) }))`), so every extra key reaches the widget, whose option vocabulary is objectui @b9814b334 packages/types/src/field-types.ts#SelectOptionMetadata, extending objectui @b9814b334 packages/types/src/select-option.ts#SelectOptionBase (`color`, `visibleWhen`, `disabled` and `icon` beyond the pair); the select cell reads `option?.color` in objectui @b9814b334 packages/fields/src/index.tsx#SelectCellRenderer. The renderer's own param type now agrees: objectui @b9814b334 packages/types/src/objectql.ts#BulkActionParam types an `options` entry open (an index signature, citing objectstack#4001), where the source's sha-less reading had it closed (`Array<{ label; value }>`). Re-read 2026-10-15, first cited by line, sha-less."
"note": "objectui: ListView.tsx:1343 forwards rich defs to ObjectGrid (BulkActionDialog). Dispatch: useBulkExecutor.ts run() — per-record fan-out by default; execution:'aggregate' defs go through the ONE-call bulkCall branch injecting params._selectedIds (ObjectGrid.runBulkActionAggregate, objectui#3139). Verified objectui@4bf612c. #4457 gave the def a SHAPE (it was z.record(z.any())): the entry schema is BulkActionDefSchema, strict, and it refuses the combinations that parse but the executor never reads — so this row's liveness now covers the keys inside a def, not just the array. A PROMOTED def's field-backed params (packages/spec/src/ui/bulk-action.zod.ts; moved out of spec source, #23434): resolved when the dialog opens by objectui main @dca25aff2 packages/plugin-grid/src/resolveBulkActions.ts#resolvePromotedBulkParams through `resolveActionParams` (objectui 29b99490), then renamed onto the bulk vocabulary by the same file's #toBulkParam, which carries the field's type, translated label, options, required, description, placeholder, defaultValue, multiple, `dependsOn`, `accept` and `maxSize` (plus a reference-bearing field's reference and display field) and that field's lookup settings `lookupFilters`, `lookupColumns`, `lookupPageSize` and `descriptionField`, which reach the selection bar's picker as they reach the single-record dialog's (objectstack#26951); `idField` / `titleFormat` are still not carried. An authored param's `options[]` entries (packages/spec/src/ui/bulk-action.zod.ts, the `.passthrough()` rationale; moved out of spec source, #23434): objectui @b9814b334 packages/plugin-grid/src/components/bulkParamToField.ts#bulkParamToField spreads each entry into the field metadata (`options?.map(o => ({ ...o, value: String(o.value) }))`), so every extra key reaches the widget, whose option vocabulary is objectui @b9814b334 packages/types/src/field-types.ts#SelectOptionMetadata, extending objectui @b9814b334 packages/types/src/select-option.ts#SelectOptionBase (`color`, `visibleWhen`, `disabled` and `icon` beyond the pair); the select cell reads `option?.color` in objectui @b9814b334 packages/fields/src/index.tsx#SelectCellRenderer. The renderer's own param type now agrees: objectui @b9814b334 packages/types/src/objectql.ts#BulkActionParam types an `options` entry open (an index signature, citing objectstack#4001), where the source's sha-less reading had it closed (`Array<{ label; value }>`). Re-read 2026-10-15, first cited by line, sha-less."
},
"conditionalFormatting": {
"status": "live",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ export const entry: SemanticMigration = {
+ 'the picker knobs `lookupFilters` / `lookupColumns` / `lookupPageSize` / '
+ '`descriptionField` / `picker` / `subtitle` / `avatarField` / `idField` / `allowCreate`) '
+ 'is answered with one prescription naming `FieldSchema` as the shape those keys are real '
+ 'on, and saying that of them only `accept` / `maxSize` declared there reach this dialog, '
+ 'through a promoted action. `dependsOn` needs NO edit — it is declared, in the same '
+ 'on, and saying that of them only `accept` / `maxSize` and the picker settings '
+ '`lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField` declared there '
+ 'reach this dialog, through a promoted action. `dependsOn` needs NO edit — it is declared, in the same '
+ 'shape the field-level key takes (`[\'parent\']`, or `[{ field, param }]` when the remote '
+ 'filter key differs).',
reason:
Expand Down Expand Up @@ -64,9 +65,10 @@ export const entry: SemanticMigration = {
+ 'does not reach this dialog either. The one route that consults the field is a PROMOTED '
+ 'action — an action the object declares, named in the view\'s `bulkActions`, whose '
+ 'field-backed params (`{ field }`) are resolved through the single-record dialog\'s '
+ 'resolver when the bulk dialog opens — and of these keys only `accept` / `maxSize` arrive '
+ 'that way: `lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField` are '
+ 'resolved and then dropped on the bulk path, and the rest are never read off the field. '
+ 'resolver when the bulk dialog opens — and of these keys only `accept` / `maxSize` and '
+ 'the picker settings `lookupFilters` / `lookupColumns` / `lookupPageSize` / '
+ '`descriptionField` arrive that way, reaching the selection bar\'s picker as they reach '
+ 'the single-record dialog\'s; the rest are never read off the field. '
+ 'A census of authored bulk params taken at registration time over the two repositories '
+ 'reachable from that session found ZERO carrying an undeclared key '
+ '(objectstack@176b03582e: 7 param literals; objectui@3e4f6324f7: 3), so no in-corpus '
Expand Down
7 changes: 4 additions & 3 deletions packages/spec/src/ui/bulk-action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ describe('BulkActionDefSchema — the def shape is typed, not `z.any()`', () =>
expect(issues).toContain('FieldSchema');
// ⛔ The prescription must NOT send this author to the field: a key
// declared there reaches the bulk dialog only through a PROMOTED action
// (`bulkActions`), and of this family only `accept` / `maxSize` make that
// trip. Both are named by subject, so the rejection says which route and
// which keys rather than implying `min` would follow.
// (`bulkActions`), and of this family only `accept` / `maxSize` and the
// four lookup picker settings make that trip. They are named by subject,
// so the rejection says which route and which keys rather than implying
// `min` would follow.
expect(issues).toContain('`bulkActions`');
expect(issues).toContain('`accept` / `maxSize`');
});
Expand Down
32 changes: 17 additions & 15 deletions packages/spec/src/ui/bulk-action.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export type BulkActionExecution = z.input<typeof BulkActionExecutionSchema>;
* to name it beside min/max/step, and the sweep found no FORM widget reading it
* at all.
*
* ⚠️ The prescription refuses the obvious-sounding remedy for all but two of
* ⚠️ The prescription refuses the obvious-sounding remedy for all but six of
* these keys. An AUTHORED def's params reach the dialog as written: nothing on
* that path consults the object's field definitions. The bulk surface's
* field-backed route is the PROMOTED def, an action the object declares, named
Expand All @@ -157,15 +157,16 @@ export type BulkActionExecution = z.input<typeof BulkActionExecutionSchema>;
* carries the field's `type`, translated `label`, `options`, `required`,
* `description` (as the help text), `placeholder`, `defaultValue`, `multiple`,
* `accept` and `maxSize`, plus a reference-bearing field's `reference`,
* `displayField` and `dependsOn`. From THIS list only `accept` and `maxSize`
* make the trip. The lookup settings `lookupFilters` / `lookupColumns` /
* `lookupPageSize` / `descriptionField` are resolved and then dropped by the
* bulk adapter, and the rest are never read off the field (`idField` has no
* `displayField` and `dependsOn`, and that field's lookup settings
* `lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField`,
* which reach the selection bar's picker as they reach the single-record
* dialog's. From THIS list only those four and `accept` / `maxSize` make the
* trip, and the rest are never read off the field (`idField` has no
* `FieldSchema` spelling to declare; the resolver's snake `id_field` read is
* dropped with the lookup settings). So
* "declare it on the field and let the dialog inherit" is right for `accept` /
* `maxSize` on a promoted def, and a confidently wrong answer for every other
* key here, the shape this campaign has already shipped more than once.
* still not carried onto the bulk param). So
* "declare it on the field and let the dialog inherit" is right for those six
* keys on a promoted def, and a confidently wrong answer for every other key
* here, the shape this campaign has already shipped more than once.
*/
const BULK_PARAM_WIDGET_CONFIG_KEYS = [
'min', 'max', 'step', 'precision', 'scale', 'rows',
Expand Down Expand Up @@ -257,12 +258,13 @@ export const BulkActionParamSchema = lazySchema(() => strictObject(
+ 'field-backed param (`{ field }`), named in the view\'s `bulkActions`. That route '
+ 'carries the field\'s `type`, `label`, `options`, `required`, `description` (as the help '
+ 'text), `placeholder`, `defaultValue`, `multiple`, `accept` and `maxSize`, plus a '
+ 'reference field\'s `reference`, `displayField` and `dependsOn`. ⛔ So of these keys only '
+ '`accept` / `maxSize` arrive that way: the picker settings `lookupFilters` / '
+ '`lookupColumns` / `lookupPageSize` / `descriptionField` are dropped on the bulk path, '
+ 'and the rest are never read off the field. Remove the key, or for `accept` / '
+ '`maxSize` move the param onto a promoted action; open an issue if a bulk param '
+ 'genuinely needs a key declared here.',
+ 'reference field\'s `reference`, `displayField` and `dependsOn` and its picker settings '
+ '`lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField`, which reach '
+ 'the selection bar\'s picker as they reach the single-record dialog\'s. ⛔ So of these '
+ 'keys only `accept` / `maxSize` and those four picker settings arrive that way, and the '
+ 'rest are never read off the field. Remove the key, or for one of those six move the '
+ 'param onto a promoted action; open an issue if a bulk param genuinely needs a key '
+ 'declared here.',
}],
history:
'Until this shape was closed, `params[]` was `.passthrough()` — every unknown key rode through '
Expand Down
Loading