### Description
Takes over #2807 (the "calendar" external integration type — sp…ec and milestone 1 implementation, written this summer) in a fresh PR: the branch is brought up to date with `master`, and the spec and every modified file were reviewed again against what `master` has become since. #2807 can be closed in favor of this one.
A calendar provider (Nextcloud/CalDAV servers, iCloud, token-based APIs, public ICS feeds) becomes an ordinary external integration that pushes calendars and events into the core calendar store, feeding the same calendar view, chips box, scene trigger/actions and MCP tool as the internal CalDAV service. Push model: the integration syncs, the core stores. Per-user OAuth2 (Google, Outlook) stays design-only (milestone 2).
**What the original PR brings** (unchanged in substance, see #2807 for the details):
- `lib/calendar`: transactional `upsertCalendars` / `upsertEvents` (window prune by overlap, 10 000 events per calendar, event moves, rows of another owner never stolen), ownership checks and user-editable field whitelists on the user calendar routes, `CALENDAR_TYPES`, the duplicated `calendar.associate` fixed;
- external-integration supervisor: `type: "calendar"`, `account_schema` (per-user accounts, enable/disable as the consent gesture), user-scoped `ext:<selector>:<user_selector>:` ids, host API `GET/POST/DELETE /api/integration/v1/calendar`, `GET .../calendar/account`, `POST .../calendar/event`, management routes `.../:selector/calendar/account` and `PATCH .../:selector/calendar/:calendar_selector`, `calendar.updated` / `external-integration.calendar.account-updated` pushes, write rate limit, explicit uninstall cleanup;
- front: "My calendars" block, live refresh of the calendar view, calendar integrations routed to the configuration screen.
**Merge with master**
- `provider` type (#3109), dashboard widgets and scene declarations (#3110): `calendar` added next to `provider` in `MANIFEST_TYPES`, the vendored `manifest.schema.json` (enum + `account_schema` rule, merged with the `provider` rule) and the shared front list `TYPES_WITHOUT_DEVICE_SCREENS` (tabs, catalog URL, discover/device guards, and now the post-install redirect).
- `ConfigSchemaForm` moved to `components/integration/` on master: the `CalendarAccountCard` import was broken by the merge (front build failure), fixed.
- Spanish translation (#3119): the new keys added to `es.json`.
**Review fixes**
- Spec: the file no longer allocates the "B.19" identifier (the spec README forbids new identifiers and says "there is no B.19"); every reference now cites `capabilities/calendar-type.md`, and the README/AGENTS.md edits that listed capabilities are reverted (the README never lists them). The "current state" section is now a dated "state before this workstream", and a new paragraph records why calendar is a type and not a capability field, against the bar set by `provider-type.md` (which, with `integration-catalog-categories.md` §2.2, now counts the calendar type).
- Privacy: a publication batch mixing shared and private calendars, or an event moving between a shared and a private calendar, broadcast the private calendars' selectors (their slugified names) to every connected user. All `calendar.updated` pushes now go through one helper, `notifyCalendarsUpdated`, that sends each calendar to its own audience.
- Isolation: the user creation routes (`POST /api/v1/calendar`, `POST /api/v1/calendar/:selector/event`) accepted any `external_id`. A household member could squat the user-scoped id another member's integration would push, and that member's sync then failed with 409. The `ext:` namespace is now reserved to the integrations (`400`).
- Events cap: the window prune now runs before the upsert, in the same transaction and with the same final state. The 10 000-event cap is then measured on the resulting calendar, so a window republished at the cap is no longer refused because of the rows it replaces.
- Install screen: the calendar information line the spec promised was missing, now added (en/fr/de/es).
- Calendar view: an event without `end` (optional in the contract) rendered in 1970; it now renders at its start.
- `account_schema` validation reuses `ACCOUNT_FIELD_TYPES`; small comment and constant cleanups.
No DB migration. No new device feature category.
**Cross-repo follow-ups** (listed in the spec): the canonical manifest schema of `GladysAssistant/integration-store` must accept `type: "calendar"` + `account_schema` before a calendar integration can be published to the store, and the SDK methods land in `integration-sdk-js`.
### Related request
Forum: https://community.gladysassistant.com/t/10432
Supersedes #2807.
### Checklist
- [x] If a forum topic or GitHub issue exists, the description links it (`Forum: https://community.gladysassistant.com/t/...` or `Closes #...`)
- [ ] Tests pass: `cd server && npm run coverage` (Codecov requires 100% coverage on changed lines) and Cypress (`npm run cypress:run`) if the UI changed — locally: every calendar / external-integration test passes and every changed server line is covered; the only failures are environmental (Gladys Plus gateway network calls, no `sqlite3` CLI, no Docker) and unrelated to this diff. Cypress could not run locally (binary download blocked), left to CI.
- [x] Linter and prettier pass on both front and server (`npm run eslint`, `npm run prettier`)
- [x] No undocumented breaking change
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01TaHFVjnhhWusXLXL9wCpTu
---
_Generated by [Claude Code](https://claude.ai/code/session_01TaHFVjnhhWusXLXL9wCpTu)_