feat: support external and step images with structured cooking instructions
No files matched your search
@@ -25,15 +25,17 @@ Antworte ausschließlich mit einem vollständigen JSON-Objekt, ohne Markdown-Cod
|
||||
- `optional`: JSON-Boolean `true` oder `false`, kein Text.
|
||||
- „Nach Geschmack“ als `"amount": null, "unit": "", "note": "nach Geschmack"`.
|
||||
- **Niemals `quantity` statt `amount` oder `group` statt `section` verwenden.** Diese Felder werden nicht als Mengen bzw. Abschnitte übernommen.
|
||||
7. Jeder Schritt enthält exakt `id`, `title`, `text`, `ingredients`, `timers`:
|
||||
7. Jeder Schritt enthält exakt `id`, `title`, `text`, `ingredients`, `images`, `timers`:
|
||||
- `text` enthält die vollständige Anleitung, niemals `description` auf Schrittebene.
|
||||
- `ingredients` ist eine Liste vorhandener Zutaten-IDs, etwa `["fleisch", "salz"]`, sonst `[]`. Nicht `ingredientIds` verwenden und hier keine Zutatenobjekte einsetzen.
|
||||
- Die Reihenfolge ergibt sich aus der Position im `steps`-Array. Kein `order`-Feld.
|
||||
- `timers` ist eine Liste von Timerobjekten oder `[]`.
|
||||
- `images` ist eine Liste von bis zu 10 Bildadressen für genau diesen Schritt, sonst `[]`.
|
||||
- Gliedere `text` in kurze Absätze mit `\n\n`. Bei mehreren Teilaufgaben verwende eine eigene Zeile pro Aufgabe mit `- ` oder `1. `, `2. ` usw. Schreibe keinen langen Textblock. Kein HTML oder Markdown für Überschriften, Fettdruck oder eingebettete Bilder; Bilder gehören in `images`.
|
||||
8. Jeder Timer enthält exakt `id`, `label`, `seconds`. `seconds` ist eine ganze JSON-Zahl von 1 bis 172800 in **Sekunden**, etwa `{"id":"timer_ruhen","label":"Ruhen lassen","seconds":600}`. Die `id` darf auch bei nur einem Timer nicht fehlen.
|
||||
9. IDs für Zutaten, Schritte und Timer bestehen aus 1–80 Zeichen: ausschließlich `a–z`, `A–Z`, `0–9`, `_` und `-`. Keine Leerzeichen oder Umlaute. Verwende eindeutige lesbare IDs, beispielsweise `z_fleisch`, `s_mischen`, `t_ruhen`; wiederhole keine ID innerhalb eines Rezepts.
|
||||
10. `tips` und `storage` sind jeweils **ein einzelner Text**, niemals Listen oder Objekte. Mehrere Hinweise durch `\n\n` innerhalb des JSON-Strings trennen. Verwende `storage`, niemals `storageNotes`.
|
||||
11. Setze `"image": ""`, `"images": []` und `"published": false`. Keine erfundenen Bild-URLs.
|
||||
11. Bilder sind über lokale HamHam-Pfade (`/media/<uuid>.webp`, mitgelieferte `/images/<name>.jpg|webp|svg`) oder vollständige **HTTPS-Bildadressen** möglich (höchstens 2048 Zeichen, ohne Zugangsdaten in der URL). `image` ist das Titelbild, `images` auf Rezeptebene die Galerie (bis zu 20 Bilder), `images` innerhalb eines Schritts dessen Bilder (bis zu 10). Verwende nur von mir bereitgestellte oder tatsächlich überprüfte direkte Bildadressen, keine Webseiten-Links oder erfundenen URLs. Keine `http:`, `data:`- oder Base64-Bilder. Ohne geeignete Bilder setze `"image": ""` und alle `"images": []`. Setze `"published": false`.
|
||||
12. Setze `"nutrition": null`, sofern keine verlässlichen Nährwerte **pro Portion** verfügbar sind. Andernfalls exakt `{"kcal": 0, "protein": 0, "carbs": 0, "fat": 0}` mit den tatsächlichen nicht negativen Zahlen ausfüllen; Makronährstoffe in Gramm. Keine scheinpräzisen Werte erfinden.
|
||||
|
||||
## Inhalt und Grenzen
|
||||
@@ -102,13 +104,15 @@ Dieses kleine Beispiel zeigt die verbindliche Struktur. Ersetze den Inhalt volls
|
||||
"title": "Gurke vorbereiten",
|
||||
"text": "{{z_gurke}} waschen, die Enden entfernen und die Gurke fein hobeln.",
|
||||
"ingredients": ["z_gurke"],
|
||||
"images": [],
|
||||
"timers": []
|
||||
},
|
||||
{
|
||||
"id": "s_marinieren",
|
||||
"title": "Marinieren und ziehen lassen",
|
||||
"text": "Die Gurkenscheiben mit {{z_essig}} und {{z_salz}} vermengen. Im Kühlschrank 10 Minuten ziehen lassen, danach nochmals abschmecken und servieren.",
|
||||
"text": "- Die Gurkenscheiben mit {{z_essig}} und {{z_salz}} vermengen.\n- Im Kühlschrank 10 Minuten ziehen lassen.\n- Danach nochmals abschmecken und servieren.",
|
||||
"ingredients": ["z_essig", "z_salz"],
|
||||
"images": [],
|
||||
"timers": [
|
||||
{
|
||||
"id": "t_ziehen",
|
||||
@@ -138,4 +142,5 @@ Prüfe und korrigiere die Ausgabe vor dem Antworten. Gib diese Prüfliste nicht
|
||||
- Jede Zutat, jeder Schritt und jeder Timer hat eine gültige, eindeutige `id`. Alle Zutatenreferenzen existieren.
|
||||
- `tips` und `storage` sind Texte; `quantity`, `group`, `ingredientIds`, `order`, `storageNotes`, `country`, `format` und `schemaVersion` kommen als Feldnamen nicht vor.
|
||||
- Mengenverhältnisse, Portionszahl, Zeiten und Timer sind plausibel; alle Grenzen sind eingehalten.
|
||||
- Schrittanleitungen sind in kurze Absätze oder Listen gegliedert. Bildfelder enthalten nur geeignete lokale Pfade oder HTTPS-Bildadressen, sonst bleiben sie leer.
|
||||
- Die gesamte Antwort ist syntaktisch gültiges JSON und enthält keinen Begleittext.
|
||||
@@ -50,6 +50,12 @@ Zutaten werden als kompakte Liste mit Name, Menge und Einheit erfasst. Abschnitt
|
||||
|
||||
Zutaten und Schritte lassen sich per Drag-Handle sowie per Auf/Ab-Buttons sortieren; bei Zutaten befinden sich die Buttons in den aufgeklappten Details. So ist die Funktion auch ohne Maus bedienbar. Bilder werden sofort in die lokale Medienablage hochgeladen und können dort später entfernt werden, solange kein Rezept sie verwendet.
|
||||
|
||||
Titelbild, Rezeptgalerie und Schrittbilder unterstützen auch direkte HTTPS-Bildadressen. Diese werden ausschließlich im Browser geladen, nicht serverseitig abgerufen. `imageSchema` in `src/lib/recipe.ts` validiert lokale Pfade und HTTPS-URLs; die CSP erlaubt HTTPS-Bilder. `RecipeImage` setzt `no-referrer` und zeigt bei Ladefehlern einen Platzhalter. Die Medienlöschung berücksichtigt auch Bildreferenzen innerhalb von Schritten.
|
||||
|
||||
`steps[].images` ist optional beim Import (Standard `[]`, maximal 10 Bilder). Bestehende Datenbankeinträge und lokale Entwürfe erhalten beim Lesen leere Schrittbilder; eine Datenbankmigration ist dafür nicht erforderlich. JSON- und XML-Export erhalten die Referenzen. Externe Bilder werden nicht durch den Service Worker offline gespeichert.
|
||||
|
||||
`StepInstructions` wird in Rezeptansicht, Kochmodus und Vorschauen gemeinsam verwendet: kurze Absätze, einfache Listen und hervorgehobene skalierbare Zutatenreferenzen. `instructionBlocks` zerlegt ausschließlich Klartext; lange ältere Ein-Absatz-Texte werden mit `Intl.Segmenter` für Deutsch nach jeweils zwei Sätzen optisch gegliedert. Es wird kein HTML interpretiert und der gespeicherte Anleitungstext wird nicht umgeschrieben.
|
||||
|
||||
## Tests
|
||||
|
||||
Unit: `npm test`. E2E: `npm run build && npm run test:e2e`. Tests verwenden echte PostgreSQL-Persistenz, echte HTTP-Authentifizierung und Chromium ohne gemockte Produktfunktionen. Die E2E-Datenbank muss auf `_test` enden und wird zurückgesetzt. Playwright erzeugt Screenshots für Desktop (1440 px), Smartphone (390 px) und Tablet (768 px).
|
||||
|
||||
@@ -47,17 +47,26 @@ Schritt:
|
||||
{
|
||||
"id": "anbraten",
|
||||
"title": "Goldbraun anbraten",
|
||||
"text": "{{huhn}} in Würfel schneiden und anbraten.",
|
||||
"text": "- {{huhn}} in Würfel schneiden.\n- In der heißen Pfanne anbraten.",
|
||||
"ingredients": ["huhn"],
|
||||
"images": [],
|
||||
"timers": [{ "id": "bratzeit", "label": "Hühnerbrust braten", "seconds": 480 }]
|
||||
}
|
||||
```
|
||||
|
||||
`{{huhn}}` wird zu **Menge + Einheit + Zutatenname + Hinweis** aufgelöst. Diese Referenz bleibt beim Skalieren konsistent. Keine konkreten skalierbaren Mengen als Freitext wiederholen. Temperaturen und Zeiten bleiben normaler Text. `ingredients` enthält die in diesem Schritt benötigten Zutaten-IDs. Timer-IDs müssen im gesamten Rezept eindeutig sein; ein Timer dauert 1–172800 Sekunden. Zeiten werden nicht mit Portionen skaliert.
|
||||
|
||||
Schritttexte unterstützen Absätze bzw. Zeilenumbrüche (`\n`, `\n\n`), Aufzählungen mit `- `, `* ` oder `• ` und nummerierte Listen mit `1. ` bzw. `1) `. Die Darstellung funktioniert in Rezeptansicht, Kochmodus und Vorschau. Lange ältere Texte ohne Zeilenumbrüche werden für die Anzeige nach jeweils zwei Sätzen gegliedert; der gespeicherte Text bleibt erhalten. Zutatenreferenzen werden hervorgehoben. HTML und weitere Markdown-Formatierung sind nicht vorgesehen.
|
||||
|
||||
## Bilder
|
||||
|
||||
Nur lokale URLs: `/media/<uuid>.webp` oder mitgelieferte `/images/<name>.jpg|webp|svg`. Bilder zuerst im Adminbereich hochladen, dann den lokalen Pfad verwenden. ChatGPT soll `image: ""` und `images: []` setzen und keine erfundenen URLs ausgeben.
|
||||
Bildfelder akzeptieren lokale Pfade (`/media/<uuid>.webp` oder mitgelieferte `/images/<name>.jpg|webp|svg`) und vollständige externe **HTTPS-URLs** bis 2048 Zeichen ohne Zugangsdaten. Queryparameter sind erlaubt, z. B. für Bild-CDNs. HTTP, eingebettete Base64-/Data-URLs und Webseiten statt direkter Bildadressen sind nicht geeignet.
|
||||
|
||||
- `recipe.image`: ein Titelbild oder `""`.
|
||||
- `recipe.images`: Galerie mit bis zu 20 Bildadressen, Standard `[]`.
|
||||
- `recipe.steps[].images`: bis zu 10 Bildadressen pro Schritt, Standard `[]`. Bestehende Rezepte ohne dieses Feld bleiben kompatibel.
|
||||
|
||||
Lokale Bilder können im Editor hochgeladen werden. HTTPS-Adressen können für Titelbild, Galerie und Schrittbilder im Editor eingetragen oder über JSON/XML importiert werden. Externe Bilder lädt der Browser direkt mit `referrerPolicy="no-referrer"`; HamHam lädt sie nicht auf den Server herunter. Bei nicht erreichbaren oder vom Anbieter blockierten Bildern erscheint ein Platzhalter. Externe Bilder sind daher nicht Bestandteil des lokalen Bild-Backups. ChatGPT soll nur bereitgestellte oder überprüfte Bildadressen verwenden und die Bildfelder andernfalls leer lassen.
|
||||
|
||||
JSON/XML-Exporte enthalten Bildreferenzen, keine Binärdaten. Für einen Umzug das Bildvolume mitnehmen; für ein vollständiges Backup zusätzlich die Datenbank sichern. Metadaten, Zutaten, Schritte, Timer, Zeitstempel und IDs bleiben beim Export erhalten.
|
||||
|
||||
@@ -65,16 +74,17 @@ JSON/XML-Exporte enthalten Bildreferenzen, keine Binärdaten. Für einen Umzug d
|
||||
|
||||
Das vollständige Beispiel ist `public/examples/pljeskavica.xml`. UTF-8, Wurzel `<hamham version="1">`, darunter `<recipes><recipe>…</recipe></recipes>`. Rezeptfelder entsprechen JSON. Array-Container:
|
||||
|
||||
| JSON | XML |
|
||||
| ---------------- | ----------------------------------------------------------- |
|
||||
| recipes | `<recipes><recipe>…</recipe></recipes>` |
|
||||
| ingredients | `<ingredients><ingredient>…</ingredient></ingredients>` |
|
||||
| steps | `<steps><step>…</step></steps>` |
|
||||
| timers | `<timers><timer>…</timer></timers>` |
|
||||
| categories | `<categories><category>Vegetarisch</category></categories>` |
|
||||
| tags | `<tags><tag>Schnell</tag></tags>` |
|
||||
| images | `<images><image>/media/…webp</image></images>` |
|
||||
| step.ingredients | `<ingredients><ref>huhn</ref></ingredients>` |
|
||||
| JSON | XML |
|
||||
| ---------------- | ----------------------------------------------------------------- |
|
||||
| recipes | `<recipes><recipe>…</recipe></recipes>` |
|
||||
| ingredients | `<ingredients><ingredient>…</ingredient></ingredients>` |
|
||||
| steps | `<steps><step>…</step></steps>` |
|
||||
| timers | `<timers><timer>…</timer></timers>` |
|
||||
| categories | `<categories><category>Vegetarisch</category></categories>` |
|
||||
| tags | `<tags><tag>Schnell</tag></tags>` |
|
||||
| images | `<images><image>/media/…webp</image></images>` |
|
||||
| step.ingredients | `<ingredients><ref>huhn</ref></ingredients>` |
|
||||
| step.images | `<images><image>https://example.org/schritt.jpg</image></images>` |
|
||||
|
||||
Leere Menge: `<amount/>`. Keine Nährwerte: `<nutrition/>`. Boolean: `true`/`false`. Texte standardkonform escapen (`&`, `<` etc.). DOCTYPE und Entity-Deklarationen sind verboten, es werden keine externen Ressourcen aufgelöst. Nach dem XML-Parsing gilt dieselbe Zod-Validierung wie bei JSON.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Stand: **24. Oktober 2026**. Die folgenden Prüfungen wurden tatsächlich ausgeführt, nicht lediglich vorbereitet.
|
||||
|
||||
## Ergebnisse
|
||||
## Ergebnisse der Erstprüfung
|
||||
|
||||
| Prüfung | Ergebnis |
|
||||
| ---------------------- | ------------------------------------------------------------------ |
|
||||
@@ -68,6 +68,15 @@ Tatsächlich angesehen: Desktop- und Mobile-Bibliothek, Tablet-Bibliothek, mobil
|
||||
|
||||
Nach der gestalterischen Überarbeitung wurden TypeScript-Prüfung und Produktionsbuild erneut erfolgreich ausgeführt. Die vier betroffenen Browserabläufe bestanden: vollständige Rezeptanlage/-bearbeitung auf Desktop sowie der Zutateneditor auf Desktop, Smartphone und Tablet. Geprüft wurden aufklappbare Details, Mengen, optionale Zutaten, Fokus nach Hinzufügen, Enter zum Weitererfassen, Reihenfolge per Buttons und Desktop-Drag-and-drop, Entwurfswiederherstellung sowie fehlender horizontaler Überlauf mit und ohne Live-Vorschau. Ein zunächst falsch auf `textbox` statt `combobox` gerichteter Testselektor für die Abschnittsauswahl wurde korrigiert; anschließend bestanden alle vier Tests. Die neuen Screenshots der hellen Desktop-/Smartphone-Ansicht und der dunklen Tablet-Ansicht wurden visuell kontrolliert.
|
||||
|
||||
### Nachprüfung: externe Bilder und strukturierte Schritte
|
||||
|
||||
- TypeScript-Prüfung und Produktionsbuild erfolgreich; **49/49 Unit-Tests** in vier Dateien bestanden.
|
||||
- **13 betroffene Browser-/API-Abläufe** erfolgreich geprüft: Editor mit Schrittbild-Upload und Löschsperre für verwendete Medien, JSON/XML-Import, API-Validierung, Offline-Kochmodus sowie auf Desktop/Smartphone/Tablet jeweils externer Bildimport mit Editoränderung/Export, Portionsrechner/Druckansicht und Kochfortschritt/Timer.
|
||||
- HTTPS-Titelbilder, Rezeptgalerien und Schrittbilder werden in JSON und XML verlustfrei erhalten. Alte Rezepte ohne Schrittbilder bleiben lesbar. Ungültige URL-Protokolle, Zugangsdaten in Bildadressen und Bildlimits sind getestet.
|
||||
- Die neuen Browserabläufe verwenden deterministische HTTPS-Bildantworten per Playwright-Routing, einschließlich einer 404-Antwort. Sie prüfen die reale CSP, tatsächlich geladene Bilder, Fehlerplatzhalter, Listen/Absätze, skalierte Referenzen und fehlenden horizontalen Überlauf. Ein dabei entdeckter Bildfehler vor React-Hydration wurde durch Prüfung des bereits abgeschlossenen Bildladezustands behoben.
|
||||
- Visuell angesehen: neue Desktop-Rezeptkarten für Schritte, Smartphone-Rezeptseite mit Schrittbildern, dunkle Desktop-Schrittkarte und mobiler Kochmodus. Die Rezept-/Kochmodus-Screenshots unter `docs/screenshots/` wurden aktualisiert; zusätzliche Bild-/Listenansichten liegen in den Playwright-Testartefakten.
|
||||
- Die JSON-Vorlage in `docs/CHATGPT-PROMPT.md` wurde mit dem tatsächlichen Importparser geprüft; das öffentliche JSON-Schema und die Beispiele wurden neu erzeugt.
|
||||
|
||||
## Präzise Grenzen
|
||||
|
||||
- Kein Test auf physischem Smartphone, Safari oder Firefox; keine tatsächliche iOS-/Android-Installation über das Betriebssystem-Installationsmenü.
|
||||
@@ -76,7 +85,7 @@ Nach der gestalterischen Überarbeitung wurden TypeScript-Prüfung und Produktio
|
||||
- Linux ARM64 ist tatsächlich getestet; ein separater AMD64-Deploy wurde nicht durchgeführt.
|
||||
- Kein externer Penetrationstest, keine Garantie, dass ein Abhängigkeitsaudit jede Schwachstelle erkennt. Es sind aus den durchgeführten Prüfungen keine kritischen Sicherheitsfehler bekannt.
|
||||
- Editorentwürfe, Favoriten, Einkaufslisten und Kochfortschritt sind bewusst geräte-/browserlokal; kein geräteübergreifender Sync.
|
||||
- JSON/XML transportiert Rezeptdaten und lokale Bildreferenzen. Bilddateien werden separat über das persistente Volume gesichert und übertragen.
|
||||
- JSON/XML transportiert Rezeptdaten sowie lokale und externe Bildreferenzen. Lokale Bilddateien werden separat über das persistente Volume gesichert und übertragen; externe Bilder verbleiben beim jeweiligen Anbieter und werden nicht für den Offlinebetrieb gespeichert.
|
||||
- Offline umfasst tatsächlich gespeicherte öffentliche Ansichten; neue oder aus dem Browsercache entfernte Inhalte benötigen Netz. Administration bleibt onlinepflichtig.
|
||||
|
||||
Diese Grenzen sind keine als fertig ausgegebenen ungeprüften Plattformversprechen. Die oben genannten implementierten Kernabläufe wurden in der dokumentierten Umgebung erfolgreich geprüft.
|
||||
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 277 KiB After Width: | Height: | Size: 536 KiB |
|
Before Width: | Height: | Size: 913 KiB After Width: | Height: | Size: 941 KiB |
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.9 MiB |