assessment-model/docs
Daniel Roth 1019e3ea3f feat(dates): enter and display dates as dd/mm/yyyy, site-wide
The create-project form's date fields were `<input type="date">`, whose
display format follows the *browser's* locale rather than the document's. On a
US-configured browser they rendered mm/dd/yyyy inside an app that renders
en-GB everywhere else (~40 call sites). That is not cosmetic: 03/09/2026 is a
valid date under both readings, so a user cannot tell which one the box took
and no validation can catch it. The failure mode is silently wrong data, not
an error message. No attribute, `lang` value or CSS overrides the native
control, so the control had to go.

`DateInput` replaces it: a text field that masks to dd/mm/yyyy as you type,
whose value and onChange remain YYYY-MM-DD. The display format therefore stops
at the input's edge — the schema, the request body and the Drizzle `date`
column are untouched, and dates keep sorting lexicographically with no
timezone in play. The cost is the native calendar picker, which ADR-0019
argues is the right way round: a picker is a convenience, an ambiguous entry
format is a correctness problem. If one is wanted later it belongs behind
`DateInput`, not in a revert to the native control.

The parsing lives in `src/utils/dates.ts`, pure and Date-free — `new
Date("2026-03-01")` is midnight UTC, which is the previous day in a
negative-offset zone, so this compares and builds strings instead. Impossible
days are rejected explicitly because `Date` would roll 31/02 forward into
March rather than refuse it.

Two behaviours worth knowing, both covered by tests. A *finished* but
impossible date is handed to the schema as typed rather than reported as
empty: a field visibly holding 31/02/2026 while an optional-date schema reads
it as unfilled would tell the user nothing. Unfinished input does report
empty, so no error fires mid-keystroke. And the mask only re-adds a separator
while characters are being added — doing it during a backspace would make the
slash undeletable.

`DateInput` adjusts state during render rather than in a `useEffect` (the
documented alternative) to resync when the form sets the field from outside.

Per CLAUDE.md's utils split, dates.ts is generic and lives in the new
`src/utils/`; `src/app/utils.ts` stays for company-specific helpers (EPC
bands, SAP points) and `src/lib/utils.ts` is vendored Tremor/shadcn.

CLAUDE.md records the rule and the "never `<input type=\"date\">`"
prohibition so the next agent finds it before writing the wrong thing.

Five other date inputs remain wrong under this ADR — PibiSection,
PibiDatesEditor and PropertyFilters — left alone as unrelated to this branch
and recorded in ADR-0019 as convert-when-next-touched.

TESTS: tsc --noEmit and ESLint clean; vitest 576 passing (53 files), 18 of
them new for the date helpers. Cypress was NOT run — this environment's
DATABASE_URL still points at production. The spec's four date entries were
converted to day-first, an assertion added that 01/03/2026 crosses the wire as
2026-03-01, and a new case covers the mask and the impossible-date refusal;
all three are unverified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 10:50:09 +00:00
..
adr feat(dates): enter and display dates as dd/mm/yyyy, site-wide 2026-07-22 10:50:09 +00:00
backend-asks feat(tags): table query + tag filter, filter-driven bulk assign, preview join, settings UI 2026-07-13 16:23:49 +00:00
design docs(design): home-page redesign context — Stitch refs, PRODUCT.md, impeccable trial 2026-07-07 15:12:07 +00:00
runbooks perf(backfill): faster, resumable recommendation denormalization + ops tooling 2026-07-02 10:00:46 +00:00
wip fix(postcode-search): PR #355 review feedback 2026-07-06 15:13:49 +00:00
wireframes/ara-projects Add Ara Projects UX wireframes under docs/wireframes 2026-07-20 15:15:21 +00:00