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

253 lines
28 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:** umgesetzt (Etappe 4a–4e, Version 0.7.0)
- **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) wurden bis 4d immer abgelehnt (Ruling S36), mit irreführender Meldung „nur innerhalb von Divi-Schleifen“ (G12); seit 4e gelten die Regeln aus §9.
### 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 (Formate aus 5.2.1/5.14.0 geprüft, 5.15.1 maßgeblich); freigegeben sind 5.2.1, 5.14.0 und 5.15.1. **Erster Umsetzungsschritt:** Formate und Funktionssignaturen gegen 5.15.1 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, Werte im selben Format wie `lit-divi-node-get` (Roh-Verweise `$variable(…)$`; Namen und Platzhalter liefert `lit-divi-design-system`), `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 (Presets, §6.4) bzw. das Archivieren (Variablen, §7.2) möglich.
## 7. Farben und Variablen
### 7.1 `lit/divi-colors-set`
- `items`: `{ id?, "label": "Primär", "color": "#5b3a29" | "{{color:Primär}}" + "relative": {hue, saturation, lightness, opacity}, "folder"?, "status": "active"|"inactive" }`. `id` nennt eine vorhandene Farbe (ID, Name, Alias oder Optionsname); ohne `id` wird neu angelegt. Die fünf Hauptfarben über ihre festen Namen (`accent_color` …). Unbekannte Felder (z. B. `delete`) lehnt das Tool ab.
- 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 in Seiten, Bibliothek und Theme Builder unter `affected`; nur Presets und relative Farben, die auf die Farbe bauen, sind `warnings` (Ruling V8).
- Zyklen bei relativen Farben (Tiefe max. 10 wie Divi) → `validation_failed`.
### 7.2 `lit/divi-variables-set`
- `items`: `{ id?, "type": "numbers|strings|images|links|fonts", "label", "value", "status": "active"|"archived" }`. Echtes Löschen gibt es nicht: Divi 5.15.1 verweigert jeden Bestand, dem eine gespeicherte `gvid-`-ID fehlt (Plan 4c, Ruling V1). `id` nennt eine vorhandene Variable (ID, Name oder Alias); ohne `id` wird neu angelegt. Unbekannte Felder (z. B. `delete`) lehnt das Tool ab.
- 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`).
- Archivieren geht immer (Divi-Verhalten); der Probelauf nennt Fundstellen in Seiten, Bibliothek und Theme Builder unter `affected`; nur Presets sind `warnings` (Ruling V8). `lit/divi-design-detach` löst die Verweise vorher (§6.8). Ein Restore behält später angelegte Variablen archiviert.
## 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 Hauptfarben listet `get` nicht; es verweist für sie nur auf `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`).
- `body_header_style` nimmt genau einen Wert (`italic`, `uppercase` oder `underline`); Divis Sanitizer lehnt Kombinationen ab. `null` setzt einen Schlüssel auf den Divi-Standard zurück (`et_delete_option`); `'none'` und `false` werden abgelehnt.
### 8.2 Schriften
- Schriftnamen werden für Theme-Optionen und Schrift-Variablen geprüft (Preset- und Modul-Schriftattribute folgen in 4e), und zwar gegen Divis Google-Fonts-Liste, hochgeladene Schriften (`et_uploaded_fonts`) und Web-Safe-Schriften; 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), Installation im Prozess über `FontsUtility::font_add()` mit Temp-Dateien (`action: wp_handle_upload_rest`, wie Divis KI-Pfad `install_to_custom_fonts()`); Divi legt Datei und `et_uploaded_fonts`-Eintrag selbst an, ein Mirror `font-install` sichert die Aufrufkonvention. Formate: `woff2`, `woff`, `ttf`, `otf`. 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, umgesetzt in 4e; aus Divi 5.15.1 abgeleitet)
Belege: `docs/superpowers/notes/2026-10-19-etappe-4e-recherche-schleifen.md`; Audit: `…-etappe-4e-security-audit.md`.
- **Kontext (X1):** Der Schleifen-Kontext eines Knotens ist der des nächsten Vorfahren-oder-selbst, dessen `module.advanced.loop.desktop.value.enable` genau `"on"` ist (Tablet-, Phone- und Hover-Varianten zählen nicht, wie bei Divi). `queryType` wird wie bei Divi mit `sanitize_key` gelesen; fehlt er (auch `null`), gilt `post_types`; `terms` und `users` sind Aliase für Term- und Benutzer-Schleifen (L2). Verschachtelte Schleifen: die innerste gilt für alles darunter; für `loop_<art>_meta_key_*`-Optionen muss die Art jeder eingeschalteten Schleife der Vorfahrenkette passen, weil eine leere innere Schleife ihren Block unverändert an die äußere gibt (Kettenregel, Audit M1).
- **Arten und Optionen (X2):** `post` (`post_types`, `current_page`), `term` (`post_taxonomies`, `terms`), `user` (`user_roles`, `users`), `menu` (`menus`). Die Art einer Option folgt der Gruppe der Divi-Option, die drei Meta-Schlüssel-Optionen dem Namen; gespeichert wird der Array-Schlüssel (`loop_term_name`). `repeater_<ACF>`-Schleifen sind nicht unterstützt: `loop_*` darin wird abgelehnt (X7e). `DynamicContentMap::loopFields($art)` liefert die Optionen je Art, `DynamicContentRules`/`VariableRef` nehmen einen `LoopScope`.
- **Typabgleich als Sicherheitsgrenze:** Ein `loop_*`-Wert ist nur erlaubt, wenn die Art des Kontexts zur Art der Option passt; ohne Schleife ist jede `loop_*`-Option abgelehnt. Grund: Divi liest `loop_post_meta_key_*` in einer Term-/Benutzer-Schleife als Post-Meta des Objekts mit der Schleifen-ID (und umgekehrt). Die Meta-Schlüssel-Regeln S29/S30 gelten auch für `loop_*_meta_key_`.
- **Meldungen (X5):** ohne Schleife: „Schleifen-Inhalt "%s" braucht eine Divi-Schleife in diesem Modul oder einem übergeordneten Element (Abschnitt, Zeile, Spalte, Gruppe).“; falsche Art: „… passt nicht zur Schleife dieses Elements (Typ: Terme).“ (Typ: Beiträge, Terme, Benutzer, Menü).
- **Kurzform (X4):** `{{loop:<option>}}` (z. B. `{{loop:post_date}}` → `loop_post_date`) mit Einstellungen wie `{{post:…}}` (A10); die Tabelle `LOOP` in `Placeholders` ordnet Kurznamen den Optionen zu.
- **`lit/divi-dynamic-sources` (X6):** optional `post_id` + `node` (Pfad-ID): zusätzlich die zur Schleife dieses Knotens passenden `loop_*`-Optionen; ohne Schleife im Kontext ein Hinweis statt einer Liste; Leserecht auf die Seite wie `lit-divi-node-get`.
- **Insert, Update, Move (X3):** Insert prüft im Kontext der Zielposition; schaltet ein Aufruf eine neue Schleife und `loop_*`-Inhalt zugleich ein, wird fail closed auf den komponierten Blöcken entschieden. Update prüft im Kontext des Knotens (ein Composer je Kontext, neue `loop`-Attribute desselben Aufrufs zählen) und warnt, wenn der Wechsel der Schleifen-Art unveränderte Kinder mit `loop_*` betrifft. Move prüft am Ziel und **warnt** (lehnt nicht ab; A11), wenn `loop_*`-Inhalte ihre Schleife verlassen oder in eine andere Art kommen. Die Warnung gilt nur für Inhalte, die vorher zur Schleifen-Kette passten (M1). `lit-content-create` und Theme-Builder-Anlage haben nie einen Schleifen-Kontext.
- **Rechte (Audit L1, Option B):** Eine Schleife in `module.advanced.loop`, die Benutzer (`user_roles`) ausgibt, verlangt `list_users`; eine Beitrags-Schleife mit leeren, `any`- oder `attachment`-`subTypes` verlangt `read_private_posts`; das gilt auch beim Kopieren.
- **Grenzen (X7, dokumentiert):** Schleifen schalten über `attrs` bleibt möglich (A3 verbietet nur ein eigenes Werkzeug); globale Module werden nicht betreten, `loop_*` in Bibliothekseinträgen wird abgelehnt (die Einfügung globaler Instanzen und das Kopieren mit `unfiltered_html` prüfen die Art nicht, Audit L3, offen); Schleifen aus Presets erkennt die Baumprüfung nicht (fail closed).
- Ruling S36 ist abgelöst (X5).
## 10. Rechte
| Bereich / Tool | Voraussetzung |
|---|---|
| Lesen (`preset-get`, `theme-options-get`, `design-snapshots`) | wie `lit/divi-design-system` (`edit_posts`) |
| Farben, Presets schreiben, Restore (Farben/Presets/Variablen) | `edit_theme_options` (strenger als Divi, Formate §7.4) + Divi-Rolle `use_visual_builder` |
| Variablen | zusätzlich `variables_manager` |
| Theme-Optionen (schreiben, Restore) | `edit_theme_options` + Divi-Rolle `theme_customizer` (kein Visual Builder) |
| Fonts-Upload | `upload_files` + `edit_theme_options` + Divi-Rolle `custom_fonts_management` |
| 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; alternativ `rev_mismatch` mit eigener Meldung, Plan entscheidet nach Bestand), `in_use` (vorhandener Code, 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 wie bei globalen Modulen und `lit/tb-activate`: Fehlerobjekt mit `diff`, `affected`, `design_rev` in den Details), `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.15.1 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.