mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-27 22:45:03 +00:00
Dropping `<input type="date">` for the dd/mm/yyyy mask also dropped its
calendar, leaving typing as the only way to enter a date. ADR-0019 accepted
that cost but named the remedy: add a picker *behind* `DateInput`, writing the
same ISO value, rather than revert to the native control. That was felt
immediately in use, so this does it.
`DateInput` now renders a calendar button inside the field's right edge and a
Calendar in a Radix popover. Typing and picking are equal routes to the same
value — picking fills the text, typing moves the grid — so neither is
privileged and the two cannot disagree. The trigger is `type="button"` so it
cannot submit the form, and carries an aria-label, a bare icon having no
accessible name.
The calendar wrapper is written by hand rather than taken from the shadcn
registry: the published template targets react-day-picker v8/v9, whose
`classNames` keys differ from the `UI` enum v10 uses. It defaults to the enGB
locale, which carries `weekStartsOn: 1`, so the grid starts on Monday without
being told to.
The delicate part is the `Date` boundary. A calendar works in `Date`s, and
both obvious conversions are wrong: `new Date("2026-03-01")` is midnight UTC,
which is 29 February west of Greenwich, and `toISOString()` shifts the day back
the other way. So `isoToLocalDate` and `localDateToIso` build and read local
parts, and they are the only two functions in `src/utils/dates.ts` that touch
`Date` at all — everything else stays string-only. The round trip is tested
across a full month rather than at a sample day, because the failure only
appears on some days in some zones; the suite was also run under
TZ=America/Los_Angeles and TZ=Pacific/Auckland.
Dependencies: react-day-picker and date-fns, the latter already present
transitively and now direct at v4. `@tremor/react` bundles react-day-picker v8
nested and npm kept it isolated on date-fns v3, so nothing existing changed
version; no Tremor DatePicker is used anywhere in src, so there is no second
copy in the bundle. The alternative was a hand-rolled month grid with no
dependency, rejected because keyboard navigation and ARIA on a calendar are
easy to get subtly wrong and not worth owning.
ADR-0019's "the native calendar picker is gone" consequence was now false and
has been rewritten.
TESTS: tsc --noEmit and ESLint clean; vitest 582 passing (53 files), 24 of them
for the date helpers. NOT verified in a browser — this project has no jsdom or
testing-library, and `next build` was not run because it would statically
render pages against a DATABASE_URL still pointing at production. The new
Cypress case, which picks 17 March from the grid and expects 17/03/2026 in the
field, is unrun like the rest of that spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2.3 KiB
2.3 KiB
Claude guidance for this project
Project conventions
- Domain language lives in
CONTEXT.md. Read it before naming or discussing BulkUpload, Portfolio, Property, etc. concepts. - Architectural decisions live in
docs/adr/. Read existing ADRs before proposing changes that touch state machines or core flows. Write a new ADR for any decision that's hard to reverse, surprising without context, and the result of a real trade-off.
React
- Avoid
useEffectanduseMemo. Derive values inline, prefer Server Components + Route Handlers, prefer event handlers. If a hook is genuinely the only option, flag it and ask before using it. - Use TanStack Query (
@tanstack/react-query), not rawfetch, for client-side HTTP. Reads useuseQuery; writes useuseMutation. This project is on v4 — note thatrefetchInterval's callback signature is(data, query), not v5's(query).
Dates
dd/mm/yyyyeverywhere a person reads or types a date. This is a UK product.03/09/2026is 3 September; rendering it as 3 March is silently wrong data, not a cosmetic bug.- Never
<input type="date">. Its format follows the browser's locale, so a US-configured browser showsmm/dd/yyyyand no attribute overrides it. UseDateInput(@/app/components/DateInput) instead — it masks todd/mm/yyyy, carries a calendar picker, and itsvalue/onChangestayYYYY-MM-DD. YYYY-MM-DDis the only format that crosses a boundary — form state, request bodies, Drizzledatecolumns. The display format stops at the input's edge. Parsing/formatting helpers are in@/utils/dates; for output, pass"en-GB"totoLocaleDateString/Intl.DateTimeFormat.- See ADR-0019.
Utilities
src/utils/is for generic helpers — no domain knowledge (e.g.dates.ts).src/app/utils.tsis for company-specific ones (EPC bands, SAP points, portfolio ratings).src/lib/utils.tsis vendored Tremor/shadcn class helpers — don't add to it.
Next.js 15 route handlers
paramsis aPromise— type as{ params: Promise<{ ... }> }andawait paramsbefore destructuring.- Error responses use
{ error: string }consistently. Don't drift to{ msg }or other shapes.