Commit Graph
100 Commits
Author SHA1 Message Date
janniclas.loosenandClaude Opus 5.5 e0ab6f53e5 docs: D11 superseded by the single Divi save path (Task 9)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:17:55 +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 b044cacc9d fix: admin UI review round 1 (S47) - Art labels, usage only on success, result fallback
- Art column: only lit/media-delete and lit/divi-delete say "löscht",
  lit/post-trash "Papierkorb", other destructive tools "überschreibt".
- ConnectionUsage records only responses with status < 400 (mcp-oauth answers
  auth failures with WP_REST_Response 401, not WP_Error).
- Log result column: translated "unbekannt" instead of the raw value.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:02:39 +02:00
janniclas.loosenandClaude Opus 5.5 843f45aeec feat: admin settings page UI/UX pass with WordPress-native patterns
- Header: h1 + logo in a wrapping flex row, hr.wp-header-end before the tabs,
  aria-current on the active tab; own CSS/JS enqueued only on this screen.
- Verbindung: ordered "So verbindest du Claude" steps, copy button (core
  clipboard.js + wp.a11y.speak), Site-Health-style status list, German details.
- Zugriff: roles as form-table fieldset, connections with site date format,
  empty-state row, confirm on revoke; ConnectionUsage records last use via
  WP_Application_Passwords::record_application_password_usage (once a day).
- Tools: plain-language legend, counter as inline notice, groups as core
  postboxes (postbox.js toggles, per-user closed state, summary when closed),
  fixed column widths with new "Art" column, submit above and below.
- Protokoll: site-timezone times with UTC tooltip, result icons, filters with
  labels in the list table nav, retention link to Limits.
- Limits: short labels with units and descriptions; snapshot count hidden but
  preserved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:50:58 +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 3efcfaabec merge: tool groups with inherited mode and 'nur lesen' per group
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:32:39 +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 bb85868192 fix: answer /.well-known OAuth discovery without a canonical redirect; smoke checks the real path
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:27:25 +02:00
janniclas.loosenandClaude Opus 5.5 776dbf8f4a feat: group tools by integration with inherited group mode and "nur lesen" switch
Tools tab shows one block per integration with a group mode (inherited by its
tools) and a read-only switch that disables all non-readonly abilities of the
group. ExposurePolicy resolves tool override > group override > tool default;
lit/guide keeps its default, direct-only tools fall back to direct under a
group "indirekt". Group entries live in lit_mcp_exposure under "@group:<id>"
and "@readonly:<id>"; the old flat format stays valid. The ability->group map
comes lazily from IntegrationRegistry (Divi is not loaded at plugins_loaded).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:17:05 +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
janniclas.loosenandClaude Opus 5.5 acd57c40c0 feat: lit/divi-move, lit/divi-delete, lit/divi-duplicate-node
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 15:38:38 +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 68ca4f45ec feat: Loosen-IT logo top right on the settings page
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 15:26:44 +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
janniclas.loosenandClaude Opus 5.5 395e9644ac docs: Etappe 2a rulings
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:16:30 +02:00
janniclas.loosenandClaude Opus 5.5 c24b91d8a5 fix: anchor Divi name/id patterns at string end, number-advanced stays unknown, doc wording
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:16:20 +02:00
janniclas.loosenandClaude Opus 5.5 4356e69bb9 test: update divi-read-smoke.sh for node-get's short type name + rev
node-get's "type" is now the short module name ("heading"), matching
lit-divi-outline, instead of the namespaced "divi/heading"; the smoke test's
own hardcoded expectation is updated to match, and now also checks the new
post-level rev is present.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:12:50 +02:00
janniclas.loosenandClaude Opus 5.5 45dcd9176c fix: divi-node-get rev + short type, divi-outline id validation
- lit/divi-node-get now also returns the post-level rev (Rev::ofPost, same
  optimistic lock lit-content-update needs) next to tree_rev, and reports a
  node's type as the short name ("button"), matching lit-divi-outline instead
  of the namespaced "divi/button". description() notes that $variable(...)$
  values shown are re-validated when written, never taken over unchecked.
- lit/divi-outline validates its optional `id` input against the same
  path-ID pattern lit-divi-node-get uses (a malformed id is now a validation
  error, not a "not found"), and its outputSchema's per-node `required` list
  now includes info/presets/children, which are always present.
- NodeSummary::label() (the admin label shown in outlines and node-get)
  whitespace-normalizes and bounds its result via Text::shorten(), like every
  other summary string, instead of returning a raw, unbounded value.
- docs/superpowers/notes/.../etappe-2a-abnahme.md corrected: only a global
  module is "nur lesbar" - a specialty section is a normal, editable
  divi/section block; the outline just marks it "[specialty]".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:12:50 +02:00
janniclas.loosenandClaude Opus 5.5 73433ae862 feat: per-subName attribute-path kinds for composite Divi paths
lit/divi-catalog's path_kinds previously named one kind per whole attribute
path, built from Divi's metadata component name alone. For a composite path
(Divi groups several named sub-fields, "subName", under one path – e.g.
buttonOne.innerContent holds "text" and "linkUrl") that was wrong: the whole
path got one guessed kind (or none), even though the sub-fields can differ
(fullwidth-header's buttonOne/buttonTwo.innerContent -> "text", though it
also carries a "linkUrl"; image.innerContent -> "image", though the value
also carries alt/id/titleText the metadata never names on its own).

AttrPathIndex::kinds() now resolves a composite path to a map
subName => kind instead, preferring Divi's features.dynamicContent.type over
the (often shared/generic) field component for a named sub-field – this is
what tells a button's plain text sub-field apart from its linkUrl sub-field,
both of which use the same generic "divi/text" component. A plain (non-
composite) path is unaffected. Any subName the metadata never names (or that
disagrees between candidates) is therefore simply absent from the map, and
ModuleCatalog::pathKind() defaults it, like every unresolved path, to
"unknown" - the strictest routing for 2b's sanitizer, never a guess.

ModuleCatalog::pathKind() gains an optional $subName parameter to read a
composite path's map. docs/guides/divi-attrs.md documents the new shape and
that lit-divi-catalog without `module` lists all modules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:12:49 +02:00
janniclas.loosenandClaude Opus 5.5 4137882008 fix: harden the Divi BlockTree against opaque/invalid writes
Final fix wave for stage 2a, load-bearing for 2b's writers:
- Block::setAttrs()/Container::insertChild()/removeChild() refuse on an opaque
  block (undecodable attrs or a global module) instead of silently corrupting it.
- Container::append() is now @internal (Parser only) and refuses on a void
  block, so a child can no longer be silently dropped from the serialized output.
- Block::create() validates the block name against namespace/block-name.
- Json::decodeObject() returns null (-> opaque) for a top-level object whose
  keys are a plain list ("0","1",...) rather than returning [] and letting the
  Serializer re-encode it as a corrupting JSON array.
- AttrMap::set() rejects empty path segments and keeps an existing stdClass
  value an object when rewrapping it (no silent object->list flip); AttrMap::get()
  is updated in step so it can still read back through such an object.
- Block::copy() deep-clones stdClass attribute values instead of sharing them.
- Json::encode()'s existing JsonException-on-invalid-UTF-8 behaviour is documented.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:12:49 +02:00
janniclas.loosenandClaude Opus 5.5 f433fef8f7 feat: Divi hints point to the read tools; guides divi-structure/attrs/design-system; test: Divi read end-to-end
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 12:41:42 +02:00
janniclas.loosenandClaude Opus 5.5 b3cd0eee62 fix: bound render-summary strings, restore postdata globals, drain buffers
Review fix round 1 for lit/page-render-summary: extract shared Text::shorten()
(reused by NodeSummary), replace data: URIs with a short marker, prefer
data-src/data-lazy-src over placeholder src, dedup nested p/li/blockquote
text and ignore script/style/noscript content, restore every setup_postdata
global (not just $post), drain output buffers robustly around the_content,
and disclose that rendering can trigger oEmbed/network requests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 12:31:20 +02:00
janniclas.loosenandClaude Opus 5.5 2e9acd686a feat: lit/page-render-summary – rendered content as structured excerpt
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 12:21:23 +02:00
janniclas.loosenandClaude Opus 5.5 d27504a750 feat: lit/divi-outline and lit/divi-node-get with tree_rev
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 12:14:38 +02:00
janniclas.loosenandClaude Opus 5.5 a54a337e0b feat: read the Divi design system live – lit/divi-design-system
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 12:06:15 +02:00
janniclas.loosenandClaude Opus 5.5 02ae76d4f3 fix: stop guessing a kind for composite Divi attribute paths
Review fix round 1 on the lit/divi-catalog task:
- AttrPathIndex::collectKinds() no longer records a kind from a
  component.type === "group" node (a composite of several sub-kinds,
  e.g. button.innerContent = text + link + target), and kinds() now
  drops any attrName with more than one distinct candidate kind
  instead of last-write-wins (e.g. imageIcon.innerContent had onoff +
  icon + image sub-fields sharing one attrName). Both previously
  produced a wrong-but-present kind, which is more dangerous for 2b's
  sanitization routing than "unknown". Verified against the committed
  blurb/button/fullwidth-header fixture entries and live against the
  full 100-module catalog.
- AttrPathIndex::fromDefaults() now builds on AttrMap::flatten()
  instead of a parallel walker.
- ModuleCatalog::shortName() replaces the duplicated
  substr($name, strlen('divi/')) in DiviCatalogAbility.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 12:00:35 +02:00
janniclas.loosenandClaude Opus 5.5 2f4dcf9ce4 feat: Divi module catalog, attribute paths, short form and lit/divi-catalog
Adds per-path attribute kind (text/richtext/code/url/image/color/enum/
onoff/number/list/icon), derived from Divi's field component metadata
where an attribute path names one unambiguously (module.paths() itself
is unchanged; kinds() is a new, separate lookup exposed as path_kinds
on lit/divi-catalog's module detail). This is not in the task-4 brief;
it is added because 2b's sanitization routing (ruling R3) needs a
metadata-derived field kind per attribute path. Verified live against
the full 100-module catalog (564/4358 paths resolve a kind; the rest
default to "unknown" and fall back to 2b's key-name routing).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 11:48:27 +02:00
janniclas.loosenandClaude Opus 5.5 08d70abfc7 feat: Divi path IDs, attribute paths and outline
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 11:35:03 +02:00
janniclas.loosenandClaude Opus 5.5 8222240ed6 feat: Divi BlockTree – byte-exact parser/serializer with round-trip fixtures
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 11:26:14 +02:00
janniclas.loosenandClaude Opus 5.5 c9b31a61da docs: fix review round 1 - tree_rev in save pipeline, single-source D7 in Sec 10/11.3/14
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 11:24:58 +02:00
janniclas.loosenandClaude Opus 5.5 040a60ff18 docs: spec - tree_rev lock (P16), tool budget and decisions for Etappe 2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 11:20:16 +02:00
janniclas.loosenandClaude Opus 5.5 703fecb6d4 docs: implementation plans Etappe 2a (Divi lesen) and 2b (Divi schreiben)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 11:07:49 +02:00
janniclas.loosenandClaude Opus 5.5 ef445937e5 docs: record stage 1b rulings and deferred items
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 05:16:51 +02:00
janniclas.loosen ff0ab049e4 test: media smoke - direct-only download, file-name search 2026-09-26 05:10:25 +02:00
janniclas.loosen 50f0f98c8e chore: uuid string guard in revoke, docblock placement, stricter schedule warning test 2026-09-26 05:07:35 +02:00
janniclas.loosen 6ed63ec008 docs: readme - MEDIA_TRASH constant, developer filters, multisite super admins 2026-09-26 05:07:35 +02:00
janniclas.loosen 14b9508dfc fix: menu location assignment is destructive, neutral download hint in media preview 2026-09-26 05:06:53 +02:00
janniclas.loosen 66663daa99 fix: slash media title/alt on import, keep sideload errors out of client responses 2026-09-26 05:06:34 +02:00
janniclas.loosen 648d53cb96 fix: hide direct-only abilities from the adapter meta-tools 2026-09-26 05:05:48 +02:00
janniclas.loosen 8d0979cb73 fix: connections only manageable for users the admin may edit 2026-09-26 05:05:27 +02:00
janniclas.loosen 9634ca740d fix: media search also matches file names 2026-09-26 05:05:10 +02:00
janniclas.loosen 28bb033a8b fix: cap Base64 media transfers at 10 MB, upload limit at most 32 MB 2026-09-26 05:04:38 +02:00
janniclas.loosenandClaude Opus 5.5 7f58301a71 docs: readme with site-wide effects, media guide; test: media/admin end-to-end; version 0.2.0
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 04:51:11 +02:00
janniclas.loosenandClaude Opus 5.5 251934ce53 feat: deferred 1a items - menu_rev lock, lit_mcp_guides filter, warnings, schedule guard precision
Task 14 (rulings P17/P18, Q10):
- lit/menu-get returns menu_rev (Rev::ofMenu over id, parent, position,
  title, url, object, object_id); lit/menu-set-items requires it, refuses
  a stale one with rev_mismatch (hint: lit/menu-get) and returns the new
  menu_rev. Errors::revMismatch takes an optional message/hint.
- GuideRepository: filter lit_mcp_guides adds/overrides topics; invalid
  topic names and non-string content are ignored, files stay the default.
- lit/post-trash names front page, posts page and linking menus in the
  confirmation and as warnings; lit/menu-assign-location needs confirm
  to remove an assigned menu and names it; lit/post-schedule warns that
  a private entry becomes public; Divi hints no longer point to
  non-existing lit-divi-* tools.
- PostGuard::missedSchedule: 60 s publish window, zero GMT date falls
  back to get_gmt_from_date(post_date); post-schedule refuses dates
  within that window.
- write_failed after a clean rollback that keeps a revision/backup says
  "Die Aenderung wurde nicht gespeichert (... bleibt erhalten)" via the
  new state parameter (Errors::writeFailed / WriteFailed rolledBackState).
- wp-basis-smoke: menu_rev flow, unassign confirm, trash/menu and
  private-schedule warnings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 04:33:15 +02:00
janniclas.loosenandClaude Opus 5.5 7035448f7d feat: clean up own data on deactivation and uninstall
Deactivation now clears the endpoint-check transient and the cached rewrite
rules (forcing WordPress to regenerate them without this plugin's routes on
the next request) and activation asks mcp-oauth to re-flush its own rules.
uninstall.php removes all own options/transient/cron/table across every site
of a network, delegating to the new self-contained LIT\MCP\Uninstaller class
so it works without the plugin's autoloader and is unit-testable on its own.
mcp-oauth's data (JWT secret, rewrite marker, refresh-token meta, application
passwords) is left untouched, as is every post/attachment/revision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 04:21:23 +02:00
janniclas.loosenandClaude Opus 5.5 522f5bbd19 feat: change log tab with filters, revision links and clear button
ChangeLog gains query()/abilities()/clear() (filters: ability, user,
result, date range - all optional and additive). LogTab/LogListTable
render the "Protokoll" admin tab: WP_List_Table with pagination, links
to the post/attachment/nav_menu edit screen by object_type, a revision
link shown only when the user may edit the revision's parent post, and
a nonce+manage_options-gated "Protokoll leeren" with a JS confirmation
and highlighted 'partial' rows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 04:12:13 +02:00
janniclas.loosenandClaude Opus 5.5 9104989c43 fix: neutral hint form for remaining client-facing tool mentions, Limits bounds accessor
Fix round 1 (task 10 review):
- FieldValidator's "unknown field" validation error (reaches the
  client via validation_failed's fields) and PostPresenter's
  DIVI_HINT (surfaced in the result's hints array) still named
  lit-content-types/lit-content-update as plain lit-<name>; converted
  both to the neutral "lit/<name> (direkt: lit-<name>)" form like the
  other hints from task 10. Re-grepped src/ for any other client-facing
  error/field/hints text naming a direct-by-default tool as lit-<name>
  (descriptions/schemas excluded per Q11c) - none left.
- Added Limits::bounds() (field => [min, max]) and made LimitsTab use
  it for the number inputs' min/max instead of a second, hardcoded
  copy of the same bounds.
- LimitsTab now shows a description note when the configured upload_mb
  exceeds wp_max_upload_size() (PHP's upload_max_filesize/post_max_size),
  since WordPress would refuse a larger upload before this limit ever
  applies.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 03:57:15 +02:00
janniclas.loosenandClaude Opus 5.5 bca35f6166 feat: tools and limits settings tabs
Adds Einstellungen → LIT MCP → Tools (direct/indirect/disabled per
ability, only ExposurePolicy-allowed modes offered) and → Limits
(upload size, log retention, snapshot count), wired into Plugin.php.

Ruling Q11b (carried from task 6): overview.md notes that direct
visibility is configurable; hints in src/ that name a tool which is
direct by default but can be switched to indirect (content-get,
content-query, content-types, post-publish, media-search) now use the
neutral "lit/<name> (direkt: lit-<name>)" form so they stay correct
after an admin override. lit/guide (always direct) and the
media-preview/-download DIRECT_ONLY tools are unaffected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 03:47:40 +02:00
janniclas.loosenandClaude Opus 5.5 eee1840411 feat: access tab with allowed roles and connection revocation
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 03:33:54 +02:00
janniclas.loosenandClaude Opus 5.5 169d5772b5 fix: discovery admin notice fires on cached 'recommended' too, guarded against oauth/permalink early returns
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 03:30:11 +02:00
janniclas.loosenandClaude Opus 5.5 f084c47311 feat: settings page with connection tab, admin notices and site health test
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 03:21:56 +02:00
janniclas.loosenandClaude Opus 5.5 2674bf2ed1 feat: hide tool list and instructions from users without LIT MCP access
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 03:13:05 +02:00
janniclas.loosenandClaude Opus 5.5 2f8a9f0c61 feat: configurable exposure policy for direct, indirect and disabled tools
Introduces ExposurePolicy (option lit_mcp_exposure) deciding per ability
whether it is a direct MCP tool, only reachable via mcp-adapter's
execute-ability meta-tool, or disabled entirely (not registered with the
Abilities API at all). Ability::isDirect() remains the default when no
override applies. lit/guide can never be disabled or made indirect;
lit/media-preview and lit/media-download (image/embedded-resource results)
can never be made indirect, only direct or disabled.

AbilityRegistrar and ServerRegistrar take an optional ExposurePolicy and
use it to filter registered abilities / compute the direct tool list.
IntegrationRegistry::directAbilityNames() is removed (superseded by
ExposurePolicy::directNames()). InstructionsBuilder now also takes the
policy and lists the currently direct tools by name (lit-<name>), noting
the rest are reachable via mcp-adapter-execute-ability as lit/<name>.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 03:04:14 +02:00
janniclas.loosenandClaude Opus 5.5 7f136f6f4c fix: media usage check fails closed on database errors; refused deletes checked for partial state
MediaUsageFinder treats a failed query (null result or last_error) as a possible
usage and an incomplete scan, so media-delete refuses without force. A refused
wp_delete_attachment() is write_failed only when the attachment is intact,
otherwise partial_write. media-update: re-read hint on write_failed, normalized
ids in failures, documented lock resolution and kses on restore.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 02:54:37 +02:00
janniclas.loosenandClaude Opus 5.5 931ddeb137 feat: batch media metadata updates and delete with usage check
lit/media-update validates the whole batch first (edit_post, lengths, optional
modified lock) and restores already written items when a write fails midway.
lit/media-delete requires confirm, refuses used media without force and
reports refused deletes as write_failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 02:49:41 +02:00
janniclas.loosenandClaude Opus 5.5 56f561c436 fix: media upload whitelist per extension, no stray files, IPv4-only import
Empty effective whitelist is rejected instead of falling back to all types; the
whitelist is matched per extension against core's keys (mp3|m4a|m4b); a file moved
into uploads is deleted when the attachment cannot be created; the import request
resolves IPv4 only; empty temp name guard for URL imports.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 02:42:45 +02:00
janniclas.loosenandClaude Opus 5.5 2e29a6cd4d feat: media upload by URL and base64 with whitelist and size limit
MediaImporter streams URL imports via wp_safe_remote_get with limit_response_size,
checks Content-Length, intersects the MIME whitelist with get_allowed_mime_types and
validates real content; base64 is size-checked before decoding. Server-side failures
are write_failed; a failed alt text deletes the new attachment again (or partial_write).
MediaUploads is the shared permission/result helper of both upload abilities.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 02:37:43 +02:00
janniclas.loosenandClaude Opus 5.5 f027da1785 feat: media preview as image content and download as embedded resource
lit/media-preview downscales an image attachment via wp_get_image_editor
and returns it as ImageContent; lit/media-download returns a file (full
or a named size) as an embedded resource, rejecting files above the
upload limit. Both are direct tools (ruling Q6: embedded resources, like
ImageContent, only become content blocks for tools called directly).
Adds MediaFiles::isWithinUploads() so neither tool ever reads a file
outside wp_upload_dir()'s basedir.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 02:29:12 +02:00
janniclas.loosenandClaude Opus 5.5 b0da2cdecc fix: restrict media-search to the query, not just editable() after it
I1: query the attachments themselves author-restricted when the user
lacks edit_others_posts (PostGuard::ownPostsOnly, extracted from
ContentQueryAbility so content-query reuses the exact same rule)
instead of relying solely on a post-query editable() filter, which
could leak the existence of other users' attachments via an empty
page plus next_cursor. editable() stays as a backstop.

Also: M1 document that only status=inherit attachments are listed;
M2 tie-break ordering by ID (content-query and media-search) for
stable pagination; M3 no next_cursor once the last candidate was
examined; M4 reject an explicit non-image mime with missing_alt;
M5 additional coverage (posts_per_page, cursor offset pass-through,
invalid cursor, post_status/date/mime args); mime maxLength 100.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 02:19:50 +02:00
janniclas.loosenandClaude Opus 5.5 5099c9fc89 feat: media-search with missing_alt and unused filters
Ruling Q8 caps the unused scan at 20 candidates per call (not the
brief's 500/50), and each result (search and unused alike) is
filtered to attachments the current user may edit; overview.md lists
media-search as a direct tool (ruling Q2).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 02:12:18 +02:00
janniclas.loosenandClaude Opus 5.5 b197d8171f fix: media usage search never reports a capped scan as unused
Adds widgets and header/background theme mods, integer galleryIds,
wider pre-filter, regex failures as possible usages, newest-first
candidates, and usages_complete/usages_truncated in media-get.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 02:02:21 +02:00
janniclas.loosenandClaude Opus 5.5 a2a2da1ae7 feat: media usage finder and media-get tool
Usage search distinguishes certain (ID referenced) and possible (file URL
only) usages, hides titles the user cannot read, and keeps queries bounded.
MediaGuard checks per-attachment capabilities and refuses trashed media
unless the caller opts in.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:54:55 +02:00
janniclas.loosenandClaude Opus 5.5 51e8846e1f docs: record stage 1a rulings and deferred items
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:35:44 +02:00
janniclas.loosenandClaude Opus 5.5 e4d666af8d test: smoke checks for text with &, trashed entries and missed schedules
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:31:50 +02:00
janniclas.loosenandClaude Opus 5.5 fea75b03d6 docs: name tools and error codes the way the client sees them
Indirect abilities are written lit/<name> (via mcp-adapter-execute-ability) in
guides, descriptions and hints, direct tools lit-<name>; guides use the
client-visible codes (write_failed, partial_write) and say exactly which tools
return a revision_id. ToolNamingTest keeps guides and sources consistent with
Ability::isDirect() and checks the overview's direct-tool list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:28:18 +02:00
janniclas.loosenandClaude Opus 5.5 b3c9109e47 fix: log partial menu writes; describe that the link gate covers new items only
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:26:40 +02:00
janniclas.loosenandClaude Opus 5.5 ff0da9f406 fix: settings compare against the sanitized value, fail via WriteFailed
- isStored() compares the stored option with sanitize_option() of the new value
  (core esc_html()s blogname/blogdescription): setting "Müller & Söhne" again
  no longer looks like a failed write; lit/wp-settings-get and previous return
  those two decoded, as the user would type them.
- The change log runs as FollowUp (warning on failure); a failed write throws
  WriteFailed (write_failed / partial_write with keys), partial writes are
  logged with result partial.
- permalink_structure needs a real %tag% (WordPress' own /%[^/%]+%/ rule).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:26:11 +02:00
janniclas.loosenandClaude Opus 5.5 d67a1d4fb3 fix: relation fields require a readable target and hide its post type
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:24:51 +02:00
janniclas.loosenandClaude Opus 5.5 106a099483 fix: check WordPress meta capabilities per field key
Fields from the meta/sample sources need edit_post_meta (update, checked in
PostWriter::prepare) or add_post_meta (create, checked right after the insert;
a refusal deletes the new draft again) for their key; a refusal is a
fields.<key> validation error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:24:20 +02:00
janniclas.loosenandClaude Opus 5.5 563d9e8d15 fix: status tools report a hook throwing during the write as partial_write
publish/unpublish/schedule/trash run their WordPress call through StatusWrite:
a throwing hook becomes WriteFailed (rollback incomplete, with post_id) and is
logged with result partial instead of surfacing as internal_error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:23:09 +02:00
janniclas.loosenandClaude Opus 5.5 e57b53d0e5 refactor: PostChangeResult for the after-save block of the post tools
after_save hook, re-read, change log and ToolResult (rev, log_id, warnings)
were repeated in nine post abilities; they now share one helper. Behaviour
and warning texts are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:22:34 +02:00
janniclas.loosenandClaude Opus 5.5 d831cf59ca fix: never publish a missed schedule, refuse trashed posts, fail on meta read errors
- PostGuard::editable() refuses trashed entries unless the caller opts in
  (content-get, revisions-list, post-trash); publish/schedule/unpublish/update/
  restore/duplicate/replace now refuse them.
- PostGuard::statusStable(): content-update, revision-restore and
  replace-with-copy refuse a future post whose date has passed (any save would
  publish it); the content-update rollback and PostCloner row writes re-check.
- PostCloner::ownMeta() treats a failed SELECT as an error; replace() reads the
  copy before its first write.
- replace-with-copy needs create_posts (backup clone) and reports backup_id in
  write_failed/partial_write.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:20:21 +02:00
janniclas.loosenandClaude Opus 5.5 6a7193424c refactor: Rev::matches replaces the inline rev checks
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:15:29 +02:00
janniclas.loosenandClaude Opus 5.5 0c4da28855 refactor: ChangeLog::recordPartial and column-size truncation
recordPartial() replaces the partial-log FollowUp blocks in content-create,
content-update and post-replace-with-copy; record() truncates ability,
object_type and object_id to their column sizes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:15:13 +02:00
janniclas.loosenandClaude Opus 5.5 46a445fe9d test: end-to-end checks for the WordPress basics; content and workflow guides
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 01:01:39 +02:00
janniclas.loosenandClaude Opus 5.5 fc216e3eaf fix: verify option writes before treating update_option() false as failure
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 00:47:35 +02:00
janniclas.loosenandClaude Opus 5.5 9cc9d4f2dc feat: whitelisted WordPress settings tools
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 00:34:26 +02:00
janniclas.loosenandClaude Opus 5.5 a2e9dabb17 fix: refuse private/unpublished menu links, write only changed menu items
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 00:20:34 +02:00
janniclas.loosenandClaude Opus 5.5 f80cfb9e44 feat: declarative and operation-based menu item editing
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 00:14:25 +02:00
janniclas.loosenandClaude Opus 5.5 954f683dd2 fix: keep menu items in parent cycles, tighten menu write tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 00:07:44 +02:00
janniclas.loosenandClaude Opus 5.5 1d59d1d2ad feat: menu list, get, create and location tools
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 00:00:30 +02:00
janniclas.loosenandClaude Opus 5.5 236d25325d fix: guard revision-restore follow-ups, mark divi revisions
RevisionRestoreAbility now wraps the after-save action and change-log
write in FollowUp::run() and reports failures as warnings instead of
letting them turn a saved restore into an error response, matching
ContentUpdateAbility; a failed wp_restore_post_revision() is now
Errors::writeFailed() (nothing was changed) instead of unsupported().
RevisionsListAbility marks each item divi (reusing
PostPresenter::usesDivi()/DIVI_HINT) since a Divi/block revision's
excerpt is stripped markup, not prose.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-25 23:53:36 +02:00
janniclas.loosenandClaude Opus 5.5 b9dd86d223 feat: list and restore revisions
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-25 23:41:39 +02:00
janniclas.loosenandClaude Opus 5.5 e183874ff7 fix: trashed backup before replace, byte-exact meta copy, partial rollback
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-25 23:34:13 +02:00
janniclas.loosenandClaude Opus 5.5 4fed69cf65 feat: draft copies and replace-with-copy
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-25 23:27:07 +02:00