docs: guides, readme and spec for the Divi Library (v0.4.0)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 03:33:38 +02:00
co-authored by Claude Opus 5.5
parent 8808a7dcdb
commit 2729e2800e
15 changed files with 320 additions and 30 deletions
@@ -160,3 +160,62 @@ Quellen der Web-Recherche: [Divi AI Agent – Ankündigung](https://www.elegantt
- F3 Reihenfolge: erst 3a Bibliothek, dann 3b Theme Builder.
- F4 Pods-Beziehungs-/Dateifelder: als dynamische Quelle nur textartige Felder anbieten; andere mit Hinweis auslassen.
- F5 Testdaten: selbst erzeugen („Probier es selbst“) – über Divis eigene Speicherwege (Builder-REST-Controller/Services im Prozess), als „[Fixture] …“ markiert, danach als Fixtures ins Plugin exportiert. Pods ist lokal deaktiviert; Aktivierung für 3b beim Benutzer anfragen.
## 6. Umsetzung Etappe 3a (Plan 2026-09-27-etappe-3a-bibliothek.md)
- E5 präzisiert durch die Builder-Fixtures: Instanzen werden wie im Visual Builder erzeugt – `localAttrs` =
vollständiger Schnappschuss der Wurzel-Attribute (oberste Schlüssel sortiert), Kinder mit `globalParent`,
ohne Kinder selbstschließend – statt der minimalen Form ohne `localAttrs`. Byte-Test gegen
`page--globale-instanzen.html`.
- Neuer Befund 9: Bibliothekseinträge aus Divis Dialog haben `\r\n` zwischen Blöcken (Formular-Upload);
`LibraryContent::load()` vereinheitlicht das vor jeder Übernahme (undurchsichtige Blöcke bleiben unberührt).
- Kopie eines normalen Eintrags mit globalen Instanzen: Instanzen bleiben verknüpft (wie Divis `/load`);
entglobalisiert wird nur der Inhalt globaler Einträge (Divis Schadensbegrenzung, F10). `lit/library-save`
mit `scope: "normal"` übernimmt enthaltene Instanzen ebenfalls als Verweise. `mode: "global"` nur für
globale Einträge mit genau einer Wurzel ohne globale Module (P11: nie verschachtelte Instanzen).
- E6 erweitert: `StoredNodeGuard` prüft neben den S7/S26-Pfaden und dem Code-Modul auch fremde Blöcke,
Freitext, nicht lesbare Blöcke, kses-instabilen Rich-Text (P3) und `$variable`-Verweise (Ablehnung, wenn
`canonical()` sie für das Ziel ablehnt oder ihre Einstellungen verändern würde) – Grund: R2-Befund 1
(before/after nach dem Autor des Ziel-Posts). Lesbare globale Instanzen werden geprüft (localAttrs und
Schnappschuss), nicht abgelehnt (P15). Strenger als Divi; sichtbare Ablehnung. URL-Werte: `url($v) ===
trim($v)` oder relativer Link ohne Schema (P17/P18) – `kontakt/`, `/pfad`, `#anker` bleiben kopierbar.
- Builder-only erweitert (S7-Gate, gilt auch für Seiten): `decoration.interactions` (P16 – der Effekt
„Attribut“ setzt im Browser beliebige Attribute, z. B. `onclick`; „Cookie“ setzt Cookies) und
`lottie.innerContent` (P19 – `src` lädt fremdes JSON, lottie-web wertet Ausdrücke aus). Offen für
Etappe 4: Divi-Presets können beides tragen; Preset-Schreibwege brauchen dasselbe Gate.
- E3 ergänzt: neue Einträge zusätzlich nur mit `publish_posts` (Bibliothekseinträge sind immer `publish`);
`scope: "global"` braucht zusätzlich `edit_global_library` (P7). Ohne das Divi-Recht „Divi-Bibliothek“
(oder mit deaktivierter Gruppe, P9) ist jede Bibliotheks-ID `not_found`, byte-gleich mit einer fehlenden ID
(P12); ohne `edit_post` auf dem Eintrag `forbidden` ohne Titel. `lit/library-list` listet nur Einträge mit
`edit_post` und liest höchstens 500 Zeilen je Aufruf.
- E9 umgesetzt mit: `lit/divi-node-copy` und `lit/divi-dynamic-sources` in der Gruppe „Divi“; die Gruppe
„Divi-Bibliothek“ sperrt als Ganzes („nur lesen“/deaktiviert) auch Schreibzugriffe der Divi-Werkzeuge auf
Bibliothekseinträge (`ExposurePolicy::groupWritable`). `lit-library-insert` und `lit/divi-node-copy` folgen
zusätzlich dem Zustand von `lit/divi-insert` (M2). Offen (P13, Schlusswelle): bei deaktivierter Gruppe
nennen Outline/node-get/Render-Auszug einer Seite noch die ID globaler Module.
- Antwort F2: jede Änderung eines globalen Eintrags ist zuerst ein Probelauf (auch ohne veröffentlichte
Instanzen); `lit-divi-delete` nutzt dasselbe `confirm`, sein `confirm_required` nennt dann ebenfalls die
betroffenen Seiten. P8a: die Bestätigung wird abgelehnt, wenn der Benutzer einen Eintrag mit Status
`publish`, `private` oder `future`, der den Eintrag zeigt, nicht bearbeiten darf (auch TB-Layouts und andere
Bibliothekseinträge); Entwürfe blockieren nicht. Der Probelauf nennt solche IDs vorab in `blocked_ids`
(P14). Revisionen aus: abgelehnt ohne `unfiltered_html` (die Sicherung liefe durch kses) oder bei
deaktiviertem Papierkorb (P9); scheitert das Speichern nach einer Sicherung, nennt der Fehler `backup_id`
(P14). Cache-Leerung für alle Seiten und den Theme Builder.
- P1: `lit/divi-replace-text` speichert normale Bibliothekseinträge über `DiviWriter::commit()`; globale nie
(die Ziele tragen kein `confirm`) – das Ergebnis nennt die betroffenen Seiten und verweist auf
`lit-divi-update` mit `confirm`.
- Bibliothekseinträge bekommen keine Divi-Flags (Befund 4); `_et_pb_row_layout` wird nicht geschrieben (Befund 5).
- E13a: `{{link:<ID>}}` nur in URL-Feldern; Ziel muss existieren, `read_post` bestehen, nicht Papierkorb,
`auto-draft` oder `inherit` sein – Anhänge sind damit ausgeschlossen (P10). Entwürfe und private Ziele
rendert Divi für Besucher als `?page_id=<ID>` (nur die ID wird sichtbar, Preflight).
- F4/E12: `lit/divi-dynamic-sources` listet nur textartige Felder; Beziehungs-/Dateifelder („nur IDs“),
Tabellen-Speicher und unbekannte Feldtypen (P9) stehen unter `omitted`; Auswahlfelder zeigen den
gespeicherten Wert, nicht die Beschriftung. Der Beispiel-Post ist nur einer, den der Benutzer bearbeiten
darf.
- E15: Adapter `DiviRoles`, `DiviLibraryStore`, Prüfung `DiviCompatibility` in `lit-site-status`
(`problems` je Komponente).
- E13d umgesetzt als Hinweis in `notes`; eine eigene Expansion globaler Module im Auszug unterbleibt.
- Tests: die Unit-Suite läuft mit `memory_limit` 512M (`phpunit.xml.dist` und `composer test`), weil der
Divi-Katalog beim Standard von 128M nicht mehr in den Speicher passt.
- Server-Instructions: Zeile „Divi-Bibliothek: …“ nur bei aktiver, beschreibbarer Gruppe; MCP-Prompts gibt es
noch nicht, der Ablauf „globale Module vorschlagen“ steht im Guide `divi-library` (E14).
@@ -114,7 +114,7 @@ Ob dieser Aufbau stabil funktioniert, entscheidet Etappe 0 (§12).
- **Direkte Tools (~20):** mit vollem Schema sofort sichtbar; nur direkte Tools können Bilder (`ImageContent`) zurückgeben. Standard-Set:
`lit/guide`, `lit/content-types`, `lit/content-query`, `lit/content-get`, `lit/content-create`, `lit/content-update`, `lit/post-publish`, `lit/media-search`, `lit/media-preview`, `lit/media-upload-from-url`, `lit/divi-outline`, `lit/divi-node-get`, `lit/divi-insert`, `lit/divi-update`, `lit/divi-move`, `lit/divi-delete`, `lit/divi-design-system`, `lit/divi-catalog`, `lit/library-insert`, `lit/page-render-summary`.
- **Stand nach Etappe 2 (Entscheidung D2, 2026-09-26):** 21 direkte Tools – das Standard-Set oben ohne `lit/library-insert` (kommt mit Etappe 3), dazu `lit/site-status` (leeres Eingabeschema, Anker der Verbindungstests) und `lit/media-download` (eingebettete Ressource, funktioniert nur direkt). `lit/divi-duplicate-node` und `lit/divi-replace-text` sind indirekt. Mit Etappe 3 sind es 22; das Budget wird dort erneut geprüft (Kosten entstehen vor allem durch große Eingabeschemas, nicht durch die Zahl).
- **Stand nach Etappe 2 (Entscheidung D2, 2026-09-26):** 21 direkte Tools – das Standard-Set oben ohne `lit/library-insert` (kommt mit Etappe 3), dazu `lit/site-status` (leeres Eingabeschema, Anker der Verbindungstests) und `lit/media-download` (eingebettete Ressource, funktioniert nur direkt). `lit/divi-duplicate-node` und `lit/divi-replace-text` sind indirekt. Mit Etappe 3 sind es 22; das Budget wird dort erneut geprüft (Kosten entstehen vor allem durch große Eingabeschemas, nicht durch die Zahl). **Stand nach Etappe 3a:** 22 direkte Tools (dazu `lit/library-insert`); indirekt neu `lit/library-list`, `lit/library-save`, `lit/divi-node-copy`, `lit/divi-dynamic-sources`. Neue Tool-Gruppe „Divi-Bibliothek“ (`lit/library-*`); `lit/divi-node-copy` und `lit/divi-dynamic-sources` gehören zur Gruppe „Divi“.
- **Alle übrigen Abilities:** über die Meta-Tools des Adapters (`discover-abilities`, `get-ability-info`, `execute-ability`).
- In den Einstellungen änderbar, welche Abilities direkt, indirekt oder deaktiviert sind.
- Ability-Namen: `lit/<bereich>-<aktion>`. Der Adapter bildet daraus Tool-Namen (`lit-<bereich>-<aktion>`).
@@ -174,7 +174,7 @@ Seiten, Theme-Builder-Layouts (`et_header_layout`, `et_body_layout`, `et_footer_
- **Invariante:** Parse → Serialize ohne Änderung ergibt byte-identischen Inhalt. Fremde/unbekannte Blöcke bleiben unberührt. Ausnahme beim Speichern (nicht im BlockTree): Divi selbst normalisiert Seiten mit eigenen HTML-/Attribut-Feldern beim Speichern (`module.decoration.attributes`, `module.advanced.html` mit `htmlBefore`/`htmlAfter`/`elementType`) – Divi parst und serialisiert dann die ganze Seite neu, was nur Bytes ändert, die noch nicht in Divis `serialize_blocks()`-Form vorlagen; gemeldet als `CHANGED_ON_SAVE` (Ruling S7).
- Reines PHP ohne WordPress-Abhängigkeit (vollständig unit-testbar).
- Attributwerte mit `$variable(...)$` werden beim Serialisieren wie von Divi erwartet escaped (`"` → `"`).
- Umsetzung (D3): eigener Tokenizer mit dem Delimiter-Muster von `WP_Block_Parser` statt `parse_blocks()` – ohne WordPress testbar, jedes Byte zwischen den Delimitern bleibt erhalten. Knoten mit `globalModule`, `divi/global-layout` oder nicht dekodierbaren Attributen sind undurchsichtig: sie werden nicht betreten, nur als Ganzes verschoben, dupliziert oder gelöscht (Bearbeitung ab Etappe 3). Unbalanciertes Markup wird abgelehnt, nie repariert.
- Umsetzung (D3): eigener Tokenizer mit dem Delimiter-Muster von `WP_Block_Parser` statt `parse_blocks()` – ohne WordPress testbar, jedes Byte zwischen den Delimitern bleibt erhalten. Knoten mit `globalModule`, `divi/global-layout` oder nicht dekodierbaren Attributen sind undurchsichtig: sie werden nicht betreten, nur als Ganzes verschoben, dupliziert oder gelöscht (bearbeitet werden globale Module seit Etappe 3a im Bibliothekseintrag, E2). Unbalanciertes Markup wird abgelehnt, nie repariert.
### 5.3 Adressierung und optimistische Sperre
@@ -216,6 +216,7 @@ Seiten, Theme-Builder-Layouts (`et_header_layout`, `et_body_layout`, `et_footer_
| `{{font:heading}}`, `{{font:body}}` | Theme-Schriften (`--et_global_heading_font` / `--et_global_body_font`) |
| `{{post:title}}`, `{{post:excerpt}}`, `{{post:date}}`, … | Dynamic Content (Post-Daten) |
| `{{field:<meta_key>}}` | Dynamic Content `custom_meta_<meta_key>` (Pods/Meta) |
| `{{link:<ID>}}` | interner Link: Dynamic Content `post_link_url_<Typ>` mit `post_id` (nur Link-Felder; Ziel muss für den Benutzer lesbar sein, nicht im Papierkorb, kein automatischer Entwurf, kein Anhang; Entwürfe/private Ziele rendert Divi als `?page_id=`; Etappe 3a, E13a) |
| `"preset": "<Name>"` | `modulePreset: ["<id>"]` |
Unbekannte Namen → Fehler mit Liste der vorhandenen.
@@ -249,7 +250,7 @@ Kurzform setzt Admin-Labels automatisch (`<Abschnitt> – <Modul>`), sofern nich
| `lit/divi-node-get` | lesen | vollständige Attribute eines Knotens (Vollform) |
| `lit/divi-catalog` | lesen | Module, Kurzform-Felder, wichtige Attributpfade; Detail pro Modul |
| `lit/divi-design-system` | lesen | Farben, Variablen, Presets, Theme-Schriften/-Größen |
| `lit/divi-dynamic-sources` | lesen | verfügbare Dynamic-Content-Quellen für einen Post-Typ (über `DynamicContentOptions::get_options` mit einem Beispiel-Post des Typs) – Etappe 3, mit dem Theme Builder (D6) |
| `lit/divi-dynamic-sources` | lesen | verfügbare Dynamic-Content-Quellen für einen Post-Typ (über `DynamicContentOptions::get_options` mit einem Beispiel-Post des Typs) – Etappe 3a (E12, E14); nur textartige Felder (Antwort F4): Beziehungs-, Datei-, Tabellen- und unbekannte Felder stehen mit Grund unter `omitted`, Auswahlfelder liefern den gespeicherten Wert; Beispiel-Post nur einer, den der Benutzer bearbeiten darf |
| `lit/page-render-summary` | lesen | gerenderte Seite als strukturierter Auszug (Überschriften, Texte, Links, Bilder+Alt); Inhaltsbereich, im Prozess über `the_content` gerendert (ohne Header/Footer/Theme Builder); Teil der WordPress-Integration, funktioniert auch ohne Divi (D5) |
| `lit/divi-insert` | schreiben | Knoten (Kurz-/Vollform, auch ganze Abschnitte) an Position `parent`/`before`/`after` einfügen |
| `lit/divi-update` | schreiben | Werte setzen/entfernen (Kurz- oder Vollform) |
@@ -257,10 +258,10 @@ Kurzform setzt Admin-Labels automatisch (`<Abschnitt> – <Modul>`), sofern nich
| `lit/divi-delete` | schreiben, destruktiv | Knoten entfernen |
| `lit/divi-duplicate-node` | schreiben | Knoten duplizieren |
| `lit/divi-replace-text` | schreiben | Text seitenübergreifend (je Inhaltstyp) ersetzen, `dry_run` (Standard `true`) liefert Treffer je Seite mit `tree_rev`; ausgeführt wird nur auf diesen `targets` mit `confirm: true`, jede Seite mit eigener Revision (D10) |
| `lit/library-list`, `lit/library-get` | lesen | Library-Einträge nach Typ (`layout_type`) und Scope |
| `lit/library-save` | schreiben | Knoten als Library-Eintrag speichern (normal oder global) |
| `lit/library-insert` | schreiben | Library-Eintrag als Kopie oder globale Instanz einfügen |
| `lit/divi-node-copy` | schreiben | Knoten von Seite A nach Seite B kopieren |
| `lit/library-list` | lesen | Einträge nach Typ (`layout_type`) und Scope, mit `cursor`; mit `id` Detail inkl. Knoten-Übersicht und betroffener Seiten (ersetzt `lit/library-get`, E9); nur Einträge mit `edit_post` |
| `lit/library-save` | schreiben | Knoten als Library-Eintrag speichern (normal oder global; global braucht Divis „Globale Bibliothekselemente bearbeiten“, P7); Knoten auf der Seite bleibt unverändert; StoredNodeGuard (E6) |
| `lit/library-insert` | schreiben | Library-Eintrag als Kopie oder globale Instanz im Format des Visual Builders (`localAttrs`-Schnappschuss, `globalParent`) einfügen; direkt |
| `lit/divi-node-copy` | schreiben | Knoten von Eintrag A nach Eintrag B kopieren (Seiten und Bibliothekseinträge; byte-gleich, `source_tree_rev` Pflicht, S7/S26-Prüfung E6) |
| `lit/library-make-global` | schreiben | Knoten in globales Modul umwandeln; optional gleichartige Vorkommen ersetzen |
| `lit/library-find-similar` | lesen | ähnliche Abschnitte seitenübergreifend (Struktur-Fingerabdruck: Modultypen, Aufbau, Presets) |
| `lit/tb-list` | lesen | Vorlagen, Bedingungen, Layouts |
@@ -401,15 +402,17 @@ Dynamisch pro Seite erzeugt (Server-Beschreibung → `instructions`), ca. 15 Zei
- aktive Integrationen und Post-Typen,
- Kernregeln: vor dem Schreiben `lit/divi-outline` und `lit/divi-design-system` lesen; Variablen/Presets vor festen Werten; native Module vor Code-Modul; Status nie ändern ohne ausdrücklichen Wunsch; immer `rev` (bei Divi-Änderungen `tree_rev`) mitgeben,
- Verweis auf `lit/guide`.
- Etappe 3a: Zeile „Divi-Bibliothek: …“ (globale Module im Bibliothekseintrag ändern, Probelauf und `confirm: true`, Guide `divi-library`), nur wenn die Gruppe „Divi-Bibliothek“ aktiv und beschreibbar ist.
### 9.2 `lit/guide(topic)`
Direktes Tool; Anleitungen als Markdown in `docs/guides/`, mit dem Plugin versioniert, per Filter `lit_mcp_guides` erweiterbar.
Themen: `overview`, `divi-structure`, `divi-shortform`, `divi-attrs`, `divi-placeholders`, `divi-design-system`, `theme-builder`, `content-types`, `workflows`.
Themen: `overview`, `divi-structure`, `divi-shortform`, `divi-attrs`, `divi-placeholders`, `divi-design-system`, `divi-library`, `theme-builder`, `content-types`, `media`, `workflows`. Thema `all` liefert alle Themen in einem Aufruf (Etappe 3a).
### 9.3 MCP-Prompts
`corporate-design-uebernehmen`, `neue-website-aufsetzen`, `seite-pruefen`, `globale-module-vorschlagen`, `custom-post-type-vorlage`.
(Stand Etappe 3a: noch keine MCP-Prompts im Plugin; `globale-module-vorschlagen` steht als Ablauf im Guide `divi-library`.)
## 10. Sicherheit