docs: Gerüst Plan Etappe 4e – Schleifen-Inhalte, Guides, Audit, Version (Rulings, Schnittstellenvertrag)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
7678d228e7
commit
6203dbb62b
@@ -0,0 +1,102 @@
|
||||
# Etappe 4e – Schleifen-Inhalte, Guides, Security-Audit, Version – Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Der KI-Assistent kann `loop_*`-Inhalte (z. B. Datum, Kategorien, Autor) in Modulen setzen, die in einem Divi-Loop-Element liegen oder selbst eines sind – mit Kontextprüfung aus dem Blockbaum, passenden Fehlermeldungen und der Kurzform `{{loop:<option>}}`; `lit/divi-dynamic-sources` zeigt die passenden Loop-Optionen je Knoten. Außerdem: Guides für alle Etappe-4-Werkzeuge, ein Security-Audit der ganzen Etappe mit Fix-Welle und der Versionssprung auf 0.7.0.
|
||||
|
||||
**Architecture:** Der Schleifen-Kontext wird **im Blockbaum** bestimmt (`LoopContext`: nächster Vorfahre-oder-selbst mit `module.advanced.loop.desktop.value.enable === "on"`, Typ aus `queryType`, Standard `post_types`) und als reines Wertobjekt `LoopScope` an `VariableRef`/`DynamicContentRules` gegeben; dort erlaubt er nur `loop_*`-Optionen der passenden Art (Post/Term/Benutzer/Menü). Die Einspeisung liegt in den Abilities (Insert, Update, Move), nicht in den unteren Schichten: `DiviComposition::inLoop()` liefert eine Komposition mit Kontext. `DynamicContentMap` behält die `loop_*`-Optionen (heute verworfen) gruppiert nach Art; Divi-Aufrufe ändern sich nicht (`DynamicContentOptions::get_options()` liefert sie schon). Guides, Audit und Version sind Dokumentations-/Prozess-Tasks mit klaren Checklisten.
|
||||
|
||||
**Tech Stack:** PHP ≥ 8.1, WordPress 7.x, Divi 5.15.1 (nur über vorhandene Adapter), PHPUnit 9.6 + Brain Monkey + Mockery, phpcs, DDEV + bash/jq-Smokes.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-10-16-etappe-4-designsystem-design.md` (§9 Schleifen-Inhalte, §12 Audit, §14 Schritt 8–9, §1.3 Nr. 5–6, A3, A10, A11), Entscheidungen `docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md` (G11, G12), Recherchen 4e: `docs/superpowers/notes/2026-10-19-etappe-4e-recherche-schleifen.md` (Divi-Interna; Task 10 legt sie ab, bis dahin `…/scratchpad/research-4e-loop-divi.md`, `…/research-4e-loop-plugin.md`, `…/research-4e-guides-audit-version.md` – die Task-Autoren haben sie gelesen), Pläne 4c/4d als Vorbild; der Code ist maßgeblich, wo er von den Plänen abweicht.
|
||||
|
||||
**Voraussetzung:** Branch `etappe-4` (= `main`), Stand 7678d22 (4a–4d fertig), 3368 Unit-Tests grün (`composer test`), `composer lint` sauber.
|
||||
|
||||
**Teil von Etappe 4:** 4a–4d (fertig) → **4e (dieser Plan)** = Abschluss von Etappe 4.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- **Dateien nur im Plugin-Verzeichnis** `/home/janniclas/Projekte/WordPress/wp-content/plugins/WP-LIT-MCP` anlegen oder ändern. Integrationsskripte stellen im EXIT-Trap alles wieder her, was sie ändern (Seiten, Optionen).
|
||||
- PHP ≥ 8.1 ohne 8.2+-Features; WordPress ≥ 6.9; Divi 5.15.1. Namespace `LIT\MCP\` → `src/`, PSR-4, eine Klasse pro Datei, jede Datei mit `defined('ABSPATH') || exit;`, `declare(strict_types=1);`, höchstens 120 Zeichen je Zeile. Nach jedem Task `composer test` und `composer lint` sauber (`composer test` = `php -d memory_limit=512M vendor/bin/phpunit`).
|
||||
- **Divi-Grenze (Divi-Absicherung §3):** Divi-Aufrufe nur in Adaptern aus `DiviAdapters::CLASSES`. 4e braucht **keinen neuen Divi-Aufruf**: `DynamicContentMap` ruft `DynamicContentOptions::get_options()` schon und behält jetzt nur mehr von dessen Ergebnis. Ergibt sich doch ein neuer Divi-Aufruf, Task anhalten und melden (NEEDS_CONTEXT). `LoopScope`, `LoopContext` und die Kontext-Teile der Regeln sind **reines PHP** (keine WordPress-/Divi-Aufrufe).
|
||||
- **Sicherheitsgrenze Typabgleich:** Ein `loop_*`-Wert ist nur erlaubt, wenn der Schleifen-Kontext des Knotens (Art) zur Art der Option passt. Das ist **keine Komfortmeldung**: In Divi liest `loop_post_meta_key_*` in einer Term-/Benutzer-/Menü-Schleife Post-Meta des Objekts mit derselben ID (und Benutzer-/Term-Meta in einer Post-Schleife umgekehrt) – ein Fehlabgleich würde fremde Daten ausgeben (Recherche 4e, Befund 1). Ohne Schleifen-Kontext ist jede `loop_*`-Option abgelehnt.
|
||||
- **Rechte/Sperren unverändert:** Alle Dynamic-Content-Einstellungen laufen weiter durch `ValueCleaner`/`VariableRef`/`DynamicContentRules` mit den Meta-Schlüssel-Regeln S29/S30 (auch für `loop_*_meta_key_`) und `unfiltered_html`-Regeln; die Release-Gate-Fähigkeiten der Tools ändern sich nicht (`dynamic-sources` ist schon überall deklariert, wo Inhalte entstehen).
|
||||
- **Sprache:** Meldungen, Hinweise, Guides, Notizen auf Deutsch mit echten Umlauten; neutral „KI-Assistent“, „MCP-Client“. Tool-Namen: direkte Tools `lit-<name>`, indirekte `lit/<name>` (ToolNamingTest).
|
||||
- **Tests:** Abilities über `LIT\MCP\Tests\Unit\TestCase` (Brain Monkey + Mockery, `assertToolError`); vorbelegte Stubs (`wp_json_encode`, `is_wp_error`, `absint`, `wp_slash`, `wp_unslash`, `sanitize_text_field`) nie mit `Functions\expect()` prüfen. Reine Klassen erben von `PHPUnit\Framework\TestCase`. TDD: Test zuerst, RED-Lauf, Code, GREEN; ein Test muss den Codepfad erreichen, den sein Name behauptet (keine vakuösen Assertions – Lehre aus 4c/4d). Echte Loop-Optionsnamen aus Divi 5.15.1 verwenden (die bisherigen Testdaten `loop_post_meta_key` u. ä. sind erfunden und werden in Task 2 korrigiert). Integration: `cd /home/janniclas/Projekte/WordPress && timeout 900 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/<skript>.sh` (läuft nur mit deaktivierten Plugins `postlos` und `asynclistry`; vorher Zustand mit `ddev exec wp plugin list` notieren und danach wiederherstellen).
|
||||
- **Kein clean-code-review-Skill je Task** (die globale CLAUDE.md-Regel zieht sonst die Antwort des Implementierers an sich); der Gesamtreview folgt am Ende (Task 10).
|
||||
- Commits: `git commit -m "<Nachricht>" -m "Co-Authored-By: Claude <Modellname> <noreply@anthropic.com>"` mit dem Namen des ausführenden Modells; nur die Dateien des Tasks stagen.
|
||||
|
||||
## Entscheidungen für 4e (Rulings, verbindlich)
|
||||
|
||||
- **X1 Kontextregel (Divi 5.15.1):** Der Schleifen-Kontext eines Knotens ist der des **nächsten Vorfahren-oder-selbst**, dessen `attrs.module.advanced.loop.desktop.value.enable` genau `"on"` ist (Tablet/Phone/Hover-Varianten und `true` zählen nicht – das liest auch Divi so). `queryType` fehlt → `post_types`. Verschachtelte Schleifen: die innerste gilt für alles darunter, egal welcher Art. Loop-Einstellungen, die nur über ein **Preset** kommen (`LoopPresetAttrsMap`), erkennt die Baum-Prüfung nicht – das ist eine dokumentierte Grenze (fail closed: ohne erkannte Schleife werden `loop_*` abgelehnt).
|
||||
- **X2 Arten:** `post` (`post_types`, `current_page`), `term` (`post_taxonomies`), `user` (`user_roles`), `menu` (`menus`), `repeater` (`repeater_<acf-name>`, nur falls die Recherche zeigt, dass es eigene Optionen gibt – sonst fällt `repeater_*` unter `post` oder wird abgelehnt; der Task-Autor entscheidet anhand der Recherchetabelle und dokumentiert es). Die Zuordnung Option → Art stammt aus der `group` der Divi-Option („Loop Terms“ → term, „Loop Users“ → user, alle übrigen `loop_*` → post, Menü nach Tabelle); die drei Meta-Schlüssel-Optionen (`loop_post_meta_key_manual_custom_field`, `loop_user_…`, `loop_term_…`) ordnet der Name zu, weil ihre Gruppennamen übersetzt sind. Gespeichert wird der **Array-Schlüssel** der Option (`loop_term_name`), nicht die interne `id`.
|
||||
- **X3 Einspeisung in den Abilities:** `VariableRef` und `DynamicContentRules` bekommen einen optionalen `?LoopScope` (Standard `LoopScope::none()` → verhält sich wie heute, kein bestehender Aufruf bricht). `DiviComposition::inLoop(LoopScope)` liefert eine Komposition mit Kontext (Vorbild `forPost()`). Insert: Kontext der Zielposition (Eltern-Knoten, alle neuen Knoten eines Aufrufs teilen ihn, solange sie nicht selbst eine Schleife einschalten – ein neuer Knoten mit eigener `loop`-Einstellung im Aufruf schaltet den Kontext für seine Nachfahren um: der Task-Autor löst das über die neuen Attribute des Knotens, wenn nötig fail closed = Schleife zählt erst ab dem gespeicherten Zustand und der Fehler nennt das). Update: Kontext des bestehenden Knotens (Vorfahren aus dem Baum, plus eigene neue `loop`-Attribute desselben Aufrufs); **ein Composer je Kontext** (Ruling S31 „ein VariableRef je Komposition“ wird entsprechend umformuliert). Move: Kontext am neuen Ziel; ein Move, der vorhandene `loop_*`-Inhalte aus einer Schleife herausführt oder in eine Schleife anderer Art, erzeugt eine **Warnung** (Divi lässt das zu), keine Ablehnung (A11). `lit-content-create` und Theme-Builder-Anlage haben nie einen Schleifen-Kontext.
|
||||
- **X4 Kurzform:** `{{loop:<option>}}` (Option ohne Präfix, z. B. `{{loop:post_date}}` → `loop_post_date`) mit Einstellungen wie `{{post:…}}` (A10); die Tabelle `LOOP` in `Placeholders` ordnet den Kurznamen den Optionen zu; welche Optionen before/after tragen, kommt aus der `DynamicContentMap`.
|
||||
- **X5 Fehlermeldungen (Spec §9):** ohne Schleife: „Schleifen-Inhalt "%s" braucht eine Divi-Schleife in diesem Modul oder einem übergeordneten Element (Abschnitt, Zeile, Spalte, Gruppe).“; falsche Art: „… passt nicht zur Schleife dieses Elements (Typ: %s).“ (Typ deutsch: Beiträge, Terme, Benutzer, Menü). Die irreführende alte Meldung „nur innerhalb von Divi-Schleifen“ entfällt; Ruling S36 ist abgelöst.
|
||||
- **X6 `lit/divi-dynamic-sources`:** optional `post_id` + `node` (Pfad-ID): zusätzlich die zum Schleifen-Kontext dieses Knotens passenden `loop_*`-Optionen (Feldtypen wie die übrigen Optionen); lesen mit Leserecht auf die Seite (Muster `lit-divi-node-get`, `DiviPageLoader`); ohne Schleife im Kontext: Hinweis „kein Schleifen-Kontext“ statt Liste. Der Konstruktor bekommt den `DiviPageLoader` (Task 9 verdrahtet; die Tool-Fähigkeiten ändern sich nicht).
|
||||
- **X7 Grenzen, dokumentiert statt gelöst:** (a) `module.advanced.loop` ist als Attributpfad schreibbar – Schleifen ein-/ausschalten über `attrs` war schon vorher möglich und bleibt (A3 verbietet nur ein *Anlegen/Konfigurieren-Werkzeug*); (b) globale Module werden nicht betreten – `loop_*` in Bibliothekseinträgen hat keinen Schleifen-Kontext und wird abgelehnt; (c) `StoredNodeGuard`/Kopieren: kopierte Schleifen-Container mit `loop_*`-Kindern bleiben für Benutzer ohne `unfiltered_html` abgelehnt (fail closed), solange die Prüfung nur einen Kontext je Unterbaum kennt; (d) Presets, die eine Schleife einschalten, erkennt die Baumprüfung nicht (X1); (e) ACF-Wiederholungsfelder verlangen die Einstellung `acf_type`, die die Einstellungsregeln heute ablehnen – bleibt außen vor, wenn die Task-Autoren keinen sicheren Weg finden. Alle Punkte kommen in die Audit-Notiz (Task 8) und den Guide (Task 7).
|
||||
- **X8 Guides (G11):** keine Prompts, keine neue Guide-Datei ohne Not; Ablauf „Corporate Design übernehmen“ in `workflows.md`; `divi-design-system.md` bekommt Farben, Variablen, Theme-Optionen, Schriften, Restore je Bereich, Rechte; `overview.md` die vollständige Liste der indirekten Design-Tools und die neuen Fehlercodes (`design_rev`, `rev_mismatch`, `conflict`, `in_use`, `font_*`); `divi-placeholders.md`/`divi-shortform.md` `{{loop:…}}` und die Loop-Regeln. Die Inhalte folgen der Checkliste der Recherche (A) und dem echten Code der Tools.
|
||||
- **X9 Audit (Task 8):** Ein unabhängiger Audit (Opus, Skill `anthropic-skills:security-audit` wenn verfügbar, sonst eigenes Vorgehen) über den Umfang von Spec §12 (Preset-`attrs`/XSS, Font-Upload, Massen-Schreibrechte, Schleifen-Optionen/Meta-Schlüssel/Typabgleich, Snapshot-Restore) im Format der Audits 3a/3b (`docs/superpowers/notes/2026-09-27-etappe-3a-security-audit.md`); Befunde mit Schwere; kritische/hohe Befunde werden in einer Fix-Welle behoben, mittlere nach Urteil, der Rest steht als Restrisiko in der Notiz. Das DNS-Rebinding-Fenster zwischen `allows()` und cURL (W13) gehört in den Audit.
|
||||
- **X10 Version:** 0.6.1 → 0.7.0 (`wp-lit-mcp.php:6`, `src/Plugin.php:48`, `readme.txt:7` plus Changelog-Abschnitt in `readme.txt`); kein Git-Tag, kein Update-Server, kein `CHANGELOG`; `DiviReleases::TESTED` bleibt (5.2.1, 5.14.0, 5.15.1). Spec/§9 nennt 5.14.0 – Task 10 berichtigt auf 5.15.1.
|
||||
|
||||
## Schnittstellenvertrag (Namen und Signaturen, die spätere Tasks voraussetzen)
|
||||
|
||||
Klassen unter `src/Integrations/Divi/Compose/` (Namespace `LIT\MCP\Integrations\Divi\Compose`), Tests unter `tests/Unit/Integrations/Divi/Compose/`, sofern nicht anders genannt. Task-Autoren dürfen Details verfeinern, aber nur mit Eintrag unter `## ABWEICHUNGEN` am Ende ihres Abschnitts.
|
||||
|
||||
```php
|
||||
// LoopScope.php (Task 1) – reines PHP, unveränderlich
|
||||
public const POST = 'post'; public const TERM = 'term'; public const USER = 'user'; public const MENU = 'menu';
|
||||
public static function none(): self;
|
||||
public static function ofQueryType(string $queryType): self; // post_types|current_page → post, post_taxonomies → term, user_roles → user, menus → menu; unbekannt (repeater_… nach X2) → laut Task-Entscheidung
|
||||
public function active(): bool;
|
||||
public function kind(): ?string; // null ohne Schleife
|
||||
public function queryType(): ?string;
|
||||
public function label(): string; // deutsch für Meldungen: 'Beiträge' | 'Terme' | 'Benutzer' | 'Menü'
|
||||
|
||||
// LoopContext.php (Task 1) – reines PHP
|
||||
/** @param array<string, mixed> $attrs Attribute eines Blocks */
|
||||
public static function enabled(array $attrs): bool; // desktop.value.enable === 'on' (X1)
|
||||
public static function ownScope(array $attrs): ?LoopScope; // Schleife an genau diesem Block, sonst null
|
||||
/** @param list<array<string, mixed>> $attrChain Attribute von der Wurzel bis zum Knoten (inklusive) */
|
||||
public static function fromChain(array $attrChain): LoopScope; // innerste gewinnt
|
||||
public static function of(NodeIndex $index, string $nodeId): LoopScope; // Vorfahren-oder-selbst über die Pfad-ID (Namespace BlockTree\NodeIndex)
|
||||
|
||||
// DynamicContentMap (Pages/, Task 2) – loop_*-Optionen bleiben erhalten
|
||||
/** @return array<string, array<string, mixed>> Option => Feldspezifikation, nur loop_*-Optionen der Art $kind */
|
||||
public function loopFields(string $kind): array; // Name final nach Task 2; Task 3–5 nutzen genau diese Methode
|
||||
|
||||
// DynamicContentRules / VariableRef (Task 2) – optionaler Kontext
|
||||
// VariableRef::__construct(DesignSystem $design, ?array $dynamicContent = null, ?LoopScope $loop = null)
|
||||
// DynamicContentRules::__construct(DesignSystem $design, ?array $map, ?LoopScope $loop = null)
|
||||
|
||||
// DiviComposition (Pages/, Task 4)
|
||||
public function inLoop(LoopScope $scope): self; // Klon mit Kontext, Vorbild forPost()
|
||||
|
||||
// Placeholders (Task 3): {{loop:<kurzname>}} → loop_<name>; Tabelle LOOP wie POST
|
||||
// DiviDynamicSourcesAbility (Task 5): __construct(…, ?DiviPageLoader $loader = null); Eingabe post_id + node optional
|
||||
```
|
||||
|
||||
## Dateistruktur
|
||||
|
||||
**Neu:** `Compose/LoopScope.php`, `Compose/LoopContext.php`, `tests/Integration/divi-loop-smoke.sh`, ggf. Fixture unter `tests/Fixtures/divi/` (echte Loop-Seite), Audit-Notiz `docs/superpowers/notes/2026-10-19-etappe-4e-security-audit.md`, Recherche-Notiz `docs/superpowers/notes/2026-10-19-etappe-4e-recherche-schleifen.md`.
|
||||
|
||||
**Geändert:** `Compose/{DynamicContentRules,VariableRef,Placeholders}.php`, `Pages/{DynamicContentMap,DiviComposition,DiviInsertAbility,DiviUpdateAbility,DiviMoveAbility,DiviDynamicSourcesAbility,DiviWriter}.php` (je nach Task), `DiviIntegration.php` (Loader für dynamic-sources), Guides (`docs/guides/*.md`), `wp-lit-mcp.php`, `src/Plugin.php`, `readme.txt`, Spec/Notizen, Tests der genannten Klassen (inkl. `DynamicContentRulesTest`, `VariableRefTest`, `DiviFixtures`).
|
||||
|
||||
## Task-Übersicht
|
||||
|
||||
| # | Task | Ergebnis |
|
||||
|---|---|---|
|
||||
| 1 | `LoopScope`, `LoopContext` | reiner Schleifen-Kontext aus dem Baum |
|
||||
| 2 | `DynamicContentMap` behält `loop_*`; `DynamicContentRules`/`VariableRef` mit Kontext; Meldungen; Fixtures korrigieren | `loop_*` im passenden Kontext erlaubt, sonst Fehler |
|
||||
| 3 | `{{loop:…}}` in `Placeholders`/Kurzform | Kurzform |
|
||||
| 4 | Kontext in Insert, Update, Move; Move-Warnung | Werkzeuge nutzen den Kontext |
|
||||
| 5 | `lit/divi-dynamic-sources` mit `post_id` + `node` | Optionen je Knoten |
|
||||
| 6 | Smoke gegen echtes Divi (Loop-Seite, Speichern, Rendern) | `divi-loop-smoke.sh` |
|
||||
| 7 | Guides | Etappe-4-Werkzeuge und Loops dokumentiert |
|
||||
| 8 | Security-Audit Etappe 4 + Fix-Welle | Audit-Notiz, Fixes |
|
||||
| 9 | Version 0.7.0, Spec-/Notiz-Nachträge, Verdrahtung | Release-Stand |
|
||||
| 10 | Gesamtverifikation, Gesamtreview, Memory | Abschluss Etappe 4 |
|
||||
|
||||
---
|
||||
|
||||
<!-- TASKS-PLACEHOLDER -->
|
||||
Reference in New Issue
Block a user