refactor: ValueCleaner is the named choke point (H1)

The choke point moves out of AttrWriter into ValueCleaner (catalog, the
composition's VariableRef, sanitizer): clean() (was AttrWriter::cleanValue),
kindOf(), guardPath() and the private cleanNode/kind/unknownKind/tightens with
VALUE_KEY, URL_KEY, URL_SEGMENT, STRICTNESS, BUILDER_ONLY (its docblock now sits
on it; STRUCTURE stays in AttrWriter). Code of the pipeline moved unchanged.
- ValueCleaner's docblock is the one canonical description: steps 0-8 with their
  rulings in code order, invariants I1-I3; the step comments in cleanNode carry
  the same numbers. AttrWriter/ShortformWriter/Composer/TextReplacer docblocks
  point there and keep only their own rules.
- ValueCleaner::textReplacementFor($module) holds the D10 rule that was a
  closure in DiviReplaceTextAbility::replace (text/html only, clean() by kind).
- AttrWriter, ShortformWriter and Composer take the ValueCleaner; DiviComposition
  builds one per composition (cleaner()).
Tests: the eight AttrWriterTest methods that only exercised cleanValue/kindOf
moved verbatim to ValueCleanerTest (clean/kindOf, ComposeFixture::cleaner());
mixed tests call the cleaner in place; +1 test for textReplacementFor. Every
provider row and per-test assertion count unchanged.

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 18:11:28 +02:00
co-authored by Claude Opus 5.5
parent 203d14a2f9
commit 62ce99e558
13 changed files with 552 additions and 447 deletions
+15 -10
View File
@@ -11,6 +11,7 @@ use LIT\MCP\Integrations\Divi\Compose\Placeholders;
use LIT\MCP\Integrations\Divi\Compose\ShortformWriter;
use LIT\MCP\Integrations\Divi\Compose\StructureRules;
use LIT\MCP\Integrations\Divi\Compose\TreeEditor;
use LIT\MCP\Integrations\Divi\Compose\ValueCleaner;
use LIT\MCP\Integrations\Divi\Compose\ValueSanitizer;
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
use LIT\MCP\Integrations\Divi\Compose\Warnings;
@@ -20,9 +21,9 @@ use LIT\MCP\Integrations\Divi\Schema\DiviModules;
/**
* Wires the pure Compose classes with the live catalog, design system, Divi's dynamic content
* registry and the WordPress sanitizer/media lookup – once per request and entry. Ruling S31:
* ONE VariableRef (carrying the live dynamic content map of the entry, S22) and ONE Placeholders
* instance are shared by AttrWriter and ShortformWriter, and the same AttrWriter serves
* ShortformWriter and Composer.
* ONE VariableRef (carrying the live dynamic content map of the entry, S22), ONE Placeholders
* and ONE ValueCleaner instance are shared by AttrWriter, ShortformWriter and Composer, and the
* same AttrWriter serves Composer and the abilities.
*
* forPost() binds the composition to the entry being written (DiviWriter::composition() does it
* for the abilities); unbound, VariableRef's default map applies. Not final so abilities can be
@@ -33,6 +34,7 @@ class DiviComposition
private ?int $postId = null;
private ?VariableRef $references = null;
private ?Placeholders $placeholders = null;
private ?ValueCleaner $cleaner = null;
private ?AttrWriter $attrWriter = null;
private ?Composer $composer = null;
@@ -52,6 +54,7 @@ class DiviComposition
$bound->postId = $postId;
$bound->references = null;
$bound->placeholders = null;
$bound->cleaner = null;
$bound->attrWriter = null;
$bound->composer = null;
@@ -73,14 +76,15 @@ class DiviComposition
return $this->placeholders ??= new Placeholders($this->design->read());
}
/** The one choke point of this composition (ValueCleaner, rulings S1–S3, S7, S13, S28, S32). */
public function cleaner(): ValueCleaner
{
return $this->cleaner ??= new ValueCleaner($this->modules->catalog(), $this->references(), $this->sanitizer);
}
public function attrWriter(): AttrWriter
{
return $this->attrWriter ??= new AttrWriter(
$this->modules->catalog(),
$this->placeholders(),
$this->references(),
$this->sanitizer
);
return $this->attrWriter ??= new AttrWriter($this->modules->catalog(), $this->placeholders(), $this->cleaner());
}
public function composer(): Composer
@@ -88,8 +92,9 @@ class DiviComposition
return $this->composer ??= new Composer(
$this->modules->catalog(),
$this->rules(),
new ShortformWriter($this->placeholders(), $this->media, $this->attrWriter()),
new ShortformWriter($this->placeholders(), $this->media, $this->cleaner()),
$this->attrWriter(),
$this->cleaner(),
$this->design->read(),
$this->modules->builderVersion()
);