feat: add caching for backend objects
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# Session Handoff: Project Brief + Web Awesome Spike
|
||||
**Date:** 2026-04-21
|
||||
**Session Duration:** ~1.5 hours
|
||||
**Session Focus:** Created project brief, evaluated and spiked Web Awesome as a PrimeVue replacement, abandoned after discovering no calendar component
|
||||
**Context Usage at Handoff:** ~50%
|
||||
|
||||
## What Was Accomplished
|
||||
1. Created project brief template → `docs/summaries/00-project-brief.md` (user filled in club name, deadline, boat/member counts, booking rules)
|
||||
2. Evaluated Web Awesome as PrimeVue replacement — determined feasible given PrimeVue was only used for `<DatePicker>` in one file
|
||||
3. Spiked Web Awesome Pro installation: configured `@web.awesome.me` registry on Cloudsmith, fixed stray backslash in auth token, installed `@web.awesome.me/webawesome-pro@3.5.0`
|
||||
4. Discovered Web Awesome Pro 3.5.0 has no calendar component → abandoned spike, reverted to main
|
||||
|
||||
## Exact State of Work in Progress
|
||||
- Web Awesome spike: fully reverted — main branch is clean, `node_modules` restored from lockfile
|
||||
- `docs/summaries/00-project-brief.md`: created and partially filled; `[FILL: Current Phase]` and booking rule detail `[FILL: any other rules]` remain open
|
||||
|
||||
## Decisions Made This Session
|
||||
- **Abandon Web Awesome**: no calendar/date-picker component in v3.5.0 — STATUS: confirmed
|
||||
- **Keep PrimeVue**: only one component in use (`<DatePicker inline>` on home page); not worth replacing until a suitable alternative exists — STATUS: confirmed
|
||||
- **`WEBAWESOME_NPM_TOKEN` in `.env`**: registry token stored in `.env` (gitignored), referenced via `${WEBAWESOME_NPM_TOKEN}` in `.npmrc`/`.yarnrc` — STATUS: confirmed pattern for future private registries
|
||||
- **`webawesome` branch deleted (implicitly)**: all work was uncommitted; restored via `git restore` + `yarn install` — no branch to clean up
|
||||
|
||||
## Key Numbers Generated or Discovered This Session
|
||||
- PrimeVue usage in app: 1 component (`<DatePicker>` in `app/pages/index.vue:59`)
|
||||
- Web Awesome Pro version spiked: 3.5.0
|
||||
- Cloudsmith registry: `https://npm.cloudsmith.io/fortawesome/webawesome-pro/`
|
||||
- Deadline: April 30 (9 days away at time of handoff)
|
||||
- Boats in program: 4
|
||||
- Members: 20–30
|
||||
- Weekly pre-booking limit: 2
|
||||
|
||||
## Files Created or Modified
|
||||
| File Path | Action | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `docs/summaries/00-project-brief.md` | Created | Project brief — club name, personas, stack, CI/CD, constraints, booking rules; partially filled |
|
||||
|
||||
## What the NEXT Session Should Do
|
||||
1. **First**: Decide what replaces the `<DatePicker>` on the home page — options: remove the Calendar card entirely (simplest, given deadline), keep PrimeVue just for that one component, or use a native `<input type="date">` unstyled
|
||||
2. **Then**: Build out reservations UI — `app/pages/reservations/create.vue` exists (scaffolded with Ionic components); wire it to the `create-reservation` Edge Function
|
||||
3. **Then**: Build `app/pages/admin/reservations.vue` (exists as untracked file per git status) — admin view of all bookings
|
||||
|
||||
## Open Questions Requiring User Input
|
||||
- [ ] What replaces `<DatePicker inline>` on the home page? Remove card, keep PrimeVue, or native input? — impacts whether PrimeVue stays in the stack
|
||||
- [ ] What is the full set of admin pages needed beyond `reservations` and `boat`? — impacts session planning before April 30
|
||||
- [ ] Are cancel-reservation and other Edge Functions planned? — impacts backend scope
|
||||
|
||||
## Assumptions That Need Validation
|
||||
- ASSUMED: `app/pages/admin/reservations.vue` is scaffolded but incomplete — verify by reading file
|
||||
- ASSUMED: `app/pages/reservations/create.vue` is scaffolded but not wired to Edge Function — verify by reading file
|
||||
|
||||
## Files to Load Next Session
|
||||
- `docs/summaries/handoff-2026-04-21-project-brief-webawesome-spike.md` — this file
|
||||
- `docs/summaries/00-project-brief.md` — for project context
|
||||
- `app/pages/reservations/create.vue` — if working on reservations UI
|
||||
- `app/pages/admin/reservations.vue` — if working on admin bookings view
|
||||
90
docs/summaries/00-project-brief.md
Normal file
90
docs/summaries/00-project-brief.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Project Brief: OYS Borrow a Boat (oysqn.app)
|
||||
**Created:** 2026-04-21
|
||||
**Owner:** Patrick Toal
|
||||
|
||||
---
|
||||
|
||||
## Identity
|
||||
|
||||
- **App name:** OYS Borrow a Boat (oysqn.app)
|
||||
- **Previous app:** bab-app (Appwrite backend — retired)
|
||||
- **Purpose:** Manage a Borrow a Boat program for a Yacht Club (OYS = Oakville Yacht Squadron)
|
||||
- **Club:** Oakville Yacht Club, Oakville, ON Canada
|
||||
- **Target users:** Club members enrolled in the Borrow a Boat program
|
||||
|
||||
---
|
||||
|
||||
## Personas
|
||||
|
||||
| Persona | Description |
|
||||
|---------|-------------|
|
||||
| BAB Member | Enrolled club member; can browse boats and make reservations |
|
||||
| Certified Skipper | Credentialed to take boats without supervision |
|
||||
| Program Administrator | Manages boats, intervals, rules, and member access |
|
||||
| Boatswain | Responsible for boat maintenance and readiness |
|
||||
| Volunteer | Assists with program logistics |
|
||||
| Instructor | Delivers sailing instruction; may certify members |
|
||||
|
||||
---
|
||||
|
||||
## Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|-------|-----------|
|
||||
| Framework | Nuxt 4 (SSR=false, SPA mode) |
|
||||
| UI | Ionic Vue (@ionic/vue) + PrimeVue 4 |
|
||||
| Language | TypeScript |
|
||||
| Backend | Supabase (Auth, DB, Edge Functions, Storage) |
|
||||
| Testing | Vitest (unit), Vitest (integration, hits real Supabase), Playwright (E2E) |
|
||||
| Package manager | Yarn |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Highlights
|
||||
|
||||
- `app/app.vue` — IonApp + IonMenu + IonRouterOutlet (no NuxtLayout/NuxtPage)
|
||||
- Auth: magic link + OTP only; no password auth; no self-service signup; admin-only invite
|
||||
- Edge Functions: Bearer JWT → `adminClient.auth.getUser(token)` pattern; all DB ops via service role
|
||||
- Icons: Ionicons only (`ionicons/icons`)
|
||||
|
||||
---
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
- **Source control:** Gitea
|
||||
- **CI:** Gitea Actions (unit tests + build on PR)
|
||||
- **Deploy (dev):** EDA (Event-Driven Ansible) → AAP → nginx artifact swap on bab1
|
||||
- **Deploy (prod):** EDA → AAP (manual approval gate) → S3 sync
|
||||
- **E2E:** Playwright, run post-deploy via AAP
|
||||
- **Supabase environments:** Two projects — dev and prod (project IDs in Vault)
|
||||
- **Secrets:** HashiCorp Vault at `http://nas.lan.toal.ca:8200`, path `kv/oys/`
|
||||
|
||||
---
|
||||
|
||||
## Current Phase
|
||||
|
||||
[FILL: e.g., "Active development — reservations UI", "Beta testing", "Production"]
|
||||
|
||||
---
|
||||
|
||||
## Key Constraints
|
||||
|
||||
- Deadline: April 30th (Launch Day)
|
||||
- Four boats in the program
|
||||
- Between 20 - 30 Members
|
||||
|
||||
---
|
||||
|
||||
## Booking Rules (known so far)
|
||||
|
||||
- Weekly pre-booking limit enforced (exact number: 2)
|
||||
- Overlap constraint: same boat cannot be double-booked
|
||||
|
||||
---
|
||||
|
||||
## Open Items
|
||||
|
||||
- [ ] Are cancel-reservation and admin Edge Functions planned?
|
||||
- [ ] What is the full set of admin pages needed?
|
||||
- [ ] What is the club name for display in the app?
|
||||
- [X] What is the season start date / go-live target?
|
||||
@@ -0,0 +1,100 @@
|
||||
# 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 parsing** — `response.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 integration** → `app/pages/schedule.vue`: `fetchBoats` and `fetchSchedule` write to cache on success, read from cache when offline
|
||||
9. **Create page booking draft** → `app/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 `data`** — `data` 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
|
||||
Reference in New Issue
Block a user