Skip to content

Feat/atomic wind styles on dynamic pages#2921

Merged
abaicus merged 3 commits into
developmentfrom
feat/atomic-wind-styles-on-dynamic-pages
Jul 16, 2026
Merged

Feat/atomic wind styles on dynamic pages#2921
abaicus merged 3 commits into
developmentfrom
feat/atomic-wind-styles-on-dynamic-pages

Conversation

@abaicus

@abaicus abaicus commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Atomic Wind block styles now load wherever a post is displayed: single views, archives and search listings, and content attached via theme hooks (e.g. Neve Pro Custom Layouts on private post types). Previously they only loaded on singular pages via global $post. The queried post's CSS loads in the head on singular views; everything else is gathered at render time, with an in-browser Tailwind generator as the correctness fallback.

Changes

  • Render-time attribution: a render_block tracker counts rendered atomic-wind/* blocks per current post ID (skipping query-loop re-renders so counts stay aligned with raw content). At wp_footer priority 19, before WordPress prints footer assets at 20, output_late_css inlines each tracked post's cached _atomic_wind_css meta into a single style tag, md5-deduped across posts.
  • Generator fallback: the DOM-scanning frontend Tailwind generator is enqueued only when a tracked post has no cached CSS, or when more blocks rendered than the post's content declares (markup that can't be attributed to a post, e.g. embedders that don't set up postdata or blocks inside reusable-block refs). The page always ends up styled; the cost is a brief flash on this last-resort path only.
  • Base margin-reset CSS unconditional on the frontend: the ~130-byte reset is registered on every frontend page and enqueued lazily when the first Atomic Wind block renders. Gating it on global $post had the same structural hole as the old loader. Editor path unchanged.
  • Style builder extraction: the editor-facing regeneration path is its own gated method (maybe_enqueue_style_builder): singular views only, queried post has Atomic Wind blocks, no cached CSS, current user can edit it. It regenerates and persists CSS via the existing REST route.
  • Head inline for singular views: on singular pages the queried post is guaranteed to render, so its cached CSS is inlined in the head (and the generator enqueued early when uncached), eliminating the unstyled flash for the main content. The head pass records the post in the expected/inlined maps so the footer collector neither duplicates the blob nor re-fetches the meta. Hooked layouts and embeds stay on the footer path.
  • Tests: new coverage for the tracker (per-post counts, non-atomic blocks skipped, query-loop renders skipped), late inline (tracked posts, dedupe, blocks rendered by ordinary footer callbacks at priority 10), generator-on-count-mismatch, the singular head path (head inline, generator when uncached, no-op on non-singular views, no double-inline in the footer), and base CSS registration without a global post.

Test instructions

Setup: enable Atomic Wind Blocks in Otter settings. You need one regular post with Atomic Wind blocks, and (for hook coverage) a Neve Pro Custom Layout containing Atomic Wind blocks, hooked to display on single posts.

  • Singular, cached: edit and save the post with Atomic Wind blocks (the save warms its CSS cache), then view it logged out. Blocks are styled with no flash; the post's CSS is in a head inline <style id="atomic-wind-tailwind-inline-css"> and the page does not load tailwind-generator-frontend.js.
  • Hooked private content: with the Custom Layout active, view a single post. The layout's Tailwind classes are styled (check something layout-specific, e.g. a background/backdrop utility that isn't used by the post itself), served from the footer inline <style id="atomic-wind-tailwind-late-inline-css">. The post's own CSS is not duplicated there.
  • Archive/search: view the blog home or a search result listing full content of at least two Atomic Wind posts. All listed posts are styled; the footer inline style appears once, without duplicated CSS blobs.
  • Generator fallback: delete a displayed post's cache (wp post meta delete <id> _atomic_wind_css) and reload logged out. tailwind-generator-frontend.js loads and the blocks are styled after page load (brief flash is expected).
  • Editor re-cache: visit that same post logged in as someone who can edit it (singular view). style-builder.js loads and the _atomic_wind_css meta is re-created (verify with wp post meta get <id> _atomic_wind_css); subsequent logged-out loads use the head inline style again, no generator.
  • Regression, no Atomic Wind content: a page without Atomic Wind blocks anywhere outputs no atomic-wind styles or scripts (base reset style is registered but not printed).
  • Regression, editor: blocks still render styled in the block editor, and saving a post still warms the cache (the hidden-iframe warm request fires on save).

Note

  • The queried post's CSS loads in the head on singular views; everything else (hooked layouts, archive listings) is emitted at the bottom of the HTML (wp_footer 19). For above-the-fold hooked content (header layouts) a brief unstyled flash during progressive rendering is possible on slow connections. This is a deliberate trade-off: beyond the guaranteed-to-render singular post, CSS is gathered only for posts that actually display, with no speculative site-wide preloading and no extra queries or options.
  • save_post still clears a post's cached CSS on any update (including programmatic ones); pages self-heal via the generator until the next editor save re-warms the cache.

Checklist before the final review

  • Included E2E or unit tests for the changes in this PR.
  • Visual elements are not affected by independent changes.
  • It is at least compatible with the minimum WordPress version.
  • It loads additional script in frontend only if it is required.
  • Does not impact the Core Web Vitals.
  • In case of deprecation, old blocks are safely migrated.
  • It is usable in Widgets and FSE.
  • Copy/Paste is working if the attributes are modified.
  • PR is following the best practices

abaicus and others added 3 commits July 15, 2026 13:13
The main content of a singular page is guaranteed to render, so its cached
CSS can load speculatively in the head instead of the footer, removing the
flash of unstyled content for the post itself. Hooked layouts and embeds
stay on the render-tracked footer path. The head pass records the post in
the expected/inlined maps so the footer collector neither duplicates the
blob nor re-fetches the meta.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pirate-bot pirate-bot added pr-checklist-incomplete The Pull Request checklist is incomplete. (automatic label) labels Jul 15, 2026
@pirate-bot

Copy link
Copy Markdown
Contributor

Bundle Size Diff

Package Old Size New Size Diff
Animations 178.27 KB 178.27 KB 0 B (0.00%)
Blocks 1.65 MB 1.65 MB 0 B (0.00%)
CSS 7.83 KB 7.83 KB 0 B (0.00%)
Dashboard 172.49 KB 172.49 KB 0 B (0.00%)
Onboarding 68.14 KB 68.14 KB 0 B (0.00%)
Export Import 4.73 KB 4.73 KB 0 B (0.00%)
Pro 439.82 KB 439.82 KB 0 B (0.00%)

@pirate-bot

Copy link
Copy Markdown
Contributor

Plugin build for c928a90 is ready 🛎️!

@pirate-bot

Copy link
Copy Markdown
Contributor

E2E Tests

Playwright Test Status: See serial and parallel matrix jobs

Performance Results serverResponse: {"q25":438.7,"q50":469.7,"q75":475.7,"cnt":10}, firstPaint: {"q25":1308.4,"q50":1483.5,"q75":1677.8,"cnt":10}, domContentLoaded: {"q25":3772.1,"q50":3787.25,"q75":3809.9,"cnt":10}, loaded: {"q25":3774.1,"q50":3789.1,"q75":3811.8,"cnt":10}, firstContentfulPaint: {"q25":4320,"q50":4326.6,"q75":4348.6,"cnt":10}, firstBlock: {"q25":14915.5,"q50":14968.75,"q75":15060.9,"cnt":10}, type: {"q25":28.91,"q50":29.61,"q75":33.64,"cnt":10}, typeWithoutInspector: {"q25":25.57,"q50":26.67,"q75":28.45,"cnt":10}, typeWithTopToolbar: {"q25":37.14,"q50":40.03,"q75":41.7,"cnt":10}, typeContainer: {"q25":17.96,"q50":19.07,"q75":23.43,"cnt":10}, focus: {"q25":154.93,"q50":165.41,"q75":169.9,"cnt":10}, inserterOpen: {"q25":45.29,"q50":50.21,"q75":54.28,"cnt":10}, inserterSearch: {"q25":18.45,"q50":19.03,"q75":22.35,"cnt":10}, inserterHover: {"q25":6.16,"q50":6.63,"q75":7.22,"cnt":20}, loadPatterns: {"q25":1922.15,"q50":1990.75,"q75":2011.26,"cnt":10}, listViewOpen: {"q25":261.23,"q50":293.25,"q75":297.49,"cnt":10}

@abaicus
abaicus merged commit 4d981f2 into development Jul 16, 2026
15 of 28 checks passed
@abaicus
abaicus deleted the feat/atomic-wind-styles-on-dynamic-pages branch July 16, 2026 12:56
@pirate-bot

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.2.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@pirate-bot pirate-bot added the released Indicate that an issue has been resolved and released in a particular version of the product. label Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-checklist-incomplete The Pull Request checklist is incomplete. (automatic label) released Indicate that an issue has been resolved and released in a particular version of the product.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants