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>
- Neuer Endpoint POST/DELETE /api/recipes/:id/image.
* Multipart-Upload mit Feld "file".
* Whitelist: JPG, PNG, WebP, GIF, AVIF. Max 10 MB.
* Dedupe per SHA-256-Filename analog zu downloadImage().
- updateImagePath()-Repo-Funktion ergänzt.
- RecipeEditor: neuer Block "Bild" ganz oben. Preview + Buttons
"Hochladen"/"Ersetzen"/"Entfernen". Upload passiert direkt beim
Auswählen, nicht erst bei "Speichern" — das Bild ist eigene
Ressource, Abbrechen rollt es nicht zurück (okay, da dedupliziert).
- onimagechange-Callback informiert die Detail-Ansicht, damit die
Preview im RecipeView auch nach Abbrechen aktuell bleibt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der User pastet bewusst eine URL und erwartet, dass der Import
klappt — die Whitelist-Prüfung (DOMAIN_BLOCKED) im previewRecipe
war da nur Reibung. Die Whitelist bleibt für die Web-Suche relevant
(dort muss das Crawl-Feld eingeschränkt werden), für Imports nicht
mehr.
Dropped: isDomainAllowed + whitelist.ts, DOMAIN_BLOCKED-Code in
ImporterError, die zugehörige Branch in mapImporterError. Tests
entsprechend angepasst: statt "DOMAIN_BLOCKED wenn nicht whitelisted"
prüft der Preview-Test jetzt "klappt auch ohne Whitelist-Eintrag".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Auf der Rezept-Detail-Seite ein neuer „Bearbeiten"-Button (Pencil-Icon)
in der Action-Bar. Klick schaltet RecipeView auf RecipeEditor um.
Im Editor:
- Titel, Beschreibung, Portionen, Vorbereitungs-/Koch-/Gesamtzeit als
inline-Inputs.
- Zutaten: pro Zeile Menge, Einheit, Name, Notiz + Trash-Icon zum
Entfernen. „+ Zutat hinzufügen"-Dashed-Button am Listenende.
- Schritte: nummerierte Textareas, Trash-Icon, „+ Schritt hinzufügen".
- Mengen akzeptieren Komma- oder Punkt-Dezimalen.
- Empty-Items werden beim Speichern automatisch aussortiert.
Backend:
- Neue Repo-Funktionen updateRecipeMeta(id, patch), replaceIngredients,
replaceSteps — letztere in einer Transaction mit delete+insert und
FTS-Refresh.
- PATCH /api/recipes/[id] akzeptiert jetzt zusätzlich description,
servings_default, servings_unit, prep_time_min, cook_time_min,
total_time_min, cuisine, category, ingredients[], steps[]. Vorher
nur title/hidden_from_recent; diese beiden bleiben als
Kurz-Fall erhalten, damit bestehende Aufrufer unverändert laufen.
- Zod-Schema mit expliziten Grenzen (max-Länge, positive Mengen).
Tests: 3 neue Cases für updateRecipeMeta, replaceIngredients (inkl.
FTS-Update), replaceSteps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neue Sektion unter „Zuletzt hinzugefügt": sortierbar nach Name,
Bewertung, zuletzt gekocht und Hinzugefügt. Auswahl persistiert in
localStorage (kochwas.allSort).
- Neuer Endpoint GET /api/recipes/all?sort=name&limit=10&offset=0.
- listAllRecipesPaginated(db, sort, limit, offset) im repository:
NULLS-last-Emulation per CASE für rating/cooked — funktioniert auch
auf älteren SQLite-Versionen.
- Endless Scroll per IntersectionObserver auf ein Sentinel-Element am
Listen-Ende (rootMargin 200px, damit schon vor dem harten Rand
nachgeladen wird). Pagesize 10.
- 4 neue Tests: Name-Sort, Rating-Sort, Cooked-Sort, Pagination-Offset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Links im großen Suchfeld ein Slider-Icon mit Badge („Alle" oder „2/5"),
das ein Dropdown-Menü mit allen Whitelist-Domains als Checkboxen öffnet.
Auswahl wird per localStorage persistiert und gilt global — Header-Such-
Dropdown konsumiert den gleichen Store und sendet den domains-Parameter
bei jedem Fetch mit.
Leere Menge heißt „alle aktiv", damit neu vom Admin freigeschaltete
Domains automatisch dabei sind. Aktive Auswahl landet als explizite
Intersection mit der Whitelist serverseitig.
- searchLocal nimmt jetzt optional string[] domains → `source_domain IN (…)`.
- searchWeb nimmt jetzt opts.domains → site:-Filter auf die Auswahl
eingeschränkt. Nicht-Whitelist-Einträge werden ignoriert.
- API-Endpoints: `?domains=a.de,b.de`.
- Neuer Client-Store $lib/client/search-filter.svelte.ts.
- Neue Komponente $lib/components/SearchFilter.svelte (mobile-tauglich,
44px Touch-Targets, Badge auf engen Screens versteckt).
Home-Seite re-runt die Suche bei Filter-Änderung automatisch (150ms debounce),
ohne dass der User neu tippen muss.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die Ergebnislisten waren oft kurz, weil lokale Suche auf LIMIT 30 und
die Web-Suche auf die erste SearXNG-Seite beschränkt war. Jetzt lässt
sich beides nachladen.
- `searchLocal` nimmt jetzt einen `offset` und der `/api/recipes/search`-
Endpoint einen `?offset=`-Parameter.
- `searchWeb` nimmt jetzt eine `pageno`-Option und reicht sie als
`pageno`-Parameter an SearXNG weiter. `pageno=1` wird weggelassen,
damit bestehendes Verhalten unverändert bleibt.
- `/search` und `/search/web` zeigen unterhalb der Liste einen
„+ weitere Ergebnisse"-Button. Beide deduplizieren nachgeladene
Hits (ID bzw. URL), weil SearXNG das gleiche Ergebnis auf zwei
Seiten liefern kann.
Kein Endless-Scroll: expliziter Button ist mobil robuster und spart
die teure Thumbnail-Enrichment-Roundtrip-Zeit, die bei jeder neuen
Web-Seite anfällt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ersetzt das Settings-Zahnrad im Header durch ein Dreistriche-Menü. Das
Menü enthält zwei Punkte: „Register" führt zu einer neuen /recipes-Route
mit allen Rezepten alphabetisch gruppiert (A-Z-Buchstabenchips zum
Scrollen, Live-Filter oben, Umlaut-normalisiert). „Einstellungen" zeigt
wie bisher /admin.
Auf Mobile <520px wird das App-Icon komplett ausgeblendet, damit die
Suchleiste mehr Platz bekommt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Homepage:
- Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch
sortiert, lädt wenn Profil aktiv ist; versteckt sonst)
- Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon
zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die
Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1
unterdrückt. Section versteckt sich, wenn die Liste leer wird.
DB:
- Neue Migration 004_recipe_hidden_from_recent.sql (+Index)
- listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE)
- setRecipeHiddenFromRecent in actions.ts
API:
- GET /api/recipes/favorites?profile_id=X
- PATCH /api/recipes/[id] akzeptiert jetzt title und/oder
hidden_from_recent (Zod-Schema mit refine)
Rezept-Detail:
- Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben
H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab.
Kein location.reload() mehr.
- RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override.
- Neue Aktionsreihenfolge:
Zeile 1: Favorit | Wunschliste | Drucken
Zeile 2: Heute gekocht | Löschen
(Umbenennen ist jetzt am Titel statt in der Leiste.)
Icons (lucide-svelte, neues Dep):
- Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header,
Rezept-Detail, Wunschliste, Header-Dropdown:
🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe,
♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check,
🍳→ChefHat, X→X
- Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo,
Lucide käme da nicht sauber rein).
- SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme.
Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
Bug: Beim Neuanzeigen einer Rezeptseite war der Favoriten-Button immer
grau — isFav wurde als local $state(false) initialisiert und die
checkFavorite()-Funktion war eine Stub-Implementation, die nichts
gemacht hat. State lebte nur innerhalb einer Session.
Fix:
- Neue Server-Funktion listFavoriteProfiles(db, recipeId): number[]
in $lib/server/recipes/actions.ts
- +page.server.ts lädt favorite_profile_ids mit in die Page-Daten
- +page.svelte macht isFav zum $derived aus favoriteProfileIds +
aktivem Profil. toggleFavorite mutiert die lokale Liste (Add/Remove
der aktiven Profil-ID) — beim nächsten Load ist die Server-Liste
wieder Source of Truth.
- Alte Stub-Funktion checkFavorite() entfernt (inkl. Aufruf in
onMount).
RecipeView needs scaleIngredients on the client for live portion scaling.
Moved scaler.ts from $lib/server/recipes/ to $lib/recipes/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>