Skip to content

userActions.create is boolean-only — related-list [+ New] cannot be gated on parent-record state, unlike edit/delete (#3076 left create behind) #7692

Description

@baozhoutao

The gap

@objectstack/spec@17.0.0-rc.6UserActionsSchema:

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 acceptedcreate 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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions