Commit Graph
69 Commits
Author SHA1 Message Date
Jan-Niclas LoosenandClaude Sonnet 5.5 be6ca5bee0 feat: Presets als Stapel, Gruppen-Presets und clear_overrides im Composer
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 16:48:58 +02:00
Jan-Niclas LoosenandClaude Haiku 5.5 c3f08e2d33 feat: Outline und Knoten zeigen Gruppen-Presets mit Namen
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 16:41:42 +02:00
janniclas.loosenandClaude Opus 5.5 43c0a78374 fix: Divi-Reiter ohne Divi, Zustandstexte, Schreibsperren und Aufräum-Filter aus der Abschlussprüfung
Behebt die fünf Befunde der Abschlussprüfung von divi-absicherung: Divi-Reiter ohne aktives Divi zeigt nur
noch einen Hinweis (kein Status, keine Prüfung, kein Aufräumen), Zustandstexte werden erst beim Rendern
übersetzt, DiviPages::setFlags() und DiviThemeBuilder::liveId(true) prüfen jetzt selbst die
Freigabesperre, WriteProbe::removeOrphans() löscht nur noch Entwürfe mit passendem Titel, und
wp lit-mcp divi-check --update-reference verweigert sich ohne Entwicklungskopie, bevor irgendetwas
geschrieben wird.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 03:33:16 +02:00
janniclas.loosenandClaude Opus 5.5 c6a1996be6 feat: adapter-level Divi release lock, content-create and site status
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 02:04:49 +02:00
janniclas.loosenandClaude Opus 5.5 fed77b0584 refactor: Divi pages adapter replaces DiviFlags
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 01:08:08 +02:00
janniclas.loosenandClaude Opus 5.5 fc7a84d239 refactor: Divi catalog adapter with manifest
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 00:54:48 +02:00
janniclas.loosenandClaude Opus 5.5 771f3794a0 fix: failed library and layout saves name the revision screen (audit L3)
A failed save of a library entry or Theme Builder layout now carries
restore_url when a revision exists; a partial write's hint points to the
WordPress revision screen and lit-divi-outline instead of
lit/revisions-list and lit/revision-restore, which answer not found for
these types. Pages keep the default hint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-27 23:25:16 +02:00
janniclas.loosenandClaude Opus 5.5 6288acf0e6 perf: DynamicFields reads each post type's fields once per check (review M-12 b)
reason() and note() run per key and type of a body check; they now share one
offered() result per post type, so the default storage callback loads each
pod once instead of per call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-27 23:12:14 +02:00
janniclas.loosenandClaude Opus 5.5 5290a9d81d refactor: one site-wide Divi cache clear and one compatibility row (review M-1, M-2)
DiviSaver's site-wide scopes and the Theme Builder tools now share
DiviThemeBuilder::clearSiteCaches(), which clears the Theme Builder cache
even when ET_Core_PageResource is missing. lit-site-status no longer lists
et_theme_builder_clear_wp_cache twice: the Theme Builder functions row
covers it, and the post-type row uses DiviThemeBuilder::postTypes().

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-27 23:07:57 +02:00
janniclas.loosenandClaude Opus 5.5 dcae2d4b91 fix: name the WordPress revision screen for library and layout revisions (audit L3)
lit/revision-restore takes only content types, so a write to a library entry
or a Theme Builder layout now answers with restore_url (wp-admin
revision.php for its revision_id), also per page in lit/divi-replace-text.
The guides and readme no longer present lit/revision-restore as the way back
for these types.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-27 23:05:05 +02:00
janniclas.loosenandClaude Opus 5.5 7590da4d96 fix: layout-edit dry runs name only editable live templates (ruling Q22)
ThemeBuilderEdits::showing() splits the live templates that show a layout
into editable ones (named under templates) and a hidden_templates count;
confirm is still decided on all of them, so a layout shown only by
uneditable templates still needs confirm. The live split is read once per
write for the confirm check and the field warnings, and field warnings now
also survive a non-confirm refusal such as the P9 backup refusal (M-12 a).
replace-text passes hidden_templates on; the write schema lists it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-27 22:57:45 +02:00
janniclas.loosenandClaude Opus 5.5 3db96a3f5b docs: Theme Builder guide, instructions, readme and spec for 0.5.0
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:05:33 +02:00
janniclas.loosenandClaude Opus 5.5 4f70886006 feat: write results warn about sections built like a library section (spec §5.5)
SimilarSections compares inserted sections (not global instances) with
at most 200 editable library section entries by structure fingerprint,
read lazily once per request, only while library IDs may be named (P13).
lit/library-insert is not asked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 21:16:16 +02:00
janniclas.loosenandClaude Opus 5.5 6be2e65432 feat: Pods fields in Theme Builder bodies checked against the template's post types (E12)
Both forms Divi writes are recognized (ruling Q11): custom_meta_<key> and the
dialog form post_meta_key + select_meta_key. Choice fields get a note about the
stored value, relation fields name IDs (F4). Only templates the user may edit
are checked or named; others are counted (Q21 pattern). Warnings only, also in
the dry run of a live body. DynamicFields extracted from lit/divi-dynamic-sources.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 21:02:53 +02:00
janniclas.loosenandClaude Opus 5.5 42b9b384d5 feat: lit/tb-create-template – disabled templates, all or nothing
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 16:09:49 +02:00
janniclas.loosenandClaude Opus 5.5 b1dd35abd8 feat: Divi tools edit Theme Builder layouts; visible layouts need confirm
TB layouts load through ThemeBuilderAccess (P12), live layouts (attached +
enabled, Q8) need a dry run and confirm (Q9) and get the shared restore point
of VisibleEdits, extracted from GlobalEdits (Q2: backup when revisions are
off, P9 refusals, P14 naming). divi/post-content only in body layouts (F22),
also for nested stored nodes. SaveScope::ThemeBuilderLayout sets Divi flags
and clears site CSS + TB cache.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 15:45:07 +02:00
janniclas.loosenandClaude Opus 5.5 82ef69a99c fix: link targets never reveal library entries by type (ruling Q14)
WpLinkTargets answers the library, internal "_" types and every type
without a Divi post_link_url option like a missing ID; replace-text strips
library IDs only from global subtrees.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 15:13:40 +02:00
janniclas.loosenandClaude Opus 5.5 c7ce924e5a fix: library IDs stay hidden without library access (ruling P13)
Outline, node-get, write outlines, the render summary and replace-text's
opaque-node match name no library ID unless the library group is visible
and the user has divi_library; read-only still names them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 15:00:39 +02:00
janniclas.loosenandClaude Opus 5.5 06f13799de fix: full-form internal links follow the {{link:}} target rules (ruling P10)
One PostIdParser for {{link:}} and select_post; post_link_url_<type> needs a
living, readable, non-attachment target of exactly that type.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 14:54:51 +02:00
janniclas.loosenandClaude Opus 5.5 a413f761e7 refactor: DiviSaver docblocks, one diviWritable field, REASON_CREATE with the constants (M-4, M-5, M-13)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 04:22:04 +02:00
janniclas.loosenandClaude Opus 5.5 ae994ad7bd fix: lit/divi-dynamic-sources offers no {{link:<ID>}} for attachments (M-9)
Also none for internal post types (post_link_url__et_pb_speculation).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 04:20:44 +02:00
janniclas.loosenandClaude Opus 5.5 1c2acdfbbe fix: node-copy from a library entry unifies CRLF; smoke seeds a real CRLF entry (M-1, M-2/M-19)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 04:20:03 +02:00
janniclas.loosenandClaude Opus 5.5 e8f69f67ea fix: bounded usage scan and count-only unreadable posts in global edits (L1, L4)
GlobalUsages runs one query per request (ID, status, type – no content),
checks read_post/edit_post in chunks of 200 with a primed and released post
cache, lists at most 50 readable posts and refuses a confirmed global edit
above 2000 live references (unsupported, edit in the Divi builder). Posts the
caller may not read appear only as hidden_count / blocked_hidden_count;
blocked_ids names readable posts only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 04:12:38 +02:00
janniclas.loosenandClaude Opus 5.5 1b42f9b640 fix: lit/divi-node-copy from a library entry needs the insert rights (I-1, L5)
A library entry as source now requires LibraryAccess::canInsert() (add_library,
writable library group) and follows lit-library-insert's Tools-tab state
(disabled -> unsupported). DiviPageLoader::library() gets its caller.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 04:06:56 +02:00
janniclas.loosenandClaude Opus 5.5 8808a7dcdb feat: lit/divi-dynamic-sources with text-like fields only (E12, F4)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 03:22:13 +02:00
janniclas.loosenandClaude Opus 5.5 6f6c691b5d feat: lit/divi-node-copy between pages and library entries
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 03:12:57 +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 520a639411 fix: global library edits check private and scheduled pages, name the backup on failure (P8a, P14)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 01:25:16 +02:00
janniclas.loosenandClaude Opus 5.5 d66ad36c2e feat: Divi tools write library entries; global ones with dry run, confirm and backup (E2, F2)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 01:15:58 +02:00
janniclas.loosenandClaude Opus 5.5 48ecc627aa feat: Divi read tools accept library entries (library access, E1/E3)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 01:02:25 +02:00
janniclas.loosenandClaude Opus 5.5 ce1ad7c6c3 feat: Divi adapters for roles and library with a live compatibility check
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 00:51:09 +02:00
janniclas.loosenandClaude Opus 5.5 a3a807051f feat: design-system filters and global module ids in node-get/outline (S19)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-27 00:44:58 +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 097fef2e32 fix: final review minors m-1 to m-8
- m-1: the no-change Divi result carries preview_url/edit_url too
- m-2: overview names replace-text's per-page tree_rev
- m-3: divi-shortform documents validation_failed vs not_found for unknown IDs
- m-4: DiviWriter/DiviPageLoader final (no mocks left), neutral NewDraft
  log detail, snapshots are Etappe 4
- m-5: Tested up to 7.1, changelog completed
- m-6: content-create warns when the extras changed the stored layout
- m-7: deleting a whole section outlines nothing instead of the next one
- m-8: uninstall removes the user meta lit_mcp_log_per_page; readme lists it

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:35:24 +02:00
janniclas.loosenandClaude Opus 5.5 b24eb6c3e9 fix(security): no direct HTTP access to plugin PHP files (audit L2, cheap part)
Every file under src/ exits outside WordPress (defined('ABSPATH') ||
exit; after the use block, so a direct request no longer prints a fatal
error with the server path); divi-convert-fixture.php gets the WP_CLI
guard the other integration scripts have. A unit test keeps both true;
PSR1 side-effects warning excluded for the guard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:32:12 +02:00
janniclas.loosenandClaude Opus 5.5 4fe0810049 fix: content-create with divi returns the Visual Builder edit_url (review I-1)
DiviPage::builderUrl() is shared by DiviPage::editUrl() and the content
composer, which now returns edit_url; content-create without divi keeps
the wp-admin link. divi-write-smoke checks the builder URL and, new in
section 9, the M1/M2 gates live (raw Divi markup refused; Divi read-only
stops content-create divi and execute-ability lit/divi-insert).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:19:04 +02:00
janniclas.loosenandClaude Opus 5.5 5f596b8a17 fix(security): content-create divi follows the Divi group switches (audit M2)
The divi input of lit/content-create is refused with unsupported when
lit/divi-insert is disabled or the Divi group is disabled or read-only
(ExposurePolicy::allows as a cross-group gate, asked at execute time).
content-create without divi keeps working. Adds a registrar test that
read-only write tools are never registered, so execute-ability can't
reach them either.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:16:35 +02:00
janniclas.loosenandClaude Opus 5.5 3a5f658f35 docs: DiviSaver names ValueCleaner as the choke point (W2 note)
Docblock only; the kses window description is otherwise 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:37:17 +02:00
janniclas.loosenandClaude Opus 5.5 e04a5304f7 refactor: annotation presets (L5)
The 41 abilities return Annotations::read(), ::write(idempotent) or
::destructive(idempotent) instead of hand-written arrays (same arrays, same
key order).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:33:14 +02:00
janniclas.loosenandClaude Opus 5.5 9ed5d9d2fa refactor: rename for intent (L3)
PostChangeResult is PostSaveFollowUp (the after-save pipeline, not a
result); DiviChange::changed() is record() (moved nodes are recorded but
not touched). cleanValue/withoutControls were renamed in wave 2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:32:18 +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 afa19ffcd8 refactor: DiviTextSearch out of the replace-text ability (M2)
The SQL prefilter and the per-page hit computation move to DiviTextSearch;
DiviPageLoader::loadWritable serves DiviWriter::page and the dry run;
PostStatuses::NOT_CONTENT replaces the duplicated status list (same set,
order in the SQL list only). The private log() wrapper is inlined.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:26:10 +02:00
janniclas.loosenandClaude Opus 5.5 d2cc82f31b refactor: small Divi tree helpers out of DiviWriter (M3)
Block::isPlaceholder replaces the divi/placeholder literals (ledger T4);
NodeIndex::parentBlock/sectionLabel replace the two parentBlock copies and
DiviWriter::sectionLabel; DiviChange::idOf and Outline::summary replace the
duplicated id resolution and outline text; DiviSchema holds the shared
input properties. 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:24:29 +02:00
janniclas.loosenandClaude Opus 5.5 ecfece719d refactor: DiviWriteAbility base and shared DiviErrors (H4)
The five Divi write tools extend DiviWriteAbility (final permission and
execute, change() per tool, shared output schema). DiviErrors holds the
unknown-node, position and compose-error mapping; DiviContentComposer uses
the same mapping (ledger T9). ToolNamingTest skips abstract classes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 18:22:51 +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 6cf0a565e6 refactor: DiviSaver writes content through one guarded writeContent() (M4)
writeContent($postId, $content, ?$before) holds the kses window plus
wp_update_post(wp_slash([ID, post_content]), true); save() passes the restore
point capture as $before, so the window still holds exactly RevisionPoint::
capture() and wp_update_post() (S37), saveNew() and the restore() rollback use it
without. refusal() turns WP_Error / 0 into the message ("returned 0" as before)
for save() and saveNew(); restore() keeps its own RuntimeException text. The
class docblock (the M-5 window description) is 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:15:28 +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 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 874a92c6be 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
2026-09-26 18:05:38 +02:00