Skip to content

Commit c7d1367

Browse files
feat(spec): an action param can name the widget that draws it (permission-ref picker) (#30321)
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K <!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBYXdLWhK9kFDrFra5KK9ZbM)_ domain:spec#3 · os-support-ai · session_01WGufzhmTfrR3b13itPTmu3 Fixes #30259 Part of #30117 (this is slice 1, the spec half; #30260 adds the console reader, and #30117 keeps the server switch of Create User's param) Clause-②: yes (widening) Metadata-protocol: added Skills-docs: follow-up owed once #30260 lands (the skills lane is paused, and the key does nothing in the console until then, so teaching it now would teach an inert key). Owed lines: skills/objectui/rules/protocol.md:309, the inline-param widget-config list (`options`, `multiple`, `accept`, …) gains `widget`; skills/objectstack-ui/rules/actions.md:174-175 says a field-backed param takes its "widget" from the object metadata, which the console does not do today (its resolver copies no field `widget` onto a param, measured on #30117), so that line is #30260's to make true or to correct. Prior rulings read: permission-ref,actionparamschema,action param widget → 1 hits; ADR-0104 D2; thread: none; repo: objectstack-ai/objectstack ADR-0087: not needed. A new optional key; nothing is removed, renamed or narrowed, and no stored param changes meaning, so no conversion is owed. ## 维护者速读 - **改了什么:** 动作参数(`ActionParamSchema`)新增可选键 `widget`,与字段上的 `widget` 同名、同词汇、同含义:指定用哪个已注册的控件来画这个参数,`type` 仍是值的类型。Studio 的动作表单参数列表多一列「控件」,四种语言都有标签。 - **为什么改:** 新建用户时「权限集」是一个只认机器名的文本框(#30117)。授权表上的同一列早已用 `permission-ref` 选择器(ADR-0131 D4),但动作参数没有任何方式请求这个选择器。这是拆分后的第一片:先让规范能表达。 - **风险与回滚:** 低。纯新增可选键:不写它的参数解析结果与之前完全一样;未注册的控件名会被保留并退回 `type` 的默认控件,和字段一致。控制台在 #30260 落地前不读它,所以现在声明了也只是照旧显示文本框;键的说明写明了这一点,`os validate` / `os lint` 也会提醒写了它的作者。回滚即还原本 PR。 - **先看:** `packages/spec/src/ui/action.zod.ts`(新键与 TSDoc);`packages/spec/src/ui/action-param-widget.test.ts`(与字段键逐值同判的钉);`packages/spec/liveness/action.json`(`params` 钻取后的逐成员判定,`widget` 为 planned 并提醒作者)。 - **两分钟验证:** `pnpm --filter @objectstack/spec exec vitest run src/ui/action-param-widget.test.ts`,预期 `17 passed`。 - **需要你做的:** 无,席位落地。 ## What changes - **`@objectstack/spec`: `ActionParamSchema.widget`**, `z.string().optional()`, placed after `type`. Same key, vocabulary and meaning as `FieldSchema.widget` (`data/field.zod.ts`): it names a registered field component, looked up as `field:` plus the name, that draws the param instead of the `type` default. `type` stays the value's type. The TSDoc cites ADR-0131 D4 and names the case: `widget: 'permission-ref'` on a `text` param lists the security catalog's permission sets and submits the chosen machine name. The `.describe()` carries the same contract for the generated docs and JSON Schema. - **The action form (`ui/action.form.ts`)**: the `params` repeater offers `widget` as a row property (label `Widget`), and `@objectstack/platform-objects`' metadata-form bundles label it in en / zh-CN / ja-JP / es-ES, reusing each locale's existing word for the field form's `widget` row (`控件覆盖`, `ウィジェット指定`, `Control del campo`). - **Liveness, `action/params` drilled**: `liveness/action.json`'s `params` row now carries a `children` map, one verdict per member, and `action/params` leaves the `containers` list of `scripts/liveness/undrilled-containers.baseline.json` (the debt shrinks by 19 child keys). 18 members are `live`, each with dated in-repo evidence anchored on the consuming symbol: the console resolver `resolveActionParam`, the dialog (`ActionParamDialog`, `filterVisibleParams`, `CarryOverParam`, `serializeParamValues`), `paramToField`, and the dispatcher's ADR-0104 D2 path (`resolveDeclaredActionParams`, `validateActionParams`, `summarizeActionParams`); `requiresFeature` cites `lowerRequiresFeature`, its parse-time lowering, as the action-level row does. `options` is itself a container, so it is drilled too (`label`, `value`, `visibleWhen`, all `live`). `widget` is `planned` with `authorWarn` and an `authorHint` saying the dialog does not read it yet (the row's note names #30260 as the reader; author-facing text carries no issue id, per `check:doc-authoring`), so `os validate` / `os lint` warn an author who writes it (rule `liveness-planned-property`); its note records what each reader does with it today. `state-counts/action.md` moves 51/2/54 → 70/3/74 (live/planned/classified). Two measured notes ride on rows: `objectOverride` is read by the console only (the dispatcher resolves a field-backed param against the action's own object), and `options.visibleWhen` is client-side only. - **Describe**: the key's `.describe()` (and so the generated reference) now says it is not read yet: until the console's dialog passes it through, every param is drawn with its `type` renderer. A `//` comment beside the key says the sentence comes off with #30260. - **Generated**: `authorable-surface/ui.json` gains `ui/ActionParam:widget`; `content/docs/references/ui/action.mdx` gains the row (both regenerated, not hand-edited). - **Count pins moved by the new form row** (platform-objects): `action-body-panel-echo-decisions.test.ts` (params children 18 → 19) and `object-lifecycle-panel-echo-decisions.test.ts` (positive control 701 → 702), each with a dated comment line. - **Not changed**: `sys-user.object.ts`. Its `permissionSet` param switches to `widget: 'permission-ref'` in #30117's server PR, once #30260 gives the key a reader; declaring it now would ship a key the console ignores. `BulkActionParamSchema` does not get the key: no bulk action asks for a picker of this kind today. ### The choice: `widget` on the param, not `permission-ref` as a param `type` Measured on `origin/main` `959c26be`: - **`permission-ref` is a widget hint, not a field type.** It is not in `FieldType`. The grant column declares it as `Field.text({ widget: 'permission-ref' })` (`plugin-security/src/objects/sys-user-permission-set.object.ts:104`), the console registers it as `field:permission-ref` beside `object-ref` and `position-ref`, and both the console's changelog and `ui/packages/app-shell/src/utils/paramValueShape.ts:181` say it is "widget-hint only — not a declarable param type". A param `type` reads `FieldType`, so the `type` route means either adding `permission-ref` to `FieldType` (which also makes it a declarable FIELD type, against ADR-0131 D4's text-column-plus-widget shape) or forking a param-only type vocabulary. - **The `type` route would loosen the server's value check.** ADR-0104 D2 (the one prior ruling the check found) makes `type` the enforced value contract: the dispatcher validates each param through `valueSchemaFor(type)`. A `type` it does not know falls to the explicitly-open branch (`field-value.zod.ts:797`, `z.unknown()`), so `type: 'permission-ref'` would stop checking that the value is a string. With `widget`, `type` stays `text` and D2's check is unchanged. - **`widget` is one key with the field's own meaning**, so an author who knows the field key already knows this one, and the console's resolver can pass it through to the same `field:WIDGET` lookup the record form uses. **Value verdict: kept, as on the field.** `FieldSchema.widget` is an open `z.string()`: an unregistered name parses and degrades to the `type` renderer. The param key makes the same promise, and the pin checks seven values (three registered names, one unregistered, a number, a boolean, `null`) get the same verdict on both schemas. ### Who reads action params, and what each does with the key today | Reader | What it does with `widget` before #30260 | |:--|:--| | Spec parse door: `ActionParamSchema` (strict) behind `PUT /meta/action`, `MetadataManager.validate`, `defineStack`, `os validate` | Before this PR: refused as an unrecognized key. After: kept in the parsed param. | | Console resolver `ui/packages/fields/src/resolveActionParams.ts` | Builds `ActionParamDef` from named members only; `ActionParamDef` has no `widget`, so the key is not carried. The param is still resolved and drawn. | | Console adapter `ui/packages/app-shell/src/utils/paramToField.ts` | Sets a field `widget` only for `boolean` (`checkbox`); otherwise draws the `type` renderer. A `text` param stays a text box and its value is submitted as before. Nothing is dropped. | | REST / MCP dispatch (`packages/runtime/src/action-execution.ts` → `validateActionParams`) | Resolves each param to `{ name, type, multiple, required, options }` (`ResolvedActionParam`) and validates the value by `type` (ADR-0104 D2). `widget` is not read; the value check is unchanged. | | AI action tools (cloud `service-ai` `action-tools.ts#buildParametersSchema`, the `params` row's evidence) | NOT MEASURED: closed repo, not in this container. Per the ledger row it builds one JSON Schema property per param from its declared shape; an extra string key gives it nothing new to misread. | | `os i18n extract` (`packages/cli/src/utils/i18n-extract.ts`) | Emits `label` / `helpText` / `placeholder` / `options` leaves only; `widget` is a machine name, not translatable copy. | | `@object-ui/types` `ActionParam` | Derived from the spec schema's `z.input` type, so it gains the member with no edit; its parity pins are subset (direction) checks. Not run locally (ui is out of scope here); CI's UI job runs them. | ## Tests - New `packages/spec/src/ui/action-param-widget.test.ts` (17 cases): accepts `permission-ref` on an inline `text` param and keeps it with `type` unchanged; accepts it on a field-backed param; it is a declared key (no `unrecognized_keys`); per-value parity with `FieldSchema.widget` over seven values; an unregistered name is kept; a non-string is refused on the `widget` path (`invalid_type`); five existing param shapes (Create User's current inline `permissionSet` among them) parse with no `widget` member added. - New pin in `packages/lint/src/lint-liveness-properties.test.ts`: an action whose params name `widget` draws exactly one `liveness-planned-property` warning (through the shipped ledger and the array fan-out); its live siblings stay silent; an action whose params name no widget draws nothing. - Suites, under the verify lock: - `pnpm --filter @objectstack/spec test`: Test Files 813 passed (813), Tests 25592 passed, 1 todo, on the drilled tree (4fe77e8's content). `pnpm --filter @objectstack/spec typecheck`: exit 0 (first head). - On `1d215c5c` (the describe and hint rewording): `lint-liveness-properties.test.ts` 113/113; `action-param-widget.test.ts` + `action.test.ts` 168/168. - `pnpm --filter @objectstack/metadata-protocol test`: Test Files 312 passed, 4 skipped (316), Tests 22354 passed, 26 skipped (first head; the later commits touch no metadata-protocol input). - `pnpm --filter @objectstack/platform-objects test`: the first run had 2 failures, both count pins the new form row moves (`action-body-panel-echo-decisions.test.ts` 18 → 19, `object-lifecycle-panel-echo-decisions.test.ts` 701 → 702). Both moved in the second commit; the two files then pass (57/57), and the other 108 files passed. - Ablation of the key (first head, `ablation-replace.mjs`, see *Verification*). The ledger drill is data, so it is not ablated; the lint pin is its red-without-it check. <details><summary>Derived gates on the final head 1d215c5: 107 derived, 107 run, all exit 0</summary> - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 107 families against merge base 959c26b; a runner recorded `CMD :: exit N` for each (with `NODE_OPTIONS=--max-old-space-size=4096`, after a full `turbo run build`), and `--ran` reconciled: "107 derived famil(ies) accounted for — 107 run, 0 NOT-MEASURED". - `check:liveness`: "every governed-type property, at every depth the ledger drills, is classified … state-counts/ is current". - `check:dual-build-cjs-loads`: "116 published require entry point(s) across 71 package(s) load; 2456 emitted CommonJS file(s) parse" (NOT MEASURED on the first head; measured here after the full build). - `check:doc-authoring`: "21066 customer-facing string(s) … clean — no internal issue-id references". Its first run on `4fe77e88` refused the describe's `objectstack#30260`; `1d215c5c` moved that pointer to a `//` comment and out of the `authorHint`. - `check:docs`: "240 generated files in sync with packages/spec". - `check-changeset-no-major --base origin/main`: no `major` bump; the declaration axis reads this body in CI. </details> ## Acceptance notes - **The liveness row, after review.** The first head (`792c7abe`) wrote `widget`'s planned status into the note of the undrilled `params` row, which no check scans and no lint reads. The contract review (6651175841) asked for the drill instead, and this head does it: 22 rows (19 members plus `options`' 3), each verdict from its own reader, none fanned out from the parent. #30260 flips `params.widget` to `live` and drops its `authorWarn` when the pass-through lands. - **Found while drilling, not changed here**: the dispatcher's `resolveDeclaredActionParams` does not read `objectOverride`, so a cross-object field-backed param's server-side value check inherits from the action's own object's field of that name (or is open when there is none), while the console resolves it against the override object. Recorded on the row's note; it is outside this card. - The existing skills line that says a field-backed param takes its widget from the object field (skills/objectstack-ui/rules/actions.md:174-175) is false in the console today; it is listed under *Skills-docs* above and in the card report, with #30260 as its carrier. ## Verification - `Metadata-protocol: added` (corrected from the malformed `additive` the first head carried, which `Check Changeset` read as no declaration) is read off this body by CI's `Check Changeset`; locally `check-changeset-no-major` found no `major` bump. The changeset bumps `@objectstack/spec` and `@objectstack/platform-objects` minor and carries both declaration lines. - Ablation, one run on the first head `792c7abe` through `scripts/ablation-replace.mjs` (spec tests import the schema from `src`, so no build sits between): renaming the key to `widgetAblated` (anchor 1 → 0, blob `8cd40792` → `893ac68c`) turns `action-param-widget.test.ts` red, 9 failed / 8 passed of 17 (the 8 are the five existing-param cases and the three values both schemas refuse). Restored: blob equals HEAD `8cd40792` and `git diff HEAD` is empty. - Reverse check of the parity pin's premise: the field key's verdicts are read from the built schema in the same test run, so if `FieldSchema.widget` ever narrows to an enum the per-value cases go red rather than silently diverging. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` is left to CI (the repo-wide run is CI's). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the maintainer brief is in Chinese by the repo's rule). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzhmTfrR3b13itPTmu3 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fe835b4 commit c7d1367

16 files changed

Lines changed: 334 additions & 6 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/platform-objects': minor
4+
---
5+
6+
feat(spec): an action param can name the widget that draws it (`widget`, same key as on a field), so a param can ask for the permission-set picker (ADR-0131 D4)
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
- **`@objectstack/spec`: `ActionParamSchema.widget`**, an optional string with the same key, vocabulary and meaning as `FieldSchema.widget`. It names a registered field component, looked up as `field:` plus the name, to draw the param in the action dialog instead of the `type` default. `type` stays the value's type. The case it exists for is `widget: 'permission-ref'` on a `text` param: it lists the permission sets of the security catalog and submits the chosen machine name, the picker the grant column `sys_user_permission_set.permission_set` already uses. Like the field key it is an open string: an unregistered name is kept, and the dialog draws the `type` renderer instead. A non-string is refused. A param that does not declare the key parses exactly as before. Until now a param naming `widget` was refused as an unknown key.
12+
- **Not read yet.** The console's param resolver does not carry the key to the dialog's widget until objectstack#30260 lands. Until then a param that declares it is drawn by its `type` renderer, and its value is submitted as before. The key's description says so, and the liveness ledger grades it `planned` with an author warning: `os validate` and `os lint` warn an author who writes it.
13+
- **`@objectstack/platform-objects`: the action form's Params repeater offers the key** as a `Widget` column, labelled in en, zh-CN, ja-JP and es-ES.

‎content/docs/references/ui/action.mdx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ L2 sandboxed JS body — runs inside an isolated VM with declared capabilities
141141
| **objectOverride** | `string` | optional | Snake case identifier (lowercase with underscores only) |
142142
| **label** | `string \| Record<string, string>` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time |
143143
| **type** | `Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| …>` | optional | |
144+
| **widget** | `string` | optional | Widget override for this param — names a registered field component (resolved as `field:<widget>`) to draw it instead of the `type` default; same key and vocabulary as FieldSchema.widget, and `type` stays the value's type. e.g. "permission-ref" on a text param picks a permission set by name (ADR-0131 D4). Degrades to the `type` renderer when unregistered. Not read yet: until the console's action dialog passes it through, the dialog draws every param with its `type` renderer whatever this names. |
144145
| **required** | `boolean` | optional (default: `false`) | |
145146
| **options** | `{ label: string \| Record<string, string>; value: string; visibleWhen?: string \| object }[]` | optional | |
146147
| **placeholder** | `string` | optional | |
@@ -234,6 +235,7 @@ L2 sandboxed JS body — runs inside an isolated VM with declared capabilities
234235
| **objectOverride** | `string` | optional | Snake case identifier (lowercase with underscores only) |
235236
| **label** | `string \| Record<string, string>` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time |
236237
| **type** | `Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| 'markdown' \| 'html' \| 'richtext' \| 'number' \| 'currency' \| 'percent' \| 'date' \| … +35 more>` | optional | |
238+
| **widget** | `string` | optional | Widget override for this param — names a registered field component (resolved as `field:<widget>`) to draw it instead of the `type` default; same key and vocabulary as FieldSchema.widget, and `type` stays the value's type. e.g. "permission-ref" on a text param picks a permission set by name (ADR-0131 D4). Degrades to the `type` renderer when unregistered. Not read yet: until the console's action dialog passes it through, the dialog draws every param with its `type` renderer whatever this names. |
237239
| **required** | `boolean` | optional (default: `false`) | |
238240
| **options** | `{ label: string \| Record<string, string>; value: string; visibleWhen?: string \| object }[]` | optional | |
239241
| **placeholder** | `string` | optional | |
@@ -370,6 +372,7 @@ L2 sandboxed JS body — runs inside an isolated VM with declared capabilities
370372
| **objectOverride** | `string` | optional | Snake case identifier (lowercase with underscores only) |
371373
| **label** | `string \| Record<string, string>` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time |
372374
| **type** | `Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| …>` | optional | |
375+
| **widget** | `string` | optional | Widget override for this param — names a registered field component (resolved as `field:<widget>`) to draw it instead of the `type` default; same key and vocabulary as FieldSchema.widget, and `type` stays the value's type. e.g. "permission-ref" on a text param picks a permission set by name (ADR-0131 D4). Degrades to the `type` renderer when unregistered. Not read yet: until the console's action dialog passes it through, the dialog draws every param with its `type` renderer whatever this names. |
373376
| **required** | `boolean` | optional (default: `false`) | |
374377
| **options** | `{ label: string \| Record<string, string>; value: string; visibleWhen?: string \| object }[]` | optional | |
375378
| **placeholder** | `string` | optional | |

‎packages/lint/src/lint-liveness-properties.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,34 @@ describe('lintLivenessProperties', () => {
187187
}).filter((f) => f.message.includes('objectName'))).toEqual([]);
188188
});
189189

190+
// #30259 — `action.params[].widget` is declared before the console's dialog
191+
// reads it (#30260 adds the pass-through), so the drilled row is `planned` +
192+
// `authorWarn`. Pinned through the SHIPPED ledger and an array of params, so
193+
// the drill (`params.widget`) and the array fan-out both have to hold.
194+
it('warns on an action param that names a widget — planned, nothing reads it yet', () => {
195+
const findings = lintLivenessProperties({
196+
actions: [{
197+
name: 'create_user',
198+
params: [
199+
{ name: 'email', type: 'email', required: true },
200+
{ name: 'permissionSet', type: 'text', widget: 'permission-ref' },
201+
],
202+
}],
203+
});
204+
const warned = findings.filter((f) => f.message.includes('sets `params.widget`'));
205+
expect(warned).toHaveLength(1);
206+
expect(warned[0].rule).toBe('liveness-planned-property');
207+
expect(warned[0].hint).toContain('nothing reads it yet');
208+
// The live siblings the drill graded stay silent.
209+
for (const key of ['name', 'type', 'required', 'options', 'helpText', 'carryOver']) {
210+
expect(ruleOf(findings, `params.${key}`), key).toBeUndefined();
211+
}
212+
// And an action whose params name no widget draws nothing about it.
213+
expect(lintLivenessProperties({
214+
actions: [{ name: 'a2', params: [{ name: 'note', type: 'text' }] }],
215+
}).filter((f) => f.message.includes('widget'))).toEqual([]);
216+
});
217+
190218
it('warns on an experimental prop with no authorWarn of its own (tool.outputSchema)', () => {
191219
// `experimental` warns implicitly — shouldWarn() treats a declared-but-
192220
// unenforced guarantee like an opted-in dead prop. Repointed from

‎packages/platform-objects/src/apps/translations/action-body-panel-echo-decisions.test.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,8 +637,9 @@ describe('#19403 round 5 — the blind spot, FIFTH shape: one schema, two forms,
637637
// `repeater-row-properties.test.ts` derives a row property for every child
638638
// of a `type: 'repeater'` field. On `actionForm` that is exactly ONE
639639
// repeater — `params`, with eighteen children — and every one of those
640-
// carried its own text in all three locales before this round.
641-
expect(ACTION_ENUMERATED.covered.length).toBe(18);
640+
// carried its own text in all three locales before this round. Nineteen
641+
// since #30259 added `params.widget`, authored in all three locales too.
642+
expect(ACTION_ENUMERATED.covered.length).toBe(19);
642643
expect(ACTION_ENUMERATED.covered).toContain('params.carryOver');
643644
expect(ACTION_ENUMERATED.covered).toContain('params.requiresFeature');
644645
for (const [locale, forms] of TRANSLATED_LOCALES) {

‎packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,9 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
17561756
"params.type": {
17571757
label: "Type"
17581758
},
1759+
"params.widget": {
1760+
label: "Widget"
1761+
},
17591762
"params.required": {
17601763
label: "Required"
17611764
},

‎packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,9 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
17561756
"params.type": {
17571757
label: "Tipo"
17581758
},
1759+
"params.widget": {
1760+
label: "Control del campo"
1761+
},
17591762
"params.required": {
17601763
label: "Obligatorio"
17611764
},

‎packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,9 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
17561756
"params.type": {
17571757
label: "型"
17581758
},
1759+
"params.widget": {
1760+
label: "ウィジェット指定"
1761+
},
17591762
"params.required": {
17601763
label: "必須"
17611764
},

‎packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1259,7 +1259,10 @@ describe('#19403 round 10 — the verdicts, on the live bundles', () => {
12591259
// 701 since #29442: the flow form's variables repeater offers
12601260
// `objectName` (a start input declared a record id) — one row label,
12611261
// authored in all three locales.
1262-
expect(translated.length, `${locale} positive control`).toBe(701);
1262+
// 702 since #30259: the action form's params repeater offers `widget`
1263+
// (the widget that draws the param, the same key as a field's) — one
1264+
// row label, authored in all three locales.
1265+
expect(translated.length, `${locale} positive control`).toBe(702);
12631266
}
12641267
// ⭐ DARK — the blindness, executable. On a synthetic two-locale catalog the
12651268
// all-three predicate returns 0 while the per-locale one returns 1, so the

‎packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,9 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
17561756
"params.type": {
17571757
label: "类型"
17581758
},
1759+
"params.widget": {
1760+
label: "控件覆盖"
1761+
},
17591762
"params.required": {
17601763
label: "必填"
17611764
},

‎packages/spec/authorable-surface/ui.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
"ui/ActionParam:requiresFeature",
159159
"ui/ActionParam:type",
160160
"ui/ActionParam:visible",
161+
"ui/ActionParam:widget",
161162
"ui/ActionSession:organizationId",
162163
"ui/ActionSession:positions",
163164
"ui/ActionSession:roles",

0 commit comments

Comments
 (0)