Skip to content

feat: P3.3 harvest item 9 — shared atomic convenience-param mapper - #35

Merged
BenKalsky merged 1 commit into
mainfrom
feat/p33-atomic-widget-map
Aug 4, 2026
Merged

feat: P3.3 harvest item 9 — shared atomic convenience-param mapper#35
BenKalsky merged 1 commit into
mainfrom
feat/p33-atomic-widget-map

Conversation

@BenKalsky

Copy link
Copy Markdown
Member

What

P3.3 item #9 (final planned item) from references/docs/p3-3-atomic-harvest-scoping.md: Elementor_MCP_Atomic_Widget_Map — single source of the friendly-param → typed-prop mapping for the 8 atomic widget types, consumed by both the add-atomic-* tools and build-page.

Why

build-page passed widget settings through raw, so an atomic widget node with the same friendly params the convenience tools accept (content, image_url/alt, video_url, …) came out empty — its complex props have no matching raw key. The mapping lived duplicated inside 8 tool closures where build-page couldn't reach it.

How

  • The 8 settings_fn closures delegate to the map; registration bails class_exists-guarded (fatal-proof convention).
  • build-page routes atomic nodes through the map + create_atomic_widget; flat style params on the node become a local class (common + typography), exactly like the individual tools. Legacy widgets keep the raw path.
  • Correctness riding along (upstream-verified shapes):
    • Atomic_Props::image() rebuilt: image-src with id XOR url (old both-keys shape → image: invalid_value on 4.x, upstream #74 — the same XOR the fork's own e-svg fix documents), id as image-attachment-id, alt INSIDE src + _wp_attachment_image_alt write (the only alt Elementor renders for attachments; the old top-level settings.alt was silently discarded).
    • New Atomic_Props::video_src(): on 4.x e-self-hosted-video's source is a video-src SHAPE — bare url envelope refused outright on 4.2+ (upstream 3.6.2).
    • Fork's is_v4-aware switches (content_prop html-v3/string, svg-src/image-src, video plain-url on 3.x) survive the extraction.

Tests

18 new regression tests (P33AtomicWidgetMapTest): registry, per-type mapping incl. defaults and is_v4 branches, image XOR + alt-meta, svg single-url, video-src both paths, build-page routing (typed settings, style params → local class + classes ref, legacy fallback). Full suite: 827 tests, 1940 assertions, green.

Note: Codex + Copilot both at quota — review loop runs retroactively when quota resets, merging per owner's process decision (as with #33/#34), after human review.

🤖 Generated with Claude Code

Port upstream 3.6.2's EMCP_Tools_Atomic_Widget_Map as
Elementor_MCP_Atomic_Widget_Map: the single source of the friendly-param →
typed-prop mapping for the 8 atomic widget types, consumed by BOTH the
add-atomic-* convenience tools and build-page — previously build-page
passed widget settings through raw, so an atomic widget given the same
friendly params (content, image_url, video_url, …) came out empty.

- The 8 convenience settings_fn closures now delegate to the map (their
  inline mapping was the duplicated source build-page couldn't reach).
  Registration bails class_exists-guarded per the fatal-proof convention.
- build-page routes atomic widget nodes through the map +
  create_atomic_widget; the node's flat style params become a local class
  exactly as the individual tools do (common + typography). Legacy
  widgets keep the raw path.
- Correctness rides along, per the map's upstream verification:
  - Atomic_Props::image() rebuilt to the upstream shape — image-src with
    id XOR url (the old both-keys number/url shape is rejected with
    image:invalid_value on 4.x, upstream #74 — the same XOR the fork's own
    e-svg fix documents), id as image-attachment-id, alt INSIDE src, and
    the attachment alt-meta write (the only alt Elementor renders for a
    media-library image; the old top-level settings.alt was silently
    discarded).
  - New Atomic_Props::video_src(): on 4.x e-self-hosted-video's source is
    a video-src SHAPE (id XOR url) — a bare url envelope is refused
    outright on 4.2+ (upstream 3.6.2). The map keeps the fork's plain-url
    shape on 3.x-experimental, and the fork's is_v4-aware content_prop /
    svg envelope switches survive the extraction.

Test bootstrap: autoloader entry for the map, wp_get_attachment_url stub
(driven by $GLOBALS['_attachment_urls']), PHP 8.5 explicit-nullable fix in
the wp_check_filetype stub.

Board: item #9 (final planned item) in
references/docs/p3-3-atomic-harvest-scoping.md — #8 (change ledger) stays
blocked on an architecture decision; #10 deferred.
Tests: 18 new regression tests (827 total, 1940 assertions, green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 4, 2026 22:53
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@BenKalsky
BenKalsky merged commit c366732 into main Aug 4, 2026
3 checks passed
@BenKalsky
BenKalsky deleted the feat/p33-atomic-widget-map branch August 4, 2026 23:23
@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

Retroactive post-merge review (the loop was interrupted by the Codex quota limit). This PR is already merged; any real finding will be fixed in a follow-up PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c366732a68

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +186 to +187
if ( '' !== $alt ) {
update_post_meta( $image_id, '_wp_attachment_image_alt', $alt );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Authorize attachment alt updates

When add-atomic-image is called by a user who can edit the target page but cannot edit the supplied attachment, this unconditional update_post_meta() still changes that attachment because check_edit_permission() checks only the page. It also runs before the parent lookup and page save, so an invalid parent or failed save returns an error after the attachment alt has already been modified. Check permission for image_id and defer this side effect until the element is successfully persisted.

Useful? React with 👍 / 👎.

Comment on lines +361 to +365
$mapped = Elementor_MCP_Atomic_Widget_Map::settings( $widget_type, $settings );

// Third arg: the raw node settings double as flat style params
// (build_common_props + build_typography_props read them).
return $this->factory->create_atomic_widget( $widget_type, (array) $mapped, $settings );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve already typed atomic settings

When an existing build-page caller supplies the typed settings that atomic widgets require, such as title: {"$$type":"html-v3","value":"Hello"}, this now treats the envelope as a convenience value, sanitizes the array to an empty string, and discards every typed/custom prop the mapper does not recognize. The previous raw path preserved these settings, and the generic build-page schema still accepts them, so detect typed envelopes (or merge/pass them through) rather than unconditionally remapping every known atomic type.

Useful? React with 👍 / 👎.

Comment on lines 178 to 181
'src' => array(
'id' => self::number( $image_id ),
'url' => self::url( $image_url ),
'$$type' => 'image-src',
'value' => $src,
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Teach image extraction the nested image-src shape

Images created with this new shape are no longer discovered by Elementor_MCP_Content_Extractor::collect_image(): that method unwraps image.value and then reads id, url, and alt directly, while this change places them under image.value.src.value. Consequently the SEO/accessibility reports omit atomic images produced by these convenience and build-page paths; update the extractor to descend through src while retaining support for older shapes.

Useful? React with 👍 / 👎.

BenKalsky added a commit that referenced this pull request Aug 7, 2026
* fix: three P2 correctness gaps from the retroactive review round

Follow-up to merged PRs #33 (coercion) and #34/#35 (style wiring, mapper).

1. Coerce items in ALREADY-WRAPPED typed arrays (was #33). The array branch
   in candidates_for() ran only when no $$type was present, so a caller who
   supplied the documented envelope with raw items —
   {'$$type':'attributes','value':[{'key':...,'value':...}]} — got no
   item coercion and Elementor still rejected the whole tree. A value
   carrying the MEMBER'S OWN envelope is now unwrapped to its payload,
   items coerced via get_item_type(), envelope rebuilt (mirroring the
   object-shape branch). A FOREIGN envelope is still left alone — that is
   the laundering rounds 3-5 closed.

2. Restrict root-key hoisting to atomic elements (was #34). On a classic or
   custom widget,  /  are ordinary control names —
   this repo's widget builder registers any sanitized control name — so
   hoisting them deleted the control's value from settings while reporting
   success, leaving the widget rendering its old value forever. Gated on a
   new structural is_atomic_element(): elType 'widget' with an e-* widgetType,
   or an e-* elType (atomic containers). Structural, not registry-based, so
   the write path never depends on a live Elementor.

3. Teach the content extractor the nested image-src shape (was #35).
   collect_image() unwrapped image.value and read id/url/alt directly, but
   the corrected e-image shape nests them under src.value — so every atomic
   image written by the convenience tools and build-page was invisible to
   the SEO and accessibility reports. It now descends through  while
   still reading the older flat shape.

8 regression tests (835 total, green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: media library is authoritative for atomic attachment alt (Codex round-1 on #37)

The image-src descent kept the payload alt for an attachment, so the later
get_post_meta() fallback never ran. Elementor renders
_wp_attachment_image_alt for an attachment and ignores src.alt (as
Atomic_Props::image() documents), so a stale — or never-persisted, e.g. an
alt write the user was not authorized to make — payload value could make the
SEO and accessibility audits pass while the rendered image carried a
different or empty alt.

For the atomic image-src shape WITH an attachment id the library value now
wins outright, and the payload alt is not a fallback: an empty library alt
is the rendered state and the audit must see it. A url image (no attachment)
still reports its payload alt, and classic image widgets are untouched —
their settings-level alt is their own long-standing behaviour, not the
atomic contract.

2 regression tests (837 total, green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: require a positive atomic signal, not the e-* slug prefix (Codex round-2)

The gate treated the naming convention as authoritative, so a registered
classic/custom widget with an e-*-prefixed slug and a 'styles' or
'editor_settings' control was still classified atomic — retaining exactly
the data loss the gate was added to prevent.

is_atomic_element() now requires a positive signal:
 1. structural — the element already carries the atomic sibling-root keys
    (styles / editor_settings), or any of its settings is a typed $$type
    prop; both are atomic-only shapes and need no registry;
 2. the registry — an atomic type declares get_props_schema(); classic
    widgets do not, so a non-empty prop schema is authoritative.

When neither answers, the result is FALSE. Not hoisting merely leaves the
write on a dead settings.styles key (the pre-hoisting behaviour), while
hoisting wrongly destroys a real control value — the unsure case must fail
toward preserving data.

Test fixtures split into base_element() (a real atomic element, carrying the
root keys the factory writes) and classic_element() (none of them), so each
case is faithful rather than relying on the prefix.

3 regression tests: prefix-colliding custom widget stays classic; typed
settings alone are a signal; a registered atomic schema is a signal
(840 total, green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants