Files
WP-LIT-MCP/docs/superpowers/notes/2026-09-27-etappe-3-entscheidungen-entwurf.md
T
janniclas.loosenandClaude Opus 5.5 7590da4d96 fix: layout-edit dry runs name only editable live templates (ruling Q22)
ThemeBuilderEdits::showing() splits the live templates that show a layout
into editable ones (named under templates) and a hidden_templates count;
confirm is still decided on all of them, so a layout shown only by
uneditable templates still needs confirm. The live split is read once per
write for the confirm check and the field warnings, and field warnings now
also survive a non-confirm refusal such as the P9 backup refusal (M-12 a).
replace-text passes hidden_templates on; the write schema lists it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-27 22:57:45 +02:00

266 lines
45 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 3 – Entscheidungen der Planung (Entwurf)
Vorbereitung von Etappe 3 („Library & Theme Builder“), 2026-09-26/27. Grundlage: Spec §3, §4.3, §5, §7, §10, §12, §15; Entscheidungen Etappe 2 (D1–D12); Rulings 2b (S1–S52), Praxistest und Security-Audit 2b. Quelle: Divi 5.2.1 (`wp-content/themes/Divi`, `style.css:4`), Pfade relativ zu `themes/Divi/includes/`. Live geprüft nur lesend (DDEV, `wp eval-file`). Entwurf – noch nicht vom Controller freigegeben. Kosten, falls falsch, jeweils am Ende.
## 1. Verifizierte Fakten
### 1.1 Divi Library (`et_pb_layout`)
- F1: Post-Typ `et_pb_layout` (`ET_BUILDER_LAYOUT_POST_TYPE`, `builder/post/type/Layout.php:4`), `capability_type post`, `map_meta_cap`, `show_ui true`, `show_in_rest false`, unterstützt `revisions` (`Layout.php:79`). Taxonomien `layout_category`, `layout_tag`, `layout_pack`, `layout_type`, `module_width`, `scope`, `layout_location` (`Layout.php:83-90`). Live: `assign_terms` = `edit_posts`, verwalten = `manage_categories`; `layout_location` ist lokal nicht registriert.
- F2: Anlegen (Divi 4-Weg, von Divi 5 weiter benutzt): `et_pb_submit_layout()` (`builder/shortcode-core.php:654-734`) setzt Terme `scope` (`global`/`not_global`), `layout_type` (`layout`/`section`/`row`/`module`), `module_width` (`regular`/`fullwidth`), Kategorien/Tags und Meta `_et_pb_row_layout` (Zeilen), `_et_pb_module_type` (Module), `_et_pb_excluded_global_options = "[]"` (globale Module), `_et_pb_built_for_post_type`, `_et_pb_template_type`; `et_pb_create_layout()` (`:738-800`) speichert immer mit `post_status publish` über `wp_insert_post` (also mit kses). Im REST-/CLI-Kontext ist `et_pb_submit_layout` nicht geladen (live: `function_exists` = false; geladen nur über `builder/shortcode-framework.php:6`).
- F3: Divi 5 legt Library-Einträge über die Builder-Route `POST divi/v1/divi-library/create-item` an (`builder-5/server/VisualBuilder/REST/RESTRegistration.php:743-749`, `DiviLibraryController::create_item` `DiviLibraryController.php:606-757`): D4-Konvertierung, Preset-/Farb-/Variablen-Import, dann `et_pb_submit_layout` (`:715`). Recht: `edit_posts` + `et_pb_is_allowed('divi_library')` + `('save_library')` (`:958-966`). Inhalt wird vorher mit `SavingUtility::prepare_content_for_db` bereinigt (`:980`; `VisualBuilder/Saving/SavingUtility.php:230-254`: ohne `unfiltered_html` wird die Seite komplett geparst und neu serialisiert). Der Versuch, danach `_et_pb_use_divi_5` zu setzen, liest `$new_layout_meta['layout_id']` aus einem JSON-String (`:715-720`) – auf dieses Flag ist bei Library-Einträgen kein Verlass.
- F4: Library-Einträge speichern ihren Inhalt in `post_content` in einem `divi/placeholder`-Wrapper (`DiviLibraryController.php:2395, 2461-2466`).
- F5: „Global“ = Term `scope=global`; Rückfall für importierte D4-Module: `_et_pb_module_type` und `_et_pb_excluded_global_options` gesetzt (`builder-5/server/Packages/GlobalLayout/GlobalLayout.php:34-61`, `GlobalLayout::is_global_layout_template()`).
- F6: Instanz auf einer Seite = Platzhalterblock `divi/global-layout` mit `{"globalModule":"<ID>","blockName":"divi/<typ>","localAttrs":{…}}` und den Kindern als `innerBlocks` (Schnappschuss). Belegt durch die D4→D5-Konvertierung (`builder-5/server/Packages/Conversion/Conversion.php:1454-1479`) und die Serialisierung des Builders (`builder-5/visual-builder/build/serialized-post.js`, Filter `divi.serializedPost.parseFlatModuleObjectIntoBlockArray.nested`: alle Attribute außer `globalModule`, `localAttrsMap`, `localChildren`, `globalParent`, `blockName`, `globalModuleTemplate` wandern nach `localAttrs`). Kinder innerhalb einer globalen Vorlage tragen `globalParent` (`Conversion.php:1491-1494`). Modul-Metadaten: `divi/global-layout` hat keine Attribute (`builder-5/server/_all_modules_metadata.php:18550-18557`).
- F7: **Ausgabe per Verweis.** Beim Rendern ersetzt `BlockParser` jeden `divi/global-layout` durch den aktuellen Inhalt des Library-Posts (`builder-5/server/FrontEnd/BlockParser/BlockParser.php:838-884`; `BlockParserStore::get_global_layout_content`, `BlockParserStore.php:1113-1330`). Aus dem Schnappschuss `localAttrs` werden nur die Attribute übernommen, die die Vorlage in `localAttrsMap` als „nicht synchronisiert“ führt (`apply_local_attrs_filtering`, `BlockParserStore.php:348`); die Kinder aus der Seite nur, wenn die Vorlage `localChildren: true` hat (`:1307-1311`). Die Seitenkopie ist also ein Schnappschuss, maßgeblich ist der Library-Post.
- F8: **Keine Expansion bei REST-POST.** `BlockParser` überspringt die Expansion bei `MigrationContext`, beim Speichern und bei jedem REST-Request mit POST/PUT/PATCH (`BlockParser.php:827-831`, `_is_rest_update_request` `:690-701`). Unsere MCP-Aufrufe sind REST-POST – `lit/page-render-summary` (D5, `the_content` im Prozess) sieht globale Module daher **nicht**.
- F9: Bearbeitet man eine globale Instanz im Visual Builder, speichert der Builder sofort den Library-Post über `POST divi/v1/divi-library/save` (`visual-builder/build/global-layouts.js`; `DiviLibraryController::save` `:1439-1447` = `wp_update_post(wp_slash(…))`, Inhalt vorher `prepare_content_for_db`). Recht: `edit_posts` + `et_pb_is_allowed('edit_global_library')` (`:1494-1500`) – **ohne** `edit_post` auf den Eintrag.
- F10: Verschachtelte globale Module sind verboten: beim Öffnen einer globalen Vorlage im Builder werden innere `divi/global-layout` „entglobalisiert“ (`VisualBuilder/SettingsData/SettingsDataCallbacks.php:739-743`, `Framework/Portability/PortabilityPost.php:3521-3605` `maybe_deglobalize_nested_global_modules`: Platzhalter → `blockName` mit `localAttrs` als Attribute, `globalParent` entfernt). Divi selbst liest `globalModule` aus Markup per String-Scan, weil `parse_blocks()` damit nicht zuverlässig umgeht (`PortabilityPost.php:3510`, `BlockParserStore.php:1137-1184`, `FrontEnd/Assets/DetectFeature.php:349-359`).
- F11: Library-REST-Routen (`RESTRegistration.php:671-775`): `divi-library` (Liste), `/item`, `/update-terms`, `/update-item`, `/convert-item`, `/split-item`, `/load`, `/create-item`, `/save`, `/upload-image`, `/item-location` (löscht trotz Namen Posts, `DiviLibraryController.php:1197-1221`). Alle nonce-geschützt, nur für den Builder.
- F12: Rechte im Divi-Rollen-Editor: `et_pb_is_allowed()` (`builder/core.php:1128-1162`); Library-Admin braucht `divi_library` + `save_library` (`builder/functions.php:340-346`), globale Einträge zusätzlich `edit_global_library` (`functions.php:366-372`; im Builder `core.php:4248-4262`). `theme_builder` und `read_dynamic_content_custom_fields` sind „gefährlich“: für andere Rollen als Administrator standardmäßig **aus** (`core.php:1138-1156`), Multisite-Super-Admins immer an. Live: keine Rollen-Einstellungen gespeichert (`et_pb_role_settings` leer) → Standardwerte.
- F13: Live-Bestand: 0 `et_pb_layout`, 0 `et_template`, 0 `et_theme_builder`, 0 TB-Layouts, kein Inhalt mit `globalModule` (Probe `s1.php`). **Echte Library-/TB-Daten fehlen vollständig.**
### 1.2 Theme Builder
- F14: Post-Typen `et_theme_builder`, `et_template`, `et_header_layout`, `et_body_layout`, `et_footer_layout` (`builder/frontend-builder/theme-builder/theme-builder.php:7-23, 89-252`), alle `capability_type post`, `show_ui false`, `show_in_rest false`; Layouts unterstützen `revisions` (`:184, 216, 248`), Vorlagen nicht (`:152`).
- F15: Live- und Entwurfsstand: `et_theme_builder_get_theme_builder_post_id($live)` sucht den TB-Post mit Status `publish` (live) bzw. `auto-draft` (Entwurf des TB-Editors), ohne Meta `_et_library_theme_builder` (`theme-builder.php:378-413`). Die Vorlagen hängen als mehrfaches Meta `_et_template` am TB-Post, mit Sicherung in der Option `et_tb_templates_backup_<id>` während des Speicherns (`:424-441`; `api.php:223-243`).
- F16: Vorlage (`et_template`) = Meta `_et_default`, `_et_enabled`, `_et_autogenerated_title`, `_et_{header,body,footer}_layout_id`, `_et_{…}_layout_enabled`, `_et_{…}_layout_global`, `_et_library_item_id`, mehrfach `_et_use_on`/`_et_exclude_from` (`theme-builder.php:471-521`). `override` = Layout-ID ≠ 0 oder Bereich deaktiviert (`:502`).
- F17: `et_theme_builder_store_template($tb_id, $template, $allow_default)` (`theme-builder.php:1463-1586`): `wp_die()` ohne `edit_others_posts` (`:1464-1466`); neue Vorlagen sofort `publish` (`:1515-1521`); Layout-IDs nur übernommen, wenn der passende Typ **und `publish`** (`:1486-1496`); Bedingungen `sanitize_text_field`, dedupliziert (`:1565-1583`). Die Funktion hängt die Vorlage **nicht** an den TB-Post – das macht nur `et_theme_builder_api_save` (`api.php:223-243`).
- F18: `et_theme_builder_insert_layout($options)` (`theme-builder.php:1643-1667`): `wp_die()` ohne `edit_others_posts`, `wp_insert_post` mit `post_status publish` (kses!), Term `layout_type=layout`, `et_builder_enable_for_post()` (setzt `_et_pb_use_builder`, `core.php:4438-4449`).
- F19: Divis TB-Speichern läuft über admin-ajax (`wp_ajax_et_theme_builder_api_save`, `api.php:160-273`: `et_builder_security_check('theme_builder', 'edit_others_posts', …)`), ersetzt die gesamte `_et_template`-Liste des TB-Posts (`:233-240`), trasht beim Live-Speichern den Entwurf und markiert unreferenzierte Vorlagen/Layouts mit `_et_theme_builder_marked_as_unused`; nach 7 Tagen werden sie gelöscht (max. 50 je Lauf, `theme-builder.php:591-678`). Danach `et_theme_builder_clear_wp_cache('all')` und `ET_Core_PageResource::remove_static_resources('all','all')` (`api.php:249-254`).
- F20: `api.php` wird nur bei `is_admin() || wp_doing_ajax()` geladen (`theme-builder.php:50-61`); im REST-Kontext fehlen die `et_theme_builder_api_*`-Funktionen. Vorhanden (live geprüft): `et_theme_builder_insert_layout`, `et_theme_builder_store_template`, `et_theme_builder_get_theme_builder_templates`, `et_theme_builder_get_template_settings_options`, `et_theme_builder_get_flat_template_settings_options`, `et_theme_builder_clear_wp_cache`. Divi 5 hat keine eigenen TB-REST-Routen (nur `ThemeBuilder/Layout.php`, `ThemeBuilder.php` für die Ausgabe).
- F21: Bedingungs-IDs mit Trenner `:` (`theme-builder.php:28`): `singular:post_type:<pt>:all` (`:703-710`), `archive:post_type:<pt>` (nur `post` oder `has_archive`, `:724-734`), `singular:post_type:<pt>:id:<id>` (`:805-813`), `singular:post_type:<pt>:children:id:<id>` (`:822-829`), `homepage` (`:717`), `search`, `404` (`:1030-1039`), Archiv-, Taxonomie-, Autor-, Datumsvarianten (`:873-987`). Jede hat einen `validate`-Callback (`template-setting-validations.php:13-325`); `et_theme_builder_get_flat_template_settings_options()` (`:1143`) liefert die flache Liste.
- F22: Die Layouts des Theme Builders werden bei der Ausgabe live gelesen (`et_theme_builder_get_template_layouts`, `theme-builder.php:1295-1406`); Änderungen an einem aktiven Layout sind sofort öffentlich. Divi-Module mit `appearance.themeBuilderLayouts` (z. B. `divi/post-content`, `_all_modules_metadata.php:25503`; `fullwidth-post-content` `:14455`) werden in anderen Bereichen nicht gerendert (`Packages/ModuleLibrary/ModuleRegistration.php:509-527`).
- F23: Divi-Recht für TB-Layouts im Builder: `et_pb_is_allowed('theme_builder')` (`functions.php:2353`) plus die WP-Rechte; TB-Admin-AJAX zusätzlich `edit_others_posts`.
### 1.3 Dynamic Content, Pods, Loop
- F24: `DynamicContentOptions::get_options($post_id, $context)` sammelt alles über den Filter `divi_module_dynamic_content_options` (`builder-5/server/Packages/Module/Layout/Components/DynamicContent/DynamicContentOptions.php:85-133`). Live für Seite 831: 57 Optionen, davon 34 `loop_*`, `post_meta_key` (Felder `before|after|select_meta_key|meta_key|date_format|custom_date_format|enable_html`) und je Post-Typ `post_link_url_<pt>` mit Feld `post_id`.
- F25: Meta-Schlüssel-Liste: „meistgenutzte“ Meta-Schlüssel der ganzen Website plus ACF-Felder – nur mit `read_dynamic_content_custom_fields` oder im Kontext `display` (`DynamicContentOptionPostMetaKey.php:80-140`). Pods kennt Divi nicht.
- F26: Ausgabe von `custom_meta_<key>`: Wert über `get_post_meta`/ACF, Filter `divi_module_dynamic_content_resolved_custom_meta_value` (`DynamicContentOptionPostMetaKey.php:404`), dann `esc_html` ohne `enable_html` (`:452-457`). Arrays von Strings werden mit „, “ verbunden; Beziehungs-/Dateifelder ohne ACF bleiben rohe IDs.
- F27: Interner Link als natives Dynamic Content: `post_link_url_<post_type>` mit `settings.post_id`, Ausgabe `esc_url(get_permalink($id))` zur Renderzeit (`DynamicContentOptionCustomPostLinkUrl.php:34, 186-217`) – entwurfssicher, nach dem Veröffentlichen automatisch die schöne URL.
- F28: Loop Builder: `module.advanced.loop.desktop.value.{enable, queryType, …}` (`Packages/Module/Options/Loop/LoopUtils.php:135-145`), Werte im Loop über `loop_*`-Optionen; die Expansion läuft nur beim Rendern (`BlockParser.php:895-905`).
### 1.4 Stand im Plugin
- F29: `ContentSchema::EXCLUDED` schließt `et_pb_layout`, `et_theme_builder`, `et_template` und die drei TB-Layout-Typen aus (`src/Integrations/WordPress/Schema/ContentSchema.php:19-24`); `PostGuard::editable` liefert dafür `not_found`. Die Divi-Werkzeuge erreichen Library und TB heute nicht.
- F30: Opak sind Blöcke mit `globalModule`, `divi/global-layout` oder nicht dekodierbaren Attributen (`src/Integrations/Divi/BlockTree/Parser.php:56`); `Block::setAttrs` wirft dort (`Block.php:91-96`).
- F31: Parser-Kosten (live, 8 Divi-Posts, 28 KB, 20 Durchläufe): Parse + NodeIndex ≈ 2 ms je 100 KB Inhalt (Probe `s3.php`).
- F32: Divi 5.2.1 registriert **keine** WordPress Abilities (`grep wp_register_ability|wp_abilities_api_init` im Theme: 0 Treffer) und enthält kein MCP.
## 2. Empfohlene Entscheidungen
- E1 – Ein Zugang für alle Divi-Posts. Die bestehenden Divi-Werkzeuge (`lit/divi-outline`, `-node-get`, `-insert`, `-update`, `-move`, `-delete`, `-duplicate-node`) akzeptieren zusätzlich IDs von `et_pb_layout` und `et_header/body/footer_layout` (Spec §5.1). Umsetzung: eine `DiviTarget`-Richtlinie neben `PostGuard` (Typ, Recht, Status-Regel je Typ), die `DiviPageLoader` verwendet; `ContentSchema::EXCLUDED` bleibt (die Content-Tools sehen diese Typen weiter nicht). – Grund: keine zweite Werkzeugfamilie, gleiche Pipeline (D7, S39). – Kosten, falls falsch: Rechteprüfung pro Typ an einer Stelle falsch → Zugriff auf Library/TB ohne Divi-Recht; abgefangen durch eigene Tests je Typ.
- E2 – Globale Module werden im Library-Post bearbeitet, nie in der Seitenkopie. Auf Seiten bleiben `divi/global-layout`-Knoten opak (D3); `lit/divi-node-get`/`-outline` liefern für sie `global_id`, `block_name` und einen Hinweis „mit post_id=<global_id> bearbeiten“ (S19-Teil 2). Schreiben in den Library-Post wirkt auf alle Instanzen (F7); die Antwort nennt die betroffenen Seiten (Verwendungssuche über `"globalModule":"<id>"` in `post_content`, wie Divi F10) und verlangt bei mehr als einer veröffentlichten Instanz `confirm: true`. `localAttrs`/Kinder-Schnappschüsse in Seiten werden nie angefasst; das Bearbeiten nicht synchronisierter Attribute einer einzelnen Instanz ist nicht Teil von Etappe 3. – Grund: Divis Ausgabe ist per Verweis (F7), der Builder macht es genauso (F9). – Kosten: Instanz-spezifische Abweichungen nur im Builder.
- E3 – Rechte (strenger als Divi, nie lockerer). Library lesen: `edit_posts` + `divi_library`; speichern/anlegen: + `save_library`; einfügen aus der Library: + `add_library`; globalen Eintrag bearbeiten: + `edit_global_library` **und** `edit_post` auf den Eintrag (Divi prüft das nicht, F9). Theme Builder lesen und schreiben: `edit_others_posts` + `et_pb_is_allowed('theme_builder')` (F19, F23; Spec §4.5 um die Divi-Rolle ergänzt). Dynamic-Content-Meta-Liste nur mit `read_dynamic_content_custom_fields` (F25). Weil `et_theme_builder_store_template`/`insert_layout` bei fehlendem Recht `wp_die()` aufrufen (F17, F18), prüft das Plugin vorher selbst. `et_pb_is_allowed` steckt in einem Adapter (E15). – Kosten: ein Redakteur mit WP-Rechten, aber ohne Divi-Rolle, bekommt `forbidden` (gewollt, wie im Builder).
- E4 – Library-Einträge legt das Plugin selbst an (kein `et_pb_submit_layout`, keine Builder-Route): `wp_insert_post` mit leerem Inhalt und Status `publish` (Divi kennt keine Library-Entwürfe, F2), Terme/Meta wie F2, danach Inhalt über `DiviSaver::saveNew` im kses-Fenster (D7, D11-Nachtrag), eingewickelt in `divi/placeholder` (F4), Flags wie `DiviFlags`. Ist ein Schritt fehlerhaft, wird der Entwurf endgültig gelöscht (wie `lit/content-create`). – Grund: `et_pb_submit_layout` ist im REST-Kontext nicht geladen und speichert mit kses (F2); die Builder-Route braucht Nonce und importiert Presets/Farben (F3). – Kosten: Divi ändert Terme/Meta → Einträge fehlen in der Library-Ansicht; abgesichert durch Fixture-Vergleich mit echten Builder-Einträgen (E13) und Kompatibilitätsprüfung (E15).
- E5 – `lit/library-insert` als Kopie oder globale Instanz. Kopie: Inhalt ohne `divi/placeholder`, verschachtelte globale Platzhalter entglobalisiert wie Divi (F10), eingefügt wie `lit/divi-insert` (StructureRules, AdminLabel). Globale Instanz: das Plugin erzeugt `<!-- wp:divi/global-layout {"globalModule":"<id>","blockName":"<Wurzeltyp>"} --><!-- /wp:divi/global-layout -->` ohne `localAttrs` (voll synchronisiert). Prüfungen: Eintrag existiert, `publish`, `scope=global`, `read_post`, Wurzeltyp passt an die Zielposition, kein globales Modul in einem globalen Library-Post (F10). – Kosten: falls der Builder leere `localAttrs`/fehlende `innerBlocks` anders erwartet, sieht der Mensch im Builder evtl. eine andere Darstellung – vor dem Bau mit einer Builder-Fixture verifizieren (E13).
- E6 – Werte aus gespeicherten Inhalten (Library → Seite, Seite → Seite, Seite → Library) gelten als „gespeichert, nicht vom Client“ wie bei `lit/divi-duplicate-node`, werden aber beim Wechsel des Posts erneut geprüft: für Benutzer ohne `unfiltered_html` werden Knoten mit Builder-only-Feldern (S7/S26: `*.decoration.attributes`, `*.advanced.html`, `*.css.*`) und Code-Modul abgelehnt („nur im Divi-Builder“). Alles andere geht unverändert durch den BlockTree (byte-identisch). – Grund: Audit M1 – der Weg über eine andere Post-ID darf die Builder-only-Sperre nicht umgehen; Autor und damit Divis `before/after`-Filterung ändern sich mit dem Ziel-Post (R2-Befund 1). – Kosten: Redakteure können admin-gebaute Abschnitte mit eigenem CSS nicht kopieren.
- E7 – Theme Builder: Vorlagen und Layouts über Divis vorhandene Funktionen (`et_theme_builder_insert_layout` mit leerem Inhalt, Inhalt danach über `DiviSaver::saveNew`; `et_theme_builder_store_template`), Zuordnung zum Live-TB-Post selbst (`add_post_meta($live_id, '_et_template', …)`, gleiche Reihenfolge wie F19 inkl. Backup-Option), danach Cache wie F19. Nie der Entwurfs-TB-Post, nie `et_theme_builder_trash_draft_and_unused_posts()`. Existiert ein TB-Entwurf (`auto-draft`, F15), wird abgelehnt mit Hinweis „Theme Builder ist in Bearbeitung – dort speichern oder verwerfen“ (sonst überschreibt ein späteres Speichern im TB-Editor die Zuordnung und die neue Vorlage wird nach 7 Tagen gelöscht, F19). Bedingungen nur aus `et_theme_builder_get_flat_template_settings_options()` plus deren `validate`-Callback, Post-IDs mit `read_post`. – Kosten: Divi ändert die TB-Speicherung → Vorlage fehlt im TB-Editor; Kompatibilitätsprüfung + E2E.
- E8 – Neue TB-Vorlagen werden **deaktiviert** angelegt (`_et_enabled=0`); aktivieren ist eine explizite Aktion in `lit/tb-assign` (`enabled: true`, `confirm: true`, Liste der betroffenen Bedingungen). Standardvorlage (`_et_default`) nie über MCP. – Grund: Spec §2 Prinzip 5 (Status bleibt, Veröffentlichen explizit); TB-Posts kennen keinen Entwurf (F17, F18, F22). – Kosten: ein Aufruf mehr. Offen als Frage F-1.
- E9 – Werkzeugliste Etappe 3 und Exposition. Direkt: nur `lit/library-insert` (Spec §4.3) → 22 direkte Tools, unter der Warnschwelle 25 des Tools-Tabs. Indirekt: `lit/library-list` (mit `id` → Detail; ersetzt `lit/library-get`), `lit/library-save`, `lit/divi-node-copy`, `lit/library-make-global`, `lit/library-find-similar`, `lit/divi-dynamic-sources`, `lit/tb-list`, `lit/tb-create-template`, `lit/tb-assign`. Gruppen: Library und Theme Builder als eigene Gruppen („nur lesen“ wirkt wie bei Divi, Audit M2: `lit/library-insert` und `lit/divi-node-copy` folgen zusätzlich dem Modus von `lit/divi-insert`). Destruktiv-Hinweis: `lit/library-make-global` (ersetzt Knoten), `lit/tb-assign` (öffentliche Wirkung). – Kosten: `lit/library-get` aus der Spec entfällt als eigener Name (Spec §5.7 anpassen).
- E10 – `lit/library-make-global`: legt einen globalen Eintrag aus dem Knoten an (E4) und ersetzt den Knoten durch eine Instanz (E5), in einem Aufruf, alles oder nichts (Library-Post wird bei Fehlschlag der Seite gelöscht). „Gleichartige Vorkommen ersetzen“ nur für **inhaltsgleiche** Knoten (Inhalts-Fingerabdruck, E11), mit Probelauf (Standard) und `confirm: true` je Seite mit `tree_rev` (Muster D10). Strukturgleiche Knoten mit anderen Texten werden nur gemeldet, nie ersetzt (Spec §3.3 „fast gleich“ = später). – Kosten: Kunde muss ähnliche Abschnitte vorher angleichen.
- E11 – `lit/library-find-similar`: zwei Fingerabdrücke je Abschnitt/Zeile: Struktur (Blocknamen und Tiefe, Spalten-`flexType`, Preset-IDs) und Inhalt (kanonische Attribute ohne Admin-Label, Bytes der Kinder). Suche seitenweise mit `cursor`, Post-Typ-Filter, höchstens 200 Posts je Aufruf, nur Posts, die der Benutzer bearbeiten darf; kein Index/Meta (F31: ≈1 s Parse für 1000 Seiten à 50 KB, Datenbanklast dominiert). Die Schreibwarnung „ähnliche Abschnitte“ (Spec §5.5, D6) vergleicht nur mit Library-Einträgen (klein) und verweist sonst auf das Tool. – Kosten: auf sehr großen Websites mehrere Aufrufe; bei Bedarf später ein Cache nach `tree_rev`.
- E12 – Dynamic Content. `lit/divi-dynamic-sources(post_type)` liefert `DynamicContentOptions::get_options` für einen Beispiel-Post des Typs (sonst `0`), ohne `loop_*` (S36), dazu Pods-Felder des Typs aus `PodsFieldSource` mit Speicherart und Eignung (Text-ähnlich = `{{field:<name>}}`; Beziehung/Datei = „zeigt IDs“, F26). Beim Schreiben in einen TB-Body-Layout gilt als Kontext-Post-Typ der Typ aus den Bedingungen der zugeordneten Vorlage (`singular:post_type:<pt>:…`); ohne Zuordnung Warnung. Loop Builder bleibt lesbar/opak (Werte unverändert), wird nicht erzeugt. – Kosten: CPT-Archive mit Pods-Feldern in Schleifen erst später.
- E13 – Übernahmen aus Etappe 2: (a) `{{link:<post_id>}}` → `post_link_url_<post_type>` mit `settings.post_id` (F27), Prüfung `read_post`, kein Papierkorb; nur in URL-Feldern; (b) `lit/guide` mit `topic: "all"` (alle Themen in einem Aufruf, Größenlimit); (c) S19: `lit/divi-design-system` mit Filter (`section`: colors/variables/presets/fonts, `module` für Presets) und `global_id` in `node-get`; (d) `lit/page-render-summary` meldet globale Platzhalter als Hinweis „globales Modul <id> – im Auszug nicht enthalten“ (F8) und erhält für Library-/TB-IDs keinen Zugang. – Kosten: gering.
- E14 – Reihenfolge und Aufteilung. **3a „Library“**: Fixtures (E16) → Übernahmen E13 → `DiviTarget`/Rechte (E1, E3) → `lit/library-list` → Divi-Werkzeuge auf Library-Posts inkl. globaler (E2) → `lit/library-save` (E4) → `lit/library-insert` (E5) → `lit/divi-node-copy` (E6) → `lit/divi-dynamic-sources` (E12) → Guides/Prompt `globale-module-vorschlagen`. **3b „Theme Builder & Konsolidierung“**: `lit/tb-list` → TB-Layouts in den Divi-Werkzeugen (inkl. `themeBuilderLayouts`-Regel F22 in StructureRules) → `lit/tb-create-template` (E7, E8) → `lit/tb-assign` → `lit/library-find-similar` + Warnung (E11) → `lit/library-make-global` (E10) → Guide `theme-builder`, Prompt `custom-post-type-vorlage`. Abschluss jeder Teiletappe wie 2b (Review je Task, Security-Audit, Praxistest). – Kosten: find-similar/make-global kommen spät; sie hängen an stabilen Library-Grundlagen.
- E15 – Divi-Zugriffe über Adapter mit Live-Kompatibilitätsprüfung (siehe Abschnitt 5): je Divi-Bereich eine Klasse (`DiviModules` und `DesignSystemReader` gibt es schon; neu `DiviLibraryStore`, `DiviThemeBuilder`, `DiviRoles`), die Funktions-/Klassen-Existenz und Signaturen prüft und bei Abweichung `unsupported` statt Fatal liefert; `lit/site-status` zeigt den Befund. – Kosten: etwas mehr Code; spart Ausfälle nach Divi-Updates.
- E16 – Teststrategie. (1) Unit: Platzhalter-Erkennung, Fingerabdrücke, Bedingungs-Validierung, Instanz-Markup, Entglobalisieren gegen Divis `maybe_deglobalize_nested_global_modules` (gleiche Eingaben, gleiches Ergebnis). (2) Fixtures aus echten Builder-Daten: Jan-Niclas baut vor Beginn in DDEV im Visual Builder nach Checkliste globales Modul, globale Zeile mit einem nicht synchronisierten Attribut, globaler Abschnitt, normaler Library-Abschnitt, je eine Seite mit Instanzen, eine TB-Vorlage „Person“ mit Header/Body/Footer und Bedingungen; ein neues Lese-Skript `tests/Integration/divi-export-library.php` exportiert `post_content`, Terme, Meta und TB-Meta als `builder-library-*.html` / `builder-tb-*.json` (wie D12). Roundtrip-Test läuft automatisch mit. (3) DDEV-Smoke `divi-library-smoke.sh` / `tb-smoke.sh` (mit Aufräumen). (4) Manuelle Abnahme: vom Plugin erzeugte Einträge/Instanzen/Vorlagen im Builder und TB-Editor öffnen und speichern. – Kosten: ohne Fixtures (F13) bleiben E4/E5/E7 Vermutung; daher Fixtures als erster Schritt und Bedingung für den Bau.
- E17 – Sicherheit neuer Schreibwege: jeder Weg nach `post_content` von Library- und TB-Posts geht durch Composer/AttrWriter/TreeEditor → `DiviSaver` (D7, S39); Titel, Terme und Meta über WordPress mit üblicher Filterung (`sanitize_text_field`, `wp_set_object_terms` mit bestehenden Term-IDs; neue Terme nur mit `manage_categories`); keine Builder-Route und kein `et_pb_create_layout` mit Client-Inhalt; TB-Bedingungen nur aus Divis Liste; Revision vor jeder Änderung (Library und TB-Layouts unterstützen Revisionen, F1/F14), Protokolleintrag je Post; nach Library-/TB-Änderungen `remove_static_resources('all','all',true)` und `et_theme_builder_clear_wp_cache('all')` (Spec §5.6 Schritt 5). – Kosten: Review-Pflicht wie D7.
## 3. Fragen an den Benutzer (nach Wichtigkeit)
1. **Sollen neue Theme-Builder-Vorlagen sofort wirken?** Divi kennt für Vorlagen keinen Entwurf.
- (a) Deaktiviert anlegen, Aktivieren nur ausdrücklich mit `lit/tb-assign` und Bestätigung. **Empfehlung.**
- (b) Sofort aktiv, wie im TB-Editor.
- (c) Aktiv nur auf Bedingungen für einzelne IDs, alles Breitere (ganzer Post-Typ, Archiv) deaktiviert.
2. **Wie frei darf der KI-Assistent globale Module ändern?** Jede Änderung wirkt sofort auf allen Seiten, auch veröffentlichten.
- (a) Erlaubt mit Rechten wie im Builder, `confirm: true` und Liste der betroffenen Seiten. **Empfehlung.**
- (b) Nur Administratoren (`unfiltered_html`/`manage_options`).
- (c) Globale Einträge nur lesen, Änderungen nur im Builder.
3. **Was zuerst: Library oder Theme Builder?**
- (a) 3a Library (globale Module, Einfügen, Kopieren), dann 3b Theme Builder. **Empfehlung** (technisch sauberere Grundlage).
- (b) Theme Builder zuerst, weil ein Kunde CPT-Vorlagen braucht (Library-Teil danach).
4. **Pods-Beziehungs- und Dateifelder im Theme Builder:** Divi zeigt davon nur IDs.
- (a) In Etappe 3 nur Text-ähnliche Pods-Felder anbieten, andere als „nicht geeignet“ melden. **Empfehlung.**
- (b) Das Plugin formatiert Pods-Werte beim Rendern (Filter `divi_module_dynamic_content_resolved_custom_meta_value`) – wirkt dann auf der Website, auch ohne MCP.
- (c) Ganz auf später verschieben.
5. **Woher kommen die echten Fixtures?**
- (a) Du baust sie lokal in DDEV nach Checkliste, ein Skript exportiert sie. **Empfehlung.**
- (b) Export von einer Kundenseite (anonymisiert).
- (c) Nur aus Divi-Code abgeleitet (riskant, F13).
## 4. Risiken
| Risiko | Gegenmaßnahme |
|---|---|
| Keine echten Library-/TB-Daten lokal (F13); Instanz-Format (`localAttrs`, `innerBlocks`, `globalModule` an der Vorlagenwurzel) nur aus Code/minifiziertem JS abgeleitet | Fixtures zuerst (E16, Frage 5), Builder-Abnahme |
| Späteres Speichern im TB-Editor überschreibt unsere `_et_template`-Zuordnung, Vorlage wird nach 7 Tagen gelöscht (F19) | Ablehnung bei TB-Entwurf (E7), `lit/tb-list` zeigt unzugeordnete Vorlagen |
| Globale Änderung wirkt sofort auf veröffentlichte Seiten | `confirm`, Seitenliste, Revision des Library-Posts (E2, Frage 2) |
| `page-render-summary` sieht globale Module nicht (F8) | Hinweis im Auszug (E13d) |
| `et_theme_builder_*` rufen `wp_die()` statt Fehler zu liefern (F17, F18) | Vorabprüfung der Rechte, Adapter (E3, E15) |
| Divi-Interna ändern sich (Library-Meta, TB-Meta, Instanz-Format); ET kündigt ein offizielles MCP an | Adapter + Kompatibilitätsprüfung (E15), Fixture-Tests je Divi-Update |
| Builder-only-Werte gelangen über Kopie in fremde Posts (Audit M1-Muster) | E6 |
| Pods mit Tabellen-Speicherung: `get_post_meta` findet nichts | `lit/divi-dynamic-sources` meldet solche Felder als ungeeignet (E12) |
| Rollen-Editor: „gefährliche“ Divi-Rechte standardmäßig nur für Administratoren | E3, klare `forbidden`-Meldung mit Hinweis auf Divi → Rollen-Editor |
| Große Websites: find-similar lädt viel `post_content` | Cursor, 200 Posts je Aufruf (E11) |
| Loop Builder / Archive nicht abgedeckt | bewusst später (E12) |
## 5. Schnittstellen-Einordnung
Stufen: (a) öffentliche/dokumentierte API (WordPress-Kern, dokumentierte Divi-Hooks), (b) Divis Builder-REST/AJAX (`divi/v1`, admin-ajax, nonce-geschützt), (c) interne PHP-Klassen/Funktionen/Meta-Konventionen, (d) selbst nachgebildete interne Formate. Empfehlung: **B** = behalten, **W** = wechseln auf offizielleren Weg, **A** = in Adapter mit Live-Kompatibilitätsprüfung (E15).
Zur Offizialität: Elegant Themes dokumentiert Hooks und die Modul-API (dev.elegantthemes.com, u. a. `divi_module_dynamic_content_options`, `divi_module_dynamic_content_resolved_custom_meta_value`, `divi_module_dynamic_content_custom_meta_options`, `divi_module_library_register_module_attrs`), aber keine API für externen oder KI-Zugriff. Der Divi AI Agent läuft im Builder (im Browser, AI-Wallet-Abrechnung); in den Kommentaren zur Ankündigung schreibt ET, ein „official MCP is on the way“. Divi registriert keine WordPress Abilities (F32).
### 5.1 Bereits genutzt (Etappe 2a/2b)
| Schnittstelle | Stelle im Plugin | Stufe | Risiko bei Divi-Updates | Empfehlung |
|---|---|---|---|---|
| WordPress-Kern: `wp_update_post`, Revisionen, kses an/aus, Meta-API | `DiviSaver`, `RevisionPoint` | a | gering | B |
| Block-Delimiter-Format (`<!-- wp:… -->`, WP_Block_Parser-Muster) | `BlockTree/Parser` | a (WP-Format) | gering | B |
| Divi-Blocknamen und Attribut-JSON (`module.decoration…`, `flexType`, `divi/placeholder`) | `Composer`, `AttrWriter`, `ColumnWidths` | d (Schema aus c) | mittel | B – Pfade werden gegen den Live-Katalog validiert; Builder-Fixtures |
| `ModuleRegistration::get_all_core_modules_metadata()`, `get_default_attrs()` | `Schema/DiviModules` | c | mittel | A (Adapter existiert; Prüfung in `lit/site-status` ergänzen) |
| `GlobalData::get_global_colors/-variables`, `GlobalPreset::get_data`, Option `et_divi` | `Design/DesignSystemReader`, `MediaUsageFinder` | c (REST-Pendant `divi/v1/global-data/*` = b) | mittel | A; kein Wechsel auf b (nonce, Builder-Kontext) |
| `DynamicContentOptions::get_options()` | `Pages/DynamicContentMap` | c; Füllweg über dokumentierten Filter `divi_module_dynamic_content_options` (a); REST `divi/v1/dynamic-content/options` (b) | mittel | A (Rückfall auf Standard-Map existiert) |
| `$variable(...)$`-Syntax, gcid/gvid, Dynamic-Content-JSON | `Compose/VariableRef`, `Placeholders` | d | mittel | B + Prüfung gegen Divis Regex `ET_THEME_BUILDER_DYNAMIC_CONTENT_REGEX_D5` (`theme-builder.php:36`) im Kompatibilitätstest |
| Meta `_et_pb_use_divi_5`, `_et_pb_use_builder`, `_et_pb_show_page_creation` | `Pages/DiviFlags`, `PostPresenter` | d (nachgebildet aus `SyncToServerController`) | mittel | B; `_et_pb_use_builder` könnte über `et_builder_enable_for_post()` (c) laufen – kein Gewinn |
| `ET_Core_PageResource::remove_static_resources()` | `DiviSaver` | c (dokumentierter Hook `et_core_page_resource_post_id_before_clear` deutet auf stabile API) | gering | B |
| `et_builder_enabled_for_post_type()` | `ContentSchema` | c | gering | B |
| `ET_BUILDER_PRODUCT_VERSION` | `DiviModules` | c | gering | B |
| Divis `wp_insert_post_data`-Filter (AttributeSecurity, HtmlSecurity) als Verteidigung in der Tiefe | Verhalten, kein Aufruf | c | mittel | B (nie entfernen, S7) |
### 5.2 Geplant (Etappe 3)
| Schnittstelle | Zweck | Stufe | Risiko | Empfehlung |
|---|---|---|---|---|
| Post-Typen, Taxonomien, Meta der Library (F1, F2) | Liste, Anlegen (E4) | c/d | mittel | A (`DiviLibraryStore`), Vergleich mit Builder-Fixtures |
| `GlobalLayout::is_global_layout_template()` | „global?“ | c | gering | B (Divis eigene Definition statt Term-Logik) |
| `divi/global-layout`-Platzhalter (`globalModule`, `blockName`, `localAttrs`) | Instanzen (E5, E10) | d | hoch | A + Fixtures; nur minimale Form ohne `localAttrs` erzeugen |
| Entglobalisieren wie `PortabilityPost::maybe_deglobalize_nested_global_modules` | Kopie einfügen (E5) | c | mittel | Eigene Umsetzung (d), per Test gegen Divis Methode geprüft (Aufruf direkt wäre möglich, arbeitet aber auf Strings ohne unsere Byte-Garantie) |
| Builder-Routen `divi/v1/divi-library/*` (F11) | – | b | mittel | nicht nutzen: Nonce, fehlende Pro-Eintrag-Rechte (F9), `create-item` hängt an nicht geladenem Code (F2, F3) |
| `et_pb_is_allowed()` | Divi-Rollen (E3) | c | gering | A (`DiviRoles`) |
| `et_theme_builder_insert_layout`, `et_theme_builder_store_template` | TB schreiben (E7) | c (in Spec genannt) | mittel | B über Adapter `DiviThemeBuilder`, Rechte vorab (wp_die) |
| `et_theme_builder_get_theme_builder_post_id`, `…_get_theme_builder_templates`, `…_get_template` | TB lesen | c | gering | B/A |
| `_et_template`-Zuordnung + Backup-Option (F15, F19) | Vorlage aktivieren | d (admin-ajax-Weg `et_theme_builder_api_save` = b, im REST-Kontext nicht geladen) | mittel | A; bei Divi-Update prüfen, ob `et_theme_builder_api_save` die Logik ändert |
| `et_theme_builder_get_flat_template_settings_options()` + `validate`-Callbacks | Bedingungen (E7) | c | gering | B |
| `et_theme_builder_clear_wp_cache`, `remove_static_resources('all','all',true)` | Cache | c | gering | B |
| `DynamicContentOptions::get_options()` je Post-Typ | `lit/divi-dynamic-sources` | c (Filter a) | mittel | A (vorhandener `DynamicContentMap`) |
| `post_link_url_<pt>` + `post_id` | `{{link:ID}}` (E13a) | d (Format), Registry c | gering | B – Name über die Registry prüfen, nicht fest verdrahten |
| `divi_module_dynamic_content_resolved_custom_meta_value` | nur falls Frage 4 = (b) | a | gering | B (dokumentiert) |
| `appearance.themeBuilderLayouts` aus den Modul-Metadaten | StructureRules für TB-Bereiche | c | gering | B (aus Live-Katalog) |
| Loop Builder (`module.advanced.loop`) | – | d | hoch | nicht erzeugen (E12) |
| WordPress Abilities von Divi | – | – | – | gibt es nicht (F32); beobachten, ob ETs angekündigtes MCP eine offizielle Schnittstelle bringt, die c/d-Teile ersetzen kann |
Quellen der Web-Recherche: [Divi AI Agent – Ankündigung](https://www.elegantthemes.com/blog/theme-releases/ai-agent), [Divi Dev Docs – Filter](https://dev.elegantthemes.com/docs/hooks/php/filters/), [Divi Developers](https://www.elegantthemes.com/developers/).
## 5. Antworten des Benutzers (2026-09-27)
- F1 Theme-Builder-Vorlagen: standardmäßig deaktiviert angelegt; Aktivieren über ein eigenes Tool (explizit).
- F2 Globale Module bearbeiten: erlaubt mit Trockenlauf (Liste der betroffenen Seiten) und `confirm`, vorher Revision/Sicherung des Bibliothekseintrags.
- F3 Reihenfolge: erst 3a Bibliothek, dann 3b Theme Builder.
- F4 Pods-Beziehungs-/Dateifelder: als dynamische Quelle nur textartige Felder anbieten; andere mit Hinweis auslassen.
- F5 Testdaten: selbst erzeugen („Probier es selbst“) – über Divis eigene Speicherwege (Builder-REST-Controller/Services im Prozess), als „[Fixture] …“ markiert, danach als Fixtures ins Plugin exportiert. Pods ist lokal deaktiviert; Aktivierung für 3b beim Benutzer anfragen.
## 6. Umsetzung Etappe 3a (Plan 2026-09-27-etappe-3a-bibliothek.md)
- E5 präzisiert durch die Builder-Fixtures: Instanzen werden wie im Visual Builder erzeugt – `localAttrs` =
vollständiger Schnappschuss der Wurzel-Attribute (oberste Schlüssel sortiert), Kinder mit `globalParent`,
ohne Kinder selbstschließend – statt der minimalen Form ohne `localAttrs`. Byte-Test gegen
`page--globale-instanzen.html`.
- Neuer Befund 9: Bibliothekseinträge aus Divis Dialog haben `\r\n` zwischen Blöcken (Formular-Upload);
`LibraryContent::load()` vereinheitlicht das vor jeder Übernahme (undurchsichtige Blöcke bleiben unberührt).
- Kopie eines normalen Eintrags mit globalen Instanzen: Instanzen bleiben verknüpft (wie Divis `/load`);
entglobalisiert wird nur der Inhalt globaler Einträge (Divis Schadensbegrenzung, F10). `lit/library-save`
mit `scope: "normal"` übernimmt enthaltene Instanzen ebenfalls als Verweise. `mode: "global"` nur für
globale Einträge mit genau einer Wurzel ohne globale Module (P11: nie verschachtelte Instanzen).
- E6 erweitert: `StoredNodeGuard` prüft neben den S7/S26-Pfaden und dem Code-Modul auch fremde Blöcke,
Freitext, nicht lesbare Blöcke, kses-instabilen Rich-Text (P3) und `$variable`-Verweise (Ablehnung, wenn
`canonical()` sie für das Ziel ablehnt oder ihre Einstellungen verändern würde) – Grund: R2-Befund 1
(before/after nach dem Autor des Ziel-Posts). Lesbare globale Instanzen werden geprüft (localAttrs und
Schnappschuss), nicht abgelehnt (P15). Strenger als Divi; sichtbare Ablehnung. URL-Werte: `url($v) ===
trim($v)` oder relativer Link ohne Schema (P17/P18) – `kontakt/`, `/pfad`, `#anker` bleiben kopierbar.
- Builder-only erweitert (S7-Gate, gilt auch für Seiten): `decoration.interactions` (P16 – der Effekt
„Attribut“ setzt im Browser beliebige Attribute, z. B. `onclick`; „Cookie“ setzt Cookies) und
`lottie.innerContent` (P19 – `src` lädt fremdes JSON, lottie-web wertet Ausdrücke aus). Offen für
Etappe 4: Divi-Presets können beides tragen; Preset-Schreibwege brauchen dasselbe Gate.
- E3 ergänzt: neue Einträge zusätzlich nur mit `publish_posts` (Bibliothekseinträge sind immer `publish`);
`scope: "global"` braucht zusätzlich `edit_global_library` (P7). Ohne das Divi-Recht „Divi-Bibliothek“
(oder mit deaktivierter Gruppe, P9) ist jede Bibliotheks-ID `not_found`, byte-gleich mit einer fehlenden ID
(P12); ohne `edit_post` auf dem Eintrag `forbidden` ohne Titel. `lit/library-list` listet nur Einträge mit
`edit_post` und liest höchstens 500 Zeilen je Aufruf.
- E9 umgesetzt mit: `lit/divi-node-copy` und `lit/divi-dynamic-sources` in der Gruppe „Divi“; die Gruppe
„Divi-Bibliothek“ sperrt als Ganzes („nur lesen“/deaktiviert) auch Schreibzugriffe der Divi-Werkzeuge auf
Bibliothekseinträge (`ExposurePolicy::groupWritable`). `lit-library-insert` und `lit/divi-node-copy` folgen
zusätzlich dem Zustand von `lit/divi-insert` (M2). P13 in 3a nur dokumentiert (Guide „Rechte“, Grenze): bei
deaktivierter Gruppe oder ohne `divi_library` nennen Outline/node-get/Render-Auszug einer Seite noch die ID
globaler Module; der Eintrag selbst bleibt `not_found`. Vollständiges Ausblenden folgt in 3b.
- Antwort F2: jede Änderung eines globalen Eintrags ist zuerst ein Probelauf (auch ohne veröffentlichte
Instanzen); `lit-divi-delete` nutzt dasselbe `confirm`, sein `confirm_required` nennt dann ebenfalls die
betroffenen Seiten. P8a: die Bestätigung wird abgelehnt, wenn der Benutzer einen Eintrag mit Status
`publish`, `private` oder `future`, der den Eintrag zeigt, nicht bearbeiten darf (auch TB-Layouts und andere
Bibliothekseinträge); Entwürfe blockieren nicht. Der Probelauf nennt solche IDs vorab in `blocked_ids`
(P14). Revisionen aus: abgelehnt ohne `unfiltered_html` (die Sicherung liefe durch kses) oder bei
deaktiviertem Papierkorb (P9); scheitert das Speichern nach einer Sicherung, nennt der Fehler `backup_id`
(P14). Cache-Leerung für alle Seiten und den Theme Builder.
- P1: `lit/divi-replace-text` speichert normale Bibliothekseinträge über `DiviWriter::commit()`; globale nie
(die Ziele tragen kein `confirm`) – das Ergebnis nennt die betroffenen Seiten und verweist auf
`lit-divi-update` mit `confirm`.
- Bibliothekseinträge bekommen keine Divi-Flags (Befund 4); `_et_pb_row_layout` wird nicht geschrieben (Befund 5).
- E13a: `{{link:<ID>}}` nur in URL-Feldern; Ziel muss existieren, `read_post` bestehen, nicht Papierkorb,
`auto-draft` oder `inherit` sein – Anhänge sind damit ausgeschlossen (P10). Entwürfe und private Ziele
rendert Divi für Besucher als `?page_id=<ID>` (nur die ID wird sichtbar, Preflight).
- F4/E12: `lit/divi-dynamic-sources` listet nur textartige Felder; Beziehungs-/Dateifelder („nur IDs“),
Tabellen-Speicher und unbekannte Feldtypen (P9) stehen unter `omitted`; Auswahlfelder zeigen den
gespeicherten Wert, nicht die Beschriftung. Der Beispiel-Post ist nur einer, den der Benutzer bearbeiten
darf.
- E15: Adapter `DiviRoles`, `DiviLibraryStore`, Prüfung `DiviCompatibility` in `lit-site-status`
(`problems` je Komponente).
- E13d umgesetzt als Hinweis in `notes`; eine eigene Expansion globaler Module im Auszug unterbleibt.
- Tests: die Unit-Suite läuft mit `memory_limit` 512M (`phpunit.xml.dist` und `composer test`), weil der
Divi-Katalog beim Standard von 128M nicht mehr in den Speicher passt.
- Server-Instructions: Zeile „Divi-Bibliothek: …“ nur bei aktiver, beschreibbarer Gruppe; MCP-Prompts gibt es
noch nicht, der Ablauf „globale Module vorschlagen“ steht im Guide `divi-library` (E14).
## 7. Umsetzung Etappe 3b (Plan 2026-09-27-etappe-3b-theme-builder.md)
Rulings Q1–Q25: `.superpowers/sdd/2026-09-27-etappe-3b-theme-builder/progress.md`.
- E7 präzisiert: Layouts und neue Vorlagen über `et_theme_builder_insert_layout`/`et_theme_builder_store_template`
mit Rechteprüfung davor und `wp_die`-Sperre währenddessen; Zuordnung zur Live-Liste, Bedingungen und
`_et_enabled` als Nachbildung von Divis Meta-Schreibweise (store_template würde bei bestehenden Vorlagen auch
Standard-Kennzeichen, Titel und Onboarding-Meta neu schreiben). Nach dem Anlegen Rücklesen; Abweichung =
alles zurücknehmen, auch Posts, die Divi vor einem Fehler schon eingefügt hatte (Q19).
- E8/Antwort F1 umgesetzt als eigenes Tool `lit/tb-activate` (statt `enabled` in `lit/tb-assign`); neue Vorlagen
sind immer deaktiviert. Jede andere Änderung, die Besucher sofort sähen (Bedingungen aktiver Vorlagen, Layouts
aktiver Vorlagen), ist zuerst ein Probelauf und braucht `confirm`; Layouts mit der Sicherung aus
`VisibleEdits` (Q2, Q9). Aktiv = zugeordnet und `_et_enabled`, unabhängig vom Post-Status (Q8).
- Der Ausschalter (`lit/tb-activate` mit `enabled: false`) geht immer: auch bei TB-Entwurf, dann mit Warnung
(Q20), und bei einer aktiven Vorlage im Papierkorb (Q16).
- TB-Editor: Ablehnung bei TB-Entwurf; neue Vorlagen hängen sofort an der Live-Liste; `lit/tb-list` zeigt vom
Theme Builder entfernte Vorlagen (`detached`), `lit/tb-assign` mit `reattach` ordnet sie deaktiviert wieder zu
(nur mit aktiven Layouts, Q8/B10). Befunde 10–12: `tests/Fixtures/divi/stage3b/README.md`.
- E3 für den Theme Builder: `edit_others_posts`, `edit_theme_options` (Q15) und Divis Rollen-Recht
„Theme Builder“, je Vorlage/Layout `edit_post`, zum Anlegen die Veröffentlichen-Rechte. Nicht bearbeitbare
Vorlagen antworten wie fehlende (Q18); aktive davon nur als Anzahl in Liste und Probeläufen
von `lit/tb-assign`/`lit/tb-activate` (Q21) und der Layout-Änderungen (`hidden_templates`; die Rückfrage
gilt trotzdem für alle aktiven Vorlagen, Q22).
- Bedingungen nur aus Divis Katalog; Objekte lesbar (Begriffe: `assign_term`); strenger als Divi:
Autoren-Bedingungen (`archive:user:id:`) nur mit `list_users` (Q10).
- E9: eigene Gruppe „Divi Theme Builder“ (`divi-theme-builder`); alle neuen Tools indirekt, 22 direkte Tools.
- E10/E11 umgesetzt mit `Fingerprint` (Struktur/Inhalt). „Inhaltsgleich“ heißt gleiche Bytes einschließlich
Textteilen zwischen Blöcken und Inhalt von Core-Blöcken, nur Admin-Labels ausgenommen (Q25); ersetzt wird nur
auf Seiten und Beiträgen (`page`, `post`), die das Konto bearbeiten darf – Theme-Builder-Layouts auch nicht als
Quelle (Q24). Globale Einträge enthalten keinen Verweis auf einen Eintrag in irgendeiner Schreibweise (Q7).
D10-Semantik beim Speichern mehrerer Seiten: Abbruch bei der ersten gescheiterten Seite, Fehler nennt
geänderte, gescheiterte und unveränderte Seiten.
- E12: Feldprüfung im Inhalt-Layout als Warnung gegen die Inhaltstypen der Vorlagen-Bedingungen, beide
Feldformen (Q11).
- P13 (vollständiges Ausblenden der Bibliotheks-IDs, auch im Suchen-und-Ersetzen) und P10 (Zielregeln der
Vollform `post_link_url_*`, Bibliothekseinträge und Typen ohne Divi-Linkoption wie fehlende IDs, Q14)
umgesetzt.
- Restrisiken: P25 (ungewöhnliche Schreibweisen globaler Verweise), dazu Divis erneutes Auswerten von Blöcken in
Shortcode-Modulen und WooCommerce-Textmodulen (dokumentiert im Guide `divi-library`); das Präfilter-Orakel von
`lit/divi-replace-text` (Q13) und die Race-Bedingungen zwischen Prüfen und Schreiben bleiben für Etappe U.
- Server-Instructions: Zeile „Divi Theme Builder: …“ nur bei aktiver, beschreibbarer Gruppe; der Ablauf
„custom-post-type-vorlage“ steht im Guide `theme-builder`.