Skip to content

A required field whose defaultValue is a runtime token (NOW() / current_user) cannot be submitted from a create form #4069

Description

@yinlianghui

Found while implementing #4047 (PR #4068). Filed unassigned; not fixed there, because it needs a decision the card did not cover.

What happens

@objectstack/spec lets a field's defaultValue be a runtime instruction rather than a value — the DEFAULT_VALUE_TOKENS family ('NOW()', 'current_user') or a CEL Expression envelope { dialect, source }. The server resolves those per insert, in ObjectQL.applyFieldDefaults, for any field that arrives absent or null.

A create form therefore cannot seed them: putting the literal text NOW() into a datetime input and submitting it as the field's value suppresses the very resolution the declaration asked for (applyFieldDefaults only fills fields that arrive empty). PR #4068 makes every object-form container leave such fields empty for exactly that reason.

That is correct for an optional field. Combined with required: true it is not:

remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),

The control opens empty, the client-side required rule refuses the submit, and there is nothing sensible for the user to type — the declaration already said what the value is, and the server would have supplied it had the field simply been omitted. This is the same shape as #4047 (required + a default producing the worst create experience of any modelling choice), one layer down: #4047 covered the static half, this is the runtime half.

Why it is not just "seed it client-side"

Resolving these in the browser means evaluating CEL, and reproducing the server's clock / actor / timezone snapshot, in a second place. Two implementations of one contract is how they drift. So the fix is a design choice, not a patch, which is why it is filed rather than folded into #4068.

Rough options, for whoever picks this up:

  • A. Suppress the client-side required rule when the field declares a runtime defaultValue and the form is in create mode. The field is not really "missing" — the producer guarantees a value at insert. Cheap and local; the control still reads oddly (a required marker over an empty input the user is allowed to skip).
  • B. Render a resolved preview value, non-authoritative — show "now" for NOW(), the acting user for current_user — and still omit the field from the payload so the server resolves it. Reads best; needs a display-only path that is not confusable with a real value, and needs the actor/clock, which the form does not have today.
  • C. Have the server serve resolved defaults with the object schema (a per-request resolvedDefaults alongside fields), so the form seeds real values from the one authority. Cleanest contract, largest surface — an API change in the framework repo.
  • D. Rule it an authoring error and reject required: true + a runtime defaultValue at publish/lint time.

Not measured how many real declarations hit this: defaultValue: 'NOW()' is common on platform objects, but those columns are typically not required in the authored sense. Severity is for triage to grade — filing it plainly rather than sitting on it.

Reproduce

packages/plugin-form/src/createDefaults.test.tsx (added in #4068) already renders the runtime-token case and asserts the control is empty; make that field required: true and attempt a submit.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions