Repository navigation
bug(components): ui:calendar crashes on a scalar value in mode: 'multiple' and silently drops every value in mode: 'range' — the mirror accepts documents the renderer cannot honour #10304
Description
Activity
- addeddomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
on Sep 24, 2026 objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsClaim: PM loop round 11 — objectui#10304:
ui:calendarmode: 'multiple' | 'range'values: make the authoring faces express what the read site reads, so a mirror-accepted document neither crashes nor silently drops its value
Session:session_01877XiBYSaRCk2CU7cMSg3S
Branch:claude/issue-10304-calendar-modes
Worktree:objectui-issue-10304
Domain:domain:spec
Seat:domain:spec#1
File surface:packages/types/src/form.tsandpackages/types/src/zod/form.zod.ts(CalendarSchema.value/defaultValueonly)- the
ui:calendarrenderer inpackages/components - the
zod-mirror-parity.test.tsledger figures, only if the pair's shape entry moves - their tests, and one
.changeset/10304-…md
Container & model:S,mode:subagent,model: opus(TIER_DEFAULT)
Clause-②: yes. A published type and the mirror accept set change. An isolated at-tier review happens before enqueue.
Thread-read: none
Serial constraints cleared: re-measured at 2026-09-24T19:52Z. PR objectui#10302, which this card named as serial-before, merged at8c10f4f7. If the ledger moves, this PR joins the ledger chain after feat(types)!: DetailViewFieldSchema.options reads the spec's authoring SelectOptionSchema (#7759 group F) #10307, fix(types): settle FilterField.operators, Container.maxWidth and HeaderBar.variant between mirror and declaration (objectui#10286) #10300 and fix(types): dashboard dateRange takes the spec authoring member on both faces (objectui#10334) #10338, and re-derives the figures on merge.
Direction: #7759 ruling 5617465269 rule 2.
CalendarSchemais objectui-own, so the read site is the truth. The read site (DayPicker) reads a list formultipleand{from, to}forrange, which is direction (i): mode-dependentvalue/defaultValueon both faces, with element-wise coercion throughtoDisplayDate. The dev measures in-tree authored usage first. Ifmultiple/rangehave no author AND the designer does not offer them, the dev stops and reports, because (iii), retirement, would then be the candidate and needs a PM decision. Dispatch: objectstack-ai/objectstack#19980 and 「任务很多,并发保持3」.objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 10304,
"status": "done",
"branch": "claude/issue-10304-calendar-modes",
"pr": "#10376",
"session": "session_01877XiBYSaRCk2CU7cMSg3S",
"premise_still_valid": true,
"summary": "Measured before coding: mode multiple/range are authored in-tree (examples/schema-catalog components-form-calendar multiple-dates.json and date-range.json, both shown by content/docs/components/form/calendar.mdx) and the ui:calendar registration inputs offer both modes, so the stop condition did not trigger and direction (i) was implemented. value/defaultValue now admit one day, a list of days, or a strict {from, to} range (to optional) on both faces; the zod mirror (CalendarSchema and UiCalendarSchema, built from one unexported key set because zod 4 refuses .extend() overwriting a key on a refined object) refuses a shape its mode does not read via a node superRefine (code custom, path = offending key). The ui:calendar renderer reads each mode's shape and coerces every day through toDisplayDate; a misfit shape selects nothing instead of throwing and is not reshaped. Docs page and a minor changeset for types+components added. Deviation from the dispatch wording: the TS declaration stays an interface with the key-level union (mode pairing documented, enforced by the mirror only) rather than a mode-discriminated union; see open_questions. zod-mirror-parity ledger unchanged (both faces moved to the same key-level set), no figures re-derived. Assignee on the card was os-bill at start (not written by me). Worktree removed after push; the shared checkout is on claude/vibrant-newton-02en1o with a clean tree.",
"tests": "All at head dd123d7 (code final since b1c42ef; the later commit only adds docs and the changeset). Run under os-verify-lock. vitest packages/types/: 225 files, 5056 tests passed. vitest packages/components/: 292 files passed, 1 skipped; 2821 tests passed, 17 skipped. vitest examples/schema-catalog/: 33 files, 2182 tests passed. Zone driver scripts/tests/date-only-zone-pins-10183.test.ts: 6/6, including 3 new driven cases west of UTC in calendar.dateValueZone-10293.test.tsx. type-check for @object-ui/types and @object-ui/components: exit 0, after building the closure of @object-ui/components and its dependencies; tsc --listFilesOnly confirms the 3 calendar test files are in the test programs. eslint . in both packages: exit 0. Ablations (each committed first, restored with git checkout HEAD --; each restore proven by an empty git diff HEAD and a HEAD blob hash match; mutation proven on disk by grep -c of the marker, 1 before and 0 after): (1) renderer set back to its 8c10f4f bytes: calendar.selectionModes-10304 1 failed / 5 passed, the multiple-plus-one-day crash case. The range cases stay green in UTC because DayPicker's own parse lands on the named day there, hence (2). (2) per-day coercion replaced by new Date(value), run on the forked pool with OBJECTUI_DATE_ZONE_CHILD=1: 3 failed / 10 passed (single-day west, multiple west, range west). (3) mirror refinement forced to always pass: 7 failed / 10 passed across calendar-selection-mode-10304 and calendar-date-value-boundary-10293. Gates exit 0: check:spec-symbols, designer-field-key-parity, component-surface-parity, new-line-citations, control-bytes, doc-types, doc-fences, doc-example-ids, prompt-keys, registry-bare-names, changeset-claims, pending-changeset-literals, test-path-roots, handler-key-reads, check-changeset-presence, check-changeset-no-major, spec-main-shape-gate --self-test 26/26. NOT MEASURED: spec-main-shape-gate inject. The documented usage requires --tarball, --sha and --upstream-checkout, i.e. a spec packed from objectstack main plus a repo-wide typecheck; the bare 'inject' as dispatched exits 2 with a usage message. That is the CI job, and the diff touches no @objectstack/spec import. NOT MEASURED: check:doc-examples, doc-snippets, skill-examples, readme-exports; PREREQUISITE NOT MET, they need a whole-tree build, and they read ts/tsx fences and READMEs only, none of which were edited. Substitute: the 3 JSON examples added to calendar.mdx were parsed against UiCalendarSchema and all 3 are accepted. Local narrowing declared: repo-wide pnpm lint is left to CI.",
"mcp_calls": "0",
"api_writes": "2 fleet-write relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create, which became POST /repos/objectstack-ai/objectui/pulls (draft #10376); (2) comment, which became POST /repos//issues/10304/comments (this report). No label write: objectui path labels come from labeler.yml, and the dispatch named no label. Plus git pushes (not REST).",
"open_questions": [
{
"question": "The dispatch asked for value/defaultValue to be mode-dependent on BOTH faces. The TS declaration is still an interface whose keys are the union of all three shapes, so TypeScript accepts {mode:'multiple', value:'2026-09-15'} and only the mirror refuses it. Should the TS face become a real discriminated union on mode?",
"options": [
"A: keep it as is. The interface carries the key-level union with the pairing documented, and the mirror enforces it. Cost: TS authors get no compile-time pairing check. Every gate that looks declarations up as InterfaceDeclaration keeps working.",
"B: turn CalendarSchema into a type alias: a base interface intersected with a union of Single, Multiple and Range members. Cost: it changes a published declaration kind, and name-keyed InterfaceDeclaration lookups (check-component-surface-parity, check-readme-exports, check-designer-field-key-parity, the zod-mirror-parity ledger lookups) must learn type aliases. UiCalendarSchema's Omit already collapses to the index signature, so it gains nothing."
],
"recommendation": "A. The mirror is the authoring gate that JSON and AI authors hit, and B widens the diff into several gates for a TS-only benefit. If B is wanted, it should be its own card."
}
],
"out_of_scope_findings": [
"class: c · evidence: the ui:calendar registration inputs (the designer, packages/components/src/renderers/form/calendar.tsx) offer mode enum ['default','single','multiple','range'], and UiCalendarSchema.safeParse({type:'ui:calendar', mode:'default'}) fails with invalid_value at path mode. The designer offers a value the mirror refuses. Runtime effect of 'default' on react-day-picker 10 not measured. Seam: spec:form.zod.ts#CalendarSchema.mode -> renderer:packages/components ui:calendar registration inputs · dedupe words: ui:calendar mode default; calendar inputs enum default; calendar mode invalid_value designer · noted in PR Acceptance notes, not filed"
]
}- added a commit that references this issue
on Sep 28, 2026
Surfaced by the dev executing objectui#10293 (PR objectui#10302, #7759 group D date values). Filed by the
domain:spec@ objectui execution seat, sessionsession_01877XiBYSaRCk2CU7cMSg3S. ⛔ Not graded: the priority and domain here are the seat's proposal (domain:spec, contract ↔ renderer), and triage may regrade. Both defects are present onmain5ea623eand remain after PR objectui#10302.1 · class a: a document the mirror accepts crashes the node
CalendarSchema.safeParsesucceeds on both. DayPicker'smultiplemode expects an array, and neither face can author one: the mirror isstring | Date, and after objectui#10302 the declaration isDate | string.2 · class c: a value the mirror accepts is silently dropped
ui:calendar {mode: 'range'}with any value the mirror accepts (an ISO string or aDate; aDate[]was also tried) selects no day. DayPicker'srangemode reads{from, to}, which neither face can express, so an AI or JSON author's value vanishes with no error.Direction (to be decided; ruling 5617465269 rule 2:
CalendarSchemais objectui-own, so the read site is the truth)value/defaultValuemode-dependent on both faces: a list formultiple,{from, to}forrange, with element-wise coercion throughtoDisplayDate. This adds authoring surface.valuewhose shape does not fit itsmode, and keep single-date authoring only. This narrows the accept set.mode: 'multiple' | 'range'if no in-tree author uses them.The seat's reading: measure authored usage of
multiple/rangefirst (inexamples/,content/,apps/and the designerinputs), then choose. Serial after PR objectui#10302, which touches the sameCalendarSchemaand renderer lines.Seam:
spec:form.zod.ts#CalendarSchema.mode+value→renderer:packages/components ui:calendar.