Skip to content

Interactivity API: Print data-wp-html tokens as the wpServerHtml marker. - #14489

Closed
SantosGuillamot wants to merge 1 commit into
WordPress:trunkfrom
SantosGuillamot:update/interactivity-api-wp-html-marker
Closed

SantosGuillamot wants to merge 1 commit into
WordPress:trunkfrom
SantosGuillamot:update/interactivity-api-wp-html-marker

Conversation

@SantosGuillamot

@SantosGuillamot SantosGuillamot commented Oct 19, 2026 •

Copy link
Copy Markdown

Follow-up to #14301 (r64386), which added server-side processing of the data-wp-html directive. The client side of this change is Gutenberg #84889; both sides share the {"wpServerHtml":true} representation.

What

A WP_Interactivity_Dangerous_HTML token now prints as the marker {"wpServerHtml":true} wherever the server sends it to the browser: in the Interactivity client data and in the attribute wp_interactivity_data_wp_context() prints, at any depth. The server stops listing token paths under derivedStateClosures; derived-state closures are listed as before.

Why

Before this change a token printed as {}, and the server listed each token's state path under derivedStateClosures so the client would keep the server-rendered content of the host element. That could not cover a token reached through context, such as context.item.html inside a data-wp-each loop: the client received a plain {} and, with the client-side data-wp-html directive from Gutenberg #84652, emptied the element on hydration.

With the marker, the client (Gutenberg #84889) recognizes a token's place wherever the reference reaches it and keeps the element's content until the client store supplies a value. The marker carries no HTML, and the derivedStateClosures workaround is no longer needed.

How

  • WP_Interactivity_Dangerous_HTML implements JsonSerializable, so json_encode() prints the marker, and IteratorAggregate, so the fallback path of wp_json_encode(), which rebuilds objects with foreach when a string is not valid UTF-8, prints it too. The HTML stays in a private property and never reaches JSON.
  • WP_Interactivity_API::evaluate() drops the step that listed token state paths, together with its flag and @since line. Closure tracking is unchanged.
  • No directive processor, call site, or trust check changes. A marker-shaped value coming back from JSON (a data-wp-context attribute, json_decode() output in state) is an array or stdClass, not a token, so it never renders and data-wp-html raises its existing non-token notice.

Tests

tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-html.php: the {} expectations become the marker, the path-listing assertions go away with the listing, and new coverage checks the marker on both encoding paths of wp_json_encode(), iteration of a token, and a marker decoded from JSON being refused as a non-token.

Testing instructions

Tested with a block temporarily modified to render three data-wp-html elements: one bound to a token in state, two generated by data-wp-each over items whose html is a token (context.item.html), and one bound to a token passed through a data-wp-context attribute generated with wp_interactivity_data_wp_context(). The same page was loaded on trunk and on this branch.

Server HTML (view source, or JavaScript disabled)

Element trunk this branch
Bound to a token in state renders the server HTML same
data-wp-each items both render the server HTML same
Bound to a token from data-wp-context keeps its fallback; the attribute carries {"h":{}} keeps its fallback; the attribute carries {"h":{"wpServerHtml":true}}

After hydration (JavaScript enabled)

Element trunk this branch
Bound to a token in state keeps its content, through the derivedStateClosures workaround keeps its content, through the marker
data-wp-each items emptied, with a console warning under SCRIPT_DEBUG keep their content, no warning
Bound to a token from data-wp-context emptied keeps its fallback

The data-wp-each items are the bug this fixes. The element bound through data-wp-context shows that a forged marker is harmless: it can only keep content the element already has.

Trac ticket: https://core.trac.wordpress.org/ticket/66246

Use of AI Tools

AI assistance: Yes
Tool(s): OpenCode and Claude Code, running an autonomous multi-agent pipeline (Radical Pipelines) that wrote and reviewed each stage separately
Model(s): Claude Opus 5.5, Claude Sonnet 5.5, Claude Fable 5.1, GPT-6 Luna, GPT-6.1 Sol
Used for: 100% of the code, tests, and comments in this PR were autonomously generated by AI and reviewed by @SantosGuillamot before the PR was opened.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

…marker.

Encode `WP_Interactivity_Dangerous_HTML` tokens as `{"wpServerHtml":true}` on both paths of `wp_json_encode()`, so the client keeps the server-rendered content of a `data-wp-html` host wherever the reference reaches the token, including `data-wp-each` items read from context. Stop listing tokens reached through state in `derivedStateClosures`, which the marker makes unnecessary.

See #66246.
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props santosguillamot.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@luisherranz luisherranz left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM! Thanks Mario 🙂

@github-actions

Copy link
Copy Markdown

A commit was made that fixes the Trac ticket referenced in the description of this pull request.

SVN changeset: 64501
GitHub commit: f64aa5a

This PR will be closed, but please confirm the accuracy of this and reopen if there is more work to be done.

@github-actions github-actions Bot closed this Oct 19, 2026
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