Repository navigation
feat(ui): an action param's widget draws the named widget, so the action dialog can pick a permission set (#30260) - #30543
Conversation
ActionParamDef gains widget; resolveActionParam copies the param's own widget (field-backed: the field's while the param inherits its type) and paramToField draws a registered widget in place of the type widget. The spec liveness row params.widget flips to live and the zod describe drops its not-read-yet sentence. Claude-Session: https://claude.ai/code/session_01L6bhgXcNPmrQhpwYYn7fEq Co-authored-by: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01L6bhgXcNPmrQhpwYYn7fEq Co-authored-by: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01L6bhgXcNPmrQhpwYYn7fEq Co-authored-by: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01L6bhgXcNPmrQhpwYYn7fEq Co-authored-by: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01L6bhgXcNPmrQhpwYYn7fEq Co-authored-by: Claude <[email protected]>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 170 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin de3349a55ae358a17c25bbae6c38773d10ece7b3 && git checkout de3349a55ae358a17c25bbae6c38773d10ece7b3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8e5fabe58b860203774acf212e78b54761499513 57e88ede62adc3fe3804fcb2fdf22cfe37f17e3c && git checkout -B drift-repro 8e5fabe58b860203774acf212e78b54761499513 && git merge --no-ff 57e88ede62adc3fe3804fcb2fdf22cfe37f17e3c
node scripts/docs-audit/affected-docs.mjs --json 8e5fabe58b860203774acf212e78b54761499513
|
|
domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU Contract-review-claim: session_01L6bhgVfPeFZkN1FP93imKU · head 4d88bf5 · 2026-11-05T15:04Z The dispatching seat runs the Generated by Claude Code |
|
domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU Contract reviewServed-tier: Inputs read: card #30260 (body and all four comments, including ruling comment 6652583019 and os-dev-report 6661310924), PR #30543 (body, file list, both comments), the net diff ① Derived judgments
② Semver level
③ Boundary flagsEvery flag in the PR body's Acceptance notes and the os-dev-report (6661310924);
④ Design judgment
Against the ruling. Comment 6652583019 asked for three things in this PR: the liveness row to Does the describe now tell the truth? Clause by clause, read in the tree:
Against the first consumer. On Attempts to break it.
Must-fix: none. Check-runs on the head at read time (42): 12 success — Auto Label, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint · ESLint, PR-body guards, Plan Test Core shards, Spec property liveness, filter; 7 skipped (opt-in and schedule gates); 23 in_progress — Build Core, Build Docs, Build UI Deps, the seven Lint groups, Live E2E UI, Test Core 1/8–8/8, Type Check · consumer gates / debt ledger / source gates / workspace. 0 failure. This verdict is the design verdict; the Tier S landing rule's "every check is green" is read off the head separately, and the 23 still running are named here so the seat does not read this record as that reading. Implemented-by: VERDICT: PASS Generated by Claude Code |
…h a restated field type through the param-type fold Contract-review should-fixes: paramDegradesWithoutTarget asks the drawn widget key, so a text param naming the lookup widget with no target degrades and warns; inheritedWidget compares the param and field types through resolveParamWidgetType, so an alias spelling still restates the type; the spec JSDoc says inherits or restates. Claude-Session: https://claude.ai/code/session_01L6bhgXcNPmrQhpwYYn7fEq Co-authored-by: Claude <[email protected]>
…erty warning The params.widget liveness row is live now that the console reads it, so the pin that expected the planned warning becomes its inverse, with a control that the same walk still warns on a planned row. Claude-Session: https://claude.ai/code/session_01L6bhgXcNPmrQhpwYYn7fEq Co-authored-by: Claude <[email protected]>
|
domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU Contract review · delta · 2026-11-05T15:47ZServed-tier: Delta record on PR #30543: the two commits after the PASS record 6661565862 on ① Derived judgments
② Semver levelUnchanged by the delta: ③ Boundary flagsShould-fixes of record 6661565862, each judged on the tree:
④ Design judgment
Attempts to break the delta.
Must-fix: none. Check-runs on the head at read time (65): 58 success, 7 skipped, 0 failure, 0 in progress — every gate family the first record named as still running (Build Core/Docs/UI Deps, the seven Lint groups, Live E2E UI, Test Core 1/8–8/8, Test UI 1/16–16/16, Type Check ×4 and Type Check UI) concluded success on this head. For the record: the first head Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU 维护者速读
席位核对 — ACCEPT · head 57e88ed
Generated by Claude Code |
Requested by Sales · project thread
Fixes #30260
Clause-②: yes (widening) —
@object-ui/core's exportedActionParamDefgains one optional member,widget?: string, and@object-ui/fields'RawActionParamgains the same optional member (measured on the builtui/packages/core/dist/actions/ActionRunner.d.tsandui/packages/fields/dist/resolveActionParams.d.ts). No accepted key, value or answer is taken away.Metadata-protocol: semantic
Skills-docs: follow-up owed — skills/objectui/rules/protocol.md, skills/objectstack-ui/rules/actions.md
维护者速读
widget现在真的生效。参数写widget: 'permission-ref'时,动作对话框画权限集选择器(列出标签与机器名,提交机器名);未注册的名字退回type控件。widget的参数(或继承字段 widget 的字段型参数);未写的参数渲染逐字节不变。回滚即 revert 本 PR。ui/packages/app-shell/src/utils/paramToField.ts(authoredWidgetKey与降级判断)、ui/packages/fields/src/resolveActionParams.ts(inheritedWidget)、packages/spec/liveness/action.json(widget行翻 live)。pnpm --dir ui exec vitest run -t 30260 packages/app-shell/src/views/ActionParamDialog.test.tsx,预期 3 条通过(选择器提交support_agent、无 widget 的对照是文本框、未注册名退回 textarea)。What changes
ActionParamDef.widget(ui/packages/core/src/actions/ActionRunner.ts, the type only): the resolved param carries the spec'sActionParamSchema.widget.resolveActionParam(ui/packages/fields/src/resolveActionParams.ts): copies the param'swidgeton the inline branch and the unresolved-field branch. On the field-backed branch the param's ownwidgetwins; otherwise it inherits the field'sFieldSchema.widget, but only while the param also inherits the field'stype(or restates it). A param that overridestypeasks for a different value, so a widget chosen for the field's value is not carried onto it (inheritedWidget).paramToField(ui/packages/app-shell/src/utils/paramToField.ts): awidgetthat names a registered field widget replaces thetypewidget key the dialog mounts throughgetLazyFieldWidget; a name nothing registers keeps thetypewidget. The test is the shared resolver's own answer (resolveFormWidgetType, read for its documentedtextfallback), the same reading the console'sFormPagemakes of a field'swidget.paramDegradesWithoutTargetanswers false when a registered widget is drawn, so a targetless lookup param with such a widget gets neither the text fallback nor the paste-an-id hints, and the predicate/adapter equivalence still holds.packages/spec/liveness/action.json'sparams.widgetrow islive, citing both readers, withauthorWarn/authorHintdropped;packages/spec/src/ui/action.zod.tsloses the describe's "Not read yet" sentence, the//pointer comment and the JSDoc "Planned" sentence. Generated with them:content/docs/references/ui/action.mdx(gen:schema && gen:docs) andpackages/spec/liveness/state-counts/action.md(gen:liveness-counts).minoron@object-ui/core,@object-ui/fields,@object-ui/app-shell,@objectstack/spec).Why
ActionParamSchema.widgetwas accepted and stored but read by nothing, so a param that asked for thepermission-refpicker drew a text box. This is the console half of #30117's split; Create User's param switch stays with #30117 (packages/platform-objects/**untouched).Tests
New pins, each a top-level
describein the existing harness file:resolveActionParams.test.ts— inline copy, field-backed inheritance, restated type inherits, overridden type does not, param beats field, no-widget control, unresolved branch keeps it, the publicActionParamtype accepts it (8).paramToField.test.ts— registered widget drawn, no-widget control, unregistered falls back to the param's own type widget (text/select/textarea), an authoredtexthonoured, a targetless lookup with a registered widget is drawn and does not degrade, with the unregistered control still degrading (5).ActionParamDialog.test.tsx— mounted insideSchemaRendererProviderwith a catalog: the picker listsSupport Agent (support_agent), submits{ permissionSet: 'support_agent' }, and the emitted value matchesexpectedParamShape(string); CONTROL withoutwidgetis an input that lists nothing; an unregistered name on atextareaparam is the textarea (3).Acceptance notes
toBulkParam/bulkParamToField, whoseBulkActionParamshape has nowidget, so a promoted bulk action's param still draws itstypewidget. Outside this card's file surface; recorded in the liveness note. carrier: seat.authoredWidgetKeyrestatesFormPage'snamesRegisteredWidget(one line overresolveFormWidgetType). A shared export would mean editing@object-ui/fields' barrel andui/apps/console, both outside the surface. carrier: seat.widgetonly while it inherits or restates the field'stype, a restated type matched through the param-type fold; a param that overridestypedoes not) is this PR's reading of the spec's "widget mapping from object metadata"; the seat accepted it at ACCEPT, and the spec JSDoc says the same.claude/project-thread-ijgrij(claim amendment 6659791806), notclaude/issue-30260-*.paramValueShape.tsneeded no edit:expectedParamShaperesolves throughparamToField, so apermission-refparam already reads itsstringcontract row.Verification
Ablations (each with
scripts/ablation-replace.mjs, mutation proven on disk, restored to the HEAD blob; the ui suites alias@object-ui/*to source, so no build leg):paramToField: authored widget leg removed ⇒ 5 of the 16 new tests red (the picker render, registered widget drawn,texthonoured, targetless lookup, public type); controls green.resolveActionParaminlinewidget: param.widget⇒widget: undefined⇒ 2 red.widget: inheritedWidget(param, field)deleted ⇒ 3 red.Local gates on
4d88bf5a(the head after mergingorigin/main):node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 105 families, all 105 run, each exit code captured before any pipe;--ranreconciliation: 105 derived, 105 run, 0 NOT-MEASURED, 0 UNRUN. Plus the order's extras:pnpm --dir ui run check:spec-symbolsexit 0 andnode scripts/check-sdui-manifest.mjsexit 0 (in the derived set).check:dual-build-cjs-loadsread built output after a fullturbo run build(131 packages, exit 0).Type-check:
pnpm --filter @object-ui/core run type-check,@object-ui/fields,@object-ui/app-shell(the last re-run after the merge), all exit 0. Tests after the merge:resolveActionParams.test.ts,paramToField.test.ts,ActionParamDialog.test.tsx,paramValueShape.test.tsexit 0.Dogfood battery: n/a — no
!in the title and no(narrowing)arm.Gate run record (105 derived + 1 extra)
Generated by Claude Code