Commit Graph

203 Commits

Author SHA1 Message Date
hsiegeln
e53cdc96fe feat(shopping): DELETE /api/shopping-list/checked (Erledigte entfernen)
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:26:32 +02:00
hsiegeln
a500a5623e feat(shopping): POST/DELETE /api/shopping-list/check
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:25:05 +02:00
hsiegeln
2750c298e9 feat(shopping): PATCH/DELETE /api/shopping-list/recipe/[id]
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:23:21 +02:00
hsiegeln
7baf60f422 feat(shopping): POST /api/shopping-list/recipe
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:21:35 +02:00
hsiegeln
e176b8c3f2 style(shopping): GET/DELETE endpoint 2-space indent
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:20:03 +02:00
hsiegeln
8570d41f53 feat(shopping): GET /api/shopping-list + DELETE (Liste leeren)
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:18:24 +02:00
hsiegeln
76864a6034 feat(shopping): formatQuantity-Utility
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:16:23 +02:00
hsiegeln
2c61d82935 feat(shopping): clearCart
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m14s
2026-04-21 23:13:58 +02:00
hsiegeln
974227590f feat(shopping): clearCheckedItems + Orphan-Cleanup
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m13s
2026-04-21 23:11:25 +02:00
hsiegeln
1889b0dea0 feat(shopping): toggleCheck (idempotent)
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m20s
Idempotentes Setzen/Loeschen von shopping_cart_check-Eintraegen
ueber (name_key, unit_key). Check ueberlebt Recipe-Removals,
solange ein anderes Rezept weiterhin zur Zeile beitraegt —
verifiziert durch 3 neue Integrationstests (17 total).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:08:20 +02:00
hsiegeln
494b672e8d fix(shopping): NULLIF-Guard gegen servings_default=0 in Aggregation
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:06:19 +02:00
hsiegeln
c31a9c6110 feat(shopping): listShoppingList mit Aggregation + Skalierung
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m14s
2026-04-21 23:02:05 +02:00
hsiegeln
85bf197084 feat(shopping): setCartServings mit Positiv-Validation
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m15s
2026-04-21 22:59:12 +02:00
hsiegeln
83fe95ac76 feat(shopping): removeRecipeFromCart
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m27s
2026-04-21 22:56:26 +02:00
hsiegeln
95ba14ad6f refactor(shopping): DEFAULT_SERVINGS-Konstante + Kommentare
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 22:54:54 +02:00
hsiegeln
8ceb5e95d7 feat(shopping): addRecipeToCart (idempotent via ON CONFLICT)
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m20s
2026-04-21 22:50:58 +02:00
hsiegeln
7dab267033 feat(shopping): Repository-Skeleton mit Types
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m16s
2026-04-21 22:47:21 +02:00
hsiegeln
45223df86d chore(db): Migration 013 fuer Einkaufsliste-Tabellen
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m20s
2026-04-21 22:43:50 +02:00
hsiegeln
d9490c8073 refactor(search): local search ignores domain filter
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 3m11s
Der Domain-Filter im Header-Dropdown wirkt ab jetzt ausschliesslich auf
die Web-Suche (SearXNG). Die Suche in gespeicherten Rezepten liefert
immer alle Treffer, unabhaengig von der Quelldomain -- wer ein Rezept
gespeichert hat, will es finden, selbst wenn er die Domain aus dem
Filter ausgeschlossen hat.

- SearchStore: filterParam -> webFilterParam, nur noch an Web-Calls
- /api/recipes/search: domains-Query-Param wird nicht mehr gelesen
- searchLocal(): domains-Parameter + SQL-Branch entfernt
- Tests entsprechend angepasst

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:59:48 +02:00
hsiegeln
0373dc32da feat(ai): Deutsch als starker Prior im OCR-Prompt
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 30s
Neue SPRACHE-Sektion weist Gemini explizit darauf hin, dass die
Texte ausschliesslich deutsch sind -- Umlaute, deutsche Zutaten,
deutsche Masseinheiten als Prior fuer die Zeichen-Rekonstruktion.
Soll die "Kontext-Detektiv"-Logik bei handgeschriebenen oder
verblassten Rezepten verbessern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:28:38 +02:00
hsiegeln
272a07777e feat(ai): OCR-Experten-Framing + expliziter User-Prompt
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m18s
Auf Gemini-Empfehlung: System-Instruction als OCR-Experte fuer
kulinarische Dokumente, mit "Kontext-Detektiv"-Regel fuer schwer
lesbare Zeichen, "[?]" fuer Unleserliches und strikter "keine
Halluzination"-Regel.

User-Prompt wird jetzt als eigene text-part bei jedem Call
mitgeschickt (Bild + User-Prompt + bei Retry die Korrektur-Note).

Inline-Schema aus dem Prompt entfernt, da es mit unserem
responseSchema konfligierte (servings vs servings_default+unit,
times-nested vs flat, instructions vs steps, kein note-Feld) --
das kann die beobachteten AI_FAILED-Schema-Validation-Fehler
beguenstigt haben. Struktur wird jetzt ausschliesslich ueber
responseSchema enforced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:26:18 +02:00
hsiegeln
efdcace892 feat(ai): reichhaltigeres Logging fuer AI_FAILED-Diagnose
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m15s
Der bisherige Log "[extract-from-photo] AI_FAILED after 43165ms,
385807 bytes" verriet nicht, ob es JSON-Parse, Schema-Validierung
oder ein SDK-Fehler war. Endpoint haengt jetzt e.message an;
gemini-client loggt den First-Attempt-Fehler vor dem Retry und
packt bei AI_FAILED beide Messages in den finalen Error.

Keine Prompt-/Response-Inhalte werden geloggt -- nur unsere eigenen
GeminiError-Messages (Zod-Pfade, "non-JSON output", SDK-toString).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:08:10 +02:00
hsiegeln
fb7c2f0e9b feat(photo-upload): zwei Buttons fuer Kamera vs. Datei-Picker
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 30s
Android-Chrome auf Tablet verhaelt sich zickig: mit capture="environment"
nur Kamera, ohne capture nur Datei-Picker -- nie beide. Zwei separate
Buttons (mit jeweils eigenem Input-Element) machen die Wahl explizit
und funktionieren ueberall eindeutig.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:45:37 +02:00
hsiegeln
33ee6fbf2e feat(photo-upload): Picker ohne capture -> auch gespeicherte Fotos
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m26s
capture="environment" zwang Mobile-Browser in den Kamera-Modus. Ohne
das Attribut zeigt der Browser auf Mobile die volle Auswahl
(Kamera / Fotomediathek / Datei) -- besser fuer Tablets und User,
die ein schon existierendes Kochbuch-Foto verwenden wollen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:39:07 +02:00
hsiegeln
e2713913e7 feat(photo-upload): Logging fuer Upload-Parse-Fehler
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
Der bisherige Endpoint verschluckte den formData()-Fehler mit einem
generischen "Multipart erwartet" — wir wissen nicht, warum Chrome auf
dem Tablet scheitert. Jetzt wird beim Fehler Content-Type, -Length und
User-Agent geloggt, plus die konkrete Error-Message in der Response.
Kein Foto-Inhalt im Log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:37:42 +02:00
hsiegeln
3bc7fa16e2 feat(photo-upload): Limits hochschrauben fuer Tablet-Fotos
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m16s
Tablet- und iPad-Pro-Kameras liefern JPEGs/HEICs bis 15 MB. Mit den
alten 8-/10-MB-Limits scheiterte das Upload beim SvelteKit-Body-Parser
mit "Multipart erwartet" (undurchsichtiger Fehler, weil SvelteKit den
Body frueher abweist als unser Endpoint-Check).

- Endpoint MAX_BYTES: 8 -> 20 MB
- BODY_SIZE_LIMIT: 10 -> 25 MB (mit Multipart-Overhead)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:31:34 +02:00
hsiegeln
173d9d138d fix(ai): sharp via createRequire, nicht ES-Import
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 28s
ES-Dynamic-Import mit @vite-ignore reichte nicht -- adapter-node's
Rollup-Step extrahiert sharp trotzdem in einen shared chunk und
bundelt sharp's interne dynamic-requires kaputt.

createRequire(import.meta.url) plus require('sharp') ist pure Node-
Runtime-Logik, die Rollup komplett ignoriert. sharp wird regulaer aus
node_modules geladen -- inkl. seiner Plattform-.node-Binary aus
@img/sharp-linuxmusl-arm64.

Verifikation: Build-Output enthaelt 0 Vorkommen von "dynamicRequireTargets"
und "sharp.node" (waren vorher in einem 319KB shared chunk).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:08:53 +02:00
hsiegeln
39de08abf9 fix(ai): sharp via dynamic import, nicht top-level
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m28s
Der vorige Versuch mit ssr.external in vite.config.ts war ein No-op:
adapter-node macht einen eigenen Rollup-Bundle-Schritt nach Vite und
ignoriert ssr.external komplett. Ergebnis: sharp's dynamic-require
fuer die native .node-Binary landet kaputt im Server-Bundle (332KB
Bundle-Chunk, 297 sharp-Referenzen).

Dynamic import mit /* @vite-ignore */ verhindert, dass Rollup sharp
aufloest — die Require geht stattdessen zur Laufzeit regulaer an
Node und findet @img/sharp-linuxmusl-arm64 in node_modules.

Ergebnis lokal: Server-Chunk von 332KB auf 14KB geschrumpft, nur noch
2 Referenzen auf den Paketnamen (der Import-String selbst).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 12:33:59 +02:00
hsiegeln
eea5fb7560 feat(ui): Camera-Icon im Header mit Gemini-Config- und Offline-Gate 2026-04-21 10:48:38 +02:00
hsiegeln
47e91de0a1 feat(ui): /new/from-photo Page mit File-Picker, Lade- und Fehler-States 2026-04-21 10:47:33 +02:00
hsiegeln
bc42f35f8c feat(client): PhotoUploadStore mit idle/loading/success/error 2026-04-21 10:45:36 +02:00
hsiegeln
8c23875ba2 feat(editor): Bild-Block skip wenn recipe.id === null 2026-04-21 10:44:48 +02:00
hsiegeln
06e60afc88 feat(api): POST /api/recipes fuer Scratch-Insert aus Foto-Import 2026-04-21 10:43:30 +02:00
hsiegeln
e01f15a2a6 feat(api): POST /api/recipes/extract-from-photo 2026-04-21 10:42:46 +02:00
hsiegeln
3f259a7870 feat(ai): simpler In-Memory-Ratelimiter pro IP 2026-04-21 10:41:16 +02:00
hsiegeln
904edcb3ff feat(ai): Gemini-Client mit Timeout, 1x-Retry und Fehler-Codes 2026-04-21 10:40:58 +02:00
hsiegeln
d479fd61d8 feat(ai): Extraction-Prompt + Gemini-Schema + Zod-Validator 2026-04-21 10:40:03 +02:00
hsiegeln
0cca9a699c feat(ai): image-preprocess mit sharp (Resize + JPEG + EXIF-Strip) 2026-04-21 10:39:22 +02:00
hsiegeln
c284f4b85b feat(ai): 50er-Pool Magie-Phrasen fuer Foto-description 2026-04-21 10:38:32 +02:00
hsiegeln
24bd9c1d1b feat(header): Versionsnummer unter dem Logo
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
Liest KOCHWAS_TAG via +layout.server.ts aus $env/dynamic/private
und zeigt den Tag als kleine graue Zeile unter dem Brand-Text auf
der Startseite. Fallback "dev" wenn nicht gesetzt. Auf engen
Screens mit ausgeblendetem Brand verschwindet auch die Version.

docker-compose.prod.yml reicht die Host-Env-Variable jetzt in den
Container durch (vorher nur fuers Image-Tag-Binding interpoliert).
2026-04-20 08:41:18 +02:00
hsiegeln
633e497bdc fix(sw): network-first + 3s timeout statt SWR fuer Daten
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 30s
SWR lieferte bei jedem Cache-Hit sofort die alte Antwort und
aktualisierte das Cache nur fuer den naechsten Request. Folge:
UI zeigte stale Daten, frische Daten erst nach Refresh.

Neu: network-first mit 3 s Timeout-Fallback. Netz gewinnt bei
frischer Antwort; Timeout oder Netzwerk-Fehler fallen auf Cache
zurueck. Pre-Cache-Logik (runSync) bleibt unveraendert, Shell
und Bilder bleiben cache-first.
2026-04-20 08:29:00 +02:00
hsiegeln
6bde3909d8 polish(sections): Muelltonne statt X + Ueberschrift groesser/fetter
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m22s
- IngredientRow: Sektion-entfernen-Button nutzt Trash2 (konsistent
  mit dem Zutat-Entfernen-Button daneben)
- RecipeView: section-heading von 1rem/600 auf 1.2rem/700, mehr
  vertikaler Abstand fuer deutlichere optische Trennung
- E2E-Spec: type-inference-Trick durch APIRequestContext-Import
  ersetzt (svelte-check stolperte bei typeof test mit TestDetails-
  Overload)
- Plan-Datei der Feature-Session mitcommitet

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:26:39 +02:00
hsiegeln
8069c5c246 feat(view): Zutaten-Sektionen als Ueberschriften rendern
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m20s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 15:08:43 +02:00
hsiegeln
7d6ee04fec feat(editor): Sektionen-Handler + save-Patch mit section_heading
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 15:06:12 +02:00
hsiegeln
b646720a6e fix(editor): :global(.ing-list):hover damit Fade-in wirklich greift 2026-04-19 15:04:26 +02:00
hsiegeln
526c7433f4 feat(editor): Sektionsueberschriften in IngredientRow + Insert-Button
DraftIng bekommt section_heading: string | null. IngredientRow
rendert davor einen Fade-in-Insert-Button (null) oder ein Heading-
Input mit Entfernen-Button (string). Props onaddSection/onremoveSection
ergaenzt; Styles an bestehendem Block angehaengt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 15:03:29 +02:00
hsiegeln
a1baf7f30a feat(db): section_heading roundtrip in recipe-repository
INSERT/SELECT in insertRecipe, replaceIngredients und getRecipeById
um section_heading ergänzt. IngredientSchema im PATCH-Endpoint sowie
Ingredient-Fixtures in search-local-, scaler- und repository-Tests
auf das neue Pflichtfeld aktualisiert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 14:55:46 +02:00
hsiegeln
b0d5f921e2 docs(migration): 012 Kommentar an 010/011-Stil angleichen (DE, Begruendung) 2026-04-19 14:52:13 +02:00
hsiegeln
72816d6b35 feat(schema): ingredient.section_heading (Migration 012 + Type)
Fuegt das nullable Feld section_heading zur ingredient-Tabelle hinzu
(Migration 012), erweitert den Ingredient-Typ und aktualisiert alle drei
Return-Stellen in parseIngredient. Downstream-Sites (repository, Editor,
Tests) bleiben rot – werden in Task 2+ behoben.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 14:49:42 +02:00
hsiegeln
504fbb6cc6 refactor(view): TimeDisplay als eigenstaendige Component
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m21s
timeSummary-Formatierung in eine wiederverwendbare Component
gezogen. RecipeView liefert nur noch die drei Werte — zukuenftige
Call-Sites (Preview, Hover-Cards) koennen dieselbe Logik reusen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:50:46 +02:00