Commit Graph
277 Commits
Author SHA1 Message Date
janniclas.loosenandClaude Opus 5.5 5035092805 feat: lit/library-save creates library entries like Divi (E4)
Reuses NewDraft for insert and rollback (P6), requires edit_global_library
for scope global (P7), refuses opaque and global roots before wrapping (P11)
and checks the node with StoredNodeGuard. The source page is never written.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 02:12:50 +02:00
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 b7c714939d feat: group Divi-Bibliothek with lit/library-list
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 01:29:37 +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 681bb755ff fix: library entries read as not found without the library right (P12)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 01:07:53 +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 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 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 0531a5ffb1 docs: lit-divi-catalog shows both result shapes (trial)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:23:47 +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 26108bc9a9 feat: short-form level for every module with a heading title (trial)
contact-form, cta, toggle, accordion-item, slide, number-counter and
team-member (name) take level like heading/blurb; paths from Divi's
conversion outlines, checked live with divi-shortform-check.php. A test
keeps the guide's field table in sync with ShortformMap.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:20:23 +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 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 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 dca1bd67f8 refactor: integration labels, AdminPostAction, ExposurePolicy key parsing (M9)
Integrations name their own admin group (Integration::label, registry
labels()); ToolsTab no longer hard-codes ids and explains why it re-applies
withGroups. AccessTab and LogTab share AdminPostAction (guard, redirect,
terminate seam). ExposurePolicy parses option keys in one place, has one
isRequired() check and a clear $groups docblock (null vs []).

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:15 +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 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 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
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 4114bdb805 refactor: PostRow::update and PostGuard::assertNoMissedSchedule (M5)
- PostRow::update(id, columns, failure) / ::save(): the slashed wp_update_post of
  internal row writes, refusals as RuntimeException '<failure>: <why>'. Used by
  ContentUpdate::restoreRow, PostCloner::updateRow, MediaMetaWriter apply/restore.
- PostGuard::assertNoMissedSchedule(postId, message) replaces the re-read + check in
  ContentUpdate::restoreRow, PostCloner and DiviSaver::restore (DiviSaver keeps its
  own kses window; M4).
- PostCloner::restore uses Rollback::attempt.
Log messages unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:42:33 +02:00
janniclas.loosenandClaude Opus 5.5 8e5ba9eac4 refactor: WriteFailed named constructors, Rollback runner, Errors read-back helpers (H6)
- WriteFailed::rolledBack()/partial() replace the 11-parameter constructor (now
  private); the hint/state of either error are plain $hint/$state.
- Rollback::attempt(...steps) runs every undo step and collects failure messages;
  used by ContentCreate::discard, ContentUpdate::rollBack, MediaUpdate::rollBack and
  DiviSaver::restore.
- Errors::clientMessage/codeOf/fieldsOf replace DiviReplaceTextAbility::reason,
  the JSON digging in MediaUpdateAbility::failure and ContentCreateAbility::fields.
- Errors docblock documents the exception/WP_Error split.
- ContentCreateAbility::discard takes the client reason as a parameter (both callers
  still pass REASON_EXTRAS; changing the Divi text needs a ruling).

Error payloads unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:40:32 +02:00
janniclas.loosenandClaude Opus 5.5 fb6eabf80c refactor: one wpdb accessor style (L4)
The three lazy db() methods use the $this->db ?? $GLOBALS['wpdb'] one-liner that
SampledMetaSource, MediaUsageFinder and ConnectionRepository already use.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:36:23 +02:00
janniclas.loosenandClaude Opus 5.5 b6984d3f9a refactor: one logging helper LIT\MCP\Log (M6)
Log::failure(context, detail) keeps the '[wp-lit-mcp] <context> failed: <detail>'
format of the nine former error_log() sites; Log::line() covers the change log line.
One phpcs ignore instead of eleven.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:36:01 +02:00
janniclas.loosenandClaude Opus 5.5 10c98cd3c6 docs: Divi guides shortform/placeholders, hints name the write tools; test: Divi write end-to-end; version 0.3.0
- Hints (PostPresenter::DIVI_NOTE, outline, catalog) and server instructions (tree_rev rule)
  name the Divi write tools; guides divi-shortform and divi-placeholders, additions to
  divi-structure, divi-attrs, overview (tool groups, 'nur lesen'), workflows, content-types.
- D7 wording and the S7 exception in readme, guides and spec 1.3/5.2; readme 0.3.0.
- ToolNamingTest: every referenced guide topic exists; overview lists exactly the topics.
- tests/Integration/divi-write-smoke.sh: all write tools via OAuth, editor without
  unfiltered_html with benign probes (S6), Divi's own save filters on the new fixture
  synthetic-divi-filters.html (S7); admin-render.php accepts the new wrap class.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 17:13:59 +02:00
janniclas.loosenandClaude Opus 5.5 5965e32acb feat: lit/content-create with Divi short form
New input divi (1-30 sections, short form like lit-divi-insert) builds a Divi 5
layout in one divi/placeholder. The whole description is checked before the
insert (Divi's map for "no entry yet"); the draft is inserted with its normal
WordPress fields only, then DiviContentComposer composes again bound to the new
entry and stores it via DiviSaver::saveNew (same kses window as save(), strict
DiviFlags). A refusal or failure deletes the draft. Result adds tree_rev and the
outline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:46:50 +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 ede0221b50 fix: Task 7 review round 1 - copy the raw admin label verbatim on duplicate
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:02:33 +02:00
janniclas.loosenandClaude Opus 5.5 17402549c5 test: Task 7 review - broaden move/delete/duplicate coverage, fix outline for delete-only changes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 15:47:01 +02:00