Skip to content

Interactivity API: Mark server-side-rendered style elements for the Interactivity Router - #12963

Draft
DAreRodz wants to merge 2 commits into
WordPress:trunkfrom
DAreRodz:add/router-managed-attribute
Draft

Interactivity API: Mark server-side-rendered style elements for the Interactivity Router#12963
DAreRodz wants to merge 2 commits into
WordPress:trunkfrom
DAreRodz:add/router-managed-attribute

Conversation

@DAreRodz

Copy link
Copy Markdown

Summary

Note

This PR is an experiment to gather feedback on the mechanism. The client-side counterpart, the router honoring the attribute, lives in the Gutenberg repository and is not part of this PR.

During client-side navigation, the Interactivity API router diffs the incoming head against the current one and removes style assets missing from the new page. Stylesheets injected by JavaScript after load — theme switchers, consent banners, lazily loaded CSS — are never part of the server-rendered HTML, so the first navigation strips them. The router has no way to tell a server-rendered style asset from a client-injected one.

This PR marks every <style> and <link rel="stylesheet"> in the server-generated HTML with an empty data-wp-router-managed attribute. A style asset without the attribute is, by elimination, client-injected, and the router can preserve it when diffing the head. The attribute is only emitted when the router can act on it: every rendered block supports client-side navigation, a data-wp-router-region directive was processed, and clientNavigationDisabled is not set in the core/router config. Style assets inside inert <noscript> and <template> subtrees are not marked.

Implementation

Marking happens on the full page buffer with WP_HTML_Tag_Processor — per-tag APIs cannot reach inline <style> elements or the styles printed outside WP_Styles (custom CSS, WP_Font_Face, WP_Duotone, arbitrary wp_head output). Registration is lazy so response streaming is unaffected. The wp_template_enhancement_output_buffer filter is added from the router region processor: the buffer-start decision has already been made at that point, so the filter attaches to a running buffer (classic themes enable one by default since 6.9) but can never cause buffering by itself. Block themes with no active buffer start a dedicated fallback buffer from wp_head at PHP_INT_MIN priority; the block template canvas renders the whole template before wp_head, so the conditions are final there, and they are re-checked at buffer finalize in case a wp_footer callback rendered an incompatible block. Every failure path degrades to no attribute — current router behavior — and partial marking is never produced, since the router would preserve unmarked server styles as stale client ones.

See:

Trac ticket:

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Fable 5, Claude Opus 5
Used for: Implementation, tests, adversarial review, and drafting this description.


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.

@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.

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