# 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":"","blockName":"divi/","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_` 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::all` (`:703-710`), `archive:post_type:` (nur `post` oder `has_archive`, `:724-734`), `singular:post_type::id:` (`:805-813`), `singular:post_type::children: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_` 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_`: 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_` 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= bearbeiten“ (S19-Teil 2). Schreiben in den Library-Post wirkt auf alle Instanzen (F7); die Antwort nennt die betroffenen Seiten (Verwendungssuche über `"globalModule":""` 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 `` 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:}}`; 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::…`); 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_link_url_` 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 – 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_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_` + `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:}}` 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=` (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; für die Probeläufe der Layout-Änderungen folgt Q22 in der Schlussrunde). - 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`.