From c688d0b182275b641fee7904161a35bb77ad61c1 Mon Sep 17 00:00:00 2001
From: thedhanawada <13751641+thedhanawada@users.noreply.github.com>
Date: Fri, 2 Oct 2026 04:12:44 -0700
Subject: [PATCH 1/3] docs: refresh released integrations and verification
boundaries
---
.github/workflows/docs.yml | 18 ++
README.md | 8 +-
app/docs/[[...slug]]/page.tsx | 3 +
app/layout.tsx | 2 +-
content/docs/adapters/react.mdx | 154 ++++++++--
content/docs/adapters/vue.mdx | 151 ++++++++--
content/docs/agent/index.mdx | 43 +++
content/docs/agent/meta.json | 1 +
content/docs/api/calendar.mdx | 6 +-
content/docs/api/event-store.mdx | 13 +
content/docs/api/event.mdx | 12 +-
content/docs/api/ics-handler.mdx | 8 +-
content/docs/api/index.mdx | 6 +-
content/docs/api/recurrence-engine-v2.mdx | 6 +
content/docs/api/recurrence-engine.mdx | 10 +
content/docs/core/calendar.mdx | 14 +
content/docs/core/event-store.mdx | 8 +
content/docs/core/event.mdx | 26 +-
content/docs/core/ics.mdx | 8 +-
content/docs/core/index.mdx | 8 +-
content/docs/core/recurrence.mdx | 39 ++-
content/docs/core/search.mdx | 2 +-
content/docs/core/server-side.mdx | 83 +++---
content/docs/core/timezone.mdx | 184 ++----------
content/docs/index.mdx | 98 ++-----
content/docs/installation.mdx | 162 ++++-------
content/docs/interface/index.mdx | 6 +-
content/docs/interface/views.mdx | 2 +-
content/docs/interface/web-components.mdx | 303 ++++++++------------
content/docs/meta.json | 5 +-
content/docs/migration.mdx | 53 ++++
content/docs/quick-start.mdx | 203 ++++---------
content/docs/salesforce/apex-controller.mdx | 203 ++-----------
content/docs/salesforce/deployment.mdx | 193 ++++---------
content/docs/salesforce/index.mdx | 90 ++----
content/docs/salesforce/locker-service.mdx | 126 +-------
content/docs/salesforce/lwc-integration.mdx | 199 +++----------
content/docs/salesforce/meta.json | 1 +
content/docs/salesforce/permissions.mdx | 37 +++
content/docs/security/index.mdx | 87 ++----
content/docs/security/model.mdx | 166 ++---------
content/docs/security/recent-fixes.mdx | 74 ++---
content/docs/whats-new.mdx | 46 +--
next.config.mjs | 2 +
package-lock.json | 8 +-
package.json | 9 +-
scripts/check-content.mjs | 38 +++
scripts/check-examples.mjs | 55 ++++
48 files changed, 1217 insertions(+), 1762 deletions(-)
create mode 100644 .github/workflows/docs.yml
create mode 100644 content/docs/agent/index.mdx
create mode 100644 content/docs/agent/meta.json
create mode 100644 content/docs/migration.mdx
create mode 100644 content/docs/salesforce/permissions.mdx
create mode 100644 scripts/check-content.mjs
create mode 100644 scripts/check-examples.mjs
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..8ca2b7d
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,18 @@
+name: Documentation checks
+on:
+ pull_request:
+ push:
+ branches: [main]
+permissions:
+ contents: read
+jobs:
+ docs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+ - run: npm ci
+ - run: npm run build
diff --git a/README.md b/README.md
index 753de30..2a8418d 100644
--- a/README.md
+++ b/README.md
@@ -8,11 +8,12 @@ Documentation portal for [forceCalendar](https://forcecalendar.org), built with
All content is MDX under `content/docs/`:
-- **Getting started** — installation, quick start
+- **Getting started** — installation, quick start, migration, release status
- **`core/`** — engine guides: calendar, events, recurrence, timezones, ICS, search, state, performance
- **`api/`** — per-class API reference (Calendar, EventStore, RecurrenceEngine, RRuleParser, ICSParser/Handler, StateManager, …)
- **`interface/`** — Web Components, views, theming, event bus
-- **`salesforce/`** — LWC integration, Apex controller, Locker Service, deployment
+- **`salesforce/`** — LWC integration, Apex controller, LWS, permissions, deployment
+- **`agent/`** — private pre-release scope and integration boundaries
- **`security/`** — security model and remediation history
Navigation order is controlled by `meta.json` files alongside the content.
@@ -20,7 +21,8 @@ Navigation order is controlled by `meta.json` files alongside the content.
## Development
```bash
-npm install
+npm ci
+npm test # API coverage, internal links/navigation, and runnable Core examples
npm run dev # http://localhost:3000
npm run build
```
diff --git a/app/docs/[[...slug]]/page.tsx b/app/docs/[[...slug]]/page.tsx
index 4d1dc8b..eb6f793 100644
--- a/app/docs/[[...slug]]/page.tsx
+++ b/app/docs/[[...slug]]/page.tsx
@@ -27,6 +27,9 @@ export default async function Page(props: PageProps) {
+
);
}
diff --git a/app/layout.tsx b/app/layout.tsx
index be47517..41c25bf 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -13,7 +13,7 @@ export const metadata: Metadata = {
template: '%s | forceCalendar',
},
description:
- 'Enterprise-grade calendar infrastructure for Salesforce and strict-CSP environments. Zero dependencies.',
+ 'Calendar engine, Web Components, framework adapters, and Salesforce LWS integration.',
};
export default function Layout({ children }: { children: ReactNode }) {
diff --git a/content/docs/adapters/react.mdx b/content/docs/adapters/react.mdx
index 69eac58..7b460d7 100644
--- a/content/docs/adapters/react.mdx
+++ b/content/docs/adapters/react.mdx
@@ -1,50 +1,146 @@
---
title: React
-description: Use forceCalendar in React and Next.js with @forcecalendar/react
+description: React 0.3.1 props, typed events, snapshots, refs, SSR, and read-only integration.
---
-`@forcecalendar/react` is a thin adapter around the `` Web Component: props map to attributes, DOM events map to callbacks. It has no dependencies beyond its peers.
-
-## Install
+`@forcecalendar/react` **0.3.1** maps props, callbacks and a ref to the Interface Web Component. Install React 18 or later alongside the documented package set:
```bash
-npm install @forcecalendar/react @forcecalendar/core @forcecalendar/interface
+npm install @forcecalendar/react@0.3.1 @forcecalendar/core@2.5.6 @forcecalendar/interface@1.9.0
```
-## Use
+## Use in React or Next.js
```tsx
-import { ForceCalendar } from '@forcecalendar/react';
+'use client';
+import { useState } from 'react';
+import { ForceCalendar, type CalendarEvent } from '@forcecalendar/react';
+
+export default function Schedule() {
+ const [events] = useState([{
+ id: 'planning', title: 'Planning',
+ start: '2026-10-05T09:00:00Z', end: '2026-10-05T10:00:00Z',
+ }]);
+ return console.log('Persist local edit:', event.id)} />;
+}
+```
+
+In Next.js App Router, put interactive usage behind a client boundary as shown. Server rendering can emit the tag; registration runs in an effect. Imperative methods still need `whenReady()`. No `ssr: false` wrapper is required just to avoid registering custom elements on the server.
+
+The examples below use application-provided `fetchEvents` when loading a backend; implement it with authentication, error handling and stale-response protection. UI callbacks alone do not persist edits.
+
+## Props
+
+| Prop | Type | Description |
+| --- | --- | --- |
+| `view` | `'month' \| 'week' \| 'day'` | Initial/controlled view. |
+| `date` | `Date \| string` | Date the view is centred on (`Date` is serialised to ISO). |
+| `locale` | `string` | BCP 47 locale, e.g. `en-AU`. |
+| `timezone` | `string` | IANA time zone. |
+| `weekStartsOn` | `0..6` | First day of the week (0 = Sunday). |
+| `height` | `string` | CSS height of the calendar. |
+| `theme` | `'slds' \| string` | Named theme preset applied as `--fc-*` custom properties. |
+| `events` | `CalendarEvent[]` | Complete snapshot of events, applied through `setEvents()` (see below). |
+| `removeMissingEvents` | `boolean` (default `true`) | Whether events absent from `events` are removed from the calendar. |
+| `className`, `style` | | Applied to the element (`style` as an object; a string would wipe the theme tokens). |
+| any other HTML attribute | `id`, `role`, `aria-*`, `data-*`, `tabIndex`, `onClick`, ... | Passed straight through to ``. |
+
+### Callbacks
+
+Each callback receives the `detail` of the corresponding `calendar-*` DOM event.
+
+| Callback | DOM event | `detail` |
+| --- | --- | --- |
+| `onEventAdded` | `calendar-event-added` | `{ event }` |
+| `onEventUpdated` | `calendar-event-updated` | `{ event }` |
+| `onEventDeleted` | `calendar-event-deleted` | `{ eventId }` |
+| `onEventsSet` | `calendar-events-set` | `{ events, added, updated, removed, unchanged }` |
+| `onDateSelect` | `calendar-date-select` | `{ date }` |
+| `onViewChange` | `calendar-view-change` | `{ view }` |
+| `onNavigate` | `calendar-navigate` | `{ action, date }` |
+| `onRangeChange` | `calendar-range-change` | `{ start, end, view, date }` |
+| `onRangeSelect` | `calendar-range-select` | `{ start, end }` |
+
+`onRangeChange` also reports the initial visible window right after the element is ready, so you can fetch data for it without waiting for the user to navigate. The legacy `calendar-event-add`/`-update`/`-remove` aliases are not mapped; listen to them on the element directly if you need them.
+
+### The `events` prop
+
+`events` is the declarative form of the element's `setEvents()`: pass the complete list and the calendar reconciles it — unchanged events are kept, changed ones replaced, new ones added and (unless `removeMissingEvents={false}`) missing ones removed. The view re-renders at most once and a single `onEventsSet` call describes the change set.
+
+- A snapshot load does **not** emit per-event `onEventAdded` / `onEventDeleted` callbacks, so handlers that persist user edits are not triggered by your own data.
+- The snapshot is re-applied whenever the array identity changes. Inline arrays (`events={[...]}`) therefore re-run the reconciliation on every render; it is cheap when nothing changed (everything lands in `unchanged`) but `onEventsSet` still fires, so wrap the array in `useMemo` or keep it in state.
+- `events` is never rendered as an attribute: it is applied imperatively after mount, and is buffered by the element if it is still loading.
+
+## Imperative handle
+
+The ref exposes the element and all of its methods. Methods are no-ops that log a warning until the element is mounted and `@forcecalendar/interface` has loaded, so await `whenReady()` first when calling them outside an event handler.
+
+```tsx
+import { useEffect, useRef } from 'react';
+import { ForceCalendar, type ForceCalendarHandle } from '@forcecalendar/react';
+
+export function Agenda() {
+ const calendar = useRef(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ calendar.current?.whenReady().then(async () => {
+ if (cancelled) return;
+ const range = calendar.current?.getVisibleRange();
+ if (!range) return;
+ const rows = await fetchEvents(range.start, range.end);
+ calendar.current?.setEvents(rows, { removeMissing: true });
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
-export default function Scheduling() {
return (
- console.log('selected', date)}
- onEventAdded={detail => console.log('added', detail)}
- />
+ <>
+
+
+
+
+
+ >
);
}
```
-## Props
+| Member | Signature |
+| --- | --- |
+| `element` | `ForceCalendarElement \| null` — the underlying `` |
+| `whenReady()` | `Promise` — resolves once the element is defined |
+| `addEvent(event)` | `CalendarEvent \| null` |
+| `updateEvent(id, updates)` | `CalendarEvent \| null` |
+| `deleteEvent(id)` | `boolean` |
+| `getEvents()` | `CalendarEvent[]` |
+| `setEvents(events, { removeMissing? })` | `EventsSetResult \| null` |
+| `getVisibleRange()` | `{ start, end } \| null` (`end` inclusive) |
+| `setView(view)`, `setDate(date)`, `next()`, `previous()`, `today()` | `void` |
+
+## Types
+
+The package exports `ForceCalendarProps`, `ForceCalendarHandle`, `CalendarEvent`, `CalendarView`, `VisibleRange`, `EventsSetOptions`, `EventsSetResult`, `ForceCalendarElement`, `ForceCalendarEventMap` and one `*Detail` type per callback. It also declares `forcecal-main` in `JSX.IntrinsicElements`, so the raw element is type-checked in JSX.
+
+The global `HTMLElementTagNameMap` mapping belongs to `@forcecalendar/interface` 1.7 and newer. Import its element type when using the raw DOM API. The adapter keeps its exported structural types, including plain `CalendarEvent` inputs, without redeclaring the global mapping:
+
+```ts
+import type { ForceCalendarElement } from '@forcecalendar/interface';
+
+const element: ForceCalendarElement = document.createElement('forcecal-main');
+```
-| Prop | Type | Maps to |
-|---|---|---|
-| `view` | `'month' \| 'week' \| 'day'` | `view` attribute |
-| `date` | `Date \| string` | `date` attribute (Dates are serialized to ISO) |
-| `locale` | `string` | `locale` attribute |
-| `timezone` | `string` | `timezone` attribute (IANA name) |
-| `weekStartsOn` | `0–6` | `week-starts-on` attribute |
-| `height` | `string` | `height` attribute |
-| `className`, `style` | — | passed through |
+With interface 1.6, use the adapter's exported type explicitly for direct DOM access: `document.querySelector('forcecal-main')`. The React component and its ref keep the same types across supported interface versions.
-## Callbacks
-`onEventAdded`, `onEventUpdated`, `onEventDeleted`, `onDateSelect`, `onViewChange`, `onNavigate` — each receives the `detail` object of the corresponding `calendar-*` DOM event.
-## Next.js / SSR
+### Read-only calendars
-The adapter is SSR-safe out of the box: the custom elements are registered client-side only (inside `useEffect`), so server rendering emits the tag without touching browser APIs. No `dynamic(() => ..., { ssr: false })` workaround is needed.
+Pass the boolean `readOnly` prop to disable interactive editing (requires
+`@forcecalendar/interface >= 1.8.0`). `false` or omission keeps editing enabled.
+The adapter maps this to the `readonly` boolean attribute consistently during
+server rendering, lazy element registration, and later prop changes.
+Programmatic event methods remain available in read-only mode.
diff --git a/content/docs/adapters/vue.mdx b/content/docs/adapters/vue.mdx
index 71b356e..e0da33c 100644
--- a/content/docs/adapters/vue.mdx
+++ b/content/docs/adapters/vue.mdx
@@ -1,51 +1,148 @@
---
title: Vue
-description: Use forceCalendar in Vue 3 and Nuxt with @forcecalendar/vue
+description: Vue 0.3.1 props, typed events, snapshots, refs, SSR, and read-only integration.
---
-`@forcecalendar/vue` is a thin adapter around the `` Web Component: props map to attributes, DOM events map to Vue emits. It has no dependencies beyond its peers.
-
-## Install
+`@forcecalendar/vue` **0.3.1** maps props, typed emits and a template ref to the Interface Web Component. Install Vue 3.3 or later alongside:
```bash
-npm install @forcecalendar/vue @forcecalendar/core @forcecalendar/interface
+npm install @forcecalendar/vue@0.3.1 @forcecalendar/core@2.5.6 @forcecalendar/interface@1.9.0
```
-## Use
+## Use in Vue or Nuxt
```vue
-
- console.log('selected', d)"
- @event-added="e => console.log('added', e)"
- />
+ console.log('Persist local edit:', event.id)" />
```
+SSR emits the tag and attributes; registration happens in `onMounted`. Browser methods still require readiness. A `` wrapper is not required solely for registration. Examples below use an application-provided `fetchEvents` function; implement authorization, errors and stale-response protection in your host.
+
## Props
-`view`, `date` (Date or string), `locale`, `timezone`, `weekStartsOn`, `height` — each maps to the corresponding element attribute.
+| Prop | Type | Notes |
+| --- | --- | --- |
+| `view` | `'month' \| 'week' \| 'day'` | Rendered as the `view` attribute. |
+| `date` | `Date \| string` | A `Date` is serialised with `toISOString()`. |
+| `locale` | `string` | BCP 47 tag, e.g. `en-AU`. |
+| `timezone` | `string` | IANA zone, e.g. `Australia/Sydney`. |
+| `weekStartsOn` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | Rendered as `week-starts-on`. |
+| `height` | `string` | Any CSS length. |
+| `theme` | `'slds' \| string` | Built-in preset or a custom theme name. |
+| `events` | `CalendarEvent[]` | Complete snapshot of the calendar's events; see below. |
+| `removeMissingEvents` | `boolean` (default `true`) | Whether events absent from the next `events` snapshot are removed. |
+
+Anything else — `class`, `id`, `aria-*`, `data-*`, an object `:style` — falls through to ``.
+
+### The `events` prop
+
+The snapshot is applied imperatively with `element.setEvents(events, { removeMissing })` and is never rendered as an attribute. Details worth knowing:
+
+- Replace the array to update the calendar (`events.value = [...]`). The prop is watched by identity, not deeply; the element then diffs the snapshot, so unchanged events keep their instances and the view re-renders at most once.
+- A snapshot load emits one `events-set` describing the change set (`added`, `updated`, `removed`, `unchanged`). It does **not** emit per-event `event-added` / `event-updated` / `event-deleted`; those fire for changes made through the element's own UI or `addEvent()` / `updateEvent()` / `deleteEvent()`.
+- Keep the array in a `ref` or `computed`. An inline literal in the template is a new array every render, which re-applies the snapshot (cheap, but `events-set` fires with everything `unchanged`).
+- `undefined` (or `null`) leaves the calendar's events alone — use it when you manage events through the exposed methods instead. Pass `[]` to clear.
+- `removeMissingEvents` is read when the next snapshot is applied; toggling it alone does nothing.
+- When the component mounts before `@forcecalendar/interface` has registered the element, the snapshot is stored on the element and applied as soon as it upgrades (interface 1.6.0 and later).
+
+### Styling caveat
+
+A string `style` attribute replaces the element's inline `style` wholesale and wipes the `--fc-*` theme tokens the calendar sets there. Use the object form (`:style="{ borderRadius: '8px' }"`), which Vue merges property by property.
## Emits
-| Emit | Fired on |
-|---|---|
-| `event-added` | `calendar-event-added` |
-| `event-updated` | `calendar-event-updated` |
-| `event-deleted` | `calendar-event-deleted` |
-| `date-select` | `calendar-date-select` |
-| `view-change` | `calendar-view-change` |
-| `navigate` | `calendar-navigate` |
+All emits deliver the DOM event's `detail` as their single argument and are fully typed. Use the kebab-case spelling in templates (`@events-set`, `@range-change`, `@range-select`, …) or the `onXxx` form with `h()` / TSX.
+
+| Emit | Payload | Fires when |
+| --- | --- | --- |
+| `@navigate` | `{ action: 'next' \| 'previous' \| 'today' \| 'goto'; date: Date }` | The visible date changes through navigation. |
+| `@view-change` | `{ view: CalendarView }` | The view switches. |
+| `@date-select` | `{ date: Date }` | A day is selected. |
+| `@range-select` | `{ start: Date; end: Date }` | A range is selected by dragging. |
+| `@range-change` | `{ start: Date; end: Date; view: CalendarView; date: Date }` | The visible window changes — also once after mount with the initial window, so you can load data for it. |
+| `@event-added` | `{ event: CalendarEvent }` | An event was added. |
+| `@event-updated` | `{ event: CalendarEvent }` | An event was changed. |
+| `@event-deleted` | `{ eventId: string }` | An event was removed. |
+| `@events-set` | `EventsSetResult` | A snapshot was applied through `events` / `setEvents()`. |
+
+> **Listen through the emits, not the raw DOM events.** `` dispatches `calendar-range-change`, `calendar-events-set` and friends; because they are undeclared, a `@calendar-range-change` listener still falls through to the element and receives the raw `CustomEvent`, untyped, with the payload on `.detail`. That path is not supported and may stop working — use `@range-change`.
+
+The element's legacy `calendar-event-add` / `-update` / `-remove` aliases are intentionally not mapped: each operation already fires its `-added` / `-updated` / `-deleted` event, and mapping both would run every handler twice.
+
+## Methods (template ref)
+
+A template ref resolves to a `ForceCalendarHandle`: the element's public methods, the element itself, and `whenReady()`, which resolves once the component is mounted and `forcecal-main` is defined. Until then (or with an `@forcecalendar/interface` older than 1.6.0) a method call logs a warning and returns `undefined` rather than throwing.
+
+```vue
+
+
+
+
+
+
+```
+
+| Method | Returns |
+| --- | --- |
+| `element` | The `` element (`null` before mount). |
+| `whenReady()` | `Promise` |
+| `addEvent(event)` | `CalendarEvent \| null` |
+| `updateEvent(id, updates)` | `CalendarEvent \| null` |
+| `deleteEvent(id)` | `boolean` |
+| `getEvents()` | `CalendarEvent[]` |
+| `setEvents(events, { removeMissing? })` | `EventsSetResult \| null` (null when applied before initialisation) |
+| `getVisibleRange()` | `{ start: Date; end: Date } \| null` |
+| `setView(view)`, `setDate(date)`, `next()`, `previous()`, `today()` | `void` |
+
+`$el` still resolves to the element for anyone relying on the Vue instance API.
+
+## Types
+
+Registering the component globally (`app.component('ForceCalendar', ForceCalendar)`) is typed in templates through Vue's `GlobalComponents` augmentation. The package also exports `CalendarEvent`, `CalendarView`, `CalendarTheme`, `VisibleRange`, `EventsSetOptions`, `EventsSetResult`, `EventsSetUpdate`, `ForceCalendarElement`, `ForceCalendarEventMap`, `ForceCalendarHandle` and one `*Detail` type per emit. Event inputs remain structural, so plain objects are accepted.
+
+`@forcecalendar/interface` 1.7 and later owns the global `HTMLElementTagNameMap` declaration for `forcecal-main`. Load its declarations with `import type {} from '@forcecalendar/interface'` to get automatic typing for direct DOM queries. The adapter does not redeclare it, avoiding conflicts when both packages' types are loaded. When using interface 1.6, import `ForceCalendarElement` from this adapter and annotate direct DOM queries explicitly, for example `document.querySelector('forcecal-main')`. Component props, emits and template refs keep the same types on both versions.
+
+## SSR
+
+`renderToString` produces `` with its attributes; the custom elements are registered on the client by importing `@forcecalendar/interface` in `onMounted`. If that import fails the error is logged with a `[@forcecalendar/vue]` prefix instead of being swallowed.
+
+## Upgrading from 0.2
+
+- `@forcecalendar/interface` 1.6.0 or later is required.
+- Emits are declared as an object with typed validators. Templates that relied on the previous string-array typing to accept arbitrary `onXxx` listeners or payload types now get type errors — the intended fix is to use one of the nine emits above.
+- `@range-change`, `@range-select` and `@events-set` are new (in 0.2 they silently never fired).
+
-Each emit receives the DOM event's `detail` object.
-## Nuxt / SSR
+### Read-only calendars
-The adapter is SSR-safe: custom elements register client-side only (inside `onMounted`), so server rendering emits the tag without touching browser APIs. No `` wrapper is needed.
+Pass the boolean `readOnly` prop to disable interactive editing (requires
+`@forcecalendar/interface >= 1.8.0`). `false` or omission keeps editing enabled.
+The adapter maps this to the `readonly` boolean attribute consistently during
+server rendering, lazy element registration, and later prop changes.
+Programmatic event methods remain available in read-only mode.
diff --git a/content/docs/agent/index.mdx b/content/docs/agent/index.mdx
new file mode 100644
index 0000000..1872613
--- /dev/null
+++ b/content/docs/agent/index.mdx
@@ -0,0 +1,43 @@
+---
+title: Human and agent calendars
+description: Private pre-release scope, durability contract, interoperability, and remaining validation.
+---
+
+## Availability
+
+Agent calendaring is a **private pre-release prototype**. There is no public npm installation or production-readiness claim on this page. It is an adapter over Core, not an AI model, an autonomous task executor, or a replacement for an external calendar provider.
+
+The product direction is one durable calendar shared by people and authorized agents, with explicit permissions and inspectable changes.
+
+## Implemented prototype capabilities
+
+- SQLite-backed calendars, events, membership and audit history on one trusted host
+- Owner, editor, reader and worker roles
+- Fixed-principal stdio MCP tools for event create/read/replace/cancel, conflicts and ICS export
+- Revision checks and idempotency keys for writes
+- Lease-based due-item claim/ack/retry with at-least-once delivery and a five-attempt dead state
+- ICS snapshot export and an authentication-required feed handler, plus a loopback-only server wrapper
+- Preview-first Google/Microsoft synchronization building blocks and durable mapping/checkpoint handling
+
+Event text is data. The adapter does not execute commands, evaluate event content, follow event URLs, invoke a model or wake an external worker. The host must arrange polling and authorized execution.
+
+## Current event contract
+
+The prototype accepts one-off timed events with explicit UTC offsets and seconds. Intervals are half-open `[start, end)`; adjacent appointments do not conflict. Recurrence, all-day events, attendee invitations, reminders, resources and business-hours rules are rejected by this adapter even when Core supports related concepts.
+
+Create/replace/cancel need idempotency keys; replace also checks the expected revision. A worker must deduplicate external effects by delivery identity. A crash after an external effect but before acknowledgment can repeat delivery. Cancellation cannot recall an already-running external action.
+
+## Integration status
+
+| Surface | Verified scope | Still required |
+| --- | --- | --- |
+| Local service and stdio MCP | Automated persistence, role, revision, lease, restart and conflict tests | Host operations, backups, retention and monitoring |
+| ICS | Escaping/folding, bounds and authentication checks | Actual client import/subscription behavior and authentication compatibility |
+| Google/Microsoft | Mocked provider requests and reconciliation tests | Explicit live-account authorization and real two-way validation |
+| Hosted pilot | Authenticated list/read path | Hosted create/update/delete validation and broader operational review |
+
+An ICS file import is a snapshot, not continuous two-way synchronization. The feed handler requires host authentication, TLS and routing; some native calendar clients cannot supply the required authentication. Do not remove authentication to make a client work.
+
+## Security responsibilities
+
+A deployment host owns authentication, role grants, credential handling, encryption/backup, audit retention, quotas and provider authorization. Do not put credentials in event descriptions or expose an unauthenticated feed. A preview is not permission to apply provider writes. Production availability and public setup instructions will be documented only after those boundaries are verified.
diff --git a/content/docs/agent/meta.json b/content/docs/agent/meta.json
new file mode 100644
index 0000000..db6559e
--- /dev/null
+++ b/content/docs/agent/meta.json
@@ -0,0 +1 @@
+{"title":"Agent Calendars","pages":["index"]}
diff --git a/content/docs/api/calendar.mdx b/content/docs/api/calendar.mdx
index 58551ee..03f2132 100644
--- a/content/docs/api/calendar.mdx
+++ b/content/docs/api/calendar.mdx
@@ -52,7 +52,9 @@ new Calendar(config?: CalendarConfig)
| `removeEvent` | `(eventId: string) => void` | — |
| `getEvent` | `(eventId: string) => Event \| undefined` | Event or undefined |
| `getEvents` | `() => Event[]` | All events |
-| `setEvents` | `(events: EventData[]) => void` | — |
+| `setEvents` | `(events: EventData[], options?: SetEventsOptions) => EventsSetPayload` | Snapshot change set |
+| `reconcileEvents` | `(events: EventData[], options?: ReconcileOptions) => EventsSetPayload` | Reconciled change set |
+| `getEventsVersion` | `() => number` | Store change counter |
| `queryEvents` | `(filters: QueryFilters) => Event[]` | Filtered events |
| `getEventsForDate` | `(date: Date, timezone?: string) => Event[]` | Events on date |
| `getEventsInRange` | `(start: Date, end: Date, timezone?: string) => Event[]` | Events in range |
@@ -123,3 +125,5 @@ new Calendar(config?: CalendarConfig)
| `state` | `StateManager` | Internal state manager |
| `timezoneManager` | `TimezoneManager` | Timezone singleton |
| `config` | `object` | Resolved configuration |
+
+Core `setEvents()` clears/reloads by default; opt into `{ reconcile: true }` or call `reconcileEvents()`. Interface snapshots reconcile by default. See [snapshot reconciliation](/docs/core/calendar#snapshot-reconciliation).
diff --git a/content/docs/api/event-store.mdx b/content/docs/api/event-store.mdx
index 04ada46..cf64cf4 100644
--- a/content/docs/api/event-store.mdx
+++ b/content/docs/api/event-store.mdx
@@ -94,3 +94,16 @@ new EventStore(options?: { timezone?: string })
| Method | Signature | Returns |
|--------|-----------|---------|
| `destroy` | `() => void` | — |
+
+## Snapshot and occurrence APIs
+
+| Method | Purpose |
+| --- | --- |
+| `reconcile(events, options?)` | Diff a complete snapshot; return added/updated/removed/unchanged |
+| `resolveEventId(id)` | Resolve an occurrence identity to its master |
+| `getOccurrence(id, timezone?)` | Read the specific occurrence |
+| `iterateOccurrences(eventId, options?)` | Lazy single-use occurrence generator |
+| `getNextOccurrence(eventId, after?, options?)` | Next occurrence or null |
+| `takeOccurrences(eventId, count, options?)` | Bounded next occurrences |
+
+Reconcile options include `removeMissing` and `isEquivalent`. Lazy options include exclusive `after`/`before` bounds, `inclusive` and `timezone`. See the [store guide](/docs/core/event-store#reconciliation-and-stable-occurrence-identity).
diff --git a/content/docs/api/event.mdx b/content/docs/api/event.mdx
index 1a7fad1..29d7fd4 100644
--- a/content/docs/api/event.mdx
+++ b/content/docs/api/event.mdx
@@ -20,7 +20,7 @@ See [Event Model](/docs/core/event) for the full list of constructor properties.
| Method | Signature | Returns |
|--------|-----------|---------|
| `normalize` | `(data: object) => EventData` | Normalized event data |
-| `validate` | `(data: object) => { valid: boolean, errors: string[] }` | Validation result |
+| `validate` | `(data: EventData) => void` | Throws on invalid normalized data |
| `fromObject` | `(obj: object) => Event` | Event instance |
## Getters
@@ -92,3 +92,13 @@ See [Event Model](/docs/core/event) for the full list of constructor properties.
| `hasCategory` | `(category: string) => boolean` | — |
| `hasAnyCategory` | `(categories: string[]) => boolean` | — |
| `hasAllCategories` | `(categories: string[]) => boolean` | — |
+
+## Snapshot and occurrence helpers
+
+| Static method | Purpose |
+| --- | --- |
+| `isEquivalent(a, b)` | Compare supported event fields for reconciliation |
+| `occurrenceId(masterId, start)` | Generate stable occurrence identity |
+| `parseOccurrenceId(id)` | Parse occurrence identity |
+
+Core 2.5.6 preserves `endTimeZone` through `clone()`/`toObject()`. See [full-field snapshots](/docs/core/event#full-field-snapshots-and-serialization).
diff --git a/content/docs/api/ics-handler.mdx b/content/docs/api/ics-handler.mdx
index c97ba71..299de25 100644
--- a/content/docs/api/ics-handler.mdx
+++ b/content/docs/api/ics-handler.mdx
@@ -40,7 +40,7 @@ new ICSHandler(calendar: Calendar)
|--------|------|---------|
| `dateRange` | `{ start: Date, end: Date }` | `undefined` |
| `categories` | `string[]` | `[]` |
-| `calendarName` | `string` | `'Calendar'` |
+| `calendarName` | `string` | `'Lightning Calendar Export'` |
| `includeRecurring` | `boolean` | `true` |
| `expandRecurring` | `boolean` | `false` |
@@ -57,3 +57,9 @@ new ICSHandler(calendar: Calendar)
```ts
{ valid: boolean, errors: string[], eventCount: number }
```
+
+## URL transport options
+
+`importFromURL(url, options)` additionally accepts `requestTimeout` (default 30,000 ms), `maxRedirects` (5), and `maxFileSize` (parser default). This helper performs network requests and does not replace host egress controls. See [network boundaries](/docs/security/model#network-limits-matter).
+
+Keep the object returned by `subscribe()` and call `stop()` on teardown. Its initial import is asynchronous, and the subscription is in-process rather than durable provider synchronization.
diff --git a/content/docs/api/index.mdx b/content/docs/api/index.mdx
index 3102813..a4ac33b 100644
--- a/content/docs/api/index.mdx
+++ b/content/docs/api/index.mdx
@@ -26,6 +26,7 @@ Unless marked internal, these classes are exported from `@forcecalendar/core`:
| Class | Import | Description |
|-------|--------|-------------|
| [RecurrenceEngine](/docs/api/recurrence-engine) | `@forcecalendar/core` | Static recurrence expansion |
+| [RecurrenceEngineV2](/docs/api/recurrence-engine-v2) | `@forcecalendar/core` | Instance expansion and modified occurrences |
| [RRuleParser](/docs/api/rrule-parser) | `@forcecalendar/core` | RRULE string parsing |
### ICS
@@ -46,12 +47,15 @@ Unless marked internal, these classes are exported from `@forcecalendar/core`:
| Class | Import | Description |
|-------|--------|-------------|
| [EventSearch](/docs/api/event-search) | `@forcecalendar/core` | Full-text search |
+| [InvertedIndex](/docs/api/inverted-index) | `@forcecalendar/core` | Synchronous search index |
| [SearchWorkerManager](/docs/api/search-worker-manager) | `@forcecalendar/core` | Web Worker search |
### Utilities
| Class | Import | Description |
|-------|--------|-------------|
+| [TimezoneManager](/docs/api/timezone-manager) | `@forcecalendar/core` | Timezone operations |
+| [ConflictDetector](/docs/api/conflict-detector) | `@forcecalendar/core` | Event conflict analysis |
| [DateUtils](/docs/api/date-utils) | `@forcecalendar/core` | Static date utilities |
### Performance (internal)
@@ -67,7 +71,7 @@ These classes are documented for reference but are **not** exported from the `@f
```js
// Everything from main entry
-import { Calendar, Event, EventStore, ... } from '@forcecalendar/core';
+import { Calendar, Event, EventStore, TimezoneManager, ConflictDetector } from '@forcecalendar/core';
// Specific subpath exports
import { Calendar } from '@forcecalendar/core/calendar';
diff --git a/content/docs/api/recurrence-engine-v2.mdx b/content/docs/api/recurrence-engine-v2.mdx
index 7b68ada..08497ae 100644
--- a/content/docs/api/recurrence-engine-v2.mdx
+++ b/content/docs/api/recurrence-engine-v2.mdx
@@ -22,3 +22,9 @@ const occurrences = engine.expandEvent(event, rangeStart, rangeEnd, {
| `addModifiedInstance(eventId, occurrenceDate, modifications)` | Override a single occurrence (reschedule, retitle) without breaking the series |
| `findDSTTransitions(start, end, timezone)` | DST transitions within a range |
| `adjustForDST(start, end, timezone, transitions)` | Wall-clock correction across transitions |
+
+## Lazy series queries and release boundary
+
+`iterateOccurrences(event, options?)`, `nextOccurrence(event, after?, options?)` and `takeOccurrences(event, count, options?)` are instance methods. See [bounds and semantics](/docs/core/recurrence#lazy-occurrence-queries).
+
+Core 2.5.6 has a [known cross-host DST limitation](/docs/core/recurrence#cross-host-dst-limitation). Modified instances and timezone support do not imply universal recurrence conformance.
diff --git a/content/docs/api/recurrence-engine.mdx b/content/docs/api/recurrence-engine.mdx
index 4c248ef..8750ad2 100644
--- a/content/docs/api/recurrence-engine.mdx
+++ b/content/docs/api/recurrence-engine.mdx
@@ -44,3 +44,13 @@ Each expanded occurrence has:
| `isOccurrence` | `boolean` | Always `true` |
| `originalEventId` | `string` | ID of the recurring event |
| `occurrenceDate` | `Date` | Date of this occurrence |
+
+## Lazy series queries
+
+| Static method | Result |
+| --- | --- |
+| `iterateOccurrences(event, options?)` | Single-use generator |
+| `nextOccurrence(event, after?, options?)` | Next occurrence or `null` |
+| `takeOccurrences(event, count, options?)` | Bounded occurrence array |
+
+Options include `after`, `before`, `inclusive` and `timezone`. Bounds are exclusive unless `inclusive: true`. Use `nextOccurrence` for COUNT/UNTIL/exception-aware queries; the older `getNextOccurrence` only steps a rule. See [lazy occurrence semantics](/docs/core/recurrence#lazy-occurrence-queries).
diff --git a/content/docs/core/calendar.mdx b/content/docs/core/calendar.mdx
index a4f69ed..40ec574 100644
--- a/content/docs/core/calendar.mdx
+++ b/content/docs/core/calendar.mdx
@@ -453,3 +453,17 @@ calendar.destroy();
```
Emits: `destroy`.
+
+## Snapshot reconciliation
+
+Core `setEvents(rows)` keeps its clear-and-reload default. Use `setEvents(rows, { reconcile: true })` or `reconcileEvents(rows, options)` to retain unchanged Event instances and apply only differences. Interface `setEvents()` already uses reconciliation by default.
+
+```js
+const result = calendar.reconcileEvents(rows, { removeMissing: true });
+// { events, added, updated: [{ event, oldEvent }], removed, unchanged }
+const version = calendar.getEventsVersion();
+```
+
+A snapshot emits one `eventsSet` with the change set and no `eventAdd`/`eventUpdate`/`eventRemove` callbacks. `removeMissing: false` preserves absent records. `isEquivalent` optionally replaces the comparison function. Duplicate IDs and invalid reconciliation input fail before mutation; occurrence references retain their master.
+
+`getEventsVersion()` reads the store's change counter. `destroy()` releases the owned EventStore and its maintenance resources; call it in server `finally` blocks or when permanently disposing an instance.
diff --git a/content/docs/core/event-store.mdx b/content/docs/core/event-store.mdx
index 843129c..3e73e20 100644
--- a/content/docs/core/event-store.mdx
+++ b/content/docs/core/event-store.mdx
@@ -300,3 +300,11 @@ const occurrences = store.expandRecurringEvent(
### destroy()
Clean up subscriptions and internal state.
+
+## Reconciliation and stable occurrence identity
+
+`store.reconcile(rows, { removeMissing: true })` retains unchanged objects and returns `{ added, updated, removed, unchanged }`. Updated entries have `{ event, oldEvent }`. It emits one batch notification when changes exist. Invalid data/duplicate IDs are validated before mutation; if the caller already opened a batch, the caller owns rollback on an application/comparator failure.
+
+An occurrence reference resolves to its recurring master. `resolveEventId(id)` resolves the identity; `getOccurrence(id, timezone?)` retrieves a specific occurrence. Do not write a clicked occurrence back as a replacement master.
+
+The store uses `RecurrenceEngineV2` by default and exposes lazy `iterateOccurrences(eventId, options)`, `getNextOccurrence(eventId, after, options)`, and `takeOccurrences(eventId, count, options)`. See [recurrence bounds and limitations](/docs/core/recurrence#lazy-occurrence-queries).
diff --git a/content/docs/core/event.mdx b/content/docs/core/event.mdx
index 8f4641b..26d6f52 100644
--- a/content/docs/core/event.mdx
+++ b/content/docs/core/event.mdx
@@ -11,6 +11,7 @@ The `Event` class represents a calendar event with full timezone support, attend
import { Event } from '@forcecalendar/core';
const event = new Event({
+ id: 'team-meeting',
title: 'Team Meeting',
start: new Date('2026-03-01T09:00:00'),
end: new Date('2026-03-01T10:00:00'),
@@ -65,20 +66,17 @@ const normalized = Event.normalize({
### Event.validate(data)
-Validate event data. Returns `{ valid: boolean, errors: string[] }`.
+Validate normalized event data. Returns `void` and **throws an Error** on failure.
```js
-const result = Event.validate({ title: '', start: 'invalid' });
-// { valid: false, errors: ['Title is required', 'Invalid start date'] }
+const normalized = Event.normalize({
+ id: 'meeting', title: 'Meeting',
+ start: new Date(2026, 9, 5, 9), end: new Date(2026, 9, 5, 10),
+});
+Event.validate(normalized);
```
-Validation checks:
-- Title must be a non-empty string
-- Start must be a valid Date
-- End must be a valid Date
-- End must be after start (unless all-day)
-- Status must be one of the valid values
-- Visibility must be one of the valid values
+IDs, titles and start dates are required. Dates must be valid, and an end cannot precede the start (equal instants are allowed). Recurring events require a rule. Attendees and metadata also have validation/size constraints. Normalization defaults invalid status/visibility to supported values; it does not generate an ID. The constructor normalizes and validates its input.
### Event.fromObject(obj)
@@ -275,3 +273,11 @@ event.hasAnyCategory(['meeting', 'review']); // true if has either
### hasAllCategories(categories)
Returns `true` if the event has all of the given categories.
+
+## Full-field snapshots and serialization
+
+Core 2.5.6 retains `endTimeZone` in `clone()` and `toObject()`, alongside supported event fields. `Event.isEquivalent(a, b)` compares the fields used by reconciliation, including nested host data; it is distinct from relying on object identity.
+
+All-day normalization uses local start-of-day through the inclusive last day's 23:59:59.999. Use civil-date parsing at an external adapter boundary; see [date migration](/docs/migration#dates-and-timezones).
+
+Occurrence Events produced by EventStore carry `isOccurrence`, `recurringEventId` and `occurrenceStart`, with stable IDs derived from the master and occurrence instant. `Event.occurrenceId(masterId, start)` and `Event.parseOccurrenceId(id)` support that identity contract. Reconciliation resolves occurrence references back to their master rather than replacing a recurring series with one visible instance.
diff --git a/content/docs/core/ics.mdx b/content/docs/core/ics.mdx
index 290f861..517da28 100644
--- a/content/docs/core/ics.mdx
+++ b/content/docs/core/ics.mdx
@@ -106,7 +106,7 @@ The parser handles three RFC 5545 date formats:
## ICSHandler
-High-level ICS operations built on top of `ICSParser`. Requires a `Calendar` or `EventStore` instance.
+High-level ICS operations built on top of `ICSParser`. Requires a `Calendar`-compatible instance implementing its event methods; use a `Calendar` for the documented integration.
```js
import { ICSHandler } from '@forcecalendar/core';
@@ -238,3 +238,9 @@ handler.downloadAsFile('monthly-calendar.ics', {
calendarName: 'Monthly Export',
});
```
+
+## URL imports and external clients
+
+`importFromURL` performs network requests, unlike pure parsing. Options include `requestTimeout` (30,000 ms), `maxRedirects` (5) and `maxFileSize` (parser default). Node validates DNS and redirect destinations; non-Node runtimes can follow redirects before final-response validation. Host egress/allowlisting remains necessary. See [network limits](/docs/security/model#network-limits-matter).
+
+`subscribe()` initially imports and can poll; retain its handle and call `stop()` during teardown. This is an in-process import helper, not a durable two-way provider sync service. An ICS file export/import is a snapshot, and all-day exclusive end dates need explicit conversion at an integration boundary.
diff --git a/content/docs/core/index.mdx b/content/docs/core/index.mdx
index a19eb7f..d571d43 100644
--- a/content/docs/core/index.mdx
+++ b/content/docs/core/index.mdx
@@ -5,7 +5,7 @@ description: Architecture and module overview for @forcecalendar/core.
## Architecture
-`@forcecalendar/core` is the headless calendar engine. It has zero runtime dependencies and ships as pure ESM JavaScript. The primary public classes are exported from the main entry point at `core/index.js`; some supporting classes -- `LRUCache`, `PerformanceOptimizer`, `AdaptiveMemoryManager`, `TimezoneManager`, `TimezoneDatabase`, and `ConflictDetector` -- are internal and not exported from the package.
+`@forcecalendar/core` is the headless calendar engine. It has zero runtime dependencies and ships as pure ESM JavaScript. The primary public classes are exported from the main entry point at `core/index.js`; some supporting classes -- `LRUCache`, `PerformanceOptimizer`, `AdaptiveMemoryManager`, and `TimezoneDatabase` -- are internal and not exported from the package.
```
core/
@@ -55,14 +55,14 @@ The `Calendar` class is the primary entry point. It composes:
The `EventStore` uses:
-- **RecurrenceEngine** for expanding recurring events in date ranges
+- **RecurrenceEngineV2** by default for expanding recurring events in date ranges
- **ConflictDetector** for overlap detection and busy/free period calculation
## Key Design Decisions
-**No DOM dependency.** The entire core package runs in any JavaScript environment -- browser, Node.js, Web Worker, or Salesforce Locker Service. There are no references to `document`, `window`, or any browser API except `Intl.DateTimeFormat` for timezone support and `performance.memory` (with graceful fallback) for memory monitoring.
+**Headless computation.** Core calendar computation can run without a DOM. Browser download helpers, optional Workers and URL-based ICS operations require their host APIs. Test runtime/bundler capabilities for the features you use; the Salesforce Web Component integration requires LWS.
-**O(1) event lookups.** EventStore maintains five indices (`byDate`, `byMonth`, `recurring`, `byCategory`, `byStatus`) backed by `Map>`. Adding or removing an event updates all indices in constant time.
+**O(1) event lookups.** EventStore maintains five indices (`byDate`, `byMonth`, `recurring`, `byCategory`, `byStatus`) backed by `Map>`. Direct ID lookup is constant-time; date indexing, recurrence and batch work depend on the event span and workload.
**Immutable state transitions.** StateManager creates a new state object on every `setState()` call. Nested objects (`filters`, `businessHours`, `metadata`) are shallow-merged. All state snapshots are deep-cloned before being added to undo/redo history.
diff --git a/content/docs/core/recurrence.mdx b/content/docs/core/recurrence.mdx
index 020b11a..f1e1ed7 100644
--- a/content/docs/core/recurrence.mdx
+++ b/content/docs/core/recurrence.mdx
@@ -13,7 +13,7 @@ forceCalendar provides three classes for recurrence handling:
| `RecurrenceEngine` | Static | Expand recurring events into occurrences |
| `RecurrenceEngineV2` | Instance | Enhanced expansion with modified instances, DST, caching |
-All three follow RFC 5545 (iCalendar) for recurrence rule syntax and semantics.
+These implement RFC 5545 recurrence features with bounded expansion. Supported syntax is not a claim that every RFC combination or host timezone is fully conformant.
## RRuleParser
@@ -113,6 +113,7 @@ Expand a recurring event into occurrences within a date range.
import { RecurrenceEngine } from '@forcecalendar/core';
const event = {
+ id: 'weekly', title: 'Weekly review', recurring: true,
start: new Date('2026-01-05T09:00:00'),
end: new Date('2026-01-05T10:00:00'),
recurrenceRule: 'FREQ=WEEKLY;BYDAY=MO;COUNT=12',
@@ -128,11 +129,7 @@ const occurrences = RecurrenceEngine.expandEvent(
// Returns array of Event-like objects, each with adjusted start/end dates
```
-Each occurrence is a clone of the original event with:
-- `start` and `end` adjusted to the occurrence date
-- `isOccurrence: true` flag
-- `originalEventId` referencing the recurring event
-- `occurrenceDate` for this specific occurrence
+The engine returns occurrence data with adjusted `start`/`end` and recurrence metadata such as `recurringEventId`. EventStore wraps rendered occurrences as Event objects carrying `isOccurrence`, `recurringEventId` and `occurrenceStart`. Do not conflate the raw engine result with a stored master or its rendered occurrence ID.
Regardless of the `maxOccurrences` parameter, expansion is capped by a `MAX_OCCURRENCES_HARD_LIMIT` of **10,000** to prevent runaway expansion from malformed or unbounded rules.
@@ -254,3 +251,33 @@ FREQ=YEARLY;BYMONTH=11;BYDAY=4TH 4th Thursday in November
FREQ=WEEKLY;BYDAY=MO;COUNT=10 10 Mondays
FREQ=DAILY;UNTIL=20261231T235959Z Daily until end of 2026
```
+
+## Lazy occurrence queries
+
+Both engines expose `iterateOccurrences(event, options)`, `nextOccurrence(event, after, options)` and `takeOccurrences(event, count, options)`. Use static methods on `RecurrenceEngine` or instance methods on `RecurrenceEngineV2`.
+
+```js
+import { RecurrenceEngine } from '@forcecalendar/core';
+const event = {
+ id: 'daily', title: 'Daily check', recurring: true,
+ start: new Date('2026-10-05T09:00:00Z'), end: new Date('2026-10-05T09:15:00Z'),
+ timeZone: 'UTC', recurrenceRule: 'FREQ=DAILY;COUNT=3',
+};
+const nextTwo = RecurrenceEngine.takeOccurrences(event, 2, {
+ after: event.start, inclusive: true, timezone: 'UTC',
+});
+```
+
+`after` and `before` are exclusive by default; `inclusive: true` includes both bounds. Bounds are valid Dates or timestamps. A generator is single-use. `takeOccurrences` caps the requested count at the 10,000 hard limit; bound consumers of an open-ended iterator explicitly.
+
+`nextOccurrence(event, after, options)` honors the complete event's COUNT/UNTIL/exceptions. The older `getNextOccurrence(currentDate, rule, timezone)` helper only steps a parsed rule and does not provide those series-query guarantees.
+
+EventStore exposes `iterateOccurrences(eventId, options)`, `getNextOccurrence(eventId, after, options)` and `takeOccurrences(eventId, count, options)` against stored masters, using V2 by default.
+
+## Cross-host DST limitation
+
+In Core **2.5.6**, `RecurrenceEngineV2` can drift when the host timezone differs from the event's named timezone across daylight-saving transitions. This affects a correctness boundary used by EventStore and EnhancedCalendar. A fix is under validation but is not part of this documented release baseline.
+
+Test your event zones, runtime host zones, date boundaries, exceptions and modified instances before relying on recurrence. Do not infer correctness from a benchmark speed result. Legacy `startUTC`/`endUTC` metadata and conversion helpers have their own wall-clock conventions; a recurrence fix must not be described as a redesign of all timezone APIs.
+
+Interface intentionally disallows built-in recurring edit/delete/drag/resize until an explicit series/occurrence workflow is supported.
diff --git a/content/docs/core/search.mdx b/content/docs/core/search.mdx
index 21f7d08..3e0b2aa 100644
--- a/content/docs/core/search.mdx
+++ b/content/docs/core/search.mdx
@@ -133,7 +133,7 @@ Force a rebuild of the search index. Called automatically when events change.
## SearchWorkerManager
-Offloads search to a Web Worker for non-blocking performance on large datasets. Falls back to `InvertedIndex` on the main thread if Web Workers are unavailable (e.g., Salesforce Locker Service).
+Offloads search to a Web Worker for non-blocking performance on large datasets. Falls back to `InvertedIndex` on the main thread if Web Workers are unavailable (for example, a host that blocks Workers).
```js
import { SearchWorkerManager } from '@forcecalendar/core';
diff --git a/content/docs/core/server-side.mdx b/content/docs/core/server-side.mdx
index d934c0c..c476b10 100644
--- a/content/docs/core/server-side.mdx
+++ b/content/docs/core/server-side.mdx
@@ -1,68 +1,59 @@
---
title: Server-Side Scheduling
-description: Use the headless engine in Node, serverless functions, and edge runtimes
+description: Bounded recurrence, ICS, cleanup, and runtime checks for headless applications.
---
-`@forcecalendar/core` is DOM-free, dependency-free, pure ES modules — it runs anywhere JavaScript does, including runtimes that ban `eval` and dynamic code such as Cloudflare Workers. That makes it a server-side scheduling engine, not just a UI companion.
+Core computation runs without a DOM and ships as ESM with no runtime package dependencies. That is useful in Node.js, serverless functions and compatible edge runtimes. Optional network/Worker/browser helpers still depend on their host APIs; verify the actual runtime and bundler.
-## What you can do server-side
-
-- **Expand recurrence rules** — turn an RFC 5545 RRULE into concrete occurrences for a date range
-- **Detect conflicts** — time, attendee, and resource overlap across event sets
-- **Import/export ICS** — parse calendar feeds and generate iCalendar files
-- **Search** — full-text queries across large event sets
-
-## Expanding recurrences in Node
+## Bounded recurrence queries
```js
import { RecurrenceEngine } from '@forcecalendar/core';
-const occurrences = RecurrenceEngine.expandEvent(
- {
- id: 'standup',
- title: 'Team Standup',
- start: new Date('2026-01-05T09:00:00Z'),
- end: new Date('2026-01-05T09:15:00Z'),
- recurring: true,
- recurrenceRule: 'FREQ=WEEKLY;BYDAY=MO,WE,FR',
- timeZone: 'America/New_York'
- },
- new Date('2026-01-01'),
- new Date('2026-03-31'),
- 500 // max occurrences
-);
-// -> [{ start, end, recurringEventId, timezone, originalStart }, ...]
+const event = {
+ id: 'standup', title: 'Standup',
+ start: new Date('2026-10-05T09:00:00Z'),
+ end: new Date('2026-10-05T09:15:00Z'),
+ recurring: true, recurrenceRule: 'FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=12',
+ timeZone: 'UTC',
+};
+const upcoming = RecurrenceEngine.takeOccurrences(event, 3, {
+ after: new Date('2026-10-01T00:00:00Z'),
+ before: new Date('2026-11-01T00:00:00Z'),
+ timezone: 'UTC',
+});
```
-Daily and weekly rules use a numeric fast path (pure timestamp arithmetic between DST transitions), so expanding multi-year series takes fractions of a millisecond — safe inside request handlers.
+This example deliberately uses UTC. Lazy queries avoid materializing an entire open-ended series, but still require explicit workload bounds. Do not assume every recurrence completes within a fixed request budget. See [iterator semantics](/docs/core/recurrence#lazy-occurrence-queries) and the [V2 cross-host DST limitation](/docs/core/recurrence#cross-host-dst-limitation).
-## Generating ICS
+## Generate an ICS snapshot
```js
import { Calendar, ICSHandler } from '@forcecalendar/core';
const calendar = new Calendar({ timeZone: 'UTC' });
-calendar.addEvent({
- id: 'evt-1',
- title: 'Quarterly Review',
- start: new Date('2026-04-01T14:00:00Z'),
- end: new Date('2026-04-01T15:00:00Z'),
- recurring: true,
- recurrenceRule: 'FREQ=MONTHLY;INTERVAL=3;COUNT=4'
-});
-
-const ics = new ICSHandler(calendar).export({ calendarName: 'Reviews' });
-// -> "BEGIN:VCALENDAR..." — serve with Content-Type: text/calendar
+try {
+ calendar.addEvent({
+ id: 'review', title: 'Review',
+ start: new Date('2026-10-05T14:00:00Z'),
+ end: new Date('2026-10-05T15:00:00Z'),
+ });
+ const ics = new ICSHandler(calendar).export({ calendarName: 'Reviews' });
+ console.log(ics); // Serve as text/calendar using your host's response API.
+} finally {
+ calendar.destroy();
+}
```
-## Edge runtimes
+Always dispose owned Calendars/EventStores when finished, especially in jobs and tests. Stop any independent ICS subscription handles as well. File download helpers need a browser; return the ICS string from server routes instead.
-The engine deploys to Cloudflare Workers unmodified — see the runnable
-[Worker example](https://github.com/forcecalendar/examples/tree/main/cloudflare-worker-scheduling),
-which exposes `/occurrences` and `/ics` endpoints and includes a smoke test.
+## Deployment responsibilities
-## Timezones
+- Authorize access to calendar data before queries, exports or mutations.
+- Bound input size, event count, expansion windows, concurrency and response size.
+- Set egress policies for [ICS URL imports](/docs/security/model#network-limits-matter).
+- Test host/event timezone combinations and DST transitions; use occurrence `start`/`end` according to the engine's documented contract.
+- Keep IDs stable and use [reconciliation](/docs/core/calendar#snapshot-reconciliation) for data reloads.
+- Treat text as data; calendar storage does not authorize an agent to execute work.
-Expansion is timezone-aware per occurrence: pass an IANA `timeZone` on the event
-(or as the fifth argument to `expandEvent`) and DST transitions are handled for
-you, including half-hour and 45-minute offset zones.
+The [examples repository](https://github.com/forceCalendar/examples) contains platform starters, including server-side scheduling. A starter is not proof of production certification on every platform.
diff --git a/content/docs/core/timezone.mdx b/content/docs/core/timezone.mdx
index 1115190..4888381 100644
--- a/content/docs/core/timezone.mdx
+++ b/content/docs/core/timezone.mdx
@@ -1,178 +1,54 @@
---
title: Timezone Support
-description: Full IANA timezone support via Intl API with DST handling and cross-timezone conversion.
+description: IANA formatting, legacy conversion conventions, UI limits, and recurrence verification.
---
-## Overview
+Core uses the runtime's `Intl.DateTimeFormat` timezone data. A JavaScript Date represents an instant; formatting that instant in another zone is different from reinterpreting a Date's wall-clock fields.
-forceCalendar uses the browser's `Intl.DateTimeFormat` API for timezone operations. There are no bundled timezone databases or polyfills -- this keeps the package size minimal and ensures timezone data stays up-to-date with the browser.
-
-Timezone support is spread across three classes:
-
-| Class | Role |
-|-------|------|
-| `TimezoneManager` | Singleton for timezone operations and IANA data |
-| `TimezoneDatabase` | IANA timezone data: names, regions, UTC offsets |
-| `DateUtils` | Static timezone conversion and DST methods |
-
-## TimezoneManager
-
-A singleton class that caches timezone operations for performance.
-
-`TimezoneManager` is an internal class -- it is not exported from `@forcecalendar/core`. Access it through a calendar instance via `calendar.timezoneManager`, or use the `Calendar` wrapper methods (`convertTimezone`, `toCalendarTimezone`, `fromCalendarTimezone`, `formatInTimezone`, `getTimezones`) shown below.
+## Format an instant safely
```js
-// Accessed via a Calendar instance
-const tz = calendar.timezoneManager;
+const instant = new Date('2026-10-05T13:00:00Z');
+const text = new Intl.DateTimeFormat('en-US', {
+ timeZone: 'America/New_York', dateStyle: 'medium', timeStyle: 'short',
+}).format(instant);
```
-### Key Methods
-
-| Method | Description |
-|--------|-------------|
-| `getSystemTimezone()` | Detect the system's IANA timezone |
-| `convertTimezone(date, from, to)` | Convert a date between timezones |
-| `getTimezoneOffset(date, timezone)` | Get UTC offset in minutes |
-| `formatInTimezone(date, timezone, options)` | Format in a timezone |
-| `getCommonTimezones()` | List common timezones as `Array<{ value, label, offset }>` |
-| `isValidTimezone(tz)` | Check if a timezone string is valid |
-
-## Calendar Timezone API
-
-The `Calendar` class exposes these timezone methods:
-
-### setTimezone(timezone)
-
-Change the calendar's timezone. All view data, event queries, and date comparisons will use the new timezone.
-
-```js
-calendar.setTimezone('Asia/Tokyo');
-```
-
-When the timezone changes:
-- All date-based indices are recalculated
-- View data is regenerated
-- The `timezoneChange` event fires with `{ timezone, previousTimezone }`
-
-### getTimezone()
-
-```js
-const tz = calendar.getTimezone(); // 'America/New_York'
-```
-
-### convertTimezone(date, fromTz, toTz)
-
-Convert a date between any two IANA timezones.
-
-```js
-// What time is 9am New York in Tokyo?
-const tokyoTime = calendar.convertTimezone(
- new Date('2026-03-01T09:00:00-05:00'),
- 'America/New_York',
- 'Asia/Tokyo'
-);
-// 2026-03-01T23:00:00+09:00
-```
-
-### toCalendarTimezone(date, fromTz?)
-
-Convert a date to the calendar's configured timezone.
+Core exposes `TimezoneManager` publicly:
```js
-// Convert UTC time to calendar timezone
-const local = calendar.toCalendarTimezone(utcDate, 'UTC');
+import { TimezoneManager } from '@forcecalendar/core';
+const tz = TimezoneManager.getInstance();
+const offset = tz.getTimezoneOffset(new Date(), 'America/New_York');
```
-### fromCalendarTimezone(date, toTz?)
+## Calendar timezone API
-Convert a date from the calendar's timezone to another timezone.
+| Method | Purpose |
+| --- | --- |
+| `setTimezone(timezone)` / `getTimezone()` | Configure/read Core's calendar zone |
+| `convertTimezone(date, from, to)` | Legacy wall-clock conversion between zones |
+| `toCalendarTimezone(date, from?)` | Convert to the configured calendar zone |
+| `fromCalendarTimezone(date, to?)` | Convert from the configured calendar zone |
+| `formatInTimezone(date, timezone?, options?)` | Format through the timezone manager |
+| `getTimezones()` | Common `{ value, label, offset }` descriptors |
-### formatInTimezone(date, timezone?, options?)
+`TimezoneManager` also exposes validation, offsets, DST/transition helpers and caches. See its [API reference](/docs/api/timezone-manager). `TimezoneDatabase` is an internal supporting class, not a package root export.
-Format a date in a specific timezone.
-
-```js
-calendar.formatInTimezone(new Date(), 'Asia/Tokyo', {
- weekday: 'long',
- hour: 'numeric',
- minute: 'numeric',
-});
-```
-
-### getTimezones()
-
-Returns an array of common timezone descriptors -- `Array<{ value, label, offset }>` -- where `value` is the IANA identifier. (Wraps `TimezoneManager.getCommonTimezones()`.)
-
-## Event Timezone Support
-
-Events can specify their own timezones independent of the calendar timezone:
-
-```js
-calendar.addEvent({
- title: 'Cross-timezone Meeting',
- start: new Date('2026-03-01T09:00:00'),
- end: new Date('2026-03-01T10:00:00'),
- timeZone: 'America/Los_Angeles', // Start time is in LA
- endTimeZone: 'America/New_York', // End time is in NY (rare)
-});
-```
-
-### Event Timezone Methods
-
-```js
-// Get start time in any timezone
-const tokyoStart = event.getStartInTimezone('Asia/Tokyo');
-const nyEnd = event.getEndInTimezone('America/New_York');
-
-// Update times in a specific timezone
-event.updateTimes(
- new Date('2026-03-01T09:00:00'),
- new Date('2026-03-01T10:00:00'),
- 'Europe/London'
-);
-```
-
-## DST Handling
-
-### Detection
-
-```js
-DateUtils.isDST(new Date('2026-03-08'), 'America/New_York'); // false (before spring forward)
-DateUtils.isDST(new Date('2026-03-09'), 'America/New_York'); // true (after spring forward)
-```
-
-### Safe Arithmetic
-
-`DateUtils.addHoursWithDST()` accounts for DST transitions:
-
-```js
-// During spring forward (2am -> 3am), adding 1 hour to 1:30am gives 3:30am
-DateUtils.addHoursWithDST(
- new Date('2026-03-08T01:30:00'),
- 1,
- 'America/New_York'
-);
-```
+## Event fields and conversions
-### Recurrence and DST
+`timeZone` and `endTimeZone` retain event zone metadata; the latter can describe a different end zone. Core 2.5.6 preserves `endTimeZone` in `clone()` and `toObject()`. Invalid Event timezone identifiers throw.
-`RecurrenceEngineV2` pre-calculates DST transitions for expansion ranges using `findDSTTransitions()`. This ensures that a recurring 9:00 AM meeting stays at 9:00 AM wall-clock time across DST boundaries, rather than shifting by the offset change.
+Legacy Event and `TimezoneManager.toUTC`/`fromUTC` APIs use wall-clock carrier conventions. In particular, do not assume `startUTC`/`endUTC` are a second universally host-independent encoding of the supplied timestamps, or apply those conversions twice to an already-normalized instant. Validate the relevant API against your input contract.
-## TimezoneDatabase
+## Interface grid and visible range
-Provides static data about IANA timezones. Like `TimezoneManager`, this class is internal and is not exported from `@forcecalendar/core` -- its data is surfaced through `calendar.getTimezones()` and `calendar.timezoneManager`.
+The current Interface grid, editor display and `getVisibleRange()` use the browser's local timezone. The `timezone` attribute configures Core but does **not** change the grid into an arbitrary-zone view. `getVisibleRange().end` is inclusive.
-The database includes:
-- All IANA timezone identifiers
-- Timezone display names
-- UTC offsets
-- Region groupings (Americas, Europe, Asia, etc.)
+All-day values are civil dates, not instants: local start-of-day through the inclusive last day's 23:59:59.999. Use calendar-day arithmetic across DST transitions. Providers/ICS with exclusive ends and Salesforce's inclusive UTC-date representation need explicit adapters. See [date migration](/docs/migration#dates-and-timezones).
-This class is used internally by `TimezoneManager` and the UI timezone selector.
+## Recurrence and DST
-## Implementation Notes
+Core 2.5.6 has a known `RecurrenceEngineV2` cross-host DST drift when the host zone differs from the event zone. See [recurrence status](/docs/core/recurrence#cross-host-dst-limitation). A future scoped recurrence fix must not be interpreted as a redesign of every legacy conversion API.
-- Timezone operations rely entirely on `Intl.DateTimeFormat`. If the browser doesn't support a timezone, operations gracefully fall back to UTC.
-- `TimezoneManager` uses a singleton pattern. Multiple `Calendar` instances share the same manager and its cache.
-- Timezone conversions are cached within `TimezoneManager` for performance.
-- The `Event.occursOn(date)` method respects the event's `timeZone` property when checking date membership.
+Test the host timezone, event timezone, clock gaps/overlaps, exception dates and query bounds relevant to the deployment. Host `Intl` data may differ between runtime versions; keep runtimes maintained.
diff --git a/content/docs/index.mdx b/content/docs/index.mdx
index 0ee7cea..518f102 100644
--- a/content/docs/index.mdx
+++ b/content/docs/index.mdx
@@ -1,88 +1,40 @@
---
title: Introduction
-description: Enterprise-grade calendar infrastructure for Salesforce and strict-CSP environments.
+description: Calendar infrastructure for the web, frameworks, and Salesforce Lightning Web Security.
---
-## What is forceCalendar?
+forceCalendar separates calendar logic, presentation and platform persistence. Choose the layer you need:
-forceCalendar is a modular, zero-dependency calendar engine built from the ground up for environments where most calendar libraries fail: Salesforce Lightning Locker Service, strict Content Security Policy (CSP) restrictions, and enterprise deployments that cannot tolerate third-party supply chain risk.
+| Layer | Current documented version | What it provides |
+| --- | --- | --- |
+| [Core](/docs/core) | 2.5.6 | Headless events, recurrence, search, timezone utilities and ICS |
+| [Interface](/docs/interface) | 1.9.0 | Month/week/day Web Components, snapshots, selection and event editing |
+| [React](/docs/adapters/react) | 0.3.1 | Typed props, callbacks, refs and client-side registration |
+| [Vue](/docs/adapters/vue) | 0.3.1 | Typed props, emits, template refs and client-side registration |
+| [Salesforce](/docs/salesforce) | 0.3 beta | LWC, Apex persistence and optional Reader/Editor permission sets |
+| [Agent calendars](/docs/agent) | Private pre-release | Durable shared calendars and scoped agent tools; availability is limited |
-The architecture follows a strict layered separation:
+Versions and verification boundaries are summarized in [What's new](/docs/whats-new). The npm packages and the Salesforce beta have different release processes.
-```
-@forcecalendar/core Headless engine (pure JS, zero deps)
- ↓
-@forcecalendar/interface Web Components UI layer
- ↓
-Applications www (Next.js), Salesforce LWC
-```
+## Start with a working integration
-Each layer can be used independently. The core package works in any JavaScript environment. The interface package adds a visual layer using Web Components with Shadow DOM encapsulation. Application layers adapt the interface to specific platforms.
+- [Quick start](/docs/quick-start): render a calendar and load a snapshot
+- [Installation](/docs/installation): pinned packages, runtime requirements and TypeScript
+- [Migration](/docs/migration): snapshots, read-only behavior, dates and lifecycle changes
+- [Salesforce installation](/docs/salesforce/deployment): supported environment and deployment choices
+- [Permissions](/docs/salesforce/permissions): least-privilege setup and its platform-wide effects
+- [API reference](/docs/api): public Core APIs and labeled internal utilities
-## Design Principles
+## Design and boundaries
-**Zero dependencies.** The entire core package ships as pure ESM JavaScript with no `node_modules` requirements. This eliminates supply chain risk and guarantees compatibility with locked-down environments.
+**Headless Core.** Core ships as ESM with no runtime package dependencies. Calendar computation works without a DOM; browser download helpers and optional Workers still need the corresponding host APIs. No dependencies reduces dependency exposure, but does not eliminate security risk.
-**Locker Service first.** Every API is designed to work within Salesforce Lightning Locker Service constraints: no `eval()`, no `innerHTML` in security-critical paths, no dynamic `import()`, no `Function()` constructors, no direct `window` or `document` global access.
+**Web Components.** Interface supplies `` and `` with Shadow DOM. Applications own persistence and server authorization. A browser event notification is not proof that a remote save succeeded.
-**Headless core.** The calendar engine manages state, events, recurrence, search, and ICS parsing without any DOM dependency. This enables server-side usage, testing without a browser, and framework-agnostic integration.
+**Salesforce support.** The LWC requires Lightning Web Security (LWS). Legacy Lightning Locker does not support the third-party custom elements used by this integration. Manual DOM insertion does not bypass that requirement.
-**RFC 5545 compliance.** Recurrence rules, ICS import/export, and timezone handling follow the iCalendar specification. The RRULE parser supports all RFC 5545 frequencies (SECONDLY through YEARLY), BYDAY with nth positioning, BYMONTHDAY, BYYEARDAY, BYWEEKNO, BYSETPOS, EXDATE exceptions, and WKST configuration.
+**Dates are an integration contract.** Timed events, inclusive all-day civil dates, visible-range ends and provider-exclusive end dates are different concepts. Read the [date guidance](/docs/migration#dates-and-timezones) before adapting an external calendar. The Interface grid and visible range currently use the browser's local timezone; setting `timezone` does not turn them into an arbitrary-zone grid.
-**Immutable state management.** All state transitions produce new objects. The StateManager provides undo/redo history, key-specific watchers, and prototype pollution protection.
+**Recurrence is bounded.** Core implements RFC 5545 recurrence features, with explicit resource limits and known edge cases. Built-in recurring-event mutation is disabled until a series/occurrence scope workflow exists. See [recurrence](/docs/core/recurrence) for the current cross-host DST limitation.
-## Package Overview
-
-### @forcecalendar/core (v2.1.68)
-
-The headless engine. Published on npm as `@forcecalendar/core`.
-
-| Module | Purpose |
-|--------|---------|
-| `Calendar` | Main calendar controller with view management, navigation, event CRUD |
-| `EnhancedCalendar` | Extended Calendar with Web Worker search and RecurrenceEngineV2 |
-| `Event` | Event model with timezone support, attendees, reminders, categories |
-| `EventStore` | Indexed event storage with O(1) lookups, batch operations, conflict detection |
-| `RecurrenceEngine` | RFC 5545 recurrence expansion (static API) |
-| `RecurrenceEngineV2` | Enhanced recurrence with modified instances, DST handling, caching |
-| `RRuleParser` | RRULE string parsing and generation |
-| `ICSParser` | Low-level RFC 5545 ICS parser |
-| `ICSHandler` | High-level ICS import/export with subscriptions |
-| `EventSearch` | Full-text search with fuzzy matching and Levenshtein distance |
-| `SearchWorkerManager` | Web Worker-based search with InvertedIndex fallback |
-| `StateManager` | Immutable state with undo/redo and change subscriptions |
-| `DateUtils` | Static date utility methods with timezone support |
-| `PerformanceOptimizer` | LRU caching, lazy indexing, metrics collection |
-| `AdaptiveMemoryManager` | Dynamic cache sizing based on memory pressure |
-| `LRUCache` | O(1) LRU cache using Map insertion order |
-
-### @forcecalendar/interface
-
-Web Components that consume the core engine.
-
-| Component | Element | Purpose |
-|-----------|---------|---------|
-| `ForceCalendar` | `` | Main calendar Web Component |
-| `BaseComponent` | - | Foundation class for all components |
-| `MonthViewRenderer` | - | Month grid renderer |
-| `WeekViewRenderer` | - | Week timeline renderer |
-| `DayViewRenderer` | - | Day timeline renderer |
-| `EventBus` | - | Cross-component event communication |
-| `StyleUtils` | - | CSS custom property theming system |
-
-### Salesforce LWC
-
-A Lightning Web Component wrapper that bridges `@forcecalendar/interface` with Salesforce:
-
-- Apex controller with `WITH SECURITY_ENFORCED` and CRUD/FLS checks
-- `@wire` integration for reactive event loading
-- Manual DOM insertion to bypass Locker Service static analysis
-- SLDS-styled chrome (spinner, error display, refresh)
-
-## Quick Links
-
-- [Quick Start](/docs/quick-start) -- Get running in 5 minutes
-- [Installation](/docs/installation) -- Package installation and configuration
-- [Calendar API](/docs/core/calendar) -- Main Calendar class documentation
-- [Event Model](/docs/core/event) -- Event creation and manipulation
-- [API Reference](/docs/api) -- Complete API reference for all public classes
+**Verify your environment.** Shipped tests cover specific paths, not every browser, Salesforce org, assistive technology or external provider. See [security](/docs/security) and the component guides for the remaining boundaries.
diff --git a/content/docs/installation.mdx b/content/docs/installation.mdx
index 0a42d34..f8c19d0 100644
--- a/content/docs/installation.mdx
+++ b/content/docs/installation.mdx
@@ -1,146 +1,82 @@
---
title: Installation
-description: Install and configure forceCalendar packages.
+description: Install the documented release set and choose a supported runtime.
---
-## Core Package
+## Pinned npm release set
+
+For a reproducible baseline:
```bash
-npm install @forcecalendar/core
+npm install @forcecalendar/core@2.5.6 @forcecalendar/interface@1.9.0
```
-The core package is published as pure ESM with no dependencies. It requires a JavaScript environment that supports ES modules.
-
-### Package Exports
-
-The package exposes the following entry points via `exports` in `package.json`:
-
-| Import Path | Module |
-|------------|--------|
-| `@forcecalendar/core` | All exports (Calendar, Event, EventStore, etc.) |
-| `@forcecalendar/core/calendar` | Calendar class |
-| `@forcecalendar/core/events` | Event class |
-| `@forcecalendar/core/state` | StateManager class |
-| `@forcecalendar/core/search` | EventSearch class |
-| `@forcecalendar/core/ics` | ICSHandler class |
-| `@forcecalendar/core/types` | TypeScript type definitions |
+Core alone is useful for server-side computation:
-### Import Examples
-
-```js
-// Import everything
-import {
- Calendar,
- Event,
- EventStore,
- StateManager,
- DateUtils,
- ICSParser,
- ICSHandler,
- EventSearch,
- RecurrenceEngine,
- RecurrenceEngineV2,
- RRuleParser,
- EnhancedCalendar,
- SearchWorkerManager,
- InvertedIndex,
-} from '@forcecalendar/core';
-
-// Import specific modules (tree-shakeable)
-import { Calendar } from '@forcecalendar/core/calendar';
-import { Event } from '@forcecalendar/core/events';
-import { StateManager } from '@forcecalendar/core/state';
-import { ICSHandler } from '@forcecalendar/core/ics';
+```bash
+npm install @forcecalendar/core@2.5.6
```
-## Interface Package
+Add the adapter for your framework:
```bash
-npm install @forcecalendar/interface
+npm install @forcecalendar/react@0.3.1
+# OR
+npm install @forcecalendar/vue@0.3.1
```
-The interface package provides Web Components that consume the core engine. It registers the `` custom element automatically on import.
+Keep the Core and Interface peers installed. React requires React 18 or later; Vue requires Vue 3.3 or later. Adapter 0.3 requires Interface 1.6 or later; its `readOnly` support requires Interface 1.8 or later. The documented release set above includes both features.
-```js
-import '@forcecalendar/interface';
-// is now available in the DOM
-```
+## Core imports
+
+Core is ESM and has no runtime package dependencies. Use a maintained Node.js release for server workloads. `fetch`-based ICS helpers need a runtime with `fetch`, `AbortController` and the relevant network permissions; `downloadAsFile()` is browser-only. Optional search Workers have a synchronous fallback.
-### Individual Imports
+| Import | Exports |
+| --- | --- |
+| `@forcecalendar/core` | Public classes, `VERSION`, and default `Calendar` |
+| `@forcecalendar/core/calendar` | `Calendar` |
+| `@forcecalendar/core/events` | `Event` |
+| `@forcecalendar/core/state` | `StateManager` |
+| `@forcecalendar/core/search` | `EventSearch` |
+| `@forcecalendar/core/ics` | `ICSHandler` |
+| `@forcecalendar/core/types` | Shared TypeScript types |
```js
-import {
- ForceCalendar,
- BaseComponent,
- MonthViewRenderer,
- WeekViewRenderer,
- DayViewRenderer,
- EventBus,
- eventBus, // Singleton instance
- StyleUtils,
- DateUtils,
- StateManager,
-} from '@forcecalendar/interface';
+import { Calendar, Event, TimezoneManager, ConflictDetector } from '@forcecalendar/core';
+import { ICSHandler } from '@forcecalendar/core/ics';
```
-## Salesforce Deployment
+`LRUCache`, `PerformanceOptimizer`, `AdaptiveMemoryManager` and `TimezoneDatabase` are internal utilities, not root exports. Do not deep-import unexported package paths.
-The Salesforce package is deployed as a Lightning Web Component with an Apex controller. It is not published on npm.
+## Browser Interface
-### Prerequisites
-
-- Salesforce org with Lightning Experience enabled
-- API version 58.0 or higher
-- Read access to the `Event` standard object (or your custom object)
-
-### Deployment Steps
-
-1. Deploy the Apex controller: `ForceCalendarController.cls`
-2. Deploy the LWC component: `forceCalendar/`
-3. Deploy `@forcecalendar/core` and `@forcecalendar/interface` as static resources
-4. Add the component to a Lightning page via App Builder
-
-See the [Salesforce guide](/docs/salesforce) for detailed deployment instructions.
-
-## Browser Compatibility
+```js
+import '@forcecalendar/interface';
+```
-forceCalendar uses standard Web APIs with no polyfills required in modern browsers:
+This registers `` and ``. Named imports from the same entry point also run its registration side effects. Interface needs a browser DOM, Custom Elements and Shadow DOM; use the framework adapters for client-side registration during SSR rather than importing Interface in server-only code.
-| Feature | Used By | Minimum Support |
-|---------|---------|-----------------|
-| ES Modules | Core | All modern browsers |
-| `Intl.DateTimeFormat` | Timezone handling | All modern browsers |
-| `Map` / `Set` | EventStore, StateManager | All modern browsers |
-| `CustomEvent` | Interface events | All modern browsers |
-| `Shadow DOM` | Web Components | Chrome 53+, Firefox 63+, Safari 10+ |
-| `Web Workers` | SearchWorkerManager | All modern browsers (optional) |
-| `performance.memory` | AdaptiveMemoryManager | Chrome only (graceful fallback) |
+Core declarations are generated from source. Interface also owns the global `HTMLElementTagNameMap` entry for `forcecal-main`:
-The core package works in Node.js 16+ for server-side usage and testing.
+```ts
+import type { ForceCalendarElement } from '@forcecalendar/interface';
+const calendar: ForceCalendarElement = document.createElement('forcecal-main');
+```
-## CDN Usage
+See [Quick start](/docs/quick-start) for snapshot loading and [migration](/docs/migration) for readiness and cleanup.
-For quick prototyping without a build step:
+## Salesforce
-```html
-
-```
+Follow [deployment](/docs/salesforce/deployment), then select the minimum appropriate [permissions](/docs/salesforce/permissions). No permission set is assigned automatically.
-## TypeScript Support
+## Compatibility checks
-Type definitions are included in the core package at `@forcecalendar/core/types`:
+Test your actual browser and host policies. In particular:
-```ts
-import type {
- CalendarConfig,
- CalendarState,
- EventData,
- RecurrenceRule,
- ICSExportOptions,
- SearchOptions,
-} from '@forcecalendar/core/types';
-```
+- The Interface uses Shadow DOM styles and inline style properties. A strict `style-src` policy needs host-specific validation; absence of `eval()` alone does not establish full CSP compatibility.
+- The grid and visible range use the browser's local timezone even when `timezone` is configured.
+- An SSR-safe adapter does not make browser-only imperative methods callable on the server.
+- Read-only UI is separate from server authorization.
+- Agent/provider integrations have their own [pre-release boundaries](/docs/agent).
diff --git a/content/docs/interface/index.mdx b/content/docs/interface/index.mdx
index 7bc2bbf..1aceb2e 100644
--- a/content/docs/interface/index.mdx
+++ b/content/docs/interface/index.mdx
@@ -43,7 +43,7 @@ import '@forcecalendar/interface';
// is now available
```
-Or import specific classes without auto-registration:
+Named imports from the same entry point also run custom-element registration:
```js
import { ForceCalendar, BaseComponent, EventBus } from '@forcecalendar/interface';
@@ -64,3 +64,7 @@ import { ForceCalendar, BaseComponent, EventBus } from '@forcecalendar/interface
| `DateUtils` | Date formatting utilities |
| `DOMUtils` | DOM manipulation utilities |
| `StateManager` | Interface package's own state management class (`src/core/StateManager.js`) that wraps the core `Calendar` |
+
+## Current interaction contract
+
+Interface 1.9 includes snapshots, visible-range callbacks, details/edit/delete dialogs, read-only interaction, recurring-event guards and explicit lifecycle cleanup. See [Web Components](/docs/interface/web-components) for the supported methods and limitations. The grid uses browser-local dates; the `timezone` attribute is not arbitrary-zone rendering. Salesforce requires LWS.
diff --git a/content/docs/interface/views.mdx b/content/docs/interface/views.mdx
index 1548852..cbcb6ab 100644
--- a/content/docs/interface/views.mdx
+++ b/content/docs/interface/views.mdx
@@ -5,7 +5,7 @@ description: Month, week, and day view rendering -- layout, event positioning, a
## Overview
-Each calendar view has a dedicated renderer class that produces DOM content for the `` component. Renderers use pure JavaScript DOM creation (no `innerHTML`) for Locker Service compatibility.
+Each calendar view has a dedicated renderer class that produces DOM content for the `` component. Renderers use pure JavaScript DOM creation (no `innerHTML`) to keep event text separate from HTML. Salesforce deployment requires LWS.
## MonthViewRenderer
diff --git a/content/docs/interface/web-components.mdx b/content/docs/interface/web-components.mdx
index 0e01d51..b541cea 100644
--- a/content/docs/interface/web-components.mdx
+++ b/content/docs/interface/web-components.mdx
@@ -1,233 +1,176 @@
---
title: Web Components
-description: The forcecal-main custom element -- attributes, API, events, and lifecycle.
+description: Snapshots, editing, read-only mode, dates, typed events, and lifecycle in Interface 1.9.
---
-## forcecal-main
-
-The primary Web Component. Renders a full calendar with header navigation, view switching, and event display.
-
-```html
-
-```
-
-## Observed Attributes
-
-These HTML attributes are reactive -- changes trigger re-renders.
-
-| Attribute | Type | Default | Description |
-|-----------|------|---------|-------------|
-| `view` | `string` | `'month'` | `'month'`, `'week'`, `'day'` |
-| `date` | `string` | Today | ISO date string |
-| `locale` | `string` | `'en-US'` | BCP 47 locale |
-| `timezone` | `string` | System | IANA timezone |
-| `week-starts-on` | `string` | `'0'` | 0=Sunday, 1=Monday |
-| `height` | `string` | `'800px'` | CSS height value |
-
-```js
-const el = document.querySelector('forcecal-main');
-el.setAttribute('view', 'week');
-el.setAttribute('timezone', 'Asia/Tokyo');
-```
-
-## JavaScript API
-
-### addEvent(eventData)
+Interface **1.9.0** registers `` and `` when imported in a browser. Named imports from the same entry point also perform registration.
```js
-el.addEvent({
- title: 'Meeting',
- start: new Date('2026-03-01T10:00:00'),
- end: new Date('2026-03-01T11:00:00'),
-});
-```
-
-### updateEvent(eventId, updates)
-
-```js
-el.updateEvent('evt_123', { title: 'Updated Meeting' });
+import '@forcecalendar/interface';
```
-### deleteEvent(eventId)
+| Attribute | JavaScript property | Meaning |
+| --- | --- | --- |
+| `view` | Use `setView(view)` | `month`, `week`, or `day` |
+| `date` | Use `setDate(date)` | Visible date |
+| `locale` | Attribute | BCP 47 locale |
+| `timezone` | Attribute | Core timezone configuration; does not change the browser-local grid |
+| `week-starts-on` | Attribute | `0`–`6`, Sunday through Saturday |
+| `height` | Attribute | CSS height |
+| `theme` | Attribute | Theme preset, including `slds` |
+| `readonly` | `readOnly` | Boolean interaction mode |
+| `show-color-picker` | `showColorPicker` | Explicit `false` hides the editor color picker |
+
+## Public methods
+
+| Method | Result |
+| --- | --- |
+| `addEvent(data)` / `updateEvent(id, updates)` | Event or `null` |
+| `deleteEvent(id)` | Boolean |
+| `getEvents()` | Event array |
+| `setEvents(events, { removeMissing? })` | Change set or `null` while queued |
+| `getVisibleRange()` | `{ start, end }` or `null` before initialization |
+| `setView(view)` / `setDate(date)` | Change view/date |
+| `next()` / `previous()` / `today()` | Navigate |
+| `destroy()` | Tear down owned state and resources |
+
+## Loading events
+
+`setEvents()` and the `events` property take a _complete snapshot_ and reconcile it with what the calendar already holds: unchanged events keep their instance, changed ones are replaced, new ones are added and events missing from the snapshot are removed (pass `{ removeMissing: false }` to keep them). The view re-renders at most once per snapshot.
```js
-el.deleteEvent('evt_123');
-```
+const calendar = document.querySelector('forcecal-main');
-### getEvents()
+// Declarative: reconcile with removeMissing: true
+calendar.events = rows;
-Returns all events from the internal calendar instance.
+// Imperative: the change set is returned and dispatched as calendar-events-set
+const { added, updated, removed, unchanged } = calendar.setEvents(rows, { removeMissing: false });
-### setView(viewType)
-
-```js
-el.setView('week');
-```
-
-### setDate(date)
-
-```js
-el.setDate(new Date('2026-06-15'));
+calendar.addEventListener('calendar-events-set', e => {
+ console.log(e.detail.added.length, 'added', e.detail.removed.length, 'removed');
+});
```
-### next()
-
-Navigate to the next period.
-
-### previous()
+Rules of the road:
-Navigate to the previous period.
+- Every entry must be an object with an `id`; `null`/`undefined` clears the calendar and anything else that is not iterable throws a `TypeError` (nothing is applied).
+- No per-event `calendar-event-add`/`-update`/`-remove` events are dispatched for a snapshot, so listeners that persist user edits are not triggered by a data load.
+- Occurrences of a recurring series (the expanded instances found in view data, `getEventsInRange()` results and click events) are mapped back to their master, so displayed events can be echoed through a snapshot without collapsing the series.
+- Calls made before the element is connected (or after `destroy()`) are queued and replayed in order once it initialises; `setEvents()` returns `null` in that case and `events` reads back the queued snapshot.
+- `calendar-events-set` carries `{ events, added, updated, removed, unchanged }`; `updated` entries are `{ event, oldEvent }` pairs.
-### today()
+### Recurring events
-Navigate to today.
+Rendered chips of a recurring series carry occurrence ids (`_`). Clicking or selecting an occurrence resolves to its master (`stateManager.findEvent(id)`), while details retain the clicked occurrence's time. Built-in drag, resize, Edit and Delete are unavailable for recurring events: changing one occurrence or a whole series requires an explicit scope workflow, which is not supported yet. Host APIs remain available for applications that implement their own scoped editing.
-## Custom Events
+For instances already expanded by an external service (for example Salesforce),
+keep `recurring`/`recurrenceRule` unset and add
+`metadata: { forceCalendarRecurring: true }`. This explicit host marker disables
+built-in Edit/Delete/drag/resize while rendering the ordinary supplied event once;
+it does not generate occurrences or invent an RRULE. Hosts remain responsible for
+server-side recurrence mutation permissions. The standalone editor also declines
+recurring events; imperative host CRUD APIs retain their documented behavior.
-The component dispatches these `CustomEvent`s with `bubbles: true` and `composed: true` (crosses Shadow DOM boundaries).
-
-| Event | `detail` | When |
-|-------|----------|------|
-| `calendar-navigate` | `{ action, date }` | Navigation occurs |
-| `calendar-view-change` | `{ view }` | View type changes |
-| `calendar-event-add` | `{ event }` | Event creation requested |
-| `calendar-event-update` | `{ event }` | Event update requested |
-| `calendar-event-remove` | `{ eventId }` | Event deletion requested |
-| `calendar-event-added` | `{ event }` | Event was added |
-| `calendar-event-updated` | `{ event }` | Event was updated |
-| `calendar-event-deleted` | `{ eventId }` | Event was deleted |
-| `calendar-date-select` | `{ date }` | Date cell clicked |
-
-```js
-el.addEventListener('calendar-event-added', (e) => {
- console.log('Event created:', e.detail.event);
-});
+## Event details and editing
-el.addEventListener('calendar-date-select', (e) => {
- console.log('Date selected:', e.detail.date);
-});
-```
+Click an event, or focus it and press Enter/Space, to open its details: title, the displayed instance's date/time, location and available actions. Times use the browser's local display, as the calendar grid does. The calendar-owned dialog uses ordinary DOM nodes inside the calendar's shadow root, without a portal or the native Popover API.
-## Template Structure
+- **Edit** prefills the existing event. Save updates its stable id and emits the normal `calendar-event-update` / `calendar-event-updated` pair; it does not add a duplicate. Description, attendees, metadata and other host-owned fields are retained. Unchanged date inputs preserve the original full-precision instants, including DST-overlap times.
+- **Delete** first opens an explicit confirmation. Cancel or Escape returns to details; confirming emits the normal remove/deleted pair. Failed saves/deletes leave the draft or confirmation available to retry.
+- Close, backdrop click and Escape dismiss details; Tab stays inside the dialog and focus returns to the event (or Today after deletion). Cancelling the editor discards its draft. Navigation, teardown and enabling read-only mode dismiss active interactions.
+- If the event changes or disappears while being edited, the draft stays visible with an error and cannot overwrite the newer data. Close and reopen the event to edit its current state. Changes to unrelated events or host-owned metadata do not block a save.
+- Recurring details display the clicked occurrence's time, with a clear recurring-event notice. Built-in Edit/Delete are intentionally unavailable for recurring events until an explicit series/occurrence editing workflow is supported; host APIs remain available.
-The component renders this internal structure inside its Shadow DOM:
+The standalone `EventForm` also supports `edit(event)` and `open(start, end)` for prefilled editing and creation ranges. Its cancellable `save` event carries editable fields; a host can call `preventDefault()` and `showError(message)` to retain a failed draft. Its `close` event reports `{ restoreFocus }`. The main calendar wires these to its state manager automatically.
-```
-
-
-
-
-
-
-
-
March 2026
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
+## All-day date boundaries
-## BaseComponent
+All-day editing uses date-only **Start date** and **Last day (inclusive)** fields.
+A same-day event uses the same date in both fields. This preserves Core's existing
+contract: local start-of-day through local 23:59:59.999 on the last included day.
+Dates are parsed as local civil dates, not UTC strings; DST days may span 23 or 25
+hours. Timed events retain exact instants, including unchanged DST-overlap times.
+Adapters for systems with an exclusive end date must convert that boundary using
+calendar-day arithmetic rather than a fixed 24-hour duration.
-All Web Components extend `BaseComponent`, which provides:
+## Optional editor color control
-### Lifecycle
+The color picker remains visible by default. Hosts that cannot persist colors can use
+`` or `calendar.showColorPicker = false`.
+The explicit attribute value `false` hides it; `true`, an empty attribute, or removing
+the attribute shows it. Changing this option while editing keeps the unsaved draft.
+Hidden controls do not reset existing event colors. Standalone `EventForm` supports
+the same attribute and property.
-| Method | Called When |
-|--------|-----------|
-| `initialize()` | Component first connected to DOM |
-| `mount()` | After initialization |
-| `render()` | When state changes require a re-render |
-| `afterRender()` | After render completes |
-| `unmount()` | Component disconnected from DOM |
-| `cleanup()` | Final cleanup (remove listeners) |
+## Read-only interaction mode
-### State Management
+Set the boolean HTML attribute `readonly`, or the reflected JavaScript property `readOnly`, to disable built-in user editing. The default is `false`.
-```js
-// Inside a component
-this.setState({ loading: true });
-const state = this.getState();
+```html
+
```
-### Properties
-
```js
-this.setProp('locale', 'en-US');
-const locale = this.getProp('locale');
+const calendar = document.createElement('forcecal-main');
+calendar.readOnly = true; // Can be set before registration/connection
+calendar.events = rows; // Snapshot hydration still works
+host.appendChild(calendar);
+calendar.readOnly = false; // Re-enable editing at any time
```
-### DOM Helpers
+Attribute spelling is `readonly` (no hyphen); property spelling is `readOnly`. Like native boolean attributes, `readonly="false"` still enables it: remove the attribute or assign `calendar.readOnly = false` to turn it off. Framework adapters, including LWC, should assign the boolean property before inserting the element and whenever their option changes.
-```js
-// Query within shadow root
-const btn = this.$('.fc-btn-today');
-const allBtns = this.$$('.fc-btn');
-```
+- Disables New Event, form creation/saves, event dragging, resizing and drag-to-create in month/week/day views. Resize handles and details Edit/Delete actions are omitted and grids expose `aria-readonly`.
+- Keeps event details, mouse/keyboard event and date selection, grid focus navigation, view switching and date navigation available.
+- Enabling it closes the current details, deletion confirmation or event form, discards its unsaved edits, cancels an active drag/resize/creation gesture and releases the gesture's document listeners. Disabling it restores editing without duplicating listeners. Instances remain independent.
+- Host code can still call `setEvents()`, assign `events`, and call `addEvent()`, `updateEvent()` or `deleteEvent()`. Imperative CRUD retains its usual mutation notifications; snapshots still emit only `calendar-events-set`.
-### Event Emission
+This is a UI interaction option, **not a security or authorization boundary**. Host-provided editors/context menus must also honor the option, and applications must enforce permissions and validate all writes on the server.
-```js
-// Emit a custom event that crosses shadow DOM boundaries
-this.emit('calendar-navigate', { date: new Date(), view: 'month' });
-```
+## Visible range
-### Listener Management
+`getVisibleRange()` returns the `{ start, end }` window the current view covers, including the leading and trailing other-month days of the month grid. `end` is inclusive (the last millisecond of the window), so the pair can be passed straight to a range query. The window is expressed in the browser's local time zone regardless of the `timezone` attribute, and it is computed from the date, view and week start alone, so it is cheap to call.
-Listeners registered via `addListener()` are automatically cleaned up on disconnect:
+`calendar-range-change` fires whenever the window changes (navigation, view switch, `week-starts-on`), after the `calendar-navigate`/`calendar-view-change` event that caused it, with `{ start, end, view, date }`. It is also announced once per attach, on the next macrotask after the element is connected, so listeners added right after `appendChild` (framework refs and effects, a lazy `customElements.define()`) receive it. `getVisibleRange()` is the source of truth if you need the window synchronously.
```js
-this.addListener(this.$('.fc-nav-arrow'), 'click', () => this.previous());
+calendar.addEventListener('calendar-range-change', async e => {
+ calendar.events = await api.fetchEvents(e.detail.start, e.detail.end);
+});
```
-### Re-render Preservation
-
-`BaseComponent` preserves scroll positions and focus across re-renders. Before rendering, it saves the current scroll position and active element reference. After rendering, it restores both.
-
-## View Renderers
+## Lifecycle: detach and destroy
-Each view has a dedicated renderer that produces DOM content:
+Removing the element from the document releases its rendered tree, DOM listeners and view timers but keeps its state (view, date, events) and keeps dispatching `calendar-*` events for API calls, so a re-attach (framework reconciliation, portals, StrictMode double-mount) picks up where it left off. Attribute changes made while detached are applied to state and rendered on the next attach.
-### MonthViewRenderer
+`destroy()` tears the state manager and its owned Core Calendar down, including background maintenance timers (use Core 2.5.4 or later for full timer cleanup). Call it when you are finished with an element permanently; detaching alone intentionally preserves the calendar. Repeated calls are safe. Afterwards the public API no-ops or queues instead of throwing (`events` and `getEvents()` read the queued snapshot, `getVisibleRange()` is `null`, `setEvents()` queues, CRUD mutations return `null`/`false`, and navigation no-ops) and the next attach initialises a fresh calendar from the attributes and any queued snapshot.
-Renders a grid of weeks. Each day cell shows up to 3 events with a "+N more" overflow indicator.
+## Events
-- Pure JS rendering (no innerHTML) for Locker Service compatibility
-- Respects `weekStartsOn` configuration
-- Highlights today and selected date
-- Shows week numbers when enabled
+| Event | `detail` |
+| -------------------------------------------------- | ------------------------------------------------------------- |
+| `calendar-navigate` | `{ action: 'next' \| 'previous' \| 'today' \| 'goto', date }` |
+| `calendar-view-change` | `{ view }` |
+| `calendar-range-change` | `{ start, end, view, date }` |
+| `calendar-events-set` | `{ events, added, updated, removed, unchanged }` |
+| `calendar-event-add` / `calendar-event-added` | `{ event }` |
+| `calendar-event-update` / `calendar-event-updated` | `{ event }` |
+| `calendar-event-remove` / `calendar-event-deleted` | `{ eventId }` |
+| `calendar-date-select` | `{ date }` |
+| `calendar-range-select` | `{ start, end }` |
-### WeekViewRenderer
+All events bubble and are composed.
-Renders a 7-day timeline with hourly slots.
+## TypeScript
-- All-day events displayed in a header row
-- Timed events positioned based on start/end times
-- Business hours highlighted
-- Overlap groups handled via `calculateEventPositions()`
+The package ships declarations generated from JSDoc plus DOM typings for the element, so `document.createElement('forcecal-main')` is a `ForceCalendarElement` and `addEventListener` is typed for every `calendar-*` event:
-### DayViewRenderer
+```ts
+import type { ForceCalendarElement, CalendarEvent } from '@forcecalendar/interface';
-Renders a single-day timeline with hourly slots.
-
-- Same layout principles as WeekViewRenderer
-- More detail per event (full title, time, location)
-- All-day events in header
+const calendar = document.createElement('forcecal-main');
+calendar.addEventListener('calendar-events-set', e => {
+ const added: CalendarEvent[] = e.detail.added;
+});
+```
diff --git a/content/docs/meta.json b/content/docs/meta.json
index 203ca15..7db348a 100644
--- a/content/docs/meta.json
+++ b/content/docs/meta.json
@@ -5,6 +5,7 @@
"whats-new",
"quick-start",
"installation",
+ "migration",
"---Core Package---",
"core",
"---Interface Package---",
@@ -13,9 +14,11 @@
"adapters",
"---Salesforce---",
"salesforce",
+ "---Agent Calendars---",
+ "agent",
"---Security---",
"security",
"---API Reference---",
"api"
]
-}
\ No newline at end of file
+}
diff --git a/content/docs/migration.mdx b/content/docs/migration.mdx
new file mode 100644
index 0000000..36ab986
--- /dev/null
+++ b/content/docs/migration.mdx
@@ -0,0 +1,53 @@
+---
+title: Migration and compatibility
+description: Upgrade guidance for package versions, snapshots, events, dates, SSR, and Salesforce.
+---
+
+## Recommended release set
+
+Use Core **2.5.6**, Interface **1.9.0**, and React/Vue **0.3.1** together. Pin versions for rollout, commit the lockfile, and test your actual persistence callbacks before deployment. Salesforce 0.3 uses a matching bundled resource and remains beta.
+
+## Snapshot loading replaces CRUD loops
+
+Use `element.setEvents(rows)` or `element.events = rows` for a server snapshot. Every item needs a stable ID. Missing events are removed unless `{ removeMissing: false }` is passed. Snapshots emit only `calendar-events-set`; do not synthesize mutation callbacks while hydrating.
+
+Adapter `events` props update by array identity. Replace the array (React state/memo or Vue ref/computed), rather than mutating it in place. Adapter `undefined` leaves state alone; use `[]` to clear. Raw element `events = null` clears the snapshot.
+
+## Subscribe once per mutation
+
+The Interface emits legacy and current spellings together: `calendar-event-add`/`calendar-event-added`, `-update`/`-updated`, `-remove`/`-deleted`. These are aliases, not separate request and server-success stages. Listen to one per action. Adapters expose only the current spellings.
+
+Persistence belongs to the host. Reject unauthorized writes server-side, keep the stable ID across saves, and reload confirmed data after an error. Protect async range fetches from overwriting a newer visible range.
+
+## Read-only mode
+
+Interface uses `readonly` as a boolean HTML attribute and `readOnly` as a boolean property. `readonly="false"` is still enabled. Remove the attribute or set `element.readOnly = false`.
+
+React/Vue 0.3.1 accept a boolean `readOnly` prop. Use Interface 1.8 or later. This blocks built-in interaction, not programmatic APIs or server writes. The Salesforce wrapper additionally guards its persistence callbacks and public `addEvent` method; Apex independently enforces permissions.
+
+## Event editor and recurrence
+
+Interface 1.9 adds details, prefilled editing and confirmed deletion. Existing IDs and non-editable event fields are retained. A concurrent change to an edited field blocks a stale save; reopen details to use the current event.
+
+Built-in recurring-event edit/delete/drag/resize is unavailable. Hosts supplying already-expanded instances should use `metadata: { forceCalendarRecurring: true }`, leaving `recurring` and `recurrenceRule` unset. This protects UI mutation without causing a second expansion. It is not server-side authorization.
+
+If your backend has no color field, set `showColorPicker = false` or `show-color-picker="false"`. Hiding it retains the existing color.
+
+## Dates and timezones
+
+- Timed events represent instants; agree on timezone interpretation at the boundary. An offset-free datetime or `new Date('YYYY-MM-DD')` is not a safe universal civil-date parser.
+- Core/Interface all-day values cover local start-of-day through **23:59:59.999 on the inclusive last day**. Use `new Date(year, monthIndex, day)` for local civil dates. A same-day event has the same start/last-day date.
+- External APIs and ICS often use an exclusive end date. Convert explicitly with calendar-day arithmetic; never assume every day lasts 24 hours.
+- The Salesforce adapter has separate `startDate`/`lastDay` civil-date transport. Its native UTC-date representation must not be shifted through the browser timezone or corrected by blindly subtracting a day.
+- `getVisibleRange()` has an inclusive end and uses the browser's local timezone. The Interface `timezone` attribute does not make the grid render in an arbitrary IANA zone.
+- Core 2.5.6 preserves `endTimeZone` during clone/serialization. A [cross-host V2 recurrence issue](/docs/core/recurrence#cross-host-dst-limitation) remains a release boundary; validate recurrence in both the event zone and the deployment host zone.
+
+## Lifecycle and SSR
+
+Detach preserves state for reattachment. `destroy()` permanently releases owned Core state/timers; Core 2.5.4 or later is needed for complete timer cleanup. Reattaching a destroyed element initializes a fresh calendar and replays queued snapshots.
+
+Adapters register on the client. Await the ref's `whenReady()` before imperative methods, and use a client component for interactive Next.js usage. Interface 1.7+ owns the DOM tag-name type mapping; adapter structural event types no longer redeclare it.
+
+## Salesforce upgrade
+
+Deploy LWC, Apex and static resources together. The 0.3 LWC calls date-aware `createCalendarEvent`/`updateCalendarEvent`; installing only its JavaScript against an older controller is unsupported. Enable LWS, select an optional permission set deliberately, and test as intended users. Native Salesforce recurrence remains managed in Salesforce Calendar. See [deployment](/docs/salesforce/deployment).
diff --git a/content/docs/quick-start.mdx b/content/docs/quick-start.mdx
index fd0c370..53fde21 100644
--- a/content/docs/quick-start.mdx
+++ b/content/docs/quick-start.mdx
@@ -1,178 +1,89 @@
---
title: Quick Start
-description: Get a working forceCalendar instance in under 5 minutes.
+description: Render a calendar, apply a snapshot, and listen without duplicating persistence.
---
-## Browser / Vanilla JS
+## Render a browser calendar
-Import the core package and create a calendar:
+Install Core 2.5.6 and Interface 1.9.0, then include the element:
-```js
-import { Calendar } from '@forcecalendar/core';
-
-const calendar = new Calendar({
- view: 'month',
- date: new Date(),
- weekStartsOn: 0, // 0 = Sunday, 1 = Monday
- locale: 'en-US',
- timeZone: 'America/New_York',
- showWeekNumbers: false,
- showWeekends: true,
- fixedWeekCount: true, // Always show 6 weeks in month view
- businessHours: {
- start: '09:00',
- end: '17:00',
- },
-});
+```html
+
```
-## Adding Events
+In your browser entry module:
```js
-// Add a single event
-calendar.addEvent({
- title: 'Team Standup',
- start: new Date('2026-03-01T09:00:00'),
- end: new Date('2026-03-01T09:30:00'),
- location: 'Conference Room A',
- color: '#4285f4',
-});
+import '@forcecalendar/interface';
-// Add a recurring event
-calendar.addEvent({
- title: 'Weekly Review',
- start: new Date('2026-03-02T14:00:00'),
- end: new Date('2026-03-02T15:00:00'),
- recurring: true,
- recurrenceRule: 'FREQ=WEEKLY;BYDAY=MO;COUNT=12',
-});
+const calendar = document.querySelector('#calendar');
+calendar.events = [
+ {
+ id: 'planning',
+ title: 'Planning',
+ start: new Date(2026, 9, 5, 9),
+ end: new Date(2026, 9, 5, 10),
+ location: 'Room A',
+ },
+ {
+ id: 'offsite',
+ title: 'Team offsite',
+ allDay: true,
+ start: new Date(2026, 9, 6),
+ end: new Date(2026, 9, 7, 23, 59, 59, 999),
+ },
+];
-// Add an all-day event
-calendar.addEvent({
- title: 'Company Holiday',
- start: new Date('2026-03-15'),
- allDay: true,
- color: '#34a853',
+calendar.addEventListener('calendar-events-set', ({ detail }) => {
+ console.log('Snapshot applied:', detail.added.length, 'new events');
});
-```
-
-## Querying Events
-
-```js
-// Get events for a specific date
-const todayEvents = calendar.getEventsForDate(new Date());
-
-// Get events in a date range
-const marchEvents = calendar.getEventsInRange(
- new Date('2026-03-01'),
- new Date('2026-03-31')
-);
-
-// Query with filters
-const meetings = calendar.queryEvents({
- categories: ['meeting'],
- status: 'confirmed',
- start: new Date('2026-03-01'),
- end: new Date('2026-03-31'),
+calendar.addEventListener('calendar-event-updated', ({ detail }) => {
+ console.log('Local change to persist:', detail.event.id);
});
```
-## Navigation
-
-```js
-// Navigate between periods
-calendar.next(); // Next month/week/day (based on current view)
-calendar.previous(); // Previous month/week/day
-calendar.today(); // Jump to today
-
-// Switch views
-calendar.setView('week');
-calendar.setView('day');
-calendar.setView('month');
-calendar.setView('list');
-
-// Jump to a specific date
-calendar.goToDate(new Date('2026-06-15'));
-```
-
-## Listening to Events
-
-```js
-calendar.on('eventAdd', ({ event }) => {
- console.log('Event added:', event.title);
-});
-
-calendar.on('navigate', ({ date, view }) => {
- console.log(`Navigated to ${date} in ${view} view`);
-});
+These examples deliberately construct local civil dates: JavaScript's month index is zero-based. The offsite covers October 6–7 inclusive. For API data, agree on the source timezone and all-day boundary before converting it.
-calendar.on('viewChange', ({ view }) => {
- console.log(`View changed to: ${view}`);
-});
+Click an event to view its details, edit it, or confirm deletion. Those actions change local calendar state. Connect your own backend, handle rejected writes and refresh authoritative state; the npm Interface does not save to a server for you.
-calendar.on('eventUpdate', ({ event, oldEvent }) => {
- console.log('Event updated:', event.id, oldEvent);
-});
+## Snapshots and range loading
-calendar.on('eventRemove', ({ event }) => {
- console.log('Event removed:', event.id);
-});
-```
-
-## Web Component Usage
-
-If using `@forcecalendar/interface`, drop the custom element into your HTML:
+`events`/`setEvents()` reconcile a complete snapshot. A snapshot emits one `calendar-events-set`, not per-event mutation events, so loading server data does not trigger duplicate saves.
-```html
-
-```
+Use `calendar-range-change` to request the visible window. It fires initially and after navigation/view changes; `{ start, end }` has an inclusive end and uses the browser's local timezone. Protect asynchronous requests from out-of-order responses:
```js
-import '@forcecalendar/interface';
-
-const el = document.querySelector('forcecal-main');
-
-// Add events via the element API
-el.addEvent({
- title: 'Meeting',
- start: new Date('2026-03-01T10:00:00'),
- end: new Date('2026-03-01T11:00:00'),
-});
-
-// Listen for calendar events
-el.addEventListener('calendar-event-added', (e) => {
- console.log('Added:', e.detail);
+let request = 0;
+async function loadRange(start, end) {
+ const current = ++request;
+ // Implement fetchEvents in your application with authorization and error handling.
+ const rows = await fetchEvents(start, end);
+ if (current === request && calendar.isConnected) calendar.events = rows;
+}
+calendar.addEventListener('calendar-range-change', ({ detail }) => {
+ loadRange(detail.start, detail.end).catch(console.error);
});
+const initialRange = calendar.getVisibleRange();
+if (initialRange) loadRange(initialRange.start, initialRange.end).catch(console.error);
```
-## Salesforce LWC Usage
+For a single-event mutation use `addEvent`, `updateEvent` or `deleteEvent`. Listen to one spelling of each event: the legacy `-add` and current `-added` forms are aliases for the same mutation, not separate request/success stages.
-In a Lightning Web Component:
-
-```html
-
-
-
-
-```
+## Headless Core
```js
-// myComponent.js
-import { LightningElement, api } from 'lwc';
+import { Calendar } from '@forcecalendar/core';
-export default class MyComponent extends LightningElement {
- @api recordId;
+const calendar = new Calendar({ view: 'month', date: new Date(2026, 9, 5) });
+try {
+ calendar.addEvent({
+ id: 'planning', title: 'Planning',
+ start: new Date(2026, 9, 5, 9), end: new Date(2026, 9, 5, 10),
+ });
+ console.log(calendar.getEventsForDate(new Date(2026, 9, 5)));
+} finally {
+ calendar.destroy();
}
```
-The LWC wrapper handles Apex CRUD operations automatically via `@wire`.
+Core returns data and does not render a UI. Use the [React](/docs/adapters/react), [Vue](/docs/adapters/vue), or [Salesforce](/docs/salesforce) guide for those hosts. Continue with [Web Component methods](/docs/interface/web-components) and [migration](/docs/migration).
diff --git a/content/docs/salesforce/apex-controller.mdx b/content/docs/salesforce/apex-controller.mdx
index 7e0c4ee..5c59e88 100644
--- a/content/docs/salesforce/apex-controller.mdx
+++ b/content/docs/salesforce/apex-controller.mdx
@@ -1,200 +1,39 @@
---
title: Apex Controller
-description: ForceCalendarController -- SOQL queries, CRUD operations, and security enforcement.
+description: Date-aware APIs, overlap queries, record association, and user-mode authorization.
---
-## Overview
+`ForceCalendarController` is declared `public with sharing`. Queries and writes use Salesforce user-mode enforcement plus explicit validation. Deploy the controller together with the 0.3 LWC and resource.
-`ForceCalendarController` is the server-side Apex class that handles data access for the forceCalendar LWC. It uses `with sharing` to respect the running user's sharing rules and `WITH SECURITY_ENFORCED` in all SOQL queries.
+## Current LWC entry points
-```apex
-public with sharing class ForceCalendarController {
- // All methods use security enforcement
-}
-```
+| Method | Parameters | Result |
+| --- | --- | --- |
+| `getEvents` | `Datetime startDateTime, Datetime endDateTime, Id recordId` | `List