# Teknisk Beskrivning - Flutter Frontend Viktigt att komma ihåg vid implementering av nya funktioner och kodning är att inte använda Windows-sökvägar. Använd inte `c:/dev/recipe-app/...` eftersom bygg- och testmiljön är på en remote Ubuntu-server. Utveckling sker lokalt och test samt drift sker på remote server. Säkerställ att inga absoluta Windows-sökvägar används i koden, för att stödja bygg och drift på Linux/Ubuntu. ## Senaste ändringar (2026-05-01, session 3) ### Separering av AI-chip och produktsuggestions-chip **Problem:** AI-chipet visade felaktigt produktnamnsförslag som om de vore kategoriförslag, vilket skapade förvirring när användaren såg "AI-förslag: Dryck Multivitamin" (ett produktnamn) istället för en kategori. **Lösning:** - **Blå chip** "Förslag: [produktnamn]" — när systemet hittat en trolig produkt via ordmatchning (inga AI-anrop inblandade). Klick väljer produkten. - **Grön chip** "AI-kategori: [kategoriväg]" — när AI:n föreslagit en kategori från databasen. Klick öppnar produktpickern filtrerad på den kategorin. **Kodändringar:** - `aiLabel` beräknas enbart från `categorySuggestionName`/`categorySuggestionPath` (kategoriförslag). - Nytt fält `suggestedProductLabel` för produktsuggestions-chip. - Separata villkor och UI-block för de två chipen i `_EditDialogState.build()`. ### Produktnamns-normalisering **Problem:** Kvittonamn i VERSALER (t.ex. "APRIKOSMARMELAD 284G") såg oprofessionella ut i UI:n. **Lösning:** Ny funktion `_normalizeProductName()` som tillämpar smarta regler: - Token med `/` (förkortningar) lämnas i versaler: `KY/KAL/LE/TO` - Token som börjar med siffra (mängd/storlek) görs till gemener: `284G` → `284g`, `12X85G` → `12x85g` - Övriga token: första bokstav versal, resten gemen: `APRIKOSMARMELAD` → `Aprikosmarmelad`, `JUICE TROPISK` → `Juice Tropisk` **Implementering:** - Top-level-funktion i `receipt_import_tab.dart` - Tillämpas när "Ny produkt"-fältet prefylls: `_newProductNameCtrl.text = _normalizeProductName(widget.current.productName ?? widget.item.rawName)` ### AI-kategorisering — validering i backend **Problem:** Användaren rapporterade att AI föreslog kategorin "Dryck Multivitamin", som inte fanns i databasen. **Undersökning:** - Backend-AI:n (`ai.service.ts`) validerar redan att `categoryId` finns i `categories`-listan och faller tillbaka på "Övrigt" om inte. - Problemet var att frontend visade produktnamnsförslag som om de vore kategoriförslag. **Lösning:** - Separering av chipen (se ovan) gör det tydligt att AI-kategoriförslag alltid kommer från databasen. --- ## Senaste ändringar (2026-05-01, session 2) ### Tvåstegs-picker: Kategori → Produkt Problembeskrivning: AI:n kan föreslå en kategori men produktpickern sökte bara på produktnamn, vilket gav nollresultat när kategorinamnet matades in som söktext. Lösningen är ett nytt separat arbetsflöde för produktval via kategoriträdet. **Ny widget: `lib/core/ui/category_then_product_picker.dart`** `CategoryThenProductPicker.show()` är en statisk metod som orkestrerar hela flödet: 1. **Steg 1 — Välj kategori:** Öppnar ett bottenark (`_CategoryPickerSheet`) med hela kategoriträdet (L1 → L2 → L3). Trädet är sökbart — sökfältet filtrerar till matchande lövnoder och visar hela sökvägen som brödsmula (t.ex. *Mat > Frukt & Grönt > Äpplen*). 2. **Steg 2 — Välj produkt:** Öppnar `ProductPickerField.showSheet()` filtrerad på alla produkter som tillhör den valda kategorin **eller någon av dess ättlingar** (L1 samlar alltså in L2- och L3-produkter rekursivt via `_collectIds()`). **Inbyggt AI-stöd:** Om `preselectedCategoryId` skickas in (från AI-förslaget) hoppar `show()` direkt till steg 2 — kategoriträdet visas aldrig. Om kategorin inte hittas i trädet faller den tillbaka till att visa trädet. **Fallback för L1/L2-noder:** Mellanliggande noder (L1, L2) som inte är löv har en liten "Välj"-knapp till höger i raden. Klick på kategorinamnet/pilen expanderar/kollapsar som vanligt; klick på "Välj" väljer kategorin och öppnar produktpickern direkt. **Trädknapp i redigeringsdialogen:** Bredvid det vanliga produktsökfältet finns nu en `OutlinedButton` med trädikon (🌳). Klick öppnar tvåstegs-pickern utan AI-förval. ### Skapa ny privat produkt i importflödet Om inget i produktlistan matchar kvittoradens vara kan användaren skapa en egen produkt direkt från produktpickern. **Flöde:** 1. Välj kategori (via trädet eller AI-direkthopp). 2. Produktpickern visas med en **"Skapa ny"**-knapp i rubrikraden. 3. En enkel dialog öppnas med ett namnfält (förfyllt med söksträngen om sådan finns). 4. Vid bekräftelse anropas `POST /products/private` — produkten skapas som privat och user-scopad. 5. Den nya produkten läggs till i den lokala produktlistan och väljs direkt. **Privata produkter — arkitektur:** | | Globala produkter | Privata produkter | |---|---|---| | Endpoint (hämta) | `GET /products` | `GET /products/mine` | | Endpoint (skapa) | `POST /products` (admin) | `POST /products/private` (alla inloggade) | | Synlighet | Alla användare | Bara ägaren | | `isPrivate` | `false` | `true` | | `normalizedName` | `normalize(name)` | `private:{userId}:{normalize(name)}` | | Kategori | Global (admin-only) | Väljs vid skapandet, global kategori | Importfliken laddar globala och privata produkter parallellt via `Future.wait` och slår ihop dem till en gemensam `_products`-lista. Den lokala `_localProducts`-listan i `_EditDialogState` utökas om en ny produkt skapas under dialogen, utan att en ny nätverksanrop krävs. ### Redigeringsdialog — sammanfattning av alla fält `_EditDialog` i `receipt_import_tab.dart` innehåller nu: - **AI-chip (grön):** Klickbar. Om AI föreslog en matchad produkt direkt → väljer den. Om AI föreslog en kategori → öppnar produktpickern filtrerad på den kategorin (utan att visa trädet). - **Destinationsväljare:** `SegmentedButton` — Inventarie eller Baslager. - **Produktfält + trädknapp:** `ProductPickerField` (fritext-sökning) + knapp för att öppna tvåstegs-picker. - **Antal/Enhet:** Visas bara vid Inventarie-destination. ### Designregel bekräftad: User-scope User-scope-principen dokumenterades formellt i båda tekniska beskrivningarna (2026-05-01). Privata produkter är det första exemplet på mönstret för resurser som är varken globala (alla ser dem) eller fullt user-owned (bara ägaren ser dem): - `Product.isPrivate = true` + `Product.ownerId = userId` - `normalizedName`-prefix undviker databaskollision med globala produkter - Migration: `20260501000000_add_product_is_private ## Senaste ändringar (2026-05-01, session 1) **Kvittoimport Fas 6b — komplett:** - Granskningssteg i `receipt_import_tab.dart` med per-rad checkbox, redigeringsdialog och destinationsväljare. - Destinationsväljare: **Inventarie** eller **Baslager** via `SegmentedButton<_Destination>` i redigeringsdialogen. - Vid vald destination **Baslager** dolt antal/enhet-fält med informationstext. - Inventarie-flöde: skapar ny post eller slår ihop (PATCH quantity) med befintlig post. Förhandsvisning av ny mängd visas i listan. - Baslager-flöde: lägger till produkten om den inte redan finns. Befintliga baslagerprodukter visas med orange chip. - Snackbar visar separat räkning: skapade/sammanslagna i inventarie + tillagda/hoppa-över i baslager. - `_loadInventory()` laddar nu inventarie och baslager i parallell via `Future.wait`. - AI-kategorisuggestion chip (grön) visas för premium-användare i granskningslistan. ## Senaste ändringar (2026-04-25) **Arkitektur- och UX-förbättringar:** - Grid-vy för recept: Kolumnval (2/4/6/8) via ikon i AppShell, med Riverpod-provider och SharedPreferences. - RecipesScreen är nu body-only, ingen egen Scaffold/AppBar. - AppShell visar grid-ikon endast på /recipes. - Buggfix: Produktväljaren i pantry/inventarie (ProductPickerField) — bottenark implementeras. - Kodkvalitet: Inga absoluta Windows-sökvägar. **Tekniska förbättringar:** - Förbättrad bildhantering med normalisering av URL:er, fallback och diagnostikloggning. - Robustare importflöde för recept med stöd för PDF, bild och ICA-länkar. - User-scope för pantry och matplan: Alla baslager- och matplansdata är nu per användare. ## Syfte och mål - Isolerad Flutter-baserad frontend i separat Docker-service. - Web först, men med arkitektur som kan återanvändas för Android/iOS. - Stegvis migrering av funktioner från befintlig Next.js-frontend. ## Arkitektur ### Lager - **Presentation:** Skärmar och widgets i `flutter/lib/features/*/presentation`. - **State/Application:** Riverpod providers/notifiers i `flutter/lib/features/*/data`. - **Data/API:** `ApiClient` i `flutter/lib/core/api`. - **Platform abstraction:** Token storage interface i `flutter/lib/core/platform`. ### Routing - GoRouter i [flutter/lib/core/router/app_router.dart](flutter/lib/core/router/app_router.dart). - Nuvarande routes: - `/login` — loginskärm - `/recipes` — receptlista (ShellRoute med AppShell) - `/recipes/create` — nytt recept, utanför ShellRoute - `/recipes/:id` — receptdetalj, utanför ShellRoute - `/recipes/:id/edit` — redigera recept, utanför ShellRoute - `/profile` — profil (ShellRoute med AppShell) - `/recipes/create` måste vara listad före `/recipes/:id` i routelistan för att undvika konflikt. - Detaljsidor (detalj, skapa, redigera) ligger utanför ShellRoute för att få full-screen med automatisk back-knapp. ### Auth - Login endpoint: `POST /api/auth/login`. ## Kända fallgropar och API-gotchas ### Designregel: User-scope vid ny funktionalitet > **Regel:** Kontrollera alltid om ny data tillhör en specifik användare. Om ja — data **måste** ha user-scope i backend (`userId`-fält, filtrering i service) och Flutter-klienten får **inte** returnera/visa andra användares data. > > Resurser och deras scope: > | Resurs | Scope | > |---|---| > | Produkter (globala) | Publik — `GET /products` | > | Produkter (privata) | Per användare — `GET /products/mine`, `POST /products/private` | > | Kategorier | Global (admin-only att skapa) | > | Inventarie | Per användare — alltid filtrera på `userId` | > | Baslager | Per användare — alltid filtrera på `userId` | > | Matplan | Per användare — alltid filtrera på `userId` | > | Recept | Per användare (ägare) eller delat | > > I Flutter: ladda user-scopad data med token, kombinera globala och egna produktlistor vid behov. ### Flutter Web och PDF MIME-typ - Flutter Web skickar PDF-filer med MIME-typ `application/octet-stream` istället för `application/pdf`. - Backend (`receipt-import.controller.ts`) måste tillåta båda: `application/pdf` och `application/octet-stream` i `ALLOWED_MIMES`. - Symptom om detta saknas: HTTP 400 "Otillåten filtyp. Använd JPEG, PNG, WebP eller PDF." ### NestJS @Post() returnerar HTTP 201 som standard - NestJS returnerar 201 (Created) för alla `@Post()`-endpoints som standard. - Flutter-klienten (`ImportRepository`) accepterar nu alla 2xx-svar (`statusCode < 200 || statusCode >= 300`). - Alternativt: lägg till `@HttpCode(200)` på backend-controllern för att returnera 200. - Detta gjordes i `receipt-import.controller.ts` (2026-04-30). ### Kvittoimport — backend och arkitektur - `/receipt-import` hanteras av en **dedikerad** `ReceiptImportController` och `ReceiptImportService`, **inte** av `QuickImportController`. - `ReceiptImportService` använder **Mistral AI** för att parsa kvitton till strukturerade `ParsedReceiptItem[]` direkt — inget markdown. - Svaret från `/receipt-import` är en array av `ParsedReceiptItem`, inte `{ markdown: "..." }`. - `{ markdown: "..." }` är svaret från `/quick-import` (receptimport) — inte kvittoimport. ## Övrigt - **Kvittoimport (Fas 6b):** ✅ Klar (2026-05-01) — granskningssteg, destination-väljare, merge och spara till inventarie/baslager. - **Bildimport:** Säkerställa att containrar är uppdaterade med senaste kod och att diagnostikloggar syns vid felsökning. - **Adminfunktioner:** Avancerad AI-integration och ytterligare adminfunktioner planeras men är ej migrerade. ## Syfte och mål - Isolerad Flutter-baserad frontend i separat Docker-service. - Web först, men med arkitektur som kan återanvändas för Android/iOS. - Stegvis migrering av funktioner från befintlig Next.js-frontend. ## Relaterade dokument - [next_steps_flutter.md](next_steps_flutter.md) - [README.md](README.md) - [teknisk_beskrivning.md](../TEKNISK_BESKRIVNING.md) (för backend-kontext) ## Arkitektur ### Lager - **Presentation:** Skärmar och widgets i `flutter/lib/features/*/presentation`. - **State/Application:** Riverpod providers/notifiers i `flutter/lib/features/*/data`. - **Data/API:** `ApiClient` i `flutter/lib/core/api`. - **Platform abstraction:** Token storage interface i `flutter/lib/core/platform`. ### Routing - GoRouter i [flutter/lib/core/router/app_router.dart](flutter/lib/core/router/app_router.dart). - Nuvarande routes: - `/login` — loginskärm - `/recipes` — receptlista (ShellRoute med AppShell) - `/recipes/create` — nytt recept, utanför ShellRoute - `/recipes/:id` — receptdetalj, utanför ShellRoute - `/recipes/:id/edit` — redigera recept, utanför ShellRoute - `/profile` — profil (ShellRoute med AppShell) - `/recipes/create` måste vara listad före `/recipes/:id` i routelistan för att undvika konflikt. - Detaljsidor (detalj, skapa, redigera) ligger utanför ShellRoute för att få full-screen med automatisk back-knapp. ### Auth - Login endpoint: `POST /api/auth/login`. - Backend kontrakt använder `username` + `password`. - Token-fält i svar: `accessToken`. - Token lagras via `ITokenStorage` (web implementation med SharedPreferences). - Auth-gate i router: utloggad användare redirectas till `/login`, inloggad redirectas från `/login` till `/recipes`. - Splash-skärm (`/`) visas medan auth-state läses från storage vid app-start. - `guardedApiCall()` i `flutter/lib/core/api/guarded_api_call.dart` hanterar automatisk logout vid 401. ### API-lager - `ApiClient` i `flutter/lib/core/api/api_client.dart` exponerar: `getJson`, `postJson`, `patchJson`, `putJson`, `deleteJson`. - Centraliserad HTTP-felklassning: 401 -> `ApiErrorType.unauthorized`, 403 -> `forbidden`, 5xx -> `server`, nätverksfel -> `network`. - `ApiException` i `flutter/lib/core/api/api_exception.dart` är den enda feltypen som propageras från repositories. - `mapErrorToUserMessage(error, context)` i `flutter/lib/core/api/api_error_mapper.dart` översätter fel till lokaliserade användarmeddelanden. Tar numera `BuildContext` som andra argument för att hämta korrekt språk från `AppLocalizations`. ### Lokalisering - Flutter `flutter_localizations` + `intl` tillagda i `pubspec.yaml` med `generate: true`. - Konfigurationsfil: [flutter/l10n.yaml](flutter/l10n.yaml) med `synthetic-package: false`. - Källsträngar i [flutter/lib/l10n/app_sv.arb](flutter/lib/l10n/app_sv.arb) och [flutter/lib/l10n/app_en.arb](flutter/lib/l10n/app_en.arb). - Genererade filer hamnar i `flutter/lib/l10n/generated/` och checkas inte in. - Hjälpare `context.l10n` i [flutter/lib/core/l10n/l10n.dart](flutter/lib/core/l10n/l10n.dart). - `MaterialApp.router` konfigurerad med `localizationsDelegates`, `supportedLocales` och `locale: const Locale('sv')`. - Dockerfilen kör `flutter gen-l10n` innan `flutter build web` för att generera Dart-koden i containerbygget. - Regressionstest i [flutter/test/core/swedish_strings_regression_test.dart](flutter/test/core/swedish_strings_regression_test.dart) misslyckas om vanliga ASCII-varianter (Välj, Lägg, Försök, etc.) dyker upp igen i `lib/`. #### Användning av lokalisering För att lägga till en ny lokaliserad sträng: 1. Lägg till nyckeln i `app_sv.arb` (och `app_en.arb`). 2. Kör `flutter gen-l10n` lokalt (eller låt Docker-bygget göra det). 3. Använd `context.l10n.dinNyckel` i widgetkoden. För felsträngar från API: använd alltid `mapErrorToUserMessage(error, context)` — lägg inte in hårdkodade strängar. ## Vad som är gjort ### Infrastruktur - Ny compose override: [compose.flutter.yml](compose.flutter.yml). - Ny Flutter-service: `recipe-flutter`. - Service ansluten till nätverk: - `recipe-internal` (backend access) - `proxy` (external Caddy reachability) - Flutter-container serverar web build via intern Caddy. - Exponering via extern Caddy med site `test.gynther.se` -> `recipe-flutter:5000`. - API-anrop går same-origin via `/api` och proxas internt till `recipe-api:8080`. ### Inventarie (2026-04-22) - Filtrering per plats (alla/kyl/frys/skåfferi) via `inventoryLocationFilterProvider`. - Sortering (namn A-Ö, bäst före stigande/fallande) via `inventorySortFilterProvider`. - Riverpod-query (`InventoryQuery`) skickar `location` och `sort` som queryparametrar till backenden. - Alla felmeddelanden går via `mapErrorToUserMessage(error, context)`. ### Baslager/Pantry (2026-04-22) - Pantry-produkter grupperas nu på kategori utifrån backend-relationen `categoryRef` (rekursiv `parent`-kedja -> `categoryPath`), med fallback till legacy `product.category` och sist `Övrigt`. - `PantryProduct` har `categoryId` och `categoryPath` som parsas från API-svaret. ### Backend: User-scope för pantry och matplan (2026-04-22) - `PantryItem` och `MealPlanEntry` är nu user-scopade i Prisma-schemat. - `pantry`-controller/service filtrerar alltid per `userId` från JWT. - `meal-plan`-controller/service filtrerar alltid per `userId`; `inventoryCompare` använder inloggad användares pantry. - Migration: `20260422130000_user_scope_pantry_meal_plan` applicerad på server. - Next.js-frontenden krävde inga funktionella ändringar (förfrågningar går redan via auth-proxy). ## Arkitektur ### Lager - Presentation: skarmar och widgets i `flutter/lib/features/*/presentation`. - State/Application: Riverpod providers/notifiers i `flutter/lib/features/*/data`. - Data/API: `ApiClient` i `flutter/lib/core/api`. - Platform abstraction: token storage interface i `flutter/lib/core/platform`. ### Routing - GoRouter i [flutter/lib/core/router/app_router.dart](flutter/lib/core/router/app_router.dart). - Nuvarande routes: - `/login` — loginskarm - `/recipes` — receptlista (ShellRoute med AppShell) - `/recipes/create` — nytt recept, utanfor ShellRoute - `/recipes/:id` — receptdetalj, utanfor ShellRoute - `/recipes/:id/edit` — redigera recept, utanfor ShellRoute - `/profile` — profil (ShellRoute med AppShell) - `/recipes/create` maste vara listad fore `/recipes/:id` i routelistan for att undvika konflikt. - Detaljsidor (detalj, skapa, redigera) ligger utanfor ShellRoute for att fa full-screen med automatisk back-knapp. ### Auth - Login endpoint: `POST /api/auth/login`. - Backend kontrakt anvander `username` + `password`. - Token-falt i svar: `accessToken`. - Token lagras via `ITokenStorage` (web implementation med SharedPreferences). - Auth-gate i router: utloggad anvandare redirectas till `/login`, inloggad redirectas fran `/login` till `/recipes`. - Splash-skarm (`/`) visas medan auth-state lases fran storage vid app-start. - `guardedApiCall()` i `flutter/lib/core/api/guarded_api_call.dart` hanterar automatisk logout vid 401. ### API-lager - `ApiClient` i `flutter/lib/core/api/api_client.dart` exponerar: `getJson`, `postJson`, `patchJson`, `putJson`, `deleteJson`. - Centralicerad HTTP-felklassning: 401 -> `ApiErrorType.unauthorized`, 403 -> `forbidden`, 5xx -> `server`, natverksfel -> `network`. - `ApiException` i `flutter/lib/core/api/api_exception.dart` ar den enda feltypen som propageras fran repositories. - `mapErrorToUserMessage(error, context)` i `flutter/lib/core/api/api_error_mapper.dart` oversatter fel till lokaliserade anvandarmedddelanden. Tar numera `BuildContext` som andra argument for att hämta korrekt sprak fran `AppLocalizations`. ### Lokalisering (2026-04-22) - Flutter `flutter_localizations` + `intl` tillagda i `pubspec.yaml` med `generate: true`. - Konfigurationsfil: [flutter/l10n.yaml](flutter/l10n.yaml) med `synthetic-package: false`. - Kallstrangar i [flutter/lib/l10n/app_sv.arb](flutter/lib/l10n/app_sv.arb) och [flutter/lib/l10n/app_en.arb](flutter/lib/l10n/app_en.arb). - Generade filer hamnar i `flutter/lib/l10n/generated/` och checkas inte in. - Hjalpare `context.l10n` i [flutter/lib/core/l10n/l10n.dart](flutter/lib/core/l10n/l10n.dart). - `MaterialApp.router` konfigurerad med `localizationsDelegates`, `supportedLocales` och `locale: const Locale('sv')`. - Dockerfilen kor `flutter gen-l10n` innan `flutter build web` for att generera Dart-koden i containerbygget. - Regressionstest i [flutter/test/core/swedish_strings_regression_test.dart](flutter/test/core/swedish_strings_regression_test.dart) failar om vanliga ASCII-varianter (Valj, Lagg, Forsok, etc.) dyker upp igen i `lib/`. #### Anvandning av lokalisering For att lagga till en ny lokaliserad strang: 1. Lagg till nyckeln i `app_sv.arb` (och `app_en.arb`). 2. Kör `flutter gen-l10n` lokalt (eller lat Docker-bygget gora det). 3. Anvand `context.l10n.dinNyckel` i widgetkoden. For felstrangar fran API: anvand alltid `mapErrorToUserMessage(error, context)` — lagg inte in hardkodade strängar. ### Recipes - `GET /api/recipes` — receptlista. - `GET /api/recipes/:id` — receptdetalj inkl. ingredienser. - `POST /api/recipes` — skapa recept. - `PATCH /api/recipes/:id` — uppdatera recept (OBS: PATCH, inte PUT). - `DELETE /api/recipes/:id` — ta bort recept (returnerar 204, ingen body). - `POST /api/recipes/parse-markdown` — tolka Markdown, returnerar ParsedRecipe med ingrediensforslagslistor. - Flutter Recipe-model: `name`-fallback till `title`, null-safe parsing av Decimal-strang till double. ### Gemensamma UI-komponenter - `LoadingStateView`, `EmptyStateView`, `ErrorStateView` i `flutter/lib/core/ui/async_state_views.dart`. - `AppShell` i `flutter/lib/core/ui/app_shell.dart`: responsiv NavigationRail (bred) / NavigationBar (smal), delad AppBar med logout. ## Kända API-fallgropar (viktigt!) > **Regel:** Kontrollera alltid HTTP-metod i [TEKNISK_BESKRIVNING.md i /recipe-app] innan en ny repository-metod implementeras. | Operation | Korrekt metod | Fel som gjorts | |-------------------------|-----------------------------|------------------------------| | Uppdatera recept | `PATCH /api/recipes/:id` | `PUT` — ger 404 | | Uppdatera inventariepost| `PATCH /api/inventory/:id` | Använd PATCH | | Uppdatera matplan | `POST /api/meal-plan` (upsert) | Ingen PUT/PATCH | Generell regel: NestJS-backenden använder `PATCH` för partiella uppdateringar, inte `PUT`. `PUT` exponeras inte på några resurser i detta projekt. ## Drift och deploy ### Build/run - Build argument i compose: `API_BASE_URL=/api`. - Build command: - `docker compose -f compose.yml -f compose.flutter.yml build recipe-flutter` - Run command: - `docker compose -f compose.yml -f compose.flutter.yml up -d --no-deps recipe-flutter` ### Rekommenderat kommandomönster För att minska risken för fel startordning eller missförstånd mellan huvudappen och Flutter-spåret: **När huvudappen ska vara uppe:** - `docker compose up -d recipe-db recipe-api recipe-frontend` **När Flutter-klienten ska vara uppe:** - `docker compose -f compose.yml -f compose.flutter.yml up -d --no-deps recipe-flutter` **När både huvudappen och Flutter testas parallellt:** 1. Starta huvudappen med `compose.yml`. 2. Starta sedan Flutter med override-filen `compose.flutter.yml`. Viktigt: - `docker compose build ...` bygger bara image. - `docker compose up -d ...` krävs alltid för att containern faktiskt ska starta. ### Viktiga verifieringar - Compose merge valid: - `docker compose -f compose.yml -f compose.flutter.yml config` - Container reachable from external Caddy: - `docker exec caddy wget -S -O- http://recipe-flutter:5000` - Public endpoint: - `curl -I https://test.gynther.se` ## Viktiga beslut - `compose.yml` lämnas orörd; Flutter ligger i separat override-fil. - Flutter-web använder same-origin API (`/api`) för att undvika mixed-content. - Intern Caddy i Flutter-container hanterar static hosting + `/api` proxy. - Feature migration sker stegvis enligt [next_steps_flutter.md](next_steps_flutter.md). ## Kända fallgropar - Om `recipe-flutter` inte är i `proxy` nätverket blir det 502 från extern Caddy. - Om webbläsaren visar gammal JS kan gamla API-URL:er leva kvar i cache/service worker. - Login med e-post fungerar inte om backend förväntar sig användarnamn. - `recipe-flutter` kan stoppas av `docker compose down --remove-orphans` om kommandot körs utan override-filen och Flutter-spåret tidigare varit uppe. - En orphan-varning för `recipe-flutter` är normalt förväntad när man kör huvudappen med bara `compose.yml`; det betyder inte att backend eller Prisma är trasiga. ### Orphan-varning i praktiken Om du ser en varning om orphan-containers under arbete med huvudappen betyder det oftast att `recipe-flutter` tidigare startats via: - `docker compose -f compose.yml -f compose.flutter.yml up -d recipe-flutter` och att du nu kör ett kommando som bara använder `compose.yml`. Detta är normalt och ofarligt så länge du vet vilken stack du avser att köra. Om `test.gynther.se` slutar svara efter städning med `--remove-orphans`, starta om Flutter-spåret med: - `docker compose -f compose.yml -f compose.flutter.yml up -d --no-deps recipe-flutter` --- ## Nyheter och förbättringar (2026-04-22) - **User-scope för pantry och matplan** — Flutter-klienten använder nu user-scopade endpoints för baslager och matplan. JWT används för filtrering i alla anrop. - **Robust bildimport** — Bild-URL normaliseras och skickas hela vägen till UI. Flutter hanterar både markdown och bild-url vid import. - **Importflöde** — Quick-import och receipt-import har förbättrats med robust multipart-hantering, timeout, och felhantering. Prefill av markdown och bild-url fungerar i Flutter. - **Flutter-parity** — Matplan, inventarie, baslager och receptflöden är nu fullt migrerade till Flutter med user-scope och robust felhantering. - **Felsökningslogg** — Se `../IMPORT_IMAGE_DEBUG_2026-04-22.md` för detaljerad felsökningshistorik kring bildimport och importflöde. ### Kända begränsningar - Kvittoimport (Fas 6b) är påbörjad men granskningssteg och bulk-spara återstår. - Bildimport kräver att containrar är uppdaterade med senaste kod — kontrollera att diagnostikloggar syns vid felsökning. - Vissa adminfunktioner och avancerad AI-integration är planerade men ej migrerade. ## Nyheter och tekniska förbättringar (2026-04-24) - **Navigationsflöden:** - Navigationslänkar har lagts till mellan recept, inventarie, baslager och matplan för att förbättra användarupplevelsen och minska antalet klick. - Efter redigering av recept och konsumtion av inventariepost sker nu automatisk navigering till relevant vy. - Efter import av recept sker automatisk navigering till receptlistan. - **UX och UI:** - Receptdetaljvyn har fått förbättrad bakgrundsbildshantering och mjukare scrollning. - **Plattformsoberoende kod:** - Genomgång av kodbasen för att säkerställa att inga absoluta Windows-sökvägar används, vilket gör projektet robust för Linux/Ubuntu-baserad bygg- och driftmiljö. Se även README.md och next_steps_flutter.md för mer detaljer om användarflöden och leveransplan.