refactor: split VariableRef into ControlChars and DynamicContentRules (H2)

- ControlChars::strip()/stripDeep() (was VariableRef::withoutControls/Deep) with
  the S32/S32a/S35 docblock; callers AttrWriter, ShortformWriter, TextReplacer and
  the setting cleaner use it.
- DynamicContentRules (design system + dynamic content map): fieldsFor() (was
  contentFields: meta prefix -> map -> loop_ -> design names -> unknown),
  cleanSetting() (was setting(): raw strip -> raw marker -> field type ->
  final-string check -> meta key check, same order), one private method per
  field family (textSetting, selectPostSetting, scalarSetting), isMetaKey(),
  checkMap(), and the constants DEFAULT_DYNAMIC_CONTENT, META_SETTINGS,
  DESIGN_SETTINGS, META_KEY.
- VariableRef keeps the syntax: const END = ')$' replaces the six literals,
  payloadOf() is shared by typeOf() and canonical(); unknownName() is public for
  the rules. Error messages unchanged.
Tests unchanged apart from a class name in DynamicContentMapTest.

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:05:38 +02:00
co-authored by Claude Opus 5.5
parent e420145cbe
commit 874a92c6be
9 changed files with 403 additions and 316 deletions
@@ -49,7 +49,7 @@ final class ShortformWriter
public function apply(string $type, array $attrs, array $values, string $field): array
{
// Ruling S35: controls go before placeholder resolution and trim ("{\0{post:title}}").
$values = VariableRef::withoutControlsDeep($values);
$values = ControlChars::stripDeep($values);
$fields = $this->modules === null ? ShortformMap::fields($type) : ($this->modules[$type] ?? []);
$errors = [];
foreach ($values as $name => $value) {
@@ -200,7 +200,7 @@ final class ShortformWriter
{
// Controls are stripped at the choke point before its marker check (S32a) – so a
// NUL-split marker counts as a marker here too, and a canonical result is refused.
if ($alt === '' || VariableRef::contains(VariableRef::withoutControls($alt))) {
if ($alt === '' || VariableRef::contains(ControlChars::strip($alt))) {
return '';
}