Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/action-param-widget-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@objectstack/spec": minor
---

feat(spec): `ActionParamSchema` gains optional widget config — `multiple`, `accept`, `maxSize`

The console now renders action params through the same field-widget renderer
the record form uses (objectui#2700, objectui ADR-0059), so inline params can
declare the widget config the form widgets consume: `multiple` (array value
shape, mirrors `FieldSchema.multiple`), and the upload constraints `accept`
(MIME types / extensions) and `maxSize` (bytes) for `file`/`image` params.
Field-backed params (`{ field }`) keep inheriting these from the referenced
field at runtime; inline values override. Purely additive — no existing
schema changes shape.
2 changes: 1 addition & 1 deletion .changeset/console-fd45313b4d00.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"@objectstack/console": major
"@objectstack/console": minor
---

Console (objectui) refreshed to `fd45313b4d00`. Frontend changes in this range:
Expand Down
3 changes: 3 additions & 0 deletions content/docs/references/ui/action.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ const result = Action.parse(data);
| **placeholder** | `string` | optional | |
| **helpText** | `string` | optional | |
| **defaultValue** | `any` | optional | |
| **multiple** | `boolean` | optional | Allow multiple values (array value shape); mirrors FieldSchema.multiple. |
| **accept** | `string[]` | optional | Accepted upload types (MIME types / extensions) for file/image params. |
| **maxSize** | `integer` | optional | Max upload size in bytes for file/image params. |
| **defaultFromRow** | `boolean` | optional | |
| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Param visibility predicate (CEL); omits the param when false. |
| **requiresFeature** | `Enum<'twoFactor' \| 'passkeys' \| 'magicLink' \| 'organization' \| 'multiOrgEnabled' \| 'degradedTenancy' \| 'oidcProvider' \| 'sso' \| 'ssoEnforced' \| 'deviceAuthorization' \| 'admin' \| 'phoneNumber' \| 'phoneNumberOtp'>` | optional | Public auth feature flag gating this param; lowered into `visible` at parse time. |
Expand Down
8 changes: 6 additions & 2 deletions content/docs/ui/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,13 @@ engine still requires the action to declare the matching location (that's why

- **`params`** — prompt the user for input before execution. Prefer
field-backed params (`{ field: 'due_date' }`) which inherit the object
field's label, type, and validation; inline params
field's label, type, validation, and widget config; inline params
(`{ name, label, type, required, options }`) cover the rest.
`defaultFromRow` prefills from the current record.
`defaultFromRow` prefills from the current record. The console renders each
param through the **same field widgets as the record form** (objectui
ADR-0059), so any `FieldType` works — a `file` param shows a real upload
control (`multiple` / `accept` / `maxSize` honored), `lookup` a record
picker, `date` a date picker, `richtext` the rich-text editor, and so on.
- **`confirmText`** — confirmation dialog before running.
- **`successMessage` / `errorMessage` / `refreshAfter`** — post-run feedback
and an automatic data refresh.
Expand Down
36 changes: 36 additions & 0 deletions packages/spec/src/ui/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,42 @@ describe('ActionParamSchema', () => {

expect(() => ActionParamSchema.parse(param)).not.toThrow();
});

// objectui ADR-0059 — params render through the shared form field-widget
// renderer, so inline params can carry the widget config the form widgets
// consume (field-backed params inherit these from the field at runtime).
it('accepts a file param with upload widget config (multiple/accept/maxSize)', () => {
const result = ActionParamSchema.parse({
name: 'attachments',
label: 'Attachments',
type: 'file' as const,
multiple: true,
accept: ['application/pdf', 'image/*'],
maxSize: 5 * 1024 * 1024,
});
expect(result.multiple).toBe(true);
expect(result.accept).toEqual(['application/pdf', 'image/*']);
expect(result.maxSize).toBe(5 * 1024 * 1024);
});

it('leaves widget config undefined when not declared (runtime inherits from the field)', () => {
const result = ActionParamSchema.parse({ field: 'avatar_file' });
expect(result.multiple).toBeUndefined();
expect(result.accept).toBeUndefined();
expect(result.maxSize).toBeUndefined();
});

it('rejects a non-positive or fractional maxSize', () => {
expect(() => ActionParamSchema.parse({ name: 'f', type: 'file', maxSize: 0 })).toThrow();
expect(() => ActionParamSchema.parse({ name: 'f', type: 'file', maxSize: -1 })).toThrow();
expect(() => ActionParamSchema.parse({ name: 'f', type: 'file', maxSize: 1.5 })).toThrow();
});

it('accepts rich field types for params (rendered by the shared widget renderer)', () => {
for (const type of ['file', 'image', 'richtext', 'color', 'date', 'address', 'code'] as const) {
expect(() => ActionParamSchema.parse({ name: 'p', label: 'P', type })).not.toThrow();
}
});
});

// #2874 P1 — declarative `requiresFeature` sugar, lowered at parse time into
Expand Down
13 changes: 13 additions & 0 deletions packages/spec/src/ui/action.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ export const ActionParamSchema = lazySchema(() => z.object({
helpText: z.string().optional(),
/** Default value for the dialog input. */
defaultValue: z.unknown().optional(),
/**
* Widget config for inline params (field-backed params inherit these from
* the referenced field at runtime; inline values override). The param
* dialog renders every param through the same field-widget renderer the
* object form uses (objectui ADR-0059), so these mirror the corresponding
* `FieldSchema` knobs.
*/
/** Allow multiple values (file/image/lookup/user params → array value). */
multiple: z.boolean().optional().describe('Allow multiple values (array value shape); mirrors FieldSchema.multiple.'),
/** Accepted upload types (MIME types / extensions) for `file`/`image` params. */
accept: z.array(z.string()).optional().describe('Accepted upload types (MIME types / extensions) for file/image params.'),
/** Max upload size in bytes for `file`/`image` params. */
maxSize: z.number().int().positive().optional().describe('Max upload size in bytes for file/image params.'),
/**
* When true, the param's default value is pulled from the current row record
* (key = the resolved field name) when the action runs from a list_item
Expand Down
Loading