Skip to content

Commit 609cb13

Browse files
os-zhuangclaude
andauthored
feat(spec,platform-objects): action param 'visible' predicate; gate create-user phone on features.phoneNumber (#2871)
ActionParamSchema gains an optional 'visible' (ExpressionInputSchema / CEL), evaluated against the same scope as action 'visible'. The sys_user create_user action's phoneNumber param is gated on features.phoneNumber == true, so the form only offers a phone field when the opt-in phoneNumber auth plugin is loaded — otherwise admin/create-user rejects the phone. Pairs with objectui's ActionParamDialog honoring param.visible (objectstack-ai/objectui#<pr>). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 50c97aa commit 609cb13

3 files changed

Lines changed: 22 additions & 0 deletions

File tree

.changeset/action-param-visible.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/platform-objects': patch
4+
---
5+
6+
**Action params gain a `visible` predicate; the create-user `phoneNumber` param is gated on `features.phoneNumber`.**
7+
8+
`ActionParamSchema` gains an optional `visible` (CEL, `ExpressionInputSchema`) evaluated against the same scope as action `visible` (`current_user`/`app`/`data`/`features`); a UI that honors it omits the param when it's false. The `sys_user` `create_user` action's `phoneNumber` param now carries `visible: 'features.phoneNumber == true'`, so the form no longer offers a Phone Number field when the opt-in `phoneNumber` auth plugin is off — otherwise the endpoint rejects it with "Phone numbers require the phoneNumber auth plugin". Pairs with the objectui `ActionParamDialog` change that evaluates `param.visible`.

packages/platform-objects/src/identity/sys-user.object.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ export const SysUser = ObjectSchema.create({
153153
type: 'text',
154154
required: false,
155155
helpText: 'Sign-in phone number (E.164, e.g. +8613800000000). Required when no email is given.',
156+
// Only offer phone when the opt-in phoneNumber auth plugin is loaded —
157+
// otherwise the create-user endpoint rejects a phone with
158+
// "Phone numbers require the phoneNumber auth plugin". `features.phoneNumber`
159+
// is served in /api/v1/auth/config (getPublicConfig).
160+
visible: 'features.phoneNumber == true',
156161
},
157162
{ field: 'name', required: false },
158163
{

packages/spec/src/ui/action.zod.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ export const ActionParamSchema = lazySchema(() => z.object({
6666
* context. Useful for edit dialogs that pre-fill from the selected row.
6767
*/
6868
defaultFromRow: z.boolean().optional(),
69+
/**
70+
* Visibility predicate (CEL) — same scope as the action-level `visible`
71+
* (`current_user` / `app` / `data` / `features`). When it evaluates false the
72+
* dialog omits this param entirely. Use it to hide a param that the backend
73+
* only accepts under an opt-in capability, e.g. the create-user `phoneNumber`
74+
* param gated on `features.phoneNumber` so the form never offers a field the
75+
* default backend rejects. Absent = always visible.
76+
*/
77+
visible: ExpressionInputSchema.optional().describe('Param visibility predicate (CEL); omits the param when false.'),
6978
}).refine(
7079
(p) => Boolean(p.name) || Boolean(p.field),
7180
{ message: 'ActionParam requires either "name" or "field"' },

0 commit comments

Comments
 (0)