# Handoff: Nuxt 3 Migration Plan
Date: 2026-03-18
Type: Architecture / Planning

## Decision
Migrate bab-app from Quasar 2 + Vue 3 PWA to **Nuxt 3 + Capacitor + FullCalendar**.
Retain Appwrite backend unchanged. Auth simplified to magic link / email OTP only.

## Current Stack (Source of Truth)
- Quasar 2 / Vue 3 / TypeScript / Pinia / Appwrite
- Build: PWA only (`quasar build -m pwa`)
- Appwrite project: `65ede55a213134f2b688` @ `https://appwrite.toal.ca/v1`
- Database: `bab_prod`
- Collections: boat, reservation, interval, intervalTemplate, task, taskTags, skillTags
- 9 Pinia stores, 21 components, 2 layouts, ~20 pages/routes
- Auth: email token + magic link + password (vue3-google-login present but UNUSED)

## Target Stack
- **Nuxt 3** (file-based routing, Nitro, Vite)
- **nuxt-quasar-ui** module — keeps all Quasar components during migration, de-risks UI rewrite
- **@fullcalendar/vue3** — replaces @quasar/quasar-ui-qcalendar
- **@capacitor/core** + plugins — camera, geolocation (native mobile future)
- **Pinia** — unchanged
- **Appwrite SDK** — unchanged, via Nuxt plugin
- **Auth**: magic link + email OTP only; remove vue3-google-login

## Key Architectural Decisions
- Use `nuxt-quasar-ui` to avoid a big-bang UI rewrite — keeps q-* components working
- File-based routing replaces routes.ts — directory structure maps 1:1 to current routes
- Boot file → Nuxt plugin (appwrite.ts becomes plugins/appwrite.ts)
- Router guards → Nuxt middleware (auth.ts global middleware)
- **PWA only initially** — Capacitor deferred until native camera/GPS features are needed
- FullCalendar resource-timeline view for multi-boat scheduling (replaces QCalendarResource)
- **FullCalendar non-commercial license confirmed** — key: `CC-Attribution-NonCommercial-NoDerivatives` (OYS is a registered Ontario not-for-profit #000082982)
- **Static output** — `nuxt generate`, served via nginx, consistent with current Ansible deploy
- **TestFlight / App Store deferred** — revisit when PWA hits UX or capability limits

## Migration Phases (see plan in this file)

### Phase 1 — Foundation (1-2 days) ✅ COMPLETE 2026-03-19
Scaffold new Nuxt 3 project alongside existing. Do NOT delete old project until Phase 6 complete.

New project: `/home/ptoal/Dev/mobile-projects/bab-app-nuxt/`

Tasks:
- `npx nuxi@latest init bab-app-nuxt`
- Add modules: `nuxt-quasar-ui`, `@pinia/nuxt`, `@vite-pwa/nuxt`
- Configure `nuxt.config.ts`: Quasar options, Vite aliases, env vars
- Migrate `.env.local` vars (VITE_ prefix → NUXT_PUBLIC_ for public vars)
- Capacitor: DEFERRED — PWA only until native features needed

### Phase 2 — Appwrite Plugin + Types (0.5 days)
- Copy `src/boot/appwrite.ts` → `plugins/appwrite.ts`
- Change export pattern to Nuxt plugin format (`defineNuxtPlugin`)
- Provide `$appwrite` via `useNuxtApp()`
- Copy all TypeScript type files verbatim

### Phase 3 — Auth (1 day)
- Migrate `stores/auth.ts` → `stores/auth.ts` (Pinia, near-verbatim)
- Remove all password auth methods (createEmailPasswordSession, etc.)
- Keep: `createMagicURLToken`, `updateMagicURLSession`, `createEmailToken`, `updatePhoneSession`
- Remove `vue3-google-login` from package.json
- Create `middleware/auth.global.ts` — replaces router/index.ts navigation guard
- Public routes: `/login`, `/signup`, `/pwreset`, `/terms-of-service`, `/privacy-policy`
- Magic link callback: `pages/auth/callback.vue` — handles `?userId=&secret=` params

### Phase 4 — Remaining Stores (0.5 days)
All stores are framework-agnostic Pinia — direct copy with minor import path updates:
- stores/boat.ts
- stores/reservation.ts
- stores/interval.ts
- stores/intervalTemplate.ts
- stores/task.ts
- stores/reference.ts
- stores/realtime.ts
- stores/memberProfile.ts

### Phase 5 — File-Based Routing (Page Scaffold) (0.5 days)
Create empty page files matching Nuxt convention. Route mapping:

| Old path | Nuxt file |
|---|---|
| `/` | `pages/index.vue` |
| `/boat` | `pages/boat.vue` |
| `/certification` | `pages/certification.vue` |
| `/profile` | `pages/profile.vue` |
| `/checklist` | `pages/checklist.vue` |
| `/reference` | `pages/reference/index.vue` |
| `/reference/:id/view` | `pages/reference/[id]/view.vue` |
| `/schedule` | `pages/schedule/index.vue` |
| `/schedule/book` | `pages/schedule/book.vue` |
| `/schedule/view` | `pages/schedule/view.vue` |
| `/schedule/list` | `pages/schedule/list.vue` |
| `/schedule/edit/:id` | `pages/schedule/edit/[id].vue` |
| `/schedule/manage` | `pages/schedule/manage.vue` |
| `/task` | PARKED — task feature not migrated (collections absent from bab_prod) |
| `/task/:id/edit` | PARKED |
| `/admin/user` | `pages/admin/user.vue` |
| `/admin/boat` | `pages/admin/boat.vue` |
| `/login` | `pages/login.vue` |
| `/signup` | `pages/signup.vue` |
| `/pwreset` | `pages/pwreset.vue` |
| `/terms-of-service` | `pages/terms-of-service.vue` |
| `/privacy-policy` | `pages/privacy-policy.vue` |

Admin route guard: `definePageMeta({ middleware: ['auth', 'admin'] })` in admin pages.
Schedule manage guard: `definePageMeta({ middleware: ['auth', 'schedule-admin'] })`.

### Phase 6 — Layouts + Global Components (1 day)
- `layouts/default.vue` — replaces MainLayout.vue (q-layout, q-header, LeftDrawer, BottomNav)
- `layouts/admin.vue` — replaces AdminLayout.vue
- Migrate components verbatim first; replace Quasar components later if needed:
  - components/BottomNavComponent.vue
  - components/LeftDrawer.vue (OPEN: was listed in agent findings but not in original glob — confirm file exists)
  - components/ToolbarComponent.vue

### Phase 7 — Page Migration (3-5 days)
Migrate pages in this order (lowest to highest complexity):

1. Public pages: login, signup, pwreset, terms, privacy (simple forms)
2. Profile, Certification, Checklist, Boat (read-only/simple CRUD)
3. Reference pages
4. Admin pages (UserAdmin, BoatAdmin — q-table heavy)
5. Schedule pages (most complex — last)
6. Task pages — PARKED. Skip until bab_prod collections (task, taskTags, skillTags) are created.
   Affected: stores/task.ts, components/task/*, pages/task/*, BottomNav task link, navlinks.ts task entry.

### Phase 8 — FullCalendar (2-3 days)
Replace QCalendar with FullCalendar Vue 3.

Packages:
```
@fullcalendar/vue3
@fullcalendar/core
@fullcalendar/resource-timeline   # multi-boat resource view
@fullcalendar/daygrid             # month/day grid
@fullcalendar/timegrid            # week/day time grid
@fullcalendar/interaction         # drag and drop
@fullcalendar/list                # list view
```

Mapping:
- `ResourceScheduleViewerComponent.vue` → new `FullCalendarResourceView.vue`
  - Resources = boats (one row per boat)
  - Events = reservations + intervals
  - DnD for admin interval management (replaces IntervalTemplateComponent drag behavior)
- `CalendarHeaderComponent.vue` — FullCalendar has built-in header toolbar, simplify or remove
- `BoatScheduleTableComponent.vue` — replace with FullCalendar list/grid view

FullCalendar license: open-source plugins are free; resource-timeline requires **FullCalendar Premium** license ($0 for open-source/non-commercial projects — confirm this applies, otherwise budget ~$200/yr).

OPEN: Confirm if OYS Borrow a Boat qualifies for FullCalendar open-source license.

### Phase 9 — Capacitor (1 day)
- `npx cap add ios` / `npx cap add android`
- Install plugins (not wired to features yet, just scaffold):
  - `@capacitor/camera`
  - `@capacitor/geolocation`
- Create `composables/useCamera.ts` and `composables/useGeolocation.ts`
  - Detect Capacitor.isNativePlatform() and fall back to browser APIs on web
- Update `nuxt.config.ts` ssr: false (Capacitor requires SPA mode)
- capacitor.config.ts: `webDir: '.output/public'`

### Phase 10 — CI/CD (0.5 days)
Update `.gitea/workflows/build.yaml`:
- Replace `quasar build -m pwa` with `nuxt build`
- Output dir: `.output/public/` (static) or `.output/` (server)
- Ansible deploy: serve `.output/public` as static site (nginx), same as current

## Open Questions
- OPEN: Does OYS qualify for FullCalendar non-commercial license? (resource-timeline is premium)
- OPEN: Confirm `LeftDrawer.vue` exists in src/layouts/ or src/components/ — agent referenced it but not in initial glob
- OPEN: Is Capacitor native app publishing (App Store / Play Store) in scope, or just Capacitor for future native API access with PWA as primary delivery?
- ASSUMED: SSR is not needed — Appwrite client SDK runs browser-side; deploy as SPA/static
- ASSUMED: Nuxt deployed as static output (not Node server) — consistent with current nginx/Ansible deploy

## Effort Estimate
| Phase | Days |
|---|---|
| 1-4 (Foundation, Plugin, Auth, Stores) | 3 |
| 5-6 (Routing, Layouts) | 1.5 |
| 7 (Page migration) | 4 |
| 8 (FullCalendar) | 2.5 |
| 9 (Capacitor) | 1 |
| 10 (CI/CD) | 0.5 |
| **Total** | **~12.5 days** |
