Files
oysqn.app/docs/summaries/handoff-2026-04-21-historical-booking-offline-cache.md

7.8 KiB

Session Handoff: Historical Booking Constraint + Offline Cache + Booking Draft

Date: 2026-04-21 Session Duration: ~2 hours Session Focus: Enforced historical booking rule, fixed Edge Function error surfacing, implemented offline cache system with Realtime updates, and fixed slot-click navigation from schedule to create page using a booking draft composable. Context Usage at Handoff: ~75%


What Was Accomplished

  1. Historical booking constraint — tests written and Edge Function enforced → tests/integration/booking-constraints.test.ts, supabase/functions/create-reservation/index.ts
  2. Error message fix — "Can not book a reservation in the past." is the user-facing message for historical_booking_not_allowed (422)
  3. Fixed functions-js v2 error body parsingresponse.data is null on non-2xx; body must be read from response.error.context.json()app/pages/reservations/create.vue
  4. Historical booking uses end_time — a booking is historical only when its end_time < now() (not start_time)
  5. Offline cache system — localStorage, 24h TTL, Realtime-updated → app/composables/useAppCache.ts, app/composables/useOfflineStatus.ts
  6. Booking draft composable — module-level ref for cross-page state (replaces broken query-param deep-link) → app/composables/useBookingDraft.ts
  7. Offline indicator — fixed chip in top-right corner in app/app.vue; Realtime channel app-cache-sync patches slots:, intervals:, and boats cache on DB changes
  8. Schedule page cache integrationapp/pages/schedule.vue: fetchBoats and fetchSchedule write to cache on success, read from cache when offline
  9. Create page booking draftapp/pages/reservations/create.vue: onIonViewWillEnter reads draft (no async fetch needed), jumps to step 2 with pre-filled boat + slot; also cache-aware loadSlots for offline step 1
  10. CLAUDE.md caching rule documented — "Every table/view read from Supabase must be written to useAppCache on success and read from it when offline" — includes pattern, key conventions, Realtime guidance

Exact State of Work in Progress

  • Booking draft navigation: complete — clicking a slot in schedule sets draft and routes to /reservations/create; create page reads draft in onIonViewWillEnter and goes directly to step 2
  • Offline cache: complete for boats, intervals, slots (schedule + create pages); NOT yet applied to other pages (/boat, /reference, /profile, admin pages)
  • Realtime: subscribed to reservations, intervals, boats in app.vue; if new tables are cached in future, add subscriptions to app-cache-sync channel

Decisions Made This Session

  • Historical = end_time < now() BECAUSE a session that started in the past but hasn't ended yet is still bookable — STATUS: confirmed
  • Query params abandoned for slot navigation BECAUSE + in ISO timestamps (+00:00) URL-encodes to %2B, which can be decoded as a space by some parsers; async boat fetch adds an auth-timing race; onIonViewWillEnter route-timing is unreliable — CHOSE module-level reactive ref (booking draft) instead — STATUS: confirmed
  • functions-js v2 error body is in error.context, not datadata is null on non-2xx; error.context is the raw Response — applies to all error code handling in create.vue — STATUS: confirmed
  • Cache key for schedule data = ISO week Monday (cache.weekKey(utcIso)weekMonday(date)) so desktop (week view) and mobile (day view) share the same cache entries — STATUS: confirmed
  • Offline = read-only — booking submission is not attempted offline (Edge Function call fails naturally); no explicit offline guard on submit — ASSUMED acceptable

Key Numbers Generated or Discovered This Session

  • functions-js version in use: 2.100.0 (confirmed from node_modules)
  • Cache TTL: 24 hours (ms: 86_400_000)
  • Realtime channel name: app-cache-sync
  • Error code for historical booking: historical_booking_not_allowed (HTTP 422)
  • localStorage key prefix: cache: (e.g., cache:boats, cache:slots:2026-04-20, cache:intervals:2026-04-20)

Files Created or Modified

File Path Action Description
app/composables/useOfflineStatus.ts Created Module-level isOnline ref; wires online/offline browser events
app/composables/useAppCache.ts Created localStorage cache; get (TTL-enforced), peek (stale-ok), set, invalidate, invalidatePrefix, weekKey
app/composables/useBookingDraft.ts Created Module-level BookingDraft ref; set(boat, startTime, endTime) / take()
app/app.vue Modified Offline chip (fixed top-right, warning colour); Realtime app-cache-sync channel for reservations, intervals, boats
app/pages/schedule.vue Modified fetchBoats/fetchSchedule cache-aware; bookSlot sets draft instead of query params
app/pages/reservations/create.vue Modified onIonViewWillEnter consumes draft (jumps to step 2); loadSlots cache-aware; functions-js v2 error body fix; historical_booking_not_allowed in codeMessages
supabase/functions/create-reservation/index.ts Modified Historical guard: endDate < now() → admin-only (422 historical_booking_not_allowed); admin skips cert check + booking-limit checks
tests/integration/booking-constraints.test.ts Modified Added describe('historical booking constraint'): member rejected (422), admin allowed (201), skipper rejected (422)
CLAUDE.md Modified Added "Offline Cache" section under Architecture with pattern, key conventions, Realtime guidance, and booking-draft pattern

What the NEXT Session Should Do

  1. Verify slot-click flow end to end: open schedule page, click an available slot, confirm create page opens at step 2 with the correct boat name and time pre-filled, submit a reservation
  2. Apply cache pattern to remaining pages: /boat (boats list), /reference (reference_docs), /profile (member record), admin pages as needed — follow the pattern documented in CLAUDE.md
  3. Decide on the DatePicker replacement (OPEN from previous session): <DatePicker inline> on app/pages/index.vue:59 is the only PrimeVue usage — options are remove the calendar card, keep PrimeVue, or use native <input type="date">
  4. Build app/pages/admin/reservations.vue — admin view of all bookings (file exists, likely scaffolded)
  5. Wire cancel-reservation — OPEN: is a cancel Edge Function planned?

Open Questions Requiring User Input

  • app/pages/index.vue:59 — what replaces <DatePicker inline>? Remove card, keep PrimeVue, or native input? Impacts whether PrimeVue stays in the stack
  • Is a cancel-reservation Edge Function planned? Impacts backend scope before April 30
  • Should offline submission attempts show an explicit "You are offline — cannot book" message rather than a generic network error?

Assumptions That Need Validation

  • ASSUMED: app/pages/admin/reservations.vue is scaffolded but incomplete — verify by reading the file
  • ASSUMED: offline booking submission failing silently (network error toast) is acceptable — validate with Patrick
  • ASSUMED: onIonViewWillEnter fires before the user can interact with the page — if there is a visible flash of step 1 before the draft is consumed, add step.value = 2 synchronously before the await in the handler

Files to Load Next Session

  • docs/summaries/handoff-2026-04-21-historical-booking-offline-cache.md — this file
  • docs/summaries/00-project-brief.md — project context
  • app/pages/reservations/create.vue — if continuing booking flow work
  • app/pages/admin/reservations.vue — if building admin bookings view
  • app/pages/index.vue — if resolving the DatePicker question