Repository navigation
fix(types,components): ui:calendar selection shape follows its mode (objectui#10304) - #10376
Conversation
…objectui#10304)
`mode: 'multiple'` reads a list of days and `mode: 'range'` reads
`{ from, to }`, but both faces declared one day only: the mirror accepted a
lone day in multiple mode, which crashed the node, and no document could
select a range at all.
- types: `value` / `defaultValue` admit one day, a list of days, or a
`{ from, to }` range on both faces; the mirror (`CalendarSchema` and
`UiCalendarSchema`) refuses a shape its mode does not read, through a
refinement on the node.
- components: the `ui:calendar` renderer reads each mode's shape and coerces
every day through `toDisplayDate`; a misfit shape selects nothing instead of
crashing, and is not reshaped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S
…ui#10304) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S
…bjectui#10304) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Contract correctness.
② Tests.
③ Blast radius.
Implemented-by: |
Fixes #10304
Clause-②: yes. A published type and the mirror accept set change. An isolated at-tier review happens before enqueue.
Direction (i) per objectui#7759 ruling 5617465269 rule 2 (
CalendarSchemais objectui-own, so the read site is the truth). Serial after objectui#10302, which merged at8c10f4f7; this branch is cut from it.Measurement before coding (the claim's stop condition)
The claim said: stop and report if
mode: 'multiple' | 'range'has no in-tree author AND the designer does not offer them. Both halves are false on8c10f4f7:examples/schema-catalog/src/schemas/components-form-calendar/multiple-dates.json(mode: "multiple") anddate-range.json(mode: "range"), both referenced bycontent/docs/components/form/calendar.mdx;ui:calendarregistrationinputsoffersmodeas an enum containingmultipleandrange.So retirement (iii) is not the candidate, and this PR implements (i).
What changed
packages/types/src/form.tsCalendarSchema.value/.defaultValueDate | string{ from, to }(tooptional); a day isDate | stringpackages/types/src/zod/form.zod.tsCalendarSchema/UiCalendarSchemaz.union([z.string(), z.date()])per keystart/endis refused), plus a node refinement that refuses a shape itsmodedoes not read:single(default) takes one day,multiplea list,rangea pair. Issue codecustom, path the offending keyui:calendarrenderer (packages/components/src/renderers/form/calendar.tsx)toDisplayDate. A misfit shape selects nothing instead of throwing, and is NOT reshaped (a lone day is not wrapped into a list: AGENTS.md #0.1)content/docs/components/form/calendar.mdxvalueBoth bugs from the card:
multiple+ one day: refused at the mirror (valuepath); if it reaches the renderer anyway, it no longer throwsselected?.some is not a functionand selects nothing.range+{ from, to }: accepted by the mirror and selects every day of the range.The mirror refinement is on the node, not on the key, so
CalendarSchemaandUiCalendarSchemaare both built from one unexported key set (CalendarObjectSchema) and each applies the refinement. The reason: zod 4 refuses.extend()that overwrites a key on an object that already carries a refinement.The TS face admits every shape on every mode
The declaration is still a single
interface, so itsvalue/defaultValueare the key-level union of all three shapes, and the mode pairing is documented on the key and enforced only by the mirror. A real discriminated union onmodewould turnCalendarSchemafrom aninterfaceinto a type alias. Several gates and tests look the declaration up as anInterfaceDeclarationby name, for examplecheck-component-surface-parity,check-readme-exports,check-designer-field-key-parityand the parity test's ledger lookups. I did not make that change here. It is raised in the report as an open question.Parity ledger
zod-mirror-parity.test.tspasses unchanged. Both faces moved to the same key-level set, so noKnownDriftorWiderThanDeclaredrow appeared or left. No figure re-derived.Tests (head
dd123d7f3)packages/types/src/__tests__/calendar-selection-mode-10304.test.ts. It checks every mode against every shape, on both mirrors and both keys, and assertssuccess, the issue path andcode: custom.packages/components/src/renderers/form/__tests__/calendar.selectionModes-10304.test.tsx. It checks range selecting its days, multiple selecting each listed day, and multiple plus one day not throwing.calendar.dateValueZone-10293.test.tsxhas three new driven cases west of UTC, for list and range days. The existing driverscripts/__tests__/date-only-zone-pins-10183.test.tsalready runs that file and passes (6/6).calendar-date-value-boundary-10293.test.ts. The type pin now expects the three-shape union, and the list refusal now holds in single mode.pnpm exec vitest run packages/types/: 225 files, 5056 tests passed.packages/components/: 292 passed, 1 skipped file; 2821 passed, 17 skipped tests.examples/schema-catalog/: 33 files, 2182 tests passed.type-checkfor@object-ui/typesand@object-ui/componentspassed after building the closure of@object-ui/componentsand its dependencies.--listFilesOnlyconfirms the three test files are in the test programs.eslint .in both packages: exit 0.Ablations. Each was committed first, then mutated, then restored with
git checkout HEAD --. Each restore was checked by an emptygit diff HEADand a match against the HEAD blob hash.8c10f4f7bytes: the multiple-plus-one-day case goes red (1 failed, 5 passed). The range cases stay green in the suite zone (UTC), because the picker's own parse already lands on the named day there. That is why ablation 2 exists.start/endand a missingfromwithout the refinement, as expected.Gates
Exit 0:
check:spec-symbols,check:designer-field-key-parity,check:component-surface-parity,check:new-line-citations,check:control-bytes,check:doc-types,check:doc-fences,check:doc-example-ids,check:prompt-keys,check:registry-bare-names,check:changeset-claims,check:pending-changeset-literals,check:test-path-roots,check:handler-key-reads,check-changeset-presence,check-changeset-no-major, andspec-main-shape-gate --self-test(26/26).NOT MEASURED:
spec-main-shape-gate inject: it needs a spec tarball packed from objectstackmainfollowed by a repo-wide typecheck. That is the gate's CI job. This diff does not touch any@objectstack/specimport.check:doc-examples,check:doc-snippets,check:skill-examples,check:readme-exports: prerequisite not met, because they need a whole-tree build. None of them reads the JSON or plaintext fences edited here. The three JSON examples added to the doc were parsed againstUiCalendarSchemaand all three pass.Acceptance notes
ui:calendarregistrationinputsoffersmode: 'default', and the mirror refuses it (invalid_valueatmode). That is a designer-offered value that fails validation. It is not fixed here and was not filed; it is listed in the report for the seat.Changeset:
.changeset/10304-calendar-selection-modes.md(minor for@object-ui/typesand@object-ui/components).Session:
https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3SGenerated by Claude Code