Skip to content

Latest commit

 

History

History
161 lines (140 loc) · 10.6 KB

File metadata and controls

161 lines (140 loc) · 10.6 KB

Block-suite lab findings (July 6, 2026)

Disposable local WordPress site with minn-admin active and five suites installed: Kadence Blocks 3.7.8, Spectra 2.19.29, GenerateBlocks 2.3.0, Otter 3.2.0, Essential Blocks 6.3.0. Stackable findings (the reference integration) live in block-inspector.md and adapters/stackable.php.

What already works with zero adapter code

Verified against a real Otter pattern page: islands preserve atomic-wind/suite markup byte-identically through double saves, previews server-render, text runs are editable, and the core editor suites pass untouched on the lab site (markdown 20/20 with all five suites active). The generic machinery — islands, render probe, text runs, image swaps, lazy-CSS pickup — needed nothing suite-specific.

Registration models (server's-eye view)

Suite Blocks is_dynamic Real server attrs Render-probe pass
Essential Blocks 75 75 75 6
Kadence 59 59 59 0
GenerateBlocks 16 16 16 0
Spectra (uagb) 13 12 13 9
Otter (themeisle-blocks) 40 11 40 6

The headline: is_dynamic is nearly meaningless as a "server-rendered" signal in this ecosystem. Kadence and GenerateBlocks register every block with a render_callback that exists to generate CSS or decorate saved content, not to produce markup — a bare comment renders empty. Essential Blocks' ~40 "static" blocks have callbacks that just return $content. The render probe is the only honest gate, and it holds: everything it passes (post grids, FAQs, maps, feeds, breadcrumbs) genuinely works from a bare comment.

Unlike Stackable, all five register real attribute schemas server-side, so the generic inspector has material to work with. But Spectra's schemas are enormous (uagb/post-grid: 315 attributes, 313 form-renderable) — the generic form needs a scaling strategy before that's usable (see roadmap).

Template/pattern libraries — where the markup data lives

Suite Source Markup as data? Account?
Otter 61 local PHP pattern files, registered in WP_Block_Patterns_Registry Yes, on disk No
Essential Blocks 12 local JSON pattern files (patterns/*.json), registered as patterns Yes, on disk No (Templately packs are gated + not wired)
Kadence Cloud (patterns.startertemplatecloud.com) via its own kb-design-library/v1 REST proxy; file-cached in uploads Yes — get_pattern_content returns serialized markup; free sections served with an empty api_key No for free tier
Spectra Cloud (websitedemos.net REST) via its ajax proxy; catalog synced to JSON files in uploads Yes — template content endpoint returns markup No for blocks/pages
GenerateBlocks Cloud (patterns.generatepress.com) with a public key hardcoded in the plugin; own generateblocks/v1 REST proxy, transient-cached Yes No

Every one of the five publishes its design markup as fetchable data. The Stackable adapter shape (slim list endpoint + insert-ready template endpoint with image sideload) transfers directly; and for Otter + Essential Blocks not even that is needed — their patterns are already in the core pattern registry.

CSS models — why previews can render unstyled

Confirmed empirically: uagb/faq and essential-blocks/post-grid enqueue nothing during a bare do_blocks() render. Each suite generates instance CSS from attributes and emits it through front-end-only machinery:

  • GenerateBlocks: per-block css attribute; render_block path prepends inline <style> only when did_action('wp_head') or the generateblocks_do_inline_styles filter is true. One-line shim available.

  • Kadence: per-block base stylesheets registered lazily at render (the queue-diff in render-blocks catches those), plus instance CSS prepended inline by render_css() — but suppressed on block themes, where it goes through a wp_enqueue_scripts head pass that needs a real $post.

  • Otter: CSS generated on save into postmeta _themeisle_gutenberg_block_styles + an uploads file; emitted only on wp/wp_head/wp_footer with is_singular().

  • Spectra: per-post CSS built by UAGB_Post_Assets on wp_enqueue_scripts (inline by default; optional file mode in uploads/uag-plugin/).

  • Essential Blocks: CSS baked into each block's blockMeta attribute in the saved markup; materialized to uploads/eb-style/eb-style-{post}.min.css on save and enqueued with a real $post.

  • Gutenslider (added 2026-08-10): the sixth model, and the one no server-side collector can reach. Its base layout sheet (build/vendor/gs-base.css, the grid the slide is built on) is fetched by the front-end script as a webpack async chunk, so it never passes through wp_styles() at all. The plugin's own wp_enqueue_style('eedee-gutenslider-block-editor') names a handle registered only as a script, so even that is a no-op. Without the sheet the background div and the slide content stack in normal flow and the content is clipped out of a fixed-height frame.

The render-blocks style-queue diff covers the lazy-register class (Stackable, Kadence base styles). The per-post-generated class needs per-suite shims — candidates ranked in the roadmap. The runtime-loaded class (Gutenslider) is covered by the runtime-CSS harvest: harvestRuntimeStyles() loads the post's front end in the hidden same-origin iframe the Otter warm-up already uses, reads the <link> sheets the page's own scripts pulled in, and sends the new ones through the same scoper. Trigger is geometric, not library-specific: a preview holding text at or past its own bottom edge. Runtime <style> elements are deliberately skipped (a page's inline CSS is mostly what the server already reported). Suite: tests/runtime-css.test.js, fixture minn-test/runtime-css.

Image attribute conventions (for swapIslandImage coverage)

  • Flat XxxUrl/XxxId pairs (already handled): Essential Blocks (imageUrl/imageId, sometimes imageURL).
  • bgImg ↔ bgImgID (Kadence rows/sections; also overlayBgImg, responsive variants in tabletBackground/mobileBackground arrays): key + ID pairing not yet handled.
  • Media objects { url, id, alt } in one attribute (Spectra image, backgroundImage, mediaGallery[]; Otter image, backgroundImage; EB image, sources[]): URL swap works (string replace), id retarget needs a same-object heuristic.
  • GenerateBlocks: no URL attribute at all — mediaId + htmlAttributes.src + data-media-id on the img. URL swap works; id lives in mediaId/data-media-id.
  • Otter slider: img[data-id] carries the attachment id.
  • JSON-escaped https:\/\/ URL forms occur in server-authored markup — handled as of this sweep (Kadence's own importer does the same normalization, which validates the string-surgery approach wholesale).

Roadmap (ranked)

  1. Server-registered patterns in the slash menu — SHIPPED (same day): minn-admin/v1/patterns (slim list; blockTypes/templateTypes-contextual patterns excluded, postTypes restrictions client-filtered) + minn-admin/v1/pattern?name= (content; query arg because names contain slashes). Multi-root patterns insert as one island per top-level block via insertPatternIslands(); on reload the load pipeline upgrades simple blocks to editable prose. Lab site surfaces 101 patterns (Otter 61 + theme 40); suite: tests/patterns.test.js.
  2. Preview CSS shims — SHIPPED (same day) for three of four: render-blocks flips generateblocks_do_inline_styles (GB blocks inline their own <style>), adapters/essential-blocks.php extracts blockMeta desktop CSS from the submitted markup, and adapters/otter.php recovers the per-post CSS caches (_themeisle_gutenberg_block_styles + _atomic_wind_css — atomic-wind is Tailwind compiled in the browser on first front-end view, so a never-viewed section has no cache until someone views the page once). Plumbing: render-blocks accepts a post param and applies the minn_admin_render_styles filter; editor-styles also carries handles the fired hooks enqueued directly (atomic-wind's base CSS). The client scoper now unwraps @layer (compiled Tailwind) and preserves CSS nesting. REMAINING: Spectra generates per-post CSS at request time with no persistent store — needs its UAGB_Post_Assets generator run over the edited post; investigate before committing.
  3. Library adapters on the Stackable shape — Kadence SHIPPED (adapters/kadence.php: drives the plugin's own kb-design-library/v1 proxy via rest_do_request so its file cache applies; 352 free sections, pro/locked filtered; images localized via the shared adapters/media-localize.php helper; client generalizes to a DESIGN_SOURCES table — adding a source is one PHP adapter + one table row). GenerateBlocks SHIPPED (adapters/generateblocks.php: drives its generateblocks/v1 proxy via rest_do_request — NOTE its responses carry objects with protected props that only flatten via JsonSerializable, so normalize through a wp_json_encode/json_decode round-trip; the listing carries full markup inline, 37 free patterns, ids are {libraryId}--{patternId} across all enabled libraries). Spectra is a documented DEAD END: the websitedemos cloud has fully migrated to a spectra/* block system (v2/v3 entries, markup in the item response's original_content field) that the shipping wp.org plugin (2.19.x, uagb/*) does NOT register — inserting it would create Gutenberg-unavailable blocks, the exact broken-insert class the render probe exists to prevent. Revisit when the plugin ships the new block system.
  4. Inspector form scaling — SHIPPED: fields explicitly set on the block (or adapter-ordered) stay in view; the rest collapse behind "More settings (N)" with a filter box (threshold 16). Setting a value promotes the field. Suite: tests/inspector-scaling.test.js against the minn-test/big-schema fixture block.
  5. Image-swap heuristic widening — SHIPPED: Img/Image keys pair with Id/ID suffixes (Kadence bgImg→bgImgID), media objects retarget id only inside the object carrying the swapped URL, GB mediaId comment-scoped, and img-tag markers data-media-id / data-id / wp-image-N in either attribute order. Suite: tests/image-swap.test.js (synthetic block carrying every convention).

Lab housekeeping

A multi-root Otter pattern page is a useful standing fixture for pattern-insert and preview-CSS checks. Tear down the disposable lab when suite work is done.