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).
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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):
GetChildCalendarEventsQuery(Parent, Child, range)— uncached (cache hit would skip authz), guard = theGetChildActivityparent-link precedent via newCalendarParentalAccess.EnsureOwnerOrParentAsync, then nested send of the child-keyedGetCalendarEventsByDateRangeQuery(shares the child's cached+recurrence-expanded entries; invalidated by child writes for free). NewGET api/calendar/children/{childUserId}/events. Write path: byId/Update/Delete/Position handlers load by id then owner-or-parent guard; commands gain handler-setOwnerUserIdfor cache invalidation (CacheInvalidationBehavior resolves placeholders post-handler).UpdateCalendarEventPositionCommandhad NO[CacheInvalidation](drag/drop left the 5-min range cache stale), and thecalendar:event:{EventId}pattern never matched the storedcalendar:event:{id}:{userId}keys (byId cache never invalidated).getChildEventsfan-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#eventContenttemplate, per-childmat-slide-togglefamily bar (parent-dashboard idiom),BrowserStorageServicepersistence. Reminder scheduling on parent-edited events follows the event OWNER.Explicit follow-up (separate ticket): create-for-child from the parent view (owner selector in the event modal; backend is nearly free —
CreatedBy = childIdthrough the existing create command).Resolved — both halves merged to master:
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 viaCalendarParentalAccess; owner-keyed cache invalidation via handler-setOwnerUserId(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.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
/calendarshows 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 onchildren/{id}/eventsand on PUT of a child event.Follow-up create-for-child is #790. Closing.