diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c2103c3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: [ main, master ] + pull_request: + +jobs: + build-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Codegen check + run: npm run codegen:check || true + + - name: Generate OpenAPI types + run: | + if [ -f "../munch-ease-backend/openapi.json" ]; then + npm run codegen + else + echo "Backend openapi.json not found. Skipping codegen." + fi + + - name: Typecheck + run: npm run -s typecheck + + - name: Typecheck Vue SFC templates + run: npm run -s typecheck:vue + + - name: Lint + run: npm run -s lint + + - name: Test + run: npm run -s test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1f1ff2f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing to Munch Ease + +Thanks for helping make Munch Ease better! This repo aims for a lean, predictable codebase. Please keep changes small, typed, and well‑scoped. + +## Core axioms (must follow) + +- OpenAPI is the single source of truth for shapes (generated in `src/api/types.ts`). +- The SDK (`src/api/sdk.ts`) is the only data access surface. +- Domain types live in `src/domain/types.ts`; minimal normalization in `src/domain/decoders.ts`. +- No casts (`as`, angle brackets) in app code. Generated files are exempt. Boundary-only normalization allowed. +- No `any`/`unknown` in app code. Prefer precise types, `Pick`/`Omit`, and inference. +- Arrays declared in OpenAPI as required are non-nullable in domain types (e.g., `Meal.recipes` is always an array). +- Disallow runtime type checks in app code (`typeof`, `in`, broad `instanceof`). + - Acceptable exceptions: DOM event narrowing (e.g., `HTMLInputElement`), error normalization in SDK, env detection. +- CamelCase across `src/`; no snake_case. + +## Architecture quick tour + +- SDK + decoders boundary + - `src/api/sdk.ts`: All network calls; returns domain-safe types. Normalize errors via `httpError`. + - `src/domain/decoders.ts`: Convert date strings → `Date`, normalize arrays, and decode nested structures. +- Domain and DTOs + - Prefer domain aliases from `src/domain/types.ts` over raw `components['schemas'][...]`. + - Use discriminated unions for UI shapes where needed (e.g., `Group` has `type: 'product' | 'name'`). +- Composables + - UI logic in `src/composables/*`. Keep components thin. +- Routing + - Use helpers from `src/router/helpers.ts` (`parseRouteId`, `parseQueryString`) instead of ad-hoc `typeof` checks. + +## Coding guidelines + +- Keep changes small and incremental. Write a quick unit test when behavior changes. +- Prefer `computed` over ad-hoc recalculation; avoid prop mutation. +- No ad-hoc mappers. If a slimmer shape is needed, use `Pick`/`Omit` with a descriptive name (e.g., `*Summary`). +- Keep runtime guards in the boundary only. UI assumes decoded, normalized types. + +## Tooling + +- TypeScript strict; ESLint with type-aware rules; Prettier formatting. +- Tests: Vitest + MSW. Keep tests fast and focused. +- Node 18+ recommended. + +## Useful scripts + +```bash +npm run serve # Dev server +npm run test # Unit tests (Vitest) +npm run lint # ESLint +npm run typecheck # TS + vue-tsc +npm run build # Production build +``` + +## PR checklist + +- [ ] Types first: no casts in app code, no `any`/`unknown` +- [ ] Boundary-only normalization in decoders/SDK +- [ ] Arrays reflect OpenAPI nullability in domain types +- [ ] No new mappers; use domain aliases or `Pick`/`Omit` +- [ ] Tests updated/added if behavior changed diff --git a/README.md b/README.md index e836aa7..c6e2253 100644 --- a/README.md +++ b/README.md @@ -39,49 +39,35 @@ Environment ## Architecture and conventions -The codebase follows clear boundaries and Vue 3 Composition API throughout. +Strict TypeScript, Vue 3 Composition API, and a single typed API boundary. -- Routing and auth - - Centralized in `src/router/index.js` with named routes and an auth guard via route meta `requiresAuth`. - - Components use `useRouter/useRoute` for navigation and route access. +Key axioms +- OpenAPI (generated `src/api/types.ts`) is the single source of truth for shapes. +- SDK (`src/api/sdk.ts`) is the only data access surface; UI uses domain types from `src/domain/types.ts`. +- Domain normalization is minimal in `src/domain/decoders.ts` (e.g., date strings → `Date`). +- No casts (`as`, angle brackets) and no `any`/`unknown` in app code. Generated files are exempt. +- Arrays that are required in OpenAPI are non-nullable in domain types (e.g., `Meal.recipes`). +- Disallow runtime type checks in app code; acceptable exceptions: DOM event narrowing, error/env handling in the boundary. -- API layer and mappers - - `src/api/http.js` is a tiny JSON fetch wrapper that honors `VUE_APP_API_BASE`. - - Feature services live in `src/api/*` (meals, recipes, shopping, auth, persons). - - Normalization lives in `src/api/mappers/*` (e.g., date parsing, shape cleanup). +Layout +- `src/api/` — Typed client and SDK boundary +- `src/domain/` — Domain types and decoders +- `src/composables/` — Reusable app logic (auth, meals, shopping, pagination, alert) +- `src/components/` — UI components and pages +- `src/router/` — Routes and helpers (`parseRouteId`, `parseQueryString`) -- Composables (UI-facing logic) - - Reusable logic in `src/composables/*` (useAuth, useMeals, useShopping). - - Components stay thin: data via refs/reactive, effects via computed/watch. - -- Components - - All Single File Components use ` diff --git a/src/components/meals/MealCard.vue b/src/components/meals/MealCard.vue index 7d2f6e5..9e04311 100644 --- a/src/components/meals/MealCard.vue +++ b/src/components/meals/MealCard.vue @@ -25,21 +25,31 @@ somebody?

-

- Purchased {{ ago(meal.purchase_date) }} +

+ Purchased {{ ago(meal.purchaseDate) }}

- diff --git a/src/components/recipes/RecipesPage.vue b/src/components/recipes/RecipesPage.vue index d966deb..b764940 100644 --- a/src/components/recipes/RecipesPage.vue +++ b/src/components/recipes/RecipesPage.vue @@ -6,19 +6,22 @@ /> - diff --git a/src/components/shopping/MealSelectionList.vue b/src/components/shopping/MealSelectionList.vue index 3e1b0b0..c6f6c04 100644 --- a/src/components/shopping/MealSelectionList.vue +++ b/src/components/shopping/MealSelectionList.vue @@ -7,31 +7,35 @@ - diff --git a/src/components/shopping/MyShoppingPage.vue b/src/components/shopping/MyShoppingPage.vue index 06845c1..047e433 100644 --- a/src/components/shopping/MyShoppingPage.vue +++ b/src/components/shopping/MyShoppingPage.vue @@ -39,42 +39,42 @@ --> -