Skip to content

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

Description

@yinlianghui

Measured while grading #4433's collision surface (that card's condition 1b: sweep the renderer's other computed props for the same authored-key overwrite exposure). events was crash-capable and is fixed. This one is the remaining exposure, and it is filed rather than fixed because closing it needs a decision the #4433 ruling did not cover.

The exposure

calendar-view's renderer passes onEventClick={handleEventClick} and then spreads {...props} after it. Anything arriving under that key wins — the same shape #4433 fixed for events.

Measured, both channels, before and after #4433's fix (unchanged by it):

authored renders? click
onEventClick: 'NOT-A-FUNCTION' (node key) yes, normally window error: onEventClick is not a function
props: { onEventClick: 'NOT-A-FUNCTION' } yes, normally same

Note the failure mode is worse than an error boundary, which is why it is not simply cosmetic: React does not route event-handler errors to SchemaErrorBoundary, so this surfaces as an uncaught window error. The calendar keeps looking fine and its click handling is dead.

Why this was not fixed alongside events

The two keys are not the same case, and the difference is what needs deciding.

events is injected metadata: SchemaRenderer forwards a node's events key as a prop, no code in the renderer layer consumes it, and no host has a reason to pass one to this renderer. Dropping it is free.

onEventClick arrives through the same channel for two very different producers:

  1. an SDUI author writing JSON — the value can never be a function, so it is always broken; and
  2. a React host rendering SchemaRenderer schema={…} onEventClick={fn} — the trailing ...props spread makes this work today, and it is the component's genuine escape hatch (the same passthrough MetricWidget deliberately keeps).

The key name cannot separate them; only the value's type can. So the options are:

C and D are the same direction at two altitudes and are probably the real answer; A and B are local patches. This is left for triage rather than guessed at.

Same grading round, for the record: className is not exposed — the renderer destructures it out of its own props, so the rest object cannot carry it, and SchemaRenderer sets className after the node's props container anyway (measured: an authored props.className never reaches the region).

Related: #4433 (the events collision, fixed), #4425 (the phase-2 whitelist question), #4357 / #4428 (the deny-list precedent).


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