Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

plugin-calendar: object-calendar / view:calendar spread the authored node into ObjectCalendar — authored onDateClick / onNavigate throw uncaught, authored locale kills the render #4492

Closed

Description

@yinlianghui

Measured while closing #4453 (the calendar-view renderer's consume-or-declare migration). That card's surface is calendar-view's renderer only, so this sibling registration is filed rather than fixed.

The mechanism

ObjectCalendarRenderer — registered as both plugin-calendar:object-calendar and view:calendar (packages/plugin-calendar/src/index.tsx:45-81) — ends in the same raw spread #4453 removed from calendar-view (index.tsx:58, JSX tag spelled with a space after < so GitHub's body sanitizer does not eat it):

{(bound) => < ObjectCalendar schema={bound} dataSource={dataSource} {...props} />}

props is everything SchemaRenderer hands a registered widget: the node's authored keys, the contents of its props container, the injected runtime props and a host's trailing props. ObjectCalendarProps declares onEventClick, onRowClick, onDateClick, onEdit, onDelete, onNavigate, onViewChange, onEventDrop and locale (ObjectCalendar.tsx:64-81), so an authored value under any of those names lands on the declared prop — and an SDUI author writing JSON can never produce a function.

Measured on origin/main @ 31ab1ac

Node: { type: 'plugin-calendar:object-calendar', objectName: 'accounts', startDateField: 'start_at', titleField: 'name', … }, rendered through SchemaRendererProvider.

authored gesture result
onDateClick: 'NOT-A-FUNCTION' click an empty day cell window.error: onDateClick is not a function
onNavigate: 'NOT-A-FUNCTION' click Next period window.error: onNavigate is not a function
locale: 'en_US' render SchemaErrorBoundary: Component "plugin-calendar:object-calendar" failed to render / Incorrect locale information provided

The first two are the #4453 failure mode: React does not route event-handler errors to SchemaErrorBoundary, so they surface as uncaught window errors while the calendar keeps looking fine and that gesture is dead. onDateClick is additionally the unconditional shape — ObjectCalendar.tsx:596 tests if (onDateClick) and a non-empty string is truthy, so the guard passes the value straight to a call.

locale is a render-time crash: any string Intl rejects throws out of toLocaleDateString. en_US (the underscore spelling a producer writes by accident) is the realistic case; '', '123' and 'a' throw the same way, while 'garbage' happens to be a well-formed tag and does not.

Not reproduced, recorded honestly: authored onEventClick did not throw in the probe's default configuration. ObjectCalendar.tsx:585-592 only calls the parent handler when the local navigation is not an overlay (if (!navIsOverlay)), and the probe resolved to an overlay, so the call was skipped. The exposure is in the code path; its reachability depends on the navigation mode and was not established here.

Fix shape

The #4425 phase-2 ruling applied to this renderer, exactly as #4453 did for calendar-view: replace the raw spread with consume-or-declare — declared registry inputs consumed, ObjectCalendarProps' callbacks kept as a declared function-typed host hatch (a host-passed function must keep working), locale accepted only when Intl.getCanonicalLocales takes it, everything else dropped. #4453's renderer is the worked example, including the resolvers and the pin file (packages/plugin-calendar/src/calendar-view-renderer.propsContract.test.tsx).

Note the two registrations share one implementation, so one fix closes object-calendar and view:calendar together — and both are plugin-calendar targets in the #4425 DOM-leak sweep ledger, which does not measure this class (a crash-free spread onto a component that declares its props leaks nothing to the DOM).

Related: #4453 (the same defect on calendar-view, fixed), #4425 (the whitelist contract), #4433 / #4452 (the two earlier collisions on the same spread), #4454 (allowCreate, unrelated but same widget family).


Generated by Claude Code

Activity

  1. self-assigned this
    on Aug 12, 2026
  2. yinlianghui commented on Aug 12, 2026

    @yinlianghui
    ContributorAuthor

    CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4492-objectcalendar-props-contract.

    Ruling (delegated authority; open veto window): apply the #4453 pattern (PR #4494) to the shared ObjectCalendarRenderer behind object-calendar / view:calendar — consume-or-declare, no raw spread into ObjectCalendar:

    1. Census every key the spread can carry → declared input consumed / declared function-typed hatch / dropped. The hatch family is bounded by ObjectCalendar's declared callback props; locale validated with Intl.getCanonicalLocales (the fix(plugin-calendar): calendar-view consumes or declares every forwarded prop — authored onEventClick can no longer crash a click (#4453) #4494 lesson — typeof string leaves the measured RangeError crash open); numeric knobs validated as finite where present.
    2. Red-first = the card's own measured repros: authored onDateClick: 'NOT-A-FUNCTION' → day-cell click throws uncaught pre-fix, no-op post-fix; authored onNavigate string → Next-click same; authored locale: 'en_US' → error boundary pre-fix, default-locale render post-fix. Use the fix(plugin-calendar): calendar-view consumes or declares every forwarded prop — authored onEventClick can no longer crash a click (#4453) #4494 union-of-channels error capture (window event + sync throw + console.error). The honestly-unproven onEventClick path: cover it under the same declared-hatch rule and pin the drop, but do NOT claim a pre-fix crash repro the filing could not prove — assert the contract, state the provenance.
    3. Must-not-change: host-passed functions still fire; both registrations still render; the plugin-calendar: an authored onEventClick reaches CalendarView through the spread and throws an UNCAUGHT error on click — needs a contract decision, not a strip #4453/plugin-calendar: authoring events on a calendar-view node crashes the component — the SDUI action object overwrites the computed CalendarEvent array #4433/fix(plugin-calendar): authored ISO currentDate is parsed to a Date instead of crashing the calendar (#4452) #4484 sibling pins stay green; the DOM-leak sweep (read-only — finding: the DOM-leak "is this attribute HTML-defined" judge now exists in two copies, in two test files that cannot import each other #4434 in flight owns judge internals, you own NOTHING in that file) stays green.

    Sequencing gate: PR #4494 in origin/main first (armed, CI green — poll ~180s, ≤40min; report BLOCKED otherwise). Also re-read in-flight #4434's claim comment before running the sweep — it must still be judge-internals-only.

    Grading: patch @object-ui/plugin-calendar unless the emitted .d.ts moves (measure both ways; minor per precedent if the published surface grows). Never major.

    Surface (mutual exclusion): the ObjectCalendarRenderer source file + one new pin test + one changeset, all in packages/plugin-calendar/**. ⛔ NOT: calendar-view-renderer.tsx (landed by #4494 — read-only reference), ObjectCalendar.tsx/CalendarView.tsx component internals (boundary-only fix; if the fix genuinely requires a component change, STOP and report), the sweep file, console/**, chart files (#4466 in flight), content/docs/releases/. #4493/#4454 (inert inputs) stay their own pooled pair — census-confirm only.


    Generated by Claude Code


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions