Version 0.22.0

July 26, 2026

Bis v0.21.1 konnte Deon AI eine Seite lesen und bestehende Sektionen umschreiben, aus-/einblenden und umsortieren — aber es konnte weder eine Sektion anlegen noch eine entfernen. op:"disable" blendet einen Block nur im Frontend aus; im Craft-CP steht er weiterhin ausgegraut in der Liste. Genau das war die berechtigte Kundenrückmeldung („es wurden keine Sektionen entfernt oder neu hinzugefügt"). v0.22.0 schließt diese Lücke und macht damit den eigentlichen Produktgedanken erst möglich: Deon AI wählt aus den vorhandenen Skeletten des Kundendesigns dasjenige aus, das zu einer neuen H2 bzw. zum Inhalt passt, legt es an und befüllt es. Es wird nie neues Design erfunden — es wird immer mit dem gearbeitet, was der Kunde bereits hat.

Added

  • GET /deon-ai/section-catalog und GET /deon-ai/section-catalog/<id> — der Skelett-Katalog. Ohne diesen Katalog kann die AI keinen Block anlegen, weil block_type ein existierender Handle sein muss. Site-Scope listet jedes Matrix-/Neo-Feld einmal (inkl. used_in), Entry-Scope listet nur die Block-Felder dieses Entries, dafür mit je einem Beispielblock pro Typ aus dem Entry selbst — damit sieht die AI, wie ein Skelett beim Kunden tatsächlich befüllt aussieht, statt es zu raten. Pro Block-Typ werden Handle, Name, has_title_field, can_add und die Sub-Felder mit Typ und Rollen-Hinweis geliefert. Mögliche Werte von role_hint: headline, subline, body, cta_label, cta_url, media — oder null, wenn der Handle zu keiner Rolle passt. can_add ist bei Neo-Feldern bewusst false statt eines Fehlers beim Schreiben.
  • op:"add" in /deon-ai/entry-sections — Skelett nehmen und befüllen. Body: {"op":"add","field_handle":"…","block_type":"faqSection","position":5,"enabled":true, "title":"…","fields":{"headline":"…","text":"…"}}. position ist optional (ohne wird hinten angehängt). Nicht setzbare Sub-Felder werden nicht still verschluckt, sondern in skipped_fields mit Begründung zurückgemeldet (sub_field_not_found, value_not_string, field_not_text_writable). Ein unbekannter block_type antwortet mit block_type_not_found plus der Liste der verfügbaren Handles.
  • op:"remove" in /deon-ai/entry-sections — strukturelle Entfernung, aber umkehrbar. Der Block verschwindet auch im CP aus der Liste, technisch aber als Soft-Delete (deleteElement($el, false) setzt nur dateDeleted). Vor dem Löschen wird ein vollständiger Snapshot (Typ, Enabled-State, Position, alle Textwerte) in den Change-Log geschrieben.
  • Rollback für beide neuen Operationen. entry-section-added → der erzeugte Block wird wieder soft-gelöscht; entry-section-removedrestoreElement(). Beide Richtungen sind idempotent: ist der Zielzustand schon erreicht (already_absent / already_present), gilt der Rollback als erfolgreich, nicht als Fehler. Die Rollback-Vorschau (/rollback/<id>/preview) hat eigene Sätze für beide Fälle statt des generischen „wird zurückgesetzt".
  • Capabilities section_catalog, entry_section_add, entry_section_remove in /ping. Der Worker ruft die neuen Wege ausschließlich gegatet auf — ältere Installationen laufen unverändert weiter.

Notes zur Sicherheit der Implementierung

  • Kein serialisiertes ['entries' => …]-Delta im Add-Pfad. Angelegt wird über einen direkten saveElement() auf dem neuen Block-Element. Der Delta-Pfad ist exakt der, der in v0.20.0 echten Datenverlust verursacht hat (Craft behandelt alles Nichtgenannte als gelöscht); ein direkter Element-Save kann bestehende Blöcke strukturell nicht verlieren.
  • Craft 4 und 5 getrennt behandelt, aber nicht über version_compare, sondern über den tatsächlichen Typ des Block-Typ-Objekts. Craft 5: der Block ist ein Entry, saveOwnership() hängt ihn mit sortOrder = MAX+1 automatisch an — sortOrder wird deshalb bewusst nicht gesetzt. Craft 4: MatrixBlock::afterSave() schreibt sortOrder ?? 0, es gibt kein Auto-Append — sortOrder wird deshalb explizit gesetzt.
  • Soft-Delete ist gegen späteres Hard-Delete abgesichert. Weder NestedElementManager::deleteOtherNestedElements() (Craft 5) noch Matrix::_deleteOtherBlocks() (Craft 4) queryen mit ->trashed(null); ein soft-gelöschter Block ist für sie unsichtbar und kann durch einen späteren Reorder oder Owner-Save nicht nachträglich hart gelöscht werden.
  • Das optionale position-Delta läuft nie auf dem Entry-Objekt der laufenden Schleife, sondern auf einer frisch geladenen Kopie — sonst würde der memoisierte Matrix-Wert die Blockliste der nachfolgenden Patches im selben Request vergiften. Und es ist nach Regel 3 ein reines ['sortOrder' => [...]] ohne entries-Key.
  • patches[] ist batchfest gemacht. Craft memoisiert den Wert eines Matrix-Feldes auf der Entry-Instanz. Nach einem add/remove im selben Request wäre dieser Wert veraltet: der neue Block fehlte darin, der entfernte stünde noch drin, und block_index wäre um eins verschoben. Der Endpoint merkt sich deshalb pro Feld, ob strukturell eingegriffen wurde, und liest die Blockliste ab dann aus einer frisch geladenen Kopie (nur als Lesequelle — gespeichert wird weiterhin ausschließlich das jeweilige Block-Element). Das ist kein Randfall, sondern die Normalform der Aufrufe: „Sektion anlegen, dann ihre Felder füllen" in einem Request.
  • reorder{} nach einem add/remove im selben Request läuft ebenfalls auf frischen Daten. Das ist der kritischste Pfad des Endpoints, weil reorder als einziger den Owner-Entry speichert: ein aus veralteter Blockliste gebautes sortOrder hätte den soeben angelegten Block nicht enthalten — und nicht gelistete Blöcke entfernt Crafts deleteOtherNestedElements(). Lässt sich die frische Kopie nicht laden, wird der Reorder mit reorder_skipped_stale_entry abgebrochen statt geraten; die bereits angewendeten Patches bleiben gültig und werden normal zurückgemeldet.
  • Keine Migration, schemaVersion bleibt 1.5.0. Alle Änderungen sind additiv; bestehende Response-Felder wurden nicht umbenannt.

Version 0.21.1

July 25, 2026

Behebt die Klasse von Ausfällen, bei denen das Plugin nach einem Self-Update funktionsfähig installiert war und trotzdem nichts mehr tat. Ursache ist kein Fehler in der Update-Kette, sondern eine Eigenschaft von Craft: Plugin-Settings liegen in der Project Config. Läuft die Installation mit allowAdminChanges = false — der Produktions-Standard — schreibt savePluginSettings() nur in die plugins-Tabelle, und der auf jedes Self-Update folgende php craft upproject-config/apply bügelt die YAML-Werte darüber. Auf der Pilot-Installation standen nach 0.20.1 → 0.21.0 alle fünf von /setup-blog geschriebenen Handles wieder auf ihren Klassen-Defaults (blog, pages, body, '', ''), die dort nichts auflösen. /ping meldete sections_ok und fields_ok vollständig false, /entries lief in ein 422 — und /entry und /page überlebten nur, weil der Worker bei section_not_found reaktiv /setup-blog nachfeuert. Alles, was an diesem Self-Heal vorbeiläuft, war schlicht tot. Ein erneuter /setup-blog-Aufruf repariert das zwar, aber nur bis zum nächsten Update — deshalb löst v0.21.1 das Problem dort, wo es dauerhaft verschwindet: beim Lesen.

Fixed

  • Kritisch — alle Endpoints arbeiten mit aufgelösten statt mit rohen Handle-Settings. Neue private Resolver im ApiController (resolvedBlogSectionHandle(), resolvedPagesSectionHandle(), resolvedBodyFieldHandle(), resolvedImageFieldHandle(), resolvedAssetVolumeHandle()) lösen je Handle in fester Reihenfolge auf: expliziter Request-Parameter → Plugin-Setting, sofern es auf ein existierendes Objekt zeigt → Deon-Bootstrap-Handle (deonBlog / deonPages / deonBody / deonFeaturedImage) → null. Damit ist ein Setting ab sofort eine Präferenz, kein Fakt: ein verlorenes Setting kostet höchstens noch die Präferenz, nie die Funktion. Betroffen sind /ping, /entries, /entry, /page, /asset, der FAQ-Inject und der Restore-Point-Snapshot. Die Auflösung wird pro Request gecacht.
  • null heißt 422, nicht „irgendwas bauen". Löst kein Kandidat auf, existiert das Ziel wirklich nicht — dann antwortet der Endpoint mit einem sauberen, benannten Fehler (/asset etwa mit asset_volume_not_configured plus Hinweis), statt eine halbfertige Seite zu erzeugen. Der Volume-Resolver spiegelt exakt die Ableitung aus /setup-blog (Restriktion des Deon-Bildfelds → erstes vorhandenes Volume) und legt wie dort nie selbst ein Volume an, weil Filesystem und Storage hosting-abhängig sind.
  • sections_ok / fields_ok in /ping prüfen jetzt die aufgelösten Handles. Ein zurückgesetztes Setting erscheint dort nicht mehr als „Section fehlt". false bedeutet ab v0.21.1 verlässlich, dass das Objekt tatsächlich nicht existiert.

Added

  • Capability handle_fallback in /ping. Der Worker darf daraus schließen, dass sections_ok / fields_ok bei dieser Plugin-Version echte Existenz-Aussagen sind.
  • /ping liefert additiv handles und handles_repaired. handles nennt die in diesem Request tatsächlich benutzten Handles (blog_section, pages_section, body_field, featured_image_field, asset_volume), handles_repaired die Setting-Properties, die von den aufgelösten Werten abwichen und zurückgeschrieben wurden. Ein nicht-leeres handles_repaired direkt nach einem Update ist der Beleg für den Project-Config-Revert — kein Fehler. Beide Felder sind rein additiv; ältere Worker ignorieren sie.
  • Opportunistische Selbstheilung der Settings. /ping schreibt abgedriftete Handles per saveSettingsWithoutBootstrap() zurück. Ausdrücklich best effort: schlägt das fehl oder wird es beim nächsten craft up erneut überschrieben, ist das kein Fehlerfall — die Endpoints laufen ohnehin über die Resolver. Die Methode wirft nie, sie loggt via Craft::warning().

Notes

  • Keine Migration, schemaVersion bleibt 1.5.0. Alle Änderungen sind additiv; /setup-blog liest bewusst weiterhin die rohen Settings, weil genau dort die Abweichung erkannt werden muss, um settings_updated zu berechnen.

Version 0.21.0

July 24, 2026

Schließt die letzte offene Lücke der Copy-&-Rebuild-Vision: Die Startseite war bis hierher als Kopiervorlage strukturell unerreichbar. /duplicate-page klonte immer in die Section der Quelle zurück, und die Startseite liegt in einer single-Section, die per Definition genau einen Entry fasst — ein Klon dorthin hätte die bestehende Seite verdrängt. Der Worker musste single-Sections deshalb korrekt ausschließen und landete auf einer beliebigen anderen Section, deren Beispiel-Entry oft nur einen einzigen Block hat. Ergebnis beim Kunden: eine Seite, die nichts vom Design der Startseite hat. Der in v0.19.0 als Brücke gebaute Endpoint /build-template-page legte seine Vorlagen-Section zudem mit hasUrls: true und template: null an — Craft validiert template nie, die Section speichert also klaglos, und jede daraus erzeugte Seite lief beim Rendern in einen 404.

Fixed

  • Kritisch — /build-template-page legte eine dauerhaft nicht renderbare Section an. Section_SiteSettings wurde mit hasUrls: true, uriFormat: 'deon-vorlagen/{slug}' und template: null erzeugt. Gegen den Quelltext von craftcms/cms 5.10.10 geprüft verlangt Section_SiteSettings::defineRules() bei hasUrls ausschließlich uriFormattemplate wird nie validiert. Die Section speicherte damit fehlerfrei und der Defekt zeigte sich erst beim Aufruf der erzeugten Seite als 404. Jetzt erbt die Vorlagen-Section die Site-Settings der Quell-Section (nur wenn deren Template per View::doesTemplateExist() tatsächlich existiert); ist dort kein nutzbares Template hinterlegt, wird die Vorlagen-Section bewusst ganz ohne URLs angelegt — eine Vorlage ohne URL ist korrekt, eine Vorlage mit kaputter URL ist eine Falle.
  • Selbstheilung für bereits betroffene Sites. Wurde die Section deonTemplates von v0.19.0 oder v0.20.x bereits im kaputten Zustand angelegt, repariert /build-template-page sie beim nächsten Aufruf automatisch (neues Response-Feld section_repaired: true). Ohne diesen Schritt bliebe der 404 dort dauerhaft bestehen, weil der Endpoint idempotent ist und eine vorhandene Section nie wieder anfasste.

Added

  • POST /deon-ai/duplicate-page — neue optionale Body-Keys target_section_handle und target_entry_type_handle. Damit landet der Klon in einer anderen Section als die Quelle; sectionId/typeId gehen als $newAttributes direkt in Elements::duplicateElement() (nachträglich wäre der Klon bereits in der Quell-Section gespeichert). Das macht die Startseite erstmals zu einer legalen Klonquelle: Craft-Feld-Handles sind global eindeutig, dasselbe Matrix-Feld hängt daher in aller Regel auch am Entry-Type der regulären Unterseiten — die komplette Block-Palette der Startseite ist dort gültig. Ohne die neuen Keys ist das Verhalten unverändert (voll rückwärtskompatibel). Validierung vor dem Klon, jeweils mit eigenem Fehlercode statt einer 500er-Exception: target_section_not_found (404), target_section_is_single (409 — ein Klon in eine Single-Section würde die dortige Seite verdrängen), target_section_has_no_entry_type (409), target_entry_type_not_in_section (409, inkl. available_entry_types), title_field_missing (422, bestehender Check) und target_block_field_missing (409, inkl. source_block_fields/target_block_fields — der Klon wäre sonst ohne Sektionen). Ohne expliziten target_entry_type_handle wird der erste Entry-Type der Ziel-Section gewählt, der ein Block-Feld mit der Quelle teilt.
  • POST /deon-ai/duplicate-page liefert zusätzlich section_handle, entry_type_handle und cloned_into_section (bool). Alle bestehenden Felder inkl. success bleiben unverändert; der Worker erkennt die neue Fähigkeit an typeof body.cloned_into_section === "boolean".
  • GET /deon-ai/site-inventory liefert pro Section jetzt has_urls, uri_format, template, template_exists, renderable sowie entry_types[{handle, name, has_title_field, block_field_handles[]}] (dazu plugin_version auf oberster Ebene). Bisher standen dort nur Handle, Typ, Name, Entry-Zahl und ein Beispiel-Entry — zu wenig, um ein Klonziel zu wählen, ohne zu raten. renderable ist bewusst die Konjunktion aus URL und existierendem Template, weil genau deren Auseinanderfallen den 404-Defekt oben verursacht hat.
  • Neue Einträge im capabilities-Array von /deon-ai/ping: clone_into_section und site_inventory_targets.

Hinweis — Worker-Gegenpart erforderlich

Minor-Release: neue Response-Felder und ein neuer Body-Key, keine neue Migration (schemaVersion bleibt 1.5.0). Der Worker deon-mvp muss die Zielwahl aus dem angereicherten Inventory treffen und target_section_handle mitschicken — hinter einem Capability-Gate, sonst brechen Sites mit älterer Plugin-Version. Reihenfolge zwingend: erst dieses Plugin-Release beim Kunden installieren und smoke-testen, dann den Worker deployen. Wie v0.19.0/v0.20.1 ist auch dieser Stand nur mit php -l syntaxgeprüft, nicht gegen eine laufende Craft-Instanz ausgeführt.

Version 0.20.1

July 24, 2026

Ersetzt den nie veröffentlichten internen Entwurf 0.20.0 vollständig. Ein Craft-Review gegen den echten Quelltext von craftcms/cms 5.10.10 hat in diesem Entwurf zwei Defekte gefunden, einer davon mit echtem Datenverlust-Potenzial. Beide sind unten dokumentiert und behoben; der 0.20.0-Handoff darf nicht angewendet werden.

Fixed (gegenüber dem 0.20.0-Entwurf)

  • Kritisch — reorder hätte alle Blöcke der Sektion löschen können. Der Entwurf übergab Entry::setFieldValue($fieldHandle, $orderedBlocks) ein Array instanziierter Entry-Objekte. Gegen craft\fields\Matrix (5.10.10) geprüft ist das kein gültiges Wertformat: _normalizeValueInternal() reicht ein Array an _createEntriesFromSerializedData() weiter, wo ein Objekt-Array im Legacy-Zweig mit numerischen Schlüsseln 0,1,2… landet, zu keiner echten Entry-ID passt, in den "neuer Block"-Zweig fällt (der ein type verlangt) und per continue für jeden Block verworfen wird. Übrig bleibt ein leeres Set, das NestedElementManager::saveNestedElements() an deleteOtherNestedElements() weitergibt — womit sämtliche Blöcke des Feldes gelöscht worden wären. Jetzt wird das korrekte Delta-Format ['sortOrder' => [id, id, …]] (reine Integer-IDs, kein entries-Key) verwendet; Craft führt damit ausschließlich ein UPDATE auf elements_owners.sortOrder aus — kein Anlegen, kein Löschen. Zusätzliches Sicherheitsnetz: hat nicht jeder vorhandene Block eine verwertbare ID, bricht der Aufruf mit reorder_incomplete_block_ids ab, statt mit unvollständiger Liste zu speichern. Betraf applyBlockReorder() und rollbackEntrySectionOrder().
  • Kritisch — deaktivierte Blöcke waren für das Plugin unsichtbar. Matrix::createEntryQuery() baut den Feldwert als normale EntryQuery mit ->fieldId()->siteId(), aber ohne ->status(null) — der Default-Statusfilter enabled blendet also genau die Blöcke aus, die per op:"disable" ausgeblendet wurden. Folgen im Entwurf: op:"enable" konnte einen zuvor deaktivierten Block nie wiederfinden (block_not_found), das neue enabled-Feld im GET-Read wäre strukturell immer true gewesen, und — am gefährlichsten — reorder hätte deaktivierte Blöcke nicht in die sortOrder aufgenommen, woraufhin Craft sie beim Speichern gelöscht hätte. Alle Block-Queries laufen jetzt über den neuen Helper blockQueryAll(), der ->status(null) setzt, bevor gelesen wird (Query wird bewusst mutiert statt geklont: Craft definiert auf ElementQuery/Query kein __clone, und NestedElementManager::getValue() erweitert denselben memoisierten Feldwert intern um exakt dieselben Kriterien). Betraf resolveMatrixBlocks(), resolveNeoBlocks(), die Neo-getChildren()-Rekursion, applyEntrySectionPatches(), applyBlockReorder(), rollbackEntrySection(), rollbackEntrySectionEnabled() und rollbackEntrySectionOrder().

Changed

  • GET /deon-ai/entry-sections/<id> listet als Folge des Status-Fixes jetzt auch deaktivierte Blöcke auf (erkennbar an enabled: false). Das ist beabsichtigt und Voraussetzung dafür, dass op:"enable" und reorder überhaupt korrekt arbeiten können — Aufrufer, die nur die sichtbaren Sektionen brauchen, filtern auf enabled === true.

Added

  • POST /deon-ai/entry-sections/<id> — neue Patch-Operation op je Eintrag in patches[] (Default weiterhin "set", unverändertes Text-Patch-Verhalten aus v0.18.0 — voll rückwärtskompatibel). Neu: op: "disable"/"enable" — schaltet einen einzelnen Matrix-/Neo-Block sichtbar aus bzw. wieder ein ({ field_handle, block_id?, block_index?, op: "disable" }, kein field/value nötig). Bewusst KEIN Hard-Delete: der Block bleibt als Element erhalten, nur enabled=false — reversibel über op:"enable" oder /deon-ai/rollback, konsistent mit dem Rest des Plugins, das nie destruktiv löscht. Schließt den "Sektion aus der kopierten Seite rausnehmen"-Teil der Copy-&-Rebuild-Vision.
  • POST /deon-ai/entry-sections/<id> — neuer optionaler Body-Key reorder: { "field_handle": "heroSection", "block_ids": [123, 456, 789] } bringt die Blöcke eines Matrix-Feldes in eine neu vorgegebene Reihenfolge (nicht erwähnte Blöcke werden ans Ende angehängt statt zu verschwinden). Kann zusammen mit patches[] oder allein aufgerufen werden (patches[] ist dafür nicht mehr Pflicht). Nutzt dasselbe non-destruktive "Feldwert neu setzen"-Prinzip wie duplicateElement() in /build-template-page — keine Elemente werden neu erstellt oder gelöscht, nur umsortiert. Aktuell nur für Matrix-Felder (Neo liefert reorder_neo_not_supported — Neo verschachtelt Kind-Blöcke strukturell anders, dafür bräuchte es ein eigenes, hier noch nicht gebautes Vorgehen). Schließt den "Sektionen umstrukturieren"-Teil der Vision.
  • GET /deon-ai/entry-sections/<id> liefert pro Block jetzt zusätzlich enabled (bool) — Voraussetzung dafür, dass ein Aufrufer vor einem disable/enable-Patch den aktuellen Stand kennt.
  • Beide neuen Operationen sind einzeln über /deon-ai/rollback rückgängig machbar (rollbackEntrySectionEnabled(), rollbackEntrySectionOrder() — eigene Rollback-Pfade statt Wiederverwendung von rollbackEntrySection(), da dessen Ziel-Key-Format zwingend ein echtes Custom-Field im 4. Segment erwartet).
  • /deon-ai/ping-capabilities um entry_section_write, entry_section_visibility, entry_section_reorder, template_page_build ergänzt (fehlte im ursprünglichen Handoff für dieses Feature sowie rückwirkend für den entry-sections-Write aus v0.18.0).

Hinweis — noch nicht live smoke-getestet

Das Reorder-Wertformat ['sortOrder' => [ids]] ist gegen den echten Quelltext von craftcms/cms 5.10.10 verifiziert (Matrix::_createEntriesFromSerializedData() + NestedElementManager::saveNestedElements()), aber wie der sectionId/typeId-Override in /build-template-page in dieser Session nur mit php -l syntaxgeprüft, nicht gegen eine laufende Craft-Instanz ausgeführt. disable/enable ist risikoärmer (nur $block->enabled + saveElement(), dieselbe Mechanik, die das Plugin an vielen Stellen bereits produktiv nutzt), aber ebenfalls ungetestet gegen den echten Content dieser Site. Vor produktivem Einsatz einmal manuell smoke-testen (siehe Handoff-README) — reorder sinnvollerweise zuerst an der disabled Vorlagen-Section, nicht an einer Live-Seite.

Version 0.19.0

July 24, 2026

Added

  • POST /deon-ai/build-template-page — baut vollautomatisch EINE Vorlagenseite mit den echten nativen Design-Blöcken der Site (demselben Matrix-/Neo-Feld, das z. B. die Startseite nutzt). Body: { source_entry_id, field_handle?, title?, force? }. Hintergrund: /duplicate-page klont eine Seite bereits strukturell perfekt im echten Theme, aber auf frisch angebundenen Sites gibt es oft noch KEINEN Entry mit echten Blöcken außer der Startseite — und die ist als Single-Section keine sichere Massen-Kopiervorlage (URI-Format hat i. d. R. kein {slug}, mehrere Entries würden kollidieren). Dieser Endpoint klont die gewünschten Blöcke stattdessen in eine dedizierte, automatisch angelegte Section (deonTemplates, Channel-Typ, eigenes {slug}-URI-Format, immer disabled — landet nie live) und macht sie so zur sicheren Kopiervorlage für künftige Standort-/Leistungsseiten. Nutzt für den eigentlichen Klon dieselbe native, nicht-destruktive duplicateElement()-API wie /duplicate-page (kein handgebautes Matrix-/Neo-Block-Array) — die Quelle (z. B. die Startseite) wird dabei nur gelesen, nie verändert, auch wenn der Section-Wechsel fehlschlägt. Gate: page_create (dieselbe Freigabe wie /duplicate-page//page). Idempotent: liefert ohne force=true bei wiederholten Aufrufen den bereits vorhandenen Vorlagen-Entry zurück statt Duplikate anzuhäufen.

Version 0.18.0

July 24, 2026

Added

  • POST /deon-ai/entry-sections/<id> — Text-Werte in nativen Matrix-/Neo-Blöcken patchen (dieselbe Route wie der bestehende GET-Read, per HTTP-Methode verzweigt). Body: { patches: [{ field_handle, block_id?, block_index?, field, value }] }. Schließt die im "Kopieren & Umbauen"-Konzept beschriebene Lücke: /duplicate-page klont eine Seite bereits strukturell perfekt (native duplicateElement(), inkl. aller Matrix-/Neo-Blöcke im Original-Theme-Template), aber bisher gab es keinen Weg, den Text in diesen geklonten Blöcken für die neue Seite (z. B. eine andere Stadt/Leistung) zu ersetzen — nur das eine Legacy-deonBody-HTML-Feld war beschreibbar. Schreibt bewusst nur "textartige" Felder (PlainText/CKEditor/Redactor sowie jedes Feld mit einfachem String-Wert — bewusst is_string() statt is_scalar(), sonst wären auch Number-/Lightswitch-/Date-Felder fälschlich als "textartig" durchgerutscht) — Assets/Relationen/verschachtelte Matrix-Neo-Felder werden nie angefasst. Gate: content_edit (dieselbe Freigabe wie /deon-ai/faq). Jeder Patch wird einzeln im Change-Log erfasst und ist über /deon-ai/rollback einzeln rückgängig machbar (rollbackEntrySection(), Ziel-Typ entry-section).
  • GET /deon-ai/entry-sections/<id> liefert pro Block jetzt zusätzlich block_id (stabile Element-ID, nicht nur der positionsabhängige block_index) — Voraussetzung für zuverlässiges Patchen nach einem separaten Read. Bei genesteten Neo-Blöcken block_id statt block_index verwenden (block_index zählt beim GET pro Verschachtelungsebene neu bei 0, beim POST-Fallback indiziert er die flache Blockliste — beide Nummerierungen sind bei genesteten Neo-Strukturen nicht deckungsgleich; für Matrix und flache Neo-Strukturen kein Unterschied).

Version 0.17.0

July 23, 2026

Fixed

  • /deon-ai/faq konnte generierten FAQ-Text stillschweigend korrumpieren. Der FAQ-HTML-Block wurde als Replacement-String an preg_replace() übergeben — PHP interpretiert darin $1/\1 etc. als Backreferences. Enthielt der generierte Text z. B. einen Preis ("Kosten: $29/Monat"), wurde "$29" durch einen Leerstring ersetzt. Jetzt über preg_replace_callback() eingefügt, keine Backreference-Interpretation mehr.
  • /deon-ai/seoenabled wurde bei jedem Teil-Update ungewollt zurückgesetzt. Wurde ein Override zuvor bewusst deaktiviert (enabled: false), reaktivierte ihn jedes spätere Update von z. B. nur title stillschweigend wieder, weil enabled ohne explizites Feld im Request immer auf true statt auf den bestehenden Wert zurückfiel.
  • /deon-ai/entry und /deon-ai/page legten bei ungültiger entry_id still ein Duplikat an. Eine nicht mehr existierende entry_id (gelöschter Entry, Worker-Retry mit veralteter ID) führte zum kommentarlosen Anlegen eines komplett neuen Entries statt eines 404. Beide Endpoints geben jetzt 404 entry_not_found zurück, wenn eine explizit übergebene entry_id nicht auflösbar ist.
  • actionRollbackCreateRestorePoint/-Restore — der Entry-slug wurde beim Wiederherstellen eines Sicherungspunkts nie zurückgeschrieben, obwohl der Snapshot ihn sicherte (im Gegensatz zum korrekten Einzel-Rollback rollbackEntry()). Ein Restore-Point-Restore meldete Erfolg, stellte die URL aber nicht wieder her.
  • setup-blog konnte auf Multi-Site-Sections funktionierende Custom-Templates überschreiben. Hatte eine Section mindestens eine Site mit leerem Template und gleichzeitig eine andere Site mit einem funktionierenden, individuellen Custom-Template, wurden ohne fix_template-Flag trotzdem alle Site-Templates überschrieben — der eigene Docblock-Anspruch ("überschreibt nie stillschweigend eine funktionierende Custom-Konfiguration") war für diesen Fall nicht erfüllt. Jetzt werden ohne force nur Sites mit leerem Template befüllt.
  • createDeonSection() konnte bei einem Section-Speicherfehler eine Datenleiche hinterlassen. saveEntryType() und saveSection() liefen ohne Transaktion; schlug saveSection() fehl, blieb der bereits gespeicherte EntryType mit dem gewählten Handle (deonBlog/deonPages) in der DB zurück — ein erneuter setup-blog-Lauf scheiterte dann dauerhaft an der Handle-Kollision statt sich selbst zu heilen. Beide Saves laufen jetzt in einer gemeinsamen DB-Transaktion.

Alle sechs Funde stammen aus demselben Code-Audit wie v0.16.0 (Daten-Integrität-/Robustheit-Teil der zwölf Funde).

Version 0.16.0

July 23, 2026

Security

  • /deon-ai/publish-lp — Slug ohne Zeichen-Whitelist konnte Kern-Routen kapern. Der Slug wurde in Plugin::init() 1:1 als Array-Key einer Yii-UrlManager-Rule verwendet, deren Key-Syntax Platzhalter wie <id:\d+> unterstützt — ungefiltert also nicht nur eine Kollisionsgefahr, sondern potenzielles Routing-Pattern-Injection. Da die LP-Routen in derselben $event->rules-Array-Instanz nach allen Kern-Routen registriert werden (PHP überschreibt Array-Keys bei Kollision), konnte ein Slug wie "deon-ai/self-update" die gleichnamige Kern-Route kapern. Slug jetzt strikt auf [a-z0-9-/] beschränkt, gegen die deon-ai/*-Reserved-Liste sowie gegen bestehende Entry-URIs geprüft.
  • /deon-ai/seo — Homepage-Schutz war wirkungslos. Die Klausel "Homepage nur mit explizitem allow_homepage-Flag" griff nur, wenn uri komplett fehlte — ein Request mit {"uri":"/"} (ohne das Flag) überschrieb die Homepage-SEO trotzdem. Jetzt greift der Schutz unabhängig vom Rohwert, sobald die normalisierte URI / ist.
  • /deon-ai/configure-ab und /deon-ai/configure-tracker hatten gar keine Berechtigungsprüfung. Jeder gültige API-Key konnte A/B-Testing/Tracking site-weit an-/abschalten, ohne dass es dafür einen Consent-Schalter gab. Neue Berechtigung allowAbTest (Default aus) gattert jetzt beide Endpoints; CP-Settings um den entsprechenden Schalter ergänzt.
  • /deon-ai/publish-winner konnte SEO-Overrides ohne seo_meta-Freigabe schreiben. Die Methode prüfte nur content_edit, ihr seo_meta-Change-Type schrieb aber direkt in dieselbe Tabelle, die /deon-ai/seo nur nach separater seo_meta-Freigabe beschreiben darf. Wird jetzt zusätzlich geprüft; fehlt die Freigabe, wird der seo_meta-Change übersprungen (errors: ["seo_meta: consent_required"]") statt den Rest des Requests abzubrechen.
  • fetchUrlBytes() (Bild-URL-Fetch für Featured Images/Asset-Uploads) war per DNS-Rebinding gegen den SSRF-Schutz umgehbar. Der Host wurde per gethostbyname() gegen private/reservierte IP-Ranges geprüft, der eigentliche Fetch löste den Hostnamen aber erneut auf — bei kurzer TTL könnte der DNS-Eintrag zwischen Check und Fetch auf eine interne IP wechseln (TOCTOU). Die geprüfte IP wird jetzt direkt für die Verbindung verwendet (Host-Header + ssl.peer_name halten virtuelles Hosting/TLS-Zertifikatsprüfung weiterhin korrekt).

Alle fünf Funde stammen aus einem vollständigen Code-Audit der Plugin-Funktionen gegen echten Craft-Core-/Neo-Plugin-Quellcode. Nicht übernommen: der Befund "Rollback-Restore-Endpoints ohne Berechtigungsprüfung" — das ist laut README (Zeile 65) bewusstes Design ("Rückgängig machen funktioniert unabhängig von diesen Schaltern immer"), kein Bug.

Version 0.15.0

July 23, 2026

Added

  • GET /deon-ai/site-inventory — Überblick über alle Sections der Site (handle, type, name, entry_count). Der Worker kann damit erstmals selbst herausfinden, welche Seitentypen eine Kunden-Site überhaupt hat (Leistungen, Branchen, Use-Cases, Über uns, …), statt Section-Handles zu raten oder fest zu verdrahten.
  • GET /deon-ai/entry-sections/<id> — strukturierte Block-/Sektionsinhalte eines beliebigen Entries: native Matrix-Felder (Craft 5: verschachtelte Entries, Craft 4: MatrixBlocks) sowie Neo-Felder (spicyweb/craft-neo, falls installiert), rekursiv aufgelöst, jeweils mit Block-Typ-Handle/-Name und den einzelnen Feldwerten (Text, Bilder inkl. Alt-Text, Entry-/Category-Relationen). Feature-detected über die tatsächliche Feld-Klasse — rät nicht anhand von Feld- oder Block-Namen, da jede Kunden-Site eigene Handles verwendet. Grund: /deon-ai/page-structure (bisher der einzige Content-Lese-Endpoint) kennt ausschließlich das eine deonBody-HTML-Feld; echte, handgebaute Seiten (Startseite, Leistungsseiten, Branchenseiten) bestehen aber i. d. R. aus benannten Block-Typen (Hero, FAQ, CTA, …), nicht aus einem HTML-Blob — für diese Struktur gab es bislang keinen Lese-Pfad. Hat der Entry-Type kein Matrix-/Neo-Feld (heutiger Fall bei Deons eigenen Blog-/Standortseiten), greift legacy_body_fallback (identisch zu /page-structure), damit nichts kaputt geht, was heute schon funktioniert. Tiefen-/Blockzahl-Limit (4 Ebenen / 200 Blöcke) gegen teure, tief verschachtelte Neo-Strukturen. Beide Endpoints read-only, nicht consent-gated (wie /pages, /media, /page-structure). /deon-ai/ping-capabilities um site_inventory, entry_sections ergänzt.

Version 0.14.0

July 23, 2026

Fixed

  • Kritisch: Featured Images wurden nie angehängt. /deon-ai/entry und /deon-ai/page hängen ein Bild nur an, wenn beide Settings gesetzt sind — featuredImageFieldHandle und assetVolumeHandle. setup-blog hat aber ausschließlich featuredImageFieldHandle in den Settings verdrahtet; assetVolumeHandle blieb auf jeder Site leer, außer jemand hat es manuell im CP eingetragen. hero_image_url/image_url vom Worker wurde dadurch bei jedem Blog-/Seiten-Publish stillschweigend ignoriert. setup-blog schreibt assetVolumeHandle jetzt mit — für neu angelegte Felder aus dem verwendeten Volume, für bereits bestehende deonFeaturedImage-Felder aus deren restrictedLocationSource (rückwirkende Heilung von Alt-Setups, ohne das Feld anzufassen).
  • /deon-ai/pings fields_ok.featured_image prüfte bisher nur featuredImageFieldHandle und meldete dadurch true, obwohl Bild-Uploads faktisch nie liefen. Da der Worker-Self-Heal genau dieses Flag als Gate nutzt (setup-blog wird nur erneut aufgerufen, wenn es false ist), blieb der Bug auf bereits betroffenen Sites für die automatische Reparatur dauerhaft unsichtbar. fields_ok.featured_image prüft jetzt zusätzlich assetVolumeHandle samt echter Volume-Existenz.

Version 0.13.0

July 21, 2026

Added

  • GET /deon-ai/media — Bild-Bibliothek der Site über alle Asset-Volumes (url, alt, title, filename, w, h), Pendant zu WPs wp-json/wp/v2/media?media_type=image. Der Worker matcht damit passende Bilder in generierte Sektionen — bislang lief der Aufruf für Craft ins Leere, weil kein Endpoint existierte. Read-only, nicht consent-gated. /deon-ai/ping-capabilities um media_inventory ergänzt.
  • setup-blog prüft und repariert jetzt zusätzlich, ob der Entry-Type der Blog-/Seiten-Section überhaupt ein Titel-Attribut tragen kann. Response neu: title_field/title_field_pages ("ok"|"missing"|"fixed"), Reparatur nur mit { "fix_template": true } (dieselbe Freigabe wie für die Template-Reparatur).
  • /deon-ai/entry und /deon-ai/page brechen jetzt mit 422 title_field_missing ab, statt eine Seite zu veröffentlichen, deren Titel Craft beim Speichern automatisch leert.

Fixed

  • Ursache von „Eintrag ohne Titel" im CP gefunden und behoben (nicht der im Handoff vermutete Ort): /entry und /page haben entry->title schon immer korrekt gesetzt. Der eigentliche Grund liegt in Craft selbst — craft\elements\Entry::updateTitle() läuft unumgehbar in jedem beforeSave() und überschreibt den gesetzten Titel automatisch mit leer, sobald der Entry-Type kein Titel-Feld (hasTitleField) und kein titleFormat hat. Betroffen sind ausschließlich extern (nicht über setup-blog) angelegte Sections — die vom Plugin selbst erzeugten (deonBlog/deonPages) hatten hasTitleField schon immer aktiv.

Version 0.12.0

July 21, 2026

Added

  • Plugin liefert jetzt zusätzlich zum Artikel-Template (v0.11.0) ein eigenes, self-contained Seiten-Template (templates/page.twig, adressierbar als deon-ai/page) für die per POST /deon-ai/page erzeugten Standort-/Leistungsseiten. Rendert entry.title immer als H1 (der Worker liefert bewusst keinen H1 im body_html) und gibt das vom Worker gelieferte, bereits gestylte HTML (FAQ-Akkordeon, CTA-Button etc.) unverändert |raw aus — kein RTE-Sanitizer, kein Theme-Dependency.
  • setup-blog setzt/repariert das Template jetzt auch für die pages-Section — gleiches idempotentes Verhalten wie beim Blog: neue Sections bekommen deon-ai/page direkt, bestehende Sections mit leerem Template werden automatisch repariert, bestehende Sections mit gesetztem Template nur mit { "fix_template": true }. template_pages/previous_template_pages in der Response (additiv neben template/previous_template für Blog).

Fixed

  • Kritisch: setup-blog prüfte/reparierte bislang ausschließlich die eigenen Bootstrap-Sections deonBlog/deonPages — unabhängig davon, ob settings.blogSectionHandle/pagesSectionHandle (Standard "blog"/"pages") bereits auf eine andere, tatsächlich existierende Section zeigten. /deon-ai/entry und /deon-ai/page bespielen aber genau diese konfigurierten Handles, nicht die Bootstrap-Konstanten. Auf Installationen, deren Blog-/Seiten-Section nicht deonBlog/deonPages heißt (z. B. der Default "pages"), reparierte setup-blog dadurch eine ungenutzte Section, während die tatsächlich ausgelieferte Seite weiterhin ohne Template/Styling blieb. setup-blog löst den Ziel-Handle jetzt zuerst aus den aktuellen Settings auf (nur Fallback auf deonBlog/deonPages, wenn der konfigurierte Handle leer oder ungültig ist) und wirkt dadurch immer auf die Section, die auch wirklich ausliefert.

Version 0.11.0

July 20, 2026

Added

  • Plugin liefert jetzt ein eigenes, self-contained Artikel-Template (templates/entry.twig, adressierbar als deon-ai/entry über einen registrierten Site-Template-Root) — kein Theme-Dependency, kein {% extends %}, eigenes Minimal-CSS, keine Deon-Werbung. Behebt den Pilot-Fall, dass ein Custom-Theme-Template Entry-Variablen gar nicht ausgibt und Blog-Artikel dadurch leer bleiben (Titel/Body/Bild liegen korrekt im Entry, nur das Section-Template rendert nichts davon).
  • setup-blog setzt dieses Template jetzt direkt auf neu angelegte Sections (deonBlog/deonPages), statt sie leer zu lassen und nur template_missing zu melden.
  • setup-blog repariert außerdem bestehende Blog-/Seiten-Sections mit leerem Template automatisch — und mit optionalem Body-Flag { "fix_template": true } auch Sections mit einem gesetzten, aber offensichtlich kaputten Template (der Worker prüft das serverseitig, bevor er das Flag sendet). Eine funktionierende Custom-Konfiguration wird nie stillschweigend überschrieben. Response neu: template/previous_template (Blog-Section, primärer Contract-Wert) sowie additiv template_pages/previous_template_pages. Jede Template-Änderung ist rollback-fähig (neuer Change-Log-Typ section_template).

Fixed

  • Kritisch, seit v0.1.0: Sämtliche Section-Operationen (getSectionByHandle, saveSection, saveEntryType, getAllSections) riefen Craft::$app->getEntries() auf. Das funktioniert nur auf Craft 5 — dort wurden Section-Methoden in den Entries-Service gemergt. Auf Craft 4 existieren sie ausschließlich im separaten craft\services\Sections (Craft::$app->getSections()); craft\services\Entries kennt dort nur Entry-Element-Methoden. Jeder schreibende Endpoint, der Sections anfasst (/entry, /page, /entries, /setup-blog, /duplicate-page, /pages, /nav-Structure-Fallback u. a.), war auf Craft 4 dadurch ein Fatal Error — trotz composer.json-Anspruch craftcms/cms: ^4.0.0|^5.0.0. Neuer versionsübergreifender Helper sectionsService() löst den richtigen Service anhand des registrierten Component-Namens auf, ohne version_compare.

Version 0.10.0

July 19, 2026

Added

  • POST /deon-ai/audit-fix — Content-Write-Fixes vom Deon-AI-Worker (Contract = WP /audit-fix, beschränkt auf die zwei für Craft nötigen Actions; alle anderen Fix-Actions laufen bereits über /deon-ai/seo bzw. /deon-ai/faq):
    • replace_content — kompletten Body-HTML ersetzen (Freshness-Refresh). Bewusst ohne HTML-Stripping — authentifizierter Plugin-Kontext, der CKEditor-/Redactor-Purifier greift beim Rendern.
    • append_html_box — HTML-Box idempotent anhängen (interne Verlinkung, Pillar-Backrefs): existiert bereits ein <aside class="…{box_marker}…">-Block (Default-Marker deon-cluster-ref, konfigurierbar über payload.box_marker), wird er ersetzt statt dupliziert — Marker-Sanitize und Regex identisch zum WP-Plugin.
    • Beide Actions mit Rollback-Snapshot (Pflichtfeld rollback_id in der Response), Entry-Auflösung per page_url (URI, Fallback Slug), Berechtigung allowContentEdit.
    • Response liefert meta_keys_applied (exakter WP-Key) und applied als Alias, inkl. no_change-Erkennung wie im WP-Original.

Version 0.9.0

July 17, 2026

Added

Section-Tests + A/B-Varianten — der letzte fehlende Funktionsblock aus dem WordPress-Plugin, Craft-nativ adaptiert (WP arbeitet auf Gutenberg-/Elementor-/Fusion-Strukturen; in Craft sind die „Sections" die Top-Level-Elemente des Body-HTML, builder html):

  • POST /deon-ai/section-test/create — Variante als geklonter, deaktivierter Entry (duplicateElement), Section-Änderungen (insert/remove/move/replace, Selector = Index oder tag[n]) per DOM-Engine auf den Body angewandt. Neue Tabelle deonai_section_tests.
  • Server-seitiger 50/50-Split beim Ausspielen: Cookie aideon_st_<id> (Name identisch zu WP, damit die SDK-Conversion-Attribution gleich funktioniert), Bot-Ausschluss, Cache-Control: no-store + Vary: Cookie, Besucher-Zähler. Variante B ersetzt den Original-Body im gerenderten HTML.
  • GET /deon-ai/section-test/list/<id>, POST /deon-ai/section-test/preview (anwenden ohne speichern), POST /deon-ai/section-test/stop — Winner B wird mit Rollback-Snapshot ins Original gemerged, die Variante wandert in den Craft-Papierkorb (weiches Löschen statt WP-Force-Delete).
  • POST /deon-ai/publish-winner — Änderungs-Liste anwenden: seo_meta → SEO-Override, content_replace → Body-Austausch, html_section → Section-Replace (Craft-Pendant zu elementor_section, das einen klaren Fehler meldet). Immer mit Rollback-Snapshot.
  • POST /deon-ai/ab-variant/create, GET /deon-ai/ab-variant/list/<id>, POST /deon-ai/ab-variant/stop — Selector-basierte A/B-Varianten (alle WP-Modi: text/html/attr/link/style/form), neue Tabelle deonai_ab_variants. Ausspielung über ein 1:1 portiertes Frontend-Snippet (Cookie aideon_ab_assign, ?aideon_force=-Preview mit Banner, sendBeacon-Impression-Tracking).
  • POST /deon-ai/configure-ab + GET /deon-ai/ab-status, POST /deon-ai/configure-tracker + GET /deon-ai/tracker-status — Remote-Konfiguration (WP-Shapes); tracker_enabled=false schaltet zusätzlich zur CP-Einstellung die SDK-Injection ab.
  • /deon-ai/ping-capabilities um content_replace, section_test, ab_variant_split, ab_script_inject, tracker_inject erweitert.

Version 0.8.0

July 17, 2026

Added

Seiten-Anbindung — Contract-Parität zum WordPress-Plugin aideon-connect (v3.67.0), damit Deon AI Craft-Seiten lesen, im Original-Design klonen und texturieren kann. Neue Endpoints (Response-Shapes bewusst identisch zu WP, damit der Worker 1:1 durchreichen kann):

  • GET /deon-ai/match-url?url= — Entry per URL finden (inkl. Slug-Fallback)
  • GET /deon-ai/pages — Entries aller Sections, nach Änderungsdatum (ergänzt /entries, das nur eine Section pro Aufruf listet)
  • GET /deon-ai/page-structure/<id> — kompletter Seiteninhalt: Titel, Slug, Body-HTML, SEO-Override, plus walkbare Text-Blöcke (content_blocks mit pc-N-IDs: h1–h3 = title, p = editor — identisch zum builder-agnostischen WP-Contract)
  • POST /deon-ai/set-widget-texts — gezielte Text-Sets per pc-N auf den Entry-Body (SEO-Texturierung geklonter Standortseiten), mit Rollback-Protokoll
  • POST /deon-ai/duplicate-page — 1:1-Seiten-Klon mit find/replace-Textaustausch, h1_override, SEO-Metas, idempotent per page_id (der Standortseiten-Pfad im Original-Design)
  • GET /deon-ai/render-preview — gerendertes Frontend-HTML für die Dashboard-Preview; HMAC-Preview-Token (gleiches Format wie WP: 60s TTL) + Same-Origin-Guard
  • POST /deon-ai/publish-lp — Full-Page-Landingpage aus Roh-HTML (inkl. <style>/<script>), neue Tabelle deonai_landing_pages, ausgeliefert über eigene Route pro Slug, idempotent per page_id/Slug
  • GET /deon-ai/theme-tokens — Design-Tokens (Farben, Fonts, Radius, Palette). Craft hat kein theme.json, daher CSS-Extraktion aus der gerenderten Startseite inkl. einstufiger var(--x)-Auflösung (source: "css_extract")
  • POST /deon-ai/site-schema — Site-weites JSON-LD (Organization/LocalBusiness), ausgespielt im <head> aller Seiten
  • GET /deon-ai/sitemap-discover — Sitemap-URL-Kandidaten
  • GET|POST /deon-ai/footer-links — Plugin-eigener Footer-Block („Servicegebiete") vor </body>, Markup identisch zum WP-Pendant
  • /deon-ai/ping liefert jetzt eine capabilities-Liste (Namensschema wie WP /capabilities) für einheitliches Feature-Gating im Worker

Changed

  • /deon-ai/hygiene-list liefert nur noch die Typen robots/llms (die Tabelle speichert jetzt zusätzlich site_schema/footer_links)

Version 0.7.0

July 17, 2026

Added

  • POST /deon-ai/setup-blog — Blog-/Seiten-Bootstrap für Pilotinnen ohne bestehendes Schema: legt bei Bedarf ein Body-Feld (deonBody, CKEditor falls installiert → Redactor falls installiert → PlainText multiline), ein Featured-Image-Feld (deonFeaturedImage, auf das erste vorhandene Volume beschränkt, wird übersprungen statt ein Volume zu erfinden) sowie die Sections deonBlog (Channel, blog/{slug}) und deonPages (Structure, {slug}) an — jeweils idempotent, bestehende Handles werden nie überschrieben. Verdrahtet leere/ungültige Plugin-Settings automatisch mit den neuen Handles. Meldet fehlende Section-Templates statt sie selbst anzulegen (liefert stattdessen ein Beispiel-Template im Response mit).
  • /deon-ai/entry und /deon-ai/page unterstützen jetzt beide image_url/asset_id fürs Featured Image (bisher nur /entry) — fail-soft, ein Bildfehler blockiert den Entry/die Seite nie.
  • /deon-ai/ping liefert zusätzlich fields_ok: { body, featured_image } (echte Feld-Existenz, nicht nur ob das Setting gesetzt ist) sowie nav: { verbb, editable }.
  • POST /deon-ai/nav (DEO-80) — verlinkt eine generierte Seite in Hauptnavigation oder Footer. Neuer Consent-Schalter allowNavEdit (Standard aus). Strategie-Kaskade, da Craft keine Kern-Navigation hat: (1) verbb/navigation, falls installiert — Nav per Handle/Name-Heuristik wählen, Node anlegen, Dedupe über URL/verlinkte Entry; (2) Structure-Section mit Handle nav/menu und einem linkUrl/url-Feld, falls vorhanden; (3) sonst 422 nav_not_automatable mit Hinweis zur manuellen Verlinkung bzw. Tipp auf das verbb-Plugin.

Version 0.6.0

July 16, 2026

Added

  • Berechtigungen: 6 neue Schalter im Control Panel („Berechtigungen — was darf Deon AI ändern?"), mit denen der Kunde selbst entscheidet, was Deon AI ändern darf — allowSeoMeta (Standard an), allowContentEdit, allowPageCreate, allowFiles, allowAssets (Standard jeweils aus), allowSelfUpdate (Standard an, siehe Remote-Self-Update)
  • Schreibende Endpoints prüfen jetzt die passende Berechtigung und liefern 403 { ok: false, error: "consent_required", permission: "…" }, solange sie nicht freigegeben ist: /deon-ai/seoseo_meta, /deon-ai/faqcontent_edit, /deon-ai/entry + /deon-ai/pagepage_create, /deon-ai/files + /deon-ai/hygienefiles, /deon-ai/assetassets, /deon-ai/self-updateself_update. Lese-Endpoints (ping, seo-list, entries, hygiene-list, rollback/*) bleiben ungegated — Rückgängig machen funktioniert immer.
  • /deon-ai/ping liefert jetzt ein permissions-Objekt mit dem aktuellen Freigabe-Stand aller sechs Kategorien, damit Deon AI nicht freigegebene 1-Klick-Fixes im Dashboard ausgrauen kann. Zusätzlich plugin_version/craft_version/php_version (Aliase der bestehenden Felder), ein Fähigkeits-Flag self_update (kann dieser Server technisch überhaupt selbst updaten — proc_open, Speicher, composer.phar) und sections_ok (prüft, ob die konfigurierten Section-Handles für Blog/Seiten tatsächlich existieren).
  • Remote-Self-Update: POST /deon-ai/self-update ({ version }) hebt das Plugin per Composer auf eine konkrete Zielversion an (nur das eigene Paket, nie craft update all), danach POST /deon-ai/up in einem neuen Request, um die Plugin-Migrationen auszuführen — zwei Phasen, weil nach dem Composer-Swap im selben Request noch der alte Klassen-Code geladen ist. Preflight prüft proc_open, memory_limit und composer.phar, bevor überhaupt etwas angefasst wird; schlägt er fehl, bleibt die Installation unverändert (422 self_update_unavailable). Fail-soft DB-Backup vor dem Swap. Konsolen-Fallback php craft deon-ai-connect/update <version> für Hosting, auf dem Composer im Web-Request an exec-/Speicher-Limits scheitert.

Version 0.5.0

July 16, 2026

Added

  • /deon-ai/files — robots.txt/llms.txt direkt im Webroot lesen/schreiben (strikte Dateinamen-Whitelist, Backup vor jedem Schreiben)
  • /deon-ai/faq — FAQ-Block sichtbar in den Entry-Body einbauen, idempotent über einen data-deon-faq-Marker (ersetzt statt zu duplizieren)
  • /deon-ai/page — native Seiten anlegen (Standortseiten, KI-Faktenseite), eigene Section-Auflösung über neues Setting pagesSectionHandle, geht standardmäßig als Entwurf raus
  • Neue Tabelle deonai_content_backups — Vorher-Inhalt wird vor jeder /files- oder /faq-Änderung fail-soft gesichert

Fixed

  • Install.php enthielt nur die ursprüngliche deonai_seo_overrides-Tabelle. Da Craft bei einer Neuinstallation ausschließlich Install.php ausführt und alle zu dem Zeitpunkt bereits vorhandenen nummerierten Migrationen ungeprüft als "erledigt" markiert (ohne sie laufen zu lassen), fehlten frischen Installationen bisher deonai_seo_hygiene und deonai_change_log komplett. Install.php enthält jetzt den vollständigen Tabellenstand.

Version 0.4.2

July 15, 2026

Changed

  • Plugin-Store-Vorbereitung: composer.json ohne version-Feld (Versionen kommen aus Git-Tags), support.source ergänzt; LICENSE.mdLICENSE.txt; .github/workflows/create-release.yml für automatische GitHub-Releases bei neuen, vom Craft Plugin Store erkannten Tags. Keine funktionale Änderung am Plugin selbst.

Version 0.4.1

July 15, 2026

Fixed

  • Kritisch: Bootstrap-Save (v0.4.0) hat beim Speichern nur siteId/sdkKey/verificationUuid an savePluginSettings() übergeben — Craft merged dabei nicht mit den bestehenden Settings, wodurch apiKey (und alle anderen Felder) auf ihre Defaults zurückgesetzt wurden. Der Connection-Key erschien danach leer, obwohl die Verbindung erfolgreich war. Betroffene Installationen (Key wurde geleert) müssen den Connection-Key einmal neu eintragen; ab diesem Fix bleibt er erhalten.

Version 0.4.0

July 15, 2026

Changed

  • Ein-Key-Onboarding: Setup verlangt jetzt nur noch den "Deon AI Connection-Key" statt vier separaten Feldern (API-Key, Site-ID, SDK-Key, Verifizierungs-UUID). Beim Speichern holt das Plugin Site-ID, SDK-Key und Verifizierungs-UUID automatisch per Bootstrap-Call (GET https://audit.deon-ai.de/api/plugin/craft/bootstrap, authentifiziert über denselben Key) — analog zum WordPress-Plugin-Flow. Fail-soft: schlägt der Bootstrap fehl, bleiben gespeicherte Settings unverändert, nur eine CP-Meldung informiert.

Version 0.3.0

July 15, 2026

Added

  • Änderungsprotokoll mit Rollback: jede Deon-AI-Änderung (SEO-Override, Entry, robots.txt/llms.txt) speichert automatisch den Vorher-Zustand — kein separater Backup-Schritt, funktioniert auf jedem Hosting (reines SQL, kein shell_exec/mysqldump nötig)
  • /deon-ai/rollback/list, /rollback/<rb_id>, /rollback/<rb_id>/preview, /rollback/<rb_id>/restore — folgt derselben Proxy-Konvention wie das WordPress-/TYPO3-Plugin, erscheint damit im bestehenden "Änderungs-Journal"-Tab des Dashboards statt eines eigenen, unverbundenen Endpoints
  • /deon-ai/rollback/restore-point — kompletter Sicherungspunkt (Snapshot aller SEO-Overrides, robots.txt/llms.txt, Entries) als reines SQL-Snapshot
  • Konflikt-Erkennung: restore bricht ab (HTTP 409), wenn der Live-Zustand seit der Deon-AI-Änderung manuell verändert wurde — überschreibbar mit force: true
  • Alle schreibenden Endpoints (/deon-ai/seo, /deon-ai/entry, /deon-ai/hygiene) akzeptieren optional note und geben rollback_id in der Antwort zurück

Version 0.2.0

July 14, 2026

Added

  • robots.txt/llms.txt-Verwaltung am Origin: neues Setting manageRobotsLlms, Endpoints /deon-ai/hygiene (setzen) und /deon-ai/hygiene-list (auslesen), ausgeliefert über /robots.txt und /llms.txt
  • /deon-ai/entries — bestehende Entries einer Section auflisten (Duplikat-Check vor dem Anlegen)
  • /deon-ai/asset — Bild-Upload (URL oder Base64) für Featured Images, Settings assetVolumeHandle + featuredImageFieldHandle
  • /deon-ai/entry unterstützt jetzt section/body_field-Override sowie image_url/asset_id für Featured Images — Multi-Section-Publishing ohne separate Plugin-Installation

Changed

  • Feature-Parität zum WordPress-Plugin (AideonConnect) angenähert: SEO-Hygiene (robots/llms) und erweiterte Content-API waren zuvor WP/TYPO3-exklusiv

Version 0.1.1

July 10, 2026

Fixed

  • Falsche Repo-URLs in composer.json (support.issues, extra.changelogUrl) korrigiert — der 0.1.0-Tag zeigte noch auf github.com/baestmarketing/craft-connect statt baestmarketing-dot/craft-connect

Version 0.1.0

July 2, 2026

Added

  • SEO-Overrides (Title, Meta-Description, Canonical, Schema.org) — serverseitig ins Frontend-HTML gepatcht, sichtbar für alle Crawler inkl. KI-Bots
  • Automatische SDK- und Verifizierungs-Tag-Injection (kein Template-Edit nötig)
  • REST-Endpoints für Deon AI: /deon-ai/ping, /deon-ai/seo, /deon-ai/seo-list, /deon-ai/entry
  • Blog-Publishing in konfigurierbare Section (Entwurf/live)
  • Settings-Seite im Control Panel (Env-Var-Support)