From ae8b489f977e9ce86ac1ca915fff5204ed6d695b Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:00:45 -0600 Subject: [PATCH] dev: Add `copilot-instructions.md` (#6659) --- .devcontainer/devcontainer.json | 1 - .github/copilot-instructions.md | 240 ++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 .github/copilot-instructions.md diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e2f935682..258d63fba 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -31,7 +31,6 @@ "charliermarsh.ruff", "dbaeumer.vscode-eslint", "matangover.mypy", - "ms-python.black-formatter", "ms-python.pylint", "ms-python.python", "ms-python.vscode-pylance", diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..97cbc382a --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,240 @@ +# Mealie Development Guide for AI Agents + +## Project Overview + +Mealie is a self-hosted recipe manager, meal planner, and shopping list application with a FastAPI backend (Python 3.12) and Nuxt 3 frontend (Vue 3 + TypeScript). It uses SQLAlchemy ORM with support for SQLite and PostgreSQL databases. + +**Development vs Production:** +- **Development:** Frontend (port 3000) and backend (port 9000) run as separate processes +- **Production:** Frontend is statically generated and served via FastAPI's SPA module (`mealie/routes/spa/`) in a single container + +## Architecture & Key Patterns + +### Backend Architecture (mealie/) + +**Repository-Service-Controller Pattern:** +- **Controllers** (`mealie/routes/**/controller_*.py`): Inherit from `BaseUserController` or `BaseAdminController`, handle HTTP concerns, delegate to services +- **Services** (`mealie/services/`): Business logic layer, inherit from `BaseService`, coordinate repos and external dependencies +- **Repositories** (`mealie/repos/`): Data access layer using SQLAlchemy, accessed via `AllRepositories` factory + - Get repos via dependency injection: `repos: AllRepositories = Depends(get_repositories)` + - All repos scoped to group/household context automatically + +**Route Organization:** +- Routes in `mealie/routes/` organized by domain (auth, recipe, groups, households, admin) +- Use `APIRouter` with FastAPI dependency injection +- Apply `@router.get/post/put/delete` decorators with Pydantic response models +- Route controllers use `HttpRepo` mixin for common CRUD operations (see `mealie/routes/_base/mixins.py`) + +**Schemas & Type Generation:** +- Pydantic schemas in `mealie/schema/` with strict separation: `*In`, `*Out`, `*Create`, `*Update` suffixes +- Auto-exported from submodules via `__init__.py` files (generated by `task dev:generate`) +- TypeScript types auto-generated from Pydantic schemas - **never manually edit** `frontend/lib/api/types/` + +**Database & Sessions:** +- Session management via `Depends(generate_session)` in FastAPI routes +- Use `session_context()` context manager in services/scripts +- SQLAlchemy models in `mealie/db/models/`, migrations in `mealie/alembic/` +- Create migrations: `task py:migrate -- "description"` + +### Frontend Architecture (frontend/) + +**Component Organization (strict naming conventions):** +- **Domain Components** (`components/Domain/`): Feature-specific, prefix with domain (e.g., `AdminDashboard`) +- **Global Components** (`components/global/`): Reusable primitives, prefix with `Base` (e.g., `BaseButton`) +- **Layout Components** (`components/Layout/`): Layout-only, prefix with `App` if props or `The` if singleton +- **Page Components** (`components/` with page prefix): Last resort for breaking up complex pages + +**API Client Pattern:** +- API clients in `frontend/lib/api/` extend `BaseAPI`, `BaseCRUDAPI`, or `BaseCRUDAPIReadOnly` +- Types imported from auto-generated `frontend/lib/api/types/` (DO NOT EDIT MANUALLY) +- Composables in `frontend/composables/` for shared state and API logic (e.g., `use-mealie-auth.ts`) +- Use `useAuthBackend()` for authentication state, `useMealieAuth()` for user management + +**State Management:** +- Nuxt 3 composables for state (no Vuex) +- Auth state via `use-mealie-auth.ts` composable +- Prefer composables over global state stores + +## Essential Commands (via Task/Taskfile.yml) + +**Development workflow:** +```bash +task setup # Install all dependencies (Python + Node) +task dev:services # Start Postgres & Mailpit containers +task py # Start FastAPI backend (port 9000) +task ui # Start Nuxt frontend (port 3000) +task docs # Start MkDocs documentation server +``` + +**Code generation (REQUIRED after schema changes):** +```bash +task dev:generate # Generate TypeScript types, schema exports, test helpers +``` + +**Testing & Quality:** +```bash +task py:test # Run pytest (supports args: task py:test -- -k test_name) +task py:check # Format + lint + type-check + test (full validation) +task py:format # Ruff format +task py:lint # Ruff check +task py:mypy # Type checking +task ui:test # Vitest frontend tests +task ui:check # Frontend lint + test +``` + +**Database:** +```bash +task py:migrate -- "description" # Generate Alembic migration +task py:postgres # Run backend with PostgreSQL config +``` + +**Docker:** +```bash +task docker:prod # Build and run production Docker compose +``` + +## Critical Development Practices + +### Python Backend + +1. **Always use `uv` for Python commands** (not `python` or `pip`): + ```bash + uv run python mealie/app.py + uv run pytest tests/ + ``` + +2. **Type hints are mandatory:** Use mypy-compatible annotations, handle Optional types explicitly + +3. **Dependency injection pattern:** + ```python + from fastapi import Depends + from mealie.repos.all_repositories import get_repositories, AllRepositories + + def my_route( + repos: AllRepositories = Depends(get_repositories), + user: PrivateUser = Depends(get_current_user) + ): + recipe = repos.recipes.get_one(recipe_id) + ``` + +4. **Settings & Configuration:** + - Get settings: `settings = get_app_settings()` (cached singleton) + - Get directories: `dirs = get_app_dirs()` + - Never instantiate `AppSettings()` directly + +5. **Testing:** + - Fixtures in `tests/fixtures/` + - Use `api_client` fixture for integration tests + - Follow existing patterns in `tests/integration_tests/` and `tests/unit_tests/` + +### Frontend + +1. **Run code generation after backend schema changes:** `task dev:generate` + +2. **TypeScript strict mode:** All code must pass type checking + +3. **Component naming:** Follow strict conventions (see Architecture section above) + +4. **API calls pattern:** + ```typescript + const api = useUserApi(); + const recipe = await api.recipes.getOne(recipeId); + ``` + +5. **Composables for shared logic:** Prefer composables in `composables/` over inline code duplication + +6. **Translations:** Only modify `en-US` locale files when adding new translation strings - other locales are managed via Crowdin and **must never be modified** (PRs modifying non-English locales will be rejected) + +### Cross-Cutting Concerns + +1. **Code generation is source of truth:** After Pydantic schema changes, run `task dev:generate` to update: + - TypeScript types (`frontend/lib/api/types/`) + - Schema exports (`mealie/schema/*/__init__.py`) + - Test data paths and routes + +2. **Multi-tenancy:** All data scoped to **groups** and **households**: + - Groups contain multiple households + - Households contain recipes, meal plans, shopping lists + - Repositories automatically filter by group/household context + +3. **Pre-commit hooks:** Install via `task setup:py`, enforces Ruff formatting/linting + +4. **Testing before PRs:** Run `task py:check` and `task ui:check` before submitting PRs + +## Pull Request Best Practices + +### Before Submitting a PR + +1. **Draft PRs are optional:** Create a draft PR early if you want feedback while working, or open directly as ready when complete +2. **Verify code generation:** If you modified Pydantic schemas, ensure `task dev:generate` was run +3. **Follow Conventional Commits:** Title your PR according to the conventional commits format (see PR template) +4. **Add release notes:** Include user-facing changes in the PR description + +### What to Review + +**Architecture & Patterns:** +- Does the code follow the repository-service-controller pattern? +- Are controllers delegating business logic to services? +- Are services coordinating repositories, not accessing the database directly? +- Is dependency injection used properly (`Depends(get_repositories)`, `Depends(get_current_user)`)? + +**Data Scoping:** +- Are repositories correctly scoped to group/household context? +- Do route handlers properly validate group/household ownership before operations? +- Are multi-tenant boundaries enforced (users can't access other groups' data)? + +**Type Safety:** +- Are type hints present on all functions and methods? +- Are Pydantic schemas using correct suffixes (`*In`, `*Out`, `*Create`, `*Update`)? +- For frontend, does TypeScript code pass strict type checking? + +**Generated Files:** +- Verify `frontend/lib/api/types/` files weren't manually edited (they're auto-generated) +- Check that `mealie/schema/*/__init__.py` exports match actual schema files (auto-generated) +- If schemas changed, confirm generated files were updated via `task dev:generate` + +**Code Quality:** +- Is the code readable and well-organized? +- Are complex operations documented with clear comments? +- Do component names follow the strict naming conventions (Domain/Global/Layout/Page prefixes)? +- Are composables used for shared frontend logic instead of duplication? + +**Translations:** +- Were only `en-US` locale files modified for new translation strings? +- Verify no other locale files (managed by Crowdin) were touched + +**Database Changes:** +- Are Alembic migrations included for schema changes? +- Are migrations tested against both SQLite and PostgreSQL? + +### Review Etiquette + +- Be constructive and specific in feedback +- Suggest code examples when proposing changes +- Focus on architecture and logic - formatting/linting is handled by CI +- Use "Approve" when ready to merge, "Request Changes" for blocking issues, "Comment" for non-blocking suggestions + +## Common Gotchas + +- **Don't manually edit generated files:** `frontend/lib/api/types/`, schema `__init__.py` files +- **Repository context:** Repos are group/household-scoped - passing wrong IDs causes 404s +- **Session handling:** Don't create sessions manually, use dependency injection or `session_context()` +- **Schema changes require codegen:** After changing Pydantic models, run `task dev:generate` +- **Translation files:** Only modify `en-US` locale files - all other locales are managed by Crowdin +- **Dev containers:** This project uses VS Code dev containers - leverage the pre-configured environment +- **Task commands:** Use `task` commands instead of direct tool invocation for consistency + +## Key Files to Reference + +- `Taskfile.yml` - All development commands and workflows +- `mealie/routes/_base/base_controllers.py` - Controller base classes and patterns +- `mealie/repos/repository_factory.py` - Repository factory and available repos +- `frontend/lib/api/base/base-clients.ts` - API client base classes +- `tests/conftest.py` - Test fixtures and setup +- `dev/code-generation/main.py` - Code generation entry point + +## Additional Resources + +- [Documentation](https://docs.mealie.io/) +- [Contributors Guide](https://nightly.mealie.io/contributors/developers-guide/code-contributions/) +- [Discord](https://discord.gg/QuStdQGSGK)