diff --git a/.changeset/calendar-view-schema-converge.md b/.changeset/calendar-view-schema-converge.md
new file mode 100644
index 000000000..61f9a3c1d
--- /dev/null
+++ b/.changeset/calendar-view-schema-converge.md
@@ -0,0 +1,33 @@
+---
+'@object-ui/types': minor
+'@object-ui/plugin-calendar': patch
+---
+
+`CalendarViewSchema` (TS interface and zod mirror) converges on the registered
+`calendar-view` renderer's measured read set (objectui#5667, maintainer ruling
+option A — the renderer is authoritative).
+
+**Breaking for consumers of the published type** (deliberate; per-repo policy
+breaking changes ship as `minor` — the fixed group's `major` tracks
+`@objectstack`):
+
+- Nine inert keys are retired: `events` (the interface's only required key,
+ which the renderer deliberately drops — objectui#4433), `defaultView`,
+ `defaultDate`, `date`, `views`, `editable`, `onEventCreate`,
+ `onEventUpdate`, `onDateChange`. None had a read site on the authored-node
+ path and no measured app authors them (ADR-0049 enforce-or-remove).
+- The type now declares what the renderer actually reads: `data`, `titleField`,
+ `startDateField`, `endDateField`, `allDayField`, `colorField`, `view`,
+ `currentDate`, `allowCreate`, `className`, plus the two host-only function
+ hatches it forwards (`onEventClick`, `onViewChange`).
+- Practical radius, measured: `BaseSchema` carries an index signature and the
+ zod `BaseSchema` is `.passthrough()`, so nodes still authoring retired keys
+ neither fail to compile nor get rejected at validation — they are simply no
+ longer declared, documented, or type-checked. The material accept change is
+ that zod no longer **requires** `events`: a `{ "type": "calendar-view" }`
+ node without it now validates (previously the one key validation demanded
+ was the one key guaranteed to do nothing).
+
+Runtime renderer behaviour is unchanged. `@object-ui/plugin-calendar`'s README
+and `content/docs/api/schema-reference.md` are repaired to the converged
+surface in the same change, so no copy of the old contradiction survives.
diff --git a/content/docs/api/schema-reference.md b/content/docs/api/schema-reference.md
index 70cd5ba2a..6a65a83a7 100644
--- a/content/docs/api/schema-reference.md
+++ b/content/docs/api/schema-reference.md
@@ -1036,15 +1036,17 @@ Each widget supports `colSpan` and `rowSpan` to control its size in the grid. Th
### CalendarViewSchema
-A multi-view calendar for displaying and managing events.
+A multi-view calendar computed from the node's `data` records. There is no
+authorable `events` key: the renderer builds one event per record in `data`,
+reading the fields the field-name properties point at (objectui#5667; an
+authored `events` is dropped by design, objectui#4433).
```json
{
"type": "calendar-view",
- "defaultView": "month",
- "views": ["month", "week", "day", "agenda"],
- "editable": true,
- "events": [
+ "view": "month",
+ "currentDate": "2024-03-15T12:00:00.000Z",
+ "data": [
{
"id": "evt-1",
"title": "Team Standup",
@@ -1060,21 +1062,30 @@ A multi-view calendar for displaying and managing events.
"color": "#8b5cf6",
"allDay": false
}
- ]
+ ],
+ "allowCreate": true,
+ "className": "h-[600px] border rounded-lg"
}
```
| Property | Type | Description |
|----------|------|-------------|
-| `events` | `CalendarEvent[]` | **Required.** Events with `id`, `title`, `start`, `end`, optional `color` and `allDay`. |
-| `defaultView` | `CalendarViewMode` | Initial view: `"month"`, `"week"`, `"day"`, `"agenda"`. |
-| `views` | `CalendarViewMode[]` | Available view modes the user can switch between. |
-| `editable` | `boolean` | Allow creating and modifying events. |
-| `defaultDate` | `string \| Date` | Initial calendar date. |
-| `onEventClick` | `function` | Callback when an event is clicked. |
-| `onEventCreate` | `function` | Callback for new event creation: `(start, end)`. |
-| `onEventUpdate` | `function` | Callback when an event is modified. |
-| `onDateChange` | `function` | Callback when the visible date range changes. |
+| `data` | `any` | Records rendered as events — an array, or a binding expression that resolves to one. |
+| `titleField` | `string` | Record field for the event title. Default `"title"`. |
+| `startDateField` | `string` | Record field for the event start date/time. Default `"start"`. |
+| `endDateField` | `string` | Record field for the event end date/time. Default `"end"`. |
+| `allDayField` | `string` | Record field for the all-day flag. Default `"allDay"`. |
+| `colorField` | `string` | Record field for the event color. Default `"color"`. |
+| `view` | `CalendarViewMode` | View mode: `"month"`, `"week"`, `"day"`. Any other value falls back to `"month"`. |
+| `currentDate` | `string \| Date` | Initial calendar date — an ISO date string when authored as JSON. |
+| `allowCreate` | `boolean` | Show the "New event" affordance; clicking it dispatches a `create` action. Default `false`. |
+| `onEventClick` | `function` | Host-only: forwarded when a React host supplies a function; authored JSON cannot produce one. |
+| `onViewChange` | `function` | Host-only: same rule as `onEventClick`. |
+
+Nine formerly declared keys — `events` (was required, and dropped by the
+renderer), `defaultView`, `defaultDate`, `date`, `views`, `editable`,
+`onEventCreate`, `onEventUpdate`, `onDateChange` — were retired in
+objectui#5667: nothing read them on the authored-node path.
**Related:** [ObjectViewSchema](#objectviewschema), [DashboardComponentSchema](#dashboardcomponentschema)
diff --git a/packages/plugin-calendar/README.md b/packages/plugin-calendar/README.md
index 16b2dce16..372802c8c 100644
--- a/packages/plugin-calendar/README.md
+++ b/packages/plugin-calendar/README.md
@@ -89,7 +89,7 @@ import '@object-ui/plugin-calendar';
// Now you can use calendar types in your schemas
const schema = {
type: 'calendar-view',
- events: [
+ data: [
{
id: '1',
title: 'Team Meeting',
@@ -148,60 +148,64 @@ ComponentRegistry.register('my-calendar', ObjectCalendarRenderer);
### CalendarView
-Display a monthly calendar with events. This is a **partial summary**: the full
-contract is `CalendarViewSchema` in `@object-ui/types`, which declares 13 keys of
-its own on top of the common `BaseSchema` keys (`className`, `id`, `data`,
-`visible`, ...).
+Display a calendar computed from the node's `data` records. This is the full
+authored surface: `CalendarViewSchema` in `@object-ui/types` declares 13 keys of
+its own, converged on what the registered `calendar-view` renderer actually
+reads (objectui#5667). Two of them — `data` and `className` — refine common
+`BaseSchema` keys; the rest of `BaseSchema` (`id`, `visible`, ...) applies as on
+any node.
```typescript
{
type: 'calendar-view',
- events: CalendarEvent[], // REQUIRED — the only required key besides `type`
- defaultView?: CalendarViewMode, // 'month' | 'week' | 'day' | 'agenda' (default 'month')
- view?: CalendarViewMode, // controlled
- views?: CalendarViewMode[], // default ['month', 'week', 'day']
- defaultDate?: string | Date,
- date?: string | Date, // controlled
- editable?: boolean, // default false
- onEventClick?: (event: CalendarEvent) => void,
- onDateChange?: (date: Date) => void,
- onViewChange?: (view: CalendarViewMode) => void,
- className?: string // from `BaseSchema`, not `CalendarViewSchema`
+ data?: any, // records rendered as events (array, or a binding expression)
+ titleField?: string, // default 'title'
+ startDateField?: string, // default 'start'
+ endDateField?: string, // default 'end'
+ allDayField?: string, // default 'allDay'
+ colorField?: string, // default 'color'
+ view?: CalendarViewMode, // 'month' | 'week' | 'day' (default 'month')
+ currentDate?: string | Date, // ISO string authored; Date from a React host
+ allowCreate?: boolean, // default false — shows the "New event" button
+ className?: string, // Tailwind classes for the container
+ onEventClick?: (event: CalendarEvent) => void, // HOST-ONLY (see below)
+ onViewChange?: (view: CalendarViewMode) => void // HOST-ONLY (see below)
}
```
-`onEventCreate` and `onEventUpdate` complete the 13; see `CalendarViewSchema` for
-their signatures. `onDateClick` is **not** on this schema — it is a
-`CalendarViewProps` component prop (see [Drag-and-Drop](#drag-and-drop) and
-[Click-to-Create](#click-to-create)).
-
-> **The type is not the renderer.** `CalendarViewSchema` is the shape
-> `@object-ui/types` publishes for this node, not a description of what the
-> registered `calendar-view` renderer reads. That renderer builds its events from
-> the node's `data` array plus the `titleField` / `startDateField` /
-> `endDateField` / `colorField` / `allDayField` inputs, and **drops an authored
-> `events` key** (objectui#4433) — so the type's one required key does nothing
-> when written as JSON. Of the keys above it also reads `view` and `className`;
-> the handlers only ever arrive from a React host
-> (``), because JSON cannot carry a
-> function.
+There is deliberately **no authorable `events` key**: the renderer computes its
+events from `data` plus the field-name keys, and drops an authored `events`
+(objectui#4433). Nine formerly declared keys — `events`, `defaultView`,
+`defaultDate`, `date`, `views`, `editable`, `onEventCreate`, `onEventUpdate`,
+`onDateChange` — were retired in objectui#5667 because nothing read them on the
+authored-node path and no measured app authors them. `onDateClick` is **not**
+on this schema — it is a `CalendarViewProps` component prop (see
+[Drag-and-Drop](#drag-and-drop) and [Click-to-Create](#click-to-create)).
+
+> **The handlers are host-only.** `onEventClick` and `onViewChange` are
+> forwarded to the component only when the value really is a function, which
+> authored JSON can never produce — supply them from a React host
+> (``). Authored as JSON strings they
+> are dropped, same as absent.
### Calendar Event Structure
-The authored event shape, declared by `@object-ui/types`:
-
-```typescript
-interface CalendarEvent {
- id: string;
- title: string;
- start: string; // ISO datetime string
- end: string; // ISO datetime string
- description?: string;
- color?: string; // Tailwind color class
- allDay?: boolean;
-}
+Events are not authored directly — the renderer computes one event per record
+in the node's `data` array, reading the fields the field-name keys point at:
+
+```text
+id: record.id // falls back to record._id, then the array index
+title: record[titleField] // default field 'title'
+start: record[startDateField] // default field 'start' (ISO datetime string)
+end: record[endDateField] // default field 'end' (optional)
+allDay: record[allDayField] // default field 'allDay'
+color: record[colorField] // default field 'color'
+data: record // the whole record rides along
```
+`@object-ui/types` still exports the `CalendarEvent` interface — it is the
+declared payload type of the host-only `onEventClick` callback.
+
## Examples
### Basic Calendar
@@ -209,7 +213,7 @@ interface CalendarEvent {
```typescript
const schema = {
type: 'calendar-view',
- events: [
+ data: [
{
id: '1',
title: 'Product Launch',
@@ -228,6 +232,11 @@ const schema = {
};
```
+The records above already use the default field names (`title`, `start`, `end`,
+`color`), so no field-name keys are needed; point `titleField` /
+`startDateField` / `endDateField` / `allDayField` / `colorField` at your own
+fields when they differ.
+
### With ObjectQL Integration
```typescript
@@ -243,21 +252,28 @@ const schema = {
### Interactive Calendar
+The handlers are host-only — a function can only come from a React host
+building the node in code, never from authored JSON:
+
```typescript
const schema = {
type: 'calendar-view',
- events: [],
+ data: [],
onEventClick: (event) => {
console.log('Event clicked:', event);
// Open event details modal
},
- onDateChange: (date) => {
- console.log('Visible date changed:', date);
- // React to the calendar moving to another month/week/day
+ onViewChange: (view) => {
+ console.log('View changed:', view);
+ // React to the calendar switching between month/week/day
}
};
```
+Authored JSON reacts to clicks through the node's action channel instead
+(`allowCreate: true` dispatches `{ type: 'create' }`; event clicks dispatch
+`{ type: 'event-click' }`).
+
## ObjectQL Integration
When using with ObjectStack, the calendar can automatically fetch and display events:
@@ -291,7 +307,7 @@ Style the calendar with Tailwind classes:
const schema = {
type: 'calendar-view',
className: 'border rounded-lg shadow-lg',
- events: [...]
+ data: [...]
};
```
@@ -301,30 +317,30 @@ The **authored** (JSON metadata) types live in `@object-ui/types`, not in this
package — this package imports them too, and does not re-export them:
```typescript
-import type { CalendarViewSchema, CalendarEvent } from '@object-ui/types';
-
-const event: CalendarEvent = {
- id: '1',
- title: 'Meeting',
- start: '2024-01-15T10:00:00',
- end: '2024-01-15T11:00:00'
-};
+import type { CalendarViewSchema } from '@object-ui/types';
const schema: CalendarViewSchema = {
type: 'calendar-view',
- events: [event]
+ data: [
+ { id: '1', name: 'Meeting', begins: '2024-01-15T10:00:00', ends: '2024-01-15T11:00:00' }
+ ],
+ titleField: 'name',
+ startDateField: 'begins',
+ endDateField: 'ends',
+ view: 'week',
+ allowCreate: true
};
```
-> **Two different `CalendarEvent` types, same name.** The one above, from
-> `@object-ui/types`, is the *authored* event: `id: string` and
-> `start` / `end` accept ISO strings — that is the shape documented under
-> [Calendar Event Structure](#calendar-event-structure) and the one a
-> `calendar-view` schema carries. This package also exports a `CalendarEvent`
-> (`CalendarViewProps['events']`), which is the `CalendarView` **component's
-> runtime** type: `id: string | number` and `start: Date` / `end?: Date`. Use
-> the `@object-ui/types` one for metadata and the plugin one when you render
-> `CalendarView` yourself in React; they are not interchangeable.
+> **Two different `CalendarEvent` types, same name.** `@object-ui/types`
+> exports a `CalendarEvent` (`id: string`, `start` / `end` accept ISO strings)
+> — since objectui#5667 it is no longer part of the authored surface (a
+> `calendar-view` node carries `data` records, not events; see
+> [Calendar Event Structure](#calendar-event-structure)), but it remains the
+> declared payload type of the host-only `onEventClick` callback. This package
+> also exports a `CalendarEvent` (`CalendarViewProps['events']`), which is the
+> `CalendarView` **component's runtime** type: `id: string | number` and
+> `start: Date` / `end?: Date`. They are not interchangeable.
## Links
diff --git a/packages/types/src/complex.ts b/packages/types/src/complex.ts
index 24af60d12..b272f3ac0 100644
--- a/packages/types/src/complex.ts
+++ b/packages/types/src/complex.ts
@@ -170,59 +170,91 @@ export interface CalendarEvent {
}
/**
- * Calendar view component
+ * Calendar view component.
+ *
+ * The authored surface of the registered `calendar-view` renderer, converged
+ * on the renderer's measured read set (objectui#5667): the calendar's events
+ * are COMPUTED from the node's `data` array plus the five field-name keys
+ * below — there is no authorable `events` key. An authored `events` is dropped
+ * by design (objectui#4433); this repo's action metadata rides
+ * `properties.action`, not a calendar prop.
+ *
+ * Nine formerly declared keys — `events` (the interface's only required key
+ * besides `type`, and the one the renderer refuses), `defaultView`,
+ * `defaultDate`, `date`, `views`, `editable`, `onEventCreate`,
+ * `onEventUpdate`, `onDateChange` — are RETIRED rather than implemented:
+ * none had a read site on the authored-node path, and no measured app authors
+ * any of them (ADR-0049 enforce-or-remove, objectui#5667).
*/
export interface CalendarViewSchema extends BaseSchema {
type: 'calendar-view';
/**
- * Calendar events
+ * Records to display as events — the renderer computes its events from this
+ * array plus the field-name keys below.
+ *
+ * Redeclared from `BaseSchema` (same `any` type) because a binding
+ * expression string is also legal here and resolves to the array before the
+ * renderer reads it. A non-array value renders an empty calendar.
*/
- events: CalendarEvent[];
+ data?: any;
/**
- * Default view mode
- * @default 'month'
+ * Record field to use for the event title.
+ * @default 'title'
*/
- defaultView?: CalendarViewMode;
+ titleField?: string;
/**
- * Controlled view mode
+ * Record field containing the event start date/time.
+ * @default 'start'
*/
- view?: CalendarViewMode;
+ startDateField?: string;
/**
- * Default date
+ * Record field containing the event end date/time.
+ * @default 'end'
*/
- defaultDate?: string | Date;
+ endDateField?: string;
/**
- * Controlled date
+ * Record field indicating an all-day event.
+ * @default 'allDay'
*/
- date?: string | Date;
+ allDayField?: string;
/**
- * Available views
- * @default ['month', 'week', 'day']
+ * Record field to use for the event color.
+ * @default 'color'
*/
- views?: CalendarViewMode[];
+ colorField?: string;
/**
- * Enable event creation
- * @default false
+ * Calendar view mode.
+ *
+ * The registered renderer renders `'month' | 'week' | 'day'` and falls back
+ * to `'month'` for any other value — including `'agenda'`, which
+ * {@link CalendarViewMode} still names.
+ * @default 'month'
*/
- editable?: boolean;
+ view?: CalendarViewMode;
/**
- * Event click handler
+ * Initial calendar date — an ISO date string when authored as JSON; a
+ * `Date` instance is accepted from a React host.
*/
- onEventClick?: (event: CalendarEvent) => void;
+ currentDate?: string | Date;
/**
- * Event create handler
+ * Show the "New event" affordance; clicking it dispatches a
+ * `{ type: 'create' }` action on the node's action channel (objectui#4454).
+ * @default false
*/
- onEventCreate?: (start: Date, end: Date) => void;
+ allowCreate?: boolean;
/**
- * Event update handler
+ * Tailwind classes for the calendar container. Redeclared from `BaseSchema`
+ * as part of the converged authored surface.
*/
- onEventUpdate?: (event: CalendarEvent) => void;
+ className?: string;
/**
- * Date change handler
+ * Event click handler — HOST-ONLY. The renderer forwards it only when the
+ * value is a function, which authored JSON can never produce; supply it from
+ * a React host (``).
*/
- onDateChange?: (date: Date) => void;
+ onEventClick?: (event: CalendarEvent) => void;
/**
- * View change handler
+ * View change handler — HOST-ONLY, same rule as {@link CalendarViewSchema.onEventClick}.
*/
onViewChange?: (view: CalendarViewMode) => void;
}
diff --git a/packages/types/src/zod/complex.zod.ts b/packages/types/src/zod/complex.zod.ts
index ea4f9b148..2f437c2b2 100644
--- a/packages/types/src/zod/complex.zod.ts
+++ b/packages/types/src/zod/complex.zod.ts
@@ -86,21 +86,51 @@ export const CalendarEventSchema = z.object({
/**
* Calendar View Schema - Calendar component
+ *
+ * Mirrors `CalendarViewSchema` in `../complex.ts`, converged on the registered
+ * `calendar-view` renderer's measured read set (objectui#5667): events are
+ * computed from `data` plus the field-name keys; the formerly required
+ * `events` and the eight other inert keys (`defaultView`, `defaultDate`,
+ * `date`, `views`, `editable`, `onEventCreate`, `onEventUpdate`,
+ * `onDateChange`) are retired (ADR-0049 enforce-or-remove).
+ *
+ * `BaseSchema` is `.passthrough()`, so the retired keys are not REJECTED here
+ * — they are simply no longer declared or type-checked. The material accept
+ * change is that `events` is no longer required.
*/
export const CalendarViewSchema = BaseSchema.extend({
type: z.literal('calendar-view'),
- events: z.array(CalendarEventSchema).describe('Calendar events'),
- defaultView: CalendarViewModeSchema.optional().describe('Default view mode'),
- view: CalendarViewModeSchema.optional().describe('Controlled view mode'),
- defaultDate: z.union([z.string(), z.date()]).optional().describe('Default date'),
- date: z.union([z.string(), z.date()]).optional().describe('Controlled date'),
- views: z.array(CalendarViewModeSchema).optional().describe('Available views'),
- editable: z.boolean().optional().describe('Whether events are editable'),
- onEventClick: z.function().optional().describe('Event click handler'),
- onEventCreate: z.function().optional().describe('Event create handler'),
- onEventUpdate: z.function().optional().describe('Event update handler'),
- onDateChange: z.function().optional().describe('Date change handler'),
- onViewChange: z.function().optional().describe('View change handler'),
+ data: z
+ .any()
+ .optional()
+ .describe(
+ 'Records to display as events (computed with the field-name keys; binding expressions resolve before the renderer reads it)',
+ ),
+ titleField: z.string().optional().describe("Record field for the event title (default 'title')"),
+ startDateField: z
+ .string()
+ .optional()
+ .describe("Record field for the event start date/time (default 'start')"),
+ endDateField: z
+ .string()
+ .optional()
+ .describe("Record field for the event end date/time (default 'end')"),
+ allDayField: z.string().optional().describe("Record field for the all-day flag (default 'allDay')"),
+ colorField: z.string().optional().describe("Record field for the event color (default 'color')"),
+ view: CalendarViewModeSchema.optional().describe(
+ "View mode (the renderer renders 'month' | 'week' | 'day'; other values fall back to 'month')",
+ ),
+ currentDate: z
+ .union([z.string(), z.date()])
+ .optional()
+ .describe('Initial calendar date (ISO string authored; Date instance from a React host)'),
+ allowCreate: z.boolean().optional().describe('Show the "New event" affordance (default false)'),
+ className: z.string().optional().describe('Tailwind classes for the calendar container'),
+ onEventClick: z
+ .function()
+ .optional()
+ .describe('Host-only event click handler (authored JSON cannot produce a function)'),
+ onViewChange: z.function().optional().describe('Host-only view change handler'),
});
/**