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>
Option B aus dem Roadmap-Plan. requireProfile bekommt einen optionalen
message-Parameter mit dem bisherigen Text als Default — die 5 Bestands-
Aufrufe aendern sich nicht, die Wunschliste nutzt die Custom-Message
„um mitzuwünschen" sauber ueber den Helper statt mit dupliziertem
alertAction-Block.
Netto: -3 Zeilen in wishlist/+page.svelte, eine Duplikation weniger,
Helper dokumentiert jetzt explizit den Message-Override-Use-Case.
Gate: svelte-check 0 Warnings, 184/184 Tests, Wunschliste zeigt
korrekte Message beim Klick ohne Profil.
Refs docs/superpowers/plans/2026-04-19-post-review-roadmap.md Item G.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
border-radius: 999px war 15x im CSS dupliziert. Ausgelagert als
:root --pill-radius Variable im globalen :root-Block in +layout.svelte,
Call-Sites auf var(--pill-radius) umgestellt.
Bewusst NICHT angefasst (plan war "nur Werte die mehrfach vorkommen"):
- z-index: 10 Distinct Values in 14 Sites, bilden ein implizites
Layer-System. Konsolidieren = behavior-change-Risiko ohne konkreten
Nutzen. Wenn kuenftig einheitliche Modal-/Popover-Layer noetig,
separate Phase.
- setTimeout(): 3 Sites, jeder mit eigener Semantik (Debounce/Print/
Spinner). Kein DRY-Nutzen durch Extraktion.
Gate: svelte-check 0 Warnings, 184/184 Tests, Build clean, kein
sichtbarer Unterschied (einzige Aenderung: selber Wert ueber Variable).
Refs docs/superpowers/plans/2026-04-19-post-review-roadmap.md Item F.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RecipeEditor war noch die letzte Stelle im UI, die das
handgeschriebene "if (!res.ok) { alertAction(...) }"-Pattern
benutzte, welches wir in review-fixes-2026-04-18 ueberall sonst
durch asyncFetch() ersetzt hatten.
Netto: -14 Zeilen, konsistenter Fehlermessage-Fallback (body.message
> res.status), eine Import-Zeile weniger (alertAction raus, asyncFetch
rein).
Gate: svelte-check clean, 184/184 Tests, Upload/Delete-Flow per
Hand zu testen beim naechsten Editor-Touch.
Refs docs/superpowers/plans/2026-04-19-post-review-roadmap.md Item H.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Alle 10 pre-existing svelte-check WARNINGs ("state_referenced_locally")
in RecipeEditor.svelte und recipes/[id]/+page.svelte addressiert.
Die betroffenen `let foo = $state(recipe.bar)`-Pattern sind
intentional Snapshots: der Editor soll User-Edits behalten und nicht
von prop-Updates ueberschrieben werden. untrack() macht die Intent
explizit und silenced die Warnung sauber statt sie unter den Teppich
zu kehren.
Scope: imagePath, title, description, servings, prepMin, cookMin,
totalMin, ingredients, steps (RecipeEditor) + recipeState
(recipes/[id]/+page).
Gate: svelte-check 0 Warnings (war 10), Tests 184/184.
Refs docs/superpowers/plans/2026-04-19-post-review-roadmap.md Item I.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ingredient.ts:
- UNICODE_FRACTION_MAP fuer ½ ¼ ¾ ⅓ ⅔ ⅕ ⅖ ⅗ ⅘ ⅙ ⅚ ⅛ ⅜ ⅝ ⅞
- clampQuantity() weist 0, negative, > 10000 als null ab
- splitUnitAndName() helper, vorher 2x dupliziert (Unicode + ASCII Pfad)
Tests:
- 13 neue Tests fuer Unicode-Brueche (mit/ohne Unit) und Bounds
- bestaetigt dass deutsches Kommadezimal (0,25 l) bereits funktioniert
Hintergrund: Apple Food App liefert haeufig ½ und ⅓ in JSON-LD
Quantity-Feldern. Vor diesem Fix wurden die Felder als unparsable
behandelt (quantity null, name = '½ TL Salz'), was den Portionen-Slider
fuer importierte Rezepte unbrauchbar machte.
Findings aus REVIEW-2026-04-18.md (Refactor D) und structure.md
Die drei [searxng]-Logs sind absichtlich produktiv (Hilfe beim
Debuggen 'warum wurde Domain X gefiltert?'). Kommentar dokumentiert
das, damit kein zukuenftiger Cleanup sie pauschal entfernt.
baseRecipe-Fixture bleibt in tests/integration/recipe-repository.test.ts —
nur dort verwendet, nicht dupliziert (Review-Annahme war falsch).
yauzl/@types/yauzl bleiben als Dependency — bereits in
session-handoff-2026-04-17.md (Phase 5b) und ARCHITECTURE.md
verankert.
Findings aus REVIEW-2026-04-18.md (Wave 5 Cleanup) und structure.md
- src/lib/server/api-helpers.ts mit parsePositiveIntParam(),
validateBody<T>() und ErrorResponse type
- 13 unit tests fuer die beiden helper (HttpError-Shape verifiziert)
- Konsolidiert spaeter 9x parseId und 11x safeParse-Bloecke aus den
+server.ts handlern
Findings aus REVIEW-2026-04-18.md (Refactor A) und redundancy.md