Skip to content

Commit c082af6

Browse files
docs(spec): a promoted bulk lookup param carries its field's picker settings, and the spec now says so (#27255)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBLWnL4JRuSrtWuf1RiKCCsJ)_ Fixes #27050 Ruling-ref: none (triage 6460237597) Claim: 6468678025 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么:** 只改文字:`bulk-action.zod.ts` 中 `BULK_PARAM_WIDGET_CONFIG_KEYS` 上方的 JSDoc 与拒收处方、`liveness/view.json` 的 `list.bulkActionDefs` 行、v18 迁移条目 `ui-bulk-action-param-unknown-keys-refused` 的升级说明,改为:批量操作的字段型查找参数会把字段的 `lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField` 带到选择栏的选择器,与单条记录对话框一致;`idField` / `titleFormat` 仍不带。 - **为什么改:** #27049 已让批量路径转发这四项,规格文字仍说「被丢弃」,会让作者去绕一个已不存在的问题。 - **风险与回滚:** 不改任何 schema,接受集合不变;只改发布包里的说明文字。回滚 = revert 本 PR。 - **先看:** `packages/spec/src/ui/bulk-action.zod.ts`(拒收处方字符串那一段)。 - **两分钟验证:** `git grep -n -E "dropped (by|on) the bulk|drops the lookup settings" -- packages ':!**/CHANGELOG.md'` 预期零命中。 - **需要你做的:** 无,席位落地。 ## What changes Prose only. The accept set of `BulkActionParamSchema` does not change, and no key is added to or removed from `BULK_PARAM_WIDGET_CONFIG_KEYS`. - **`packages/spec/src/ui/bulk-action.zod.ts`, JSDoc above `BULK_PARAM_WIDGET_CONFIG_KEYS`.** The promoted route now carries the field's lookup settings `lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField` to the selection bar's picker, as the single-record dialog does. "From THIS list only `accept` and `maxSize` make the trip" becomes "only those four and `accept` / `maxSize`", "all but two of these keys" becomes "all but six", and "declare it on the field and let the dialog inherit" is right for those six. The `idField` sentence stays: `idField` has no `FieldSchema` spelling, and the resolver's snake `id_field` read is still not carried onto the bulk param. - **Same file, the `BULK_PARAM_WIDGET_CONFIG_KEYS` refusal prescription.** It now lists the four picker settings among what the promoted route carries, says only `accept` / `maxSize` and those four arrive that way, and its remedy says "for one of those six move the param onto a promoted action". - **`packages/spec/liveness/view.json`, row `list.bulkActionDefs`.** "…and drops the lookup settings." becomes "…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." - **Another copy the merge made false: `packages/spec/src/migrations/entries/semantic/18.ui-bulk-action-param-unknown-keys-refused.ts`.** Its `replacement` and `reason` said only `accept` / `maxSize` reach the dialog and the four picker settings are "resolved and then dropped on the bulk path". Both now name the six keys. This is the upgrade guidance `os migrate meta` prints, so it ships in the package. - **`packages/spec/src/ui/bulk-action.test.ts`.** One comment said "only `accept` / `maxSize` make that trip"; it now names the four picker settings too. The assertions are unchanged and still hold: the prescription still contains the text `accept` / `maxSize`. - **Changeset.** `@objectstack/spec` patch (the prescription and the migration entry text ship in the package). Not touched: the `packages/spec/CHANGELOG.md` / `packages/rest/CHANGELOG.md` lines that quote the old text (release history). `skills/**`, `content/docs/**` and `ui/**` hold no copy of the claim (grep below), so this PR is not governed. Nothing generated reads these strings: the grep finds no generated copy of the prescription, the JSDoc or the liveness note, so there is nothing to regenerate. ## Measure first `ui/packages/plugin-grid/src/resolveBulkActions.ts` `toBulkParam` on `origin/main` `d745bb5e` (PR #27049 merged as `c4ad55ca`, an ancestor: `git merge-base --is-ancestor c4ad55c HEAD` → exit 0): ```ts ...(resolved.descriptionField !== undefined && { descriptionField: resolved.descriptionField }), ...(resolved.lookupColumns !== undefined && { lookupColumns: resolved.lookupColumns }), ...(resolved.lookupFilters !== undefined && { lookupFilters: resolved.lookupFilters }), ...(resolved.lookupPageSize !== undefined && { lookupPageSize: resolved.lookupPageSize }), ``` and its JSDoc: "`idField` / `titleFormat` are still not carried: the resolver reads them off undeclared snake keys only, and no bulk reader has asked for them." `toBulkParam` has no `idField` or `titleFormat` line. The resolver (`ui/packages/fields/src/resolveActionParams.ts`) fills them from `field.id_field` and `field.title_format`, and the other four from `field.descriptionField`, `field.lookupColumns`, `field.lookupFilters` and `field.lookupPageSize`, which `FieldSchema` declares (`packages/spec/src/data/field.zod.ts` L1823–1835). Copy sweep: `git grep -n -E "dropped (by|on) the bulk|resolved and then dropped|drops the lookup settings|dropped with the lookup settings|make the trip|only .?accept.? (and|/) .?maxSize"` over `packages/**`, `ui/**`, `skills/**` and `content/docs/**`, CHANGELOGs excluded. On base it found the three sentences on the card, the migration entry (two places) and the test comment. On head, every hit of the last two patterns names the six keys, and the first four patterns have zero hits (`git grep` exit 1). ## Verification Base `origin/main` `d745bb5e`; head `0b20e1f14`. Every reading below is from that head. <details><summary>Spec tests and typecheck</summary> - `pnpm --filter @objectstack/spec build` under `os-verify-lock` → exit 0. - `pnpm --filter @objectstack/spec test && pnpm --filter @objectstack/spec typecheck` under `os-verify-lock` → `VERDICT command-exit 0`; Test Files 743 passed (743), Tests 22263 passed, 1 todo; `check:test-typecheck: OK`. - `test:repo` not run locally: the diff edits no repo-project test and the derived list does not name it. CI's Test Core runs it. </details> <details><summary>Gates</summary> - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` → 83 commands, each run serially and recorded as `CMD :: exit N`. - 81 exit 0, among them `check-adr-0087-registration --base origin/main`, `check:liveness`, `check-changeset-no-major`. Two first exited 3 on a prerequisite and were re-run after it was met: `check-plugin-teardown-shape --self-test` (pinned fixture commit fetched) → exit 0; `check:doc-formula-expressions` (formula and lint built) → exit 0. - NOT MEASURED, reason: `check:dual-build-cjs-loads` and `check:lean-entry-closure` exit 3, PREREQUISITE NOT MET (they load every package's built entry points, which needs a full build). CI's Build Core runs them. - `dispatch-gates --ran` → `✓ 83 derived famil(ies) accounted for — 81 run, 2 NOT-MEASURED`. - `check-changeset-no-major` with this body as a pull_request event → exit 0 (`Metadata-protocol: none`, patch). - Control-byte scan of the three edited prose files → no hits. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzh5MQsqK7dBe3KZ9Aa --- _Generated by [Claude Code](https://claude.ai/code/session_01WGufzh5MQsqK7dBe3KZ9Aa)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1287b2f commit c082af6

5 files changed

Lines changed: 39 additions & 24 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): a promoted bulk action's lookup param carries its field's picker settings, and the spec now says so
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
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.

‎packages/spec/liveness/view.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@
195195
"bulkActionDefs": {
196196
"status": "live",
197197
"evidence": "packages/spec/src/ui/bulk-action.zod.ts",
198-
"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."
198+
"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."
199199
},
200200
"conditionalFormatting": {
201201
"status": "live",

‎packages/spec/src/migrations/entries/semantic/18.ui-bulk-action-param-unknown-keys-refused.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ export const entry: SemanticMigration = {
3434
+ 'the picker knobs `lookupFilters` / `lookupColumns` / `lookupPageSize` / '
3535
+ '`descriptionField` / `picker` / `subtitle` / `avatarField` / `idField` / `allowCreate`) '
3636
+ 'is answered with one prescription naming `FieldSchema` as the shape those keys are real '
37-
+ 'on, and saying that of them only `accept` / `maxSize` declared there reach this dialog, '
38-
+ 'through a promoted action. `dependsOn` needs NO edit — it is declared, in the same '
37+
+ 'on, and saying that of them only `accept` / `maxSize` and the picker settings '
38+
+ '`lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField` declared there '
39+
+ 'reach this dialog, through a promoted action. `dependsOn` needs NO edit — it is declared, in the same '
3940
+ 'shape the field-level key takes (`[\'parent\']`, or `[{ field, param }]` when the remote '
4041
+ 'filter key differs).',
4142
reason:
@@ -64,9 +65,10 @@ export const entry: SemanticMigration = {
6465
+ 'does not reach this dialog either. The one route that consults the field is a PROMOTED '
6566
+ 'action — an action the object declares, named in the view\'s `bulkActions`, whose '
6667
+ 'field-backed params (`{ field }`) are resolved through the single-record dialog\'s '
67-
+ 'resolver when the bulk dialog opens — and of these keys only `accept` / `maxSize` arrive '
68-
+ 'that way: `lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField` are '
69-
+ 'resolved and then dropped on the bulk path, and the rest are never read off the field. '
68+
+ 'resolver when the bulk dialog opens — and of these keys only `accept` / `maxSize` and '
69+
+ 'the picker settings `lookupFilters` / `lookupColumns` / `lookupPageSize` / '
70+
+ '`descriptionField` arrive that way, reaching the selection bar\'s picker as they reach '
71+
+ 'the single-record dialog\'s; the rest are never read off the field. '
7072
+ 'A census of authored bulk params taken at registration time over the two repositories '
7173
+ 'reachable from that session found ZERO carrying an undeclared key '
7274
+ '(objectstack@176b03582e: 7 param literals; objectui@3e4f6324f7: 3), so no in-corpus '

‎packages/spec/src/ui/bulk-action.test.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ describe('BulkActionDefSchema — the def shape is typed, not `z.any()`', () =>
7777
expect(issues).toContain('FieldSchema');
7878
// ⛔ The prescription must NOT send this author to the field: a key
7979
// declared there reaches the bulk dialog only through a PROMOTED action
80-
// (`bulkActions`), and of this family only `accept` / `maxSize` make that
81-
// trip. Both are named by subject, so the rejection says which route and
82-
// which keys rather than implying `min` would follow.
80+
// (`bulkActions`), and of this family only `accept` / `maxSize` and the
81+
// four lookup picker settings make that trip. They are named by subject,
82+
// so the rejection says which route and which keys rather than implying
83+
// `min` would follow.
8384
expect(issues).toContain('`bulkActions`');
8485
expect(issues).toContain('`accept` / `maxSize`');
8586
});

‎packages/spec/src/ui/bulk-action.zod.ts‎

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export type BulkActionExecution = z.input<typeof BulkActionExecutionSchema>;
144144
* to name it beside min/max/step, and the sweep found no FORM widget reading it
145145
* at all.
146146
*
147-
* ⚠️ The prescription refuses the obvious-sounding remedy for all but two of
147+
* ⚠️ The prescription refuses the obvious-sounding remedy for all but six of
148148
* these keys. An AUTHORED def's params reach the dialog as written: nothing on
149149
* that path consults the object's field definitions. The bulk surface's
150150
* field-backed route is the PROMOTED def, an action the object declares, named
@@ -157,15 +157,16 @@ export type BulkActionExecution = z.input<typeof BulkActionExecutionSchema>;
157157
* carries the field's `type`, translated `label`, `options`, `required`,
158158
* `description` (as the help text), `placeholder`, `defaultValue`, `multiple`,
159159
* `accept` and `maxSize`, plus a reference-bearing field's `reference`,
160-
* `displayField` and `dependsOn`. From THIS list only `accept` and `maxSize`
161-
* make the trip. The lookup settings `lookupFilters` / `lookupColumns` /
162-
* `lookupPageSize` / `descriptionField` are resolved and then dropped by the
163-
* bulk adapter, and the rest are never read off the field (`idField` has no
160+
* `displayField` and `dependsOn`, and that field's lookup settings
161+
* `lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField`,
162+
* which reach the selection bar's picker as they reach the single-record
163+
* dialog's. From THIS list only those four and `accept` / `maxSize` make the
164+
* trip, and the rest are never read off the field (`idField` has no
164165
* `FieldSchema` spelling to declare; the resolver's snake `id_field` read is
165-
* dropped with the lookup settings). So
166-
* "declare it on the field and let the dialog inherit" is right for `accept` /
167-
* `maxSize` on a promoted def, and a confidently wrong answer for every other
168-
* key here, the shape this campaign has already shipped more than once.
166+
* still not carried onto the bulk param). So
167+
* "declare it on the field and let the dialog inherit" is right for those six
168+
* keys on a promoted def, and a confidently wrong answer for every other key
169+
* here, the shape this campaign has already shipped more than once.
169170
*/
170171
const BULK_PARAM_WIDGET_CONFIG_KEYS = [
171172
'min', 'max', 'step', 'precision', 'scale', 'rows',
@@ -257,12 +258,13 @@ export const BulkActionParamSchema = lazySchema(() => strictObject(
257258
+ 'field-backed param (`{ field }`), named in the view\'s `bulkActions`. That route '
258259
+ 'carries the field\'s `type`, `label`, `options`, `required`, `description` (as the help '
259260
+ 'text), `placeholder`, `defaultValue`, `multiple`, `accept` and `maxSize`, plus a '
260-
+ 'reference field\'s `reference`, `displayField` and `dependsOn`. ⛔ So of these keys only '
261-
+ '`accept` / `maxSize` arrive that way: the picker settings `lookupFilters` / '
262-
+ '`lookupColumns` / `lookupPageSize` / `descriptionField` are dropped on the bulk path, '
263-
+ 'and the rest are never read off the field. Remove the key, or for `accept` / '
264-
+ '`maxSize` move the param onto a promoted action; open an issue if a bulk param '
265-
+ 'genuinely needs a key declared here.',
261+
+ 'reference field\'s `reference`, `displayField` and `dependsOn` and its picker settings '
262+
+ '`lookupFilters` / `lookupColumns` / `lookupPageSize` / `descriptionField`, which reach '
263+
+ 'the selection bar\'s picker as they reach the single-record dialog\'s. ⛔ So of these '
264+
+ 'keys only `accept` / `maxSize` and those four picker settings arrive that way, and the '
265+
+ 'rest are never read off the field. Remove the key, or for one of those six move the '
266+
+ 'param onto a promoted action; open an issue if a bulk param genuinely needs a key '
267+
+ 'declared here.',
266268
}],
267269
history:
268270
'Until this shape was closed, `params[]` was `.passthrough()` — every unknown key rode through '

0 commit comments

Comments
 (0)