…shing
Hosts seed MetadataForm date and datetime fields from JSON, so values
arrive as strings. DateTimePicker crashed on any string and DatePicker
on unparseable ones, while z.coerce.date() guessed at numbers, years and
locale strings and read date-only strings as UTC midnight.
A new toDateValue helper accepts only a Date or an RFC 3339 date /
date-time string (date-only as a local calendar day); anything else is
no value. The form layer owns the parsing: FieldControl and
QuickFormField parse before handing a Date to the pickers, and
validation preprocesses with it, so an unrecognised value is absent
rather than "Invalid date". The pickers keep their Date contract and
only guard against non-Date input so they never throw.
DateTimePicker also keeps milliseconds when a new day is picked.
QuickFormField's own parseDateValue is replaced by toDateValue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Hosts seed
MetadataFormdate and datetime fields from JSON (schema.initialData, fielddefaultValue), so values arrive as strings. The HITL quick-form runtime in Flow's debug panel crashes on them today:DateTimePickerthrew on any string, including""(value?.getTime is not a function).DatePickerthrew on unparseable strings (Invalid time value).z.coerce.date(), which guesses at numbers, years and locale strings, and reads"2026-09-23"as UTC midnight: the previous day west of UTC.Changes:
toDateValuehelper (src/lib/date-value.ts) accepts only:Date2026-09-23, read as a local calendar day)2026-09-23T07:00:00.123Z; no offset means local time).Anything else is no value:
"",null, numbers, digit-only strings, locale strings, expressions. A regex runs beforeparseISO, which alone accepts reduced forms like"26".The form layer parses.
FieldControlandQuickFormFieldhand the pickers a parsedDate.Validation:
z.preprocess(toDateValue, …)wraps the optional/required schema, so an unrecognised value counts as absent rather than "Invalid date". Required date fields now show the required message (including a custommessages.required) instead of zod's generic one.Pickers keep
value?: Date. A one-line check treats anything other than a validDateas no value, so they never throw.DateTimePickerkeeps milliseconds when a new day is picked.QuickFormField drops its own
parseDateValuein favour oftoDateValue.Behaviour changes
"2026-09-23"DateTimePickerDateTimePicker; fine inDatePicker""DateTimePicker; an optional field blocked submit with "Invalid input"nullon a required field"2026", a numbernew Date()made of itundefined, required shows the required messageDecisions worth a look
new Date(2026)vsnew Date("2026"),parseISO("26")), so only JSON Schema'sdate/date-timeformats are accepted. A host sending another format now gets an empty field, not a guess.watchstill see it. Only the control and the submitted data change.Datecontract.Demo
Tested as dev package in hitl-schema to confirm that the runtime form does not crash with a datetime field added.
hitl-before.mov
hitl-after.mov
Test plan
toDateValue: ISO dates and date-times (UTC, offsets, local, milliseconds);undefinedfor each rejected kind of valueFieldControl/MetadataFormseeded with"", a locale string and an expression render placeholders without throwing; ISO values show the right dateundefined; required fields (plain and conditionally required) show the required message; milliseconds survive parsing🤖 Generated with Claude Code