Live-Test auf kochwas-dev offenbarte: bei Browser-Back hat der Browser
die History bereits gepoppt, bevor SvelteKit beforeNavigate feuert —
location.pathname war damit schon die Ziel-URL. recordScroll() schrieb
also den 0-Wert der Recipe-Page in den Slot der Home-Page und wischte
den eigentlich gemerkten Wert (z. B. 500) raus. Restore las dann 0,
fiel unter MIN_RESTORE_Y und tat nichts.
Fix: recordScroll(nav.from?.url) und restoreScroll(type, nav.to?.url).
Helper bekommen die URL explizit reingereicht — keine Abhängigkeit
mehr von location und damit kein Race mit der Browser-History.
Tests: zusätzliche Regression "does not overwrite a stored URL when
called with a different from-url" plus Skip-Pfade fuer null URLs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pages, die ihre Daten in onMount per fetch laden (Home, Wunschliste,
Einkaufsliste), waren bei popstate-Navigation kaputt: SvelteKit ruft
scrollTo() synchron nach Mount, aber die Listen sind dann noch leer
und das Dokument zu kurz — der Browser clamped auf 0.
Neuer Helper src/lib/client/scroll-restore.ts merkt scrollY pro URL in
sessionStorage (beforeNavigate) und stellt sie bei popstate per rAF-
Polling wieder her, sobald document.scrollHeight gross genug ist
(Hard-Budget 1.5s, danach best-effort scrollTo).
Layout ruft die zwei Helper im beforeNavigate / afterNavigate. Pages
mit SSR-Daten (z. B. /recipes) bleiben unbeeinflusst — dort matcht
unser Wert SvelteKits eigenen scrollTo bereits beim ersten Frame.
Tests: 7 neue Unit-Tests in tests/unit/scroll-restore.test.ts decken
Recording, Pro-URL-Trennung, Skip fuer Forward-Nav, sofortiges und
verzoegertes Restore ab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
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>
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>
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>
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>
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>
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>
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>
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>
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.
- 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>
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>
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>
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>
Zubereitungs-Liste mit Add + Remove als Sub-Component. Parent steuert
nur noch den Wrapper und reicht steps + die zwei Callbacks rein.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IngredientRow rendert eine einzelne editierbare Zutat-Zeile. DraftIng
und DraftStep sind jetzt in recipe-editor-types.ts, damit Parent und
Sub-Components auf dieselbe Form referenzieren.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Isoliert den Bild-Upload-Flow (File-Input, Preview, Entfernen-Dialog)
aus dem RecipeEditor. Parent haelt nur noch den <section>-Wrapper und
reicht recipe.id + image_path rein, kriegt Aenderungen per onchange
callback zurueck.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Enter waehrend Debounce-Fenster feuerte bislang eine zweite Fetch
fuer dieselbe Query. Race-Guard greift nicht, weil q identisch ist.
runSearch clearTimeout am Anfang behebt's, neuer Unit-Test sichert es.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der _q-Parameter wurde nie benutzt — Consumer sollen stattdessen
store.query im \$effect lesen, dann runDebounced() callen. Weniger
Footgun, explizitere Call-Site.
Tests-Rename: "mid-flight" → "cleared/changed", beschreibt was der
Test tatsaechlich absichert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der vorherige Test setzte query NACH dem Fetch-Abschluss und erzwang
dafuer einen setter-Side-Effect, der bei normalem Tippen die Treffer
waehrend des Debounce-Fensters fuer 300ms leer geblitzt haette.
Jetzt: echter Race-Test mit manuell aufloesbarem fetch. Setter-Nebenwirkung
entfernt, query ist wieder plain \$state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extrahiert die duplizierte Such-Logik aus +page.svelte und
+layout.svelte in eine gemeinsame Klasse. Pure Datenschicht
mit injizierbarem fetch — UI-Concerns (URL-Sync, Dropdown,
Snapshot) bleiben in den Komponenten.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>