The gap
@objectstack/spec@17.0.0-rc.6 — UserActionsSchema:
userActions: z.ZodOptional<z.ZodObject<{
create: z.ZodOptional<z.ZodBoolean>; // ← boolean only
import: z.ZodOptional<z.ZodBoolean>; // ← boolean only
edit: z.ZodOptional<z.ZodUnion<[z.ZodBoolean, z.ZodObject<{ enabled, visibleWhen: CEL }>]>>;
delete: /* same union as edit */
}>>
#3076 (feat(spec): userActions.edit/delete accept per-record CEL predicates, objectui#2614) gave edit and delete a per-record CEL predicate. create was left behind, and there is no other lever for it.
Why that is a real hole, not a nice-to-have
A child object's [+ New] button in a related list must very often be gated on the parent record's state — the classic case being "the parent is frozen/published/archived, so nothing may be added under it any more".
With edit/delete you can express this today: snapshot the parent's status onto the child and write userActions.delete.visibleWhen. For create there is nothing to write — the button is either always on or always off for the whole object, and "always off" is wrong because the child is creatable while the parent is a draft.
Concrete case that hit us
App-side object model:
task_version (parent) — has states draft / published / deprecated
task_version_check_item, task_position (children, rendered as related lists on the version record page)
Spec requirement: once a version is published or deprecated, its children are frozen — no add, no edit, no delete.
What we could implement:
| control |
gated on parent state? |
child row Edit / Delete |
✅ via userActions.edit/delete.visibleWhen against a version_status snapshot field |
related-list [+ New] |
❌ no expression accepted — create is boolean |
Result: on a published version the row actions correctly grey out, but the [+ New] button still renders. The server-side guard does reject the insert (HTTP 409 with a business message), so this is not a data-integrity problem — it is a pure UI affordance leak: users are shown an action that can never succeed, and QA legitimately reports it as a defect every round.
We deliberately did not work around it (no custom page, no patched component), which is why we are reporting it rather than shipping a local hack.
Requested change
Widen create (and import, same shape and same argument) to the union already used by edit / delete:
create: z.union([
z.boolean(),
z.object({ enabled: z.boolean().optional(), visibleWhen: CelPredicate.optional() })
]).optional()
Evaluation context should be the same one edit/delete already get. For a related list the useful context is the parent record; if that is not available at that point, the snapshot-field workaround we already use for edit/delete (parent status denormalised onto the child) would be enough — the blocker is purely that create accepts no expression at all.
Renderer side presumably needs the objectui counterpart of objectui#2614/#2617 for the related-list toolbar.
Environment
@objectstack/*@17.0.0-rc.6
- Reproduced in an app project; server-side guard confirmed working (409 on insert), so the request is strictly about the affordance.
The gap
@objectstack/spec@17.0.0-rc.6—UserActionsSchema:#3076 (
feat(spec): userActions.edit/delete accept per-record CEL predicates, objectui#2614) gaveeditanddeletea per-record CEL predicate.createwas left behind, and there is no other lever for it.Why that is a real hole, not a nice-to-have
A child object's
[+ New]button in a related list must very often be gated on the parent record's state — the classic case being "the parent is frozen/published/archived, so nothing may be added under it any more".With
edit/deleteyou can express this today: snapshot the parent's status onto the child and writeuserActions.delete.visibleWhen. Forcreatethere is nothing to write — the button is either always on or always off for the whole object, and "always off" is wrong because the child is creatable while the parent is a draft.Concrete case that hit us
App-side object model:
task_version(parent) — has states draft / published / deprecatedtask_version_check_item,task_position(children, rendered as related lists on the version record page)Spec requirement: once a version is published or deprecated, its children are frozen — no add, no edit, no delete.
What we could implement:
Edit/DeleteuserActions.edit/delete.visibleWhenagainst aversion_statussnapshot field[+ New]createis booleanResult: on a published version the row actions correctly grey out, but the
[+ New]button still renders. The server-side guard does reject the insert (HTTP 409 with a business message), so this is not a data-integrity problem — it is a pure UI affordance leak: users are shown an action that can never succeed, and QA legitimately reports it as a defect every round.We deliberately did not work around it (no custom page, no patched component), which is why we are reporting it rather than shipping a local hack.
Requested change
Widen
create(andimport, same shape and same argument) to the union already used byedit/delete:Evaluation context should be the same one
edit/deletealready get. For a related list the useful context is the parent record; if that is not available at that point, the snapshot-field workaround we already use foredit/delete(parent status denormalised onto the child) would be enough — the blocker is purely thatcreateaccepts no expression at all.Renderer side presumably needs the objectui counterpart of objectui#2614/#2617 for the related-list toolbar.
Environment
@objectstack/*@17.0.0-rc.6