Skip to content

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

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions