Commit Graph

213 Commits

Author SHA1 Message Date
hsiegeln
4afc597689 fix(nav): Header-Back-Pfeil als echtes history.back()
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 3m19s
Der Zurück-Pfeil im Header war fest auf "/" verdrahtet und navigierte
forward, nicht backward. Damit ging die Scroll-Position der Origin-Seite
verloren und z. B. Wunschliste -> Rezept -> Zurück landete auf der
Startseite statt zurück auf der Wunschliste.

Jetzt: history.back() (mit goto('/') als Fallback bei leerer History).
SvelteKits eingebaute Scroll-Restoration greift dadurch wieder, und der
Pfeil tut was er optisch verspricht.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:09:45 +02:00
hsiegeln
a15390f4b8 fix(shopping): requireOnline-Guards + 2-space indent
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 3m14s
2026-04-21 23:59:14 +02:00
hsiegeln
0346a699b9 feat(shopping): Footer-Actions (Erledigte entfernen, Liste leeren)
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:47:30 +02:00
hsiegeln
f4eac4d9c3 feat(shopping): Rezept-Chips mit Portions-Stepper
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:45:32 +02:00
hsiegeln
3c30d1f35a feat(shopping): Zutaten-Rows mit Abhaken
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m17s
2026-04-21 23:43:00 +02:00
hsiegeln
943a645095 feat(shopping): Einkaufslisten-Seite mit Empty-State
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-21 23:40:52 +02:00
hsiegeln
7fa1079125 refactor(wishlist): horizontale Actions + Einkaufswagen-Button
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m15s
2026-04-21 23:38:26 +02:00
hsiegeln
0e6d2c93a6 feat(shopping): Header-Badge mit Einkaufswagen-Icon
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m14s
2026-04-21 23:34:57 +02:00
hsiegeln
1bd5dd106f feat(shopping): ShoppingCartStore (Client)
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m16s
Svelte-5-Runes-Store mit uncheckedCount, recipeIds und loaded.
refresh() holt Snapshot via GET /api/shopping-list, addRecipe/
removeRecipe posten bzw. loeschen und refreshen anschliessend.
Bei Netzwerkfehler bleibt der letzte bekannte State erhalten.
2026-04-21 23:31:29 +02:00
hsiegeln
dc15cf04a9 feat(shopping): Service-Worker network-only fuer /api/shopping-list/*
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m25s
Einkaufsliste-Endpunkte duerfen vom SW nie gecached werden — Liste
ist zustaendig fuer Check-States und muss immer live vom Server
gelesen werden. Test + resolveStrategy-Erweiterung analog zu den
anderen online-only-Endpunkten.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:28:17 +02:00
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