Commit Graph
36 Commits
Author SHA1 Message Date
janniclas.loosenandClaude Opus 5.5 8fa285a265 fix: the Lottie source is builder-only for users without unfiltered_html (P19)
lottie.innerContent (src → data-lottie-src) makes the frontend load an
animation whose expressions lottie-web runs with eval; it joins the S7 gate
in ValueCleaner, which covers removals and StoredNodeGuard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 02:06:38 +02:00
janniclas.loosenandClaude Opus 5.5 16c6d59725 fix: StoredNodeGuard keeps plain relative links copyable (P18)
A link without scheme and without whitespace, quotes, <, >, backslashes or
control characters passes although esc_url_raw() would prefix http://; every
value with a scheme, and protocol-relative //host, must survive url() unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 01:58:47 +02:00
janniclas.loosenandClaude Opus 5.5 e808832fd3 fix: StoredNodeGuard checks URLs, value keys and library ids strictly (P17)
URLs must be sanitizer-stable, keys inside values follow ValueCleaner::VALUE_KEY,
instances need a digits-only globalModule; state keys reuse AttrWriter::STATES.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 01:56:47 +02:00
janniclas.loosenandClaude Opus 5.5 9d92808918 fix: interactions are builder-only for users without unfiltered_html (P16)
Divi's frontend applies the interaction effect "Attribute" with setAttribute
(e.g. an onclick handler) and sets cookies; *.decoration.interactions joins
the S7 gate in ValueCleaner, which also covers removals and StoredNodeGuard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 01:54:25 +02:00
janniclas.loosenandClaude Opus 5.5 f8275ad325 feat: StoredNodeGuard keeps S7/S26 for nodes moving between posts (E6)
Refuses, for users without unfiltered_html, non-Divi blocks, raw text parts,
unreadable opaque blocks, code modules, builder-only paths (also below the
state value), strings that are not sanitizer-stable and stored $variable
references that VariableRef::canonical() refuses or would change (P2, P3).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 01:42:26 +02:00
janniclas.loosenandClaude Opus 5.5 2700fd615a feat: global instance in the builder's format, deglobalize, library content rules
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 00:53:38 +02:00
janniclas.loosenandClaude Opus 5.5 788943e809 feat: {{link:ID}} placeholder via Divi's post_link_url dynamic content
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 00:32:59 +02:00
janniclas.loosenandClaude Opus 5.5 cda8b025cc feat: explain the type collision on contact fields (trial)
A contact-field item whose type is no allowed child now gets a hint that
type is the node type, with the attrs path (fieldItem.advanced.type) and
the lit-divi-update set alternative, instead of "unbekanntes Modul
divi/email". The metadata fixture gains contact-form/contact-field
(regenerated from Divi 5.2.1, additions only).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:22:13 +02:00
janniclas.loosenandClaude Opus 5.5 482575b732 fix: a nested row gets its own auto label (ledger T3)
A row inside a column is labelled "<Abschnitt> – Zeile (innen)" instead of
repeating its outer row's "<Abschnitt> – Zeile". Guide updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:36:47 +02:00
janniclas.loosenandClaude Opus 5.5 eda592548e fix: builder column classes only – no 4_5, all breakpoints checked (S49)
New rows no longer accept 4_5 (the builder does not offer it). On update,
the flexType of every non-desktop breakpoint (tablet, phone, …) is
format-checked against the builder list like desktop. Guide updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:34:45 +02:00
janniclas.loosenandClaude Opus 5.5 d2c5e82d63 refactor: narrow over-exposed API (L2)
Composer::autoLabel is private (tested through compose()); the
DiviComposition getters used only by tests are marked @internal;
Limits::snapshotCount is documented as reserved; the ShortformWriter test
seam is named $modulesForTests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:31:51 +02:00
janniclas.loosenandClaude Opus 5.5 7963aa19bd test: split DiviWriteAbilitiesTest per tool (T5)
One test class per Divi write tool on a shared DiviWriteAbilityTestCase;
the 30 tests move verbatim (per-test assertion counts identical). Adds the
ledger T4 test for a TreeEditor::move() StructureRules refusal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:27:59 +02:00
janniclas.loosenandClaude Opus 5.5 9da236b0e3 refactor: ColumnWidths holds the column-width rules (H5)
Composer::row uses ColumnWidths::parse; rowWidthsError and its error-state
save/restore are gone. The update width check moves out of
DiviUpdateAbility into ColumnWidths::updateErrors. Behaviour unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:21:03 +02:00
janniclas.loosenandClaude Opus 5.5 3eeb7eee90 refactor: split TextReplacer into HtmlTokens and StoredForms (M1)
- HtmlTokens (static, with the S45 docblock): SPLIT, RAW_OPEN, tokenize(),
  structure(), closes(), unterminated() moved unchanged; new RAW_ELEMENTS list.
- StoredForms: of() (was TextReplacer::forms), mentions(), unicodeEscaped() –
  the DB prefilter forms; TextReplacer::mentions() delegates, the ability's
  candidate query uses StoredForms::of().
- TextReplacer keeps problems(), apply() and the splice/safe() verification;
  safe() still runs the tokenizer and HtmlContext as two independent scans, in
  the same order.
HtmlTokensTest asserts RAW_ELEMENTS === HtmlContext::RAW and that RAW_OPEN opens
exactly those elements. TextReplacerTest and HtmlContextTest unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:14:49 +02:00
janniclas.loosenandClaude Opus 5.5 570c1e6142 refactor: ValueKind enum instead of kind string literals (L1)
enum ValueKind: string { Html, Code, Url, Text } with strictness() and
tightens(self $routed) (the S33a rule formerly AttrWriter/ValueCleaner::tightens
over STRICTNESS). ValueCleaner::clean() takes ?ValueKind, kindOf()/kind()/
unknownKind() return it, the sanitizer match is exhaustive over the four cases;
the "Feldart" message prints ->value, so its text is unchanged. ShortformWriter
drops the identity map CLEAN_KIND for ValueKind::tryFrom($spec['kind']) ??
ValueKind::Text; Composer and ShortformWriter pass ValueKind::Text/Url.
The installed PHPCompatibility sniffs do not know enums: two sniffs are disabled
in ValueKind.php only, with the reason.
Tests: ValueCleanerTest passes/asserts ValueKind cases instead of strings;
+ValueKindTest (full tightens matrix, case values).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:13:23 +02:00
janniclas.loosenandClaude Opus 5.5 62ce99e558 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
2026-09-26 18:11:28 +02:00
janniclas.loosenandClaude Opus 5.5 203d14a2f9 refactor: one VariableRef per composition, design lookups in DesignSystem (H3)
- DiviComposition::references() builds the one VariableRef with the entry's
  live map (S22/S31) and passes it to AttrWriter; Placeholders only needs the
  design system now, Placeholders::references() is gone (no reach-through).
- DesignSystem::colorIdFor()/variableFor() replace the dead colorId()/variable()
  and carry Placeholders' lookup incl. the S13a ambiguity check (byLabel moved,
  messages unchanged); isColorVariable() decides color vs content for {{var:}}.
- DesignSystem::colorReferenceIds() (VariableRef color names) and
  contentVariableIds() (DynamicContentRules design names) replace the two
  inline computations; same lists, same order.
Tests: fixtures/tests get the VariableRef from ComposeFixture::references() or
build it with the map; DesignSystemTest uses the new lookups (+2 tests).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:07:56 +02:00
janniclas.loosenandClaude Opus 5.5 e420145cbe fix: anchored input patterns end at the last character (/D)
Without /D, "$" also matches before a trailing "\n", so "custom_meta_telefon\n",
a color setting "5\n" (stored as sent) or a whole reference followed by "\n"
passed VariableRef. Added /D to every ^...$ pattern that validates client or
admin input:
- VariableRef META_KEY, NUMBER, WHOLE (canonical/typeOf; Placeholders::field
  shares META_KEY)
- Cursor::decode, GuideRepository topic names (MCP input and filter keys)
- ExposurePolicy::sanitize ability keys and group ids, LogTab list filters

Not changed: Placeholders::PATTERN (runs on trim()), Warnings hex checks (hints
only), NodeSummary/AttrPathIndex/LogListTable (stored or Divi data, no client
input). Each fixed pattern has a test with a trailing "\n" that is rejected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:02:51 +02:00
janniclas.loosenandClaude Opus 5.5 3d96a41366 test: shared Divi fixtures in tests/Support (T1)
- DiviFixtures: markup(), givenPost(), schema(), designSystem(), catalog(),
  dynamicContentRegistry(), maliciousRefs() - moved verbatim out of
  DiviPageLoaderTest, DesignSystemTest, ModuleCatalogTest, DynamicContentMapTest
  and VariableRefTest (its malicious() provider now returns maliciousRefs()).
- ComposeFixture moves to tests/Support.
- DiviWriterFixture: modules(), designReader(), composition(), a recording saver(),
  log() and writer(&$saved, &$failures, unfiltered, loader) for the five tests that
  wired the Divi write stack by hand.
No test cases or assertions changed (1397 / 5834).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:47:11 +02:00
janniclas.loosenandClaude Opus 5.5 93d2bde286 fix: Task 8 review round 1 - quote-aware tokenizer, context check, skipped global nodes (S45)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 16:32:00 +02:00
janniclas.loosenandClaude Opus 5.5 d67cd0ede6 feat: lit/divi-replace-text with dry run and per-page tree_rev targets
TextReplacer replaces only in plain-text segments (S8 split), outside
script/style bodies, inserts the replacement cleaned per field kind and
refuses any result whose tags, entities or references differ from the
original plus the inserted text. Unchanged strings are never cleaned.
Per page all or nothing with the tree_rev lock; Throwables are reported
per page (S10).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 16:16:01 +02:00
janniclas.loosenandClaude Opus 5.5 0fddece18d fix: Task 6 review round 2 - builder column classes, sum check only for exact rows, unknown path wins, column structure text (S43)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 15:31:05 +02:00
janniclas.loosenandClaude Opus 5.5 909ac82594 fix: Task 6 review round 1 - admin label overlap, heading default level, structure deny-list, row widths (S41)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 15:26:12 +02:00
janniclas.loosenandClaude Opus 5.5 22bb1528db fix: S40 - short form and attrs on the same path refused for new nodes too
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 15:17:42 +02:00
janniclas.loosenandClaude Opus 5.5 110beb7698 feat: lit/divi-insert and lit/divi-update
All-or-nothing at ability level (S39), duplicate ids refused (S14),
set and attrs on the same path refused (S38), invalid_attr_path error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 15:15:37 +02:00
janniclas.loosenandClaude Opus 5.5 b8cb01812e fix: Task 5 review round 1 - restore point inside the kses window, strict map field types (S37)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 15:08:48 +02:00
janniclas.loosenandClaude Opus 5.5 7762b76305 feat: Divi save pipeline – tree_rev lock, kses-safe save, revision, flags, cache, ToolResult
DiviSaver/DiviWriter/DiviComposition/DiviChange, WpValueSanitizer (S4),
WpMediaResolver, DiviFlags (S11), DynamicContentMap from Divi's live
registry (S22), S31 wiring, S35 control strip at apply() entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 14:56:47 +02:00
janniclas.loosenandClaude Opus 5.5 ea1fd607b2 feat: Divi tree editor and quality warnings
TreeEditor (position/insert/move/remove/duplicate on the BlockTree) and
Warnings (post-write quality checks), plus S18's outline breadth limit
on the 2a Outline/DiviOutlineAbility.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 14:42:26 +02:00
janniclas.loosenandClaude Opus 5.5 8688a02402 fix: Task 3 review round 1 - strip control chars before the sanitizer (S32a), S34 minors
Control characters now go on the raw value in AttrWriter::cleanNode and
VariableRef::setting; after the sanitizer only rejection remains ('<' + VT/FF
no longer turns into a tag). VALUE_KEY anchored with D, exact column-width
fractions, nested rows in columns, specialty columns refused, fullwidth by
catalog category, preset ambiguity in DesignSystem::modulePresetId().

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 14:26:18 +02:00
janniclas.loosenandClaude Opus 5.5 4b72c2bb42 feat: Divi composer – short/full form to blocks with structure rules, presets and labels
Labels, background_color and preset ids go through the choke point (S13);
ambiguous preset names fail with their ids (S13a); fullwidth + rows is an
error. Carry-ins: control characters stripped before the marker check (S32),
kind override may only tighten, reference alt texts dropped, color check via
VariableRef::typeOf (S33).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 14:15:33 +02:00
janniclas.loosenandClaude Opus 5.5 f86cd5b34a feat: Divi short-form writer
ShortformWriter routes every short-form string through AttrWriter::cleanValue()
with the curated field kind (new optional kind override), exposes the color
check for background_color and guards builder-only paths on removal too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 13:59:24 +02:00
janniclas.loosenandClaude Opus 5.5 4832ec1e10 fix: Task 1 review round 4 - meta keys checked after Divi's prefix derivation (S30)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:52:00 +02:00
janniclas.loosenandClaude Opus 5.5 665e603abe fix: Task 1 review round 3 - re-check $variable marker after sanitizing, meta-key rule
Critical-1/S28: sanitizers that strip <x>, NUL or %XX could rebuild the
$variable( marker after the raw check; every cleaned string (AttrWriter
at every depth, VariableRef settings) is checked again. S29: custom_meta_
map names and *meta_key setting values obey META_KEY. Minor-2: malformed
dynamic content maps fail with InvalidArgumentException.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:46:52 +02:00
janniclas.loosenandClaude Opus 5.5 eb99b360b4 fix: Task 1 review round 2 - custom CSS builder-only, no settings on gvid/font references
S26: every css.* path is gated like S7 for users without unfiltered_html.
S27: Divi ignores settings on global variable and theme font references,
so any settings there are rejected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:39:36 +02:00
janniclas.loosenandClaude Opus 5.5 7180690611 fix: Task 1 review round 1 - escaped )$ in reference settings, allow map, routing rulings
I-1: reject )$ in every decoded string setting and in the re-serialized
JSON. S21: string settings cleaned and re-serialized. S22: injectable
dynamic-content allow map (name => setting => field type), loop_* only via
the map. S23: unknown content.innerContent is rich text. S24: keys below
scalar kinds are unknown, ...Url path segments are URLs. S25: isVariable
removed. M-1: real \u escapes in tests, malicious suite as editor, fixture
html() raw for unfiltered_html. M-2: S7 gate at any depth plus
css.advanced.customCss.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:37:56 +02:00
janniclas.loosenandClaude Opus 5.5 1ea2e16c49 feat: Divi placeholders, strict $variable references and full-form attribute writer
VariableRef parses, validates and re-serializes every $variable(...)$ value
(S1/S2/S12); AttrWriter routes strings by catalog kind incl. per-subName
composite kinds and key-name URL fallback (S3/S3a), rejects odd value keys
(S13) and Divi HTML/attribute fields without unfiltered_html (S7);
ambiguous design-system labels fail with their ids (S13a); R2 finding
recorded (S5).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:23:59 +02:00