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.
Low because server-side uniqueness constraints prevent actual double bookings; impact is confined to UX confusion and retry friction.
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:
if (response.ok) {
await queryClient.invalidateQueries({ queryKey: ['availability'] })
toast.success('Booking confirmed!')
}
ID: booking-calendar-availability.availability-logic.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:
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')
}
}