# Editing a recurring event prompts choice of 'this only', 'this and future', or 'all'

- **Pattern:** `ab-000536` (`booking-calendar-availability.recurring.recurring-edit-scope`)
- **Severity:** info
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000536
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000536)

## Why it matters

Without a scope prompt, editing a single weekly-recurring slot rewrites every past and future occurrence, corrupting historical schedules and silently changing appointments customers have already booked. Hosts lose the ability to handle one-off changes (vacation, a shifted single week) without breaking the entire series. This erodes trust in the scheduling tool, forces manual database fixes, and can cause calendar-invite mismatches where the host's system shows one time and the attendee's another.

## Severity rationale

Info because this is a capability gap rather than an active defect, but absence blocks common scheduling workflows and causes data churn when workarounds are used.

## Remediation

Before applying any edit to a recurring event, open a dialog offering three choices: 'this only' (insert a row into `availability_exceptions` with the occurrence date), 'this and future' (set a new `dtstart` on a cloned rule), and 'all' (mutate the original rrule). Branch your persistence logic on the returned scope. See `src/components/RecurringEditScopeDialog.tsx`:

```tsx
const scope = await showScopeDialog() // 'this' | 'thisFuture' | 'all'
if (scope === 'this') await db.availabilityExceptions.create({ recurringId, exceptionDate })
```

## Detection

- **ID:** `recurring-edit-scope`
- **Severity:** `info`
- **What to look for:** Search for recurring event edit UI components. Count all edit scope options presented to the user. Look for a dialog, modal, or prompt that offers at least 3 choices when editing a recurring event instance:
  1. "Edit this only" — applies change to this single instance (creates an exception).
  2. "Edit this and future" — applies change to this and all future occurrences (modifies rule dtstart).
  3. "Edit all" — applies change to the entire series (modifies the rule itself).
  Verify that the code branches on the user's choice and applies the correct update. Examine files matching `src/components/*RecurringEdit*`, `src/components/*ScopeDialog*`, `src/components/*Schedule*`.
- **Pass criteria:** Enumerate all edit scope options in the recurring edit UI. At least 3 scope options must be presented ("this only", "this and future", "all"). Each option must trigger a distinct code path (exception creation, rule modification from date, or full rule update). Report: "Edit scope options: [list]. X of 3 required scopes implemented."
- **Fail criteria:** No scope prompt appears when editing a recurring event, or only 1 scope option exists (e.g., always edits all occurrences).
- **Skip (N/A) when:** Platform does not support editing recurring schedules, or does not have recurring schedule features.
- **Detail on fail:** Example: `"Editing a recurring Wednesday availability updates all occurrences, even past ones. No option to edit just future occurrences."`
- **Cross-reference:** Check `rule-based-storage` — edit scopes require rule-based storage to function properly.
- **Cross-reference:** Check `exception-handling` — "edit this only" creates an exception that must be applied in real time.
- **Cross-reference:** Check `data-freshness` — after editing scope, the calendar must refetch to reflect the change.
- **Remediation:** Add a scope prompt when editing recurring schedules:

  ```tsx
  async function handleEditRecurring(eventId) {
    const scope = await showScopeDialog()
    // Options: 'this', 'thisFuture', 'all'

    if (scope === 'this') {
      // Create exception for this date only
      await db.availabilityExceptions.create({
        recurringId: eventId,
        exceptionDate: selectedDate,
        isAvailable: false,
      })
    } else if (scope === 'thisFuture') {
      // Update rule with new dtstart
      await db.recurringAvailability.update({
        where: { id: eventId },
        data: { rrule: updatedRRule },
      })
    } else if (scope === 'all') {
      // Update entire recurring rule
      await db.recurringAvailability.update({
        where: { id: eventId },
        data: { rrule: updatedRRule },
      })
    }
  }
  ```

---

Taxons: user-experience

HTML version: https://auditbuffet.com/patterns/ab-000536
