# 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** |