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

6.8 KiB
Raw Blame History

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

# 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 ConversionEntryrawToCooked = 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