fix!: expose honest Temporal values in RootState (remove type lies) - #18
Merged
Conversation
BREAKING CHANGE: the render-prop `RootState` (`state.root`) now exposes
all selection values as `Temporal.PlainDate` instead of the configured
value `format`. The format-typed values still flow via
`value`/`defaultValue`/`onValueChange`; `RootState` is for rendering
(e.g. `selected?.toLocaleString(locale)`). `focused`/`viewing` were
already Temporal. `RootState` is now non-generic.
Fixes two runtime lies the audit flagged:
- MonthView built `selectedDates`/`rangeStart`/`rangeEnd` with `as any`
(PlainDate behind a format type) and `selected` as the format value;
now all are honest PlainDate.
- WeeksView's MonthViewState shims used `{} as RootState`, so every
`state.root` field read `undefined` inside WeeksView. Now both the
WeeksView-root and inner-Grid shims expose a real, populated rootState.
The selection part of rootState is built once in CalendarProvider
(`baseRootState`, on the calendar state context) and spread by each view,
which adds its own `focused`/`viewing` — so MonthView and WeeksView are
identical. `selected` is the first committed date (the PlainDate behind
the old `selected` DateValueObject).
Because the date fields are no longer format-typed, `RootState` and the
~15 leaf component State/Props types that referenced `RootState<F>` lose
their now-unused `<F>` generic (the leaf display components become
non-generic). Genuinely format-generic types (Root/MonthView/WeeksView/
CalendarProvider, DateRange, RawValueForFormat) keep `F`. Removes ~12
`as any` and 2 `{} as RootState` casts; format subpaths regenerated.
Adds regression tests: MonthView rootState exposes PlainDate under
format="Date" (was a JS Date); WeeksView shim rootState is populated
(was all undefined).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The semantic-release publish failed npm provenance validation
("repository.url" is "", expected to match the GitHub repo). Add the
repository (with monorepo directory), homepage, and bugs fields so the
published package's provenance bundle validates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
|
🎉 This PR is included in version 2.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Audit fix E (5 of 5). Breaking.
Summary
The render-prop
RootState(state.root, passed to every component'srenderfunction) lied about its types, and was entirely empty inside WeeksView. This fixes both and makesRootStatehonest.The two runtime lies (audit finding)
selectedDates/rangeStart/rangeEndwithas any—Temporal.PlainDatebehind the consumer'sformattype. Withformat="Date",state.root.rangeStart.getFullYear()type-checks but crashes (it's aPlainDate). Andselectedwas the format value while the others werePlainDate— inconsistent.{} as RootStatein itsMonthViewStateshims, so everystate.root.*readundefinedat runtime inside WeeksView (state.root.viewing.year→ crash). There are two such shims (WeeksView root + the inner Grid); both are now real.Fix (Option 1, chosen by the maintainer)
RootState's date fields (selected,selectedDates,rangeStart,rangeEnd) are nowTemporal.PlainDate— the internal representation — matchingfocused/viewingwhich already were. The format-typed values still flow viavalue/defaultValue/onValueChange;RootStateis for rendering (e.g.selected?.toLocaleString(locale)).CalendarProviderasbaseRootState(on the calendar state context); each view spreads it and adds its ownfocused/viewing, so MonthView and WeeksView expose identical state.selectedis the first committed date (thePlainDatebehind the oldselectedDateValueObject— semantics verified to match).RootStatebecomes non-generic, which removes all ~12as anycasts and 2{} as RootStateshims, and de-genericizes the ~15 leaf component State/Props types whose onlyFuse wasroot(the leaf display components become non-generic). Genuinely format-generic types —Root*,MonthView*,WeeksView*,CalendarProvider*,DateRange,RawValueForFormat— keepF.Breaking change
state.root.selected/selectedDates/rangeStart/rangeEndare nowTemporal.PlainDateinstead of the configuredformat. Display viatoLocaleString; the format-typed values remain available through the value props. The leaf render components (DateString,Grid,NavButton, etc.) are no longer generic over the format.Tests
month-view.test.tsx"rootState values": underformat="Date",state.root.selectedis aTemporal.PlainDate(not a JSDate) — fails pre-fix.weeks-view.test.tsx"rootState shim is populated": theuseMonthViewState()shim path exposes a populated rootState (viewing/focused/selected/…) — fails pre-fix ({}, all undefined).vp run ready: lint+typecheck 0/0, package + website suites green.selectedsemantics, ISOviewing, memo deps, de-genericization soundness, and idempotent subpath regeneration.🤖 Generated with Claude Code