Skip to content

feat(interactions): Elementor v4 Interactions CRUD (element animations) — 4 tools#14

Merged
BenKalsky merged 8 commits into
mainfrom
feat/v4-interactions-crud
Jul 7, 2026
Merged

feat(interactions): Elementor v4 Interactions CRUD (element animations) — 4 tools#14
BenKalsky merged 8 commits into
mainfrom
feat/v4-interactions-crud

Conversation

@BenKalsky

Copy link
Copy Markdown
Member

What

Third and final v4 design-system plank (after Global Classes #12/v1.14.0 and Variables #13/v1.15.0). Four MCP tools to author Elementor 4 Interactions — per-element scroll/hover/click animations.

Tool
list-interactions element's interactions as {interaction_id, trigger, effect, type, direction, duration_ms, delay_ms, easing}
add-interaction ergonomic fields → the nested atomic tree; returns the (canonical) id
edit-interaction id-addressable in-place patch — the differentiator
delete-interaction delete by id

Beat vs Novamira: the reference plugin only supports list / add / delete-by-index for interactions — it has no edit, because id assignment forces a save round-trip. This ships an edit-interaction keyed on the canonical interaction_id that patches only the changed fields and preserves the rest (including Pro nodes we don't model, like breakpoints/custom_effect).

Built against Elementor's actual source (modules/interactions/*)

  • Storage: each element carries a top-level interactions field — a JSON string {version:1, items:[…]}. Each item is the nested atomic tree interaction-item → {interaction_id, trigger, animation}, animation-preset-props → {effect, type, direction, timing-config → {duration, delay (size props, unit ms)}, config-v2 → {easing}}. $$type keys + the free/pro enum split (triggers load/scrollIn; effects fade/slide/scale; easing easeIn) verified per prop-type get_key() and Presets.
  • IDs round-trip: add writes a temp-<hex> id and saves through the document; Elementor's elementor/document/save/data filter (Parser::assign_interaction_ids) swaps it for a canonical {post}-{el}-{hash}; the tool re-reads and diffs to surface it (falls back to the temp id if a raw-meta write bypassed the filter).
  • Write path: interactions is a top-level element field (not settings), so a recursive by-reference setter mutates it and save_page_data() persists.

Gating / defaults

Registers only when the e_interactions + Atomic Widgets experiments are active; interactions attach only to atomic e- elements (non-atomic targets rejected with their compact schema). Pro triggers/effects/easing require Elementor Pro (Utils::has_pro()), rejected with requires_pro on Free. Writes manage_options, reads edit_posts; enabled by default. Version → 1.16.0.

Tests

+28 (7 capability, 21 functional). Functional stub emulates assign_interaction_ids() on save to prove the temp→canonical re-read. Suite 626 green.

Note

Subagent-drafted from a source-verified spec, then reviewed against the module. One live-site verification item: the temp→canonical conversion depends on Elementor's real $document->save() firing the id-assignment filter (the code handles both outcomes).

🤖 Generated with Claude Code

Add four MCP tools to author Elementor 4 Interactions — the per-element
scroll/hover/click animations attached to atomic elements:

  - list-interactions   (edit_posts)   — unwrap the element's `interactions`
                                          field to ergonomic rows
  - add-interaction      (manage_options) — build the $$type tree, write a
                                          temp-<hex> id, save, re-read to
                                          surface the canonical id
  - edit-interaction     (manage_options) — id-addressable in-place patch
                                          (the differentiator), preserving id
                                          + untouched fields
  - delete-interaction   (manage_options) — remove by interaction_id

Storage model: each element carries a TOP-LEVEL `interactions` field (a
JSON-encoded `{version:1, items:[…]}` string of nested atomic $$type trees:
interaction-item → animation-preset-props → timing-config / config-v2). The
tools walk the page tree and mutate `$element['interactions']` directly
(update_element_settings only touches `settings`), then save the page.

Gated on BOTH the `e_interactions` and Atomic Widgets experiments (permissive
only when the experiments API is unreachable) plus an atomic target element.
Pro triggers/effects/easing are rejected on non-Pro via Utils::has_pro().

Wiring: require in elementor-mcp.php, register after Variables in the
registrar, add the catalog group in the admin Tools tab (delete badged
destructive, list read-only), bump to 1.16.0, CHANGELOG entry.

Tests: +28 (7 capability, 21 functional). Suite 598 → 626, green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@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: 4621b1e22c

ℹ️ 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 +192 to +193
public function check_read_permission(): bool {
return current_user_can( 'edit_posts' );

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 Gate interaction reads by the requested post

When list-interactions is called by a user who has edit_posts but cannot edit the requested post_id (for example a contributor querying someone else's private page), this callback still authorizes the request and execute_list() reads that page's Elementor data. Other page-scoped tools in this repo check current_user_can( 'edit_post', $post_id ) from the input; this read callback should accept the input and enforce the same per-post capability before exposing element interactions.

Useful? React with 👍 / 👎.

/**
* Triggers that require Elementor Pro.
*/
const TRIGGERS_PRO = array( 'scrollOut', 'scrollOn', 'hover', 'click' );

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 Reject scrollOn until its range config is written

Allowing the scrollOn/while-scrolling trigger here creates interactions that build_item() serializes with only the generic preset timing/easing fields and no scroll start/end range, even though while-scrolling interactions require those start/end points to define when progress begins and stops. In the Pro scenario where a caller chooses trigger: "scrollOn", the tool reports success but saves an incomplete interaction that cannot behave correctly; either reject this trigger for now or add schema/build support for the required scroll range fields.

Useful? React with 👍 / 👎.

…lOn (Codex P2 x2)

- Permission: check_read_permission/check_write_permission now take $input and
  enforce current_user_can('edit_post', $post_id) (matching the other
  page-scoped tools), so a contributor with edit_posts can't read/write
  interactions on a page they can't edit. Callbacks now return true|WP_Error.
- scrollOn: removed from the accepted pro triggers — the while-scrolling /
  scroll-progress trigger needs a scroll start/end range in the Pro config that
  build_item() doesn't write, so it would save an incomplete, non-functional
  interaction. Rejected until range support is added; the other pro triggers are
  event-based (no range). +1 per-post test; capability tests updated for the
  WP_Error return.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@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: f93b614f33

ℹ️ 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".

// that build_item() does not write, so a scrollOn interaction created here would
// save "successfully" but never behave correctly. Reject it until range support
// is added. The other pro triggers are event-based (no range).
const TRIGGERS_PRO = array( 'scrollOut', 'hover', 'click' );

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 Reject unsupported scrollOut triggers

When Elementor Pro is active, validate_enum() accepts trigger: "scrollOut" and execute_add/execute_edit will persist it successfully, but Elementor's V4 interaction trigger values are page load (load), scroll into view (scrollIn), while scrolling (scrollOn), hover, and click; there is no scroll-out trigger for the frontend to run. This makes the tool report success while creating an interaction that silently never fires.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Verified false positive against Elementor core. scrollOut is a real, frontend-handled trigger:

  • modules/interactions/validators/trigger-value.php VALID_TRIGGERS = [load, scrollIn, scrollOut, scrollOn, hover, click] (line 15).
  • modules/interactions/assets/js/interactions.js references scrollOut (it is scrollIns viewport-exit counterpart — an event, not scroll-progress).
    It is distinct from scrollOn (while-scrolling / progress), which this PR does reject because it needs a scroll start/end range we do not write. scrollOut is event-based and needs no range, so it stays.

Comment on lines +731 to +732
if ( '' !== $id && ! isset( $before[ $id ] ) ) {
$new[] = $id;

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 Return the canonical id when prior temp ids are rewritten

If this element already has temp-... ids from an earlier raw-meta fallback, the next native Elementor save will canonicalize both those old temp ids and the newly-added temp id. Because this comparison keys only by the pre-save ids, every canonicalized old interaction is also treated as "new", so count($new) is no longer 1 and the method falls back to returning the new temp id even though it no longer exists; callers that immediately edit/delete using the returned id will get not_found.

Useful? React with 👍 / 👎.

…ids (Codex P2)

If an element already carried temp- interaction ids (from an earlier
raw-meta-write fallback), the next native save canonicalizes BOTH those and the
newly-added item, so the post-add id diff couldn't isolate the new one and
add-interaction returned a stale temp id (follow-up edit/delete → not_found).
execute_add now canonicalizes any pre-existing temp ids first (save unchanged +
re-read) so only the newly-added temp id is new, and the returned id is the
usable canonical one. +1 test (stub now assigns unique canonical ids).

(The paired 'scrollOut unsupported' finding was a verified false positive —
scrollOut is in Elementor's VALID_TRIGGERS and handled in interactions.js; it's
scrollIn's event-based viewport-exit counterpart, distinct from the range-based
scrollOn this PR does reject. Replied on the thread.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@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: 996c82588e

ℹ️ 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".

/**
* Animation effects that require Elementor Pro.
*/
const EFFECTS_PRO = array( 'custom' );

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 Reject custom effects until their payload is written

On Pro sites, effect=custom now passes validation because it is listed as a supported Pro effect, but the add/edit paths only write the common preset fields and never collect or persist a custom_effect/keyframe payload. A request that adds a custom interaction can therefore save successfully while leaving the interaction without the custom animation data needed to render; either reject custom like the unsupported scroll range trigger or add the missing custom-effect fields before accepting it.

Useful? React with 👍 / 👎.

Comment on lines +818 to +819
if ( ! isset( $item['value'] ) || ! is_array( $item['value'] ) ) {
$item['value'] = array();

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 IDs when patching bare interaction items

When an existing interactions list contains a bare item shape such as {interaction_id: ...}—a shape this class can decode and find via item_id()execute_edit() reaches patch_item(), but this branch replaces the missing value with an empty array and never copies the existing interaction_id into it. Editing one of those tolerated items saves it back without its ID, so subsequent list/edit/delete calls can no longer address it.

Useful? React with 👍 / 👎.

'properties' => array(
'post_id' => array( 'type' => 'integer', 'description' => __( 'The post/page ID containing the element.', 'elementor-mcp' ) ),
'element_id' => array( 'type' => 'string', 'description' => __( 'The target atomic element ID.', 'elementor-mcp' ) ),
'trigger' => array( 'type' => 'string', 'description' => __( 'When the animation fires. Free: load, scrollIn. Pro: scrollOut, scrollOn, hover, click. Default load.', 'elementor-mcp' ) ),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Stop advertising the unsupported scrollOn trigger

The tool schema tells clients that scrollOn is an accepted Pro trigger, but TRIGGERS_PRO intentionally omits it and validation returns invalid_trigger for that exact value. Agents following the schema will generate failing add/edit requests whenever they try to use while-scrolling interactions, so the description should match the supported trigger set until scroll range support is implemented.

Useful? React with 👍 / 👎.

…ems; fix schema advertising (Codex P2/P2/P3)

- effect=custom removed from the accepted set: a custom effect needs a
  custom_effect/keyframes payload build_item() doesn't write, so it would save
  an interaction with no animation data. Rejected until keyframe support (same
  class as the range-based scrollOn trigger).
- patch_item() now captures the interaction_id before normalizing to the wrapped
  shape and re-inserts it: a tolerated 'bare' item ({interaction_id:...} with no
  value wrapper) was being saved back without its id, making it unaddressable.
- schema/descriptions no longer advertise scrollOn (rejected) or custom.
+2 tests; repointed the pro-gating test to a pro trigger (no pro effects remain).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@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: 64e85ba033

ℹ️ 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 +484 to +485
$temp_id = 'temp-' . bin2hex( random_bytes( 8 ) );
$items[] = $this->build_item( $temp_id, $fields );

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 Reject adds beyond Elementor's interaction cap

When the target element already has five interactions, this still appends a sixth item. Elementor's Interactions validator caps each element at 5 interactions; the normal document save can reject this, and the plugin's WP-CLI/REST raw-meta fallback can persist an invalid list that Elementor later sanitizes or fails to save. Please check count($items) before appending and return a clear limit error.

Useful? React with 👍 / 👎.

…Codex P2)

Elementor's Interactions Validation caps each element at 5 interactions
(max_number_of_interactions = 5; a native save of a 6th throws, and the
raw-meta fallback would persist an invalid list Elementor later rejects).
add-interaction now refuses when the element already has MAX_INTERACTIONS (5)
with a clear error. +1 test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@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: a81ef7f4eb

ℹ️ 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 +1174 to +1178
if ( array_key_exists( 'duration_ms', $fields ) && (int) $fields['duration_ms'] < 0 ) {
return new \WP_Error( 'invalid_duration', __( 'duration_ms must be a non-negative integer.', 'elementor-mcp' ) );
}
if ( array_key_exists( 'delay_ms', $fields ) && (int) $fields['delay_ms'] < 0 ) {
return new \WP_Error( 'invalid_delay', __( 'delay_ms must be a non-negative integer.', 'elementor-mcp' ) );

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 Bound interaction timing to Elementor's supported ranges

When duration_ms or delay_ms is provided above Elementor's supported controls (duration 100–3000ms, delay 0–2000ms), this validation still accepts it and builds a timing_config that Elementor's own save validation rejects; on save paths that fall back to raw meta this can also persist an interaction the editor/runtime won't accept. Please reject out-of-range timing values here instead of only checking for negatives.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Verified false positive against Elementor core. Elementor's interactions save validation bounds duration/delay only by a MIN of 0, with no max:

// modules/interactions/validation.php  is_valid_timing_config()
if ( ! $this->is_valid_timing_value( $timing_value, 'duration', 0 ) ) { ... }  // min 0, max = null
if ( ! $this->is_valid_timing_value( $timing_value, 'delay', 0 ) ) { ... }     // min 0, max = null

(Contrast start/end, which ARE range-checked 0..100.) So a duration like 5000ms passes save validation and renders (motion.js animates whatever it is) — the 100–3000 / 0–2000 figures are the editor slider UX bounds, not save constraints. validate_fields() already rejects negatives, which matches the save contract exactly; adding an upper bound would reject valid, renderable interactions. Not changing.

@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@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: a81ef7f4eb

ℹ️ 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".

);
}

$save = $this->data->save_page_data( $post_id, $page );

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 Refresh the interactions cache after fallback saves

When save_page_data() falls back to the raw _elementor_data meta write (the REST/CLI path this tool explicitly supports), Elementor's elementor/document/after_save hook does not run, so the elementor-interactions-cache post meta remains stale. On pages that already have a non-empty interactions cache, add/edit/delete returns success here but the frontend continues loading the old cached interactions instead of the updated element data; this write path should also invalidate or rebuild that interactions cache.

Useful? React with 👍 / 👎.

…s (Codex P2)

Elementor's frontend reads interactions from the `elementor-interactions-cache`
postmeta, rebuilt on `elementor/document/after_save`. On the raw `_elementor_data`
meta-write fallback (the REST/CLI path this tool supports), that hook never
fires, so add/edit/delete would return success while the frontend kept loading
the stale cached interactions. write_items() now rebuilds the cache via
Interactions_Postmeta::process_content($post_id, ['elements'=>$page]) after every
save (idempotent when the document-save path already refreshed it). Guarded +
non-fatal. +1 test (+ a cache stub).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@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: 4c08110fb0

ℹ️ 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".

// `elementor/document/after_save` never fires, so the cache the frontend
// reads (`elementor-interactions-cache`) would otherwise stay stale.
// Idempotent when the document-save path already rebuilt it.
$this->refresh_interactions_cache( $post_id, $page );

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 Refresh the cache from saved data

When the native Elementor save path succeeds, Document::save() runs Elementor's save/data filter to sanitize the document and replace temp-* interaction IDs, and its after_save hook rebuilds elementor-interactions-cache from that saved payload. This unconditional refresh immediately overwrites that cache with the pre-save $page passed into write_items(); after add-interaction the frontend cache can keep the temporary ID (or otherwise unsanitized interaction data) until another save. Re-read the page after save, or only do this manual refresh on the raw-meta fallback path.

Useful? React with 👍 / 👎.

}

return array(
'interaction_id' => $interaction_id,

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 Return the canonical ID after editing temp IDs

If an element still has a temp-* interaction ID from an earlier raw-meta fallback, editing that interaction in a later request that uses Elementor's native save will cause Parser::assign_interaction_ids() to rewrite the ID to a canonical one. Returning the input $interaction_id here then hands callers an ID that no longer exists, so a follow-up edit/delete by the returned ID reports not_found. For temp IDs, re-read after save and surface the current ID, similar to execute_add().

Useful? React with 👍 / 👎.

… id (Codex P2 x2)

- Cache refresh regression: refreshing the interactions postmeta cache from the
  PRE-save $page clobbered the sanitized cache the native document save had just
  rebuilt (canonical ids) with stale temp-id data. write_items() now re-reads the
  saved page and refreshes from THAT (idempotent on the document-save path; keeps
  the raw-fallback path in sync).
- edit-interaction returned the input id even when it was a temp- id that the
  native save canonicalized, handing callers a stale not-found id. When the
  edited id was temp, edit now re-reads the item at the same position and returns
  its current (canonical) id — mirroring add. +2 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 398893eab1

ℹ️ 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".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BenKalsky BenKalsky merged commit fe305ce into main Jul 7, 2026
7 checks passed
@BenKalsky BenKalsky deleted the feat/v4-interactions-crud branch July 7, 2026 02:33
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.

1 participant