Thinkmill / Thinkmill/keystatic
fields.date() silently truncates a frontmatter value that carries a time
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.4k
- Forks
- 159
- Avg merge
- 21h 41m
- Merged PRs (30d)
- 2
Description
Summary
fields.date() silently discards the time component when the underlying YAML value carries one. No error is raised at parse time, and validate() then accepts the truncated value — so opening and saving such an entry in the Admin UI rewrites the file, permanently losing information that was in it.
The truncation happens in date's parse() (packages/keystatic/src/form/fields/date/index.tsx), in the value instanceof Date branch, which reformats via UTC getters to YYYY-MM-DD. That branch is necessary — YAML auto-parses a bare 2026-09-01 into a Date — but it treats "a Date with a time" identically to "a Date at midnight", and the time is dropped rather than rejected.
Reproduction
Runnable against @keystatic/core@0.6.8, no Keystatic app needed:
const { fields } = require('@keystatic/core');
const yaml = require('js-yaml');
const f = fields.date({ label: 'Date', validation: { isRequired: true } });
for (const line of [
'date: 2026-09-01',
'date: 2026-09-01T13:00:00.000Z',
'date: "2026-09-01T13:00:00.000Z"',
]) {
const v = yaml.load(line).date;
const parsed = f.parse(v);
let validated;
try { validated = JSON.stringify(f.validate(parsed)); }
catch (e) { validated = 'THREW ' + e.message; }
console.log(line.padEnd(36), '-> parse', JSON.stringify(parsed), '| validate', validated);
}
Output:
date: 2026-09-01 -> parse "2026-09-01" | validate "2026-09-01"
date: 2026-09-01T13:00:00.000Z -> parse "2026-09-01" | validate "2026-09-01"
date: "2026-09-01T13:00:00.000Z" -> parse "2026-09-01T13:00:00.000Z" | validate THREW Date is not a valid date
The second line is the problem: 13:00:00.000Z is gone and nothing reports it.
The third line is interesting as a contrast — quoted, YAML yields a string, the instanceof Date branch is skipped, and the value survives parse() only to be refused by validate(). So the same input is silently mangled or loudly rejected depending purely on YAML quoting.
Why it matters
A date-only value is UTC midnight. For anyone east or west of UTC that is a different calendar day locally — for us (America/Toronto) 2026-09-01 is 20:00 on August 31. We schedule posts by writing a full instant into frontmatter so a static-site visibility filter can hide them until a precise local time. That works fine with Astro's z.coerce.date(); the risk is that an editor opening the entry in Keystatic and saving it silently moves the publication ~13 hours earlier, with nothing anywhere indicating a change.
More generally: silent, lossy normalization of existing file content seems worth avoiding regardless of the use case, since the file is the source of truth and the CMS is one of several writers to it.
fields.datetime() is not an alternative here
Its validation is ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$, so it can express neither seconds nor an offset/Z — it cannot round-trip an ISO instant either.
Possible directions
Listed as options rather than a request; happy to follow whichever you prefer.
- Make the loss non-silent. Have
fields.date()refuse aDatecarrying a non-zero time instead of truncating it. Smallest change, and arguably just a bug fix — though it would surface as an error for anyone currently relying on the truncation. - A field that round-trips an instant (
fields.timestamp(), or wideningdatetimeto accept seconds and an offset). Bigger, and a public API decision that is yours to make. - Documentation only — state that
fields.date()normalizes and that frontmatter times are not preserved.
I'm glad to send a PR for whichever direction you'd want, but I didn't want to open one unsolicited given (1) and (2) differ a lot in scope and (2) adds public API.
Environment
@keystatic/core0.6.8- Astro 5,
@keystatic/astro, GitHub storage - Node 22
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/keystatic/src/form/fields/date/index.tsx at date's parse() value instanceof Date branch, then run the supplied js-yaml reproduction to compare bare, timestamped, and quoted values. Review the possible behavior options with maintainers; done means the chosen behavior is explicit and timestamp-bearing frontmatter is no longer silently lost, with regression coverage for the reported cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript, yaml
- Domain
- content
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100