Commit Graph

85 Commits

Author SHA1 Message Date
hsiegeln
657d006441 fix(recipe): Favoriten-Markierung persistiert beim Neuladen
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 50s
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).
2026-04-17 18:43:38 +02:00
hsiegeln
cf31e79fb0 feat(loader): SearchLoader mit wackelnder Pfanne und rotierenden Sprüchen
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 54s
Neue Komponente src/lib/components/SearchLoader.svelte ersetzt die
stumpfen "Suche läuft …"-Zeilen an allen vier Stellen:
- Startseite (scope=local und scope=web)
- Header-Dropdown (size=sm, beide Scopes)

Was passiert:
- Ein Pfannen-Emoji (🍳🥘🍲🍜🥣) wechselt alle 900 ms
- Wobble-Animation kippt es im 1.4-s-Takt hin und her
- Drei Dampf-Punkte steigen zeitversetzt auf und fadeen
- Caption unten rotiert alle 1.8 s durch vier passende Sprüche
  (lokal: "Stöbere im Rezeptbuch …", web: "Schnuppere in fremden
  Küchen …" etc.)

Zwei Size-Varianten: md (Homepage) und sm (Header-Dropdown).
2026-04-17 18:40:38 +02:00
hsiegeln
4d90d51501 feat(search): persistenter Thumbnail-Cache in SQLite, Default-TTL 30 Tage
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 54s
Vorher: In-Memory-Map, TTL 30 Minuten. Container-Neustart verwarf den
kompletten Cache, also musste nach jedem Deploy jede Suche wieder alle
Seiten laden.

Jetzt:
- Neue Tabelle thumbnail_cache (url PK, image, expires_at)
- Default-TTL 30 Tage, per Env KOCHWAS_THUMB_TTL_DAYS konfigurierbar
  (7, 365, was der User will — is alles ok laut Nutzer)
- Negative Cache: Seiten ohne Bild werden mit image=NULL gespeichert,
  damit wir nicht jede Suche die gleiche kaputte Seite wieder laden
- Lazy-Cleanup: pro searchWeb-Aufruf werden abgelaufene Zeilen via
  DELETE ... WHERE expires_at <= now() weggeräumt (Index-Scan, billig)

Migration 003_thumbnail_cache.sql: nicht-destruktiv, nur neue Tabelle.
Bestehende DB bekommt sie beim nächsten Start automatisch dazu.

Tests (99/99):
- Neuer Cache-Test: zweiter searchWeb für dieselbe URL macht keinen
  Page-Fetch mehr und liest die image-Spalte aus SQLite.
2026-04-17 18:34:29 +02:00
hsiegeln
1712263fd1 feat(search): HQ-Thumbnails durch immer aktive og:image-Extraktion
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 54s
Vorher: nur Treffer ohne SearXNG-Thumbnail wurden mit dem Seiten-Bild
angereichert. Treffer mit Thumbnail behielten das kleine 150-200 px-
Bildchen aus dem Such-Engine-Index.

Jetzt: Alle Treffer durchlaufen die Enrichment-Pipeline. Wenn die Seite
ein og:image/JSON-LD/Content-Bild hat (und das hat sie bei Rezept-Seiten
praktisch immer), wird das kleine SearXNG-Thumbnail damit überschrieben.
Wenn die Seite kein Bild liefert, bleibt das SearXNG-Thumbnail als
Fallback erhalten.

Das ist das gleiche Bild, das auch die Vorschau anzeigt — Suchergebnis
und Vorschau sind jetzt visuell konsistent.

Performance: Pro erster Suche bis zu ~6 Sekunden zusätzliche Latenz
(max 6 parallel, je 4 s Timeout). Der 30-min In-Memory-Cache macht
Wiederholsuchen instant.

Tests (98/98):
- Neu: SearXNG-Thumbnail wird durch og:image ersetzt.
- Neu: SearXNG-Thumbnail bleibt erhalten, wenn Seite kein Bild hat.
- Alt ("leaves existing thumbnails untouched") entfernt — Verhalten
  hat sich bewusst umgekehrt.
2026-04-17 18:31:42 +02:00
hsiegeln
53e4815508 chore(quotes): Ex-Spruch durch Mikrowellen-Pointe ersetzt
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 50s
2026-04-17 18:07:32 +02:00
hsiegeln
211d58ebec feat(search): Enter bleibt auf Seite + robustere Thumbnail-Erkennung
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 55s
Startseite:
- Enter/Return löst die Suche jetzt sofort aus (cancelt den Debounce),
  navigiert aber NICHT mehr auf /search. Der Anwender bleibt auf der
  gleichen Seite mit Inline-Ergebnissen.

Thumbnail-Enrichment (searxng.ts):
- Regex-basierte og:image-Extraktion durch linkedom-parseHTML ersetzt.
- Neue Fallback-Kette (in dieser Reihenfolge):
    1. <meta property/name = og:image | og:image:url | og:image:secure_url
                           | twitter:image | twitter:image:src>
    2. <link rel="image_src" href="...">
    3. JSON-LD image (auch tief in @graph; "image" als String, Array,
       Objekt-mit-url)
    4. Erstes <img> in article/main/.entry-content/.post-content/figure
- Relative URLs werden gegen die Seiten-URL zu absoluten aufgelöst
  (z.B. /uploads/foo.jpg → http://host/uploads/foo.jpg).
- maxBytes von 256 KB auf 512 KB angehoben, damit JSON-LD-lastige
  Recipe-Seiten nicht mitten im Script abgeschnitten werden.

Tests (97/97):
- Neu: JSON-LD-Image-Fallback-Test.
- Neu: Content-<img>-Fallback-Test mit relativer URL, die zur
  absoluten aufgelöst wird.
2026-04-17 18:04:59 +02:00
hsiegeln
9bc4465061 feat(home): zufälliger Spruch zwischen Titel und Suche
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 50s
Eine der 49 Flachwitze wird beim Laden der Startseite zufällig gewählt
und in kursiv unter "Kochwas" angezeigt. Die Auswahl passiert auf dem
Client (onMount), damit SSR und Hydration nicht miteinander streiten —
beim ersten Frame ist ein nicht-umbrechender Leerraum drin, damit das
Layout nicht springt.
2026-04-17 17:58:27 +02:00
hsiegeln
6a784488f5 fix(search): enrich missing SearXNG thumbnails with og:image
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 55s
SearXNG liefert je nach Seite mal ein thumbnail/img_src mit, mal nicht —
bei Chefkoch-Treffern hatten deshalb zufällig die Hälfte der Kacheln
einen Platzhalter, obwohl die Vorschau dann sehr wohl ein Bild fand.

searchWeb() holt jetzt für jeden Treffer ohne Thumbnail parallel
(max. 6 gleichzeitig, 4 s Timeout pro Request) die Seite und extrahiert
das og:image- oder twitter:image-Meta-Tag. Ergebnis wird 30 min
in-memory gecacht, damit wiederholte Suchen nicht wieder die gleichen
Seiten laden.

Tests:
- Neuer Test: Treffer ohne Thumbnail wird via og:image angereichert.
- Neuer Test: Treffer mit Thumbnail bleibt unverändert (keine Fetch).
- Bestehende Tests deaktivieren Enrichment via enrichThumbnails:false,
  damit sie keine echten Chefkoch-URLs aufrufen.
2026-04-17 17:55:53 +02:00
4f7c76c908 feat(ui): custom dialog replaces all remaining window.alert() calls
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 53s
alertAction({title, message}) returns Promise<void> and renders the
same ConfirmDialog with infoOnly:true — single OK button, no Abbrechen.
Replaces:
- 'Bitte Profil wählen.' (recipe rating / favorite / cooked / comment)
- 'Bitte Profil wählen, um zu liken.' (wishlist)
- 'Profil konnte nicht angelegt werden' (ProfileSwitcher)
- 'Umbenennen fehlgeschlagen' (admin/profiles)
- 'Speichern fehlgeschlagen' (preview)

No window.alert() or window.confirm() left in the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:23:07 +02:00
1b9928f806 feat(ui): custom confirmation dialog replacing native window.confirm
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 51s
Single reusable dialog with a promise-based API: confirmAction({...})
returns Promise<boolean>. Supports title, optional message body,
confirm/cancel labels, and a 'destructive' flag that paints the confirm
button red.

Accessibility: Escape cancels, Enter confirms, confirm button auto-focus,
role=dialog + aria-labelledby, backdrop click = cancel.

Rolled out to: recipe delete, domain remove, profile delete, wishlist
remove. Native confirm() is gone from the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:15:21 +02:00
18547a7301 feat(wishlist): add shared family wishlist with likes
Each recipe appears at most once on the wishlist. Any profile can add,
remove, like, and unlike. Ratings and cooking log stay independent.

Data model: wishlist(recipe_id PK, added_by_profile_id, added_at)
            wishlist_like(recipe_id, profile_id, created_at)

Why: 'das will ich essen' — family members pick candidates, everyone
can +1 to signal agreement, cook decides based on popularity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:08:22 +02:00
72019f9cb7 fix(ui): show remote image URL in preview, local file after save
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 51s
In the preview flow, recipe.image_path is still the external recipe-page
URL (we only download the image when persisting). The RecipeView
component always prefixed with /images/ and produced a 404.

Now RecipeView detects http(s):// prefix and uses it directly, else
treats the value as a local filename under /images/.

Also adds referrerpolicy=no-referrer on the preview image so remote
CDNs don't block us based on Origin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:03:13 +02:00
570a524d86 fix(search): unblock SearXNG 403 — config + headers
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 53s
SearXNG returned 403 for every query, logging
'X-Forwarded-For nor X-Real-IP header is set!'. Two fixes, both needed:

1. searxng/settings.yml was being overwritten by SearXNG's default
   config in fresh volumes. Explicitly set limiter: false,
   public_instance: false, and move secret_key to env lookup via
   ${SEARXNG_SECRET:-…}. Force a well-known JSON format list.

2. Even with the limiter off, SearXNG's bot detection still nags on
   missing forwarder headers. The Node client now sends
   X-Forwarded-For: 127.0.0.1, X-Real-IP: 127.0.0.1 and Accept: json
   deterministically. Done via a new extraHeaders option on the http
   wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 16:56:13 +02:00
041ef12582 fix(search): filter forum/magazin/listing URLs from web search results
Blocks common non-recipe paths like /forum/, /magazin/, /suche/, /themen/,
Chefkoch's /rs/s\d+/ search URLs and /Rezepte.html listings.

Before: 'ravioli' search returned forum threads and listing pages that
triggered 'No schema.org/Recipe JSON-LD' on preview.
After: only real recipe URLs pass through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:47:28 +02:00
ea9a79226a feat(db): bundle migration SQL via Vite glob + auto-create data dirs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:41:20 +02:00
0f9aabe76b refactor: move scaler out of $lib/server so it can run in browser
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>
2026-04-17 15:41:20 +02:00
2ca1bbaf07 feat(backup): add ZIP export endpoint (DB + images)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:35:19 +02:00
52c25fdd2c feat(search): add SearXNG client with whitelist-filtered web search
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:33:21 +02:00
8df09b1134 feat(ui): add unified RecipeView component with tabs and portion scaling
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:28:22 +02:00
974e3cf7ef feat(ui): add ProfileSwitcher modal and StarRating component
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:28:21 +02:00
e90545a637 feat(ui): add profile client store with LocalStorage persistence
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:28:21 +02:00
7c62c977c4 feat(recipes): add local search (FTS5 bm25) and action handlers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:23:00 +02:00
86ff4c141a feat(api): expose preview/import/profile/domain endpoints
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:12:59 +02:00
5693371673 feat(profiles): add profile repository
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:11:23 +02:00
99afc45c29 feat(recipes): add recipe importer (preview + persist)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:11:23 +02:00
aea07c5eb2 feat(recipes): add recipe repository (insert/get/delete with FTS refresh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:11:23 +02:00
757b0f720e feat(images): add sha256-deduplicated image downloader
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:09:31 +02:00
4c8f4da46c feat(domains): add allowed-domain repository and whitelist check
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:09:31 +02:00
11b6b8fff1 feat(http): add fetchText/fetchBuffer with timeout and size limits
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:09:31 +02:00
e90a37ff5e feat(db): add SQLite schema, FTS5, migration runner
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:04:59 +02:00
2f3248c9a3 feat(parser): add JSON-LD schema.org/Recipe extractor
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:04:05 +02:00
789af122f4 feat(scaler): add ingredient scaling with sensible rounding
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:02:09 +02:00
ae774e377d feat(parser): add ingredient parser
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:02:09 +02:00
b163b40860 feat(types): add shared type definitions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:02:09 +02:00
c56201c5f3 feat(parser): add ISO8601 duration parser
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:02:08 +02:00