Files
biteplan/CLAUDE.md
T
davide 494c0fdbd4 docs: aggiorna CLAUDE.md con conteggio test, coverage e insidie note
184 test, coverage ~93%; documenta il coverage helper e il vincolo
sul caricamento async dei provider nei widget test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 20:38:57 +02:00

96 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
BitePlan è un'app Android per meal planning, conversione crudo/cotto e lista della spesa, scritta in Flutter. Tutta la UI e la UX sono in **italiano**. Il rilascio avviene come APK Android buildato via Docker.
## Ruolo atteso
Quando viene richiesta una modifica o una nuova funzionalità, segui questo flusso:
1. **Analizza prima di implementare**: valuta se la richiesta ha senso — fattibilità, complessità reale, impatto su architettura/UX/persistenza. Se esiste un modo migliore di implementarla, proponilo; se la richiesta è mal posta o ha un costo sproporzionato al beneficio, dillo esplicitamente con l'alternativa. Motiva i giudizi con riferimenti concreti al codice.
2. **Implementa da esperto**: se la richiesta va bene (o dopo l'eventuale discussione), implementala seguendo le convenzioni del progetto (feature-first, Provider, tutto in italiano).
3. **Suggerisci i test, non scriverli**: al termine indica quali test coprirebbero la modifica (casi e file dove andrebbero), ma implementali solo se richiesto esplicitamente.
## Comandi
```bash
# Sviluppo (host, no Docker)
emulator -avd biteplan & # AVD dedicato, Pixel 6 / Android 14 (google_apis x86_64)
flutter run # hot reload con "r", hot restart con "R"
# Analisi statica
flutter analyze # lint: package:flutter_lints/flutter.yaml
# Test rapidi (host)
flutter test # tutta la suite
flutter test test/features/meal_planner/qr_test.dart # singolo file
# Test riproducibili (Docker, immagine biteplan-build — usati come riferimento)
docker run --rm -v "$(pwd):/workspace" -w /workspace biteplan-build \
bash -c "flutter pub get --enforce-lockfile && flutter test"
# Integration test (richiede emulatore avviato)
flutter test integration_test/app_test.dart
# Build APK (headless, via Docker)
bash docker/build.sh # debug → dist/biteplan-debug.apk
export BITEPLAN_KEYSTORE_PASS=password # richiesto solo per --release
bash docker/build.sh --release # release → dist/biteplan-release.apk
```
## Sviluppo locale
- **Flutter host**: installato in `~/flutter` (tag `3.41.9`, stessa versione pinnata nel Dockerfile), nel `PATH` via `~/.bashrc`. Docker non si usa più per il dev; resta per test di riferimento e build APK (isolamento, `--enforce-lockfile`, firma release).
- **Emulatore**: Android SDK in `/home/davide/android-sdk`; accelerazione hardware via `/dev/kvm` (WSL2 con KVM abilitato).
- **AVD `biteplan`**: dedicato a questo progetto (Pixel 6, Android 14, google_apis/x86_64). Non riutilizzare AVD di altri progetti (es. `palladium_wallet`).
- **Permessi**: le build Docker girano come root nel volume montato — se `.dart_tool/` o `build/` risultano di proprietà root, serve `sudo chown -R davide:davide .dart_tool build` prima di usare flutter da host.
## Build e firma
- **Flutter pinnato**: versione `3.41.9` in `docker/Dockerfile` (`ARG FLUTTER_VERSION`); aggiornare anche `.flutter-version` e l'install host
- **Dipendenze**: `pubspec.lock` committato; la build usa `--enforce-lockfile` e fallisce se il lock non è allineato
- **Keystore**: `docker/biteplan.jks` è in `.gitignore`; da ottenere dall'autore o generare con `keytool`. Password via `BITEPLAN_KEYSTORE_PASS` (mai hardcoded); alias: `biteplan`
- **applicationId**: `com.davide.biteplan` — non cambiare, garantisce aggiornamento diretto dalla v1.2.1
- **versionCode**: letto da `pubspec.yaml` (`version: X.Y.Z+BUILD`); BUILD deve essere sempre crescente
## Architettura
Feature-first sotto `lib/features/` (meal_planner, converter, shopping_list, guide), ognuna con `models/`, `providers/`, `presentation/`. Codice trasversale in `lib/shared/` e `lib/core/`.
```
lib/
├── app.dart # MaterialApp + NavigationBar; in initState chiama UpdateService.checkUpdate() e mostra showUpdateDialog()
├── core/constants/app_constants.dart # kDayIds, kMealSlots, kStorageKey*, kAppVersion
├── shared/
│ ├── services/storage_service.dart # wrapper SharedPreferences (load/save)
│ ├── services/update_service.dart # checkUpdate() → GitHub API; isNewer(), parseTagName() (@visibleForTesting)
│ ├── services/url_launcher_service.dart # MethodChannel 'com.davide.biteplan/launcher' → Intent.ACTION_VIEW
│ └── widgets/update_dialog.dart # showUpdateDialog() — "Più tardi" / "Scarica"
├── features/
│ ├── meal_planner/ # MealPlan/DayPlan, qr_codec.dart (buildQrPayload, parseMealPlanFromQr), pagine planner + scan QR
│ ├── converter/ # ConversionEntry con rawToCooked/cookedToRaw, dati da assets/data/conversions.json
│ ├── shopping_list/ # ShoppingItem con quantity per aggregazione duplicati
│ └── guide/ # guide_page (3 tab) + info_bottom_sheet
└── assets/data/conversions.json # 50+ alimenti × metodi cottura
```
**State management**: Provider (`ChangeNotifier`), uno per feature, registrati in `main.dart` con `MultiProvider`.
**Persistenza**: `shared_preferences`, chiavi `meals` e `shopping_list` (JSON serializzato).
**Conversione**: logica in `ConversionEntry``rawToCooked = raw * yieldFactor`, `cookedToRaw = cooked / yieldFactor`.
**UI**: Material 3, seed color `Color(0xFF2d6a4f)`, tutto in italiano.
**QR**: payload JSON `{ "v": 1, "meals": { ... } }`, limite 2953 byte (capacità QR con error correction L).
**Update checker**: `UpdateService.checkUpdate()` usa `dart:io` `HttpClient` (nessun package aggiuntivo); disabilitato su web (`kIsWeb`). Il `MethodChannel` `com.davide.biteplan/launcher` è implementato in `android/app/src/main/kotlin/com/davide/biteplan/MainActivity.kt`.
## Testing
184 test unit + widget in `test/` (rispecchia la struttura di `lib/`), più un integration test in `integration_test/app_test.dart`. I test unit/widget non richiedono device. Coverage ~93% su `lib/` (esclusi per scelta: `main.dart`, il percorso camera di `qr_scan_page`).
- Storage isolato con `SharedPreferences.setMockInitialValues({})`
- Widget test: estensione `pumpApp` in `test/helpers/pump_app.dart`
- MethodChannel nei test: `TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler` — vedi `test/shared/widgets/update_dialog_test.dart`
- `test/coverage_helper_test.dart` importa tutta `lib/` così lcov include anche i file a coverage zero — aggiungere i nuovi file lì
- **Non chiamare** `loadDb()`/`load()` dei provider dentro il body di `testWidgets`: l'async reale nella zona FakeAsync può bloccare la suite (timeout 10 min). Farlo nel `setUp` — vedi `test/features/converter/pages/converter_page_test.dart`