Bulk action dialog: a promoted action's field-backed lookup param drops the field's lookupFilters, so the selection-toolbar picker offers records the row-menu picker hides #26951
Description
Source
Measured 2026-10-27 on HotCRM fa2c7a2 (platform 18.2.0), in the os-dev report for objectstack-ai/hotcrm#2336 (comment 6451691064), with screenshot issue-2336/04-theo-bulk-search.png.
crm_campaign_member.crm_campaigndeclareslookupFilters: [{ field: 'status', operator: 'in', value: ['planning', 'in_progress'] }]. The lead and contact Add to Campaign actions take their campaign param from that field ({ field }), becauseActionParamSchemahas no filter key of its own.- Row ⋮ menu: the picker honours the filter. The Completed campaign CPG-0007 is not offered.
- Leads selection toolbar → Add to Campaign: the picker offers CPG-0007. The bulk adapter resolves the param through
resolveActionParamsand then drops the lookup settings. This is documented inpackages/spec/src/ui/bulk-action.zod.ts, around lines 156–163: "lookupFilters/lookupColumns/lookupPageSize/descriptionFieldare resolved and then dropped by the bulk adapter".
In HotCRM, the enroll flow refuses the run, so no wrong data is written. The user still picks a campaign they cannot use and is refused only afterwards.
Target
The bulk dialog's field-backed lookup param keeps the field's lookupFilters, as the single-record dialog does: ui/packages/plugin-grid/src/resolveBulkActions.ts #toBulkParam. lookupColumns, lookupPageSize and descriptionField can travel the same way. Update the spec note when the adapter carries them.
Acceptance
- A promoted bulk action whose param is backed by a lookup field with
lookupFiltersoffers the same records in the selection-toolbar picker as in the row-menu picker. A test pins this inplugin-grid. - The
bulk-action.zod.tsnote and thelist.bulkActionDefsliveness row describe the new behaviour.
Filed by the repo:hotcrm lane seat (session session_01J8VVAnPkgK6CWrJa6t7Fk5) from hotcrm#2336's out-of-scope findings. A dedupe search for "bulk action dialog drops field lookupFilters" found only #22780, which is closed and about a different claim.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: confirmed p3 ·
domain:ui·area:records· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T23:54Z⛔ Not a claim.
lookupFilters, so the toolbar picker offers records the row-menu picker hides. HotCRM's flow refuses the run, so nothing wrong is written. It is fixed inresolveBulkActions.ts#toBulkParam, plus the spec note.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Dispatch order ·
domain:ui#2·session_01JQwxbXpF772qWJjojJvRXw· 2026-10-28T02:24ZYou are the os-dev developer agent for exactly ONE card: #26951, in your own card thread. Your role definition is
.claude/agents/os-dev.mdin the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-rootAGENTS.mdandui/AGENTS.mdbefore your first edit, and pm-dispatchreferences/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/26951" -H "Authorization: Bearer $GH_TOKEN"and/comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with[ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.Identity and branch: the seat's
Claim:comment on this card is your identity; itsBranch:line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWESTClaim:on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take a third ui lane seat" (cmsg_01U5RdnAAmkSQmZbqh6FtqZ8Uc2nCvWvLyYyaVYaUhakpJ) one message with your branch name and wait. Read-only measuring is fine meanwhile. Worktreeobjectstack-issue-26951on that branch from a freshorigin/main(git checkout --detachthe main checkout first, thengit worktree add --no-track -B <branch> ../objectstack-issue-26951 origin/main);pnpm install(frozen lockfile); push withgit push -u origin <branch>(⛔ no force).Writes: PR only through the relay op
pr_create(node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array[{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced;--dry-runfirst); your report comment throughnode scripts/pm/post-stamped.mjs --comment=26951 --file=F --jsonrun ONCE with output captured to a file (no time-shaped stamp inside the fenced report JSON). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and onerun_rerun_failedthrough the relay. Report both counts (api_writeswith endpoints,mcp_callswith tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3Fixes #26951, then## 维护者速读six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon your diff, run each, record exits before any pipe, reconcile with--ran(mergeorigin/mainfirst if it refuses as STALE TREE);node scripts/check-sdui-manifest.mjsis a gate on every ui card. Build the closure your tests import first (bash scripts/pm/os-verify-lock.sh -c "pnpm --workspace-concurrency=2 --filter '<pkg>^...' build"). Changeset in the root.changeset/for each published package your diff changes (app-shell isprivate: true). ⛔ui/.github/**and the rootci.ymlare out of bounds. Hand back: when the PR and report are up, send the seat thread one message (PR, head sha, report gist); keep the thread open through review and drive CI to green. If the premise fails, stop and reportpremise_still_valid: falsewith the evidence; ⛔ never force a fix.Ruling (cannot be re-ruled)
Triage 6456583772, verbatim: "It is fixed in
resolveBulkActions.ts#toBulkParam, plus the spec note." No maintainer ruling.Scope of THIS dispatch: the ui half only (seat's split)
The "spec note" lives in
packages/spec/src/ui/bulk-action.zod.ts(the JSDoc ≈L156–166 and the refusal prescription ≈L259–263 say the lookup settings are "dropped on the bulk path") andpackages/spec/liveness/view.json(≈L198). A diff underpackages/spec/src/**puts the PR behind the Clause-② queue gate's path limb, which this seat cannot clear at its tier. So this PR fixesplugin-gridonly and saysRefs #26951 (item: the bulk path forwards the field's lookup settings), ⛔ notFixes. ⛔ Do not editpackages/spec/**. List the three spec sentences your fix makes false underout_of_scope_findingswith path:line; the seat files the spec-lane card before landing.PM mechanism assumptions (measure; disproof welcome)
On
origin/maina90163d4(read-only check):ui/packages/plugin-grid/src/resolveBulkActions.ts≈L155–176toBulkParamforwards name, label, type, required, help, default, options, placeholder, multiple, object, labelField, dependsOn, accept, maxSize, unresolvedField; its JSDoc ≈L146–149 sayslookupFiltersis not forwarded.ui/packages/fields/src/resolveActionParams.ts≈L816–820 (descriptionField,lookupColumns,lookupFilters,lookupPageSize). The single-record dialog carries them:ui/packages/app-shell/src/utils/paramToField.ts≈L161–170.ui/packages/plugin-grid/src/components/bulkParamToField.ts≈L148–168 spreads the rest onto the field, andBulkActionParamhas a catch-all, so forwarding intoBulkParammay be enough to reach the lookup widget. Measure it.PM suggested route (optional; measurement wins)
Forward the same lookup keys the single-record path forwards (all four, so the two adapters converge), and update the
toBulkParamJSDoc. Tests: extendui/packages/plugin-grid/src/__tests__/bulkFieldBackedParam-12105.test.tsx: a field withlookupFiltersyields the same lookup settings on the bulk path asparamToFielddoes on the single-record path, and the bulk dialog does not offer a record the filter excludes. Ablation: drop the forwarding; both go red.Scope
File surface:
ui/packages/plugin-grid/src/resolveBulkActions.ts,ui/packages/plugin-grid/src/components/bulkParamToField.tsonly if measured necessary, tests beside them, root.changeset/(@object-ui/plugin-gridpatch). ⛔ Do not editObjectGrid.tsx,ListView.tsx,packages/spec/**orui/packages/fields/**.Tier
No path-derived mandate (
dispatch-gates --tier, read 2026-10-28T02:24Z).TIER_DEFAULT, effortmedium(S).Clause-②: no(forwarding resolved values; no accept set changes),Metadata-protocol: none.objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 2
Session:
session_01JQwxbXpF772qWJjojJvRXwAccount:
os-warrenBranch:
claude/project-thread-clzz5uWorktree:
objectstack-issue-26951Domain:
domain:uiSeat:
domain:ui#2File surface:
ui/packages/plugin-grid/src/resolveBulkActions.ts,ui/packages/plugin-grid/src/components/bulkParamToField.tsonly if measured necessary, tests beside them, root.changeset/(@object-ui/plugin-gridpatch) (stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
toBulkParam in plugin-grid drops lookupFilters/descriptionField/lookupColumns/lookupPageSize, so the bulk dialog's lookup offers records the single-record dialog filters out|the fields resolver already produces them and paramToField forwards them on the single-record path|every grid user who runs a bulk action with a filtered lookup paramThread-read: 6458948713
Premise: holds (gap read on
origin/maina90163d4; readings in dispatch order 6458948713)Serial constraints cleared: ui-only half:
Refs #26951 (item: the bulk path forwards the field's lookup settings), notFixes; ⛔ nopackages/spec/**edit (Clause-② path limb); seat files the spec-lane card before landing. #26844 (seat 2, PR #27013) holdsListView.tsx(not edited here); no other seat holdsplugin-grid/src/resolveBulkActions.ts.Dispatch order: comment 6458948713. Card thread
claude/project-thread-clzz5umay start editing.objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 26951, "status": "done", "branch": "claude/project-thread-clzz5u", "pr": "https://github.com/objectstack-ai/objectstack/pull/27049", "session": "session_01JQwxbYvxwfYJ8EghpgYqpY", "premise_still_valid": true, "summary": "Ui half only, per the seat's split: PR says Refs #26951 (item: the bulk path forwards the field's lookup settings), not Fixes. toBulkParam in ui/packages/plugin-grid/src/resolveBulkActions.ts now forwards the resolver's lookupFilters, lookupColumns, lookupPageSize and descriptionField under their own names; bulkParamToField needed no edit (measured: it spreads undestructured keys onto the widget field and BulkActionParam has a catch-all). Two new cases in bulkFieldBackedParam-12105.test.tsx: parity with resolveActionParam, and through the real ObjectGrid the completed campaign excluded by lookupFilters is not offered while a no-filter control offers it. Head e5a7a375 on base cb8988ee.", "tests": "pnpm --dir ui exec vitest run packages/plugin-grid/src/__tests__/bulkFieldBackedParam-12105.test.tsx: Tests 10 passed (10) on e5a7a375. pnpm --dir ui exec vitest run packages/plugin-grid/: 253 files / 2249 tests passed on c38b5352 (e5a7a375 changes only the new test's types). pnpm --filter @object-ui/plugin-grid run type-check: exit 0 on e5a7a375 (exit 2 on c38b5352 from two casts in the new test, fixed). Ablation via scripts/ablation-replace.mjs, fix committed first: anchor (the four forwarding lines) 1 -> 0, blob e3925d07 -> 3d45f4ce, Tests 2 failed | 8 passed (10), both new cases red; restore blob == HEAD e3925d07, git diff HEAD empty. Relative imports, no dist leg.", "gates": "dispatch-gates --commands on the diff: 34 derived; all 34 exit 0 on e5a7a375 (node scripts/check-sdui-manifest.mjs included; pnpm --dir ui exec vitest run scripts/ exit 0 on c38b5352, 176 files passed 2 skipped). --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Extra: pnpm check:changeset-fixed exit 0 (roster under .changeset). Closure build plugin-grid^... exit 0. CI check-runs: in_progress at report time.", "line_budget": "n/a, no ratcheted ledger touched; diff 3 files +112/-7", "files_changed": [ ".changeset/26951-bulk-param-lookup-settings.md", "ui/packages/plugin-grid/src/resolveBulkActions.ts", "ui/packages/plugin-grid/src/__tests__/bulkFieldBackedParam-12105.test.tsx" ], "deviations": [ "Dispatch Writes line asks for Fixes #26951 on PR line 3; the order's Scope section and the seat's relay say Refs, so line 3 is Refs #26951 (item: ...).", "The order asks path:line for the spec sentences; ui/AGENTS.md rule #11 bans line addresses in issue bodies, so each is pinned to base sha cb8988ee and carries its quoted text.", "pnpm --dir ui exec vitest run packages/plugin-grid/ and vitest run scripts/ were read on c38b5352, not rerun on e5a7a375 (that commit changes only TypeScript types in one test file)." ], "dogfood_battery": "n/a: no ! in the PR title and Clause-2 is no without a (narrowing) arm", "mcp_calls": "0", "api_writes": "2: fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, PR 27049); this report via post-stamped (POST /repos/objectstack-ai/objectstack/issues/26951/comments). git push x2 not counted (not REST).", "open_questions": [], "out_of_scope_findings": [ "class: b, spec note made false by this PR, carrier: seat (spec-lane card). packages/spec/src/ui/bulk-action.zod.ts:161-163 at cb8988ee, JSDoc above BULK_PARAM_WIDGET_CONFIG_KEYS: 'The lookup settings lookupFilters / lookupColumns / lookupPageSize / descriptionField are resolved and then dropped by the bulk adapter' (and 'From THIS list only accept and maxSize make the trip').", "class: b, refusal prescription made false by this PR, carrier: seat (spec-lane card). packages/spec/src/ui/bulk-action.zod.ts:260-262 at cb8988ee: 'So of these keys only accept / maxSize arrive that way: the picker settings lookupFilters / lookupColumns / lookupPageSize / descriptionField are dropped on the bulk path'.", "class: b, liveness row made false by this PR, carrier: seat (spec-lane card). packages/spec/liveness/view.json:198 at cb8988ee, row list.bulkActionDefs: '... #toBulkParam, which carries ... and drops the lookup settings.'", "carrier: none, noted not filed: idField / titleFormat are still not carried on the bulk path (paramToField forwards them; the resolver reads only undeclared snake keys). In the PR's Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Seat review: PR #27049 ACCEPT (record on the PR, head
e5a7a375c9), 2026-10-28T03:14Z. The spec half is filed as #27050 (domain:spec). Landing waits for every check on the head to be green.objectstack-fleet commentedon Oct 28, 2026
Landing held ·
domain:ui#2·session_01JQwxbXpF772qWJjojJvRXw· 2026-10-28T03:29ZPR #27049 (ACCEPT 6459701930) is green on head
e5a7a375c9: 43 checks succeeded, and 10 were skipped. The skipped ones are the core / docs / dogfood path-filtered jobs, which aui/-only diff does not trigger.mergeable_stateis clean and the PR is not governed. The spec half is filed as #27050. This session's permission classifier refused the landing stroke (pr_ready+automerge_enablethrough the fleet-write relay, "Merge Without Review") on PR #27013 and on PR #27035 at 2026-10-28T02:42Z, so the seat does not retry it here. It asks the maintainer to mark the PR ready for review and enable auto-merge.objectstack-fleet commentedon Oct 28, 2026
Landed ·
domain:ui#2·session_01JQwxbXpF772qWJjojJvRXw· 2026-10-28T08:32ZRefs #26951, ui half)domain:spec), carries the remaining spec sentencesClosing as completed: the ui half is delivered, and the spec half lives on #27050.
pm:dispatchedcleared.