You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
bulk actions: a promoted action's field-backed param (field + objectOverride) renders a raw text box on the selection bar, and the confirm step lists raw ids #12105
it maps field onto name and defaults type to 'text';
it fills object from reference, but never resolves field + objectOverride against the object's field definitions.
So bulkParamToField() builds a text widget, and BulkActionDialog.tsxParamField renders <input id="bulk-param-…" type="text">.
The confirm step resolves id → label only for lookup-ish params that carry an object.
The single-record path is right because it runs app-shell/src/utils/resolveActionParams.ts (param.objectOverride ?? ctx.objectName). The bulk path never calls it.
Both the spec guidance (BulkActionParamSchema, packages/spec/src/ui/bulk-action.zod.ts) and BulkActionDialog.tsx record this as an open gap.
Measured on HotCRM: Add to Campaign with a crm_campaign param.
Done when
A promoted action's params resolve through the same field-backed resolution as the single-record path. One resolver, shared: it moves out of app-shell or is injected into plugin-grid. ⛔ No second copy.
A lookup param renders the record picker on the selection bar, and the confirm step shows labels.
Pins:
a field-backed lookup param on the bulk path;
CONTROL: a literal-typed param is unchanged.
The spec guidance sentence that names the gap is corrected in the objectstack PR that next touches bulk-action.zod.ts. Name it in this PR's body.
Claim: PM loop round 1 · 2026-10-11T04:05Z
Session: session_01AswpQDLCKiZos2jCXknwKz
Account: os-zhuang
Branch: claude/issue-12105-bulk-param-field-resolve
Worktree: objectui-issue-12105
Domain: domain:ui
Seat: domain:ui#1
File surface: packages/plugin-grid/src/resolveBulkActions.ts (toBulkParam); packages/plugin-grid/src/BulkActionDialog.tsx (ParamField and the confirm step's labels); packages/app-shell/src/utils/resolveActionParams.ts (the one field-backed resolver, either moved to a package both sides depend on or injected into the bulk path); the host wiring that hands it over (named in the PR); their tests; .changeset/12105-bulk-param-field-resolve.md (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: yes
Responsibility: objectui's plugin-grid resolveBulkActions toBulkParam, which maps a promoted action's field-backed param (field + objectOverride) to a raw text param and never resolves it against the object's field definitions, so the selection bar draws a text box and the confirm step lists raw ids | app-shell's resolveActionParams already resolves the same param shape on the single-record path; the bulk path never calls it | every bulk action whose param is field-backed; measured on objectstack-ai/hotcrm#2058 (17.7.0): Add to Campaign with a crm_campaign param
Thread-read: none
Serial constraints cleared: none blocking. Read at 2026-10-11T04:05Z: objectui#12117 (objectui#12103, ACCEPTed, landing next) edits packages/plugin-grid/src/ObjectGrid.tsx's column pass; this card's files are different ones in the same package, and if the bulk wiring has to touch ObjectGrid.tsx the later lander merges main. objectui#12116 (objectui#12081 item 2) edits packages/plugin-list/src/ListView.tsx's fetch, not its bulk-action gate. objectui#12089 (the objectui#7611 epic) touches none of these files.
Why Clause-②: yes: sharing the one resolver across the package boundary means either a new export from a package both sides depend on, or an injected resolver on a published component or context. Either widens a published surface.
os-dev-report
{
"issue": 12105,
"status": "done",
"branch": "claude/issue-12105-bulk-param-field-resolve",
"pr": "#12119",
"session": "session_01AswpQDLCKiZos2jCXknwKz",
"premise_still_valid": true,
"summary": "Premise reproduced red on main a21ff9a with a throwaway probe: the promoted param came out as {field:'crm_campaign',objectOverride:'crm_campaign_member',name:'crm_campaign',type:'text'} and the dialog drew input#bulk-param-crm_campaign type=text. Fix: app-shell's resolveActionParams.ts moved unchanged (git mv) into @object-ui/fields, the one package both sides depend on that holds every input it reads (core types and EXPANDABLE_FIELD_TYPES, the fields widget fold, spec resolveI18nLabel). Its three-row param alias table and resolveParamWidgetType moved with it, and app-shell now imports from fields. plugin-grid resolves a promoted def's params when the dialog opens (resolvePromotedBulkParams, called from ObjectGrid dispatchBulkActionDef). It first awaits useMetadata().ensureType('object'), then unions the grid's own schema with the session's objects (withKnownObjects, the grid's first). That mirrors the record dialog's order. The resolved ActionParamDef is renamed onto the bulk vocabulary, and no field lookup is copied. Unresolved params are refused in BulkActionDialog as ActionParamDialog refuses them, reusing actionDialog.unresolvedParam, with Next disabled. Move was chosen over inject because injection leaves a host-dependent second behaviour, which is the defect itself. The cost is +5 values and +3 types on fields and nothing on plugin-grid's or app-shell's published face. Clause-2 additions (seat to review): @object-ui/fields exports resolveActionParam, resolveActionParams, resolveParamWidgetType, withKnownObjects, RESOLVED_ONLY_PARAM_KEYS, and the types RawActionParam, RawActionParamOption, ResolveActionParamsContext. They were internal to app-shell (never exported). No prop, context member or locale key is added. resolvePromotedBulkParams and unresolvedFieldOf are module exports of plugin-grid's resolveBulkActions.ts, which the index does not re-export. Seat follow-up: the objectstack spec sentence (BulkActionParamSchema guidance.field in bulk-action.zod.ts at objectstack 7098acaef) is quoted verbatim in the PR under 'Spec guidance sentence for the objectstack follow-up', along with three sibling sentences in the same file. objectui#12117 landed mid-run; origin/main 206505c was merged in (no conflicts, both ObjectGrid changes kept) and everything was re-run at ed8ea95.",
"tests": "HEAD ed8ea95 (final). (1) pnpm exec vitest run --maxWorkers=2 over 48 files via os-verify-lock: plugin-grid bulk suites incl. new bulkFieldBackedParam-12105.test.tsx (8 cases) and inlineEditFieldWriteGrant-12103; app-shell resolveActionParams*, paramToField*, degradeSetTwin-5880, expandableFamily.identity-5874, decisionOutputParams, referenceArms-6837, ActionParamDialog* (9), ActionDesigner.paramVocabulary, ActionPreview* (4), ActionParam.one-authority; core column-identity.ratchet and ActionParamDef.options; scripts one-authority-per-exported-name-6273. Result 'Test Files 48 passed (48) / Tests 515 passed (515)', VERDICT command-exit 0. (2) turbo build of app-shell^... closure, then type-check for fields, plugin-grid, app-shell and core, joined with &&: VERDICT command-exit 0, each output echoes the line '@object-ui/PKG@17.7.0 type-check'. (3) Red on main a21ff9a via throwaway probe: 'Tests 2 failed (2)', expected 'text' to be 'lookup', DOM input#bulk-param-crm_campaign type=text. (4) Ablation A2 via objectstack scripts/ablation-replace.mjs (wrap mode, trap restore). Mutation: resolveActionParams(raw, ctx) became resolveActionParams(raw, { ...ctx, objects: [] }); anchor 1 to 0, blob e2e6b01a to e770d6fb. Result 'Tests 3 failed | 5 passed (8)': the three field-backed pins red; both CONTROLs, the refusal and the authored-def cases green. Restore: blob == HEAD e2e6b01a, git diff HEAD empty. (5) Ablation A1: deleted the await metadata.ensureType line in ObjectGrid; anchor 1 to 0, blob 1cb97afe to d1017243. Result 'Tests 1 failed | 7 passed (8)': only the grid-level picker pin red. Restored, blob == HEAD. (6) Reverse type check: added the unknown key zzRefusedByTheResolverContext12105 to ObjectGrid's resolver ctx. plugin-grid type-check reported 'TS2353 ... does not exist in type ResolveActionParamsContext', exit 2. Restored, git diff HEAD empty. Ablations and the reverse check ran at 5c915e9-era code (pre-merge, same diff). Vitest resolves @object-ui/fields to src through the vitest.config.mts alias, so no dist marker was needed.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, HTTP 204, by scripts/pm/fleet-write/dispatch.mjs). Dispatch 1, pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos//issues/12119/assignees ['os-zhuang']; read-back identical, 15343 bytes. Dispatch 2, comment: POST /repos//issues/12105/comments (this report). 0 label writes, 0 PATCH. git pushes are not REST: 1 empty-branch probe and 5 commits pushes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the next PR that converges plugin-grid bulkParamToField with app-shell paramToField. Noted, not filed. On the bulk path a resolved lookup param forwards only object, labelField and dependsOn; lookupFilters, lookupColumns, lookupPageSize, descriptionField, idField and titleFormat are resolved but dropped, because bulkParamToField has no widget-key rename for them. Stated in the changeset and the docs. No measured instance of a field-backed bulk param whose field declares lookupFilters. Dedupe: bulkParamToField lookupFilters, bulk picker config, paramToField twin",
"carrier: same as above. Noted, not filed. BULK_PARAM_TYPE_ALIASES in plugin-grid bulkParamToField.ts duplicates the param alias table, which now lives in @object-ui/fields beside resolveParamWidgetType. Dedupe: BULK_PARAM_TYPE_ALIASES, resolveBulkParamWidgetType, alias table twin",
"carrier: none (承接者:无). Noted in PR Acceptance notes only. Bulk param labels skip the _actions.ACTION.params.PARAM localization (actionParamText) that the record dialog applies after resolution. This gap predates this PR. Dedupe: actionParamText bulk, bulk param label i18n, _actions params localization",
"carrier: none (承接者:无). Noted in PR Acceptance notes only. BulkActionDialog paramNeedsDependentValues docblock still says spec BulkActionParamSchema is not strict, but the spec has closed it and declares dependsOn. Only the sentence this PR falsified was edited. Dedupe: BulkActionParamSchema not strict comment, bulk dependsOn docblock"
],
"gates": [
"HEAD ed8ea95, each exit 0: check:spec-symbols; check:handler-key-reads ('OK 152 arm(s) ...'); check:action-forward-parity; check:phantom-deps ('Every in-scope import is declared'); check:unused-deps; check:self-import; check:esm-specifiers (specifiers-only); check:new-line-citations ('VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only'); check-control-bytes ('OK (scanned 8310 tracked text file(s)')",
"HEAD ed8ea95, each exit 0: check-changeset-presence ('30 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s)'); check-changeset-no-major; check-changeset-fixed; check:changeset-claims ('No pending changeset names a file this change touches'); check:pending-changeset-literals; check:test-path-roots; check:vi-mock-inherit; check:vi-mock-override-shape; check:vi-mock-specifiers; check:unreferenced-sources ('Every shipped source file in every covered package is reachable'); check:i18n-keys ('every literal inline defaultValue matches the value the pack serves'); check-type-check-coverage ('43/43 packages compile their tests')",
"HEAD ed8ea95, each exit 0: check:readme-exports; check-doc-component-types ('Every documented component type is registered'; it was red once on a README type 'lookup' literal, fixed in 5c915e9); check:doc-fences; check-doc-example-ids; check-doc-links",
"HEAD 5c915e9 (pre-merge, same diff; dist-reading gates not re-run after the merge), each exit 0: check-doc-snippet-types ('Every covered documentation snippet compiles against the built types'); check-doc-example-types; check:doc-example-readers; check:published-dist; check:node-esm-load --force-build ('37 of 37 gradable entries were built by this tree'; the first run without --force-build exited 1 on provenance only, 6 untouched packages replayed from sibling worktree objectui-issue-12097's shared turbo cache)",
"check:eager-closure exit 0. Head 5c915e9: 'Console eager closure is 3170.6 KB gzipped across 290 of 2474 chunks (budget: 3204.6 KB, headroom: 34.0 KB)'. Base a21ff9a (own throwaway worktree, since removed): '3170.2 KB ... 290 of 2474 chunks ... headroom: 34.3 KB'. Delta +386 B gzipped (+1390 raw), chunk count unchanged. By chunk: ui-components +847, DeclaredActionsBar -747 (the resolver left the app-shell chunk), plugin-grid +449",
"Lint, narrowed, HEAD 5c915e9 on the 30 changed ts/tsx files. Population: eslint.config.js applies its rules to /*.{ts,tsx}; all 30 in scope, 0 ignored. Count: eslint --format json gave 30 results, 0 errors, 521 warnings, exit 0. Invariance: no type-aware linting (no parserOptions.project/projectService) and no eslint-rules/ rule reads the disk. With --no-inline-config the run shows 4 errors, exactly the 4 pre-existing eslint-disable-next-line react-hooks/static-components lines. Repo-wide pnpm lint is CI's",
"CI snapshot at ed8ea95 when this report was written: 38 check-runs completed with 0 failures, 4 in_progress (not waited on)"
],
"line_budget": "n/a (skills/ not touched)",
"deviations": [
"File surface widened beyond the claim's named files, as the move requires. Every importer of the moved resolver was repointed: app-shell useConsoleActionRuntime, RecordDetailView, ActionPreview, paramToField, and 14 tests' import lines. fields/src/index.tsx gained the exports. Two path-keyed pins were updated: the ActionPreview 10159 RESOLVER path and the core column-identity ratchet (key moved; total 12 to 11 because the hand-mapper's name||field read is gone). Four core comments that named @object-ui/app-shell as the resolver's home were corrected. The fields README and two content/docs pages got docs (AGENTS.md #2).",
"Added the unresolved-param refusal to BulkActionDialog (not in the PM's suggested route). Once bulk calls the shared resolver, the resolver's warning 'The action dialog refuses the param' would be false on the bulk path, and the missing-field case would still draw a raw text box.",
"fields/src/resolveActionParams.ts declares a module-scoped process type: @object-ui/fields has no node typings, and the build failed TS2591 without it. The runtime read is unchanged.",
"Resolution moved from render time (resolveBulkActions) to dispatch time: promoted defs no longer carry params out of the fold. resolveBulkActions.test 'maps spec param keys' now asserts through resolvePromotedBulkParams.",
"Commit trailers use the model-free pair from objectui AGENTS.md (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named trailer. The PR footer is the os-dev session-URL form.",
"One test invocation was mis-split by newlines in the lock wrapper (VERDICT batch-last-exit 126; 20 of 47 files). It was re-run correctly and is not used as a reading. One lock acquisition timed out (exit 99) behind a sibling's suite; it was retried with the same slot.",
"objectui#12117 landed during the run. origin/main 206505c was merged in (merge commit ed8ea95, no conflicts), and tests, type-check and the source-reading gates were re-run at ed8ea95."
],
"files_changed": [
".changeset/12105-bulk-param-field-resolve.md",
"content/docs/core/enhanced-actions.mdx",
"content/docs/plugins/plugin-grid.mdx",
"packages/app-shell/src/hooks/useConsoleActionRuntime.tsx",
"packages/app-shell/src/utils/tests/referenceArms-6837.divergent.test.ts",
"packages/app-shell/src/utils/decisionOutputParams.test.ts",
"packages/app-shell/src/utils/expandableFamily.identity-5874.test.ts",
"packages/app-shell/src/utils/paramToField.test.ts",
"packages/app-shell/src/utils/paramToField.ts",
"packages/app-shell/src/utils/resolveActionParams.declaredLookupLegs-7435.test.ts",
"packages/app-shell/src/utils/resolveActionParams.i18nLabel.test.ts",
"packages/app-shell/src/utils/resolveActionParams.optionVisibleWhen.test.tsx",
"packages/app-shell/src/utils/resolveActionParams.remainingLookupLegs-7435.test.ts",
"packages/app-shell/src/utils/resolveActionParams.test.ts",
"packages/app-shell/src/utils/resolveActionParams.ts moved to packages/fields/src/resolveActionParams.ts (git mv)",
"packages/app-shell/src/views/ActionParamDialog.carryOver-6246.test.tsx",
"packages/app-shell/src/views/ActionParamDialog.fieldBackedPickerReach-10129.test.tsx",
"packages/app-shell/src/views/ActionParamDialog.lookupDependsOnReach-8672.test.tsx",
"packages/app-shell/src/views/RecordDetailView.tsx",
"packages/app-shell/src/views/metadata-admin/ActionDesigner.paramVocabulary.test.tsx",
"packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsx",
"packages/app-shell/src/views/metadata-admin/previews/tests/ActionPreview.inheritedTargetCitation-10159.test.ts",
"packages/core/src/utils/tests/column-identity.ratchet.test.ts",
"packages/core/src/utils/tests/reference-keys.retiredDialect-7650.test.ts",
"packages/core/src/utils/expand-fields.ts",
"packages/core/src/utils/predicate-fields.ts",
"packages/core/src/utils/reference-keys.ts",
"packages/fields/README.md",
"packages/fields/src/index.tsx",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/tests/bulkFieldBackedParam-12105.test.tsx",
"packages/plugin-grid/src/tests/resolveBulkActions.test.ts",
"packages/plugin-grid/src/components/BulkActionDialog.tsx",
"packages/plugin-grid/src/resolveBulkActions.ts"
]
}
Reviewed against GitHub and the diff, not the report (os-dev-report on this card).
Shape:
The PR is a draft on main. Its first line is Fixes #12105, and Clause-②: yes sits at the start of a line.
No other card number sits beside a closing keyword. The assignee is os-zhuang.
34 files, +805/−109 (914 lines, under the 3000-line threshold). Nothing touches content/docs/releases/. check-governed-merges reads NOT GOVERNED on the final file list, with the rename's old path included.
Against the card's "Done when":
One resolver, shared, no second copy:resolveActionParams.ts moved with git mv from app-shell into @object-ui/fields, unchanged in behaviour. Both dialogs call it. plugin-grid's new resolvePromotedBulkParams only renames the resolved param onto the bulk vocabulary; it does no field lookup of its own.
Picker and labels: a field-backed lookup param renders lookup-trigger-crm_campaign through the real ObjectGrid and BulkActionDialog, and input#bulk-param-crm_campaign is gone. The confirm step shows "Spring Launch".
Pins: the field-backed lookup param on the bulk path, at unit level (with a parity case against resolveActionParam) and through the grid. The CONTROL is a literal-typed param, unchanged at both levels, including an inline reference: 'sys_user'.
The spec sentence:guidance.field is quoted verbatim in the PR body, with the three sibling sentences in the same file. The carrier is below.
Beyond the card, required by it: a field-backed param whose field is missing is refused before any widget is built, and Next is disabled. That is the record dialog's behaviour and its existing actionDialog.unresolvedParam copy. Without it the missing-field case would still draw the text box this card reports.
Measured on main first: a throwaway probe on a21ff9a gave type: 'text' and input#bulk-param-crm_campaign type="text" (2 of 2 red). Ablation A2 (resolver starved of metadata) turned the three field-backed pins red, and the CONTROLs, the refusal and the authored-def cases stayed green. Ablation A1 (the ensureType('object') wait removed) turned only the grid-level picker pin red. Both restores are blob-equal to the head.
Changeset prose, checked sentence by sentence against the diff:
the summary, and the HotCRM-shaped "before" paragraph;
"One resolver for both dialogs" (moved unchanged into fields; type, translated label, options, default and picker target);
"When it resolves" (at dialog open, after the session's object metadata loads);
"A missing field is refused, not drawn as a text box";
"Inline params are unchanged";
the eight new @object-ui/fields exports, and "No other export, prop, context member or language-pack key is added";
"Not covered yet" (the field's other picker settings do not reach the bulk dialog).
All match. @object-ui/fields: minor, with plugin-grid and app-shell patch (the fixed group moves together).
Contract review: record 6106031964 on PR objectui#12119 reads PASS. Served-tier: CONTRACT_REVIEW_TIER; Head-sha is this head; Local-runs: none. Implemented-by is the dev's branch, and Reviewed-by is this session.
Gates at this reading: 40 success, 3 skipped (dependabot and the two coverage-matrix rows), 0 failed, 0 pending.
Out of scope, one line each:
The four bulk-action.zod.ts sentences (guidance.field, the BULK_PARAM_WIDGET_CONFIG_KEYS prescription and its JSDoc, and the dependsOn JSDoc) become partly false for a promoted action once this lands. They ship in objectstack's refusal text, so the seat files one objectstack card after the merge and names it in the landing note. That card also carries the first finding below: rewriting the widget-config prescription has to say which field settings the promoted route forwards.
A resolved lookup param forwards only object, labelField and dependsOn to the bulk picker: noted, not filed. It is stated in the changeset and the docs, and the spec's own prescription says the same today. Carrier: the objectstack card above.
BULK_PARAM_TYPE_ALIASES in bulkParamToField.ts twins the alias table now in fields: noted, not filed (no wrong answer today; same carrier).
Bulk param labels skip the _actions.ACTION.params.PARAM overlay the record dialog applies: noted, not filed. No reach was measured. In objectstack's examples the one _actions.*.params.* translation (app-showcase, showcase_action_param_gallery.params.p_assignee) belongs to an action no view's bulkActions names. Carrier: none.
The dialog's dependsOn docblock still says BulkActionParamSchema "is not strict": noted, not filed (a comment; carrier none).
Dedupe for the above: objectstack "BulkActionParamSchema field-backed param guidance toBulkParam bulk-action.zod.ts", 3 hits, all closed; objectui "bulk param lookupFilters picker config not forwarded bulkParamToField", 3 hits, all closed.
Landing: every check on this head is green, so the seat flips the PR to ready and arms auto-merge, which enters the merge queue. The merge closes this card.
The merge: through the merge queue. Its one parent is b7cd5c89, and it is an ancestor of origin/main.
Content check: all 34 files the merge changes are blob-identical to the reviewed head ed8ea958e5. The rename's old path, packages/app-shell/src/utils/resolveActionParams.ts, is gone from main. ACCEPT 6106058431 and contract review 6106031964 (PASS) cover that head.
The card: closed completed by the PR's Fixes line. pm:dispatched is removed in this act.
Carried: the objectstack card for the four bulk-action.zod.ts sentences follows this note and names this card.
Carried: the four bulk-action.zod.ts sentences are filed as objectstack-ai/objectstack#22780 (finding, for triage), with the promoted route's forwarding gap folded in. · domain:ui seat 1, session_01AswpQDLCKiZos2jCXknwKz, 2026-10-11T06:24Z
Activity
objectstack-fleet commentedon Oct 11, 2026
Claim: PM loop round 1 · 2026-10-11T04:05Z
Session:
session_01AswpQDLCKiZos2jCXknwKzAccount:
os-zhuangBranch:
claude/issue-12105-bulk-param-field-resolveWorktree:
objectui-issue-12105Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/plugin-grid/src/resolveBulkActions.ts(toBulkParam);packages/plugin-grid/src/BulkActionDialog.tsx(ParamFieldand the confirm step's labels);packages/app-shell/src/utils/resolveActionParams.ts(the one field-backed resolver, either moved to a package both sides depend on or injected into the bulk path); the host wiring that hands it over (named in the PR); their tests;.changeset/12105-bulk-param-field-resolve.md(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: yes
Responsibility:
objectui's plugin-grid resolveBulkActions toBulkParam, which maps a promoted action's field-backed param (field + objectOverride) to a raw text param and never resolves it against the object's field definitions, so the selection bar draws a text box and the confirm step lists raw ids | app-shell's resolveActionParams already resolves the same param shape on the single-record path; the bulk path never calls it | every bulk action whose param is field-backed; measured on objectstack-ai/hotcrm#2058 (17.7.0): Add to Campaign with a crm_campaign paramThread-read: none
Serial constraints cleared: none blocking. Read at 2026-10-11T04:05Z: objectui#12117 (objectui#12103, ACCEPTed, landing next) edits
packages/plugin-grid/src/ObjectGrid.tsx's column pass; this card's files are different ones in the same package, and if the bulk wiring has to touchObjectGrid.tsxthe later lander mergesmain. objectui#12116 (objectui#12081 item 2) editspackages/plugin-list/src/ListView.tsx's fetch, not its bulk-action gate. objectui#12089 (the objectui#7611 epic) touches none of these files.Why
Clause-②: yes: sharing the one resolver across the package boundary means either a new export from a package both sides depend on, or an injected resolver on a published component or context. Either widens a published surface.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
os-dev-report
{
"issue": 12105,
"status": "done",
"branch": "claude/issue-12105-bulk-param-field-resolve",
"pr": "#12119",
"session": "session_01AswpQDLCKiZos2jCXknwKz",
"premise_still_valid": true,
"summary": "Premise reproduced red on main a21ff9a with a throwaway probe: the promoted param came out as {field:'crm_campaign',objectOverride:'crm_campaign_member',name:'crm_campaign',type:'text'} and the dialog drew input#bulk-param-crm_campaign type=text. Fix: app-shell's resolveActionParams.ts moved unchanged (git mv) into @object-ui/fields, the one package both sides depend on that holds every input it reads (core types and EXPANDABLE_FIELD_TYPES, the fields widget fold, spec resolveI18nLabel). Its three-row param alias table and resolveParamWidgetType moved with it, and app-shell now imports from fields. plugin-grid resolves a promoted def's params when the dialog opens (resolvePromotedBulkParams, called from ObjectGrid dispatchBulkActionDef). It first awaits useMetadata().ensureType('object'), then unions the grid's own schema with the session's objects (withKnownObjects, the grid's first). That mirrors the record dialog's order. The resolved ActionParamDef is renamed onto the bulk vocabulary, and no field lookup is copied. Unresolved params are refused in BulkActionDialog as ActionParamDialog refuses them, reusing actionDialog.unresolvedParam, with Next disabled. Move was chosen over inject because injection leaves a host-dependent second behaviour, which is the defect itself. The cost is +5 values and +3 types on fields and nothing on plugin-grid's or app-shell's published face. Clause-2 additions (seat to review): @object-ui/fields exports resolveActionParam, resolveActionParams, resolveParamWidgetType, withKnownObjects, RESOLVED_ONLY_PARAM_KEYS, and the types RawActionParam, RawActionParamOption, ResolveActionParamsContext. They were internal to app-shell (never exported). No prop, context member or locale key is added. resolvePromotedBulkParams and unresolvedFieldOf are module exports of plugin-grid's resolveBulkActions.ts, which the index does not re-export. Seat follow-up: the objectstack spec sentence (BulkActionParamSchema guidance.field in bulk-action.zod.ts at objectstack 7098acaef) is quoted verbatim in the PR under 'Spec guidance sentence for the objectstack follow-up', along with three sibling sentences in the same file. objectui#12117 landed mid-run; origin/main 206505c was merged in (no conflicts, both ObjectGrid changes kept) and everything was re-run at ed8ea95.",
"tests": "HEAD ed8ea95 (final). (1) pnpm exec vitest run --maxWorkers=2 over 48 files via os-verify-lock: plugin-grid bulk suites incl. new bulkFieldBackedParam-12105.test.tsx (8 cases) and inlineEditFieldWriteGrant-12103; app-shell resolveActionParams*, paramToField*, degradeSetTwin-5880, expandableFamily.identity-5874, decisionOutputParams, referenceArms-6837, ActionParamDialog* (9), ActionDesigner.paramVocabulary, ActionPreview* (4), ActionParam.one-authority; core column-identity.ratchet and ActionParamDef.options; scripts one-authority-per-exported-name-6273. Result 'Test Files 48 passed (48) / Tests 515 passed (515)', VERDICT command-exit 0. (2) turbo build of app-shell^... closure, then type-check for fields, plugin-grid, app-shell and core, joined with &&: VERDICT command-exit 0, each output echoes the line '@object-ui/PKG@17.7.0 type-check'. (3) Red on main a21ff9a via throwaway probe: 'Tests 2 failed (2)', expected 'text' to be 'lookup', DOM input#bulk-param-crm_campaign type=text. (4) Ablation A2 via objectstack scripts/ablation-replace.mjs (wrap mode, trap restore). Mutation: resolveActionParams(raw, ctx) became resolveActionParams(raw, { ...ctx, objects: [] }); anchor 1 to 0, blob e2e6b01a to e770d6fb. Result 'Tests 3 failed | 5 passed (8)': the three field-backed pins red; both CONTROLs, the refusal and the authored-def cases green. Restore: blob == HEAD e2e6b01a, git diff HEAD empty. (5) Ablation A1: deleted the await metadata.ensureType line in ObjectGrid; anchor 1 to 0, blob 1cb97afe to d1017243. Result 'Tests 1 failed | 7 passed (8)': only the grid-level picker pin red. Restored, blob == HEAD. (6) Reverse type check: added the unknown key zzRefusedByTheResolverContext12105 to ObjectGrid's resolver ctx. plugin-grid type-check reported 'TS2353 ... does not exist in type ResolveActionParamsContext', exit 2. Restored, git diff HEAD empty. Ablations and the reverse check ran at 5c915e9-era code (pre-merge, same diff). Vitest resolves @object-ui/fields to src through the vitest.config.mts alias, so no dist marker was needed.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, HTTP 204, by scripts/pm/fleet-write/dispatch.mjs). Dispatch 1, pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos//issues/12119/assignees ['os-zhuang']; read-back identical, 15343 bytes. Dispatch 2, comment: POST /repos//issues/12105/comments (this report). 0 label writes, 0 PATCH. git pushes are not REST: 1 empty-branch probe and 5 commits pushes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the next PR that converges plugin-grid bulkParamToField with app-shell paramToField. Noted, not filed. On the bulk path a resolved lookup param forwards only object, labelField and dependsOn; lookupFilters, lookupColumns, lookupPageSize, descriptionField, idField and titleFormat are resolved but dropped, because bulkParamToField has no widget-key rename for them. Stated in the changeset and the docs. No measured instance of a field-backed bulk param whose field declares lookupFilters. Dedupe: bulkParamToField lookupFilters, bulk picker config, paramToField twin",
"carrier: same as above. Noted, not filed. BULK_PARAM_TYPE_ALIASES in plugin-grid bulkParamToField.ts duplicates the param alias table, which now lives in @object-ui/fields beside resolveParamWidgetType. Dedupe: BULK_PARAM_TYPE_ALIASES, resolveBulkParamWidgetType, alias table twin",
"carrier: none (承接者:无). Noted in PR Acceptance notes only. Bulk param labels skip the _actions.ACTION.params.PARAM localization (actionParamText) that the record dialog applies after resolution. This gap predates this PR. Dedupe: actionParamText bulk, bulk param label i18n, _actions params localization",
"carrier: none (承接者:无). Noted in PR Acceptance notes only. BulkActionDialog paramNeedsDependentValues docblock still says spec BulkActionParamSchema is not strict, but the spec has closed it and declares dependsOn. Only the sentence this PR falsified was edited. Dedupe: BulkActionParamSchema not strict comment, bulk dependsOn docblock"
],
"gates": [
"HEAD ed8ea95, each exit 0: check:spec-symbols; check:handler-key-reads ('OK 152 arm(s) ...'); check:action-forward-parity; check:phantom-deps ('Every in-scope import is declared'); check:unused-deps; check:self-import; check:esm-specifiers (specifiers-only); check:new-line-citations ('VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only'); check-control-bytes ('OK (scanned 8310 tracked text file(s)')",
"HEAD ed8ea95, each exit 0: check-changeset-presence ('30 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s)'); check-changeset-no-major; check-changeset-fixed; check:changeset-claims ('No pending changeset names a file this change touches'); check:pending-changeset-literals; check:test-path-roots; check:vi-mock-inherit; check:vi-mock-override-shape; check:vi-mock-specifiers; check:unreferenced-sources ('Every shipped source file in every covered package is reachable'); check:i18n-keys ('every literal inline defaultValue matches the value the pack serves'); check-type-check-coverage ('43/43 packages compile their tests')",
"HEAD ed8ea95, each exit 0: check:readme-exports; check-doc-component-types ('Every documented component type is registered'; it was red once on a README type 'lookup' literal, fixed in 5c915e9); check:doc-fences; check-doc-example-ids; check-doc-links",
"HEAD 5c915e9 (pre-merge, same diff; dist-reading gates not re-run after the merge), each exit 0: check-doc-snippet-types ('Every covered documentation snippet compiles against the built types'); check-doc-example-types; check:doc-example-readers; check:published-dist; check:node-esm-load --force-build ('37 of 37 gradable entries were built by this tree'; the first run without --force-build exited 1 on provenance only, 6 untouched packages replayed from sibling worktree objectui-issue-12097's shared turbo cache)",
"check:eager-closure exit 0. Head 5c915e9: 'Console eager closure is 3170.6 KB gzipped across 290 of 2474 chunks (budget: 3204.6 KB, headroom: 34.0 KB)'. Base a21ff9a (own throwaway worktree, since removed): '3170.2 KB ... 290 of 2474 chunks ... headroom: 34.3 KB'. Delta +386 B gzipped (+1390 raw), chunk count unchanged. By chunk: ui-components +847, DeclaredActionsBar -747 (the resolver left the app-shell chunk), plugin-grid +449",
"Lint, narrowed, HEAD 5c915e9 on the 30 changed ts/tsx files. Population: eslint.config.js applies its rules to /*.{ts,tsx}; all 30 in scope, 0 ignored. Count: eslint --format json gave 30 results, 0 errors, 521 warnings, exit 0. Invariance: no type-aware linting (no parserOptions.project/projectService) and no eslint-rules/ rule reads the disk. With --no-inline-config the run shows 4 errors, exactly the 4 pre-existing eslint-disable-next-line react-hooks/static-components lines. Repo-wide pnpm lint is CI's",
"CI snapshot at ed8ea95 when this report was written: 38 check-runs completed with 0 failures, 4 in_progress (not waited on)"
],
"line_budget": "n/a (skills/ not touched)",
"deviations": [
"File surface widened beyond the claim's named files, as the move requires. Every importer of the moved resolver was repointed: app-shell useConsoleActionRuntime, RecordDetailView, ActionPreview, paramToField, and 14 tests' import lines. fields/src/index.tsx gained the exports. Two path-keyed pins were updated: the ActionPreview 10159 RESOLVER path and the core column-identity ratchet (key moved; total 12 to 11 because the hand-mapper's name||field read is gone). Four core comments that named @object-ui/app-shell as the resolver's home were corrected. The fields README and two content/docs pages got docs (AGENTS.md #2).",
"Added the unresolved-param refusal to BulkActionDialog (not in the PM's suggested route). Once bulk calls the shared resolver, the resolver's warning 'The action dialog refuses the param' would be false on the bulk path, and the missing-field case would still draw a raw text box.",
"fields/src/resolveActionParams.ts declares a module-scoped process type: @object-ui/fields has no node typings, and the build failed TS2591 without it. The runtime read is unchanged.",
"Resolution moved from render time (resolveBulkActions) to dispatch time: promoted defs no longer carry params out of the fold. resolveBulkActions.test 'maps spec param keys' now asserts through resolvePromotedBulkParams.",
"Commit trailers use the model-free pair from objectui AGENTS.md (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named trailer. The PR footer is the os-dev session-URL form.",
"One test invocation was mis-split by newlines in the lock wrapper (VERDICT batch-last-exit 126; 20 of 47 files). It was re-run correctly and is not used as a reading. One lock acquisition timed out (exit 99) behind a sibling's suite; it was retried with the same slot.",
"objectui#12117 landed during the run. origin/main 206505c was merged in (merge commit ed8ea95, no conflicts), and tests, type-check and the source-reading gates were re-run at ed8ea95."
],
"files_changed": [
".changeset/12105-bulk-param-field-resolve.md",
"content/docs/core/enhanced-actions.mdx",
"content/docs/plugins/plugin-grid.mdx",
"packages/app-shell/src/hooks/useConsoleActionRuntime.tsx",
"packages/app-shell/src/utils/tests/referenceArms-6837.divergent.test.ts",
"packages/app-shell/src/utils/decisionOutputParams.test.ts",
"packages/app-shell/src/utils/expandableFamily.identity-5874.test.ts",
"packages/app-shell/src/utils/paramToField.test.ts",
"packages/app-shell/src/utils/paramToField.ts",
"packages/app-shell/src/utils/resolveActionParams.declaredLookupLegs-7435.test.ts",
"packages/app-shell/src/utils/resolveActionParams.i18nLabel.test.ts",
"packages/app-shell/src/utils/resolveActionParams.optionVisibleWhen.test.tsx",
"packages/app-shell/src/utils/resolveActionParams.remainingLookupLegs-7435.test.ts",
"packages/app-shell/src/utils/resolveActionParams.test.ts",
"packages/app-shell/src/utils/resolveActionParams.ts moved to packages/fields/src/resolveActionParams.ts (git mv)",
"packages/app-shell/src/views/ActionParamDialog.carryOver-6246.test.tsx",
"packages/app-shell/src/views/ActionParamDialog.fieldBackedPickerReach-10129.test.tsx",
"packages/app-shell/src/views/ActionParamDialog.lookupDependsOnReach-8672.test.tsx",
"packages/app-shell/src/views/RecordDetailView.tsx",
"packages/app-shell/src/views/metadata-admin/ActionDesigner.paramVocabulary.test.tsx",
"packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsx",
"packages/app-shell/src/views/metadata-admin/previews/tests/ActionPreview.inheritedTargetCitation-10159.test.ts",
"packages/core/src/utils/tests/column-identity.ratchet.test.ts",
"packages/core/src/utils/tests/reference-keys.retiredDialect-7650.test.ts",
"packages/core/src/utils/expand-fields.ts",
"packages/core/src/utils/predicate-fields.ts",
"packages/core/src/utils/reference-keys.ts",
"packages/fields/README.md",
"packages/fields/src/index.tsx",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/tests/bulkFieldBackedParam-12105.test.tsx",
"packages/plugin-grid/src/tests/resolveBulkActions.test.ts",
"packages/plugin-grid/src/components/BulkActionDialog.tsx",
"packages/plugin-grid/src/resolveBulkActions.ts"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
ACCEPT — PR objectui#12119 (head
ed8ea958e5) ·domain:uiseat 1,session_01AswpQDLCKiZos2jCXknwKz, 2026-10-11T06:01ZReviewed against GitHub and the diff, not the report (
os-dev-reporton this card).Shape:
main. Its first line isFixes #12105, andClause-②: yessits at the start of a line.os-zhuang.content/docs/releases/.check-governed-mergesreads NOT GOVERNED on the final file list, with the rename's old path included.Against the card's "Done when":
resolveActionParams.tsmoved withgit mvfromapp-shellinto@object-ui/fields, unchanged in behaviour. Both dialogs call it. plugin-grid's newresolvePromotedBulkParamsonly renames the resolved param onto the bulk vocabulary; it does no field lookup of its own.lookup-trigger-crm_campaignthrough the realObjectGridandBulkActionDialog, andinput#bulk-param-crm_campaignis gone. The confirm step shows "Spring Launch".resolveActionParam) and through the grid. The CONTROL is a literal-typed param, unchanged at both levels, including an inlinereference: 'sys_user'.guidance.fieldis quoted verbatim in the PR body, with the three sibling sentences in the same file. The carrier is below.actionDialog.unresolvedParamcopy. Without it the missing-field case would still draw the text box this card reports.Measured on
mainfirst: a throwaway probe ona21ff9agavetype: 'text'andinput#bulk-param-crm_campaign type="text"(2 of 2 red). Ablation A2 (resolver starved of metadata) turned the three field-backed pins red, and the CONTROLs, the refusal and the authored-def cases stayed green. Ablation A1 (theensureType('object')wait removed) turned only the grid-level picker pin red. Both restores are blob-equal to the head.Changeset prose, checked sentence by sentence against the diff:
@object-ui/fieldsexports, and "No other export, prop, context member or language-pack key is added";All match.
@object-ui/fields: minor, with plugin-grid and app-shellpatch(the fixed group moves together).Contract review: record
6106031964on PR objectui#12119 reads PASS.Served-tier: CONTRACT_REVIEW_TIER;Head-shais this head;Local-runs: none.Implemented-byis the dev's branch, andReviewed-byis this session.Gates at this reading: 40 success, 3 skipped (
dependabotand the two coverage-matrix rows), 0 failed, 0 pending.Out of scope, one line each:
bulk-action.zod.tssentences (guidance.field, theBULK_PARAM_WIDGET_CONFIG_KEYSprescription and its JSDoc, and thedependsOnJSDoc) become partly false for a promoted action once this lands. They ship in objectstack's refusal text, so the seat files one objectstack card after the merge and names it in the landing note. That card also carries the first finding below: rewriting the widget-config prescription has to say which field settings the promoted route forwards.object,labelFieldanddependsOnto the bulk picker: noted, not filed. It is stated in the changeset and the docs, and the spec's own prescription says the same today. Carrier: the objectstack card above.BULK_PARAM_TYPE_ALIASESinbulkParamToField.tstwins the alias table now in fields: noted, not filed (no wrong answer today; same carrier)._actions.ACTION.params.PARAMoverlay the record dialog applies: noted, not filed. No reach was measured. In objectstack's examples the one_actions.*.params.*translation (app-showcase,showcase_action_param_gallery.params.p_assignee) belongs to an action no view'sbulkActionsnames. Carrier: none.dependsOndocblock still saysBulkActionParamSchema"is not strict": noted, not filed (a comment; carrier none).Landing: every check on this head is green, so the seat flips the PR to ready and arms auto-merge, which enters the merge queue. The merge closes this card.
Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Landed: PR objectui#12119 →
main29b99490·domain:uiseat 1,session_01AswpQDLCKiZos2jCXknwKz, 2026-10-11T06:22Zb7cd5c89, and it is an ancestor oforigin/main.ed8ea958e5. The rename's old path,packages/app-shell/src/utils/resolveActionParams.ts, is gone frommain. ACCEPT6106058431and contract review6106031964(PASS) cover that head.completedby the PR'sFixesline.pm:dispatchedis removed in this act.bulk-action.zod.tssentences follows this note and names this card.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Carried: the four
bulk-action.zod.tssentences are filed as objectstack-ai/objectstack#22780 (finding, for triage), with the promoted route's forwarding gap folded in. ·domain:uiseat 1,session_01AswpQDLCKiZos2jCXknwKz, 2026-10-11T06:24ZGenerated by Claude Code
fix(plugin-grid,fields,app-shell): a promoted bulk action's field-bac…