Skip to content

plugin-calendar: authoring events on a calendar-view node crashes the component — the SDUI action object overwrites the computed CalendarEvent array #4433

Description

@yinlianghui

Measured by the #4425 phase-1 DOM-leak sweep gate. This one is not a DOM leak — it is a crash, found because the sweep plants the same canary set on every target and this target refused to render.

The defect

packages/plugin-calendar/src/calendar-view-renderer.tsx computes a CalendarEvent[] from schema.data, passes it as events, and then spreads the remaining props after it:

return (
  CalendarView
    className={className}
    events={events}
    onEventClick={handleEventClick}
    {...props}
  /
);

SchemaRenderer forwards a node's events key as a prop (it is not on the renderer's strip list). So an SDUI node that authors events — the ordinary action metadata of AGENTS.md section 4, legal on any node — has its { onClick: [...] } object land on the events prop, replacing the array. CalendarView then iterates it and throws.

Measured, in isolation

events alone, nothing else authored:

{ "type": "plugin-calendar:calendar-view", "id": "n",
  "events": { "onClick": [{ "action": "navigate" }] } }

renders the error boundary, not the calendar:

Component "plugin-calendar:calendar-view" failed to render
events is not iterable

The same node without events renders the real calendar (role="region" aria-label="Calendar"), and so does one carrying every other canary. So events is the sole trigger — this is not a side effect of the sweep's other canaries.

Why this is worse than the leaks filed alongside it

#4431 and #4432 put junk attributes on the DOM. This one takes the whole component down: a perfectly spec-legal node loses its calendar and shows an error card instead. A prop-name collision between injected SDUI metadata and a component's own prop is a different failure mode from the leak class, and a whitelist at the spread site would close it as a side effect — one more input for #4425 phase 2.

Interaction with the gate

The sweep renders plugin-calendar:calendar-view without the events canary, recorded in the gate as CALENDAR_VIEW_OMITS with the reason, because a crashing render produces a tidy error-boundary DOM with no leaked attributes and would otherwise read as a clean pass. The omission is not silent: the gate carries a dedicated case pinning this crash, so it cannot regress unnoticed. Fixing it turns that case red, and both halves — the omission and the pin — come out in the same change, putting events back into the sweep for this target.


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