munch-ease-frontend/refactor-strategy.md
2025-10-18 13:53:24 +11:00

7.5 KiB

Refactor Strategy

This document outlines a pragmatic, step-by-step refactor plan to improve structure, readability, and maintainability. Each step includes a clear outcome and a checkbox to track progress.

Last updated: 2025-10-18

Goals

  • Separate concerns (routing, HTTP/API, mapping/normalization, UI logic)
  • Improve readability and testability
  • Establish light-weight standards (naming, lint/format) without blocking development
  • Keep changes incremental and safe

Phases and Steps

Phase 1 — Routing and Auth (Foundational)

  • Extract router into src/router/index.js with named routes
  • Add route meta requiresAuth and a global auth guard
  • Remove auth-redirect from App.vue (handled by guard instead)
  • Convert top-level nav to use route names consistently (optional)

Outcome: Routing logic is centralized and testable; pages redirect consistently based on auth.

Phase 2 — API Layer Split (Incremental)

  • Add src/api/http.js wrapper for JSON fetch with error handling and env-based base URL
  • Add src/api/mappers/mealMapper.js to normalize Meal data (dates)
  • Add src/api/meals.js and migrate MealPlan API calls (get upcoming, mark consumed, delete)
  • Create src/api/recipes.js and migrate recipe endpoints
  • Create src/api/shopping.js and migrate shopping endpoints
  • Create src/api/auth.js and migrate auth endpoints

Outcome: Feature modules call cohesive services; logic for mapping/normalization is isolated and testable.

Phase 3 — Composables (UI-Facing Logic)

  • Add src/composables/useAuth.js (user ref, ensureAuth)
  • Add src/composables/useMeals.js (fetch and mutate meals)
  • Refactor pages to use composables and <script setup> where appropriate
    • Converted: MealPlanPage.vue, EditMealPage.vue, CurrentShoppingListPage.vue, MyShoppingPage.vue, PurchasedShoppingListPage.vue
    • Added: src/composables/useShopping.js; adopted by shopping pages

Outcome: Components get smaller and easier to read; business logic is reusable.

Phase 4 — Tooling and Standards

  • Add Prettier config and .editorconfig; wire Prettier with ESLint
  • Upgrade ESLint (if/when convenient) and align with Vue 3 rules
  • Ensure Volar is used (dev environment) for Vue 3 type intelligence
    • Add lint-staged + husky for pre-commit formatting
    • Optional next: enable Vue macros (defineProps/defineOptions) in ESLint or upgrade ESLint/vue plugin

Outcome: Stable formatting and consistent linting across contributors.

Phase 5 — Tests (Targeted)

Composition API Migration Plan

This document tracks the migration of remaining components to Vue 3 Composition API using <script setup>, with clear ordering and acceptance criteria.

Last updated: 2025-10-18

Goals

  • Convert all SFCs to Composition API <script setup>.
  • Remove Options API patterns (data, methods, computed, watch, this.*).
  • Standardize on composables for cross-cutting concerns (auth, alerts, API calls).
  • Keep changes incremental and safe with focused PRs and existing tests.

Current status

Already using <script setup>:

  • Meals: MealPlanPage.vue, EditMealPage.vue, meals/MealCard.vue
  • Shopping: CurrentShoppingListPage.vue, MyShoppingPage.vue, PurchasedShoppingListPage.vue, shopping/MealSelectionList.vue, shopping/ShoppingListItem.vue
  • Core/Leaf: components/ActionItem.vue, recipes/RecipeCard.vue, ingredients/CompactParsedIngredient.vue
  • Ingredients: ingredients/IngredientLine.vue, ingredients/EditableIngredientsPanel.vue
  • Recipes: components/recipes/RecipesPage.vue, components/recipes/RecipeSearchBox.vue

Remaining to migrate (Options API or mixed):

  • Core

    • App.vue
    • components/AlertToast.vue
    • components/LoginPage.vue
  • Recipes

    • components/recipes/EditRecipePage.vue (most complex)
  • Meals

    • components/meals/DatePicker.vue
    • components/meals/PersonList.vue (mixed Options+setup, unify under <script setup>)
  • Ingredients

    • components/ingredients/EditableIngredientsPanel.vue
  • Shopping

    • components/shopping/ShoppingListItem.vue

Migration order (batches)

  1. Leaf/presentational components (low risk)
  • ActionItem.vue, RecipeCard.vue, CompactParsedIngredient.vue
  • Patterns: defineProps, no router; replace props: ['x'] with defineProps<{...}> (or JSDoc). Simple emits with defineEmits.
  1. Simple interactive components
  • IngredientLine.vue, MealCard.vue, MealSelectionList.vue, DatePicker.vue
  • Patterns: replace data with ref/reactive, computed with computed, methods with local functions. Replace this.$emit with emit.
  1. Components with watchers and DOM refs
  • EditableIngredientsPanel.vue, PersonList.vue, ShoppingListItem.vue
  • Patterns: use ref for elements, onMounted for subscriptions/layout, watch for reactive sources. Ensure timeouts/listeners are cleaned up.
  1. Pages and core scaffolding
  • RecipesPage.vue, RecipeSearchBox.vue, EditRecipePage.vue, LoginPage.vue, App.vue, AlertToast.vue
  • Patterns: replace this.$router/this.$route with useRouter/useRoute. Consider a small useAlert composable to replace the event bus pattern used by AlertToast.vue and current alert.js.

Conventions and helpers

  • Routing: const router = useRouter(); const route = useRoute();
  • Props/Emits:
    • const props = defineProps({ ... })
    • const emit = defineEmits(['event-name'])
  • State: const state = reactive({...}) or const x = ref(initial)
  • Computed/Watch: const y = computed(() => ...); watch(source, (val, old) => ...)
  • Lifecycle: onMounted, onBeforeUnmount
  • Assets: import statics via new URL('@/assets/foo.svg', import.meta.url).href or leave template require() where needed (non-blocking).
  • Testing: keep current Vitest setup; prefer unit tests for any functional changes.

Acceptance criteria per component

  • The component uses a single <script setup> block.
  • No this.* usage remains; props accessed via props or destructured; emits via emit.
  • Route navigation uses useRouter/useRoute where applicable.
  • All existing functionality and events preserved.
  • Lint/test/build pass.

Tracking checklist

  • Core: App.vue

  • Core: components/AlertToast.vue

  • Core: components/ActionItem.vue

  • Core: components/LoginPage.vue

  • Recipes: components/recipes/RecipesPage.vue

  • Recipes: components/recipes/RecipeSearchBox.vue

  • Recipes: components/recipes/RecipeCard.vue

  • Recipes: components/recipes/EditRecipePage.vue

  • Meals: components/meals/MealCard.vue

  • Meals: components/meals/DatePicker.vue

  • Meals: components/meals/PersonList.vue

  • Ingredients: components/ingredients/CompactParsedIngredient.vue

  • Ingredients: components/ingredients/IngredientLine.vue

  • Ingredients: components/ingredients/EditableIngredientsPanel.vue

  • Shopping: components/shopping/MealSelectionList.vue

  • Shopping: components/shopping/ShoppingListItem.vue

Notes and risks

  • PersonList.vue aligns a dropdown to an input via DOM measurements; ensure the ref-based approach updates positions correctly on focus/resize.
  • RecipeSearchBox.vue uses timeouts for debouncing; prefer watch with a debounced effect and clean up on unmount.
  • AlertToast.vue uses a simple event-bus (alert.js); consider migrating to a useAlert composable with a ref-based queue to simplify subscriptions.

Done (context)

  • Routing extracted with auth guard; API layer split; composables for auth/meals/shopping; prettier/husky configured; Vitest tests for mappers and units in place.