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
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.tsxcomputes aCalendarEvent[]fromschema.data, passes it asevents, and then spreads the remaining props after it:SchemaRendererforwards a node'seventskey as a prop (it is not on the renderer's strip list). So an SDUI node that authorsevents— the ordinary action metadata of AGENTS.md section 4, legal on any node — has its{ onClick: [...] }object land on theeventsprop, replacing the array.CalendarViewthen iterates it and throws.Measured, in isolation
eventsalone, nothing else authored:{ "type": "plugin-calendar:calendar-view", "id": "n", "events": { "onClick": [{ "action": "navigate" }] } }renders the error boundary, not the calendar:
The same node without
eventsrenders the real calendar (role="region" aria-label="Calendar"), and so does one carrying every other canary. Soeventsis 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-viewwithout theeventscanary, recorded in the gate asCALENDAR_VIEW_OMITSwith 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, puttingeventsback into the sweep for this target.Generated by Claude Code