docs: Etappe 4 Designsystem – Spec (Presets, Farben, Variablen, Theme-Optionen, Schriften, Schleifen)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-16 16:13:12 +02:00
co-authored by Claude Opus 5.5
parent e5f0c65eb0
commit d391cb5112
2 changed files with 260 additions and 5 deletions
@@ -0,0 +1,246 @@
# Etappe 4 – Designsystem schreiben – Design-Spezifikation
- **Datum:** 2026-10-16
- **Status:** Entwurf zur Freigabe
- **Autor:** Jan-Niclas Loosen (Loosen-IT), erarbeitet mit Claude
- **Einordnung:** Etappe 4 der Gesamt-Spec (`2026-09-25-wp-lit-mcp-design.md` §7, §13); baut auf der Divi-Absicherung (`2026-09-29-divi-absicherung-design.md`) auf. Entscheidungen G1–G12: `docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md`. Datenformate: `docs/superpowers/notes/2026-09-28-etappe-4-divi-formate.md` (Divi 5.2.1).
- **Annahmen** sind mit **(A…)** markiert: Punkte, die im Brainstorming nicht einzeln gefragt wurden; bei der Freigabe der Spec bestätigen oder ändern.
## 1. Ziel und Kontext
### 1.1 Problem
Das Plugin liest das Designsystem (`lit/divi-design-system`: Farben, Variablen, Theme-Schriften/-Größen, Presets mit Name und ID) und kann einem Modul beim Einfügen/Ändern **ein** Modul-Preset per Name geben (`"preset": "<Name>"`). Es fehlt:
- Einstellungen eines Presets lesen; Presets anlegen, ändern, löschen; Gruppen-Presets und Preset-Stapel zuweisen; Presets auf viele Seiten auf einmal ausrollen oder überall entfernen.
- Globale Farben, Variablen, Design-Theme-Optionen und Schriften schreiben.
- Eine Wiederherstellung: Divi 5 führt für Farben, Variablen und Presets keine serverseitige Historie (Formate §7.6).
- Schleifen-Inhalte (`loop_*`, z. B. Datum oder Kategorien in Karten einer Loop-Spalte) werden immer abgelehnt (Ruling S36), mit irreführender Meldung „nur innerhalb von Divi-Schleifen“ (G12).
### 1.2 Ziele
1. Ein Corporate Design lässt sich über die Tools vollständig übernehmen: Farben, Variablen, Schriften, Theme-Optionen, Presets – und auf bestehende Seiten ausrollen.
2. Jede site-weite Änderung: Probelauf mit Diff und betroffenen Seiten, erst `confirm: true` schreibt, vorher immer ein Snapshot (G2, G6).
3. Schreiben nur über Divis eigene Funktionen, abgesichert über Manifest und Freigabe (Divi-Absicherung).
4. Schleifen-Inhalte in Modulen innerhalb einer Divi-Schleife setzen können (G12).
### 1.3 Erfolgskriterien
1. Ein Preset lässt sich lesen (Kurz- und Vollform), anlegen (aus `set`/`attrs` oder aus einem Modul), ändern, als Standard setzen und – wenn unbenutzt – löschen; die Frontend-Ausgabe entspricht einem im Builder angelegten Preset (Integrationstest gegen echtes Divi).
2. Modul- und Gruppen-Presets, auch gestapelt, lassen sich einzelnen Modulen und per Massenzuweisung vielen Seiten zuweisen; Verweise lassen sich überall entfernen.
3. Farben, Variablen, Design-Theme-Optionen und Schriften lassen sich schreiben; der Builder zeigt sie danach unverändert an.
4. Jede Änderung eines Bereichs erzeugt vorher einen Snapshot; `lit/divi-design-restore` stellt ihn mit Probelauf wieder her.
5. `{{…}}`/Vollform mit `loop_post_date` in einem Modul unter einer Spalte mit aktiver Schleife wird gespeichert; ohne Schleifen-Kontext kommt eine verständliche Ablehnung.
6. Architekturtest (Divi-Grenze) grün; alle bestehenden Tests grün; Security-Audit ohne offene kritische/hohe Punkte.
### 1.4 Nicht Teil dieser Etappe
- SEO-Optionen, Logo (`divi_logo`), Button-Standards (`all_buttons_*`) (G3, A1).
- MCP-Prompts und eigener Guide „Corporate Design übernehmen“ (G11) – nur Tools; Tool-Beschreibungen tragen die Reihenfolge.
- Echtes Löschen von Farben (nur `inactive`, wie Divi); Löschen hochgeladener Schriften (A2).
- Loop Builder **anlegen** (Schleife an einem Container einschalten/konfigurieren) – nur Inhalte innerhalb bestehender Schleifen (A3).
- Snapshots von Seiteninhalten (dafür WordPress-Revisionen).
## 2. Begriffe
- **Bereich:** `colors`, `variables`, `presets`, `theme_options` – Einheit für Snapshot, `design_rev` und Rechte.
- **`design_rev`:** Hash über den gelesenen Rohbestand eines Bereichs; Sperre gegen gleichzeitige Änderungen (wie `tree_rev` bei Seiten).
- **Änderungs-Pipeline:** gemeinsamer Ablauf aller Schreib-Tools des Designsystems (§4).
- **Seiten-Umschreiber:** gemeinsamer Baustein, der Verweise in vielen Seiten ändert (§6.6).
- **Fundstellen:** Seiten, Bibliothekseinträge und Theme-Builder-Layouts, die ein Preset, eine Farbe oder Variable verwenden.
## 3. Architektur
```
Tools (Abilities) Pipeline / Domäne (reines PHP) Adapter (Divi-Grenze)
lit/divi-presets-set ─┐ DesignChange (Rechte→Prüfung→Diff→ Designsystem-Adapter
lit/divi-colors-set ─┤ confirm→Snapshot→Schreiben→Protokoll) ├ Reader (vorhanden)
lit/divi-variables-set┤──────────▶ PresetStore / ColorStore / … ──────▶ └ Writer (neu)
lit/divi-theme-options-* SnapshotStore Katalog-Adapter
lit/divi-design-restore DesignUsages (Fundstellen) (Gruppen-Namen, Preset-
lit/divi-presets-assign ─┐ SiteRewrite (Seiten-Umschreiber) ─────▶ Attr-Mapping, Google-Fonts)
lit/divi-design-detach ─┘ Seiten-Adapter (DiviSaver)
```
- Neue Divi-Aufrufe liegen nur im **Designsystem-Adapter** (Schreiben: `GlobalData::set_global_colors`, `set_global_variables`, `GlobalPreset::prepare_data`, `validate_preset_count`, `save_data`, `et_update_option`, `FontsUtility`/Divis Upload-Filter) und im **Katalog-Adapter** (`GlobalPreset::get_group_preset_default_attr`, `get_default_group_name`, `Conversion::get_preset_attrs_mapping`, `get_preset_attrs`, `et_builder_get_google_fonts`, Loop-Optionen). Jeder Eintrag steht im Manifest; nachgebaute Logik bekommt einen Fingerabdruck. Divi-Funktionen werden aufgerufen, wo möglich, statt nachgebaut.
- Domänenklassen (Bestände berechnen, Diff, Prüfregeln, Überdeckungen) sind reines PHP und unit-testbar; sie arbeiten auf den Rohdaten des Adapters.
- Seitenänderungen laufen über die vorhandenen Bausteine: `Parser`/`TreeEditor`/`Composer`/`Serializer` und `DiviSaver` (Revision je Seite, Cache), Rechte und Grenzen wie `GlobalUsages`/`VisibleEdits`.
- Formate stammen aus Divi 5.2.1; freigegeben ist inzwischen 5.14.0. **Erster Umsetzungsschritt:** Formate und Funktionssignaturen gegen 5.14.0 prüfen und die Formate-Notiz nachführen (Spec §14 Gesamt-Spec: „nur aus Code abgeleitet“).
## 4. Änderungs-Pipeline (freigegeben)
Jedes Schreib-Tool des Designsystems:
1. **Rechte** des Bereichs (§10).
2. **Eingabe prüfen:** JSON-Schema, dann fachlich (Namen, IDs, Platzhalter `{{color:…}}`/`{{var:…}}`/`{{font:…}}`, Werte über `ValueCleaner`/Composer mit denselben Sperren wie bei Modulen: Lottie, Interactions, rohes HTML/CSS ohne `unfiltered_html`). Fehler gesammelt als `validation_failed` mit `fields`.
3. **Neuen Bestand berechnen** aus aktuellem Rohbestand (live gelesen) + Änderung.
4. **Probelauf** (ohne `confirm`): `diff` (je Eintrag vorher/nachher, Eintragsart `added|changed|removed|deactivated`), `affected` (Fundstellen über `DesignUsages`, Regeln wie `UsageReport`: lesbare Seiten mit Titel, sonst Anzahl; `blocked_ids`), `design_rev`. Nichts gespeichert.
5. **Mit `confirm: true`:** `design_rev` vergleichen (sonst `conflict`, nichts geschrieben) → Snapshot → Schreiben über Divi → nach dem Schreiben zurücklesen und mit dem erwarteten Bestand vergleichen (fängt stille Fehlschläge wie bei `set_global_variables`) → Caches → Protokoll.
6. **Antwort:** `ToolResult` mit `diff`, `snapshot_id`, `log_id`, neuem `design_rev`, `warnings`.
7. **Fehler beim Schreiben oder Zurücklesen:** Snapshot zurückschreiben (`Rollback`), `WriteFailed` (`rolled_back`/`partial`).
Jedes Tool nimmt **mehrere Einträge** eines Bereichs je Aufruf (ein Snapshot, ein Diff) (A4). Keine Ausnahme für „harmlose“ Änderungen: auch ein neues, unbenutztes Preset braucht Probelauf + `confirm`.
## 5. Snapshots (freigegeben)
- **Speicher:** je Bereich eine Option `lit_mcp_snapshots_<bereich>` (autoload aus), höchstens 20 Einträge (FIFO). Eintrag: `id`, `created` (UTC), `user_id`, `ability`, `log_id`, `size`, `data` (Rohbestand wie Divi ihn speichert; Farben inkl. der fünf Customizer-Farben aus `et_divi`).
- **Größe:** Snapshot über 2 MB → Änderung abgelehnt (`snapshot_too_large`), statt ohne Sicherung zu schreiben.
- **`lit/divi-design-snapshots`** (lesen): je Bereich die Liste ohne `data`, mit `cursor`/`limit`.
- **`lit/divi-design-restore`** (`snapshot_id`): Probelauf = Diff Snapshot ↔ aktueller Stand; mit `confirm` Snapshot des aktuellen Stands, dann Zurückschreiben über dieselben Divi-Funktionen wie beim Schreiben. Presets: `save_data()` mit dem Snapshot-Bestand (ohne `validate_preset_count`, weil das Wiederherstellen Presets entfernen darf); verwaiste Verweise in Seiten meldet der Probelauf als Warnung.
- Deinstallation entfernt die Snapshot-Optionen.
## 6. Presets
### 6.1 Identifikation
- Modul-Presets über `module` (`button` oder `divi/button`), Gruppen-Presets über `group` (`font-body` oder `divi/font-body`).
- Ein Preset wird über `id` oder `name` angesprochen. Namen sind in Divi nicht eindeutig: Ein mehrdeutiger Name ist ein Fehler mit den passenden IDs (wie Ruling S13a). Neue Presets mit einem schon vergebenen Namen desselben Moduls/derselben Gruppe werden abgelehnt (A5) – strenger als Divi, damit Namen beim Zuweisen eindeutig bleiben.
### 6.2 Lesen: `lit/divi-preset-get`
- Eingabe: `module` oder `group`, `id` oder `name`.
- Antwort: `id`, `name`, `kind`, `module`/`group`, `default` (Standard-Preset?), `set` (Einstellungen in Kurzform, soweit abbildbar), `attrs` (Vollform, nur was nicht in `set` steht), verschachtelte `group_presets` mit Namen, Platzhalter statt Roh-Referenzen (`{{color:…}}`, `{{var:…}}`), `usage` (Fundstellen wie §4 Schritt 4, gekappt), `design_rev`.
- `lit-divi-design-system` bleibt die Übersicht (Name, ID, Standard).
### 6.3 Anlegen und ändern: `lit/divi-presets-set`
`items` = Liste von Einträgen:
```json
{ "module": "button", "name": "CTA primär", // oder "group": "font-body"; "id" zum Ändern
"rename": "CTA Primär", // optional, nur beim Ändern
"set": { "background_color": "{{color:primary}}", "text_color": "#ffffff" },
"attrs": { "button.decoration.border.desktop.value.radius": "{{var:Eckenradius}}" },
"from_node": { "post_id": 12, "id": "s0.r0.c1.m2" },
"group_presets": { "font": "Button-Schrift" }, // verschachtelte Gruppen-Presets (nur Modul-Presets)
"make_default": false }
```
- Ohne `id` und ohne gleichnamiges Preset → anlegen; mit `id` (oder eindeutigem `name`) → ändern. Ändern **mischt**: genannte Pfade werden gesetzt, `null` entfernt einen Pfad/Breakpoint, Nicht-Genanntes bleibt (wie `lit-divi-update`).
- `set`/`attrs` laufen durch den Composer des Moduls (`module`) bzw. des Quellmoduls der Gruppe: dieselbe Kurzform, Pfadprüfung, Platzhalter, Bereinigung und Sperren wie bei Modulen. Nur Pfade, die Divis Preset-Attribut-Mapping des Moduls kennt (`Conversion::get_preset_attrs_mapping`), sind erlaubt; Inhalte (Texte, URLs, Bilder-IDs …) werden abgelehnt (`validation_failed` mit Pfad).
- **Gruppen-Presets:** nur Pfade unterhalb der Gruppe (`groupId`, z. B. `content.decoration.bodyFont`); Speicherung mit `groupName`, `groupId`, Quell-`moduleName` wie der Builder (Formate §3).
- **`from_node`:** liest den Knoten (Leserecht auf die Seite), übernimmt alle Attribute, die das Preset-Mapping des Moduls als Preset-Attribute führt; Inhalte nicht. Kombinierbar mit `set`/`attrs` (gehen vor). Gruppen: nur die Pfade der Gruppe.
- **`styleAttrs`/`renderAttrs`** werden serverseitig mit Divis `Conversion::get_preset_attrs(…)` berechnet, `version` = installierte Builder-Version, `created`/`updated` in Millisekunden, IDs 10 Zeichen `[0-9a-z]` (Formate §3).
- **`make_default: true`:** wird Standard-Preset des Moduls/der Gruppe; Probelauf nennt alle Seiten mit Modulen dieses Typs ohne eigenes Preset als betroffen.
- Schreiben: vollständiger Bestand → `prepare_data()` (Sanitize wie der Controller) → `validate_preset_count()` → `save_data()`.
### 6.4 Löschen: `lit/divi-presets-delete`
- `items` = Presets (`module`/`group` + `id`/`name`).
- Nur unbenutzte Presets (G5): Fundstellen in Seiten, Bibliothek, Theme Builder **und** als verschachteltes Gruppen-Preset in anderen Presets. Sonst Fehler `in_use` mit Fundstellen und dem Hinweis auf `lit/divi-design-detach`.
- Ein Standard-Preset wird erst gelöscht, nachdem `default` auf `""` gesetzt ist (im selben Aufruf automatisch, im Diff sichtbar).
- Schreiben wie §6.3 mit `actionType`-Semantik `DELETE_*` (kein `preset_count_decreased`); Divi-4-Altoption wie der Controller bereinigen (`remove_preset_from_legacy_option`).
### 6.5 Zuweisen an einzelne Module (`lit-divi-insert`, `lit-divi-update`, `lit-content-create`)
- `"preset"`: Name **oder Liste von Namen** (Stapel, letzter gewinnt wie in Divi); `null` entfernt. Speicherung `modulePreset: [ids]`.
- `"group_presets"`: `{ "<Gruppe>": "<Name>" | ["<Name>", …] | null }`, Gruppe als Kurzname (`font-body`) oder `divi/font-body`; Zuordnung zum Attributpfad über Divis Gruppenliste des Moduls (`get_group_preset_default_attr`). Speicherung `groupPreset: { "<groupId>": { presetId: [ids], groupName } }`.
- **Überdeckungen (G9):** Lokale Werte des Moduls auf denselben Pfaden (Pfad + Breakpoint + Zustand) wie das Preset werden gemeldet (`warnings`: „s0.r0.c1.m2: Hintergrundfarbe, Schriftgröße überdecken ‚CTA primär‘“). `clear_overrides: true` entfernt genau diese lokalen Werte.
- `lit-divi-outline` zeigt Gruppen-Presets mit Namen zusätzlich zu Modul-Presets.
### 6.6 Seiten-Umschreiber (gemeinsam für §6.7 und §6.8)
- Findet Fundstellen (`DesignUsages`: lesbare Seiten, Bibliothekseinträge, Theme-Builder-Layouts; Rechte- und Theme-Builder-Regeln wie `GlobalUsages`), ändert je Seite den Baum über `TreeEditor`/Composer und speichert über `DiviSaver` (Revision je Seite, Cache, Protokoll).
- Probelauf: je Seite die betroffenen Knoten (Pfad-ID, Modul, Label) und Überdeckungen; `blocked_ids` für Live-Seiten ohne `edit_post`; keine Änderung, wenn eine Seite blockiert ist (wie `GlobalEdits`).
- **Grenze je Aufruf** (A6): höchstens 50 Seiten werden mit `confirm` geschrieben; die Antwort nennt `remaining`. Beide Operationen sind wiederholbar (Filter treffen bereits geänderte Module nicht mehr), der nächste Aufruf macht weiter. Über `GlobalUsages::HARD_CAP` Live-Seiten: Ablehnung wie bisher.
- Seiten mit globalen Modulen: Instanzen werden nicht umgeschrieben, sondern der Bibliothekseintrag (eigene Fundstelle, F7-Regeln); nicht bearbeitbare/gesperrte Knoten (StoredNodeGuard) bleiben unverändert und werden gemeldet.
- Pro Seite gilt ein frisch gelesener Baum (kein `tree_rev` nötig); schlägt eine Seite fehl, sind die bereits gespeicherten Seiten gespeichert (Revisionen in der Antwort), die Antwort meldet `partial`.
### 6.7 Massenzuweisung: `lit/divi-presets-assign`
- Eingabe: `module` (Pflicht), `preset` (Name/Liste) und/oder `group_presets`, Filter: `post_ids` (optional, sonst alle Fundstellen mit diesem Modultyp), `only_without_preset` (bool) oder `only_with_preset` (Name), `clear_overrides` (bool, G9).
- Probelauf → `confirm` wie §6.6.
### 6.8 Verweise überall entfernen: `lit/divi-design-detach`
- Eingabe: `preset` (`module`/`group` + Name/ID) **oder** `variable` (Name/ID). Optional `post_ids`.
- Preset: entfernt die ID aus `modulePreset`/`groupPreset`-Stapeln; Module fallen auf das nächste Preset im Stapel bzw. das Standard-Preset zurück (G5 a). Verschachtelte Verwendung in anderen Presets entfernt der Aufruf im selben Schritt über die Pipeline (Snapshot `presets`).
- Variable: ersetzt die Referenz durch ihren aktuellen Wert als festen Wert (A7) – „Verweis entfernen“ ohne Wert würde Inhalte leeren; bei Farben entfällt detach (sie werden nur deaktiviert).
- Danach ist das Löschen (§6.4, §7.2) möglich.
## 7. Farben und Variablen
### 7.1 `lit/divi-colors-set`
- `items`: `{ id? | name?, "label": "Primär", "color": "#5b3a29" | "{{color:Primär}}" + "relative": {hue, saturation, lightness, opacity}, "folder"?, "status": "active"|"inactive" }`. Die fünf Hauptfarben über ihre festen Namen (`accent_color` …).
- Schreiben über `set_global_colors($data, true)` (mischen), Hauptfarben nur, wenn sie geändert werden (Standardwert-Abweichung Formate §4). Neue IDs `gcid-` + 10 Zeichen.
- „Löschen“ = `status: inactive`; der Probelauf nennt Fundstellen als Warnung.
- Zyklen bei relativen Farben (Tiefe max. 10 wie Divi) → `validation_failed`.
### 7.2 `lit/divi-variables-set`
- `items`: `{ id? | name?, "type": "numbers|strings|images|links|fonts", "label", "value", "status": "active"|"archived" }`, `delete: true` für echtes Löschen.
- Prüfung je Typ (Zahlen: CSS-Ausdruck ohne `;{}`, Links: URL, Bilder: Anhang-ID/URL der Mediathek, Schriften: §8.2, Strings: Text).
- Schreiben: vollständig lesen, `stdClass` → Array, `allowedActions` und Customizer-Schriften entfernen, `set_global_variables()` (ersetzt), zurücklesen (stiller Fehlschlag bei fehlendem `variables_manager`).
- Löschen nur unbenutzt (G5; Fundstellen auch in Presets und relativen Farben), sonst `in_use` mit Hinweis auf detach. „Archivieren“ geht immer (Divi-Verhalten).
## 8. Theme-Optionen und Schriften
### 8.1 `lit/divi-theme-options-get` / `lit/divi-theme-options-update`
- Whitelist (G3 a): Schriften (`heading_font`, `body_font`, `primary_nav_font`, `secondary_nav_font`, `heading_font_weight`, `body_font_weight`), Größen (`body_font_size`, `tablet_…`, `phone_…`, `body_header_size`, `tablet_header_font_size`, `phone_header_font_size`, `body_font_height`, `body_header_height`, `body_header_spacing`, `body_header_style`), Layout (`content_width`, `gutter_width`, `section_padding`, `row_padding`).
- Die fünf Farben stehen in `get` (lesend), geschrieben werden sie nur über `lit/divi-colors-set` (eine Quelle, Snapshot `colors`) (A8).
- `get` liefert je Feld Typ, Beschreibung, aktuellen Wert und Divi-Standard; `update` bereinigt mit Divis Customizer-Sanitize-Callbacks (Katalog-Adapter), schreibt mit `et_update_option()` und leert die Caches wie der Customizer (`remove_static_resources`, `et_pb_force_regenerate_templates`).
### 8.2 Schriften
- Schriftnamen (Theme-Optionen, Presets, Schrift-Variablen) werden gegen Divis Google-Fonts-Liste, hochgeladene Schriften (`et_uploaded_fonts`) und Web-Safe-Schriften geprüft; Unbekanntes → Fehler mit ähnlichen Namen.
- **`lit/divi-fonts-upload`** (G10 b): `name`, `files` (`{ "woff2": "<URL>", "woff": …, "ttf": …, "otf": … }`), `weights` (`all` oder Liste), `generic_family`. Laden mit `wp_safe_remote_get` (Größenlimit wie Medien, Grenze je Datei 5 MB (A9)), Dateityp über Inhalt prüfen (Magic Bytes), Ablage mit Divis Upload-Verzeichnis- und Dateityp-Filtern (`upload_dir_font`, `check_filetype_and_ext_font`) über einen eigenen Sideload, weil `FontsUtility::font_add()` nur echte Browser-Uploads annimmt; Eintrag in `et_uploaded_fonts` im Format von `font_add()` (Nachbau mit Fingerabdruck). Namensregeln wie Divi (Sonderzeichen, Länge, kein Google-/vorhandener Name).
- Keine Snapshots für Schriften: ein Upload legt nur neu an, überschreibt nie (A2). Probelauf + `confirm` gelten trotzdem.
- DSGVO: lokales Laden der Google-Fonts bleibt Theme-Einstellung; Tool-Beschreibung weist auf hochgeladene Schriften als datensparsame Variante hin.
## 9. Schleifen-Inhalte (G12)
- **Kontext:** Für den Zielknoten wird der nächste Knoten auf dem Weg Wurzel → Knoten (einschließlich des Knotens selbst) gesucht, dessen `module.advanced.loop.desktop.value.enable` an ist; daraus `queryType` (`post_types`, `post_taxonomies`/`terms`, `user_roles`/Benutzer, Menü …). Genaue Regeln (Selbst vs. Vorfahre, verschachtelte Schleifen, Werte von `queryType`) werden im ersten Umsetzungsschritt aus Divi 5.14.0 abgeleitet (`LoopUtils`, `LoopContext`, `DynamicContentLoopOptions`, Visual-Builder-Logik) und in der Formate-Notiz festgehalten.
- **Erlaubte Optionen:** `DynamicContentMap` liefert `loop_*`-Optionen nicht mehr verworfen, sondern getrennt je Query-Typ (Optionen aus Divis Registry, Zuordnung zum Query-Typ aus Divis Code). `DynamicContentRules::fieldsFor()` bekommt den Schleifen-Kontext; `loop_*` nur, wenn eine Schleife vorhanden ist und die Option zum Query-Typ passt. Einstellungen werden wie alle Dynamic-Content-Einstellungen je Feldtyp bereinigt, Meta-Schlüssel-Regeln S29/S30 inklusive `loop_*_meta_key_`.
- **Meldungen:** ohne Schleife: „Schleifen-Inhalt "%s" braucht eine Divi-Schleife in diesem Modul oder einem übergeordneten Element (Abschnitt, Zeile, Spalte, Gruppe).“; falscher Typ: „… passt nicht zur Schleife dieses Elements (Typ: Terme).“
- **Kurzform:** Platzhalter `{{loop:<option>}}` (z. B. `{{loop:post_date}}` → `loop_post_date`) mit Einstellungen wie `{{post:…}}` (A10).
- **`lit/divi-dynamic-sources`:** optional `post_id` + `node` (Pfad-ID) → zusätzlich die `loop_*`-Optionen des Schleifen-Kontexts dieses Knotens.
- Knoten, die per Insert/Move in eine Schleife gelangen oder sie verlassen: Prüfung am Zielort; ein Move, der vorhandene `loop_*`-Inhalte aus der Schleife herausführt, erzeugt eine Warnung (nicht abgelehnt – Divi lässt das zu) (A11).
- Ruling S36 wird abgelöst; Sicherheitsblick im Audit (§12).
## 10. Rechte
| Bereich / Tool | Voraussetzung |
|---|---|
| Lesen (`preset-get`, `theme-options-get`, `design-snapshots`) | wie `lit/divi-design-system` (`edit_posts`) |
| Farben, Presets schreiben, Restore, Fonts-Upload | `edit_theme_options` (strenger als Divi, Formate §7.4) + Divi-Rolle `use_visual_builder` |
| Variablen | zusätzlich `variables_manager` |
| Theme-Optionen | `edit_theme_options` + Divi-Rolle `theme_customizer` |
| Fonts-Upload | zusätzlich `upload_files` |
| Massenzuweisung, Detach | Recht des Bereichs + `edit_post` für jede Live-Fundstelle (Regeln P8/P8a), Theme-Builder-Zugriff wie bisher |
| `set` mit rohem HTML/CSS, Lottie, Interactions | wie bei Modulen (`unfiltered_html`) |
Alle Schreib-Tools sind Divi-Schreib-Tools im Sinne der Divi-Absicherung (gesperrt bei `divi_untested`/`divi_incompatible`).
## 11. Fehler und Antworten
- Neue Codes: `conflict` (design_rev), `in_use` (mit Fundstellen), `ambiguous_name` (mit IDs), `snapshot_too_large`, `snapshot_not_found`, `font_fetch_failed`, `font_invalid`.
- Vorhandene: `validation_failed` (`fields`), `forbidden`, `confirm_required` (Probelauf-Antwort mit `ok: true, dry_run: true`), `divi_untested`/`divi_incompatible`, `write_failed` (`rolled_back`/`partial`).
- Jede Antwort mit `hint` bei Fehlern; Probeläufe nennen den nächsten Schritt (`confirm: true`, `remaining`).
## 12. Daten, Deinstallation, Sicherheit
- Neue Optionen: `lit_mcp_snapshots_{colors,variables,presets,theme_options}`; Deinstallation entfernt sie. Hochgeladene Schriften bleiben (gehören Divi).
- Protokoll: je Aufruf ein Eintrag mit Bereich, Anzahl Einträge, `snapshot_id`, betroffene Seiten-IDs (keine Inhalte).
- Security-Audit am Ende: Preset-`attrs` (XSS über Style-/Render-Attribute, `unfiltered_html`), Font-Upload (SSRF, Dateityp, Pfad), Massen-Schreibrechte, Schleifen-Optionen (Meta-Schlüssel), Snapshot-Restore (Rechte, Größe).
## 13. Tests
**Unit (reines PHP):** Bestandsberechnung je Bereich (anlegen/ändern/mischen/löschen/deaktivieren), Diff, `design_rev`-Konflikt, Snapshot-FIFO und Größenlimit, Restore-Diff; Preset-Pfadfilter (Mapping, Gruppen-Pfade, Inhalte abgelehnt), `from_node`, Namensregeln, Stapel und Gruppen-Zuweisung, Überdeckungen und `clear_overrides`; Fundstellen-Erkennung (Modul-/Gruppen-Preset, verschachtelt, Variablen in Seiten/Presets/Farben); Seiten-Umschreiber (Grenze 50, `remaining`, blockierte Seiten, globale Module, partial); Schleifen-Kontext (selbst, Vorfahre, verschachtelt, falscher Typ, keiner), Meldungen; Rechte-Matrix; Architekturtest.
**Integration gegen echtes Divi (Smoke-Skripte, Fixture-Seite 3182):** Preset anlegen → Frontend-Klassen und CSS wie beim Builder-Preset; Gruppen-Preset und Stapel zuweisen; Massenzuweisung über zwei Seiten; detach + delete; Farben/Variablen schreiben und im Builder-Lesepfad unverändert; Variablen ohne `variables_manager` → Fehler statt stiller Erfolg; Theme-Option schreiben; Font-Upload (woff2) und Verwendung in Preset; Snapshot + Restore je Bereich; Loop-Karte mit `loop_post_date` speichern und rendern.
**Regression:** alle bestehenden Unit-Tests und Smoke-Skripte grün; `lit-divi-design-system`-Antwort unverändert bis auf neue Felder.
## 14. Umsetzungsreihenfolge
1. Formate und Signaturen gegen Divi 5.14.0 prüfen (Presets, Farben, Variablen, Theme-Optionen, Fonts, Loop-Kontext); Formate-Notiz nachführen; Manifest-Einträge.
2. Pipeline, `design_rev`, SnapshotStore, `design-snapshots`/`design-restore` (zuerst mit Bereich `presets`).
3. Presets: `preset-get`, `presets-set` (inkl. `from_node`, Gruppen, Standard), `presets-delete`.
4. Zuweisen an Module: Stapel, `group_presets`, Überdeckungen/`clear_overrides`, Outline mit Gruppen-Presets.
5. Fundstellen (`DesignUsages`) und Seiten-Umschreiber; `presets-assign`, `design-detach`.
6. Farben, Variablen (inkl. detach für Variablen).
7. Theme-Optionen, Schriftprüfung, Fonts-Upload.
8. Schleifen-Inhalte.
9. Guides (`divi-design-system`, `divi-shortform`, `workflows`) nachführen; Security-Audit; Version.