# Availability recalculated after each booking without requiring full page reload

- **Pattern:** `ab-000530` (`booking-calendar-availability.availability-logic.live-availability-update`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000530
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000530)

## Why it matters

When the calendar does not refresh after a successful booking, the just-booked slot still appears green and selectable. A second customer in the same session clicks it, hits a 409 conflict from the server, and sees a generic error. This creates double-booking races, duplicate conflict-handling work, and a confusing 'did my booking go through?' moment that drives support contacts. The defect also misleads the original user, who may refresh and fear their booking failed.

## Severity rationale

Low because server-side uniqueness constraints prevent actual double bookings; impact is confined to UX confusion and retry friction.

## Remediation

After the booking POST resolves with `response.ok`, immediately refetch availability (or call `queryClient.invalidateQueries(['availability'])` with React Query) before showing the success toast. Prefer cache invalidation over manual `setSlots` so stale data anywhere in the tree clears too. See `src/hooks/useBooking.ts`:

```tsx
if (response.ok) {
  await queryClient.invalidateQueries({ queryKey: ['availability'] })
  toast.success('Booking confirmed!')
}
```

## Detection

- **ID:** `live-availability-update`
- **Severity:** `low`
- **What to look for:** Trace the booking success handler. After a successful `POST` to the booking API, look for at least 1 of these update mechanisms: (1) API refetch of availability (e.g., `fetch('/api/availability')` after booking response), (2) optimistic UI update (e.g., `setSlots(prev => prev.filter(...))`), (3) React Query/SWR invalidation (e.g., `queryClient.invalidateQueries`), (4) WebSocket/real-time subscription. Count all booking success paths and verify each triggers a calendar update. Examine files matching `src/components/*Booking*`, `src/components/*Calendar*`, `src/hooks/*booking*`.
- **Pass criteria:** Count all booking success code paths. 100% of booking success handlers must trigger a calendar data update (refetch, optimistic update, cache invalidation, or real-time push) without requiring a full page reload. At least 1 update mechanism must exist. Report: "Update mechanism: [refetch/optimistic/invalidation/websocket]. X of Y success paths trigger update."
- **Fail criteria:** Calendar does not reflect the new booking until the page is manually refreshed by the user.
- **Skip (N/A) when:** Calendar is display-only (no bookings are created from the calendar page).
- **Detail on fail:** Example: `"After booking a slot successfully, the calendar still shows it as available. User must refresh the page to see the update."`
- **Cross-reference:** Check `data-freshness` — live update after booking complements fresh data on view load.
- **Cross-reference:** Check `performance-load-time` — post-booking refetch should not block the UI or cause a full re-render.
- **Cross-reference:** Check `confirmation-calendar-parity` — after update, the calendar time must match the confirmation time.
- **Remediation:** Refetch availability after a booking:

  ```tsx
  async function handleBooking(slot) {
    try {
      const response = await fetch('/api/appointments', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(slot),
      })

      if (response.ok) {
        // Refetch availability
        const availResponse = await fetch('/api/availability')
        const newSlots = await availResponse.json()
        setSlots(newSlots)

        toast.success('Booking confirmed!')
      }
    } catch (error) {
      toast.error('Booking failed')
    }
  }
  ```

Taxons: user-experience

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