Make availability, the existing /calendar, and iCal one integrated scheduling system. "When is this person/kid free" is a general platform primitive — first consumer is trip scheduling (find weeks everyone's free → realistic date-anchored budgets), next is planning classes/lessons around a child's public-school calendar and existing sports.
This also fixes a real gap: the calendar had no recurrence (dead RecurringType enum; Ical.Net referenced but unused; ICS export wrote single VEVENTs).
Architecture
One Ical.Net/RRULE recurrence engine → expands calendar events → availability projection (weekly Free/Limited/Busy) → IntersectFreeWeeks (trip scheduling) + day+time fit (class scheduling). Commitments (school/sports/work) are recurring calendar events flagged availability-affecting.
Phases
Phase 1 — Recurrence engine (foundation). Ical.Net expansion service; RRULE fields on CalendarEvent; range query expands series; recurrence editor in the calendar event modal; RRULE ICS export. ✅ implemented — backend spikersoft-backend PR + angular spikersoft-angular PR (linked below).
Phase 2 — Availability projection + Schedule tab.AvailabilityProfile sub-document; ExpandWeeks/IntersectFreeWeeks; dedicated Schedule profile tab (deduce vs direct modes, presets, week grid); parent-manages-child. Threaded through every UpdateProfile layer.
Week-level ratings Free/Limited/Busy; user-chosen week-start normalized to dates; two input modes (deduce from commitments / enter free weeks directly); commitments carry day-of-week + morning/afternoon/evening bands; dedicated Schedule tab; parents edit children's; unified Ical.Net engine. Not touching sponsor cards; not building the missing /api/sponsor/families backend.
Known follow-ups
Per-occurrence reminders for recurring events (current delayed-reminder pipeline can't cancel / schedules one delay).
Latent Social profile-section silent-drop bug (Angular saves {social} the controller never maps) — Phase 2 threads availability through every layer to avoid the same class.
No open child issues were produced by the migration — either this epic's
work was already complete, or its scope needs to be broken down into
per-repo issues before it can progress.
Checklist generated by the umbrella-tracker migration, 2026-08-07 — Opus 5 Agent
## Goal
Make availability, the existing `/calendar`, and iCal one integrated scheduling system. "When is this person/kid free" is a general platform primitive — first consumer is **trip scheduling** (find weeks everyone's free → realistic date-anchored budgets), next is **planning classes/lessons around a child's public-school calendar and existing sports**.
This also fixes a real gap: the calendar had **no recurrence** (dead `RecurringType` enum; `Ical.Net` referenced but unused; ICS export wrote single VEVENTs).
## Architecture
One `Ical.Net`/RRULE recurrence engine → expands calendar events → availability projection (weekly Free/Limited/Busy) → `IntersectFreeWeeks` (trip scheduling) + day+time fit (class scheduling). Commitments (school/sports/work) are recurring calendar events flagged availability-affecting.
## Phases
- **Phase 1 — Recurrence engine (foundation).** Ical.Net expansion service; RRULE fields on `CalendarEvent`; range query expands series; recurrence editor in the calendar event modal; RRULE ICS export. ✅ implemented — backend `spikersoft-backend` PR + angular `spikersoft-angular` PR (linked below).
- **Phase 2 — Availability projection + Schedule tab.** `AvailabilityProfile` sub-document; `ExpandWeeks`/`IntersectFreeWeeks`; dedicated `Schedule` profile tab (deduce vs direct modes, presets, week grid); parent-manages-child. Threaded through every UpdateProfile layer.
- **Phase 3 — iCal import / external calendars.** `.ics` upload (school/Google) → events → availability; `.ics` export.
## Confirmed decisions
Week-level ratings Free/Limited/Busy; user-chosen week-start normalized to dates; two input modes (deduce from commitments / enter free weeks directly); commitments carry day-of-week + morning/afternoon/evening bands; dedicated Schedule tab; parents edit children's; unified Ical.Net engine. Not touching sponsor cards; not building the missing `/api/sponsor/families` backend.
## Known follow-ups
- Per-occurrence **reminders** for recurring events (current delayed-reminder pipeline can't cancel / schedules one delay).
- Latent `Social` profile-section silent-drop bug (Angular saves `{social}` the controller never maps) — Phase 2 threads availability through every layer to avoid the same class.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- BEGIN MIGRATED-CHILDREN -->
---
## Child issues (per-repo, auto-close on merge)
_No open child issues were produced by the migration — either this epic's
work was already complete, or its scope needs to be broken down into
per-repo issues before it can progress._
<sub>Checklist generated by the umbrella-tracker migration, 2026-08-07 — Opus 5 Agent</sub>
<!-- END MIGRATED-CHILDREN -->
Separately surfaced during this work (worth their own tickets): the Social profile-section silent-drop bug (Angular PUTs {social} the controller never maps), and per-occurrence reminders for recurring calendar events (the delayed-reminder pipeline can't cancel / schedules one delay).
Progress — **Phase 1 and Phase 2 are merged.**
**Phase 1 (recurrence engine)** ✅
- spikersoft-backend **#448** — `RecurrenceExpansionService` (Ical.Net/RRULE), recurrence + availability fields on `CalendarEvent`, range query expands series. (Closed the calendar's no-recurrence gap.)
- spikersoft-angular **#513** — recurrence editor in the event modal, series-targeted edit/delete, RRULE `.ics` export.
**Phase 2 (availability projection)** ✅
- spikersoft-backend **#449** — `AvailabilityProfile` + `AvailabilityService` (`ExpandWeeks`/`IntersectFreeWeeks`), threaded through every UpdateProfile layer, parent-manages-child endpoint.
- spikersoft-backend **#450** — `GET /api/profile/availability/weeks` (derived grid).
- spikersoft-angular **#515** — the `Schedule` profile tab (`profile?tab=schedule`): week-start, deduce/direct mode, preset, 52-week paint-to-override grid.
**Remaining (keeping this epic open):**
- Phase 2 follow-up: **commitment editor** for deduce mode (create availability-affecting recurring `CalendarEvent`s from the preset + custom entries).
- Phase 2 follow-up: **parent-manages-child** UI selector (service method + `PUT /api/profile/parental/availability/{childUserId}` already exist).
- **Phase 3** — iCal `.ics` import (school/Google calendars → availability) + export.
Separately surfaced during this work (worth their own tickets): the `Social` profile-section **silent-drop** bug (Angular PUTs `{social}` the controller never maps), and **per-occurrence reminders** for recurring calendar events (the delayed-reminder pipeline can't cancel / schedules one delay).
Epic status roll-up — audited all three phases plus both known follow-ups against origin/master. All three phases have shipped, including Phase 3, which the body still lists as unstarted.
Phase 3 is done and the epic body doesn't reflect it.CalendarController.cs:830 — ImportIcs(IFormFile file, [FromForm] string? source = null) with a CalendarImportResultDto (:826) and rejection logging (:860). Backed by a real parser: SpikerSoft.Business.Scheduling/Calendar/IcsImportParser.cs, DI-registered at ServiceCollectionExtensions.cs:1109-1111 alongside ICalendarImportService. The parser handles the two things that actually matter for school/Google feeds — RRULE preserved as its serialised string (:21-23), and VEVENTs carrying a RECURRENCE-ID (a moved single occurrence of a series) detected and skipped rather than silently duplicating the series (:24, :90), with counters for both (:39-42).
Phase 2 is threaded through properly, which was the stated design goal after the Social near-miss: AvailabilityProfile reaches ProfileController, UpdateProfileCommand/Handler, and a dedicated UpdateChildAvailabilityCommand/Handler for the parent-manages-child path.
Phase 1's recurrence expansion is real: RecurrenceExpansionService.cs:106 mints OccurrenceId = $"{seriesId}_{occ.Start.Ticks}" per occurrence, and CalendarController.cs:127-129 surfaces it so FullCalendar sees distinct events.
Two things keep this open:
Follow-up 2 — the Social silent-drop bug is still live.git grep -niE "\bSocial\b" across ProfileController.cs and UpdateProfileCommand*.cs returns nothing. Angular still saves a {social} section the controller never maps, so it's silently discarded. The epic explicitly cites this as the failure class Phase 2 was designed to avoid — and Phase 2 succeeded at that, but the original instance was never fixed.
#785 — the Schedule tab's boundary-month placeholders exist in the model but the template never binds them.padBoundaryMonths and unschedulableReason are in schedule-tab.component.ts:137-170, the .unschedulable SCSS rule and both i18n keys exist — and zero templates reference any of them. Placeholders render as ordinary interactive tiles. (Details on that ticket.)
Follow-up 1 — per-occurrence reminders — is not done, as expected; OccurrenceId exists on the read path but nothing in the reminder pipeline consumes it. Still correctly scoped as a follow-up rather than a phase.
Also worth noting for scope:#790 (create-for-child from the parent calendar) is filed as a follow-up to #788 and is entirely unstarted — neither the backend endpoint nor createChildEvent exists.
Suggested: update the body to mark Phase 3 shipped, then close on #785's template binding + the Social mapping fix. Those are both small, and the epic's substance is delivered.
**Epic status roll-up** — audited all three phases plus both known follow-ups against `origin/master`. **All three phases have shipped**, including Phase 3, which the body still lists as unstarted.
| Phase | State |
|---|---|
| 1 — Recurrence engine | ✅ shipped |
| 2 — Availability projection + Schedule tab | ✅ shipped (one UI gap, #785) |
| 3 — iCal import / export | ✅ **shipped — body is stale** |
**Phase 3 is done and the epic body doesn't reflect it.** `CalendarController.cs:830` — `ImportIcs(IFormFile file, [FromForm] string? source = null)` with a `CalendarImportResultDto` (`:826`) and rejection logging (`:860`). Backed by a real parser: `SpikerSoft.Business.Scheduling/Calendar/IcsImportParser.cs`, DI-registered at `ServiceCollectionExtensions.cs:1109-1111` alongside `ICalendarImportService`. The parser handles the two things that actually matter for school/Google feeds — RRULE preserved as its serialised string (`:21-23`), and VEVENTs carrying a `RECURRENCE-ID` (a moved single occurrence of a series) detected and skipped rather than silently duplicating the series (`:24`, `:90`), with counters for both (`:39-42`).
**Phase 2 is threaded through properly**, which was the stated design goal after the `Social` near-miss: `AvailabilityProfile` reaches `ProfileController`, `UpdateProfileCommand`/`Handler`, and a dedicated `UpdateChildAvailabilityCommand`/`Handler` for the parent-manages-child path.
**Phase 1's recurrence expansion is real**: `RecurrenceExpansionService.cs:106` mints `OccurrenceId = $"{seriesId}_{occ.Start.Ticks}"` per occurrence, and `CalendarController.cs:127-129` surfaces it so FullCalendar sees distinct events.
**Two things keep this open:**
1. **Follow-up 2 — the `Social` silent-drop bug is still live.** `git grep -niE "\bSocial\b"` across `ProfileController.cs` and `UpdateProfileCommand*.cs` returns **nothing**. Angular still saves a `{social}` section the controller never maps, so it's silently discarded. The epic explicitly cites this as the failure class Phase 2 was designed to avoid — and Phase 2 succeeded at that, but the original instance was never fixed.
2. **#785 — the Schedule tab's boundary-month placeholders exist in the model but the template never binds them.** `padBoundaryMonths` and `unschedulableReason` are in `schedule-tab.component.ts:137-170`, the `.unschedulable` SCSS rule and both i18n keys exist — and zero templates reference any of them. Placeholders render as ordinary interactive tiles. (Details on that ticket.)
**Follow-up 1 — per-occurrence reminders — is not done**, as expected; `OccurrenceId` exists on the read path but nothing in the reminder pipeline consumes it. Still correctly scoped as a follow-up rather than a phase.
**Also worth noting for scope:** **#790** (create-for-child from the parent calendar) is filed as a follow-up to #788 and is entirely unstarted — neither the backend endpoint nor `createChildEvent` exists.
**Suggested:** update the body to mark Phase 3 shipped, then close on #785's template binding + the `Social` mapping fix. Those are both small, and the epic's substance is delivered.
spikerj
added the epic label 2026-08-07 13:43:02 +00:00
Dissolved into per-repo issues as part of the umbrella-tracker breakup. This epic held
implementation work that could never auto-close from a merge; it now lives where the code is:
Backend — spikerj/spikersoft-backend
#619 — PUT /api/profile silently drops the social section. UpdateProfileRequest
(ProfileController.cs:554-576) has no Social member, so the command initialiser (:77-100)
never sets one and ApplySocial (UpdateProfileCommandHandler.cs:334-345) early-returns every
time. This is the epic's known follow-up 2 — and the 2026-07-29 audit above is now half-stale:
the command and handler do carry Social today; the controller still does not.
#620 — expose the availability engine beyond self. IntersectFreeWeeks
(AvailabilityService.cs:68-101) is implemented and unit-tested but has no production caller — git grep outside the service finds only its tests and the DI line
(ServiceCollectionExtensions.cs:1103). Trip scheduling, the epic's stated first consumer, cannot
reach it. Also folds in the missing child-scoped grid read: the parental surface has a PUT parental/availability/{childUserId} (ProfileController.cs:422) and no matching GET.
#621 — per-occurrence reminders for recurring events. One delayed message per series
(CalendarNotificationService.cs:90-115), and CancelEventReminderAsync (:127-143) is an
explicitly-commented no-op. Epic follow-up 1, confirmed still unstarted.
Angular — spikerj/spikersoft-angular
#720 — commitment editor. Deduce mode derives the weekly grid from events flagged affectsAvailability, and no UI can set that flag: affectsAvailability, availabilityIntensity and timeBands appear only in calendar-api.models.ts:41-43,73-75 and in
zero components or templates, while the backend round-trips all three
(CalendarController.cs:478-480, 592-594). So deduce mode derives from an empty set for every
user. Also covers the preset picker, which today writes a label and seeds nothing.
#721 — .ics import + subscription UI. Phase 3's server half is deployed and has no client: POST /api/calendar/import and the three /api/calendar/subscriptions routes have zero callers
anywhere in projects/ or libraries/.
#722 — parent-manages-child availability selector. profileService.updateChildAvailability
(profile.service.ts:150) exists and is called from nowhere; schedule-tab.component.ts has no
child concept at all. Depends on #620 for the read side.
#723 — the #785 fix never reached the template. See below.
Already shipped, no issue filed:
Phase 1 — recurrence engine. Live. RecurrenceExpansionService.cs:106 mints OccurrenceId = $"{seriesId}_{occ.Start.Ticks}"; CalendarController.cs:127-129 surfaces it; GetCalendarEventsByDateRangeQueryHandler expands series on the range query. Angular ships the
recurrence editor (event-modal.component.html:191-252, .ts:309-323) and RRULE .ics export
(calendar-export.service.ts:27,75-88). Backend PR #448, angular PR #513.
Phase 2 — availability projection + Schedule tab. Live. AvailabilityProfile threaded through ProfileController → UpdateProfileCommand/Handler and the dedicated UpdateChildAvailability
path; GET /api/profile/availability/weeks → 401 on api.spikersoft.com (deployed,
auth-gated); the 52-week paint-to-override grid renders. Backend PRs #449/#450, angular PR #515.
Phase 3 — iCal import. Live, and the body above never reflected it. POST /api/calendar/import
→ 405 on GET (route present, POST-only), GET /api/calendar/subscriptions → 401. Real
parser (SpikerSoft.Business.Scheduling/Calendar/IcsImportParser.cs), idempotent upsert on (CreatedBy, ExternalUid) (CalendarImportService.cs), plus subscription-feed refresh. Backend
PRs #456/#458 (spikersoft-issues#802/#803). Only the frontend half was never built → #721.
A correction to the 2026-07-29 audit above, and a note for whoever tracks #785:
#785 was closed on angular PR #519 (c4d1e096), but the fix is still incomplete in exactly the way
that audit described. On spikersoft-angular@8e5a4048, padBoundaryMonths + schedulable/unschedulableReason are in schedule-tab.component.ts:22-29,137-171, the .week-cell.unschedulable rule is at schedule-tab.component.scss:182, both i18n keys are in en.json:227-228andes.json:573-574, and the spec covers the model at schedule-tab.component.spec.ts:45-70 — but grep "schedulable\|unschedulableReason" schedule-tab.component.html returns zero matches. The
cell at .html:68-79 binds only free/limited/busy/overridden. Placeholders are emitted with level: "Busy", so they render as ordinary busy tiles at full opacity inside an undisabled <button> with no tooltip; clicks are inert only because of a TS-side guard. The tests pass because
they assert on the model, never on the render. Filed as spikerj/spikersoft-angular#723.
Also verified, and worth flagging elsewhere:Ical.Net is not an unused dependency. It is
the live engine behind RecurrenceExpansionService and IcsImportParser. spikersoft-backend#548
currently lists it among unused NuGet references — removing it would break recurrence expansion and .ics import.
The unit is tracked by the shared [Scheduling] title prefix and by sibling cross-links in each
issue. Closing here — the umbrella tracker is being emptied.
— Opus 5 Agent
Dissolved into per-repo issues as part of the umbrella-tracker breakup. This epic held
implementation work that could never auto-close from a merge; it now lives where the code is:
**Backend — `spikerj/spikersoft-backend`**
- **#619** — `PUT /api/profile` silently drops the `social` section. `UpdateProfileRequest`
(`ProfileController.cs:554-576`) has no `Social` member, so the command initialiser (`:77-100`)
never sets one and `ApplySocial` (`UpdateProfileCommandHandler.cs:334-345`) early-returns every
time. This is the epic's known follow-up 2 — and the 2026-07-29 audit above is now half-stale:
the *command and handler* do carry `Social` today; the *controller* still does not.
- **#620** — expose the availability engine beyond self. `IntersectFreeWeeks`
(`AvailabilityService.cs:68-101`) is implemented and unit-tested but has **no production caller** —
`git grep` outside the service finds only its tests and the DI line
(`ServiceCollectionExtensions.cs:1103`). Trip scheduling, the epic's stated first consumer, cannot
reach it. Also folds in the missing **child-scoped** grid read: the parental surface has a
`PUT parental/availability/{childUserId}` (`ProfileController.cs:422`) and no matching GET.
- **#621** — per-occurrence reminders for recurring events. One delayed message per *series*
(`CalendarNotificationService.cs:90-115`), and `CancelEventReminderAsync` (`:127-143`) is an
explicitly-commented no-op. Epic follow-up 1, confirmed still unstarted.
**Angular — `spikerj/spikersoft-angular`**
- **#720** — commitment editor. Deduce mode derives the weekly grid from events flagged
`affectsAvailability`, and **no UI can set that flag**: `affectsAvailability`,
`availabilityIntensity` and `timeBands` appear only in `calendar-api.models.ts:41-43,73-75` and in
zero components or templates, while the backend round-trips all three
(`CalendarController.cs:478-480, 592-594`). So deduce mode derives from an empty set for every
user. Also covers the preset picker, which today writes a label and seeds nothing.
- **#721** — `.ics` import + subscription UI. Phase 3's server half is deployed and has **no client**:
`POST /api/calendar/import` and the three `/api/calendar/subscriptions` routes have zero callers
anywhere in `projects/` or `libraries/`.
- **#722** — parent-manages-child availability selector. `profileService.updateChildAvailability`
(`profile.service.ts:150`) exists and is called from nowhere; `schedule-tab.component.ts` has no
child concept at all. Depends on #620 for the read side.
- **#723** — the #785 fix never reached the template. See below.
**Already shipped, no issue filed:**
- **Phase 1 — recurrence engine.** Live. `RecurrenceExpansionService.cs:106` mints
`OccurrenceId = $"{seriesId}_{occ.Start.Ticks}"`; `CalendarController.cs:127-129` surfaces it;
`GetCalendarEventsByDateRangeQueryHandler` expands series on the range query. Angular ships the
recurrence editor (`event-modal.component.html:191-252`, `.ts:309-323`) and RRULE `.ics` export
(`calendar-export.service.ts:27,75-88`). Backend PR #448, angular PR #513.
- **Phase 2 — availability projection + Schedule tab.** Live. `AvailabilityProfile` threaded through
`ProfileController` → `UpdateProfileCommand`/Handler and the dedicated `UpdateChildAvailability`
path; `GET /api/profile/availability/weeks` → **401** on `api.spikersoft.com` (deployed,
auth-gated); the 52-week paint-to-override grid renders. Backend PRs #449/#450, angular PR #515.
- **Phase 3 — iCal import.** Live, and the body above never reflected it. `POST /api/calendar/import`
→ **405** on GET (route present, POST-only), `GET /api/calendar/subscriptions` → **401**. Real
parser (`SpikerSoft.Business.Scheduling/Calendar/IcsImportParser.cs`), idempotent upsert on
`(CreatedBy, ExternalUid)` (`CalendarImportService.cs`), plus subscription-feed refresh. Backend
PRs #456/#458 (spikersoft-issues#802/#803). Only the **frontend** half was never built → #721.
**A correction to the 2026-07-29 audit above, and a note for whoever tracks #785:**
#785 was closed on angular PR #519 (`c4d1e096`), but the fix is still incomplete in exactly the way
that audit described. On `spikersoft-angular@8e5a4048`, `padBoundaryMonths` +
`schedulable`/`unschedulableReason` are in `schedule-tab.component.ts:22-29,137-171`, the
`.week-cell.unschedulable` rule is at `schedule-tab.component.scss:182`, both i18n keys are in
`en.json:227-228` **and** `es.json:573-574`, and the spec covers the model at
`schedule-tab.component.spec.ts:45-70` — but
`grep "schedulable\|unschedulableReason" schedule-tab.component.html` returns **zero matches**. The
cell at `.html:68-79` binds only free/limited/busy/overridden. Placeholders are emitted with
`level: "Busy"`, so they render as ordinary busy tiles at full opacity inside an undisabled
`<button>` with no tooltip; clicks are inert only because of a TS-side guard. The tests pass because
they assert on the model, never on the render. Filed as **spikerj/spikersoft-angular#723**.
**Also verified, and worth flagging elsewhere:** `Ical.Net` is **not** an unused dependency. It is
the live engine behind `RecurrenceExpansionService` and `IcsImportParser`. `spikersoft-backend#548`
currently lists it among unused NuGet references — removing it would break recurrence expansion and
`.ics` import.
The unit is tracked by the shared `[Scheduling]` title prefix and by sibling cross-links in each
issue. Closing here — the umbrella tracker is being emptied.
— Opus 5 Agent
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.
Goal
Make availability, the existing
/calendar, and iCal one integrated scheduling system. "When is this person/kid free" is a general platform primitive — first consumer is trip scheduling (find weeks everyone's free → realistic date-anchored budgets), next is planning classes/lessons around a child's public-school calendar and existing sports.This also fixes a real gap: the calendar had no recurrence (dead
RecurringTypeenum;Ical.Netreferenced but unused; ICS export wrote single VEVENTs).Architecture
One
Ical.Net/RRULE recurrence engine → expands calendar events → availability projection (weekly Free/Limited/Busy) →IntersectFreeWeeks(trip scheduling) + day+time fit (class scheduling). Commitments (school/sports/work) are recurring calendar events flagged availability-affecting.Phases
CalendarEvent; range query expands series; recurrence editor in the calendar event modal; RRULE ICS export. ✅ implemented — backendspikersoft-backendPR + angularspikersoft-angularPR (linked below).AvailabilityProfilesub-document;ExpandWeeks/IntersectFreeWeeks; dedicatedScheduleprofile tab (deduce vs direct modes, presets, week grid); parent-manages-child. Threaded through every UpdateProfile layer..icsupload (school/Google) → events → availability;.icsexport.Confirmed decisions
Week-level ratings Free/Limited/Busy; user-chosen week-start normalized to dates; two input modes (deduce from commitments / enter free weeks directly); commitments carry day-of-week + morning/afternoon/evening bands; dedicated Schedule tab; parents edit children's; unified Ical.Net engine. Not touching sponsor cards; not building the missing
/api/sponsor/familiesbackend.Known follow-ups
Socialprofile-section silent-drop bug (Angular saves{social}the controller never maps) — Phase 2 threads availability through every layer to avoid the same class.🤖 Generated with Claude Code
Child issues (per-repo, auto-close on merge)
No open child issues were produced by the migration — either this epic's
work was already complete, or its scope needs to be broken down into
per-repo issues before it can progress.
Checklist generated by the umbrella-tracker migration, 2026-08-07 — Opus 5 Agent
Progress — Phase 1 and Phase 2 are merged.
Phase 1 (recurrence engine) ✅
RecurrenceExpansionService(Ical.Net/RRULE), recurrence + availability fields onCalendarEvent, range query expands series. (Closed the calendar's no-recurrence gap.).icsexport.Phase 2 (availability projection) ✅
AvailabilityProfile+AvailabilityService(ExpandWeeks/IntersectFreeWeeks), threaded through every UpdateProfile layer, parent-manages-child endpoint.GET /api/profile/availability/weeks(derived grid).Scheduleprofile tab (profile?tab=schedule): week-start, deduce/direct mode, preset, 52-week paint-to-override grid.Remaining (keeping this epic open):
CalendarEvents from the preset + custom entries).PUT /api/profile/parental/availability/{childUserId}already exist)..icsimport (school/Google calendars → availability) + export.Separately surfaced during this work (worth their own tickets): the
Socialprofile-section silent-drop bug (Angular PUTs{social}the controller never maps), and per-occurrence reminders for recurring calendar events (the delayed-reminder pipeline can't cancel / schedules one delay).Epic status roll-up — audited all three phases plus both known follow-ups against
origin/master. All three phases have shipped, including Phase 3, which the body still lists as unstarted.Phase 3 is done and the epic body doesn't reflect it.
CalendarController.cs:830—ImportIcs(IFormFile file, [FromForm] string? source = null)with aCalendarImportResultDto(:826) and rejection logging (:860). Backed by a real parser:SpikerSoft.Business.Scheduling/Calendar/IcsImportParser.cs, DI-registered atServiceCollectionExtensions.cs:1109-1111alongsideICalendarImportService. The parser handles the two things that actually matter for school/Google feeds — RRULE preserved as its serialised string (:21-23), and VEVENTs carrying aRECURRENCE-ID(a moved single occurrence of a series) detected and skipped rather than silently duplicating the series (:24,:90), with counters for both (:39-42).Phase 2 is threaded through properly, which was the stated design goal after the
Socialnear-miss:AvailabilityProfilereachesProfileController,UpdateProfileCommand/Handler, and a dedicatedUpdateChildAvailabilityCommand/Handlerfor the parent-manages-child path.Phase 1's recurrence expansion is real:
RecurrenceExpansionService.cs:106mintsOccurrenceId = $"{seriesId}_{occ.Start.Ticks}"per occurrence, andCalendarController.cs:127-129surfaces it so FullCalendar sees distinct events.Two things keep this open:
Socialsilent-drop bug is still live.git grep -niE "\bSocial\b"acrossProfileController.csandUpdateProfileCommand*.csreturns nothing. Angular still saves a{social}section the controller never maps, so it's silently discarded. The epic explicitly cites this as the failure class Phase 2 was designed to avoid — and Phase 2 succeeded at that, but the original instance was never fixed.padBoundaryMonthsandunschedulableReasonare inschedule-tab.component.ts:137-170, the.unschedulableSCSS rule and both i18n keys exist — and zero templates reference any of them. Placeholders render as ordinary interactive tiles. (Details on that ticket.)Follow-up 1 — per-occurrence reminders — is not done, as expected;
OccurrenceIdexists on the read path but nothing in the reminder pipeline consumes it. Still correctly scoped as a follow-up rather than a phase.Also worth noting for scope: #790 (create-for-child from the parent calendar) is filed as a follow-up to #788 and is entirely unstarted — neither the backend endpoint nor
createChildEventexists.Suggested: update the body to mark Phase 3 shipped, then close on #785's template binding + the
Socialmapping fix. Those are both small, and the epic's substance is delivered.Dissolved into per-repo issues as part of the umbrella-tracker breakup. This epic held
implementation work that could never auto-close from a merge; it now lives where the code is:
Backend —
spikerj/spikersoft-backendPUT /api/profilesilently drops thesocialsection.UpdateProfileRequest(
ProfileController.cs:554-576) has noSocialmember, so the command initialiser (:77-100)never sets one and
ApplySocial(UpdateProfileCommandHandler.cs:334-345) early-returns everytime. This is the epic's known follow-up 2 — and the 2026-07-29 audit above is now half-stale:
the command and handler do carry
Socialtoday; the controller still does not.IntersectFreeWeeks(
AvailabilityService.cs:68-101) is implemented and unit-tested but has no production caller —git grepoutside the service finds only its tests and the DI line(
ServiceCollectionExtensions.cs:1103). Trip scheduling, the epic's stated first consumer, cannotreach it. Also folds in the missing child-scoped grid read: the parental surface has a
PUT parental/availability/{childUserId}(ProfileController.cs:422) and no matching GET.(
CalendarNotificationService.cs:90-115), andCancelEventReminderAsync(:127-143) is anexplicitly-commented no-op. Epic follow-up 1, confirmed still unstarted.
Angular —
spikerj/spikersoft-angularaffectsAvailability, and no UI can set that flag:affectsAvailability,availabilityIntensityandtimeBandsappear only incalendar-api.models.ts:41-43,73-75and inzero components or templates, while the backend round-trips all three
(
CalendarController.cs:478-480, 592-594). So deduce mode derives from an empty set for everyuser. Also covers the preset picker, which today writes a label and seeds nothing.
.icsimport + subscription UI. Phase 3's server half is deployed and has no client:POST /api/calendar/importand the three/api/calendar/subscriptionsroutes have zero callersanywhere in
projects/orlibraries/.profileService.updateChildAvailability(
profile.service.ts:150) exists and is called from nowhere;schedule-tab.component.tshas nochild concept at all. Depends on #620 for the read side.
Already shipped, no issue filed:
RecurrenceExpansionService.cs:106mintsOccurrenceId = $"{seriesId}_{occ.Start.Ticks}";CalendarController.cs:127-129surfaces it;GetCalendarEventsByDateRangeQueryHandlerexpands series on the range query. Angular ships therecurrence editor (
event-modal.component.html:191-252,.ts:309-323) and RRULE.icsexport(
calendar-export.service.ts:27,75-88). Backend PR #448, angular PR #513.AvailabilityProfilethreaded throughProfileController→UpdateProfileCommand/Handler and the dedicatedUpdateChildAvailabilitypath;
GET /api/profile/availability/weeks→ 401 onapi.spikersoft.com(deployed,auth-gated); the 52-week paint-to-override grid renders. Backend PRs #449/#450, angular PR #515.
POST /api/calendar/import→ 405 on GET (route present, POST-only),
GET /api/calendar/subscriptions→ 401. Realparser (
SpikerSoft.Business.Scheduling/Calendar/IcsImportParser.cs), idempotent upsert on(CreatedBy, ExternalUid)(CalendarImportService.cs), plus subscription-feed refresh. BackendPRs #456/#458 (spikersoft-issues#802/#803). Only the frontend half was never built → #721.
A correction to the 2026-07-29 audit above, and a note for whoever tracks #785:
#785 was closed on angular PR #519 (
c4d1e096), but the fix is still incomplete in exactly the waythat audit described. On
spikersoft-angular@8e5a4048,padBoundaryMonths+schedulable/unschedulableReasonare inschedule-tab.component.ts:22-29,137-171, the.week-cell.unschedulablerule is atschedule-tab.component.scss:182, both i18n keys are inen.json:227-228andes.json:573-574, and the spec covers the model atschedule-tab.component.spec.ts:45-70— butgrep "schedulable\|unschedulableReason" schedule-tab.component.htmlreturns zero matches. Thecell at
.html:68-79binds only free/limited/busy/overridden. Placeholders are emitted withlevel: "Busy", so they render as ordinary busy tiles at full opacity inside an undisabled<button>with no tooltip; clicks are inert only because of a TS-side guard. The tests pass becausethey assert on the model, never on the render. Filed as spikerj/spikersoft-angular#723.
Also verified, and worth flagging elsewhere:
Ical.Netis not an unused dependency. It isthe live engine behind
RecurrenceExpansionServiceandIcsImportParser.spikersoft-backend#548currently lists it among unused NuGet references — removing it would break recurrence expansion and
.icsimport.The unit is tracked by the shared
[Scheduling]title prefix and by sibling cross-links in eachissue. Closing here — the umbrella tracker is being emptied.
— Opus 5 Agent