From 784e3f03e2226d5405a30edc8a02540a1df5205c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 12:27:52 +0000 Subject: [PATCH 1/2] docs(spec): the bulk path now carries a promoted lookup param's picker settings The bulk adapter's toBulkParam forwards lookupFilters, lookupColumns, lookupPageSize and descriptionField since the plugin-grid fix landed, so the BULK_PARAM_WIDGET_CONFIG_KEYS JSDoc, its refusal prescription, the list.bulkActionDefs liveness row and the v18 migration entry stop saying they are dropped. idField / titleFormat still do not travel. Prose only; the accept set does not change. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01WGufzh5MQsqK7dBe3KZ9Aa --- packages/spec/liveness/view.json | 2 +- ...-bulk-action-param-unknown-keys-refused.ts | 12 ++++--- packages/spec/src/ui/bulk-action.test.ts | 7 ++-- packages/spec/src/ui/bulk-action.zod.ts | 32 ++++++++++--------- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/packages/spec/liveness/view.json b/packages/spec/liveness/view.json index f3b690913cc..35932782519 100644 --- a/packages/spec/liveness/view.json +++ b/packages/spec/liveness/view.json @@ -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", diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-bulk-action-param-unknown-keys-refused.ts b/packages/spec/src/migrations/entries/semantic/18.ui-bulk-action-param-unknown-keys-refused.ts index 0bb1153d5ef..f37b4661c3b 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-bulk-action-param-unknown-keys-refused.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-bulk-action-param-unknown-keys-refused.ts @@ -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: @@ -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 ' diff --git a/packages/spec/src/ui/bulk-action.test.ts b/packages/spec/src/ui/bulk-action.test.ts index 916861d8447..bf7db305a8e 100644 --- a/packages/spec/src/ui/bulk-action.test.ts +++ b/packages/spec/src/ui/bulk-action.test.ts @@ -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`'); }); diff --git a/packages/spec/src/ui/bulk-action.zod.ts b/packages/spec/src/ui/bulk-action.zod.ts index 56e3f56d6f1..9a7277590f3 100644 --- a/packages/spec/src/ui/bulk-action.zod.ts +++ b/packages/spec/src/ui/bulk-action.zod.ts @@ -144,7 +144,7 @@ export type BulkActionExecution = z.input; * 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 @@ -157,15 +157,16 @@ export type BulkActionExecution = z.input; * 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', @@ -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 ' From 0b20e1f14256008e02bbb74712e5adaba7b94632 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 12:28:01 +0000 Subject: [PATCH 2/2] chore(changeset): spec patch for the bulk lookup-param prose Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01WGufzh5MQsqK7dBe3KZ9Aa --- .changeset/27050-bulk-lookup-param-prose.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/27050-bulk-lookup-param-prose.md diff --git a/.changeset/27050-bulk-lookup-param-prose.md b/.changeset/27050-bulk-lookup-param-prose.md new file mode 100644 index 00000000000..3d7b74ecf62 --- /dev/null +++ b/.changeset/27050-bulk-lookup-param-prose.md @@ -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.