Skip to content

fix(types,components): ui:calendar selection shape follows its mode (objectui#10304) - #10376

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10304-calendar-modes
Sep 24, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10304-calendar-modes

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 (CalendarSchema is objectui-own, so the read site is the truth). Serial after objectui#10302, which merged at 8c10f4f7; 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 on 8c10f4f7:

  • authors: examples/schema-catalog/src/schemas/components-form-calendar/multiple-dates.json (mode: "multiple") and date-range.json (mode: "range"), both referenced by content/docs/components/form/calendar.mdx;
  • designer: the ui:calendar registration inputs offers mode as an enum containing multiple and range.

So retirement (iii) is not the candidate, and this PR implements (i).

What changed

face before after
packages/types/src/form.ts CalendarSchema.value / .defaultValue Date | string one day, a list of days, or { from, to } (to optional); a day is Date | string
packages/types/src/zod/form.zod.ts CalendarSchema / UiCalendarSchema z.union([z.string(), z.date()]) per key the same three shapes per key (the range is a strict object, so start / end is refused), plus a node refinement that refuses a shape its mode does not read: single (default) takes one day, multiple a list, range a pair. Issue code custom, path the offending key
ui:calendar renderer (packages/components/src/renderers/form/calendar.tsx) one-day read for every mode; strings coerced only when the whole value is a string per-mode read; every day coerced through 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)
docs content/docs/components/form/calendar.mdx one-day value per-mode table, a JSON example per mode, and the schema block

Both bugs from the card:

  • multiple + one day: refused at the mirror (value path); if it reaches the renderer anyway, it no longer throws selected?.some is not a function and 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 CalendarSchema and UiCalendarSchema are 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 its value / defaultValue are 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 on mode would turn CalendarSchema from an interface into a type alias. Several gates and tests look the declaration up as an InterfaceDeclaration by name, for example check-component-surface-parity, check-readme-exports, check-designer-field-key-parity and 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.ts passes unchanged. Both faces moved to the same key-level set, so no KnownDrift or WiderThanDeclared row appeared or left. No figure re-derived.

Tests (head dd123d7f3)

  • New: packages/types/src/__tests__/calendar-selection-mode-10304.test.ts. It checks every mode against every shape, on both mirrors and both keys, and asserts success, the issue path and code: custom.
  • New: 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.
  • Extended: calendar.dateValueZone-10293.test.tsx has three new driven cases west of UTC, for list and range days. The existing driver scripts/__tests__/date-only-zone-pins-10183.test.ts already runs that file and passes (6/6).
  • Updated: 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-check for @object-ui/types and @object-ui/components passed after building the closure of @object-ui/components and its dependencies. --listFilesOnly confirms 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 empty git diff HEAD and a match against the HEAD blob hash.

  1. Renderer set back to its 8c10f4f7 bytes: 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.
  2. Per-day coercion replaced by the engine parse, run in the forked child with the zone moved west of UTC: 3 failed, 10 passed. The failures are the existing single-day case plus the new list and range cases.
  3. Mirror refinement disabled: 7 failed, 10 passed across the two type test files. The strict range object still refuses start / end and a missing from without 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, and spec-main-shape-gate --self-test (26/26).

NOT MEASURED:

  • spec-main-shape-gate inject: it needs a spec tarball packed from objectstack main followed by a repo-wide typecheck. That is the gate's CI job. This diff does not touch any @objectstack/spec import.
  • 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 against UiCalendarSchema and all three pass.

Acceptance notes

  • The ui:calendar registration inputs offers mode: 'default', and the mirror refuses it (invalid_value at mode). 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/types and @object-ui/components).

Session: https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S


Generated by Claude Code

…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
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 16 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/5905-componentinput-inputtype-tombstone.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    The write was measured as a no-op before it was deleted, and re-measured on this branch's base rather than inherited from the card. A structural census over every inputs: array in the repository (211 regions, all tracked TS/TSX/JS sources) scores inputType at exactly ONE authoring site — the plugin-markdown registration — against name 953, type 969, label 966, description 194, enum 119, required 86 and binding 4 in the same pass over the same regions, so the instrument was not blind. The other 192 in-repo inputType hits are a DIFFERENT face: FormField.inputType (zod/form.zod.ts), the text-input renderer's prop, and SchemaBuilder.inputType, none of which sit on a ComponentInput. The publication path is unchanged and was re-confirmed: packages/sdui-parser/src/index.ts forwards exactly seven keys per input — name, type, of, required, enum, binding, description — so an authored inputType could not reach the published sdui.manifest.json even in principle.

.changeset/6349-name-authority-batch-3.md

  • names form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    @object-ui/components — ComboboxOption now IS @object-ui/types' declaration. The component declared its own { value, label }, a strict subset of the ComboboxOption that @object-ui/types declares for ComboboxSchema.options and mirrors in form.zod.ts ({ value, label, disabled? }). The component now re-exports the types declaration (through the @object-ui/types/form subpath — the root barrel does not publish the name), so the name ComboboxOption exported from @object-ui/components gains the optional disabled?: boolean member. Every value that type-checked before still does — nothing narrows and no key changes type; the one thing that moves is keyof ComboboxOption, so a consumer that EXHAUSTS the type (a Record over its keys) will need the new key. Note that the Combobox component itself does not read option.disabled — that member was already declared on the @object-ui/types face and is now visible on this one too; it is recorded as a separate finding, not changed here.

.changeset/6396-previous-values-dom-leak.md

  • names packages/types/src/form.ts → packages/types/src/form.ts — edited by this change

    Scope is the runtime leak only. The declared key stays exactly as declared (packages/types/src/form.ts, packages/types/src/zod/form.zod.ts are untouched): it has a live consumer, so there is nothing here for the enforce-or-remove channel.

  • names packages/types/src/zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    Scope is the runtime leak only. The declared key stays exactly as declared (packages/types/src/form.ts, packages/types/src/zod/form.zod.ts are untouched): it has a live consumer, so there is nothing here for the enforce-or-remove channel.

.changeset/6938-checkbox-wrapper-class.md

  • names packages/types/src/form.ts → packages/types/src/form.ts — edited by this change

    packages/components/src/renderers/form/checkbox.tsx:36 reads cn("flex items-center space-x-2", schema.wrapperClass) — classes on the wrapper div around the box and its label — and neither the TypeScript interface in packages/types/src/form.ts nor the zod mirror in zod/form.zod.ts declared the key. It compiled through BaseSchema's index signature and parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on FileUploadSchema and FilterBuilderSchema (objectui#6150); the checkbox was left out only because its doc page's schema block is a six-line summary.

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    packages/components/src/renderers/form/checkbox.tsx:36 reads cn("flex items-center space-x-2", schema.wrapperClass) — classes on the wrapper div around the box and its label — and neither the TypeScript interface in packages/types/src/form.ts nor the zod mirror in zod/form.zod.ts declared the key. It compiled through BaseSchema's index signature and parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on FileUploadSchema and FilterBuilderSchema (objectui#6150); the checkbox was left out only because its doc page's schema block is a six-line summary.

.changeset/7113-chart-data-model.md

  • names form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

.changeset/7530-predicate-envelope-declared.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    • ExpressionWire (type, main entry) — the TypeScript wire union, in packages/types/src/expression.ts. - ExpressionWireSchema (@object-ui/types/zod) — its runtime twin, hoisted out of zod/form.zod.ts (where it was module-private) into zod/expression.zod.ts and imported by both base.zod.ts and form.zod.ts. One envelope type, reused by reference; no second spelling.
  • names form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    • ExpressionWire (type, main entry) — the TypeScript wire union, in packages/types/src/expression.ts. - ExpressionWireSchema (@object-ui/types/zod) — its runtime twin, hoisted out of zod/form.zod.ts (where it was module-private) into zod/expression.zod.ts and imported by both base.zod.ts and form.zod.ts. One envelope type, reused by reference; no second spelling.

.changeset/7697-combobox-option-root-barrel.md

  • names src/form.ts → packages/types/src/form.ts — edited by this change

    Additive only. ComboboxOption is added to the root barrel's existing named re-export list from ./form.js, next to the sibling option types that were already there (SelectOption, RadioOption). Nothing is removed, retyped or narrowed: the declaration stays in src/form.ts, its three members (value, label, disabled?) are unchanged, and the @object-ui/types/form subpath spelling keeps working exactly as before. Both spellings now resolve to the same declaration.

.changeset/7722-wrapper-class-five-more.md

  • names form.ts → packages/types/src/form.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (objectui#6938), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (objectui#6938), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

.changeset/7735-zod-mirrors-stop-authoring-defaults.md

  • names form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    What changed. All 41 .default() call sites under packages/types/src/zod/ are removed — layout.zod.ts 22, crud.zod.ts 11, form.zod.ts 5, views.zod.ts 2, app.zod.ts 1. @object-ui/components reconciles the third face objectui#8229 found: flex's registration defaultProps.align seeded 'center', the value its own renderer never applies, so a designer-made node laid out differently from a hand-authored one; it now seeds 'start'.

.changeset/8072-input-wrapper-class-mirrored.md

  • names form.ts → packages/types/src/form.ts — edited by this change

    packages/components/src/renderers/form/input.tsx reads cn("grid w-full items-center gap-1.5", schema.wrapperClass) onto the wrapper div around the input and its label. The TypeScript face has declared the key all along (form.ts, docblock "Input wrapper CSS class"); the zod mirror never did, so the value rode through .passthrough() and { type: 'input', wrapperClass: 42 } validated GREEN — while the identical document on any of the other eight schema.wrapperClass readers (checkbox, file-upload, filter-builder — objectui#6150 / finding(types): 4 more genuinely-read undeclared keys the #6150 census could not see, plus one declared-but-dead key — all on the same 8 renderers #6938; switch, textarea, date-picker, select, list — objectui#7722) was refused at the key.

.changeset/8478-describe-line-addresses.md

.changeset/8478-zod-pins-form-layout.md

.changeset/8499-node-slot-registered-arms.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    • SemanticElementSchema (zod/layout.zod.ts) — the seven HTML sectioning tags renderers/layout/semantic.tsx registers: aside main header nav footer section article. - HtmlElementSchema (zod/layout.zod.ts) — the 37 safe flow/inline tags renderers/basic/html-elements.tsx registers (h1…h6, p, a, ul, img, …), plus the per-tag keys that module forwards to the DOM (href, target, rel, title, src, alt, width, height, dateTime, cite). - InputShorthandSchema (zod/form.zod.ts) — email / password, the two aliases renderers/form/input.tsx registers onto the input renderer with inputType pinned. inputType is deliberately NOT declared on this arm: the wrapper spreads its own value last, so an authored one is overwritten. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers under exactly that key (skipFallback, because bare calendar belongs to the plugin-calendar view).
  • names renderers/form/calendar.tsx → packages/components/src/renderers/form/calendar.tsx — edited by this change

    • SemanticElementSchema (zod/layout.zod.ts) — the seven HTML sectioning tags renderers/layout/semantic.tsx registers: aside main header nav footer section article. - HtmlElementSchema (zod/layout.zod.ts) — the 37 safe flow/inline tags renderers/basic/html-elements.tsx registers (h1…h6, p, a, ul, img, …), plus the per-tag keys that module forwards to the DOM (href, target, rel, title, src, alt, width, height, dateTime, cite). - InputShorthandSchema (zod/form.zod.ts) — email / password, the two aliases renderers/form/input.tsx registers onto the input renderer with inputType pinned. inputType is deliberately NOT declared on this arm: the wrapper spreads its own value last, so an authored one is overwritten. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers under exactly that key (skipFallback, because bare calendar belongs to the plugin-calendar view).

.changeset/9067-zod-barrel-named-arms.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    • InputShorthandSchema (zod/form.zod.ts) — the email / password shorthand arm. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers, a different component from the calendar plugin view that owns the bare literal.
  • names renderers/form/calendar.tsx → packages/components/src/renderers/form/calendar.tsx — edited by this change

    • InputShorthandSchema (zod/form.zod.ts) — the email / password shorthand arm. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers, a different component from the calendar plugin view that owns the bare literal.

.changeset/9406-types-root-barrel-two-names.md

  • names form.ts → packages/types/src/form.ts — edited by this change

    form-barrel-mirror-9406.test.ts keeps it closed, and it is DERIVED rather than a pair of presence assertions: it reads form.ts's export list and the root barrel's ./form.js re-export list on every run and names whatever is in the first and not the second. A pin asserting "these two names are present" would pass on the day the next declaration lands in form.ts and is forgotten, which is this class reopening yet again. Names deliberately left off the list get a ledger row carrying the reason instead, and a row goes red once its name reaches the barrel or stops being declared.

.changeset/9526-types-root-barrel-command-element-types.md

  • names form.ts → packages/types/src/form.ts — edited by this change

    The change is purely additive: the two names join the root barrel's existing named re-export list from ./form.js, beside CommandSchema. Nothing is removed, renamed or narrowed, the declarations stay in form.ts, and the /form subpath keeps working. It is the third instance of one class, repaired by the same route each time: objectui#7697 for ComboboxOption, and objectui#9406 (director decision batch 133, item 2, letter (a)) for InputShorthandSchema and UiCalendarSchema.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 462bafb9e (merge-base with origin/main): 8 file(s) changed outside .changeset/, read against 1345 pending declaration(s) that publish a body (1918 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3042.1 KB 3104.5 KB
Main entry chunk (gzip) 148.4 KB 350 KB
Entry file index-OGY2dmwM.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 542.11KB 129.60KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 222.47KB 61.83KB
fields (index.js) 253.70KB 64.09KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 34.99KB 11.45KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.15KB 11.05KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.08KB 3.95KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 72.59KB 20.30KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.19KB 35.25KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 260.94KB 67.93KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.31KB 41.45KB
plugin-grid (index.js) 215.22KB 58.85KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.17KB 28.16KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.72KB 8.96KB
plugin-tree (index.js) 10.74KB 3.76KB
plugin-view (index.js) 85.79KB 21.34KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dd123d7f3b6cd770934654126e1649097687b214

① Contract correctness.

  • Accept set. Probed with CalendarSchema.safeParse, the accept set is exactly what the PM directed (27 keys across 18 pairs have a zod mirror that ACCEPTS a spelling its TypeScript declaration refuses — each needs a narrow-the-mirror / widen-the-declaration ruling #7759 ruling 5617465269 rule 2; the read site is react-day-picker):
    • single = string | Date;
    • multiple = a list of those;
    • range = z.strictObject({ from, to? }).
  • Misfits refused.
    • Every misfit (10-row table) is refused with code: 'custom' at path: [key], on both CalendarSchema and UiCalendarSchema and on both value and defaultValue.
    • This also holds through the FormComponentSchema discriminated union.
  • No in-tree author broken. No in-tree author is newly refused. The six schema-catalog calendar examples carry no value/defaultValue, and the three new calendar.mdx examples each fit their mode.
  • Renderer never throws. The renderer was probed with 19 inputs (garbage days, Invalid Date, reversed ranges, cross-mode shapes, mode: 'default') and never throws. Misfits select nothing.
  • Time zones. Day coercion runs through toDisplayDate, and the forked west-of-UTC driver (date-only-zone-pins-10183) passes 6/6 with the new list/range cases.
  • DayPicker shapes. The runtime selection shapes match react-day-picker 10's PropsSingle/PropsMulti/PropsRange.
  • Ledger. zod-mirror-parity.test.ts passes unchanged: both faces carry the same key-level union.
  • PM decision on the dev's open question: option A. The TS face stays an interface with the key-level union. The mode pairing is documented there and enforced by the mirror, which is the gate JSON and AI authors hit. A discriminated TS union would reshape a published declaration kind across several name-keyed gates for a TS-only benefit; if wanted, it gets its own card.

② Tests.

  • Non-vacuous, re-ablated by the reviewer:
    • refinement forced to pass: 7 failed / 10 passed;
    • renderer restored to 8c10f4f7: 1 failed / 5 passed with TypeError: selected?.some is not a function.
  • Sibling tests. Sibling form-mirror suites pass: 10 files / 1388 tests.
  • Changeset. The minor changeset for @object-ui/types and @object-ui/components spells out the break and the migration, per check-changeset-no-major.

③ Blast radius.

  • Readers. The only reader of value/defaultValue is the ui:calendar renderer.
  • Refined schemas. No in-tree .omit/.pick/.extend-overwrite on the two refined schemas; .shape still works.
  • Gates. check:doc-types, designer-field-key-parity, component-surface-parity, changeset-claims and changeset-no-major all exit 0.
  • Out of scope, filed as objectui#10377. The designer offers mode: 'default', which the mirror refuses. It renders without throwing.

Implemented-by: claude/issue-10304-calendar-modes
Reviewed-by: session_01877XiBYSaRCk2CU7cMSg3S
VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 24, 2026 20:53
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit ea02938 Sep 24, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10304-calendar-modes branch September 24, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant