[Feature][Calendar] Family calendar overlay — parents see + manage children's events with per-child toggles (epic #781 adjacent) #788

Closed
opened 2026-07-22 05:22:16 +00:00 by spikerj · 1 comment
Owner

A parent's calendar shows each linked child's events overlaid, with a per-child visibility toggle, so the family can compose a workable schedule (Joey, 2026-07-22).

Locked decisions: parent can EDIT/move/delete child events (full write path, server-authorized); per-device toggle persistence (localStorage); plain parental oversight (no child-side flag — consistent with activity/vault/screen-time); all children visible by default.

Design (verified against code):

  • Backend: GetChildCalendarEventsQuery(Parent, Child, range)uncached (cache hit would skip authz), guard = the GetChildActivity parent-link precedent via new CalendarParentalAccess.EnsureOwnerOrParentAsync, then nested send of the child-keyed GetCalendarEventsByDateRangeQuery (shares the child's cached+recurrence-expanded entries; invalidated by child writes for free). New GET api/calendar/children/{childUserId}/events. Write path: byId/Update/Delete/Position handlers load by id then owner-or-parent guard; commands gain handler-set OwnerUserId for cache invalidation (CacheInvalidationBehavior resolves placeholders post-handler).
  • Fixes two pre-existing cache bugs in passing: UpdateCalendarEventPositionCommand had NO [CacheInvalidation] (drag/drop left the 5-min range cache stale), and the calendar:event:{EventId} pattern never matched the stored calendar:event:{id}:{userId} keys (byId cache never invalidated).
  • Angular: getChildEvents fan-out per visible child in the FullCalendar events callback (forkJoin, per-child error isolation), deterministic per-child color palette + owner name pill via the existing #eventContent template, per-child mat-slide-toggle family bar (parent-dashboard idiom), BrowserStorageService persistence. Reminder scheduling on parent-edited events follows the event OWNER.
  • No CalendarEvent schema changes (deliberately — the #786 required-field trap).

Explicit follow-up (separate ticket): create-for-child from the parent view (owner selector in the event modal; backend is nearly free — CreatedBy = childId through the existing create command).

A parent's calendar shows each linked child's events overlaid, with a per-child visibility toggle, so the family can compose a workable schedule (Joey, 2026-07-22). **Locked decisions:** parent can EDIT/move/delete child events (full write path, server-authorized); per-device toggle persistence (localStorage); plain parental oversight (no child-side flag — consistent with activity/vault/screen-time); all children visible by default. **Design (verified against code):** - Backend: `GetChildCalendarEventsQuery(Parent, Child, range)` — **uncached** (cache hit would skip authz), guard = the `GetChildActivity` parent-link precedent via new `CalendarParentalAccess.EnsureOwnerOrParentAsync`, then nested send of the child-keyed `GetCalendarEventsByDateRangeQuery` (shares the child's cached+recurrence-expanded entries; invalidated by child writes for free). New `GET api/calendar/children/{childUserId}/events`. Write path: byId/Update/Delete/Position handlers load by id then owner-or-parent guard; commands gain handler-set `OwnerUserId` for cache invalidation (CacheInvalidationBehavior resolves placeholders post-handler). - **Fixes two pre-existing cache bugs in passing:** `UpdateCalendarEventPositionCommand` had NO `[CacheInvalidation]` (drag/drop left the 5-min range cache stale), and the `calendar:event:{EventId}` pattern never matched the stored `calendar:event:{id}:{userId}` keys (byId cache never invalidated). - Angular: `getChildEvents` fan-out per visible child in the FullCalendar events callback (forkJoin, per-child error isolation), deterministic per-child color palette + owner name pill via the existing `#eventContent` template, per-child `mat-slide-toggle` family bar (parent-dashboard idiom), `BrowserStorageService` persistence. Reminder scheduling on parent-edited events follows the event OWNER. - No CalendarEvent schema changes (deliberately — the #786 required-field trap). **Explicit follow-up (separate ticket):** create-for-child from the parent view (owner selector in the event modal; backend is nearly free — `CreatedBy = childId` through the existing create command).
Author
Owner

Resolved — both halves merged to master:

  • spikersoft-backend PR #453 (merged 05:36): GET api/calendar/children/{childUserId}/events (uncached authz query → nested child-keyed range query, sharing the child's cached recurrence-expanded entries); owner-or-parent authorization on byId/Update/Delete/Position via CalendarParentalAccess; owner-keyed cache invalidation via handler-set OwnerUserId (also fixed the two pre-existing invalidation bugs: position command had none, byId pattern never matched); reminders follow the event owner. 87 calendar tests green incl. the invalidation contract lock.
  • spikersoft-angular PR #527 (merged 05:37): family bar with per-child mat-slide-toggle + color dot (parents only), per-visible-child fan-out with error isolation, deterministic per-child palette + owner pill, per-device visibility persistence (default all visible). 23/23 specs green.

Verification runbook (post-deploy): as e2e-child1 create a timed + weekly recurring event → as e2e-parent1 /calendar shows both in the child's color with the name pill; toggle hides/persists across reload; drag the child's event as parent, then confirm as the child the new time shows immediately (owner-keyed invalidation); unrelated user gets 403 on children/{id}/events and on PUT of a child event.

Follow-up create-for-child is #790. Closing.

Resolved — both halves merged to master: - **spikersoft-backend PR #453** (merged 05:36): `GET api/calendar/children/{childUserId}/events` (uncached authz query → nested child-keyed range query, sharing the child's cached recurrence-expanded entries); owner-or-parent authorization on byId/Update/Delete/Position via `CalendarParentalAccess`; owner-keyed cache invalidation via handler-set `OwnerUserId` (also fixed the two pre-existing invalidation bugs: position command had none, byId pattern never matched); reminders follow the event owner. 87 calendar tests green incl. the invalidation contract lock. - **spikersoft-angular PR #527** (merged 05:37): family bar with per-child `mat-slide-toggle` + color dot (parents only), per-visible-child fan-out with error isolation, deterministic per-child palette + owner pill, per-device visibility persistence (default all visible). 23/23 specs green. Verification runbook (post-deploy): as e2e-child1 create a timed + weekly recurring event → as e2e-parent1 `/calendar` shows both in the child's color with the name pill; toggle hides/persists across reload; **drag the child's event as parent, then confirm as the child the new time shows immediately** (owner-keyed invalidation); unrelated user gets 403 on `children/{id}/events` and on PUT of a child event. Follow-up create-for-child is #790. Closing.
Sign in to join this conversation.