Files
WP-LIT-MCP/docs/superpowers/specs/2026-10-16-etappe-4-designsystem-design.md
T

247 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.