This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Flutter desktop app for weekly meal planning. Users create ingredients, build recipes, configure time constraints per meal slot, and generate optimized weekly menus with auto-aggregated shopping lists. All Flutter source code lives under menu_management/.
Before implementing any non-trivial feature, delegate to the pattern-scout agent.
Before implementing features that touch state management, data models, persistence, menu generation, or app architecture, delegate to the adr-checker agent in consult mode. After such changes, delegate in maintain mode.
After writing or modifying code, delegate to the test-runner agent.
After completing changes that affect documented content, delegate to the docs-checker agent.
When the user's request is broad or exploratory, ask whether they'd like to run multi-agent research (/research-agents) before proceeding.
This file is self-improving. When you discover something worth recording, update it immediately:
| Discovery | Where to write |
|---|---|
| Project-specific constraint, gotcha, or pattern | This file |
| Cross-project preference or pattern | ~/.claude/CLAUDE.md |
| Architectural decision with rationale | New ADR in adr/ and index it here |
| Wrong or outdated instruction | Correct it in place |
Document non-obvious behavior -- things a competent agent would get wrong without an explicit rule. Skip anything easily discoverable via normal code reading.
| File | Audience | Content |
|---|---|---|
README.md |
Humans | What the project is, how to install/run/deploy, features, troubleshooting |
CLAUDE.md |
AI agents | Architecture, patterns, coding rules, gotchas, procedures |
No duplication between them. If setup instructions are in README, CLAUDE.md only references them or includes a quick command table.
| Task | Command | Notes |
|---|---|---|
| Run app (Windows) | cd menu_management && flutter run -d windows |
Also supports -d linux, -d macos |
| Analyze | cd menu_management && flutter analyze |
Enforced lint rules in analysis_options.yaml |
| Code generation | cd menu_management && dart run build_runner build --delete-conflicting-outputs |
Required after changing any Freezed/json_serializable model |
| Code gen (watch) | cd menu_management && dart run build_runner watch --delete-conflicting-outputs |
|
| Build release | cd menu_management && flutter build windows |
|
| Build + copy to Desktop | cd menu_management && ./build_and_copy.bat |
Windows only; copies portable build to Desktop |
| Run all tests | cd menu_management && flutter test test/ |
249 tests across 7 files |
| Run single test | cd menu_management && flutter test test/<file>.dart |
|
| List devices | flutter devices |
UI (Widgets) --> State (Providers) --> Data (Freezed Models)
- UI: Stateful widgets organized by feature domain
- State: Provider pattern with singleton providers (
IngredientsProvider,RecipesProvider,MenuProvider) - Data: Freezed immutable models with JSON serialization; business logic lives on the models themselves
hub.dart is the central navigation rail with three main sections (Ingredients, Recipes, Menu) plus load/save buttons. ShoppingPage is accessible after menu generation.
| Domain | Provider | Key Models | Purpose |
|---|---|---|---|
ingredients/ |
IngredientsProvider |
Ingredient, Product |
CRUD for food items; optional store product link per ingredient |
recipes/ |
RecipesProvider |
Recipe, Instruction, IngredientUsage, Quantity, Result |
Recipe management with multi-step instructions, inputs/outputs |
menu/ |
MenuProvider |
MultiWeekMenu, Menu, Meal, MealTime, Cooking, MenuConfiguration |
Multi-week menus (each week = 21 meal slots), generation algorithm |
shopping/ |
(derived) | ShoppingIngredient |
Aggregated shopping list from generated menu |
theme/ |
- | DynamicTheme, ThemeCustom |
Material 3 theming |
Core logic in menu_generator.dart. Fully parameterized: receives List<Recipe> and List<MenuConfiguration> as parameters, never accesses providers. Multi-phase assignment with priority ordering, yield calculation for leftovers, and time-constraint fitting. See ADR 0004 for detailed algorithm documentation.
persistency.dart handles file I/O. Fully parameterized: all public methods receive data as parameters (ingredients, recipes, lookup maps), never accessing provider singletons internally. Save is unavailable on iOS/Android due to FilePicker limitations. See ADR 0003 and ADR 0009.
.tsrfiles: JSON with top-level"Ingredients"and"Recipes"arrays. On save,ref_namefields are injected intoIngredientUsageentries for human readability..tsmfiles: Menus storerecipeId(UUID) +ref_nameper meal, not full Recipe objects. On load, eachrecipeIdis validated; missing recipes are skipped with a warning.- Data is not automatically saved -- users must manually save via the save button
- On startup, dialogs ask whether to load last session, bundled defaults, or skip (for both recipes and menus)
- Menu configurations are not persisted (generated on-demand)
All changes must follow red-green TDD:
- RED: Write failing tests first that describe the expected behavior.
- GREEN: Write the minimum code to make the tests pass.
- Repeat for each incremental behavior.
Tests live in menu_management/test/. Run with cd menu_management && flutter test test/<file>.dart.
This applies to new features, bug fixes, and refactors. Do not write production code without a failing test driving it.
- Each provider is a singleton
ChangeNotifierwith static mutation methods (addOrUpdate,remove) - Use
listenableOf()helper for context-based listening to individual items - Always call
notifyListeners()after state changes - Use
listen: falsewhen reading without reacting to changes (avoids expensive rebuilds) - Provider access is restricted to the UI layer (widgets, hub.dart, main.dart). Non-UI code (models, generators, persistency helpers) never imports or accesses
Provider.instance; it receives all required data as parameters. - Symmetric provider API: both
IngredientsProviderandRecipesProviderexposeget(id)(withfirstWhereOrNull+Debug.logError+ null assertion), a list getter (.ingredients/.recipes), andaddOrUpdate()/remove()static methods for mutation. - Provider responsibilities:
IngredientsProvider: CRUD for ingredients, search historyRecipesProvider: CRUD for recipes/instructions, filtering by type/nutrition, result/input trackingMenuProvider: Holds 21 MenuConfigurations (7 days x 3 meals), triggers menu generation
- Data flow: Widget -> static provider method -> provider updates state ->
notifyListeners()-> listening widgets rebuild
- All data models use Freezed for immutability,
copyWith, equality, and JSON serialization - Generated files (
*.freezed.dart,*.g.dart) are excluded from analysis - Business logic methods are embedded directly on models (e.g.,
Recipe.fitsConfiguration(),Menu.copyWithUpdatedYields()) - Models never import or call providers. Cross-entity references use string IDs (e.g.,
Cooking.recipeId,IngredientUsage.ingredient), not embedded objects. When a method needs data from another entity, the caller passes it as a list (e.g.,List<Recipe> recipes). - Use
constconstructors where possible - Add empty
const Model._()constructor to enable custom methods - Prefer derived getters over storing redundant state
- Key business logic methods (methods needing cross-entity data receive
List<Recipe> recipes):Recipe.fitsConfiguration(): Check if recipe matches meal requirementsMenu.copyWithUpdatedRecipe(recipes:): Update a meal's recipe and recalculate yieldsMenu.copyWithUpdatedYields(recipes:): Calculate yields based on recipe reuseMenu.allIngredients(recipes:): Aggregate all ingredients across meals (respects yields)MenuConfiguration.canBeCookedAtTheSpot: Derived from time availability
When adding a new Product to an Ingredient or editing an existing one, always look up the product on the Mercadona API to fill all fields accurately. Do not guess weights, pack sizes, or shelf life. For countable ingredients (used with unit: pieces in recipes), add a separate Product entry with unit: pieces alongside the weight-based product.
See mercadona-api.md for full API documentation, field mapping, and examples of how to determine items per pack for both weight-based and pieces-based products.
normalizeForSearch()extension (influtter_essentials/) with optional space removal- Applied consistently to ingredient and recipe search/filter
flutter_essentials/ contains shared extensions, widgets, and debug helpers used across all features.
From analysis_options.yaml:
- Double quotes (
prefer_double_quotes) - Package imports only (
always_use_package_imports, no relative lib imports) - Explicit types on public APIs, return types, and init formals
- Page width: 150 characters
use_build_context_synchronouslyis disabled (context.mounted checks used manually)- Models: PascalCase (
Ingredient,MenuConfiguration) - Providers:
<Feature>Provider(IngredientsProvider) - Files: snake_case matching the class name
- Enums: PascalCase with lowercase values
Stored in adr/ at the repo root. Format: NNNN-short-descriptive-title.md with sequential numbering. Each ADR has three sections: Context, Decision, Consequences.
ADRs capture why decisions were made, not just what was built. This includes architectural choices, feature design rationale, goals and constraints that shaped the solution, and trade-offs considered. Write in plain text so a reader understands the reasoning without needing to read the code. ADRs are the place to record anything that would otherwise live only in someone's head.
| ADR | Topic |
|---|---|
| 0001 | Freezed models with embedded business logic |
| 0002 | Singleton Provider state management |
| 0003 | .tsr file-based persistence |
| 0004 | Menu generation algorithm (phases, priority, yield, nutritional balance) |
| 0005 | Multi-step recipes with inputs/outputs dependency chain |
| 0006 | Cook mode: step-by-step cooking guide with timers and scaling |
| 0007 | flutter_essentials as a local library directory, not a separate package |
| 0008 | Multi-week menus: MultiWeekMenu wraps List<Menu>, independent generation per week |
| 0009 | Cooking stores recipe ID (not full Recipe), parameterized model methods, ref_name for readability |
| 0010 | Product entity on Ingredient: optional store product data enabling pack-based shopping list display |
| 0011 | Grams-per-piece conversion: optional field bridging pieces and grams for shopping list calculations |
Create a new ADR when making an architectural decision with trade-offs worth preserving. Use the next sequential number.
Keep ADRs current. When a change affects an existing decision, update the relevant ADR. If a decision is superseded, mark the old ADR as superseded and reference the new one.
- After changing any Freezed model, you must run
dart run build_runner build --delete-conflicting-outputsor the app will not compile. *.g.dartand*.freezed.dartare committed to the repo (no build step in CI).- The
flutter_essentials/library is a local package insidelib/, not a separate pub package. - Platform target is desktop-first. Mobile platforms have limited save/load support.
- Always self-assign PRs when creating them.
- Always link PRs to issues using
Closes #Nin the PR body so issues auto-close on merge. - Always add the
waiting-for-human-checklabel when creating GitHub issues, pull requests, or any other reviewable artifact. This signals that no human has verified the content yet -- it is direct AI output. Once a human reviews it, the label is removed. The label communicates state (unreviewed), not origin.
If the repository does not have a waiting-for-human-check label, create it first:
gh label create "waiting-for-human-check" --description "No human has verified this yet -- direct AI output" --color "D93F0B"When you discover something during a task that was non-obvious and would save time in future sessions, add it to the relevant CLAUDE.md. Examples: an undocumented implicit dependency, a silent failure mode, a config quirk that causes hard-to-diagnose bugs.
This also applies when the user tells you to do something "every time", "always", or "never": immediately persist it rather than applying it only for the current session. Always prefer the most specific scope: project-level over global when the rule only applies to this repo.
Also add a rule whenever the codebase does something in a way that diverges from what a software developer or an AI would naturally write. If the correct approach here is not the standard/obvious one, a future agent will implement it the wrong way without an explicit rule.
Do NOT add:
- Standard patterns discoverable via normal code reading or search
- Things already covered by existing rules
- One-off context unlikely to recur
The bar: if a future agent could reasonably figure it out within a few seconds of exploration, don't add it. Only record knowledge that took a painful detour to uncover, or that diverges from what any competent developer would write by default.
Desktop-only distribution. build_and_copy.bat builds a Windows release and copies the portable EXE to the Desktop. No CI/CD pipeline -- builds are manual.