docs: Divi guides shortform/placeholders, hints name the write tools; test: Divi write end-to-end; version 0.3.0

- Hints (PostPresenter::DIVI_NOTE, outline, catalog) and server instructions (tree_rev rule)
  name the Divi write tools; guides divi-shortform and divi-placeholders, additions to
  divi-structure, divi-attrs, overview (tool groups, 'nur lesen'), workflows, content-types.
- D7 wording and the S7 exception in readme, guides and spec 1.3/5.2; readme 0.3.0.
- ToolNamingTest: every referenced guide topic exists; overview lists exactly the topics.
- tests/Integration/divi-write-smoke.sh: all write tools via OAuth, editor without
  unfiltered_html with benign probes (S6), Divi's own save filters on the new fixture
  synthetic-divi-filters.html (S7); admin-render.php accepts the new wrap class.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 17:13:59 +02:00
co-authored by Claude Opus 5.5
parent b044cacc9d
commit 10c98cd3c6
24 changed files with 811 additions and 26 deletions
+8 -6
View File
@@ -23,14 +23,16 @@ Werkzeugnamen: `lit-…` sind direkt aufrufbare Tools; `lit/…` sind Abilities,
Trefferliste. Geliefert werden nur Einträge, die der verbundene Nutzer selbst bearbeiten darf (fremde
Entwürfe/private Inhalte ohne die passende Berechtigung erscheinen nicht).
- `lit-content-get`: alle Daten eines Eintrags inkl. Feldern, Begriffen, Beitragsbild und `rev`.
Bei Divi-Seiten fehlt `content`; den Aufbau liest `lit-divi-outline` (siehe `divi-structure`), geändert
wird er vorerst im Divi-Builder (Titel, Auszug, Felder und Begriffe gehen weiter über
`lit-content-update`).
Bei Divi-Seiten fehlt `content`; den Aufbau liest `lit-divi-outline`, geändert wird er mit den
Divi-Werkzeugen (siehe `divi-structure`); Titel, Auszug, Felder und Begriffe gehen weiter über
`lit-content-update`.
## Anlegen und Ändern
- `lit-content-create` legt immer einen **Entwurf** an. Felder werden gegen das Schema geprüft; ein Fehler
bricht den ganzen Aufruf ab (`fields.<schlüssel>` im Fehler nennt die Ursache).
bricht den ganzen Aufruf ab (`fields.<schlüssel>` im Fehler nennt die Ursache). Divi-Seiten gleich mit
Inhalt: `divi` = Liste von Abschnitten in Kurzform (siehe `divi-shortform`) statt `content`; die Antwort
enthält dann die `tree_rev` für weitere Divi-Änderungen.
- `lit-content-update` ändert nur die übergebenen Werte, der Status bleibt. `rev` aus `lit-content-get` ist
**Pflicht** – hat jemand parallel gespeichert (Inhalt, Titel, Auszug, Status oder irgendetwas anderes am
Eintrag), kommt `rev_mismatch`: neu lesen, Änderung auf dem aktuellen Stand wiederholen.
@@ -59,8 +61,8 @@ Werkzeugnamen: `lit-…` sind direkt aufrufbare Tools; `lit/…` sind Abilities,
## Sicher überarbeiten
1. `lit/post-duplicate` → Entwurfskopie; die Antwort enthält die `rev` der Kopie für `lit-content-update`.
2. Kopie mit `lit-content-update` (Divi-Inhalte vorerst im Divi-Builder) bearbeiten und dem Nutzer die
Vorschau zeigen; `lit-page-render-summary` fasst den gerenderten Inhalt zusammen.
2. Kopie mit `lit-content-update` bzw. den Divi-Werkzeugen bearbeiten und dem Nutzer die Vorschau zeigen;
`lit-page-render-summary` fasst den gerenderten Inhalt zusammen.
3. `lit/post-replace-with-copy` übernimmt die Kopie ins Original (ID, URL, Status und Menüeinträge bleiben).
Voraussetzungen: die Kopie muss noch Entwurf oder ausstehend sein, `rev` des Originals aus
`lit-content-get` ist Pflicht (sie deckt Titel, Inhalt, Auszug und Status ab – Felder/Meta, die direkt am
+25
View File
@@ -18,6 +18,31 @@ Der Wert eines Pfads ist eine Zuordnung **Breakpoint → Zustand → Wert**:
Breakpoints: `desktop`, `tablet`, `phone` (je nach Website auch `tabletWide`, `phoneWide`, `widescreen`,
`ultraWide`); Zustände: `value`, `hover`, `sticky`.
## Setzen
In `attrs` (bei `lit-divi-insert` je Knoten, bei `lit-divi-update` je Änderung) steht Pfad → Wert je Breakpoint.
Kurz: `{"desktop": "#1a1a1a", "phone": "…"}` bzw. bei Objektwerten `{"desktop": {"size": "28px"}}`; Hover und
Sticky: `{"hover": {…}}` bzw. `{"desktop": {"value": {…}, "hover": {…}}}`. Nur die genannten Breakpoints
werden ersetzt, der Rest bleibt; `null` entfernt einen Pfad, Breakpoint oder Zustand:
```json
{ "id": "s0.r0.c1.m0",
"attrs": { "title.decoration.font.font": { "desktop": { "color": "{{color:heading}}" },
"phone": { "size": "28px" } },
"module.decoration.spacing": null } }
```
Ein Objektwert ersetzt den bisherigen Wert dieses Breakpoints ganz (Unterschlüssel werden nicht
zusammengeführt): z. B. bei `module.decoration.sizing` einer Spalte `flexType` immer mitschicken (siehe
`divi-shortform`).
Unbekannte Pfade → Fehler `invalid_attr_path` mit Vorschlägen unter `fields`. Texte werden bereinigt
(HTML nur, wo Divi Rich-Text erwartet; Links ohne `javascript:`). `$variable`-Verweise werden geparst,
geprüft und vom Plugin neu erzeugt (siehe `divi-placeholders`). Eigenes HTML, CSS und HTML-Attribute
(`…decoration.attributes`, `…advanced.html`, `…css…`) nur mit dem Recht `unfiltered_html`, sonst „nur im
Divi-Builder“. Unberührte Teile der Seite bleiben byte-identisch; Ausnahme: Divi selbst normalisiert Seiten
mit eigenen HTML-/Attribut-Feldern beim Speichern (Warnung in der Antwort, neue `tree_rev` gilt).
## Pfade finden
- `lit-divi-catalog` ohne `module` listet alle Module dieser Website; mit `module` (z. B. `"heading"`) alle
+47
View File
@@ -0,0 +1,47 @@
# Platzhalter für Divi-Inhalte
Platzhalter stehen **allein** im Wert (nicht mitten im Text) und werden beim Speichern in Divis eigene
Verweise übersetzt. So bleibt die Seite mit dem Designsystem verbunden: ändert sich die Primärfarbe, ändert
sich auch der Button.
| Platzhalter | Bedeutung |
|---|---|
| `{{color:primary}}`, `secondary`, `heading`, `body`, `link` | die fünf Theme-Farben |
| `{{color:<Name>}}` | eigene globale Farbe (Name wie in `lit-divi-design-system`) |
| `{{var:<Name>}}` | globale Variable (Zahl, Text, Bild, Link) |
| `{{font:heading}}`, `{{font:body}}` | Theme-Schrift für Überschriften bzw. Fließtext |
| `{{post:title}}`, `excerpt`, `date`, `modified`, `author`, `url`, `featured_image`, `categories`, `tags` | dynamischer Inhalt des angezeigten Beitrags |
| `{{field:<meta_key>}}` | dynamischer Inhalt aus einem Feld (Pods/Meta), z. B. `{{field:telefon}}` |
| `"preset": "<Name>"` | Modul-Preset (kein Platzhalter, sondern eigenes Feld) |
Beispiele:
```json
{ "type": "button", "text": "Anrufen", "url": "{{field:telefon_link}}",
"attrs": { "button.decoration.background": { "desktop": { "color": "{{color:primary}}" },
"hover": { "color": "{{color:Nachtblau}}" } } } }
```
```json
{ "type": "heading", "text": "{{post:title}}", "level": "h1" }
```
Unbekannte Namen lehnt das Werkzeug ab und nennt die vorhandenen; ist ein Name doppelt vergeben, statt des
Namens die ID aus `lit-divi-design-system` angeben (z. B. `{{color:gcid-…}}`). Felder, deren Schlüssel mit `_`
beginnt (geschützte Meta-Daten), sind nicht erlaubt.
In Link-Feldern (`url`, `button_url` …) nimmt das Werkzeug jeden dynamischen Inhalt an, auch `{{post:title}}`;
sinnvoll sind dort nur Werte, die eine Adresse ergeben (`{{post:url}}`, ein Feld mit einer URL, eine
Link-Variable).
## `$variable`-Werte
In `lit-divi-node-get` erscheinen die übersetzten Verweise als `$variable({…})$`. Diese Werte unverändert
lassen oder wieder durch einen Platzhalter ersetzen. `$variable`-Verweise werden geparst, geprüft und vom
Plugin neu erzeugt: erlaubt sind nur ganze Werte mit bekannten Farben, Variablen, Schriften und
dynamischen Quellen; Zusatztexte davor/danach (`before`/`after`) werden bereinigt; alles andere (z. B. ein
Verweis mitten im Text, eigenes HTML-Ausgeben von Feldern) lehnt das Werkzeug mit `validation_failed` ab.
Unberührte Teile der Seite bleiben dabei byte-identisch. Ausnahme: Divi selbst normalisiert Seiten mit
eigenen HTML-/Attribut-Feldern beim Speichern; die Antwort warnt dann, dass der Inhalt beim Speichern
verändert wurde, und die neue `tree_rev` gilt.
+112
View File
@@ -0,0 +1,112 @@
# Divi-Kurzform
Neue Divi-Inhalte beschreibt man als JSON-Knoten. `lit-divi-insert` (in `nodes`) und `lit-content-create` (in
`divi`) nehmen dieselbe Form. Jeder Knoten hat einen `type`; dazu Kurzform-Felder, optional `label`
(Admin-Label), `preset` (Name eines Modul-Presets aus `lit-divi-design-system`) und `attrs` (Vollform, siehe
`divi-attrs`). Kurz- und Vollform lassen sich kombinieren, aber nicht für denselben Pfad (dann Fehler
`validation_failed` mit dem Pfad).
## Aufbau
```json
{ "type": "section", "label": "Hero", "background_color": "{{color:primary}}",
"rows": [
{ "columns": "1_3,2_3",
"modules": [
[ { "type": "image", "image_id": 33 } ],
[ { "type": "heading", "text": "Willkommen", "level": "h1" },
{ "type": "text", "content": "<p>Seit 1987 in Köln.</p>" },
{ "type": "button", "text": "Termin buchen", "url": "/kontakt", "preset": "CTA primär" } ]
] }
] }
```
- **Abschnitt** (`section`): `rows` (Zeilen) oder – für eine einspaltige Zeile – direkt `modules`;
`section_type: "fullwidth"` für Vollbreite-Abschnitte (dann nur `fullwidth-…`-Module in `modules`);
`background_color`. Spezialabschnitte (specialty) nur im Divi-Builder bauen.
- **Zeile** (`row`): `columns` = Breiten als Brüche, die zusammen 1 ergeben (`"4_4"`, `"1_2,1_2"`,
`"1_3,2_3"`, `"1_4,1_4,1_2"`, auch Fünftel); `modules` = je Spalte eine Liste (bei einer Spalte reicht eine
einfache Liste).
- **Module** mit Kind-Modulen (Akkordeon, Tabs, Slider, Kontaktformular, Karte, Social-Media-Follow,
Icon-Liste) bekommen ihre Einträge in `items`; der Eintragstyp ergibt sich von selbst.
- `label` fehlt → Admin-Label „<Abschnitt> – <Modul>“, z. B. „Hero – Button“. Abschnitten deshalb ein Label
geben.
- `heading` ohne `level` wird `h2` (eine Seite hat meist schon eine `h1` im Titel).
## Kurzform-Felder
| Modul | Felder |
|---|---|
| `text` | `content` (HTML) |
| `heading` | `text`, `level` (`h1`–`h6`) |
| `button` | `text`, `url`, `new_window` (true/false) |
| `image` | `image_id` (Mediathek, setzt URL/ID/Alt), `src`, `alt`, `title`, `url` |
| `blurb` | `title`, `url`, `content`, `image_id`, `src`, `alt`, `level` |
| `cta` | `title`, `content`, `button_text`, `button_url` |
| `icon` | `url`, `title` |
| `divider` | `show`, `color` |
| `testimonial` | `author`, `job_title`, `company`, `company_url`, `content`, `portrait_id`, `portrait_url` |
| `team-member` | `name`, `position`, `content`, `image_id`, `image_url` |
| `accordion` → `accordion-item` | Eintrag: `title`, `content`, `open` |
| `tabs` → `tab` | Eintrag: `title`, `content` |
| `toggle` | `title`, `content`, `open` |
| `gallery` | `image_ids` (Liste von Anhang-IDs) |
| `slider` → `slide` | Eintrag: `title`, `content`, `button_text`, `button_url`, `image_id`, `src`, `alt` |
| `contact-form` → `contact-field` | Formular: `title`, `email`, `success_message`, `submit_text`; Feld: `title`, `id`, `type` (`input`, `email`, `text`, `checkbox`, `radio`, `select`), `required` |
| `map` → `map-pin` | Karte: `address`, `lat`, `lng`, `zoom`; Pin: `title`, `content`, `address`, `lat`, `lng` |
| `video` | `src`, `thumbnail` |
| `number-counter` | `number`, `title`, `percent` |
| `social-media-follow` → `social-media-follow-network` | Eintrag: `network` (z. B. `facebook`), `url`, `label` |
| `icon-list` → `icon-list-item` | Eintrag: `text` |
| `code` | `content` – nur, wenn kein natives Modul passt (Warnung) |
Alle anderen Module (siehe `lit-divi-catalog`) gehen über `attrs`. Die Kurzform-Felder eines Moduls samt Pfad
zeigt `lit-divi-catalog` mit `module`.
Zwei Felder heißen wie Knoten-Schlüssel und lassen sich deshalb beim Einfügen **nicht** in Kurzform setzen:
`type` des Kontaktformular-Felds (`type` ist dort der Modultyp) und `label` des Social-Media-Netzwerks (`label`
ist dort das Admin-Label). Beim Einfügen über `attrs` (`fieldItem.advanced.type` bzw.
`socialNetwork.innerContent` mit `label`) setzen, danach auch mit `lit-divi-update` über `set`
(`{"set": {"type": "email"}}`).
## Einfügen, ändern, verschieben, löschen
- `lit-divi-insert`: `nodes` + Position – `parent` (Pfad-ID, z. B. Spalte `s0.r0.c1`; optional `index`),
`before` oder `after`; ohne Position ans Seitenende. Oben auf der Seite sind nur Abschnitte erlaubt, in
Abschnitten Zeilen, in Spalten Module.
- `lit-divi-update`: `changes` = `[{"id": "s0.r0.c1.m2", "set": {"text": "Jetzt buchen"}, "preset": "CTA
primär", "label": "Hero – Button"}]` – mehrere Knoten in einem Aufruf; `null` entfernt einen Wert. Denselben
Pfad nicht zugleich über `set` und `attrs` ändern (dann zwei Änderungen senden).
- `lit-divi-move`: `id` + Position (auch in andere Zeilen oder Abschnitte).
- `lit-divi-delete`: `ids`, erst nach Rückfrage mit `confirm: true`.
- `lit/divi-duplicate-node`: Kopie direkt hinter dem Original.
- Jede Antwort enthält die neue `tree_rev` (für die nächste Änderung), `changed` mit den neuen Pfad-IDs,
den betroffenen Abschnitt als `outline`, `warnings` (Qualität, siehe unten) und `revision_id`.
Fehler: `validation_failed` bzw. `invalid_attr_path` mit `fields` (Eingabepfad → Problem, z. B.
`nodes[0].rows[0].columns`); dann wurde nichts gespeichert.
## Spaltenbreiten ändern
Die Breite einer Spalte steht in `module.decoration.sizing` (Desktop: `flexType`, z. B. `8_24` = 1/3). Wer
diesen Desktop-Wert über `attrs` ersetzt, ersetzt das ganze Objekt: `flexType` immer mitschicken, sonst fällt
die Breite weg. In einer Zeile mit festen Breiten alle Spalten zusammen ändern (Summe 1). Die Angabe
`flexColumnStructure` der Zeile (Spaltenvorlage im Divi-Builder) passt das Werkzeug nicht an; nach einer
Breitenänderung zeigt der Builder dort evtl. noch die alte Vorlage; gespeichert sind die neuen Breiten (bei
Bedarf die Vorlage im Builder neu wählen).
## Sicherheit und Speichern
Alle Werte werden vor dem Speichern bereinigt: Texte ohne HTML, Rich-Text ohne `unfiltered_html` nur mit dem
HTML, das WordPress in Beiträgen erlaubt, Links ohne `javascript:`.
`$variable`-Verweise werden geparst, geprüft und vom Plugin neu erzeugt. Eigenes HTML, CSS und HTML-Attribute eines Moduls (`…decoration.attributes`, `…advanced.html`,
`…css…`) ändert nur, wer das Recht `unfiltered_html` hat – sonst „nur im Divi-Builder“. Unberührte Teile der
Seite bleiben byte-identisch; Ausnahme: Divi selbst normalisiert Seiten mit eigenen HTML-/Attribut-Feldern
beim Speichern (dann Warnung, dass der Inhalt beim Speichern verändert wurde – mit der neuen `tree_rev`
weiterarbeiten).
## Warnungen
Neue oder geänderte Knoten werden geprüft: feste Farben/Schriften, obwohl eine passende globale existiert;
Bilder ohne Alt-Text; leere Module; Sprünge in der Überschriftenhierarchie; Code-Modul. Warnungen dem Nutzer
nennen und möglichst gleich beheben.
+16 -1
View File
@@ -22,9 +22,24 @@ Spalten (`column`), Spalten Module (`text`, `heading`, `button`, …). Manche Mo
des letzten Lesevorgangs.
- `rev` ist davon getrennt: der Stand des ganzen Eintrags (Titel, Auszug, Status …) für `lit-content-update`.
## Ändern
Neue Inhalte und Änderungen: siehe `divi-shortform` (Kurzform, `lit-divi-insert`, `lit-divi-update`,
`lit-divi-move`, `lit-divi-delete`), `divi-attrs` (Vollform) und `divi-placeholders`. Ablauf:
1. `lit-divi-outline` lesen (liefert `tree_rev`), `lit-divi-design-system` für Farben, Variablen, Presets.
2. Änderung mit dieser `tree_rev` senden. Die Antwort enthält die neue `tree_rev` und die neuen Pfad-IDs –
damit direkt weiterarbeiten, ohne erneut zu lesen.
3. `rev_mismatch`: jemand hat die Seite inzwischen geändert (z. B. im Divi-Builder) → neu lesen.
4. Zum Schluss `lit-page-render-summary` prüfen und dem Nutzer die Vorschau (`preview_url`) bzw. den
Divi-Builder (`edit_url`) nennen. Der Status bleibt unverändert; veröffentlichte Seiten werden live
geändert (vorher entsteht eine Revision, `revision_id`).
## Was nicht bearbeitet wird
- Knoten mit „nur lesbar“ (globale Module `g…`, fremde Blöcke `x…`) ändern die Divi-Werkzeuge nicht.
- Knoten mit „nur lesbar“ (globale Module `g…`, fremde Blöcke `x…`) lassen sich nur als Ganzes verschieben,
duplizieren oder löschen; ihr Inhalt wird im Divi-Builder bearbeitet (globale Module folgen später).
`lit/divi-replace-text` ändert sie nicht, sondern nennt sie in `skipped_nodes`.
- Divi-4-Inhalt (Shortcodes `[et_pb_…]`) wird nicht gelesen oder geändert: Seite einmal im Divi-Builder
öffnen und speichern, Divi 5 wandelt sie dabei um.
- Ist das Markup beschädigt, lehnen die Werkzeuge ab (`unsupported`); die Seite im Divi-Builder öffnen und
+9 -4
View File
@@ -23,8 +23,12 @@ Direkt sichtbar: `lit-guide`, `lit-site-status`, `lit-content-types`, `lit-conte
`lit-divi-outline`, `lit-divi-node-get`, `lit-divi-catalog`, `lit-divi-design-system`, `lit-divi-insert`,
`lit-divi-update`, `lit-divi-move`, `lit-divi-delete`.
Welche Tools direkt sichtbar sind, lässt sich unter Einstellungen → LIT MCP → Tools ändern (außer bei
`lit-guide`, das immer direkt bleibt).
Die Tools sind nach Integration gruppiert (Grundlagen, WordPress, Medien, Divi). Jede Gruppe hat unter
Einstellungen → LIT MCP → Tools eine eigene Einstellung (direkt/indirekt/deaktiviert), die ihre Tools übernehmen
(„wie Gruppe“). Eine Einstellung am einzelnen Tool geht vor; die Gruppe zeigt dann „gemischt“. „nur lesen“
schaltet alle schreibenden Tools der Gruppe ab – auch solche mit eigener Einstellung. `lit-guide` bleibt immer
direkt; `lit-media-preview`/`lit-media-download` bleiben bei Gruppe „indirekt“ direkt. Ab mehr als 25 direkten
Tools warnt die Seite (Kontextkosten in claude.ai). Änderungen gelten ab der nächsten Verbindung.
Über `mcp-adapter-discover-abilities` / `mcp-adapter-execute-ability` (Ability-Namen `lit/…`; in Guides und
Hinweisen immer so geschrieben):
@@ -37,12 +41,13 @@ Einstellungen (`lit/wp-settings-get`, `lit/wp-settings-update`), Medien (`lit/me
Die `lit-divi-…`-Werkzeuge gibt es nur, wenn der Divi-5-Builder aktiv ist.
Weitere Themen: `lit-guide` mit `topic` = `content-types`, `workflows`, `media`, `divi-structure`,
`divi-attrs` oder `divi-design-system`.
`divi-shortform`, `divi-attrs`, `divi-placeholders` oder `divi-design-system`.
## Rückmeldungen
- Erfolg: `ok`, `summary`, `changed`, `warnings`, `rev`, `revision_id` (Revision mit dem Stand vorher, nur bei
Tools, die Inhalt überschreiben, sonst `null`), `log_id`, URLs.
Tools, die Inhalt überschreiben, sonst `null`), `log_id`, URLs. Divi-Werkzeuge zusätzlich `tree_rev` (Stand
des Seiteninhalts, Pflicht für die nächste Divi-Änderung) und `outline` (betroffener Abschnitt).
- Fehler: JSON mit `code` (z. B. `validation_failed`, `rev_mismatch`, `write_failed`, `partial_write`),
`message`, `hint` (und ggf. `fields`). Dem `hint` folgen.
- `confirm_required`: Nutzer fragen, dann mit `confirm: true` wiederholen.
+19
View File
@@ -11,6 +11,25 @@
5. Menü: `lit/menu-create` (optional mit `location` aus `lit/menu-list`), Einträge mit `lit/menu-set-items`.
6. Veröffentlichen erst, wenn der Nutzer es ausdrücklich möchte.
## Divi-Seite bauen
1. `lit-divi-design-system` lesen (Farben, Schriften, Presets) und `lit-divi-catalog` für die Module.
2. Seite als Entwurf mit Inhalt anlegen: `lit-content-create` mit `divi` (Abschnitte in Kurzform, siehe
`divi-shortform`) – oder eine leere Seite anlegen und Abschnitte mit `lit-divi-insert` einfügen.
3. Weitere Abschnitte/Module mit `lit-divi-insert`, Anpassungen mit `lit-divi-update`; immer die `tree_rev`
der letzten Antwort mitgeben.
4. `lit-page-render-summary` prüfen, `warnings` beheben, dem Nutzer `preview_url` und `edit_url` (Divi-Builder)
nennen. Veröffentlichen nur auf Wunsch (`lit-post-publish`).
## Text auf vielen Seiten ersetzen
`lit/divi-replace-text` zuerst mit `dry_run: true` (Standard): zeigt Treffer je Seite mit `tree_rev`. Nach
Rückfrage mit `dry_run: false`, `targets` (post_id + tree_rev aus dem Probelauf) und `confirm: true`
ausführen; jede Seite bekommt eine eigene Revision, `results` meldet das Ergebnis je Seite. Ersetzt wird nur
sichtbarer Text (keine Links, Stile, Code oder dynamischen Inhalte); in reinen Textfeldern (Titel,
Button-Texte) werden Leerzeichen am Anfang und Ende der Ersetzung entfernt. Globale Module werden nicht
geändert, sondern in `skipped_nodes` genannt – dem Nutzer sagen, dass er sie in der Divi-Bibliothek ändern muss.
## Menüs pflegen
- `lit/menu-get` liefert den Baum und `menu_rev` (Stand des Menüs). `lit/menu-set-items` verlangt diese
@@ -0,0 +1,112 @@
# Etappe 2b – Manuelle Abnahme (Divi schreiben)
Vorbereitet von Task 10 (Hinweise, Guides, Version 0.3.0, Ende-zu-Ende-Test). Automatisch geprüft sind
bereits:
- alle Unit-Tests (`composer test`) und Lint (`composer lint`);
- die fünf Integrationsskripte `divi-write-smoke.sh`, `divi-read-smoke.sh`, `mcp-smoke.sh`,
`wp-basis-smoke.sh` und `media-admin-smoke.sh` (alle in `tests/Integration/`).
`divi-write-smoke.sh` prüft über den OAuth-Endpunkt:
- Einfügen, Ändern, Verschieben, Löschen und Duplizieren;
- Ersetzen mit Probelauf;
- `lit-content-create` mit Divi-Inhalt;
- den ersten Abschnitt auf einer leeren Seite;
- einen Redakteur ohne `unfiltered_html`: unberührte Bytes bleiben gleich, und es wird weder Skript,
Event-Handler, `javascript:` noch ein eingeschleustes Attribut gespeichert oder gerendert;
- Divis eigene Speicherfilter (Seite mit eigenen HTML-Attributen).
Details stehen im Report von Task 10 (`.superpowers/sdd/2026-09-26-etappe-2b-divi-schreiben/task-10-report.md`).
Die folgenden Punkte lassen sich nicht automatisch prüfen, weil sie den echten Divi Visual Builder und den
claude.ai-Connector brauchen. Jan-Niclas prüft sie selbst (Spec §1.3 Nr. 2, §13 „Bearbeitbarkeit im
Visual Builder“). Abweichungen als kurze Notiz zurückmelden; sie werden Folgeaufgaben (z. B. anders
gespeicherte Spaltenbreiten, D8).
## 1. Visual Builder: von Claude gebaute Seite
- [ ] Dieser Auftrag in claude.ai:
„Lege eine Entwurfsseite ‚Abnahme Divi‘ an mit Hero (Überschrift, Text, Button mit Preset), einer
Zeile 1/3 + 2/3 mit Bild und Blurb, einem Akkordeon und einem Kontaktformular.“
Erwartet:
- Claude liest vorher `lit-divi-design-system` und `lit-divi-catalog`;
- dann `lit-content-create` mit `divi` (oder eine leere Seite plus `lit-divi-insert`);
- die Antwort nennt `preview_url` und `edit_url`.
- [ ] Die Seite im Divi Visual Builder öffnen (`edit_url`) und prüfen:
- [ ] Alle Module erscheinen als **native Module**, nicht als Code-Modul.
- [ ] Die Admin-Labels folgen dem Muster „<Abschnitt> – <Modul>“, z. B. „Hero – Button“.
- [ ] Jedes Modul lässt sich anklicken, ändern und speichern, ohne Fehlermeldung des Builders.
- [ ] Die Spaltenbreiten stimmen (1/3 + 2/3), auch in der Spalteneinstellung des Builders.
- [ ] Farben aus `{{color:…}}` sind als **globale Farbe** verknüpft: der Farbwähler zeigt die globale
Farbe, nicht einen festen Hex-Wert.
- [ ] Das Button-Preset ist im Builder als Preset ausgewählt.
- [ ] Akkordeon-Einträge und Kontaktformular-Felder sind einzeln bearbeitbar.
- [ ] Eine zweite Seite nur mit `lit-divi-insert` aufbauen lassen: leere Seite anlegen, dann
„Füge einen Abschnitt ‚Start‘ mit Überschrift und Text ein“. Im Builder öffnen: der Abschnitt
erscheint normal und ist bearbeitbar (Ruling S9: Hülle `divi/placeholder` wie beim Builder).
## 2. Visual Builder: von Claude geänderte Seite
- [ ] Eine bestehende, im Builder gebaute Seite kopieren (`lit/post-duplicate`) und an der Kopie per
claude.ai einen Button-Text ändern, ein Modul verschieben und einen Abschnitt duplizieren lassen.
- [ ] Die Kopie im Builder öffnen und prüfen:
- [ ] Alle unveränderten Module sehen aus wie vorher (Einstellungen, Presets, globale Farben).
- [ ] Die geänderten Module sind bearbeitbar, und die Änderung ist sichtbar.
- [ ] Die Kopie eines Abschnitts heißt „<Label> (Kopie)“.
## 3. Zurück zu Claude nach einer Builder-Änderung
- [ ] Die Seite aus Schritt 1 im Builder ändern und speichern.
- [ ] Danach per claude.ai eine weitere Änderung verlangen. Erwartet:
- [ ] `rev_mismatch` erscheint nur, wenn Claude eine alte `tree_rev` benutzt.
- [ ] Nach erneutem Lesen (`lit-divi-outline`) gelingt die Änderung.
- [ ] Die im Builder gemachten Einstellungen bleiben erhalten.
## 4. Rückgängig
- [ ] Einen Abschnitt löschen lassen. Claude fragt nach (`confirm_required`) und löscht erst nach der
Bestätigung.
- [ ] Den Abschnitt über `lit/revision-restore` mit der gemeldeten `revision_id` zurückholen und im Builder
prüfen, dass er wieder vollständig da ist.
## 5. Text auf mehreren Seiten ersetzen
- [ ] „Ersetze auf allen Seiten ‚Termin‘ durch ‚Beratungstermin‘“. Erwartet:
- [ ] Claude zeigt erst den Probelauf (Treffer je Seite) und fragt nach.
- [ ] Erst danach ersetzt es.
- [ ] Im Protokoll-Tab (Einstellungen → LIT MCP → Protokoll) steht je geänderter Seite ein Eintrag mit
eigener Revision.
- [ ] Liegt ein Treffer in einem **globalen Modul**, ändert Claude ihn nicht, sondern nennt ihn
(`skipped_nodes`, „in der Divi-Bibliothek bearbeiten“).
## 6. Redakteur ohne `unfiltered_html`
- [ ] Mit einem Redakteurs-Konto verbinden; unter Einstellungen → LIT MCP → Zugriff muss die Rolle
freigegeben sein.
- [ ] Auf einer Seite eines Administrators einen Text ändern lassen.
- [ ] Die Seite im Builder öffnen. Erwartet:
- [ ] Eingebettete Inhalte des Administrators (z. B. iframe, eigenes HTML im Code-Modul) sind
unverändert.
- [ ] Die Änderung des Redakteurs ist da.
- [ ] Den Redakteur eigenes HTML/CSS/Attribute eines Moduls setzen lassen, z. B. „füge dem Button das
HTML-Attribut data-x hinzu“. Erwartet: Ablehnung mit „nur im Divi-Builder änderbar“.
## 7. Tool-Gruppen und „nur lesen“
- [ ] Unter Einstellungen → LIT MCP → Tools die Gruppe Divi auf „nur lesen“ stellen.
- [ ] Neu verbinden. Die Divi-Schreibwerkzeuge (`lit-divi-insert` usw.) fehlen; die Lesewerkzeuge sind
noch da.
- [ ] Die Einstellung zurücksetzen.
## 8. Builder-Fixtures aus 2a (falls noch offen; Ruling S20)
- [ ] Seiten mit **globalem Modul**, Preset, Variablen und Dynamic Content exportieren:
`tests/Integration/divi-export-page.php` (Anleitung in `2026-09-26-etappe-2a-abnahme.md` §1).
`composer test` bleibt grün.
- [ ] Auf einer Kopie einer solchen Seite `lit-divi-update` an einem normalen Modul ausführen. Das
globale Modul bleibt byte-identisch: Export vorher und nachher vergleichen.
- [ ] Hat die Seite Module mit eigenen HTML-Attributen oder einem eigenen HTML-Element (Erweitert →
Attribute/HTML), meldet die Antwort evtl. „Inhalt beim Speichern verändert“. Das ist dokumentiert
(Ruling S7: Divi normalisiert solche Seiten selbst). Prüfen, dass die Seite im Builder danach
einwandfrei ist.
@@ -18,7 +18,7 @@ Ein WordPress-Plugin, das auf jeder Kundenseite einen MCP-Server bereitstellt, m
1. Ein claude.ai-Connector verbindet sich per OAuth mit einer Kundenseite; nur freigegebene Rollen können Tools nutzen.
2. Von Claude erzeugte oder geänderte Divi-Inhalte sind im Visual Builder vollständig bearbeitbar (manuell verifiziert pro Etappe).
3. Unangetastete Inhalte bleiben byte-identisch erhalten.
3. Unangetastete Inhalte bleiben byte-identisch erhalten. Dokumentierte Ausnahme (Ruling S7): Divi selbst normalisiert Seiten mit eigenen HTML-/Attribut-Feldern beim Speichern (`AttributeSecurity`/`HtmlSecurity` auf `wp_insert_post_data`, laufen für jeden Benutzer und werden nie entfernt); die Antwort meldet das als Warnung „Inhalt beim Speichern verändert“ (`CHANGED_ON_SAVE`), die Seite bleibt gültig (E2E `divi-write-smoke.sh` Abschnitt 8).
4. Eine neue Website (Seitenstruktur, Menü, Einstellungen, Designsystem aus einem CD) lässt sich im Dialog aufsetzen.
5. Jede Änderung ist nachvollziehbar (Protokoll) und umkehrbar (Revision bzw. Snapshot).
@@ -171,7 +171,7 @@ Seiten, Theme-Builder-Layouts (`et_header_layout`, `et_body_layout`, `et_footer_
- Parst `post_content` in einen Baum (auf Basis von `parse_blocks`, mit Sonderbehandlung für `divi/global-layout` und `globalModule`, die `parse_blocks` laut Divi-Code nicht korrekt verarbeitet).
- Serialisiert den Baum zurück; unveränderte Knoten werden aus ihrem Original-Markup übernommen.
- **Invariante:** Parse → Serialize ohne Änderung ergibt byte-identischen Inhalt. Fremde/unbekannte Blöcke bleiben unberührt.
- **Invariante:** Parse → Serialize ohne Änderung ergibt byte-identischen Inhalt. Fremde/unbekannte Blöcke bleiben unberührt. Ausnahme beim Speichern (nicht im BlockTree): Divi selbst normalisiert Seiten mit eigenen HTML-/Attribut-Feldern beim Speichern (`module.decoration.attributes`, `module.advanced.html` mit `htmlBefore`/`htmlAfter`/`elementType`) – Divi parst und serialisiert dann die ganze Seite neu; gemeldet als `CHANGED_ON_SAVE` (Ruling S7).
- Reines PHP ohne WordPress-Abhängigkeit (vollständig unit-testbar).
- Attributwerte mit `$variable(...)$` werden beim Serialisieren wie von Divi erwartet escaped (`"` → `"`).
- Umsetzung (D3): eigener Tokenizer mit dem Delimiter-Muster von `WP_Block_Parser` statt `parse_blocks()` – ohne WordPress testbar, jedes Byte zwischen den Delimitern bleibt erhalten. Knoten mit `globalModule`, `divi/global-layout` oder nicht dekodierbaren Attributen sind undurchsichtig: sie werden nicht betreten, nur als Ganzes verschoben, dupliziert oder gelöscht (Bearbeitung ab Etappe 3). Unbalanciertes Markup wird abgelehnt, nie repariert.