Files
WP-LIT-MCP/docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md

165 lines
24 KiB
Markdown
Raw Permalink 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: Entscheidungen (Entwurf)
Stand 2026-09-28, Brainstorming läuft (Weg: architektonisch → Spec → Plan). Branch `etappe-4` (ab main 903246d).
## Verstandenes Ziel
- Corporate Design in einem Rutsch übernehmen: lesen (`lit/divi-design-system`) → Zuordnung vorschlagen → nach Bestätigung schreiben.
- Schreiben nur über Divis eigene Funktionen; vor jeder Änderung ein Snapshot (Standard 20), Wiederherstellen per Tool.
- Preset-Sperren wie bei Modulen (Lottie, Interactions, rohes HTML/CSS ohne `unfiltered_html`) – offen aus 3a.
- Rechte: `edit_theme_options`, für Variablen zusätzlich Divis `variables_manager` (strenger als Divi selbst).
## Antworten der Auftraggeberin/des Auftraggebers
- G1 Echte Designdaten: (b) selbst über Divis Funktionen anlegen → erledigt, Commit 65bd86f, Formate in `2026-09-28-etappe-4-divi-formate.md`, Fixtures in `tests/Fixtures/divi/stage4/`, Seite 3182 „[Fixture] Designsystem“ (Entwurf).
- G2 Site-weite Änderungen (Presets, Farben, Variablen): (a) wie globale Module – Probelauf zeigt Änderung und betroffene Seiten, erst `confirm: true` schreibt; Snapshot immer.
- G4 Zuschnitt (2026-10-16): (b) ganz Etappe 4 in einer Spec; Presets lesen (inkl. Einstellungen), anlegen, ändern und zuweisen (Modul- und Gruppen-Presets) sind ausdrücklicher Teil.
- G3 Theme-Optionen-Umfang (2026-10-16): (a) Design-Optionen ohne SEO (Theme-Schriften/-Größen, Akzent-/Linkfarben, Layout-Breiten). Annahme: Button-Standards (`all_buttons_*`) und Logo (`divi_logo`, ePanel) bleiben draußen.
- G5 Löschen (2026-10-16): (b) Preset/Variable nur löschen, wenn nirgends verwendet; sonst Fehler mit Fundstellen. Dazu eigene site-weite Funktion „Verweise überall entfernen“ (Seiten, Bibliothek, Theme Builder): (a) nur den Verweis entfernen, Module fallen auf Standard zurück; Probelauf je Seite, `confirm: true`, Revision je Seite, Snapshot vorher.
- G6 Snapshots (2026-10-16): (a) je Bereich (Farben, Variablen, Presets, Theme-Optionen) getrennt, je 20; Liste per Tool; Wiederherstellen mit Probelauf (Diff) und `confirm: true`, vorher selbst ein Snapshot; Seiteninhalte nicht (WordPress-Revisionen).
- Farben löschen: nur „inaktiv“ wie Divi (Annahme, nicht widersprochen).
- G7 Preset-Inhalt (2026-10-16): (a) `set` (Kurzform) + `attrs` (Vollform) wie lit-divi-update, gleiche Prüfung/Sperren/Platzhalter; zusätzlich `from_node` {post_id, id} übernimmt das Design eines Moduls (ohne Texte/Inhalte). Gruppen-Presets analog mit `group`, nur Attribute dieser Gruppe.
- G8 Zuweisen (2026-10-16): insert/update um Stapel (`preset` als Liste) und `group_presets` {Gruppe: Name|null} erweitern; zusätzlich site-weite Massenzuweisung (a) – gleiches Gerüst wie „Verweise entfernen“ (G5), Filter Modultyp, Seiten, nur ohne Preset / mit bestimmtem bisherigem Preset.
- G9 Lokale Werte vs. Preset (2026-10-16): (a) Probelauf/Antwort meldet je Modul lokale Werte, die das Preset überdecken; `clear_overrides: true` entfernt genau diese; sonst nur Warnung. Gilt für Einzel- und Massenzuweisung.
- G10 Schriften (2026-10-16): (b) Google-Fonts aus Divis Liste (Namensprüfung mit Vorschlägen) und eigene Schriftdateien hochladen (Divi „Custom Fonts“, Option `et_uploaded_fonts`). Annahme: Quelle per URL wie lit-media-upload-from-url. Code: `FontsUtility::font_add()` (B5 `VisualBuilder/Fonts/FontsUtility.php:102`) nutzt `wp_handle_upload` (prüft `is_uploaded_file`) → für geladene Dateien eigener Sideload mit Divis Filtern `upload_dir_font`/`check_filetype_and_ext_font` nötig; DSGVO-Lokal-Laden bleibt Theme-Einstellung.
- G11 Ablauf „Corporate Design übernehmen“ (2026-10-16): (c) kein eigener Ablauf/Prompt, nur Tools; Claude stellt den Ablauf selbst zusammen.
- G12 Schleifen-Inhalte (2026-10-16): in Etappe 4 aufnehmen. Befund: `DynamicContentRules::fieldsFor()` (`src/Integrations/Divi/Compose/DynamicContentRules.php:117`) lehnt `loop_*` immer ab (Ruling S36, E12) – kein Erkennungsfehler; Meldung „nur innerhalb von Divi-Schleifen“ irreführend. Praxisfall: Datumsformat in Karten (`loop_post_date`), Pills (Kategorien/Tags) in Modulen unter einer Schleifen-Spalte. Ziel: Schleifen-Kontext aus dem nächsten Vorfahren mit `module.advanced.loop.desktop.value.enable` (+ `queryType`) bestimmen und passende `loop_*`-Optionen zulassen (S36 lockern, Sicherheitsblick).
- Ansatz (2026-10-16): A – ein Tool je Bereich, gemeinsame Änderungs-Pipeline; gemeinsamer Baustein für Seiten-Umschreiben (Zuweisen/Entfernen); `lit/divi-preset-get` zum Lesen.
- Entwurf Abschnitt 1 (Pipeline + Snapshots) freigegeben (2026-10-16); übrige Abschnitte direkt in die Spec `docs/superpowers/specs/2026-10-16-etappe-4-designsystem-design.md` (Annahmen A1–A11), Freigabe der Spec ausstehend.
## Noch zu klären (je eine Frage)
- (alle Fragen beantwortet)
## Danach
Ansätze vorschlagen → Entwurf abschnittsweise freigeben → Spec schreiben → Plan (writing-plans) → Umsetzung subagent-driven mit Reviews, Security-Audit am Ende.
## Umsetzung 4a (Plan 2026-10-16-etappe-4a-presets.md)
- Pipeline `DesignChange` (alle Bereiche), Snapshots `SnapshotStore`, Bereich `presets`.
- Tools: lit/divi-preset-get, lit/divi-presets-set, lit/divi-presets-delete, lit/divi-design-snapshots, lit/divi-design-restore (alle indirekt).
- Schreiben über die Schrittfolge von `GlobalPresetController::sync` (Mirror `preset-sync`).
- Offen für 4b (erledigt in 4b, R11 und Task 10): Fundstellen von Standard-Gruppen-Presets (Gruppenliste je Modul), lit/divi-design-detach.
## Etappe 4b (Plan 2026-10-18-etappe-4b-zuweisen.md)
- Tools: lit-divi-insert/-update/lit-content-create (preset-Stapel, group_presets, clear_overrides), lit/divi-presets-assign, lit/divi-design-detach (beide indirekt); Outline und node-get zeigen Gruppen-Presets.
- R1 Fundstellen: keine Klasse DesignUsages; GlobalUsages::reportFor()/rowsFor() plus ModuleUsageQuery (alle Module eines Typs).
- R2 Stapel: preset = String | Liste (1–10) | null; doppelte IDs und leere Liste sind Fehler; letzter im Stapel gewinnt bei gleicher Priorität.
- R3 Gruppen-Schlüssel: Kurzname, voller Name oder Attributpfad; mehrdeutiger Name → Fehler mit den Pfaden; gleichnamige Gruppen in einem Modul sind auch in lit/divi-presets-set und im Composer (group_presets) ein Fehler, dort hilft der Attributpfad als Schlüssel; GroupKeys ersetzt die private Suche im PresetComposer.
- R4 Überdeckungen (G9): Vergleich je Blatt (Pfad, Breakpoint, Zustand), nur ausdrücklich zugewiesene Presets, Warnung nur bei abweichendem Wert; clear_overrides entfernt alle lokalen Blätter auf Preset-Blättern.
- R5 Warnungstext je Preset eine Zeile, bis zu 5 Pfade.
- R6 Outline: group_presets je Zeile und in node-get; Fingerprint unverändert.
- R7 clear_overrides beim Einfügen wirkt nach set/attrs desselben Knotens; nicht-boolesch → Fehler.
- R8 Seiten-Umschreiber: globale Instanzen nie umgeschrieben (skipped; Bibliotheks-ID nur bei sichtbaren Bibliotheks-IDs, P13), Bibliothekseintrag ist eigene Fundstelle; nicht ladbare Seiten skipped; remaining zählt nur Seiten mit Änderungen.
- P8a mit post_ids: nur die genannten Seiten können das Schreiben blockieren, HARD_CAP gilt nicht (die Rechteprüfung der Live-Seiten entfällt dann vorab, loadWritable() prüft jede Seite einzeln und meldet skipped). Eine genannte veröffentlichte, private oder geplante Seite ohne Bearbeitungsrecht verweigert den ganzen Aufruf (forbidden, blocked_ids, nichts geschrieben; der Probelauf listet sie in blocked_ids); eine genannte Seite, die nicht live ist (Entwurf usw.) und nicht bearbeitet werden darf, steht in skipped. Ohne post_ids verweigert jede blockierte Live-Fundstelle oder zu viele Fundstellen den ganzen Aufruf, nichts wird geschrieben.
- R9 Kein unfiltered_html-Zwang: der Umschreiber ändert nur Preset-Verweise und entfernt lokale Werte, Speichern über DiviSaver.
- R10 detach nur Presets: Eingabe preset = Objekt {module|group, preset}; variable → unsupported (4c); ohne post_ids zuerst verschachtelte Verweise über DesignChange (Snapshot, design_rev), danach Seiten (≤ 50); scheitert die erste Seite danach, nennt der Fehler Snapshot und diff; Standard-Presets nur ausdrückliche Verweise.
- lit/divi-presets-assign: preset: null, leerer Text und null in group_presets werden abgewiesen (Hinweis auf lit/divi-design-detach); ein leerer Probelauf ist ein normales „nichts zu tun“.
- R11 Standard-Gruppen-Presets: PresetUsageMatcher erkennt implizite Nutzung (Modul hat die Gruppe, kein ausdrücklicher Eintrag); Kandidaten fail closed über wp:divi/; der 4a-Hinweis „nicht aufgelistet“ entfällt.
- 4b-Nachtrag zu R11: Das ändert das 4a-Verhalten. lit/divi-presets-set und lit/divi-presets-delete eines Standard-GRUPPEN-Presets nehmen jetzt jede Divi-Seite mit dieser Gruppe in ihren Fundstellenbericht auf; auf Sites mit mehr als 2000 Live-Divi-Seiten wird die bestätigte Änderung daher abgewiesen (tooMany), wie bisher nur bei Standard-Modul-Presets; konsistent mit P8a.
- Grenze je Aufruf A6: 50 Seiten, remaining; Teilerfolg partial_write mit den gespeicherten Seiten.
- Offen für 4c: detach für Variablen (A7: Referenz durch den aktuellen Wert ersetzen, bei Farben entfällt detach), Wiederverwendung des PageRewriter/TreeRewrite dafür (NodeRewrite-Strategie `VariableDetachRewrite`).
- Offen für 4c/4e: Smoke divi-presets-assign-smoke.sh läuft nur mit deaktivierten Plugins postlos und asynclistry.
## Offene Kleinpunkte aus 4a (Reviews, nicht merge-relevant)
- Task 1: minor (deferred): Nachtrag verweist nicht auf §7 Punkt 4 („strenger als Divi“ gilt für Presets nicht mehr)
- Task 2: minor (deferred): PresetBook::with() erzwingt kein id-Feld
- Task 2: minor (deferred): rein numerische IDs/Namen werden int-Keys → TypeError bei new PresetScope (scopeOf/nestedUsers/toRest); (string)-Cast
- Task 2: minor (deferred): PresetScope-Regex lehnt "_" ab
- Task 3: minor (deferred): Standardwechsel X→Y meldet nur default_set Y, nicht default_cleared X
- Task 3: minor (deferred): Testabdeckung dünn (Gruppen, Standardwechsel, name-only)
- Task 4: minor (deferred): attachLog ignoriert fehlgeschlagenes update_option; RMW ohne Lock; Testlücken (Grenze MAX_BYTES, attachLog null/unbekannt)
- Task 5: minor (deferred): savePresets bereinigt bei mayRemove beide Arten (Obermenge von Divi), ACTION_DELETE_GROUP ungenutzt, Docblock erwähnt es nicht
- Task 5: minor (deferred): keine Unit-Tests für WP_Error→RuntimeException und Cleanup-Pfad (Integration in Task 14)
- Task 6: minor (deferred): UsageQuery-Vertrag „bereits vorbereitet“ nur Konvention; kein reportFor()-Test
- Task 7: minor (deferred): leere ID → LIKE %% (teuer); Testlücken _initial/[""]/Legacy-String beim Standard
- Task 8: minor (deferred): kein RED-Lauf dokumentiert (Tests und Code in einem Schritt)
- Task 8: minor (deferred): kein Test für DiviLocked aus area->write(); Read-back-Test prüft Rollback nur lose; kein Test für fehlende design_rev
- Task 9: minor (deferred): META_KEYS (modulePreset, groupPreset, globalModule, localAttrs) im Post-Check unsichtbar – heute nicht erreichbar; explizite Ablehnung als Defense in Depth
- Task 10: minor (deferred): Scope-Fehler immer unter fields.module; fehlendes preset → not_found statt validation_failed
- Task 11: minor (deferred): Test „nichts geschrieben“ prüft nicht, dass items[0] fehlerfrei ist
- Task 12: minor (deferred): in_use-Antwort ohne affected_count/hidden_count; Testlücken (verschachtelt+mitgelöscht, mehrere Items, Rechte)
- Task 13: minor (deferred): kein Paginierungs-/next_cursor-Test; Restore-Rechte nicht im Ability-Test
- Task 14: minor (deferred): Guide nennt Exklusivität name/rename und Namensgrenzen nicht ausdrücklich; Smoke hinterlässt Snapshots im SnapshotStore; Restore-Check vergleicht nicht den ganzen Bestand
- DesignLock: zwei gleichzeitige Übernahmen einer veralteten Sperre möglich (sehr kleines Fenster); Schreibvorgang > 120 s kann Sperre verlieren
- Verwaister @param-Docblock in DiviPresetsDeleteAbility
- Smoke divi-presets-smoke.sh läuft nur mit deaktivierten Plugins postlos (Monolog 2) und asynclistry (Deprecated-Ausgabe)
## Etappe 4d (Plan 2026-10-19-etappe-4d-theme-optionen-schriften.md)
Belege: `2026-10-19-etappe-4d-divi-recherche.md`; Formate-Nachtrag „Etappe 4d“. Die Rulings amendieren Spec §8 und §10.
- **W1:** Drei neue Fähigkeiten `theme-option-read`, `theme-option-write` und `font-upload`; der Mirror `customizer-typography` trägt Lesen und Schreiben, die Schriftlisten-Aufrufe tragen `theme-option-write`, `font-upload` und `variable-write`.
- **W2:** Whitelist von 20 Theme-Optionen (vier Schriften, zwei Gewichte, Größen, Zeilenhöhen, Abstände, `body_header_style`, vier Layout-Werte); Standards, Bereiche und Sanitizer sind Literale in `ThemeOptionSpec`/`ThemeOptionsAdapter`, gesichert durch den Mirror.
- **W3:** „Nicht gesetzt“ ist der Divi-Standard; `null` setzt zurück (`et_delete_option`), `'none'` und `false` werden abgelehnt; Tablet-/Phone-Größen erben, solange sie nicht gesetzt sind.
- **W4:** Das Plugin prüft Bereiche, Schritte und Auswahl selbst (Divis Sanitizer klemmen nicht); `body_header_style` nimmt genau einen Wert; Zahlen als Zahl oder Ziffern-String.
- **W5:** Schriftnamen gelten exakt (Groß-/Kleinschreibung) gegen Divis gemergte Liste; unbekannt → `validation_failed` mit bis zu fünf ähnlichen Namen; die Liste wird je Request einmal geladen.
- **W6:** Snapshot `theme_options` hält die vorhandenen Whitelist-Schlüssel mit Rohwert und Typ; Restore ersetzt die Whitelist vollständig (`et_update_option`/`et_delete_option`) und leert die Caches wie der Customizer.
- **W7:** Theme-Optionen haben keinen Seitenbezug; der Probelauf warnt „wirkt auf die gesamte Website“ (bei Schriften auch auf den Builder), `usages` bleibt `null`.
- **W8:** Der Upload installiert im Prozess über `FontsUtility::font_add()` mit `wp_handle_upload_rest`; Mirror `font-install`; Formate `woff2`, `woff`, `ttf`, `otf`; Teilerfolg wird erkannt und mit `font_remove` zurückgenommen.
- **W9:** Name 1–100 Zeichen aus `A–Z a–z 0–9`, Leerzeichen, `_`, `-`, nicht in Google-, Web-Safe- oder hochgeladenen Schriften; je Datei höchstens `min(Upload-Limit, 5 MB)`, 1–4 Dateien, Magic Bytes passend zur Endung, URL-Regeln über den geteilten `RemoteFileFetcher`.
- **W10:** Der Probelauf lädt wirklich herunter (nur Temp-Dateien) und meldet Dateien mit Größe und SHA-256; mit `confirm: true` erneut laden, prüfen, installieren; kein Snapshot.
- **W11:** Restore kennt den Bereich `theme_options` (`ThemeOptionsRestorePlanner`); Rechte wie beim Schreiben.
- **W12:** Variablen vom Typ `fonts` werden zusätzlich gegen den Schriftkatalog geprüft; `VariableValue` bleibt rein.
- **W13:** Divi-Grenzen, die dokumentiert statt behoben sind:
- `et_update_option` schreibt das ganze `et_divi`-Array aus dem Request-Global (Lost-Update-Risiko; `design_rev` und Sperre mindern es).
- `body_header_style` nimmt in Divi nur einen Wert.
- Divi prüft keine Bereiche; das Plugin tut es.
- `font_add` meldet Teilerfolg als Erfolg (Plugin vergleicht und nimmt zurück).
- `remove_static_resources` wirkt nur mit angemeldetem Benutzer (Smokes laufen mit `--user`).
- Divis Leser kennen für `tablet_*`/`phone_*` feste Standards 14 bzw. 30; bei „gleich dem Größeren“ erzeugt Divi keine CSS-Regel.
- Zwischen `RemoteFileFetcher::allows()` und dem cURL-Abruf bleibt ein DNS-Rebinding-Fenster (das „IPv4-Pinning“ ist nur `CURL_IPRESOLVE_V4`); Thema für das Security-Audit in 4e.
- Schema-Fehler (Bereich, unbekannter Schlüssel) lehnt der MCP-Adapter mit dem Eingabeschema ab, bevor die Tools laufen; sie tragen dann nicht die `validation_failed`-Feldpfade des Plugins.
- **W14:** `DesignRights::themeOptions()` verlangt `edit_theme_options` und `theme_customizer` (der Parameter `$writer` ist ungenutzt, B2), `DesignRights::fonts()` verlangt `upload_files`, `edit_theme_options` und `custom_fonts_management`; die Fehlertexte nennen das fehlende Recht und die Divi-Rolle.
**Folgepunkte (außerhalb 4d):** Schriftprüfung in Preset- und Modul-Attributen; `lit/divi-fonts-list` bzw. Schriftsuche (Vorschläge gibt es nur in Fehlern); Entfernen hochgeladener Schriften (A2); Format `eot`.
### Offene Kleinpunkte aus 4d (Reviews, nicht merge-relevant)
- Theme-Optionen: `ThemeOptionSpec` Zahlen-Regex ohne `/D` („15\n“); Werte neben dem Schritt (1.70000001) werden nicht gerundet; `rev()` von INF/NAN; `effective()` gibt gespeicherte Unwerte bei Auswahlfeldern unverändert zurück; `FontSuggest` rechnet bytebasiert.
- Update-Tool: unnötiger Schreibvorgang bei gespeichertem „16“ gegen gesetzte 16 (`ThemeOptionsArea::same` nutzen); Schriftname wird vor dem Trimmen geprüft (" Roboto"); `DesignRights::themeOptions` mit ungenutztem `$writer` samt Test-Mock; kein Hinweis zu Schriftstärken im Builder.
- Gespeichertes `false`: Tests für `get`/Planer/Restore und `design_rev`-Vergleich fehlen; ungesetzte, gespeicherte `false`-Schlüssel entfernt `write()` still.
- Schrift-Variablen: Test, dass reine Status-/Label-Änderungen den Katalog nicht laden; ein erneut gesendeter Wert einer Schrift, die Divis Liste verlassen hat, wird `validation_failed`.
- Upload: `confirm` bindet die im Probelauf gezeigte SHA-256 nicht (optionale Eingabe); der Probelauf prüft den Rohnamen, `install()` nutzt `sanitize_file_name`; `FontInstaller::install()` prüft die Endung nicht selbst gegen `FORMATS`; Meldung bei fehlendem `fileinfo` irreführend; Tests für Fetcher-Ausnahme/Aufräumen und leeres `wp_tempnam` fehlen; nur vier Magic-Byte-Signaturen; gleichzeitiger Upload desselben Namens in anderer Schreibweise.
- `FontInstaller` lässt die verschobene Datei in `et-fonts`, wenn `font_add` wirft (nicht-Array `et_uploaded_fonts`); Aufräumen mit `catch Throwable` ergänzen.
- Smoke-Skripte: fehlende `file_exists`-Prüfung in Schritt 7, Format-Grep im Probelauf vakuos (stattdessen `sha256`/`bytes` prüfen), Namenskonflikt prüft nicht auf `conflict`, `'none'` nicht isoliert, kein Zurücklesen von `body_font`, EXIT-Trap stellt ganz `et_divi` wieder her (Admin-Änderungen während des Laufs gehen verloren) und räumt Snapshots sowie Dateien fälschlich akzeptierter Schriften nicht auf, Sentinel-Prüfung vakuos, `restore()` verbirgt stderr.
- Protokoll: `src/Admin/LogObjects.php` kennt den Objekttyp `divi_font` nicht (Anzeige als Code).
- Kleinkram: Reihenfolge der `use`-Blöcke; `DiviLocked`-Rethrow setzt voraus, dass die Sperre vor jedem Schreiben greift.
## Etappe 4e (Plan 2026-10-19-etappe-4e-schleifen-guides-audit.md)
Belege: `2026-10-19-etappe-4e-recherche-schleifen.md` (rekonstruiert, siehe dort), Audit `2026-10-19-etappe-4e-security-audit.md` (Status nach der Fix-Welle). Die Rulings amendieren Spec §9.
- **X1:** Der Schleifen-Kontext ist der des nächsten Vorfahren-oder-selbst mit `loop.desktop.value.enable === "on"`; `queryType` fehlt → `post_types`; die innerste Schleife gilt.
- **X2:** Arten `post`, `term`, `user`, `menu`; die Art einer Option folgt der Divi-Gruppe, die Meta-Schlüssel-Optionen dem Namen; gespeichert wird der Array-Schlüssel.
- **X3:** `VariableRef` und `DynamicContentRules` bekommen einen `LoopScope`; Insert, Update und Move prüfen im Kontext des Ziels, Move warnt statt abzulehnen (A11); `lit-content-create` hat nie einen Kontext.
- **X4:** Kurzform `{{loop:<option>}}` über die Tabelle `LOOP` in `Placeholders`.
- **X5:** Neue Meldungen („braucht eine Divi-Schleife“, „passt nicht zur Schleife … (Typ: …)“); Ruling S36 abgelöst.
- **X6:** `lit/divi-dynamic-sources` mit `post_id` + `node` nennt die zur Schleife des Knotens passenden Quellen.
- **X7:** Grenzen statt Lösung: Schleifen über `attrs` schaltbar, globale Module ohne Kontext, Kopieren mit `unfiltered_html`, Preset-Schleifen unerkannt, ACF-Wiederholungsfelder nicht unterstützt.
- **X8:** Guides ohne Prompts: Ablauf „Corporate Design übernehmen“ in `workflows.md`, Design-Tools und Fehlercodes in `divi-design-system.md`/`overview.md`, Loop-Regeln in `divi-placeholders.md`/`divi-shortform.md`.
- **X9:** Unabhängiger Security-Audit (0 kritisch, 0 hoch, 1 mittel, 9 niedrig, 10 Info); Fix-Welle für M1, L1 (Option B), L2, L4–L8 und DNS-Rebinding.
- **X10:** Version 0.6.1 → 0.7.0, kein Tag, kein Release.
- **Zusätze aus dem Audit:** Kettenregel für `_meta_key_`-Optionen (M1), `queryType` wie `sanitize_key` mit Aliasen `terms`/`users` (L2), Rechteprüfung für Schleifen-Einstellungen (L1 B: `list_users`, `read_private_posts`), Restore berechnet Fundstellen (L4), ein Lock für Farben und Theme-Optionen, atomarer `DesignLock` (L5, L6), `CURLOPT_RESOLVE` gegen DNS-Rebinding und Abruf nur mit cURL (L8).
### Folgearbeit nach Etappe 4
Aus dem Audit (nicht behoben):
- **L3:** Globale Instanzen und Kopien (`unfiltered_html`: `lit/library-insert` im Kopiermodus, `lit/divi-node-copy`) tragen `loop_*`-Inhalt über Schleifen-Arten ohne Prüfung oder Warnung; braucht einen eigenen Plan.
- **L9:** Der Seiten-Umschreiber parst jeden Kandidaten bei jedem Probelauf und Confirm, ohne Cap für Entwürfe (Administrator-Niveau, Wirkung endet mit dem Request).
- **L8-Rest:** Bei https mit einem cURL ohne SSL kann Requests auf fsockopen ausweichen (selten, bewusst belassen).
- **I1–I10:** I1 Preset-Detach ohne Marker-Prüfung der Affixe; I2 abgelöster Variablenwert ohne Bereinigung für den Feldtyp; I3 `from_node` mit `unfiltered_html` kopiert `loop_*` ungeprüft in Presets; I4 Lese-Tools prüfen Rechte nur in `permission()`; I5 Shortcodes und Bedingungen in Term-/Benutzer-/Menü-Schleifen laufen gegen den Beitrag mit der Term-/Benutzer-ID; I6 Font-Probelauf verrät private DNS-Auflösung; I7 Font-Confirm ist nicht an die Probelauf-SHA-256 gebunden; I8 Font-Download bis ca. 120 s je Aufruf, Temp-Reste bei Abbruch; I9 Lost Update auf `et_uploaded_fonts`; I10 `numbers`-Variablen akzeptieren `url(https://…)`.
- **Gesamtreview 4e:** M-4 `DesignLock`: im Fallback `wp_cache_flush_runtime` statt `wp_cache_flush` (kein Flush des persistenten Caches); M-5 Guide-Hinweis, dass Inhalt unter einer Administrator-Benutzer-Schleife Konten-Daten zeigen kann; `ValueCleaner::listsPrivatePosts` entflechten; `DiviDynamicSourcesAbility::page()` lädt zweimal; `DesignLock::acquire` aufteilen (Sperre nehmen, Cache verwerfen).
- **Z1:** Schleifen und `loop_*`-Verweise aus Presets erkennt die Baumprüfung nicht (fail closed; auf der Website ohne Wirkung, weil Divi die Schleife aus dem rohen Dokument liest).
Übernommen aus 4d und früher:
- Schema-Fehler-Form projektweit (Adapter-Eingabeschema statt `validation_failed`-Feldpfade).
- Schriftnamen in Preset- und Modul-Attributen prüfen; `lit/divi-fonts-list`/Schriftsuche; Entfernen hochgeladener Schriften; Format `eot`.
- Rev-Helfer der Book-Klassen vereinheitlichen.
- `dryRun()`/`installed()` aus `DiviFontsUploadAbility::upload()` herauslösen.
- sha256-Pin beim Upload-`confirm` (`expected_sha256`).
- Weitere Minor-Punkte aus den Abschnitten „Offene Kleinpunkte“ (4a, 4d) und unten (4e).
### Offene Kleinpunkte aus 4e (Ledger, nicht behoben)
- `LoopScope`/`LoopContext`: `LOOP`-Tabelle mit 31 Identitätszuordnungen (Stil); Lücken bei Tests (int/bool `queryType`, Groß-/Kleinschreibung, Wurzelknoten opak, kein Test für den Standard-Konstruktor von `Placeholders`).
- `DynamicContentMapTest` prüft nur „Schleifen“ statt „braucht eine Divi-Schleife“; die Fehlanpassungsmatrix hat nicht alle Zeilen (Benutzer-Meta in Term-/Menü-Schleife, `loop_product_*` in Nicht-Beitrags-Schleifen); ein nicht unterstützter `queryType` erscheint roh im Fehlertext; `VariableRefTest` akzeptiert zwei Meldungen.
- Insert: Meldung „braucht eine Divi-Schleife“, wenn derselbe Aufruf die Schleife am Knoten setzt, dessen Elternteil keine hat; doppelter Warntext, wenn Vorfahr und Nachfahr zugleich geändert werden; Wechsel von keiner zu nicht unterstützter Schleife ohne Warnung; Cache-Schlüssel `''` für „keine“ und „nicht unterstützt“ geteilt; Wortlaut der Move-Warnung „Divi zeigt sie dort leer“ für nicht unterstützte Ziele.
- Tests: `testInLoopChecksLoopContentAgainstItsScope` und `testLoopContentBelowANewLoopContainerIsRefused` zu schwach; Obfuskations-Datenprovider prüft nur `problems !== ''`; kein Test „keine Schleifen-Warnung bei einfachem Update in der Schleife“; Testzeilen über 120 Zeichen (Tests außerhalb des Lints).
- `lit/divi-dynamic-sources`: Bibliothekseinträge und Theme-Builder-Layouts nicht erreichbar (Docblock verspricht mehr); `page()` lädt zweimal; tote Fixture-Einträge in `withLoopPage`.
- Audit-Spur: Innerhalb einer Schleife reicht Divi `loop_id`/`loop_query_type`/`loop_object` an jede Inhaltsvariable; Resolver von Nicht-Loop-Optionen, die `loop_id` nutzen, sind nicht einzeln nachverfolgt (I5 deckt Shortcodes ab).